Skip to content

feat: loadout rm — artifact removal with clean uninstall - #21

Merged
machado144 merged 1 commit into
mainfrom
feat/008-artifact-removal
Aug 10, 2026
Merged

feat: loadout rm — artifact removal with clean uninstall#21
machado144 merged 1 commit into
mainfrom
feat/008-artifact-removal

Conversation

@machado144

@machado144 machado144 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

What

Spec 009 (spec · plan, 10/10 steps done): the store's missing delete verb.

`loadout rm ...` uninstalls from every agent home and deletes from the store in one confirmation-gated pass — files deleted, managed blocks stripped, and (new) merged JSON entries removed surgically. Flags: `--dry-run`, `--yes`, `--force`, `--keep-store`.

Why

  • No way to remove an artifact except hand-editing `loadout.yaml`.
  • ROADMAP "Open questions": removing an MCP/hook artifact leaked its merged `settings.json` / `.claude.json` entry forever. Closed here via `jsonRemove`, the exact inverse of `jsonMerge` (delete fragment keys, deep-equal array-item removal, prune emptied objects, preserve every other byte).

Example

$ loadout rm demo lint gh --yes
  delete claude-code: skills/demo  (demo)
  delete claude-code: hooks/loadout-lint.sh  (lint)
  update claude-code: settings.json  (lint)
  update claude-code: .claude.json  (gh)
  delete store: skills/demo
  delete store: hooks/lint
  delete store: mcps/gh
Removed: 4 agent change(s), 3 store artifact(s).

User-authored keys keep their exact bytes; `mcpServers`/`hooks` objects are pruned only when the removal empties them.

Safety

  • Confirmation gate (invariant suite extended to `rm`), pre-write snapshot with trigger `rm` that also captures the store manifest + content under the same `store` pseudo-agent spec 008 introduced — `loadout restore` resurrects a regretted rm even in a non-git store.
  • Drift-safe: targets you edited since install are skipped with a warning; `--force` removes them (snapshot first). Skipped targets keep their install records so a later `--force` still works.

Behavior change

`loadouts switch` now skips drifted targets instead of deleting them (strictly safer; `--force` restores the old behavior). One integration test updated to the new semantics.

Testing

TDD: failing engine tests reproducing the JSON leak came first. 10 `jsonRemove` behavior tests, drift/retain/ApplyRemoval engine tests, 3 `Store.RemoveArtifact` tests, 7 integration scenarios driving the built binary (full removal, declined confirm, keep-store, force/drift, unknown ID, source-origin warning, restore round-trip). Rebased on #20 (spec renumbered 008→009, store snapshots harmonized onto `SnapshotAgent`); `make ci` + `make test-integration` green on the rebased tree.

🤖 Generated with Claude Code

Adds the missing delete verb (spec 009): one confirmation-gated pass
uninstalls an artifact from every agent home and deletes it from the
store. Fills the MergeJSON removal hole with jsonRemove — the surgical
inverse of jsonMerge — so removing an MCP or hook artifact now cleans
the merged settings.json/.claude.json/.mcp.json entry instead of
leaking it (closes the ROADMAP MCP-uninstall open question).

- Drift safety: targets edited since install are skipped with a warning
  (--force removes, snapshot first); their install records are retained
  so a later --force still works. loadouts switch inherits this.
- rm snapshots (trigger "rm") also capture the store manifest + content
  under the shared 'store' pseudo-agent (same layout as spec 008), so
  'loadout restore' resurrects a regretted rm even without git.
- Store.RemoveArtifact: manifest first, then content, path-safe.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@machado144
machado144 force-pushed the feat/008-artifact-removal branch from 2fea599 to db8e789 Compare August 10, 2026 16:15
@github-actions

Copy link
Copy Markdown

✅ PR title follows the required format

Current title: feat: loadout rm — artifact removal with clean uninstall

@machado144
machado144 merged commit 0d28e8d into main Aug 10, 2026
10 checks passed
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