feat(doctor): read-only agent-bundle doctor for host installs and runtime endpoints (#101 stage 1) - #236
Conversation
🦋 Changeset detectedLatest commit: b555172 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. |
commit: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b5551722e2
ℹ️ 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".
| finding: Object.freeze({ ...base, state: 'failed' }), | ||
| }; | ||
| } | ||
| if (!containsPluginName(inventory, identity.name)) { |
There was a problem hiding this comment.
Match Claude registration against the emitted plugin id
For a successful claude --plugin-dir ... plugin list --json, the pinned native harness validates registration through an id such as packed-native-smoke@inline (tests/support/packed-native-smoke.ts, lines 271–280), not through a property whose value exactly equals the bare plugin name. Consequently, a healthy direct-loaded bundle can fail this predicate, receive AB7311, and make doctor exit 1. Parse the documented list shape and match the expected <name>@inline id.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in #243: the registration proof now follows the pinned contract from tests/support/packed-native-smoke.ts — top-level array, registered exactly when some entry has id === '<name>@inline'. A non-array shape is an honest AB7312 failure, and the name-only false positive is pinned by a regression test.
| typeof (value as { readonly name?: unknown }).name === 'string' && | ||
| typeof (value as { readonly version?: unknown }).version === 'string' |
There was a problem hiding this comment.
Accept valid versionless Cursor manifests
When a locally installed Cursor plugin omits version, this rejects the manifest and reports the whole entry as corrupt with an error exit. The repository's pinned Cursor schema requires only name and explicitly leaves version optional (src/adapters/schemas/cursor/plugin.schema.json, lines 7–26), so Doctor misdiagnoses a host-valid installation; return the finding with an optional version instead of requiring this field.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in #243: readInstalledManifest now matches the pinned Cursor schema — name required, version optional (non-string version still invalidates the candidate) — and the bundle version-collision branch only fires when the installed copy records a version, so a versionless destination with differing content reports drifted. Regression tests cover versionless-installed, non-string-version, and drifted-not-conflicted.
Summary
Stage 1 of #101 per the G4 gate decision in #107 rev 2: a strictly read-only
agent-bundle doctorCLI command that diagnoses install and runtime health per host. The deferred mutation lifecycle (repair/uninstall/reinstall, any--fixsurface) stays deferred; repair guidance is expressed only as recovery text. Per G6, nothing here touches the Workbench.Diagnostic surface
claude/codexvia bounded--versionprobes (5 s timeout, 1 MiB output cap, reusing the feat(claude): validate artifacts with host devtools #193 pattern):available(with version),unavailable(ENOENT — honest info skip),failed(started-but-failed probes are errors). Cursor via pinned directory evidence (~/.cursor), since no Cursor version-probe contract is pinned.~/.cursor/plugins/localusing the pinned loader-manifest candidates (.cursor-plugin/plugin.json,.claude-plugin/plugin.json,plugin.json), flagging symlinked entries and manifest-less directories ascorruptand.name.stage-*leftovers asinterrupted-install. Claude/Codex: honestlyunknown— the hosts own their registries and no read-only inventory verb is pinned.--from <bundle-dir>) — identity resolution mirrors feat(install): make host bundles directly installable #195's install contract. Cursor:installed/missing/drifted(content digest differs, via the exported feat(install): make host bundles directly installable #195treeHash) /conflicted(version collision) /corrupt(unsafe tree or invalid destination). Claude: registration proof via the pinnedclaude --plugin-dir <dir> plugin list --jsonverb (registered/unregistered/failed, skipped honestly when the binary is unavailable). Codex:unknown(recorded deferral).events/ipc.tsby a regression test; read-only): live sockets,stale-socketwarnings, and claim locks interpreted per the fix(events): reclaim provably-dead orphaned endpoint claims instead of failing permanently #229 owner-record semantics — invalid owner record or provably-dead owner →stale-lockwarning; an owner that cannot be proven dead →livewith an info note that the runtime fails closed rather than stealing claims. win32 scan is an honest skip (no pinned named-pipe enumeration contract).Conventions
AB7300–AB7315, registered indocs/diagnostics.md(also records feat(install): make host bundles directly installable #195's previously undocumentedAB700xfamily).--jsonemits one stable JSON document.install.tsexportstreeHash(no behavior change).events/ipc.tsuntouched.Test plan
pnpm lint— 0 errors, 0 warningspnpm typecheck