@event4u/agent-config 9.36.0 → 10.0.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 (79) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +57 -0
  3. package/agents/templates/.ai-council.yml.example +31 -51
  4. package/dist/agent-src/commands/analyze/conformance.md +15 -4
  5. package/dist/agent-src/rules/session-canary.md +28 -0
  6. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  7. package/dist/cli-delegate/{chunk-SFGARMAB.js → chunk-2H5SE7DU.js} +24 -4
  8. package/dist/cli-delegate/{chunk-U7CMAIOC.js → chunk-3JUTT26I.js} +1 -1
  9. package/dist/cli-delegate/{chunk-JP53LNVT.js → chunk-HJSKTMIF.js} +6 -14
  10. package/dist/cli-delegate/{chunk-QEEB4LHO.js → chunk-LRXJK6GN.js} +1 -1
  11. package/dist/cli-delegate/{chunk-NR7HSN3P.js → chunk-NX5AXSLD.js} +3 -3
  12. package/dist/cli-delegate/{chunk-AG47LAGQ.js → chunk-TVGXH7VA.js} +1 -1
  13. package/dist/cli-delegate/{chunk-55RI5UXK.js → chunk-XT2YTJCZ.js} +22 -18
  14. package/dist/cli-delegate/{chunk-JEJABGKB.js → chunk-XTC5ARB6.js} +16 -12
  15. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  16. package/dist/cli-delegate/cmd_brand_status.js +1 -1
  17. package/dist/cli-delegate/cmd_conformance.js +1037 -196
  18. package/dist/cli-delegate/cmd_doctor.js +3 -3
  19. package/dist/cli-delegate/cmd_explain.js +2 -2
  20. package/dist/cli-delegate/cmd_export.js +4 -4
  21. package/dist/cli-delegate/cmd_handoff.js +3 -3
  22. package/dist/cli-delegate/cmd_mcp_available.js +1 -1
  23. package/dist/cli-delegate/cmd_migrate.js +2 -2
  24. package/dist/cli-delegate/cmd_packs_active.js +2 -2
  25. package/dist/cli-delegate/cmd_prune.js +4 -4
  26. package/dist/cli-delegate/cmd_route_audit.js +2 -2
  27. package/dist/cli-delegate/cmd_route_explain.js +1 -1
  28. package/dist/cli-delegate/cmd_session_recycle.js +1 -1
  29. package/dist/cli-delegate/cmd_settings_get.js +1 -1
  30. package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
  31. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  32. package/dist/cli-delegate/cmd_sync.js +4 -4
  33. package/dist/cli-delegate/cmd_uninstall.js +4 -4
  34. package/dist/cli-delegate/cmd_update.js +1 -1
  35. package/dist/cli-delegate/cmd_upgrade.js +1 -1
  36. package/dist/cli-delegate/cmd_validate.js +4 -4
  37. package/dist/cli-delegate/cmd_versions.js +1 -1
  38. package/dist/discovery/deprecation-report.md +1 -1
  39. package/dist/discovery/discovery-manifest.json +4 -4
  40. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  41. package/dist/discovery/discovery-manifest.summary.md +1 -1
  42. package/dist/discovery/dormancy-report.md +1 -1
  43. package/dist/discovery/orphan-report.md +1 -1
  44. package/dist/discovery/packs.json +2 -2
  45. package/dist/discovery/trust-report.md +1 -1
  46. package/dist/discovery/workspaces.json +2 -2
  47. package/dist/hooks/dispatch.js +125 -89
  48. package/dist/install/install.mjs +22 -26
  49. package/dist/mcp/registry-manifest.json +1 -1
  50. package/dist/mcp/server.json +2 -2
  51. package/dist/mcp/server.mjs +1556 -613
  52. package/dist/scripts/_lib/agent_settings.js +39 -25
  53. package/dist/scripts/_lib/agent_settings.js.map +1 -1
  54. package/dist/scripts/condense.js +15 -2
  55. package/dist/scripts/condense.js.map +1 -1
  56. package/dist/server/routes/wizard.js +23 -3
  57. package/dist/server/routes/wizard.js.map +1 -1
  58. package/dist/server/schemas/settings.js +6 -6
  59. package/dist/server/schemas/settings.js.map +1 -1
  60. package/docs/contracts/settings-classes.md +14 -9
  61. package/package.json +1 -1
  62. package/src/config/agent-settings.template.yml +6 -36
  63. package/src/config/ci-local-parity.yml +44 -0
  64. package/src/scripts/_lib/agent_settings.ts +39 -25
  65. package/src/scripts/_lib/environment_detector.ts +52 -1
  66. package/src/scripts/ai_council/config.ts +62 -24
  67. package/src/scripts/before_complete_hook.ts +39 -0
  68. package/src/scripts/check_bridge_derivation.ts +89 -3
  69. package/src/scripts/check_ci_local_parity.ts +74 -0
  70. package/src/scripts/check_references.ts +15 -1
  71. package/src/scripts/condense.ts +16 -2
  72. package/src/scripts/conformance_scan.ts +200 -9
  73. package/src/scripts/council_cli.ts +95 -3
  74. package/src/scripts/hook_manifest.yaml +12 -6
  75. package/src/scripts/hooks/block_no_verify.ts +82 -9
  76. package/src/scripts/hooks/turn_end_gate_hook.ts +138 -105
  77. package/src/scripts/probe_promissory_closing.ts +180 -0
  78. package/src/scripts/probe_session_canary.ts +180 -0
  79. package/src/server/schemas/settings.ts +6 -14
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Bootstrap shim for the event4u agent-config suite. This plugin ships ONLY the dispatcher hooks plus one install-pointer skill — ALL content (skills, rules, commands) is installed via `npx -y @event4u/agent-config init`, which writes the file projection AND registers the same hooks in ~/.claude/settings.json. DEPRECATED as a content channel; kept so the marketplace listing stays discoverable and existing installs keep working hooks.",
9
- "version": "9.36.0",
9
+ "version": "10.0.0",
10
10
  "keywords": [
11
11
  "agent-config",
12
12
  "skills",
package/CHANGELOG.md CHANGED
@@ -339,6 +339,63 @@ Entry-shape contract: [`docs/contracts/CHANGELOG-conventions.md`](docs/contracts
339
339
  > that forces a new era split (`# Era: 9.37.x`, etc.) — see
340
340
  > [`docs/contracts/CHANGELOG-conventions.md § Era splits`](docs/contracts/CHANGELOG-conventions.md).
341
341
 
342
+ ## [10.0.0](https://github.com/event4u-app/agent-config/compare/9.36.0...10.0.0) (2026-08-12)
343
+
344
+ ### Release highlights
345
+
346
+ <!-- Curated head: fill before merge, keep it under 10 lines, and leave `_none_` where it is genuinely the answer. The generated log below is unchanged. -->
347
+ - **Behaviour changes:** _auto-derived, rewrite before merge:_ rule/schema diffs, breaking commits or removed public surface in 030ca0d, c1bc2aa, 4eda4ff.
348
+ - **Default changes + migration:** _none_
349
+ - **Security and correctness:** _none_
350
+ - **Honest nulls:** _auto-derived, rewrite before merge:_ commits carrying an honest-null marker in 7c6b404.
351
+ - **Known limitations:** _none_
352
+
353
+ ### BREAKING CHANGES
354
+
355
+ * **council:** transport is resolved per machine, not configured ([4eda4ff](https://github.com/event4u-app/agent-config/commit/4eda4ff0f518cb30778dc139852576efd35bd57a))
356
+
357
+ ### Features
358
+
359
+ * **ci:** the parity gate learns about preflight, which it never inspected ([44151b0](https://github.com/event4u-app/agent-config/commit/44151b02cd765aff16d7e95459752ec553c1d57b))
360
+ * **conformance:** a fifth check, an era guard, and the denominator the rate was missing ([f7edbdc](https://github.com/event4u-app/agent-config/commit/f7edbdc99533d433ff2b1857bac5bc7fb2929fe8))
361
+ * **hooks:** refuse a completion claim while the last CI read is unsettled ([1ea7536](https://github.com/event4u-app/agent-config/commit/1ea753629ce6a468e061af1ee2f7ac3f38f6bebd))
362
+ * **turn-end-gate:** arm it unconditionally, with no settings surface ([42cd613](https://github.com/event4u-app/agent-config/commit/42cd6131761a5b6681583abd8b7100f4415d84bf))
363
+
364
+ ### Bug Fixes
365
+
366
+ * **round7:** close the R2 findings — the two high ones are in my own Phase 1 ([030ca0d](https://github.com/event4u-app/agent-config/commit/030ca0d6e1a6b27ebbebf74a34caa1403cc98dfa))
367
+ * **council:** validate the output path before spending, and count attendance from answers ([a23d6c8](https://github.com/event4u-app/agent-config/commit/a23d6c84d4963f17ca94838bdbc9f7067b72e77a))
368
+ * **hooks:** block-no-verify stops failing closed on heredoc prose ([7ac5cd2](https://github.com/event4u-app/agent-config/commit/7ac5cd227d34dbc92f9f0438746ff5cd1317e5d2))
369
+ * **taskfile:** block-scalar the bridge-derivation desc so the taskfile parses ([9b0f7b9](https://github.com/event4u-app/agent-config/commit/9b0f7b90ba9bac04c7b0e80dbeaeb99a3aab4cc3))
370
+ * **council:** use a Json-typed sentinel for the ignored-key presence check ([29b9f42](https://github.com/event4u-app/agent-config/commit/29b9f42e505c187f2b35abcb81cc6e7055d5507f))
371
+ * **detection:** find the Claude subscription in the macOS Keychain ([72de870](https://github.com/event4u-app/agent-config/commit/72de8706812ea2cf30940697887964ae1406a3e4))
372
+ * **gates:** scope bridge-derivation to the roots this checkout writes ([ea1b1d4](https://github.com/event4u-app/agent-config/commit/ea1b1d4e230375db0ae1c8e01226d8527a08a1e7))
373
+
374
+ ### Documentation
375
+
376
+ * **review:** re-bind the R2 artefact to the fixed scope ([304b898](https://github.com/event4u-app/agent-config/commit/304b89866880c23f68106c096df20ce18ed207b7))
377
+ * **review:** record the R2 findings before fixing any of them ([bdf45c2](https://github.com/event4u-app/agent-config/commit/bdf45c2e86b2839f3c64d432d50285f043f08d3d))
378
+ * **roadmaps:** the fifth check makes two Context claims stale, in both directions ([6915456](https://github.com/event4u-app/agent-config/commit/6915456005fd164bc525103c6f0b59f05dbc8169))
379
+ * **analyze:** the conformance command documented four checks, there are five ([d05f97e](https://github.com/event4u-app/agent-config/commit/d05f97e13b03cc04036d83aba4df6635e0323932))
380
+ * **conformance:** round 7 — the honest downgrades and the archived roadmap ([c1bc2aa](https://github.com/event4u-app/agent-config/commit/c1bc2aaef41b04aa76024f9949c9e10eacd9886c))
381
+ * **council:** the template and contract stop documenting a setting that is gone ([49d58fa](https://github.com/event4u-app/agent-config/commit/49d58fa28778359416459465cf066b7d8fccb363))
382
+ * **roadmap:** close the stop-refusal blocker, open the two follow-up tracks ([7c6b404](https://github.com/event4u-app/agent-config/commit/7c6b4045c2cadf023d1f434ff851cec86372613a))
383
+
384
+ ### Refactoring
385
+
386
+ * **condense:** expose the active-tool set root-parameterised ([d795d6a](https://github.com/event4u-app/agent-config/commit/d795d6a666f57be451d0dd5462b8725b56e44e75))
387
+ * **settings:** delete hooks.turn_end_gate.* and give REMOVED_KEYS per-key reasons ([f5b316b](https://github.com/event4u-app/agent-config/commit/f5b316b4bc531e2cf7ce0d65556c4a2737cbe0dc))
388
+
389
+ ### Tests
390
+
391
+ * **hooks:** prove the completion detector is wired, not just correct ([4d284ef](https://github.com/event4u-app/agent-config/commit/4d284efa10ba939066edfc42c4a735d94e794def))
392
+
393
+ ### Chores
394
+
395
+ * **condense:** re-project session-canary after the reproduction-command fix ([2d344c4](https://github.com/event4u-app/agent-config/commit/2d344c4d2183a608986f9dce485f1e5006e48863))
396
+
397
+ Tests: 13384 (+51 since 9.36.0)
398
+
342
399
  ## [9.36.0](https://github.com/event4u-app/agent-config/compare/9.35.0...9.36.0) (2026-08-12)
343
400
 
344
401
  ### Release highlights
@@ -25,9 +25,13 @@
25
25
  # never at the raw secret. See docs/contracts/ai-council-config.md for
26
26
  # the full schema.
27
27
  #
28
- # Cost note: every consultation makes real, paid API calls. The
29
- # autonomy directive does NOT silently spend tokens — the /council
30
- # command always asks before invoking, even under autonomy: on.
28
+ # Cost note: a consultation is billed only when it resolves to the metered
29
+ # `api` rung. A vendor CLI under your subscription is preferred whenever one
30
+ # resolves, so the common case costs nothing per token — run
31
+ # `agent-config council status` to see the resolved transport and billing class
32
+ # per member before invoking. The autonomy directive does NOT silently spend
33
+ # tokens — the /council command always asks before invoking, even under
34
+ # autonomy: on.
31
35
 
32
36
  # Master switch (true, false). REQUIRED — the loader has no default for it,
33
37
  # so this file's value is the only value.
@@ -44,51 +48,30 @@
44
48
  # "could work" and "may spend" is visible rather than mysterious.
45
49
  enabled: true
46
50
 
47
- # Per-invocation defaults. Each member may override `mode:` below.
51
+ # Per-invocation defaults.
48
52
  defaults:
49
- # Default transport mode for every member (overridable per-member
50
- # below, and per-invocation via `/council mode:<x>`).
53
+ # There is NO transport-mode setting, deliberately.
51
54
  #
52
- # manual = copy & paste — the human transports prompt + reply
53
- # between the agent and an external chat surface. Free,
54
- # no key.
55
- # api = SDK call against a stored key, per-token billing.
56
- # cli = shell out to a locally-installed provider CLI under the
57
- # user's subscription auth (Claude Pro/Max, ChatGPT
58
- # Plus/Pro, Google Gemini, etc.). Spend is covered by the
59
- # flat-rate subscription, not per-token. `cost_budget.
60
- # max_total_usd` does NOT apply; use `cli_call_budget`
61
- # below to guard subscription quotas.
62
- # auto = resolve per provider per invocation: the provider's CLI
63
- # binary resolves AND a credential is present -> cli; else a
64
- # key resolves -> api; else unavailable with a one-line
65
- # reason. `manual` is NEVER part of the auto chain (it would
66
- # always "succeed" and silently turn every call into a
67
- # copy-paste request) — it stays an explicit opt-in.
68
- # THE SHIPPED DEFAULT (road-to-always-on-orchestration
69
- # Phase 3.1 — CLI-first is the owner-set transport doctrine):
70
- # ride the vendor CLI under your subscription first, fall to
71
- # the metered API only where no CLI resolves. Pin `manual` /
72
- # `api` / `cli` here (or per-member below) to override it.
73
- # `cli_call_budget` below ships populated with a generous
74
- # per-provider guard so this default never runs your
75
- # subscription quota uncapped.
55
+ # Transport is resolved per machine per invocation — `cli → api →
56
+ # unavailable`, and an airgapped host forces the `api` rung. A vendor CLI
57
+ # under your subscription is preferred whenever one resolves with a
58
+ # credential, so the free transport wins without anybody configuring it.
76
59
  #
77
- # Precedence: invocation flag > per-member mode > defaults.mode >
78
- # built-in. Two DIFFERENT defaults sit at the bottom, and they are not
79
- # the same thing:
80
- # · omit `defaults.mode` entirely and the loader fills "auto" (was
81
- # "api" before the Phase 3.1 flip) — that is what every real config
82
- # file observes.
83
- # · the resolver's built-in fallback, reached only when no layer at all
84
- # supplies a mode (a settings dict handed straight to the resolver,
85
- # no config file involved), is "manual" — free, no key. A caller who
86
- # named no transport has not asked to spend money. This did NOT flip
87
- # alongside the loader default: `auto` still resolves to a paying
88
- # rung when no CLI is usable, so flipping this too would let a
89
- # caller that bypassed the config file spend money on a preference
90
- # it never stated.
91
- mode: auto
60
+ # It used to be `defaults.mode`, with a per-member override. The default was
61
+ # flipped to CLI-first, but a default only fires on an ABSENT key: every
62
+ # config that had spelled the key out — which this template did, and which
63
+ # the setup wizard wrote — kept its old `api` value and went on paying per
64
+ # token while a paid-for subscription CLI sat unused on the same machine.
65
+ # A knob whose stale value silently costs money is worse than no knob.
66
+ #
67
+ # A file that still carries `mode:` (here or under a member) loads fine; the
68
+ # key is ignored and `agent-config council status` lists it as ignored so you
69
+ # can delete it. To pin one transport for ONE run — including the free,
70
+ # key-free `manual` copy-paste loop — pass `--mode-override` on the
71
+ # invocation. That is a flag, not a setting: it cannot go stale.
72
+ #
73
+ # `agent-config council status` prints the resolved transport and the billing
74
+ # class per member, which is the only honest answer to "will this cost money".
92
75
 
93
76
  # Default number of debate rounds per /council invocation.
94
77
  #
@@ -264,7 +247,9 @@ quorum: majority
264
247
  # under ~/.event4u/agent-config/, legacy
265
248
  # ~/.config/agent-config/ read as a fallback) or an env
266
249
  # var (`env:<VAR>`). Raw keys here are a hard validation
267
- # error. Required for mode: api; optional for cli /
250
+ # error. Optional: it is the credential for the metered `api`
251
+ # fallback rung. Absent, that rung is simply unavailable and
252
+ # the member needs a working CLI. Not needed for cli /
268
253
  # manual / auto (those may authenticate via the CLI).
269
254
  # mode: overrides defaults.mode for this member only.
270
255
  # binary: absolute path to the provider's CLI — only valid when
@@ -290,7 +275,6 @@ members:
290
275
  enabled: true
291
276
  model: claude-sonnet-4-5
292
277
  api_key_ref: file:anthropic.key
293
- # mode: cli # use subscription auth (Claude Pro/Max) — free, see cli_call_budget above
294
278
  # binary: /usr/local/bin/claude # only valid when mode == cli
295
279
  # Phase 7 — size-fit downgrade ladder (smallest → largest). The
296
280
  # active `model:` above must appear on this list. When the size
@@ -325,7 +309,6 @@ members:
325
309
  enabled: true
326
310
  model: gpt-4o
327
311
  api_key_ref: file:openai.key
328
- # mode: cli # use subscription auth (ChatGPT Plus/Pro via Codex CLI) — free
329
312
  # binary: /usr/local/bin/codex # only valid when mode == cli
330
313
  # Phase 7 — size-fit downgrade ladder (smallest → largest). The
331
314
  # active `model:` above must appear on this list.
@@ -340,14 +323,12 @@ members:
340
323
  enabled: false
341
324
  model: gemini-2.5-pro
342
325
  api_key_ref: env:GEMINI_API_KEY
343
- # mode: cli # use Google account (free-tier limits apply) — free
344
326
  # binary: /usr/local/bin/gemini # only valid when mode == cli
345
327
 
346
328
  xai:
347
329
  enabled: false
348
330
  model: grok-4
349
331
  api_key_ref: env:XAI_API_KEY
350
- # mode: cli # community `grok` CLI (Superagent) — ergonomic shortcut, NOT a billing change.
351
332
  # binary: /usr/local/bin/grok # The CLI consumes XAI_API_KEY and remains billable=True;
352
333
  # # cost_budget.max_total_usd STILL applies. Opt-in only.
353
334
 
@@ -355,7 +336,6 @@ members:
355
336
  enabled: false
356
337
  model: sonar-pro
357
338
  api_key_ref: env:PERPLEXITY_API_KEY
358
- # mode: cli # community `perplexity` CLI — ergonomic shortcut, NOT a billing change.
359
339
  # binary: /usr/local/bin/perplexity # The CLI consumes PERPLEXITY_API_KEY and remains billable=True;
360
340
  # # cost_budget.max_total_usd STILL applies. Opt-in only.
361
341
 
@@ -72,10 +72,21 @@ how they make it.
72
72
  agent-config conformance:behavior --limit <N> --output conformance-report.json
73
73
  ```
74
74
 
75
- Replays the local transcript store through the four **mechanised** checks:
76
- language pin, git authorization, vacuous evidence, evidence steering. Every
77
- classifier is imported from the gate it measures, so the scan and the gate
78
- cannot drift apart.
75
+ Replays the local transcript store through the five **mechanised** checks:
76
+ language pin, git authorization, vacuous evidence, evidence steering, and — since
77
+ round 7 — completion claim (a turn claiming the work is done while the last CI
78
+ read in that session was not settled). Every classifier is imported from the gate
79
+ it measures, so the scan and the gate cannot drift apart.
80
+
81
+ Two readings the report now separates, both because round 7's first run got them
82
+ wrong. The **language-pin rate is printed over two denominators** — all assistant
83
+ turns, which is what the band's three reference values used, and turns under a
84
+ German pin, which is the only population the check can fire on; the same corpus
85
+ read 6.5 % on the first and 9.2 % on the second, enough to move the verdict. And
86
+ the **band verdict is withheld over a corpus that spans a carrier landing**
87
+ (`CARRIER_CHANGES`): pooling 23.1 % before a mechanism with 0.0 % after it is a
88
+ weighted average of two systems, and the pooled figure was announced as the
89
+ pre-registered falsifier firing.
79
90
 
80
91
  This is the floor, not the audit. It sees only what already has a gate — which
81
92
  is exactly why it is trustworthy and exactly why it is not sufficient.
@@ -118,6 +118,34 @@ refused turn-end continues in the same turn) that rejects a task-start reply
118
118
  carrying no greeting. Proposed, not shipped; until it is, this obligation is
119
119
  model-carried in practice, whatever the frequency join reports.
120
120
 
121
+ **Round 7 downgrade: that proposed mechanism is UNDECIDABLE as written, and the
122
+ obligation splits into a measured half and an unmeasurable one.** A *task* start
123
+ is recorded nowhere in a transcript — there is no task event on any host, and
124
+ `user-interrupt-priority`'s continuation/clarification/interrupt distinction is a
125
+ judgement, not a field. So a delivery check "that rejects a task-start reply" has
126
+ no way to know a task started. The proposal is not merely unbuilt; as specified it
127
+ cannot be built, and saying "proposed, not shipped" implied otherwise.
128
+
129
+ What IS decidable is the per-**session** instance, and it was measured rather than
130
+ asserted — reproducibly, which took a correction: the probe resolves the name
131
+ through the same settings layers this rule names and REFUSES when no layer carries
132
+ one, so the figure below is reproduced with the name passed explicitly
133
+ (`./scripts-run src/scripts/probe_session_canary --limit 30 --name <your name>`;
134
+ the first version hardcoded a maintainer's nickname as its default, which made the
135
+ number irreproducible for anyone else and silently read 0 %). Over 30 sessions on
136
+ 2026-08-12: the opening greeting is present in **25 of 28** sessions carrying
137
+ assistant prose (89.3 %), and in **24 of 25** post-carrier (96.0 %). Of the three
138
+ misses, two predate the carrier (2026-07-21, 2026-07-29) and one is a session whose
139
+ first thirty turns were tool calls.
140
+
141
+ Both halves of that are load-bearing. The carrier works exactly where it has a
142
+ slot — `session_start` — which is why the session-scope figure is high; and the
143
+ 24-of-29 task-start figure above is not contradicted by it, because the two count
144
+ different events. The honest statement is therefore: **session-scope compliance is
145
+ measured and good; task-scope compliance is unmeasured and unmeasurable with
146
+ today's transcript, and no gate can change that until a task boundary is recorded
147
+ somewhere.** Do not read the 96 % as covering the obligation as a whole.
148
+
121
149
  **The wrong-name half needs nothing, and that is worth stating.** The beat
122
150
  already carries the resolved value — `build_canary_reminder` emits
123
151
  `Canary active for "<name>"`, and the hook no-ops when no layer resolves a name —
@@ -39,7 +39,7 @@ schema_version: 1
39
39
  # CI guard: a release bump of `package.json` must update this value
40
40
  # in lockstep — see scripts/check_template_pin_drift.ts (road-to-
41
41
  # portable-runtime-and-update-check P3.3).
42
- agent_config_version: "9.36.0"
42
+ agent_config_version: "10.0.0"
43
43
 
44
44
  # --- Project identity ---
45
45
  project:
@@ -18,7 +18,7 @@ import {
18
18
  hardenedSpawnEnv,
19
19
  scanTranscripts,
20
20
  weightedInputUnits
21
- } from "./chunk-55RI5UXK.js";
21
+ } from "./chunk-XT2YTJCZ.js";
22
22
  import {
23
23
  git_common_dir
24
24
  } from "./chunk-PZWRPRWP.js";
@@ -52,7 +52,7 @@ import {
52
52
  load_agent_settings,
53
53
  project_settings_path,
54
54
  resolve_project_root
55
- } from "./chunk-JEJABGKB.js";
55
+ } from "./chunk-XTC5ARB6.js";
56
56
  import {
57
57
  event4u_root,
58
58
  resolve_with_fallback,
@@ -3777,6 +3777,22 @@ function homeOf(env) {
3777
3777
  return injected !== "" ? injected : os.homedir();
3778
3778
  }
3779
3779
  var VERSION_PROBE_TIMEOUT_MS = 2e3;
3780
+ var CLAUDE_KEYCHAIN_SERVICE = "Claude Code-credentials";
3781
+ function defaultProbeKeychain(service) {
3782
+ if (process.platform !== "darwin") return false;
3783
+ try {
3784
+ const res = spawnSync2("security", ["find-generic-password", "-s", service], {
3785
+ timeout: VERSION_PROBE_TIMEOUT_MS,
3786
+ encoding: "utf-8",
3787
+ windowsHide: true,
3788
+ env: hardenedSpawnEnv()
3789
+ });
3790
+ if (res.error !== void 0 || res.signal !== null) return false;
3791
+ return res.status === 0;
3792
+ } catch {
3793
+ return false;
3794
+ }
3795
+ }
3780
3796
  function defaultProbeVersion(binary) {
3781
3797
  try {
3782
3798
  const res = spawnSync2(binary, ["--version"], {
@@ -3822,13 +3838,16 @@ function classifyCodexAuth(authPath) {
3822
3838
  if (hasOauth) return "cli-subscription";
3823
3839
  return "cli-api-key";
3824
3840
  }
3825
- function cliSubscriptionEvidence(provider, home, env, claudeConfigDir) {
3841
+ function cliSubscriptionEvidence(provider, home, env, claudeConfigDir, probeKeychain) {
3826
3842
  if (provider === "anthropic") {
3827
3843
  const configDir = claudeConfigDir;
3828
3844
  for (const rel of [".credentials.json", "credentials.json"]) {
3829
3845
  const p = path12.join(configDir, rel);
3830
3846
  if (isFile(p)) return { source: "cli-subscription", evidence: p };
3831
3847
  }
3848
+ if (probeKeychain(CLAUDE_KEYCHAIN_SERVICE)) {
3849
+ return { source: "cli-subscription", evidence: `keychain:${CLAUDE_KEYCHAIN_SERVICE}` };
3850
+ }
3832
3851
  return null;
3833
3852
  }
3834
3853
  if (provider === "openai") {
@@ -3870,10 +3889,11 @@ function detectAuth(opts) {
3870
3889
  const env = opts.env ?? process.env;
3871
3890
  const home = opts.home ?? homeOf(opts.env);
3872
3891
  const claudeConfigDir = opts.home === void 0 && opts.env === void 0 ? claude_config_dir() : (env["CLAUDE_CONFIG_DIR"] ?? "").trim() || path12.join(home, ".claude");
3892
+ const probeKeychain = opts.probeKeychain ?? defaultProbeKeychain;
3873
3893
  const auth = [];
3874
3894
  const keys = [];
3875
3895
  for (const provider of knownProviders()) {
3876
- const cli = cliSubscriptionEvidence(provider, home, env, claudeConfigDir);
3896
+ const cli = cliSubscriptionEvidence(provider, home, env, claudeConfigDir, probeKeychain);
3877
3897
  if (cli !== null) {
3878
3898
  auth.push({ provider, source: cli.source, evidence: cli.evidence });
3879
3899
  }
@@ -1,7 +1,7 @@
1
1
  import { createRequire as __acCreateRequire } from 'node:module'; const require = globalThis.require ?? __acCreateRequire(import.meta.url);
2
2
  import {
3
3
  resolve_project_root
4
- } from "./chunk-JEJABGKB.js";
4
+ } from "./chunk-XTC5ARB6.js";
5
5
  import {
6
6
  resolvePackageRoot
7
7
  } from "./chunk-MCJYCQPF.js";
@@ -4552,21 +4552,13 @@ var settingsSchema = external_exports.object({
4552
4552
  enabled: external_exports.boolean().default(false).describe(
4553
4553
  "PreToolUse code-graph nudge (ADR-124 Phase 4). Default off. When on AND a native code-graph cache or a consumer-shipped graph.json/SCIP index is present, warns once per session (never blocks) as the agent is about to Grep/Glob or Read a source file \u2014 query the graph first for who-calls/where-used/impact questions (or rebuild if stale, build if absent). Source G\u2019s strict block-first-read mode is deliberately un-ported."
4554
4554
  )
4555
- }).default({}),
4556
- turn_end_gate: external_exports.object({
4557
- enabled: external_exports.boolean().default(false).describe(
4558
- "Stop-slot turn-end gate (road-to-conformance-round5 Phase 3). Default off, and it is the only concern that can REFUSE a turn-end rather than a tool call \u2014 so it soaks behind an explicit opt-in before it binds. Round 5 measured the reason: both blocking carriers reached zero violations, neither advisory carrier did, and 19 wrong-language replies survived a pin that had fired 26-35 seconds earlier. A refused turn continues in the SAME turn and is never refused twice."
4559
- ),
4560
- promissory: external_exports.boolean().default(true).describe(
4561
- "Detector A of the turn-end gate: refuse a turn whose closing paragraph promises work not yet performed while asking the user nothing (20 measured occurrences). No fire rate is quoted here on purpose: the measured rate moved twice while the instrument itself was being corrected, so a number in a shipped description would be a moving target. Re-derive it with `measure-turn-end-gate --store <transcript dir>` against your own corpus before arming the gate. Inert while hooks.turn_end_gate.enabled is false."
4562
- ),
4563
- language: external_exports.boolean().default(true).describe(
4564
- "Detector B of the turn-end gate: refuse a turn whose reply is not in the pinned language (19 measured occurrences). Fenced code, inline code, quoted tool output, tables, URLs and paths are stripped first, and the remainder is then scored LEAD-FIRST by the same classifier that sets the pin \u2014 so the verdict usually comes from the reply opening, not from all of it. Sharing that classifier is deliberate: it stops the pin and the detector disagreeing about a turn. Accepted opt-in spellings for the switches below include YAML true/false and yes/no/on/off. Inert while hooks.turn_end_gate.enabled is false."
4565
- ),
4566
- verification: external_exports.boolean().default(true).describe(
4567
- "Detector C of the turn-end gate: refuse a turn that changed a file and then ran nothing that could have checked it \u2014 no test, type-check, lint or build call follows the LAST edit. Ordering is the point: a run before the final edit did not exercise it, which is the freshness clause verify-before-complete states. Read off the turn tool calls, not the prose, because a reply cannot say whether an edit was verified. Deliberately narrow on what counts as verification \u2014 a missed detection costs one unguarded turn, a false refusal teaches you to switch the gate off. No fire rate is quoted: none has been measured yet. Inert while hooks.turn_end_gate.enabled is false."
4568
- )
4569
4555
  }).default({})
4556
+ // `turn_end_gate` is deliberately ABSENT. The stop-slot turn-end gate is
4557
+ // always armed (2026-08-12) and has no settings surface: whether it
4558
+ // fires is decided by each detector's own trigger conditions, not by a
4559
+ // flag. A leftover `hooks.turn_end_gate.*` block from an older install
4560
+ // warns once on stderr and is ignored — see REMOVED_KEYS in
4561
+ // `src/scripts/_lib/agent_settings.ts`.
4570
4562
  }),
4571
4563
  decision_engine: external_exports.object({
4572
4564
  surface_traces: external_exports.boolean().default(false).describe(
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-MMJL4TIX.js";
5
5
  import {
6
6
  load_agent_settings
7
- } from "./chunk-JEJABGKB.js";
7
+ } from "./chunk-XTC5ARB6.js";
8
8
 
9
9
  // src/scripts/chat_history.ts
10
10
  import { createHash } from "node:crypto";
@@ -2,10 +2,10 @@ import { createRequire as __acCreateRequire } from 'node:module'; const require
2
2
  import {
3
3
  ZodFirstPartyTypeKind,
4
4
  settingsSchema
5
- } from "./chunk-JP53LNVT.js";
5
+ } from "./chunk-HJSKTMIF.js";
6
6
  import {
7
7
  main
8
- } from "./chunk-U7CMAIOC.js";
8
+ } from "./chunk-3JUTT26I.js";
9
9
  import {
10
10
  RULE_SOURCE_REL
11
11
  } from "./chunk-BKO2AZ7Z.js";
@@ -48,7 +48,7 @@ import {
48
48
  import {
49
49
  find_project_root_with_anchor,
50
50
  load_agent_settings
51
- } from "./chunk-JEJABGKB.js";
51
+ } from "./chunk-XTC5ARB6.js";
52
52
  import {
53
53
  event4u_root,
54
54
  legacy_xdg_root,
@@ -2,7 +2,7 @@ import { createRequire as __acCreateRequire } from 'node:module'; const require
2
2
  import {
3
3
  DEFAULT_PROJECT_FILE,
4
4
  logger
5
- } from "./chunk-JEJABGKB.js";
5
+ } from "./chunk-XTC5ARB6.js";
6
6
 
7
7
  // src/scripts/config/profiles.ts
8
8
  import * as fs2 from "node:fs";
@@ -40,7 +40,6 @@ var _VALID_PROVIDERS = /* @__PURE__ */ new Set([
40
40
  "xai",
41
41
  "perplexity"
42
42
  ]);
43
- var _VALID_MODES = /* @__PURE__ */ new Set(["api", "manual", "cli", "auto"]);
44
43
  var _RAW_KEY_PREFIXES = [
45
44
  "sk-",
46
45
  "sk-ant-",
@@ -303,7 +302,11 @@ function _build_config(raw, source_path) {
303
302
  if (!_isBool(enabled)) {
304
303
  throw new CouncilConfigError("`enabled` must be a bool.");
305
304
  }
306
- const defaults = _build_defaults(_asDict(_getOr(raw, "defaults", {})));
305
+ const ignored_transport_keys = [];
306
+ const defaults = _build_defaults(
307
+ _asDict(_getOr(raw, "defaults", {})),
308
+ ignored_transport_keys
309
+ );
307
310
  const cost_budget = _build_cost_budget(
308
311
  _asDict(_getOr(raw, "cost_budget", {}))
309
312
  );
@@ -315,7 +318,12 @@ function _build_config(raw, source_path) {
315
318
  for (const [name, cfg] of Object.entries(members_raw)) {
316
319
  members.set(
317
320
  name,
318
- _build_member(name, _asDict(_orEmpty(cfg)), defaults.mode)
321
+ _build_member(
322
+ name,
323
+ _asDict(_orEmpty(cfg)),
324
+ defaults.mode,
325
+ ignored_transport_keys
326
+ )
319
327
  );
320
328
  }
321
329
  const advisors_raw = _getOr(raw, "advisors", {});
@@ -414,7 +422,8 @@ function _build_config(raw, source_path) {
414
422
  routing,
415
423
  low_impact,
416
424
  lens_overrides,
417
- source_path
425
+ source_path,
426
+ ignored_transport_keys
418
427
  };
419
428
  }
420
429
  function _orEmpty(value) {
@@ -892,16 +901,14 @@ function _build_consensus_scoring(d) {
892
901
  };
893
902
  }
894
903
  var _VALID_MEMBER_MODES = /* @__PURE__ */ new Set(["cli", "api"]);
895
- function _build_defaults(d) {
904
+ function _build_defaults(d, ignored = []) {
896
905
  if (!_isDict(d)) {
897
906
  throw new CouncilConfigError("`defaults` must be a mapping.");
898
907
  }
899
- const mode = _get(d, "mode", "auto");
900
- if (!(_isStr(mode) && _VALID_MODES.has(mode))) {
901
- throw new CouncilConfigError(
902
- `defaults.mode=${_pyRepr(mode)} not in ${_sortedListRepr(_VALID_MODES)}.`
903
- );
908
+ if (_get(d, "mode", null) !== null) {
909
+ ignored.push("defaults.mode");
904
910
  }
911
+ const mode = "auto";
905
912
  const member_mode = _get(d, "member_mode", "cli");
906
913
  if (!(_isStr(member_mode) && _VALID_MEMBER_MODES.has(member_mode))) {
907
914
  throw new CouncilConfigError(
@@ -1058,7 +1065,7 @@ function _build_cost_budget(d) {
1058
1065
  }
1059
1066
  return cb;
1060
1067
  }
1061
- function _build_member(name, cfg, default_mode = "auto") {
1068
+ function _build_member(name, cfg, default_mode = "auto", ignored = []) {
1062
1069
  if (!_VALID_PROVIDERS.has(name)) {
1063
1070
  throw new CouncilConfigError(
1064
1071
  `members.${name}: unknown provider; valid: ${_sortedListRepr(_VALID_PROVIDERS)}.`
@@ -1069,14 +1076,11 @@ function _build_member(name, cfg, default_mode = "auto") {
1069
1076
  const model = _pyTruthy(modelVal) ? modelVal : "";
1070
1077
  const api_key_ref_raw = _get(cfg, "api_key_ref", null);
1071
1078
  const api_key_ref = api_key_ref_raw === void 0 ? null : api_key_ref_raw;
1072
- const member_mode_raw = _get(cfg, "mode", null);
1073
- const member_mode = member_mode_raw === void 0 ? null : member_mode_raw;
1074
- if (member_mode !== null && !(_isStr(member_mode) && _VALID_MODES.has(member_mode))) {
1075
- throw new CouncilConfigError(
1076
- `members.${name}.mode=${_pyRepr(member_mode)} not in ${_sortedListRepr(_VALID_MODES)}.`
1077
- );
1079
+ if (_get(cfg, "mode", null) !== null) {
1080
+ ignored.push(`members.${name}.mode`);
1078
1081
  }
1079
- const effective_mode = member_mode !== null ? member_mode : default_mode;
1082
+ const member_mode = null;
1083
+ const effective_mode = default_mode;
1080
1084
  const binary_raw = _get(cfg, "binary", null);
1081
1085
  const binary = binary_raw === void 0 ? null : binary_raw;
1082
1086
  if (binary !== null) {
@@ -504,14 +504,18 @@ function load_agent_settings(options = {}) {
504
504
  _warn_removed_always_on_keys(merged);
505
505
  return merged;
506
506
  }
507
- var REMOVED_ALWAYS_ON_KEYS = [
508
- "subagents.enabled",
509
- "subagents.auto",
510
- "subagents.host_capabilities",
511
- "subagents.budget_routing",
512
- "ai_team.enabled"
513
- ];
514
- var _warnedRemovedAlwaysOnKeys = /* @__PURE__ */ new Set();
507
+ var REMOVED_KEYS = /* @__PURE__ */ new Map([
508
+ ["subagents.enabled", "always-on orchestration"],
509
+ ["subagents.auto", "always-on orchestration"],
510
+ ["subagents.host_capabilities", "always-on orchestration"],
511
+ ["subagents.budget_routing", "always-on orchestration"],
512
+ ["ai_team.enabled", "always-on orchestration"],
513
+ ["hooks.turn_end_gate.enabled", "the turn-end gate is always armed"],
514
+ ["hooks.turn_end_gate.promissory", "the turn-end gate is always armed"],
515
+ ["hooks.turn_end_gate.language", "the turn-end gate is always armed"],
516
+ ["hooks.turn_end_gate.verification", "the turn-end gate is always armed"]
517
+ ]);
518
+ var _warnedRemovedKeys = /* @__PURE__ */ new Set();
515
519
  function _readDottedSettingsPath(root, dotted) {
516
520
  let node = root;
517
521
  for (const part of dotted.split(".")) {
@@ -523,15 +527,15 @@ function _readDottedSettingsPath(root, dotted) {
523
527
  return node;
524
528
  }
525
529
  function _warn_removed_always_on_keys(merged) {
526
- for (const key of REMOVED_ALWAYS_ON_KEYS) {
527
- if (_warnedRemovedAlwaysOnKeys.has(key)) {
530
+ for (const [key, reason] of REMOVED_KEYS) {
531
+ if (_warnedRemovedKeys.has(key)) {
528
532
  continue;
529
533
  }
530
534
  if (_readDottedSettingsPath(merged, key) === void 0) {
531
535
  continue;
532
536
  }
533
- _warnedRemovedAlwaysOnKeys.add(key);
534
- process.stderr.write(`${key} was removed (always-on orchestration); ignored.
537
+ _warnedRemovedKeys.add(key);
538
+ process.stderr.write(`${key} was removed (${reason}); ignored.
535
539
  `);
536
540
  }
537
541
  }
@@ -7,7 +7,7 @@ import {
7
7
  import {
8
8
  ProjectRootError,
9
9
  resolve_project_root
10
- } from "./chunk-JEJABGKB.js";
10
+ } from "./chunk-XTC5ARB6.js";
11
11
  import "./chunk-NDJLNC6Q.js";
12
12
  import "./chunk-J55WI3RR.js";
13
13
 
@@ -1,7 +1,7 @@
1
1
  import { createRequire as __acCreateRequire } from 'node:module'; const require = globalThis.require ?? __acCreateRequire(import.meta.url);
2
2
  import {
3
3
  resolve_project_root
4
- } from "./chunk-JEJABGKB.js";
4
+ } from "./chunk-XTC5ARB6.js";
5
5
  import "./chunk-NDJLNC6Q.js";
6
6
  import "./chunk-J55WI3RR.js";
7
7