@event4u/agent-config 14.20.0 → 14.22.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 (242) hide show
  1. package/.augment-plugin/marketplace.json +3 -3
  2. package/.augment-plugin/plugin.json +1 -1
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CHANGELOG.md +441 -0
  5. package/CREDITS.md +37 -0
  6. package/README.md +1 -1
  7. package/agents/templates/.ai-council.yml.example +10 -0
  8. package/dist/agent-src/commands/analyze/inbox.md +381 -6
  9. package/dist/agent-src/commands/analyze/{reference-repo.md → repo.md} +251 -34
  10. package/dist/agent-src/commands/analyze/roadmap-repos.md +129 -0
  11. package/dist/agent-src/commands/analyze.md +12 -7
  12. package/dist/agent-src/commands/feature/plan.md +55 -16
  13. package/dist/agent-src/commands/feature/refactor.md +8 -3
  14. package/dist/agent-src/commands/humanize.md +48 -4
  15. package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +69 -0
  16. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +5 -5
  17. package/dist/agent-src/contexts/execution/contract-decision-sheet.md +22 -1
  18. package/dist/agent-src/contexts/execution/host-capability-manifest.md +4 -1
  19. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +35 -2
  20. package/dist/agent-src/contexts/execution/terminal-states.md +4 -1
  21. package/dist/agent-src/rules/evaluator-independence.md +4 -4
  22. package/dist/agent-src/rules/external-code-graph-interop.md +19 -14
  23. package/dist/agent-src/rules/external-reference-deep-dive.md +2 -2
  24. package/dist/agent-src/rules/token-efficiency.md +26 -3
  25. package/dist/agent-src/scripts/_cli_entry.ts +51 -0
  26. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +14 -23
  27. package/dist/agent-src/scripts/roadmap_gates.ts +4 -20
  28. package/dist/agent-src/scripts/roadmap_set_step.ts +5 -2
  29. package/dist/agent-src/scripts/stubs_due.ts +4 -18
  30. package/dist/agent-src/scripts/update_roadmap_progress.ts +7 -24
  31. package/dist/agent-src/skills/code-intelligence/SKILL.md +25 -0
  32. package/dist/agent-src/skills/competitive-positioning/SKILL.md +2 -2
  33. package/dist/agent-src/skills/humanizer/references/anti-aiisms.md +27 -11
  34. package/dist/agent-src/skills/learning-to-rule-or-skill/SKILL.md +1 -1
  35. package/dist/agent-src/skills/markitdown/SKILL.md +1 -1
  36. package/dist/agent-src/skills/repomix-packer/SKILL.md +1 -1
  37. package/dist/agent-src/skills/roadmap-writing/SKILL.md +17 -0
  38. package/dist/agent-src/skills/technical-specification/SKILL.md +4 -0
  39. package/dist/agent-src/templates/agent-settings.md +0 -1
  40. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  41. package/dist/agent-src/templates/features.md +5 -0
  42. package/dist/agent-src/templates/roadmaps.md +15 -0
  43. package/dist/cli-delegate/_cli_entry.js +8 -0
  44. package/dist/cli-delegate/_lib/pin_resolver.js +170 -0
  45. package/dist/cli-delegate/archival_sweep.js +8 -0
  46. package/dist/cli-delegate/archive_completed_roadmaps.js +494 -0
  47. package/dist/cli-delegate/check_update_banner.js +923 -0
  48. package/dist/cli-delegate/{chunk-KPBWFOPZ.js → chunk-2FTMENJL.js} +194 -37
  49. package/dist/cli-delegate/{chunk-PI2GU3DP.js → chunk-2TAQ2K4V.js} +4 -1
  50. package/dist/cli-delegate/chunk-5EK5WQN6.js +28 -0
  51. package/dist/cli-delegate/chunk-7XGD2OEU.js +96 -0
  52. package/dist/cli-delegate/{chunk-C6IPZRDA.js → chunk-BIXEDQPZ.js} +7 -1
  53. package/dist/cli-delegate/chunk-DBH4AHDK.js +155 -0
  54. package/dist/cli-delegate/{chunk-NUMQHLS5.js → chunk-J3RFNKG4.js} +21 -2
  55. package/dist/cli-delegate/chunk-JO25G5H7.js +20 -0
  56. package/dist/cli-delegate/chunk-LTYVIY76.js +7961 -0
  57. package/dist/cli-delegate/chunk-QAJS5YWY.js +999 -0
  58. package/dist/cli-delegate/{chunk-ZVBOHSVF.js → chunk-TGIPZBCW.js} +1 -1
  59. package/dist/cli-delegate/{chunk-YS6XTLEK.js → chunk-TYSY33MD.js} +56 -6
  60. package/dist/cli-delegate/chunk-ULET4BYS.js +328 -0
  61. package/dist/cli-delegate/chunk-YDQSHTD7.js +187 -0
  62. package/dist/cli-delegate/chunk-YWVXLR44.js +48 -0
  63. package/dist/cli-delegate/chunk-ZZQBUHFK.js +273 -0
  64. package/dist/cli-delegate/cmd_conformance.js +19 -5
  65. package/dist/cli-delegate/cmd_doctor.js +3 -3
  66. package/dist/cli-delegate/cmd_export.js +2 -2
  67. package/dist/cli-delegate/cmd_handoff.js +27 -3
  68. package/dist/cli-delegate/cmd_prune.js +2 -2
  69. package/dist/cli-delegate/cmd_session_recycle.js +2 -2
  70. package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
  71. package/dist/cli-delegate/cmd_sync.js +2 -2
  72. package/dist/cli-delegate/cmd_uninstall.js +2 -2
  73. package/dist/cli-delegate/cmd_validate.js +2 -2
  74. package/dist/cli-delegate/dashboard_mode.js +16 -0
  75. package/dist/cli-delegate/gate_budget.js +24 -0
  76. package/dist/cli-delegate/gate_execute.js +25 -0
  77. package/dist/cli-delegate/guarded_baseline.js +18 -0
  78. package/dist/cli-delegate/resume_probe.js +22 -0
  79. package/dist/cli-delegate/roadmap_gates.js +638 -0
  80. package/dist/cli-delegate/roadmap_set_step.js +173 -0
  81. package/dist/cli-delegate/stub_queue.js +47 -0
  82. package/dist/cli-delegate/stubs_due.js +38 -0
  83. package/dist/cli-delegate/ticket_bundles.js +59 -0
  84. package/dist/cli-delegate/update_roadmap_progress.js +60 -0
  85. package/dist/discovery/deprecation-report.md +1 -1
  86. package/dist/discovery/discovery-manifest.json +60 -29
  87. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  88. package/dist/discovery/discovery-manifest.summary.md +3 -3
  89. package/dist/discovery/dormancy-report.md +1 -1
  90. package/dist/discovery/orphan-report.md +1 -1
  91. package/dist/discovery/packs.json +7 -6
  92. package/dist/discovery/trust-report.md +2 -2
  93. package/dist/discovery/workspaces.json +6 -5
  94. package/dist/hooks/dispatch.js +1522 -1008
  95. package/dist/install/claudeLayerCarriage.js +237 -0
  96. package/dist/install/claudePathsPlan.js +79 -0
  97. package/dist/install/hostLayerFingerprint.js +1 -1
  98. package/dist/install/install.mjs +10 -1
  99. package/dist/install/partitionEligibility.js +231 -117
  100. package/dist/install/ruleLayerPartition.js +10 -7
  101. package/dist/mcp/registry-manifest.json +3 -3
  102. package/dist/mcp/server.json +2 -2
  103. package/dist/mcp/server.mjs +39376 -38902
  104. package/dist/scripts/ai_council/content_ceiling.js +63 -0
  105. package/dist/scripts/ai_council/seat_policy.js +106 -0
  106. package/dist/scripts/ai_council/transport_resolver.js +21 -0
  107. package/dist/scripts/condense.js +32 -25
  108. package/docs/MIGRATION.md +49 -1
  109. package/docs/contracts/settings-classes.md +1 -1
  110. package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +73 -0
  111. package/llms.txt +14 -14
  112. package/package.json +7 -4
  113. package/src/agent-src/scripts/_cli_entry.ts +51 -0
  114. package/src/agent-src/scripts/archive_completed_roadmaps.ts +14 -23
  115. package/src/agent-src/scripts/roadmap_gates.ts +4 -20
  116. package/src/agent-src/scripts/roadmap_set_step.ts +5 -2
  117. package/src/agent-src/scripts/stubs_due.ts +4 -18
  118. package/src/agent-src/scripts/update_roadmap_progress.ts +7 -24
  119. package/src/config/continuity-surface.json +509 -0
  120. package/src/config/estate-count-budget.json +1 -1
  121. package/src/config/gate-coverage.yml +29 -8
  122. package/src/config/gate-violation-baselines.json +8 -8
  123. package/src/config/gitignore-block.txt +19 -0
  124. package/src/config/host-injection-effect.json +73 -0
  125. package/src/config/packed-binary-manifest.json +33 -0
  126. package/src/config/preamble-payload-budget.json +112 -105
  127. package/src/config/publish-surface.json +4 -2
  128. package/src/config/release-gate-locality.yml +4 -3
  129. package/src/install/claudeLayerCarriage.ts +273 -0
  130. package/src/install/claudePathsPlan.ts +76 -0
  131. package/src/install/hostLayerFingerprint.ts +1 -1
  132. package/src/install/partitionEligibility.ts +261 -127
  133. package/src/install/ruleLayerPartition.ts +19 -23
  134. package/src/scripts/_dispatch.bash +77 -27
  135. package/src/scripts/_lib/auto_dispatch.ts +2 -2
  136. package/src/scripts/_lib/blocked_by_marker.ts +189 -0
  137. package/src/scripts/_lib/catalogue_layer_parity.ts +146 -0
  138. package/src/scripts/_lib/continuity_surface.ts +296 -0
  139. package/src/scripts/_lib/corpus_manifest.ts +26 -9
  140. package/src/scripts/_lib/hook_settings.ts +1 -1
  141. package/src/scripts/_lib/host_capability.ts +38 -1
  142. package/src/scripts/_lib/injection_effect.ts +151 -0
  143. package/src/scripts/_lib/layer_overlap_notice.ts +50 -6
  144. package/src/scripts/_lib/packed_binary_predicate.ts +225 -0
  145. package/src/scripts/_lib/pin_resolver.ts +13 -0
  146. package/src/scripts/_lib/regression_neighbourhood.ts +1 -1
  147. package/src/scripts/_lib/release_highlights.ts +19 -211
  148. package/src/scripts/_lib/release_material.ts +27 -77
  149. package/src/scripts/_lib/retired_status.ts +74 -0
  150. package/src/scripts/_lib/skill_catalogue.ts +156 -21
  151. package/src/scripts/_lib/structured_ask.ts +117 -0
  152. package/src/scripts/_lib/subagent_capsule.ts +117 -8
  153. package/src/scripts/_lib/tty_prompt.ts +76 -0
  154. package/src/scripts/ai_council/api_transport.ts +168 -0
  155. package/src/scripts/ai_council/clients.ts +58 -63
  156. package/src/scripts/ai_council/config.ts +13 -0
  157. package/src/scripts/ai_council/content_ceiling.ts +79 -0
  158. package/src/scripts/ai_council/deliberation_metrics.ts +353 -0
  159. package/src/scripts/ai_council/jury_aggregate.ts +178 -0
  160. package/src/scripts/ai_council/prompts.ts +9 -0
  161. package/src/scripts/ai_council/seat_policy.ts +137 -0
  162. package/src/scripts/ai_council/status_surface.ts +79 -0
  163. package/src/scripts/ai_council/synthesis_disagreement.ts +206 -0
  164. package/src/scripts/ai_council/transport_resolver.ts +59 -1
  165. package/src/scripts/ai_tells_rules.ts +446 -5
  166. package/src/scripts/ask_block_census.ts +499 -0
  167. package/src/scripts/bench_humanizer_eval.ts +264 -40
  168. package/src/scripts/bench_prose_tells_fp.ts +198 -0
  169. package/src/scripts/capture_skill_catalogue.ts +24 -11
  170. package/src/scripts/check_branch_work_committed.ts +234 -0
  171. package/src/scripts/check_candidate_lines.ts +499 -0
  172. package/src/scripts/check_continuity_surface.ts +475 -0
  173. package/src/scripts/check_dependency_floors.ts +13 -6
  174. package/src/scripts/check_estate_count.ts +40 -79
  175. package/src/scripts/check_pack_size.ts +263 -24
  176. package/src/scripts/check_release_highlights.ts +0 -157
  177. package/src/scripts/check_roadmap_trackable.ts +19 -16
  178. package/src/scripts/check_rule_layer_partition.ts +54 -34
  179. package/src/scripts/check_single_delivery.ts +97 -31
  180. package/src/scripts/check_update_banner.ts +13 -0
  181. package/src/scripts/code_graph/build.ts +138 -15
  182. package/src/scripts/code_graph/cli.ts +76 -8
  183. package/src/scripts/code_graph/extract.ts +11 -0
  184. package/src/scripts/code_graph/grammar_probe.ts +43 -0
  185. package/src/scripts/code_graph/loader.ts +88 -14
  186. package/src/scripts/code_graph/query.ts +121 -11
  187. package/src/scripts/code_graph/resolution_tiers.ts +238 -0
  188. package/src/scripts/code_graph/sqlite_store.ts +247 -6
  189. package/src/scripts/code_graph/types.ts +66 -1
  190. package/src/scripts/code_graph/validate.ts +23 -0
  191. package/src/scripts/condense.ts +34 -22
  192. package/src/scripts/corpus_manifest.ts +5 -5
  193. package/src/scripts/council_cli.ts +29 -39
  194. package/src/scripts/count_intent_disagreement.ts +426 -0
  195. package/src/scripts/detect_ai_tells.ts +142 -20
  196. package/src/scripts/handoff_context_hook.ts +79 -0
  197. package/src/scripts/harvest_reference_tokens.ts +698 -0
  198. package/src/scripts/hook_effect_doctor.ts +82 -1
  199. package/src/scripts/hook_manifest.json +1 -1
  200. package/src/scripts/hook_manifest.yaml +94 -12
  201. package/src/scripts/hooks/chain_nudge_hook.ts +282 -0
  202. package/src/scripts/hooks/code_graph_context_hook.ts +212 -0
  203. package/src/scripts/hooks/concern_registry.ts +6 -2
  204. package/src/scripts/hooks/delegation_nudge_hook.ts +45 -1
  205. package/src/scripts/hooks/host_semantics.ts +1 -1
  206. package/src/scripts/hooks/one_question_per_ask_hook.ts +191 -0
  207. package/src/scripts/hooks/orchestration_record_hook.ts +2 -2
  208. package/src/scripts/hooks/rule_inject_hook.ts +1 -1
  209. package/src/scripts/hooks/skill_route_hook.ts +11 -4
  210. package/src/scripts/inbox_source_census.ts +322 -0
  211. package/src/scripts/install-hooks.sh +134 -0
  212. package/src/scripts/{lint_carrier_integrity.ts → lint_deferral_integrity.ts} +60 -214
  213. package/src/scripts/lint_plan_risk_register.ts +13 -17
  214. package/src/scripts/lint_roadmap_blockers.ts +51 -1
  215. package/src/scripts/mcp_server/suggest_skill.ts +72 -0
  216. package/src/scripts/mcp_server/tools.ts +2 -56
  217. package/src/scripts/media/lib/adapter-contract.md +7 -2
  218. package/src/scripts/media/lib/fixtures/flux/asset-0001.png +0 -0
  219. package/src/scripts/media/lib/fixtures/gemini-image/asset-0001.png +0 -0
  220. package/src/scripts/media/lib/fixtures/ideogram/asset-0001.png +0 -0
  221. package/src/scripts/migrate_command_suggestions.ts +1 -1
  222. package/src/scripts/probe_unblocked_ask.ts +135 -2
  223. package/src/scripts/rdp_candidates_delta.ts +276 -0
  224. package/src/scripts/rdp_quality_eval.ts +217 -19
  225. package/src/scripts/release.ts +26 -71
  226. package/src/scripts/release_drill.ts +6 -10
  227. package/src/scripts/release_env.ts +1 -0
  228. package/src/scripts/release_mix_taxonomy.json +3 -2
  229. package/src/scripts/release_publication.ts +34 -29
  230. package/src/scripts/report_host_injection_effect.ts +189 -0
  231. package/src/scripts/report_layer_overlap.ts +41 -0
  232. package/src/scripts/report_skill_menu_census.ts +341 -0
  233. package/src/scripts/self_review_gate.ts +409 -15
  234. package/src/scripts/skill_tools/score_skill_relevance.ts +70 -6
  235. package/src/scripts/skill_tools/suggest_skill_for_task.ts +37 -3
  236. package/src/vendor/grammars/README.md +61 -0
  237. package/src/vendor/grammars/tree-sitter-javascript.wasm +0 -0
  238. package/src/vendor/grammars/tree-sitter-php.wasm +0 -0
  239. package/src/vendor/grammars/tree-sitter-typescript.wasm +0 -0
  240. package/src/scripts/_lib/carrier_status.ts +0 -118
  241. package/src/scripts/_lib/estate_carrier_cases.ts +0 -72
  242. package/src/scripts/hooks/code_graph_nudge_hook.ts +0 -191
@@ -300,95 +300,45 @@ function _first_diff(a: string, b: string): string {
300
300
  * a branch, a PR and a CI run — the exact cost `guard_release_branch_push` was
301
301
  * added to avoid for the sibling obligation two releases earlier.
302
302
  *
303
- * This module has no imports on purpose (see `CURATED_HEAD_INSTRUCTION`), which
304
- * is what lets the writer, the local push guard and the CI gate all reach one
305
- * definition instead of three copies.
303
+ * This module has no imports on purpose (see `CURATED_HEAD_INSTRUCTION`).
304
+ *
305
+ * **One reader left, as of ADR-261 (2026-09-07).** This paragraph used to say
306
+ * the definition was shared by "the writer, the local push guard and the CI
307
+ * gate", which was true while `mix_response_blockers` and
308
+ * `check_governance_mix_response` scanned a section for this marker. Both are
309
+ * deleted, and with them every search for it: the only consumer now is
310
+ * `render_mix_response` in this same file, which WRITES the line. Nothing reads
311
+ * a changelog looking for it, so do not assume a guard notices when it is
312
+ * missing — none does.
306
313
  */
307
314
  export const MIX_RESPONSE_MARKER = '**Governance mix:**';
308
315
 
309
316
  /**
310
- * The sentinel that marks an EMITTED-BUT-UNANSWERED response.
311
- *
312
- * A generator that emits the measured level alone would satisfy the gate's
313
- * length floor (`MIX_RESPONSE_MIN_CHARS`, 40) with a number the machine
314
- * produced, turning a written-answer obligation into a formality the tool
315
- * discharges for itself. That is a smuggled auto-approval, not a fix.
317
+ * Render the measured governance-versus-product line the writer emits.
316
318
  *
317
- * So the writer emits the level AND this sentinel, and every gate refuses the
318
- * sentinel — the same discipline `CURATED_HEAD_INSTRUCTION` and
319
- * `DERIVED_MARKER` already follow. The net effect is only that the obligation
320
- * moves from CI to the moment the section is written, where the fix is an edit
321
- * to a file already open in the working tree.
322
- */
323
- export const MIX_RESPONSE_PLACEHOLDER = '<the consumer work>';
324
-
325
- /**
326
- * Every placeholder token the writer emits, so none of them can ship.
319
+ * ONE line, and machine-written end to end. It reports what the span measured
320
+ * and asks the releaser for nothing.
327
321
  *
328
- * The block carries two, and refusing only the first would leave
329
- * `<roadmap or issue>` publishable — `CHANGELOG.md` is in `package.json`
330
- * `files`, so that is the same "the generator's own scaffolding reached npm"
331
- * failure `CURATED_HEAD_INSTRUCTION` exists for, one token to the right.
322
+ * The written-answer obligation is gone (ADR-261). This used to emit a second line — `Next cycle ships <the consumer work>,
323
+ * tracked in <roadmap or issue>.` — a placeholder no generator was allowed to
324
+ * fill, backed by four refusals, an interactive prompt, a staging channel under
325
+ * `## [Unreleased]` and a read-back obligation on the following release. The
326
+ * releaser had to type prose into a running pipeline to get a version out.
332
327
  *
333
- * A named list rather than a shape match over angle brackets, for the reason
334
- * that constant already states: a pattern such as `/<[a-z ]+>/` rejects
335
- * unrelated legitimate prose and misses a reworded placeholder. Adding a token
336
- * to `render_mix_response` means adding it here.
337
- */
338
- export const MIX_RESPONSE_PLACEHOLDERS: readonly string[] = [
339
- MIX_RESPONSE_PLACEHOLDER,
340
- '<roadmap or issue>',
341
- ];
342
-
343
- /**
344
- * Render the response block the writer emits when the obligation triggers.
328
+ * The owner ended it: this package exists to make the maintainer's work
329
+ * cheaper, and a release gate that stops a green pipeline until a human writes
330
+ * a sentence about a FUTURE cycle is the opposite. The measurement stays
331
+ * because it is free and true; the homework does not.
345
332
  *
346
- * Two blockquote lines, immediately under the curated head and outside it, per
347
- * `docs/contracts/CHANGELOG-conventions.md` § Governance-versus-product
348
- * response — the head's ten-line cap is for product lines, and a sixth label
349
- * would make every historical section retroactively incomplete.
333
+ * The removed placeholder constants (`MIX_RESPONSE_PLACEHOLDER(S)`) and promise
334
+ * sentinels (`PROMISE_PHRASE`, `PROMISE_READBACK_MARKER`, `PROMISE_OUTCOMES`)
335
+ * are deleted rather than deprecated — a sentinel nothing writes and nothing
336
+ * refuses is dead weight that reads as live policy.
350
337
  */
351
338
  export function render_mix_response(level: string): string[] {
352
- return [
353
- `> ${MIX_RESPONSE_MARKER} ${level}.`,
354
- `> ${PROMISE_PHRASE} ${MIX_RESPONSE_PLACEHOLDER}, tracked in <roadmap or issue>.`,
355
- ];
339
+ return [`> ${MIX_RESPONSE_MARKER} ${level}.`];
356
340
  }
357
341
 
358
- /**
359
- * The words that turn a governance response into a PROMISE about a later cycle.
360
- *
361
- * Named rather than inlined because two sides now read it: the writer emits it,
362
- * and the next release reads the previous section for it to decide whether a
363
- * read-back is owed. A promise nothing reads back costs nothing to restate, so
364
- * it can be restated for as many cycles as there are cycles — the defect this
365
- * constant exists to make addressable.
366
- */
367
- export const PROMISE_PHRASE = 'Next cycle ships';
368
-
369
- /**
370
- * The marker under which a release head answers the PREVIOUS head's promise.
371
- *
372
- * A named sentinel, on the same discipline as `MIX_RESPONSE_MARKER` and
373
- * `DERIVED_MARKER`: a shape match over prose would accept a sentence that
374
- * happens to mention last cycle and reject a reworded answer.
375
- */
376
- export const PROMISE_READBACK_MARKER = '**Previous cycle:**';
377
-
378
- /**
379
- * The three outcomes a read-back may report, and the reason there are exactly
380
- * three.
381
- *
382
- * `shipped` and `did not ship` are the honest halves of the fact. `withdrawn`
383
- * exists because a promise the project decided not to keep is a legitimate
384
- * third state — but only WITH a reason, which is why the answer also has to
385
- * clear a written-length floor rather than being one of these three words on
386
- * its own. Without the third state the mechanism would push an author toward
387
- * restating a dead promise as still-outstanding, which is the failure it is
388
- * built to stop.
389
- */
390
- export const PROMISE_OUTCOMES: readonly string[] = ['shipped', 'did not ship', 'withdrawn'];
391
-
392
342
  /** Every `## X.Y.Z` release version in the file, in document order (newest first). */
393
343
  export function changelog_versions(text: string): string[] {
394
344
  const out: string[] = [];
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Roadmap `status:` values this repository has RETIRED, and the diagnostic a
3
+ * reader gets when one survives in a file.
4
+ *
5
+ * `status: carrier` was deleted by ADR-262. It had two jobs welded together: a
6
+ * structural one (this roadmap is the live destination of an archived parent's
7
+ * `[~]` carries) and an authority one (an autonomous run may not act on it
8
+ * until a human flips it to `ready`). The second was authored by the repository
9
+ * about its own maintainer, was enforced by nothing, and contradicted the drain
10
+ * command's own FORBIDDEN NON-HALT REASONS. The first is a property of the
11
+ * deferral EDGES, which `lint_deferral_integrity` already verifies from both
12
+ * ends, so nothing was left for a node-level status to carry.
13
+ *
14
+ * Deleting a vocabulary term silently is the failure this module exists to
15
+ * prevent: an old file would keep a value nothing reads, and the next author
16
+ * would infer a meaning from its presence. So a retired value is REJECTED with
17
+ * a migration diagnostic rather than ignored.
18
+ *
19
+ * The read is frontmatter-scoped on purpose, and that is inherited rather than
20
+ * newly decided: a fenced `status: carrier` inside a roadmap documenting the
21
+ * syntax is not a declaration, and a body-wide match turned exactly that into a
22
+ * hard failure before a review caught it.
23
+ */
24
+
25
+ /** Retired `status:` values, mapped to the record that retired each one. */
26
+ export const RETIRED_STATUS_VALUES: ReadonlyMap<string, string> = new Map([
27
+ ['carrier', 'ADR-262'],
28
+ ]);
29
+
30
+ /** The leading `---\n…\n---` block, or null when the text carries none. */
31
+ export function frontmatterBlock(text: string): string | null {
32
+ const m = /^---\n([\s\S]*?)\n---/.exec(text);
33
+ return m === null ? null : (m[1] ?? '');
34
+ }
35
+
36
+ /** The frontmatter `status:` value, lower-cased, or null when absent. */
37
+ export function frontmatterStatus(text: string | null | undefined): string | null {
38
+ if (text === null || text === undefined) {
39
+ return null;
40
+ }
41
+ const fm = frontmatterBlock(text);
42
+ if (fm === null) {
43
+ return null;
44
+ }
45
+ const m = /^status:[ \t]*([A-Za-z-]+)[ \t]*$/m.exec(fm);
46
+ return m === null ? null : (m[1] ?? '').toLowerCase();
47
+ }
48
+
49
+ /**
50
+ * The retired value this text declares, or null when it declares none.
51
+ *
52
+ * Returns the value rather than a boolean so the caller can name it in the
53
+ * diagnostic — "status: carrier is retired" is actionable, "invalid status" is
54
+ * not.
55
+ */
56
+ export function declaresRetiredStatus(text: string | null | undefined): string | null {
57
+ const status = frontmatterStatus(text);
58
+ if (status === null) {
59
+ return null;
60
+ }
61
+ return RETIRED_STATUS_VALUES.has(status) ? status : null;
62
+ }
63
+
64
+ /** The migration diagnostic for a retired value. Names the record and the fix. */
65
+ export function retiredStatusDiagnostic(value: string): string {
66
+ const record = RETIRED_STATUS_VALUES.get(value) ?? 'a decision record';
67
+ return (
68
+ `declares the retired \`status: ${value}\`, deleted by ${record}. ` +
69
+ 'A `status:` value no longer decides whether an autonomous run may act on ' +
70
+ 'a roadmap. Use `status: ready` and put any genuine human-ACTION gate in a ' +
71
+ '`## Blockers` entry, or park the file under `agents/roadmaps/later/` with ' +
72
+ 'an `entry_condition`. Both are read by a gate; a status is not.'
73
+ );
74
+ }
@@ -75,40 +75,146 @@
75
75
  */
76
76
 
77
77
  import * as fs from 'node:fs';
78
+ import * as os from 'node:os';
78
79
  import * as path from 'node:path';
79
80
 
80
- /** Catalogue roots tried in order; the first that exists wins. */
81
- export const DEFAULT_CATALOGUE_ROOTS = ['.claude/skills', 'src/skills'] as const;
81
+ /**
82
+ * Catalogue roots under the WORKSPACE, in precedence order.
83
+ *
84
+ * `src/skills` is the authored tree and exists only in a maintainer checkout.
85
+ * `.claude/skills` is the project-scope projection — a consumer's own skills
86
+ * live there, and so do this repository's flat-command wrappers.
87
+ */
88
+ export const PROJECT_CATALOGUE_ROOTS = ['src/skills', '.claude/skills'] as const;
89
+
90
+ /**
91
+ * The catalogue root under the user's HOME — what the host itself delivers.
92
+ *
93
+ * Home-relative rather than workspace-relative, which is why it cannot live in
94
+ * the list above: `~/.claude/skills` is one directory for every project on the
95
+ * machine, and on a consumer machine it is where `agent-config install` puts the
96
+ * shipped skills.
97
+ */
98
+ export const HOST_CATALOGUE_ROOT = '.claude/skills';
99
+
100
+ /**
101
+ * The PROJECTION root, on its own, for the consumers that need exactly it.
102
+ *
103
+ * `capture_skill_catalogue` compares what a host DELIVERED against what was
104
+ * PROJECTED, so the authored tree is the wrong answer for it even though the
105
+ * ranker wants the authored tree first. Both questions were sharing
106
+ * `DEFAULT_CATALOGUE_ROOTS` and the 2026-09-07 reorder silently changed the
107
+ * second one — the finding a neutral review returned the same day. One constant
108
+ * for two questions is the defect; this is the second question, named.
109
+ */
110
+ export const PROJECTION_CATALOGUE_ROOT = '.claude/skills';
82
111
 
83
112
  /**
84
- * First existing catalogue root under `workspaceRoot`, or `null`.
113
+ * Every root the resolver looks at, as human-readable LABELS.
114
+ *
115
+ * **Labels, never path inputs — this changed on 2026-09-07.**
116
+ *
117
+ * It used to be the resolver's own candidate list and callers joined it onto a
118
+ * root. It now carries a `~/`-prefixed entry, which `path.join(repo, ...)` turns
119
+ * into `<repo>/~/.claude/skills` — a candidate that can never exist. Use
120
+ * {@link resolveSkillCatalogueRoots} to RESOLVE and this list only to REPORT
121
+ * what was searched.
122
+ *
123
+ * For the `searched:` / `tried:` fields of the no-catalogue answers only — a
124
+ * reader needs to know `~` was consulted, and an absolute home path in a
125
+ * machine-readable payload is both noise and a small privacy leak.
126
+ */
127
+ export const DEFAULT_CATALOGUE_ROOTS = [
128
+ ...PROJECT_CATALOGUE_ROOTS,
129
+ `~/${HOST_CATALOGUE_ROOT}`,
130
+ ] as const;
131
+
132
+ /**
133
+ * EVERY readable, non-empty catalogue root, in precedence order.
134
+ *
135
+ * Shared by the `skill-route` concern and the `suggest_skill_for_task` MCP
136
+ * handler on purpose: two resolvers over one catalogue is how a ranker and the
137
+ * tool that exposes it start ranking different trees.
138
+ *
139
+ * **Why a LIST, and not the first hit — owner decision, 2026-09-07.**
140
+ *
141
+ * This returned one root and stopped at the first match, which made the ranker
142
+ * see one tree and the session see another. A consumer project can carry its own
143
+ * skills in `<project>/.claude/skills` while the shipped catalogue sits in
144
+ * `~/.claude/skills`; first-hit-wins ranked the smaller of the two and reported
145
+ * the result as if it were the catalogue. Ranking the UNION ranks what the
146
+ * session can actually reach, which is the only set the answer is about.
147
+ *
148
+ * **The order, and what it is for.**
85
149
  *
86
- * `.claude/skills` before `src/skills` because a CONSUMER install carries the
87
- * former and only a maintainer checkout carries the latter. Shared by the
88
- * `skill-route` concern and the `suggest_skill_for_task` MCP handler on
89
- * purpose: two resolvers over one catalogue is how a ranker and the tool that
90
- * exposes it start ranking different trees.
150
+ * Precedence applies to a NAME COLLISION, never to which roots are read:
151
+ *
152
+ * 1. `src/skills` — the authored tree. In this repository it is the truth, and
153
+ * a maintainer editing a skill must see their edit ranked, not the
154
+ * projection of the last `generate-tools` run.
155
+ * 2. `<project>/.claude/skills` — project scope beats machine scope, the same
156
+ * direction every other per-project override in this suite takes.
157
+ * 3. `~/${HOST_CATALOGUE_ROOT}` — the host-global catalogue.
158
+ *
159
+ * **The two defects this replaced, both real.**
160
+ *
161
+ * It used to try `.claude/skills` FIRST, "because a CONSUMER install carries the
162
+ * former and only a maintainer checkout carries the latter". The premise was
163
+ * right and the conclusion did not follow: a consumer has no `src/skills`, so
164
+ * reading it first costs one `existsSync`.
165
+ *
166
+ * The ADR-236 amendment of 2026-09-07 turned that order into a live defect. The
167
+ * project layer now withholds every skill `~/.claude/skills` carries, so this
168
+ * repository's `.claude/skills` holds only the ~49 flat-command WRAPPERS —
169
+ * non-empty, so the old order resolved it, and each wrapper carries a `SKILL.md`,
170
+ * so nothing downstream could tell it was not the catalogue. Measured the same
171
+ * day: the `skill-route` hook ranked over 49 command wrappers instead of 299
172
+ * skills. Under the union those wrappers are still ranked, which is correct —
173
+ * they are reachable — but they no longer displace the catalogue.
174
+ *
175
+ * The non-empty guard keeps its own separate job: an EMPTY root read as a match
176
+ * made the ranker report an empty catalogue as an empty RESULT, the silent
177
+ * failure `road-to-inbox-harvest-2026-08-f-skill-selection-evidence` is about.
178
+ * Measured in that run: a worktree that had never run `generate-tools` ranked
179
+ * zero skills and exited 0 on a task that scores 47 against `src/skills`.
180
+ *
181
+ * @param home override for the user's home; production callers omit it.
91
182
  */
92
- export function resolveSkillsRoot(workspaceRoot: string): string | null {
93
- for (const candidate of DEFAULT_CATALOGUE_ROOTS) {
94
- const abs = path.join(workspaceRoot, candidate);
183
+ export function resolveSkillCatalogueRoots(workspaceRoot: string, home?: string): string[] {
184
+ const userHome = home ?? process.env['HOME'] ?? os.homedir();
185
+ const candidates = [
186
+ ...PROJECT_CATALOGUE_ROOTS.map((c) => path.join(workspaceRoot, c)),
187
+ path.join(userHome, HOST_CATALOGUE_ROOT),
188
+ ];
189
+ const roots: string[] = [];
190
+ const seen = new Set<string>();
191
+ for (const abs of candidates) {
192
+ // A workspace that IS the home directory would offer `~/.claude/skills`
193
+ // twice and rank every skill in it twice. Cheap to exclude, and the
194
+ // duplicate would be invisible in the output.
195
+ if (seen.has(abs)) continue;
196
+ seen.add(abs);
95
197
  try {
96
198
  if (!fs.existsSync(abs) || !fs.statSync(abs).isDirectory()) continue;
97
- // NON-EMPTY, and that qualifier is the whole fix. `.claude/skills`
98
- // is a gitignored projection, so in a fresh worktree it EXISTS and
99
- // holds nothing — and an empty root resolved as a match makes the
100
- // ranker report an empty catalogue as an empty RESULT, which is the
101
- // silent failure `road-to-inbox-harvest-2026-08-f-skill-selection-evidence`
102
- // is about, arriving through a second door. Measured in that run: a
103
- // worktree that had never run `generate-tools` ranked zero skills
104
- // and exited 0 on a task that scores 47 against `src/skills`.
105
199
  if (fs.readdirSync(abs).length === 0) continue;
106
- return abs;
200
+ roots.push(abs);
107
201
  } catch {
108
202
  // Unreadable candidate is not a match; try the next.
109
203
  }
110
204
  }
111
- return null;
205
+ return roots;
206
+ }
207
+
208
+ /**
209
+ * The highest-precedence root, or `null` when none is readable.
210
+ *
211
+ * Kept for the surfaces that genuinely name ONE directory — a report line, a
212
+ * `skills_root` field. Anything that RANKS should take
213
+ * {@link resolveSkillCatalogueRoots} instead: ranking the first root only is the
214
+ * defect that function exists to remove.
215
+ */
216
+ export function resolveSkillsRoot(workspaceRoot: string, home?: string): string | null {
217
+ return resolveSkillCatalogueRoots(workspaceRoot, home)[0] ?? null;
112
218
  }
113
219
 
114
220
  /** Where observations accumulate. Append-only, one JSON object per line. */
@@ -223,6 +329,35 @@ function descriptionOf(frontmatter: string): string {
223
329
  * the order a host listing follows and therefore the order a positional
224
330
  * hypothesis is measured against.
225
331
  */
332
+ /**
333
+ * The catalogue as the RANKER sees it — every readable root, first name winning.
334
+ *
335
+ * Added 2026-09-07 after a second neutral review. `capture_skill_catalogue`'s D-4
336
+ * join says it reads "the catalogue the runtime ranker reads", and once the ranker
337
+ * moved to the union a single-root read stopped being that. Worse, the root the
338
+ * capture had been pointed at — `.claude/skills` — is the one the ADR-236
339
+ * amendment EMPTIED of skills: the join then read 49 command wrappers as the
340
+ * catalogue and published `pointableBare: 0` against `unpointableBare: 16`, a
341
+ * clean-looking verdict off the wrong tree, with the empty-guard silent because
342
+ * 49 is not 0.
343
+ *
344
+ * Positions are re-derived over the merged list, because a position is a property
345
+ * of the catalogue the host is shown, not of the root a name came from.
346
+ */
347
+ export function readCatalogueAcross(roots: readonly string[]): CatalogueEntry[] {
348
+ const seen = new Set<string>();
349
+ const merged: CatalogueEntry[] = [];
350
+ for (const root of roots) {
351
+ for (const entry of readProjectedCatalogue(root)) {
352
+ if (seen.has(entry.name)) continue;
353
+ seen.add(entry.name);
354
+ merged.push(entry);
355
+ }
356
+ }
357
+ merged.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
358
+ return merged.map((e, i) => ({ ...e, position: i + 1 }));
359
+ }
360
+
226
361
  export function readProjectedCatalogue(root: string): CatalogueEntry[] {
227
362
  // Membership is decided by "does <name>/SKILL.md resolve", never by
228
363
  // `Dirent.isDirectory()`. The host-facing projection is a tree of SYMLINKS
@@ -0,0 +1,117 @@
1
+ /**
2
+ * The structured-ask surface — one shared definition of "a host's own question
3
+ * tool", read by every consumer that needs it.
4
+ *
5
+ * Three consumers exist and they must not each invent their own answer:
6
+ *
7
+ * - `probe_unblocked_ask.ts` partitions hand-back ask turns into `text` and
8
+ * `native`, where `native` means the turn carried a call to such a tool.
9
+ * - `hooks/one_question_per_ask_hook.ts` denies a call carrying more than one
10
+ * question.
11
+ * - `_lib/host_capability.ts` carries the boolean `structured_ask` capability;
12
+ * the per-host SHAPE lives here rather than in the manifest, because every
13
+ * manifest field is a strict boolean that `asBool` coerces — a nested object
14
+ * put there would normalize silently to `false`.
15
+ *
16
+ * What is observed, and what is a shape guess — the line is drawn explicitly.
17
+ *
18
+ * `STRUCTURED_ASK_SHAPES` is **empty**. No host in this repository's registry
19
+ * carries an observed structured-ask tool, so there is no per-host tool name to
20
+ * match on, and inventing one from a vendor's documentation is exactly what
21
+ * `host-capability-manifest.md` § Observation protocol forbids.
22
+ *
23
+ * `STRUCTURED_ASK_TOOL_NAME_RE` is therefore a **name-shape pattern, not a host
24
+ * fact**. It exists so that if such a tool ever appears in a transcript, the
25
+ * probe MEASURES it rather than missing it — and so the number it reports today
26
+ * (zero) is a measurement rather than an absence of instrumentation. A reader
27
+ * must not read a match as evidence that a named host ships that tool; the only
28
+ * thing that establishes that is an observation written into
29
+ * `STRUCTURED_ASK_SHAPES` with the protocol's four-part citation.
30
+ *
31
+ * Consequence, stated rather than discovered later: `native` is expected to be
32
+ * **0** on every corpus this repo can scan today. That is the honest reading of
33
+ * a capability nobody has observed, not a broken detector.
34
+ */
35
+
36
+ /** The per-host shape of a structured-ask tool, once one has been OBSERVED. */
37
+ export interface StructuredAskShape {
38
+ /** The tool name as it appears in a transcript's `tool_use` block. */
39
+ readonly tool: string;
40
+ /** How many questions one call may carry. `1` is the shape this repo wants. */
41
+ readonly max_questions: number;
42
+ /** How many options one question may offer. */
43
+ readonly max_options_per_question: number;
44
+ /** Whether the host's picker admits a free-text answer alongside the options. */
45
+ readonly free_text: boolean;
46
+ }
47
+
48
+ /**
49
+ * Observed per-host structured-ask shapes.
50
+ *
51
+ * EMPTY, deliberately. A row is written only from an observation in a real
52
+ * session under `contexts/execution/host-capability-manifest.md` § Observation
53
+ * protocol — host · host version · transcript reference · date. Never from a
54
+ * host's documentation, and never by analogy to another host.
55
+ */
56
+ export const STRUCTURED_ASK_SHAPES: Readonly<Record<string, StructuredAskShape>> = {};
57
+
58
+ /** The observed shape for `hostId`, or `undefined` when none has been observed. */
59
+ export function structuredAskShape(
60
+ hostId: string | null | undefined,
61
+ ): StructuredAskShape | undefined {
62
+ if (hostId === null || hostId === undefined) {
63
+ return undefined;
64
+ }
65
+ return STRUCTURED_ASK_SHAPES[hostId];
66
+ }
67
+
68
+ /**
69
+ * Name-SHAPE pattern for a structured-ask tool. See the module header: this is
70
+ * a matcher, not a claim about any host.
71
+ */
72
+ export const STRUCTURED_ASK_TOOL_NAME_RE = /^(ask[_-]?user[_-]?question|user[_-]?question|ask[_-]?question)s?$/i;
73
+
74
+ /**
75
+ * Is `name` a structured-ask tool call?
76
+ *
77
+ * An observed per-host shape wins outright — that is a fact about the host. The
78
+ * name-shape pattern is the fallback, and it is why the probe can report a
79
+ * measured zero instead of nothing at all.
80
+ */
81
+ export function isStructuredAskTool(name: string, hostId?: string | null): boolean {
82
+ const shape = structuredAskShape(hostId);
83
+ if (shape !== undefined) {
84
+ return name === shape.tool;
85
+ }
86
+ return STRUCTURED_ASK_TOOL_NAME_RE.test(name);
87
+ }
88
+
89
+ /**
90
+ * How many questions a structured-ask tool payload carries.
91
+ *
92
+ * Returns `-1` when the payload's shape is not recognised at all — a caller
93
+ * that gates on this must treat `-1` as "cannot tell", never as "one". The two
94
+ * recognised shapes are the only ones a picker can plausibly use: an array of
95
+ * question objects under a `questions`-like key, or a single question field.
96
+ *
97
+ * A `questions` array of length 0 is 0, not 1: an empty ask is malformed and a
98
+ * gate should be able to say so.
99
+ */
100
+ export function countStructuredAskQuestions(input: unknown): number {
101
+ if (input === null || typeof input !== 'object' || Array.isArray(input)) {
102
+ return -1;
103
+ }
104
+ const src = input as Record<string, unknown>;
105
+ for (const key of ['questions', 'question_list', 'items', 'prompts']) {
106
+ const v = src[key];
107
+ if (Array.isArray(v)) {
108
+ return v.length;
109
+ }
110
+ }
111
+ for (const key of ['question', 'prompt', 'header']) {
112
+ if (typeof src[key] === 'string' && (src[key] as string).trim() !== '') {
113
+ return 1;
114
+ }
115
+ }
116
+ return -1;
117
+ }