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,298 @@
1
+ package lib
2
+
3
+ import (
4
+ "crypto/sha256"
5
+ "encoding/hex"
6
+ "encoding/json"
7
+ "fmt"
8
+ "io"
9
+ "os"
10
+ "regexp"
11
+ "strings"
12
+
13
+ "github.com/devrites/devrites/internal/fsutil"
14
+ )
15
+
16
+ const (
17
+ recoveryAttemptsFile = "recovery-attempts.jsonl"
18
+ recoveryAttemptLimit = 3
19
+ recoveryRouteSchema = "recovery-route/v1"
20
+ )
21
+
22
+ type recoveryClass string
23
+ type recoveryOwner string
24
+ type recoveryAction string
25
+
26
+ const (
27
+ recoveryIntentGap recoveryClass = "intent_gap"
28
+ recoverySpecGap recoveryClass = "spec_gap"
29
+ recoveryPlanGap recoveryClass = "plan_gap"
30
+ recoveryImplementationDefect recoveryClass = "implementation_defect"
31
+ recoveryProofToolDefect recoveryClass = "proof_tool_defect"
32
+ recoveryEnvironmentDefect recoveryClass = "environment_defect"
33
+ recoveryPreexisting recoveryClass = "preexisting"
34
+ recoveryNotADefect recoveryClass = "not_a_defect"
35
+ )
36
+
37
+ type recoveryRoute struct {
38
+ Schema string `json:"schema"`
39
+ Class recoveryClass `json:"class"`
40
+ Owner recoveryOwner `json:"owner"`
41
+ Action recoveryAction `json:"action"`
42
+ HumanPause bool `json:"humanPause"`
43
+ }
44
+
45
+ var recoveryRoutes = [...]recoveryRoute{
46
+ {recoveryRouteSchema, recoveryIntentGap, "human_clarify", "clarify_intent", true},
47
+ {recoveryRouteSchema, recoverySpecGap, "clarify", "clarify_missing_decision", true},
48
+ {recoveryRouteSchema, recoveryPlanGap, "plan", "rite_plan_repair", false},
49
+ {recoveryRouteSchema, recoveryImplementationDefect, "wright", "repair_implementation_and_rerun_proof", false},
50
+ {recoveryRouteSchema, recoveryProofToolDefect, "debug_recovery", "repair_proof_tool_and_rerun_original_proof", false},
51
+ {recoveryRouteSchema, recoveryEnvironmentDefect, "debug_recovery", "normalize_environment_and_run_discriminator", false},
52
+ {recoveryRouteSchema, recoveryPreexisting, "baseline", "record_baseline_and_fix_if_acceptance_blocked", false},
53
+ {recoveryRouteSchema, recoveryNotADefect, "caller", "record_authority_and_continue", false},
54
+ }
55
+
56
+ type recoveryAttempt struct {
57
+ Fingerprint string `json:"fingerprint"`
58
+ RootCause string `json:"rootCause"`
59
+ Class recoveryClass `json:"class,omitempty"`
60
+ Attempt int `json:"attempt"`
61
+ Status string `json:"status"`
62
+ Failure string `json:"failure,omitempty"`
63
+ At string `json:"at"`
64
+ }
65
+
66
+ var recoveryWhitespace = regexp.MustCompile(`\s+`)
67
+
68
+ // RecoveryAttempts durably accounts bounded technical recovery across agents
69
+ // and sessions.
70
+ //
71
+ // recovery route <class>
72
+ // recovery record [--class <class>] "<root cause>" "<failure>" [slug]
73
+ // recovery check "<root cause>" [slug]
74
+ // recovery clear [--class <class>] "<root cause>" [slug]
75
+ // recovery fingerprint "<root cause>"
76
+ func RecoveryAttempts(root string, args []string, stdout, stderr io.Writer) int {
77
+ sub := strings.ToLower(strings.TrimSpace(argAt(args, 0)))
78
+ if sub == "route" {
79
+ return writeRecoveryRoute(args[1:], stdout, stderr)
80
+ }
81
+
82
+ commandArgs := args[1:]
83
+ var class recoveryClass
84
+ if sub == "record" || sub == "clear" {
85
+ var err error
86
+ class, commandArgs, err = parseRecoveryClass(commandArgs)
87
+ if err != nil {
88
+ fmt.Fprintf(stderr, "recovery: %v\n", err)
89
+ return 2
90
+ }
91
+ }
92
+ rootCause := strings.TrimSpace(argAt(commandArgs, 0))
93
+ if sub == "fingerprint" {
94
+ if rootCause == "" || len(commandArgs) != 1 {
95
+ fmt.Fprintln(stderr, `usage: devrites-engine recovery fingerprint "<root cause>"`)
96
+ return 2
97
+ }
98
+ fmt.Fprintln(stdout, recoveryFingerprint(rootCause))
99
+ return 0
100
+ }
101
+ if sub != "record" && sub != "check" && sub != "clear" {
102
+ fmt.Fprintln(stderr, `usage: devrites-engine recovery <route|record|check|clear|fingerprint> ...`)
103
+ return 2
104
+ }
105
+ if rootCause == "" {
106
+ fmt.Fprintln(stderr, "recovery: root cause is required")
107
+ return 2
108
+ }
109
+
110
+ var failure string
111
+ var slugArgs []string
112
+ if sub == "record" {
113
+ failure = strings.TrimSpace(argAt(commandArgs, 1))
114
+ if failure == "" {
115
+ fmt.Fprintln(stderr, "recovery: record requires a concise failure")
116
+ return 2
117
+ }
118
+ slugArgs = commandArgs[2:]
119
+ } else {
120
+ slugArgs = commandArgs[1:]
121
+ }
122
+ if len(slugArgs) > 1 {
123
+ fmt.Fprintln(stderr, "recovery: at most one slug is allowed")
124
+ return 2
125
+ }
126
+ slug := slugOrActive(root, slugArgs)
127
+ if slug == "" {
128
+ fmt.Fprintln(stderr, "recovery: no active workspace")
129
+ return 2
130
+ }
131
+ path := featureFile(root, slug, recoveryAttemptsFile)
132
+ entries, err := readRecoveryAttempts(path)
133
+ if err != nil {
134
+ fmt.Fprintf(stderr, "recovery: %v\n", err)
135
+ return 2
136
+ }
137
+ fingerprint := recoveryFingerprint(rootCause)
138
+ attempts := activeRecoveryAttempts(entries, fingerprint)
139
+
140
+ switch sub {
141
+ case "check":
142
+ if attempts >= recoveryAttemptLimit {
143
+ fmt.Fprintf(stderr, "recovery: exhausted %d/%d for %s\n", attempts, recoveryAttemptLimit, fingerprint)
144
+ return 3
145
+ }
146
+ fmt.Fprintf(stdout, "recovery: available %d/%d used for %s\n", attempts, recoveryAttemptLimit, fingerprint)
147
+ return 0
148
+ case "record":
149
+ if attempts >= recoveryAttemptLimit {
150
+ fmt.Fprintf(stderr, "recovery: exhausted %d/%d for %s; no new attempt recorded\n", attempts, recoveryAttemptLimit, fingerprint)
151
+ return 3
152
+ }
153
+ attempts++
154
+ entries = append(entries, recoveryAttempt{
155
+ Fingerprint: fingerprint,
156
+ RootCause: normalizeRecoveryCause(rootCause),
157
+ Class: class,
158
+ Attempt: attempts,
159
+ Status: "failed",
160
+ Failure: failure,
161
+ At: nowUTC(),
162
+ })
163
+ if err := writeRecoveryAttempts(path, entries); err != nil {
164
+ fmt.Fprintf(stderr, "recovery: persist attempt: %v\n", err)
165
+ return 1
166
+ }
167
+ if attempts >= recoveryAttemptLimit {
168
+ fmt.Fprintf(stderr, "recovery: recorded attempt %d/%d; automatic recovery exhausted for %s\n", attempts, recoveryAttemptLimit, fingerprint)
169
+ return 3
170
+ }
171
+ fmt.Fprintf(stdout, "recovery: recorded attempt %d/%d for %s\n", attempts, recoveryAttemptLimit, fingerprint)
172
+ return 0
173
+ case "clear":
174
+ entries = append(entries, recoveryAttempt{
175
+ Fingerprint: fingerprint,
176
+ RootCause: normalizeRecoveryCause(rootCause),
177
+ Class: class,
178
+ Status: "cleared",
179
+ At: nowUTC(),
180
+ })
181
+ if err := writeRecoveryAttempts(path, entries); err != nil {
182
+ fmt.Fprintf(stderr, "recovery: clear attempts: %v\n", err)
183
+ return 1
184
+ }
185
+ fmt.Fprintf(stdout, "recovery: cleared attempts for %s\n", fingerprint)
186
+ return 0
187
+ }
188
+ return 2
189
+ }
190
+
191
+ func writeRecoveryRoute(args []string, stdout, stderr io.Writer) int {
192
+ if len(args) != 1 {
193
+ fmt.Fprintln(stderr, "usage: devrites-engine recovery route <class>")
194
+ return 2
195
+ }
196
+ route, ok := recoveryRouteFor(recoveryClass(strings.TrimSpace(args[0])))
197
+ if !ok {
198
+ fmt.Fprintf(stderr, "recovery: unknown class %q\n", args[0])
199
+ return 2
200
+ }
201
+ if err := json.NewEncoder(stdout).Encode(route); err != nil {
202
+ fmt.Fprintf(stderr, "recovery: encode route: %v\n", err)
203
+ return 1
204
+ }
205
+ return 0
206
+ }
207
+
208
+ func recoveryRouteFor(class recoveryClass) (recoveryRoute, bool) {
209
+ for _, route := range recoveryRoutes {
210
+ if route.Class == class {
211
+ return route, true
212
+ }
213
+ }
214
+ return recoveryRoute{}, false
215
+ }
216
+
217
+ func parseRecoveryClass(args []string) (recoveryClass, []string, error) {
218
+ if argAt(args, 0) != "--class" {
219
+ return "", args, nil
220
+ }
221
+ if len(args) < 2 {
222
+ return "", nil, fmt.Errorf("--class requires a value")
223
+ }
224
+ class := recoveryClass(strings.TrimSpace(args[1]))
225
+ if _, ok := recoveryRouteFor(class); !ok {
226
+ return "", nil, fmt.Errorf("unknown class %q", args[1])
227
+ }
228
+ return class, args[2:], nil
229
+ }
230
+
231
+ func recoveryFingerprint(rootCause string) string {
232
+ sum := sha256.Sum256([]byte(normalizeRecoveryCause(rootCause)))
233
+ return hex.EncodeToString(sum[:])
234
+ }
235
+
236
+ func normalizeRecoveryCause(rootCause string) string {
237
+ return strings.ToLower(recoveryWhitespace.ReplaceAllString(strings.TrimSpace(rootCause), " "))
238
+ }
239
+
240
+ func readRecoveryAttempts(path string) ([]recoveryAttempt, error) {
241
+ raw, err := os.ReadFile(path)
242
+ if os.IsNotExist(err) {
243
+ return nil, nil
244
+ }
245
+ if err != nil {
246
+ return nil, fmt.Errorf("read %s: %w", recoveryAttemptsFile, err)
247
+ }
248
+ var entries []recoveryAttempt
249
+ for index, line := range strings.Split(strings.TrimSpace(string(raw)), "\n") {
250
+ if strings.TrimSpace(line) == "" {
251
+ continue
252
+ }
253
+ var entry recoveryAttempt
254
+ if err := json.Unmarshal([]byte(line), &entry); err != nil {
255
+ return nil, fmt.Errorf("%s line %d is invalid: %w", recoveryAttemptsFile, index+1, err)
256
+ }
257
+ if entry.Fingerprint == "" || entry.Status == "" {
258
+ return nil, fmt.Errorf("%s line %d is incomplete", recoveryAttemptsFile, index+1)
259
+ }
260
+ if entry.Class != "" {
261
+ if _, ok := recoveryRouteFor(entry.Class); !ok {
262
+ return nil, fmt.Errorf("%s line %d has unknown class %q", recoveryAttemptsFile, index+1, entry.Class)
263
+ }
264
+ }
265
+ entries = append(entries, entry)
266
+ }
267
+ return entries, nil
268
+ }
269
+
270
+ func activeRecoveryAttempts(entries []recoveryAttempt, fingerprint string) int {
271
+ attempts := 0
272
+ for _, entry := range entries {
273
+ if entry.Fingerprint != fingerprint {
274
+ continue
275
+ }
276
+ if entry.Status == "cleared" {
277
+ attempts = 0
278
+ continue
279
+ }
280
+ if entry.Status == "failed" && entry.Attempt > attempts {
281
+ attempts = entry.Attempt
282
+ }
283
+ }
284
+ return attempts
285
+ }
286
+
287
+ func writeRecoveryAttempts(path string, entries []recoveryAttempt) error {
288
+ var out strings.Builder
289
+ for _, entry := range entries {
290
+ data, err := json.Marshal(entry)
291
+ if err != nil {
292
+ return err
293
+ }
294
+ out.Write(data)
295
+ out.WriteByte('\n')
296
+ }
297
+ return fsutil.WriteFileAtomic(path, []byte(out.String()), 0o600)
298
+ }
@@ -0,0 +1,215 @@
1
+ package lib
2
+
3
+ import (
4
+ "bytes"
5
+ "encoding/json"
6
+ "os"
7
+ "path/filepath"
8
+ "strings"
9
+ "testing"
10
+ )
11
+
12
+ func TestRecoveryAttemptsPersistLimitAcrossCalls(t *testing.T) {
13
+ root := t.TempDir()
14
+ slug := "recovery"
15
+ writeReadinessFile(t, root, slug, "state.md", "- Phase: build\n")
16
+ if err := os.WriteFile(filepath.Join(root, "ACTIVE"), []byte(slug+"\n"), 0o644); err != nil {
17
+ t.Fatal(err)
18
+ }
19
+ cause := "CSS extractor mishandles same-line imports"
20
+
21
+ for attempt := 1; attempt <= recoveryAttemptLimit; attempt++ {
22
+ var stdout, stderr bytes.Buffer
23
+ code := RecoveryAttempts(root, []string{"record", cause, "scanner exit 3"}, &stdout, &stderr)
24
+ if attempt < recoveryAttemptLimit && code != 0 {
25
+ t.Fatalf("attempt %d code=%d stderr=%q", attempt, code, stderr.String())
26
+ }
27
+ if attempt == recoveryAttemptLimit && code != 3 {
28
+ t.Fatalf("third attempt code=%d stdout=%q stderr=%q, want exhausted", code, stdout.String(), stderr.String())
29
+ }
30
+ }
31
+
32
+ // A fresh invocation reads the durable ledger and cannot receive three more
33
+ // attempts merely because chat/agent context changed.
34
+ var stderr bytes.Buffer
35
+ if code := RecoveryAttempts(root, []string{"check", cause}, &bytes.Buffer{}, &stderr); code != 3 {
36
+ t.Fatalf("restart check code=%d stderr=%q, want exhausted", code, stderr.String())
37
+ }
38
+ if code := RecoveryAttempts(root, []string{"record", cause, "fourth failure"}, &bytes.Buffer{}, &stderr); code != 3 {
39
+ t.Fatalf("fourth record code=%d, want refused", code)
40
+ }
41
+
42
+ if code := RecoveryAttempts(root, []string{"clear", cause}, &bytes.Buffer{}, &bytes.Buffer{}); code != 0 {
43
+ t.Fatalf("clear code=%d", code)
44
+ }
45
+ if code := RecoveryAttempts(root, []string{"check", cause}, &bytes.Buffer{}, &bytes.Buffer{}); code != 0 {
46
+ t.Fatalf("check after green clear code=%d", code)
47
+ }
48
+ }
49
+
50
+ func TestRecoveryAttemptsKeyByNormalizedRootCause(t *testing.T) {
51
+ root := t.TempDir()
52
+ slug := "recovery"
53
+ writeReadinessFile(t, root, slug, "state.md", "- Phase: build\n")
54
+ if got, want := recoveryFingerprint(" CSS GAP "), recoveryFingerprint("css gap"); got != want {
55
+ t.Fatalf("normalized fingerprints differ: %s != %s", got, want)
56
+ }
57
+ if recoveryFingerprint("css gap") == recoveryFingerprint("browser timeout") {
58
+ t.Fatal("different root causes share a fingerprint")
59
+ }
60
+
61
+ if code := RecoveryAttempts(root, []string{"record", "css gap", "exit 3", slug}, &bytes.Buffer{}, &bytes.Buffer{}); code != 0 {
62
+ t.Fatalf("record code=%d", code)
63
+ }
64
+ var stdout bytes.Buffer
65
+ if code := RecoveryAttempts(root, []string{"check", "browser timeout", slug}, &stdout, &bytes.Buffer{}); code != 0 {
66
+ t.Fatalf("different cause check code=%d", code)
67
+ }
68
+ if !strings.Contains(stdout.String(), "0/3") {
69
+ t.Fatalf("different cause inherited attempts: %q", stdout.String())
70
+ }
71
+ }
72
+
73
+ func TestRecoveryAttemptsFailsClosedOnCorruptLedger(t *testing.T) {
74
+ root := t.TempDir()
75
+ slug := "recovery"
76
+ writeReadinessFile(t, root, slug, "state.md", "- Phase: build\n")
77
+ writeReadinessFile(t, root, slug, recoveryAttemptsFile, "{not-json}\n")
78
+ var stderr bytes.Buffer
79
+ if code := RecoveryAttempts(root, []string{"check", "css gap", slug}, &bytes.Buffer{}, &stderr); code != 2 {
80
+ t.Fatalf("code=%d stderr=%q, want fail closed", code, stderr.String())
81
+ }
82
+ }
83
+
84
+ func TestRecoveryRoutesCoverEveryClass(t *testing.T) {
85
+ tests := []struct {
86
+ scenario string
87
+ class recoveryClass
88
+ owner recoveryOwner
89
+ action recoveryAction
90
+ humanPause bool
91
+ }{
92
+ {"desired behavior is undecided", recoveryIntentGap, "human_clarify", "clarify_intent", true},
93
+ {"acceptance omits an error outcome", recoverySpecGap, "clarify", "clarify_missing_decision", true},
94
+ {"proof plan omits required provenance", recoveryPlanGap, "plan", "rite_plan_repair", false},
95
+ {"stable product assertion is red", recoveryImplementationDefect, "wright", "repair_implementation_and_rerun_proof", false},
96
+ {"declared CSS import defeats the scanner", recoveryProofToolDefect, "debug_recovery", "repair_proof_tool_and_rerun_original_proof", false},
97
+ {"browser runner exhausts resources", recoveryEnvironmentDefect, "debug_recovery", "normalize_environment_and_run_discriminator", false},
98
+ {"unrelated baseline lint already fails", recoveryPreexisting, "baseline", "record_baseline_and_fix_if_acceptance_blocked", false},
99
+ {"observed behavior matches accepted authority", recoveryNotADefect, "caller", "record_authority_and_continue", false},
100
+ }
101
+
102
+ if len(recoveryRoutes) != len(tests) {
103
+ t.Fatalf("route count=%d, want %d", len(recoveryRoutes), len(tests))
104
+ }
105
+ for _, test := range tests {
106
+ t.Run(test.scenario, func(t *testing.T) {
107
+ route, ok := recoveryRouteFor(test.class)
108
+ if !ok {
109
+ t.Fatalf("missing route for %q", test.class)
110
+ }
111
+ if route.Schema != recoveryRouteSchema || route.Owner != test.owner ||
112
+ route.Action != test.action || route.HumanPause != test.humanPause {
113
+ t.Fatalf("route=%+v", route)
114
+ }
115
+ })
116
+ }
117
+ }
118
+
119
+ func TestRecoveryRouteOutputIsStableJSON(t *testing.T) {
120
+ var stdout, stderr bytes.Buffer
121
+ if code := RecoveryAttempts("", []string{"route", "proof_tool_defect"}, &stdout, &stderr); code != 0 {
122
+ t.Fatalf("code=%d stderr=%q", code, stderr.String())
123
+ }
124
+ const want = `{"schema":"recovery-route/v1","class":"proof_tool_defect","owner":"debug_recovery","action":"repair_proof_tool_and_rerun_original_proof","humanPause":false}` + "\n"
125
+ if stdout.String() != want {
126
+ t.Fatalf("stdout=%q, want %q", stdout.String(), want)
127
+ }
128
+ }
129
+
130
+ func TestRecoveryReplayDEC031RoutesWithoutAuthorization(t *testing.T) {
131
+ proofRoute, ok := recoveryRouteFor(recoveryProofToolDefect)
132
+ if !ok || proofRoute.HumanPause || proofRoute.Action != "repair_proof_tool_and_rerun_original_proof" {
133
+ t.Fatalf("DEC-031 proof route=%+v, found=%v", proofRoute, ok)
134
+ }
135
+ planRoute, ok := recoveryRouteFor(recoveryPlanGap)
136
+ if !ok || planRoute.HumanPause || planRoute.Action != "rite_plan_repair" {
137
+ t.Fatalf("DEC-031 provenance route=%+v, found=%v", planRoute, ok)
138
+ }
139
+ }
140
+
141
+ func TestRecoveryReplayDEC032SeparatesScannerAndEnvironment(t *testing.T) {
142
+ scannerCause := "CSS extractor mishandles same-line imports"
143
+ environmentCause := "browser target timeout ERR_INSUFFICIENT_RESOURCES"
144
+ if recoveryFingerprint(scannerCause) == recoveryFingerprint(environmentCause) {
145
+ t.Fatal("DEC-032 causal fingerprints must not share an attempt budget")
146
+ }
147
+ for _, class := range []recoveryClass{recoveryProofToolDefect, recoveryEnvironmentDefect} {
148
+ route, ok := recoveryRouteFor(class)
149
+ if !ok || route.HumanPause || route.Owner != "debug_recovery" {
150
+ t.Fatalf("DEC-032 route for %s=%+v, found=%v", class, route, ok)
151
+ }
152
+ }
153
+ }
154
+
155
+ func TestRecoveryAttemptsPersistsClassAndReadsLegacyJSONL(t *testing.T) {
156
+ root := t.TempDir()
157
+ slug := "recovery"
158
+ cause := "declared CSS import exits 3"
159
+ writeReadinessFile(t, root, slug, "state.md", "- Phase: build\n")
160
+ legacy := recoveryAttempt{
161
+ Fingerprint: recoveryFingerprint(cause),
162
+ RootCause: normalizeRecoveryCause(cause),
163
+ Attempt: 1,
164
+ Status: "failed",
165
+ Failure: "legacy scanner failure",
166
+ At: "2026-07-23T00:00:00Z",
167
+ }
168
+ legacyJSON, err := json.Marshal(legacy)
169
+ if err != nil {
170
+ t.Fatal(err)
171
+ }
172
+ path := filepath.Join(root, "work", slug, recoveryAttemptsFile)
173
+ writeReadinessFile(t, root, slug, recoveryAttemptsFile, string(legacyJSON)+"\n")
174
+
175
+ var stdout, stderr bytes.Buffer
176
+ args := []string{"record", "--class", "proof_tool_defect", cause, "scanner still exits 3", slug}
177
+ if code := RecoveryAttempts(root, args, &stdout, &stderr); code != 0 {
178
+ t.Fatalf("record code=%d stdout=%q stderr=%q", code, stdout.String(), stderr.String())
179
+ }
180
+ entries, err := readRecoveryAttempts(path)
181
+ if err != nil {
182
+ t.Fatal(err)
183
+ }
184
+ if len(entries) != 2 || entries[0].Class != "" || entries[1].Class != recoveryProofToolDefect {
185
+ t.Fatalf("entries=%+v", entries)
186
+ }
187
+ if entries[1].Attempt != 2 {
188
+ t.Fatalf("typed attempt=%d, want legacy budget continuation at 2", entries[1].Attempt)
189
+ }
190
+
191
+ stdout.Reset()
192
+ stderr.Reset()
193
+ clearArgs := []string{"clear", "--class", "proof_tool_defect", cause, slug}
194
+ if code := RecoveryAttempts(root, clearArgs, &stdout, &stderr); code != 0 {
195
+ t.Fatalf("clear code=%d stdout=%q stderr=%q", code, stdout.String(), stderr.String())
196
+ }
197
+ entries, err = readRecoveryAttempts(path)
198
+ if err != nil {
199
+ t.Fatal(err)
200
+ }
201
+ if len(entries) != 3 || entries[2].Class != recoveryProofToolDefect || entries[2].Status != "cleared" {
202
+ t.Fatalf("typed clear=%+v", entries)
203
+ }
204
+ }
205
+
206
+ func TestRecoveryAttemptsRejectsUnknownClass(t *testing.T) {
207
+ var stderr bytes.Buffer
208
+ args := []string{"record", "--class", "retry_authorization", "cause", "failure", "slug"}
209
+ if code := RecoveryAttempts(t.TempDir(), args, &bytes.Buffer{}, &stderr); code != 2 {
210
+ t.Fatalf("code=%d stderr=%q, want usage failure", code, stderr.String())
211
+ }
212
+ if !strings.Contains(stderr.String(), "unknown class") {
213
+ t.Fatalf("stderr=%q", stderr.String())
214
+ }
215
+ }
@@ -15,8 +15,8 @@ import (
15
15
  )
16
16
 
17
17
  // Resolve answers or drops an open question and keeps questions.md and state.md
18
- // consistent: it rewrites the question's block and, when state.md is awaiting that
19
- // question, clears the "Awaiting human" block and flips Status back to running.
18
+ // consistent by updating the question block. When state.md is waiting for that
19
+ // question, it clears the "Awaiting human" block and restores the running status.
20
20
  // The workspace is <root>/features/<slug>.
21
21
  //
22
22
  // 0 resolved 2 no active workspace 3 qid not found
@@ -67,6 +67,17 @@ func Resolve(root string, args []string, stdout, stderr io.Writer) int {
67
67
  }
68
68
  }
69
69
 
70
+ code := 0
71
+ if err := state.WithFeatureLock(root, slug, func() error {
72
+ code = resolveMutation(mode, qid, payload, qfile, sfile, stdout, stderr)
73
+ return nil
74
+ }); err != nil {
75
+ return fail(stderr, err.Error(), 1)
76
+ }
77
+ return code
78
+ }
79
+
80
+ func resolveMutation(mode, qid, payload, qfile, sfile string, stdout, stderr io.Writer) int {
70
81
  switch mode {
71
82
  case "answer":
72
83
  if code := resolveQuestion(qfile, qid, "answered", payload, stderr); code != 0 {
@@ -148,12 +159,9 @@ func splitColon(s string) (before, after string) {
148
159
  return before, after
149
160
  }
150
161
 
151
- // clockNow is the single wall-clock read for the resolve command. When
152
- // DEVRITES_NOW is set to an RFC-3339 timestamp or a bare YYYY-MM-DD date it pins
153
- // the clock, so date-derived output (the next question id) is deterministic
154
- // under test and stable across the day boundaries that otherwise rot golden
155
- // snapshots. One overridable seam instead of scattered time.Now() reads. See
156
- // ADR-0006.
162
+ // clockNow is the resolve command's single wall-clock read. DEVRITES_NOW accepts
163
+ // an RFC-3339 timestamp or YYYY-MM-DD date so tests can keep date-derived
164
+ // question IDs stable. See ADR-0006.
157
165
  func clockNow() time.Time {
158
166
  if s := os.Getenv("DEVRITES_NOW"); s != "" {
159
167
  for _, layout := range []string{time.RFC3339, "2006-01-02"} {
@@ -174,21 +182,13 @@ func resolveNextQID(qpath string, stdout, stderr io.Writer) int {
174
182
  if qpath == "" {
175
183
  return fail(stderr, "Usage: devrites-engine resolve next-qid <questions.md path>", 5)
176
184
  }
177
- today := clockNow().Format("2006-01-02")
178
- used := 0
179
185
  var content []byte
180
186
  if isFile(qpath) {
181
187
  content, _ = os.ReadFile(qpath)
182
- countRe := regexp.MustCompile(`(?m)^## q-` + regexp.QuoteMeta(today) + `-`)
183
- used = len(countRe.FindAllIndex(content, -1))
184
188
  }
185
- next := used + 1
186
- qid := fmt.Sprintf("q-%s-%03d", today, next)
187
- if content != nil {
188
- collideRe := regexp.MustCompile(`(?m)^## ` + regexp.QuoteMeta(qid) + `([[:space:]]|$)`)
189
- if collideRe.Match(content) {
190
- return fail(stderr, "qid already present: "+qid+" (questions.md edited out of sequence)", 6)
191
- }
189
+ qid, err := nextQuestionID(content, clockNow())
190
+ if err != nil {
191
+ return fail(stderr, "qid already present: "+qid+" (questions.md edited out of sequence)", 6)
192
192
  }
193
193
  fmt.Fprintln(stdout, qid)
194
194
  return 0
@@ -202,6 +202,10 @@ func resolveQuestion(qfile, qid, status, answer string, stderr io.Writer) int {
202
202
  if err != nil {
203
203
  return fail(stderr, "questions.md missing at "+qfile, 2)
204
204
  }
205
+ answer, err = normalizeGitAuthorityResolution(data, qid, status, answer)
206
+ if err != nil {
207
+ return fail(stderr, err.Error(), 5)
208
+ }
205
209
  ts := nowUTC()
206
210
  target := regexp.MustCompile(`^## ` + regexp.QuoteMeta(qid) + `([[:space:]]|$)`)
207
211
 
@@ -60,6 +60,26 @@ func TestContextSyncRejectsUnsafePath(t *testing.T) {
60
60
  }
61
61
  }
62
62
 
63
+ func TestContextSyncRefusesExternalWorkspaceOverride(t *testing.T) {
64
+ project := t.TempDir()
65
+ root := filepath.Join(project, ".devrites")
66
+ if err := os.MkdirAll(root, 0o755); err != nil {
67
+ t.Fatal(err)
68
+ }
69
+ target := filepath.Join(project, "AGENTS.md")
70
+ if err := os.WriteFile(target, []byte("unchanged\n"), 0o644); err != nil {
71
+ t.Fatal(err)
72
+ }
73
+ t.Setenv("DEVRITES_WORKSPACE", filepath.Join(t.TempDir(), "feature"))
74
+ stderr := &bytes.Buffer{}
75
+ if code := Context(root, []string{"sync", "AGENTS.md"}, &bytes.Buffer{}, stderr); code != 3 {
76
+ t.Fatalf("external workspace context sync = %d, want refusal\n%s", code, stderr)
77
+ }
78
+ if got := testutil.ReadFile(t, target); got != "unchanged\n" {
79
+ t.Fatalf("refused context sync mutated target:\n%s", got)
80
+ }
81
+ }
82
+
63
83
  func TestRunbookValidateAndGateResume(t *testing.T) {
64
84
  project := t.TempDir()
65
85
  root := filepath.Join(project, ".devrites")