@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
@@ -32,7 +32,18 @@
32
32
  * Cost controls inherited: --model pin (sonnet), --max-budget-usd cap. Cheap-by-
33
33
  * construction: the v2 fixtures are tiny, so per-run tokens are far below the v1
34
34
  * big-repo tasks.
35
+ *
36
+ * Checkpoint / resume (2026-07): every completed run (task × arm × seed) is
37
+ * appended to a JSONL checkpoint under reports/ab-v2/checkpoints/, keyed by a
38
+ * hash of the run configuration. A killed sweep restarted with the SAME flags
39
+ * resumes from the checkpoint instead of re-spending the completed runs (a
40
+ * 117/180 kill previously burned the whole sweep — results lived only in
41
+ * memory until the final report write). The checkpoint is deleted after the
42
+ * paired report is written, so a finished sweep leaves no residue. Opt out
43
+ * with --no-checkpoint; force a from-scratch run with --fresh.
35
44
  */
45
+ import { spawnSync } from 'node:child_process';
46
+ import { createHash } from 'node:crypto';
36
47
  import * as fs from 'node:fs';
37
48
  import * as os from 'node:os';
38
49
  import * as path from 'node:path';
@@ -49,9 +60,12 @@ const _HERE = fileURLToPath(import.meta.url);
49
60
  // export it, so resolve the identical value here (parents[2] of the script —
50
61
  // src/scripts/bench_ab_v2_run.ts → repo root). Same path, single derivation.
51
62
  const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
63
+ const ROUTER_PATH = path.join(REPO_ROOT, 'dist', 'router.json');
64
+ const RULES_DIR = path.join(REPO_ROOT, 'dist', 'agent-src', 'rules');
52
65
  const CORPUS_PATH = path.join(REPO_ROOT, 'internal', 'bench', 'corpora', 'ab-trackb-v2.yaml');
53
66
  const FIXTURES_ROOT = path.join(REPO_ROOT, 'internal', 'bench', 'ab');
54
67
  const REPORTS_DIR = path.join(REPO_ROOT, 'internal', 'bench', 'reports', 'ab-v2');
68
+ const CHECKPOINTS_DIR = path.join(REPORTS_DIR, 'checkpoints');
55
69
  // CRITICAL (2026-06-15): clones MUST live OUTSIDE the agent-config repo. A clone
56
70
  // under the repo lets Claude discover the repo's own project surface (CLAUDE.md /
57
71
  // AGENTS.md / .claude/rules+skills) walking up from the cwd — so the `vanilla`
@@ -81,7 +95,17 @@ interface ArmSpec {
81
95
  // config PLUS the compile-time HARD CONSTRAINT blocks injected via sysprompt;
82
96
  // `hardened-placebo` = `package` + inert prose of the SAME length, so the
83
97
  // hardened Δ is length-controlled (measures emphasis, not verbosity).
84
- const ARMS: Record<string, ArmSpec> = {
98
+ // `rules-kernel-dc` / `rules-balanced` (cost-factor sweep, 2026-07): plugin OFF
99
+ // + ONLY a rule subset injected via sysprompt, to measure how much of the
100
+ // weak-host discipline lift survives at a fraction of the full package's
101
+ // loaded-context cost. `rules-kernel-dc` = the router kernel (9 Iron-Law rules)
102
+ // + `downstream-changes` (one of the two lift-carrying rules; the other,
103
+ // `scope-control`, is already in the kernel) — the cheapest configuration that
104
+ // contains both. `rules-balanced` = kernel + tier_1 rule bodies, i.e. the
105
+ // shipped `balanced` router profile (which does NOT include downstream-changes
106
+ // — tier_2 — so this arm also tests whether that profile cut loses the lift).
107
+ // Both are opt-in: NOT in the default arm list; existing runs unchanged.
108
+ export const ARMS: Record<string, ArmSpec> = {
85
109
  vanilla: { setting_sources: 'project,local', inject: null },
86
110
  package: { setting_sources: null, inject: null },
87
111
  'package-rdp': { setting_sources: null, inject: 'rdp' },
@@ -89,6 +113,8 @@ const ARMS: Record<string, ArmSpec> = {
89
113
  'package-recursive': { setting_sources: null, inject: null, recursive: true },
90
114
  hardened: { setting_sources: null, inject: 'hardened' },
91
115
  'hardened-placebo': { setting_sources: null, inject: 'hardened-placebo' },
116
+ 'rules-kernel-dc': { setting_sources: 'project,local', inject: 'rules-kernel-dc' },
117
+ 'rules-balanced': { setting_sources: 'project,local', inject: 'rules-balanced' },
92
118
  };
93
119
 
94
120
  /**
@@ -139,6 +165,30 @@ export function hardened_blocks_text(): string {
139
165
  ].join('\n');
140
166
  }
141
167
 
168
+ /**
169
+ * Rule-subset injection bodies for the cost-factor sweep arms.
170
+ *
171
+ * Reads the tier membership from `dist/router.json` and the rule bodies from
172
+ * `dist/agent-src/rules/` — deterministic per checkout, so the injected text is
173
+ * exactly what the shipped profiles would always-load:
174
+ * - 'rules-kernel-dc' → kernel (9 rules) + `downstream-changes`.
175
+ * - 'rules-balanced' → kernel + tier_1 (the shipped `balanced` profile).
176
+ */
177
+ export function rules_subset_text(subset: 'rules-kernel-dc' | 'rules-balanced'): string {
178
+ const router = JSON.parse(fs.readFileSync(ROUTER_PATH, 'utf-8')) as {
179
+ kernel: string[];
180
+ tier_1: { id: string }[];
181
+ };
182
+ const ids = [...router.kernel];
183
+ if (subset === 'rules-kernel-dc') {
184
+ ids.push('downstream-changes');
185
+ } else {
186
+ ids.push(...router.tier_1.map((t) => t.id));
187
+ }
188
+ const bodies = ids.map((id) => fs.readFileSync(path.join(RULES_DIR, `${id}.md`), 'utf-8').trim());
189
+ return bodies.join('\n\n---\n\n');
190
+ }
191
+
142
192
  export function injected_text(inject: string | null, placebo_chars: number): string | null {
143
193
  if (inject === 'rdp') {
144
194
  return v1.system_prompt_for('with-rdp');
@@ -153,6 +203,9 @@ export function injected_text(inject: string | null, placebo_chars: number): str
153
203
  // Length-matched control: inert prose the SAME length as the hardened blocks.
154
204
  return placebo_prose(hardened_blocks_text().length);
155
205
  }
206
+ if (inject === 'rules-kernel-dc' || inject === 'rules-balanced') {
207
+ return rules_subset_text(inject);
208
+ }
156
209
  return null;
157
210
  }
158
211
 
@@ -210,6 +263,202 @@ interface RunOneOpts {
210
263
  placebo_chars: number;
211
264
  sp_dir: string;
212
265
  max_depth?: number | null; // recursion arm only — hard cap on correction rounds (default 1)
266
+ host?: string; // 'claude' (default) | 'codex' — P2 non-Claude replication (ADR-110 roadmap Phase 4)
267
+ }
268
+
269
+ // ── codex host adapter (P2 non-Claude weak-host replication) ────────────────
270
+ //
271
+ // Drives the OpenAI `codex` CLI non-interactively with the SAME paired design
272
+ // and deterministic scorer as the claude host. Differences, documented for
273
+ // methodology honesty:
274
+ // - Injection surface: codex exec has no --append-system-prompt-file, so arm
275
+ // injections are PREPENDED to the task prompt inside a marked block (system
276
+ // vs user surface caveat — record it wherever results are published).
277
+ // - Auth/home: honours CODEX_BENCH_HOME (exported as CODEX_HOME) so the bench
278
+ // uses an isolated, API-key-authenticated codex home instead of the
279
+ // operator's interactive ChatGPT login.
280
+ // - Plugin-dependent arms (package / package-rdp / package-recursive /
281
+ // hardened variants that ride the real plugin) are refused on this host —
282
+ // only vanilla + injection arms are meaningful.
283
+ // - Cost: codex has no --max-budget-usd; the timeout + tiny fixtures bound
284
+ // spend. Tokens are summed from `--json` event usage payloads best-effort.
285
+
286
+ /** Arms that are valid on the codex host (vanilla + pure-injection arms). */
287
+ export const CODEX_VALID_ARMS: readonly string[] = [
288
+ 'vanilla',
289
+ 'placebo',
290
+ 'rules-kernel-dc',
291
+ 'rules-balanced',
292
+ ];
293
+
294
+ export function codex_executable(): string | null {
295
+ const envBin = process.env['CODEX_CLI'];
296
+ if (envBin && fs.existsSync(envBin)) {
297
+ return envBin;
298
+ }
299
+ const pathVar = process.env['PATH'] ?? '';
300
+ for (const dir of pathVar.split(path.delimiter)) {
301
+ if (!dir) {
302
+ continue;
303
+ }
304
+ const candidate = path.join(dir, 'codex');
305
+ try {
306
+ fs.accessSync(candidate, fs.constants.X_OK);
307
+ return candidate;
308
+ } catch {
309
+ /* keep scanning */
310
+ }
311
+ }
312
+ return null;
313
+ }
314
+
315
+ /** Wrap an arm's injected rules so they read as standing instructions. */
316
+ export function codex_prompt(inject_text: string | null, task_prompt: string): string {
317
+ if (!inject_text) {
318
+ return task_prompt;
319
+ }
320
+ return (
321
+ '<project-instructions>\n' +
322
+ 'The following are the standing project instructions that apply to every task.\n\n' +
323
+ inject_text +
324
+ '\n</project-instructions>\n\n' +
325
+ task_prompt
326
+ );
327
+ }
328
+
329
+ type SpawnFn = typeof spawnSync;
330
+
331
+ /** Sum token usage from codex `--json` JSONL events, best-effort. */
332
+ export function parse_codex_events(stdout: string): {
333
+ tokens: number;
334
+ num_turns: number;
335
+ transcript: string;
336
+ failed: string | null;
337
+ } {
338
+ let tokens = 0;
339
+ let num_turns = 0;
340
+ let failed: string | null = null;
341
+ const texts: string[] = [];
342
+ const addUsage = (u: unknown): void => {
343
+ if (u === null || typeof u !== 'object' || Array.isArray(u)) {
344
+ return;
345
+ }
346
+ for (const [k, v] of Object.entries(u as Record<string, unknown>)) {
347
+ if (typeof v === 'number' && Number.isFinite(v) && /tokens?$/.test(k)) {
348
+ tokens += v;
349
+ } else if (v && typeof v === 'object') {
350
+ addUsage(v);
351
+ }
352
+ }
353
+ };
354
+ for (const line of stdout.split('\n')) {
355
+ const trimmed = line.trim();
356
+ if (!trimmed.startsWith('{')) {
357
+ continue;
358
+ }
359
+ let evt: Record<string, unknown>;
360
+ try {
361
+ evt = JSON.parse(trimmed) as Record<string, unknown>;
362
+ } catch {
363
+ continue;
364
+ }
365
+ if (evt === null || typeof evt !== 'object') {
366
+ continue;
367
+ }
368
+ const type = String(evt['type'] ?? '');
369
+ if (type === 'turn.completed') {
370
+ num_turns += 1;
371
+ addUsage(evt['usage']);
372
+ } else if (type === 'turn.failed' || type === 'error') {
373
+ const err = evt['error'];
374
+ failed =
375
+ err && typeof err === 'object'
376
+ ? String((err as Record<string, unknown>)['message'] ?? 'codex turn failed')
377
+ : String(evt['message'] ?? 'codex turn failed');
378
+ } else if (type === 'item.completed') {
379
+ const item = evt['item'];
380
+ if (item && typeof item === 'object') {
381
+ const it = item as Record<string, unknown>;
382
+ if (String(it['type'] ?? '') === 'agent_message' && typeof it['text'] === 'string') {
383
+ texts.push(it['text']);
384
+ }
385
+ }
386
+ }
387
+ }
388
+ return { tokens, num_turns, transcript: texts.join('\n'), failed };
389
+ }
390
+
391
+ export function run_live_codex(
392
+ task: Dict,
393
+ cloneRoot: string,
394
+ timeoutS: number,
395
+ opts: { model: string | null; inject_text: string | null; spawn?: SpawnFn },
396
+ ): Dict {
397
+ const spawn = opts.spawn ?? spawnSync;
398
+ const binary = codex_executable();
399
+ if (binary === null) {
400
+ return {
401
+ mode: 'live-skipped',
402
+ reason: 'codex CLI not found; set CODEX_CLI or install it',
403
+ transcript: '',
404
+ exit_code: null,
405
+ wall_time_seconds: 0.0,
406
+ tokens: 0,
407
+ errored: true,
408
+ };
409
+ }
410
+ const prompt = codex_prompt(opts.inject_text, String(task['prompt'] ?? ''));
411
+ const args = [
412
+ 'exec',
413
+ '--skip-git-repo-check',
414
+ '--sandbox',
415
+ 'workspace-write',
416
+ '-c',
417
+ 'approval_policy=never',
418
+ '--json',
419
+ ];
420
+ if (opts.model) {
421
+ args.push('-m', opts.model);
422
+ }
423
+ args.push(prompt);
424
+ const env: NodeJS.ProcessEnv = { ...process.env };
425
+ if (process.env['CODEX_BENCH_HOME']) {
426
+ env['CODEX_HOME'] = process.env['CODEX_BENCH_HOME'];
427
+ }
428
+ const started = Date.now();
429
+ const result = spawn(binary, args, {
430
+ cwd: cloneRoot,
431
+ encoding: 'utf-8',
432
+ timeout: timeoutS * 1000,
433
+ env,
434
+ });
435
+ const wall = (Date.now() - started) / 1000;
436
+ const isTimeout = Boolean(result.error) && (result.error as NodeJS.ErrnoException).code === 'ETIMEDOUT';
437
+ if (isTimeout) {
438
+ return {
439
+ mode: 'live',
440
+ reason: `timeout after ${timeoutS}s`,
441
+ transcript: (result.stdout ?? '') + '\n[TIMEOUT]',
442
+ exit_code: -1,
443
+ wall_time_seconds: Math.round(wall * 1000) / 1000,
444
+ tokens: 0,
445
+ errored: true,
446
+ };
447
+ }
448
+ const stdout = result.stdout ?? '';
449
+ const parsed = parse_codex_events(stdout);
450
+ const exitCode = result.status ?? -1;
451
+ const errored = exitCode !== 0 || parsed.failed !== null;
452
+ return {
453
+ mode: 'live',
454
+ reason: errored ? (parsed.failed ?? `codex exited ${exitCode}`) : 'ok',
455
+ transcript: parsed.transcript || stdout,
456
+ exit_code: exitCode,
457
+ wall_time_seconds: Math.round(wall * 1000) / 1000,
458
+ tokens: parsed.tokens,
459
+ num_turns: parsed.num_turns,
460
+ errored,
461
+ };
213
462
  }
214
463
 
215
464
  export function run_one(task: Dict, arm: string, opts: RunOneOpts): Dict {
@@ -217,19 +466,28 @@ export function run_one(task: Dict, arm: string, opts: RunOneOpts): Dict {
217
466
  if (spec.recursive) {
218
467
  return run_one_recursive(task, opts);
219
468
  }
469
+ const host = opts.host ?? 'claude';
220
470
  const [clone, fixture] = reset_fixture(task);
221
471
  const sp_text = injected_text(spec.inject, opts.placebo_chars);
222
- let sp_file: string | null = null;
223
- if (sp_text) {
224
- sp_file = path.join(opts.sp_dir, `.sp-${arm}.txt`);
225
- fs.writeFileSync(sp_file, sp_text, { encoding: 'utf-8' });
226
- }
227
- const run = v1.run_live(task, clone, opts.timeout, {
228
- sysprompt_file: sp_file,
229
- setting_sources: spec.setting_sources,
230
- max_budget: opts.max_budget,
231
- model: opts.model,
232
- }) as unknown as Dict;
472
+ let run: Dict;
473
+ if (host === 'codex') {
474
+ run = run_live_codex(task, clone, opts.timeout, {
475
+ model: opts.model,
476
+ inject_text: sp_text,
477
+ });
478
+ } else {
479
+ let sp_file: string | null = null;
480
+ if (sp_text) {
481
+ sp_file = path.join(opts.sp_dir, `.sp-${arm}.txt`);
482
+ fs.writeFileSync(sp_file, sp_text, { encoding: 'utf-8' });
483
+ }
484
+ run = v1.run_live(task, clone, opts.timeout, {
485
+ sysprompt_file: sp_file,
486
+ setting_sources: spec.setting_sources,
487
+ max_budget: opts.max_budget,
488
+ model: opts.model,
489
+ }) as unknown as Dict;
490
+ }
233
491
  const score = scoring.score_task_v2(task, {
234
492
  fixture_root: fixture,
235
493
  clone_root: clone,
@@ -363,6 +621,192 @@ export function run_one_recursive(
363
621
  };
364
622
  }
365
623
 
624
+ // ── checkpoint / resume ─────────────────────────────────────────────────────
625
+ //
626
+ // A sweep of tasks × arms × seeds can run for hours; without a checkpoint, a
627
+ // kill (manual stop, harness timeout, machine sleep) loses EVERY completed run
628
+ // because results only exist in memory until the single final report write.
629
+ // The checkpoint is a JSONL file (one line per completed run) so each write is
630
+ // a single atomic-enough append and a truncated tail line (killed mid-write)
631
+ // is simply skipped on load. The file is keyed by a hash of the run config —
632
+ // a restart with different flags gets a different checkpoint and never mixes
633
+ // results from an incompatible sweep.
634
+
635
+ /** Stable id key for one run inside a sweep. */
636
+ export function run_key(task_id: string, arm: string, seed: number): string {
637
+ return `${task_id}|${arm}|${seed}`;
638
+ }
639
+
640
+ /**
641
+ * Hash of the sweep configuration — everything that changes what a run means.
642
+ * Same flags → same key → resume; any config change → fresh checkpoint file.
643
+ */
644
+ export function checkpoint_key(cfg: {
645
+ corpus: string;
646
+ model: string;
647
+ seeds: number;
648
+ arms: string[];
649
+ budget: number;
650
+ timeout: number;
651
+ host: string;
652
+ task_ids: string[];
653
+ }): string {
654
+ const stable = JSON.stringify([
655
+ cfg.corpus,
656
+ cfg.model,
657
+ cfg.seeds,
658
+ cfg.arms,
659
+ cfg.budget,
660
+ cfg.timeout,
661
+ cfg.host,
662
+ cfg.task_ids,
663
+ ]);
664
+ return createHash('sha256').update(stable).digest('hex').slice(0, 16);
665
+ }
666
+
667
+ /**
668
+ * JSON round-trip loses the PyFloat wrapper (1.0 parses back as 1), which
669
+ * would break the byte-parity float rendering of the final report on resume.
670
+ * freeze/thaw make the checkpoint lossless: PyFloat → {"__pyfloat__": n} on
671
+ * write, restored to PyFloat on load.
672
+ */
673
+ export function freeze_result(v: unknown): unknown {
674
+ if (v instanceof PyFloat) {
675
+ return { __pyfloat__: v.value };
676
+ }
677
+ if (Array.isArray(v)) {
678
+ return v.map((x) => freeze_result(x));
679
+ }
680
+ if (v && typeof v === 'object') {
681
+ const out: Record<string, unknown> = {};
682
+ for (const [k, val] of Object.entries(v as Record<string, unknown>)) {
683
+ out[k] = freeze_result(val);
684
+ }
685
+ return out;
686
+ }
687
+ return v;
688
+ }
689
+
690
+ export function thaw_result(v: unknown): unknown {
691
+ if (Array.isArray(v)) {
692
+ return v.map((x) => thaw_result(x));
693
+ }
694
+ if (v && typeof v === 'object') {
695
+ const o = v as Record<string, unknown>;
696
+ const keys = Object.keys(o);
697
+ if (keys.length === 1 && keys[0] === '__pyfloat__' && typeof o['__pyfloat__'] === 'number') {
698
+ return new PyFloat(o['__pyfloat__']);
699
+ }
700
+ const out: Record<string, unknown> = {};
701
+ for (const [k, val] of Object.entries(o)) {
702
+ out[k] = thaw_result(val);
703
+ }
704
+ return out;
705
+ }
706
+ return v;
707
+ }
708
+
709
+ /**
710
+ * Load completed runs from a checkpoint file. Tolerant by design: a missing
711
+ * file → empty map; an unparseable line (truncated tail from a mid-write
712
+ * kill) → skipped; a duplicate key → last line wins.
713
+ */
714
+ export function load_checkpoint(file: string): Map<string, Dict> {
715
+ const map = new Map<string, Dict>();
716
+ let raw: string;
717
+ try {
718
+ raw = fs.readFileSync(file, 'utf-8');
719
+ } catch {
720
+ return map;
721
+ }
722
+ for (const line of raw.split('\n')) {
723
+ const trimmed = line.trim();
724
+ if (!trimmed) {
725
+ continue;
726
+ }
727
+ let entry: unknown;
728
+ try {
729
+ entry = JSON.parse(trimmed);
730
+ } catch {
731
+ continue; // truncated tail — the run it held simply re-executes
732
+ }
733
+ if (entry && typeof entry === 'object' && !Array.isArray(entry)) {
734
+ const e = entry as Record<string, unknown>;
735
+ if (typeof e['key'] === 'string' && e['result'] && typeof e['result'] === 'object') {
736
+ map.set(e['key'], thaw_result(e['result']) as Dict);
737
+ }
738
+ }
739
+ }
740
+ return map;
741
+ }
742
+
743
+ /** Append one completed run to the checkpoint (single-line atomic append). */
744
+ export function append_checkpoint(file: string, key: string, result: Dict): void {
745
+ fs.appendFileSync(file, JSON.stringify({ key, result: freeze_result(result) }) + '\n', {
746
+ encoding: 'utf-8',
747
+ });
748
+ }
749
+
750
+ export interface CheckpointIO {
751
+ /** Checkpoint file to append completed runs to (created on first append). */
752
+ path: string;
753
+ /** Runs already completed by a prior (killed) sweep — reused, not re-run. */
754
+ completed: Map<string, Dict>;
755
+ }
756
+
757
+ /**
758
+ * The sweep loop, extracted from main() with an injectable run function so the
759
+ * resume behaviour is testable without live model calls. Semantics with no
760
+ * checkpoint are byte-identical to the pre-checkpoint loop (same stderr
761
+ * progress lines, same record shape).
762
+ */
763
+ export function collect_records(
764
+ tasks: Dict[],
765
+ arms: string[],
766
+ seeds: number,
767
+ run_fn: (task: Dict, arm: string, seed: number) => Dict,
768
+ ckpt: CheckpointIO | null = null,
769
+ log: (msg: string) => void = (m) => process.stderr.write(m),
770
+ ): { records: Dict[]; executed: number; reused: number } {
771
+ const total = tasks.length * arms.length * seeds;
772
+ let done = 0;
773
+ let executed = 0;
774
+ let reused = 0;
775
+ const records: Dict[] = [];
776
+ for (const task of tasks) {
777
+ const per_arm: Record<string, Dict[]> = {};
778
+ for (const arm of arms) {
779
+ const seed_runs: Dict[] = [];
780
+ for (let seed = 0; seed < seeds; seed += 1) {
781
+ done += 1;
782
+ const key = run_key(String(task['id']), arm, seed);
783
+ const cached = ckpt?.completed.get(key);
784
+ if (cached) {
785
+ reused += 1;
786
+ seed_runs.push(cached);
787
+ continue;
788
+ }
789
+ log(`[${done}/${total}] ${String(task['id'])} · ${arm} · seed ${seed}\n`);
790
+ const r = run_fn(task, arm, seed);
791
+ r['seed'] = seed;
792
+ executed += 1;
793
+ if (ckpt) {
794
+ append_checkpoint(ckpt.path, key, r);
795
+ }
796
+ seed_runs.push(r);
797
+ }
798
+ per_arm[arm] = seed_runs;
799
+ }
800
+ records.push({
801
+ id: task['id'],
802
+ archetype: task['archetype'],
803
+ rule: task['rule'],
804
+ arms: per_arm,
805
+ });
806
+ }
807
+ return { records, executed, reused };
808
+ }
809
+
366
810
  export function main(argv: string[] | null = null): number {
367
811
  const args = parse_args(argv ?? process.argv.slice(2));
368
812
 
@@ -388,6 +832,13 @@ export function main(argv: string[] | null = null): number {
388
832
  process.stderr.write(`unknown arm: ${a}\n`);
389
833
  return 1;
390
834
  }
835
+ if (args.host === 'codex' && !CODEX_VALID_ARMS.includes(a)) {
836
+ process.stderr.write(
837
+ `arm ${a} requires the claude plugin host; valid --host codex arms: ` +
838
+ `${CODEX_VALID_ARMS.join(',')}\n`,
839
+ );
840
+ return 1;
841
+ }
391
842
  }
392
843
 
393
844
  if (args.mode === 'dry-run') {
@@ -412,35 +863,50 @@ export function main(argv: string[] | null = null): number {
412
863
  const placebo_chars = Math.max(rdp_text.length, 2000);
413
864
 
414
865
  const total = tasks.length * arms.length * args.seeds;
415
- let done = 0;
416
- const records: Dict[] = [];
417
- for (const task of tasks) {
418
- const per_arm: Record<string, Dict[]> = {};
419
- for (const arm of arms) {
420
- const seed_runs: Dict[] = [];
421
- for (let seed = 0; seed < args.seeds; seed += 1) {
422
- done += 1;
423
- process.stderr.write(`[${done}/${total}] ${String(task['id'])} · ${arm} · seed ${seed}\n`);
424
- const r = run_one(task, arm, {
425
- model: args.model,
426
- max_budget,
427
- timeout: args.timeout,
428
- placebo_chars,
429
- sp_dir,
430
- });
431
- r['seed'] = seed;
432
- seed_runs.push(r);
433
- }
434
- per_arm[arm] = seed_runs;
435
- }
436
- records.push({
437
- id: task['id'],
438
- archetype: task['archetype'],
439
- rule: task['rule'],
440
- arms: per_arm,
866
+
867
+ let ckpt: CheckpointIO | null = null;
868
+ if (args.checkpoint) {
869
+ const key = checkpoint_key({
870
+ corpus: 'ab-trackb-v2',
871
+ model: args.model,
872
+ seeds: args.seeds,
873
+ arms,
874
+ budget: args.budget,
875
+ timeout: args.timeout,
876
+ host: args.host,
877
+ task_ids: tasks.map((t) => String(t['id'])),
441
878
  });
879
+ const ckpt_path = path.join(CHECKPOINTS_DIR, `${key}.jsonl`);
880
+ fs.mkdirSync(CHECKPOINTS_DIR, { recursive: true });
881
+ if (args.fresh && fs.existsSync(ckpt_path)) {
882
+ fs.rmSync(ckpt_path);
883
+ }
884
+ const completed = load_checkpoint(ckpt_path);
885
+ if (completed.size > 0) {
886
+ process.stderr.write(
887
+ `bench_ab_v2: resuming — ${completed.size}/${total} runs already complete ` +
888
+ `(checkpoint ${_relToRootPosix(ckpt_path)}; use --fresh to discard)\n`,
889
+ );
890
+ }
891
+ ckpt = { path: ckpt_path, completed };
442
892
  }
443
893
 
894
+ const { records } = collect_records(
895
+ tasks,
896
+ arms,
897
+ args.seeds,
898
+ (task, _arm, _seed) =>
899
+ run_one(task, _arm, {
900
+ model: args.model,
901
+ max_budget,
902
+ timeout: args.timeout,
903
+ placebo_chars,
904
+ sp_dir,
905
+ host: args.host,
906
+ }),
907
+ ckpt,
908
+ );
909
+
444
910
  const stamp = v1.utc_stamp();
445
911
  const payload: Dict = {
446
912
  schema: 'ab-bench-v2/0.1',
@@ -451,10 +917,16 @@ export function main(argv: string[] | null = null): number {
451
917
  budget_usd_per_run: args.budget,
452
918
  placebo_chars,
453
919
  corpus: 'ab-trackb-v2',
920
+ host: args.host,
454
921
  records,
455
922
  };
456
923
  const out = path.join(REPORTS_DIR, `${stamp}-ab-v2-paired.json`);
457
924
  fs.writeFileSync(out, _jsonDumps(_toJson(payload), 2) + '\n');
925
+ // The paired report now holds everything — retire the checkpoint so a
926
+ // finished sweep leaves no residue and a later identical sweep starts fresh.
927
+ if (ckpt && fs.existsSync(ckpt.path)) {
928
+ fs.rmSync(ckpt.path);
929
+ }
458
930
  process.stdout.write(
459
931
  `bench_ab_v2: wrote ${_relToRootPosix(out)} ` + `(${records.length} tasks, ${total} runs)\n`,
460
932
  );
@@ -472,6 +944,9 @@ interface ParsedArgs {
472
944
  budget: number;
473
945
  timeout: number;
474
946
  mode: string;
947
+ host: string;
948
+ checkpoint: boolean;
949
+ fresh: boolean;
475
950
  }
476
951
 
477
952
  class ArgExit extends Error {}
@@ -487,8 +962,11 @@ function parse_args(argv: string[]): ParsedArgs {
487
962
  budget: 1.0,
488
963
  timeout: 180,
489
964
  mode: 'live',
965
+ host: 'claude',
966
+ checkpoint: true,
967
+ fresh: false,
490
968
  };
491
- const usage = `usage: ${prog} [-h] [--arms ARMS] [--seeds SEEDS] [--tasks TASKS] [--limit LIMIT] [--model MODEL] [--budget BUDGET] [--timeout TIMEOUT] [--mode {live,dry-run}]\n`;
969
+ const usage = `usage: ${prog} [-h] [--arms ARMS] [--seeds SEEDS] [--tasks TASKS] [--limit LIMIT] [--model MODEL] [--budget BUDGET] [--timeout TIMEOUT] [--mode {live,dry-run}] [--host {claude,codex}] [--no-checkpoint] [--fresh]\n`;
492
970
  const argErr = (msg: string): never => {
493
971
  process.stderr.write(usage);
494
972
  process.stderr.write(`${prog}: error: ${msg}\n`);
@@ -544,8 +1022,17 @@ function parse_args(argv: string[]): ParsedArgs {
544
1022
  } else if (a === '--mode') {
545
1023
  out.mode = _choice(need(i, '--mode'), '--mode', ['live', 'dry-run'], argErr);
546
1024
  i += 1;
1025
+ } else if (a === '--host') {
1026
+ out.host = _choice(need(i, '--host'), '--host', ['claude', 'codex'], argErr);
1027
+ i += 1;
1028
+ } else if (a.startsWith('--host=')) {
1029
+ out.host = _choice(a.slice('--host='.length), '--host', ['claude', 'codex'], argErr);
547
1030
  } else if (a.startsWith('--mode=')) {
548
1031
  out.mode = _choice(a.slice('--mode='.length), '--mode', ['live', 'dry-run'], argErr);
1032
+ } else if (a === '--no-checkpoint') {
1033
+ out.checkpoint = false;
1034
+ } else if (a === '--fresh') {
1035
+ out.fresh = true;
549
1036
  } else {
550
1037
  argErr(`unrecognized arguments: ${a}`);
551
1038
  }
@@ -646,7 +1133,7 @@ function _pyNumStr(x: number): string {
646
1133
 
647
1134
  // ── JSON byte-parity (json.dumps(payload, indent=2)) ──────────────────────
648
1135
 
649
- class PyFloat {
1136
+ export class PyFloat {
650
1137
  constructor(readonly value: number) {}
651
1138
  }
652
1139