@event4u/agent-config 7.5.0 → 8.0.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 (428) hide show
  1. package/.claude-plugin/marketplace.json +8 -1
  2. package/AGENTS.md +9 -9
  3. package/CHANGELOG.md +325 -0
  4. package/README.md +35 -5
  5. package/dist/agent-src/commands/analytics/prune.md +1 -1
  6. package/dist/agent-src/commands/analytics/show.md +1 -1
  7. package/dist/agent-src/commands/analyze/decision.md +2 -2
  8. package/dist/agent-src/commands/analyze/incident.md +2 -2
  9. package/dist/agent-src/commands/analyze/near-miss.md +2 -2
  10. package/dist/agent-src/commands/analyze/postmortem.md +2 -2
  11. package/dist/agent-src/commands/analyze/premortem.md +2 -2
  12. package/dist/agent-src/commands/cost-report.md +25 -0
  13. package/dist/agent-src/commands/evals/analyze.json +30 -0
  14. package/dist/agent-src/commands/evals/mission-upgrade.json +30 -0
  15. package/dist/agent-src/commands/implement-ticket.md +1 -1
  16. package/dist/agent-src/commands/knowledge/forget.md +1 -1
  17. package/dist/agent-src/commands/knowledge/ingest.md +1 -1
  18. package/dist/agent-src/commands/knowledge/list.md +1 -1
  19. package/dist/agent-src/commands/memory/load.md +12 -11
  20. package/dist/agent-src/commands/memory/promote.md +30 -0
  21. package/dist/agent-src/commands/memory/propose.md +16 -2
  22. package/dist/agent-src/commands/mission/upgrade.md +1 -1
  23. package/dist/agent-src/commands/module/explore.md +11 -7
  24. package/dist/agent-src/commands/optimize-project.md +243 -0
  25. package/dist/agent-src/commands/optimize.md +5 -0
  26. package/dist/agent-src/commands/pr/create.md +37 -8
  27. package/dist/agent-src/commands/prediction-pool.md +2 -2
  28. package/dist/agent-src/commands/profile/activate.md +3 -3
  29. package/dist/agent-src/commands/profile/deactivate.md +2 -2
  30. package/dist/agent-src/commands/profile/show.md +3 -3
  31. package/dist/agent-src/commands/research/deep.md +5 -0
  32. package/dist/agent-src/commands/research/report.md +2 -1
  33. package/dist/agent-src/commands/roadmap/create.md +88 -10
  34. package/dist/agent-src/commands/roadmap/materialize.md +1 -1
  35. package/dist/agent-src/commands/roadmap/process-full.md +15 -2
  36. package/dist/agent-src/commands/roadmap/process-phase.md +18 -6
  37. package/dist/agent-src/commands/roadmap/process-step.md +4 -0
  38. package/dist/agent-src/commands/roadmap.md +1 -1
  39. package/dist/agent-src/commands/security-audit-config.md +1 -1
  40. package/dist/agent-src/commands/sync-agent-settings.md +1 -1
  41. package/dist/agent-src/commands/sync-gitignore/fix.md +42 -6
  42. package/dist/agent-src/commands/team-knowledge/bootstrap.md +106 -0
  43. package/dist/agent-src/commands/team-knowledge/consolidate.md +105 -0
  44. package/dist/agent-src/commands/team-knowledge.md +64 -0
  45. package/dist/agent-src/contexts/authority/commit-mechanics.md +8 -0
  46. package/dist/agent-src/contexts/authority/destructive-mechanics.md +11 -0
  47. package/dist/agent-src/contexts/authority/scope-mechanics.md +25 -0
  48. package/dist/agent-src/contexts/communication/rules-auto/reply-close-mechanics.md +43 -0
  49. package/dist/agent-src/contexts/contracts/agents-md-anatomy.md +1 -1
  50. package/dist/agent-src/contexts/contracts/artifact-engagement-flow.md +2 -2
  51. package/dist/agent-src/contexts/contracts/frugality-charter.md +1 -0
  52. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +14 -4
  53. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +11 -0
  54. package/dist/agent-src/contexts/execution/orchestration-telemetry.md +32 -8
  55. package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +107 -0
  56. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +71 -15
  57. package/dist/agent-src/contexts/execution/subagent-steering.md +41 -1
  58. package/dist/agent-src/contexts/override-system.md +1 -1
  59. package/dist/agent-src/personas/brand-strategist.md +0 -1
  60. package/dist/agent-src/personas/data-integrity.md +66 -0
  61. package/dist/agent-src/personas/design-director.md +0 -1
  62. package/dist/agent-src/personas/performance-engineer.md +66 -0
  63. package/dist/agent-src/personas/production-validator.md +73 -0
  64. package/dist/agent-src/rules/agent-authority.md +3 -4
  65. package/dist/agent-src/rules/analysis-skill-routing.md +3 -5
  66. package/dist/agent-src/rules/architecture.md +2 -4
  67. package/dist/agent-src/rules/artifact-drafting-protocol.md +35 -6
  68. package/dist/agent-src/rules/artifact-engagement-recording.md +2 -4
  69. package/dist/agent-src/rules/ask-when-uncertain.md +10 -14
  70. package/dist/agent-src/rules/augment-edit-discipline.md +3 -5
  71. package/dist/agent-src/rules/autonomous-execution.md +3 -5
  72. package/dist/agent-src/rules/brand-consistency.md +3 -5
  73. package/dist/agent-src/rules/brand-source-of-truth.md +3 -5
  74. package/dist/agent-src/rules/cli-output-handling.md +4 -6
  75. package/dist/agent-src/rules/command-suggestion-policy.md +3 -5
  76. package/dist/agent-src/rules/commit-conventions.md +3 -5
  77. package/dist/agent-src/rules/commit-policy.md +8 -10
  78. package/dist/agent-src/rules/content-quoting-floor.md +88 -0
  79. package/dist/agent-src/rules/context-hygiene.md +3 -5
  80. package/dist/agent-src/rules/copilot-routing.md +4 -6
  81. package/dist/agent-src/rules/decision-revisit-gate.md +120 -0
  82. package/dist/agent-src/rules/delegation-policy.md +10 -6
  83. package/dist/agent-src/rules/design-fidelity.md +3 -5
  84. package/dist/agent-src/rules/devcontainer-routing.md +4 -6
  85. package/dist/agent-src/rules/direct-answers.md +13 -17
  86. package/dist/agent-src/rules/docker-commands.md +3 -5
  87. package/dist/agent-src/rules/domain-adoption-policy.md +3 -5
  88. package/dist/agent-src/rules/domain-safety-disclaimer.md +3 -5
  89. package/dist/agent-src/rules/domain-safety-pii.md +3 -5
  90. package/dist/agent-src/rules/domain-safety-retention.md +3 -5
  91. package/dist/agent-src/rules/downstream-changes.md +3 -4
  92. package/dist/agent-src/rules/engineering-safety-floor.md +3 -5
  93. package/dist/agent-src/rules/external-reference-deep-dive.md +3 -5
  94. package/dist/agent-src/rules/fast-path-marker-visibility.md +3 -5
  95. package/dist/agent-src/rules/finance-safety-floor.md +3 -6
  96. package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +3 -5
  97. package/dist/agent-src/rules/git-history-discipline.md +3 -5
  98. package/dist/agent-src/rules/guidelines.md +2 -4
  99. package/dist/agent-src/rules/icon-consistency.md +3 -5
  100. package/dist/agent-src/rules/image-likeness-and-rights.md +3 -5
  101. package/dist/agent-src/rules/improve-before-implement.md +5 -5
  102. package/dist/agent-src/rules/invite-challenge.md +3 -5
  103. package/dist/agent-src/rules/language-and-tone.md +13 -15
  104. package/dist/agent-src/rules/laravel-routing.md +3 -5
  105. package/dist/agent-src/rules/laravel-translations.md +3 -5
  106. package/dist/agent-src/rules/legal-safety-floor.md +3 -5
  107. package/dist/agent-src/rules/lethal-trifecta-guard.md +3 -5
  108. package/dist/agent-src/rules/linked-projects-onboarding-gate.md +4 -7
  109. package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +3 -5
  110. package/dist/agent-src/rules/markdown-safe-codeblocks.md +2 -4
  111. package/dist/agent-src/rules/media-governance-routing.md +3 -5
  112. package/dist/agent-src/rules/media-sync-ground-truth.md +3 -5
  113. package/dist/agent-src/rules/minimal-safe-diff.md +5 -5
  114. package/dist/agent-src/rules/missing-tool-handling.md +3 -5
  115. package/dist/agent-src/rules/model-recommendation.md +4 -6
  116. package/dist/agent-src/rules/no-attribution-footers.md +3 -5
  117. package/dist/agent-src/rules/no-cheap-questions.md +5 -8
  118. package/dist/agent-src/rules/no-decorative-emojis-in-git-surfaces.md +3 -6
  119. package/dist/agent-src/rules/no-pr-progress-comments.md +3 -6
  120. package/dist/agent-src/rules/no-roadmap-references.md +3 -5
  121. package/dist/agent-src/rules/non-destructive-by-default.md +11 -13
  122. package/dist/agent-src/rules/notes-first-reasoning.md +3 -5
  123. package/dist/agent-src/rules/onboarding-gate.md +4 -6
  124. package/dist/agent-src/rules/output-discipline.md +3 -5
  125. package/dist/agent-src/rules/package-ci-checks.md +3 -5
  126. package/dist/agent-src/rules/persona-governance.md +2 -4
  127. package/dist/agent-src/rules/php-coding.md +3 -5
  128. package/dist/agent-src/rules/preservation-guard.md +2 -4
  129. package/dist/agent-src/rules/provider-lifecycle-discipline.md +3 -5
  130. package/dist/agent-src/rules/reviewer-awareness.md +5 -6
  131. package/dist/agent-src/rules/roadmap-ci-steps-policy.md +3 -5
  132. package/dist/agent-src/rules/roadmap-progress-sync.md +5 -5
  133. package/dist/agent-src/rules/role-mode-adherence.md +2 -4
  134. package/dist/agent-src/rules/rule-type-governance.md +3 -5
  135. package/dist/agent-src/rules/runtime-safety.md +2 -4
  136. package/dist/agent-src/rules/scope-control.md +10 -11
  137. package/dist/agent-src/rules/security-sensitive-stop.md +4 -5
  138. package/dist/agent-src/rules/size-enforcement.md +2 -4
  139. package/dist/agent-src/rules/skill-improvement-trigger.md +3 -5
  140. package/dist/agent-src/rules/skill-quality.md +3 -5
  141. package/dist/agent-src/rules/slash-command-routing-policy.md +3 -5
  142. package/dist/agent-src/rules/source-confidentiality.md +3 -5
  143. package/dist/agent-src/rules/source-discovery-gate.md +3 -5
  144. package/dist/agent-src/rules/source-of-truth.md +3 -5
  145. package/dist/agent-src/rules/strategy-safety-floor.md +3 -5
  146. package/dist/agent-src/rules/symfony-routing.md +4 -6
  147. package/dist/agent-src/rules/telegraph-speak.md +4 -5
  148. package/dist/agent-src/rules/think-before-action.md +7 -4
  149. package/dist/agent-src/rules/token-budget-discipline.md +29 -5
  150. package/dist/agent-src/rules/token-efficiency.md +3 -5
  151. package/dist/agent-src/rules/token-optimizer-maintenance.md +3 -5
  152. package/dist/agent-src/rules/tool-safety.md +2 -4
  153. package/dist/agent-src/rules/ui-audit-gate.md +3 -6
  154. package/dist/agent-src/rules/untrusted-input-defense.md +3 -5
  155. package/dist/agent-src/rules/upstream-proposal.md +4 -6
  156. package/dist/agent-src/rules/user-interaction.md +7 -5
  157. package/dist/agent-src/rules/user-interrupt-priority.md +17 -8
  158. package/dist/agent-src/rules/verify-before-complete.md +12 -22
  159. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +3 -3
  160. package/dist/agent-src/scripts/update_roadmap_progress.ts +197 -7
  161. package/dist/agent-src/skills/adr-create/SKILL.md +48 -42
  162. package/dist/agent-src/skills/agent-docs-writing/SKILL.md +18 -0
  163. package/dist/agent-src/skills/agent-security-review/SKILL.md +1 -1
  164. package/dist/agent-src/skills/ai-council/SKILL.md +14 -1
  165. package/dist/agent-src/skills/blameless-post-mortem/SKILL.md +0 -3
  166. package/dist/agent-src/skills/brand/SKILL.md +1 -1
  167. package/dist/agent-src/skills/brand-identity/SKILL.md +3 -3
  168. package/dist/agent-src/skills/brand-strategy/SKILL.md +1 -1
  169. package/dist/agent-src/skills/brand-to-tokens/SKILL.md +3 -3
  170. package/dist/agent-src/skills/canvas-design/SKILL.md +6 -0
  171. package/dist/agent-src/skills/check-refs/SKILL.md +7 -4
  172. package/dist/agent-src/skills/code-review/SKILL.md +1 -0
  173. package/dist/agent-src/skills/complexity-first-planning/SKILL.md +6 -5
  174. package/dist/agent-src/skills/condense-memory/SKILL.md +11 -11
  175. package/dist/agent-src/skills/context-document/SKILL.md +17 -0
  176. package/dist/agent-src/skills/corpus-grounding/SKILL.md +3 -3
  177. package/dist/agent-src/skills/decision-record/SKILL.md +6 -0
  178. package/dist/agent-src/skills/decision-review/SKILL.md +5 -3
  179. package/dist/agent-src/skills/deep-reading-analyst/SKILL.md +6 -1
  180. package/dist/agent-src/skills/design-intelligence/SKILL.md +5 -2
  181. package/dist/agent-src/skills/design-intelligence/references/design-languages.md +1 -1
  182. package/dist/agent-src/skills/design-tokens/SKILL.md +10 -4
  183. package/dist/agent-src/skills/design-variations/SKILL.md +127 -0
  184. package/dist/agent-src/skills/design-variations/evals/triggers.json +17 -0
  185. package/dist/agent-src/skills/emit-tickets/SKILL.md +4 -4
  186. package/dist/agent-src/skills/fe-design/SKILL.md +20 -0
  187. package/dist/agent-src/skills/html-deck/SKILL.md +123 -0
  188. package/dist/agent-src/skills/html-deck/evals/triggers.json +17 -0
  189. package/dist/agent-src/skills/html-deck/templates/deck-shell.html +77 -0
  190. package/dist/agent-src/skills/judge-code-quality/SKILL.md +1 -0
  191. package/dist/agent-src/skills/laravel/SKILL.md +0 -1
  192. package/dist/agent-src/skills/learning-to-rule-or-skill/SKILL.md +46 -0
  193. package/dist/agent-src/skills/lint-skills/SKILL.md +6 -6
  194. package/dist/agent-src/skills/md-language-check/SKILL.md +3 -3
  195. package/dist/agent-src/skills/memory-consolidation/SKILL.md +66 -1
  196. package/dist/agent-src/skills/migration-architect/SKILL.md +1 -0
  197. package/dist/agent-src/skills/module-detect-on-the-fly/SKILL.md +2 -2
  198. package/dist/agent-src/skills/performance-analysis/SKILL.md +2 -0
  199. package/dist/agent-src/skills/prediction-pool-optimizer/SKILL.md +4 -4
  200. package/dist/agent-src/skills/premortem/SKILL.md +0 -3
  201. package/dist/agent-src/skills/project-analysis-core/SKILL.md +16 -0
  202. package/dist/agent-src/skills/reasoning-orchestrator/SKILL.md +3 -1
  203. package/dist/agent-src/skills/release-comms/SKILL.md +46 -45
  204. package/dist/agent-src/skills/roadmap-management/SKILL.md +21 -0
  205. package/dist/agent-src/skills/roadmap-writing/SKILL.md +32 -2
  206. package/dist/agent-src/skills/root-cause-frameworks/SKILL.md +0 -3
  207. package/dist/agent-src/skills/security-audit/SKILL.md +1 -0
  208. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +4 -0
  209. package/dist/agent-src/skills/source-discovery/SKILL.md +22 -3
  210. package/dist/agent-src/skills/subagent-orchestration/SKILL.md +33 -14
  211. package/dist/agent-src/skills/subagent-orchestration/prompts/README.md +11 -0
  212. package/dist/agent-src/skills/subagent-orchestration/prompts/do-in-steps.md +8 -0
  213. package/dist/agent-src/skills/systematic-debugging/SKILL.md +29 -0
  214. package/dist/agent-src/skills/threat-modeling/SKILL.md +2 -2
  215. package/dist/agent-src/skills/typography-system/SKILL.md +2 -2
  216. package/dist/agent-src/skills/verify-completion-evidence/SKILL.md +2 -0
  217. package/dist/agent-src/skills/wireframe/SKILL.md +118 -0
  218. package/dist/agent-src/skills/wireframe/evals/triggers.json +17 -0
  219. package/dist/agent-src/templates/agent-settings.md +22 -1
  220. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  221. package/dist/agent-src/templates/command.md +2 -2
  222. package/dist/agent-src/templates/contexts/knowledge-pages.md +116 -0
  223. package/dist/agent-src/templates/hooks/pre-commit-frontmatter +1 -1
  224. package/dist/agent-src/templates/roadmaps.md +70 -2
  225. package/dist/agent-src/templates/rule.md +1 -1
  226. package/dist/agent-src/templates/scripts/README.md +3 -3
  227. package/dist/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +109 -0
  228. package/dist/agent-src/templates/tickets.md +8 -8
  229. package/dist/cli/commands/recordTriggerEval.test.js +1 -1
  230. package/dist/cli/commands/recordTriggerEval.test.js.map +1 -1
  231. package/dist/cli/initRouting.js +4 -2
  232. package/dist/cli/initRouting.js.map +1 -1
  233. package/dist/cli/registry.js +1 -0
  234. package/dist/cli/registry.js.map +1 -1
  235. package/dist/discovery/deprecation-report.md +1 -1
  236. package/dist/discovery/discovery-manifest.json +718 -240
  237. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  238. package/dist/discovery/discovery-manifest.summary.md +8 -8
  239. package/dist/discovery/orphan-report.md +1 -1
  240. package/dist/discovery/packs.json +43 -28
  241. package/dist/discovery/trust-report.md +12 -11
  242. package/dist/discovery/workspaces.json +29 -18
  243. package/dist/install/preflight.js +173 -0
  244. package/dist/install/preflight.js.map +1 -0
  245. package/dist/mcp/registry-manifest.json +4 -4
  246. package/dist/router.json +1 -1
  247. package/dist/server/routes/settings.js +4 -0
  248. package/dist/server/routes/settings.js.map +1 -1
  249. package/dist/server/schemas/settings.js +3 -1
  250. package/dist/server/schemas/settings.js.map +1 -1
  251. package/docs/CLAIMS.md +108 -0
  252. package/docs/MIGRATION.md +3 -3
  253. package/docs/architecture.md +5 -5
  254. package/docs/benchmark.md +133 -2
  255. package/docs/benchmark.pinned.yml +31 -0
  256. package/docs/capability-matrix.md +1 -0
  257. package/docs/catalog.md +70 -60
  258. package/docs/command-flows.md +15 -3
  259. package/docs/comparison.yaml +35 -0
  260. package/docs/contracts/agents-layout.md +148 -0
  261. package/docs/contracts/brand-token-consumption.md +5 -0
  262. package/docs/contracts/capability-boundary.md +5 -0
  263. package/docs/contracts/command-category-governance.md +5 -0
  264. package/docs/contracts/command-clusters.md +1 -0
  265. package/docs/contracts/command-surface-tiers.md +1 -1
  266. package/docs/contracts/conformance.md +84 -0
  267. package/docs/contracts/creative-pack-boundary.md +5 -0
  268. package/docs/contracts/discovery-manifest.schema.json +5 -4
  269. package/docs/contracts/file-ownership-matrix.json +4605 -985
  270. package/docs/contracts/governance-enforcement-projection.md +5 -0
  271. package/docs/contracts/gui-wizard.md +1 -1
  272. package/docs/contracts/hook-architecture-v1.md +2 -2
  273. package/docs/contracts/implement-ticket-flow.md +1 -1
  274. package/docs/contracts/kernel-membership.md +13 -0
  275. package/docs/contracts/mcp-tool-inventory.md +23 -23
  276. package/docs/contracts/no-runtime-boundary.md +6 -1
  277. package/docs/contracts/review-lens-schema.md +5 -0
  278. package/docs/contracts/rule-router.md +102 -8
  279. package/docs/contracts/skill-bundled-assets.md +1 -1
  280. package/docs/contracts/skill-dry-run.md +1 -1
  281. package/docs/contracts/subagent-boundary.md +5 -0
  282. package/docs/contracts/ticket-bundle-format.md +9 -4
  283. package/docs/contracts/tier-3-contrib-plugin.md +2 -2
  284. package/docs/contracts/write-engine.md +3 -0
  285. package/docs/customization.md +87 -15
  286. package/docs/decisions/ADR-107-legal-domain-pack-adoption.md +1 -1
  287. package/docs/decisions/ADR-109-subagent-v1-contract.md +246 -0
  288. package/docs/decisions/ADR-110-discipline-profile-resolution-locus.md +105 -0
  289. package/docs/decisions/INDEX.md +2 -0
  290. package/docs/distribution/awesome-list-submission.md +46 -0
  291. package/docs/distribution/launch-story-draft.md +58 -0
  292. package/docs/end-to-end-walkthroughs.md +2 -2
  293. package/docs/getting-started.md +2 -2
  294. package/docs/guidelines/agent-infra/asking-and-brevity-examples.md +24 -0
  295. package/docs/guidelines/agent-infra/corpus-grounding-authoring.md +1 -1
  296. package/docs/guidelines/agent-infra/linked-projects-onboarding-gate.md +2 -2
  297. package/docs/guidelines/agent-infra/missing-tool-handling.md +3 -3
  298. package/docs/guidelines/agent-infra/verify-before-complete-demos.md +2 -2
  299. package/docs/guidelines/augment-portability-patterns.md +1 -1
  300. package/docs/guidelines/code-clarity.md +19 -0
  301. package/docs/guidelines/design-antipatterns-triggers.json +2 -1
  302. package/docs/guidelines/design-antipatterns.md +10 -1
  303. package/docs/guidelines/design-modes.md +6 -0
  304. package/docs/guidelines/php/general.md +9 -1
  305. package/docs/guidelines/php/php-coding-patterns.md +7 -0
  306. package/docs/guides/cross-repo-retrieval.md +1 -1
  307. package/docs/guides/frontend-design-corpus-refresh.md +2 -2
  308. package/docs/guides/local-analytics.md +4 -4
  309. package/docs/guides/skill-discovery.md +1 -1
  310. package/docs/guides/skill-preview.md +1 -1
  311. package/docs/hook-payload-capture.md +6 -6
  312. package/docs/install-friction-report.md +66 -0
  313. package/docs/installation.md +1 -1
  314. package/docs/maintainers/optional-media-deps.md +50 -0
  315. package/docs/mcp-server.md +11 -12
  316. package/docs/media/proof-demo.cast +14 -0
  317. package/docs/media/proof-demo.gif +0 -0
  318. package/docs/migration/consumer-template-consumption-model.md +1 -1
  319. package/docs/migration/divergences/bench-stats-float-precision.md +1 -1
  320. package/docs/migration/node-floor.md +1 -1
  321. package/docs/migration/yaml-roundtrip-spike.md +1 -1
  322. package/docs/personas.md +16 -5
  323. package/docs/positioning-evidence.md +86 -0
  324. package/docs/proof.md +86 -0
  325. package/docs/skills-catalog.md +1 -1
  326. package/docs/troubleshooting.md +8 -8
  327. package/docs/wedge/production-validator/README.md +57 -0
  328. package/docs/wedge/production-validator/production-validator.md +56 -0
  329. package/docs/wizard.md +2 -2
  330. package/package.json +1 -1
  331. package/src/config/agent-settings.template.yml +59 -4
  332. package/src/config/agents-paths.yml +254 -0
  333. package/src/config/budgets.yml +16 -0
  334. package/src/config/discovery/packs.yml +1 -0
  335. package/src/config/discovery/unassigned-artefacts.yml +2 -0
  336. package/src/config/gitignore-block.txt +25 -0
  337. package/src/config/host-capabilities.yml +31 -0
  338. package/src/config/profiles/balanced.ini +4 -0
  339. package/src/config/profiles/full.ini +4 -0
  340. package/src/config/profiles/minimal.ini +4 -0
  341. package/src/scripts/_cli/cmd_conformance.ts +716 -0
  342. package/src/scripts/_cli/cmd_doctor.ts +23 -2
  343. package/src/scripts/_cli/cmd_fleet.ts +401 -0
  344. package/src/scripts/_cli/cmd_preflight.ts +159 -0
  345. package/src/scripts/_dispatch.bash +10 -0
  346. package/src/scripts/_lib/knowledge_events.ts +193 -0
  347. package/src/scripts/_lib/subagent_steering.ts +115 -3
  348. package/src/scripts/_lib/text_similarity.ts +75 -0
  349. package/src/scripts/ai-video/test-pipeline.sh +1 -1
  350. package/src/scripts/ai_council/clients.ts +19 -3
  351. package/src/scripts/append_contested.ts +112 -0
  352. package/src/scripts/audit_auto_rules.ts +11 -1
  353. package/src/scripts/audit_mcp_tools.ts +25 -11
  354. package/src/scripts/bench_ab_v2_run.ts +527 -40
  355. package/src/scripts/bench_ab_v2_stats.ts +6 -0
  356. package/src/scripts/bench_matrix.ts +266 -0
  357. package/src/scripts/bootstrap_knowledge.ts +267 -0
  358. package/src/scripts/build_discovery_manifest.ts +42 -2
  359. package/src/scripts/build_proof.ts +214 -0
  360. package/src/scripts/check_always_budget.ts +3 -0
  361. package/src/scripts/check_augment_description_cap.ts +2 -2
  362. package/src/scripts/check_claims.ts +270 -0
  363. package/src/scripts/check_comparison.ts +79 -0
  364. package/src/scripts/check_generated_artefact_headers.ts +109 -0
  365. package/src/scripts/check_generator_output_coverage.ts +142 -0
  366. package/src/scripts/check_gitignore_freshness.ts +127 -0
  367. package/src/scripts/check_knowledge_pages.ts +210 -0
  368. package/src/scripts/check_knowledge_sharing.ts +162 -0
  369. package/src/scripts/check_media_deps.ts +91 -0
  370. package/src/scripts/check_memory_contradiction.ts +280 -0
  371. package/src/scripts/check_memory_similarity.ts +134 -0
  372. package/src/scripts/check_quality_regression.ts +56 -10
  373. package/src/scripts/check_references.ts +3 -1
  374. package/src/scripts/check_release_pr_shape.ts +4 -0
  375. package/src/scripts/check_site_links.ts +135 -0
  376. package/src/scripts/check_skill_gaps.ts +99 -0
  377. package/src/scripts/check_token_quality_golden.ts +197 -11
  378. package/src/scripts/check_tracked_but_ignored.ts +67 -0
  379. package/src/scripts/cluster_near_miss_patterns.ts +0 -0
  380. package/src/scripts/compile_router.ts +30 -3
  381. package/src/scripts/condense.ts +350 -14
  382. package/src/scripts/consolidate_knowledge_events.ts +192 -0
  383. package/src/scripts/degrade_to_pointer.ts +136 -0
  384. package/src/scripts/design_slop_rules.test.ts +15 -0
  385. package/src/scripts/design_slop_rules.ts +28 -0
  386. package/src/scripts/emit_knowledge_event.ts +158 -0
  387. package/src/scripts/export_replay_corpus.ts +187 -0
  388. package/src/scripts/external_sources_denylist.json +7 -2
  389. package/src/scripts/fold_intake.ts +281 -0
  390. package/src/scripts/generate_capability_matrix.ts +9 -1
  391. package/src/scripts/generate_knowledge_index.ts +222 -0
  392. package/src/scripts/hook_manifest.yaml +26 -14
  393. package/src/scripts/hooks/dispatch_hook.ts +18 -0
  394. package/src/scripts/hot_context_hook.ts +372 -0
  395. package/src/scripts/install +47 -0
  396. package/src/scripts/install-hooks.sh +17 -0
  397. package/src/scripts/inventory_meta_layers.ts +11 -0
  398. package/src/scripts/janitor.ts +258 -0
  399. package/src/scripts/lint_agents_layout.ts +74 -15
  400. package/src/scripts/lint_bench_ab.ts +4 -1
  401. package/src/scripts/lint_featured_skills.ts +0 -0
  402. package/src/scripts/lint_framework_leakage_allowlist.json +26 -3
  403. package/src/scripts/lint_knowledge_scale.ts +250 -0
  404. package/src/scripts/lint_legal_pack.ts +67 -0
  405. package/src/scripts/lint_persona_governance.ts +8 -3
  406. package/src/scripts/lint_regression.ts +15 -0
  407. package/src/scripts/lint_roadmap_blockers.ts +202 -0
  408. package/src/scripts/lint_roadmap_complexity.ts +81 -2
  409. package/src/scripts/lint_subagent_determinism.ts +125 -0
  410. package/src/scripts/measure_augment_budget.ts +46 -6
  411. package/src/scripts/measure_patterns.ts +4 -3
  412. package/src/scripts/measure_skill_reduction.ts +6 -6
  413. package/src/scripts/migration_status.ts +6 -2
  414. package/src/scripts/mine_missing_guardrails.ts +189 -0
  415. package/src/scripts/parity/phase_gate.ts +1 -1
  416. package/src/scripts/parity/replay.ts +21 -0
  417. package/src/scripts/project_thin_rules.ts +120 -16
  418. package/src/scripts/release.ts +39 -1
  419. package/src/scripts/render_benchmark_composite.ts +378 -0
  420. package/src/scripts/roadmap_progress_hook.ts +101 -43
  421. package/src/scripts/schemas/skill.schema.json +27 -0
  422. package/src/scripts/schemas/subagent.schema.json +106 -0
  423. package/src/scripts/skill_linter.ts +1 -0
  424. package/src/scripts/smoke/kernel.sh +5 -4
  425. package/src/scripts/surface-tiers.yml +1 -0
  426. package/src/scripts/sync_gitignore.ts +4 -0
  427. package/src/scripts/update_skill_candidates.ts +172 -0
  428. package/src/scripts/validate_frontmatter.ts +1 -0
@@ -68,6 +68,70 @@ export function kernel_ids(): Set<string> {
68
68
  return new Set();
69
69
  }
70
70
 
71
+ /**
72
+ * id → workspaces for every non-kernel router entry (router.json schema v2,
73
+ * road-to-request-scoped-rule-load Phase 1). Rules absent from the map (or
74
+ * with an empty list) fail safe: they stay in scope.
75
+ */
76
+ export function rule_workspaces_map(): Map<string, string[]> {
77
+ const data = JSON.parse(fs.readFileSync(ROUTER, 'utf-8')) as Record<string, unknown>;
78
+ const map = new Map<string, string[]>();
79
+ for (const tier of ['tier_1', 'tier_2']) {
80
+ const entries = data[tier];
81
+ if (!Array.isArray(entries)) continue;
82
+ for (const e of entries) {
83
+ const obj = e as Record<string, unknown>;
84
+ const ws = Array.isArray(obj.workspaces) ? obj.workspaces.map((w) => String(w)) : [];
85
+ map.set(String(obj.id), ws);
86
+ }
87
+ }
88
+ return map;
89
+ }
90
+
91
+ /**
92
+ * Whether a rule id survives a workspace scope. Kernel always survives;
93
+ * `scope === null` = legacy-all. An out-of-scope non-kernel rule is dropped
94
+ * entirely — neither body nor pointer line (the pointer floor shrinks with
95
+ * consumer scoping).
96
+ */
97
+ export function id_in_scope(
98
+ rule_id: string,
99
+ scope: readonly string[] | null,
100
+ kernel: ReadonlySet<string>,
101
+ wsMap: ReadonlyMap<string, string[]>,
102
+ fallback_ws: readonly string[] = [],
103
+ ): boolean {
104
+ if (scope === null || kernel.has(rule_id)) {
105
+ return true;
106
+ }
107
+ // Router map first (schema v2); `type: manual` rules are reference-only
108
+ // and never emitted to the router — their frontmatter is the fallback.
109
+ const ws = wsMap.get(rule_id) ?? fallback_ws;
110
+ if (ws.length === 0) {
111
+ return true; // untagged / unknown → fail safe
112
+ }
113
+ return ws.some((w) => scope.includes(w));
114
+ }
115
+
116
+ /** `workspaces:` list parsed straight from a rule file's frontmatter. */
117
+ export function fm_workspaces(text: string): string[] {
118
+ const [fm] = split_frontmatter(text);
119
+ // Flow style: `workspaces: [a, b, c]`
120
+ const flow = /^workspaces:[ \t]*\[([^\]]*)\]/m.exec(fm);
121
+ if (flow) {
122
+ return (flow[1] as string)
123
+ .split(',')
124
+ .map((s) => s.trim().replace(/^["']|["']$/g, ''))
125
+ .filter((s) => s.length > 0);
126
+ }
127
+ // Block style; tolerate the list being the LAST frontmatter key.
128
+ const m = /^workspaces:[ \t]*\n((?:[ \t]+-[ \t]+.*(?:\n|$))+)/m.exec(fm);
129
+ if (!m) {
130
+ return [];
131
+ }
132
+ return [...(m[1] as string).matchAll(/-[ \t]+(\S+)/g)].map((x) => x[1] as string);
133
+ }
134
+
71
135
  /** Return [frontmatter_including_fences, body]. Empty fm if none. */
72
136
  export function split_frontmatter(text: string): [string, string] {
73
137
  if (text.startsWith('---\n')) {
@@ -123,12 +187,19 @@ export function thin_entry(rule_id: string, text: string): string {
123
187
  }
124
188
 
125
189
  /** Map {filename: thin_or_full_text} for every rule. Kernel stays full. */
126
- export function build_thin(rules_dir: string = RULES_SOURCE): Map<string, string> {
190
+ export function build_thin(
191
+ rules_dir: string = RULES_SOURCE,
192
+ scope: readonly string[] | null = null,
193
+ ): Map<string, string> {
127
194
  const kernel = kernel_ids();
195
+ const wsMap = scope !== null ? rule_workspaces_map() : new Map<string, string[]>();
128
196
  const out = new Map<string, string>();
129
197
  for (const p of _globSortedMd(rules_dir)) {
130
198
  const text = fs.readFileSync(p, 'utf-8');
131
199
  const stem = path.basename(p).replace(/\.md$/, '');
200
+ if (!id_in_scope(stem, scope, kernel, wsMap, fm_workspaces(text))) {
201
+ continue; // out of workspace scope — no body, no pointer line
202
+ }
132
203
  out.set(path.basename(p), kernel.has(stem) ? text : thin_entry(stem, text));
133
204
  }
134
205
  return out;
@@ -163,12 +234,24 @@ function _python_round1(x: number): number {
163
234
  return r / 10;
164
235
  }
165
236
 
166
- /** Eager vs thin token footprint for the rule layer. */
167
- export function measure(rules_dir: string = RULES_SOURCE): ThinMeasure {
237
+ /** Eager vs thin token footprint for the rule layer (scope applies to both arms). */
238
+ export function measure(
239
+ rules_dir: string = RULES_SOURCE,
240
+ scope: readonly string[] | null = null,
241
+ ): ThinMeasure {
168
242
  const kernel = kernel_ids();
169
- const mdPaths = _globSortedMd(rules_dir);
243
+ const wsMap = scope !== null ? rule_workspaces_map() : new Map<string, string[]>();
244
+ const mdPaths = _globSortedMd(rules_dir).filter((p) =>
245
+ id_in_scope(
246
+ path.basename(p).replace(/\.md$/, ''),
247
+ scope,
248
+ kernel,
249
+ wsMap,
250
+ fm_workspaces(fs.readFileSync(p, 'utf-8')),
251
+ ),
252
+ );
170
253
  const eager_blob = mdPaths.map((p) => fs.readFileSync(p, 'utf-8')).join('');
171
- const thin_blob = [...build_thin(rules_dir).values()].join('');
254
+ const thin_blob = [...build_thin(rules_dir, scope).values()].join('');
172
255
  const eager = token_count.measure(eager_blob);
173
256
  const thin = token_count.measure(thin_blob);
174
257
  const n = mdPaths.length;
@@ -198,9 +281,13 @@ export function measure(rules_dir: string = RULES_SOURCE): ThinMeasure {
198
281
  }
199
282
 
200
283
  /** Write thin rule files to `out_dir`; return the count written. */
201
- export function write_thin(out_dir: string, rules_dir: string = RULES_SOURCE): number {
284
+ export function write_thin(
285
+ out_dir: string,
286
+ rules_dir: string = RULES_SOURCE,
287
+ scope: readonly string[] | null = null,
288
+ ): number {
202
289
  fs.mkdirSync(out_dir, { recursive: true });
203
- const files = build_thin(rules_dir);
290
+ const files = build_thin(rules_dir, scope);
204
291
  for (const [name, text] of files) {
205
292
  fs.writeFileSync(path.join(out_dir, name), text, 'utf-8');
206
293
  }
@@ -293,22 +380,26 @@ interface ParsedArgs {
293
380
  measure: boolean;
294
381
  out: string | null;
295
382
  json: boolean;
383
+ workspaces: string[] | null;
296
384
  }
297
385
 
298
386
  // argparse `prog` defaults to basename(sys.argv[0]) → `project_thin_rules.py`.
299
- const _USAGE = 'usage: project_thin_rules.py [-h] [--measure] [--out OUT] [--json]\n';
387
+ const _USAGE =
388
+ 'usage: project_thin_rules.py [-h] [--measure] [--out OUT] [--json] [--workspaces WS]\n';
300
389
 
301
- // Full `-h` help block, byte-identical to argparse's rendering.
390
+ // Full `-h` help block.
302
391
  const _HELP =
303
392
  _USAGE +
304
393
  '\n' +
305
394
  'Thin-projection of the rule layer (lean-initial-context build-out, Phase 3.1).\n' +
306
395
  '\n' +
307
396
  'optional arguments:\n' +
308
- ' -h, --help show this help message and exit\n' +
309
- ' --measure print the eager-vs-thin token delta\n' +
310
- ' --out OUT write thin rule files to this dir\n' +
311
- ' --json\n';
397
+ ' -h, --help show this help message and exit\n' +
398
+ ' --measure print the eager-vs-thin token delta\n' +
399
+ ' --out OUT write thin rule files to this dir\n' +
400
+ ' --json\n' +
401
+ ' --workspaces WS comma-separated workspace scope (router.json v2 fields);\n' +
402
+ ' kernel always survives, out-of-scope rules drop entirely\n';
312
403
 
313
404
  function _argError(msg: string): never {
314
405
  process.stderr.write(_USAGE);
@@ -317,7 +408,7 @@ function _argError(msg: string): never {
317
408
  }
318
409
 
319
410
  function parse_args(argv: string[]): ParsedArgs {
320
- const out: ParsedArgs = { measure: false, out: null, json: false };
411
+ const out: ParsedArgs = { measure: false, out: null, json: false, workspaces: null };
321
412
  for (let i = 0; i < argv.length; i += 1) {
322
413
  const a = argv[i] as string;
323
414
  if (a === '-h' || a === '--help') {
@@ -336,6 +427,19 @@ function parse_args(argv: string[]): ParsedArgs {
336
427
  i += 1;
337
428
  } else if (a.startsWith('--out=')) {
338
429
  out.out = a.slice('--out='.length);
430
+ } else if (a === '--workspaces') {
431
+ const next = argv[i + 1];
432
+ if (next === undefined) {
433
+ _argError('argument --workspaces: expected one argument');
434
+ }
435
+ out.workspaces = next.split(',').map((s) => s.trim()).filter((s) => s.length > 0);
436
+ i += 1;
437
+ } else if (a.startsWith('--workspaces=')) {
438
+ out.workspaces = a
439
+ .slice('--workspaces='.length)
440
+ .split(',')
441
+ .map((s) => s.trim())
442
+ .filter((s) => s.length > 0);
339
443
  } else {
340
444
  _argError(`unrecognized arguments: ${a}`);
341
445
  }
@@ -357,12 +461,12 @@ export function main(argv: string[] | null = null): number {
357
461
  const args = parse_args(argv ?? process.argv.slice(2));
358
462
 
359
463
  if (args.out !== null) {
360
- const n = write_thin(args.out);
464
+ const n = write_thin(args.out, RULES_SOURCE, args.workspaces);
361
465
  process.stdout.write(`wrote ${n} thin rule files → ${args.out}\n`);
362
466
  return 0;
363
467
  }
364
468
 
365
- const m = measure();
469
+ const m = measure(RULES_SOURCE, args.workspaces);
366
470
  if (args.json) {
367
471
  // `saved_pct` is a Python float (round(..., 1)); wrap it so an
368
472
  // integer-valued result still renders as `N.0` (json.dumps parity).
@@ -151,6 +151,18 @@ class CalledProcessError extends Error {
151
151
  const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
152
152
  const PACKAGE_JSON = path.join(REPO_ROOT, 'package.json');
153
153
  const MARKETPLACE_JSON = path.join(REPO_ROOT, '.claude-plugin', 'marketplace.json');
154
+ // Source-of-truth project-settings template. Carries an `agent_config_version`
155
+ // pin that check_template_pin_drift requires to equal package.json.version. We
156
+ // bump the src twin here; `task release-prepare` (run right after the bump)
157
+ // regenerates the dist/agent-src/ twin from it.
158
+ const PROJECT_TEMPLATE = path.join(
159
+ REPO_ROOT,
160
+ 'src',
161
+ 'agent-src',
162
+ 'templates',
163
+ 'agents',
164
+ 'agent-project-settings.example.yml',
165
+ );
154
166
  const CHANGELOG = path.join(REPO_ROOT, 'CHANGELOG.md');
155
167
  const MAIN_BRANCH = 'main';
156
168
  const REMOTE = 'origin';
@@ -879,6 +891,29 @@ function set_marketplace_version(p: string, version: string): void {
879
891
  fs.writeFileSync(p, jsonDumpsIndent(data, 2) + '\n', 'utf-8');
880
892
  }
881
893
 
894
+ function set_template_pin(p: string, version: string): void {
895
+ // Rewrite the single `agent_config_version:` line in place, preserving the
896
+ // rest of the YAML byte-for-byte. Quoted value to match the existing pin
897
+ // style and check_template_pin_drift's tolerant parse.
898
+ const text = fs.readFileSync(p, 'utf-8');
899
+ const lines = text.split('\n');
900
+ let found = false;
901
+ for (let i = 0; i < lines.length; i += 1) {
902
+ if (/^\s*agent_config_version\s*:/.test(lines[i]!)) {
903
+ lines[i] = lines[i]!.replace(
904
+ /^(\s*agent_config_version\s*:\s*).*/,
905
+ `$1"${version}"`,
906
+ );
907
+ found = true;
908
+ break;
909
+ }
910
+ }
911
+ if (!found) {
912
+ die(`set_template_pin: no \`agent_config_version:\` line in ${p}`);
913
+ }
914
+ fs.writeFileSync(p, lines.join('\n'), 'utf-8');
915
+ }
916
+
882
917
  // ─── preflight ────────────────────────────────────────────────────────────────
883
918
 
884
919
  /**
@@ -982,6 +1017,7 @@ function print_preview(plan: Plan): void {
982
1017
  process.stdout.write('Files to change:\n');
983
1018
  process.stdout.write(` · ${path.relative(REPO_ROOT, PACKAGE_JSON)}\n`);
984
1019
  process.stdout.write(` · ${path.relative(REPO_ROOT, MARKETPLACE_JSON)}\n`);
1020
+ process.stdout.write(` · ${path.relative(REPO_ROOT, PROJECT_TEMPLATE)}\n`);
985
1021
  process.stdout.write(` · ${path.relative(REPO_ROOT, CHANGELOG)}\n`);
986
1022
  process.stdout.write(' · regenerated derived files via `task release-prepare`\n');
987
1023
  process.stdout.write(
@@ -1204,9 +1240,10 @@ function execute(
1204
1240
  if (resume && current_pkg === plan.target) {
1205
1241
  _step(2, total, `Files already at ${plan.target} — skip bump`);
1206
1242
  } else {
1207
- _step(2, total, 'Bump package.json + marketplace.json, prepend CHANGELOG');
1243
+ _step(2, total, 'Bump package.json + marketplace.json + template pin, prepend CHANGELOG');
1208
1244
  set_package_version(PACKAGE_JSON, plan.target);
1209
1245
  set_marketplace_version(MARKETPLACE_JSON, plan.target);
1246
+ set_template_pin(PROJECT_TEMPLATE, plan.target);
1210
1247
  prepend_changelog(CHANGELOG, plan.changelog_entry);
1211
1248
  }
1212
1249
 
@@ -1727,6 +1764,7 @@ export {
1727
1764
  prepend_changelog,
1728
1765
  set_package_version,
1729
1766
  set_marketplace_version,
1767
+ set_template_pin,
1730
1768
  resolve_bump,
1731
1769
  _detect_in_flight_target,
1732
1770
  print_preview,
@@ -0,0 +1,378 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Per-pinned-section renderer for the curated composite `docs/benchmark.md`
4
+ * (road-to-flow-learnings Phase 3).
5
+ *
6
+ * The composite is DELIBERATELY not a single-latest-report render (a
7
+ * single-report render would bury one host's finding — the in-file rationale
8
+ * stands). This script keeps that decision and removes only the manual
9
+ * table-editing step: each `<!-- pinned:<id> -->` … `<!-- /pinned:<id> -->`
10
+ * marker region in the doc is regenerated INDEPENDENTLY from ITS pinned
11
+ * report (`docs/benchmark.pinned.yml`); every line outside the markers —
12
+ * curated headings, verdict framing, findings prose, the history section —
13
+ * is preserved byte-for-byte.
14
+ *
15
+ * Section modes:
16
+ * - `honesty-labels` — the generated "Honesty labels" block.
17
+ * - `stats-body` — the full generated stats body (verdict bullets +
18
+ * interpretation + tables + methodology).
19
+ * - `compact-table` — one 3-row capability/discipline/tokens table for
20
+ * `package` vs `vanilla`.
21
+ * - `arm-cost-table` — the per-arm cost-factor table (lift per arm vs
22
+ * vanilla; labels curated in the manifest).
23
+ *
24
+ * Deterministic: same pinned inputs → byte-identical output. Pinned reports
25
+ * are operator-local (untracked); when a report is missing the script exits
26
+ * 0 with a skip notice so CI stays green — it can only verify where the
27
+ * pinned inputs exist.
28
+ *
29
+ * Exit: 0 ok/skip · 1 `--check` drift · 2 config error.
30
+ */
31
+
32
+ import * as fs from 'node:fs';
33
+ import * as path from 'node:path';
34
+ import process from 'node:process';
35
+ import { fileURLToPath, pathToFileURL } from 'node:url';
36
+
37
+ import { parse as parseYaml } from 'yaml';
38
+
39
+ import { analyse, compare, gate_verdict, mean_tokens_by_arm, to_markdown } from './bench_ab_v2_stats.js';
40
+
41
+ const _HERE = fileURLToPath(import.meta.url);
42
+ export const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
43
+ const DOC_PATH = path.join(REPO_ROOT, 'docs', 'benchmark.md');
44
+ const MANIFEST_PATH = path.join(REPO_ROOT, 'docs', 'benchmark.pinned.yml');
45
+
46
+ type Dict = Record<string, unknown>;
47
+
48
+ export class CompositeConfigError extends Error {}
49
+
50
+ export interface PinnedSection {
51
+ readonly id: string;
52
+ readonly report: string;
53
+ readonly mode: 'honesty-labels' | 'stats-body' | 'compact-table' | 'arm-cost-table';
54
+ /** arm-cost-table only: ordered arm list with curated labels. */
55
+ readonly arms?: ReadonlyArray<{ id: string; label: string }>;
56
+ }
57
+
58
+ export function parseManifest(text: string): PinnedSection[] {
59
+ const raw = parseYaml(text) as Dict | null;
60
+ const sections = raw?.['sections'];
61
+ if (!Array.isArray(sections) || sections.length === 0) {
62
+ throw new CompositeConfigError('`sections:` must be a non-empty list');
63
+ }
64
+ const modes = new Set(['honesty-labels', 'stats-body', 'compact-table', 'arm-cost-table']);
65
+ return sections.map((s, i) => {
66
+ const e = s as Dict;
67
+ const id = String(e['id'] ?? '');
68
+ const report = String(e['report'] ?? '');
69
+ const mode = String(e['mode'] ?? '');
70
+ if (!id || !report || !modes.has(mode)) {
71
+ throw new CompositeConfigError(`sections[${i}] needs id, report, and a valid mode`);
72
+ }
73
+ const armsRaw = e['arms'];
74
+ let arms: Array<{ id: string; label: string }> | undefined;
75
+ if (armsRaw !== undefined) {
76
+ if (!Array.isArray(armsRaw)) {
77
+ throw new CompositeConfigError(`sections[${i}].arms must be a list`);
78
+ }
79
+ arms = armsRaw.map((a) => {
80
+ const ar = a as Dict;
81
+ return { id: String(ar['id'] ?? ''), label: String(ar['label'] ?? '') };
82
+ });
83
+ }
84
+ return { id, report, mode: mode as PinnedSection['mode'], ...(arms ? { arms } : {}) };
85
+ });
86
+ }
87
+
88
+ // ---------------------------------------------------------------------------
89
+ // Section renderers — every value comes from the section's OWN pinned report.
90
+ // ---------------------------------------------------------------------------
91
+
92
+ function loadReport(reportPath: string): { analysis: Dict; payload: Dict } {
93
+ const payload = JSON.parse(fs.readFileSync(reportPath, 'utf-8')) as Dict;
94
+ const analysis = analyse(payload);
95
+ // Mirror the stats CLI: the gate verdict is merged before rendering.
96
+ analysis['gate'] = gate_verdict(analysis);
97
+ return { analysis, payload };
98
+ }
99
+
100
+ /** Unwrap the stats module's PyFloat wrapper (duck-typed — the class is private). */
101
+ function num(v: unknown): number {
102
+ if (v !== null && typeof v === 'object' && 'value' in (v as Dict)) {
103
+ return Number((v as { value: unknown }).value);
104
+ }
105
+ return Number(v);
106
+ }
107
+
108
+ /** Python float repr for p-values / effect sizes: `1` → `1.0`, `0.81` → `0.81`. */
109
+ function pyf(v: unknown): string {
110
+ const n = num(v);
111
+ if (!Number.isFinite(n)) return String(n);
112
+ return Number.isInteger(n) ? `${n}.0` : String(n);
113
+ }
114
+
115
+ /** Slice a generated full render between two headings (end exclusive of next `## `). */
116
+ function sliceFrom(md: string, startHeadingPrefix: string, includeHeading: boolean): string {
117
+ const lines = md.split('\n');
118
+ const start = lines.findIndex((l) => l.startsWith(startHeadingPrefix));
119
+ if (start < 0) {
120
+ throw new CompositeConfigError(`generated render lacks heading ${startHeadingPrefix}`);
121
+ }
122
+ return lines.slice(includeHeading ? start : start + 1).join('\n').trimEnd();
123
+ }
124
+
125
+ export function renderHonestyLabels(reportPath: string): string {
126
+ const { analysis, payload } = loadReport(reportPath);
127
+ const md = to_markdown(analysis, payload);
128
+ const lines = md.split('\n');
129
+ const start = lines.findIndex((l) => l.startsWith('## Honesty labels'));
130
+ const end = lines.findIndex((l, i) => i > start && l.startsWith('## '));
131
+ if (start < 0 || end < 0) {
132
+ throw new CompositeConfigError('generated render lacks the honesty-labels block');
133
+ }
134
+ // Body only — the curated doc owns the section heading.
135
+ return lines.slice(start + 1, end).join('\n').trim();
136
+ }
137
+
138
+ export function renderStatsBody(reportPath: string): string {
139
+ const { analysis, payload } = loadReport(reportPath);
140
+ const md = to_markdown(analysis, payload);
141
+ // Everything from the verdict bullets (line after `## Gate verdict`) to EOF:
142
+ // verdict bullets + interpretation + per-arm tables + status buckets +
143
+ // methodology. The curated doc supplies its own retitled section heading.
144
+ return sliceFrom(md, '## Gate verdict:', false).trim();
145
+ }
146
+
147
+ const fmtInt = (n: number): string => Math.round(n).toLocaleString('en-US');
148
+
149
+ function armRuns(rec: Dict, arm: string): Dict[] {
150
+ const runs = (rec['arms'] as Dict | undefined)?.[arm];
151
+ return Array.isArray(runs) ? (runs as Dict[]) : [];
152
+ }
153
+
154
+ function disciplineMean(records: Dict[], arm: string): number {
155
+ let sum = 0;
156
+ let n = 0;
157
+ for (const rec of records) {
158
+ for (const r of armRuns(rec, arm)) {
159
+ if (r['errored']) continue;
160
+ const d = Number(r['discipline_score'] ?? NaN);
161
+ if (Number.isFinite(d)) {
162
+ sum += d;
163
+ n += 1;
164
+ }
165
+ }
166
+ }
167
+ return n > 0 ? sum / n : 0;
168
+ }
169
+
170
+ function meanTokens(records: Dict[], arm: string): number {
171
+ const per = mean_tokens_by_arm(records, [arm]) as Dict;
172
+ return Number((per[arm] as Dict | undefined)?.['mean_tokens'] ?? 0);
173
+ }
174
+
175
+ export function renderCompactTable(reportPath: string): string {
176
+ const { payload } = loadReport(reportPath);
177
+ const records = (payload['records'] as Dict[] | undefined) ?? [];
178
+ const host = String(payload['model'] ?? 'unknown');
179
+ const cmp = compare(records, 'package', 'vanilla') as Dict;
180
+ const nPairs = Number(cmp['n_pairs'] ?? 0);
181
+ const cap = cmp['capability'] as Dict;
182
+ const disc = cmp['discipline'] as Dict;
183
+ const tv = meanTokens(records, 'vanilla');
184
+ const tp = meanTokens(records, 'package');
185
+ const capB = num(cap['rate_baseline']);
186
+ const capT = num(cap['rate_treatment']);
187
+ const discB = num(disc['mean_baseline']);
188
+ const discT = num(disc['mean_treatment']);
189
+ const dPP = Math.round((capT - capB) * 100);
190
+ const ratio = tv > 0 ? ` (~${Math.round(tp / tv)}×)` : '';
191
+ const sign = (v: number, f: (x: number) => string): string =>
192
+ v < 0 ? `−${f(Math.abs(v))}` : `+${f(v)}`;
193
+ return [
194
+ `| axis | vanilla | package | Δ | test |`,
195
+ `|---|---|---|---|---|`,
196
+ `| capability (pass-rate) | ${Math.round(capB * 100)}% | ${Math.round(capT * 100)}% | ` +
197
+ `${sign(dPP, (x) => String(x))}pp | McNemar p=${pyf(cap['mcnemar_p'])}, h=${pyf(cap['cohens_h'])} |`,
198
+ `| discipline (0–1) | ${discB.toFixed(3)} | ${discT.toFixed(3)} | ` +
199
+ `${sign(discT - discB, (x) => x.toFixed(3))} | Wilcoxon p=${pyf(disc['wilcoxon_p'])}, ` +
200
+ `rb=${pyf(disc['rank_biserial'])} (n≠0=${disc['n_nonzero']}) |`,
201
+ `| mean tokens/run | ${fmtInt(tv)} | ${fmtInt(tp)} | ${sign(tp - tv, fmtInt)}${ratio} | — |`,
202
+ ``,
203
+ `(host \`${host}\`, n=${nPairs} pairs — generated from the pinned report)`,
204
+ ].join('\n');
205
+ }
206
+
207
+ export function renderArmCostTable(
208
+ reportPath: string,
209
+ arms: ReadonlyArray<{ id: string; label: string }>,
210
+ ): string {
211
+ const { payload } = loadReport(reportPath);
212
+ const records = (payload['records'] as Dict[] | undefined) ?? [];
213
+ const baseTokens = meanTokens(records, 'vanilla');
214
+ const rows: string[] = [
215
+ `| arm | loaded content | injected chars | mean tokens/run | cost factor | mean discipline | lift vs vanilla |`,
216
+ `|---|---|---|---|---|---|---|`,
217
+ ];
218
+ for (const arm of arms) {
219
+ const t = meanTokens(records, arm.id);
220
+ const factor = baseTokens > 0 ? (t / baseTokens).toFixed(1) : '?';
221
+ const disc = disciplineMean(records, arm.id);
222
+ let injected = 0;
223
+ for (const rec of records) {
224
+ for (const r of armRuns(rec, arm.id)) {
225
+ injected = Math.max(injected, Number(r['injected_chars'] ?? 0));
226
+ }
227
+ }
228
+ if (arm.id === 'vanilla') {
229
+ rows.push(
230
+ `| \`vanilla\` | ${arm.label} | 0 | ${fmtInt(t)} | 1.0× | ${disc.toFixed(3)} | — |`,
231
+ );
232
+ continue;
233
+ }
234
+ const cmp = compare(records, arm.id, 'vanilla') as Dict;
235
+ const d = cmp['discipline'] as Dict;
236
+ const delta = num(d['mean_delta']);
237
+ const p = num(d['wilcoxon_p']);
238
+ const deltaStr = `${delta < 0 ? '−' : '+'}${Math.abs(delta).toFixed(3)}`;
239
+ const lift =
240
+ p < 0.05
241
+ ? `**${deltaStr} (p=${pyf(d['wilcoxon_p'])}, significant)**`
242
+ : `${deltaStr} (p=${pyf(d['wilcoxon_p'])}, **NULL**)`;
243
+ rows.push(
244
+ `| \`${arm.id}\` | ${arm.label} | ${fmtInt(injected)} | ${fmtInt(t)} | ` +
245
+ `**${factor}×** | ${disc.toFixed(3)} | ${lift} |`,
246
+ );
247
+ }
248
+ rows.push('');
249
+ rows.push('(generated from the pinned report — curated labels from `docs/benchmark.pinned.yml`)');
250
+ return rows.join('\n');
251
+ }
252
+
253
+ export function renderSection(section: PinnedSection, repoRoot: string = REPO_ROOT): string {
254
+ const reportPath = path.isAbsolute(section.report)
255
+ ? section.report
256
+ : path.join(repoRoot, section.report);
257
+ switch (section.mode) {
258
+ case 'honesty-labels':
259
+ return renderHonestyLabels(reportPath);
260
+ case 'stats-body':
261
+ return renderStatsBody(reportPath);
262
+ case 'compact-table':
263
+ return renderCompactTable(reportPath);
264
+ case 'arm-cost-table':
265
+ if (!section.arms || section.arms.length === 0) {
266
+ throw new CompositeConfigError(`section '${section.id}' (arm-cost-table) needs arms`);
267
+ }
268
+ return renderArmCostTable(reportPath, section.arms);
269
+ }
270
+ }
271
+
272
+ // ---------------------------------------------------------------------------
273
+ // Marker splice — pure, unit-tested.
274
+ // ---------------------------------------------------------------------------
275
+
276
+ export function markerBounds(id: string): [string, string] {
277
+ return [`<!-- pinned:${id} -->`, `<!-- /pinned:${id} -->`];
278
+ }
279
+
280
+ /**
281
+ * Replace every marker region with its rendered content; everything outside
282
+ * the markers is preserved byte-for-byte. Throws on a missing or unbalanced
283
+ * marker so drift never passes silently.
284
+ */
285
+ export function spliceMarkers(doc: string, contentById: ReadonlyMap<string, string>): string {
286
+ let out = doc;
287
+ for (const [id, content] of contentById) {
288
+ const [begin, end] = markerBounds(id);
289
+ const b = out.indexOf(begin);
290
+ const e = out.indexOf(end);
291
+ if (b < 0 || e < 0 || e < b) {
292
+ throw new CompositeConfigError(`marker pair for '${id}' missing or unbalanced in the doc`);
293
+ }
294
+ out = `${out.slice(0, b + begin.length)}\n${content}\n${out.slice(e)}`;
295
+ }
296
+ return out;
297
+ }
298
+
299
+ // ---------------------------------------------------------------------------
300
+ // CLI.
301
+ // ---------------------------------------------------------------------------
302
+
303
+ const USAGE = 'usage: render_benchmark_composite [-h] [--check]\n';
304
+
305
+ export function main(argv: string[] | null = null): number {
306
+ const args = argv !== null ? Array.from(argv) : process.argv.slice(2);
307
+ let check = false;
308
+ for (const a of args) {
309
+ if (a === '-h' || a === '--help') {
310
+ process.stdout.write(USAGE);
311
+ return 0;
312
+ }
313
+ if (a === '--check') {
314
+ check = true;
315
+ } else {
316
+ process.stderr.write(USAGE);
317
+ return 2;
318
+ }
319
+ }
320
+ let sections: PinnedSection[];
321
+ try {
322
+ sections = parseManifest(fs.readFileSync(MANIFEST_PATH, 'utf-8'));
323
+ } catch (exc) {
324
+ process.stderr.write(`render_benchmark_composite: error: ${(exc as Error).message}\n`);
325
+ return 2;
326
+ }
327
+ const missing = sections.filter(
328
+ (s) => !fs.existsSync(path.isAbsolute(s.report) ? s.report : path.join(REPO_ROOT, s.report)),
329
+ );
330
+ if (missing.length > 0) {
331
+ process.stdout.write(
332
+ `render_benchmark_composite: SKIP — pinned report(s) not present locally ` +
333
+ `(${missing.map((m) => m.id).join(', ')}); nothing rendered.\n`,
334
+ );
335
+ return 0;
336
+ }
337
+ const contentById = new Map<string, string>();
338
+ try {
339
+ for (const s of sections) {
340
+ contentById.set(s.id, renderSection(s));
341
+ }
342
+ const doc = fs.readFileSync(DOC_PATH, 'utf-8');
343
+ const next = spliceMarkers(doc, contentById);
344
+ if (check) {
345
+ if (next !== doc) {
346
+ process.stderr.write(
347
+ 'render_benchmark_composite: DRIFT — docs/benchmark.md pinned sections ' +
348
+ 'do not match their pinned reports; re-run without --check.\n',
349
+ );
350
+ return 1;
351
+ }
352
+ process.stdout.write('render_benchmark_composite: clean — pinned sections match.\n');
353
+ return 0;
354
+ }
355
+ if (next !== doc) {
356
+ fs.writeFileSync(DOC_PATH, next);
357
+ process.stdout.write(`render_benchmark_composite: wrote docs/benchmark.md (${contentById.size} section(s)).\n`);
358
+ } else {
359
+ process.stdout.write('render_benchmark_composite: no changes.\n');
360
+ }
361
+ return 0;
362
+ } catch (exc) {
363
+ if (exc instanceof CompositeConfigError) {
364
+ process.stderr.write(`render_benchmark_composite: error: ${exc.message}\n`);
365
+ return 2;
366
+ }
367
+ throw exc;
368
+ }
369
+ }
370
+
371
+ // --- CLI entry ---
372
+
373
+ const _isCliEntry =
374
+ process.argv[1] !== undefined &&
375
+ import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href;
376
+ if (_isCliEntry || process.argv[1] === _HERE) {
377
+ process.exitCode = main(process.argv.slice(2));
378
+ }