Skip to content

Profiles: reconciling apply, multi-extends, exclude, always_installed, init --apply - #3

Open
jlrickert wants to merge 6 commits into
mainfrom
development
Open

jlrickert wants to merge 6 commits into
mainfrom
development

Conversation

@jlrickert

@jlrickert jlrickert commented Sep 19, 2026 •

Copy link
Copy Markdown
Owner

Summary

Docs-first change: the spec lands first, then the code that implements it.

Docs

  • CLAUDE.md now imports a new AGENTS.md (workflow + critical rules), which delegates to docs/development/. docs/ is declared the source of truth; dots-design.md is kept as the design record.
  • Profile spec in docs/profiles.md, docs/bootstrapping.md, docs/config-yaml.md, docs/cli-reference.md, and the JSON schemas.

Behavior

  • dots profile apply reconciles: removes installed packages not in the resolved profile (including hand-installed ones), installs missing ones, leaves the rest untouched, and supports --dry-run. profile switch is removed.
  • extends accepts a list. Parents resolve in order with first-occurrence dedupe; cycles and missing parents are reported with the chain.
  • exclude drops inherited packages; a profile's own packages always win.
  • core.always_installed is part of every apply and overrides exclude. Platform overrides append to it. This keeps the bootstrap dots package from uninstalling itself.
  • dots init --apply <profile> applies a profile after bootstrap, reloading config first so the bootstrap package's always_installed takes effect.
  • The active profile lives only in the lockfile; core.active_profile is dropped.
  • New commands: profile create --extends, extend/unextend, exclude/unexclude. profile show prints the resolved list with each package's source and flags excludes that match nothing.
  • Profile storage now goes through toolkit.Runtime instead of os.

Breaking changes

  • dots profile switch is gone; use dots profile apply <name>.
  • profile apply now uninstalls packages that aren't in the profile.
  • core.active_profile in config.yaml is ignored.

Container testing

  • openssh-client is now in the image, so git@… clones work inside it.
  • The Containerfile is split into stages: machine (clean box: dots, git, ssh, nothing installed), fixture-src, runtime (the smoke image), and a new profiles stage. Builds now need an explicit --target, because profiles is the last stage.
  • task shell, task shell:fixture, task shell:profiles drop into throwaway machines with ~/.ssh mounted. containers/entry-live.sh copies the mount in so rootless podman's ownership mapping doesn't make ssh reject the keys.
  • containers/fixture-profiles/ is a tap with base/work/personal profiles and always_installed; containers/verify-profiles.sh (task podman:test:profiles) asserts reconcile, exclude, always-installed and --dry-run behavior.

Test plan

  • go vet ./..., go test -race ./...
  • New unit tests: extends scalar/list round-trip, always_installed platform concatenation, resolution (docs example, diamond, cycles, missing parent, exclude rules), apply reconcile/dry-run/always_installed, init --apply, editing commands, CLI show output
  • End-to-end with the built binary against a local git tap: init --from … --path dots --apply work, profile show, apply personal --dry-run, then a real apply; the dots package survives both applies
  • task podman:test (51 passed) and task podman:test:profiles (28 passed)
  • task shell: ssh -T git@github.com authenticates from inside the container against a real key

CLAUDE.md now imports AGENTS.md, which states the docs-first workflow
and critical rules and delegates detail to docs/development/. docs/ is
the source of truth; dots-design.md is kept as the design record.
- profile apply reconciles installed packages to the profile and
  replaces profile switch
- extends accepts a list; exclude drops inherited packages
- core.always_installed is included in every apply and overrides exclude
- init --apply applies a profile after bootstrap
- active profile lives only in the lockfile
- profile apply removes packages not in the resolved profile, installs
  missing ones, leaves the rest untouched, and supports --dry-run
- remove profile switch; apply is how profiles change
- extends accepts a list; exclude drops inherited packages; cycles and
  missing parents are reported with the chain
- core.always_installed is prepended to every apply, overrides exclude,
  and platform overrides append to it
- active profile is read from the lockfile only; core.active_profile is
  dropped
- profile storage goes through toolkit.Runtime instead of os
Applies a profile once init (including the bootstrap install) finishes.
Config is reloaded first so core.always_installed from the bootstrap
package's config.yaml keeps that package installed. Apply errors note
that init itself completed.
- profile create --extends (repeatable)
- profile extend/unextend and exclude/unexclude edit the lists; extend
  and create reject missing parents and cycles before saving
- profile show prints the resolved list with each package's source,
  including always_installed, and flags excludes no parent provides
- openssh-client in the image so git@ clones work
- split the Containerfile: machine (clean box), fixture-src, runtime,
  and a new profiles stage; builds now need an explicit --target
- task shell / shell:fixture / shell:profiles mount ~/.ssh (copied in by
  entry-live.sh so rootless podman ownership does not break ssh)
- containers/fixture-profiles: a tap with base/work/personal profiles and
  always_installed, plus verify-profiles.sh asserting apply reconcile,
  exclude, always_installed, and --dry-run
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant