@event4u/agent-config 9.28.0 → 9.30.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 (209) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/AGENTS.md +6 -6
  3. package/CHANGELOG.md +172 -5
  4. package/README.md +2 -1
  5. package/agents/templates/.ai-council.yml.example +55 -24
  6. package/dist/agent-src/commands/analyze/inbox.md +22 -1
  7. package/dist/agent-src/commands/cost/report.md +2 -2
  8. package/dist/agent-src/commands/explain-run.md +31 -1
  9. package/dist/agent-src/commands/team/adversarial.md +8 -7
  10. package/dist/agent-src/commands/team/delegate.md +14 -12
  11. package/dist/agent-src/commands/team/review.md +9 -8
  12. package/dist/agent-src/commands/team/status.md +9 -8
  13. package/dist/agent-src/commands/team.md +29 -19
  14. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +88 -2
  15. package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +29 -21
  16. package/dist/agent-src/contexts/execution/host-capability-manifest.md +26 -15
  17. package/dist/agent-src/contexts/execution/orchestration-telemetry.md +22 -1
  18. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +17 -0
  19. package/dist/agent-src/contexts/execution/subagent-response-contract.md +65 -1
  20. package/dist/agent-src/contexts/execution/subagent-routing.md +13 -11
  21. package/dist/agent-src/contexts/execution/subagent-spawn-contract.md +25 -0
  22. package/dist/agent-src/contexts/execution/subagent-steering.md +6 -3
  23. package/dist/agent-src/rules/delegation-policy.md +11 -7
  24. package/dist/agent-src/rules/preservation-guard.md +9 -0
  25. package/dist/agent-src/rules/settings-ask-protocol.md +8 -7
  26. package/dist/agent-src/rules/token-budget-discipline.md +1 -1
  27. package/dist/agent-src/scripts/roadmap_gates.ts +312 -0
  28. package/dist/agent-src/scripts/update_roadmap_progress.ts +72 -4
  29. package/dist/agent-src/skills/forensics-report/SKILL.md +114 -0
  30. package/dist/agent-src/skills/forensics-report/evals/triggers.json +17 -0
  31. package/dist/agent-src/skills/premortem/SKILL.md +30 -1
  32. package/dist/agent-src/skills/reasoning-orchestrator/SKILL.md +2 -1
  33. package/dist/agent-src/skills/subagent-orchestration/SKILL.md +17 -5
  34. package/dist/agent-src/skills/subagent-orchestration/schemas/subagent-status.json +61 -3
  35. package/dist/agent-src/skills/test-case-discovery/SKILL.md +1 -1
  36. package/dist/agent-src/templates/AGENTS.md +4 -4
  37. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  38. package/dist/agent-src/templates/roadmaps.md +23 -0
  39. package/dist/cli/registry.js +2 -0
  40. package/dist/cli/registry.js.map +1 -1
  41. package/dist/cli-delegate/{chunk-22GCIJ2O.js → chunk-66OVWGB5.js} +1 -1
  42. package/dist/cli-delegate/{chunk-7OMQCH4V.js → chunk-BTJNRUX3.js} +22 -35
  43. package/dist/cli-delegate/{chunk-DVRS2KVR.js → chunk-DDUNNFYX.js} +24 -17
  44. package/dist/cli-delegate/{chunk-PQSAQKHW.js → chunk-EQY4FLJB.js} +3 -3
  45. package/dist/cli-delegate/{chunk-2BE5I2MN.js → chunk-IYTGIG2Y.js} +155 -91
  46. package/dist/cli-delegate/{chunk-GB2FSUEE.js → chunk-LCFJIQ4J.js} +32 -0
  47. package/dist/cli-delegate/{chunk-VSYT7OHO.js → chunk-LVRYBKTI.js} +31 -2
  48. package/dist/cli-delegate/chunk-MMJL4TIX.js +18 -0
  49. package/dist/cli-delegate/{chunk-JXARCGVN.js → chunk-MP3JT3IU.js} +1 -1
  50. package/dist/cli-delegate/chunk-O3CO5BFB.js +161 -0
  51. package/dist/cli-delegate/{chunk-REKCMYOU.js → chunk-PFBV6XRT.js} +36 -20
  52. package/dist/cli-delegate/chunk-RVGBH4XK.js +142 -0
  53. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  54. package/dist/cli-delegate/cmd_brand_status.js +1 -1
  55. package/dist/cli-delegate/cmd_conformance.js +2803 -137
  56. package/dist/cli-delegate/cmd_doctor.js +10 -5
  57. package/dist/cli-delegate/cmd_explain.js +2 -2
  58. package/dist/cli-delegate/cmd_export.js +4 -4
  59. package/dist/cli-delegate/cmd_handoff.js +88 -8
  60. package/dist/cli-delegate/cmd_mcp_available.js +1 -1
  61. package/dist/cli-delegate/cmd_migrate.js +2 -2
  62. package/dist/cli-delegate/cmd_packs_active.js +2 -2
  63. package/dist/cli-delegate/cmd_prune.js +4 -4
  64. package/dist/cli-delegate/cmd_route_audit.js +3 -2
  65. package/dist/cli-delegate/cmd_route_explain.js +1 -1
  66. package/dist/cli-delegate/cmd_session_recycle.js +177 -0
  67. package/dist/cli-delegate/cmd_settings_get.js +2 -8
  68. package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
  69. package/dist/cli-delegate/cmd_settings_set.js +2 -2
  70. package/dist/cli-delegate/cmd_sync.js +4 -4
  71. package/dist/cli-delegate/cmd_uninstall.js +4 -4
  72. package/dist/cli-delegate/cmd_update.js +1 -1
  73. package/dist/cli-delegate/cmd_upgrade.js +1 -1
  74. package/dist/cli-delegate/cmd_validate.js +4 -4
  75. package/dist/cli-delegate/cmd_versions.js +1 -1
  76. package/dist/discovery/deprecation-report.md +1 -1
  77. package/dist/discovery/discovery-manifest.json +72 -23
  78. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  79. package/dist/discovery/discovery-manifest.summary.md +3 -2
  80. package/dist/discovery/orphan-report.md +1 -1
  81. package/dist/discovery/packs.json +39 -2
  82. package/dist/discovery/trust-report.md +2 -2
  83. package/dist/discovery/workspaces.json +12 -4
  84. package/dist/hooks/dispatch.js +2278 -611
  85. package/dist/install/install.mjs +56 -17
  86. package/dist/mcp/registry-manifest.json +3 -3
  87. package/dist/mcp/server.json +2 -2
  88. package/dist/mcp/server.mjs +4122 -699
  89. package/dist/router.json +1 -1
  90. package/dist/scripts/_lib/agent_settings.js +54 -0
  91. package/dist/scripts/_lib/agent_settings.js.map +1 -1
  92. package/dist/scripts/condense.js +147 -2
  93. package/dist/scripts/condense.js.map +1 -1
  94. package/dist/server/schemas/settings.js +12 -7
  95. package/dist/server/schemas/settings.js.map +1 -1
  96. package/dist/shared/settingsCarveOut.js +0 -9
  97. package/dist/shared/settingsCarveOut.js.map +1 -1
  98. package/dist/shared/settingsClasses.js +11 -9
  99. package/dist/shared/settingsClasses.js.map +1 -1
  100. package/dist/ui/assets/index-B5qGYOJo.js +26 -0
  101. package/dist/ui/assets/index-B5qGYOJo.js.map +1 -0
  102. package/dist/ui/index.html +1 -1
  103. package/docs/contracts/settings-classes.md +58 -12
  104. package/package.json +1 -1
  105. package/src/agent-src/scripts/roadmap_gates.ts +312 -0
  106. package/src/agent-src/scripts/update_roadmap_progress.ts +72 -4
  107. package/src/agent-src/templates/AGENTS.md +4 -4
  108. package/src/config/agent-settings.template.yml +88 -66
  109. package/src/config/discovery/packs.yml +11 -0
  110. package/src/config/discovery/workspaces.yml +1 -1
  111. package/src/config/dispatch-economy-metrics.json +61 -0
  112. package/src/config/evaluator-budgets.json +4 -4
  113. package/src/config/gate-violation-baselines.json +3 -3
  114. package/src/config/hook-token-budget.json +91 -0
  115. package/src/config/recycle-threshold-budget.json +12 -0
  116. package/src/config/rule-enforcement-baseline.json +86 -0
  117. package/src/scripts/_cli/cmd_conformance.ts +31 -44
  118. package/src/scripts/_cli/cmd_doctor.ts +65 -48
  119. package/src/scripts/_cli/cmd_session_recycle.ts +226 -0
  120. package/src/scripts/_dispatch.bash +23 -0
  121. package/src/scripts/_lib/agent_settings.ts +58 -0
  122. package/src/scripts/_lib/auto_dispatch.corpus.test.ts +1 -1
  123. package/src/scripts/_lib/auto_dispatch.ts +23 -18
  124. package/src/scripts/_lib/capsule_trigger.ts +124 -0
  125. package/src/scripts/_lib/host_capability.ts +63 -6
  126. package/src/scripts/_lib/judgment_ladder.ts +634 -0
  127. package/src/scripts/_lib/orchestration_gate.ts +8 -0
  128. package/src/scripts/_lib/orchestration_record.ts +78 -10
  129. package/src/scripts/_lib/orchestration_savings.ts +1 -1
  130. package/src/scripts/_lib/recycle_envelope_paths.ts +35 -0
  131. package/src/scripts/_lib/self_repair.ts +354 -13
  132. package/src/scripts/_lib/self_repair_store.ts +28 -0
  133. package/src/scripts/_lib/session_eol.ts +230 -0
  134. package/src/scripts/_lib/session_role.ts +42 -0
  135. package/src/scripts/_lib/subagent_capsule.ts +341 -0
  136. package/src/scripts/_lib/subagent_response.ts +94 -0
  137. package/src/scripts/_lib/subagent_spawn.ts +13 -0
  138. package/src/scripts/_lib/subagent_steering.ts +9 -4
  139. package/src/scripts/_lib/tier_budget_routing.ts +6 -1
  140. package/src/scripts/_lib/worker_budget.ts +37 -2
  141. package/src/scripts/ai_council/clients.ts +126 -55
  142. package/src/scripts/ai_council/config.ts +94 -6
  143. package/src/scripts/ai_council/handoff.ts +113 -0
  144. package/src/scripts/ai_council/modes.ts +19 -4
  145. package/src/scripts/ai_council/orchestrator.ts +105 -0
  146. package/src/scripts/ai_council/quorum.ts +84 -0
  147. package/src/scripts/ai_council/session.ts +44 -0
  148. package/src/scripts/ai_council/transport_resolver.ts +127 -1
  149. package/src/scripts/ai_team/availability.ts +110 -0
  150. package/src/scripts/ai_team/config.ts +34 -14
  151. package/src/scripts/ai_team/team_dispatch.ts +71 -29
  152. package/src/scripts/ask_transport.ts +221 -0
  153. package/src/scripts/bench_critic_protocol.ts +396 -0
  154. package/src/scripts/bench_hook_injection.ts +234 -0
  155. package/src/scripts/check_adr_frontmatter.ts +84 -40
  156. package/src/scripts/check_branch_freshness.ts +1 -0
  157. package/src/scripts/check_corpus_staleness.ts +18 -4
  158. package/src/scripts/check_detector_corpus.ts +219 -0
  159. package/src/scripts/check_gitignore_freshness.ts +42 -8
  160. package/src/scripts/check_hook_bundle_freshness.ts +24 -1
  161. package/src/scripts/check_kernel_prefix_stability.ts +1 -0
  162. package/src/scripts/check_standing_rule_delivery.ts +73 -32
  163. package/src/scripts/check_static_layer_stability.ts +154 -0
  164. package/src/scripts/check_suppression_hygiene.ts +48 -0
  165. package/src/scripts/condense.ts +162 -2
  166. package/src/scripts/council_cli.ts +401 -25
  167. package/src/scripts/dispatch_economy_report.ts +448 -0
  168. package/src/scripts/dispatch_r2_reviewer.ts +15 -2
  169. package/src/scripts/explain_run.ts +206 -4
  170. package/src/scripts/forensics_report.ts +333 -0
  171. package/src/scripts/generate_settings_reference.ts +1 -0
  172. package/src/scripts/handoff_context_hook.ts +112 -4
  173. package/src/scripts/hook_manifest.yaml +117 -10
  174. package/src/scripts/hooks/concern_registry.ts +8 -0
  175. package/src/scripts/hooks/delegation_nudge_hook.ts +106 -35
  176. package/src/scripts/hooks/dispatch_hook.ts +39 -2
  177. package/src/scripts/hooks/edit_shape_hook.ts +303 -0
  178. package/src/scripts/hooks/orchestration_record_hook.ts +19 -1
  179. package/src/scripts/hooks/reread_guard_hook.ts +314 -0
  180. package/src/scripts/hooks/rtk_wrap_hook.ts +133 -14
  181. package/src/scripts/hooks/session_eol_hook.ts +268 -0
  182. package/src/scripts/hooks/turn_end_gate_hook.ts +507 -0
  183. package/src/scripts/install-hooks.sh +1 -1
  184. package/src/scripts/language_mirror_hook.ts +89 -0
  185. package/src/scripts/lint_decision_impact.ts +14 -1
  186. package/src/scripts/lint_discovery_vocabulary.ts +2 -0
  187. package/src/scripts/lint_hook_manifest.ts +82 -0
  188. package/src/scripts/lint_never_silent.ts +276 -0
  189. package/src/scripts/lint_no_activation_gates.ts +271 -0
  190. package/src/scripts/lint_readme_serial_comma.ts +1 -0
  191. package/src/scripts/lint_rule_enforcement_declaration.ts +347 -0
  192. package/src/scripts/measure_turn_end_gate.ts +194 -0
  193. package/src/scripts/orchestration_record.ts +12 -1
  194. package/src/scripts/rebuild_hook_bundle.ts +178 -0
  195. package/src/scripts/release.ts +74 -2
  196. package/src/scripts/report_conformance_funnel.ts +284 -0
  197. package/src/scripts/report_imperative_density.ts +1 -0
  198. package/src/scripts/routing_doctor.ts +118 -36
  199. package/src/scripts/score_skill_selection.ts +19 -5
  200. package/src/scripts/self_repair_cli.ts +337 -59
  201. package/src/scripts/session_eol_report.ts +261 -0
  202. package/src/scripts/team_events_spike.ts +271 -0
  203. package/src/scripts/team_review_gate_hook.ts +31 -9
  204. package/src/server/schemas/settings.ts +24 -17
  205. package/src/shared/settingsCarveOut.ts +0 -10
  206. package/src/shared/settingsClasses.ts +11 -9
  207. package/src/templates/compact-instructions.md +40 -0
  208. package/dist/ui/assets/index-DmR0mbk4.js +0 -26
  209. package/dist/ui/assets/index-DmR0mbk4.js.map +0 -1
@@ -27,7 +27,7 @@
27
27
  document.documentElement.setAttribute('data-theme', theme);
28
28
  })();
29
29
  </script>
30
- <script type="module" crossorigin src="/assets/index-DmR0mbk4.js"></script>
30
+ <script type="module" crossorigin src="/assets/index-B5qGYOJo.js"></script>
31
31
  <link rel="stylesheet" crossorigin href="/assets/index-jSfMW8_t.css">
32
32
  </head>
33
33
  <body>
@@ -22,14 +22,26 @@ raise a spend ceiling, empty an allowlist, or switch off a gate.
22
22
  | **B — consent** | the agent, but only after ONE just-in-time ask that the user answered | **once**, then persisted with provenance. |
23
23
  | **C — guarded** | the GUI's write route or a hand-edit, by a human | never asked by the agent; **every agent write path refuses it.** |
24
24
 
25
+ ### The provenance sidecar's record shape
26
+
27
+ "Persisted with provenance" means one record per dotted key in
28
+ `settings/.agent-settings.provenance.json`, written by `settings:set` beside
29
+ the value: `{ "source": "auto-detected" | "jit-answer" | "manual" | "gui",
30
+ "at": "<ISO timestamp>" }`. For a class-B key the `at` stamp IS the grant
31
+ time — the consent reader (`consentVerdict`) grants on `jit-answer` / `gui` /
32
+ `manual` provenance, so source + timestamp together are the audit record of
33
+ who consented and when. `expires_at` / `revoked_at` / contract-version fields
34
+ stay deliberately unbuilt until a reader needs them — a field nothing reads is
35
+ surface without a consumer.
36
+
25
37
  ## What the fence covers — and what it does not
26
38
 
27
39
  **It governs writes, not reads.** Every class is readable by everything. A C
28
40
  class does not hide a value; it refuses to let a non-human change it.
29
41
 
30
- **It governs writes, not asks.** Six settings ship an `ask` value in their own
31
- enum (`tokens.rich_skills`, `subagents.auto`, `subagents.budget_routing`,
32
- `subagents.adversarial_council`, `worktrees.mode`, `decision_engine.on_block`).
42
+ **It governs writes, not asks.** Four settings ship an `ask` value in their own
43
+ enum (`tokens.rich_skills`, `subagents.adversarial_council`, `worktrees.mode`,
44
+ `decision_engine.on_block`).
33
45
  Those are C-class here, and that is not a contradiction: the class says who may
34
46
  *persist* a new value; the `ask` value says what happens *at runtime* when the
35
47
  setting is already set to `ask`. A C-class key set to `ask` still produces a
@@ -129,18 +141,51 @@ larger set; it is the set that passes both halves.
129
141
  | `personal.canary_name` | un-inferrable, and answering arms the session-degradation canary; one keypress to accept the prefill | `""` (canary dark) |
130
142
  | `memory.learn_on_session_end` | it turns on automatic memory writes at the end of every session — a standing write the user should authorise | `false` |
131
143
 
144
+ ## The one exception — `emergency.orchestration_halt`
145
+
146
+ The always-on-orchestration doctrine (subagents, council, team) deletes every
147
+ per-layer on/off setting: `subagents.enabled`, `subagents.auto`, and
148
+ `subagents.host_capabilities` are gone from the template, and the layer
149
+ activates on any capable host with an EMPTY settings file. One switch survives
150
+ that deletion on purpose, and it is not the same shape wearing a different
151
+ name.
152
+
153
+ ```
154
+ ON-BY-DEFAULT, NOT OFF-BY-DEFAULT. ABSENT OR false MEANS THE STACK RUNS.
155
+ ARMING THE HALT IS CEREMONY-FREE — INCIDENTS ARE URGENT.
156
+ DISARMING IT REQUIRES A NON-EMPTY orchestration_halt_justification.
157
+ INCIDENT-RESPONSE USE ONLY.
158
+ ```
159
+
160
+ **Enforcement, stated honestly (`enforced_by: none`).** The settings file is
161
+ hand-edited YAML — no machine sits on the write path, so the
162
+ justification-on-disarm rule and any per-transition telemetry are
163
+ MODEL-CARRIED conventions, not checked gates (the same honesty stance as
164
+ `security-sensitive-stop`). What IS checked: readers treat only strict
165
+ `true` as halted, and the `no-activation-gates` lint prevents the switch
166
+ from ever growing activation siblings. An agent editing this key follows
167
+ the convention; a human editing it is the owner exercising it.
168
+
169
+ The distinguishing test against "an activation gate under a new name": an
170
+ activation gate is symmetric — flipping it either way costs the same nothing,
171
+ and its purpose is to let a capability sit unused by default. This switch is
172
+ asymmetric by convention — cheap to arm during an incident, costed (a stated
173
+ reason) to disarm, never a silent preference. `subagents.downshift`, `subagents.quota_arbitrage`, and
174
+ `subagents.model_map` are unaffected by the deletion — they tune HOW a
175
+ dispatch runs, not WHETHER the layer exists, so they keep their own C rows.
176
+
132
177
  ## Counts
133
178
 
134
179
  | Class | Keys |
135
180
  |---|---|
136
181
  | A — preference | 27 |
137
182
  | B — consent | 3 |
138
- | C — guarded | 110 |
139
- | **Total** | **140** |
183
+ | C — guarded | 111 |
184
+ | **Total** | **141** |
140
185
 
141
186
  The total is every leaf in the template, where *leaf* means anything that is not
142
- a **non-empty** map. An empty map (`subagents.host_capabilities: {}`) is a real
143
- configurable value with a real default, so it is a leaf here — one row more than
187
+ a **non-empty** map. An empty map (like the former `subagents.host_capabilities: {}`) is a real
188
+ configurable value with a real default, so such keys count as leaves here — one row more than
144
189
  the template↔schema parity test walks, deliberately, because a key with no
145
190
  class is exactly what this contract exists to prevent.
146
191
 
@@ -229,27 +274,25 @@ Rows follow template order, so a diff against the template reads straight down.
229
274
  | `screenshots.forbid_terminal_capture` | C | `true` | kill-switch over the highest-leak capture path |
230
275
  | `screenshots.data_bearing_gate` | C | `"on"` | the human-confirmation gate over a published egress |
231
276
  | `code_style.docblocks` | A | `minimal` | code convention |
232
- | `subagents.enabled` | C | `true` | kill-switch for the whole subagent layer |
233
- | `subagents.auto` | C | `"on"` | dispatches work without asking |
234
277
  | `subagents.downshift` | C | `true` | routes to another model tier, which is spend and quality |
235
- | `subagents.budget_routing` | C | `ask` | the budget-aware routing gate |
236
278
  | `subagents.quota_arbitrage` | C | `true` | spends from a separate quota pool |
237
279
  | `subagents.model_map.lite` | C | `""` | names an external model endpoint |
238
280
  | `subagents.model_map.medium` | C | `""` | names an external model endpoint |
239
281
  | `subagents.model_map.high` | C | `""` | names an external model endpoint |
240
- | `subagents.host_capabilities` | C | `{}` | overrides the host capability manifest |
241
282
  | `subagents.implementer_model` | C | `""` | names an external model endpoint |
242
283
  | `subagents.judge_model` | C | `""` | names an external model endpoint |
284
+ | `subagents.model_ceiling` | C | `""` | session-wide subagent model cap (exported as `CLAUDE_CODE_SUBAGENT_MODEL` by suite-owned CLI spawn wrappers), which is spend and quality |
243
285
  | `subagents.max_parallel` | C | `3` | parallelism cap, and therefore a spend rate |
244
286
  | `subagents.adversarial_council` | C | `"off"` | governs a paid verification step |
245
287
  | `worktrees.mode` | C | `ask` | standing permission for autonomous worktree creation |
246
- | `ai_team.enabled` | C | `false` | authorises paid external CLI calls |
247
288
  | `ai_team.model` | C | `auto` | names an external model |
248
289
  | `ai_team.allow_delegate` | C | `false` | grants an external tool write access to the repository |
249
290
  | `ai_team.max_calls_per_day` | C | `50` | quota cap on a shared budget |
250
291
  | `ai_team.suppress_setup_hint` | A | `false` | hint cosmetics |
251
292
  | `ai_team.review_gate.managed` | C | `false` | governs an upstream review gate |
252
293
  | `ai_team.review_gate.max_consecutive_blocks` | C | `3` | circuit-breaker threshold |
294
+ | `emergency.orchestration_halt` | C | `false` | the one audited incident switch over the always-on orchestration stack — see § The one exception above |
295
+ | `emergency.orchestration_halt_justification` | C | `""` | required non-empty before the halt may be lifted; an audit-trail field |
253
296
  | `onboarding.onboarded` | C | `false` | flipping it bypasses the onboarding gate |
254
297
  | `commands.auto_detect` | C | `enabled` | kill-switch for orchestrator auto-detection |
255
298
  | `commands.suggestion.enabled` | A | `true` | a convenience layer; governs no gate and no spend |
@@ -282,6 +325,9 @@ Rows follow template order, so a diff against the template reads straight down.
282
325
  | `hooks.rtk_wrap.enabled` | C | `false` | configures code that runs on every tool call |
283
326
  | `hooks.design_slop.enabled` | C | `false` | configures code that runs on every tool call |
284
327
  | `hooks.code_graph.enabled` | C | `false` | configures code that runs on every tool call |
328
+ | `hooks.turn_end_gate.enabled` | C | `false` | arms the only concern that can REFUSE a turn-end; an agent must never be able to switch its own delivery gate on |
329
+ | `hooks.turn_end_gate.promissory` | C | `true` | which refusal the turn-end gate may raise; inert while the master switch is off |
330
+ | `hooks.turn_end_gate.language` | C | `true` | which refusal the turn-end gate may raise; inert while the master switch is off |
285
331
  | `decision_engine.surface_traces` | C | `false` | the decision engine’s own black box; the agent must not be able to close it |
286
332
  | `decision_engine.min_confidence` | C | `"off"` | the confidence gate |
287
333
  | `decision_engine.block_on_risk` | C | `"off"` | the risk-class gate |
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@event4u/agent-config",
3
3
  "mcpName": "io.github.event4u-app/agent-config",
4
- "version": "9.28.0",
4
+ "version": "9.30.0",
5
5
  "description": "Governed skills, rules and commands for AI coding tools (Claude Code, Cursor, Windsurf, Copilot) — every claim machine-checked, including \"zero runtime daemon\".",
6
6
  "license": "MIT",
7
7
  "private": false,
@@ -0,0 +1,312 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * `agent-config gates` — the open decisions that need the user, as actions.
4
+ *
5
+ * The dashboard (`update_roadmap_progress`) already parses every `## Blockers`
6
+ * entry and already counts them per roadmap. What it does not do is separate
7
+ * the blockers the *user* must resolve from the ones the maintainer or an
8
+ * external party owns: the count links into a per-roadmap breakdown, so
9
+ * answering "what do I have to do" means opening every anchor and filtering
10
+ * `Owner:` by hand across the whole file. The fields exist, are CI-enforced by
11
+ * `lint_roadmap_blockers`, and never reach the user in a form they can act on.
12
+ *
13
+ * This command is that missing projection, and nothing more: same parser
14
+ * (`parse_blockers` / `collect`, imported — never re-implemented), filtered by
15
+ * owner, ordered by how much each blocker unblocks, rendered action-first.
16
+ *
17
+ * Deliberately NOT a gate: it exits 0 whether or not decisions are pending.
18
+ * Blocking a pipeline on "the user has an open decision" would turn a
19
+ * visibility aid into a second thing to fight.
20
+ *
21
+ * Invocation (from project root):
22
+ * ./agent-config gates # decisions owned by the user
23
+ * ./agent-config gates --all # every open blocker, grouped by owner
24
+ * ./agent-config gates --json # machine-readable
25
+ */
26
+
27
+ import * as path from 'node:path';
28
+ import { fileURLToPath, pathToFileURL } from 'node:url';
29
+ import * as fs from 'node:fs';
30
+ import { spawnSync } from 'node:child_process';
31
+
32
+ import {
33
+ collect,
34
+ blocker_needs_user as needsUser,
35
+ type Blocker,
36
+ } from './update_roadmap_progress.js';
37
+
38
+ const _HERE = fileURLToPath(import.meta.url);
39
+
40
+ /** Hard wrap for the rendered body — keeps output readable in a narrow pane. */
41
+ const WIDTH = 78;
42
+
43
+ interface Entry {
44
+ blocker: Blocker;
45
+ roadmapRel: string;
46
+ /** Open steps in the roadmap this blocker sits in — the "unblocks" weight. */
47
+ openSteps: number;
48
+ }
49
+
50
+ /**
51
+ * Rejoin wrapped continuation lines into one step each.
52
+ *
53
+ * `parse_blockers` returns `todo` as raw stripped lines, so a step that wraps
54
+ * over three source lines arrives as three entries. A new step starts at an
55
+ * ordered marker (`1.`, `2)`) or a bullet; everything else continues the one
56
+ * before it.
57
+ */
58
+ function regroupTodo(todo: readonly string[]): string[] {
59
+ const steps: string[] = [];
60
+ for (const line of todo) {
61
+ const startsStep = /^(\d+[.)]|[-*+])\s/.test(line);
62
+ if (startsStep || steps.length === 0) {
63
+ steps.push(line);
64
+ } else {
65
+ steps[steps.length - 1] = `${steps[steps.length - 1] as string} ${line}`;
66
+ }
67
+ }
68
+ return steps;
69
+ }
70
+
71
+ /** Wrap `text` to `width`, indenting every line after the first by `indent`. */
72
+ function wrap(text: string, width: number, indent: string): string[] {
73
+ const words = text.split(/\s+/).filter(Boolean);
74
+ if (words.length === 0) {
75
+ return [];
76
+ }
77
+ const lines: string[] = [];
78
+ let cur = '';
79
+ for (const w of words) {
80
+ const candidate = cur === '' ? w : `${cur} ${w}`;
81
+ if (candidate.length > width && cur !== '') {
82
+ lines.push(cur);
83
+ cur = w;
84
+ } else {
85
+ cur = candidate;
86
+ }
87
+ }
88
+ if (cur !== '') {
89
+ lines.push(cur);
90
+ }
91
+ return lines.map((l, i) => (i === 0 ? l : indent + l));
92
+ }
93
+
94
+ /**
95
+ * Column width for the label gutter — sized to the longest label in use
96
+ * (`Blocked until:`, 14 chars) plus one separating space, so no value ever
97
+ * runs flush against its own label.
98
+ */
99
+ const LABEL_W = 15;
100
+
101
+ /** A labelled block: ` Label: value`, wrapped and hanging-indented. */
102
+ function field(label: string, value: string): string[] {
103
+ const pad = ' ' + label.padEnd(LABEL_W);
104
+ const indent = ' '.repeat(pad.length);
105
+ const lines = wrap(value, WIDTH - pad.length, indent);
106
+ if (lines.length === 0) {
107
+ return [];
108
+ }
109
+ return [pad + lines[0], ...lines.slice(1)];
110
+ }
111
+
112
+ function collectEntries(roadmapRoot: string): Entry[] {
113
+ const entries: Entry[] = [];
114
+ for (const r of collect(roadmapRoot)) {
115
+ for (const b of r.open_blockers) {
116
+ entries.push({ blocker: b, roadmapRel: r.rel, openSteps: r.open_ });
117
+ }
118
+ }
119
+ // Most-unblocking first; ties broken deterministically so two runs on an
120
+ // unchanged tree print byte-identical output.
121
+ entries.sort(
122
+ (a, b) =>
123
+ b.openSteps - a.openSteps ||
124
+ a.roadmapRel.localeCompare(b.roadmapRel) ||
125
+ a.blocker.id.localeCompare(b.blocker.id),
126
+ );
127
+ return entries;
128
+ }
129
+
130
+ /**
131
+ * The parser's legacy fallback synthesises one blocker per `> Blocked until …`
132
+ * note under the fixed id `legacy`. That id is an implementation detail and
133
+ * says nothing to a reader, and its text is a *condition* ("blocked until X"),
134
+ * not an imperative — so both the heading and the field label are renamed for
135
+ * this one parser-defined case rather than dressed up as a step list.
136
+ */
137
+ function isLegacy(b: Blocker): boolean {
138
+ return b.id === 'legacy';
139
+ }
140
+
141
+ function renderEntry(e: Entry, index: number): string[] {
142
+ const head = `${index} · ${isLegacy(e.blocker) ? 'blocked-until note' : e.blocker.id}`;
143
+ const tail =
144
+ e.openSteps > 0 ? `unblocks: ${e.openSteps} step${e.openSteps !== 1 ? 's' : ''}` : '';
145
+ const dashes = Math.max(3, WIDTH - head.length - tail.length - 6);
146
+ const out: string[] = [
147
+ '',
148
+ `── ${head} ${'─'.repeat(dashes)} ${tail}`.trimEnd(),
149
+ ...field('Roadmap:', e.roadmapRel),
150
+ ...field('Blocks:', e.blocker.blocks),
151
+ ];
152
+
153
+ const doLabel = isLegacy(e.blocker) ? 'Blocked until:' : 'Do this:';
154
+ const steps = regroupTodo(e.blocker.todo);
155
+ if (steps.length === 0) {
156
+ out.push(
157
+ ...field(
158
+ doLabel,
159
+ '(no steps recorded — the blocker entry in the roadmap needs a ' +
160
+ '**What to do:** list)',
161
+ ),
162
+ );
163
+ } else {
164
+ out.push(...field(doLabel, steps[0] as string));
165
+ for (const s of steps.slice(1)) {
166
+ // Steps 2..n have no label of their own — align them under the
167
+ // first step's text, i.e. the same gutter `field()` produces.
168
+ const indent = ' '.repeat(LABEL_W + 2);
169
+ const lines = wrap(s, WIDTH - LABEL_W - 2, indent);
170
+ out.push(...lines.map((l, i) => (i === 0 ? indent + l : l)));
171
+ }
172
+ }
173
+ out.push(...field('Done when:', e.blocker.resolvedWhen));
174
+ return out;
175
+ }
176
+
177
+ function render(entries: readonly Entry[], all: boolean): string {
178
+ const mine = entries.filter((e) => needsUser(e.blocker.owner));
179
+ const others = entries.filter((e) => !needsUser(e.blocker.owner));
180
+ const shown = all ? entries : mine;
181
+ const lines: string[] = [];
182
+
183
+ if (mine.length === 0) {
184
+ lines.push(
185
+ others.length === 0
186
+ ? 'No open blockers at all.'
187
+ : `Nothing is waiting on you. ${others.length} open blocker` +
188
+ `${others.length !== 1 ? 's' : ''} sit with maintainer/external` +
189
+ `${all ? '' : ' — see --all'}.`,
190
+ );
191
+ } else {
192
+ const head =
193
+ `${mine.length} decision${mine.length !== 1 ? 's' : ''} need${mine.length === 1 ? 's' : ''} you`;
194
+ const rest =
195
+ others.length > 0
196
+ ? ` · ${others.length} more with maintainer/external${all ? '' : ' (--all)'}`
197
+ : '';
198
+ lines.push(head + rest);
199
+ }
200
+
201
+ let n = 0;
202
+ for (const e of shown) {
203
+ n += 1;
204
+ lines.push(...renderEntry(e, n));
205
+ }
206
+ if (shown.length > 0) {
207
+ lines.push('');
208
+ }
209
+ return lines.join('\n') + '\n';
210
+ }
211
+
212
+ function renderJson(entries: readonly Entry[], all: boolean): string {
213
+ const pick = all ? entries : entries.filter((e) => needsUser(e.blocker.owner));
214
+ return (
215
+ JSON.stringify(
216
+ {
217
+ needsYou: entries.filter((e) => needsUser(e.blocker.owner)).length,
218
+ other: entries.filter((e) => !needsUser(e.blocker.owner)).length,
219
+ blockers: pick.map((e) => ({
220
+ id: e.blocker.id,
221
+ roadmap: e.roadmapRel,
222
+ owner: e.blocker.owner,
223
+ needsYou: needsUser(e.blocker.owner),
224
+ blocks: e.blocker.blocks,
225
+ unblocksSteps: e.openSteps,
226
+ todo: regroupTodo(e.blocker.todo),
227
+ resolvedWhen: e.blocker.resolvedWhen,
228
+ })),
229
+ },
230
+ null,
231
+ 2,
232
+ ) + '\n'
233
+ );
234
+ }
235
+
236
+ function _isDir(p: string): boolean {
237
+ try {
238
+ return fs.statSync(p).isDirectory();
239
+ } catch {
240
+ return false;
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Same repo-root resolution as the dashboard: honour the cwd when it holds
246
+ * `agents/roadmaps`, otherwise fall back to the git toplevel (monorepo
247
+ * sub-project support).
248
+ */
249
+ function _resolveRepoRoot(start: string): string {
250
+ if (_isDir(path.join(start, 'agents', 'roadmaps'))) {
251
+ return start;
252
+ }
253
+ try {
254
+ const r = spawnSync('git', ['rev-parse', '--show-toplevel'], {
255
+ cwd: start,
256
+ encoding: 'utf-8',
257
+ timeout: 10_000,
258
+ });
259
+ const top = (r.stdout || '').trim();
260
+ if (r.status === 0 && top !== '' && _isDir(path.join(top, 'agents', 'roadmaps'))) {
261
+ return top;
262
+ }
263
+ } catch {
264
+ /* not a git repo / git missing — keep the cwd default */
265
+ }
266
+ return start;
267
+ }
268
+
269
+ function main(argv?: readonly string[]): number {
270
+ const args = argv ?? process.argv.slice(2);
271
+ const all = args.includes('--all');
272
+ const json = args.includes('--json');
273
+
274
+ const repoRoot = _resolveRepoRoot(process.cwd());
275
+ const roadmapRoot = path.join(repoRoot, 'agents', 'roadmaps');
276
+ if (!_isDir(roadmapRoot)) {
277
+ process.stdout.write(
278
+ json ? '{"needsYou":0,"other":0,"blockers":[]}\n' : 'No roadmaps directory — nothing to report.\n',
279
+ );
280
+ return 0;
281
+ }
282
+
283
+ const entries = collectEntries(roadmapRoot);
284
+ process.stdout.write(json ? renderJson(entries, all) : render(entries, all));
285
+ return 0;
286
+ }
287
+
288
+ function _isCliEntry(): boolean {
289
+ if (process.argv[1] === undefined) {
290
+ return false;
291
+ }
292
+ const argvPath = path.resolve(process.argv[1]);
293
+ if (import.meta.url === pathToFileURL(argvPath).href) {
294
+ return true;
295
+ }
296
+ // A symlinked invocation (`.augment/scripts` → `dist/agent-src/scripts`, or
297
+ // macOS /var → /private/var) makes the raw URLs differ: import.meta.url is
298
+ // the resolved real path while argv[1] keeps the symlink. Compare realpaths
299
+ // so the entry guard still fires through the projection.
300
+ try {
301
+ return fs.realpathSync(_HERE) === fs.realpathSync(argvPath);
302
+ } catch {
303
+ return false;
304
+ }
305
+ }
306
+
307
+ if (_isCliEntry()) {
308
+ process.exitCode = main();
309
+ }
310
+
311
+ export { main, needsUser, regroupTodo, wrap, collectEntries, render, renderJson };
312
+ export type { Entry };
@@ -398,14 +398,32 @@ function _blockerField(slice: string, label: string): string | null {
398
398
  return parts.length ? parts.join(' ') : null;
399
399
  }
400
400
 
401
- /** Lines under `- **What to do:**` up to the next `- **Field:**` marker. */
401
+ /**
402
+ * The `- **What to do:**` text: the remainder of the marker line, plus every
403
+ * line under it up to the next `- **Field:**` marker.
404
+ *
405
+ * The marker line's own remainder is load-bearing and used to be dropped.
406
+ * Authors write both shapes — a numbered list under the marker, and prose that
407
+ * starts inline (`- **What to do:** the build work is done; only real …`) —
408
+ * and every *other* blocker field reads the inline part via `_blockerField`.
409
+ * Skipping it here truncated instructions mid-sentence in both the dashboard
410
+ * and `agent-config gates`, which is precisely the content those surfaces
411
+ * exist to deliver.
412
+ */
402
413
  function _blockerTodo(slice: string): string[] {
403
414
  const lines = _splitlines(slice);
404
- const startIdx = lines.findIndex((l) => /^-[ \t]*\*\*What to do:\*\*/i.test(_strip(l)));
415
+ const markerRe = /^-[ \t]*\*\*What to do:\*\*[ \t]*(.*)$/i;
416
+ const startIdx = lines.findIndex((l) => markerRe.test(_strip(l)));
405
417
  if (startIdx === -1) {
406
418
  return [];
407
419
  }
408
420
  const out: string[] = [];
421
+ const inline = _stripComment(
422
+ (markerRe.exec(_strip(lines[startIdx] as string))?.[1] as string) ?? '',
423
+ );
424
+ if (inline) {
425
+ out.push(inline);
426
+ }
409
427
  for (let i = startIdx + 1; i < lines.length; i++) {
410
428
  const trimmed = _strip(lines[i] as string);
411
429
  if (BLOCKER_FIELD_RE.test(trimmed) || trimmed.startsWith('#')) {
@@ -458,18 +476,56 @@ function parse_blockers(raw_text: string): Blocker[] {
458
476
  }
459
477
  const legacyMatch = LEGACY_BLOCKED_UNTIL_RE.exec(text);
460
478
  if (legacyMatch) {
479
+ // The legacy note is a blockquote and routinely wraps over several
480
+ // `> ` lines. The regex captures one line, so the continuation used to
481
+ // be dropped and the instruction ended mid-sentence — the same defect
482
+ // `_blockerTodo` carried above. Walk the following quoted lines until
483
+ // the quote block ends.
484
+ const parts = [_strip(legacyMatch[1] as string)];
485
+ const allLines = _splitlines(text);
486
+ const noteIdx = allLines.findIndex((l) => LEGACY_BLOCKED_UNTIL_RE.test(_strip(l)));
487
+ for (let i = noteIdx + 1; noteIdx !== -1 && i < allLines.length; i++) {
488
+ const quoted = /^>[ \t]?(.*)$/.exec(_strip(allLines[i] as string));
489
+ if (!quoted) {
490
+ break;
491
+ }
492
+ const cont = _strip(quoted[1] as string);
493
+ if (cont === '') {
494
+ break;
495
+ }
496
+ parts.push(cont);
497
+ }
461
498
  blockers.push({
462
499
  id: 'legacy',
463
500
  status: 'open',
464
501
  owner: 'user',
465
502
  blocks: 'entire roadmap',
466
- todo: [_strip(legacyMatch[1] as string)],
503
+ todo: [parts.join(' ')],
467
504
  resolvedWhen: 'condition described above clears',
468
505
  });
469
506
  }
470
507
  return blockers;
471
508
  }
472
509
 
510
+ /**
511
+ * Does this blocker need the human at the keyboard, rather than the maintainer
512
+ * or an external party?
513
+ *
514
+ * Owner values in the wild are not the clean `user | maintainer | external`
515
+ * enum the template comment suggests — real entries carry qualifiers
516
+ * (`user (billable spend)`, `user / maintainer`, `maintainer (security role)`).
517
+ * Word-boundary matching on `user` keeps the qualified forms and rejects
518
+ * `maintainer`. A shared `user / maintainer` blocker deliberately counts as the
519
+ * user's: showing one decision too many costs a line, hiding one costs the wait.
520
+ *
521
+ * Lives here rather than in `roadmap_gates` because it is owner *semantics*,
522
+ * not presentation — the dashboard header and `agent-config gates` must agree
523
+ * on the split, and two copies of this regex would drift.
524
+ */
525
+ function blocker_needs_user(owner: string): boolean {
526
+ return /\buser\b/i.test(owner);
527
+ }
528
+
473
529
  function parse_roadmap(p: string, roadmap_root: string): RoadmapStats | null {
474
530
  const text = fs.readFileSync(p, { encoding: 'utf-8' });
475
531
  const phase_matches: Array<{ start: number; end: number; g2: string; g3: string | undefined }> =
@@ -674,6 +730,14 @@ function render(roadmaps: RoadmapStats[], bundles: Bundle[] | null = null): stri
674
730
  const pending = pending_iron_law_3(roadmaps);
675
731
  const gated = merge_gated_pending(roadmaps);
676
732
  const total_open_blockers = roadmaps.reduce((s, r) => s + r.open_blockers.length, 0);
733
+ // The count alone answers "is something blocked", never "is it on me" —
734
+ // and the per-roadmap anchors make the owner split a manual read across the
735
+ // whole file. Name the user's share here and point at the command that
736
+ // renders those entries as actions.
737
+ const user_open_blockers = roadmaps.reduce(
738
+ (s, r) => s + r.open_blockers.filter((b) => blocker_needs_user(b.owner)).length,
739
+ 0,
740
+ );
677
741
  const lines: string[] = [];
678
742
  lines.push('# Roadmap Progress\n');
679
743
  const header_meta =
@@ -682,7 +746,10 @@ function render(roadmaps: RoadmapStats[], bundles: Bundle[] | null = null): stri
682
746
  ' · [roadmaps/](roadmaps/) · [archive/](roadmaps/archive/) · ' +
683
747
  '[skipped/](roadmaps/skipped/) · [later/](roadmaps/later/)' +
684
748
  (total_open_blockers > 0
685
- ? ` · **${total_open_blockers}** open blocker${total_open_blockers !== 1 ? 's' : ''}`
749
+ ? ` · **${total_open_blockers}** open blocker${total_open_blockers !== 1 ? 's' : ''}` +
750
+ (user_open_blockers > 0
751
+ ? `, **${user_open_blockers}** need you → \`agent-config gates\``
752
+ : '')
686
753
  : '') +
687
754
  '\n';
688
755
  lines.push(
@@ -1074,6 +1141,7 @@ export {
1074
1141
  is_roadmap_candidate,
1075
1142
  count_checkboxes,
1076
1143
  parse_blockers,
1144
+ blocker_needs_user,
1077
1145
  parse_roadmap,
1078
1146
  bar,
1079
1147
  collect,
@@ -1,6 +1,6 @@
1
1
  # {{project_name}}
2
2
 
3
- <!-- Fill placeholders or run `/agents init`; delete after. Capability bullets, not paths. Recipes: `.augment/contexts/contracts/agents-md-anatomy.md`. -->
3
+ <!-- Fill placeholders or run `/agents init`; delete after. Recipes: `.augment/contexts/contracts/agents-md-anatomy.md`. -->
4
4
 
5
5
  > {{project_description}}
6
6
 
@@ -8,8 +8,8 @@
8
8
 
9
9
  | Layer | Location | Edits |
10
10
  |---|---|---|
11
- | Installed package | `.augment/`, `dist/agent-src/` | hands-off — managed by `event4u/agent-config` |
12
- | Project layer | `agents/`, `agents/overrides/`, `.agent-settings.yml` | your customizations and config |
11
+ | Installed package | `.augment/`, `dist/agent-src/` | managed by `event4u/agent-config` |
12
+ | Project layer | `agents/`, `agents/overrides/`, `.agent-settings.yml` | your customizations |
13
13
 
14
14
  ## Pointers
15
15
 
@@ -18,7 +18,7 @@
18
18
  - **Skills (on-demand expertise)** — domain skills surfaced by description, invoked on trigger match: [`.augment/skills/`](.augment/skills/).
19
19
  - **Commands (workflows)** — slash-commands the agent runs end-to-end (`/work`, `/create-pr`, …): [`.augment/commands/`](.augment/commands/).
20
20
  - **Project-specific docs** — your own architecture notes, roadmaps, sessions, contexts: [`agents/`](agents/).
21
- - **Delegation & review** — dispatch independent-slice tasks to subagents instead of serial work; a mutating session ends after a neutral cross-model review: [`delegation-policy`](.augment/rules/delegation-policy.md).
21
+ - **Delegation & review** — dispatch independent-slice tasks to subagents instead of serial work; in a team (host-flag gated) the lead coordinates, teammates implement; a mutating session ends after a neutral cross-model review: [`delegation-policy`](.augment/rules/delegation-policy.md).
22
22
 
23
23
  ## Emergency triage — read this when nothing else is reachable
24
24