@event4u/agent-config 14.18.0 → 14.20.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 (210) 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 +120 -129
  5. package/README.md +71 -149
  6. package/dist/agent-src/README.md +1 -1
  7. package/dist/agent-src/commands/agent-handoff.md +44 -43
  8. package/dist/agent-src/commands/review/changes.md +19 -19
  9. package/dist/agent-src/commands/review.md +2 -2
  10. package/dist/agent-src/contexts/execution/host-capability-manifest.md +37 -4
  11. package/dist/agent-src/rules/autonomous-execution.md +1 -1
  12. package/dist/agent-src/rules/git-history-discipline.md +2 -2
  13. package/dist/agent-src/rules/guidelines.md +1 -1
  14. package/dist/agent-src/rules/session-canary.md +2 -2
  15. package/dist/agent-src/rules/token-efficiency.md +1 -1
  16. package/dist/agent-src/rules/untrusted-input-defense.md +0 -2
  17. package/dist/agent-src/skills/adversarial-review/SKILL.md +0 -8
  18. package/dist/agent-src/skills/canvas-design/SKILL.md +1 -1
  19. package/dist/agent-src/skills/design-intelligence/SKILL.md +1 -1
  20. package/dist/agent-src/skills/design-intelligence/data/app-interface.csv +1 -1
  21. package/dist/agent-src/skills/design-intelligence/data/motion.csv +17 -17
  22. package/dist/agent-src/skills/design-intelligence/data/ux-guidelines.csv +2 -2
  23. package/dist/agent-src/skills/design-intelligence/references/context-and-registers.md +17 -0
  24. package/dist/agent-src/skills/design-intelligence/references/design-rules-checklist.md +4 -4
  25. package/dist/agent-src/skills/design-review/SKILL.md +1 -1
  26. package/dist/agent-src/skills/design-variations/SKILL.md +1 -1
  27. package/dist/agent-src/skills/evaluate-llm-feature/SKILL.md +1 -1
  28. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +1 -1
  29. package/dist/agent-src/skills/fe-design/SKILL.md +32 -5
  30. package/dist/agent-src/skills/fe-design/references/craft-floor.md +7 -0
  31. package/dist/agent-src/skills/fe-design/references/design-patterns.md +47 -2
  32. package/dist/agent-src/skills/laravel-scheduling/SKILL.md +3 -1
  33. package/dist/agent-src/skills/markitdown/SKILL.md +1 -1
  34. package/dist/agent-src/skills/project-analyzer/SKILL.md +0 -6
  35. package/dist/agent-src/skills/react-native-setup/SKILL.md +3 -3
  36. package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +1 -1
  37. package/dist/agent-src/skills/sequential-thinking/SKILL.md +18 -8
  38. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +28 -6
  39. package/dist/agent-src/skills/tailwind-engineer/SKILL.md +1 -1
  40. package/dist/agent-src/skills/test-driven-development/SKILL.md +1 -3
  41. package/dist/agent-src/skills/ui-apply-generic/SKILL.md +1 -1
  42. package/dist/agent-src/skills/ui-component-architect/SKILL.md +1 -1
  43. package/dist/agent-src/skills/using-git-worktrees/SKILL.md +17 -2
  44. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  45. package/dist/agent-src/templates/roadmaps.md +14 -1
  46. package/dist/agent-src/templates/scripts/telemetry/mcp_lite.ts +213 -0
  47. package/dist/agent-src/templates/scripts/telemetry/report_renderer.ts +16 -0
  48. package/dist/agent-src/templates/scripts/telemetry_report.ts +22 -2
  49. package/dist/cli/commands/recordTriggerEval.js +2 -2
  50. package/dist/cli/main.js +1 -1
  51. package/dist/cli/mcp/dispatch.js +10 -0
  52. package/dist/cli/mcp/stdio.js +33 -2
  53. package/dist/cli/mcp/telemetry.js +227 -0
  54. package/dist/cli-delegate/{chunk-AKJGUQNO.js → chunk-C6IPZRDA.js} +484 -353
  55. package/dist/cli-delegate/{chunk-CYH5SP6M.js → chunk-I6RHFQEG.js} +151 -16
  56. package/dist/cli-delegate/{chunk-7RXIKF62.js → chunk-NUMQHLS5.js} +304 -136
  57. package/dist/cli-delegate/{chunk-PMEKGDED.js → chunk-PI2GU3DP.js} +163 -1
  58. package/dist/cli-delegate/{chunk-7PRE6ZV2.js → chunk-YS6XTLEK.js} +199 -4
  59. package/dist/cli-delegate/chunk-ZVBOHSVF.js +1109 -0
  60. package/dist/cli-delegate/cmd_adr_effective.js +318 -81
  61. package/dist/cli-delegate/cmd_conformance.js +489 -121
  62. package/dist/cli-delegate/cmd_doctor.js +2 -2
  63. package/dist/cli-delegate/cmd_export.js +2 -2
  64. package/dist/cli-delegate/cmd_handoff.js +42 -12
  65. package/dist/cli-delegate/cmd_memory_get.js +1 -1
  66. package/dist/cli-delegate/cmd_prune.js +2 -2
  67. package/dist/cli-delegate/cmd_route_audit.js +2 -2
  68. package/dist/cli-delegate/cmd_session_recycle.js +59 -902
  69. package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
  70. package/dist/cli-delegate/cmd_sync.js +2 -2
  71. package/dist/cli-delegate/cmd_uninstall.js +2 -2
  72. package/dist/cli-delegate/cmd_validate.js +2 -2
  73. package/dist/discovery/deprecation-report.md +1 -1
  74. package/dist/discovery/discovery-manifest.json +34 -33
  75. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  76. package/dist/discovery/discovery-manifest.summary.md +1 -1
  77. package/dist/discovery/dormancy-report.md +1 -1
  78. package/dist/discovery/orphan-report.md +1 -1
  79. package/dist/discovery/packs.json +2 -2
  80. package/dist/discovery/trust-report.md +1 -1
  81. package/dist/discovery/workspaces.json +2 -2
  82. package/dist/hooks/dispatch.js +4455 -3025
  83. package/dist/install/install.mjs +975 -689
  84. package/dist/mcp/registry-manifest.json +2 -2
  85. package/dist/mcp/server.json +2 -2
  86. package/dist/mcp/server.mjs +2087 -1134
  87. package/dist/scripts/_lib/model_tier.js +1 -1
  88. package/dist/scripts/mcp_server/telemetry.js +148 -0
  89. package/docs/guidelines/agent-infra/evaluator-independence-mechanics.md +1 -1
  90. package/docs/guidelines/agent-infra/naming.md +1 -1
  91. package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +1 -1
  92. package/docs/guidelines/agent-infra/security-lint-containment.md +46 -6
  93. package/docs/guidelines/agent-infra/self-improvement-pipeline.md +4 -4
  94. package/docs/guidelines/agent-infra/untrusted-input-spotlighting.md +1 -1
  95. package/docs/guidelines/design-antipatterns.md +87 -64
  96. package/docs/guidelines/redundancy-taxonomy.md +28 -0
  97. package/package.json +1 -1
  98. package/src/agent-src/README.md +1 -1
  99. package/src/agent-src/templates/scripts/telemetry/mcp_lite.ts +213 -0
  100. package/src/agent-src/templates/scripts/telemetry/report_renderer.ts +16 -0
  101. package/src/agent-src/templates/scripts/telemetry_report.ts +22 -2
  102. package/src/config/assurance-capability-registry.json +9 -0
  103. package/src/config/ci-local-parity.yml +6 -0
  104. package/src/config/gate-coverage.yml +225 -0
  105. package/src/config/gate-violation-baselines.json +18 -6
  106. package/src/config/release-gate-locality.yml +12 -0
  107. package/src/scripts/_cli/cmd_doctor.ts +2 -2
  108. package/src/scripts/_cli/cmd_handoff.ts +1 -1
  109. package/src/scripts/_cli/cmd_session_recycle.ts +51 -3
  110. package/src/scripts/_cli/cmd_update.ts +1 -1
  111. package/src/scripts/_cli/cmd_versions.ts +1 -1
  112. package/src/scripts/_lib/adr_frontmatter.ts +44 -0
  113. package/src/scripts/_lib/audit_field_provenance.ts +44 -3
  114. package/src/scripts/_lib/consequence_objects.ts +152 -0
  115. package/src/scripts/_lib/exec_evidence.ts +7 -0
  116. package/src/scripts/_lib/global_deploy_inventory.ts +2 -2
  117. package/src/scripts/_lib/host_capability.ts +38 -0
  118. package/src/scripts/_lib/host_permission_checks.ts +251 -0
  119. package/src/scripts/_lib/install_reach_checks.ts +33 -1
  120. package/src/scripts/_lib/mcp_bridge.ts +222 -10
  121. package/src/scripts/_lib/mcp_consent_residual.ts +173 -0
  122. package/src/scripts/_lib/model_tier.ts +1 -1
  123. package/src/scripts/_lib/obligation_frequency.ts +2 -2
  124. package/src/scripts/_lib/orchestration_record.ts +23 -1
  125. package/src/scripts/_lib/recycle_envelope_paths.ts +190 -0
  126. package/src/scripts/_lib/release_highlights.ts +93 -0
  127. package/src/scripts/_lib/release_material.ts +64 -1
  128. package/src/scripts/_lib/release_position.ts +96 -0
  129. package/src/scripts/_lib/review_skipped_record.ts +17 -1
  130. package/src/scripts/_lib/runtime_journal.ts +26 -3
  131. package/src/scripts/_lib/runtime_wiring_checks.ts +12 -1
  132. package/src/scripts/_lib/security_lint.ts +179 -7
  133. package/src/scripts/_lib/self_repair.ts +141 -6
  134. package/src/scripts/_lib/self_repair_store.ts +273 -7
  135. package/src/scripts/_lib/session_register.ts +11 -6
  136. package/src/scripts/_lib/sqlite_schema.ts +22 -0
  137. package/src/scripts/_lib/subagent_capsule.ts +155 -3
  138. package/src/scripts/_lib/surface.ts +86 -0
  139. package/src/scripts/_lib/ui_authority.ts +116 -0
  140. package/src/scripts/adr/regenerate_index.ts +54 -1
  141. package/src/scripts/adr_cite_check.ts +71 -15
  142. package/src/scripts/ai_council/clients.ts +1 -1
  143. package/src/scripts/autonomy_friction_corpus.ts +229 -0
  144. package/src/scripts/build_proof.ts +15 -2
  145. package/src/scripts/build_rule_trigger_matrix.ts +5 -0
  146. package/src/scripts/check_beta_review_markers.ts +141 -17
  147. package/src/scripts/check_enforcement_coverage.ts +1 -1
  148. package/src/scripts/check_enforcement_denominator.ts +121 -11
  149. package/src/scripts/check_finding_dispositions.ts +66 -9
  150. package/src/scripts/check_gate_coverage.ts +96 -0
  151. package/src/scripts/check_mcp_doc_drift.ts +217 -0
  152. package/src/scripts/check_pack_size.ts +321 -3
  153. package/src/scripts/check_release_highlights.ts +74 -1
  154. package/src/scripts/check_skill_activation_claim.ts +179 -0
  155. package/src/scripts/check_trigger_evals.ts +1 -1
  156. package/src/scripts/conformance_scan.ts +44 -3
  157. package/src/scripts/explain_run.ts +1 -1
  158. package/src/scripts/generate_capability_matrix.ts +1 -1
  159. package/src/scripts/git_authorization_hook.ts +75 -1
  160. package/src/scripts/handoff_context_hook.ts +54 -7
  161. package/src/scripts/hook_manifest.json +1 -1
  162. package/src/scripts/hook_manifest.yaml +14 -11
  163. package/src/scripts/hooks/augment-dispatcher.sh +1 -1
  164. package/src/scripts/hooks/category_a.ts +291 -0
  165. package/src/scripts/hooks/cline-dispatcher.sh +1 -1
  166. package/src/scripts/hooks/cursor-dispatcher.sh +11 -5
  167. package/src/scripts/hooks/dispatch_hook.ts +17 -22
  168. package/src/scripts/hooks/git_command_classifier.ts +156 -1
  169. package/src/scripts/hooks/host_lowering.ts +215 -0
  170. package/src/scripts/hooks/host_lowering.yaml +185 -0
  171. package/src/scripts/hooks/host_semantics.ts +105 -37
  172. package/src/scripts/hooks/journal_record_hook.ts +10 -0
  173. package/src/scripts/hooks/permission_gate.ts +117 -0
  174. package/src/scripts/hooks/session_eol_hook.ts +18 -7
  175. package/src/scripts/hooks_status.ts +12 -10
  176. package/src/scripts/install.ts +57 -86
  177. package/src/scripts/install_anthropic_key.sh +2 -2
  178. package/src/scripts/lint_agent_security.ts +316 -25
  179. package/src/scripts/lint_confusables.ts +42 -6
  180. package/src/scripts/lint_design_antipattern_parity.ts +80 -0
  181. package/src/scripts/lint_evidence_artifacts.ts +45 -1
  182. package/src/scripts/lint_handoffs.ts +0 -132
  183. package/src/scripts/lint_hidden_unicode.ts +49 -7
  184. package/src/scripts/lint_hook_manifest.ts +87 -3
  185. package/src/scripts/lint_instruction_smuggling.ts +42 -6
  186. package/src/scripts/lint_mcp_config_security.ts +42 -6
  187. package/src/scripts/lint_motion_authority_drift.ts +731 -0
  188. package/src/scripts/lint_readme_size.ts +158 -12
  189. package/src/scripts/lint_reasoning_fixtures.ts +216 -0
  190. package/src/scripts/lint_roadmap_later_disposition.ts +217 -27
  191. package/src/scripts/lint_skill_frontmatter_safety.ts +43 -7
  192. package/src/scripts/lint_skill_trigger_contradiction.ts +161 -0
  193. package/src/scripts/mcp_server/resources.ts +41 -0
  194. package/src/scripts/mcp_server/telemetry.ts +19 -4
  195. package/src/scripts/measure_sibling_naming.ts +150 -0
  196. package/src/scripts/orchestration_record.ts +12 -1
  197. package/src/scripts/redact_hook_capture.ts +1 -1
  198. package/src/scripts/release.ts +24 -24
  199. package/src/scripts/release_publication.ts +6 -2
  200. package/src/scripts/report_skill_activation.ts +319 -7
  201. package/src/scripts/schemas/review-findings.schema.json +5 -0
  202. package/src/scripts/schemas/skill.schema.json +2 -2
  203. package/src/scripts/self_repair_cli.ts +31 -1
  204. package/src/scripts/session_canary_hook.ts +1 -1
  205. package/src/scripts/session_register_hook.ts +4 -3
  206. package/src/scripts/skill_scout.ts +69 -0
  207. package/src/scripts/trigger_eval_grandfather.json +5 -1
  208. package/src/scripts/validate_agent_settings.ts +1 -1
  209. package/dist/cli-delegate/chunk-2GNWGMAL.js +0 -172
  210. package/dist/cli-delegate/chunk-LPPWO5CE.js +0 -161
@@ -34,9 +34,9 @@
34
34
  "note": "The nine active non-draft roadmaps that predate the `relates:` requirement (road-to-agentic-engineering-assurance, road-to-component-library-lifecycle, road-to-council-evidence-integrity, road-to-frontend-fidelity-calibration, road-to-monorepo-scope-and-detection, road-to-observability-plate, road-to-role-scoped-spawn-profiles, road-to-skill-delivery-over-mcp, road-to-standing-payload-diet). This is a NEW obligation rather than a repaired scan root, and the ratchet is the point: `depends:` shipped in the template with nothing requiring it and measured 0 adoption, so requiring `relates:` retroactively across the estate would be an owner-reserved write with no analysis behind the edges it would have to invent (held as step 4.3, deferred). Clearing an entry here is one roadmap at a time: run `agent-config roadmap:context` on it, write the rows the probe surfaces, and lower the count."
35
35
  },
36
36
  "check_source_size_budget": {
37
- "count": 18068,
38
- "landed": "2026-09-05",
39
- "note": "Lowered 18,245 -> 18,230 on 2026-09-02 by drain/publication-integrity-option-a. Phase 2 Option A moved the authoring instruction out of the generator: release.ts, which sits ~530 lines PAST the 1500 cap so every line there costs one excess, loses the comment emission, a 16-line HEAD_DEFAULT doc block and its alias const, and gains a shorter writer docstring. The doc block moved to _lib/release_material.ts, which is 289 lines and therefore UNDER the cap, so the same prose now costs nothing. Net -15. The branch first came in at +8 and paid its own way down rather than raising the baseline.",
37
+ "count": 18062,
38
+ "landed": "2026-09-06",
39
+ "note": "Lowered 18,068 -> 18,062 on 2026-09-06 by drain/host-enforcement-truth. Collapsing the five per-host binding constants in install.ts into src/scripts/hooks/host_lowering.yaml removed 29 lines from a file ~3,900 past the 1500 cap, where every line costs one excess; the journal's surface column and its extracted migration helper gave 23 back. Lowered 18,245 -> 18,230 on 2026-09-02 by drain/publication-integrity-option-a. Phase 2 Option A moved the authoring instruction out of the generator: release.ts, which sits ~530 lines PAST the 1500 cap so every line there costs one excess, loses the comment emission, a 16-line HEAD_DEFAULT doc block and its alias const, and gains a shorter writer docstring. The doc block moved to _lib/release_material.ts, which is 289 lines and therefore UNDER the cap, so the same prose now costs nothing. Net -15. The branch first came in at +8 and paid its own way down rather than raising the baseline.",
40
40
  "reason": "Lowered 18,230 -> 18,209 on 2026-09-03 by drain/artifact-location. `doctor --strict` needed 16 lines of integration wiring in cmd_doctor.ts, which sits ~2,100 lines PAST the 1500 cap so every line there costs one excess. The branch first came in at +137 and paid its own way down rather than raising the baseline: the strict semantics, threshold, kill switch, payload helper and argv fragments moved to _cli/_doctor_strict.ts (~160 lines, under the cap, costs nothing), taking it to +15; then the 37-line drift renderer moved to _cli/_doctor_emit.ts. Net -21. The renderer was chosen deliberately: pure rendering, two dependencies, no argparse involvement and therefore none of the byte-parity risk the parser surface carries against the retired Python implementation; nothing outside cmd_doctor.ts imported it. An AI council on the residual split (a: raise by 15 / d: descope the feature); neither was taken, because a parallel branch demonstrated the same day that paying down is achievable in this codebase, which is the evidence the split lacked. This gate's own test comment prescribes it: a commit that splits a god-file lowers the excess and must carry the lowered baseline. MERGE RECONCILIATION 2026-09-03, 18,209 -> 18,205: drain/wired-instruments and drain/artifact-location each lowered from the SAME 18,230 base in parallel -- the branch to 18,226 (net -4, run_continuation_hook.ts's termination ladder moved to _lib/continuation_ladder.ts and runtime_journal.ts's prose to _lib/outcome_vocabularies.ts, both under the cap) and main to 18,209 (net -21, the doctor --strict split above). Taking either side alone is wrong in a different direction: main's value silently discards the branch's earned -4, and the branch's value RAISES the baseline against main and weakens the gate. The committed number is neither -- it is the gate's own output on the merged tree, where both reductions apply. THIS LOCAL READING IS ADMISSIBLE HERE, and the discriminator matters because this file's ci-parity:local-only entry records a 165 -> 164 lowering that was reverted for exactly the opposite case: that count depends on how the environment resolves the projection surface, whereas this gate walks only SOURCE_ROOTS = ['src'] (check_source_size_budget.ts:88) counting excess lines over the 1500 ceiling, and `git status --porcelain -uall -- src/` reported ZERO untracked files, so a clean CI checkout of this commit measures the same 18,205. If CI reads anything else, CI's number is the correct one and this note is the record of why. LOWERED 18,205 -> 18,193 on 2026-09-03 by fix/release-curation-ask. The change adds a fifth publication guard, and its own additions landed at +10 excess: release.ts 2,008 -> 2,018, every line of which is past the 1500 cap. Paid down rather than baselined, in the two ways this gate's doctrine prescribes. The guard's rationale (~13 lines of docstring, including why prMerged is a parameter rather than a call-site if) lives in release_publication.ts, 915 lines and therefore UNDER the cap, so the prose costs nothing; and the two pure text helpers _splitlines and _rstrip moved out of release.ts into release_env.ts (333 lines, also under the cap), taking release.ts to 1,996 -- 12 BELOW where it started. The branch came in at +10 and leaves at -12. Recorded while moving them: _splitlines is duplicated a third and fourth time at adoption_report.ts:106 and readme_linter.ts:418; collapsing all three onto one definition touches three modules and was left out of this change rather than done silently. LOWERED 18,193 -> 18,180 on 2026-09-03 by fix/release-branch-reuse. The step-1 branch decision -- twenty lines of if/else in release.ts -- became one call to checkout_release_branch in release_publication.ts, which is 1,019 lines and therefore UNDER the 1500 cap, so the twenty lines plus a ~35-line rationale now cost nothing. release.ts 1,996 -> 1,983, every one of those lines being past the cap. The drill grew 765 -> 918 for two new scenarios and the snapshot/restore that keeps them from writing the real CHANGELOG, and it is under the cap too. A change that adds a feature and lowers the excess is the shape this gate's doctrine asks for; the alternative was a +13 raise for logic that had no business in the god-file. LOWERED 18,180 -> 18,068 on 2026-09-05 by fix/release-gate-producer-parity. The change wires the governance-mix obligation into the writer and the two local guards and adds a release-gate locality registry; its own additions to release.ts landed at +19 excess, every one of those lines past the 1500 cap. Paid down rather than baselined, in the way this entry's doctrine prescribes: the 132-line test-count-trend unit (_TEST_COUNT_LINE_RE, _TEST_LIST_MAX_BUFFER, _count_from_list_result, _count_tests_current, _previous_test_count_from_changelog, _render_test_trend_line) moved from release.ts into release_publication.ts, which is under the cap and where it now belongs on merit rather than only on arithmetic -- the same diff makes the missing `Tests: N` footer a section-level publication blocker, so the writer that emits the footer and the guard that refuses its absence sit in one module. release.ts 1,983 -> 1,871. All four names are re-exported from release.ts, so tests/scripts/release.test.ts is unchanged. LOCAL READING, ADMISSIBLE ON THIS ENTRY'S OWN DISCRIMINATOR: the gate walks only SOURCE_ROOTS=['src'] counting excess over the ceiling, and `git status --porcelain -uall -- src/` reports ZERO untracked files with the two new src/ files staged, so a clean CI checkout of this commit measures the same 18,068. If CI reads anything else, CI's number is the correct one and this note is the record of why."
41
41
  },
42
42
  "ci-parity:local-only": {
@@ -52,9 +52,9 @@
52
52
  "falsifier": "./scripts-run src/scripts/check_gate_coverage"
53
53
  },
54
54
  "lint_canonical_terms": {
55
- "count": 1007,
56
- "landed": "2026-08-26",
57
- "note": "House-dialect debt in authored prose under src/ and docs/, measured by the gate this entry governs (road-to-canonical-terms 2.2). NOT a dead-root repair: the gate is new, and the debt is the corpus that predates the dialect decision recorded in src/config/canonical-terms.yml. A hard-zero gate was refused on the same ground the AI council refused an unconditional sweep on 2026-08-25 — `sweep_authorised: false` is a live constraint, and 1,025 occurrences cannot be rewritten in the change that introduces the check. The ratchet IS the council's staged promotion: report-only for existing debt, blocking for anything new. Measured 1,025 before the bounded pilot and 1,007 after it — the pilot was chosen by BLAST RADIUS, not by pair, per the council's explicit condition: the three smallest-radius pairs (`sub-agent`→`subagent`, `grey`→`gray`, `canonicalise`→`canonicalize`) at 18 occurrences across 12 files, with zero overlap with active work (no open PRs at the time). What remains is dominated by two large-radius pairs — `artefact`→`artifact` (619 occurrences / 239 files) and `behaviour`→`behavior` (325 / 171) — plus `hand-off`→`handoff` (46 / 36) and `licence`→`license` (17 / 12), the last of which the map flags `semantic-care-required` because licence TITLES are protected text. Shrink-only from here: each further wave lowers this number. Full classified inventory: agents/evidence/analysis/canonical-terms-inventory-2026-08-26.md.",
55
+ "count": 1006,
56
+ "landed": "2026-09-07",
57
+ "note": "Lowered 1007 -> 1006 on 2026-09-07 by drain/observed-learning-signal, and the gain was INCIDENTAL rather than sought: the branch's own new prose introduced five house-dialect occurrences under src/ and docs/ (four `artefact`/`artefacts`, one `behaviour`), which were rewritten LINE-SCOPED — only lines this diff authored, never a file-wide sweep, because `sweep_authorised: false` is still a live constraint. Four of the five were net-neutral (they replaced deleted lines carrying the same word) and one was a genuine reduction. The gate reported the tree at 1006 against a baseline of 1007 and asked for exactly this lowering, so the gain cannot be silently given back. LOCAL READING, ADMISSIBLE ON THE SAME DISCRIMINATOR THIS FILE'S check_source_size_budget ENTRY USES: the gate walks only authored prose under src/ and docs/, and `git status --porcelain -uall -- src docs` reported ZERO untracked files, so a clean CI checkout of this commit measures the same 1006. If CI reads anything else, CI's number is the correct one and this note is the record of why. `landed` is refreshed deliberately: checkRatchet returns `improved` BEFORE it evaluates staleness, so an entry left at 1007 while the tree measures 1006 would never expire at any age. PRIOR ENTRY: House-dialect debt in authored prose under src/ and docs/, measured by the gate this entry governs (road-to-canonical-terms 2.2). NOT a dead-root repair: the gate is new, and the debt is the corpus that predates the dialect decision recorded in src/config/canonical-terms.yml. A hard-zero gate was refused on the same ground the AI council refused an unconditional sweep on 2026-08-25. The ratchet IS the council's staged promotion: report-only for existing debt, blocking for anything new. What remains is dominated by two large-radius pairs — `artefact`->`artifact` and `behaviour`->`behavior` — plus `hand-off`->`handoff` and `licence`->`license`, the last of which the map flags `semantic-care-required` because licence TITLES are protected text. Shrink-only from here. Full classified inventory: agents/evidence/analysis/canonical-terms-inventory-2026-08-26.md.",
58
58
  "falsifier": "./scripts-run src/scripts/lint_canonical_terms"
59
59
  },
60
60
  "lint_handoffs": {
@@ -89,6 +89,18 @@
89
89
  "count": 243,
90
90
  "landed": "2026-09-02",
91
91
  "note": "243 pre-contract usages of `[~]` in archived roadmaps that carry no `deferred-resolution:` annotation. The annotation contract was settled by council on 2026-08-27; every file in this population predates it, the oldest by months (agents/roadmaps/archive/step-99-north-star-restructure.md:92 is 2026-05 and uses the glyph to mean 'partially green, not a regression'). They are NOT repaired: rewriting an archived roadmap's step semantics retroactively invents an intent nobody recorded, and the honest alternative glyph `[-]` is owner-reserved (archive_completed_roadmaps.ts:396). Shrink-only from here, one owner decision at a time. This ratchet covers ONLY the unannotated class; the broken-destination class -- a receiver deleted, renamed, re-parented, skipped, or archived with open steps -- is a hard failure at zero with no baseline, because deleting agents/roadmaps/road-to-council-topology-evidence-followups.md produces 38 of those and must red immediately rather than nudge a count. REMEDY WHEN THE 56-DAY NON-STAGNATION CLAUSE FIRES (from `landed`, i.e. 2026-10-28), named here because neither remedy the checker prints fits this population: \"fix the findings\" is ruled out by the sentence above -- rewriting an archived roadmap's step semantics retroactively invents an intent nobody recorded -- and \"lower the count\" is ruled out with it, since this population only shrinks when an owner decides one entry at a time. The remedy that fits is a `reaffirmed: {date, reason}` block on THIS entry (`_lib/gate_baseline.ts`), whose reason lands in the diff of the change that makes it: that is the path for debt which is genuinely unrepairable rather than merely unattended. A reaffirmation is not automatic. It asserts that the 243 were re-read and are still unrepairable, and if any of them has become repairable in the meantime the count goes down instead. Do not reaffirm at age 0 -- the clause exists so the question is asked once every 56 days, and answering it early answers nothing."
92
+ },
93
+ "lint_roadmap_later_disposition:review-by": {
94
+ "count": 68,
95
+ "landed": "2026-09-07",
96
+ "falsifier": "./scripts-run src/scripts/lint_roadmap_later_disposition # the ratchet line names the current count; it fails when the count RISES above this entry",
97
+ "note": "Parked roadmaps under agents/roadmaps/later/ carrying no `review_by:` in their frontmatter. NOT a dead-scan-root repair — the obligation is NEW (road-to-observed-learning-signal step 3.4), so every pre-existing parked roadmap lacks it by construction and 68 of 82 is the count at introduction rather than revealed debt. A hard-zero gate was refused on this repository's own recorded ground: a gate that fails its own tree on 68 files the landing change did not cause is reverted rather than adopted. Shrink-only from here, which is the property that matters: a NEWLY parked roadmap must carry `review_by` or this reds immediately. Clearing an entry is one roadmap at a time — read what the park is waiting on, pick a date a human would actually honour, and lower the count. The figure is the gate's own output on this branch (`--json`, class `review-by`), and the same 68 was measured independently over a tree that had grown by one file since the roadmap's own pin, which is why the roadmap's stated floor and this entry agree."
98
+ },
99
+ "lint_roadmap_later_disposition:wake-condition": {
100
+ "count": 18,
101
+ "landed": "2026-09-07",
102
+ "falsifier": "./scripts-run src/scripts/lint_roadmap_later_disposition # the ratchet line names the current count; it fails when the count RISES above this entry",
103
+ "note": "Parked roadmaps carrying no machine-readable wake condition — neither a structured three-part `entry_condition` (what / when / who) nor one of the four unambiguous body phrases. REVEALED rather than created: until 2026-09-07 the gate short-circuited on the frontmatter status word (`if (status !== 'later' && !RESUME_RE.test(body))`), so 63 of 82 parked roadmaps passed with no resume text present at all, and `trigger` was in RESUME_RE so 11 more passed on a prose mention of the word. Measured after the correction and after migrating the three pre-contract scalar `entry_condition` fields to the mapping form: 19 before the migration, 18 after. Shrink-only from here: a NEWLY parked roadmap must carry a real wake condition. A malformed `entry_condition` is deliberately OUTSIDE this ratchet and fails hard at zero — a scalar or a blank part is a defect the author introduced in the change that wrote it, not inherited debt."
92
104
  }
93
105
  },
94
106
  "retired": {
@@ -127,3 +127,15 @@ jobs:
127
127
  verify: false
128
128
  script: ~
129
129
  guard: ~
130
+
131
+ activation-census:
132
+ # The one row where the local command asserts MORE than the job it mirrors,
133
+ # not less, so `partial` does not apply: the transcript store the census
134
+ # reads is keyed on the directory it runs in and does not exist on a runner,
135
+ # so CI can only re-take an empty reading and check the published figure
136
+ # against its record. Run locally, the same command reads the real store,
137
+ # which is the only place the census means anything.
138
+ local: './scripts-run src/scripts/report_skill_activation && ./scripts-run src/scripts/check_skill_activation_claim'
139
+ verify: true
140
+ script: src/scripts/report_skill_activation.ts
141
+ guard: ~
@@ -2881,7 +2881,7 @@ function _run_checks(
2881
2881
  'claude-command-wrappers': _check_claude_command_wrappers,
2882
2882
  'surface-state': _check_surface_state,
2883
2883
  'hook-wiring': _check_hook_wiring,
2884
- ...(runtime_wiring.wiringRunners({ packageRoot: _package_root(), iterOverrides: () => iter_setting_overrides({ cwd: project_root }) }) as unknown as Record<string, CheckRunner>),
2884
+ ...(runtime_wiring.wiringRunners({ packageRoot: _package_root(), projectRoot: project_root, iterOverrides: () => iter_setting_overrides({ cwd: project_root }) }) as unknown as Record<string, CheckRunner>),
2885
2885
  ...(install_reach.reachRunners({ projectRoot: project_root, resolvableVersion: _current_package_version() }) as unknown as Record<string, CheckRunner>),
2886
2886
  'stale-orphans': _check_stale_orphans,
2887
2887
  overrides: () => _check_overrides(project_root),
@@ -2967,7 +2967,7 @@ function _run_checks_no_manifest(
2967
2967
  'claude-command-wrappers': _check_claude_command_wrappers,
2968
2968
  'surface-state': _check_surface_state,
2969
2969
  'hook-wiring': _check_hook_wiring,
2970
- ...(runtime_wiring.wiringRunners({ packageRoot: _package_root(), iterOverrides: () => iter_setting_overrides({ cwd: project_root }) }) as unknown as Record<string, CheckRunner>),
2970
+ ...(runtime_wiring.wiringRunners({ packageRoot: _package_root(), projectRoot: project_root, iterOverrides: () => iter_setting_overrides({ cwd: project_root }) }) as unknown as Record<string, CheckRunner>),
2971
2971
  ...(install_reach.reachRunners({ projectRoot: project_root, resolvableVersion: _current_package_version() }) as unknown as Record<string, CheckRunner>),
2972
2972
  'stale-orphans': _check_stale_orphans,
2973
2973
  // BOTH registries need the id. Registering only the first one crashed with
@@ -17,7 +17,7 @@
17
17
  * file (legacy copy-paste mode)
18
18
  * --launch <host> spawn a fresh interactive host session after
19
19
  * generating (claude: hook-seeded; codex: handoff as
20
- * initial prompt — no hook surface there)
20
+ * initial prompt — this package binds no hook there)
21
21
  * --llm narrative-polish seam; v1: clear not-implemented error
22
22
  * --root <path> repo root override (default: cwd)
23
23
  *
@@ -33,7 +33,8 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
33
33
  import { ORIGIN_CWD_FALLBACK, resolve_project_root } from '../_lib/agent_settings.js';
34
34
  import {
35
35
  RECYCLE_ENVELOPE_MAX_BYTES,
36
- RECYCLE_ENVELOPE_REL,
36
+ recycle_consumed_rel,
37
+ recycle_envelope_rel,
37
38
  } from '../_lib/recycle_envelope_paths.js';
38
39
  import {
39
40
  CAPSULE_SCHEMA_VERSION,
@@ -72,9 +73,42 @@ export function templateEnvelope(): Record<string, unknown> {
72
73
  next_task: '<the ONE task this envelope is written for — select content for it>',
73
74
  suggested_skills: ['<skill the successor should invoke>'],
74
75
  failed_approaches: ['<tried X, failed because Y — or the single entry "none">'],
76
+ successful_approaches: ['<did X, it worked because Y — or the single entry "none">'],
77
+ open_questions: ['<question the successor must not silently drop>'],
78
+ // Producer-owned, like `written_at` and `workspace`: `runSessionRecycle`
79
+ // deletes and re-derives it, so a value written here is discarded. It
80
+ // ships in the skeleton anyway so the template is a VALID record on its
81
+ // own — a template that cannot pass its own validator teaches the wrong
82
+ // shape to everyone who copies it.
83
+ predecessor: 'none',
75
84
  };
76
85
  }
77
86
 
87
+ /**
88
+ * Which session this one continues — read, never composed.
89
+ *
90
+ * The chain is derivable without any new artifact: when this session started,
91
+ * its reader consumed the predecessor's record and MOVED it to this session's
92
+ * consumed path (`recycle_consumed_rel`). So the file sitting there is, by
93
+ * construction, the record this session resumed from, and its `session_id` is
94
+ * the predecessor.
95
+ *
96
+ * Returns the explicit string `none` when there is nothing there. `none` is a
97
+ * CLAIM — "this session starts a chain" — and is deliberately not an empty
98
+ * value: a reader must be able to tell "no predecessor" from "nobody wrote the
99
+ * field", because only the second one is a reason to go looking.
100
+ */
101
+ export function resolvePredecessor(projectRoot: string, sessionId: string | null): string {
102
+ try {
103
+ const consumed = path.join(projectRoot, recycle_consumed_rel(sessionId));
104
+ const raw = JSON.parse(fs.readFileSync(consumed, 'utf-8')) as { session_id?: unknown };
105
+ const prior = String(raw.session_id ?? '').trim();
106
+ return prior === '' ? 'none' : prior;
107
+ } catch {
108
+ return 'none';
109
+ }
110
+ }
111
+
78
112
  interface ParsedArgv {
79
113
  ok: boolean;
80
114
  message?: string;
@@ -193,7 +227,18 @@ export function runSessionRecycle(
193
227
  // order would drift from the one a test covers. A host that exports neither
194
228
  // yields `null`, and `last_verify` is then simply absent from the envelope
195
229
  // (the delete-then-set below already treats an unreadable fact as absent).
196
- const grounding = collectGrounding(projectRoot, env_session_id());
230
+ const sessionId = env_session_id();
231
+ const grounding = collectGrounding(projectRoot, sessionId);
232
+
233
+ // Identity and lineage are READ, exactly like the git anchors below, and for
234
+ // the same reason: a model-composed predecessor is a guess about which
235
+ // session came before, and a wrong one hands the successor a stranger's
236
+ // chain. Both keys are dropped before being set so a composer cannot
237
+ // supply either.
238
+ delete envelope['session_id'];
239
+ delete envelope['predecessor'];
240
+ if (sessionId !== null && sessionId !== '') envelope['session_id'] = sessionId;
241
+ envelope['predecessor'] = resolvePredecessor(projectRoot, sessionId);
197
242
  // Drop the composer's factual keys UNCONDITIONALLY first. Guarding each
198
243
  // assignment on `!== null` would leave a model-composed branch or head
199
244
  // standing whenever the git read fails — the consumer would then compare
@@ -236,7 +281,10 @@ export function runSessionRecycle(
236
281
  // Absolute, not `RECYCLE_ENVELOPE_REL`: the relative form is identical for
237
282
  // every root, so it cannot tell the reader WHICH tree was written — the
238
283
  // one thing in doubt at the moment this line is read.
239
- const target = path.join(projectRoot, RECYCLE_ENVELOPE_REL);
284
+ // Phase 2.1 — keyed by session, so two sessions in one checkout no longer
285
+ // overwrite each other. No id resolvable falls back to the shared name,
286
+ // which is exactly the pre-key behaviour rather than a lost record.
287
+ const target = path.join(projectRoot, recycle_envelope_rel(env_session_id()));
240
288
 
241
289
  // `--verify` stops HERE: every rejection above has already run, and the
242
290
  // only thing skipped is the write. Validating through a different path
@@ -446,7 +446,7 @@ export async function main(argv: string[] | null = null, options: MainOptions =
446
446
  const installed_version = options.installed_version || _detect_installed_version();
447
447
  const state_path = options.state_path || update_check.DEFAULT_STATE_PATH;
448
448
 
449
- // AGENT_CONFIG_OFFLINE=1 (set by `install.py --offline`) is honored as an
449
+ // AGENT_CONFIG_OFFLINE=1 (set by `agent-config install --offline`) is honored as an
450
450
  // env-level kill-switch. Mirrors cmd_versions.py.
451
451
  const offline = args.offline || process.env['AGENT_CONFIG_OFFLINE'] === '1';
452
452
 
@@ -426,7 +426,7 @@ export function main(argv: string[] | null = null, options: MainOptions = {}): n
426
426
  const err = options.err ?? _stderrSink();
427
427
  const opts = _parse(argv ?? process.argv.slice(2), out, err);
428
428
 
429
- // AGENT_CONFIG_OFFLINE=1 (set by `install.py --offline`) is honored as a
429
+ // AGENT_CONFIG_OFFLINE=1 (set by `agent-config install --offline`) is honored as a
430
430
  // global kill-switch even when the per-command --offline flag is absent.
431
431
  const offline = opts.offline || process.env['AGENT_CONFIG_OFFLINE'] === '1';
432
432
 
@@ -483,3 +483,47 @@ export function readAdrAxisCells(text: string): AdrAxisCells {
483
483
  : `${strength} (${discovery})`,
484
484
  };
485
485
  }
486
+
487
+ /**
488
+ * The one shape this tool decides: a trigger whose condition IS a leading date.
489
+ *
490
+ * Deliberately anchored at the start of the string and deliberately narrow. The
491
+ * boundary is the whole safety property — the moment a trigger is matched on a
492
+ * date found anywhere in its prose, "reopens when N exceeds M" is one edit away
493
+ * and the machine-readable grammar two council seats rejected on 2026-08-19 has
494
+ * been built by increments.
495
+ */
496
+ export const DATED_TRIGGER_RE = /^\s*(?:expir(?:y|es)\b\s*[:—-]?\s*)?(\d{4})-(\d{2})-(\d{2})(?!\d)/iu;
497
+
498
+ /**
499
+ * A trigger that ANNOUNCES itself as dated, whether or not the date parses.
500
+ *
501
+ * The gap between this and `DATED_TRIGGER_RE` is the `dated-unparsed` state.
502
+ * Exactly one ADR in the corpus carries a dated expiry, so a parse tuned to its
503
+ * phrasing can fail on the second one — and the failure mode is silence,
504
+ * because the old answer for every trigger was `indeterminate`. This probe is
505
+ * what turns that silence into a reported defect.
506
+ */
507
+ export const DATED_TRIGGER_PROBE = /^\s*(?:expir(?:y|es)\b|\d{4}-\d{1,2}-\d{1,2}\b)/iu;
508
+
509
+ /** The UTC calendar day of an instant, as `YYYY-MM-DD`. */
510
+ export function iso_day(d: Date): string {
511
+ return d.toISOString().slice(0, 10);
512
+ }
513
+
514
+ /**
515
+ * The calendar day a dated trigger names, or null when there is none to read.
516
+ *
517
+ * Round-tripping through `Date.UTC` rejects `2026-13-45`, which matches the
518
+ * shape and is not a date.
519
+ */
520
+ export function dated_trigger_day(trigger: string): string | null {
521
+ const m = DATED_TRIGGER_RE.exec(trigger);
522
+ if (m === null) return null;
523
+ const [y, mo, d] = [Number(m[1]), Number(m[2]), Number(m[3])];
524
+ const at = new Date(Date.UTC(y, mo - 1, d));
525
+ if (at.getUTCFullYear() !== y || at.getUTCMonth() !== mo - 1 || at.getUTCDate() !== d) {
526
+ return null;
527
+ }
528
+ return iso_day(at);
529
+ }
@@ -30,9 +30,28 @@
30
30
  * producer actually compute the field.
31
31
  */
32
32
  export const PRODUCER_CONSTANT_FIELDS: ReadonlyMap<string, readonly string[]> = new Map([
33
- // Written identically by `orchestration_record.ts` and
34
- // `review_skipped_record.ts`. Neither computes it from rules that fired.
35
- ['rules_applied', ['delegation-policy']],
33
+ // EMPTY, and that is the assertion. `rules_applied` was the single row here
34
+ // until 2026-09-07: both shipped producers wrote the literal
35
+ // `['delegation-policy']` on every line. They now call `appliedIds` below,
36
+ // which carries what the caller observed and writes `[]` when the caller
37
+ // observed nothing — an honest absence, and the same answer
38
+ // `activation_receipt_producer.ts` has always given.
39
+ //
40
+ // Removing the row IS the claim that the field is now observed, so the row
41
+ // and the experience card that recorded the constancy
42
+ // (`agents/knowledge/experience-rules-applied-is-a-producer-constant.md`)
43
+ // were retired in the SAME change. Leaving the card standing after its
44
+ // falsifier fired would make the tree assert something false about itself,
45
+ // which is the failure the card exists to prevent.
46
+ //
47
+ // WHAT THIS DOES NOT CLAIM: the historical stream. Audit lines are
48
+ // append-only, so every line written before the cutover still carries the
49
+ // constant, and mining the real stream will keep surfacing it until new
50
+ // lines accumulate. The falsifier's second half — "the mined pattern's
51
+ // count falls below the audit line count" — is demonstrated over a fixture
52
+ // stream built through the changed producers
53
+ // (`tests/scripts/rules_applied_is_observed.test.ts`), which is the only
54
+ // stream where it CAN be demonstrated on the day the change lands.
36
55
  ]);
37
56
 
38
57
  /** True when a per-asset rate over this field would measure the producer. */
@@ -52,3 +71,25 @@ export function constantFieldReason(field: string): string | null {
52
71
  'producer, so any rate computed over it measures the writer rather than the work.'
53
72
  );
54
73
  }
74
+
75
+ /**
76
+ * Normalise an applied-id array for the `rules_applied` field: drop blanks,
77
+ * de-duplicate, bound to <= 32 (the contract's own limit).
78
+ *
79
+ * Lives HERE, in the module that recorded the constancy, because both shipped
80
+ * producers now call it and the one property that must not drift between them
81
+ * is exactly this field's derivation. A caller that offers nothing gets `[]` —
82
+ * an honest absence — never a fabricated id.
83
+ */
84
+ export const MAX_APPLIED_IDS = 32;
85
+
86
+ export function appliedIds(values: readonly string[] | null | undefined): string[] {
87
+ const out: string[] = [];
88
+ for (const raw of values ?? []) {
89
+ const v = String(raw).trim();
90
+ if (v === '' || out.includes(v)) continue;
91
+ out.push(v);
92
+ if (out.length >= MAX_APPLIED_IDS) break;
93
+ }
94
+ return out;
95
+ }
@@ -0,0 +1,152 @@
1
+ /**
2
+ * consequence_objects — the consequence vocabulary's prose half.
3
+ *
4
+ * Two things live here: the phrase rows that recognise a consequence
5
+ * authorization, and the extraction of the OBJECT such an authorization names.
6
+ * An authorization for one table, one environment or one recipient class is not
7
+ * an authorization for another, and until this existed the ledger could express
8
+ * the difference for nothing but pull-request numbers.
9
+ *
10
+ * IT IS RECOGNITION AND EXTRACTION, NOT MATCHING. Nothing here decides
11
+ * anything; the one consumer that acts on the vocabulary is `conformance_scan`,
12
+ * which counts after the fact. Making the object a matching condition is the
13
+ * ledger item ADR-254 left to the owner.
14
+ *
15
+ * It sits beside `git_authorization_hook` rather than inside it because that
16
+ * file is against the per-file source-size budget, which charges every line
17
+ * above 1500. The split is along a real seam: the hook owns the machinery —
18
+ * sentence bounds, negation, fenced-paste handling — and this module owns the
19
+ * consequence vocabulary that machinery is applied to.
20
+ */
21
+ import type { GitOp } from "../git_authorization_hook.js";
22
+
23
+ /**
24
+ * Prose phrases, German and English, that authorize a consequence operation.
25
+ *
26
+ * Spliced into `PHRASES` at its definition, so every row inherits the
27
+ * interrogative filter, the clause-scoped negation check and the fenced-paste
28
+ * split rather than reimplementing them.
29
+ *
30
+ * Every row REQUIRES ITS OBJECT NOUN, and that is why the patterns are long. A
31
+ * bare verb cannot tell an authorization from a mention, and for this class the
32
+ * mention is the common case: "deploy" appears in a hundred sentences that
33
+ * authorize nothing, while "deploy nach production" appears only when someone
34
+ * means it.
35
+ *
36
+ * German verb endings are `(e|en)?` because the imperative drops its `-e`, and
37
+ * both word orders are covered — the house style stated above `PHRASES`. The
38
+ * leading boundary is an explicit character class rather than `\b`: JavaScript's
39
+ * `\b` is ASCII-only, so a word STARTING with an umlaut ("Ändere") has no
40
+ * boundary before it and the row silently never fires.
41
+ */
42
+ export const CONSEQUENCE_PHRASES: ReadonlyArray<{ op: GitOp; re: RegExp }> = [
43
+ {
44
+ op: "prod-deploy",
45
+ re: /(?<![A-Za-z0-9_À-ɏ])(deploy(e|en|t|st)?|ausroll(e|en)?|ship(pe|pen)?|live\s*schalt(e|en)?|rollout)\b[^.\n]{0,30}\b(produktion|production|prod|live|prod-umgebung)\b|\b(produktion|production|prod|live)\b[^.\n]{0,30}\b(deploy(e|en)?|ausrollen|ausroll(e|en)?|live\s*schalten|rollout)\b/i,
46
+ },
47
+ {
48
+ op: "prod-data-destroy",
49
+ re: /(?<![A-Za-z0-9_À-ɏ])(l(ö|oe)sch(e|en)?|entfern(e|en)?|leer(e|en)?|verwerf(e|en)?|drop|truncate|delete|wipe|purge|zerst(ö|oe)r(e|en)?)\b[^.\n]{0,40}\b(tabelle|tabellen|datenbank|daten|bucket|collection|zeilen|rows|table|database|dataset)\b|\b(tabelle|tabellen|datenbank|daten|bucket|collection|table|database)\b[^.\n]{0,40}\b(l(ö|oe)schen|leeren|verwerfen|droppen|truncaten|entfernen|drop|truncate|delete|wipe|purge)\b/i,
50
+ },
51
+ {
52
+ op: "prod-infra-change",
53
+ re: /(?<![A-Za-z0-9_À-ɏ])((ver)?(ä|ae)nder(e|n)?|anpass(e|en)?|umbau(e|en)?|apply|destroy|(neu\s*)?provisionier(e|en)?)\b[^.\n]{0,40}\b(infrastruktur|infrastructure|cluster|terraform|iam|dns|netzwerk|network|firewall|policy|policies|security[-\s]group)\b|\b(infrastruktur|infrastructure|cluster|terraform|iam|dns|netzwerk|firewall)\b[^.\n]{0,40}\b((ver)?(ä|ae)ndern|anpassen|umbauen|zerst(ö|oe)ren|apply|destroy)\b/i,
54
+ },
55
+ {
56
+ op: "external-send",
57
+ re: /(?<![A-Za-z0-9_À-ɏ])(schick(e|en)?|verschick(e|en)?|send(e|en|et)?|mail(e|en)?|raussend(e|en)?|versend(e|en)?|post|publish)\b[^.\n]{0,40}\b(mail|mails|email|e-mail|nachricht|nachrichten|newsletter|webhook|kunden|kundin|kunde|empf(ä|ae)nger|customers?|recipients?|message)\b|\b(mail|email|e-mail|nachricht|newsletter|kunden|customers?)\b[^.\n]{0,40}\b(schicken|verschicken|senden|versenden|rausschicken|send|deliver)\b/i,
58
+ },
59
+ {
60
+ op: "money-movement",
61
+ re: /(?<![A-Za-z0-9_À-ɏ])(erstatt(e|en)?|r(ü|ue)ckerstatt(e|en)?|zahl(e|en)?|(ü|ue)berweis(e|en)?|abbuch(e|en)?|belast(e|en)?|refund|charge|payout|transfer|bill)\b[^.\n]{0,40}\b(betrag|geld|rechnung|zahlung|euro|dollar|kunde|kunden|karte|konto|invoice|payment|customer|card|account)\b|\b(betrag|geld|rechnung|zahlung|invoice|payment)\b[^.\n]{0,40}\b(erstatten|zur(ü|ue)ckzahlen|(ü|ue)berweisen|abbuchen|belasten|refund|charge|transfer)\b/i,
62
+ },
63
+ {
64
+ op: "trunk-force-push",
65
+ re: /(?<![A-Za-z0-9_À-ɏ])(force[-\s]?push(e|en|t)?|force[-\s]?pushen|(ü|ue)berschreib(e|en)?)\b[^.\n]{0,40}\b(main|master|trunk|produktions?[-\s]?branch|production|release[-\s]?branch)\b|\b(main|master|trunk|production)\b[^.\n]{0,40}\b(force[-\s]?pushen|force[-\s]?push|(ü|ue)berschreiben)\b/i,
66
+ },
67
+ {
68
+ op: "out-of-scope-destruction",
69
+ re: /(?<![A-Za-z0-9_À-ɏ])(l(ö|oe)sch(e|en)?|entfern(e|en)?|r(ä|ae)um(e|en)?\s*auf|aufr(ä|ae)um(e|en)?|delete|remove|wipe)\b[^.\n]{0,40}\b(au(ß|ss)erhalb|ausserhalb|unabh(ä|ae)ngig|nicht\s+zur\s+aufgabe|outside\s+(the\s+)?(task|scope)|unrelated|everything\s+else|alles\s+andere)\b|\b(au(ß|ss)erhalb|outside\s+(the\s+)?(task|scope)|unrelated)\b[^.\n]{0,40}\b(l(ö|oe)schen|entfernen|aufr(ä|ae)umen|delete|remove|wipe)\b/i,
70
+ },
71
+ ];
72
+
73
+ /**
74
+ * Operations whose authorization is meaningless without its object.
75
+ *
76
+ * "Delete the table" is not an authorization to delete a table; it is an
77
+ * authorization to delete THAT table. The same holds for a recipient class and
78
+ * for a payment. The other four consequence operations are environment-scoped
79
+ * rather than object-scoped, so an object is recorded for them when the prose
80
+ * names one and never required.
81
+ */
82
+ export const OBJECT_REQUIRED_OPS: ReadonlySet<GitOp> = new Set<GitOp>([
83
+ "prod-data-destroy",
84
+ "external-send",
85
+ "money-movement",
86
+ ]);
87
+
88
+ /**
89
+ * Words that can follow an object noun and are never the object's NAME.
90
+ *
91
+ * "Lösch die Tabelle bitte" names no table; the extractor's next-token rule
92
+ * captures `bitte` unless something says otherwise. A stop list is the honest
93
+ * shape for that — the same reviewable-keyword-list discipline the negation and
94
+ * pasted-output vocabularies above use, and for the same reason: a parser that
95
+ * guessed here would record a fabricated object, and a fabricated object is
96
+ * worse than a missing one because the conformance count treats a recorded
97
+ * object as an authorization the turn actually gave.
98
+ */
99
+ const OBJECT_STOP_WORDS: ReadonlySet<string> = new Set([
100
+ "bitte", "doch", "mal", "nicht", "jetzt", "gleich", "auch", "noch", "wieder",
101
+ "in", "im", "aus", "von", "vom", ": ", "der", "die", "das", "den", "dem",
102
+ "und", "oder", "mit", "auf", "zu", "komplett", "ganz", "alle", "allen",
103
+ "please", "now", "again", "too", "also", "from", "and", "or", "the", "a",
104
+ "an", "there", "here", "completely", "all", "with", "on", "to", "in",
105
+ ]);
106
+
107
+ /** Lower-case, strip surrounding quotes and trailing punctuation. */
108
+ function _normalizeObject(raw: string): string {
109
+ return raw.trim().replace(/^["'`]+|["'`]+$/g, "").replace(/[.,;:]+$/, "").toLowerCase();
110
+ }
111
+
112
+ /** Per-operation object patterns, each with the capture group that holds it. */
113
+ const OBJECT_PATTERNS: Readonly<Record<string, readonly RegExp[]>> = {
114
+ "prod-data-destroy": [
115
+ /\b(?:tabelle|table|datenbank|database|schema|collection|index)\s+["'`]?([A-Za-z_][\w.$-]*)/gi,
116
+ /\bs3:\/\/([\w.-]+)/gi,
117
+ /\b(?:bucket)\s+["'`]?([A-Za-z_][\w.-]*)/gi,
118
+ ],
119
+ "external-send": [
120
+ /([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,})/g,
121
+ /\bhttps?:\/\/([^\s/"'`]+)/gi,
122
+ /\b(?:an\s+(?:die|den|alle)|to\s+(?:the|all))\s+([A-Za-z][\w-]*)/gi,
123
+ ],
124
+ "money-movement": [
125
+ /(\d[\d.,]*)\s*(?:€|eur\b|euro\b|\$|usd\b)/gi,
126
+ /(?:€|\$)\s*(\d[\d.,]*)/g,
127
+ /\b(?:kunde|kunden|customer|account|konto)\s+["'`]?([A-Za-z0-9][\w.-]*)/gi,
128
+ ],
129
+ };
130
+
131
+ /**
132
+ * The objects a sentence names for one operation.
133
+ *
134
+ * Returns `[]` when the prose names none — which is a real answer and the one
135
+ * `conformance_scan` reads as "the record does not name this object". An empty
136
+ * list is never widened into a wildcard.
137
+ */
138
+ export function extractObjects(op: GitOp, text: string): string[] {
139
+ const patterns = OBJECT_PATTERNS[op];
140
+ if (!patterns) return [];
141
+ const out: string[] = [];
142
+ for (const re of patterns) {
143
+ const local = new RegExp(re.source, re.flags.includes("g") ? re.flags : `${re.flags}g`);
144
+ let m: RegExpExecArray | null;
145
+ while ((m = local.exec(text)) !== null) {
146
+ const value = _normalizeObject(m[1] ?? "");
147
+ if (value && !OBJECT_STOP_WORDS.has(value) && !out.includes(value)) out.push(value);
148
+ if (m.index === local.lastIndex) local.lastIndex++;
149
+ }
150
+ }
151
+ return out;
152
+ }
@@ -68,6 +68,13 @@ export const ALLOWLIST_PREFIXES: readonly (readonly string[])[] = [
68
68
  // a name. Read-only, deterministic, model-free, and already run in CI for its
69
69
  // own reasons — the same class as its neighbours here.
70
70
  ['check_no_external_sources'],
71
+ // Compares two COMMITTED files — the skill-activation census record against
72
+ // the sentence `docs/CLAIMS.md` publishes — so its exit code IS the verdict
73
+ // for that claim rather than evidence that some report exists. The census
74
+ // itself is deliberately NOT here: it reads a transcript store under `$HOME`
75
+ // that is absent on a runner, which would make the verdict an artifact of
76
+ // where it ran.
77
+ ['check_skill_activation_claim'],
71
78
  ['check_token_regression'],
72
79
  ['lint_agent_security'],
73
80
  ['measure_lexical_ranking'],
@@ -356,7 +356,7 @@ function as_posix(p: string): string {
356
356
  *
357
357
  * `dry_run=true` computes and returns the would-delete set (only paths that
358
358
  * currently exist on disk) WITHOUT unlinking anything or pruning empty
359
- * directories — the preview surface for `install.py --dry-run`. The selection
359
+ * directories — the preview surface for `agent-config install --dry-run`. The selection
360
360
  * logic (orphan diff, containment proof, directory guard) is identical to the
361
361
  * live path, so the preview is exact.
362
362
  */
@@ -456,7 +456,7 @@ export function reap_stale(
456
456
  *
457
457
  * `dry_run=true` returns the would-delete set (tagged orphans actually present
458
458
  * on disk) WITHOUT unlinking or pruning — the preview surface for
459
- * `install.py --dry-run`. Selection logic is identical to the live path.
459
+ * `agent-config install --dry-run`. Selection logic is identical to the live path.
460
460
  */
461
461
  export function reap_tagged_orphans(
462
462
  anchor: string,
@@ -67,6 +67,33 @@ export interface HostCapabilityManifest {
67
67
  * stop-loss behaviour — loudly, never silently.
68
68
  */
69
69
  worker_respawn: boolean;
70
+ /**
71
+ * The host reads a PROJECT-scope MCP config file (`.mcp.json` or its
72
+ * per-host equivalent) and starts the servers it names.
73
+ *
74
+ * Added because nothing in this tree could answer it: the installer writes
75
+ * `.mcp.json` for `claude-code` alone while writing other hosts' config
76
+ * files for hooks, and no code could say whether that omission was a
77
+ * measured absence or an unasked question.
78
+ *
79
+ * Same discipline as `worker_respawn`: `true` only once OBSERVED on a host,
80
+ * never inferred from the fact that a host supports MCP at all — a host may
81
+ * read a user-scope config and ignore a project one, which is precisely the
82
+ * distinction a consumer needs and the one an inference would erase.
83
+ */
84
+ reads_project_mcp_config: boolean;
85
+ /**
86
+ * The host requires a human action after installation before the MCP server
87
+ * is live — an approval dialog, a restart, a manual enable.
88
+ *
89
+ * `true` is the SAFE default here, and it is the opposite of every other
90
+ * field in this manifest. The others answer "can the host do X", where an
91
+ * unknown must not read as a capability. This one answers "is there a step
92
+ * left for the user", where an unknown must not read as "nothing to do":
93
+ * an install that silently omits a required step leaves a server nobody
94
+ * started and no message saying so.
95
+ */
96
+ mcp_needs_manual_activation: boolean;
70
97
  }
71
98
 
72
99
  /** Safe default — unknown host assumes no subagent primitive. */
@@ -78,6 +105,10 @@ const SAFE_DEFAULT: HostCapabilityManifest = {
78
105
  separate_quota_pool: false,
79
106
  agent_teams: false,
80
107
  worker_respawn: false,
108
+ reads_project_mcp_config: false,
109
+ // `true` because the safe answer here is "there is still a step", not
110
+ // "there is nothing to do" — see the field's own note.
111
+ mcp_needs_manual_activation: true,
81
112
  };
82
113
 
83
114
  /** Coerce one field: only a strict boolean `true` survives; everything else is `false`. */
@@ -106,6 +137,11 @@ export function normalizeHostManifest(input: unknown): HostCapabilityManifest {
106
137
  separate_quota_pool: asBool(src.separate_quota_pool),
107
138
  agent_teams: asBool(src.agent_teams),
108
139
  worker_respawn: asBool(src.worker_respawn),
140
+ reads_project_mcp_config: asBool(src.reads_project_mcp_config),
141
+ // Inverted coercion, matching the inverted default: only an explicit
142
+ // `false` clears the residual, so an absent or malformed value keeps
143
+ // the manual step visible instead of silently dropping it.
144
+ mcp_needs_manual_activation: src.mcp_needs_manual_activation !== false,
109
145
  };
110
146
  }
111
147
 
@@ -287,6 +323,8 @@ const CAPABILITY_FIELDS: readonly CapabilityField[] = [
287
323
  'separate_quota_pool',
288
324
  'agent_teams',
289
325
  'worker_respawn',
326
+ 'reads_project_mcp_config',
327
+ 'mcp_needs_manual_activation',
290
328
  ];
291
329
 
292
330
  /**