devrites 3.0.7 → 3.2.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 (484) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +82 -50
  3. package/SECURITY.md +31 -29
  4. package/docs/adr/0001-go-engine-as-control-plane.md +1 -1
  5. package/docs/adr/0002-dual-host-harness.md +1 -1
  6. package/docs/adr/0004-state-schema-phases-sections.md +1 -2
  7. package/docs/adr/0006-clock-seam-and-engine-ci-gates.md +3 -4
  8. package/docs/adr/0009-prebuild-decision-coverage-and-readiness.md +67 -0
  9. package/docs/adr/0010-agent-first-fresh-context-orchestration.md +71 -0
  10. package/docs/adr/0011-define-plan-transition-rights.md +28 -0
  11. package/docs/adr/0012-semantic-workspace-upgrades.md +77 -0
  12. package/docs/adr/README.md +4 -0
  13. package/docs/agents/triage-labels.md +7 -7
  14. package/docs/architecture.md +179 -141
  15. package/docs/cli.md +47 -12
  16. package/docs/command-map.md +61 -38
  17. package/docs/engine/agent-contract.md +92 -15
  18. package/docs/engine/commands.md +248 -72
  19. package/docs/engine/state-schema.md +52 -23
  20. package/docs/engine/workspace-schema.md +103 -15
  21. package/docs/extensions.md +1 -1
  22. package/docs/flow.md +86 -51
  23. package/docs/harness-compliance.md +29 -5
  24. package/docs/orchestration.md +107 -79
  25. package/docs/quick-reference.md +11 -3
  26. package/docs/release.md +4 -3
  27. package/docs/skills.md +67 -38
  28. package/docs/usage.md +84 -39
  29. package/engine/cmd/releasepack/main.go +219 -0
  30. package/engine/cmd/releasepack/main_test.go +170 -0
  31. package/engine/commands.go +170 -23
  32. package/engine/git_guard.go +187 -0
  33. package/engine/git_guard_test.go +283 -0
  34. package/engine/hookpolicy.go +53 -55
  35. package/engine/hookpolicy_test.go +91 -1
  36. package/engine/hooks.go +296 -75
  37. package/engine/hooks_events_test.go +276 -6
  38. package/engine/hooks_workspace.go +640 -159
  39. package/engine/internal/devritespaths/paths.go +65 -10
  40. package/engine/internal/devritespaths/paths_test.go +110 -0
  41. package/engine/internal/doctor/doctor.go +153 -23
  42. package/engine/internal/doctor/doctor_test.go +74 -0
  43. package/engine/internal/forge/forge.go +940 -0
  44. package/engine/internal/forge/forge_test.go +576 -0
  45. package/engine/internal/forge/git.go +245 -0
  46. package/engine/internal/forge/liveness_unix.go +48 -0
  47. package/engine/internal/forge/liveness_windows.go +67 -0
  48. package/engine/internal/forge/manifest.go +402 -0
  49. package/engine/internal/gate/gate.go +90 -71
  50. package/engine/internal/gate/gate_test.go +71 -2
  51. package/engine/internal/harness/compliance.go +18 -24
  52. package/engine/internal/harness/harness.go +37 -44
  53. package/engine/internal/harness/harness_test.go +21 -5
  54. package/engine/internal/install/install.go +486 -38
  55. package/engine/internal/install/install_test.go +383 -10
  56. package/engine/internal/iohooks/iohooks.go +350 -59
  57. package/engine/internal/iohooks/iohooks_test.go +421 -1
  58. package/engine/internal/lib/buildreadiness.go +43 -18
  59. package/engine/internal/lib/clarifyreturn.go +91 -0
  60. package/engine/internal/lib/clarifyreturn_test.go +123 -0
  61. package/engine/internal/lib/context.go +54 -15
  62. package/engine/internal/lib/cursor_compat_test.go +8 -0
  63. package/engine/internal/lib/extensions.go +2 -3
  64. package/engine/internal/lib/gitauthority.go +601 -0
  65. package/engine/internal/lib/gitauthority_test.go +346 -0
  66. package/engine/internal/lib/jsonout.go +25 -11
  67. package/engine/internal/lib/jsonout_test.go +16 -0
  68. package/engine/internal/lib/lanes.go +14 -8
  69. package/engine/internal/lib/observability_test.go +358 -0
  70. package/engine/internal/lib/packageexistence.go +151 -35
  71. package/engine/internal/lib/packageexistence_test.go +163 -0
  72. package/engine/internal/lib/progress.go +11 -9
  73. package/engine/internal/lib/provenance.go +462 -0
  74. package/engine/internal/lib/provenance_test.go +154 -0
  75. package/engine/internal/lib/readiness_contract.json +118 -0
  76. package/engine/internal/lib/readinessartifact.go +533 -0
  77. package/engine/internal/lib/readinessartifact_test.go +422 -0
  78. package/engine/internal/lib/reconcile.go +712 -88
  79. package/engine/internal/lib/reconcile_test.go +335 -16
  80. package/engine/internal/lib/recoveryattempts.go +298 -0
  81. package/engine/internal/lib/recoveryattempts_test.go +215 -0
  82. package/engine/internal/lib/resolve.go +23 -19
  83. package/engine/internal/lib/runbook_context_test.go +20 -0
  84. package/engine/internal/lib/session.go +701 -9
  85. package/engine/internal/lib/session_test.go +80 -13
  86. package/engine/internal/lib/testintegrity.go +33 -37
  87. package/engine/internal/lib/testintegrity_test.go +63 -1
  88. package/engine/internal/migrate/migrate.go +81 -1
  89. package/engine/internal/migrate/migrate_test.go +22 -1
  90. package/engine/internal/reason/reason.go +180 -0
  91. package/engine/internal/reason/reason_test.go +35 -0
  92. package/engine/internal/rootfacts/facts.go +466 -0
  93. package/engine/internal/rootfacts/facts_test.go +306 -0
  94. package/engine/internal/safepath/safepath.go +55 -0
  95. package/engine/internal/safepath/safepath_test.go +69 -0
  96. package/engine/internal/safepath/safepath_windows_test.go +26 -0
  97. package/engine/internal/state/clarify_transition.go +165 -0
  98. package/engine/internal/state/clarify_transition_test.go +130 -0
  99. package/engine/internal/state/cmd/workflowmanifest/main.go +12 -8
  100. package/engine/internal/state/cursor.go +58 -0
  101. package/engine/internal/state/cursor_test.go +42 -1
  102. package/engine/internal/state/feature.go +35 -48
  103. package/engine/internal/state/schema.go +90 -37
  104. package/engine/internal/state/snapshot.go +18 -2
  105. package/engine/internal/state/state_test.go +187 -7
  106. package/engine/internal/state/status.go +48 -11
  107. package/engine/internal/state/workflow_manifest.json +231 -50
  108. package/engine/internal/toolpolicy/classifier.go +533 -0
  109. package/engine/internal/toolpolicy/classifier_test.go +424 -0
  110. package/engine/internal/toolpolicy/git.go +616 -0
  111. package/engine/internal/toolpolicy/scanner.go +382 -0
  112. package/engine/main.go +160 -77
  113. package/engine/observability_cli_test.go +52 -0
  114. package/engine/root_routing_test.go +277 -0
  115. package/engine/testdata/golden/TestParityBuildReadiness/arg=approved.golden +1 -1
  116. package/engine/testdata/golden/TestParityBuildReadiness/arg=emptystatus.golden +1 -1
  117. package/engine/testdata/golden/TestParityBuildReadiness/arg=legacycontract.golden +1 -0
  118. package/engine/testdata/golden/TestParityBuildReadiness/arg=noclarify.golden +1 -0
  119. package/engine/testdata/golden/TestParityBuildReadiness/arg=novet.golden +1 -0
  120. package/engine/testdata/golden/TestParityBuildReadiness/arg=stalevet.golden +1 -0
  121. package/engine/testdata/golden/TestParityBuildReadiness/arg=trailhash.golden +1 -1
  122. package/engine/testdata/golden/TestParityBuildReadiness/arg=trailpipe.golden +1 -1
  123. package/engine/testdata/golden/TestParityBuildReadiness/arg=vetnotready.golden +1 -0
  124. package/engine/testdata/golden/TestParityProgress/arg=allbuilt.golden +1 -1
  125. package/engine/testdata/golden/TestParityProgress/arg=done.golden +1 -1
  126. package/engine/testdata/golden/TestParityProgress/arg=mid.golden +1 -1
  127. package/engine/testdata/golden/TestParityProgress/arg=nophase.golden +1 -1
  128. package/engine/testdata/golden/TestParityProgress/arg=noslice.golden +1 -1
  129. package/engine/testdata/golden/TestParityProgress/arg=plan.golden +1 -1
  130. package/engine/testdata/golden/TestParityProgress/arg=seal.golden +1 -1
  131. package/engine/testdata/golden/TestParityReconcile/check-clean.golden +1 -1
  132. package/engine/testdata/golden/TestParityReconcile/inline-fallback.golden +1 -0
  133. package/engine/testdata/golden/TestParityReconcile/snapshot-no-allowlist.golden +1 -0
  134. package/engine/testdata/golden/TestParityWrightScope/devrites-edit-denied.golden +2 -0
  135. package/engine/testdata/golden/TestParityWrightScope/out-of-scope-enforce-denies.golden +1 -1
  136. package/engine/tests/adr_0011_define_plan_test.go +30 -0
  137. package/engine/tests/budget_test.go +19 -8
  138. package/engine/tests/concurrency_cli_test.go +7 -1
  139. package/engine/tests/doctor_cli_test.go +148 -2
  140. package/engine/tests/forge_cli_test.go +463 -0
  141. package/engine/tests/gate_test.go +63 -8
  142. package/engine/tests/hook_test.go +260 -8
  143. package/engine/tests/hooks_io_test.go +94 -3
  144. package/engine/tests/json_contract_test.go +127 -2
  145. package/engine/tests/migrate_cli_test.go +2 -2
  146. package/engine/tests/parity_buildreadiness_test.go +167 -10
  147. package/engine/tests/parity_learnings_test.go +16 -17
  148. package/engine/tests/parity_reconcile_test.go +20 -17
  149. package/engine/tests/parity_resolve_test.go +12 -11
  150. package/engine/tests/parity_test.go +21 -17
  151. package/pack/.claude/agents/devrites-code-reviewer.md +62 -48
  152. package/pack/.claude/agents/devrites-devex-reviewer.md +69 -53
  153. package/pack/.claude/agents/devrites-doubt-reviewer.md +29 -22
  154. package/pack/.claude/agents/devrites-evidence-scout.md +69 -0
  155. package/pack/.claude/agents/devrites-forge-judge.md +74 -61
  156. package/pack/.claude/agents/devrites-frontend-reviewer.md +48 -39
  157. package/pack/.claude/agents/devrites-performance-reviewer.md +49 -40
  158. package/pack/.claude/agents/devrites-plan-drafter.md +73 -0
  159. package/pack/.claude/agents/devrites-plan-reviewer.md +80 -47
  160. package/pack/.claude/agents/devrites-proof-runner.md +74 -0
  161. package/pack/.claude/agents/devrites-retrospector.md +48 -45
  162. package/pack/.claude/agents/devrites-security-auditor.md +46 -36
  163. package/pack/.claude/agents/devrites-simplifier-reviewer.md +50 -46
  164. package/pack/.claude/agents/devrites-slice-wright.md +153 -165
  165. package/pack/.claude/agents/devrites-spec-reviewer.md +34 -32
  166. package/pack/.claude/agents/devrites-strategy-reviewer.md +63 -34
  167. package/pack/.claude/agents/devrites-test-analyst.md +40 -30
  168. package/pack/.claude/agents/devrites-upgrade-planner.md +100 -0
  169. package/pack/.claude/settings.json +2 -1
  170. package/pack/.claude/skills/devrites-audit/SKILL.md +40 -61
  171. package/pack/.claude/skills/devrites-debug-recovery/SKILL.md +24 -13
  172. package/pack/.claude/skills/devrites-debug-recovery/reference/build-the-loop.md +24 -21
  173. package/pack/.claude/skills/devrites-debug-recovery/reference/cleanup-and-classify.md +34 -6
  174. package/pack/.claude/skills/devrites-debug-recovery/reference/instrumentation.md +2 -2
  175. package/pack/.claude/skills/devrites-doubt/SKILL.md +25 -17
  176. package/pack/.claude/skills/devrites-interview/SKILL.md +53 -52
  177. package/pack/.claude/skills/devrites-lib/SKILL.md +12 -9
  178. package/pack/.claude/skills/devrites-lib/reference/intent-map.md +4 -2
  179. package/pack/.claude/skills/devrites-lib/reference/parallel-dispatch.md +82 -137
  180. package/pack/.claude/skills/devrites-lib/reference/reply-contract.md +8 -1
  181. package/pack/.claude/skills/devrites-lib/reference/standards/README.md +38 -55
  182. package/pack/.claude/skills/devrites-lib/reference/standards/afk-hitl.md +53 -27
  183. package/pack/.claude/skills/devrites-lib/reference/standards/agents.md +200 -190
  184. package/pack/.claude/skills/devrites-lib/reference/standards/anti-patterns.md +5 -15
  185. package/pack/.claude/skills/devrites-lib/reference/standards/ci-cd.md +27 -58
  186. package/pack/.claude/skills/devrites-lib/reference/standards/code-review.md +16 -52
  187. package/pack/.claude/skills/devrites-lib/reference/standards/coding-style.md +2 -10
  188. package/pack/.claude/skills/devrites-lib/reference/standards/context-hygiene.md +18 -61
  189. package/pack/.claude/skills/devrites-lib/reference/standards/core.md +23 -18
  190. package/pack/.claude/skills/devrites-lib/reference/standards/deprecation.md +17 -57
  191. package/pack/.claude/skills/devrites-lib/reference/standards/development-workflow.md +4 -33
  192. package/pack/.claude/skills/devrites-lib/reference/standards/git-workflow.md +4 -20
  193. package/pack/.claude/skills/devrites-lib/reference/standards/hooks.md +3 -14
  194. package/pack/.claude/skills/devrites-lib/reference/standards/patterns.md +9 -25
  195. package/pack/.claude/skills/devrites-lib/reference/standards/performance.md +0 -9
  196. package/pack/.claude/skills/devrites-lib/reference/standards/principles.md +1 -3
  197. package/pack/.claude/skills/devrites-lib/reference/standards/security.md +17 -7
  198. package/pack/.claude/skills/devrites-lib/reference/standards/testing.md +1 -27
  199. package/pack/.claude/skills/devrites-lib/reference/standards/tooling.md +46 -50
  200. package/pack/.claude/skills/devrites-lib/reference/workspace-artifact-schema.md +40 -10
  201. package/pack/.claude/skills/devrites-source-driven/SKILL.md +23 -22
  202. package/pack/.claude/skills/rite/SKILL.md +10 -6
  203. package/pack/.claude/skills/rite/reference/menu.md +13 -7
  204. package/pack/.claude/skills/rite-adopt/SKILL.md +33 -37
  205. package/pack/.claude/skills/rite-autocomplete/SKILL.md +33 -28
  206. package/pack/.claude/skills/rite-autocomplete/reference/loop.md +21 -21
  207. package/pack/.claude/skills/rite-autocomplete/reference/stop-conditions.md +15 -6
  208. package/pack/.claude/skills/rite-build/SKILL.md +35 -30
  209. package/pack/.claude/skills/rite-build/reference/afk-discipline.md +41 -39
  210. package/pack/.claude/skills/rite-build/reference/evidence-standard.md +10 -0
  211. package/pack/.claude/skills/rite-build/reference/forge.md +186 -156
  212. package/pack/.claude/skills/rite-build/reference/one-slice-cycle.md +4 -3
  213. package/pack/.claude/skills/rite-build/reference/phase-contract.md +96 -175
  214. package/pack/.claude/skills/rite-build/reference/spec-drift-guard.md +10 -4
  215. package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +129 -134
  216. package/pack/.claude/skills/rite-clarify/SKILL.md +90 -0
  217. package/pack/.claude/skills/rite-clarify/reference/decision-coverage.md +61 -0
  218. package/pack/.claude/skills/rite-converge/SKILL.md +35 -25
  219. package/pack/.claude/skills/rite-define/SKILL.md +58 -32
  220. package/pack/.claude/skills/rite-define/reference/gates.md +16 -15
  221. package/pack/.claude/skills/rite-define/reference/plan-template.md +18 -8
  222. package/pack/.claude/skills/rite-frame/reference/failure-modes.md +22 -24
  223. package/pack/.claude/skills/rite-plan/SKILL.md +47 -16
  224. package/pack/.claude/skills/rite-plan/reference/task-breakdown.md +4 -0
  225. package/pack/.claude/skills/rite-polish/SKILL.md +27 -23
  226. package/pack/.claude/skills/rite-prototype/SKILL.md +25 -26
  227. package/pack/.claude/skills/rite-prove/SKILL.md +27 -17
  228. package/pack/.claude/skills/rite-resolve/SKILL.md +12 -11
  229. package/pack/.claude/skills/rite-resolve/reference/answer-protocol.md +3 -0
  230. package/pack/.claude/skills/rite-review/SKILL.md +37 -28
  231. package/pack/.claude/skills/rite-seal/reference/phase-contract.md +27 -92
  232. package/pack/.claude/skills/rite-seal/reference/risk-and-rollback.md +13 -0
  233. package/pack/.claude/skills/rite-ship/reference/design-memory.md +31 -36
  234. package/pack/.claude/skills/rite-spec/SKILL.md +84 -135
  235. package/pack/.claude/skills/rite-spec/reference/investigation.md +37 -33
  236. package/pack/.claude/skills/rite-spec/reference/question-protocol.md +6 -2
  237. package/pack/.claude/skills/rite-spec/reference/spec-checklists.md +25 -24
  238. package/pack/.claude/skills/rite-spec/reference/spec-template.md +9 -2
  239. package/pack/.claude/skills/rite-spec/reference/state-workspace.md +13 -3
  240. package/pack/.claude/skills/rite-temper/SKILL.md +62 -47
  241. package/pack/.claude/skills/rite-temper/reference/review-dimensions.md +24 -22
  242. package/pack/.claude/skills/rite-upgrade/SKILL.md +125 -0
  243. package/pack/.claude/skills/rite-vet/SKILL.md +114 -113
  244. package/pack/.claude/skills/rite-vet/reference/artifacts.md +50 -9
  245. package/pack/.claude/skills/rite-vet/reference/review-axes.md +39 -37
  246. package/pack/generated/claude/agents/devrites-code-reviewer.md +62 -48
  247. package/pack/generated/claude/agents/devrites-devex-reviewer.md +69 -53
  248. package/pack/generated/claude/agents/devrites-doubt-reviewer.md +29 -22
  249. package/pack/generated/claude/agents/devrites-evidence-scout.md +69 -0
  250. package/pack/generated/claude/agents/devrites-forge-judge.md +74 -61
  251. package/pack/generated/claude/agents/devrites-frontend-reviewer.md +48 -39
  252. package/pack/generated/claude/agents/devrites-performance-reviewer.md +49 -40
  253. package/pack/generated/claude/agents/devrites-plan-drafter.md +73 -0
  254. package/pack/generated/claude/agents/devrites-plan-reviewer.md +80 -47
  255. package/pack/generated/claude/agents/devrites-proof-runner.md +74 -0
  256. package/pack/generated/claude/agents/devrites-retrospector.md +48 -45
  257. package/pack/generated/claude/agents/devrites-security-auditor.md +46 -36
  258. package/pack/generated/claude/agents/devrites-simplifier-reviewer.md +50 -46
  259. package/pack/generated/claude/agents/devrites-slice-wright.md +153 -165
  260. package/pack/generated/claude/agents/devrites-spec-reviewer.md +34 -32
  261. package/pack/generated/claude/agents/devrites-strategy-reviewer.md +63 -34
  262. package/pack/generated/claude/agents/devrites-test-analyst.md +40 -30
  263. package/pack/generated/claude/agents/devrites-upgrade-planner.md +100 -0
  264. package/pack/generated/claude/settings.json +2 -1
  265. package/pack/generated/claude/skills/devrites-audit/SKILL.md +40 -61
  266. package/pack/generated/claude/skills/devrites-debug-recovery/SKILL.md +24 -13
  267. package/pack/generated/claude/skills/devrites-debug-recovery/reference/build-the-loop.md +24 -21
  268. package/pack/generated/claude/skills/devrites-debug-recovery/reference/cleanup-and-classify.md +34 -6
  269. package/pack/generated/claude/skills/devrites-debug-recovery/reference/instrumentation.md +2 -2
  270. package/pack/generated/claude/skills/devrites-doubt/SKILL.md +25 -17
  271. package/pack/generated/claude/skills/devrites-interview/SKILL.md +53 -52
  272. package/pack/generated/claude/skills/devrites-lib/SKILL.md +12 -9
  273. package/pack/generated/claude/skills/devrites-lib/reference/intent-map.md +4 -2
  274. package/pack/generated/claude/skills/devrites-lib/reference/parallel-dispatch.md +82 -137
  275. package/pack/generated/claude/skills/devrites-lib/reference/reply-contract.md +8 -1
  276. package/pack/generated/claude/skills/devrites-lib/reference/standards/README.md +38 -55
  277. package/pack/generated/claude/skills/devrites-lib/reference/standards/afk-hitl.md +53 -27
  278. package/pack/generated/claude/skills/devrites-lib/reference/standards/agents.md +200 -190
  279. package/pack/generated/claude/skills/devrites-lib/reference/standards/anti-patterns.md +5 -15
  280. package/pack/generated/claude/skills/devrites-lib/reference/standards/ci-cd.md +27 -58
  281. package/pack/generated/claude/skills/devrites-lib/reference/standards/code-review.md +16 -52
  282. package/pack/generated/claude/skills/devrites-lib/reference/standards/coding-style.md +2 -10
  283. package/pack/generated/claude/skills/devrites-lib/reference/standards/context-hygiene.md +18 -61
  284. package/pack/generated/claude/skills/devrites-lib/reference/standards/core.md +23 -18
  285. package/pack/generated/claude/skills/devrites-lib/reference/standards/deprecation.md +17 -57
  286. package/pack/generated/claude/skills/devrites-lib/reference/standards/development-workflow.md +4 -33
  287. package/pack/generated/claude/skills/devrites-lib/reference/standards/git-workflow.md +4 -20
  288. package/pack/generated/claude/skills/devrites-lib/reference/standards/hooks.md +3 -14
  289. package/pack/generated/claude/skills/devrites-lib/reference/standards/patterns.md +9 -25
  290. package/pack/generated/claude/skills/devrites-lib/reference/standards/performance.md +0 -9
  291. package/pack/generated/claude/skills/devrites-lib/reference/standards/principles.md +1 -3
  292. package/pack/generated/claude/skills/devrites-lib/reference/standards/security.md +17 -7
  293. package/pack/generated/claude/skills/devrites-lib/reference/standards/testing.md +1 -27
  294. package/pack/generated/claude/skills/devrites-lib/reference/standards/tooling.md +46 -50
  295. package/pack/generated/claude/skills/devrites-lib/reference/workspace-artifact-schema.md +40 -10
  296. package/pack/generated/claude/skills/devrites-source-driven/SKILL.md +23 -22
  297. package/pack/generated/claude/skills/rite/SKILL.md +10 -6
  298. package/pack/generated/claude/skills/rite/reference/menu.md +13 -7
  299. package/pack/generated/claude/skills/rite-adopt/SKILL.md +33 -37
  300. package/pack/generated/claude/skills/rite-autocomplete/SKILL.md +33 -28
  301. package/pack/generated/claude/skills/rite-autocomplete/reference/loop.md +21 -21
  302. package/pack/generated/claude/skills/rite-autocomplete/reference/stop-conditions.md +15 -6
  303. package/pack/generated/claude/skills/rite-build/SKILL.md +35 -30
  304. package/pack/generated/claude/skills/rite-build/reference/afk-discipline.md +41 -39
  305. package/pack/generated/claude/skills/rite-build/reference/evidence-standard.md +10 -0
  306. package/pack/generated/claude/skills/rite-build/reference/forge.md +186 -156
  307. package/pack/generated/claude/skills/rite-build/reference/one-slice-cycle.md +4 -3
  308. package/pack/generated/claude/skills/rite-build/reference/phase-contract.md +96 -175
  309. package/pack/generated/claude/skills/rite-build/reference/spec-drift-guard.md +10 -4
  310. package/pack/generated/claude/skills/rite-build/reference/wright-dispatch.md +129 -134
  311. package/pack/generated/claude/skills/rite-clarify/SKILL.md +90 -0
  312. package/pack/generated/claude/skills/rite-clarify/reference/decision-coverage.md +61 -0
  313. package/pack/generated/claude/skills/rite-converge/SKILL.md +35 -25
  314. package/pack/generated/claude/skills/rite-define/SKILL.md +58 -32
  315. package/pack/generated/claude/skills/rite-define/reference/gates.md +16 -15
  316. package/pack/generated/claude/skills/rite-define/reference/plan-template.md +18 -8
  317. package/pack/generated/claude/skills/rite-frame/reference/failure-modes.md +22 -24
  318. package/pack/generated/claude/skills/rite-plan/SKILL.md +47 -16
  319. package/pack/generated/claude/skills/rite-plan/reference/task-breakdown.md +4 -0
  320. package/pack/generated/claude/skills/rite-polish/SKILL.md +27 -23
  321. package/pack/generated/claude/skills/rite-prototype/SKILL.md +25 -26
  322. package/pack/generated/claude/skills/rite-prove/SKILL.md +27 -17
  323. package/pack/generated/claude/skills/rite-resolve/SKILL.md +12 -11
  324. package/pack/generated/claude/skills/rite-resolve/reference/answer-protocol.md +3 -0
  325. package/pack/generated/claude/skills/rite-review/SKILL.md +37 -28
  326. package/pack/generated/claude/skills/rite-seal/reference/phase-contract.md +27 -92
  327. package/pack/generated/claude/skills/rite-seal/reference/risk-and-rollback.md +13 -0
  328. package/pack/generated/claude/skills/rite-ship/reference/design-memory.md +31 -36
  329. package/pack/generated/claude/skills/rite-spec/SKILL.md +84 -135
  330. package/pack/generated/claude/skills/rite-spec/reference/investigation.md +37 -33
  331. package/pack/generated/claude/skills/rite-spec/reference/question-protocol.md +6 -2
  332. package/pack/generated/claude/skills/rite-spec/reference/spec-checklists.md +25 -24
  333. package/pack/generated/claude/skills/rite-spec/reference/spec-template.md +9 -2
  334. package/pack/generated/claude/skills/rite-spec/reference/state-workspace.md +13 -3
  335. package/pack/generated/claude/skills/rite-temper/SKILL.md +62 -47
  336. package/pack/generated/claude/skills/rite-temper/reference/review-dimensions.md +24 -22
  337. package/pack/generated/claude/skills/rite-upgrade/SKILL.md +125 -0
  338. package/pack/generated/claude/skills/rite-vet/SKILL.md +114 -113
  339. package/pack/generated/claude/skills/rite-vet/reference/artifacts.md +50 -9
  340. package/pack/generated/claude/skills/rite-vet/reference/review-axes.md +39 -37
  341. package/pack/generated/codex/AGENTS.md +5 -2
  342. package/pack/generated/codex/agents/devrites-code-reviewer.toml +69 -47
  343. package/pack/generated/codex/agents/devrites-devex-reviewer.toml +75 -51
  344. package/pack/generated/codex/agents/devrites-doubt-reviewer.toml +35 -20
  345. package/pack/generated/codex/agents/devrites-evidence-scout.toml +75 -0
  346. package/pack/generated/codex/agents/devrites-forge-judge.toml +80 -59
  347. package/pack/generated/codex/agents/devrites-frontend-reviewer.toml +54 -37
  348. package/pack/generated/codex/agents/devrites-performance-reviewer.toml +55 -38
  349. package/pack/generated/codex/agents/devrites-plan-drafter.toml +79 -0
  350. package/pack/generated/codex/agents/devrites-plan-reviewer.toml +82 -47
  351. package/pack/generated/codex/agents/devrites-proof-runner.toml +80 -0
  352. package/pack/generated/codex/agents/devrites-retrospector.toml +54 -43
  353. package/pack/generated/codex/agents/devrites-security-auditor.toml +53 -35
  354. package/pack/generated/codex/agents/devrites-simplifier-reviewer.toml +56 -44
  355. package/pack/generated/codex/agents/devrites-slice-wright.toml +159 -163
  356. package/pack/generated/codex/agents/devrites-spec-reviewer.toml +40 -30
  357. package/pack/generated/codex/agents/devrites-strategy-reviewer.toml +65 -34
  358. package/pack/generated/codex/agents/devrites-test-analyst.toml +46 -28
  359. package/pack/generated/codex/agents/devrites-upgrade-planner.toml +106 -0
  360. package/pack/generated/codex/hooks.json +4 -14
  361. package/pack/generated/codex/skills/devrites-api-interface/SKILL.md +7 -3
  362. package/pack/generated/codex/skills/devrites-audit/SKILL.md +47 -64
  363. package/pack/generated/codex/skills/devrites-browser-proof/SKILL.md +7 -3
  364. package/pack/generated/codex/skills/devrites-debug-recovery/SKILL.md +31 -16
  365. package/pack/generated/codex/skills/devrites-debug-recovery/reference/build-the-loop.md +24 -21
  366. package/pack/generated/codex/skills/devrites-debug-recovery/reference/cleanup-and-classify.md +34 -6
  367. package/pack/generated/codex/skills/devrites-debug-recovery/reference/instrumentation.md +2 -2
  368. package/pack/generated/codex/skills/devrites-doubt/SKILL.md +32 -20
  369. package/pack/generated/codex/skills/devrites-frontend-craft/SKILL.md +7 -3
  370. package/pack/generated/codex/skills/devrites-interview/SKILL.md +60 -55
  371. package/pack/generated/codex/skills/devrites-lib/SKILL.md +19 -12
  372. package/pack/generated/codex/skills/devrites-lib/reference/intent-map.md +4 -2
  373. package/pack/generated/codex/skills/devrites-lib/reference/parallel-dispatch.md +82 -137
  374. package/pack/generated/codex/skills/devrites-lib/reference/reply-contract.md +8 -1
  375. package/pack/generated/codex/skills/devrites-lib/reference/standards/README.md +38 -55
  376. package/pack/generated/codex/skills/devrites-lib/reference/standards/afk-hitl.md +53 -27
  377. package/pack/generated/codex/skills/devrites-lib/reference/standards/agents.md +200 -190
  378. package/pack/generated/codex/skills/devrites-lib/reference/standards/anti-patterns.md +5 -15
  379. package/pack/generated/codex/skills/devrites-lib/reference/standards/ci-cd.md +27 -58
  380. package/pack/generated/codex/skills/devrites-lib/reference/standards/code-review.md +16 -52
  381. package/pack/generated/codex/skills/devrites-lib/reference/standards/coding-style.md +2 -10
  382. package/pack/generated/codex/skills/devrites-lib/reference/standards/context-hygiene.md +18 -61
  383. package/pack/generated/codex/skills/devrites-lib/reference/standards/core.md +23 -18
  384. package/pack/generated/codex/skills/devrites-lib/reference/standards/deprecation.md +17 -57
  385. package/pack/generated/codex/skills/devrites-lib/reference/standards/development-workflow.md +4 -33
  386. package/pack/generated/codex/skills/devrites-lib/reference/standards/git-workflow.md +4 -20
  387. package/pack/generated/codex/skills/devrites-lib/reference/standards/hooks.md +3 -14
  388. package/pack/generated/codex/skills/devrites-lib/reference/standards/patterns.md +9 -25
  389. package/pack/generated/codex/skills/devrites-lib/reference/standards/performance.md +0 -9
  390. package/pack/generated/codex/skills/devrites-lib/reference/standards/principles.md +1 -3
  391. package/pack/generated/codex/skills/devrites-lib/reference/standards/security.md +17 -7
  392. package/pack/generated/codex/skills/devrites-lib/reference/standards/testing.md +1 -27
  393. package/pack/generated/codex/skills/devrites-lib/reference/standards/tooling.md +46 -50
  394. package/pack/generated/codex/skills/devrites-lib/reference/workspace-artifact-schema.md +40 -10
  395. package/pack/generated/codex/skills/devrites-prose-craft/SKILL.md +7 -3
  396. package/pack/generated/codex/skills/devrites-refresh-indexes/SKILL.md +7 -3
  397. package/pack/generated/codex/skills/devrites-source-driven/SKILL.md +29 -24
  398. package/pack/generated/codex/skills/devrites-ux-shape/SKILL.md +7 -3
  399. package/pack/generated/codex/skills/rite/SKILL.md +21 -13
  400. package/pack/generated/codex/skills/rite/reference/menu.md +13 -7
  401. package/pack/generated/codex/skills/rite-adopt/SKILL.md +40 -40
  402. package/pack/generated/codex/skills/rite-autocomplete/SKILL.md +40 -31
  403. package/pack/generated/codex/skills/rite-autocomplete/reference/loop.md +21 -21
  404. package/pack/generated/codex/skills/rite-autocomplete/reference/stop-conditions.md +15 -6
  405. package/pack/generated/codex/skills/rite-build/SKILL.md +42 -33
  406. package/pack/generated/codex/skills/rite-build/reference/afk-discipline.md +41 -39
  407. package/pack/generated/codex/skills/rite-build/reference/evidence-standard.md +10 -0
  408. package/pack/generated/codex/skills/rite-build/reference/forge.md +186 -156
  409. package/pack/generated/codex/skills/rite-build/reference/one-slice-cycle.md +4 -3
  410. package/pack/generated/codex/skills/rite-build/reference/phase-contract.md +96 -175
  411. package/pack/generated/codex/skills/rite-build/reference/spec-drift-guard.md +10 -4
  412. package/pack/generated/codex/skills/rite-build/reference/wright-dispatch.md +129 -134
  413. package/pack/generated/codex/skills/rite-clarify/SKILL.md +106 -0
  414. package/pack/generated/codex/skills/rite-clarify/reference/decision-coverage.md +61 -0
  415. package/pack/generated/codex/skills/rite-converge/SKILL.md +42 -28
  416. package/pack/generated/codex/skills/rite-customize/SKILL.md +7 -3
  417. package/pack/generated/codex/skills/rite-define/SKILL.md +65 -35
  418. package/pack/generated/codex/skills/rite-define/reference/gates.md +16 -15
  419. package/pack/generated/codex/skills/rite-define/reference/plan-template.md +18 -8
  420. package/pack/generated/codex/skills/rite-doctor/SKILL.md +7 -3
  421. package/pack/generated/codex/skills/rite-dogfood/SKILL.md +7 -3
  422. package/pack/generated/codex/skills/rite-explain/SKILL.md +7 -3
  423. package/pack/generated/codex/skills/rite-frame/SKILL.md +7 -3
  424. package/pack/generated/codex/skills/rite-frame/reference/failure-modes.md +22 -24
  425. package/pack/generated/codex/skills/rite-handoff/SKILL.md +7 -3
  426. package/pack/generated/codex/skills/rite-learn/SKILL.md +7 -3
  427. package/pack/generated/codex/skills/rite-plan/SKILL.md +54 -19
  428. package/pack/generated/codex/skills/rite-plan/reference/task-breakdown.md +5 -1
  429. package/pack/generated/codex/skills/rite-polish/SKILL.md +34 -26
  430. package/pack/generated/codex/skills/rite-pov/SKILL.md +7 -3
  431. package/pack/generated/codex/skills/rite-pr-feedback/SKILL.md +7 -3
  432. package/pack/generated/codex/skills/rite-pressure-test/SKILL.md +7 -3
  433. package/pack/generated/codex/skills/rite-prototype/SKILL.md +32 -29
  434. package/pack/generated/codex/skills/rite-prove/SKILL.md +34 -20
  435. package/pack/generated/codex/skills/rite-quick/SKILL.md +7 -3
  436. package/pack/generated/codex/skills/rite-resolve/SKILL.md +19 -14
  437. package/pack/generated/codex/skills/rite-resolve/reference/answer-protocol.md +3 -0
  438. package/pack/generated/codex/skills/rite-review/SKILL.md +44 -31
  439. package/pack/generated/codex/skills/rite-seal/SKILL.md +7 -3
  440. package/pack/generated/codex/skills/rite-seal/reference/phase-contract.md +27 -92
  441. package/pack/generated/codex/skills/rite-seal/reference/risk-and-rollback.md +13 -0
  442. package/pack/generated/codex/skills/rite-ship/SKILL.md +7 -3
  443. package/pack/generated/codex/skills/rite-ship/reference/design-memory.md +31 -36
  444. package/pack/generated/codex/skills/rite-spec/SKILL.md +91 -138
  445. package/pack/generated/codex/skills/rite-spec/reference/investigation.md +37 -33
  446. package/pack/generated/codex/skills/rite-spec/reference/question-protocol.md +6 -2
  447. package/pack/generated/codex/skills/rite-spec/reference/spec-checklists.md +25 -24
  448. package/pack/generated/codex/skills/rite-spec/reference/spec-template.md +9 -2
  449. package/pack/generated/codex/skills/rite-spec/reference/state-workspace.md +13 -3
  450. package/pack/generated/codex/skills/rite-status/SKILL.md +7 -3
  451. package/pack/generated/codex/skills/rite-temper/SKILL.md +69 -50
  452. package/pack/generated/codex/skills/rite-temper/reference/review-dimensions.md +24 -22
  453. package/pack/generated/codex/skills/rite-upgrade/SKILL.md +141 -0
  454. package/pack/generated/codex/skills/rite-upgrade/agents/openai.yaml +2 -0
  455. package/pack/generated/codex/skills/rite-vet/SKILL.md +121 -116
  456. package/pack/generated/codex/skills/rite-vet/reference/artifacts.md +50 -9
  457. package/pack/generated/codex/skills/rite-vet/reference/review-axes.md +40 -38
  458. package/pack/generated/codex/skills/rite-zoom-out/SKILL.md +7 -3
  459. package/package.json +1 -1
  460. package/scripts/build-release-tarball.sh +32 -15
  461. package/scripts/check-authority-drift.py +125 -0
  462. package/scripts/check-instruction-size-baseline.mjs +19 -11
  463. package/scripts/check-invocation-integrity.py +2 -0
  464. package/scripts/codex-generate.sh +69 -33
  465. package/scripts/grade-feature.sh +121 -40
  466. package/scripts/live-hosts/agent-result.schema.json +230 -0
  467. package/scripts/live-hosts/claude.sh +87 -0
  468. package/scripts/live-hosts/codex.sh +81 -0
  469. package/scripts/live-hosts/common.sh +113 -0
  470. package/scripts/live-hosts/fake-host.py +264 -0
  471. package/scripts/live-hosts/host-transport.py +287 -0
  472. package/scripts/release-check.sh +5 -1
  473. package/scripts/run-agent-contract-evals.py +1380 -0
  474. package/scripts/run-behavioral-evals.sh +24 -30
  475. package/scripts/run-evals.sh +1 -5
  476. package/scripts/run-live-behavioral-evals.py +1274 -144
  477. package/scripts/run-outcome-evals.sh +445 -88
  478. package/scripts/run-tests.mjs +30 -2
  479. package/scripts/skills-inventory.mjs +1 -1
  480. package/scripts/validate-workflow-security.py +39 -20
  481. package/scripts/validate-workspace-schema.py +362 -10
  482. package/scripts/validate.sh +21 -15
  483. package/engine/testdata/golden/TestParityWrightScope/devrites-edit-allowed.golden +0 -1
  484. /package/engine/testdata/golden/{TestParityReconcile/check-no-claimed.golden → TestParityBuildReadiness/arg=clarifyopen.golden} +0 -0
@@ -0,0 +1,576 @@
1
+ package forge
2
+
3
+ import (
4
+ "bytes"
5
+ "encoding/json"
6
+ "errors"
7
+ "os"
8
+ "os/exec"
9
+ "path/filepath"
10
+ "runtime"
11
+ "strconv"
12
+ "strings"
13
+ "testing"
14
+ "time"
15
+ )
16
+
17
+ const (
18
+ acceptanceHash = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
19
+ testPlanHash = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
20
+ forgeWorkerTestEnv = "DEVRITES_FORGE_TEST_WORKER"
21
+ )
22
+
23
+ func TestForgeFullTreeMergeCleanupAndIdempotence(t *testing.T) {
24
+ repo := newRepo(t)
25
+ result := planFixture(t, repo)
26
+ m := result.Manifest
27
+ a := candidate(t, m, CandidateA)
28
+ b := candidate(t, m, CandidateB)
29
+
30
+ worker := startWorker(t, repo, m.RunID, CandidateA, "worker-a")
31
+ writeFile(t, filepath.Join(a.Worktree, "tracked.txt"), []byte("staged\n"), 0o644)
32
+ gitOK(t, a.Worktree, "add", "tracked.txt")
33
+ appendFile(t, filepath.Join(a.Worktree, "tracked.txt"), []byte("unstaged\n"))
34
+ writeFile(t, filepath.Join(a.Worktree, "binary.bin"), []byte{0, 1, 2, 0xff}, 0o644)
35
+ if err := os.Remove(filepath.Join(a.Worktree, "delete.txt")); err != nil {
36
+ t.Fatal(err)
37
+ }
38
+ setGitExecutable(t, a.Worktree, "script.sh")
39
+ if runtime.GOOS != "windows" {
40
+ if err := os.Symlink("tracked.txt", filepath.Join(a.Worktree, "tracked.link")); err != nil {
41
+ t.Fatal(err)
42
+ }
43
+ }
44
+ worker.finish(t, StateFinished)
45
+ m, err := Extract(repo, m.RunID, CandidateA)
46
+ if err != nil {
47
+ t.Fatal(err)
48
+ }
49
+ a = candidate(t, m, CandidateA)
50
+ if a.State != StateExtracted || a.Commit == "" || a.Tree == "" || a.DeltaSHA256 == "" {
51
+ t.Fatalf("candidate A extraction incomplete: %+v", a)
52
+ }
53
+ if got := gitRaw(t, a.Worktree, "show", a.Commit+":binary.bin"); !bytes.Equal(got, []byte{0, 1, 2, 0xff}) {
54
+ t.Fatalf("binary snapshot=%v", got)
55
+ }
56
+ tree := string(gitRaw(t, a.Worktree, "ls-tree", "-r", a.Commit))
57
+ if !strings.Contains(tree, "100755 blob") || !strings.Contains(tree, "\tscript.sh") {
58
+ t.Fatalf("executable mode missing from tree:\n%s", tree)
59
+ }
60
+ if runtime.GOOS != "windows" && (!strings.Contains(tree, "120000 blob") || !strings.Contains(tree, "\ttracked.link")) {
61
+ t.Fatalf("symlink missing from tree:\n%s", tree)
62
+ }
63
+
64
+ worker = startWorker(t, repo, m.RunID, CandidateB, "worker-b")
65
+ writeFile(t, filepath.Join(b.Worktree, "runner-only.txt"), []byte("loser\n"), 0o644)
66
+ worker.finish(t, StateFinished)
67
+ if _, err := Extract(repo, m.RunID, CandidateB); err != nil {
68
+ t.Fatal(err)
69
+ }
70
+ if _, err := RecordWinner(repo, m.RunID, CandidateA, "judge-1"); err != nil {
71
+ t.Fatal(err)
72
+ }
73
+ if _, err := Merge(repo, m.RunID, CandidateA); err != nil {
74
+ t.Fatal(err)
75
+ }
76
+ if _, err := Merge(repo, m.RunID, CandidateA); err != nil {
77
+ t.Fatalf("idempotent merge: %v", err)
78
+ }
79
+ if got := readFile(t, filepath.Join(repo, "tracked.txt")); got != "staged\nunstaged\n" {
80
+ t.Fatalf("winner content=%q", got)
81
+ }
82
+ if _, err := os.Stat(filepath.Join(repo, "runner-only.txt")); !errors.Is(err, os.ErrNotExist) {
83
+ t.Fatalf("runner-up content landed: %v", err)
84
+ }
85
+ if _, err := os.Stat(filepath.Join(repo, "delete.txt")); !errors.Is(err, os.ErrNotExist) {
86
+ t.Fatalf("deletion did not land: %v", err)
87
+ }
88
+ if got := string(gitRaw(t, repo, "ls-tree", "HEAD", "--", "script.sh")); !strings.HasPrefix(got, "100755 blob ") {
89
+ t.Fatalf("merged script mode is not executable: %s", got)
90
+ }
91
+
92
+ if _, err := RecordVerification(repo, m.RunID, true, "verifier-1"); err != nil {
93
+ t.Fatal(err)
94
+ }
95
+ writeFile(t, filepath.Join(b.Worktree, "keep.ignored"), []byte("preserve\n"), 0o600)
96
+ m, err = Cleanup(repo, m.RunID)
97
+ if err != nil {
98
+ t.Fatal(err)
99
+ }
100
+ if m.Cleanup.State != "partial" || !strings.Contains(m.Cleanup.Preserved["B"], "dirt") {
101
+ t.Fatalf("cleanup did not preserve ignored dirt: %+v", m.Cleanup)
102
+ }
103
+ if _, err := os.Stat(a.Worktree); !errors.Is(err, os.ErrNotExist) {
104
+ t.Fatalf("clean winner worktree remains: %v", err)
105
+ }
106
+ if _, err := os.Stat(b.Worktree); err != nil {
107
+ t.Fatalf("dirty runner-up was removed: %v", err)
108
+ }
109
+ if err := os.Remove(filepath.Join(b.Worktree, "keep.ignored")); err != nil {
110
+ t.Fatal(err)
111
+ }
112
+ m, err = Cleanup(repo, m.RunID)
113
+ if err != nil {
114
+ t.Fatal(err)
115
+ }
116
+ if m.Cleanup.State != "complete" {
117
+ t.Fatalf("cleanup state=%s", m.Cleanup.State)
118
+ }
119
+ if _, err := Cleanup(repo, m.RunID); err != nil {
120
+ t.Fatalf("idempotent cleanup: %v", err)
121
+ }
122
+ for _, c := range m.Candidates {
123
+ if got := strings.TrimSpace(string(gitRaw(t, repo, "rev-parse", "--verify", "refs/heads/"+c.Branch))); got == "" {
124
+ t.Fatalf("branch %s was deleted", c.Branch)
125
+ }
126
+ }
127
+ }
128
+
129
+ func TestMergeDriftLeavesPrimaryByteIdentical(t *testing.T) {
130
+ repo := newRepo(t)
131
+ m := planFixture(t, repo).Manifest
132
+ for _, id := range []CandidateID{CandidateA, CandidateB} {
133
+ c := candidate(t, m, id)
134
+ worker := startWorker(t, repo, m.RunID, id, "worker-"+strings.ToLower(string(id)))
135
+ writeFile(t, filepath.Join(c.Worktree, "tracked.txt"), []byte(string(id)+"\n"), 0o644)
136
+ worker.finish(t, StateFinished)
137
+ var err error
138
+ m, err = Extract(repo, m.RunID, id)
139
+ if err != nil {
140
+ t.Fatal(err)
141
+ }
142
+ }
143
+ if _, err := RecordWinner(repo, m.RunID, CandidateA, "judge"); err != nil {
144
+ t.Fatal(err)
145
+ }
146
+ writeFile(t, filepath.Join(repo, "tracked.txt"), []byte("primary edit\n"), 0o644)
147
+ beforeWorktree := gitRaw(t, repo, "diff", "--binary")
148
+ beforeIndex := gitRaw(t, repo, "diff", "--cached", "--binary")
149
+ beforeHead := strings.TrimSpace(string(gitRaw(t, repo, "rev-parse", "HEAD")))
150
+ if _, err := Merge(repo, m.RunID, CandidateA); err == nil {
151
+ t.Fatal("merge succeeded with dirty primary")
152
+ }
153
+ if got := gitRaw(t, repo, "diff", "--binary"); !bytes.Equal(got, beforeWorktree) {
154
+ t.Fatal("failed merge changed primary worktree")
155
+ }
156
+ if got := gitRaw(t, repo, "diff", "--cached", "--binary"); !bytes.Equal(got, beforeIndex) {
157
+ t.Fatal("failed merge changed primary index")
158
+ }
159
+ if got := strings.TrimSpace(string(gitRaw(t, repo, "rev-parse", "HEAD"))); got != beforeHead {
160
+ t.Fatalf("failed merge changed HEAD: %s -> %s", beforeHead, got)
161
+ }
162
+ }
163
+
164
+ func TestManifestPathTamperingAndSymlinkEscapeFailClosed(t *testing.T) {
165
+ t.Run("candidate path", func(t *testing.T) {
166
+ repo := newRepo(t)
167
+ m := planFixture(t, repo).Manifest
168
+ foreign := filepath.Join(t.TempDir(), "foreign")
169
+ if err := os.MkdirAll(foreign, 0o755); err != nil {
170
+ t.Fatal(err)
171
+ }
172
+ m.Candidates[0].Worktree = foreign
173
+ path, err := ManifestPath(repo, m.FeatureSlug, m.RunID)
174
+ if err != nil {
175
+ t.Fatal(err)
176
+ }
177
+ raw, _ := json.MarshalIndent(m, "", " ")
178
+ writeFile(t, path, append(raw, '\n'), 0o600)
179
+ if _, _, err := Load(repo, m.RunID); err == nil {
180
+ t.Fatal("tampered manifest loaded")
181
+ }
182
+ if _, err := os.Stat(foreign); err != nil {
183
+ t.Fatalf("foreign path was touched: %v", err)
184
+ }
185
+ })
186
+
187
+ t.Run("manifest parent symlink", func(t *testing.T) {
188
+ repo := newRepo(t)
189
+ foreign := t.TempDir()
190
+ if err := os.Symlink(foreign, filepath.Join(repo, ".devrites")); err != nil {
191
+ t.Fatal(err)
192
+ }
193
+ if _, err := Plan(repo, planOptions()); err == nil {
194
+ t.Fatal("plan accepted symlinked manifest root")
195
+ }
196
+ entries, err := os.ReadDir(foreign)
197
+ if err != nil {
198
+ t.Fatal(err)
199
+ }
200
+ if len(entries) != 0 {
201
+ t.Fatalf("symlink target was written: %v", entries)
202
+ }
203
+ })
204
+ }
205
+
206
+ func TestPlanDegradesInsideSubmoduleBeforeSideEffects(t *testing.T) {
207
+ base := t.TempDir()
208
+ source := filepath.Join(base, "source")
209
+ parent := filepath.Join(base, "parent")
210
+ initRepo(t, source)
211
+ initRepo(t, parent)
212
+ gitOK(t, parent, "-c", "protocol.file.allow=always", "submodule", "add", source, "modules/child")
213
+ gitOK(t, parent, "commit", "-am", "add submodule")
214
+ child, err := physicalExisting(filepath.Join(parent, "modules/child"))
215
+ if err != nil {
216
+ t.Fatal(err)
217
+ }
218
+ result, err := Plan(child, planOptions())
219
+ if err != nil {
220
+ t.Fatal(err)
221
+ }
222
+ if result.Status != "degraded" || result.Mode != "serial" {
223
+ t.Fatalf("submodule plan=%+v", result)
224
+ }
225
+ if paths, err := manifestPaths(child, "alpha"); err != nil || len(paths) != 0 {
226
+ t.Fatalf("submodule plan wrote manifests: %v %v", paths, err)
227
+ }
228
+ if _, err := os.Stat(forgeRoot(child, "not-a-real-run")); !errors.Is(err, os.ErrNotExist) {
229
+ t.Fatalf("submodule plan created staging state: %v", err)
230
+ }
231
+ }
232
+
233
+ func TestReapUsesManifestsPreservesLiveAndForeignState(t *testing.T) {
234
+ repo := newRepo(t)
235
+ m := planFixture(t, repo).Manifest
236
+ for _, id := range []CandidateID{CandidateA, CandidateB} {
237
+ c := candidate(t, m, id)
238
+ worker := startWorker(t, repo, m.RunID, id, "worker-"+strings.ToLower(string(id)))
239
+ writeFile(t, filepath.Join(c.Worktree, "candidate-"+string(id)+".txt"), []byte("content\n"), 0o644)
240
+ worker.finish(t, StateFinished)
241
+ var err error
242
+ m, err = Extract(repo, m.RunID, id)
243
+ if err != nil {
244
+ t.Fatal(err)
245
+ }
246
+ }
247
+ token, err := ProcessStartToken(os.Getpid())
248
+ if err != nil {
249
+ t.Fatal(err)
250
+ }
251
+ b := candidate(t, m, CandidateB)
252
+ b.Worker.PID = os.Getpid()
253
+ b.Worker.ProcessStart = token
254
+ path, err := ManifestPath(repo, m.FeatureSlug, m.RunID)
255
+ if err != nil {
256
+ t.Fatal(err)
257
+ }
258
+ if err := save(path, m); err != nil {
259
+ t.Fatal(err)
260
+ }
261
+ foreign := filepath.Join(filepath.Dir(filepath.Dir(m.ForgeRoot)), "foreign-orphan")
262
+ if err := os.MkdirAll(foreign, 0o755); err != nil {
263
+ t.Fatal(err)
264
+ }
265
+ results, err := Reap(repo, "alpha")
266
+ if err != nil {
267
+ t.Fatal(err)
268
+ }
269
+ if len(results) != 1 || len(results[0].Removed) != 1 || results[0].Removed[0] != CandidateA {
270
+ t.Fatalf("reap result=%+v", results)
271
+ }
272
+ if !strings.Contains(results[0].Preserved["B"], "live") {
273
+ t.Fatalf("live candidate not preserved: %+v", results[0])
274
+ }
275
+ if _, err := os.Stat(foreign); err != nil {
276
+ t.Fatalf("foreign orphan was touched: %v", err)
277
+ }
278
+ if _, err := os.Stat(b.Worktree); err != nil {
279
+ t.Fatalf("live worktree was removed: %v", err)
280
+ }
281
+ for _, c := range m.Candidates {
282
+ gitOK(t, repo, "show-ref", "--verify", "--quiet", "refs/heads/"+c.Branch)
283
+ }
284
+ }
285
+
286
+ func TestRunPlanAcceptsPositionalBeforeFlagsAndRecordIsIdempotent(t *testing.T) {
287
+ repo := newRepo(t)
288
+ var stdout, stderr bytes.Buffer
289
+ args := append(planCLIArgs(), "--worker-binding="+WorkerBindingManifestEnvV1)
290
+ code := Run(repo, args, &stdout, &stderr)
291
+ if code != 0 {
292
+ t.Fatalf("Run plan code=%d stderr=%s", code, stderr.String())
293
+ }
294
+ var result PlanResult
295
+ if err := json.Unmarshal(stdout.Bytes(), &result); err != nil {
296
+ t.Fatal(err)
297
+ }
298
+ worker := startWorker(t, repo, result.Manifest.RunID, CandidateA, "worker-a")
299
+ opts := RecordOptions{
300
+ RunID: result.Manifest.RunID,
301
+ Candidate: CandidateA,
302
+ State: StateRunning,
303
+ WorkerID: worker.id,
304
+ PID: worker.cmd.Process.Pid,
305
+ ProcessStart: worker.token,
306
+ }
307
+ if _, err := Record(repo, opts); err != nil {
308
+ t.Fatalf("idempotent running record: %v", err)
309
+ }
310
+ worker.finish(t, StateFinished)
311
+ if _, err := Record(repo, RecordOptions{
312
+ RunID: result.Manifest.RunID, Candidate: CandidateA, State: StateFinished, WorkerID: worker.id,
313
+ }); err != nil {
314
+ t.Fatalf("idempotent finished record: %v", err)
315
+ }
316
+ }
317
+
318
+ func TestRunPlanRequiresExactWorkerBindingBeforeSideEffects(t *testing.T) {
319
+ repo := newRepo(t)
320
+ beforeWorktrees := gitRaw(t, repo, "worktree", "list", "--porcelain")
321
+ beforeBranches := gitRaw(t, repo, "for-each-ref", "--format=%(refname)", "refs/heads/devrites/forge")
322
+
323
+ var stdout, stderr bytes.Buffer
324
+ if code := Run(repo, planCLIArgs(), &stdout, &stderr); code != 0 {
325
+ t.Fatalf("unbound plan code=%d stderr=%s", code, stderr.String())
326
+ }
327
+ var result PlanResult
328
+ if err := json.Unmarshal(stdout.Bytes(), &result); err != nil {
329
+ t.Fatal(err)
330
+ }
331
+ if result.Status != "degraded" || result.Mode != "serial" || result.Manifest != nil {
332
+ t.Fatalf("unbound plan=%+v", result)
333
+ }
334
+ assertNoPlanSideEffects(t, repo, beforeWorktrees, beforeBranches)
335
+
336
+ stdout.Reset()
337
+ stderr.Reset()
338
+ args := append(planCLIArgs(), "--worker-binding=unknown-v1")
339
+ if code := Run(repo, args, &stdout, &stderr); code == 0 || !strings.Contains(stderr.String(), "unsupported worker binding") {
340
+ t.Fatalf("unknown binding code=%d stdout=%s stderr=%s", code, stdout.String(), stderr.String())
341
+ }
342
+ assertNoPlanSideEffects(t, repo, beforeWorktrees, beforeBranches)
343
+ }
344
+
345
+ func TestRunProcessTokenMatchesAPIAndRejectsInvalidPID(t *testing.T) {
346
+ want, err := ProcessStartToken(os.Getpid())
347
+ if err != nil {
348
+ t.Fatal(err)
349
+ }
350
+ var stdout, stderr bytes.Buffer
351
+ if code := Run("", []string{"process-token", strconv.Itoa(os.Getpid())}, &stdout, &stderr); code != 0 {
352
+ t.Fatalf("process-token code=%d stderr=%s", code, stderr.String())
353
+ }
354
+ var result ProcessTokenResult
355
+ if err := json.Unmarshal(stdout.Bytes(), &result); err != nil {
356
+ t.Fatal(err)
357
+ }
358
+ if result.Status != "ok" || result.PID != os.Getpid() || result.ProcessStart != want {
359
+ t.Fatalf("process-token=%+v want pid=%d token=%s", result, os.Getpid(), want)
360
+ }
361
+
362
+ for _, pid := range []string{"0", "-1", "+1", "01", " 1", "2147483647"} {
363
+ stdout.Reset()
364
+ stderr.Reset()
365
+ if code := Run("", []string{"process-token", pid}, &stdout, &stderr); code == 0 || stdout.Len() != 0 {
366
+ t.Fatalf("process-token %q code=%d stdout=%s stderr=%s", pid, code, stdout.String(), stderr.String())
367
+ }
368
+ }
369
+ }
370
+
371
+ func planCLIArgs() []string {
372
+ return []string{
373
+ "plan", "slice-1", "alpha",
374
+ "--strategy", "A=first", "--strategy", "B=second",
375
+ "--acceptance-hash", acceptanceHash, "--test-plan-hash", testPlanHash,
376
+ }
377
+ }
378
+
379
+ func assertNoPlanSideEffects(t *testing.T, repo string, wantWorktrees, wantBranches []byte) {
380
+ t.Helper()
381
+ if got := gitRaw(t, repo, "worktree", "list", "--porcelain"); !bytes.Equal(got, wantWorktrees) {
382
+ t.Fatalf("plan changed worktrees:\n%s", got)
383
+ }
384
+ if got := gitRaw(t, repo, "for-each-ref", "--format=%(refname)", "refs/heads/devrites/forge"); !bytes.Equal(got, wantBranches) {
385
+ t.Fatalf("plan changed Forge branches:\n%s", got)
386
+ }
387
+ if paths, err := manifestPaths(repo, ""); err != nil || len(paths) != 0 {
388
+ t.Fatalf("plan created manifests: paths=%v err=%v", paths, err)
389
+ }
390
+ }
391
+
392
+ type workerProcess struct {
393
+ repo string
394
+ runID string
395
+ id string
396
+ cand CandidateID
397
+ cmd *exec.Cmd
398
+ token string
399
+ }
400
+
401
+ func startWorker(t *testing.T, repo, runID string, candidate CandidateID, id string) *workerProcess {
402
+ t.Helper()
403
+ cmd := exec.Command(os.Args[0], "-test.run=^TestForgeWorkerProcess$")
404
+ cmd.Env = append(os.Environ(), forgeWorkerTestEnv+"=1")
405
+ if err := cmd.Start(); err != nil {
406
+ t.Fatal(err)
407
+ }
408
+ t.Cleanup(func() {
409
+ if cmd.ProcessState == nil {
410
+ _ = cmd.Process.Kill()
411
+ _ = cmd.Wait()
412
+ }
413
+ })
414
+ var token string
415
+ var err error
416
+ for range 20 {
417
+ token, err = ProcessStartToken(cmd.Process.Pid)
418
+ if err == nil {
419
+ break
420
+ }
421
+ time.Sleep(10 * time.Millisecond)
422
+ }
423
+ if err != nil {
424
+ t.Fatal(err)
425
+ }
426
+ if _, err := Record(repo, RecordOptions{
427
+ RunID: runID, Candidate: candidate, State: StateRunning,
428
+ WorkerID: id, PID: cmd.Process.Pid, ProcessStart: token,
429
+ }); err != nil {
430
+ t.Fatal(err)
431
+ }
432
+ return &workerProcess{repo: repo, runID: runID, id: id, cand: candidate, cmd: cmd, token: token}
433
+ }
434
+
435
+ func TestForgeWorkerProcess(t *testing.T) {
436
+ if os.Getenv(forgeWorkerTestEnv) != "1" {
437
+ return
438
+ }
439
+ for {
440
+ time.Sleep(time.Hour)
441
+ }
442
+ }
443
+
444
+ func (w *workerProcess) finish(t *testing.T, state CandidateState) {
445
+ t.Helper()
446
+ if err := w.cmd.Process.Kill(); err != nil {
447
+ t.Fatal(err)
448
+ }
449
+ if err := w.cmd.Wait(); err == nil {
450
+ t.Fatal("killed worker exited successfully")
451
+ }
452
+ if _, err := Record(w.repo, RecordOptions{
453
+ RunID: w.runID, Candidate: w.cand, State: state, WorkerID: w.id,
454
+ }); err != nil {
455
+ t.Fatal(err)
456
+ }
457
+ }
458
+
459
+ func planFixture(t *testing.T, repo string) PlanResult {
460
+ t.Helper()
461
+ result, err := Plan(repo, planOptions())
462
+ if err != nil {
463
+ t.Fatal(err)
464
+ }
465
+ if result.Status != "planned" || result.Manifest == nil {
466
+ t.Fatalf("plan=%+v", result)
467
+ }
468
+ return result
469
+ }
470
+
471
+ func planOptions() PlanOptions {
472
+ return PlanOptions{
473
+ SliceID: "slice-1",
474
+ Slug: "alpha",
475
+ Strategies: map[CandidateID]string{
476
+ CandidateA: "minimal",
477
+ CandidateB: "alternate",
478
+ },
479
+ AcceptanceHash: acceptanceHash,
480
+ TestPlanHash: testPlanHash,
481
+ }
482
+ }
483
+
484
+ func candidate(t *testing.T, m *Manifest, id CandidateID) *Candidate {
485
+ t.Helper()
486
+ c, err := m.Candidate(id)
487
+ if err != nil {
488
+ t.Fatal(err)
489
+ }
490
+ return c
491
+ }
492
+
493
+ func newRepo(t *testing.T) string {
494
+ t.Helper()
495
+ repo := filepath.Join(t.TempDir(), "repo")
496
+ initRepo(t, repo)
497
+ return repo
498
+ }
499
+
500
+ func initRepo(t *testing.T, repo string) {
501
+ t.Helper()
502
+ if err := os.MkdirAll(repo, 0o755); err != nil {
503
+ t.Fatal(err)
504
+ }
505
+ gitOK(t, repo, "init", "-b", "main")
506
+ gitOK(t, repo, "config", "user.name", "Test User")
507
+ gitOK(t, repo, "config", "user.email", "test@example.invalid")
508
+ writeFile(t, filepath.Join(repo, ".gitignore"), []byte(".devrites/\n*.ignored\n"), 0o644)
509
+ writeFile(t, filepath.Join(repo, "tracked.txt"), []byte("base\n"), 0o644)
510
+ writeFile(t, filepath.Join(repo, "delete.txt"), []byte("delete me\n"), 0o644)
511
+ writeFile(t, filepath.Join(repo, "script.sh"), []byte("#!/bin/sh\n"), 0o644)
512
+ gitOK(t, repo, "add", ".")
513
+ gitOK(t, repo, "commit", "-m", "baseline")
514
+ }
515
+
516
+ func gitOK(t *testing.T, dir string, args ...string) {
517
+ t.Helper()
518
+ cmd := exec.Command("git", append([]string{"-C", dir}, args...)...)
519
+ cmd.Env = append(os.Environ(), "LC_ALL=C", "GIT_TERMINAL_PROMPT=0")
520
+ if out, err := cmd.CombinedOutput(); err != nil {
521
+ t.Fatalf("git %s: %v\n%s", strings.Join(args, " "), err, out)
522
+ }
523
+ }
524
+
525
+ func gitRaw(t *testing.T, dir string, args ...string) []byte {
526
+ t.Helper()
527
+ cmd := exec.Command("git", append([]string{"-C", dir}, args...)...)
528
+ cmd.Env = append(os.Environ(), "LC_ALL=C", "GIT_TERMINAL_PROMPT=0")
529
+ out, err := cmd.Output()
530
+ if err != nil {
531
+ t.Fatalf("git %s: %v", strings.Join(args, " "), err)
532
+ }
533
+ return out
534
+ }
535
+
536
+ func setGitExecutable(t *testing.T, dir, path string) {
537
+ t.Helper()
538
+ if err := os.Chmod(filepath.Join(dir, path), 0o755); err != nil {
539
+ t.Fatal(err)
540
+ }
541
+ gitOK(t, dir, "update-index", "--chmod=+x", "--", path)
542
+ }
543
+
544
+ func writeFile(t *testing.T, path string, data []byte, mode os.FileMode) {
545
+ t.Helper()
546
+ if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
547
+ t.Fatal(err)
548
+ }
549
+ if err := os.WriteFile(path, data, mode); err != nil {
550
+ t.Fatal(err)
551
+ }
552
+ }
553
+
554
+ func appendFile(t *testing.T, path string, data []byte) {
555
+ t.Helper()
556
+ f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0)
557
+ if err != nil {
558
+ t.Fatal(err)
559
+ }
560
+ if _, err := f.Write(data); err != nil {
561
+ _ = f.Close()
562
+ t.Fatal(err)
563
+ }
564
+ if err := f.Close(); err != nil {
565
+ t.Fatal(err)
566
+ }
567
+ }
568
+
569
+ func readFile(t *testing.T, path string) string {
570
+ t.Helper()
571
+ raw, err := os.ReadFile(path)
572
+ if err != nil {
573
+ t.Fatal(err)
574
+ }
575
+ return string(raw)
576
+ }