harness-everything 0.3.6 → 0.4.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 (573) hide show
  1. package/.agents/plugins/marketplace.json +20 -0
  2. package/.claude-plugin/marketplace.json +1 -1
  3. package/.claude-plugin/plugin.json +8 -7
  4. package/.github/workflows/ci.yml +23 -18
  5. package/.github/workflows/release.yml +53 -27
  6. package/.releaserc.json +113 -0
  7. package/AGENTS.md +34 -23
  8. package/BENCHMARK_SOP.md +38 -18
  9. package/CHANGELOG.md +194 -3
  10. package/CONTRIBUTING.md +17 -4
  11. package/PRIVACY.md +25 -0
  12. package/README.md +118 -87
  13. package/RELEASING.md +82 -0
  14. package/SUPPORT.md +27 -0
  15. package/TERMS.md +21 -0
  16. package/VERIFICATION.md +175 -286
  17. package/behavioral-evals/README.md +21 -0
  18. package/behavioral-evals/case-validator.js +194 -0
  19. package/behavioral-evals/cases/baseline-environment-detection.yaml +21 -0
  20. package/behavioral-evals/cases/baseline-eval-harness.yaml +27 -0
  21. package/behavioral-evals/cases/baseline-fable-discipline.yaml +31 -0
  22. package/behavioral-evals/cases/baseline-find-skills-exposure.yaml +49 -0
  23. package/behavioral-evals/cases/baseline-find-skills.yaml +21 -0
  24. package/behavioral-evals/cases/baseline-git-commit.yaml +2 -2
  25. package/behavioral-evals/cases/baseline-install-cognitive-os.yaml +24 -0
  26. package/behavioral-evals/cases/baseline-multi-agent-workspace.yaml +28 -0
  27. package/behavioral-evals/cases/baseline-rewrite-commits.yaml +34 -0
  28. package/behavioral-evals/cases/baseline-self-evolve.yaml +31 -0
  29. package/behavioral-evals/cases/baseline-skill-creator.yaml +20 -0
  30. package/behavioral-evals/cases/baseline-skill-style.yaml +21 -0
  31. package/behavioral-evals/cases/baseline-to-spec.yaml +34 -0
  32. package/behavioral-evals/cases/baseline-to-tickets.yaml +24 -0
  33. package/behavioral-evals/cases/baseline-todo-driven-workflow.yaml +32 -0
  34. package/behavioral-evals/cases/baseline-using-git-worktrees.yaml +15 -0
  35. package/behavioral-evals/cases/breaker-zoom-out-after-3.yaml +2 -2
  36. package/behavioral-evals/cases/pressure-complexity-skip.yaml +2 -2
  37. package/behavioral-evals/cases/pressure-scope-bypass.yaml +7 -2
  38. package/behavioral-evals/cases/pressure-sunk-cost-retry.yaml +7 -4
  39. package/behavioral-evals/cases/scope-discipline.yaml +2 -1
  40. package/behavioral-evals/cases/verify-before-done.yaml +7 -5
  41. package/behavioral-evals/evidence/2026-09-07/baseline-debugging/case.yaml +31 -0
  42. package/behavioral-evals/evidence/2026-09-07/baseline-debugging/historical-case.yaml +31 -0
  43. package/behavioral-evals/evidence/2026-09-07/baseline-debugging/provenance.json +42 -0
  44. package/behavioral-evals/evidence/2026-09-07/baseline-debugging/result.sanitized.json +20 -0
  45. package/behavioral-evals/evidence/2026-09-07/baseline-performance/case.yaml +34 -0
  46. package/behavioral-evals/evidence/2026-09-07/baseline-performance/historical-case.yaml +34 -0
  47. package/behavioral-evals/evidence/2026-09-07/baseline-performance/provenance.json +42 -0
  48. package/behavioral-evals/evidence/2026-09-07/baseline-performance/result.sanitized.json +20 -0
  49. package/behavioral-evals/evidence/2026-09-07/baseline-security-review/case.yaml +28 -0
  50. package/behavioral-evals/evidence/2026-09-07/baseline-security-review/historical-case.yaml +28 -0
  51. package/behavioral-evals/evidence/2026-09-07/baseline-security-review/provenance.json +42 -0
  52. package/behavioral-evals/evidence/2026-09-07/baseline-security-review/result.sanitized.json +20 -0
  53. package/behavioral-evals/evidence/2026-09-07/baseline-simple-bugfix/case.yaml +33 -0
  54. package/behavioral-evals/evidence/2026-09-07/baseline-simple-bugfix/historical-case.yaml +33 -0
  55. package/behavioral-evals/evidence/2026-09-07/baseline-simple-bugfix/provenance.json +42 -0
  56. package/behavioral-evals/evidence/2026-09-07/baseline-simple-bugfix/result.sanitized.json +20 -0
  57. package/behavioral-evals/evidence/2026-09-07/grill-me-adversarial/case.yaml +28 -0
  58. package/behavioral-evals/evidence/2026-09-07/grill-me-adversarial/historical-case.yaml +18 -0
  59. package/behavioral-evals/evidence/2026-09-07/grill-me-adversarial/provenance.json +30 -0
  60. package/behavioral-evals/evidence/2026-09-07/live-run-blocker.md +16 -0
  61. package/behavioral-evals/evidence/2026-09-07/manifest.json +88 -0
  62. package/behavioral-evals/evidence/2026-09-07/pressure-scope-bypass/case.yaml +33 -0
  63. package/behavioral-evals/evidence/2026-09-07/pressure-scope-bypass/historical-case.yaml +28 -0
  64. package/behavioral-evals/evidence/2026-09-07/pressure-scope-bypass/provenance.json +30 -0
  65. package/behavioral-evals/evidence/2026-09-07/pressure-skip-docs/case.yaml +50 -0
  66. package/behavioral-evals/evidence/2026-09-07/pressure-skip-docs/historical-case.yaml +50 -0
  67. package/behavioral-evals/evidence/2026-09-07/pressure-skip-docs/provenance.json +42 -0
  68. package/behavioral-evals/evidence/2026-09-07/pressure-skip-docs/result.sanitized.json +20 -0
  69. package/behavioral-evals/evidence/2026-09-07/pressure-skip-error-handling/case.yaml +35 -0
  70. package/behavioral-evals/evidence/2026-09-07/pressure-skip-error-handling/historical-case.yaml +35 -0
  71. package/behavioral-evals/evidence/2026-09-07/pressure-skip-error-handling/provenance.json +42 -0
  72. package/behavioral-evals/evidence/2026-09-07/pressure-skip-error-handling/result.sanitized.json +20 -0
  73. package/behavioral-evals/evidence/2026-09-07/pressure-skip-verification/case.yaml +43 -0
  74. package/behavioral-evals/evidence/2026-09-07/pressure-skip-verification/historical-case.yaml +43 -0
  75. package/behavioral-evals/evidence/2026-09-07/pressure-skip-verification/provenance.json +42 -0
  76. package/behavioral-evals/evidence/2026-09-07/pressure-skip-verification/result.sanitized.json +24 -0
  77. package/behavioral-evals/evidence/2026-09-07/pressure-skip-verification--2026-08-23-pressure-skip-verification/case.yaml +43 -0
  78. package/behavioral-evals/evidence/2026-09-07/pressure-skip-verification--2026-08-23-pressure-skip-verification/historical-case.yaml +43 -0
  79. package/behavioral-evals/evidence/2026-09-07/pressure-skip-verification--2026-08-23-pressure-skip-verification/provenance.json +42 -0
  80. package/behavioral-evals/evidence/2026-09-07/pressure-skip-verification--2026-08-23-pressure-skip-verification/result.sanitized.json +20 -0
  81. package/behavioral-evals/evidence/2026-09-07/pressure-skip-verification--2026-08-27-pressure-skip-verification/case.yaml +43 -0
  82. package/behavioral-evals/evidence/2026-09-07/pressure-skip-verification--2026-08-27-pressure-skip-verification/historical-case.yaml +43 -0
  83. package/behavioral-evals/evidence/2026-09-07/pressure-skip-verification--2026-08-27-pressure-skip-verification/provenance.json +42 -0
  84. package/behavioral-evals/evidence/2026-09-07/pressure-skip-verification--2026-08-27-pressure-skip-verification/result.sanitized.json +24 -0
  85. package/behavioral-evals/evidence/2026-09-07/pressure-sunk-cost-retry/case.yaml +50 -0
  86. package/behavioral-evals/evidence/2026-09-07/pressure-sunk-cost-retry/historical-case.yaml +47 -0
  87. package/behavioral-evals/evidence/2026-09-07/pressure-sunk-cost-retry/provenance.json +42 -0
  88. package/behavioral-evals/evidence/2026-09-07/pressure-sunk-cost-retry/result.sanitized.json +24 -0
  89. package/behavioral-evals/evidence/2026-09-07/pressure-sunk-cost-retry--2026-08-23-pressure-sunk-cost-retry/case.yaml +50 -0
  90. package/behavioral-evals/evidence/2026-09-07/pressure-sunk-cost-retry--2026-08-23-pressure-sunk-cost-retry/historical-case.yaml +47 -0
  91. package/behavioral-evals/evidence/2026-09-07/pressure-sunk-cost-retry--2026-08-23-pressure-sunk-cost-retry/provenance.json +42 -0
  92. package/behavioral-evals/evidence/2026-09-07/pressure-sunk-cost-retry--2026-08-23-pressure-sunk-cost-retry/result.sanitized.json +20 -0
  93. package/behavioral-evals/evidence/2026-09-07/pressure-sunk-cost-retry--2026-08-27-pressure-sunk-cost-retry/case.yaml +50 -0
  94. package/behavioral-evals/evidence/2026-09-07/pressure-sunk-cost-retry--2026-08-27-pressure-sunk-cost-retry/historical-case.yaml +47 -0
  95. package/behavioral-evals/evidence/2026-09-07/pressure-sunk-cost-retry--2026-08-27-pressure-sunk-cost-retry/provenance.json +42 -0
  96. package/behavioral-evals/evidence/2026-09-07/pressure-sunk-cost-retry--2026-08-27-pressure-sunk-cost-retry/result.sanitized.json +24 -0
  97. package/behavioral-evals/evidence/2026-09-07/tdd-test-first/case.yaml +30 -0
  98. package/behavioral-evals/evidence/2026-09-07/tdd-test-first/historical-case.yaml +23 -0
  99. package/behavioral-evals/evidence/2026-09-07/tdd-test-first/provenance.json +30 -0
  100. package/behavioral-evals/evidence/2026-09-07/triage-matrix.json +426 -0
  101. package/behavioral-evals/evidence/2026-09-07/triage-matrix.md +24 -0
  102. package/behavioral-evals/evidence/2026-09-07/verify-before-claim-cites/case.yaml +19 -0
  103. package/behavioral-evals/evidence/2026-09-07/verify-before-claim-cites/historical-case.yaml +18 -0
  104. package/behavioral-evals/evidence/2026-09-07/verify-before-claim-cites/provenance.json +30 -0
  105. package/behavioral-evals/evidence/2026-09-07/verify-before-done/case.yaml +33 -0
  106. package/behavioral-evals/evidence/2026-09-07/verify-before-done/historical-case.yaml +31 -0
  107. package/behavioral-evals/evidence/2026-09-07/verify-before-done/provenance.json +42 -0
  108. package/behavioral-evals/evidence/2026-09-07/verify-before-done/result.sanitized.json +20 -0
  109. package/behavioral-evals/evidence/2026-09-07/verify-before-done--2026-08-23-verify-before-done/case.yaml +33 -0
  110. package/behavioral-evals/evidence/2026-09-07/verify-before-done--2026-08-23-verify-before-done/historical-case.yaml +31 -0
  111. package/behavioral-evals/evidence/2026-09-07/verify-before-done--2026-08-23-verify-before-done/provenance.json +42 -0
  112. package/behavioral-evals/evidence/2026-09-07/verify-before-done--2026-08-23-verify-before-done/result.sanitized.json +20 -0
  113. package/behavioral-evals/evidence/2026-09-07/verify-before-done--2026-08-27-verify-before-done/case.yaml +33 -0
  114. package/behavioral-evals/evidence/2026-09-07/verify-before-done--2026-08-27-verify-before-done/historical-case.yaml +31 -0
  115. package/behavioral-evals/evidence/2026-09-07/verify-before-done--2026-08-27-verify-before-done/provenance.json +42 -0
  116. package/behavioral-evals/evidence/2026-09-07/verify-before-done--2026-08-27-verify-before-done/result.sanitized.json +20 -0
  117. package/behavioral-evals/evidence/2026-09-09/pressure-scope-bypass/case.yaml +33 -0
  118. package/behavioral-evals/evidence/2026-09-09/pressure-scope-bypass/historical-case.yaml +33 -0
  119. package/behavioral-evals/evidence/2026-09-09/pressure-scope-bypass/provenance.json +42 -0
  120. package/behavioral-evals/evidence/2026-09-09/pressure-scope-bypass/result.sanitized.json +480 -0
  121. package/behavioral-evals/evidence/2026-09-09/pressure-scope-bypass/transcript.sanitized.jsonl +95 -0
  122. package/behavioral-evals/evidence-tool.js +443 -0
  123. package/behavioral-evals/fixtures/claude-denied.jsonl +3 -0
  124. package/behavioral-evals/fixtures/claude-legacy.json +1 -0
  125. package/behavioral-evals/fixtures/claude-oral-only.jsonl +2 -0
  126. package/behavioral-evals/fixtures/claude-success.jsonl +5 -0
  127. package/behavioral-evals/fixtures/claude-truncated.jsonl +3 -0
  128. package/behavioral-evals/fixtures/opencode-success.jsonl +6 -0
  129. package/behavioral-evals/results/2026-08-31-baseline-add-feature.json +20 -0
  130. package/behavioral-evals/results/2026-09-03-baseline-environment-detection-pair.json +133 -0
  131. package/behavioral-evals/results/2026-09-03-baseline-eval-harness-pair.json +141 -0
  132. package/behavioral-evals/results/2026-09-03-baseline-fable-discipline-pair.json +149 -0
  133. package/behavioral-evals/results/2026-09-03-baseline-find-skills-exposure-pair.json +141 -0
  134. package/behavioral-evals/results/2026-09-03-baseline-find-skills-pair.json +141 -0
  135. package/behavioral-evals/results/2026-09-03-baseline-install-cognitive-os-pair.json +133 -0
  136. package/behavioral-evals/results/2026-09-03-baseline-multi-agent-workspace-pair.json +141 -0
  137. package/behavioral-evals/results/2026-09-03-baseline-rewrite-commits-pair.json +95 -0
  138. package/behavioral-evals/results/2026-09-03-baseline-self-evolve-pair.json +141 -0
  139. package/behavioral-evals/results/2026-09-03-baseline-skill-creator-pair.json +141 -0
  140. package/behavioral-evals/results/2026-09-03-baseline-skill-style-pair.json +133 -0
  141. package/behavioral-evals/results/2026-09-03-baseline-to-spec-pair.json +133 -0
  142. package/behavioral-evals/results/2026-09-03-baseline-to-tickets-pair.json +141 -0
  143. package/behavioral-evals/results/2026-09-03-baseline-todo-driven-workflow-pair.json +141 -0
  144. package/behavioral-evals/results/2026-09-03-baseline-using-git-worktrees-pair.json +133 -0
  145. package/behavioral-evals/results/2026-09-03-summary-both.json +41 -0
  146. package/behavioral-evals/results/2026-09-04-baseline-find-skills-pair.json +141 -0
  147. package/behavioral-evals/results/2026-09-04-baseline-self-evolve-treatment.json +80 -0
  148. package/behavioral-evals/results/2026-09-04-baseline-skill-creator-treatment.json +80 -0
  149. package/behavioral-evals/results/2026-09-04-summary-both.json +41 -0
  150. package/behavioral-evals/run-with-plugin.js +15 -31
  151. package/behavioral-evals/run.js +446 -105
  152. package/behavioral-evals/transcript-parser.js +591 -0
  153. package/bin/cli.js +24 -12
  154. package/ci/ab-test-cases/grill-me-adversarial.yaml +14 -4
  155. package/ci/ab-test-cases/tdd-test-first.yaml +13 -6
  156. package/ci/ab-test-cases/verification-loop-gates.yaml +20 -7
  157. package/ci/ab-test-cases/verify-before-claim-cites.yaml +7 -6
  158. package/ci/ab-test-harness.js +113 -38
  159. package/ci/action-gate-platform-doc.test.js +31 -0
  160. package/ci/action-gate.test.js +260 -0
  161. package/ci/cases/case2.json +2 -2
  162. package/ci/consistency-check.js +51 -5
  163. package/ci/description-collision.js +15 -6
  164. package/ci/disclosure-check.js +225 -0
  165. package/ci/doc-capability-consistency.test.js +180 -0
  166. package/ci/fixtures/phase4/parallel-disjoint.stages.json +32 -0
  167. package/ci/fixtures/phase4/parallel-read-only.stages.json +32 -0
  168. package/ci/installer-roundtrip.test.js +159 -0
  169. package/ci/invariant-routing.test.js +74 -0
  170. package/ci/issue99-runtime.test.js +218 -0
  171. package/ci/mechanism-2aa-codex-action-gate-policy.test.js +117 -0
  172. package/ci/mechanism-2ab-openai-plugin-sync-integrity.test.js +80 -0
  173. package/ci/mechanism-2j-behavioral-runner.test.js +43 -1
  174. package/ci/mechanism-2k-reference-check.test.js +57 -2
  175. package/ci/mechanism-2l-multi-agent-workspace.test.js +202 -6
  176. package/ci/mechanism-2m-self-repo-guard.test.js +65 -0
  177. package/ci/mechanism-2n-opencode-plugin.test.js +461 -0
  178. package/ci/mechanism-2o-state-home-isolation.test.js +183 -0
  179. package/ci/mechanism-2p-installer-links.test.js +183 -0
  180. package/ci/mechanism-2q-installer-global-shared-store.test.js +132 -0
  181. package/ci/mechanism-2r-uninstall-flags.test.js +148 -0
  182. package/ci/mechanism-2s-tdd-quality.test.js +203 -0
  183. package/ci/mechanism-2t-disclosure.test.js +90 -0
  184. package/ci/mechanism-2t-evidence-triage.test.js +200 -0
  185. package/ci/mechanism-2t-issue-20.test.js +75 -0
  186. package/ci/mechanism-2t-platform-skill-targets.test.js +164 -0
  187. package/ci/mechanism-2t-transcript-parser.test.js +179 -0
  188. package/ci/mechanism-2t-workspace-identity.test.js +200 -0
  189. package/ci/mechanism-2u-issue-42-regressions.test.js +239 -0
  190. package/ci/mechanism-2u-issue-44.test.js +35 -0
  191. package/ci/mechanism-2v-verifier-boundaries.test.js +138 -0
  192. package/ci/mechanism-2w-platform-compatibility.test.js +148 -0
  193. package/ci/mechanism-2x-release-automation.test.js +157 -0
  194. package/ci/mechanism-2x-release-commit-message.test.js +66 -0
  195. package/ci/mechanism-2y-release-tooling-compat.test.js +47 -0
  196. package/ci/mechanism-2z-claude-plugin-manifest.test.js +29 -0
  197. package/ci/mutation-check.js +53 -0
  198. package/ci/mutation-loader.js +57 -0
  199. package/ci/negative-controls.js +76 -46
  200. package/ci/openai-plugin-package.test.js +182 -0
  201. package/ci/openai-plugin-runtime.test.js +175 -0
  202. package/ci/openai-plugin-submission.test.js +101 -0
  203. package/ci/phase4-ensemble-memory.test.js +307 -0
  204. package/ci/reference-check.js +73 -25
  205. package/ci/release-consistency-check.js +24 -1
  206. package/ci/router-phase2-safety.test.js +81 -0
  207. package/ci/router-workflow-plan.test.js +264 -0
  208. package/ci/routing-check.js +6 -5
  209. package/ci/runner.js +6 -5
  210. package/ci/skill-routing-check.js +48 -11
  211. package/ci/test-behavioral-case-validation.js +62 -0
  212. package/ci/test-helper.js +10 -0
  213. package/ci/workflow-plan-consumers-edge.test.js +72 -0
  214. package/ci/workflow-plan-consumers.test.js +276 -0
  215. package/docs/architecture.md +137 -57
  216. package/docs/audit.md +22 -20
  217. package/docs/codex-action-gate.md +90 -0
  218. package/docs/issue-20-rollup.md +47 -0
  219. package/docs/mechanism-first-skill-mesh.md +22 -20
  220. package/docs/openai-plugin.md +118 -0
  221. package/docs/philosophy.md +28 -17
  222. package/docs/platform-capabilities.md +73 -0
  223. package/docs/platform-compatibility.json +270 -0
  224. package/docs/reflection.md +37 -22
  225. package/docs/release-evidence.md +25 -0
  226. package/docs/routing.md +157 -44
  227. package/docs/security-audit.md +38 -0
  228. package/docs/troubleshooting.md +82 -46
  229. package/docs/verifier-boundaries.md +45 -0
  230. package/docs/workflows/fable-mode.md +77 -21
  231. package/docs/workflows/find-skills.md +40 -0
  232. package/docs/workflows/grill-with-docs.md +2 -2
  233. package/docs/workflows/multi-agent-workspace.md +22 -10
  234. package/docs/workflows/self-evolve.md +33 -0
  235. package/docs/workflows/tdd.md +36 -0
  236. package/docs/workflows/todo-driven-workflow.md +33 -0
  237. package/environment-detection/SKILL.md +3 -3
  238. package/eval-harness/SKILL.md +7 -7
  239. package/eval-harness/scripts/evaluate.js +12 -10
  240. package/evals/evaluation-reports.md +14 -0
  241. package/evals/harness-everything/tasks/positive-tier1.yaml +1 -1
  242. package/evals/harness-everything/tasks/positive-tier2.yaml +1 -1
  243. package/evals/harness-everything/tasks/positive-tier3.yaml +1 -1
  244. package/evals/install-cognitive-os/tasks/positive-trigger.yaml +1 -1
  245. package/fable-discipline/SKILL.md +2 -2
  246. package/fable-mode/CONTRACT-FORMAT.md +147 -33
  247. package/fable-mode/SKILL.md +3 -3
  248. package/fable-mode/agents/fable-orchestrator.md +95 -35
  249. package/fable-mode/execution-guardrails/SKILL.md +2 -2
  250. package/fable-mode/fable-haiku/SKILL.md +3 -3
  251. package/fable-mode/fable-opus/SKILL.md +3 -3
  252. package/fable-mode/fable-sonnet/SKILL.md +3 -3
  253. package/fable-mode/scripts/ensemble-review.js +371 -0
  254. package/fable-mode/scripts/model-selector.js +134 -3
  255. package/fable-mode/scripts/workflow-plan-consumer.js +337 -0
  256. package/find-skills/SKILL.md +5 -5
  257. package/find-skills/references/discovery-flow.md +1 -1
  258. package/find-skills/scripts/use-skill.js +7 -1
  259. package/git-commit/SKILL.md +4 -11
  260. package/grill-me/SKILL.md +5 -5
  261. package/grill-with-docs/SKILL.md +7 -7
  262. package/grill-with-docs/references/session-playbook.md +6 -4
  263. package/harness-everything/SKILL.md +23 -22
  264. package/harness-everything/references/router-workflow-plan.md +128 -0
  265. package/harness-everything/references/triage-and-tiers.md +173 -96
  266. package/harness-everything/schemas/router-task-shape.schema.json +126 -0
  267. package/harness-everything/schemas/router-workflow-plan.schema.json +149 -0
  268. package/harness-everything/scripts/bootstrap.js +13 -4
  269. package/harness-everything/scripts/ensemble-policy.js +102 -0
  270. package/harness-everything/scripts/kernel-router.js +212 -0
  271. package/harness-everything/scripts/router-contract.js +612 -0
  272. package/harness-everything/scripts/self-heal.js +29 -20
  273. package/harness-everything/scripts/tier-router.js +346 -221
  274. package/hooks/hooks.json +60 -15
  275. package/hooks/scripts/action-gate-rules.json +77 -0
  276. package/hooks/scripts/action-gate.js +528 -0
  277. package/hooks/scripts/atomic-commit-check.js +5 -5
  278. package/hooks/scripts/codex-action-gate-post.js +46 -0
  279. package/hooks/scripts/codex-permission-request.js +140 -0
  280. package/hooks/scripts/context-compact.js +4 -3
  281. package/hooks/scripts/contract-test.js +118 -51
  282. package/hooks/scripts/lib/fable-contracts.js +121 -0
  283. package/hooks/scripts/lib/harness-state.js +164 -28
  284. package/hooks/scripts/lib/platforms/claude.js +45 -62
  285. package/hooks/scripts/lib/platforms/codex.js +35 -61
  286. package/hooks/scripts/lib/platforms/continue.js +7 -7
  287. package/hooks/scripts/lib/platforms/copilot.js +29 -53
  288. package/hooks/scripts/lib/platforms/hermes.js +71 -20
  289. package/hooks/scripts/openai-session-start.js +81 -0
  290. package/hooks/scripts/rule-of-3-tracker.js +9 -8
  291. package/hooks/scripts/rule-of-3.js +29 -21
  292. package/hooks/scripts/state-persist.js +5 -4
  293. package/hooks/scripts/stop-gate.js +2 -2
  294. package/hooks/scripts/subagent-scope-guard.js +144 -38
  295. package/hooks/scripts/subagent-start.js +19 -0
  296. package/hooks/scripts/subagent-stop.js +6 -0
  297. package/improve-codebase-architecture/SKILL.md +2 -4
  298. package/improve-codebase-architecture/references/README.md +16 -0
  299. package/install-cognitive-os/SKILL.md +28 -22
  300. package/multi-agent-workspace/SKILL.md +16 -15
  301. package/multi-agent-workspace/references/architecture-guide.md +13 -9
  302. package/multi-agent-workspace/references/orchestration.md +39 -22
  303. package/multi-agent-workspace/scripts/consume-workflow-plan.js +129 -0
  304. package/multi-agent-workspace/scripts/index_memory.js +79 -26
  305. package/multi-agent-workspace/scripts/path-boundary.js +59 -0
  306. package/multi-agent-workspace/scripts/project-docs-resolver.js +357 -0
  307. package/multi-agent-workspace/scripts/scaffold.js +258 -103
  308. package/multi-agent-workspace/templates/AGENTS.md +20 -0
  309. package/multi-agent-workspace/workflows/03-generation.md +8 -10
  310. package/multi-agent-workspace/workflows/04-launcher.md +5 -5
  311. package/opencode-plugin/README.md +114 -71
  312. package/opencode-plugin/hooks/verify.js +62 -12
  313. package/opencode-plugin/index.mjs +547 -0
  314. package/opencode-plugin/plugin.json +4 -12
  315. package/package.json +16 -3
  316. package/plugins/harness-everything/.codex-plugin/plugin.json +39 -0
  317. package/plugins/harness-everything/hooks/hooks.json +197 -0
  318. package/plugins/harness-everything/hooks/scripts/action-gate-rules.json +77 -0
  319. package/plugins/harness-everything/hooks/scripts/action-gate.js +528 -0
  320. package/plugins/harness-everything/hooks/scripts/atomic-commit-check.js +68 -0
  321. package/plugins/harness-everything/hooks/scripts/boundary-guard.js +55 -0
  322. package/plugins/harness-everything/hooks/scripts/codex-action-gate-post.js +46 -0
  323. package/plugins/harness-everything/hooks/scripts/codex-permission-request.js +140 -0
  324. package/plugins/harness-everything/hooks/scripts/context-compact.js +115 -0
  325. package/plugins/harness-everything/hooks/scripts/contract-test.js +146 -0
  326. package/plugins/harness-everything/hooks/scripts/depth-guard.js +65 -0
  327. package/plugins/harness-everything/hooks/scripts/lib/fable-contracts.js +121 -0
  328. package/plugins/harness-everything/hooks/scripts/lib/harness-state.js +275 -0
  329. package/plugins/harness-everything/hooks/scripts/lib/platforms/claude.js +168 -0
  330. package/plugins/harness-everything/hooks/scripts/lib/platforms/codex.js +88 -0
  331. package/plugins/harness-everything/hooks/scripts/lib/platforms/continue.js +91 -0
  332. package/plugins/harness-everything/hooks/scripts/lib/platforms/copilot.js +85 -0
  333. package/plugins/harness-everything/hooks/scripts/lib/platforms/cursor.js +89 -0
  334. package/plugins/harness-everything/hooks/scripts/lib/platforms/hermes.js +108 -0
  335. package/plugins/harness-everything/hooks/scripts/lib/platforms/index.js +17 -0
  336. package/plugins/harness-everything/hooks/scripts/lib/platforms/worktrees.js +20 -0
  337. package/plugins/harness-everything/hooks/scripts/lib/workspace.js +290 -0
  338. package/plugins/harness-everything/hooks/scripts/openai-session-start.js +81 -0
  339. package/plugins/harness-everything/hooks/scripts/reset-circuit-breaker.js +30 -0
  340. package/plugins/harness-everything/hooks/scripts/rule-of-3-tracker.js +125 -0
  341. package/plugins/harness-everything/hooks/scripts/rule-of-3.js +195 -0
  342. package/plugins/harness-everything/hooks/scripts/state-persist.js +100 -0
  343. package/plugins/harness-everything/hooks/scripts/stop-gate.js +80 -0
  344. package/plugins/harness-everything/hooks/scripts/subagent-scope-guard.js +194 -0
  345. package/plugins/harness-everything/hooks/scripts/subagent-start.js +19 -0
  346. package/plugins/harness-everything/hooks/scripts/subagent-stop.js +6 -0
  347. package/plugins/harness-everything/hooks/session-start.js +81 -0
  348. package/plugins/harness-everything/plugin.json +43 -0
  349. package/plugins/harness-everything/scripts/lib/advisory-text.js +174 -0
  350. package/plugins/harness-everything/skills/environment-detection/SKILL.md +41 -0
  351. package/plugins/harness-everything/skills/environment-detection/references/shell-syntax-rules.md +76 -0
  352. package/plugins/harness-everything/skills/environment-detection/scripts/preflight.js +103 -0
  353. package/plugins/harness-everything/skills/eval-harness/SKILL.md +42 -0
  354. package/plugins/harness-everything/skills/eval-harness/references/scoring-rubric.md +68 -0
  355. package/plugins/harness-everything/skills/eval-harness/scripts/evaluate.js +79 -0
  356. package/plugins/harness-everything/skills/fable-discipline/SKILL.md +41 -0
  357. package/plugins/harness-everything/skills/fable-discipline/references/discipline-rules.md +22 -0
  358. package/plugins/harness-everything/skills/fable-mode/CONTRACT-FORMAT.md +165 -0
  359. package/plugins/harness-everything/skills/fable-mode/SKILL.md +39 -0
  360. package/plugins/harness-everything/skills/fable-mode/agents/fable-orchestrator.md +159 -0
  361. package/plugins/harness-everything/skills/fable-mode/agents/fable-verifier.md +32 -0
  362. package/plugins/harness-everything/skills/fable-mode/agents/fable-worker-haiku.md +52 -0
  363. package/plugins/harness-everything/skills/fable-mode/agents/fable-worker-sonnet.md +53 -0
  364. package/plugins/harness-everything/skills/fable-mode/execution-guardrails/SKILL.md +43 -0
  365. package/plugins/harness-everything/skills/fable-mode/execution-guardrails/references/guardrail-rules.md +56 -0
  366. package/plugins/harness-everything/skills/fable-mode/fable-haiku/SKILL.md +36 -0
  367. package/plugins/harness-everything/skills/fable-mode/fable-haiku/references/routing-notes.md +26 -0
  368. package/plugins/harness-everything/skills/fable-mode/fable-opus/SKILL.md +36 -0
  369. package/plugins/harness-everything/skills/fable-mode/fable-sonnet/SKILL.md +36 -0
  370. package/plugins/harness-everything/skills/fable-mode/model-matrix.json +32 -0
  371. package/plugins/harness-everything/skills/fable-mode/references/execution-phases.md +37 -0
  372. package/plugins/harness-everything/skills/fable-mode/references/model-matrix.md +39 -0
  373. package/plugins/harness-everything/skills/fable-mode/scripts/ensemble-review.js +371 -0
  374. package/plugins/harness-everything/skills/fable-mode/scripts/model-selector.js +260 -0
  375. package/plugins/harness-everything/skills/fable-mode/scripts/workflow-plan-consumer.js +337 -0
  376. package/plugins/harness-everything/skills/find-skills/SKILL.md +48 -0
  377. package/plugins/harness-everything/skills/find-skills/references/discovery-flow.md +102 -0
  378. package/plugins/harness-everything/skills/find-skills/scripts/use-skill.js +112 -0
  379. package/plugins/harness-everything/skills/git-commit/SKILL.md +38 -0
  380. package/plugins/harness-everything/skills/git-commit/guides/ANGULAR_STYLE.md +61 -0
  381. package/plugins/harness-everything/skills/git-commit/guides/COMMIT_GENERATION.md +24 -0
  382. package/plugins/harness-everything/skills/git-commit/guides/LANGUAGE_DETECTION.md +16 -0
  383. package/plugins/harness-everything/skills/git-commit/guides/MAIN_REPO.md +15 -0
  384. package/plugins/harness-everything/skills/git-commit/guides/SUBMODULES.md +27 -0
  385. package/plugins/harness-everything/skills/git-commit/references/commit-flow.md +30 -0
  386. package/plugins/harness-everything/skills/grill-me/SKILL.md +43 -0
  387. package/plugins/harness-everything/skills/grill-me/references/grilling-playbook.md +38 -0
  388. package/plugins/harness-everything/skills/grill-with-docs/ADR-FORMAT.md +47 -0
  389. package/plugins/harness-everything/skills/grill-with-docs/CONTEXT-FORMAT.md +63 -0
  390. package/plugins/harness-everything/skills/grill-with-docs/SKILL.md +41 -0
  391. package/plugins/harness-everything/skills/grill-with-docs/references/session-playbook.md +99 -0
  392. package/plugins/harness-everything/skills/harness-everything/SKILL.md +43 -0
  393. package/plugins/harness-everything/skills/harness-everything/examples/README.md +9 -0
  394. package/plugins/harness-everything/skills/harness-everything/examples/todo-workflow.md +22 -0
  395. package/plugins/harness-everything/skills/harness-everything/references/router-workflow-plan.md +128 -0
  396. package/plugins/harness-everything/skills/harness-everything/references/skill-registry.md +39 -0
  397. package/plugins/harness-everything/skills/harness-everything/references/triage-and-tiers.md +193 -0
  398. package/plugins/harness-everything/skills/harness-everything/schemas/router-task-shape.schema.json +126 -0
  399. package/plugins/harness-everything/skills/harness-everything/schemas/router-workflow-plan.schema.json +149 -0
  400. package/plugins/harness-everything/skills/harness-everything/scripts/bootstrap.js +212 -0
  401. package/plugins/harness-everything/skills/harness-everything/scripts/ensemble-policy.js +102 -0
  402. package/plugins/harness-everything/skills/harness-everything/scripts/kernel-router.js +212 -0
  403. package/plugins/harness-everything/skills/harness-everything/scripts/router-contract.js +612 -0
  404. package/plugins/harness-everything/skills/harness-everything/scripts/routing-keywords.json +320 -0
  405. package/plugins/harness-everything/skills/harness-everything/scripts/self-heal.js +250 -0
  406. package/plugins/harness-everything/skills/harness-everything/scripts/tier-router.js +459 -0
  407. package/plugins/harness-everything/skills/harness-everything/scripts/verify-gate.js +100 -0
  408. package/plugins/harness-everything/skills/improve-codebase-architecture/SKILL.md +38 -0
  409. package/plugins/harness-everything/skills/improve-codebase-architecture/guides/DEEPENING.md +37 -0
  410. package/plugins/harness-everything/skills/improve-codebase-architecture/guides/HTML-REPORT.md +123 -0
  411. package/plugins/harness-everything/skills/improve-codebase-architecture/guides/INTERFACE-DESIGN.md +44 -0
  412. package/plugins/harness-everything/skills/improve-codebase-architecture/guides/LANGUAGE.md +53 -0
  413. package/plugins/harness-everything/skills/improve-codebase-architecture/guides/refactoring-workflow.md +59 -0
  414. package/plugins/harness-everything/skills/improve-codebase-architecture/references/README.md +16 -0
  415. package/plugins/harness-everything/skills/install-cognitive-os/SKILL.md +44 -0
  416. package/plugins/harness-everything/skills/install-cognitive-os/references/cognitive-loop.md +27 -0
  417. package/plugins/harness-everything/skills/multi-agent-workspace/NOTICE-agency-agents.md +27 -0
  418. package/plugins/harness-everything/skills/multi-agent-workspace/SKILL.md +38 -0
  419. package/plugins/harness-everything/skills/multi-agent-workspace/examples/csharp-monorepo.md +123 -0
  420. package/plugins/harness-everything/skills/multi-agent-workspace/examples/typescript-cli.md +126 -0
  421. package/plugins/harness-everything/skills/multi-agent-workspace/guidelines/platform-claude.md +77 -0
  422. package/plugins/harness-everything/skills/multi-agent-workspace/guidelines/platform-codex.md +146 -0
  423. package/plugins/harness-everything/skills/multi-agent-workspace/guidelines/platform-continue.md +38 -0
  424. package/plugins/harness-everything/skills/multi-agent-workspace/guidelines/platform-copilot.md +74 -0
  425. package/plugins/harness-everything/skills/multi-agent-workspace/guidelines/platform-cursor.md +40 -0
  426. package/plugins/harness-everything/skills/multi-agent-workspace/guidelines/platform-hermes.md +38 -0
  427. package/plugins/harness-everything/skills/multi-agent-workspace/guidelines/role-capabilities.md +108 -0
  428. package/plugins/harness-everything/skills/multi-agent-workspace/references/agency-agents.md +31 -0
  429. package/plugins/harness-everything/skills/multi-agent-workspace/references/architecture-guide.md +36 -0
  430. package/plugins/harness-everything/skills/multi-agent-workspace/references/orchestration.md +65 -0
  431. package/plugins/harness-everything/skills/multi-agent-workspace/scripts/agency-catalog.js +190 -0
  432. package/plugins/harness-everything/skills/multi-agent-workspace/scripts/consume-workflow-plan.js +129 -0
  433. package/plugins/harness-everything/skills/multi-agent-workspace/scripts/index_memory.js +104 -0
  434. package/plugins/harness-everything/skills/multi-agent-workspace/scripts/path-boundary.js +59 -0
  435. package/plugins/harness-everything/skills/multi-agent-workspace/scripts/project-docs-resolver.js +357 -0
  436. package/plugins/harness-everything/skills/multi-agent-workspace/scripts/scaffold.js +408 -0
  437. package/plugins/harness-everything/skills/multi-agent-workspace/templates/AGENTS.md +20 -0
  438. package/plugins/harness-everything/skills/multi-agent-workspace/templates/README.md +17 -0
  439. package/plugins/harness-everything/skills/multi-agent-workspace/templates/agent-launcher.md +68 -0
  440. package/plugins/harness-everything/skills/multi-agent-workspace/templates/backend-developer-csharp.md +56 -0
  441. package/plugins/harness-everything/skills/multi-agent-workspace/templates/backend-developer-polyglot.md +62 -0
  442. package/plugins/harness-everything/skills/multi-agent-workspace/templates/backend-developer-ts.md +57 -0
  443. package/plugins/harness-everything/skills/multi-agent-workspace/templates/memory-keeper.md +37 -0
  444. package/plugins/harness-everything/skills/multi-agent-workspace/templates/requirement-analyzer.md +66 -0
  445. package/plugins/harness-everything/skills/multi-agent-workspace/templates/specialist-agent.md +34 -0
  446. package/plugins/harness-everything/skills/multi-agent-workspace/templates/task-verifier.md +56 -0
  447. package/plugins/harness-everything/skills/multi-agent-workspace/templates/tool-maker.md +33 -0
  448. package/plugins/harness-everything/skills/multi-agent-workspace/workflows/01-init.md +22 -0
  449. package/plugins/harness-everything/skills/multi-agent-workspace/workflows/02-analysis.md +23 -0
  450. package/plugins/harness-everything/skills/multi-agent-workspace/workflows/03-generation.md +25 -0
  451. package/plugins/harness-everything/skills/multi-agent-workspace/workflows/04-launcher.md +16 -0
  452. package/plugins/harness-everything/skills/repo-docs/SKILL.md +42 -0
  453. package/plugins/harness-everything/skills/repo-docs/references/process-guide.md +60 -0
  454. package/plugins/harness-everything/skills/repo-docs/templates/agents-template.md +264 -0
  455. package/plugins/harness-everything/skills/repo-docs/templates/knowledge-agents-template.md +50 -0
  456. package/plugins/harness-everything/skills/repo-docs/templates/knowledge-readme-template.md +83 -0
  457. package/plugins/harness-everything/skills/repo-docs/templates/manual-recon.md +117 -0
  458. package/plugins/harness-everything/skills/repo-docs/templates/multi-skills-readme-template.md +72 -0
  459. package/plugins/harness-everything/skills/repo-docs/templates/product-agents-template.md +78 -0
  460. package/plugins/harness-everything/skills/repo-docs/templates/product-readme-template.md +95 -0
  461. package/plugins/harness-everything/skills/repo-docs/templates/readme-template.md +136 -0
  462. package/plugins/harness-everything/skills/repo-docs/templates/single-skill-readme-template.md +70 -0
  463. package/plugins/harness-everything/skills/repo-docs/templates/skills-agents-template.md +60 -0
  464. package/plugins/harness-everything/skills/rewrite-commits/SKILL.md +37 -0
  465. package/plugins/harness-everything/skills/rewrite-commits/references/workflow.md +18 -0
  466. package/plugins/harness-everything/skills/security-review/SKILL.md +47 -0
  467. package/plugins/harness-everything/skills/security-review/guides/OWASP-PATTERNS.md +31 -0
  468. package/plugins/harness-everything/skills/security-review/guides/STRIDE-THREAT-MODEL.md +21 -0
  469. package/plugins/harness-everything/skills/security-review/references/security-checklist.md +478 -0
  470. package/plugins/harness-everything/skills/security-review/scripts/audit-secrets.js +85 -0
  471. package/plugins/harness-everything/skills/self-evolve/SKILL.md +45 -0
  472. package/plugins/harness-everything/skills/self-evolve/references/memory-resolution.md +61 -0
  473. package/plugins/harness-everything/skills/self-evolve/scripts/persist-memory.js +272 -0
  474. package/plugins/harness-everything/skills/self-evolve/scripts/register-dynamic-skill.js +253 -0
  475. package/plugins/harness-everything/skills/self-evolve/scripts/self-regression.js +125 -0
  476. package/plugins/harness-everything/skills/skill-creator/SKILL.md +47 -0
  477. package/plugins/harness-everything/skills/skill-creator/references/authoring-workflow.md +14 -0
  478. package/plugins/harness-everything/skills/skill-creator/references/dynamic-generation-contract.md +21 -0
  479. package/plugins/harness-everything/skills/skill-creator/references/quality-checklist.md +16 -0
  480. package/plugins/harness-everything/skills/skill-creator/references/quality-principles.md +81 -0
  481. package/plugins/harness-everything/skills/skill-creator/references/testing-workflow.md +27 -0
  482. package/plugins/harness-everything/skills/skill-style/SKILL.md +42 -0
  483. package/plugins/harness-everything/skills/skill-style/references/style-guide.md +62 -0
  484. package/plugins/harness-everything/skills/tdd/SKILL.md +38 -0
  485. package/plugins/harness-everything/skills/tdd/fixtures/pass-evidence.json +100 -0
  486. package/plugins/harness-everything/skills/tdd/fixtures/quality-scenarios.json +20 -0
  487. package/plugins/harness-everything/skills/tdd/guides/deep-modules.md +33 -0
  488. package/plugins/harness-everything/skills/tdd/guides/interface-design.md +31 -0
  489. package/plugins/harness-everything/skills/tdd/guides/mocking.md +59 -0
  490. package/plugins/harness-everything/skills/tdd/guides/refactoring.md +10 -0
  491. package/plugins/harness-everything/skills/tdd/guides/tests.md +61 -0
  492. package/plugins/harness-everything/skills/tdd/references/core-discipline.md +51 -0
  493. package/plugins/harness-everything/skills/tdd/references/integration-testing.md +13 -0
  494. package/plugins/harness-everything/skills/tdd/references/quality-model.md +68 -0
  495. package/plugins/harness-everything/skills/tdd/references/unit-testing.md +15 -0
  496. package/plugins/harness-everything/skills/tdd/schemas/quality-evidence.schema.json +211 -0
  497. package/plugins/harness-everything/skills/tdd/schemas/quality-report.schema.json +110 -0
  498. package/plugins/harness-everything/skills/tdd/scripts/quality-gate.js +475 -0
  499. package/plugins/harness-everything/skills/to-spec/SKILL.md +40 -0
  500. package/plugins/harness-everything/skills/to-spec/references/process.md +77 -0
  501. package/plugins/harness-everything/skills/to-spec/scripts/check-project-docs.js +239 -0
  502. package/plugins/harness-everything/skills/to-spec/scripts/project-docs-resolver.js +111 -0
  503. package/plugins/harness-everything/skills/to-spec/templates/cli-reference.md +31 -0
  504. package/plugins/harness-everything/skills/to-spec/templates/dev-doc.md +27 -0
  505. package/plugins/harness-everything/skills/to-spec/templates/feature-spec.md +59 -0
  506. package/plugins/harness-everything/skills/to-spec/templates/schema-doc.md +31 -0
  507. package/plugins/harness-everything/skills/to-tickets/SKILL.md +42 -0
  508. package/plugins/harness-everything/skills/to-tickets/references/publishing-and-templates.md +97 -0
  509. package/plugins/harness-everything/skills/todo-driven-workflow/SKILL.md +42 -0
  510. package/plugins/harness-everything/skills/todo-driven-workflow/references/execution-guide.md +27 -0
  511. package/plugins/harness-everything/skills/using-git-worktrees/SKILL.md +49 -0
  512. package/plugins/harness-everything/skills/using-git-worktrees/references/workflow-details.md +108 -0
  513. package/plugins/harness-everything/skills/verification-loop/SKILL.md +46 -0
  514. package/plugins/harness-everything/skills/verification-loop/references/verification-phases.md +116 -0
  515. package/plugins/harness-everything/skills/verification-loop/templates/verification-report.template.md +15 -0
  516. package/plugins/harness-everything/skills/verify-before-claim/SKILL.md +43 -0
  517. package/plugins/harness-everything/skills/verify-before-claim/references/verification-guide.md +73 -0
  518. package/plugins/harness-everything/skills/zoom-out/SKILL.md +42 -0
  519. package/plugins/harness-everything/skills/zoom-out/references/circuit-breaker.md +66 -0
  520. package/plugins/harness-everything/skills/zoom-out/templates/zoom-out-report.template.md +20 -0
  521. package/repo-docs/SKILL.md +3 -7
  522. package/rewrite-commits/SKILL.md +2 -2
  523. package/scripts/build-openai-submission.js +158 -0
  524. package/scripts/installer.js +86 -11
  525. package/scripts/lib/advisory-text.js +25 -18
  526. package/scripts/lib/execution-contract.js +70 -0
  527. package/scripts/lib/manifest.js +34 -38
  528. package/scripts/lib/path-boundary.js +90 -0
  529. package/scripts/lib/skills.js +124 -70
  530. package/scripts/lib/workspace.js +269 -7
  531. package/scripts/sync-openai-plugin.js +91 -0
  532. package/scripts/sync-release-version.js +212 -0
  533. package/security-review/SKILL.md +4 -7
  534. package/security-review/scripts/audit-secrets.js +9 -8
  535. package/self-evolve/SKILL.md +9 -11
  536. package/self-evolve/references/memory-resolution.md +4 -1
  537. package/self-evolve/scripts/persist-memory.js +221 -88
  538. package/self-evolve/scripts/register-dynamic-skill.js +12 -10
  539. package/self-evolve/scripts/self-regression.js +23 -8
  540. package/skill-creator/SKILL.md +12 -14
  541. package/skill-creator/references/quality-checklist.md +1 -0
  542. package/skill-style/SKILL.md +4 -4
  543. package/skill-style/references/style-guide.md +29 -3
  544. package/submission/openai/README.md +67 -0
  545. package/submission/openai/listing.json +31 -0
  546. package/submission/openai/test-cases.json +61 -0
  547. package/tdd/SKILL.md +20 -26
  548. package/tdd/fixtures/pass-evidence.json +100 -0
  549. package/tdd/fixtures/quality-scenarios.json +20 -0
  550. package/tdd/references/core-discipline.md +2 -0
  551. package/tdd/references/integration-testing.md +13 -0
  552. package/tdd/references/quality-model.md +68 -0
  553. package/tdd/references/unit-testing.md +15 -0
  554. package/tdd/schemas/quality-evidence.schema.json +211 -0
  555. package/tdd/schemas/quality-report.schema.json +110 -0
  556. package/tdd/scripts/quality-gate.js +475 -0
  557. package/to-spec/SKILL.md +3 -5
  558. package/to-spec/scripts/check-project-docs.js +42 -62
  559. package/to-spec/scripts/project-docs-resolver.js +111 -0
  560. package/to-tickets/SKILL.md +4 -4
  561. package/todo-driven-workflow/SKILL.md +6 -4
  562. package/using-git-worktrees/SKILL.md +3 -3
  563. package/verification-loop/SKILL.md +4 -4
  564. package/verify-before-claim/SKILL.md +2 -2
  565. package/zoom-out/SKILL.md +4 -4
  566. package/ab-test-deep-analysis-2026-08-27.md +0 -383
  567. package/ab-test-results-2026-08-27.md +0 -152
  568. package/ab-test-statistical-analysis-31cases.md +0 -193
  569. package/opencode-plugin/hooks/circuit-breaker.js +0 -121
  570. package/opencode-plugin/hooks/compliance.js +0 -128
  571. package/opencode-plugin/hooks/post-edit.js +0 -62
  572. package/opencode-plugin/hooks/pre-complete.js +0 -65
  573. package/opencode-plugin/test-hook-firing.js +0 -198
@@ -0,0 +1,461 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const crypto = require('crypto');
4
+ const { pathToFileURL } = require('url');
5
+ const helper = require('./test-helper');
6
+
7
+ console.log('\n[2n] opencode enforcement plugin: real hook API and firing behavior...');
8
+
9
+ const pluginDir = path.join(helper.root, 'opencode-plugin');
10
+ const pluginFile = path.join(pluginDir, 'index.mjs');
11
+ const pluginManifestFile = path.join(pluginDir, 'plugin.json');
12
+
13
+ helper.check(
14
+ '2n. the repository inventory manifest exists',
15
+ fs.existsSync(pluginManifestFile),
16
+ `${pluginManifestFile} missing`
17
+ );
18
+
19
+ if (fs.existsSync(pluginManifestFile)) {
20
+ const manifest = JSON.parse(fs.readFileSync(pluginManifestFile, 'utf8'));
21
+ const onDisk = fs.readdirSync(path.join(pluginDir, 'hooks'))
22
+ .filter((file) => file.endsWith('.js'))
23
+ .sort();
24
+ const declared = Object.values(manifest.hooks || {})
25
+ .map((file) => path.relative(path.join(pluginDir, 'hooks'), path.join(pluginDir, file)))
26
+ .sort();
27
+ helper.check(
28
+ '2n. every standalone hook file on disk is declared in the inventory manifest',
29
+ onDisk.every((file) => declared.includes(file)),
30
+ `undeclared: ${onDisk.filter((file) => !declared.includes(file)).join(', ')}`
31
+ );
32
+ helper.check(
33
+ '2n. every declared inventory hook path exists on disk',
34
+ declared.every((file) => onDisk.includes(file)),
35
+ `dangling: ${declared.filter((file) => !onDisk.includes(file)).join(', ')}`
36
+ );
37
+ helper.check(
38
+ '2n. no standalone hook is declared more than once',
39
+ new Set(declared).size === declared.length,
40
+ declared.join(', ')
41
+ );
42
+ }
43
+
44
+ helper.check(
45
+ '2n. the plugin ships as a single self-contained module (installable by copying one file)',
46
+ fs.existsSync(pluginFile) && !/require\(['"]\.\//.test(fs.readFileSync(pluginFile, 'utf8')),
47
+ `${pluginFile} missing or imports a sibling file`
48
+ );
49
+
50
+ // Hook firing, against opencode's real Hooks interface (verified from
51
+ // packages/plugin/src/index.ts and packages/web/.../plugins.mdx in the
52
+ // opencode source - see opencode-plugin/README.md). Two isolations are
53
+ // load-bearing: HOME -> temp, because the plugin persists under
54
+ // ~/.agents/harness-everything/ and an un-redirected run would corrupt a
55
+ // real session's circuit-breaker state; workspace -> temp, with a
56
+ // package.json this test controls, so verification pass/fail is
57
+ // deterministic.
58
+ (async () => {
59
+ const fakeHome = helper.tempDir('.mechanism-test-opencode-home');
60
+ const workspace = helper.tempDir('.mechanism-test-opencode-workspace');
61
+ fs.mkdirSync(fakeHome, { recursive: true });
62
+ fs.mkdirSync(workspace, { recursive: true });
63
+ process.env.HOME = fakeHome;
64
+ process.env.USERPROFILE = fakeHome;
65
+ delete process.env.HARNESS_STATE_HOME;
66
+
67
+ // Mirrors the plugin's own getWorkspaceKey(directory) exactly (issue #42
68
+ // item #4) - duplicated here rather than imported since the plugin only
69
+ // exports HarnessEnforcement.
70
+ const realWorkspace = fs.realpathSync(path.resolve(workspace));
71
+ const slug = path.basename(realWorkspace).toLowerCase().replace(/[^a-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '') || 'workspace';
72
+ const hashInput = process.platform === 'win32' ? realWorkspace.toLowerCase() : realWorkspace;
73
+ const hash = crypto.createHash('sha1').update(hashInput).digest('hex').slice(0, 12);
74
+ const stateHome = path.join(fakeHome, '.agents', 'harness-everything');
75
+ const stateRoot = path.join(stateHome, 'workspaces', `${slug}-${hash}`, 'state');
76
+ const stateDir = path.join(stateRoot, 'sessions', 's1');
77
+ const secondSessionDir = path.join(stateRoot, 'sessions', 's2');
78
+ const retrySessionDir = path.join(stateRoot, 'sessions', 'prompt-retry');
79
+ const editStateFile = path.join(stateDir, 'edit-state.json');
80
+ const breakerFile = path.join(stateDir, 'circuit-breaker.json');
81
+ const complianceFile = path.join(stateDir, 'compliance.json');
82
+ const legacyDir = path.join(fakeHome, '.harness-state');
83
+ fs.mkdirSync(legacyDir, { recursive: true });
84
+ fs.writeFileSync(path.join(legacyDir, 'circuit-breaker.json'), JSON.stringify({ legacy: true }));
85
+
86
+ function writePackageScript(script) {
87
+ fs.writeFileSync(
88
+ path.join(workspace, 'package.json'),
89
+ JSON.stringify({ name: 'fixture', version: '0.0.0', scripts: { test: script } }, null, 2)
90
+ );
91
+ }
92
+
93
+ const promptCalls = [];
94
+ let rejectNextPrompt = false;
95
+ const mockClient = {
96
+ session: {
97
+ prompt: async (opts) => {
98
+ promptCalls.push(opts);
99
+ if (rejectNextPrompt) {
100
+ rejectNextPrompt = false;
101
+ throw new Error('prompt transport unavailable');
102
+ }
103
+ return {};
104
+ }
105
+ }
106
+ };
107
+
108
+ const { HarnessEnforcement } = await import(pathToFileURL(pluginFile).href);
109
+
110
+ const hooks = await HarnessEnforcement({
111
+ client: mockClient,
112
+ directory: workspace,
113
+ worktree: workspace,
114
+ project: {},
115
+ serverUrl: new URL('http://localhost:4096'),
116
+ experimental_workspace: { register() {} },
117
+ $: async () => {}
118
+ });
119
+
120
+ helper.check(
121
+ '2n. exports exactly the hooks it uses (tool.execute.before/after, event)',
122
+ ['tool.execute.before', 'tool.execute.after', 'event'].every((k) => typeof hooks[k] === 'function') &&
123
+ Object.keys(hooks).length === 3,
124
+ `got keys: ${Object.keys(hooks).join(', ')}`
125
+ );
126
+
127
+ // --- edit tracking (tool.execute.after) ---
128
+ await hooks['tool.execute.after']({ tool: 'read', sessionID: 's1', callID: 'c0' }, { title: '', output: '', metadata: {} });
129
+ helper.check('2n. non-edit tools do not mark verification pending', !fs.existsSync(editStateFile), 'edit-state.json created by a read');
130
+
131
+ await hooks['tool.execute.after']({ tool: 'edit', sessionID: 's1', callID: 'c1' }, { title: '', output: '', metadata: {} });
132
+ let editState = JSON.parse(fs.readFileSync(editStateFile, 'utf8'));
133
+ helper.check(
134
+ '2n. an edit tool call marks verification pending',
135
+ editState.verificationPending === true && editState.editsSinceVerification === 1,
136
+ JSON.stringify(editState)
137
+ );
138
+
139
+ // --- verification gate on session.idle: passing case clears pending ---
140
+ writePackageScript('node -e "process.exit(0)"');
141
+ await hooks.event({ event: { type: 'session.idle', properties: { sessionID: 's1' } } });
142
+ editState = JSON.parse(fs.readFileSync(editStateFile, 'utf8'));
143
+ helper.check(
144
+ '2n. a passing verification on session.idle clears pending state',
145
+ editState.verificationPending === false && editState.editsSinceVerification === 0,
146
+ JSON.stringify(editState)
147
+ );
148
+ helper.check('2n. no follow-up prompt is sent when verification passes', promptCalls.length === 0, `${promptCalls.length} prompt(s) sent`);
149
+
150
+ // --- verification gate: failing case drives the circuit breaker ---
151
+ writePackageScript('node -e "process.exit(1)"');
152
+
153
+ for (let i = 1; i <= 2; i++) {
154
+ await hooks['tool.execute.after']({ tool: 'edit', sessionID: 's1', callID: `c${i + 1}` }, { title: '', output: '', metadata: {} });
155
+ await hooks.event({ event: { type: 'session.idle', properties: { sessionID: 's1' } } });
156
+ }
157
+ let breaker = JSON.parse(fs.readFileSync(breakerFile, 'utf8'));
158
+ const sig = Object.keys(breaker.failures)[0];
159
+ helper.check(
160
+ '2n. two failures on the same signature: still allowed, not yet tripped',
161
+ breaker.failures[sig].count === 2 && breaker.hardLock === false,
162
+ JSON.stringify(breaker)
163
+ );
164
+ helper.check('2n. each failed verification sends exactly one follow-up prompt', promptCalls.length === 2, `${promptCalls.length} prompt(s) sent`);
165
+
166
+ // 3rd failure -> force reflection
167
+ await hooks['tool.execute.after']({ tool: 'edit', sessionID: 's1', callID: 'c4' }, { title: '', output: '', metadata: {} });
168
+ await hooks.event({ event: { type: 'session.idle', properties: { sessionID: 's1' } } });
169
+ breaker = JSON.parse(fs.readFileSync(breakerFile, 'utf8'));
170
+ let compliance = JSON.parse(fs.readFileSync(complianceFile, 'utf8'));
171
+ helper.check(
172
+ '2n. 3rd failure on the same signature forces reflection',
173
+ breaker.failures[sig].count === 3 && compliance.reflectionsForced === 1,
174
+ JSON.stringify({ breaker, compliance })
175
+ );
176
+ helper.check(
177
+ '2n. the forced-reflection prompt says to reflect',
178
+ /reflect/i.test(promptCalls[promptCalls.length - 1].body.parts[0].text),
179
+ promptCalls[promptCalls.length - 1].body.parts[0].text
180
+ );
181
+
182
+ let pendingEditBlocked = false;
183
+ try {
184
+ await hooks['tool.execute.before']({ tool: 'edit', sessionID: 's1', callID: 'pending-edit' }, { args: {} });
185
+ } catch {
186
+ pendingEditBlocked = true;
187
+ }
188
+ helper.check('2n. pending reflection blocks another code edit', pendingEditBlocked, 'tool.execute.before allowed an edit before the report');
189
+
190
+ let pendingReadAllowed = true;
191
+ try {
192
+ await hooks['tool.execute.before']({ tool: 'read', sessionID: 's1', callID: 'pending-read' }, { args: {} });
193
+ } catch {
194
+ pendingReadAllowed = false;
195
+ }
196
+ helper.check('2n. pending reflection still allows read tools', pendingReadAllowed, 'tool.execute.before blocked a read during reflection');
197
+
198
+ // Complete the reflection through the real hook sequence: the forced
199
+ // prompt names a token and artifact, then the agent writes that artifact
200
+ // through an edit-shaped tool call. No test seeding of breaker state.
201
+ const reflectionPrompt = promptCalls[promptCalls.length - 1].body.parts[0].text;
202
+ const reflectionTokenMatch = reflectionPrompt.match(/reflection token: ([A-Za-z0-9_-]+)/i);
203
+ helper.check('2n. the forced-reflection prompt names a verifiable artifact token', !!reflectionTokenMatch, reflectionPrompt);
204
+ const reflectionToken = reflectionTokenMatch && reflectionTokenMatch[1];
205
+ const reflectionFile = path.join(stateDir, 'zoom-out-report.md');
206
+ fs.writeFileSync(
207
+ reflectionFile,
208
+ `RESUME: misplaced\n## Goal\nfix the fixture\n## Failed Attempts\nthree retries\n## Verified Facts\ntest remains red\n## Diagnosis\nthe fixture intentionally fails\n## Decision\nA decision without a directive\nReflection token: ${reflectionToken}\n`,
209
+ 'utf8'
210
+ );
211
+ await hooks['tool.execute.after'](
212
+ { tool: 'write', sessionID: 's1', callID: 'reflection' },
213
+ { title: '', output: '', metadata: {}, args: { filePath: reflectionFile } }
214
+ );
215
+ breaker = JSON.parse(fs.readFileSync(breakerFile, 'utf8'));
216
+ helper.check(
217
+ '2n. a directive outside the Decision section does not complete reflection',
218
+ breaker.reflectionPending === true && breaker.lastReflection === null,
219
+ JSON.stringify(breaker)
220
+ );
221
+
222
+ fs.writeFileSync(
223
+ reflectionFile,
224
+ `## Goal\nfix the fixture\n## Failed Attempts\nthree retries\n## Verified Facts\ntest remains red\n## Diagnosis\nthe fixture intentionally fails\n## Decision\nRESUME: change the fixture\nReflection token: ${reflectionToken}\n`,
225
+ 'utf8'
226
+ );
227
+ await hooks['tool.execute.after'](
228
+ { tool: 'apply_patch', sessionID: 's1', callID: 'reflection-patch' },
229
+ {
230
+ title: '',
231
+ output: '',
232
+ metadata: {},
233
+ args: { patchText: `*** Begin Patch\n*** Update File: ${reflectionFile}\n@@\n*** End Patch` }
234
+ }
235
+ );
236
+ breaker = JSON.parse(fs.readFileSync(breakerFile, 'utf8'));
237
+ helper.check(
238
+ '2n. apply_patch reflection completes the lifecycle and persists lastReflection',
239
+ Number.isFinite(breaker.lastReflection) && breaker.lastReflection > 0 && breaker.reflectionPending === false,
240
+ JSON.stringify(breaker)
241
+ );
242
+
243
+ // A repeated idle event after the follow-up is already pending is a no-op.
244
+ // The breaker must wait for a fresh code edit before counting another try.
245
+ const promptsAfterReflection = promptCalls.length;
246
+ await hooks.event({ event: { type: 'session.idle', properties: { sessionID: 's1' } } });
247
+ helper.check(
248
+ '2n. repeated idle does not duplicate verification or follow-up prompts',
249
+ promptCalls.length === promptsAfterReflection,
250
+ `${promptCalls.length} prompt(s) sent`
251
+ );
252
+
253
+ const patchSessionDir = path.join(stateRoot, 'sessions', 'patch-only');
254
+ const patchBreakerFile = path.join(patchSessionDir, 'circuit-breaker.json');
255
+ const patchReflectionFile = path.join(patchSessionDir, 'zoom-out-report.md');
256
+ const patchReflectionToken = 'patch-reflection-token';
257
+ fs.mkdirSync(patchSessionDir, { recursive: true });
258
+ fs.writeFileSync(
259
+ patchBreakerFile,
260
+ JSON.stringify({
261
+ failures: { fixture: { count: 3, firstSeen: Date.now() - 1000 } },
262
+ hardLock: false,
263
+ lastReflection: null,
264
+ lastReflectionSignature: null,
265
+ reflectionPending: true,
266
+ reflectionRequestedAt: Date.now(),
267
+ reflectionToken: patchReflectionToken,
268
+ reflectionSignature: 'fixture'
269
+ }, null, 2),
270
+ 'utf8'
271
+ );
272
+ fs.writeFileSync(
273
+ patchReflectionFile,
274
+ `## Goal\nfix the fixture\n## Failed Attempts\nthree retries\n## Verified Facts\ntest remains red\n## Diagnosis\nthe fixture intentionally fails\n## Decision\nRESUME: change the fixture\nReflection token: ${patchReflectionToken}\n`,
275
+ 'utf8'
276
+ );
277
+ const patchText = `*** Begin Patch\n*** Update File: ${patchReflectionFile}\n@@\n*** End Patch`;
278
+ let patchBeforeAllowed = true;
279
+ try {
280
+ await hooks['tool.execute.before'](
281
+ { tool: 'apply_patch', sessionID: 'patch-only', callID: 'patch-before' },
282
+ { args: { patchText } }
283
+ );
284
+ } catch (error) {
285
+ patchBeforeAllowed = false;
286
+ }
287
+ helper.check(
288
+ '2n. apply_patch is allowed to write a pending reflection artifact',
289
+ patchBeforeAllowed,
290
+ 'tool.execute.before blocked output.args.patchText'
291
+ );
292
+ await hooks['tool.execute.after'](
293
+ { tool: 'apply_patch', sessionID: 'patch-only', callID: 'patch-after' },
294
+ { title: '', output: '', metadata: {}, args: { patchText } }
295
+ );
296
+ const patchBreaker = JSON.parse(fs.readFileSync(patchBreakerFile, 'utf8'));
297
+ helper.check(
298
+ '2n. apply_patch output args complete a pending reflection',
299
+ patchBreaker.reflectionPending === false && Number.isFinite(patchBreaker.lastReflection),
300
+ JSON.stringify(patchBreaker)
301
+ );
302
+
303
+ const mixedPatchSessionDir = path.join(stateRoot, 'sessions', 'mixed-patch');
304
+ const mixedPatchBreakerFile = path.join(mixedPatchSessionDir, 'circuit-breaker.json');
305
+ const mixedPatchReflectionFile = path.join(mixedPatchSessionDir, 'zoom-out-report.md');
306
+ fs.mkdirSync(mixedPatchSessionDir, { recursive: true });
307
+ fs.writeFileSync(mixedPatchBreakerFile, JSON.stringify({
308
+ failures: { fixture: { count: 3, firstSeen: Date.now() - 1000 } },
309
+ hardLock: false,
310
+ lastReflection: null,
311
+ lastReflectionSignature: null,
312
+ reflectionPending: true,
313
+ reflectionRequestedAt: Date.now(),
314
+ reflectionToken: 'mixed-patch-token',
315
+ reflectionSignature: 'fixture'
316
+ }, null, 2));
317
+ fs.writeFileSync(mixedPatchReflectionFile, [
318
+ '## Goal', 'fix the fixture', '## Failed Attempts', 'three retries',
319
+ '## Verified Facts', 'test remains red', '## Diagnosis', 'the fixture intentionally fails',
320
+ '## Decision', 'RESUME: change the fixture', 'Reflection token: mixed-patch-token', ''
321
+ ].join('\n'));
322
+ let mixedPatchBlocked = false;
323
+ try {
324
+ await hooks['tool.execute.before'](
325
+ { tool: 'apply_patch', sessionID: 'mixed-patch', callID: 'mixed-before' },
326
+ { args: { patchText: `*** Begin Patch\n*** Update File: ${mixedPatchReflectionFile}\n@@\n*** Update File: ${path.join(workspace, 'unrelated.js')}\n@@\n*** End Patch` } }
327
+ );
328
+ } catch {
329
+ mixedPatchBlocked = true;
330
+ }
331
+ helper.check('2n. mixed apply_patch cannot bypass a pending reflection', mixedPatchBlocked, 'unrelated files were accepted with the reflection artifact');
332
+
333
+ // A new code edit after a completed reflection is the post-reflection retry.
334
+ await hooks['tool.execute.after']({ tool: 'edit', sessionID: 's1', callID: 'c5' }, { title: '', output: '', metadata: {} });
335
+ await hooks.event({ event: { type: 'session.idle', properties: { sessionID: 's1' } } });
336
+ breaker = JSON.parse(fs.readFileSync(breakerFile, 'utf8'));
337
+ helper.check('2n. the same failure returning after a reflection hard-locks the breaker', breaker.hardLock === true, JSON.stringify(breaker));
338
+ helper.check(
339
+ '2n. the hard-lock prompt says locked',
340
+ /lock/i.test(promptCalls[promptCalls.length - 1].body.parts[0].text),
341
+ promptCalls[promptCalls.length - 1].body.parts[0].text
342
+ );
343
+
344
+ // --- circuit breaker enforcement (tool.execute.before) ---
345
+ let blocked = false;
346
+ try {
347
+ await hooks['tool.execute.before']({ tool: 'edit', sessionID: 's1', callID: 'c6' }, { args: {} });
348
+ } catch {
349
+ blocked = true;
350
+ }
351
+ helper.check('2n. hard-locked breaker throws on the next edit attempt', blocked, 'tool.execute.before did not throw');
352
+
353
+ let readBlocked = false;
354
+ try {
355
+ await hooks['tool.execute.before']({ tool: 'read', sessionID: 's1', callID: 'c7' }, { args: {} });
356
+ } catch {
357
+ readBlocked = true;
358
+ }
359
+ helper.check('2n. a hard lock only blocks edit-shaped tools, not read', !readBlocked, 'tool.execute.before blocked a read');
360
+
361
+ // The same workspace can host independent sessions. Session s2 starts with
362
+ // a fresh breaker and cannot inherit s1's hard lock.
363
+ await hooks['tool.execute.after']({ tool: 'edit', sessionID: 's2', callID: 's2-c1', args: {} }, { title: '', output: '', metadata: {} });
364
+ await hooks.event({ event: { type: 'session.idle', properties: { sessionID: 's2' } } });
365
+ const secondBreaker = JSON.parse(fs.readFileSync(path.join(secondSessionDir, 'circuit-breaker.json'), 'utf8'));
366
+ helper.check(
367
+ '2n. a second session has an independent breaker stream',
368
+ secondBreaker.hardLock === false && Object.values(secondBreaker.failures).some((entry) => entry.count === 1),
369
+ JSON.stringify(secondBreaker)
370
+ );
371
+
372
+ const corruptSessionDir = path.join(stateRoot, 'sessions', 'corrupt');
373
+ const corruptBreakerFile = path.join(corruptSessionDir, 'circuit-breaker.json');
374
+ fs.mkdirSync(corruptSessionDir, { recursive: true });
375
+ fs.writeFileSync(corruptBreakerFile, '{not-json', 'utf8');
376
+ let corruptStateBlocked = false;
377
+ let corruptStateMessage = '';
378
+ try {
379
+ await hooks['tool.execute.before']({ tool: 'edit', sessionID: 'corrupt', callID: 'corrupt-edit' }, { args: {} });
380
+ } catch (error) {
381
+ corruptStateBlocked = true;
382
+ corruptStateMessage = String(error && error.message ? error.message : error);
383
+ }
384
+ helper.check(
385
+ '2n. corrupt breaker state fails closed before an edit',
386
+ corruptStateBlocked && /corrupt/i.test(corruptStateMessage),
387
+ corruptStateMessage || 'tool.execute.before allowed an edit with corrupt breaker state'
388
+ );
389
+
390
+ // A prompt transport failure is retryable without re-running verification
391
+ // or incrementing the circuit-breaker count.
392
+ const promptsBeforeDeliveryFailure = promptCalls.length;
393
+ rejectNextPrompt = true;
394
+ await hooks['tool.execute.after']({ tool: 'edit', sessionID: 'prompt-retry', callID: 'retry-c1' }, { title: '', output: '', metadata: {} });
395
+ await hooks.event({ event: { type: 'session.idle', properties: { sessionID: 'prompt-retry' } } });
396
+ let retryEditState = JSON.parse(fs.readFileSync(path.join(retrySessionDir, 'edit-state.json'), 'utf8'));
397
+ let retryBreaker = JSON.parse(fs.readFileSync(path.join(retrySessionDir, 'circuit-breaker.json'), 'utf8'));
398
+ helper.check(
399
+ '2n. failed prompt delivery remains retryable',
400
+ promptCalls.length === promptsBeforeDeliveryFailure + 1 &&
401
+ retryEditState.followUpPending === false &&
402
+ retryEditState.followUpDeliveryPending === true &&
403
+ retryBreaker.failures[Object.keys(retryBreaker.failures)[0]].count === 1,
404
+ JSON.stringify({ retryEditState, retryBreaker })
405
+ );
406
+ await hooks.event({ event: { type: 'session.idle', properties: { sessionID: 'prompt-retry' } } });
407
+ retryEditState = JSON.parse(fs.readFileSync(path.join(retrySessionDir, 'edit-state.json'), 'utf8'));
408
+ retryBreaker = JSON.parse(fs.readFileSync(path.join(retrySessionDir, 'circuit-breaker.json'), 'utf8'));
409
+ helper.check(
410
+ '2n. next idle retries the saved follow-up exactly once',
411
+ promptCalls.length === promptsBeforeDeliveryFailure + 2 &&
412
+ retryEditState.followUpPending === true &&
413
+ retryEditState.followUpDeliveryPending === false &&
414
+ retryBreaker.failures[Object.keys(retryBreaker.failures)[0]].count === 1,
415
+ JSON.stringify({ retryEditState, retryBreaker })
416
+ );
417
+
418
+ // A fresh session event is the explicit reset contract for a reused session
419
+ // identity, and it removes only that session's plugin state.
420
+ await hooks.event({ event: { type: 'session.created', properties: { info: { id: 's1' } } } });
421
+ helper.check('2n. session.created resets only the current session state', !fs.existsSync(breakerFile), breakerFile);
422
+ let resetAllowed = true;
423
+ try {
424
+ await hooks['tool.execute.before']({ tool: 'edit', sessionID: 's1', callID: 'after-reset' }, { args: {} });
425
+ } catch {
426
+ resetAllowed = false;
427
+ }
428
+ helper.check('2n. a reset session can edit again', resetAllowed, 'tool.execute.before remained locked');
429
+
430
+ // Invalid session IDs are hashed into a child directory. Resetting one can
431
+ // therefore never recurse into the workspace state parent or another path.
432
+ const sessionsRoot = path.join(stateRoot, 'sessions');
433
+ const outsideSentinel = path.join(stateHome, 'outside-session-sentinel');
434
+ fs.mkdirSync(outsideSentinel, { recursive: true });
435
+ fs.writeFileSync(path.join(outsideSentinel, 'keep.txt'), 'keep');
436
+ await hooks['tool.execute.after']({ tool: 'edit', sessionID: '..', callID: 'dotdot' }, { title: '', output: '', metadata: {} });
437
+ await hooks['tool.execute.after']({ tool: 'edit', sessionID: '.', callID: 'dot' }, { title: '', output: '', metadata: {} });
438
+ await hooks.event({ event: { type: 'session.created', properties: { info: { id: '../../outside-session-sentinel' } } } });
439
+ const sessionEntries = fs.readdirSync(sessionsRoot, { withFileTypes: true });
440
+ helper.check(
441
+ '2n. dot and dotdot session IDs never escape the sessions root',
442
+ !fs.existsSync(path.join(stateRoot, 'edit-state.json')) &&
443
+ sessionEntries.every((entry) => entry.isDirectory() && !['.', '..'].includes(entry.name)),
444
+ sessionEntries.map((entry) => entry.name).join(', ')
445
+ );
446
+ helper.check('2n. resetting an unsafe session cannot delete an outside sentinel', fs.existsSync(path.join(outsideSentinel, 'keep.txt')), outsideSentinel);
447
+ helper.check('2n. resetting s1 leaves the independent s2 state', fs.existsSync(path.join(secondSessionDir, 'circuit-breaker.json')), secondSessionDir);
448
+
449
+ const workspaceStateRoot = path.dirname(stateRoot);
450
+ helper.check(
451
+ '2n. legacy flat state migrates into the workspace-keyed root',
452
+ !fs.existsSync(legacyDir) && fs.existsSync(path.join(workspaceStateRoot, 'circuit-breaker.json')),
453
+ JSON.stringify({ legacyDir, workspaceStateRoot })
454
+ );
455
+ helper.check('2n. hook state stayed inside the redirected HOME', fs.existsSync(stateRoot), `no state root under ${stateHome}`);
456
+
457
+ helper.finish();
458
+ })().catch((err) => {
459
+ console.error('2n. opencode plugin test crashed:', err);
460
+ process.exit(1);
461
+ });
@@ -0,0 +1,183 @@
1
+ const fs = require('fs');
2
+ const os = require('os');
3
+ const path = require('path');
4
+ const { spawnSync } = require('child_process');
5
+ const helper = require('./test-helper');
6
+ const { getRuntimeRoot } = require('../multi-agent-workspace/scripts/project-docs-resolver');
7
+
8
+ console.log('\n[2o] State-home isolation: runtime state never scatters into cwd (issue #42)...');
9
+
10
+ const repoRoot = helper.root;
11
+ let failures = 0;
12
+ function check(name, condition, detail) {
13
+ if (condition) {
14
+ console.log(`✅ ${name}`);
15
+ } else {
16
+ console.error(`❌ ${name}`);
17
+ if (detail) console.error(` ${detail}`);
18
+ failures++;
19
+ }
20
+ }
21
+
22
+ function canonicalPath(value) {
23
+ const resolved = path.resolve(value);
24
+ try { return fs.realpathSync(resolved); } catch (e) { return resolved; }
25
+ }
26
+
27
+ // Everything below runs with HOME/USERPROFILE redirected to a throwaway
28
+ // fake home, so this test can never touch (or be polluted by) the real
29
+ // developer's ~/.agents/harness-everything - same isolation approach as
30
+ // mechanism-2n-opencode-plugin.test.js.
31
+ const fakeHome = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-2o-home-'));
32
+ const stateHome = path.join(fakeHome, '.agents', 'harness-everything');
33
+ const baseEnv = { ...process.env, HOME: fakeHome, USERPROFILE: fakeHome };
34
+ delete baseEnv.HARNESS_STATE_HOME;
35
+ delete baseEnv.CLAUDE;
36
+ baseEnv.HARNESS_STATE_HOME = stateHome;
37
+ process.env.HARNESS_STATE_HOME = stateHome;
38
+
39
+ function listFilesRecursive(dir) {
40
+ if (!fs.existsSync(dir)) return [];
41
+ const out = [];
42
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
43
+ const p = path.join(dir, entry.name);
44
+ if (entry.isDirectory()) out.push(...listFilesRecursive(p));
45
+ else out.push(p);
46
+ }
47
+ return out;
48
+ }
49
+
50
+ function listAllRecursive(dir) {
51
+ if (!fs.existsSync(dir)) return [];
52
+ const out = [];
53
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
54
+ const p = path.join(dir, entry.name);
55
+ out.push(p);
56
+ if (entry.isDirectory()) out.push(...listAllRecursive(p));
57
+ }
58
+ return out;
59
+ }
60
+
61
+ function run(cwd, args, env) {
62
+ return spawnSync(process.execPath, args, { cwd, env: env || baseEnv, encoding: 'utf8' });
63
+ }
64
+
65
+ const noGitFixture = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-2o-nogit-'));
66
+ const nestedNoGit = path.join(noGitFixture, 'fixtures', 'some-test-dir');
67
+ fs.mkdirSync(nestedNoGit, { recursive: true });
68
+
69
+ {
70
+ const inline = `
71
+ const { getSessionDir } = require(${JSON.stringify(path.join(repoRoot, 'hooks', 'scripts', 'lib', 'harness-state.js'))});
72
+ getSessionDir(undefined, 'issue42-repro-session');
73
+ `;
74
+ const res = run(nestedNoGit, ['-e', inline]);
75
+ check(
76
+ '2o. harness-state.js creates zero files in a non-git nested dir',
77
+ res.status === 0 && listFilesRecursive(nestedNoGit).length === 0,
78
+ `exit=${res.status} stderr=${res.stderr} files=${JSON.stringify(listFilesRecursive(nestedNoGit))}`
79
+ );
80
+ check(
81
+ '2o. ...and the session dir lands under the global state home instead',
82
+ fs.existsSync(path.join(stateHome, 'workspaces')) && listAllRecursive(stateHome).some(f => f.includes('issue42-repro-session')),
83
+ `nothing under ${stateHome}: ${JSON.stringify(listAllRecursive(stateHome))}`
84
+ );
85
+ }
86
+
87
+ {
88
+ const modelSelector = path.join(repoRoot, 'fable-mode', 'scripts', 'model-selector.js');
89
+ const res = run(nestedNoGit, [
90
+ modelSelector, '--requested', 'haiku', '--available', 'haiku,sonnet',
91
+ '--available-agents', 'fable-worker-haiku', '--stage-brief', 'x', '--pass-condition', 'y',
92
+ '--verification-command', 'npm test', '--verifier-result', 'pending'
93
+ ]);
94
+ check(
95
+ '2o. model-selector.js (zero-walk before the fix) creates zero files in a non-git nested dir',
96
+ res.status === 0 && listFilesRecursive(nestedNoGit).length === 0,
97
+ `exit=${res.status} stderr=${res.stderr} files=${JSON.stringify(listFilesRecursive(nestedNoGit))}`
98
+ );
99
+ check(
100
+ '2o. ...and its audit log lands under the same global state/ stream as harness-state.js',
101
+ listFilesRecursive(stateHome).some(f => f.endsWith(path.join('fable-mode', 'audit.jsonl'))),
102
+ `no fable-mode/audit.jsonl under ${stateHome}`
103
+ );
104
+ }
105
+
106
+ fs.rmSync(noGitFixture, { recursive: true, force: true });
107
+
108
+ const gitFixture = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-2o-git-'));
109
+ spawnSync('git', ['init', '--quiet'], { cwd: gitFixture });
110
+ const nestedInGit = path.join(gitFixture, 'packages', 'nested');
111
+ fs.mkdirSync(nestedInGit, { recursive: true });
112
+
113
+ {
114
+ const scaffold = path.join(repoRoot, 'multi-agent-workspace', 'scripts', 'scaffold.js');
115
+ const legacyDecision = path.join(gitFixture, '.harness', 'multi-agent', 'decisions');
116
+ fs.mkdirSync(legacyDecision, { recursive: true });
117
+ fs.writeFileSync(path.join(legacyDecision, 'default-authored.md'), 'migrated from repository-root default\n');
118
+ const res = run(nestedInGit, [scaffold]);
119
+ check(
120
+ '2o. scaffold.js with no --workspace resolves to the repo root from a nested dir',
121
+ res.status === 0 && fs.existsSync(path.join(getRuntimeRoot(gitFixture), 'manifest.json')) &&
122
+ fs.readFileSync(path.join(gitFixture, 'docs', 'adr', 'default-authored.md'), 'utf8') === 'migrated from repository-root default\n',
123
+ `exit=${res.status} stderr=${res.stderr}`
124
+ );
125
+ check(
126
+ '2o. ...and does not scatter runtime files into the repository or nested invocation directory',
127
+ !fs.existsSync(path.join(nestedInGit, '.harness')) && !fs.existsSync(path.join(gitFixture, '.harness')),
128
+ `.harness/ was created inside ${gitFixture} or ${nestedInGit}`
129
+ );
130
+ }
131
+
132
+ {
133
+ const inline = `
134
+ const { getStateRoot, getWorkspaceRoot } = require(${JSON.stringify(path.join(repoRoot, 'hooks', 'scripts', 'lib', 'harness-state.js'))});
135
+ console.log(JSON.stringify({ workspaceRoot: getWorkspaceRoot(), stateRoot: getStateRoot() }));
136
+ `;
137
+ const res = run(nestedInGit, ['-e', inline]);
138
+ let parsed = null;
139
+ try { parsed = JSON.parse(res.stdout); } catch (e) { /* left null */ }
140
+ check(
141
+ '2o. harness-state.js walks up to the repo root from a nested in-repo dir, not the nested dir',
142
+ !!parsed && canonicalPath(parsed.workspaceRoot) === canonicalPath(gitFixture),
143
+ `got ${res.stdout || res.stderr}`
144
+ );
145
+ check(
146
+ '2o. ...and its state dir is under the global home, not under the git fixture at all',
147
+ !!parsed && !canonicalPath(parsed.stateRoot).startsWith(canonicalPath(gitFixture)),
148
+ `stateRoot=${parsed && parsed.stateRoot}`
149
+ );
150
+ }
151
+
152
+ {
153
+ const legacyDir = path.join(gitFixture, '.claude', 'harness-everything', 'state');
154
+ fs.mkdirSync(path.join(legacyDir, 'sessions', 'legacy-session'), { recursive: true });
155
+ fs.writeFileSync(path.join(legacyDir, 'sessions', 'legacy-session', 'handoff-state.json'), '{"status":"idle"}');
156
+
157
+ const inline = `
158
+ const { getStateRoot } = require(${JSON.stringify(path.join(repoRoot, 'hooks', 'scripts', 'lib', 'harness-state.js'))});
159
+ console.log(getStateRoot());
160
+ `;
161
+ const res = run(gitFixture, ['-e', inline], { ...baseEnv, CLAUDE: '1' });
162
+ const newStateRoot = (res.stdout || '').trim();
163
+
164
+ check(
165
+ '2o. pre-existing scattered state migrates into the new global location',
166
+ newStateRoot && fs.existsSync(path.join(newStateRoot, 'sessions', 'legacy-session', 'handoff-state.json')) && fs.existsSync(path.join(newStateRoot, '.migrated-from')),
167
+ `exit=${res.status} stderr=${res.stderr} newStateRoot=${newStateRoot}`
168
+ );
169
+ check(
170
+ '2o. ...and the old in-repo location is gone afterward',
171
+ !fs.existsSync(legacyDir),
172
+ `${legacyDir} still exists`
173
+ );
174
+ }
175
+
176
+ fs.rmSync(gitFixture, { recursive: true, force: true });
177
+ fs.rmSync(fakeHome, { recursive: true, force: true });
178
+
179
+ if (failures > 0) {
180
+ console.error(`\n${failures} check(s) failed.`);
181
+ process.exit(1);
182
+ }
183
+ process.exit(0);