feat(install): receipt-owned uninstall lifecycle, format/2 receipts, and doctor activation states (#101) - #452
Conversation
🦋 Changeset detectedLatest commit: 9e69e8a The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
commit: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8338830fbc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6cad3476cd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…tory receipts without a host probe Codex review on #452: - uninstall: a same-id Claude row at another scope is a marketplace dependent (marketplace remove applies to every scope); a failed dependency re-read of plugin list --json is 'unknown' and retains the marketplace instead of reading as an empty inventory. - doctor: the receipt store is inventoried from disk even when the host executable cannot be probed; the host cross-check alone decides consistent/orphaned, otherwise unknown.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5bb7d521be
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…tory receipts without a host probe Codex review on #452: - uninstall: a same-id Claude row at another scope is a marketplace dependent (marketplace remove applies to every scope); a failed dependency re-read of plugin list --json is 'unknown' and retains the marketplace instead of reading as an empty inventory. - doctor: the receipt store is inventoried from disk even when the host executable cannot be probed; the host cross-check alone decides consistent/orphaned, otherwise unknown.
…, unconditional receipt inventory Address codex review on #452: - install claims a Claude/Codex marketplace only when it was absent before the install (or a prior receipt already claimed it); pre-existing or unreadable marketplaces are never recorded as owned - uninstall scans live host inventory plus every stored receipt for marketplace dependents and retains the marketplace when any exist or the inventory is unknown; Claude --purge-data refuses (AB7008) when the same plugin is installed at another scope or dependents are unknown - Claude project/local receipts are keyed by project root so parallel projects no longer overwrite each other's receipt - doctor inventories store receipts even when the host probe is unavailable - uninstall --plan for Cursor local simulates the prune precisely; --keep-data reruns on a remnant are a not-installed no-op - Cursor marketplace imported detection uses the receipt commit even after the staged repo is gone
5bb7d52 to
1da381c
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1da381c146
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ross-check scoped receipts from their project root Review round 4 on #452: - `uninstall`: a stored receipt whose plugin installs from the marketplace counts as a dependent even when it records only its plugin registration (an install made after the marketplace already existed), so the marketplace is retained; a stored receipt for the same plugin id at another scope or project also blocks `--purge-data` (AB7008) because the cache copy and `plugins/data/<id>` are scope-less. - `doctor`: a Claude `project`/`local` receipt is cross-checked against `plugin list --json` run from its recorded `projectRoot` (once per root), and is `unknown` — never `orphaned` (AB7326) — when that root cannot be listed or the receipt records none. Tests in uninstall.test.ts and doctor.test.ts; docs/diagnostics.md states both rules.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…tory receipts without a host probe Codex review on #452: - uninstall: a same-id Claude row at another scope is a marketplace dependent (marketplace remove applies to every scope); a failed dependency re-read of plugin list --json is 'unknown' and retains the marketplace instead of reading as an empty inventory. - doctor: the receipt store is inventoried from disk even when the host executable cannot be probed; the host cross-check alone decides consistent/orphaned, otherwise unknown.
a9e1f4d to
0226dd3
Compare
…, unconditional receipt inventory Address codex review on #452: - install claims a Claude/Codex marketplace only when it was absent before the install (or a prior receipt already claimed it); pre-existing or unreadable marketplaces are never recorded as owned - uninstall scans live host inventory plus every stored receipt for marketplace dependents and retains the marketplace when any exist or the inventory is unknown; Claude --purge-data refuses (AB7008) when the same plugin is installed at another scope or dependents are unknown - Claude project/local receipts are keyed by project root so parallel projects no longer overwrite each other's receipt - doctor inventories store receipts even when the host probe is unavailable - uninstall --plan for Cursor local simulates the prune precisely; --keep-data reruns on a remnant are a not-installed no-op - Cursor marketplace imported detection uses the receipt commit even after the staged repo is gone
…ross-check scoped receipts from their project root Review round 4 on #452: - `uninstall`: a stored receipt whose plugin installs from the marketplace counts as a dependent even when it records only its plugin registration (an install made after the marketplace already existed), so the marketplace is retained; a stored receipt for the same plugin id at another scope or project also blocks `--purge-data` (AB7008) because the cache copy and `plugins/data/<id>` are scope-less. - `doctor`: a Claude `project`/`local` receipt is cross-checked against `plugin list --json` run from its recorded `projectRoot` (once per root), and is `unknown` — never `orphaned` (AB7326) — when that root cannot be listed or the receipt records none. Tests in uninstall.test.ts and doctor.test.ts; docs/diagnostics.md states both rules.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0226dd35c5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…checks remnant contents on the bundle path Review round 5 on #452: - uninstall (core + emitted install.mjs): an unowned directory that holds nothing retained survives the prune (only owned directories are candidates), so --plan and the completed result now list it as `name/` under retained instead of leaving it unreported. - doctor --from: the exact-bundle branch reads the Cursor directory before calling a remnant state-only, like the inventory branch already did; a remnant receipt beside unowned entries names those entries in AB7307. Tests in uninstall.test.ts, install-surface.test.ts, doctor.test.ts; docs/diagnostics.md states both rules.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3ae5ca9f2e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
3ae5ca9 to
cbc13d9
Compare
…ace ownership claim to a dependent receipt; plan store pruning; purge consumes an empty remnant Review round 6 on #452: - publicHostReceiptPath: <plugin>.<marketplace>.<scope>[.<digest>].json — the same plugin from two marketplaces is two installs with two receipts. - uninstall (Claude/Codex): when the consumed receipt is the one recording the marketplace registration and dependents keep the marketplace, the claim moves to the first dependent store receipt not already recording it; with no dependent receipt the detail says the marketplace now counts as user-owned. - --plan lists the receipt store directories the run would prune (simulateRemoveStoredInstallReceipt; Cursor marketplace staging likewise). - Cursor local: an explicit --purge-data --confirm-purge consumes a remnant receipt even when state/ was removed by hand (emitted uninstaller too).
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 010e1d79c0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f2c884c137
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 939683025c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
9396830 to
6d2acce
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 348cc00496
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const others: string[] = []; | ||
| const sameOtherScopes: string[] = []; | ||
| try { | ||
| const result = await runner.run(host, ['plugin', 'list', '--json'], { cwd: identity.bundleRoot }); |
There was a problem hiding this comment.
Retain marketplaces with unenumerated Claude project dependents
When an Agent Bundle-owned marketplace is also used by a manually installed plugin in another Claude project, this listing runs only from the current bundle root, so the other project's cwd-relative registration is invisible; because a manual install has no Agent Bundle receipt, the store scan cannot recover it either. The function then reports no dependents and removes the marketplace globally, even though the pinned evidence says project/local registrations live in project settings and marketplace removal applies to every scope (claude-2.1.250.json:22,511). Unlike the already-fixed receipted other-project case, retain the marketplace when unreceipted project consumers cannot be enumerated.
Useful? React with 👍 / 👎.
| ' const dataPaths = stateMetadata === undefined ? [] : [stateDirectory];', | ||
| " const dataOutcome = dataPaths.length === 0 ? 'absent' : purgeData ? 'purged' : 'kept';", |
There was a problem hiding this comment.
Track and purge the standalone PLUGIN_DATA directory
For an Agent Plugins pack containing a stdio server, the emitted installer creates ~/.cursor/agent-bundle/plugin-data/<plugin> and records it as receipt.cursorExpansion.pluginData, but the uninstall path considers only <destination>/state to be durable data. Consequently --purge-data --confirm-purge reports purged while leaving the PLUGIN_DATA directory and all of its contents behind; consuming the receipt also discards the only recorded path. Include the recorded PLUGIN_DATA directory in the purge/report and preserve enough ownership information for later remnant cleanup.
Useful? React with 👍 / 👎.
| "lifecycle": { | ||
| "uninstall": { |
There was a problem hiding this comment.
Advance every changed adapter revision
This adds a lifecycle contract to the Claude capability table, but Claude's adapterRevision remains 1.26.0; Cursor and portable likewise keep their parent revisions even though their capability tables and generated install surfaces change, while only Codex advances. Since repository-owned capability tables are intentionally represented by the monotonic adapter revision rather than hashed (packages/agent-bundle/README.md:1003-1006), consumers cannot distinguish artifacts using these new contracts from the preceding adapter versions. Bump and repin the Claude, Cursor, and portable revisions as well.
Useful? React with 👍 / 👎.
…lugin install fails; exact standalone marketplace plans - install: when marketplace add succeeded but plugin install / receipt write fails, remove the marketplace this run registered (its claim lived only in memory); a pre-existing marketplace is never touched - install.mjs --uninstall --mode marketplace --plan names the receipt store, marketplaces root, and agent-bundle namespace it would prune, matching the completed run
… receipt-write failure, aggregate Claude lifecycle across scopes - uninstall (core + install.mjs): a staged Cursor marketplace whose working tree differs from the receipted commit (or cannot be verified) is refused with AB7007 until --force; HEAD alone proved only the commit - install: when the host install succeeded but the receipt write failed, the plugin registration is reversed before the marketplace this run created - doctor AB7328: Claude multi-scope rows aggregate — a stage holds only for every listed copy; evidence names the disabled/unplaced/flagless scopes - tests and docs for each
348cc00 to
2f9541b
Compare
…rtable (1.10.0) adapter revisions for the lifecycle capability rows and install-surface changes
… durable state during uninstall Uninstalling a local Cursor copy (core uninstallBundle and the emitted install.mjs --uninstall) now keeps a written PLUGIN_DATA directory behind a remnant receipt that carries the cursorExpansion so a later --purge-data --confirm-purge still finds it, prunes an empty installer-created one together with its agent-bundle parents, and never touches a recorded path outside this home's plugin-data. Doctor's AB7307 remnant diagnostic names the PLUGIN_DATA directory. Restores the byte-identical host-install proofs for cursor/portable and documents the behaviour in docs/diagnostics.md.
|
@codex review Head |
…made project/local installs elsewhere retain the marketplace and refuse a shared purge
…installed_plugins.json dependents in INSTALL.md, README, site (en/zh), and the changeset
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 96d31e07b0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (recorded !== undefined) { | ||
| if (recorded !== expected) { | ||
| foreignPluginData = recorded; | ||
| } else if (await realDirectory(recorded, 'cursor') !== undefined) { |
There was a problem hiding this comment.
Reject symlinked PLUGIN_DATA ancestors
When .cursor/agent-bundle or its plugin-data child is a symlink but the final <name> target is a real directory, this leaf-only lstat check succeeds. A confirmed purge later passes the path to recursive rm, which follows those ancestors and deletes the target directory outside the intended Cursor home; the existing ancestor validation covers only files below the plugin destination. Validate every ancestor between cursorRoot and the recorded PLUGIN_DATA directory in both the core and emitted uninstallers before reading or deleting it.
Useful? React with 👍 / 👎.
| ...(data.emptyPluginData === undefined ? [] : [data.emptyPluginData]), | ||
| ...(pluginDataRecorded ? [join(cursorRoot, 'agent-bundle', 'plugin-data'), join(cursorRoot, 'agent-bundle')] : []), |
There was a problem hiding this comment.
Clean an emptied PLUGIN_DATA remnant on rerun
When a keep-data uninstall leaves a remnant for a nonempty PLUGIN_DATA directory and that directory is subsequently emptied or removed, this candidate is never pruned: the remnantOnly && policy !== 'purge' shortcut below returns before simulation or execution. Repeated default uninstalls therefore leave the empty plugin root, receipt, and plugin-data parents indefinitely despite this change promising to prune empty PLUGIN_DATA; restrict the remnant no-op to cases where preserved data is still present, or process the empty candidate before returning.
Useful? React with 👍 / 👎.
| const preserved = [ | ||
| ...(stateOnly || entries.some(isPreservedRuntimeRoot) ? ['state/'] : []), | ||
| ...(pluginData === undefined ? [] : [`the PLUGIN_DATA directory ${pluginData}`]), |
There was a problem hiding this comment.
Verify PLUGIN_DATA before reporting it as preserved
After uninstall --keep-data writes a remnant, deleting its external PLUGIN_DATA directory manually still makes AB7307 claim that the destination “holds only preserved runtime state” in that directory. A remnant moved to another home similarly reports the now-foreign recorded path as preserved even though uninstall deliberately ignores it. Check that the path is the expected current-home location and an existing real directory before including it in this diagnostic.
Useful? React with 👍 / 👎.
| | `--keep-data` | on | Keep durable runtime state (`state/`: state kernel, notices journal). This is the default; the flag makes it explicit. | | ||
| | `--purge-data` | off | Also remove durable runtime state. Refused (`AB7008`) without `--confirm-purge`. | |
There was a problem hiding this comment.
Document Cursor PLUGIN_DATA retention and purge
For an Agent Plugins pack with a stdio server, --keep-data now preserves ~/.cursor/agent-bundle/plugin-data/<name> and a confirmed purge recursively deletes it, but this public CLI reference still defines durable state only as state/; the installation guide, Chinese translation, package README, and changeset repeat that omission. Users can therefore authorize deletion of a second external data tree without the documented option semantics mentioning it. Update both localized matching pages and release-facing prose to describe PLUGIN_DATA ownership, retention, and purge behavior.
AGENTS.md reference: AGENTS.md:L71-L77
Useful? React with 👍 / 👎.
… preserved data is gone, honest AB7307 (follow-up to #452) (#519) * fix(uninstall): validate the agent-bundle and plugin-data ancestors before touching PLUGIN_DATA, consume a remnant whose preserved data is gone on a default rerun, and have Doctor name PLUGIN_DATA only when it is this home's real non-empty directory * changeset: PLUGIN_DATA uninstall hardening follow-up * changeset: attribute to #519 * fix(uninstall): treat an emptied state/ as absent (pruned with the exhausted remnant) and stop Doctor's AB7307 from inventing state/ for a remnant whose preserved data is gone
Summary
Closes #101 (managed lifecycle for generated host bundles).
Deferral reversal, stated plainly: the maintainer's earlier decision kept the mutation half of #101 behind the G4 gate ("doctor's read-only suggestions are the supported surface for now"; read-only Doctor landed in #236). On 2026-09-03 the maintainer asked that every open agent-bundle issue be fixed, which reverses that deferral. This PR therefore ships the mutation half — but every mutation is strictly opt-in (a separate
uninstallverb; nothing runs on install or doctor), receipt-owned (only what the receipt records is ever removed), and fail-closed (missing receipt, mismatch, foreign, unconfirmed purge, unusable host inventory all refuse before any change). Workbench exposure stays out per G6.Builds on #420 (receipts +
--replace) and #414 (Cursor--mode marketplace, hooks doctor findings).What lands
agent-bundle uninstall <host> [--from] [--scope] [--mode local|marketplace] [--keep-data | --purge-data --confirm-purge] [--force] [--plan] [--json], the package-relative installer bin'suninstall <host>, and the emittedinstall.mjs --uninstall.~/.cursor/plugins[/local]directories the install itself created (hostDirectories). Unowned entries are retained and listed; a remnant receipt (owns no files) is written when the root survives so a later purge can still prune and Doctor can explain the dir.HEAD== recorded commit, removes the repo + store receipt; an imported copy is Cursor-owned →manual+ Customize step.plugin list --json(unusable →AB7004fail-closed), copy-vs-receipt check,claude plugin uninstall <id> --scope <s> --keep-data/codex plugin remove <id>, thenplugin marketplace removeunless another installed plugin still uses the marketplace (retained), then the store receipt. Orphaned receipt (host already forgot) →already-absent, no host verb.state/kept by default;--purge-datarequires--confirm-purge(AB7008). Typed per-hostdata.outcome:kept/purged/absent(Cursor),retained-by-host(Claude orphans the cache copy for its grace period),removed-by-host/unavailable(Codex deletes the cache and has no keep-data option — stated honestly, not faked).AB7007content/version/HEAD mismatch or foreign/symlink (foreign and other-plugin receipts refused even with--force),AB7008purge confirmation,AB7009missing receipt. (AB7006was taken by fix(install): surfaceclaude plugin list --jsonerrors in doctor/install (AB7325, AB7006); reject the auto-loaded hooks path in the Claude manifest schema #479 onmain— Claude load errors — so the uninstall refusals areAB7007–AB7009.)--planproduces the identical report (exact absolute paths, host verbs) with no writer; rerun after uninstall isnot-installed.doctor --from:lifecycle= placed → registered → enabled → active, eachobserved(with evidence) or typedunavailable(with reason). Claude/Codex fromplugin list --jsonrows andenabledflags; Cursor local dir = registration; Cursor marketplace import via.cache-complete;enabled/activeon Cursor andactiveon Claude/Codex typedunavailable(server-assignedstate.vscdbids,enable_cc_plugin_importgating, no live-session verb). NewAB7330(lifecycle stage; renumbered twice asmaintookAB7325–AB7327in fix(install): surfaceclaude plugin list --jsonerrors in doctor/install (AB7325, AB7006); reject the auto-loaded hooks path in the Claude manifest schema #479, feat(install): expand Agent Plugins placeholders for the Cursor copy at install time; doctor AB7326 (#426) #482, fix(build,validate,doctor): run the Claude host validator and --plugin-dir load check; doctor reads enabled (AB7327) (#475, #476) #504). Capability tables gain datedlifecyclerows (uninstall,keepData,activationState) per host; all five adapter revisions bumped.agent-bundle-install-receipt/2addsmode,scope,registrations,hostDirectories,updatedAt. Claude/Codex/Cursor-marketplace installs write store receipts under<host root>/agent-bundle/receipts/<plugin>.<marketplace>.<scope>[.<project digest>].json(Cursor staging:<plugin>.marketplace.json;files: []).install --replace,uninstall, anddoctorall consume them; Doctor inventories the store and flags orphaned receipts (AB7328). Migration: format/1 receipts read with synthesized fields +migratedFrom, diagnosedAB7329, rewritten as format/2 by an identical install rerun; malformed current-format receipts read as absent (never crash).--plan(no-op) → uninstall → snapshot diff: byte-identical isolated home for Cursor local and portable (install.mjs --uninstall); zero Agent Bundle residue + every remaining entry classified as host-owned bookkeeping for Claude (.orphaned_atcache copy, empty registries, settings, session bookkeeping) and Codex (emptyconfig.toml, empty dirs);--keep-datapreserves state; missing-receipt / mismatch / unconfirmed-purge refusals; packed-tarball variant through the package bin. Doctor and unit tests for every path.Docs:
docs/diagnostics.md(new sections; family table; receipt v2),docs/framework-mode.md, package README, INSTALL.md template (per-host "Uninstall" section), website en+zhcli.mdxandinstallation.mdx. Changeset:agent-bundleminor.Acceptance items (#101)
--plan; proofs assert snapshot unchangedlifecyclestages with typedunavailableEvidence
Local, this worktree, after rebase on
origin/main:pnpm typecheck✅ ·pnpm lint✅ (0 errors)pnpm test:unit✅ 3043 passed / 0 failed / 5 skipped (heada9e1f4dbe, rebased on284141958); after thec6db0ffrebase: typecheck ✅, lint ✅, install/uninstall/doctor/install-surface/installer-entry suites ✅ (135 passed), docs site build ✅pnpm test:route-unit✅ 44 ·pnpm test:projection✅ 145pnpm build && pnpm test:integration:run✅ 978 passed; the single failure (examples-real.e2e→DevLockError, pid of a concurrent lane's dev server) passed 5/5 on isolated rerunpnpm test:host-install:build✅ 23 passed / 2 skipped (opt-in Claude session) — includes the 4 new uninstall proofs against realclaude2.1.257 andcodex0.147.0 in isolated HOMEspnpm test:host-install:packed:build✅ 6 passed (3 new packed uninstall proofs)pnpm docs:site:build✅ (language parity ok)Real-host observations recorded in the capability tables (2026-09-03): Claude
plugin uninstall --keep-dataorphans the cache copy (.orphaned_at), leaves emptyinstalled_plugins.json/known_marketplaces.json/settings.json;marketplace removeof an unknown marketplace exits 1. Codexplugin removedeletes the cache,marketplace removeleaves an emptyconfig.toml;plugin removeof an unknown plugin exits 0.Test plan
tests/uninstall.test.ts(Cursor local/marketplace, Claude/Codex mocked runners, CLI, format),tests/install.test.ts(store receipts, v1 migration reader),tests/install-surface.test.ts(install.mjs --uninstall),tests/installer-entry.test.ts(binuninstall),tests/doctor.test.ts(AB7328/AB7329/AB7330, remnant)Review status
chatgpt-codex-connector, 3 threads): marketplace retention at other scopes, retention when inventory fails, doctor receipt inventory with unavailable probe — fixed, resolved.--planprune simulation,patchchangeset, remnant rerun asnot-installedno-op, project-keyed Claude receipts, state-only remnant check in doctor, purged directories reported as directories — fixed in1da381c, replied and resolved. The(#452)attribution thread was answered (AGENTS.md: summary ends with(#PR)).1da381c): plugin-only stored receipts now count as marketplace dependents and same-plugin receipts block--purge-data(AB7008); Doctor cross-checks Claudeproject/localreceipts from their recordedprojectRoot(unknown, never orphaned, when the root cannot be listed) — fixed ina9e1f4dbe, replied and resolved.0226dd3(2 threads): surviving unowned directories listed asname/underretained(core +install.mjs, plan and result);doctor --fromchecks the remnant directory's contents before calling it state-only and names retained extras in AB7307 — fixed in3ae5ca9, replied and resolved.3ae5ca9): host-CLI receipts keyed by marketplace (<plugin>.<marketplace>.<scope>); the marketplace ownership claim moves to a dependent's store receipt when the consumed receipt recorded it (or the detail states the marketplace now counts as user-owned);--planlists the receipt-store directories the run would prune; an explicit--purge-data --confirm-purgeconsumes an empty remnant — fixed in5b2c4fe.main@c6db0ff(chore(ci): remove the release audit gate #487): fix(install): surfaceclaude plugin list --jsonerrors in doctor/install (AB7325, AB7006); reject the auto-loaded hooks path in the Claude manifest schema #479 landedAB7006/AB7325for Claude load errors, so this PR's codes moved toAB7009(missing receipt) andAB7328(lifecycle stage) — code, tests, docs (en+zh), changeset. The Claude store receipt is now written before fix(install): surfaceclaude plugin list --jsonerrors in doctor/install (AB7325, AB7006); reject the auto-loaded hooks path in the Claude manifest schema #479's post-install load verdict, so a refused copy stays receipt-owned foruninstall. fix(install): surfaceclaude plugin list --jsonerrors in doctor/install (AB7325, AB7006); reject the auto-loaded hooks path in the Claude manifest schema #479'sinstall.test.tsload-error test now pins its host roots inside the fixture (it wrote a receipt to the real~/.claudeotherwise). Branch history squashed to one feature commit plus the rebase/round-5 fixes.@codex reviewrequested on010e1d7(squashed head). Merge gate: CI green on that head with every thread addressed.Release gatesfailed three times on0226dd3/1da381cinsidescripts/audit-packed-release.mjs→npm audit(registry400/503fromregistry.npmjs.org/-/npm/v1/security/audits/quick, once a hang); attempt 3 on1da381cpassed.mainwas red on the same job in the same window. Not a branch defect.mainthrough feat(install): expand Agent Plugins placeholders for the Cursor copy at install time; doctor AB7326 (#426) #482/fix(build,validate,doctor): run the Claude host validator and --plugin-dir load check; doctor reads enabled (AB7327) (#475, #476) #504/docs(site): drop the duplicate lineage Detail header key (#502 + #504 collision) #507 (Cursor placeholder expansion,AB7325–AB7327in use): this PR's doctor codes moved once more toAB7328(orphaned/unreadable store receipt),AB7329(marketplace receipt without registration),AB7330(uninstall not exposed);AB7009unchanged. Docs (en+zh), README,docs/diagnostics.md, changeset follow.adapterRevisionadvanced to 1.27.0 / 1.13.0 / 1.10.0 (f74f362); a written CursorPLUGIN_DATAdirectory is receipt-owned durable state — kept behind a remnant receipt carryingcursorExpansion, removed by--purge-data --confirm-purge, an empty installer-created one pruned with itsagent-bundle/plugin-dataparents, a recorded path outside this home'splugin-datanever touched, Doctor's AB7307 names it (96d31e0); Claude's cross-projectplugins/installed_plugins.jsonregistry is read alongsideplugin list --jsonand the receipt store, so hand-madeproject/localinstalls elsewhere retain the marketplace and block a shared purge (AB7008), with an unreadable registry failing closed (d7068b7); INSTALL.md / README / site (en+zh) / changeset updated (9e69e8a).@codex reviewrequests at 02:00Z, 02:28Z, and 03:01Z UTC after ~35 min; merged on CI green per lane rules):d795dac8f,9e5c95242,2f9541b6c,f74f3621c,96d31e07b,d7068b743,9e69e8ac9(the rebased branch; every earlier Codex thread from rounds 1–6 is addressed in these commits).