@event4u/agent-config 16.0.0 → 16.1.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 (246) 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 +297 -137
  5. package/CONTRIBUTING.md +9 -0
  6. package/README.md +2 -2
  7. package/dist/agent-src/commands/analyze/inbox.md +26 -0
  8. package/dist/agent-src/commands/analyze/roadmap-repos.md +27 -0
  9. package/dist/agent-src/commands/challenge-me/closure.md +178 -0
  10. package/dist/agent-src/commands/challenge-me.md +9 -2
  11. package/dist/agent-src/commands/council/default.md +25 -5
  12. package/dist/agent-src/commands/feature/plan.md +28 -1
  13. package/dist/agent-src/commands/feature/roadmap.md +28 -1
  14. package/dist/agent-src/commands/implement-ticket.md +27 -0
  15. package/dist/agent-src/commands/jira-ticket.md +27 -0
  16. package/dist/agent-src/commands/pr/create.md +29 -0
  17. package/dist/agent-src/commands/pr/merge.md +60 -9
  18. package/dist/agent-src/commands/roadmap/ai-council.md +7 -1
  19. package/dist/agent-src/commands/roadmap/create.md +74 -27
  20. package/dist/agent-src/commands/roadmap/materialize.md +27 -0
  21. package/dist/agent-src/commands/roadmap/process-full.md +123 -18
  22. package/dist/agent-src/commands/team.md +22 -0
  23. package/dist/agent-src/contexts/authority/scope-growth-ownership.md +80 -0
  24. package/dist/agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +47 -0
  25. package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +44 -3
  26. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +15 -15
  27. package/dist/agent-src/contexts/execution/decision-closure-execution.md +93 -0
  28. package/dist/agent-src/contexts/execution/fix-loop-ladder.md +108 -0
  29. package/dist/agent-src/contexts/execution/plan-confidence-gate.md +4 -2
  30. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +75 -6
  31. package/dist/agent-src/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
  32. package/dist/agent-src/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
  33. package/dist/agent-src/guidelines/design-fidelity-routing.md +29 -0
  34. package/dist/agent-src/rules/autonomous-execution.md +15 -8
  35. package/dist/agent-src/rules/delegation-policy.md +23 -45
  36. package/dist/agent-src/rules/design-fidelity.md +32 -37
  37. package/dist/agent-src/rules/evaluator-independence.md +43 -49
  38. package/dist/agent-src/rules/test-first.md +41 -0
  39. package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +3 -3
  40. package/dist/agent-src/skills/ai-council/SKILL.md +11 -3
  41. package/dist/agent-src/skills/api-design/data/manifest.json +3 -3
  42. package/dist/agent-src/skills/database/data/manifest.json +4 -4
  43. package/dist/agent-src/skills/database/data/query-tuning.csv +3 -3
  44. package/dist/agent-src/skills/design-review/SKILL.md +50 -0
  45. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +14 -4
  46. package/dist/agent-src/skills/experiment-loop/SKILL.md +31 -1
  47. package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
  48. package/dist/agent-src/skills/storybook-workshop/SKILL.md +42 -3
  49. package/dist/agent-src/skills/threat-modeling/SKILL.md +9 -4
  50. package/dist/agent-src/skills/threat-modeling/data/manifest.json +4 -4
  51. package/dist/agent-src/skills/verify-repair-loop/SKILL.md +37 -3
  52. package/dist/agent-src/templates/agent-settings.md +1 -1
  53. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  54. package/dist/agent-src/templates/roadmaps.md +188 -0
  55. package/dist/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
  56. package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
  57. package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
  58. package/dist/cli-delegate/check_update_banner.js +40 -2
  59. package/dist/cli-delegate/chunk-4E2W63DA.js +335 -0
  60. package/dist/cli-delegate/chunk-5R7HOLAF.js +99 -0
  61. package/dist/cli-delegate/{chunk-IVBPRCQO.js → chunk-6KFEC3G7.js} +1 -1
  62. package/dist/cli-delegate/{chunk-2TAQ2K4V.js → chunk-6KM6ORRY.js} +55 -386
  63. package/dist/cli-delegate/{chunk-2XY7ILBT.js → chunk-FC75PGJE.js} +42 -2
  64. package/dist/cli-delegate/{chunk-GYC3VRYN.js → chunk-FXYUYU4N.js} +57 -13
  65. package/dist/cli-delegate/{chunk-55ACLDUE.js → chunk-GJCBB2UG.js} +168 -10
  66. package/dist/cli-delegate/{chunk-AK6HODNQ.js → chunk-HDVVGPQW.js} +873 -539
  67. package/dist/cli-delegate/{chunk-H32DGUJH.js → chunk-P6ZHP5W2.js} +1 -1
  68. package/dist/cli-delegate/{chunk-57PN4GYJ.js → chunk-RGNIWTEA.js} +1 -1
  69. package/dist/cli-delegate/chunk-ROYYTEZU.js +39 -0
  70. package/dist/cli-delegate/{chunk-3FKR64R5.js → chunk-UNCRJBT3.js} +31 -7
  71. package/dist/cli-delegate/{chunk-2DK2VJWG.js → chunk-WGXTVKCQ.js} +20 -6
  72. package/dist/cli-delegate/chunk-YDPILCOL.js +152 -0
  73. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  74. package/dist/cli-delegate/cmd_brand_status.js +1 -1
  75. package/dist/cli-delegate/cmd_conformance.js +90 -56
  76. package/dist/cli-delegate/cmd_doctor.js +7 -5
  77. package/dist/cli-delegate/cmd_explain.js +2 -2
  78. package/dist/cli-delegate/cmd_export.js +9 -7
  79. package/dist/cli-delegate/cmd_fleet.js +4 -2
  80. package/dist/cli-delegate/cmd_handoff.js +4 -4
  81. package/dist/cli-delegate/cmd_mcp_available.js +1 -1
  82. package/dist/cli-delegate/cmd_migrate.js +2 -2
  83. package/dist/cli-delegate/cmd_packs_active.js +2 -2
  84. package/dist/cli-delegate/cmd_preflight.js +4 -2
  85. package/dist/cli-delegate/cmd_prune.js +10 -8
  86. package/dist/cli-delegate/cmd_route_audit.js +2 -2
  87. package/dist/cli-delegate/cmd_route_explain.js +1 -1
  88. package/dist/cli-delegate/cmd_session_recycle.js +2 -2
  89. package/dist/cli-delegate/cmd_settings_get.js +28 -2
  90. package/dist/cli-delegate/cmd_settings_migrate.js +9 -7
  91. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  92. package/dist/cli-delegate/cmd_sync.js +10 -8
  93. package/dist/cli-delegate/cmd_uninstall.js +16 -13
  94. package/dist/cli-delegate/cmd_update.js +1 -1
  95. package/dist/cli-delegate/cmd_upgrade.js +1 -1
  96. package/dist/cli-delegate/cmd_validate.js +12 -10
  97. package/dist/cli-delegate/cmd_versions.js +1 -1
  98. package/dist/discovery/deprecation-report.md +1 -1
  99. package/dist/discovery/discovery-manifest.json +85 -38
  100. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  101. package/dist/discovery/discovery-manifest.summary.md +4 -4
  102. package/dist/discovery/dormancy-report.md +1 -1
  103. package/dist/discovery/orphan-report.md +1 -1
  104. package/dist/discovery/packs.json +10 -8
  105. package/dist/discovery/trust-report.md +3 -3
  106. package/dist/discovery/workspaces.json +8 -6
  107. package/dist/hooks/dispatch.js +1832 -1007
  108. package/dist/install/claudeRuleRewrite.js +14 -3
  109. package/dist/install/conflict.js +60 -10
  110. package/dist/install/conflictTracker.js +218 -0
  111. package/dist/install/fsPrimitives.js +54 -0
  112. package/dist/install/install.mjs +2007 -1481
  113. package/dist/install/manifestFiles.js +259 -0
  114. package/dist/install/preflight.js +14 -2
  115. package/dist/install/preserve.js +157 -0
  116. package/dist/install/recordedOwnership.js +132 -0
  117. package/dist/install/txlog.js +11 -2
  118. package/dist/mcp/registry-manifest.json +3 -3
  119. package/dist/mcp/server.json +2 -2
  120. package/dist/mcp/server.mjs +398 -39
  121. package/dist/router.json +1 -1
  122. package/dist/scripts/_lib/agent_settings.js +3 -2
  123. package/dist/scripts/_lib/fs_atomic.js +167 -0
  124. package/dist/scripts/_lib/installed_tools.js +460 -0
  125. package/dist/scripts/_lib/lean_projection_mode.js +3 -2
  126. package/dist/scripts/_lib/settings_renamed_keys.js +78 -0
  127. package/dist/server/routes/install.js +8 -0
  128. package/dist/server/routes/wizard.js +9 -3
  129. package/dist/server/schemas/settings.js +17 -5
  130. package/dist/shared/missionExecution.js +97 -0
  131. package/dist/ui/assets/{index-D0jaCRPM.js → index-DH1xc62v.js} +17 -17
  132. package/dist/ui/index.html +1 -1
  133. package/docs/MIGRATION.md +103 -0
  134. package/docs/contracts/settings-classes.md +26 -7
  135. package/docs/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
  136. package/docs/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
  137. package/docs/guidelines/design-fidelity-routing.md +29 -0
  138. package/package.json +1 -1
  139. package/src/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
  140. package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
  141. package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
  142. package/src/config/agent-settings.template.yml +139 -27
  143. package/src/config/auto-rule-budget.json +16 -0
  144. package/src/config/ci-local-parity.yml +18 -5
  145. package/src/config/continuity-surface.json +21 -0
  146. package/src/config/gate-coverage.yml +146 -13
  147. package/src/config/gate-violation-baselines.json +6 -6
  148. package/src/config/hook-token-budget.json +2 -0
  149. package/src/config/loop-surfaces.yaml +215 -0
  150. package/src/config/platform-anchor.json +3 -2
  151. package/src/config/release-gate-locality.yml +11 -0
  152. package/src/config/routing-coverage-seed.json +18 -4
  153. package/src/config/rule-enforcement-baseline.json +0 -1
  154. package/src/install/claudeRuleRewrite.ts +16 -2
  155. package/src/install/conflict.ts +65 -9
  156. package/src/install/conflictTracker.ts +299 -0
  157. package/src/install/fsPrimitives.ts +54 -0
  158. package/src/install/manifestFiles.ts +312 -0
  159. package/src/install/preflight.ts +16 -2
  160. package/src/install/preserve.ts +205 -0
  161. package/src/install/recordedOwnership.ts +149 -0
  162. package/src/install/txlog.ts +11 -2
  163. package/src/install/types.ts +19 -4
  164. package/src/scripts/_cli/cmd_conformance.ts +88 -15
  165. package/src/scripts/_cli/cmd_doctor.ts +4 -3
  166. package/src/scripts/_cli/cmd_route_explain.ts +9 -2
  167. package/src/scripts/_cli/cmd_settings_get.ts +44 -0
  168. package/src/scripts/_cli/doctor_execution.ts +90 -0
  169. package/src/scripts/_lib/activation_payload.ts +247 -0
  170. package/src/scripts/_lib/agent_settings.ts +4 -2
  171. package/src/scripts/_lib/authority_path.ts +102 -0
  172. package/src/scripts/_lib/cascade_base.ts +94 -0
  173. package/src/scripts/_lib/changelog_eras.ts +7 -2
  174. package/src/scripts/_lib/continuation_ladder.ts +149 -2
  175. package/src/scripts/_lib/council_transport.ts +129 -0
  176. package/src/scripts/_lib/delivery_ready.ts +112 -0
  177. package/src/scripts/_lib/forge_protection.ts +267 -0
  178. package/src/scripts/_lib/hook_platform_keys.ts +27 -0
  179. package/src/scripts/_lib/json_python_parity.ts +127 -0
  180. package/src/scripts/_lib/lean_projection_mode.ts +14 -3
  181. package/src/scripts/_lib/loop_surfaces.ts +382 -0
  182. package/src/scripts/_lib/mission_record.ts +190 -0
  183. package/src/scripts/_lib/obligation_frequency.ts +217 -0
  184. package/src/scripts/_lib/obligations.ts +402 -0
  185. package/src/scripts/_lib/orchestration_record.ts +11 -20
  186. package/src/scripts/_lib/release_highlights.ts +199 -10
  187. package/src/scripts/_lib/release_holds.ts +493 -0
  188. package/src/scripts/_lib/release_migration_gate.ts +184 -0
  189. package/src/scripts/_lib/rides_along.ts +102 -0
  190. package/src/scripts/_lib/settings_renamed_keys.ts +82 -0
  191. package/src/scripts/_lib/test_delta.ts +166 -0
  192. package/src/scripts/_lib/test_provenance.ts +171 -0
  193. package/src/scripts/_lib/typed_op_grant.ts +246 -0
  194. package/src/scripts/_lib/typed_op_watch.ts +185 -0
  195. package/src/scripts/ai_council/config.ts +8 -11
  196. package/src/scripts/ai_council/fallback_config.ts +5 -3
  197. package/src/scripts/ai_council/ownership_classes.ts +118 -0
  198. package/src/scripts/ask_block_census.ts +285 -15
  199. package/src/scripts/check_enforcement_matrix.ts +624 -0
  200. package/src/scripts/check_gate_reachability.ts +173 -10
  201. package/src/scripts/check_generator_sync.ts +954 -0
  202. package/src/scripts/check_preamble_payload_budget.ts +75 -2
  203. package/src/scripts/check_release_highlights.ts +33 -1
  204. package/src/scripts/check_release_holds.ts +567 -0
  205. package/src/scripts/check_rule_projection_integrity.ts +22 -12
  206. package/src/scripts/check_test_delta.ts +294 -0
  207. package/src/scripts/check_test_weakening.ts +299 -0
  208. package/src/scripts/closure_scan.ts +333 -0
  209. package/src/scripts/compile_hook_manifest.ts +43 -3
  210. package/src/scripts/council_record_shape.ts +209 -0
  211. package/src/scripts/hook_manifest.json +1 -1
  212. package/src/scripts/hook_manifest.yaml +88 -9
  213. package/src/scripts/hooks/concern_registry.ts +4 -0
  214. package/src/scripts/hooks/design_pass_hook.ts +128 -3
  215. package/src/scripts/hooks/evidence_independence.ts +96 -0
  216. package/src/scripts/hooks/memory_recall_hook.ts +540 -0
  217. package/src/scripts/hooks/obligation_settle_hook.ts +182 -0
  218. package/src/scripts/hooks/rule_inject_hook.ts +49 -1
  219. package/src/scripts/hooks/run_continuation_hook.ts +17 -20
  220. package/src/scripts/hooks_doctor.ts +85 -0
  221. package/src/scripts/hooks_status.ts +19 -0
  222. package/src/scripts/install.ts +106 -206
  223. package/src/scripts/janitor.ts +7 -0
  224. package/src/scripts/lint_decision_classes.ts +464 -0
  225. package/src/scripts/lint_hook_manifest.ts +11 -0
  226. package/src/scripts/lint_major_migration_sections.ts +450 -0
  227. package/src/scripts/lint_plan_risk_register.ts +47 -3
  228. package/src/scripts/lint_roadmap_blockers.ts +61 -0
  229. package/src/scripts/lint_roadmap_producers.ts +202 -0
  230. package/src/scripts/measure_rule_budget.ts +101 -5
  231. package/src/scripts/new_roadmap.ts +37 -0
  232. package/src/scripts/orchestration_record.ts +0 -5
  233. package/src/scripts/release.ts +14 -98
  234. package/src/scripts/release_drill.ts +267 -1
  235. package/src/scripts/release_publication.ts +51 -0
  236. package/src/scripts/report_enforcement_drift.ts +566 -0
  237. package/src/scripts/report_host_injection_effect.ts +3 -11
  238. package/src/scripts/report_standing_payload_by_host.ts +26 -152
  239. package/src/scripts/schemas/agent-settings.schema.json +23 -0
  240. package/src/scripts/schemas/command.schema.json +4 -0
  241. package/src/scripts/scope_growth.ts +219 -0
  242. package/src/scripts/trigger_eval_grandfather.json +0 -1
  243. package/src/scripts/ui_conformance_probe.ts +622 -0
  244. package/src/server/schemas/settings.ts +31 -7
  245. package/src/shared/missionExecution.ts +126 -0
  246. package/dist/cli-delegate/chunk-523HCFYO.js +0 -90
@@ -59,8 +59,6 @@ export type LinePhase = 'refine' | 'memory' | 'analyze' | 'plan' | 'implement' |
59
59
  * table can be checked against it (`road-to-experience-loop-broadening` 1.3).
60
60
  */
61
61
  export type LineOutcome = PhaseOutcome;
62
- /** Which capsule-emission trigger arm fired first (Phase 1 shadow comparison). */
63
- export type TriggerArm = 'watermark' | 'saturation' | 'tie';
64
62
  /** The orchestration form the form-gate selected (road-to-opt-subagent-harvest P2). */
65
63
  export type DispatchModeId =
66
64
  | 'do-and-judge'
@@ -225,12 +223,16 @@ export interface RecordInput {
225
223
  capsule_emitted?: boolean | undefined;
226
224
  /** Total entries across the capsule's arrays — a size proxy, never content. */
227
225
  capsule_entries?: number | null | undefined;
228
- /** 1-based step at which the token-watermark arm fired (null = never). */
229
- watermark_step?: number | null | undefined;
230
- /** 1-based step at which the novelty-saturation arm fired (null = never). */
231
- saturation_step?: number | null | undefined;
232
- /** Which arm fired first on this dispatch — the paired-comparison datum. */
233
- trigger_arm_earlier?: TriggerArm | null | undefined;
226
+ // The three trigger-comparison fields that used to sit here —
227
+ // `watermark_step`, `saturation_step`, `trigger_arm_earlier` — were removed
228
+ // on 2026-09-13. They were accepted by the CLI and no caller could ever
229
+ // compute them: `capsule_trigger.compareTriggers` needs per-step token
230
+ // counts AND per-step surfaced terms, and no payload this tree has observed
231
+ // carries either. `subagent_ledger_hook.ts` names that gap and refuses to
232
+ // invent a proxy, citing this exact module. A flag nothing can fill is the
233
+ // same built-and-unreachable class the rest of this change detects, so it
234
+ // goes rather than waiting behind a label. Re-adding them is one commit
235
+ // once an observation source exists.
234
236
  // Audit-log envelope (sensible defaults for a dispatch record)
235
237
  phase?: LinePhase | undefined;
236
238
  outcome?: LineOutcome | undefined;
@@ -258,7 +260,6 @@ const BANDS: readonly Band[] = ['low', 'medium', 'high'];
258
260
  const PHASES: readonly LinePhase[] = ['refine', 'memory', 'analyze', 'plan', 'implement', 'test', 'verify', 'report'];
259
261
  const LOOKUP_CLASSES: readonly LookupClass[] = ['definition', 'references', 'string-existence', 'report-run'];
260
262
  const ROUTES_TAKEN: readonly RouteTaken[] = ['primitive', 'subagent', 'ask'];
261
- const TRIGGER_ARMS: readonly TriggerArm[] = ['watermark', 'saturation', 'tie'];
262
263
  /** Hex-only payload hash — a hash can never smuggle content (privacy by construction). */
263
264
  const PAYLOAD_HASH_RE = /^[a-f0-9]{8,64}$/i;
264
265
  /** Id-shaped origin tag — enum-ish, never free-form prose. */
@@ -549,16 +550,9 @@ export function buildOrchestrationLine(input: RecordInput): BuiltLine {
549
550
  if (input.capsule_emitted !== undefined && typeof input.capsule_emitted !== 'boolean') {
550
551
  errors.push('capsule_emitted must be a boolean or omitted');
551
552
  }
552
- for (const [key, v] of [
553
- ['capsule_entries', input.capsule_entries],
554
- ['watermark_step', input.watermark_step],
555
- ['saturation_step', input.saturation_step],
556
- ] as const) {
553
+ for (const [key, v] of [['capsule_entries', input.capsule_entries]] as const) {
557
554
  if (v != null && (!isInt(v) || v < 0)) errors.push(`${key} must be a non-negative integer count`);
558
555
  }
559
- if (input.trigger_arm_earlier != null && !TRIGGER_ARMS.includes(input.trigger_arm_earlier)) {
560
- errors.push(`trigger_arm_earlier must be one of ${TRIGGER_ARMS.join(' | ')} (or omitted / null when neither arm fired)`);
561
- }
562
556
 
563
557
  for (const [key, v] of [
564
558
  ['model_requested', input.model_requested],
@@ -619,9 +613,6 @@ export function buildOrchestrationLine(input: RecordInput): BuiltLine {
619
613
  // capsule shadow-measurement — readers ignore unknowns per audit-log-v1
620
614
  capsule_emitted: input.capsule_emitted ?? null,
621
615
  capsule_entries: input.capsule_entries ?? null,
622
- watermark_step: input.watermark_step ?? null,
623
- saturation_step: input.saturation_step ?? null,
624
- trigger_arm_earlier: input.trigger_arm_earlier ?? null,
625
616
  };
626
617
 
627
618
  const line: Record<string, unknown> = {
@@ -77,6 +77,21 @@ export interface SpanCommit {
77
77
  breaking: boolean;
78
78
  }
79
79
 
80
+ /**
81
+ * Optional inputs the derivation cannot get from the span alone.
82
+ *
83
+ * `readTouchedFile` is the one seam where a label needs FILE CONTENT rather
84
+ * than commit metadata, and it is a callback rather than a git call inside the
85
+ * derivation for two reasons: the derivation stays pure and unit-testable
86
+ * against a fixture map, and the caller keeps ownership of WHICH ref it is
87
+ * reading (the gate reads `--to`, the generator reads `HEAD`). Returning
88
+ * `null` means "not readable" and is never treated as "no residual" — an
89
+ * unreadable file simply does not substantiate the label.
90
+ */
91
+ export interface DeriveOptions {
92
+ readTouchedFile?: (path: string) => string | null;
93
+ }
94
+
80
95
  /** One derived hit: the commit that substantiates a label. */
81
96
  export interface CategoryHit {
82
97
  sha: string;
@@ -122,15 +137,18 @@ const _EXECUTABLE_EXACT: ReadonlySet<string> = new Set([
122
137
  'src/scripts/agent-config',
123
138
  ]);
124
139
 
125
- function _touches_executable(files: SpanCommit['files']): boolean {
126
- return files.some(
127
- (f) =>
128
- _EXECUTABLE_SUFFIX.test(f.path) ||
129
- _EXECUTABLE_EXACT.has(f.path) ||
130
- _EXECUTABLE_PREFIXES.some((p) => f.path.startsWith(p)),
140
+ function _is_executable_path(p: string): boolean {
141
+ return (
142
+ _EXECUTABLE_SUFFIX.test(p) ||
143
+ _EXECUTABLE_EXACT.has(p) ||
144
+ _EXECUTABLE_PREFIXES.some((prefix) => p.startsWith(prefix))
131
145
  );
132
146
  }
133
147
 
148
+ function _touches_executable(files: SpanCommit['files']): boolean {
149
+ return files.some((f) => _is_executable_path(f.path));
150
+ }
151
+
134
152
  /**
135
153
  * Conventional-commit type, lower-cased, or `''` when the subject carries no
136
154
  * conventional prefix.
@@ -179,15 +197,67 @@ const _NULL_FORMS: readonly RegExp[] = [
179
197
  /\bon (?:its|the) own falsifier\b/iu,
180
198
  ];
181
199
 
200
+ /**
201
+ * Self-declared-residual forms — the `Known limitations` half of the same
202
+ * shape `_NULL_FORMS` uses, and taken from real prose in this tree rather than
203
+ * invented.
204
+ *
205
+ * PHRASE-LEVEL, NEVER WORD-LEVEL, and that is the measured part. The first
206
+ * draft matched the bare word `residual`, and over `15.0.0..16.0.0` its top
207
+ * hit was a commit where `residual` is a LOCAL VARIABLE holding a parse error
208
+ * (`const residual = residualParseError(rawText)`). A residual in this repo's
209
+ * vocabulary is a sentence about what a change did not reach, so every pattern
210
+ * below carries the noun it qualifies or the verb that declares it.
211
+ *
212
+ * TWO FURTHER NARROWINGS, both taken from running this derivation over the
213
+ * `14.23.0..15.0.0` and `15.0.0..16.0.0` spans before it gated anything, and
214
+ * both the same failure as the bare word — the pattern matching the repo's
215
+ * VOCABULARY for limits rather than a declaration of one:
216
+ *
217
+ * · `residual risk` / `residual cost` are OUT; `residual limit` / `residual
218
+ * gap` are in. "carries more residual risk than one just past the fire
219
+ * threshold" (`review_skipped_record.ts`) is a threshold rationale, and it
220
+ * alone produced three of the five candidates over `15.0.0..16.0.0`.
221
+ * · `known limitation(s)` is in; bare `known limit(s)` is OUT, because
222
+ * `build_proof.ts` EMITS a report section called `## 3. Known limits` —
223
+ * the phrase is a string literal in a generator, not a limit the file has.
224
+ *
225
+ * `does not yet` and `is not covered` are the two broadest and they are kept:
226
+ * both are the plainest way this tree writes a residual down, and the second
227
+ * is what makes `obligation_frequency.ts`'s "one session is not covered by a
228
+ * `session_start` check" derivable — a true positive neither narrow pattern
229
+ * would have reached.
230
+ */
231
+ const _RESIDUAL_FORMS: readonly RegExp[] = [
232
+ /\bknown limitations?\b/iu,
233
+ /\bknown false[- ]positive/iu,
234
+ /\bresidual (?:limit|gap)\b/iu,
235
+ /\bowes? (?:its|their|the) residual\b/iu,
236
+ /\b(?:limit|gap|residual|recall limit) that remains\b/iu,
237
+ /\bdoes not yet\b/iu,
238
+ /\b(?:is|are|was|were) not covered\b/iu,
239
+ /\bnot (?:excluded|covered) and tested\b/iu,
240
+ /\bnamed rather than left to be discovered\b/iu,
241
+ /\bnot gate-checkable\b/iu,
242
+ ];
243
+
182
244
  /**
183
245
  * Why a label fired, in generator-facing prose. Keyed by label; a label absent
184
- * here is never derived (`Known limitations` is pure prose, not checkable).
246
+ * here is never derived.
247
+ *
248
+ * All FIVE labels are keyed here since 2026-09-13. `Known limitations` used to
249
+ * be the exception — "pure prose, not checkable" — and the consequence is what
250
+ * removed it: the field printed `_none_` in 15.0.0 and in 16.0.0, both times
251
+ * over a span whose own code documented its residuals, because `_none_` is
252
+ * what an underived label prints when nobody writes anything. A derived
253
+ * `_none_` and an unfilled one are now distinguishable.
185
254
  */
186
255
  const _DERIVED_REASON: Readonly<Record<string, string>> = {
187
256
  'Behaviour changes': 'rule/schema diffs, breaking commits or removed public surface',
188
257
  'Default changes + migration': 'commits naming a default, migration or migrate',
189
258
  'Security and correctness': 'security-scoped commits or fixes to executable surface',
190
259
  'Honest nulls': 'commits recording a null, waived or falsified result',
260
+ 'Known limitations': 'commits declaring a residual, known limit or uncovered case',
191
261
  };
192
262
 
193
263
  /**
@@ -204,7 +274,19 @@ const _DERIVED_REASON: Readonly<Record<string, string>> = {
204
274
  * - Honest nulls: subject or body carrying one of the recorded-null forms —
205
275
  * the literal marker, a waived-rather-than-met condition, a published or
206
276
  * recorded null, an archival on a roadmap's own falsifier.
207
- * - Known limitations: never derived — pure prose, not gate-checkable.
277
+ * - Known limitations: subject or body carrying one of the self-declared
278
+ * residual forms, OR a touched EXECUTABLE file whose text carries one. The
279
+ * second half is what makes the label reachable at all: a residual is
280
+ * written where the code that has it lives, and `6b5c574` — the commit that
281
+ * moved detector E into `_lib/dropped_decision.ts` with its two named
282
+ * false-positive paths — says nothing about a limit in its own subject or
283
+ * body. It needs `opts.readTouchedFile`; without a reader the label falls
284
+ * back to the subject/body half alone, which is the pure-metadata behaviour
285
+ * every other label has.
286
+ *
287
+ * Scoped to executable surface on purpose (Risk 1 of the roadmap that added
288
+ * it): a residual declared in a rule or a roadmap is a plan, a residual
289
+ * declared in shipped code is a property of the release.
208
290
  *
209
291
  * Derivation is deliberately conservative: a false red makes every release
210
292
  * annoying, a miss only returns the head to the pre-gate state. Conservative
@@ -220,11 +302,37 @@ const _DERIVED_REASON: Readonly<Record<string, string>> = {
220
302
  */
221
303
  export function derive_category_hits(
222
304
  commits: readonly SpanCommit[],
305
+ opts: DeriveOptions = {},
223
306
  ): Record<string, CategoryHit[]> {
224
307
  const out: Record<string, CategoryHit[]> = {};
225
308
  for (const label of HEAD_LABELS) {
226
309
  out[label] = [];
227
310
  }
311
+ // One verdict per path for the whole span, not per commit: a file touched
312
+ // by eleven commits is read once, and the eleven commits then agree by
313
+ // construction. Memoising the VERDICT rather than the text also keeps the
314
+ // peak memory of a wide span flat.
315
+ const residualPath = new Map<string, boolean>();
316
+ const _declares_residual_file = (files: SpanCommit['files']): string | null => {
317
+ if (!opts.readTouchedFile) {
318
+ return null;
319
+ }
320
+ for (const f of files) {
321
+ if (f.status.startsWith('D') || !_is_executable_path(f.path)) {
322
+ continue;
323
+ }
324
+ let verdict = residualPath.get(f.path);
325
+ if (verdict === undefined) {
326
+ const text = opts.readTouchedFile(f.path);
327
+ verdict = text !== null && _RESIDUAL_FORMS.some((re) => re.test(text));
328
+ residualPath.set(f.path, verdict);
329
+ }
330
+ if (verdict) {
331
+ return f.path;
332
+ }
333
+ }
334
+ return null;
335
+ };
228
336
  for (const c of commits) {
229
337
  const scopeMatch = /^\w+\(([^)]*)\)!?:/u.exec(c.subject);
230
338
  const scope = scopeMatch ? scopeMatch[1]! : '';
@@ -256,6 +364,17 @@ export function derive_category_hits(
256
364
  if (_NULL_FORMS.some((re) => re.test(subjectAndBody))) {
257
365
  out['Honest nulls']!.push({ sha: c.sha, text: c.subject });
258
366
  }
367
+ if (_RESIDUAL_FORMS.some((re) => re.test(subjectAndBody))) {
368
+ out['Known limitations']!.push({ sha: c.sha, text: c.subject });
369
+ } else {
370
+ const declaring = _declares_residual_file(c.files);
371
+ if (declaring !== null) {
372
+ out['Known limitations']!.push({
373
+ sha: c.sha,
374
+ text: `${c.subject} (${declaring} declares a residual)`,
375
+ });
376
+ }
377
+ }
259
378
  }
260
379
  return out;
261
380
  }
@@ -265,8 +384,11 @@ export function derive_category_hits(
265
384
  * reports to a human. Derived from the structured hits so the two can never
266
385
  * disagree.
267
386
  */
268
- export function derive_categories(commits: readonly SpanCommit[]): Record<string, string[]> {
269
- const hits = derive_category_hits(commits);
387
+ export function derive_categories(
388
+ commits: readonly SpanCommit[],
389
+ opts: DeriveOptions = {},
390
+ ): Record<string, string[]> {
391
+ const hits = derive_category_hits(commits, opts);
270
392
  const out: Record<string, string[]> = {};
271
393
  for (const label of HEAD_LABELS) {
272
394
  out[label] = (hits[label] ?? []).map((h) => `${h.sha.slice(0, 7)} ${h.text}`);
@@ -571,6 +693,73 @@ export function collect_span_commits(from: string | null, to: string, cwd: strin
571
693
  return parse_git_log(r.stdout);
572
694
  }
573
695
 
696
+ /**
697
+ * Cap on distinct blobs one derivation will read.
698
+ *
699
+ * A bound and not a tuning knob: the `15.0.0..16.0.0` span touches 52 distinct
700
+ * executable paths, so the cap is an order of magnitude of headroom over the
701
+ * widest span measured. It exists so a pathological range (an era-spanning
702
+ * `--from`) cannot turn a gate into a thousand `git show` spawns. Hitting it
703
+ * degrades the label to the subject/body half rather than failing the gate,
704
+ * which is the same best-effort stance the rest of this derivation takes.
705
+ */
706
+ export const RESIDUAL_BLOB_CAP = 600;
707
+
708
+ /**
709
+ * A `readTouchedFile` backed by `git show <ref>:<path>`, for the callers that
710
+ * have a repository.
711
+ *
712
+ * Reads at a REF rather than from the working tree on purpose: the gate runs
713
+ * on a release branch whose checkout is the release commit, and a derivation
714
+ * that read the worktree would answer about the checkout instead of about the
715
+ * span it was handed. `git show` failing (path absent at that ref, binary,
716
+ * submodule) returns null, which the derivation treats as unsubstantiated.
717
+ */
718
+ export function make_span_file_reader(
719
+ ref: string,
720
+ cwd: string,
721
+ cap: number = RESIDUAL_BLOB_CAP,
722
+ ): (path: string) => string | null {
723
+ let reads = 0;
724
+ return (p: string): string | null => {
725
+ if (reads >= cap) {
726
+ return null;
727
+ }
728
+ reads++;
729
+ const r = spawnSync('git', ['show', `${ref}:${p}`], {
730
+ encoding: 'utf-8',
731
+ cwd,
732
+ maxBuffer: 16 * 1024 * 1024,
733
+ });
734
+ return r.status === 0 ? r.stdout : null;
735
+ };
736
+ }
737
+
738
+ /**
739
+ * The whole span → curated-head prefill, in one call.
740
+ *
741
+ * The composition lives here rather than at the call site because the span's
742
+ * end ref and the file reader's ref MUST be the same one: `Known limitations`
743
+ * reads file CONTENT at a ref, so a caller that collected the span at `to`
744
+ * while reading blobs at some other ref would derive a head describing two
745
+ * different trees. Pairing them in one function makes that mismatch
746
+ * unexpressible rather than merely discouraged.
747
+ *
748
+ * Throws whatever `collect_span_commits` throws. Whether a git failure is
749
+ * fatal or degrades to the `_none_` skeleton is the caller's decision, and it
750
+ * stays the caller's — the generator catches, the gate does not.
751
+ */
752
+ export function derive_head_prefill(
753
+ from: string | null,
754
+ to: string,
755
+ cwd: string,
756
+ ): Record<string, string> {
757
+ const span = collect_span_commits(from, to, cwd);
758
+ return render_derived_head_values(
759
+ derive_category_hits(span, { readTouchedFile: make_span_file_reader(to, cwd) }),
760
+ );
761
+ }
762
+
574
763
  /** Latest reachable release tag before `ref`, or null when there is none. */
575
764
  export function previous_release_tag(ref: string, cwd: string): string | null {
576
765
  const r = spawnSync(