@event4u/agent-config 8.2.0 → 8.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/CHANGELOG.md +38 -0
  3. package/MIGRATION.md +18 -0
  4. package/README.md +86 -3
  5. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  6. package/dist/discovery/deprecation-report.md +1 -1
  7. package/dist/discovery/discovery-manifest.json +1 -1
  8. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  9. package/dist/discovery/discovery-manifest.summary.md +1 -1
  10. package/dist/discovery/orphan-report.md +1 -1
  11. package/dist/discovery/packs.json +1 -1
  12. package/dist/discovery/trust-report.md +1 -1
  13. package/dist/discovery/workspaces.json +1 -1
  14. package/dist/install/install.mjs +586 -386
  15. package/dist/mcp/registry-manifest.json +2 -2
  16. package/docs/contracts/CHANGELOG-conventions.md +8 -5
  17. package/docs/contracts/branch-protection-policy.md +14 -5
  18. package/docs/contracts/mcp-phase-1-scope.md +75 -0
  19. package/docs/contracts/mcp-tool-inventory.md +22 -15
  20. package/docs/contracts/release-pr-gating.md +22 -11
  21. package/docs/decisions/ADR-111-canonical-glama-listing.md +97 -0
  22. package/docs/decisions/ADR-112-stdio-lite-stays-read-only.md +92 -0
  23. package/docs/decisions/ADR-113-ci-native-release-label-trigger.md +201 -0
  24. package/docs/decisions/INDEX.md +3 -0
  25. package/docs/distribution/registry-submissions.md +1 -1
  26. package/docs/getting-started.md +8 -6
  27. package/docs/installation.md +13 -14
  28. package/docs/mcp-server.md +32 -20
  29. package/docs/setup/mcp-cloud-endpoints.md +9 -2
  30. package/package.json +2 -2
  31. package/src/scripts/_cli/cmd_doctor.ts +111 -11
  32. package/src/scripts/_cli/cmd_uninstall.ts +22 -0
  33. package/src/scripts/_cli/cmd_upgrade.ts +74 -13
  34. package/src/scripts/_lib/claude_settings_hooks.ts +251 -0
  35. package/src/scripts/condense.ts +7 -29
  36. package/src/scripts/install +23 -4
  37. package/src/scripts/install.ts +30 -0
  38. package/src/scripts/lint_glama_drift.ts +75 -0
  39. package/src/scripts/mcp_exec/safety_envelope.ts +105 -0
  40. package/src/scripts/mcp_registry_submit.sh +14 -3
  41. package/src/scripts/mcp_server/__main__.ts +6 -8
  42. package/src/scripts/mcp_server/consumer_tool_catalog.json +78 -3
  43. package/src/scripts/mcp_server/tools.ts +731 -0
  44. package/src/scripts/release.ts +203 -39
@@ -0,0 +1,201 @@
1
+ ---
2
+ adr: 113
3
+ status: accepted
4
+ date: 2026-07-08
5
+ decision: ci-native-release-label-trigger
6
+ supersedes: —
7
+ superseded_by: —
8
+ phase: road-to-ci-native-release · Phase 1
9
+ ---
10
+
11
+ # ADR-113 — CI-native release path, triggered by a `release` label on a merged PR
12
+
13
+ ## Status
14
+
15
+ **Accepted** · 2026-07-08.
16
+
17
+ ## Context
18
+
19
+ `task release` (`src/scripts/release.ts`) is a fully interactive, local
20
+ 9-step pipeline: preflight → plan → confirm → branch+bump → commit/push+PR →
21
+ wait for CI → merge → tag → GitHub Release. The back half is already CI
22
+ (`publish-npm.yml`, `cloud-release.yml`, `release-guard.yml` all trigger on
23
+ the pushed numeric tag), but a maintainer must run the front half from a
24
+ terminal. This ADR adds an unattended, CI-native entry point alongside
25
+ `task release` — not a replacement.
26
+
27
+ ## Decision
28
+
29
+ 1. **Trigger: a `release` / `release:major` / `release:minor` /
30
+ `release:patch` label on a PR, checked at merge time**
31
+ (`pull_request: types: [closed]`, job-guarded on `merged == true` +
32
+ an exact label match). `release` auto-detects the bump from
33
+ Conventional Commits since the last tag (same logic `task release`
34
+ uses); the `release:*` variants force the bump. `workflow_dispatch`
35
+ (with `bump` / `version` / `dry_run` / `resume` inputs) is the
36
+ manual/recovery entry for a release not tied to a PR merge.
37
+
38
+ 2. **The numeric `X.Y.Z` tag stays the pipeline's *output*, never its
39
+ input.** `publish-npm.yml`, `cloud-release.yml`, and
40
+ `release-guard.yml` all trigger on `push: tags:
41
+ [0-9]+.[0-9]+.[0-9]+` and assume the tagged commit already carries
42
+ the bumped `package.json` / `marketplace.json` / regenerated
43
+ projections. A tag-shaped trigger (hand-pushed numeric tag, or a
44
+ `cut-*` intent tag) was considered and rejected — see Alternatives.
45
+
46
+ 3. **One orchestration workflow (`release.yml`), not a prepare/finalize
47
+ split.** GitHub's `GITHUB_TOKEN` recursion guard means events caused
48
+ by the default token do not start new workflow runs (with narrow,
49
+ documented exceptions — see the bot-PR finding below). A
50
+ bot-auto-merged release PR would never trigger a separate
51
+ merge-listening "finalize" workflow. `release.yml` instead runs the
52
+ whole chain synchronously in one job, calling
53
+ `src/scripts/release.ts --ci` end-to-end — the exact same pipeline
54
+ `task release` runs locally, just non-interactively.
55
+
56
+ 4. **`release.ts` gains a `--ci` mode, not a rewrite.** `--ci` changes
57
+ exactly three things relative to the existing interactive path:
58
+ - the `gh`-auth preflight probe (`gh api repos/{slug} --jq .id`
59
+ instead of `gh api user --jq .login` — the default `GITHUB_TOKEN`
60
+ is an installation token and `gh api user` returns 403 for it even
61
+ when perfectly valid);
62
+ - a `nothing_to_release_ci` short-circuit (empty commits + no
63
+ explicit bump/version override → exit 0) so firing the label path
64
+ twice in a row is a clean no-op, not a spurious empty release;
65
+ - explicit post-release dispatch of `release-guard.yml`,
66
+ `publish-npm.yml`, and `cloud-release.yml` via their `tag` input,
67
+ because a `GITHUB_TOKEN`-pushed tag does not fire their
68
+ `push: tags:` trigger either.
69
+
70
+ Every other code path (bump inference, changelog-era rendering, the
71
+ 10-step `execute()` sequence, all idempotent-resume probes) is
72
+ shared, unmodified, between `task release` and `release.yml`.
73
+
74
+ 5. **Token: `secrets.RELEASE_PR_TOKEN` with a `GITHUB_TOKEN` fallback.**
75
+ `release.yml` uses `${{ secrets.RELEASE_PR_TOKEN || secrets.GITHUB_TOKEN
76
+ }}` for checkout, git push, and `gh` auth. See the bot-PR-approval
77
+ finding below for why this matters and why the fallback is safe to
78
+ ship without the secret configured.
79
+
80
+ ## Finding — bot-created PRs need a manual "Approve workflows to run" click (no PAT)
81
+
82
+ Verified against GitHub's own changelog and community docs (2026-06-11,
83
+ ["Bot-created pull requests can run workflows if
84
+ approved"](https://github.blog/changelog/2026-06-11-bot-created-pull-requests-can-run-workflows-if-approved/)):
85
+ when a workflow uses the default `GITHUB_TOKEN` to open or update a PR,
86
+ the resulting `pull_request` events (`opened` / `synchronize` /
87
+ `reopened`) DO create workflow runs for other `pull_request`-triggered
88
+ workflows — but those runs sit in an **approval-required** state until a
89
+ repo collaborator with write access clicks **Approve workflows to run**
90
+ in the PR's merge box. This is a deliberate GitHub security control
91
+ (distinct from, but related to, the fork-first-time-contributor approval
92
+ gate), not a bug in this design.
93
+
94
+ This repo's branch-protection ruleset (verified live via `gh api
95
+ repos/event4u-app/agent-config/rulesets/17749383`) requires
96
+ **zero** approving reviews (`required_approving_review_count: 0`) and
97
+ exactly **one** required status check, `Sync + Generate Tools
98
+ Consistency` (from `consistency.yml`, `pull_request`-triggered). That
99
+ check does not run automatically on a `GITHUB_TOKEN`-opened release PR
100
+ without the one-time approval click — so without `RELEASE_PR_TOKEN`, a
101
+ release via the label path pauses at "waiting for checks" until a
102
+ maintainer approves the run once per release.
103
+
104
+ **Accepted as the default**, not worked around, because:
105
+
106
+ - It requires zero new secrets and zero change to repo-wide security
107
+ settings (disabling the org-wide first-time-contributor approval
108
+ setting would lower security posture for every fork/bot PR, not just
109
+ release PRs — out of proportion to this problem).
110
+ - A release is a production action; per this repo's own engineering
111
+ safety floor (`engineering-safety-floor` rule) and Hard-Floor stance
112
+ on deploys, a single human checkpoint before a release's own CI even
113
+ runs is arguably a feature, not friction.
114
+ - `RELEASE_PR_TOKEN` (a fine-grained PAT or GitHub App installation
115
+ token with `contents: write` + `pull-requests: write` on this repo)
116
+ removes the approval requirement entirely — documented as the
117
+ zero-friction upgrade path in `release.yml`'s header comment. Adding
118
+ it is a maintainer action, not part of this ADR.
119
+
120
+ ## Verified facts (not assumed)
121
+
122
+ - Branch protection ruleset `main protection` (id `17749383`):
123
+ `required_approving_review_count: 0`; required check =
124
+ `Sync + Generate Tools Consistency`; `bypass_actors` includes a
125
+ repository-role bypass. No human-review gate blocks a bot merge once
126
+ checks are green.
127
+ - `consistency.yml` triggers on `pull_request` with path filters that
128
+ match every file a release PR touches (`package.json`,
129
+ `.claude-plugin/marketplace.json`, `CHANGELOG.md`, `dist/agent-src/**`,
130
+ `.augment/**`) — the required check fires (subject to the approval
131
+ gate above), it is not silently skipped.
132
+ - No `RELEASE_PR_TOKEN`-shaped secret exists in the repo today (`gh
133
+ secret list`) — the fallback path is the one that ships by default.
134
+ - `softprops/action-gh-release@v2`'s `overwrite_files` input defaults to
135
+ `true` — re-attaching the same-named cloud-release assets on a
136
+ dispatch-chain re-run is idempotent by construction, no extra guard
137
+ needed.
138
+ - `npm publish` hard-rejects re-publishing an existing version — added a
139
+ `npm view @event4u/agent-config@<tag> version` pre-check to
140
+ `publish-npm.yml` so a double-dispatch (native tag-push trigger racing
141
+ the explicit dispatch, when `RELEASE_PR_TOKEN` IS configured) is a
142
+ clean skip, not a red run.
143
+
144
+ ## Consequences
145
+
146
+ - Releasing needs exactly one human action: label a PR `release` (or a
147
+ `release:*` variant) and merge it. Without `RELEASE_PR_TOKEN`, one
148
+ additional "Approve workflows to run" click is needed once per
149
+ release on the auto-created release PR.
150
+ - `task release` is unaffected — same script, same flags, same tests.
151
+ It remains the interactive/local path and the documented fallback if
152
+ the CI path misbehaves.
153
+ - Three existing workflows (`release-guard.yml`, `publish-npm.yml`)
154
+ gained a `workflow_dispatch` input / idempotency guard respectively;
155
+ `cloud-release.yml` needed no change (already idempotent).
156
+ - A future maintainer who configures `RELEASE_PR_TOKEN` gets a fully
157
+ unattended release with zero code changes required — the fallback
158
+ expression already prefers it.
159
+
160
+ ## Alternatives considered
161
+
162
+ - **Hand-pushed numeric tag as the trigger.** Rejected: the tag-triggered
163
+ workflows require the tagged commit to already carry the bump: a
164
+ hand-pushed `X.Y.Z` on plain main fires them against the *un-bumped*
165
+ tree (red `release-guard`, wrong npm tarball), and the tag would then
166
+ have to be deleted and re-pushed onto the eventual bump-PR merge
167
+ commit — mutable tags are an anti-pattern (stale fetches, npm
168
+ provenance binds to the tag commit).
169
+ - **A `cut-*` intent tag** (`cut-X.Y.Z` / `cut-auto` / `cut-major` / …)
170
+ in a separate namespace from the numeric tags. Functionally workable
171
+ (does not collide with the publish/guard tag patterns), but the
172
+ release label keeps the intent visible on the PR itself — reviewable,
173
+ revocable pre-merge, no second git-tag vocabulary to teach.
174
+ - **`workflow_dispatch` as the sole/primary trigger**, no label. Loses
175
+ the "the intent to release lives on the PR, and is revocable until
176
+ merge" property that the label gives for free; kept as the
177
+ manual/recovery entry, not the primary path.
178
+ - **Prepare + finalize workflow split** (one workflow opens the release
179
+ PR, a second listens for its merge and finalizes). Needs a
180
+ `RELEASE_PR_TOKEN`-class token as a *hard* requirement — under plain
181
+ `GITHUB_TOKEN`, the bot-merged PR's `pull_request: closed` event is
182
+ the SAME kind of GITHUB_TOKEN-caused event subject to the same
183
+ approval/recursion nuance, and splitting the pipeline across two
184
+ workflow boundaries adds a second place idempotency can drift. The
185
+ single-workflow design collapses this into one synchronous run using
186
+ the already-battle-tested `execute()` sequence.
187
+ - **Disable the org-wide "require approval for first-time contributors"
188
+ setting** to remove the bot-PR approval gate without a PAT. Rejected:
189
+ it is an org-wide security posture change affecting every fork/bot PR
190
+ in the organization, not scoped to releases — disproportionate to
191
+ this problem; `RELEASE_PR_TOKEN` is the properly-scoped fix.
192
+
193
+ ## References
194
+
195
+ - `.github/workflows/release.yml` — the orchestration workflow.
196
+ - `src/scripts/release.ts` — the shared pipeline (`--ci` mode).
197
+ - `docs/contracts/release-pr-gating.md`,
198
+ `docs/contracts/branch-protection-policy.md` — release-PR shape and
199
+ required-check contracts this design must keep satisfying.
200
+ - [GitHub Changelog — Bot-created pull requests can run workflows if
201
+ approved (2026-06-11)](https://github.blog/changelog/2026-06-11-bot-created-pull-requests-can-run-workflows-if-approved/)
@@ -114,6 +114,9 @@ _Auto-generated by `scripts/adr/regenerate_index.py`. Do not edit._
114
114
  | [ADR-108](ADR-108-open-source-forever-no-commercial-tier.md) | Open Source Forever No Commercial Tier | accepted | 2026-06-24 | — |
115
115
  | [ADR-109](ADR-109-subagent-v1-contract.md) | Subagent V1 Contract | accepted | 2026-07-04 | — |
116
116
  | [ADR-110](ADR-110-discipline-profile-resolution-locus.md) | Discipline Profile Resolution Locus | accepted | 2026-07-07 | — |
117
+ | [ADR-111](ADR-111-canonical-glama-listing.md) | Canonical Glama Listing | accepted | 2026-07-07 | — |
118
+ | [ADR-112](ADR-112-stdio-lite-stays-read-only.md) | Stdio Lite Stays Read Only | accepted | 2026-07-07 | — |
119
+ | [ADR-113](ADR-113-ci-native-release-label-trigger.md) | Ci Native Release Label Trigger | accepted | 2026-07-08 | — |
117
120
  | [ADR-200](ADR-200-python-to-typescript-migration.md) | Python To Typescript Migration | accepted | 2026-06-11 | — |
118
121
 
119
122
  ## Unnumbered (legacy)
@@ -34,7 +34,7 @@ the submission shape evolves; this file accumulates history.
34
34
 
35
35
  | # | Registry | Submission shape | Status | PR / form URL | Date | Maintainer notes |
36
36
  |---|---|---|---|---|---|---|
37
- | 1 | `punkpeye/awesome-mcp-servers` | One-line entry under agent-tooling section; PR via `scripts/mcp_registry_submit.sh` | `pending` | — | — | Scaffold ready. Run `bash scripts/mcp_registry_submit.sh --dry-run` once before live; the live path needs the maintainer's GitHub identity. |
37
+ | 1 | `punkpeye/awesome-mcp-servers` | One-line entry under agent-tooling section; PR via `scripts/mcp_registry_submit.sh` | `submitted` | <https://github.com/punkpeye/awesome-mcp-servers/pull/9607> | 2026-07-07 | PR opened via the submit script (two script bugs fixed on the way: idempotent upstream remote, `--head OWNER:branch` format). Awaiting upstream review. |
38
38
  | 2 | `mcp.so` | Directory form submission (web form, no programmatic API) | `pending` | — | — | Form URL: <https://mcp.so/>. Submission shape: same one-line entry, paste into the form's `description` field. |
39
39
  | 3 | `mcpservers.org` | Directory form submission (web form, verify URL at submission time) | `pending` | — | — | Form URL: <https://mcpservers.org/>. Submission shape: one-line entry; check the site is live before posting. |
40
40
 
@@ -37,16 +37,18 @@ The suite is global-only (ADR-020) — refresh the global install and every
37
37
  project sees the new skills, rules, and hooks at once, no per-repo bump:
38
38
 
39
39
  ```bash
40
- agent-config upgrade # fetch + install the latest globally (binary + plugin)
41
- agent-config refresh --global # idempotent re-install, same version (root + plugin)
40
+ agent-config upgrade # fetch + install the latest globally (content + hooks)
41
+ agent-config refresh --global # idempotent re-install, same version
42
42
  agent-config refresh --project # refresh a project's minimal surface — bridge
43
43
  # marker, agents/overrides/, .gitignore (no wizard)
44
- agent-config doctor # PATH, binary↔plugin version drift, bridge presence
44
+ agent-config doctor # PATH, hook wiring, duplicate surfaces, bridge presence
45
45
  ```
46
46
 
47
- The Claude marketplace plugin updates on Claude Code's own cadence,
48
- independent of npm; `doctor` surfaces binary↔plugin drift so you know when to
49
- update the plugin from the marketplace.
47
+ Claude Code is single-surface: the file projection carries content AND the
48
+ managed hooks in `~/.claude/settings.json` — there is no marketplace plugin
49
+ to keep in sync anymore. A leftover plugin from an older install is flagged
50
+ by `doctor` as `claude-plugin: duplicate surface` (fix:
51
+ `claude plugin uninstall agent-config@event4u-agent-config`).
50
52
 
51
53
  ## Project CLI — `./agent-config`
52
54
 
@@ -413,25 +413,24 @@ This is additional to project-installed mode, not a replacement.
413
413
  auggie plugin install agent-config@event4u-agent-config
414
414
  ```
415
415
 
416
- ### Claude Code
416
+ ### Claude Code — plugin deprecated (single-surface model)
417
417
 
418
- Two equivalent paths — pick whichever surface you're already in:
418
+ **Do not install the marketplace plugin for Claude Code.** The npx/npm
419
+ global install (`npx -y @event4u/agent-config init`) is the single supported
420
+ surface: it deploys skills/commands/rules to `~/.claude/` **and** registers
421
+ the deterministic hooks in a managed `~/.claude/settings.json` block. The
422
+ plugin would only duplicate every skill/command listing while its git-SHA
423
+ snapshot silently lags each release.
419
424
 
420
- ```bash
421
- # From your shell (CLI)
422
- claude plugin install agent-config@event4u-agent-config
423
- ```
425
+ Already have the plugin from an earlier install?
424
426
 
425
- ```text
426
- # From inside Claude Code (slash command)
427
- /plugin marketplace add event4u-app/agent-config
428
- /plugin install agent-config@event4u-agent-config
427
+ ```bash
428
+ claude plugin uninstall agent-config@event4u-agent-config
429
429
  ```
430
430
 
431
- The slash-command path is the canonical Claude Code Plugin Marketplace
432
- flow ([reference](https://docs.claude.com/en/docs/claude-code/plugin-marketplaces)).
433
- It pulls the repo via git-clone and reads the skills directly from
434
- `.claude/skills/` — no separate ZIP download.
431
+ Hooks keep firing after removal (they live in `settings.json`, not the
432
+ plugin); `agent-config doctor` verifies via the `hook-wiring` check and
433
+ flags a leftover plugin as `claude-plugin: duplicate surface`.
435
434
 
436
435
  ### Copilot CLI
437
436
 
@@ -1,6 +1,6 @@
1
1
  # MCP Server
2
2
 
3
- > Status: **experimental** — Phase 1 + 2 + 3 shipped. No `tools/*` primitive yet (Phase 4, deferred behind a design call). Promotion to **beta** is gated on the six criteria in [`docs/contracts/mcp-beta-criteria.md`](contracts/mcp-beta-criteria.md); current gate status: `./agent-config doctor --check mcp-beta-readiness` (Phase 3 of `road-to-surface-discipline.md`).
3
+ > Status: **experimental** — read-only prompts + resources plus a growing `tools/*` surface (stub-by-default: every catalogued tool name resolves, unimplemented ones return a structured `not_implemented` envelope). Tool coverage and the write/exec unlock path are governed by [`agents/settings/contexts/mcp-coverage-strategy.md`](../agents/settings/contexts/mcp-coverage-strategy.md). Promotion to **beta** is gated on the six criteria in [`docs/contracts/mcp-beta-criteria.md`](contracts/mcp-beta-criteria.md); current gate status: `./agent-config doctor --check mcp-beta-readiness`.
4
4
 
5
5
  `agent-config` ships a built-in [Model Context Protocol](https://modelcontextprotocol.io)
6
6
  server that exposes the package's read-only governance surface to MCP-aware
@@ -20,22 +20,30 @@ coexist:
20
20
  [`docs/contracts/mcp-cloud-scope.md`](contracts/mcp-cloud-scope.md).
21
21
  Wire-parity-checked against the local stdio kernel on every release.
22
22
 
23
- The MCP server **never executes engine code, never writes files, never spawns
24
- shells**. It is a read-only instructional surface — see
25
- [`docs/contracts/mcp-phase-1-scope.md`](contracts/mcp-phase-1-scope.md).
23
+ Every implemented tool is either read-only or writes only inside the
24
+ consumer's project tree via a path-guard (`_validateInTreePath`) —
25
+ [`docs/contracts/mcp-phase-1-scope.md`](contracts/mcp-phase-1-scope.md)
26
+ is the scope contract; exec-tier tools (shell-spawning) and network-tier
27
+ tools (billable) are gated behind
28
+ [`agents/settings/contexts/mcp-coverage-strategy.md`](../agents/settings/contexts/mcp-coverage-strategy.md).
26
29
 
27
30
  ## What the server exposes
28
31
 
29
- | Primitive | URIs | Source | Count (this package) |
30
- |---|---|---|---|
31
- | `prompts/list` + `prompts/get` | `skill.<name>`, `command.<name>` | `dist/agent-src/skills/`, `dist/agent-src/commands/` | 174 skills + 104 commands |
32
- | `resources/list` + `resources/read` | `rule://<stem>` | `dist/agent-src/rules/` | 60 rules |
33
- | ↳ | `guideline://<relpath>` | `docs/guidelines/` | 69 guidelines |
34
- | ↳ | `context://<relpath>` | `dist/agent-src/contexts/` | 31 contexts |
32
+ | Primitive | URIs | Source |
33
+ |---|---|---|
34
+ | `prompts/list` + `prompts/get` | `skill.<name>`, `command.<name>` | `dist/agent-src/skills/`, `dist/agent-src/commands/` |
35
+ | `resources/list` + `resources/read` | `rule://<stem>` | `dist/agent-src/rules/` |
36
+ | ↳ | `guideline://<relpath>` | `docs/guidelines/` |
37
+ | ↳ | `context://<relpath>` | `dist/agent-src/contexts/` |
38
+ | `tools/list` + `tools/call` | see [`agents/settings/contexts/mcp-tool-tier-map.md`](../agents/settings/contexts/mcp-tool-tier-map.md) for the full candidate surface | `src/scripts/mcp_server/consumer_tool_catalog.json` |
35
39
 
36
40
  All resources are served with `mimeType: text/markdown`. Pagination is
37
41
  cursor-based (default page size: 100). Hot-reload triggers automatically on
38
42
  file mtime changes — edit a rule, reissue `resources/list`, see the update.
43
+ Exact prompt/resource/tool counts drift with every content change — the smoke
44
+ test below reports the live numbers rather than a number pinned in prose.
45
+ _Last verified boot (`task mcp:glama-test`, 2026-07-07): 430 prompts / 232
46
+ resources / 27 tools (18 implemented, 9 stubs — post write/exec cut)._
39
47
 
40
48
  ## Setup — one-line install
41
49
 
@@ -60,10 +68,9 @@ task mcp:run # maintainer / dev repo
60
68
  ./agent-config mcp:run # consumer projects
61
69
  ```
62
70
 
63
- Both forms launch the TypeScript MCP server over stdio via `tsx`. Use these for
64
-
65
- (Claude Desktop, Cursor, Zed, Continue) launch the server themselves via
66
- the config snippets below.
71
+ Both forms launch the TypeScript MCP server over stdio via `tsx`. Use these
72
+ for a manual smoke test — MCP clients (Claude Desktop, Cursor, Zed, Continue)
73
+ launch the server themselves via the config snippets below.
67
74
 
68
75
  ## Client configuration
69
76
 
@@ -140,8 +147,9 @@ boots cleanly:
140
147
 
141
148
  ```bash
142
149
  ./agent-config mcp:run < /dev/null
143
- # Expect stderr: "mcp-server: loaded N prompts (0 warnings)" and
144
- # "mcp-server: loaded 160 resources (0 warnings)"
150
+ # Expect stderr: "mcp-server: loaded N prompts (0 warnings)",
151
+ # "mcp-server: loaded N resources (0 warnings)", and
152
+ # "mcp-server: registered N tools (N implemented, N stubs): [...]"
145
153
  ```
146
154
 
147
155
  ## Troubleshooting
@@ -155,9 +163,13 @@ boots cleanly:
155
163
  ## Scope
156
164
 
157
165
  - **In scope:** read-only prompts + resources, pagination, hot-reload, stdio
158
- transport, free-tier client compatibility.
159
- - **Out of scope (Phase 4+):** `tools/*` primitive, SSE / HTTP transport,
160
- cloud distribution, signed payloads. Tracked in
161
- [`agents/roadmaps/road-to-mcp-server.md`](../agents/roadmaps/road-to-mcp-server.md).
166
+ transport, free-tier client compatibility, a growing read-only and
167
+ in-tree-write `tools/*` surface (see
168
+ [`agents/settings/contexts/mcp-tool-tier-map.md`](../agents/settings/contexts/mcp-tool-tier-map.md)).
169
+ - **Out of scope:** SSE / native HTTP transport for this server (the
170
+ Cloudflare Worker above is the HTTP bridge instead), shell-exec and
171
+ network-tier tools until the safety envelope in
172
+ [`agents/settings/contexts/mcp-coverage-strategy.md`](../agents/settings/contexts/mcp-coverage-strategy.md)
173
+ ships, signed payloads.
162
174
 
163
175
  ← [Architecture](architecture.md) · [MCP config generation (consumer side)](mcp.md)
@@ -19,8 +19,15 @@ resources. `tools/list` returns two **deprecated stubs**
19
19
  successors; `tools/call` against either returns `isError=true`. No
20
20
  script execution, no FS access, no shell.
21
21
 
22
- Full power — the ~112 Python scripts (linters, audits, `task ci`,
23
- work-engine hooks) — requires the local install. See
22
+ Full power — the implemented tool surface (18 tools as of the 2026-07-07
23
+ write/exec cut: memory, roadmap, capabilities, doctor/conformance,
24
+ telemetry, council-estimate, and the vitest `run_tests` pilot) — requires
25
+ the **local kernel server** (`agent-config mcp:run` on a repo checkout).
26
+ `implemented_on` stays `["stdio"]` for every execution tool, permanently:
27
+ the Worker has no kernel runtime endpoint — it serves a release-pinned
28
+ content snapshot from R2, so there is no process that could run the
29
+ handlers, and the A0-cloud contract's no-execution boundary is a design
30
+ invariant, not a backlog item. See
24
31
  [`../contracts/mcp-cloud-scope.md`](../contracts/mcp-cloud-scope.md)
25
32
  for the execution-safety boundary and the Phase-7-DEFERRED gate that
26
33
  governs any future tool restoration.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@event4u/agent-config",
3
- "version": "8.2.0",
3
+ "version": "8.3.0",
4
4
  "description": "Universal AI Agent OS — audited skills, governance rules, commands, and templates for AI coding tools (Claude Code, Cursor, Windsurf, Copilot).",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -56,7 +56,7 @@
56
56
  },
57
57
  "scripts": {
58
58
  "build:cli": "tsc -p tsconfig.json && chmod +x dist/cli/agent-config.js",
59
- "build:install-bundle": "esbuild src/scripts/install.ts --bundle --platform=node --format=esm --target=node20 --define:__AGENT_CONFIG_BUNDLE__=true --outfile=dist/install/install.mjs",
59
+ "build:install-bundle": "esbuild src/scripts/install.ts --bundle --platform=node --format=esm --target=node20 --define:__AGENT_CONFIG_BUNDLE__=true --outfile=dist/install/install.mjs --banner:js=\"import { createRequire as __acCreateRequire } from 'node:module'; const require = globalThis.require ?? __acCreateRequire(import.meta.url);\"",
60
60
  "build:ui": "vite build",
61
61
  "build:discovery": "./scripts-run src/scripts/build_discovery_manifest --write --strict",
62
62
  "build:mcp-manifest": "./scripts-run src/scripts/build_mcp_registry_manifest --write --strict",
@@ -81,6 +81,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
81
81
  import * as YAML from 'yaml';
82
82
 
83
83
  import * as claude_plugin from '../_lib/claude_plugin.js';
84
+ import * as claude_settings_hooks from '../_lib/claude_settings_hooks.js';
84
85
  import { _is_source_repo } from './cmd_refresh.js';
85
86
  import * as installed_lock from '../_lib/installed_lock.js';
86
87
  import * as installed_tools from '../_lib/installed_tools.js';
@@ -730,6 +731,7 @@ const CHECK_IDS = [
730
731
  'scope',
731
732
  'global-binary',
732
733
  'claude-plugin',
734
+ 'hook-wiring',
733
735
  'stale-orphans',
734
736
  'manifest-integrity',
735
737
  'lockfile-freshness',
@@ -749,6 +751,7 @@ const GLOBAL_CHECK_IDS: ReadonlySet<string> = new Set([
749
751
  'scope',
750
752
  'global-binary',
751
753
  'claude-plugin',
754
+ 'hook-wiring',
752
755
  'stale-orphans',
753
756
  'mcp-mode',
754
757
  'mcp-beta-readiness',
@@ -1005,12 +1008,14 @@ function lstripV(s: string): string {
1005
1008
  }
1006
1009
 
1007
1010
  /**
1008
- * Claude Code plugin state. The plugin is OPTIONAL — the `~/.claude/` file
1009
- * projection is a full install on its own — so a missing plugin is `ok`,
1010
- * never a warning. What DOES warn is a stale installed plugin: Claude Code
1011
- * pins the plugin to its install-time git SHA, so its command surface
1012
- * silently lags every upgrade until `claude plugin update` runs
1013
- * (`agent-config upgrade` now does that automatically).
1011
+ * Claude Code plugin state under the single-surface model
1012
+ * (road-to-claude-code-single-surface): the `~/.claude/` file projection is
1013
+ * THE distribution surface for Claude Code — content, and hooks via the
1014
+ * managed settings.json block. An installed marketplace plugin next to the
1015
+ * projection is a **duplicate surface**: every skill/command lists twice
1016
+ * and the plugin's SHA snapshot rots silently. That is `fail`, with the
1017
+ * uninstall command as the fix (never uninstalled autonomously — the plugin
1018
+ * is a user-owned surface).
1014
1019
  */
1015
1020
  function _check_claude_plugin(): Dict {
1016
1021
  const claude = shutilWhich('claude');
@@ -1027,11 +1032,27 @@ function _check_claude_plugin(): Dict {
1027
1032
  id: 'claude-plugin',
1028
1033
  status: 'ok',
1029
1034
  message:
1030
- 'plugin not installed (optional — the ~/.claude/ file projection ' +
1031
- 'already carries the full skill/command set)',
1035
+ 'plugin not installed (single-surface model — the ~/.claude/ file ' +
1036
+ 'projection carries content AND hooks)',
1032
1037
  remedy: '',
1033
1038
  };
1034
1039
  }
1040
+ const projection = path.join(os.homedir(), '.claude', 'skills');
1041
+ const projection_present = isDir(projection);
1042
+ if (projection_present) {
1043
+ return {
1044
+ id: 'claude-plugin',
1045
+ status: 'fail',
1046
+ message:
1047
+ 'duplicate surface: the marketplace plugin AND the ~/.claude/ file ' +
1048
+ 'projection are both installed — every skill/command lists twice and ' +
1049
+ 'the plugin snapshot rots silently',
1050
+ remedy:
1051
+ `claude plugin uninstall ${claude_plugin.CLAUDE_PLUGIN_ID}@${claude_plugin.CLAUDE_MARKETPLACE_NAME} ` +
1052
+ '(hooks stay — they are registered in ~/.claude/settings.json; ' +
1053
+ 'verify with the hook-wiring check)',
1054
+ };
1055
+ }
1035
1056
  const snapshot_v = lstripV(claude_plugin.claude_plugin_snapshot_version() || '');
1036
1057
  const binary_v = lstripV(_current_package_version() || '');
1037
1058
  if (snapshot_v && binary_v && snapshot_v !== binary_v) {
@@ -1046,9 +1067,86 @@ function _check_claude_plugin(): Dict {
1046
1067
  }
1047
1068
  return {
1048
1069
  id: 'claude-plugin',
1049
- status: 'ok',
1050
- message: `plugin installed${snapshot_v ? ` (snapshot ${snapshot_v})` : ''}`,
1051
- remedy: '',
1070
+ status: 'warn',
1071
+ message:
1072
+ `plugin installed${snapshot_v ? ` (snapshot ${snapshot_v})` : ''} without the ` +
1073
+ 'file projection — deprecated surface; the projection is the supported path',
1074
+ remedy: 'agent-config global (then uninstall the plugin per the printed hint)',
1075
+ };
1076
+ }
1077
+
1078
+ /**
1079
+ * Managed hook wiring (single-surface model): the deterministic hook matrix
1080
+ * must be registered in ~/.claude/settings.json AND the dispatch target
1081
+ * (`agent-config` binary) must resolve on PATH. Both halves are required —
1082
+ * hooks without a binary silently no-op every session.
1083
+ */
1084
+ function _check_hook_wiring(): Dict {
1085
+ const claude = shutilWhich('claude');
1086
+ if (claude === null) {
1087
+ return {
1088
+ id: 'hook-wiring',
1089
+ status: 'skipped',
1090
+ message: 'Claude Code CLI not on PATH — hook-wiring check not applicable',
1091
+ remedy: '',
1092
+ };
1093
+ }
1094
+ const settings_path = path.join(os.homedir(), '.claude', 'settings.json');
1095
+ let missing: string[];
1096
+ try {
1097
+ const matrix = claude_settings_hooks.build_claude_hook_matrix(
1098
+ path.join(_package_root(), 'src', 'scripts', 'hook_manifest.yaml'),
1099
+ );
1100
+ const settings = pathExists(settings_path)
1101
+ ? (JSON.parse(fs.readFileSync(settings_path, 'utf8')) as Record<string, unknown>)
1102
+ : {};
1103
+ const hooks = ((settings['hooks'] ?? {}) as Record<string, unknown>) || {};
1104
+ missing = Object.keys(matrix).filter((ev) => {
1105
+ const groups = hooks[ev];
1106
+ if (!Array.isArray(groups)) return true;
1107
+ return !JSON.stringify(groups).includes(claude_settings_hooks.MANAGED_SIGNATURE);
1108
+ });
1109
+ } catch (e) {
1110
+ return {
1111
+ id: 'hook-wiring',
1112
+ status: 'fail',
1113
+ message: `cannot evaluate managed hooks: ${String(e)}`,
1114
+ remedy: 'fix ~/.claude/settings.json (invalid JSON?), then agent-config refresh --global',
1115
+ };
1116
+ }
1117
+ const binary_ok = shutilWhich('agent-config') !== null;
1118
+ if (missing.length === 0 && binary_ok) {
1119
+ return {
1120
+ id: 'hook-wiring',
1121
+ status: 'ok',
1122
+ message: 'managed hooks registered in ~/.claude/settings.json; dispatch binary on PATH',
1123
+ remedy: '',
1124
+ };
1125
+ }
1126
+ if (missing.length > 0 && claude_plugin.claude_plugin_installed()) {
1127
+ return {
1128
+ id: 'hook-wiring',
1129
+ status: 'warn',
1130
+ message:
1131
+ `managed hooks missing for: ${missing.join(', ')} — currently carried by the ` +
1132
+ 'installed plugin (deprecated surface)',
1133
+ remedy: 'agent-config refresh --global (writes the managed settings.json block)',
1134
+ };
1135
+ }
1136
+ const parts: string[] = [];
1137
+ if (missing.length > 0) parts.push(`managed hooks missing for: ${missing.join(', ')}`);
1138
+ if (!binary_ok) parts.push('`agent-config` not on PATH (hooks would silently no-op)');
1139
+ if (parts.length === 0) {
1140
+ // hooks complete, binary missing was the only issue → covered above.
1141
+ parts.push('`agent-config` not on PATH');
1142
+ }
1143
+ return {
1144
+ id: 'hook-wiring',
1145
+ status: 'fail',
1146
+ message: parts.join('; '),
1147
+ remedy: binary_ok
1148
+ ? 'agent-config refresh --global'
1149
+ : 'npm install -g @event4u/agent-config, then agent-config refresh --global',
1052
1150
  };
1053
1151
  }
1054
1152
 
@@ -1818,6 +1916,7 @@ function _run_checks(
1818
1916
  scope: () => _check_scope(project_root),
1819
1917
  'global-binary': () => _check_global_binary(project_root),
1820
1918
  'claude-plugin': _check_claude_plugin,
1919
+ 'hook-wiring': _check_hook_wiring,
1821
1920
  'stale-orphans': _check_stale_orphans,
1822
1921
  'manifest-integrity': () => _check_manifest_integrity(manifest),
1823
1922
  'lockfile-freshness': () => _check_lockfile_freshness(manifest),
@@ -1890,6 +1989,7 @@ function _run_checks_no_manifest(
1890
1989
  scope: () => _check_scope(project_root),
1891
1990
  'global-binary': () => _check_global_binary(project_root),
1892
1991
  'claude-plugin': _check_claude_plugin,
1992
+ 'hook-wiring': _check_hook_wiring,
1893
1993
  'stale-orphans': _check_stale_orphans,
1894
1994
  'manifest-integrity': () => _skipped_manifest_check('manifest-integrity'),
1895
1995
  'lockfile-freshness': () => _skipped_manifest_check('lockfile-freshness'),
@@ -55,6 +55,7 @@
55
55
  */
56
56
 
57
57
  import * as fs from 'node:fs';
58
+ import * as os from 'node:os';
58
59
  import * as path from 'node:path';
59
60
  import process from 'node:process';
60
61
  import { fileURLToPath, pathToFileURL } from 'node:url';
@@ -62,6 +63,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
62
63
  import * as fs_atomic from '../_lib/fs_atomic.js';
63
64
  import * as installed_lock from '../_lib/installed_lock.js';
64
65
  import * as installed_tools from '../_lib/installed_tools.js';
66
+ import { remove_managed_hooks } from '../_lib/claude_settings_hooks.js';
65
67
  import { resolve_project_root } from '../_lib/agent_settings.js';
66
68
  import { subtract_pointers } from '../_lib/json_pointers.js';
67
69
  import { PROJECT_BRIDGE_MARKERS, USER_SCOPE_PATHS } from '../install.js';
@@ -771,6 +773,26 @@ function _uninstall_global(opts: Options): number {
771
773
  removed_names.push(tool);
772
774
  }
773
775
  }
776
+ // Managed Claude hooks live in ~/.claude/settings.json (single-surface
777
+ // model) — remove exactly our entries, never user-owned ones.
778
+ if (tools.includes('claude-code')) {
779
+ const settings_path = path.join(os.homedir(), '.claude', 'settings.json');
780
+ if (opts.dry_run) {
781
+ _print(_stdout, ` · claude-code: would remove managed hooks from ${settings_path}`);
782
+ } else {
783
+ try {
784
+ const res = remove_managed_hooks(settings_path);
785
+ if (res.changed) {
786
+ _print(
787
+ _stdout,
788
+ ` · claude-code: removed managed hooks (${res.events.join(', ')})`,
789
+ );
790
+ }
791
+ } catch (e) {
792
+ _print(_stderr, `⚠️ claude-code: managed-hook removal skipped — ${String(e)}`);
793
+ }
794
+ }
795
+ }
774
796
  if (lock !== null && !opts.dry_run) {
775
797
  const remaining = (lock.tools ?? []).filter((t) => !tools.includes(t));
776
798
  const version = String((lock as unknown as AnyDict)['agent_config_version'] ?? '');