Skip to content

Commit 10a98a0

Browse files
fix(doctor): validate the Cursor hooks document the manifest names (#438) (#442)
* fix(doctor): validate the Cursor hooks document the manifest names The Cursor static validator hard-coded `hooks/hooks.json` and parsed it with the Cursor hooks schema regardless of what `.cursor-plugin/plugin.json#hooks` pointed at. The unified `plugin` target emits the Claude/Codex-format document at that path and points Cursor at `hooks/hooks-cursor.json`, so `agent-bundle doctor` reported AB7320/AB6027 (and `doctor --from`, AB7319) against a byte-for-byte install of a bundle `validate` had accepted. Resolve the hooks source from the manifest the way the pinned loader does (`resolveCursorHooksSource`): a string is a plugin-root-relative file that replaces folder discovery, an object is validated inline, and an absent field falls back to `hooks/hooks.json`. A declared file that is missing or resolves outside the plugin root is an AB6027 error. Doctor's AB7322 registration proof and the `agent-bundle/test` installed-host check share the resolver, so the validator, `doctor`, `doctor --from`, and the registration proof agree on which file counts. Tests: validator positives/negatives for named, missing, escaping, and inline hooks; Doctor installed and `--from` unified-bundle cases plus the folder-discovery fallback; the host-install fixture now also builds the `plugin` bundle and the Cursor proof installs it into a fresh isolated home asserting zero AB7320/AB6027 findings and `hooks/hooks-cursor.json` registration. Closes #438 * chore(changeset): reference PR #442
1 parent 4edbd49 commit 10a98a0

15 files changed

Lines changed: 677 additions & 112 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"agent-bundle": patch
3+
---
4+
5+
Validate the Cursor hooks document that `.cursor-plugin/plugin.json` `hooks` names instead of always reading `hooks/hooks.json`, so `agent-bundle doctor` (`AB7319`/`AB7320`) and `validateCursorPlugin` (`AB6027`) no longer reject a unified `plugin` bundle whose Cursor manifest points at `hooks/hooks-cursor.json` beside the Claude-format `hooks/hooks.json`. A declared hooks file that is missing or leaves the plugin root is now an `AB6027` error, an inline `hooks` object is validated in place, and Doctor's `AB7322` registration proof applies the same folder-discovery fallback. (#442)

‎docs/diagnostics.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ even when no error diagnostic was reported.
5151
| Code | Severity | Trigger | Recovery |
5252
| --- | --- | --- | --- |
5353
| `AB6026` | info | Every Cursor host-validation report states that Cursor publishes no plugin-validate devtools verb and names the vendored schema pin used for local validation. | Review the pinned Cursor schema provenance before changing the local validator contract. |
54-
| `AB6027` | error | A required generated Cursor document is missing or a present plugin, marketplace, MCP, or hooks document is unreadable, invalid JSON, or rejected by its pinned schema. | Repair the generated Cursor JSON document so it satisfies the vendored pinned schema, then rebuild. |
54+
| `AB6027` | error | A required generated Cursor document is missing or a present plugin, marketplace, MCP, or hooks document is unreadable, invalid JSON, or rejected by its pinned schema. The hooks document is the one `.cursor-plugin/plugin.json` `hooks` names — a plugin-root-relative file (`hooks/hooks.json` for the `cursor` target, `hooks/hooks-cursor.json` for the unified `plugin` target, reported under that path) or an inline object (`.cursor-plugin/plugin.json#/hooks`) — falling back to `hooks/hooks.json` folder discovery only when the field is absent; a declared file that is missing or resolves outside the plugin root is an error, and any other `hooks/hooks.json` beside a named document is not read. | Repair the generated Cursor JSON document so it satisfies the vendored pinned schema, then rebuild. |
5555
| `AB6028` | error | Generated bytes violate pinned Cursor loader evidence: manifest-candidate precedence selects a fallback manifest, a symlink resolves outside the bundle, or `CURSOR_PLUGIN_ROOT` appears outside loader-substituted fields. | Repair the generated Cursor layout, token locations, or symlinks to match the pinned loader evidence, then rebuild. |
5656
| `AB6029` | info / warning | The Cursor Agent version probe is unavailable (`ENOENT`, info) or cannot complete successfully (warning). Local pinned-schema validation still runs. | Install Cursor Agent or repair `cursor-agent --version` when local CLI version evidence is required, then rerun artifact validation. |
5757

@@ -696,7 +696,7 @@ host CLI, repair a bundle, or perform a live protocol exchange.
696696
| Code | Severity | Trigger | Recovery |
697697
| --- | --- | --- | --- |
698698
| `AB7319` | error | A host tree resolved from `doctor --from` violates its pinned document schemas or process-free loader rules. The message retains the originating build-validator code and detail. | Rebuild that host bundle from valid source bytes, then rerun Doctor. |
699-
| `AB7320` | error / info | Error when a `.cursor-plugin/plugin.json` install violates Cursor's pinned document schemas or token-location rules, when a root `plugin.json` install that declares an Agent Plugins `$schema` violates the pinned Agent Plugins 1.0.0 contract (`AB6035`–`AB6037`, retained in the message), or when any local plugin contains a symlink that escapes `~/.cursor/plugins/local`; the inventory entry is reported as `corrupt`. Info naming the contract applied to an Agent Plugins install, or stating that a `.claude-plugin/plugin.json` (or schema-less root `plugin.json`) install has no Cursor-side pinned static document contract; loader-recognized entries remain `installed`. | Reinstall an invalid Cursor plugin, rebuild an invalid portable bundle, or repair an escaping symlink. For other manifest flavors, use that ecosystem's validator when static document proof is required. |
699+
| `AB7320` | error / info | Error when a `.cursor-plugin/plugin.json` install violates Cursor's pinned document schemas or token-location rules (the hooks document checked is the one the manifest `hooks` field names, so a unified `plugin` bundle's Claude-format `hooks/hooks.json` beside its `hooks/hooks-cursor.json` is not a finding), when a root `plugin.json` install that declares an Agent Plugins `$schema` violates the pinned Agent Plugins 1.0.0 contract (`AB6035`–`AB6037`, retained in the message), or when any local plugin contains a symlink that escapes `~/.cursor/plugins/local`; the inventory entry is reported as `corrupt`. Info naming the contract applied to an Agent Plugins install, or stating that a `.claude-plugin/plugin.json` (or schema-less root `plugin.json`) install has no Cursor-side pinned static document contract; loader-recognized entries remain `installed`. | Reinstall an invalid Cursor plugin, rebuild an invalid portable bundle, or repair an escaping symlink. For other manifest flavors, use that ecosystem's validator when static document proof is required. |
700700

701701
## Install replacement and Doctor install comparison (`AB7005`, `AB7307`–`AB7309`, `AB7321`)
702702

@@ -807,7 +807,7 @@ that registration statically and never writes `~/.cursor/hooks.json`.
807807

808808
| Code | Severity | Meaning | Recovery |
809809
| --- | --- | --- | --- |
810-
| `AB7322` | info / error | Info: an installed `.cursor-plugin/plugin.json` plugin registers plugin-scoped hooks (events and command count listed) and the script each command executes — `${CURSOR_PLUGIN_ROOT}/…` or any relative path, including an interpreter's entry operand — exists under the plugin root (`hooks.state = registered`). Error: the declared hooks file is missing (`missing`), is not a regular file or not a `{ version, hooks: { <event>: [{ command }] } }` document, or an executed script is absent (`stale`). Documents and scripts are probed with `stat` before any read, so a FIFO cannot stall Doctor. | Reinstall the plugin from a bundle whose emitted hooks document and scripts are intact. |
810+
| `AB7322` | info / error | Info: an installed `.cursor-plugin/plugin.json` plugin registers plugin-scoped hooks (from the document its manifest `hooks` field names, or from `hooks/hooks.json` folder discovery when the field is absent; events and command count listed) and the script each command executes — `${CURSOR_PLUGIN_ROOT}/…` or any relative path, including an interpreter's entry operand — exists under the plugin root (`hooks.state = registered`). Error: the declared hooks file is missing (`missing`), is not a regular file or not a `{ version, hooks: { <event>: [{ command }] } }` document, or an executed script is absent (`stale`). Documents and scripts are probed with `stat` before any read, so a FIFO cannot stall Doctor. | Reinstall the plugin from a bundle whose emitted hooks document and scripts are intact. |
811811
| `AB7323` | warning | `~/.cursor/hooks.json` registers a command whose executed file (after leading `NAME=value` assignments, `env`, and interpreter options) points into an installed plugin directory — compared on path-component boundaries, case-folded on Windows — so Cursor would deliver that hook twice; or the file is not a valid hooks document. | Remove the plugin-pointing entries or repair the file; manifest registration alone is sufficient. |
812812
| `AB7324` | info / warning / error | A staged marketplace repository under `~/.cursor/agent-bundle/marketplaces/<name>` (from `install cursor --mode marketplace`) is imported by Cursor (matching plugin under `~/.cursor/plugins/cache`; info, `registered`), still awaiting the Customize "Add Plugins from Local Repository" step (warning, `unregistered`), or incomplete (error, `corrupt`: manifests missing or failing the pinned schemas, no resolvable Git HEAD, HEAD naming a commit object that does not exist, or a working tree that differs from committed HEAD — verified read-only through `git cat-file -e` / `git --no-optional-locks status` when `git` is available). | Complete the Customize import, use `--mode local`, or remove the staged directory and rerun the installer. |
813813

0 commit comments

Comments
 (0)