@event4u/agent-config 16.3.0 → 16.5.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 (532) 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 +178 -78
  5. package/README.md +2 -2
  6. package/agents/templates/.ai-council.yml.example +26 -8
  7. package/dist/agent-src/commands/analyze/inbox.md +10 -7
  8. package/dist/agent-src/commands/bug/investigate.md +1 -1
  9. package/dist/agent-src/commands/commit/in-chunks.md +40 -31
  10. package/dist/agent-src/commands/commit.md +72 -33
  11. package/dist/agent-src/commands/condense.md +2 -8
  12. package/dist/agent-src/commands/council/debate.md +11 -7
  13. package/dist/agent-src/commands/council/default.md +10 -9
  14. package/dist/agent-src/commands/council.md +1 -1
  15. package/dist/agent-src/commands/design.md +1 -1
  16. package/dist/agent-src/commands/estimate-ticket.md +1 -1
  17. package/dist/agent-src/commands/feature/explore.md +3 -3
  18. package/dist/agent-src/commands/feature/plan.md +1 -1
  19. package/dist/agent-src/commands/fix/ci.md +17 -3
  20. package/dist/agent-src/commands/fix/commit-messages.md +48 -23
  21. package/dist/agent-src/commands/fix/pr-comments.md +3 -6
  22. package/dist/agent-src/commands/implement-ticket.md +3 -3
  23. package/dist/agent-src/commands/memory/add.md +4 -16
  24. package/dist/agent-src/commands/memory/load.md +5 -6
  25. package/dist/agent-src/commands/memory/promote.md +3 -16
  26. package/dist/agent-src/commands/mission/upgrade.md +3 -2
  27. package/dist/agent-src/commands/optimize/agents-dir.md +2 -2
  28. package/dist/agent-src/commands/optimize/deep.md +4 -2
  29. package/dist/agent-src/commands/optimize/rtk.md +4 -26
  30. package/dist/agent-src/commands/pr/create/description-only.md +18 -15
  31. package/dist/agent-src/commands/pr/create.md +42 -34
  32. package/dist/agent-src/commands/pr/merge.md +136 -9
  33. package/dist/agent-src/commands/prepare-for-review.md +13 -11
  34. package/dist/agent-src/commands/project/analyze.md +7 -35
  35. package/dist/agent-src/commands/refine-ticket.md +1 -1
  36. package/dist/agent-src/commands/review/changes.md +14 -19
  37. package/dist/agent-src/commands/roadmap/next.md +10 -1
  38. package/dist/agent-src/commands/roadmap/process-full.md +52 -38
  39. package/dist/agent-src/commands/roadmap/resolve-blockers.md +64 -14
  40. package/dist/agent-src/commands/worktree/create.md +7 -3
  41. package/dist/agent-src/commands/worktree/verify.md +2 -1
  42. package/dist/agent-src/contexts/communication/rules-auto/think-before-action-mechanics.md +5 -3
  43. package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +7 -9
  44. package/dist/agent-src/contexts/contracts/command-suggestion-flow.md +8 -6
  45. package/dist/agent-src/contexts/contracts/frugality-charter.md +8 -5
  46. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +1 -1
  47. package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +0 -1
  48. package/dist/agent-src/contexts/execution/evidence-discipline.md +2 -2
  49. package/dist/agent-src/contexts/execution/non-interactive-contract.md +5 -8
  50. package/dist/agent-src/contexts/execution/rdp-gate.md +4 -4
  51. package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +9 -8
  52. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +67 -52
  53. package/dist/agent-src/contexts/execution/subagent-routing.md +8 -8
  54. package/dist/agent-src/contexts/execution/subagent-steering.md +6 -4
  55. package/dist/agent-src/contexts/execution/terminal-states.md +2 -2
  56. package/dist/agent-src/contexts/execution/toolchain-resolver.md +4 -10
  57. package/dist/agent-src/contexts/subagent-configuration.md +1 -1
  58. package/dist/agent-src/guidelines/agent-infra/active-remediation-mechanics.md +39 -0
  59. package/dist/agent-src/guidelines/agent-infra/context-hygiene-mechanics.md +1 -1
  60. package/dist/agent-src/guidelines/agent-infra/cross-source-consistency-mechanics.md +8 -10
  61. package/dist/agent-src/guidelines/agent-infra/frontier-reasoning-operating-profile.md +3 -2
  62. package/dist/agent-src/guidelines/agent-infra/layered-settings.md +13 -1
  63. package/dist/agent-src/guidelines/agent-infra/roadmap-progress-mechanics.md +1 -1
  64. package/dist/agent-src/guidelines/agent-infra/self-improvement-pipeline.md +10 -3
  65. package/dist/agent-src/guidelines/agent-infra/size-and-scope.md +36 -0
  66. package/dist/agent-src/guidelines/design-antipatterns.md +1 -1
  67. package/dist/agent-src/guidelines/php/git.md +15 -2
  68. package/dist/agent-src/rules/active-remediation.md +0 -1
  69. package/dist/agent-src/rules/analysis-skill-routing.md +0 -1
  70. package/dist/agent-src/rules/architecture.md +0 -1
  71. package/dist/agent-src/rules/artifact-drafting-protocol.md +0 -1
  72. package/dist/agent-src/rules/artifact-engagement-recording.md +0 -1
  73. package/dist/agent-src/rules/augment-edit-discipline.md +0 -1
  74. package/dist/agent-src/rules/autonomous-execution.md +0 -1
  75. package/dist/agent-src/rules/brand-consistency.md +0 -1
  76. package/dist/agent-src/rules/brand-source-of-truth.md +0 -1
  77. package/dist/agent-src/rules/broken-access-control.md +0 -1
  78. package/dist/agent-src/rules/cli-output-handling.md +0 -1
  79. package/dist/agent-src/rules/code-comment-discipline.md +0 -1
  80. package/dist/agent-src/rules/code-provenance.md +0 -1
  81. package/dist/agent-src/rules/command-suggestion-policy.md +0 -1
  82. package/dist/agent-src/rules/commit-conventions.md +1 -2
  83. package/dist/agent-src/rules/communication-through-line.md +1 -2
  84. package/dist/agent-src/rules/content-quoting-floor.md +1 -2
  85. package/dist/agent-src/rules/context-hygiene.md +5 -5
  86. package/dist/agent-src/rules/copilot-routing.md +1 -2
  87. package/dist/agent-src/rules/council-availability.md +4 -4
  88. package/dist/agent-src/rules/cross-source-consistency.md +4 -6
  89. package/dist/agent-src/rules/decision-revisit-gate.md +2 -3
  90. package/dist/agent-src/rules/delegation-policy.md +2 -3
  91. package/dist/agent-src/rules/design-fidelity.md +0 -1
  92. package/dist/agent-src/rules/design-review-after-ui-write.md +3 -3
  93. package/dist/agent-src/rules/devcontainer-routing.md +1 -2
  94. package/dist/agent-src/rules/doc-screenshot-hygiene.md +0 -1
  95. package/dist/agent-src/rules/docker-commands.md +0 -1
  96. package/dist/agent-src/rules/domain-adoption-policy.md +0 -1
  97. package/dist/agent-src/rules/domain-safety-disclaimer.md +0 -1
  98. package/dist/agent-src/rules/domain-safety-pii.md +0 -1
  99. package/dist/agent-src/rules/domain-safety-retention.md +0 -1
  100. package/dist/agent-src/rules/downstream-changes.md +0 -1
  101. package/dist/agent-src/rules/engineering-safety-floor.md +0 -1
  102. package/dist/agent-src/rules/external-code-graph-interop.md +0 -1
  103. package/dist/agent-src/rules/external-reference-deep-dive.md +0 -1
  104. package/dist/agent-src/rules/fast-path-marker-visibility.md +1 -2
  105. package/dist/agent-src/rules/finance-safety-floor.md +0 -1
  106. package/dist/agent-src/rules/fix-what-you-see.md +6 -32
  107. package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +0 -1
  108. package/dist/agent-src/rules/git-history-discipline.md +1 -2
  109. package/dist/agent-src/rules/guidelines.md +0 -1
  110. package/dist/agent-src/rules/history-discipline.md +0 -1
  111. package/dist/agent-src/rules/icon-consistency.md +0 -1
  112. package/dist/agent-src/rules/image-likeness-and-rights.md +0 -1
  113. package/dist/agent-src/rules/improve-before-implement.md +0 -1
  114. package/dist/agent-src/rules/invite-challenge.md +0 -1
  115. package/dist/agent-src/rules/laravel-routing.md +1 -2
  116. package/dist/agent-src/rules/laravel-translations.md +0 -1
  117. package/dist/agent-src/rules/legal-safety-floor.md +0 -1
  118. package/dist/agent-src/rules/lethal-trifecta-guard.md +0 -1
  119. package/dist/agent-src/rules/linked-projects-onboarding-gate.md +0 -1
  120. package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +0 -1
  121. package/dist/agent-src/rules/markdown-safe-codeblocks.md +0 -1
  122. package/dist/agent-src/rules/media-governance-routing.md +1 -2
  123. package/dist/agent-src/rules/media-sync-ground-truth.md +0 -1
  124. package/dist/agent-src/rules/minimal-safe-diff.md +0 -1
  125. package/dist/agent-src/rules/missing-skill-recovery.md +0 -1
  126. package/dist/agent-src/rules/missing-tool-handling.md +0 -1
  127. package/dist/agent-src/rules/model-recommendation.md +0 -1
  128. package/dist/agent-src/rules/neighbour-precedence.md +64 -0
  129. package/dist/agent-src/rules/no-attribution-footers.md +0 -1
  130. package/dist/agent-src/rules/no-decorative-emojis-in-git-surfaces.md +0 -1
  131. package/dist/agent-src/rules/no-pr-progress-comments.md +0 -1
  132. package/dist/agent-src/rules/no-roadmap-references.md +0 -1
  133. package/dist/agent-src/rules/notes-first-reasoning.md +0 -1
  134. package/dist/agent-src/rules/onboarding-gate.md +0 -1
  135. package/dist/agent-src/rules/output-discipline.md +0 -1
  136. package/dist/agent-src/rules/package-ci-checks.md +0 -1
  137. package/dist/agent-src/rules/persona-governance.md +2 -3
  138. package/dist/agent-src/rules/php-coding.md +0 -1
  139. package/dist/agent-src/rules/prefer-enums-over-literals.md +0 -1
  140. package/dist/agent-src/rules/preservation-guard.md +0 -1
  141. package/dist/agent-src/rules/provider-lifecycle-discipline.md +2 -3
  142. package/dist/agent-src/rules/question-not-instruction.md +0 -1
  143. package/dist/agent-src/rules/reviewer-awareness.md +0 -1
  144. package/dist/agent-src/rules/roadmap-ci-steps-policy.md +0 -1
  145. package/dist/agent-src/rules/roadmap-progress-sync.md +3 -6
  146. package/dist/agent-src/rules/role-mode-adherence.md +0 -1
  147. package/dist/agent-src/rules/rule-type-governance.md +0 -1
  148. package/dist/agent-src/rules/runtime-safety.md +0 -1
  149. package/dist/agent-src/rules/scale-discipline.md +0 -1
  150. package/dist/agent-src/rules/secret-vcs-guard.md +0 -1
  151. package/dist/agent-src/rules/security-sensitive-stop.md +0 -1
  152. package/dist/agent-src/rules/self-repair-loop.md +0 -1
  153. package/dist/agent-src/rules/senior-engineering-discipline.md +0 -1
  154. package/dist/agent-src/rules/session-canary.md +0 -1
  155. package/dist/agent-src/rules/settings-ask-protocol.md +5 -6
  156. package/dist/agent-src/rules/size-enforcement.md +0 -1
  157. package/dist/agent-src/rules/skill-improvement-trigger.md +2 -3
  158. package/dist/agent-src/rules/skill-quality.md +0 -1
  159. package/dist/agent-src/rules/slash-command-routing-policy.md +0 -1
  160. package/dist/agent-src/rules/source-confidentiality.md +0 -1
  161. package/dist/agent-src/rules/source-discovery-gate.md +0 -1
  162. package/dist/agent-src/rules/source-of-truth.md +0 -1
  163. package/dist/agent-src/rules/spreadsheet-source-quality.md +1 -2
  164. package/dist/agent-src/rules/strategy-safety-floor.md +0 -1
  165. package/dist/agent-src/rules/symfony-routing.md +1 -2
  166. package/dist/agent-src/rules/think-before-action.md +0 -1
  167. package/dist/agent-src/rules/token-budget-discipline.md +14 -48
  168. package/dist/agent-src/rules/token-efficiency.md +0 -1
  169. package/dist/agent-src/rules/token-optimizer-maintenance.md +0 -1
  170. package/dist/agent-src/rules/tool-safety.md +0 -1
  171. package/dist/agent-src/rules/ui-audit-gate.md +1 -2
  172. package/dist/agent-src/rules/untrusted-input-defense.md +0 -1
  173. package/dist/agent-src/rules/upstream-proposal.md +0 -1
  174. package/dist/agent-src/rules/user-interaction.md +0 -1
  175. package/dist/agent-src/rules/user-interrupt-priority.md +0 -1
  176. package/dist/agent-src/scripts/gate_budget.ts +47 -23
  177. package/dist/agent-src/scripts/gate_execute.ts +12 -4
  178. package/dist/agent-src/skills/accessibility-auditor/data/aria-patterns.csv +2 -0
  179. package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +3 -3
  180. package/dist/agent-src/skills/ai-council/SKILL.md +7 -4
  181. package/dist/agent-src/skills/ai-council/references/cost-and-redaction.md +21 -11
  182. package/dist/agent-src/skills/ai-council/references/procedure.md +12 -6
  183. package/dist/agent-src/skills/analysis-autonomous-mode/SKILL.md +1 -1
  184. package/dist/agent-src/skills/bug-analyzer/SKILL.md +1 -1
  185. package/dist/agent-src/skills/canvas-design/SKILL.md +4 -2
  186. package/dist/agent-src/skills/command-routing/SKILL.md +3 -4
  187. package/dist/agent-src/skills/conventional-commits-writing/SKILL.md +127 -139
  188. package/dist/agent-src/skills/corpus-grounding/ATTRIBUTION.md +39 -0
  189. package/dist/agent-src/skills/corpus-grounding/SKILL.md +8 -2
  190. package/dist/agent-src/skills/database/data/manifest.json +2 -2
  191. package/dist/agent-src/skills/database/data/query-tuning.csv +13 -13
  192. package/dist/agent-src/skills/design-intelligence/references/integration-mapping.md +13 -10
  193. package/dist/agent-src/skills/design-system-capture/SKILL.md +0 -3
  194. package/dist/agent-src/skills/estimate-ticket/SKILL.md +1 -1
  195. package/dist/agent-src/skills/fe-design/references/design-patterns.md +49 -2
  196. package/dist/agent-src/skills/feature-planning/SKILL.md +1 -1
  197. package/dist/agent-src/skills/frontend-render-security/SKILL.md +9 -0
  198. package/dist/agent-src/skills/git-workflow/SKILL.md +44 -44
  199. package/dist/agent-src/skills/git-workflow/references/branch-update.md +312 -0
  200. package/dist/agent-src/skills/git-workflow/references/commit-subject.md +205 -0
  201. package/dist/agent-src/skills/iconography/SKILL.md +4 -2
  202. package/dist/agent-src/skills/image-provider-routing/SKILL.md +16 -5
  203. package/dist/agent-src/skills/jira-integration/SKILL.md +3 -3
  204. package/dist/agent-src/skills/logo-generation/SKILL.md +12 -8
  205. package/dist/agent-src/skills/merge-conflicts/SKILL.md +10 -4
  206. package/dist/agent-src/skills/po-discovery/SKILL.md +1 -1
  207. package/dist/agent-src/skills/refine-ticket/SKILL.md +4 -4
  208. package/dist/agent-src/skills/roadmap-writing/SKILL.md +2 -2
  209. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +4 -2
  210. package/dist/agent-src/skills/source-discovery/SKILL.md +1 -1
  211. package/dist/agent-src/skills/svg-authoring/SKILL.md +318 -0
  212. package/dist/agent-src/skills/tailwind-engineer/SKILL.md +3 -1
  213. package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +1 -1
  214. package/dist/agent-src/skills/using-git-worktrees/SKILL.md +1 -1
  215. package/dist/agent-src/skills/verify-repair-loop/SKILL.md +2 -2
  216. package/dist/agent-src/skills/wireframe/SKILL.md +5 -2
  217. package/dist/agent-src/templates/AGENTS.md +1 -1
  218. package/dist/agent-src/templates/agent-settings.md +46 -129
  219. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +3 -3
  220. package/dist/agent-src/templates/command.md +2 -2
  221. package/dist/agent-src/templates/roadmaps.md +6 -1
  222. package/dist/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +0 -6
  223. package/dist/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +11 -7
  224. package/dist/agent-src/templates/scripts/work_engine/hooks/builtin/decision_trace.ts +3 -2
  225. package/dist/agent-src/templates/scripts/work_engine/hooks/builtin/memory_visibility.ts +2 -1
  226. package/dist/agent-src/templates/scripts/work_engine/hooks/settings.ts +0 -6
  227. package/dist/agent-src/templates/scripts/work_engine/scoring/decision_engine.ts +9 -12
  228. package/dist/agent-src/templates/scripts/work_engine/scoring/memory_visibility.ts +2 -1
  229. package/dist/agent-src/templates/skill.md +1 -1
  230. package/dist/cli/commands/uiRender.js +201 -4
  231. package/dist/cli/commands/uiRenderAsset.js +1308 -0
  232. package/dist/cli/commands/uiRenderAssetCompare.js +752 -0
  233. package/dist/cli/commands/uiRenderAssetGrid.js +265 -0
  234. package/dist/cli/commands/uiRenderAssetGrounds.js +323 -0
  235. package/dist/cli/commands/uiRenderAssetIsolation.js +85 -0
  236. package/dist/cli/commands/uiRenderAssetTypes.js +145 -0
  237. package/dist/cli/main.js +47 -1
  238. package/dist/cli/registry.js +2 -1
  239. package/dist/cli-delegate/check_update_banner.js +195 -119
  240. package/dist/cli-delegate/{chunk-S4UMZNES.js → chunk-4DBG5K7M.js} +5 -5
  241. package/dist/cli-delegate/{chunk-TRI6DQZ6.js → chunk-5GPULQBE.js} +60 -42
  242. package/dist/cli-delegate/{chunk-JQVYKIZA.js → chunk-6SRJTYJS.js} +33 -177
  243. package/dist/cli-delegate/chunk-6V5FMA3P.js +186 -0
  244. package/dist/cli-delegate/chunk-6YQC2HLF.js +415 -0
  245. package/dist/cli-delegate/{chunk-E4XIZDIB.js → chunk-7HKRE54K.js} +1 -64
  246. package/dist/cli-delegate/{chunk-7HQ52DHU.js → chunk-AL3RZ22D.js} +1 -1
  247. package/dist/cli-delegate/{chunk-DBH4AHDK.js → chunk-ED7KCMLB.js} +9 -11
  248. package/dist/cli-delegate/{chunk-NXEILF2K.js → chunk-F3QDG2GT.js} +481 -72
  249. package/dist/cli-delegate/{chunk-CQWX336L.js → chunk-G5VVIZRC.js} +3 -3
  250. package/dist/cli-delegate/{chunk-JY2I2DXV.js → chunk-H76PHZBI.js} +137 -60
  251. package/dist/cli-delegate/chunk-IAYTIIQW.js +67 -0
  252. package/dist/cli-delegate/{chunk-LIYTLH6O.js → chunk-KXXWJTDO.js} +5 -10
  253. package/dist/cli-delegate/{chunk-T7L2QK3K.js → chunk-LQOCRFWT.js} +1 -1
  254. package/dist/cli-delegate/{chunk-S4YWNRKO.js → chunk-MDPPKQCQ.js} +34 -1
  255. package/dist/cli-delegate/chunk-OAFSJBHH.js +272 -0
  256. package/dist/cli-delegate/{chunk-GKE7ZVFA.js → chunk-PWX7CKMD.js} +1 -1
  257. package/dist/cli-delegate/{chunk-NDJLNC6Q.js → chunk-T6BP34WI.js} +1 -0
  258. package/dist/cli-delegate/{chunk-RQNNW7IF.js → chunk-T7XSJSUQ.js} +433 -632
  259. package/dist/cli-delegate/{chunk-NQ7WT43P.js → chunk-TMWYUOK4.js} +75 -12
  260. package/dist/cli-delegate/{chunk-EEHDXSYD.js → chunk-WQWXGIWB.js} +12 -273
  261. package/dist/cli-delegate/{chunk-QLDHIPWR.js → chunk-XQTPXKFA.js} +1 -1
  262. package/dist/cli-delegate/cmd_analyze_session.js +223 -43
  263. package/dist/cli-delegate/cmd_brand_status.js +3 -2
  264. package/dist/cli-delegate/cmd_conformance.js +489 -238
  265. package/dist/cli-delegate/cmd_converge.js +3 -3
  266. package/dist/cli-delegate/cmd_doctor.js +15 -12
  267. package/dist/cli-delegate/cmd_doctor_neighbours.js +11 -4
  268. package/dist/cli-delegate/cmd_explain.js +43 -19
  269. package/dist/cli-delegate/cmd_export.js +10 -10
  270. package/dist/cli-delegate/cmd_git_convention.js +1851 -0
  271. package/dist/cli-delegate/cmd_handoff.js +40 -21
  272. package/dist/cli-delegate/cmd_mcp_available.js +3 -2
  273. package/dist/cli-delegate/cmd_migrate.js +3 -3
  274. package/dist/cli-delegate/cmd_packs_active.js +4 -3
  275. package/dist/cli-delegate/cmd_prune.js +10 -10
  276. package/dist/cli-delegate/cmd_route_audit.js +7 -7
  277. package/dist/cli-delegate/cmd_route_explain.js +5 -5
  278. package/dist/cli-delegate/cmd_session_recycle.js +9 -7
  279. package/dist/cli-delegate/cmd_settings_check.js +54 -7
  280. package/dist/cli-delegate/cmd_settings_get.js +18 -8
  281. package/dist/cli-delegate/cmd_settings_migrate.js +10 -10
  282. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  283. package/dist/cli-delegate/cmd_sync.js +10 -10
  284. package/dist/cli-delegate/cmd_uninstall.js +10 -10
  285. package/dist/cli-delegate/cmd_update.js +7 -7
  286. package/dist/cli-delegate/cmd_upgrade.js +7 -7
  287. package/dist/cli-delegate/cmd_validate.js +10 -10
  288. package/dist/cli-delegate/cmd_versions.js +5 -5
  289. package/dist/cli-delegate/gate_budget.js +1 -1
  290. package/dist/cli-delegate/gate_execute.js +2 -2
  291. package/dist/cli-delegate/roadmap_gates.js +2 -2
  292. package/dist/discovery/deprecation-report.md +1 -1
  293. package/dist/discovery/discovery-manifest.json +171 -107
  294. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  295. package/dist/discovery/discovery-manifest.summary.md +7 -7
  296. package/dist/discovery/dormancy-report.md +1 -1
  297. package/dist/discovery/orphan-report.md +1 -1
  298. package/dist/discovery/packs.json +41 -22
  299. package/dist/discovery/trust-report.md +3 -3
  300. package/dist/discovery/workspaces.json +16 -11
  301. package/dist/hooks/dispatch.js +2520 -1924
  302. package/dist/hooks/dispatch.sha256 +1 -1
  303. package/dist/hooks/svg_rules.chunk.cjs +1626 -0
  304. package/dist/hooks/svg_rules.chunk.cjs.sha256 +1 -0
  305. package/dist/install/install.mjs +462 -431
  306. package/dist/install/installThinLayer.js +40 -12
  307. package/dist/install/installedRuleLinks.js +105 -19
  308. package/dist/mcp/awesome-mcp-servers.row.md +1 -1
  309. package/dist/mcp/mcp-cloudflare-catalogue.json +2 -2
  310. package/dist/mcp/registry-manifest.json +5 -5
  311. package/dist/mcp/server.json +2 -2
  312. package/dist/mcp/server.mjs +3063 -2160
  313. package/dist/router.json +1 -1
  314. package/dist/scripts/_lib/agent_settings.js +15 -120
  315. package/dist/scripts/_lib/compile_time_toggles.js +12 -21
  316. package/dist/scripts/_lib/git_convention.js +232 -0
  317. package/dist/scripts/_lib/global_deploy_inventory.js +730 -0
  318. package/dist/scripts/_lib/installed_layer.js +344 -18
  319. package/dist/scripts/_lib/package_root.js +68 -0
  320. package/dist/scripts/_lib/rule_law_section.js +1 -1
  321. package/dist/scripts/_lib/scan_scope.js +105 -0
  322. package/dist/scripts/_lib/settings_removed_keys.js +168 -0
  323. package/dist/scripts/_lib/svg_dom.js +284 -0
  324. package/dist/scripts/_lib/svg_geometry.js +477 -0
  325. package/dist/scripts/_lib/svg_motion.js +179 -0
  326. package/dist/scripts/_lib/svg_rules.js +1337 -0
  327. package/dist/scripts/_lib/svg_urls.js +67 -0
  328. package/dist/scripts/_lib/thin_rules.js +80 -3
  329. package/dist/scripts/lint_svg_asset.js +324 -0
  330. package/dist/server/app.js +2 -1
  331. package/dist/server/gitKeysGate.js +105 -0
  332. package/dist/server/routes/settings.js +67 -8
  333. package/dist/server/routes/wizard.js +4 -0
  334. package/dist/server/schemas/settings.js +23 -81
  335. package/dist/shared/skillRanking.js +30 -4
  336. package/dist/ui/assets/{index-Cwq4d0hX.js → index-C3djEJah.js} +2 -2
  337. package/dist/ui/index.html +1 -1
  338. package/docs/MIGRATION.md +72 -1
  339. package/docs/contracts/settings-classes.md +35 -65
  340. package/docs/guidelines/agent-infra/active-remediation-mechanics.md +39 -0
  341. package/docs/guidelines/agent-infra/context-hygiene-mechanics.md +1 -1
  342. package/docs/guidelines/agent-infra/cross-source-consistency-mechanics.md +8 -10
  343. package/docs/guidelines/agent-infra/frontier-reasoning-operating-profile.md +3 -2
  344. package/docs/guidelines/agent-infra/layered-settings.md +13 -1
  345. package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +1 -1
  346. package/docs/guidelines/agent-infra/self-improvement-pipeline.md +10 -3
  347. package/docs/guidelines/agent-infra/size-and-scope.md +36 -0
  348. package/docs/guidelines/design-antipatterns.md +1 -1
  349. package/docs/guidelines/php/git.md +15 -2
  350. package/package.json +4 -3
  351. package/src/agent-src/scripts/gate_budget.ts +47 -23
  352. package/src/agent-src/scripts/gate_execute.ts +12 -4
  353. package/src/agent-src/templates/AGENTS.md +1 -1
  354. package/src/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +0 -6
  355. package/src/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +11 -7
  356. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/decision_trace.ts +3 -2
  357. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/memory_visibility.ts +2 -1
  358. package/src/agent-src/templates/scripts/work_engine/hooks/settings.ts +0 -6
  359. package/src/agent-src/templates/scripts/work_engine/scoring/decision_engine.ts +9 -12
  360. package/src/agent-src/templates/scripts/work_engine/scoring/memory_visibility.ts +2 -1
  361. package/src/config/agent-settings.template.yml +79 -312
  362. package/src/config/agents-paths.yml +16 -0
  363. package/src/config/assurance-capability-registry.json +7 -3
  364. package/src/config/ci-local-parity.yml +3 -0
  365. package/src/config/continuity-surface.json +3 -3
  366. package/src/config/discovery/packs.yml +17 -1
  367. package/src/config/evaluator-budgets.json +19 -6
  368. package/src/config/gate-coverage.yml +65 -23
  369. package/src/config/gate-violation-baselines.json +56 -16
  370. package/src/config/gitignore-block.txt +19 -0
  371. package/src/config/hook-bundle-budget.json +1 -1
  372. package/src/config/release-gate-locality.yml +2 -1
  373. package/src/config/routing-coverage-seed.json +55 -6
  374. package/src/config/rule-law-ceilings.json +12 -6
  375. package/src/config/rule-obligations.json +833 -0
  376. package/src/install/installThinLayer.ts +76 -10
  377. package/src/install/installedRuleLinks.ts +105 -17
  378. package/src/scripts/_cli/cmd_analyze_session.ts +9 -15
  379. package/src/scripts/_cli/cmd_conformance.ts +13 -2
  380. package/src/scripts/_cli/cmd_doctor.ts +10 -10
  381. package/src/scripts/_cli/cmd_doctor_neighbours.ts +133 -0
  382. package/src/scripts/_cli/cmd_explain.ts +72 -14
  383. package/src/scripts/_cli/cmd_git_convention.ts +637 -0
  384. package/src/scripts/_cli/cmd_settings_check.ts +66 -8
  385. package/src/scripts/_cli/cmd_settings_get.ts +18 -3
  386. package/src/scripts/_cli/doctor_execution.ts +72 -1
  387. package/src/scripts/_dispatch.bash +37 -2
  388. package/src/scripts/_lib/agent_settings.ts +15 -124
  389. package/src/scripts/_lib/blocked_by_marker.ts +1 -1
  390. package/src/scripts/_lib/compile_time_toggles.ts +12 -21
  391. package/src/scripts/_lib/dispatch_import_closure.ts +163 -0
  392. package/src/scripts/_lib/git_base_ref.ts +92 -0
  393. package/src/scripts/_lib/git_convention.ts +291 -0
  394. package/src/scripts/_lib/git_convention_carrier.ts +389 -0
  395. package/src/scripts/_lib/git_convention_grammar.ts +246 -0
  396. package/src/scripts/_lib/git_convention_measure.ts +344 -0
  397. package/src/scripts/_lib/global_deploy_inventory.ts +74 -0
  398. package/src/scripts/_lib/graph_feeder_record.ts +236 -13
  399. package/src/scripts/_lib/installed_layer.ts +448 -23
  400. package/src/scripts/_lib/knowledge_global.ts +14 -1
  401. package/src/scripts/_lib/module_reach.ts +456 -0
  402. package/src/scripts/_lib/neighbour_contradictions.ts +236 -0
  403. package/src/scripts/_lib/neighbour_tool_use.ts +8 -10
  404. package/src/scripts/_lib/ratification_artifact.ts +220 -1
  405. package/src/scripts/_lib/ratification_subject.ts +92 -0
  406. package/src/scripts/_lib/release_findings_ingest.ts +15 -7
  407. package/src/scripts/_lib/release_highlights.ts +5 -0
  408. package/src/scripts/_lib/rule_law_section.ts +1 -1
  409. package/src/scripts/_lib/rule_obligations.ts +205 -0
  410. package/src/scripts/_lib/self_repair.ts +1 -1
  411. package/src/scripts/_lib/settings_removed_keys.ts +173 -0
  412. package/src/scripts/_lib/svg_dom.ts +318 -0
  413. package/src/scripts/_lib/svg_geometry.ts +480 -0
  414. package/src/scripts/_lib/svg_motion.ts +208 -0
  415. package/src/scripts/_lib/svg_rules.ts +1453 -0
  416. package/src/scripts/_lib/svg_urls.ts +72 -0
  417. package/src/scripts/_lib/test_red_state.ts +5 -0
  418. package/src/scripts/_lib/thin_rules.ts +83 -3
  419. package/src/scripts/_lib/touched_file_quality.ts +142 -6
  420. package/src/scripts/_lib/verification_command.ts +59 -0
  421. package/src/scripts/_lib/verify_clause.ts +27 -17
  422. package/src/scripts/_lib/worker_budget.ts +1 -1
  423. package/src/scripts/adr/regenerate_index.ts +16 -1
  424. package/src/scripts/ai_council/config.ts +7 -7
  425. package/src/scripts/ai_council/modification_review.ts +100 -0
  426. package/src/scripts/ai_council/modification_review_questions.ts +90 -0
  427. package/src/scripts/ai_council/orchestrator.ts +5 -5
  428. package/src/scripts/ai_council/spend_gate.ts +18 -7
  429. package/src/scripts/ai_team/config.ts +11 -16
  430. package/src/scripts/audit_user_type_axis.ts +20 -8
  431. package/src/scripts/bench_ab_clone.ts +1 -2
  432. package/src/scripts/bench_concern_spawn_path.ts +129 -0
  433. package/src/scripts/bench_graph_feeder_latency.ts +584 -0
  434. package/src/scripts/check_agent_artifact_location.ts +36 -0
  435. package/src/scripts/check_branch_freshness.ts +147 -24
  436. package/src/scripts/check_enforcement_coverage.ts +130 -0
  437. package/src/scripts/check_evaluator_budgets.ts +4 -0
  438. package/src/scripts/check_finding_dispositions.ts +88 -25
  439. package/src/scripts/check_gate_coverage.ts +4 -3
  440. package/src/scripts/check_gate_reachability.ts +48 -1
  441. package/src/scripts/check_generator_sync.ts +48 -0
  442. package/src/scripts/check_hook_bundle_composition.ts +107 -0
  443. package/src/scripts/check_kernel_edit_ratified.ts +86 -6
  444. package/src/scripts/check_pack_size.ts +1 -1
  445. package/src/scripts/check_release_manifest_content.ts +189 -0
  446. package/src/scripts/check_standing_rule_delivery.ts +96 -0
  447. package/src/scripts/check_suppression_hygiene.ts +2 -5
  448. package/src/scripts/ci_settle.ts +42 -5
  449. package/src/scripts/classify_merge_risk.ts +320 -0
  450. package/src/scripts/closure_scan.ts +91 -1
  451. package/src/scripts/command_suggester/settings.ts +8 -108
  452. package/src/scripts/command_suggester/types.ts +4 -5
  453. package/src/scripts/consumer_matrix.ts +102 -0
  454. package/src/scripts/context_hygiene_hook.ts +13 -23
  455. package/src/scripts/cost/preflight.mjs +12 -1
  456. package/src/scripts/council_cli.ts +7 -7
  457. package/src/scripts/dispatch_r2_reviewer.ts +24 -11
  458. package/src/scripts/explain_run.ts +8 -13
  459. package/src/scripts/generate_git_convention_grammar.ts +127 -0
  460. package/src/scripts/generate_host_cost_table.ts +20 -3
  461. package/src/scripts/handoff_context_hook.ts +86 -37
  462. package/src/scripts/hook_manifest.json +1 -1
  463. package/src/scripts/hook_manifest.yaml +25 -6
  464. package/src/scripts/hooks/block_config_weakening.ts +48 -5
  465. package/src/scripts/hooks/block_plumbing_writes.ts +8 -4
  466. package/src/scripts/hooks/block_speaking_inbox_dir.ts +16 -1
  467. package/src/scripts/hooks/concern_registry.ts +2 -0
  468. package/src/scripts/hooks/delegation_nudge_hook.ts +2 -3
  469. package/src/scripts/hooks/design_pass_hook.ts +157 -3
  470. package/src/scripts/hooks/evidence_independence.ts +12 -4
  471. package/src/scripts/hooks/mcp_usage_observation_hook.ts +121 -0
  472. package/src/scripts/hooks/push_settle_hook.ts +4 -3
  473. package/src/scripts/hooks/rule_inject_hook.ts +44 -3
  474. package/src/scripts/hooks/state_io.ts +8 -4
  475. package/src/scripts/hooks/svg_obligation.ts +497 -0
  476. package/src/scripts/hooks/svg_rules_loader.ts +133 -0
  477. package/src/scripts/hooks/telemetry_usage_hook.ts +7 -46
  478. package/src/scripts/hooks/turn_end_gate_hook.ts +6 -3
  479. package/src/scripts/install-hooks.sh +42 -14
  480. package/src/scripts/install.ts +7 -31
  481. package/src/scripts/lint_blocker_citations.ts +198 -0
  482. package/src/scripts/lint_consolidation_lineage.ts +6 -3
  483. package/src/scripts/lint_design_quality.ts +60 -12
  484. package/src/scripts/lint_evidence_artifacts.ts +9 -0
  485. package/src/scripts/lint_hook_concern_budget.ts +10 -21
  486. package/src/scripts/lint_motion_authority_drift.ts +27 -0
  487. package/src/scripts/lint_pack_boundaries.ts +406 -28
  488. package/src/scripts/lint_roadmap_blockers.ts +282 -29
  489. package/src/scripts/lint_roadmap_complexity.ts +4 -2
  490. package/src/scripts/lint_rule_law_section.ts +151 -21
  491. package/src/scripts/lint_rule_skill_pack_reach.ts +17 -0
  492. package/src/scripts/lint_settings_classes.ts +1 -1
  493. package/src/scripts/lint_svg_asset.ts +365 -0
  494. package/src/scripts/lint_ticked_unmet_criteria.ts +165 -0
  495. package/src/scripts/lint_workflow_security.ts +12 -1
  496. package/src/scripts/measure_skill_ranker_baseline.ts +5 -0
  497. package/src/scripts/model_rule_injection.ts +3 -3
  498. package/src/scripts/onboarding_gate_hook.ts +7 -6
  499. package/src/scripts/print_required_checks.ts +15 -3
  500. package/src/scripts/probe_host_compliance.ts +33 -5
  501. package/src/scripts/probe_turnaround.ts +183 -3
  502. package/src/scripts/ratification_header.ts +77 -0
  503. package/src/scripts/refusal_envelope.ts +138 -0
  504. package/src/scripts/release_publication.ts +29 -3
  505. package/src/scripts/report_dispatch_import_closure.ts +76 -0
  506. package/src/scripts/report_installed_rule_links.ts +118 -1
  507. package/src/scripts/report_module_reach.ts +212 -0
  508. package/src/scripts/report_obligation_mechanism.ts +16 -18
  509. package/src/scripts/report_parked_blockers.ts +222 -0
  510. package/src/scripts/report_required_checks_drift.ts +196 -0
  511. package/src/scripts/report_skill_ranker_confusion.ts +103 -2
  512. package/src/scripts/report_standing_payload_by_host.ts +1 -1
  513. package/src/scripts/routing_doctor.ts +2 -2
  514. package/src/scripts/schemas/agent-settings.schema.json +17 -31
  515. package/src/scripts/schemas/command.schema.json +1 -1
  516. package/src/scripts/schemas/skill.schema.json +1 -1
  517. package/src/scripts/self_review_gate.ts +39 -2
  518. package/src/scripts/skill_tools/score_skill_relevance.ts +28 -19
  519. package/src/scripts/sync_pr_branch.ts +403 -132
  520. package/src/scripts/trigger_eval_grandfather.json +0 -16
  521. package/src/server/schemas/settings.ts +33 -177
  522. package/src/shared/skillRanking.ts +53 -4
  523. package/dist/cli-delegate/chunk-NYX3L2PZ.js +0 -99
  524. package/src/scripts/_lib/config_chain.ts +0 -477
  525. package/src/scripts/_lib/conformance_report.ts +0 -403
  526. package/src/scripts/_lib/eval_discrimination.ts +0 -95
  527. package/src/scripts/_lib/evaluator_promotion.ts +0 -225
  528. package/src/scripts/_lib/file_slicer.ts +0 -117
  529. package/src/scripts/_lib/legacy_boundary_map.ts +0 -228
  530. package/src/scripts/_lib/minimality_tiebreak.ts +0 -168
  531. package/src/scripts/_lib/overbuild_lens_contract.ts +0 -196
  532. package/src/scripts/_lib/role_split.ts +0 -338
@@ -0,0 +1,456 @@
1
+ /**
2
+ * Module-level reach — which `_lib` modules does nothing in production call?
3
+ *
4
+ * `check_gate_reachability.ts` already answers this question at TASK-target
5
+ * granularity (is a gate wired into `task ci`) and at EXPORT granularity for
6
+ * the declared loop instruments (`loop_surfaces.ts`). Neither axis sees a
7
+ * whole MODULE under `src/scripts/_lib/` that nothing imports and nothing
8
+ * runs by path — that is the gap this module closes.
9
+ *
10
+ * Two independent readings, by design, because they answer different
11
+ * questions and conflating them either over- or under-counts:
12
+ *
13
+ * - **Name occurrence** ({@link buildNameIndex}) — the GENEROUS reading. A
14
+ * module named only in a comment or a prose file counts as referenced.
15
+ * This is the floor: it can only under-count how dead a module is.
16
+ * - **Import-graph reach** ({@link computeReach}) — follows actual import
17
+ * edges (plus run-by-path targets) from every file OUTSIDE `_lib/`,
18
+ * treated as live by construction. A module imported only by another
19
+ * module that is itself unreached stays unreached — reachability does
20
+ * not propagate through a dead node.
21
+ *
22
+ * Both are regex-based line scans, matching every other gate in this family
23
+ * (`check_gate_reachability.ts`'s own `parseTargets`) rather than a full TS
24
+ * parse: the failure mode of a missed edge is "reports live code as dead",
25
+ * which a human catches in the one-reading-per-module report; a full parser
26
+ * would cost a dependency this family has deliberately avoided everywhere
27
+ * else.
28
+ */
29
+ import * as fs from 'node:fs';
30
+ import * as path from 'node:path';
31
+
32
+ export const LIB_DIR_REL = 'src/scripts/_lib';
33
+ export const SCRIPTS_DIR_REL = 'src/scripts';
34
+ export const ROADMAPS_DIR_REL = 'agents/roadmaps';
35
+ export const TASKFILES_DIR_REL = 'taskfiles';
36
+ export const ROOT_TASKFILE_REL = 'Taskfile.yml';
37
+ export const WORKFLOWS_DIR_REL = '.github/workflows';
38
+ export const DISPATCH_REL = 'src/scripts/_dispatch.bash';
39
+ export const CONFIG_DIR_REL = 'src/config';
40
+ export const CONTRACTS_TESTS_DIR_REL = 'tests/contracts';
41
+
42
+ /** Roots the GENEROUS name-occurrence reading scans — per the roadmap's own list. */
43
+ export const NAME_SCAN_ROOTS: readonly string[] = [
44
+ 'src',
45
+ TASKFILES_DIR_REL,
46
+ ROOT_TASKFILE_REL,
47
+ '.github',
48
+ 'package.json',
49
+ 'docs/contracts',
50
+ ];
51
+
52
+ const IGNORE_DIR_NAMES = new Set(['node_modules', '.git', 'dist', 'coverage']);
53
+ const TEXT_EXTS = new Set(['.ts', '.tsx', '.js', '.mjs', '.yml', '.yaml', '.json', '.md', '.bash', '.sh']);
54
+
55
+ function isTestFile(relPath: string): boolean {
56
+ return (
57
+ relPath.endsWith('.test.ts') ||
58
+ relPath.endsWith('.test.tsx') ||
59
+ relPath.endsWith('.d.ts') ||
60
+ relPath.startsWith('tests/')
61
+ );
62
+ }
63
+
64
+ /** Recursively list files under `root`-relative `rel`, filtered to text-ish extensions. */
65
+ function walk(root: string, rel: string, out: string[]): void {
66
+ const abs = path.join(root, rel);
67
+ let stat: fs.Stats;
68
+ try {
69
+ stat = fs.statSync(abs);
70
+ } catch {
71
+ return;
72
+ }
73
+ if (stat.isFile()) {
74
+ if (TEXT_EXTS.has(path.extname(rel))) out.push(rel);
75
+ return;
76
+ }
77
+ if (!stat.isDirectory()) return;
78
+ for (const entry of fs.readdirSync(abs).sort()) {
79
+ if (IGNORE_DIR_NAMES.has(entry)) continue;
80
+ walk(root, path.join(rel, entry), out);
81
+ }
82
+ }
83
+
84
+ function listFiles(root: string, rels: readonly string[]): string[] {
85
+ const out: string[] = [];
86
+ for (const r of rels) walk(root, r, out);
87
+ return out;
88
+ }
89
+
90
+ export interface LibModule {
91
+ /** Basename without extension — e.g. `module_reach`. */
92
+ name: string;
93
+ /** Repo-relative path — e.g. `src/scripts/_lib/module_reach.ts`. */
94
+ relPath: string;
95
+ lines: number;
96
+ }
97
+
98
+ /** Every module directly under `_lib`, excluding test files and declaration files. */
99
+ export function listLibModules(root: string): LibModule[] {
100
+ const dir = path.join(root, LIB_DIR_REL);
101
+ if (!fs.existsSync(dir)) return [];
102
+ const out: LibModule[] = [];
103
+ for (const entry of fs.readdirSync(dir).sort()) {
104
+ if (!entry.endsWith('.ts')) continue;
105
+ const relPath = path.posix.join(LIB_DIR_REL, entry);
106
+ if (isTestFile(relPath)) continue;
107
+ const abs = path.join(root, relPath);
108
+ if (!fs.statSync(abs).isFile()) continue;
109
+ const text = fs.readFileSync(abs, 'utf-8');
110
+ out.push({ name: entry.replace(/\.ts$/, ''), relPath, lines: text.split('\n').length });
111
+ }
112
+ return out.sort((a, b) => a.name.localeCompare(b.name));
113
+ }
114
+
115
+ const IMPORT_RE = /\bfrom\s+['"]([^'"]+)['"]/g;
116
+ const REQUIRE_RE = /\brequire\(\s*['"]([^'"]+)['"]\s*\)/g;
117
+ const DYNAMIC_IMPORT_RE = /\bimport\(\s*['"]([^'"]+)['"]\s*\)/g;
118
+
119
+ /** Resolve a relative import specifier to a repo-relative `.ts` file, or `null`. */
120
+ function resolveImport(root: string, importerRel: string, specifier: string): string | null {
121
+ if (!specifier.startsWith('.')) return null; // bare specifier — an npm package or node: builtin
122
+ const importerDir = path.dirname(path.join(root, importerRel));
123
+ const target = path.resolve(importerDir, specifier);
124
+ const candidates = [
125
+ `${target}.ts`,
126
+ `${target}.tsx`,
127
+ target.endsWith('.js') ? target.replace(/\.js$/, '.ts') : null,
128
+ path.join(target, 'index.ts'),
129
+ ].filter((c): c is string => c !== null);
130
+ for (const c of candidates) {
131
+ if (fs.existsSync(c) && fs.statSync(c).isFile()) {
132
+ return path.relative(root, c).split(path.sep).join('/');
133
+ }
134
+ }
135
+ return null;
136
+ }
137
+
138
+ /** Directed import edges: importer (non-test `.ts` file) → every `.ts` file it resolves to. */
139
+ export function buildImportGraph(root: string): Map<string, Set<string>> {
140
+ const graph = new Map<string, Set<string>>();
141
+ const files = listFiles(root, ['src']).filter(
142
+ (f) => (f.endsWith('.ts') || f.endsWith('.tsx')) && !isTestFile(f),
143
+ );
144
+ for (const rel of files) {
145
+ const text = fs.readFileSync(path.join(root, rel), 'utf-8');
146
+ const edges = new Set<string>();
147
+ for (const re of [IMPORT_RE, REQUIRE_RE, DYNAMIC_IMPORT_RE]) {
148
+ re.lastIndex = 0;
149
+ let m: RegExpExecArray | null;
150
+ while ((m = re.exec(text)) !== null) {
151
+ const resolved = resolveImport(root, rel, m[1] as string);
152
+ if (resolved !== null) edges.add(resolved);
153
+ }
154
+ }
155
+ graph.set(rel, edges);
156
+ }
157
+ return graph;
158
+ }
159
+
160
+ /** A literal `_lib` module path mentioned in a taskfile / workflow / the dispatcher, run by path. */
161
+ export function runByPathTargets(root: string): Set<string> {
162
+ const sources: string[] = [];
163
+ for (const rel of [ROOT_TASKFILE_REL, DISPATCH_REL]) {
164
+ const abs = path.join(root, rel);
165
+ if (fs.existsSync(abs)) sources.push(fs.readFileSync(abs, 'utf-8'));
166
+ }
167
+ const tfDir = path.join(root, TASKFILES_DIR_REL);
168
+ if (fs.existsSync(tfDir)) {
169
+ for (const f of fs.readdirSync(tfDir).sort()) {
170
+ if (f.endsWith('.yml') || f.endsWith('.yaml')) sources.push(fs.readFileSync(path.join(tfDir, f), 'utf-8'));
171
+ }
172
+ }
173
+ const wfDir = path.join(root, WORKFLOWS_DIR_REL);
174
+ if (fs.existsSync(wfDir)) {
175
+ for (const f of fs.readdirSync(wfDir).sort()) {
176
+ if (f.endsWith('.yml') || f.endsWith('.yaml')) sources.push(fs.readFileSync(path.join(wfDir, f), 'utf-8'));
177
+ }
178
+ }
179
+ const text = sources.join('\n');
180
+ const targets = new Set<string>();
181
+ // The dispatcher also reaches `_lib` via `$SCRIPT_DIR/_lib/<name>.ts` —
182
+ // `$SCRIPT_DIR` resolves to `src/scripts` at runtime, so the literal
183
+ // `src/scripts/` prefix is optional here.
184
+ const re = /(?:src\/scripts\/)?_lib\/([a-zA-Z0-9_]+)(?:\.ts)?/g;
185
+ let m: RegExpExecArray | null;
186
+ while ((m = re.exec(text)) !== null) {
187
+ targets.add(path.posix.join(LIB_DIR_REL, `${m[1] as string}.ts`));
188
+ }
189
+ return targets;
190
+ }
191
+
192
+ export interface ReachResult {
193
+ /** Every file (not only `_lib`) reached, starting from non-`_lib` files + run-by-path targets. */
194
+ reached: Set<string>;
195
+ }
196
+
197
+ /**
198
+ * BFS from every file OUTSIDE `_lib/` (treated as live, per the roadmap's
199
+ * "every top-level script as live" reading, generalised to every non-`_lib`
200
+ * file in `src/`) plus run-by-path targets. A `_lib` module reached only
201
+ * through another unreached `_lib` module never enters the visited set,
202
+ * which is what makes "eight that only other unreached modules import" a
203
+ * distinguishable outcome rather than an automatic pass.
204
+ */
205
+ export function computeReach(root: string): ReachResult {
206
+ const graph = buildImportGraph(root);
207
+ const libDir = `${LIB_DIR_REL}/`;
208
+ const roots: string[] = [...graph.keys()].filter((f) => !f.startsWith(libDir));
209
+ for (const t of runByPathTargets(root)) roots.push(t);
210
+
211
+ const visited = new Set<string>();
212
+ const stack = [...roots];
213
+ while (stack.length > 0) {
214
+ const n = stack.pop() as string;
215
+ if (visited.has(n)) continue;
216
+ visited.add(n);
217
+ for (const e of graph.get(n) ?? []) if (!visited.has(e)) stack.push(e);
218
+ }
219
+ return { reached: visited };
220
+ }
221
+
222
+ /** `\w` includes `_`, so a snake_case module name tokenises as one identifier. */
223
+ const TOKEN_RE = /[A-Za-z0-9_]+/g;
224
+
225
+ /**
226
+ * token → set of repo-relative files mentioning it.
227
+ *
228
+ * Scans {@link NAME_SCAN_ROOTS} plus `tests/` — the latter never counts
229
+ * toward `namedByProduction` ({@link namingFiles} filters test files out),
230
+ * it exists so {@link importingTests} can see which tests mention a module
231
+ * at all, including the `tests/contracts/` subset.
232
+ */
233
+ export function buildNameIndex(root: string): Map<string, Set<string>> {
234
+ const files = listFiles(root, [...NAME_SCAN_ROOTS, 'tests']);
235
+ const index = new Map<string, Set<string>>();
236
+ for (const rel of files) {
237
+ let text: string;
238
+ try {
239
+ text = fs.readFileSync(path.join(root, rel), 'utf-8');
240
+ } catch {
241
+ continue;
242
+ }
243
+ const seen = new Set<string>();
244
+ TOKEN_RE.lastIndex = 0;
245
+ let m: RegExpExecArray | null;
246
+ while ((m = TOKEN_RE.exec(text)) !== null) seen.add(m[0]);
247
+ for (const tok of seen) {
248
+ let set = index.get(tok);
249
+ if (set === undefined) {
250
+ set = new Set();
251
+ index.set(tok, set);
252
+ }
253
+ set.add(rel);
254
+ }
255
+ }
256
+ return index;
257
+ }
258
+
259
+ /** Non-self, non-test files (from the name index) mentioning `moduleName`. */
260
+ export function namingFiles(nameIndex: Map<string, Set<string>>, moduleName: string, selfRelPath: string): string[] {
261
+ const hits = nameIndex.get(moduleName) ?? new Set<string>();
262
+ return [...hits].filter((f) => f !== selfRelPath && !isTestFile(f)).sort();
263
+ }
264
+
265
+ /** Every `tests/**` file (from the name index) mentioning `moduleName`. */
266
+ export function importingTests(nameIndex: Map<string, Set<string>>, moduleName: string): string[] {
267
+ const hits = nameIndex.get(moduleName) ?? new Set<string>();
268
+ return [...hits].filter((f) => f.startsWith('tests/')).sort();
269
+ }
270
+
271
+ export function hasOwnEntryPoint(root: string, relPath: string): boolean {
272
+ const text = fs.readFileSync(path.join(root, relPath), 'utf-8');
273
+ return text.startsWith('#!/usr/bin/env') || /\bprocess\.argv\[1\]/.test(text) || /_isCliEntry/.test(text);
274
+ }
275
+
276
+ /** Config files under `src/config/` (json/yaml) mentioning the module by name or path. */
277
+ export function registryDeclared(root: string, moduleName: string): string[] {
278
+ const dir = path.join(root, CONFIG_DIR_REL);
279
+ if (!fs.existsSync(dir)) return [];
280
+ const out: string[] = [];
281
+ const files = fs.readdirSync(dir).filter((f) => f.endsWith('.json') || f.endsWith('.yaml') || f.endsWith('.yml'));
282
+ for (const f of files.sort()) {
283
+ const rel = path.posix.join(CONFIG_DIR_REL, f);
284
+ const text = fs.readFileSync(path.join(root, rel), 'utf-8');
285
+ if (new RegExp(`\\b${moduleName}\\b`).test(text)) out.push(rel);
286
+ }
287
+ return out;
288
+ }
289
+
290
+ export type RoadmapEstate = 'active' | 'later' | 'stubs';
291
+
292
+ export interface RoadmapStepHit {
293
+ roadmapRelPath: string;
294
+ estate: RoadmapEstate;
295
+ /** `' '` open, `'x'` done, `'~'` deferred, `'-'` cancelled, or `null` for prose outside any step. */
296
+ glyph: string | null;
297
+ stepLine: string;
298
+ }
299
+
300
+ function roadmapFiles(root: string): { relPath: string; estate: RoadmapEstate }[] {
301
+ const dir = path.join(root, ROADMAPS_DIR_REL);
302
+ if (!fs.existsSync(dir)) return [];
303
+ const out: { relPath: string; estate: RoadmapEstate }[] = [];
304
+ for (const f of fs.readdirSync(dir).sort()) {
305
+ if (f === 'template.md') continue;
306
+ const abs = path.join(dir, f);
307
+ if (!fs.statSync(abs).isFile()) continue;
308
+ if (!f.endsWith('.md')) continue;
309
+ out.push({ relPath: path.posix.join(ROADMAPS_DIR_REL, f), estate: 'active' });
310
+ }
311
+ for (const [sub, estate] of [
312
+ ['later', 'later'],
313
+ ['stubs', 'stubs'],
314
+ ] as const) {
315
+ const subDir = path.join(dir, sub);
316
+ if (!fs.existsSync(subDir)) continue;
317
+ for (const f of fs.readdirSync(subDir).sort()) {
318
+ if (!f.endsWith('.md')) continue;
319
+ out.push({ relPath: path.posix.join(ROADMAPS_DIR_REL, sub, f), estate });
320
+ }
321
+ }
322
+ return out;
323
+ }
324
+
325
+ const STEP_LINE_RE = /^- \[([ x~-])\]/;
326
+
327
+ /**
328
+ * Find where `moduleName` is named across the live roadmap estate
329
+ * (active, `later/`, `stubs/`) — per-step when inside a step block,
330
+ * `glyph: null` when the mention sits in prose outside any step.
331
+ */
332
+ export function findRoadmapMentions(root: string, moduleName: string): RoadmapStepHit[] {
333
+ const hits: RoadmapStepHit[] = [];
334
+ const nameRe = new RegExp(`\\b${moduleName}\\b`);
335
+ for (const { relPath, estate } of roadmapFiles(root)) {
336
+ const lines = fs.readFileSync(path.join(root, relPath), 'utf-8').split('\n');
337
+ let currentGlyph: string | null = null;
338
+ let currentStepLine = '';
339
+ for (const line of lines) {
340
+ const stepMatch = STEP_LINE_RE.exec(line);
341
+ if (stepMatch !== null) {
342
+ currentGlyph = stepMatch[1] as string;
343
+ currentStepLine = line.trim();
344
+ } else if (/^#{1,6}\s/.test(line) || line.trim() === '') {
345
+ // A heading OR a blank line ends the current step's block for our
346
+ // purposes (kept conservative: a step's cited paths/notes are
347
+ // usually within a few indented lines of its bullet, not past
348
+ // the next heading). Both branches reset — an R2 review found
349
+ // the blank-line case was a no-op here despite this comment
350
+ // already claiming it reset, which let a mention several lines
351
+ // below a CLOSED step, separated only by a blank line, still
352
+ // read as evidence for that step. `#{1,6}` matches
353
+ // `_lib/md_section.ts`'s own ATX-heading pattern; the prior
354
+ // `(#|##|###)` here missed level-4+ headings.
355
+ currentGlyph = null;
356
+ currentStepLine = '';
357
+ }
358
+ if (nameRe.test(line)) {
359
+ hits.push({ roadmapRelPath: relPath, estate, glyph: currentGlyph, stepLine: currentStepLine });
360
+ }
361
+ }
362
+ }
363
+ return hits;
364
+ }
365
+
366
+ export type ReachGroup = 'contract-test' | 'open-or-deferred-step' | 'named-outside-open-step' | 'named-in-none';
367
+
368
+ export interface ModuleFact {
369
+ name: string;
370
+ relPath: string;
371
+ lines: number;
372
+ importingTests: string[];
373
+ namedByProduction: boolean;
374
+ namingFiles: string[];
375
+ hasOwnEntryPoint: boolean;
376
+ registryDeclared: string[];
377
+ reachedByImportOrPath: boolean;
378
+ /** Only set when `namedByProduction` is false — the roadmap-based group. */
379
+ group: ReachGroup | null;
380
+ /** The step/file citation backing `group`, when roadmaps were available. */
381
+ groupEvidence: string | null;
382
+ roadmapsAvailable: boolean;
383
+ }
384
+
385
+ export interface ModuleReachReport {
386
+ modules: ModuleFact[];
387
+ roadmapsAvailable: boolean;
388
+ }
389
+
390
+ export function analyseModuleReach(root: string): ModuleReachReport {
391
+ const modules = listLibModules(root);
392
+ const nameIndex = buildNameIndex(root);
393
+ const reach = computeReach(root);
394
+ const roadmapsAvailable = fs.existsSync(path.join(root, ROADMAPS_DIR_REL));
395
+
396
+ const facts: ModuleFact[] = modules.map((mod) => {
397
+ const nf = namingFiles(nameIndex, mod.name, mod.relPath);
398
+ const namedByProduction = nf.length > 0;
399
+ let group: ReachGroup | null = null;
400
+ let groupEvidence: string | null = null;
401
+
402
+ if (!namedByProduction) {
403
+ const contractHit = importingTests(nameIndex, mod.name).find((f) => f.startsWith(CONTRACTS_TESTS_DIR_REL));
404
+ if (contractHit !== undefined) {
405
+ group = 'contract-test';
406
+ groupEvidence = contractHit;
407
+ } else if (roadmapsAvailable) {
408
+ const mentions = findRoadmapMentions(root, mod.name);
409
+ const openOrDeferred = mentions.find((h) => h.glyph === ' ' || h.glyph === '~');
410
+ if (openOrDeferred !== undefined) {
411
+ group = 'open-or-deferred-step';
412
+ groupEvidence = `${openOrDeferred.roadmapRelPath} (${openOrDeferred.estate}): ${openOrDeferred.stepLine}`;
413
+ } else if (mentions.length > 0) {
414
+ group = 'named-outside-open-step';
415
+ const m = mentions[0] as RoadmapStepHit;
416
+ groupEvidence = `${m.roadmapRelPath} (${m.estate})${m.stepLine !== '' ? `: ${m.stepLine}` : ''}`;
417
+ } else {
418
+ group = 'named-in-none';
419
+ groupEvidence = null;
420
+ }
421
+ } else {
422
+ // Published package: no roadmaps directory to check against.
423
+ group = 'named-in-none';
424
+ groupEvidence = 'no roadmaps directory present — roadmap groups skipped';
425
+ }
426
+ }
427
+
428
+ return {
429
+ name: mod.name,
430
+ relPath: mod.relPath,
431
+ lines: mod.lines,
432
+ importingTests: importingTests(nameIndex, mod.name),
433
+ namedByProduction,
434
+ namingFiles: nf,
435
+ hasOwnEntryPoint: hasOwnEntryPoint(root, mod.relPath),
436
+ registryDeclared: registryDeclared(root, mod.name),
437
+ reachedByImportOrPath: reach.reached.has(mod.relPath),
438
+ group,
439
+ groupEvidence,
440
+ roadmapsAvailable,
441
+ };
442
+ });
443
+
444
+ return { modules: facts, roadmapsAvailable };
445
+ }
446
+
447
+ export function groupCounts(modules: readonly ModuleFact[]): Record<ReachGroup, number> {
448
+ const counts: Record<ReachGroup, number> = {
449
+ 'contract-test': 0,
450
+ 'open-or-deferred-step': 0,
451
+ 'named-outside-open-step': 0,
452
+ 'named-in-none': 0,
453
+ };
454
+ for (const m of modules) if (m.group !== null) counts[m.group] += 1;
455
+ return counts;
456
+ }
@@ -0,0 +1,236 @@
1
+ /**
2
+ * Contradiction review between a project's instructions and its neighbours'
3
+ * (road-to-neighbours-that-pull-their-weight step 2.2).
4
+ *
5
+ * REPORT-ONLY. Nothing here edits, gates or suppresses an instruction — the
6
+ * output is a table a human reads. `neighbour-precedence` (step 2.1) is the
7
+ * rubric: the "which wins" column is its source order applied to the two
8
+ * sources, computed here, and the verdict column is one council seat's reading
9
+ * of whether the pair actually disagrees.
10
+ *
11
+ * NO STATIC DETECTOR DECIDES ANYTHING. A static contradiction detector was
12
+ * measured at 67 % false positives (`docs/contracts/rule-interactions.md`), so
13
+ * this module only FORMS candidate pairs, and only from strong signals: a
14
+ * shared subject word with opposite modality, or a skill name collision the
15
+ * census already found. Whether a candidate is a contradiction is the seat's
16
+ * call, never this file's.
17
+ *
18
+ * A neighbour body the shape scan refused (step 1.3) is never read here: the
19
+ * caller passes only scanned sources, and a refused body contributes no line.
20
+ */
21
+ import * as fs from 'node:fs';
22
+
23
+ /** Rank in `neighbour-precedence`'s source order — lower wins. */
24
+ export const RANK_PROJECT = 3;
25
+ export const RANK_SUITE = 4;
26
+ export const RANK_NEIGHBOUR_ALWAYS_ON = 6;
27
+ export const RANK_NEIGHBOUR_SKILL = 7;
28
+
29
+ const RANK_LABEL: Record<number, string> = {
30
+ [RANK_PROJECT]: 'project instruction file',
31
+ [RANK_SUITE]: "this suite's routed guidance",
32
+ [RANK_NEIGHBOUR_ALWAYS_ON]: "neighbour's always-on text",
33
+ [RANK_NEIGHBOUR_SKILL]: 'neighbour skill body (discoverable)',
34
+ };
35
+
36
+ export interface InstructionSource {
37
+ /** Where the text came from, as printed. */
38
+ label: string;
39
+ file: string;
40
+ rank: number;
41
+ }
42
+
43
+ export interface InstructionLine {
44
+ source: InstructionSource;
45
+ line: number;
46
+ text: string;
47
+ negative: boolean;
48
+ stems: Set<string>;
49
+ }
50
+
51
+ export interface CandidatePair {
52
+ kind: 'opposite-modality' | 'name-collision';
53
+ a: { label: string; rank: number; text: string };
54
+ b: { label: string; rank: number; text: string };
55
+ /** The shared subject word, or the colliding name. */
56
+ subject: string;
57
+ }
58
+
59
+ export type Verdict = 'contradiction' | 'compatible' | 'unclear';
60
+
61
+ const NEGATION = /\b(never|don't|do not|must not|mustn't|should not|shouldn't|avoid|stop)\b/iu;
62
+ const DIRECTIVE_WORD = /\b(always|must|should|never|don't|do not|avoid)\b/iu;
63
+ const NON_IMPERATIVE_START = new Set([
64
+ 'the', 'a', 'an', 'this', 'that', 'these', 'those', 'it', 'its', 'we', 'you', 'i', 'our',
65
+ 'they', 'there', 'when', 'if', 'see', 'for', 'in', 'on', 'as', 'by', 'with', 'from',
66
+ ]);
67
+ const STOP = new Set([
68
+ 'always', 'never', 'must', 'should', 'avoid', 'stop', 'don', 'with', 'without', 'that', 'this',
69
+ 'they', 'them', 'then', 'than', 'into', 'from', 'your', 'when', 'what', 'will', 'each', 'every',
70
+ 'only', 'also', 'here', 'there', 'have', 'been', 'more', 'some', 'such', 'like', 'just',
71
+ ]);
72
+
73
+ /** A crude stem — enough to meet `wait` and `waiting`, never a lemmatiser. */
74
+ export function stem(word: string): string {
75
+ let w = word.toLowerCase().replace(/[^a-z]/gu, '');
76
+ for (const suffix of ['ing', 'ed', 'es', 's']) {
77
+ if (w.length > suffix.length + 3 && w.endsWith(suffix)) {
78
+ w = w.slice(0, -suffix.length);
79
+ break;
80
+ }
81
+ }
82
+ return w;
83
+ }
84
+
85
+ /** Is this line phrased as an instruction at all? */
86
+ export function isDirective(text: string): boolean {
87
+ if (DIRECTIVE_WORD.test(text)) return true;
88
+ const first = (text.split(/\s+/u)[0] ?? '').toLowerCase().replace(/[^a-z']/gu, '');
89
+ return first.length > 0 && !NON_IMPERATIVE_START.has(first);
90
+ }
91
+
92
+ /** Directive lines of one markdown source, outside fences and frontmatter. */
93
+ export function extractInstructions(source: InstructionSource, body?: string): InstructionLine[] {
94
+ let text: string;
95
+ try {
96
+ text = body ?? fs.readFileSync(source.file, 'utf-8');
97
+ } catch {
98
+ return [];
99
+ }
100
+ const out: InstructionLine[] = [];
101
+ let fenced = false;
102
+ let frontmatter = false;
103
+ text.split('\n').forEach((raw, i) => {
104
+ const line = raw.trim();
105
+ if (i === 0 && line === '---') {
106
+ frontmatter = true;
107
+ return;
108
+ }
109
+ if (frontmatter) {
110
+ if (line === '---') frontmatter = false;
111
+ return;
112
+ }
113
+ if (line.startsWith('```') || line.startsWith('~~~')) {
114
+ fenced = !fenced;
115
+ return;
116
+ }
117
+ if (fenced || line === '' || line.startsWith('#') || line.startsWith('|') || line.startsWith('>')) return;
118
+ const clean = line.replace(/^([-*+]|\d+\.)\s+/u, '').replace(/[*_`]/gu, '');
119
+ if (clean.split(/\s+/u).length < 3 || !isDirective(clean)) return;
120
+ const stems = new Set(
121
+ clean
122
+ .split(/\s+/u)
123
+ .map(stem)
124
+ .filter((s) => s.length >= 4 && !STOP.has(s)),
125
+ );
126
+ out.push({ source, line: i + 1, text: clean, negative: NEGATION.test(clean), stems });
127
+ });
128
+ return out;
129
+ }
130
+
131
+ function where(l: InstructionLine): string {
132
+ return `${l.source.label}:${l.line}`;
133
+ }
134
+
135
+ /**
136
+ * Candidate pairs from opposite modality over a shared subject word.
137
+ *
138
+ * Only ACROSS ranks — two lines of one source rank are one author's
139
+ * contradiction, which is not what this review is for — and only where one
140
+ * side is negative and the other is not. One pair per line pair, naming the
141
+ * first shared stem in sorted order so the output is stable.
142
+ */
143
+ export function modalityPairs(lines: readonly InstructionLine[], cap = 20): CandidatePair[] {
144
+ const out: CandidatePair[] = [];
145
+ for (let i = 0; i < lines.length && out.length < cap; i += 1) {
146
+ for (let j = i + 1; j < lines.length && out.length < cap; j += 1) {
147
+ const x = lines[i] as InstructionLine;
148
+ const y = lines[j] as InstructionLine;
149
+ if (x.source.rank === y.source.rank || x.negative === y.negative) continue;
150
+ const shared = [...x.stems].filter((s) => y.stems.has(s)).sort();
151
+ if (shared.length === 0) continue;
152
+ const [hi, lo] = x.source.rank <= y.source.rank ? [x, y] : [y, x];
153
+ out.push({
154
+ kind: 'opposite-modality',
155
+ a: { label: where(hi), rank: hi.source.rank, text: hi.text },
156
+ b: { label: where(lo), rank: lo.source.rank, text: lo.text },
157
+ subject: shared[0] as string,
158
+ });
159
+ }
160
+ }
161
+ return out;
162
+ }
163
+
164
+ /** A neighbour skill that shares a name with one of ours — the census's `shadowed`. */
165
+ export function collisionPair(neighbourQualified: string, ourName: string): CandidatePair {
166
+ return {
167
+ kind: 'name-collision',
168
+ a: { label: `suite:${ourName}`, rank: RANK_SUITE, text: `this suite's skill \`${ourName}\`` },
169
+ b: {
170
+ label: neighbourQualified,
171
+ rank: RANK_NEIGHBOUR_SKILL,
172
+ text: `neighbour skill \`${neighbourQualified}\``,
173
+ },
174
+ subject: ourName,
175
+ };
176
+ }
177
+
178
+ /** "Which wins by 2.1" — the lower rank, named. */
179
+ export function winnerBy21(p: CandidatePair): string {
180
+ const w = p.a.rank <= p.b.rank ? p.a : p.b;
181
+ return `${w.label} (rank ${w.rank}, ${RANK_LABEL[w.rank] ?? 'unranked'})`;
182
+ }
183
+
184
+ /** The single-seat prompt. States no expected verdict (evaluator-independence). */
185
+ export function seatPrompt(pairs: readonly CandidatePair[], rubric: string): string {
186
+ const rows = pairs.map(
187
+ (p, i) => `PAIR ${i + 1} (${p.kind}, subject "${p.subject}")\n A [${p.a.label}]: ${p.a.text}\n B [${p.b.label}]: ${p.b.text}`,
188
+ );
189
+ return [
190
+ '# Neighbour contradiction review',
191
+ '',
192
+ 'For each pair, decide whether A and B give an agent instructions that cannot both be followed.',
193
+ 'Answer one line per pair, exactly `PAIR <n>: contradiction`, `PAIR <n>: compatible` or `PAIR <n>: unclear`,',
194
+ 'then at most one sentence of reason on the same line. The rubric for which source wins is below; you do not',
195
+ 'need to restate it.',
196
+ '',
197
+ '## Rubric — neighbour-precedence',
198
+ '',
199
+ rubric.trim(),
200
+ '',
201
+ '## Pairs',
202
+ '',
203
+ ...rows,
204
+ '',
205
+ ].join('\n');
206
+ }
207
+
208
+ /** Read `PAIR <n>: <verdict>` lines; a pair the seat did not answer is `unclear`. */
209
+ export function parseVerdicts(text: string, count: number): Verdict[] {
210
+ const out: Verdict[] = Array.from({ length: count }, () => 'unclear');
211
+ for (const m of text.matchAll(/PAIR\s+(\d+)\s*:\s*(contradiction|compatible|unclear)/giu)) {
212
+ const n = Number(m[1]) - 1;
213
+ if (n >= 0 && n < count) out[n] = (m[2] ?? 'unclear').toLowerCase() as Verdict;
214
+ }
215
+ return out;
216
+ }
217
+
218
+ export interface ReviewRow {
219
+ pair: string;
220
+ verdict: Verdict;
221
+ wins: string;
222
+ }
223
+
224
+ /** The table, from pairs and a seat's verdicts. */
225
+ export function reviewRows(pairs: readonly CandidatePair[], verdicts: readonly Verdict[]): ReviewRow[] {
226
+ return pairs.map((p, i) => ({
227
+ pair: `${p.a.label} "${p.a.text}" × ${p.b.label} "${p.b.text}"`,
228
+ verdict: verdicts[i] ?? 'unclear',
229
+ wins: winnerBy21(p),
230
+ }));
231
+ }
232
+
233
+ export function renderReview(rows: readonly ReviewRow[]): string[] {
234
+ if (rows.length === 0) return [' no candidate pairs — nothing to review'];
235
+ return [' pair | verdict | which wins by 2.1', ...rows.map((r) => ` ${r.pair} | ${r.verdict} | ${r.wins}`)];
236
+ }