@event4u/agent-config 15.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 (283) 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 +301 -347
  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 +77 -29
  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 +97 -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/rules/user-interaction.md +31 -30
  40. package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +3 -3
  41. package/dist/agent-src/skills/ai-council/SKILL.md +11 -3
  42. package/dist/agent-src/skills/api-design/data/manifest.json +3 -3
  43. package/dist/agent-src/skills/database/data/manifest.json +4 -4
  44. package/dist/agent-src/skills/database/data/query-tuning.csv +3 -3
  45. package/dist/agent-src/skills/design-review/SKILL.md +50 -0
  46. package/dist/agent-src/skills/error-handling-patterns/SKILL.md +9 -0
  47. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +14 -4
  48. package/dist/agent-src/skills/experiment-loop/SKILL.md +31 -1
  49. package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
  50. package/dist/agent-src/skills/storybook-workshop/SKILL.md +42 -3
  51. package/dist/agent-src/skills/threat-modeling/SKILL.md +9 -4
  52. package/dist/agent-src/skills/threat-modeling/data/manifest.json +4 -4
  53. package/dist/agent-src/skills/verify-repair-loop/SKILL.md +37 -3
  54. package/dist/agent-src/templates/agent-settings.md +1 -1
  55. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  56. package/dist/agent-src/templates/roadmaps.md +188 -0
  57. package/dist/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
  58. package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
  59. package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
  60. package/dist/cli-delegate/check_update_banner.js +40 -2
  61. package/dist/cli-delegate/chunk-4E2W63DA.js +335 -0
  62. package/dist/cli-delegate/chunk-5R7HOLAF.js +99 -0
  63. package/dist/cli-delegate/{chunk-IVBPRCQO.js → chunk-6KFEC3G7.js} +1 -1
  64. package/dist/cli-delegate/{chunk-2TAQ2K4V.js → chunk-6KM6ORRY.js} +55 -386
  65. package/dist/cli-delegate/{chunk-2XY7ILBT.js → chunk-FC75PGJE.js} +42 -2
  66. package/dist/cli-delegate/{chunk-GYC3VRYN.js → chunk-FXYUYU4N.js} +57 -13
  67. package/dist/cli-delegate/{chunk-55ACLDUE.js → chunk-GJCBB2UG.js} +168 -10
  68. package/dist/cli-delegate/{chunk-AK6HODNQ.js → chunk-HDVVGPQW.js} +873 -539
  69. package/dist/cli-delegate/chunk-NUHLI2RX.js +80 -0
  70. package/dist/cli-delegate/{chunk-HZ52BRGG.js → chunk-P6ZHP5W2.js} +15 -90
  71. package/dist/cli-delegate/{chunk-TYSY33MD.js → chunk-QJWAUMJW.js} +12 -2
  72. package/dist/cli-delegate/{chunk-57PN4GYJ.js → chunk-RGNIWTEA.js} +1 -1
  73. package/dist/cli-delegate/chunk-ROYYTEZU.js +39 -0
  74. package/dist/cli-delegate/{chunk-2KKXJW25.js → chunk-S3VFXMTM.js} +1 -1
  75. package/dist/cli-delegate/{chunk-3FKR64R5.js → chunk-UNCRJBT3.js} +31 -7
  76. package/dist/cli-delegate/{chunk-2DK2VJWG.js → chunk-WGXTVKCQ.js} +20 -6
  77. package/dist/cli-delegate/chunk-YDPILCOL.js +152 -0
  78. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  79. package/dist/cli-delegate/cmd_brand_status.js +1 -1
  80. package/dist/cli-delegate/cmd_conformance.js +918 -325
  81. package/dist/cli-delegate/cmd_doctor.js +7 -5
  82. package/dist/cli-delegate/cmd_explain.js +5 -3
  83. package/dist/cli-delegate/cmd_export.js +9 -7
  84. package/dist/cli-delegate/cmd_fleet.js +4 -2
  85. package/dist/cli-delegate/cmd_handoff.js +6 -6
  86. package/dist/cli-delegate/cmd_mcp_available.js +1 -1
  87. package/dist/cli-delegate/cmd_migrate.js +2 -2
  88. package/dist/cli-delegate/cmd_packs_active.js +3 -2
  89. package/dist/cli-delegate/cmd_preflight.js +4 -2
  90. package/dist/cli-delegate/cmd_prune.js +10 -8
  91. package/dist/cli-delegate/cmd_route_audit.js +2 -2
  92. package/dist/cli-delegate/cmd_route_explain.js +1 -1
  93. package/dist/cli-delegate/cmd_session_recycle.js +4 -4
  94. package/dist/cli-delegate/cmd_settings_get.js +28 -2
  95. package/dist/cli-delegate/cmd_settings_migrate.js +9 -7
  96. package/dist/cli-delegate/cmd_settings_set.js +14 -1
  97. package/dist/cli-delegate/cmd_sync.js +10 -8
  98. package/dist/cli-delegate/cmd_uninstall.js +16 -13
  99. package/dist/cli-delegate/cmd_update.js +1 -1
  100. package/dist/cli-delegate/cmd_upgrade.js +1 -1
  101. package/dist/cli-delegate/cmd_validate.js +12 -10
  102. package/dist/cli-delegate/cmd_versions.js +1 -1
  103. package/dist/discovery/deprecation-report.md +1 -1
  104. package/dist/discovery/discovery-manifest.json +87 -40
  105. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  106. package/dist/discovery/discovery-manifest.summary.md +4 -4
  107. package/dist/discovery/dormancy-report.md +1 -1
  108. package/dist/discovery/orphan-report.md +1 -1
  109. package/dist/discovery/packs.json +10 -8
  110. package/dist/discovery/trust-report.md +3 -3
  111. package/dist/discovery/workspaces.json +8 -6
  112. package/dist/hooks/dispatch.js +2601 -1073
  113. package/dist/install/claudeRuleRewrite.js +14 -3
  114. package/dist/install/conflict.js +60 -10
  115. package/dist/install/conflictTracker.js +218 -0
  116. package/dist/install/fsPrimitives.js +54 -0
  117. package/dist/install/install.mjs +2008 -1481
  118. package/dist/install/manifestFiles.js +259 -0
  119. package/dist/install/preflight.js +14 -2
  120. package/dist/install/preserve.js +157 -0
  121. package/dist/install/recordedOwnership.js +132 -0
  122. package/dist/install/txlog.js +11 -2
  123. package/dist/mcp/registry-manifest.json +3 -3
  124. package/dist/mcp/server.json +2 -2
  125. package/dist/mcp/server.mjs +1612 -667
  126. package/dist/router.json +1 -1
  127. package/dist/scripts/_lib/agent_settings.js +3 -2
  128. package/dist/scripts/_lib/fs_atomic.js +167 -0
  129. package/dist/scripts/_lib/installed_lock.js +1 -1
  130. package/dist/scripts/_lib/installed_tools.js +460 -0
  131. package/dist/scripts/_lib/lean_projection_mode.js +3 -2
  132. package/dist/scripts/_lib/settings_renamed_keys.js +78 -0
  133. package/dist/server/io/yamlIO.js +50 -0
  134. package/dist/server/routes/install.js +8 -0
  135. package/dist/server/routes/wizard.js +9 -3
  136. package/dist/server/schemas/settings.js +17 -5
  137. package/dist/shared/missionExecution.js +97 -0
  138. package/dist/ui/assets/{index-D0jaCRPM.js → index-DH1xc62v.js} +17 -17
  139. package/dist/ui/index.html +1 -1
  140. package/docs/MIGRATION.md +103 -0
  141. package/docs/contracts/settings-classes.md +26 -7
  142. package/docs/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
  143. package/docs/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
  144. package/docs/guidelines/design-fidelity-routing.md +29 -0
  145. package/package.json +1 -1
  146. package/src/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
  147. package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
  148. package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
  149. package/src/config/agent-settings.template.yml +139 -27
  150. package/src/config/auto-rule-budget.json +16 -0
  151. package/src/config/ci-local-parity.yml +18 -5
  152. package/src/config/continuity-surface.json +28 -0
  153. package/src/config/gate-coverage.yml +146 -13
  154. package/src/config/gate-violation-baselines.json +6 -6
  155. package/src/config/hook-token-budget.json +2 -0
  156. package/src/config/host-injection-effect.json +1 -1
  157. package/src/config/loop-surfaces.yaml +215 -0
  158. package/src/config/platform-anchor.json +3 -2
  159. package/src/config/preamble-payload-budget.json +12 -7
  160. package/src/config/preamble-payload-exceptions.json +28 -0
  161. package/src/config/release-gate-locality.yml +11 -0
  162. package/src/config/routing-coverage-seed.json +18 -4
  163. package/src/config/rule-enforcement-baseline.json +0 -1
  164. package/src/install/claudeRuleRewrite.ts +16 -2
  165. package/src/install/conflict.ts +65 -9
  166. package/src/install/conflictTracker.ts +299 -0
  167. package/src/install/fsPrimitives.ts +54 -0
  168. package/src/install/manifestFiles.ts +312 -0
  169. package/src/install/preflight.ts +16 -2
  170. package/src/install/preserve.ts +205 -0
  171. package/src/install/recordedOwnership.ts +149 -0
  172. package/src/install/txlog.ts +11 -2
  173. package/src/install/types.ts +19 -4
  174. package/src/scripts/_cli/cmd_conformance.ts +88 -15
  175. package/src/scripts/_cli/cmd_doctor.ts +4 -3
  176. package/src/scripts/_cli/cmd_route_explain.ts +9 -2
  177. package/src/scripts/_cli/cmd_settings_get.ts +44 -0
  178. package/src/scripts/_cli/doctor_execution.ts +90 -0
  179. package/src/scripts/_dispatch.bash +3 -1
  180. package/src/scripts/_lib/activation_payload.ts +247 -0
  181. package/src/scripts/_lib/agent_settings.ts +4 -2
  182. package/src/scripts/_lib/authority_path.ts +102 -0
  183. package/src/scripts/_lib/base_ref_payload.ts +315 -0
  184. package/src/scripts/_lib/cascade_base.ts +94 -0
  185. package/src/scripts/_lib/changelog_eras.ts +7 -2
  186. package/src/scripts/_lib/continuation_ladder.ts +149 -2
  187. package/src/scripts/_lib/council_transport.ts +129 -0
  188. package/src/scripts/_lib/delivery_ready.ts +112 -0
  189. package/src/scripts/_lib/dropped_decision.ts +142 -0
  190. package/src/scripts/_lib/forge_protection.ts +267 -0
  191. package/src/scripts/_lib/hook_platform_keys.ts +27 -0
  192. package/src/scripts/_lib/installed_lock.ts +1 -1
  193. package/src/scripts/_lib/json_python_parity.ts +127 -0
  194. package/src/scripts/_lib/lean_projection_mode.ts +14 -3
  195. package/src/scripts/_lib/loop_surfaces.ts +382 -0
  196. package/src/scripts/_lib/measured_payload_ceiling.ts +424 -0
  197. package/src/scripts/_lib/mission_record.ts +190 -0
  198. package/src/scripts/_lib/obligation_frequency.ts +217 -0
  199. package/src/scripts/_lib/obligations.ts +402 -0
  200. package/src/scripts/_lib/orchestration_record.ts +11 -20
  201. package/src/scripts/_lib/payload_catalogue_completeness.ts +211 -0
  202. package/src/scripts/_lib/release_findings_ingest.ts +324 -0
  203. package/src/scripts/_lib/release_highlights.ts +199 -10
  204. package/src/scripts/_lib/release_holds.ts +493 -0
  205. package/src/scripts/_lib/release_migration_gate.ts +184 -0
  206. package/src/scripts/_lib/release_tag_dispatch.ts +63 -0
  207. package/src/scripts/_lib/review_baseline.ts +254 -0
  208. package/src/scripts/_lib/review_skipped_record.ts +61 -1
  209. package/src/scripts/_lib/rides_along.ts +102 -0
  210. package/src/scripts/_lib/settings_renamed_keys.ts +82 -0
  211. package/src/scripts/_lib/standing_bound_ratchet.ts +216 -103
  212. package/src/scripts/_lib/test_delta.ts +166 -0
  213. package/src/scripts/_lib/test_provenance.ts +171 -0
  214. package/src/scripts/_lib/turn_end_refusals.ts +51 -4
  215. package/src/scripts/_lib/typed_op_grant.ts +246 -0
  216. package/src/scripts/_lib/typed_op_watch.ts +185 -0
  217. package/src/scripts/ai_council/config.ts +8 -11
  218. package/src/scripts/ai_council/fallback_config.ts +5 -3
  219. package/src/scripts/ai_council/ownership_classes.ts +118 -0
  220. package/src/scripts/ask_block_census.ts +285 -15
  221. package/src/scripts/check_beta_review_markers.ts +35 -4
  222. package/src/scripts/check_enforcement_matrix.ts +624 -0
  223. package/src/scripts/check_finding_dispositions.ts +167 -16
  224. package/src/scripts/check_gate_reachability.ts +173 -10
  225. package/src/scripts/check_generator_sync.ts +954 -0
  226. package/src/scripts/check_held_object_arrivals.ts +381 -0
  227. package/src/scripts/check_preamble_payload_budget.ts +474 -124
  228. package/src/scripts/check_release_highlights.ts +33 -1
  229. package/src/scripts/check_release_holds.ts +567 -0
  230. package/src/scripts/check_release_published.ts +1 -1
  231. package/src/scripts/check_reply_consistency.ts +86 -17
  232. package/src/scripts/check_rule_projection_integrity.ts +22 -12
  233. package/src/scripts/check_test_delta.ts +294 -0
  234. package/src/scripts/check_test_weakening.ts +299 -0
  235. package/src/scripts/closure_scan.ts +333 -0
  236. package/src/scripts/compile_hook_manifest.ts +43 -3
  237. package/src/scripts/council_record_shape.ts +209 -0
  238. package/src/scripts/hook_manifest.json +1 -1
  239. package/src/scripts/hook_manifest.yaml +127 -12
  240. package/src/scripts/hooks/block_kernel_rule_writes.ts +12 -2
  241. package/src/scripts/hooks/concern_registry.ts +6 -0
  242. package/src/scripts/hooks/design_pass_hook.ts +128 -3
  243. package/src/scripts/hooks/end_review_nudge_hook.ts +101 -21
  244. package/src/scripts/hooks/evidence_independence.ts +96 -0
  245. package/src/scripts/hooks/memory_recall_hook.ts +540 -0
  246. package/src/scripts/hooks/obligation_settle_hook.ts +182 -0
  247. package/src/scripts/hooks/review_baseline_hook.ts +130 -0
  248. package/src/scripts/hooks/rule_inject_hook.ts +49 -1
  249. package/src/scripts/hooks/run_continuation_hook.ts +17 -20
  250. package/src/scripts/hooks/turn_end_gate_hook.ts +232 -14
  251. package/src/scripts/hooks_doctor.ts +89 -1
  252. package/src/scripts/hooks_status.ts +19 -0
  253. package/src/scripts/install.ts +107 -207
  254. package/src/scripts/interruption_report.ts +8 -2
  255. package/src/scripts/janitor.ts +15 -1
  256. package/src/scripts/lint_decision_classes.ts +464 -0
  257. package/src/scripts/lint_hook_manifest.ts +11 -0
  258. package/src/scripts/lint_major_migration_sections.ts +450 -0
  259. package/src/scripts/lint_plan_risk_register.ts +47 -3
  260. package/src/scripts/lint_roadmap_blockers.ts +61 -0
  261. package/src/scripts/lint_roadmap_producers.ts +202 -0
  262. package/src/scripts/measure_rule_budget.ts +101 -5
  263. package/src/scripts/measure_turn_end_gate.ts +302 -5
  264. package/src/scripts/new_roadmap.ts +37 -0
  265. package/src/scripts/orchestration_record.ts +0 -5
  266. package/src/scripts/release.ts +66 -162
  267. package/src/scripts/release_drill.ts +460 -15
  268. package/src/scripts/release_env.ts +1 -1
  269. package/src/scripts/release_publication.ts +52 -1
  270. package/src/scripts/report_enforcement_drift.ts +566 -0
  271. package/src/scripts/report_held_object_arrivals.ts +456 -0
  272. package/src/scripts/report_host_injection_effect.ts +3 -11
  273. package/src/scripts/report_standing_payload_by_host.ts +26 -152
  274. package/src/scripts/schemas/agent-settings.schema.json +23 -0
  275. package/src/scripts/schemas/command.schema.json +4 -0
  276. package/src/scripts/scope_growth.ts +219 -0
  277. package/src/scripts/sync_agent_settings.ts +140 -18
  278. package/src/scripts/trigger_eval_grandfather.json +0 -1
  279. package/src/scripts/ui_conformance_probe.ts +622 -0
  280. package/src/server/io/yamlIO.ts +48 -0
  281. package/src/server/schemas/settings.ts +31 -7
  282. package/src/shared/missionExecution.ts +126 -0
  283. package/dist/cli-delegate/chunk-523HCFYO.js +0 -90
@@ -0,0 +1,382 @@
1
+ /**
2
+ * The loop-surface inventory, and the reach question it exists to answer.
3
+ *
4
+ * Why an inventory at all.
5
+ *
6
+ * `archive/road-to-wired-instruments.md` closed four instances of one defect —
7
+ * an instrument this repository built, tested, and then connected to nothing —
8
+ * one instance at a time, and shipped five hand-written `grep` acceptance
9
+ * criteria instead of a detector. Two further instances were open the whole
10
+ * time and nothing in the tree could notice them. This module is the reader
11
+ * half of the fix: a declared list of loop surfaces and the exported helpers
12
+ * they are supposed to call, plus the one predicate that says whether a
13
+ * declared helper is called by anything at all.
14
+ *
15
+ * What it is NOT.
16
+ *
17
+ * Reader input. Nothing executes from it, no surface changes behaviour because
18
+ * of it, and there is no runner. That is Risk 3 of the roadmap that introduced
19
+ * it, stated as a constraint rather than a hope: the moment something
20
+ * dispatches off this file it has become the loop engine a recorded decision
21
+ * forbids.
22
+ *
23
+ * The reach predicate, and the correction that shaped it.
24
+ *
25
+ * The obvious predicate — "no file other than its own imports this export" —
26
+ * was measured against `loop_guards.ts` and calls nine of its fourteen exports
27
+ * dead. Most of those are wrong: a helper used by its own neighbours in the
28
+ * same file is reached every time that file is. So the criterion is
29
+ * occurrence-based and counts the declaration itself:
30
+ *
31
+ * an export is DEAD when it appears in `src/` exactly once,
32
+ * including its own declaration.
33
+ *
34
+ * Two is enough, deliberately. A second occurrence anywhere in `src/` means
35
+ * some production line names it; whether that line sits in the same file or
36
+ * another one is not a difference this predicate needs to resolve, and
37
+ * resolving it would mean writing a second import resolver.
38
+ *
39
+ * `tests/` is outside `src/` and therefore outside the count by construction —
40
+ * which is the point. A test-only caller is exactly the state both open
41
+ * instances are in.
42
+ *
43
+ * Why `cap` accepts a markdown anchor as well as a symbol.
44
+ *
45
+ * The roadmap step that specified this file asked for `file:symbol` and
46
+ * "never prose". Reproduced against the tree on 2026-09-13, that is
47
+ * unsatisfiable for two of the five surfaces and a category error for both:
48
+ * `verify-repair-loop` and `experiment-loop` are SKILLS, and a skill's bound
49
+ * is a documented knob, not a TypeScript constant. `grep -rn max_attempts
50
+ * src/` returns hits only inside the skill's own directory.
51
+ *
52
+ * The property that requirement was reaching for is preserved exactly:
53
+ * a bound must resolve to something that demonstrably EXISTS. So a `.ts`
54
+ * target must be a defined symbol, a `.md` target must be a literal token
55
+ * present in that file, and a markdown-bound row must additionally declare
56
+ * `bound_kind: prose` — the prose-ness is recorded rather than inferred, and
57
+ * `inventoryVerdict` reports those surfaces as a named set so a prose bound
58
+ * can never be read as equal to a code one.
59
+ */
60
+
61
+ import * as fs from 'node:fs';
62
+ import * as path from 'node:path';
63
+
64
+ import { parse as parseYaml } from 'yaml';
65
+
66
+ export const INVENTORY_REL = 'src/config/loop-surfaces.yaml';
67
+
68
+ /** Fields every surface row must carry. Order is the order they are reported in. */
69
+ export const SURFACE_FIELDS = [
70
+ 'cap',
71
+ 'no_progress',
72
+ 'success_stop',
73
+ 'checker',
74
+ 'human_gate',
75
+ 'terminal_vocabulary',
76
+ 'production_consumers',
77
+ ] as const;
78
+
79
+ export type SurfaceField = (typeof SURFACE_FIELDS)[number];
80
+
81
+ /** A loop surface — a thing that iterates under a bound. */
82
+ export interface LoopSurface {
83
+ id: string;
84
+ /** `code` or `prose` — which kind of artefact the surface itself is. */
85
+ kind: string;
86
+ /** `code` when `cap`/`no_progress` name symbols, `prose` when they name doc anchors. */
87
+ bound_kind: string;
88
+ cap: string;
89
+ no_progress: string;
90
+ success_stop: string;
91
+ checker: string;
92
+ human_gate: string;
93
+ terminal_vocabulary: string;
94
+ production_consumers: string[];
95
+ }
96
+
97
+ /**
98
+ * An exported helper a surface is meant to call.
99
+ *
100
+ * Separated from surfaces because the reach question only applies here: a
101
+ * surface is reached by being bound (a hook slot, a skill invocation), an
102
+ * instrument is reached by being called, and only the second is answerable by
103
+ * counting occurrences of a symbol.
104
+ */
105
+ export interface LoopInstrument {
106
+ id: string;
107
+ file: string;
108
+ role: string;
109
+ /** `experimental` exempts it from the reach requirement — with a deadline. */
110
+ status?: string | undefined;
111
+ /** ISO `YYYY-MM-DD`. Required under `status: experimental`; a past date reds. */
112
+ expires?: string | undefined;
113
+ note?: string | undefined;
114
+ }
115
+
116
+ export interface Inventory {
117
+ surfaces: LoopSurface[];
118
+ instruments: LoopInstrument[];
119
+ }
120
+
121
+ /** `path/to/file.ts:SYMBOL` or `path/to/file.md:ANCHOR`. */
122
+ const BOUND_TARGET = /^([A-Za-z0-9_./-]+\.(?:ts|js|md)):([A-Za-z_$][A-Za-z0-9_$-]*)$/;
123
+
124
+ function str(row: Record<string, unknown>, key: string): string {
125
+ const v = row[key];
126
+ return v === undefined || v === null ? '' : String(v);
127
+ }
128
+
129
+ export function readInventory(root: string): Inventory | null {
130
+ const p = path.join(root, INVENTORY_REL);
131
+ if (!fs.existsSync(p)) return null;
132
+ const doc = parseYaml(fs.readFileSync(p, 'utf-8')) as {
133
+ surfaces?: Record<string, Record<string, unknown>>;
134
+ instruments?: Record<string, Record<string, unknown>>;
135
+ } | null;
136
+
137
+ const surfaces: LoopSurface[] = [];
138
+ for (const [id, row] of Object.entries(doc?.surfaces ?? {})) {
139
+ surfaces.push({
140
+ id,
141
+ kind: str(row, 'kind'),
142
+ bound_kind: str(row, 'bound_kind'),
143
+ cap: str(row, 'cap'),
144
+ no_progress: str(row, 'no_progress'),
145
+ success_stop: str(row, 'success_stop'),
146
+ checker: str(row, 'checker'),
147
+ human_gate: str(row, 'human_gate'),
148
+ terminal_vocabulary: str(row, 'terminal_vocabulary'),
149
+ production_consumers: Array.isArray(row['production_consumers'])
150
+ ? (row['production_consumers'] as unknown[]).map(String)
151
+ : [],
152
+ });
153
+ }
154
+
155
+ const instruments: LoopInstrument[] = [];
156
+ for (const [id, row] of Object.entries(doc?.instruments ?? {})) {
157
+ instruments.push({
158
+ id,
159
+ file: str(row, 'file'),
160
+ role: str(row, 'role'),
161
+ status: row['status'] === undefined ? undefined : String(row['status']),
162
+ expires: row['expires'] === undefined ? undefined : String(row['expires']),
163
+ note: row['note'] === undefined ? undefined : String(row['note']),
164
+ });
165
+ }
166
+
167
+ surfaces.sort((a, b) => a.id.localeCompare(b.id));
168
+ instruments.sort((a, b) => a.id.localeCompare(b.id));
169
+ return { surfaces, instruments };
170
+ }
171
+
172
+ /** Every `.ts` / `.js` file under `src/`, sorted. Cached per root. */
173
+ const sourceCache = new Map<string, string[]>();
174
+
175
+ export function sourceFiles(root: string): string[] {
176
+ const hit = sourceCache.get(root);
177
+ if (hit !== undefined) return hit;
178
+ const out: string[] = [];
179
+ const walk = (dir: string): void => {
180
+ let entries: fs.Dirent[];
181
+ try {
182
+ entries = fs.readdirSync(dir, { withFileTypes: true });
183
+ } catch {
184
+ return;
185
+ }
186
+ for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
187
+ const full = path.join(dir, e.name);
188
+ if (e.isDirectory()) {
189
+ if (e.name === 'node_modules') continue;
190
+ walk(full);
191
+ } else if (e.name.endsWith('.ts') || e.name.endsWith('.js')) {
192
+ out.push(full);
193
+ }
194
+ }
195
+ };
196
+ walk(path.join(root, 'src'));
197
+ sourceCache.set(root, out);
198
+ return out;
199
+ }
200
+
201
+ /** Drop the file-list cache. The self-test builds throwaway trees. */
202
+ export function resetSourceCache(): void {
203
+ sourceCache.clear();
204
+ }
205
+
206
+ function escapeRe(s: string): string {
207
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
208
+ }
209
+
210
+ /**
211
+ * How many times `symbol` occurs anywhere under `src/`, declaration included.
212
+ *
213
+ * Word-bounded, so `stallSignal` does not match `stallSignalled`. Deliberately
214
+ * text-level: a symbol resolver would have to model re-exports, barrel files
215
+ * and type-only imports to beat this, and would then be a second
216
+ * implementation of the compiler. The loose version's failure mode is a count
217
+ * that is too HIGH — reporting a dead export as live, which is the quiet
218
+ * direction. It never manufactures an accusation.
219
+ */
220
+ export function occurrences(root: string, symbol: string): number {
221
+ if (symbol === '') return 0;
222
+ const re = new RegExp(`\\b${escapeRe(symbol)}\\b`, 'g');
223
+ let n = 0;
224
+ for (const f of sourceFiles(root)) {
225
+ n += (fs.readFileSync(f, 'utf-8').match(re) ?? []).length;
226
+ }
227
+ return n;
228
+ }
229
+
230
+ /** Does `file` DEFINE `symbol` (a `.ts` target) or merely contain it (a `.md` anchor)? */
231
+ export function resolvesTarget(root: string, file: string, token: string): boolean {
232
+ const p = path.join(root, file);
233
+ if (!fs.existsSync(p)) return false;
234
+ const text = fs.readFileSync(p, 'utf-8');
235
+ const esc = escapeRe(token);
236
+ if (file.endsWith('.md')) return new RegExp(`\\b${esc}\\b`).test(text);
237
+ const decl = new RegExp(`\\b(?:const|let|var|function|interface|type|class|enum)\\s+${esc}\\b`);
238
+ if (decl.test(text)) return true;
239
+ // Object keys, union members and class fields: `TOKEN:` / `TOKEN(` at line start.
240
+ return new RegExp(`^\\s*(?:readonly\\s+)?${esc}\\s*[:(]`, 'm').test(text);
241
+ }
242
+
243
+ export interface ReachFinding {
244
+ id: string;
245
+ file: string;
246
+ /** `dead` · `expired` · `no-expiry` · `bad-status` — why it reds. */
247
+ reason: string;
248
+ detail: string;
249
+ }
250
+
251
+ export interface ShapeFinding {
252
+ id: string;
253
+ reason: string;
254
+ detail: string;
255
+ }
256
+
257
+ export interface InventoryVerdict {
258
+ /** Surfaces with a missing field, or a bound that resolves to nothing. */
259
+ shape: ShapeFinding[];
260
+ /** Declared instruments with no production consumer and no valid exemption. */
261
+ reach: ReachFinding[];
262
+ /** Instruments reported live — the does-not-over-fire evidence. */
263
+ live: string[];
264
+ /** Instruments held by a valid, unexpired `experimental` exemption. */
265
+ exempt: string[];
266
+ /** Surfaces whose bound is documented prose, not a code symbol. Reported, never red. */
267
+ proseBound: string[];
268
+ }
269
+
270
+ /** An instrument is reached when it occurs more than once in `src/`. */
271
+ export const REACH_MINIMUM_OCCURRENCES = 2;
272
+
273
+ const BOUND_FIELDS = ['cap', 'no_progress'] as const;
274
+
275
+ function checkSurface(root: string, s: LoopSurface, shape: ShapeFinding[]): void {
276
+ for (const f of SURFACE_FIELDS) {
277
+ const v = s[f];
278
+ const empty = Array.isArray(v) ? v.length === 0 : String(v).trim() === '';
279
+ if (empty) shape.push({ id: s.id, reason: 'missing-field', detail: `\`${f}\` is empty` });
280
+ }
281
+ if (s.bound_kind !== 'code' && s.bound_kind !== 'prose') {
282
+ shape.push({
283
+ id: s.id,
284
+ reason: 'bad-bound-kind',
285
+ detail: `\`bound_kind: ${s.bound_kind}\` — must be \`code\` or \`prose\``,
286
+ });
287
+ }
288
+ for (const f of BOUND_FIELDS) {
289
+ const raw = String(s[f]).trim();
290
+ if (raw === '') continue;
291
+ const m = BOUND_TARGET.exec(raw);
292
+ if (m === null) {
293
+ shape.push({
294
+ id: s.id,
295
+ reason: 'not-a-target',
296
+ detail: `\`${f}: ${raw}\` is not \`path/to/file.{ts,js,md}:TOKEN\``,
297
+ });
298
+ continue;
299
+ }
300
+ const [, file, token] = m as unknown as [string, string, string];
301
+ // A markdown bound must SAY it is prose. Undeclared, it would read as a
302
+ // code bound in every report that aggregates this file.
303
+ if (file.endsWith('.md') && s.bound_kind !== 'prose') {
304
+ shape.push({
305
+ id: s.id,
306
+ reason: 'undeclared-prose-bound',
307
+ detail: `\`${f}\` points into ${file} but the row is \`bound_kind: ${s.bound_kind}\``,
308
+ });
309
+ }
310
+ if (!resolvesTarget(root, file, token)) {
311
+ shape.push({
312
+ id: s.id,
313
+ reason: 'unresolved-target',
314
+ detail: `\`${f}\` names ${token} in ${file}, which does not carry it`,
315
+ });
316
+ }
317
+ }
318
+ }
319
+
320
+ function checkInstrument(root: string, i: LoopInstrument, day: string, v: InventoryVerdict): void {
321
+ if (i.status !== undefined && i.status !== 'experimental') {
322
+ v.reach.push({
323
+ id: i.id,
324
+ file: i.file,
325
+ reason: 'bad-status',
326
+ detail: `\`status: ${i.status}\` is not a recognised exemption (only \`experimental\`)`,
327
+ });
328
+ return;
329
+ }
330
+ if (i.status === 'experimental') {
331
+ const exp = (i.expires ?? '').trim();
332
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(exp)) {
333
+ v.reach.push({
334
+ id: i.id,
335
+ file: i.file,
336
+ reason: 'no-expiry',
337
+ detail: 'an `experimental` exemption needs `expires: YYYY-MM-DD`',
338
+ });
339
+ return;
340
+ }
341
+ if (exp <= day) {
342
+ v.reach.push({
343
+ id: i.id,
344
+ file: i.file,
345
+ reason: 'expired',
346
+ detail: `the \`experimental\` exemption lapsed on ${exp} — decide, or move the date with a reason`,
347
+ });
348
+ return;
349
+ }
350
+ v.exempt.push(i.id);
351
+ return;
352
+ }
353
+ const n = occurrences(root, i.id);
354
+ if (n < REACH_MINIMUM_OCCURRENCES) {
355
+ v.reach.push({
356
+ id: i.id,
357
+ file: i.file,
358
+ reason: 'dead',
359
+ detail: `occurs ${String(n)}× in src/ (own declaration included) — nothing calls it`,
360
+ });
361
+ } else {
362
+ v.live.push(i.id);
363
+ }
364
+ }
365
+
366
+ export function inventoryVerdict(root: string, today = new Date()): InventoryVerdict {
367
+ const v: InventoryVerdict = { shape: [], reach: [], live: [], exempt: [], proseBound: [] };
368
+ const inv = readInventory(root);
369
+ if (inv === null) return v;
370
+
371
+ for (const s of inv.surfaces) {
372
+ checkSurface(root, s, v.shape);
373
+ if (s.bound_kind === 'prose') v.proseBound.push(s.id);
374
+ }
375
+ const day = today.toISOString().slice(0, 10);
376
+ for (const i of inv.instruments) checkInstrument(root, i, day, v);
377
+
378
+ v.live.sort();
379
+ v.exempt.sort();
380
+ v.proseBound.sort();
381
+ return v;
382
+ }