devrites 3.2.28 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (897) hide show
  1. package/CHANGELOG.md +16 -144
  2. package/NOTICE.md +6 -5
  3. package/README.md +187 -95
  4. package/SECURITY.md +103 -79
  5. package/bin/devrites.mjs +140 -24
  6. package/docs/adr/0003-gate-model-hitl-pause.md +1 -1
  7. package/docs/adr/0013-native-codex-agent-orchestration.md +74 -0
  8. package/docs/adr/0014-native-host-hook-boundary.md +64 -0
  9. package/docs/adr/0015-read-only-root-native-orchestration.md +96 -0
  10. package/docs/adr/0016-codex-writer-fail-closed.md +53 -0
  11. package/docs/adr/0017-native-codex-writer-agent.md +63 -0
  12. package/docs/adr/0018-native-sandbox-instruction-writer-boundary.md +66 -0
  13. package/docs/adr/0019-native-boundary-with-deterministic-gates.md +48 -0
  14. package/docs/adr/0020-thin-engine-native-orchestration-boundary.md +54 -0
  15. package/docs/adr/0021-observable-workspace-compatibility.md +49 -0
  16. package/docs/adr/0022-native-orchestration-thin-engine.md +90 -0
  17. package/docs/adr/0023-native-workspace-reads-line-output.md +71 -0
  18. package/docs/adr/0024-native-policy-offline-installer-boundary.md +111 -0
  19. package/docs/adr/0025-evidence-gated-workspace-upgrades.md +64 -0
  20. package/docs/adr/0026-content-bound-proof-and-bounded-inputs.md +169 -0
  21. package/docs/adr/0027-content-bound-build-readiness.md +77 -0
  22. package/docs/adr/README.md +26 -11
  23. package/docs/agents/domain.md +2 -1
  24. package/docs/agents/issue-driven-rites.md +1 -1
  25. package/docs/architecture.md +191 -103
  26. package/docs/candidate-integrity.md +138 -0
  27. package/docs/capability-surface-selection.md +15 -5
  28. package/docs/cli.md +139 -92
  29. package/docs/command-map.md +88 -63
  30. package/docs/engine/commands.md +115 -507
  31. package/docs/engine/state-schema.md +94 -113
  32. package/docs/engine/workspace-schema.md +169 -229
  33. package/docs/flow.md +113 -68
  34. package/docs/harness-compliance.md +29 -56
  35. package/docs/markdown-instruction-upgrade-2026-08-02.md +302 -0
  36. package/docs/orchestration.md +136 -149
  37. package/docs/porting-to-a-new-harness.md +22 -7
  38. package/docs/quick-reference.md +21 -8
  39. package/docs/release.md +47 -4
  40. package/docs/skills.md +111 -72
  41. package/docs/templates/CRITIC_REVIEW_PACKET_TEMPLATE.md +2 -0
  42. package/docs/upstream-workflow-benchmark-2026-08-01.md +655 -0
  43. package/docs/usage.md +99 -65
  44. package/engine/Makefile +1 -2
  45. package/engine/cmd/releasepack/main.go +45 -25
  46. package/engine/cmd/releasepack/main_test.go +69 -0
  47. package/engine/commands.go +8 -258
  48. package/engine/harnessmatrix_test.go +28 -41
  49. package/engine/internal/devritespaths/paths.go +12 -28
  50. package/engine/internal/fsutil/copy.go +8 -0
  51. package/engine/internal/gate/gate.go +104 -105
  52. package/engine/internal/gate/gate_test.go +94 -84
  53. package/engine/internal/gate/readiness_binding.go +203 -0
  54. package/engine/internal/gate/readiness_binding_test.go +271 -0
  55. package/engine/internal/gitenv/env.go +46 -0
  56. package/engine/internal/gitenv/env_test.go +48 -0
  57. package/engine/internal/hostpack/hostpack.go +16 -20
  58. package/engine/internal/hostpack/hostpack_test.go +6 -6
  59. package/engine/internal/install/install.go +289 -511
  60. package/engine/internal/install/install_test.go +218 -306
  61. package/engine/internal/lib/candidate.go +475 -0
  62. package/engine/internal/lib/candidate_test.go +503 -0
  63. package/engine/internal/lib/closeout.go +17 -21
  64. package/engine/internal/lib/closeout_test.go +23 -0
  65. package/engine/internal/lib/cursor_compat_test.go +12 -75
  66. package/engine/internal/lib/evidencefresh.go +58 -47
  67. package/engine/internal/lib/evidencefresh_test.go +120 -0
  68. package/engine/internal/lib/resolve.go +13 -35
  69. package/engine/internal/lib/resolve_remediation_test.go +5 -17
  70. package/engine/internal/lib/secretscan.go +432 -0
  71. package/engine/internal/lib/secretscan_test.go +555 -0
  72. package/engine/internal/lib/util.go +24 -65
  73. package/engine/internal/lib/util_test.go +44 -0
  74. package/engine/internal/markdowntext/markdowntext.go +108 -0
  75. package/engine/internal/markdowntext/markdowntext_test.go +58 -0
  76. package/engine/internal/markdowntext/testdata/structural.json +27 -0
  77. package/engine/internal/reason/reason.go +2 -123
  78. package/engine/internal/reason/reason_test.go +4 -2
  79. package/engine/internal/rootfacts/facts.go +6 -8
  80. package/engine/internal/rootfacts/facts_test.go +20 -0
  81. package/engine/internal/state/cmd/workflowmanifest/main.go +10 -12
  82. package/engine/internal/state/cursor.go +53 -14
  83. package/engine/internal/state/cursor_test.go +57 -0
  84. package/engine/internal/state/feature.go +64 -154
  85. package/engine/internal/state/schema.go +24 -50
  86. package/engine/internal/state/state_test.go +151 -140
  87. package/engine/internal/state/status.go +4 -17
  88. package/engine/internal/state/workflow_manifest.json +9 -89
  89. package/engine/main.go +73 -502
  90. package/engine/root_routing_test.go +183 -212
  91. package/engine/testdata/fixtures/basic/devrites-root/{features/auth-tokens/feature.md → work/auth-tokens/README.md} +0 -1
  92. package/engine/testdata/fixtures/basic/devrites-root/work/auth-tokens/architecture.md +3 -0
  93. package/engine/testdata/fixtures/basic/devrites-root/work/auth-tokens/assumptions.md +3 -0
  94. package/engine/testdata/fixtures/basic/devrites-root/work/auth-tokens/brief.md +3 -0
  95. package/engine/testdata/fixtures/basic/devrites-root/work/auth-tokens/decision-coverage.md +3 -0
  96. package/engine/testdata/fixtures/basic/devrites-root/work/auth-tokens/eng-review.md +3 -0
  97. package/engine/testdata/fixtures/basic/devrites-root/work/auth-tokens/questions.md +3 -0
  98. package/engine/testdata/fixtures/basic/devrites-root/{features/auth-tokens/status.md → work/auth-tokens/state.md} +3 -0
  99. package/engine/testdata/fixtures/basic/devrites-root/work/auth-tokens/test-plan.md +3 -0
  100. package/engine/testdata/fixtures/basic/devrites-root/work/auth-tokens/traceability.md +3 -0
  101. package/engine/testdata/fixtures/basic/devrites-root/{features/search-ranking/feature.md → work/search-ranking/README.md} +0 -1
  102. package/engine/testdata/fixtures/basic/devrites-root/work/search-ranking/assumptions.md +3 -0
  103. package/engine/testdata/fixtures/basic/devrites-root/work/search-ranking/brief.md +3 -0
  104. package/engine/testdata/fixtures/basic/devrites-root/work/search-ranking/decisions.md +3 -0
  105. package/engine/testdata/fixtures/basic/devrites-root/work/search-ranking/questions.md +3 -0
  106. package/engine/testdata/fixtures/basic/devrites-root/work/search-ranking/state.md +2 -0
  107. package/engine/testdata/golden/TestParityCloseOut/active_pointed_elsewhere.golden +1 -1
  108. package/engine/testdata/golden/TestParityCloseOut/active_slug_cleared.golden +1 -1
  109. package/engine/tests/adr_0003_gate_exit_code_test.go +2 -2
  110. package/engine/tests/adr_0011_define_plan_test.go +2 -2
  111. package/engine/tests/adr_0027_readiness_binding_test.go +106 -0
  112. package/engine/tests/cli_test.go +24 -107
  113. package/engine/tests/gate_test.go +177 -200
  114. package/engine/tests/meta_test.go +5 -35
  115. package/engine/tests/parity_closeout_test.go +12 -12
  116. package/engine/tests/parity_resolve_test.go +6 -16
  117. package/engine/tests/parity_test.go +8 -302
  118. package/install.sh +201 -34
  119. package/pack/.claude/agents/devrites-code-reviewer.md +9 -18
  120. package/pack/.claude/agents/devrites-devex-reviewer.md +10 -18
  121. package/pack/.claude/agents/devrites-doubt-reviewer.md +10 -20
  122. package/pack/.claude/agents/devrites-evidence-scout.md +14 -26
  123. package/pack/.claude/agents/devrites-frontend-reviewer.md +9 -15
  124. package/pack/.claude/agents/devrites-performance-reviewer.md +13 -21
  125. package/pack/.claude/agents/devrites-plan-drafter.md +24 -28
  126. package/pack/.claude/agents/devrites-plan-reviewer.md +30 -47
  127. package/pack/.claude/agents/devrites-proof-runner.md +41 -47
  128. package/pack/.claude/agents/devrites-retrospector.md +34 -79
  129. package/pack/.claude/agents/devrites-security-auditor.md +9 -17
  130. package/pack/.claude/agents/devrites-simplifier-reviewer.md +9 -18
  131. package/pack/.claude/agents/devrites-slice-wright.md +44 -62
  132. package/pack/.claude/agents/devrites-spec-reviewer.md +38 -50
  133. package/pack/.claude/agents/devrites-strategy-reviewer.md +10 -19
  134. package/pack/.claude/agents/devrites-test-analyst.md +22 -21
  135. package/pack/.claude/agents/devrites-upgrade-planner.md +52 -71
  136. package/pack/.claude/settings.json +12 -100
  137. package/pack/.claude/skills/devrites-api-interface/SKILL.md +1 -2
  138. package/pack/.claude/skills/devrites-audit/SKILL.md +6 -9
  139. package/pack/.claude/skills/devrites-browser-proof/SKILL.md +0 -1
  140. package/pack/.claude/skills/devrites-debug-recovery/SKILL.md +21 -27
  141. package/pack/.claude/skills/devrites-debug-recovery/reference/cleanup-and-classify.md +22 -33
  142. package/pack/.claude/skills/devrites-doubt/SKILL.md +4 -6
  143. package/pack/.claude/skills/devrites-frontend-craft/SKILL.md +1 -2
  144. package/pack/.claude/skills/devrites-frontend-craft/reference/design-references.md +2 -1
  145. package/pack/.claude/skills/devrites-interview/SKILL.md +31 -36
  146. package/pack/.claude/skills/devrites-lib/SKILL.md +37 -66
  147. package/pack/.claude/skills/devrites-lib/reference/candidate-integrity.md +51 -0
  148. package/pack/.claude/skills/devrites-lib/reference/intent-map.md +20 -12
  149. package/pack/.claude/skills/devrites-lib/reference/orchestration-profiles.md +22 -0
  150. package/pack/.claude/skills/devrites-lib/reference/parallel-dispatch.md +35 -110
  151. package/pack/.claude/skills/devrites-lib/reference/reply-contract.md +26 -93
  152. package/pack/.claude/skills/devrites-lib/reference/standards/README.md +6 -3
  153. package/pack/.claude/skills/devrites-lib/reference/standards/afk-hitl.md +43 -34
  154. package/pack/.claude/skills/devrites-lib/reference/standards/agents.md +90 -216
  155. package/pack/.claude/skills/devrites-lib/reference/standards/anti-patterns.md +1 -1
  156. package/pack/.claude/skills/devrites-lib/reference/standards/code-review.md +9 -28
  157. package/pack/.claude/skills/devrites-lib/reference/standards/context-hygiene.md +13 -11
  158. package/pack/.claude/skills/devrites-lib/reference/standards/core.md +59 -54
  159. package/pack/.claude/skills/devrites-lib/reference/standards/hooks.md +3 -5
  160. package/pack/.claude/skills/devrites-lib/reference/standards/principles.md +29 -143
  161. package/pack/.claude/skills/devrites-lib/reference/standards/release/ship-checklist.md +5 -6
  162. package/pack/.claude/skills/devrites-lib/reference/standards/security.md +13 -31
  163. package/pack/.claude/skills/devrites-lib/reference/standards/skill-authoring.md +112 -82
  164. package/pack/.claude/skills/devrites-lib/reference/standards/spec-grammar.md +71 -45
  165. package/pack/.claude/skills/devrites-lib/reference/standards/testing.md +22 -11
  166. package/pack/.claude/skills/devrites-lib/reference/standards/tooling.md +15 -24
  167. package/pack/.claude/skills/devrites-lib/reference/workspace-artifact-schema.md +106 -37
  168. package/pack/.claude/skills/devrites-prose-craft/SKILL.md +3 -4
  169. package/pack/.claude/skills/devrites-source-driven/SKILL.md +3 -4
  170. package/pack/.claude/skills/devrites-ux-shape/SKILL.md +3 -3
  171. package/pack/.claude/skills/rite/SKILL.md +12 -13
  172. package/pack/.claude/skills/rite/reference/menu.md +11 -11
  173. package/pack/.claude/skills/rite-adopt/SKILL.md +29 -84
  174. package/pack/.claude/skills/rite-adopt/reference/adoption.md +16 -50
  175. package/pack/.claude/skills/rite-autocomplete/SKILL.md +65 -54
  176. package/pack/.claude/skills/rite-autocomplete/reference/loop.md +24 -17
  177. package/pack/.claude/skills/rite-autocomplete/reference/stop-conditions.md +18 -21
  178. package/pack/.claude/skills/rite-build/SKILL.md +28 -34
  179. package/pack/.claude/skills/rite-build/reference/afk-discipline.md +36 -34
  180. package/pack/.claude/skills/rite-build/reference/anti-patterns.md +5 -19
  181. package/pack/.claude/skills/rite-build/reference/checkpoint-protocol.md +10 -9
  182. package/pack/.claude/skills/rite-build/reference/checkpoint.md +17 -17
  183. package/pack/.claude/skills/rite-build/reference/frontend-trigger.md +3 -3
  184. package/pack/.claude/skills/rite-build/reference/one-slice-cycle.md +11 -14
  185. package/pack/.claude/skills/rite-build/reference/output.md +14 -24
  186. package/pack/.claude/skills/rite-build/reference/phase-contract.md +49 -188
  187. package/pack/.claude/skills/rite-build/reference/spec-drift-guard.md +1 -1
  188. package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +43 -133
  189. package/pack/.claude/skills/rite-clarify/SKILL.md +46 -40
  190. package/pack/.claude/skills/rite-clarify/reference/decision-coverage.md +4 -10
  191. package/pack/.claude/skills/rite-converge/SKILL.md +9 -33
  192. package/pack/.claude/skills/rite-converge/reference/convergence-assessment.md +2 -2
  193. package/pack/.claude/skills/rite-customize/SKILL.md +34 -50
  194. package/pack/.claude/skills/rite-define/SKILL.md +34 -58
  195. package/pack/.claude/skills/rite-define/reference/plan-template.md +15 -0
  196. package/pack/.claude/skills/rite-doctor/SKILL.md +52 -64
  197. package/pack/.claude/skills/rite-dogfood/SKILL.md +1 -2
  198. package/pack/.claude/skills/rite-explain/SKILL.md +1 -20
  199. package/pack/.claude/skills/rite-frame/SKILL.md +1 -7
  200. package/pack/.claude/skills/rite-frame/reference/failure-modes.md +1 -1
  201. package/pack/.claude/skills/rite-handoff/SKILL.md +1 -2
  202. package/pack/.claude/skills/rite-handoff/reference/handoff-template.md +1 -1
  203. package/pack/.claude/skills/rite-learn/SKILL.md +44 -104
  204. package/pack/.claude/skills/rite-plan/SKILL.md +14 -26
  205. package/pack/.claude/skills/rite-plan/reference/task-breakdown.md +4 -1
  206. package/pack/.claude/skills/rite-polish/SKILL.md +20 -28
  207. package/pack/.claude/skills/rite-polish/reference/adr-promotion.md +11 -0
  208. package/pack/{generated/claude/skills/rite-ship → .claude/skills/rite-polish}/reference/design-memory.md +23 -21
  209. package/pack/.claude/skills/rite-polish/reference/ledger.md +62 -0
  210. package/pack/.claude/skills/rite-pov/SKILL.md +28 -32
  211. package/pack/.claude/skills/rite-pr-feedback/SKILL.md +0 -1
  212. package/pack/.claude/skills/rite-pressure-test/SKILL.md +6 -13
  213. package/pack/.claude/skills/rite-prototype/SKILL.md +0 -6
  214. package/pack/.claude/skills/rite-prove/SKILL.md +73 -68
  215. package/pack/.claude/skills/rite-prove/reference/acceptance-proof.md +34 -30
  216. package/pack/.claude/skills/rite-prove/reference/test-command-discovery.md +15 -14
  217. package/pack/.claude/skills/rite-quick/SKILL.md +5 -19
  218. package/pack/.claude/skills/rite-resolve/SKILL.md +4 -21
  219. package/pack/.claude/skills/rite-resolve/reference/answer-protocol.md +2 -2
  220. package/pack/.claude/skills/rite-review/SKILL.md +34 -57
  221. package/pack/.claude/skills/rite-seal/SKILL.md +54 -87
  222. package/pack/.claude/skills/rite-seal/reference/anti-patterns.md +2 -2
  223. package/pack/.claude/skills/rite-seal/reference/final-evidence.md +31 -56
  224. package/pack/.claude/skills/rite-seal/reference/go-no-go.md +27 -51
  225. package/pack/.claude/skills/rite-seal/reference/output.md +2 -4
  226. package/pack/.claude/skills/rite-seal/reference/phase-contract.md +42 -140
  227. package/pack/.claude/skills/rite-seal/reference/risk-and-rollback.md +2 -2
  228. package/pack/.claude/skills/rite-seal/reference/seal-template.md +17 -28
  229. package/pack/.claude/skills/rite-ship/SKILL.md +65 -93
  230. package/pack/.claude/skills/rite-ship/reference/anti-patterns.md +4 -1
  231. package/pack/.claude/skills/rite-ship/reference/close-out.md +4 -12
  232. package/pack/.claude/skills/rite-ship/reference/git-ship.md +86 -48
  233. package/pack/.claude/skills/rite-ship/reference/ship-template.md +22 -16
  234. package/pack/.claude/skills/rite-spec/SKILL.md +62 -95
  235. package/pack/.claude/skills/rite-spec/reference/investigation.md +1 -4
  236. package/pack/.claude/skills/rite-spec/reference/question-protocol.md +21 -20
  237. package/pack/.claude/skills/rite-spec/reference/spec-checklists.md +16 -17
  238. package/pack/.claude/skills/rite-spec/reference/spec-template.md +47 -49
  239. package/pack/.claude/skills/rite-spec/reference/state-workspace.md +28 -13
  240. package/pack/.claude/skills/rite-status/SKILL.md +30 -88
  241. package/pack/.claude/skills/rite-temper/SKILL.md +12 -31
  242. package/pack/.claude/skills/rite-upgrade/SKILL.md +86 -101
  243. package/pack/.claude/skills/rite-vet/SKILL.md +88 -117
  244. package/pack/.claude/skills/rite-vet/reference/artifacts.md +56 -50
  245. package/pack/.claude/skills/rite-vet/reference/cross-model.md +15 -39
  246. package/pack/.claude/skills/rite-vet/reference/depth.md +2 -2
  247. package/pack/.claude/skills/rite-vet/reference/review-axes.md +3 -6
  248. package/pack/.claude/skills/rite-zoom-out/SKILL.md +0 -6
  249. package/pack/generated/README.md +2 -2
  250. package/pack/generated/claude/agents/devrites-code-reviewer.md +9 -18
  251. package/pack/generated/claude/agents/devrites-devex-reviewer.md +10 -18
  252. package/pack/generated/claude/agents/devrites-doubt-reviewer.md +10 -20
  253. package/pack/generated/claude/agents/devrites-evidence-scout.md +14 -26
  254. package/pack/generated/claude/agents/devrites-frontend-reviewer.md +9 -15
  255. package/pack/generated/claude/agents/devrites-performance-reviewer.md +13 -21
  256. package/pack/generated/claude/agents/devrites-plan-drafter.md +24 -28
  257. package/pack/generated/claude/agents/devrites-plan-reviewer.md +30 -47
  258. package/pack/generated/claude/agents/devrites-proof-runner.md +41 -47
  259. package/pack/generated/claude/agents/devrites-retrospector.md +34 -79
  260. package/pack/generated/claude/agents/devrites-security-auditor.md +9 -17
  261. package/pack/generated/claude/agents/devrites-simplifier-reviewer.md +9 -18
  262. package/pack/generated/claude/agents/devrites-slice-wright.md +44 -62
  263. package/pack/generated/claude/agents/devrites-spec-reviewer.md +38 -50
  264. package/pack/generated/claude/agents/devrites-strategy-reviewer.md +10 -19
  265. package/pack/generated/claude/agents/devrites-test-analyst.md +22 -21
  266. package/pack/generated/claude/agents/devrites-upgrade-planner.md +52 -71
  267. package/pack/generated/claude/settings.json +12 -100
  268. package/pack/generated/claude/skills/devrites-api-interface/SKILL.md +1 -2
  269. package/pack/generated/claude/skills/devrites-audit/SKILL.md +6 -9
  270. package/pack/generated/claude/skills/devrites-browser-proof/SKILL.md +0 -1
  271. package/pack/generated/claude/skills/devrites-debug-recovery/SKILL.md +21 -27
  272. package/pack/generated/claude/skills/devrites-debug-recovery/reference/cleanup-and-classify.md +22 -33
  273. package/pack/generated/claude/skills/devrites-doubt/SKILL.md +4 -6
  274. package/pack/generated/claude/skills/devrites-frontend-craft/SKILL.md +1 -2
  275. package/pack/generated/claude/skills/devrites-frontend-craft/reference/design-references.md +2 -1
  276. package/pack/generated/claude/skills/devrites-interview/SKILL.md +31 -36
  277. package/pack/generated/claude/skills/devrites-lib/SKILL.md +37 -66
  278. package/pack/generated/claude/skills/devrites-lib/reference/candidate-integrity.md +51 -0
  279. package/pack/generated/claude/skills/devrites-lib/reference/intent-map.md +20 -12
  280. package/pack/generated/claude/skills/devrites-lib/reference/orchestration-profiles.md +22 -0
  281. package/pack/generated/claude/skills/devrites-lib/reference/parallel-dispatch.md +35 -110
  282. package/pack/generated/claude/skills/devrites-lib/reference/reply-contract.md +26 -93
  283. package/pack/generated/claude/skills/devrites-lib/reference/standards/README.md +6 -3
  284. package/pack/generated/claude/skills/devrites-lib/reference/standards/afk-hitl.md +43 -34
  285. package/pack/generated/claude/skills/devrites-lib/reference/standards/agents.md +90 -216
  286. package/pack/generated/claude/skills/devrites-lib/reference/standards/anti-patterns.md +1 -1
  287. package/pack/generated/claude/skills/devrites-lib/reference/standards/code-review.md +9 -28
  288. package/pack/generated/claude/skills/devrites-lib/reference/standards/context-hygiene.md +13 -11
  289. package/pack/generated/claude/skills/devrites-lib/reference/standards/core.md +59 -54
  290. package/pack/generated/claude/skills/devrites-lib/reference/standards/hooks.md +3 -5
  291. package/pack/generated/claude/skills/devrites-lib/reference/standards/principles.md +29 -143
  292. package/pack/generated/claude/skills/devrites-lib/reference/standards/release/ship-checklist.md +5 -6
  293. package/pack/generated/claude/skills/devrites-lib/reference/standards/security.md +13 -31
  294. package/pack/generated/claude/skills/devrites-lib/reference/standards/skill-authoring.md +112 -82
  295. package/pack/generated/claude/skills/devrites-lib/reference/standards/spec-grammar.md +71 -45
  296. package/pack/generated/claude/skills/devrites-lib/reference/standards/testing.md +22 -11
  297. package/pack/generated/claude/skills/devrites-lib/reference/standards/tooling.md +15 -24
  298. package/pack/generated/claude/skills/devrites-lib/reference/workspace-artifact-schema.md +106 -37
  299. package/pack/generated/claude/skills/devrites-prose-craft/SKILL.md +3 -4
  300. package/pack/generated/claude/skills/devrites-source-driven/SKILL.md +3 -4
  301. package/pack/generated/claude/skills/devrites-ux-shape/SKILL.md +3 -3
  302. package/pack/generated/claude/skills/rite/SKILL.md +12 -13
  303. package/pack/generated/claude/skills/rite/reference/menu.md +11 -11
  304. package/pack/generated/claude/skills/rite-adopt/SKILL.md +29 -84
  305. package/pack/generated/claude/skills/rite-adopt/reference/adoption.md +16 -50
  306. package/pack/generated/claude/skills/rite-autocomplete/SKILL.md +65 -54
  307. package/pack/generated/claude/skills/rite-autocomplete/reference/loop.md +24 -17
  308. package/pack/generated/claude/skills/rite-autocomplete/reference/stop-conditions.md +18 -21
  309. package/pack/generated/claude/skills/rite-build/SKILL.md +28 -34
  310. package/pack/generated/claude/skills/rite-build/reference/afk-discipline.md +36 -34
  311. package/pack/generated/claude/skills/rite-build/reference/anti-patterns.md +5 -19
  312. package/pack/generated/claude/skills/rite-build/reference/checkpoint-protocol.md +10 -9
  313. package/pack/generated/claude/skills/rite-build/reference/checkpoint.md +17 -17
  314. package/pack/generated/claude/skills/rite-build/reference/frontend-trigger.md +3 -3
  315. package/pack/generated/claude/skills/rite-build/reference/one-slice-cycle.md +11 -14
  316. package/pack/generated/claude/skills/rite-build/reference/output.md +14 -24
  317. package/pack/generated/claude/skills/rite-build/reference/phase-contract.md +49 -188
  318. package/pack/generated/claude/skills/rite-build/reference/spec-drift-guard.md +1 -1
  319. package/pack/generated/claude/skills/rite-build/reference/wright-dispatch.md +43 -133
  320. package/pack/generated/claude/skills/rite-clarify/SKILL.md +46 -40
  321. package/pack/generated/claude/skills/rite-clarify/reference/decision-coverage.md +4 -10
  322. package/pack/generated/claude/skills/rite-converge/SKILL.md +9 -33
  323. package/pack/generated/claude/skills/rite-converge/reference/convergence-assessment.md +2 -2
  324. package/pack/generated/claude/skills/rite-customize/SKILL.md +34 -50
  325. package/pack/generated/claude/skills/rite-define/SKILL.md +34 -58
  326. package/pack/generated/claude/skills/rite-define/reference/plan-template.md +15 -0
  327. package/pack/generated/claude/skills/rite-doctor/SKILL.md +52 -64
  328. package/pack/generated/claude/skills/rite-dogfood/SKILL.md +1 -2
  329. package/pack/generated/claude/skills/rite-explain/SKILL.md +1 -20
  330. package/pack/generated/claude/skills/rite-frame/SKILL.md +1 -7
  331. package/pack/generated/claude/skills/rite-frame/reference/failure-modes.md +1 -1
  332. package/pack/generated/claude/skills/rite-handoff/SKILL.md +1 -2
  333. package/pack/generated/claude/skills/rite-handoff/reference/handoff-template.md +1 -1
  334. package/pack/generated/claude/skills/rite-learn/SKILL.md +44 -104
  335. package/pack/generated/claude/skills/rite-plan/SKILL.md +14 -26
  336. package/pack/generated/claude/skills/rite-plan/reference/task-breakdown.md +4 -1
  337. package/pack/generated/claude/skills/rite-polish/SKILL.md +20 -28
  338. package/pack/generated/claude/skills/rite-polish/reference/adr-promotion.md +11 -0
  339. package/pack/generated/{codex/skills/rite-ship → claude/skills/rite-polish}/reference/design-memory.md +23 -21
  340. package/pack/generated/claude/skills/rite-polish/reference/ledger.md +62 -0
  341. package/pack/generated/claude/skills/rite-pov/SKILL.md +28 -32
  342. package/pack/generated/claude/skills/rite-pr-feedback/SKILL.md +0 -1
  343. package/pack/generated/claude/skills/rite-pressure-test/SKILL.md +6 -13
  344. package/pack/generated/claude/skills/rite-prototype/SKILL.md +0 -6
  345. package/pack/generated/claude/skills/rite-prove/SKILL.md +73 -68
  346. package/pack/generated/claude/skills/rite-prove/reference/acceptance-proof.md +34 -30
  347. package/pack/generated/claude/skills/rite-prove/reference/test-command-discovery.md +15 -14
  348. package/pack/generated/claude/skills/rite-quick/SKILL.md +5 -19
  349. package/pack/generated/claude/skills/rite-resolve/SKILL.md +4 -21
  350. package/pack/generated/claude/skills/rite-resolve/reference/answer-protocol.md +2 -2
  351. package/pack/generated/claude/skills/rite-review/SKILL.md +34 -57
  352. package/pack/generated/claude/skills/rite-seal/SKILL.md +54 -87
  353. package/pack/generated/claude/skills/rite-seal/reference/anti-patterns.md +2 -2
  354. package/pack/generated/claude/skills/rite-seal/reference/final-evidence.md +31 -56
  355. package/pack/generated/claude/skills/rite-seal/reference/go-no-go.md +27 -51
  356. package/pack/generated/claude/skills/rite-seal/reference/output.md +2 -4
  357. package/pack/generated/claude/skills/rite-seal/reference/phase-contract.md +42 -140
  358. package/pack/generated/claude/skills/rite-seal/reference/risk-and-rollback.md +2 -2
  359. package/pack/generated/claude/skills/rite-seal/reference/seal-template.md +17 -28
  360. package/pack/generated/claude/skills/rite-ship/SKILL.md +65 -93
  361. package/pack/generated/claude/skills/rite-ship/reference/anti-patterns.md +4 -1
  362. package/pack/generated/claude/skills/rite-ship/reference/close-out.md +4 -12
  363. package/pack/generated/claude/skills/rite-ship/reference/git-ship.md +86 -48
  364. package/pack/generated/claude/skills/rite-ship/reference/ship-template.md +22 -16
  365. package/pack/generated/claude/skills/rite-spec/SKILL.md +62 -95
  366. package/pack/generated/claude/skills/rite-spec/reference/investigation.md +1 -4
  367. package/pack/generated/claude/skills/rite-spec/reference/question-protocol.md +21 -20
  368. package/pack/generated/claude/skills/rite-spec/reference/spec-checklists.md +16 -17
  369. package/pack/generated/claude/skills/rite-spec/reference/spec-template.md +47 -49
  370. package/pack/generated/claude/skills/rite-spec/reference/state-workspace.md +28 -13
  371. package/pack/generated/claude/skills/rite-status/SKILL.md +30 -88
  372. package/pack/generated/claude/skills/rite-temper/SKILL.md +12 -31
  373. package/pack/generated/claude/skills/rite-upgrade/SKILL.md +86 -101
  374. package/pack/generated/claude/skills/rite-vet/SKILL.md +88 -117
  375. package/pack/generated/claude/skills/rite-vet/reference/artifacts.md +56 -50
  376. package/pack/generated/claude/skills/rite-vet/reference/cross-model.md +15 -39
  377. package/pack/generated/claude/skills/rite-vet/reference/depth.md +2 -2
  378. package/pack/generated/claude/skills/rite-vet/reference/review-axes.md +3 -6
  379. package/pack/generated/claude/skills/rite-zoom-out/SKILL.md +0 -6
  380. package/pack/generated/codex/AGENTS.md +5 -4
  381. package/pack/generated/codex/agents/devrites-code-reviewer.toml +8 -27
  382. package/pack/generated/codex/agents/devrites-devex-reviewer.toml +9 -27
  383. package/pack/generated/codex/agents/devrites-doubt-reviewer.toml +9 -29
  384. package/pack/generated/codex/agents/devrites-evidence-scout.toml +14 -36
  385. package/pack/generated/codex/agents/devrites-frontend-reviewer.toml +8 -24
  386. package/pack/generated/codex/agents/devrites-performance-reviewer.toml +12 -30
  387. package/pack/generated/codex/agents/devrites-plan-drafter.toml +24 -38
  388. package/pack/generated/codex/agents/devrites-plan-reviewer.toml +30 -57
  389. package/pack/generated/codex/agents/devrites-proof-runner.toml +41 -57
  390. package/pack/generated/codex/agents/devrites-retrospector.toml +34 -89
  391. package/pack/generated/codex/agents/devrites-security-auditor.toml +8 -26
  392. package/pack/generated/codex/agents/devrites-simplifier-reviewer.toml +8 -27
  393. package/pack/generated/codex/agents/devrites-slice-wright.toml +44 -71
  394. package/pack/generated/codex/agents/devrites-spec-reviewer.toml +38 -60
  395. package/pack/generated/codex/agents/devrites-strategy-reviewer.toml +9 -28
  396. package/pack/generated/codex/agents/devrites-test-analyst.toml +21 -30
  397. package/pack/generated/codex/agents/devrites-upgrade-planner.toml +52 -81
  398. package/pack/generated/codex/config.toml +7 -0
  399. package/pack/generated/codex/skills/devrites-api-interface/SKILL.md +1 -21
  400. package/pack/generated/codex/skills/devrites-audit/SKILL.md +6 -28
  401. package/pack/generated/codex/skills/devrites-browser-proof/SKILL.md +1 -21
  402. package/pack/generated/codex/skills/devrites-debug-recovery/SKILL.md +21 -46
  403. package/pack/generated/codex/skills/devrites-debug-recovery/reference/cleanup-and-classify.md +22 -33
  404. package/pack/generated/codex/skills/devrites-doubt/SKILL.md +4 -25
  405. package/pack/generated/codex/skills/devrites-frontend-craft/SKILL.md +1 -21
  406. package/pack/generated/codex/skills/devrites-frontend-craft/reference/design-references.md +2 -1
  407. package/pack/generated/codex/skills/devrites-interview/SKILL.md +32 -56
  408. package/pack/generated/codex/skills/devrites-lib/SKILL.md +38 -86
  409. package/pack/generated/codex/skills/devrites-lib/reference/candidate-integrity.md +51 -0
  410. package/pack/generated/codex/skills/devrites-lib/reference/intent-map.md +20 -12
  411. package/pack/generated/codex/skills/devrites-lib/reference/orchestration-profiles.md +22 -0
  412. package/pack/generated/codex/skills/devrites-lib/reference/parallel-dispatch.md +35 -110
  413. package/pack/generated/codex/skills/devrites-lib/reference/reply-contract.md +26 -93
  414. package/pack/generated/codex/skills/devrites-lib/reference/standards/README.md +6 -3
  415. package/pack/generated/codex/skills/devrites-lib/reference/standards/afk-hitl.md +43 -34
  416. package/pack/generated/codex/skills/devrites-lib/reference/standards/agents.md +90 -216
  417. package/pack/generated/codex/skills/devrites-lib/reference/standards/anti-patterns.md +1 -1
  418. package/pack/generated/codex/skills/devrites-lib/reference/standards/code-review.md +9 -28
  419. package/pack/generated/codex/skills/devrites-lib/reference/standards/context-hygiene.md +13 -11
  420. package/pack/generated/codex/skills/devrites-lib/reference/standards/core.md +59 -54
  421. package/pack/generated/codex/skills/devrites-lib/reference/standards/hooks.md +3 -5
  422. package/pack/generated/codex/skills/devrites-lib/reference/standards/principles.md +29 -143
  423. package/pack/generated/codex/skills/devrites-lib/reference/standards/release/ship-checklist.md +5 -6
  424. package/pack/generated/codex/skills/devrites-lib/reference/standards/security.md +13 -31
  425. package/pack/generated/codex/skills/devrites-lib/reference/standards/skill-authoring.md +112 -82
  426. package/pack/generated/codex/skills/devrites-lib/reference/standards/spec-grammar.md +71 -45
  427. package/pack/generated/codex/skills/devrites-lib/reference/standards/testing.md +22 -11
  428. package/pack/generated/codex/skills/devrites-lib/reference/standards/tooling.md +15 -24
  429. package/pack/generated/codex/skills/devrites-lib/reference/workspace-artifact-schema.md +106 -37
  430. package/pack/generated/codex/skills/devrites-prose-craft/SKILL.md +3 -23
  431. package/pack/generated/codex/skills/devrites-source-driven/SKILL.md +3 -23
  432. package/pack/generated/codex/skills/devrites-ux-shape/SKILL.md +3 -22
  433. package/pack/generated/codex/skills/rite/SKILL.md +12 -32
  434. package/pack/generated/codex/skills/rite/reference/menu.md +11 -11
  435. package/pack/generated/codex/skills/rite-adopt/SKILL.md +29 -103
  436. package/pack/generated/codex/skills/rite-adopt/reference/adoption.md +16 -50
  437. package/pack/generated/codex/skills/rite-autocomplete/SKILL.md +65 -73
  438. package/pack/generated/codex/skills/rite-autocomplete/reference/loop.md +24 -17
  439. package/pack/generated/codex/skills/rite-autocomplete/reference/stop-conditions.md +18 -21
  440. package/pack/generated/codex/skills/rite-build/SKILL.md +28 -53
  441. package/pack/generated/codex/skills/rite-build/reference/afk-discipline.md +36 -34
  442. package/pack/generated/codex/skills/rite-build/reference/anti-patterns.md +5 -19
  443. package/pack/generated/codex/skills/rite-build/reference/checkpoint-protocol.md +10 -9
  444. package/pack/generated/codex/skills/rite-build/reference/checkpoint.md +17 -17
  445. package/pack/generated/codex/skills/rite-build/reference/frontend-trigger.md +3 -3
  446. package/pack/generated/codex/skills/rite-build/reference/one-slice-cycle.md +11 -14
  447. package/pack/generated/codex/skills/rite-build/reference/output.md +14 -24
  448. package/pack/generated/codex/skills/rite-build/reference/phase-contract.md +49 -188
  449. package/pack/generated/codex/skills/rite-build/reference/spec-drift-guard.md +1 -1
  450. package/pack/generated/codex/skills/rite-build/reference/wright-dispatch.md +43 -133
  451. package/pack/generated/codex/skills/rite-clarify/SKILL.md +46 -59
  452. package/pack/generated/codex/skills/rite-clarify/reference/decision-coverage.md +4 -10
  453. package/pack/generated/codex/skills/rite-converge/SKILL.md +9 -52
  454. package/pack/generated/codex/skills/rite-converge/reference/convergence-assessment.md +2 -2
  455. package/pack/generated/codex/skills/rite-customize/SKILL.md +32 -67
  456. package/pack/generated/codex/skills/rite-define/SKILL.md +34 -77
  457. package/pack/generated/codex/skills/rite-define/reference/plan-template.md +15 -0
  458. package/pack/generated/codex/skills/rite-doctor/SKILL.md +52 -83
  459. package/pack/generated/codex/skills/rite-dogfood/SKILL.md +1 -21
  460. package/pack/generated/codex/skills/rite-explain/SKILL.md +1 -39
  461. package/pack/generated/codex/skills/rite-frame/SKILL.md +1 -26
  462. package/pack/generated/codex/skills/rite-frame/reference/failure-modes.md +1 -1
  463. package/pack/generated/codex/skills/rite-handoff/SKILL.md +1 -21
  464. package/pack/generated/codex/skills/rite-handoff/reference/handoff-template.md +1 -1
  465. package/pack/generated/codex/skills/rite-learn/SKILL.md +44 -123
  466. package/pack/generated/codex/skills/rite-plan/SKILL.md +14 -45
  467. package/pack/generated/codex/skills/rite-plan/reference/task-breakdown.md +4 -1
  468. package/pack/generated/codex/skills/rite-polish/SKILL.md +20 -47
  469. package/pack/generated/codex/skills/rite-polish/reference/adr-promotion.md +11 -0
  470. package/pack/{.claude/skills/rite-ship → generated/codex/skills/rite-polish}/reference/design-memory.md +23 -21
  471. package/pack/generated/codex/skills/rite-polish/reference/ledger.md +62 -0
  472. package/pack/generated/codex/skills/rite-pov/SKILL.md +28 -51
  473. package/pack/generated/codex/skills/rite-pr-feedback/SKILL.md +0 -20
  474. package/pack/generated/codex/skills/rite-pressure-test/SKILL.md +6 -32
  475. package/pack/generated/codex/skills/rite-prototype/SKILL.md +0 -25
  476. package/pack/generated/codex/skills/rite-prove/SKILL.md +73 -87
  477. package/pack/generated/codex/skills/rite-prove/reference/acceptance-proof.md +34 -30
  478. package/pack/generated/codex/skills/rite-prove/reference/test-command-discovery.md +15 -14
  479. package/pack/generated/codex/skills/rite-quick/SKILL.md +5 -38
  480. package/pack/generated/codex/skills/rite-resolve/SKILL.md +4 -40
  481. package/pack/generated/codex/skills/rite-resolve/reference/answer-protocol.md +2 -2
  482. package/pack/generated/codex/skills/rite-review/SKILL.md +34 -76
  483. package/pack/generated/codex/skills/rite-seal/SKILL.md +51 -103
  484. package/pack/generated/codex/skills/rite-seal/reference/anti-patterns.md +2 -2
  485. package/pack/generated/codex/skills/rite-seal/reference/final-evidence.md +31 -56
  486. package/pack/generated/codex/skills/rite-seal/reference/go-no-go.md +27 -51
  487. package/pack/generated/codex/skills/rite-seal/reference/output.md +2 -4
  488. package/pack/generated/codex/skills/rite-seal/reference/phase-contract.md +42 -140
  489. package/pack/generated/codex/skills/rite-seal/reference/risk-and-rollback.md +2 -2
  490. package/pack/generated/codex/skills/rite-seal/reference/seal-template.md +17 -28
  491. package/pack/generated/codex/skills/rite-ship/SKILL.md +65 -112
  492. package/pack/generated/codex/skills/rite-ship/reference/anti-patterns.md +4 -1
  493. package/pack/generated/codex/skills/rite-ship/reference/close-out.md +4 -12
  494. package/pack/generated/codex/skills/rite-ship/reference/git-ship.md +86 -48
  495. package/pack/generated/codex/skills/rite-ship/reference/ship-template.md +22 -16
  496. package/pack/generated/codex/skills/rite-spec/SKILL.md +62 -114
  497. package/pack/generated/codex/skills/rite-spec/reference/investigation.md +1 -4
  498. package/pack/generated/codex/skills/rite-spec/reference/question-protocol.md +21 -20
  499. package/pack/generated/codex/skills/rite-spec/reference/spec-checklists.md +16 -17
  500. package/pack/generated/codex/skills/rite-spec/reference/spec-template.md +47 -49
  501. package/pack/generated/codex/skills/rite-spec/reference/state-workspace.md +28 -13
  502. package/pack/generated/codex/skills/rite-status/SKILL.md +30 -107
  503. package/pack/generated/codex/skills/rite-temper/SKILL.md +12 -50
  504. package/pack/generated/codex/skills/rite-upgrade/SKILL.md +86 -120
  505. package/pack/generated/codex/skills/rite-vet/SKILL.md +88 -136
  506. package/pack/generated/codex/skills/rite-vet/reference/artifacts.md +56 -50
  507. package/pack/generated/codex/skills/rite-vet/reference/cross-model.md +15 -39
  508. package/pack/generated/codex/skills/rite-vet/reference/depth.md +2 -2
  509. package/pack/generated/codex/skills/rite-vet/reference/review-axes.md +3 -6
  510. package/pack/generated/codex/skills/rite-zoom-out/SKILL.md +0 -25
  511. package/package.json +6 -5
  512. package/scripts/build-host-artifacts.sh +5 -9
  513. package/scripts/codex-generate.sh +18 -278
  514. package/scripts/install-lib.sh +121 -34
  515. package/uninstall.sh +19 -17
  516. package/update.sh +26 -18
  517. package/docs/engine/agent-contract.md +0 -172
  518. package/docs/extensions.md +0 -178
  519. package/engine/git_guard.go +0 -187
  520. package/engine/git_guard_test.go +0 -283
  521. package/engine/harnessmatrix.go +0 -60
  522. package/engine/hookpolicy.go +0 -113
  523. package/engine/hookpolicy_test.go +0 -160
  524. package/engine/hooks.go +0 -677
  525. package/engine/hooks_agent_dispatch.go +0 -1726
  526. package/engine/hooks_events_test.go +0 -431
  527. package/engine/hooks_workspace.go +0 -1196
  528. package/engine/internal/doctor/doctor.go +0 -306
  529. package/engine/internal/doctor/doctor_test.go +0 -252
  530. package/engine/internal/forge/forge.go +0 -938
  531. package/engine/internal/forge/forge_test.go +0 -576
  532. package/engine/internal/forge/git.go +0 -245
  533. package/engine/internal/forge/liveness_unix.go +0 -48
  534. package/engine/internal/forge/liveness_windows.go +0 -67
  535. package/engine/internal/forge/manifest.go +0 -402
  536. package/engine/internal/harness/compliance.go +0 -96
  537. package/engine/internal/harness/harness.go +0 -301
  538. package/engine/internal/harness/harness_test.go +0 -124
  539. package/engine/internal/iohooks/detach_other.go +0 -11
  540. package/engine/internal/iohooks/detach_unix.go +0 -11
  541. package/engine/internal/iohooks/iohooks.go +0 -839
  542. package/engine/internal/iohooks/iohooks_test.go +0 -561
  543. package/engine/internal/lib/adoption.go +0 -659
  544. package/engine/internal/lib/adoption_test.go +0 -121
  545. package/engine/internal/lib/analyze.go +0 -232
  546. package/engine/internal/lib/archivesearch.go +0 -125
  547. package/engine/internal/lib/archivesearch_test.go +0 -138
  548. package/engine/internal/lib/budget.go +0 -172
  549. package/engine/internal/lib/buildreadiness.go +0 -111
  550. package/engine/internal/lib/checkacceptance.go +0 -176
  551. package/engine/internal/lib/clarifyreturn.go +0 -91
  552. package/engine/internal/lib/clarifyreturn_test.go +0 -123
  553. package/engine/internal/lib/command_other.go +0 -7
  554. package/engine/internal/lib/command_unix.go +0 -24
  555. package/engine/internal/lib/context.go +0 -280
  556. package/engine/internal/lib/conventions.go +0 -449
  557. package/engine/internal/lib/coverage.go +0 -145
  558. package/engine/internal/lib/dispatchwaive.go +0 -31
  559. package/engine/internal/lib/dispatchwaive_test.go +0 -23
  560. package/engine/internal/lib/doubtcoverage.go +0 -78
  561. package/engine/internal/lib/extensions.go +0 -622
  562. package/engine/internal/lib/extensions_test.go +0 -305
  563. package/engine/internal/lib/footprint.go +0 -239
  564. package/engine/internal/lib/gitauthority.go +0 -601
  565. package/engine/internal/lib/gitauthority_test.go +0 -346
  566. package/engine/internal/lib/jsonout.go +0 -129
  567. package/engine/internal/lib/jsonout_test.go +0 -73
  568. package/engine/internal/lib/lanes.go +0 -112
  569. package/engine/internal/lib/learnings.go +0 -397
  570. package/engine/internal/lib/learnings_inject_test.go +0 -77
  571. package/engine/internal/lib/learnings_remediation_test.go +0 -24
  572. package/engine/internal/lib/ledger.go +0 -311
  573. package/engine/internal/lib/ledger_test.go +0 -186
  574. package/engine/internal/lib/mutationgate.go +0 -89
  575. package/engine/internal/lib/observability_test.go +0 -358
  576. package/engine/internal/lib/overrides.go +0 -164
  577. package/engine/internal/lib/overrides_test.go +0 -112
  578. package/engine/internal/lib/preamble.go +0 -159
  579. package/engine/internal/lib/preamble_questions_test.go +0 -10
  580. package/engine/internal/lib/progress.go +0 -210
  581. package/engine/internal/lib/provenance.go +0 -462
  582. package/engine/internal/lib/provenance_test.go +0 -154
  583. package/engine/internal/lib/readiness_contract.json +0 -118
  584. package/engine/internal/lib/readinessartifact.go +0 -533
  585. package/engine/internal/lib/readinessartifact_test.go +0 -422
  586. package/engine/internal/lib/reconcile.go +0 -1496
  587. package/engine/internal/lib/reconcile_test.go +0 -1071
  588. package/engine/internal/lib/recoveryattempts.go +0 -298
  589. package/engine/internal/lib/recoveryattempts_test.go +0 -215
  590. package/engine/internal/lib/reviewers.go +0 -143
  591. package/engine/internal/lib/reviewers_test.go +0 -45
  592. package/engine/internal/lib/reviewerstats.go +0 -199
  593. package/engine/internal/lib/reviewerstats_test.go +0 -104
  594. package/engine/internal/lib/reviewintegrity.go +0 -143
  595. package/engine/internal/lib/reviewintegrity_test.go +0 -103
  596. package/engine/internal/lib/runbook.go +0 -295
  597. package/engine/internal/lib/runbook_context_test.go +0 -182
  598. package/engine/internal/lib/session.go +0 -1020
  599. package/engine/internal/lib/session_test.go +0 -177
  600. package/engine/internal/lib/specparse.go +0 -125
  601. package/engine/internal/lib/specskeleton.go +0 -79
  602. package/engine/internal/lib/specvalidate.go +0 -391
  603. package/engine/internal/lib/specvalidate_test.go +0 -185
  604. package/engine/internal/lib/stuck.go +0 -206
  605. package/engine/internal/lib/templates/conventions_drift.tmpl +0 -6
  606. package/engine/internal/lib/templates/conventions_ledger_header.md +0 -6
  607. package/engine/internal/lib/templates/conventions_orient_header.txt +0 -1
  608. package/engine/internal/lib/testintegrity.go +0 -199
  609. package/engine/internal/lib/testintegrity_test.go +0 -149
  610. package/engine/internal/lib/tickafk.go +0 -86
  611. package/engine/internal/migrate/migrate.go +0 -279
  612. package/engine/internal/migrate/migrate_test.go +0 -157
  613. package/engine/internal/orient/firstrun.go +0 -50
  614. package/engine/internal/orient/firstrun_test.go +0 -72
  615. package/engine/internal/orient/orient.go +0 -69
  616. package/engine/internal/orient/orient_test.go +0 -25
  617. package/engine/internal/profile/profile.go +0 -284
  618. package/engine/internal/profile/profile_test.go +0 -13
  619. package/engine/internal/state/clarify_transition.go +0 -165
  620. package/engine/internal/state/clarify_transition_test.go +0 -130
  621. package/engine/internal/state/snapshot.go +0 -457
  622. package/engine/internal/toolpolicy/classifier.go +0 -533
  623. package/engine/internal/toolpolicy/classifier_test.go +0 -424
  624. package/engine/internal/toolpolicy/git.go +0 -616
  625. package/engine/internal/toolpolicy/scanner.go +0 -382
  626. package/engine/observability_cli_test.go +0 -52
  627. package/engine/profile_test.go +0 -76
  628. package/engine/subagent_orient_context.txt +0 -8
  629. package/engine/testdata/golden/TestParityA1Guard/devrites-edit-allowed.golden +0 -1
  630. package/engine/testdata/golden/TestParityA1Guard/no-window-silent.golden +0 -1
  631. package/engine/testdata/golden/TestParityA1Guard/non-edit-tool.golden +0 -1
  632. package/engine/testdata/golden/TestParityA1Guard/observe-mode-silent.golden +0 -1
  633. package/engine/testdata/golden/TestParityA1Guard/source-edit-enforce-denies.golden +0 -2
  634. package/engine/testdata/golden/TestParityA1Guard/subagent-allowed.golden +0 -1
  635. package/engine/testdata/golden/TestParityAnalyze/noactive.golden +0 -1
  636. package/engine/testdata/golden/TestParityAnalyze/slug=covered.golden +0 -15
  637. package/engine/testdata/golden/TestParityAnalyze/slug=dangling.golden +0 -16
  638. package/engine/testdata/golden/TestParityAnalyze/slug=ghost.golden +0 -1
  639. package/engine/testdata/golden/TestParityAnalyze/slug=noac.golden +0 -15
  640. package/engine/testdata/golden/TestParityAnalyze/slug=order.golden +0 -17
  641. package/engine/testdata/golden/TestParityAnalyze/slug=orphan.golden +0 -16
  642. package/engine/testdata/golden/TestParityAnalyze/slug=uncovered.golden +0 -16
  643. package/engine/testdata/golden/TestParityBudget/default-active.golden +0 -4
  644. package/engine/testdata/golden/TestParityBudget/extra-md.golden +0 -5
  645. package/engine/testdata/golden/TestParityBudget/no-active.golden +0 -1
  646. package/engine/testdata/golden/TestParityBudget/no-feature.golden +0 -1
  647. package/engine/testdata/golden/TestParityBudget/over-advisory.golden +0 -6
  648. package/engine/testdata/golden/TestParityBudget/over-strict.golden +0 -6
  649. package/engine/testdata/golden/TestParityBudget/within.golden +0 -6
  650. package/engine/testdata/golden/TestParityBuildReadiness/arg=approved.golden +0 -2
  651. package/engine/testdata/golden/TestParityBuildReadiness/arg=approvenone.golden +0 -1
  652. package/engine/testdata/golden/TestParityBuildReadiness/arg=await.golden +0 -1
  653. package/engine/testdata/golden/TestParityBuildReadiness/arg=blocked.golden +0 -1
  654. package/engine/testdata/golden/TestParityBuildReadiness/arg=clarifyopen.golden +0 -1
  655. package/engine/testdata/golden/TestParityBuildReadiness/arg=emptystatus.golden +0 -2
  656. package/engine/testdata/golden/TestParityBuildReadiness/arg=ghost.golden +0 -1
  657. package/engine/testdata/golden/TestParityBuildReadiness/arg=legacycontract.golden +0 -1
  658. package/engine/testdata/golden/TestParityBuildReadiness/arg=noapprove.golden +0 -1
  659. package/engine/testdata/golden/TestParityBuildReadiness/arg=noclarify.golden +0 -1
  660. package/engine/testdata/golden/TestParityBuildReadiness/arg=novet.golden +0 -1
  661. package/engine/testdata/golden/TestParityBuildReadiness/arg=stalevet.golden +0 -1
  662. package/engine/testdata/golden/TestParityBuildReadiness/arg=trailhash.golden +0 -2
  663. package/engine/testdata/golden/TestParityBuildReadiness/arg=trailpipe.golden +0 -2
  664. package/engine/testdata/golden/TestParityBuildReadiness/arg=vetnotready.golden +0 -1
  665. package/engine/testdata/golden/TestParityCheckAcceptance/arg=blank-section.golden +0 -1
  666. package/engine/testdata/golden/TestParityCheckAcceptance/arg=empty.golden +0 -1
  667. package/engine/testdata/golden/TestParityCheckAcceptance/arg=flat-dropped.golden +0 -1
  668. package/engine/testdata/golden/TestParityCheckAcceptance/arg=flat-ok.golden +0 -2
  669. package/engine/testdata/golden/TestParityCheckAcceptance/arg=flat-unchecked.golden +0 -1
  670. package/engine/testdata/golden/TestParityCheckAcceptance/arg=id-gap.golden +0 -1
  671. package/engine/testdata/golden/TestParityCheckAcceptance/arg=id-ok.golden +0 -2
  672. package/engine/testdata/golden/TestParityCheckAcceptance/arg=no-seal.golden +0 -1
  673. package/engine/testdata/golden/TestParityCheckAcceptance/arg=no-section.golden +0 -1
  674. package/engine/testdata/golden/TestParityCheckAcceptance/arg=nonexistent.golden +0 -1
  675. package/engine/testdata/golden/TestParityCheckAcceptance.golden +0 -1
  676. package/engine/testdata/golden/TestParityCoverage/arg=flat.golden +0 -7
  677. package/engine/testdata/golden/TestParityCoverage/arg=full.golden +0 -10
  678. package/engine/testdata/golden/TestParityCoverage/arg=nonexistent.golden +0 -1
  679. package/engine/testdata/golden/TestParityCoverage/arg=nospec.golden +0 -1
  680. package/engine/testdata/golden/TestParityCoverage/arg=speconly.golden +0 -9
  681. package/engine/testdata/golden/TestParityCoverage.golden +0 -1
  682. package/engine/testdata/golden/TestParityCursor/bare.golden +0 -3
  683. package/engine/testdata/golden/TestParityCursor/full-afk.golden +0 -6
  684. package/engine/testdata/golden/TestParityCursor/full-hitl.golden +0 -5
  685. package/engine/testdata/golden/TestParityCursor/no-active.golden +0 -1
  686. package/engine/testdata/golden/TestParityCursor/red.golden +0 -5
  687. package/engine/testdata/golden/TestParityCursor/zerogates.golden +0 -5
  688. package/engine/testdata/golden/TestParityDoubtCoverage/arg=doubted.golden +0 -2
  689. package/engine/testdata/golden/TestParityDoubtCoverage/arg=ghost.golden +0 -2
  690. package/engine/testdata/golden/TestParityDoubtCoverage/arg=missing.golden +0 -3
  691. package/engine/testdata/golden/TestParityDoubtCoverage/arg=nodoubt.golden +0 -4
  692. package/engine/testdata/golden/TestParityDoubtCoverage/arg=nolog.golden +0 -2
  693. package/engine/testdata/golden/TestParityDoubtCoverage/arg=nowright.golden +0 -2
  694. package/engine/testdata/golden/TestParityDoubtCoverage.golden +0 -1
  695. package/engine/testdata/golden/TestParityMutationGate/active-slug.golden +0 -3
  696. package/engine/testdata/golden/TestParityMutationGate/explicit-slug.golden +0 -3
  697. package/engine/testdata/golden/TestParityMutationGate/ghost.golden +0 -1
  698. package/engine/testdata/golden/TestParityOrientSilentPath.golden +0 -1
  699. package/engine/testdata/golden/TestParityReconcile/bad-mode.golden +0 -1
  700. package/engine/testdata/golden/TestParityReconcile/check-clean/snapshot.golden +0 -2
  701. package/engine/testdata/golden/TestParityReconcile/check-clean.golden +0 -2
  702. package/engine/testdata/golden/TestParityReconcile/check-no-base.golden +0 -1
  703. package/engine/testdata/golden/TestParityReconcile/check-no-claimed/snapshot.golden +0 -2
  704. package/engine/testdata/golden/TestParityReconcile/check-violation/snapshot.golden +0 -2
  705. package/engine/testdata/golden/TestParityReconcile/check-violation.golden +0 -1
  706. package/engine/testdata/golden/TestParityReconcile/close-window/snapshot.golden +0 -2
  707. package/engine/testdata/golden/TestParityReconcile/close-window.golden +0 -3
  708. package/engine/testdata/golden/TestParityReconcile/not-git.golden +0 -1
  709. package/engine/testdata/golden/TestParityReconcile/snapshot/snapshot.golden +0 -2
  710. package/engine/testdata/golden/TestParityReconcile/snapshot-no-allowlist.golden +0 -1
  711. package/engine/testdata/golden/TestParityReconcile/snapshot.golden +0 -2
  712. package/engine/testdata/golden/TestParityRedwatch/fail-writes-red.golden +0 -2
  713. package/engine/testdata/golden/TestParityRedwatch/non-bash-tool.golden +0 -1
  714. package/engine/testdata/golden/TestParityRedwatch/non-test-command.golden +0 -1
  715. package/engine/testdata/golden/TestParityRedwatch/pass-clears-red.golden +0 -1
  716. package/engine/testdata/golden/TestParityResolve/next-qid.golden +0 -2
  717. package/engine/testdata/golden/TestParityReviewerReadonly/agent-required-devrites-agent-denies.golden +0 -2
  718. package/engine/testdata/golden/TestParityReviewerReadonly/agent-required-non-devrites-allows.golden +0 -1
  719. package/engine/testdata/golden/TestParityReviewerReadonly/agent-required-subagent-type-only-allows.golden +0 -1
  720. package/engine/testdata/golden/TestParityReviewerReadonly/enforce-deny-git-commit.golden +0 -2
  721. package/engine/testdata/golden/TestParityReviewerReadonly/enforce-deny-mutating.golden +0 -2
  722. package/engine/testdata/golden/TestParityReviewerReadonly/non-bash-tool.golden +0 -1
  723. package/engine/testdata/golden/TestParityReviewerReadonly/non-tool-input.golden +0 -1
  724. package/engine/testdata/golden/TestParityReviewerReadonly/observe-mutating-silent.golden +0 -1
  725. package/engine/testdata/golden/TestParityReviewerReadonly/safe-bash-command.golden +0 -1
  726. package/engine/testdata/golden/TestParitySpecValidate/arg=dup.golden +0 -1
  727. package/engine/testdata/golden/TestParitySpecValidate/arg=empty.golden +0 -1
  728. package/engine/testdata/golden/TestParitySpecValidate/arg=flat.golden +0 -2
  729. package/engine/testdata/golden/TestParitySpecValidate/arg=nonexistent.golden +0 -1
  730. package/engine/testdata/golden/TestParitySpecValidate/arg=noscenario.golden +0 -1
  731. package/engine/testdata/golden/TestParitySpecValidate/arg=noshall.golden +0 -1
  732. package/engine/testdata/golden/TestParitySpecValidate/arg=nothen.golden +0 -1
  733. package/engine/testdata/golden/TestParitySpecValidate/arg=valid/spec.md.golden +0 -2
  734. package/engine/testdata/golden/TestParitySpecValidate/arg=valid.golden +0 -2
  735. package/engine/testdata/golden/TestParitySpecValidate.golden +0 -1
  736. package/engine/testdata/golden/TestParityStatusline/full-afk.golden +0 -2
  737. package/engine/testdata/golden/TestParityStatusline/full-hitl.golden +0 -2
  738. package/engine/testdata/golden/TestParityStatusline/no-active.golden +0 -1
  739. package/engine/testdata/golden/TestParityStatusline/nophase.golden +0 -2
  740. package/engine/testdata/golden/TestParityStatusline/red.golden +0 -2
  741. package/engine/testdata/golden/TestParityStuckCheck/bad-cmd.golden +0 -1
  742. package/engine/testdata/golden/TestParityStuckCheck/custom-window-2.golden +0 -2
  743. package/engine/testdata/golden/TestParityStuckCheck/dominant.golden +0 -2
  744. package/engine/testdata/golden/TestParityStuckCheck/few.golden +0 -2
  745. package/engine/testdata/golden/TestParityStuckCheck/ghost.golden +0 -2
  746. package/engine/testdata/golden/TestParityStuckCheck/identical.golden +0 -2
  747. package/engine/testdata/golden/TestParityStuckCheck/no-cmd.golden +0 -1
  748. package/engine/testdata/golden/TestParityStuckCheck/no-slug.golden +0 -1
  749. package/engine/testdata/golden/TestParityStuckCheck/nolog.golden +0 -2
  750. package/engine/testdata/golden/TestParityStuckCheck/pingpong.golden +0 -2
  751. package/engine/testdata/golden/TestParityStuckCheck/varied.golden +0 -2
  752. package/engine/testdata/golden/TestParityStuckCheck/window-04-identical.golden +0 -2
  753. package/engine/testdata/golden/TestParityStuckCheck/window-2.5-varied4.golden +0 -2
  754. package/engine/testdata/golden/TestParityStuckCheck/window-4x-varied4.golden +0 -2
  755. package/engine/testdata/golden/TestParityStuckCheck/window-leadspace-varied4.golden +0 -2
  756. package/engine/testdata/golden/TestParityStuckCheck/window-plus4-identical.golden +0 -2
  757. package/engine/testdata/golden/TestParitySubagentOrient/devrites-agent-subagent-type-key.golden +0 -2
  758. package/engine/testdata/golden/TestParitySubagentOrient/devrites-agent.golden +0 -2
  759. package/engine/testdata/golden/TestParitySubagentOrient/garbage-input.golden +0 -1
  760. package/engine/testdata/golden/TestParitySubagentOrient/non-devrites-agent.golden +0 -1
  761. package/engine/testdata/golden/TestParityTestIntegrity/assertion-dropped.golden +0 -1
  762. package/engine/testdata/golden/TestParityTestIntegrity/clean.golden +0 -2
  763. package/engine/testdata/golden/TestParityTestIntegrity/deleted.golden +0 -1
  764. package/engine/testdata/golden/TestParityTestIntegrity/ghost-workspace.golden +0 -1
  765. package/engine/testdata/golden/TestParityTestIntegrity/not-git.golden +0 -1
  766. package/engine/testdata/golden/TestParityTestIntegrity/skip-added.golden +0 -1
  767. package/engine/testdata/golden/TestParityTickAfk/budget1/state.md.golden +0 -1
  768. package/engine/testdata/golden/TestParityTickAfk/budget1.golden +0 -2
  769. package/engine/testdata/golden/TestParityTickAfk/budget3/state.md.golden +0 -5
  770. package/engine/testdata/golden/TestParityTickAfk/budget3.golden +0 -2
  771. package/engine/testdata/golden/TestParityTickAfk/bulletPipe/state.md.golden +0 -1
  772. package/engine/testdata/golden/TestParityTickAfk/bulletPipe.golden +0 -2
  773. package/engine/testdata/golden/TestParityTickAfk/missingField/state.md.golden +0 -3
  774. package/engine/testdata/golden/TestParityTickAfk/missingField.golden +0 -2
  775. package/engine/testdata/golden/TestParityTickAfk/missingFile.golden +0 -1
  776. package/engine/testdata/golden/TestParityTickAfk/nonNumeric/state.md.golden +0 -1
  777. package/engine/testdata/golden/TestParityTickAfk/nonNumeric.golden +0 -1
  778. package/engine/testdata/golden/TestParityTickAfk/none/state.md.golden +0 -1
  779. package/engine/testdata/golden/TestParityTickAfk/none.golden +0 -2
  780. package/engine/testdata/golden/TestParityWrightScope/agent-required-non-wright.golden +0 -1
  781. package/engine/testdata/golden/TestParityWrightScope/devrites-edit-denied.golden +0 -2
  782. package/engine/testdata/golden/TestParityWrightScope/in-scope-allowed.golden +0 -1
  783. package/engine/testdata/golden/TestParityWrightScope/non-edit-tool.golden +0 -1
  784. package/engine/testdata/golden/TestParityWrightScope/out-of-scope-enforce-denies.golden +0 -2
  785. package/engine/testdata/golden/TestParityWrightScope/out-of-scope-observe-silent.golden +0 -1
  786. package/engine/testdata/golden/TestSpecSkeleton/missing-some.golden +0 -2
  787. package/engine/testdata/golden/TestSpecSkeleton/present-all.golden +0 -2
  788. package/engine/testdata/golden/TestStuckLogHashParity.golden +0 -1
  789. package/engine/tests/adr_0006_clock_seam_test.go +0 -36
  790. package/engine/tests/budget_test.go +0 -88
  791. package/engine/tests/canonical_ac_ids_test.go +0 -44
  792. package/engine/tests/concurrency_cli_test.go +0 -131
  793. package/engine/tests/conventions_test.go +0 -106
  794. package/engine/tests/doctor_cli_test.go +0 -223
  795. package/engine/tests/forge_cli_test.go +0 -463
  796. package/engine/tests/hook_test.go +0 -2778
  797. package/engine/tests/hooks_io_test.go +0 -309
  798. package/engine/tests/json_contract_test.go +0 -224
  799. package/engine/tests/lib_parity_test.go +0 -930
  800. package/engine/tests/migrate_cli_test.go +0 -195
  801. package/engine/tests/parity_analyze_test.go +0 -55
  802. package/engine/tests/parity_buildreadiness_test.go +0 -199
  803. package/engine/tests/parity_learnings_test.go +0 -89
  804. package/engine/tests/parity_mutationgate_test.go +0 -36
  805. package/engine/tests/parity_reconcile_test.go +0 -94
  806. package/engine/tests/parity_testintegrity_test.go +0 -65
  807. package/engine/tests/parity_tickafk_test.go +0 -83
  808. package/engine/validatepack.go +0 -192
  809. package/engine/validatepack_test.go +0 -79
  810. package/engine/validatepackskills.go +0 -181
  811. package/engine/validatepackskills_test.go +0 -171
  812. package/pack/.claude/agents/devrites-forge-judge.md +0 -118
  813. package/pack/.claude/skills/devrites-lib/reference/model-tiers.md +0 -44
  814. package/pack/.claude/skills/devrites-refresh-indexes/SKILL.md +0 -52
  815. package/pack/.claude/skills/rite-build/reference/evidence-standard.md +0 -45
  816. package/pack/.claude/skills/rite-build/reference/forge.md +0 -210
  817. package/pack/.claude/skills/rite-seal/reference/conventions-ledger.md +0 -60
  818. package/pack/.claude/skills/rite-ship/reference/adr-promotion.md +0 -9
  819. package/pack/.claude/skills/rite-ship/reference/ledger.md +0 -56
  820. package/pack/generated/claude/agents/devrites-forge-judge.md +0 -118
  821. package/pack/generated/claude/skills/devrites-lib/reference/model-tiers.md +0 -44
  822. package/pack/generated/claude/skills/devrites-refresh-indexes/SKILL.md +0 -52
  823. package/pack/generated/claude/skills/rite-build/reference/evidence-standard.md +0 -45
  824. package/pack/generated/claude/skills/rite-build/reference/forge.md +0 -210
  825. package/pack/generated/claude/skills/rite-seal/reference/conventions-ledger.md +0 -60
  826. package/pack/generated/claude/skills/rite-ship/reference/adr-promotion.md +0 -9
  827. package/pack/generated/claude/skills/rite-ship/reference/ledger.md +0 -56
  828. package/pack/generated/codex/agents/devrites-forge-judge.toml +0 -128
  829. package/pack/generated/codex/hooks.json +0 -185
  830. package/pack/generated/codex/skills/devrites-api-interface/agents/openai.yaml +0 -2
  831. package/pack/generated/codex/skills/devrites-audit/agents/openai.yaml +0 -2
  832. package/pack/generated/codex/skills/devrites-browser-proof/agents/openai.yaml +0 -2
  833. package/pack/generated/codex/skills/devrites-debug-recovery/agents/openai.yaml +0 -2
  834. package/pack/generated/codex/skills/devrites-doubt/agents/openai.yaml +0 -2
  835. package/pack/generated/codex/skills/devrites-frontend-craft/agents/openai.yaml +0 -2
  836. package/pack/generated/codex/skills/devrites-interview/agents/openai.yaml +0 -2
  837. package/pack/generated/codex/skills/devrites-lib/reference/model-tiers.md +0 -44
  838. package/pack/generated/codex/skills/devrites-prose-craft/agents/openai.yaml +0 -2
  839. package/pack/generated/codex/skills/devrites-refresh-indexes/SKILL.md +0 -71
  840. package/pack/generated/codex/skills/devrites-refresh-indexes/agents/openai.yaml +0 -2
  841. package/pack/generated/codex/skills/devrites-source-driven/agents/openai.yaml +0 -2
  842. package/pack/generated/codex/skills/devrites-ux-shape/agents/openai.yaml +0 -2
  843. package/pack/generated/codex/skills/rite-build/reference/evidence-standard.md +0 -45
  844. package/pack/generated/codex/skills/rite-build/reference/forge.md +0 -210
  845. package/pack/generated/codex/skills/rite-seal/reference/conventions-ledger.md +0 -60
  846. package/pack/generated/codex/skills/rite-ship/reference/adr-promotion.md +0 -9
  847. package/pack/generated/codex/skills/rite-ship/reference/ledger.md +0 -56
  848. package/scripts/build-binaries.sh +0 -75
  849. package/scripts/build-release-tarball.sh +0 -105
  850. package/scripts/check-authority-drift.py +0 -125
  851. package/scripts/check-cross-refs.py +0 -139
  852. package/scripts/check-generated-skill-budget.mjs +0 -42
  853. package/scripts/check-instruction-size-baseline.mjs +0 -88
  854. package/scripts/check-invocation-integrity.py +0 -115
  855. package/scripts/check-no-global-writes.sh +0 -66
  856. package/scripts/check-no-personal-paths.py +0 -50
  857. package/scripts/check-npm-audit.mjs +0 -110
  858. package/scripts/check-reference-governance.mjs +0 -83
  859. package/scripts/check-reply-contract.sh +0 -64
  860. package/scripts/check-rule-uniqueness.sh +0 -45
  861. package/scripts/devrites-detect.sh +0 -176
  862. package/scripts/grade-feature.sh +0 -204
  863. package/scripts/live-hosts/agent-result.schema.json +0 -231
  864. package/scripts/live-hosts/claude.sh +0 -87
  865. package/scripts/live-hosts/codex.sh +0 -85
  866. package/scripts/live-hosts/common.sh +0 -113
  867. package/scripts/live-hosts/fake-host.py +0 -257
  868. package/scripts/live-hosts/host-transport.py +0 -286
  869. package/scripts/npm-audit-exceptions.json +0 -26
  870. package/scripts/pin.sh +0 -249
  871. package/scripts/release-check.sh +0 -75
  872. package/scripts/run-agent-contract-evals.py +0 -1380
  873. package/scripts/run-behavioral-evals.sh +0 -196
  874. package/scripts/run-evals.sh +0 -132
  875. package/scripts/run-live-behavioral-evals.py +0 -1310
  876. package/scripts/run-outcome-evals.sh +0 -454
  877. package/scripts/run-routing-evals.py +0 -342
  878. package/scripts/run-tests.mjs +0 -232
  879. package/scripts/scan-pack-security.py +0 -209
  880. package/scripts/scan-supply-chain-iocs.py +0 -127
  881. package/scripts/skill-pruning-audit.mjs +0 -121
  882. package/scripts/skills-inventory.mjs +0 -125
  883. package/scripts/supply-chain-iocs.json +0 -11
  884. package/scripts/sync-version.sh +0 -54
  885. package/scripts/validate-agent-composition.py +0 -57
  886. package/scripts/validate-command-parity.py +0 -70
  887. package/scripts/validate-frontmatter.py +0 -218
  888. package/scripts/validate-skill-anatomy.py +0 -139
  889. package/scripts/validate-workflow-security.py +0 -184
  890. package/scripts/validate-workspace-schema.py +0 -800
  891. package/scripts/validate.sh +0 -354
  892. package/scripts/workflow_schema.py +0 -69
  893. /package/engine/testdata/fixtures/basic/devrites-root/{features → work}/auth-tokens/decisions.md +0 -0
  894. /package/engine/testdata/fixtures/basic/devrites-root/{features → work}/auth-tokens/plan.md +0 -0
  895. /package/engine/testdata/fixtures/basic/devrites-root/{features → work}/auth-tokens/spec.md +0 -0
  896. /package/engine/testdata/fixtures/basic/devrites-root/{features → work}/auth-tokens/tasks.md +0 -0
  897. /package/engine/testdata/fixtures/basic/devrites-root/{features → work}/search-ranking/spec.md +0 -0
@@ -1,1496 +0,0 @@
1
- package lib
2
-
3
- import (
4
- "crypto/sha256"
5
- "encoding/hex"
6
- "encoding/json"
7
- "fmt"
8
- "io"
9
- "os"
10
- "path"
11
- "path/filepath"
12
- "regexp"
13
- "sort"
14
- "strings"
15
-
16
- "github.com/devrites/devrites/internal/devritespaths"
17
- "github.com/devrites/devrites/internal/forge"
18
- "github.com/devrites/devrites/internal/safepath"
19
- )
20
-
21
- const (
22
- reconcileBaseName = ".reconcile-base"
23
- reconcileAllowlistName = ".reconcile-allowlist"
24
- reconcileCheckedName = ".reconcile-checked"
25
- reconcileDevritesName = ".reconcile-devrites"
26
- reconcileObjectsName = ".reconcile-objects"
27
- reconcileWrightStateName = ".reconcile-wright-devrites"
28
- defaultWrightAllowlistName = ".wright-allowlist"
29
- wrightAllowlistFileEnv = "DEVRITES_WRIGHT_ALLOWLIST_FILE"
30
- reconcileDevritesPathPrefix = ".devrites/"
31
- reconcileAbortSchema = "devrites.reconcile-abort-receipt.v1"
32
- )
33
-
34
- var (
35
- reconcileAbortReceiptName = regexp.MustCompile(`^\.reconcile-abort-([0-9a-f]{64})\.json$`)
36
- reconcileObjectID = regexp.MustCompile(`^(?:[0-9a-f]{40}|[0-9a-f]{64})$`)
37
- reconcileSHA256 = regexp.MustCompile(`^[0-9a-f]{64}$`)
38
- )
39
-
40
- type reconcileAbortReceipt struct {
41
- SchemaVersion string `json:"schemaVersion"`
42
- Slug string `json:"slug"`
43
- BaselineTree string `json:"baselineTree"`
44
- SourceManifestSHA256 string `json:"sourceManifestSHA256"`
45
- SourceEntryCount int `json:"sourceEntryCount"`
46
- CapturedAllowlistSHA256 string `json:"capturedAllowlistSHA256"`
47
- RestoredPaths []string `json:"restoredPaths"`
48
- WindowClosed bool `json:"windowClosed"`
49
- }
50
-
51
- // Reconcile enforces the source-write boundary around one dispatched
52
- // slice-wright. The first `snapshot` captures the dirty worktree, private object
53
- // database, exact orchestrator-authored allowlist, and .devrites state. A
54
- // confirmed wright start captures the current canonical state separately, so
55
- // retained-window recovery records are not attributed to the writer. After a
56
- // clean check, another snapshot re-arms only the dispatch state for a retry
57
- // while retaining the original source baseline.
58
- // `check` compares the captured state with the current state and retains the
59
- // immutable baseline for the later test-integrity gate.
60
- // `restore-check` rolls back only source drift introduced after the last clean
61
- // check. `abort` restores the original source baseline, records a
62
- // content-addressed receipt, and closes a rejected writer window. `close`
63
- // revalidates and then removes the private window artifacts.
64
- //
65
- // 0 clean check, snapshot/close completed, or skipped (not a git repo)
66
- // 5 VIOLATION: a path changed outside the orchestrator allowlist
67
- // 6 setup error: bad args or missing/corrupt lifecycle state
68
- func Reconcile(root string, args []string, stdout, stderr io.Writer) int {
69
- mode := argAt(args, 0)
70
- slug := argAt(args, 1)
71
- if slug == "" {
72
- slug = activeSlug(root)
73
- }
74
-
75
- if slug == "" {
76
- s := slug
77
- if s == "" {
78
- s = "<unset>"
79
- }
80
- fmt.Fprintf(stderr, "reconcile: no active workspace (slug=%s): nothing to reconcile.\n", s)
81
- return 6
82
- }
83
- d, err := devritespaths.ExistingFeatureDirChecked(root, slug)
84
- if err != nil {
85
- if os.IsNotExist(err) {
86
- fmt.Fprintf(stderr, "reconcile: no active workspace (slug=%s): nothing to reconcile.\n", slug)
87
- } else {
88
- fmt.Fprintf(stderr, "reconcile: invalid workspace for %s: %v\n", slug, err)
89
- }
90
- return 6
91
- }
92
-
93
- base := filepath.Join(d, reconcileBaseName)
94
- capturedAllowlist := filepath.Join(d, reconcileAllowlistName)
95
- devritesSnapshot := filepath.Join(d, reconcileDevritesName)
96
- objects := filepath.Join(d, reconcileObjectsName)
97
- checked := filepath.Join(d, reconcileCheckedName)
98
- wrightState := filepath.Join(d, reconcileWrightStateName)
99
-
100
- closeWindow := func() error {
101
- var failures []string
102
- for _, privateFile := range []string{base, capturedAllowlist, devritesSnapshot, checked, wrightState} {
103
- if err := os.Remove(privateFile); err != nil && !os.IsNotExist(err) {
104
- failures = append(failures, fmt.Sprintf("%s: %v", filepath.Base(privateFile), err))
105
- }
106
- }
107
- if err := os.RemoveAll(objects); err != nil {
108
- failures = append(failures, fmt.Sprintf("%s: %v", filepath.Base(objects), err))
109
- }
110
- if len(failures) > 0 {
111
- return fmt.Errorf("%s", strings.Join(failures, "; "))
112
- }
113
- return nil
114
- }
115
-
116
- if mode == "close" {
117
- info, err := os.Lstat(checked)
118
- if err != nil || !info.Mode().IsRegular() {
119
- fmt.Fprintln(stderr, `reconcile: active slice window has no clean check marker; run "reconcile check" before closing it.`)
120
- return 6
121
- }
122
- // Defense in depth: a prior marker proves the explicit check occurred;
123
- // rerunning it here binds close to the current source and canonical
124
- // workspace state rather than trusting a stale marker.
125
- if code := Reconcile(root, []string{"check", slug}, stdout, stderr); code != 0 {
126
- return code
127
- }
128
- if err := closeWindow(); err != nil {
129
- fmt.Fprintf(stderr, "reconcile: cannot close slice window: %v\n", err)
130
- return 6
131
- }
132
- fmt.Fprintf(stdout, "reconcile: closed slice window for %s.\n", slug)
133
- return 0
134
- }
135
-
136
- cwd, _ := os.Getwd()
137
- gitRoot, err := gitToplevel(cwd)
138
- if err != nil {
139
- fmt.Fprintf(stderr, "reconcile: cannot resolve git worktree: %v\n", err)
140
- return 6
141
- }
142
-
143
- switch mode {
144
- case "snapshot":
145
- _, _, activeObjects, active, err := loadReconcileBaseline(gitRoot, d)
146
- if err != nil {
147
- fmt.Fprintf(stderr, "reconcile: cannot refresh invalid slice window: %v\n", err)
148
- return 6
149
- }
150
- if active {
151
- checkedTree, err := os.ReadFile(checked)
152
- if err != nil {
153
- fmt.Fprintln(stderr, `reconcile: active slice window has no clean check marker; run "reconcile check" before refreshing the dispatch snapshot.`)
154
- return 6
155
- }
156
- nowTree, err := worktreeTree(gitRoot, activeObjects, root)
157
- if err != nil {
158
- fmt.Fprintf(stderr, "reconcile: cannot refresh worktree: %v\n", err)
159
- return 6
160
- }
161
- if strings.TrimSpace(string(checkedTree)) != nowTree {
162
- fmt.Fprintln(stderr, `reconcile: source changed after the last clean check; run "reconcile check" before refreshing the dispatch snapshot.`)
163
- return 6
164
- }
165
- allowlistPath, err := wrightAllowlistPath(gitRoot, d)
166
- if err != nil {
167
- fmt.Fprintf(stderr, "reconcile: invalid wright allowlist location: %v\n", err)
168
- return 6
169
- }
170
- allowed, err := readWrightAllowlist(gitRoot, allowlistPath)
171
- if err != nil {
172
- fmt.Fprintf(stderr, "reconcile: invalid wright allowlist %s: %v\n", allowlistPath, err)
173
- return 6
174
- }
175
- if err := os.WriteFile(capturedAllowlist, renderWrightAllowlist(allowed), 0o600); err != nil {
176
- fmt.Fprintf(stderr, "reconcile: cannot refresh wright allowlist: %v\n", err)
177
- return 6
178
- }
179
- state, err := captureReconcileDevritesState(root, devritesSnapshot, activeObjects, checked, wrightState)
180
- if err != nil {
181
- fmt.Fprintf(stderr, "reconcile: cannot refresh .devrites snapshot: %v\n", err)
182
- return 6
183
- }
184
- if err := writeDevritesState(devritesSnapshot, state); err != nil {
185
- fmt.Fprintf(stderr, "reconcile: cannot write refreshed .devrites snapshot: %v\n", err)
186
- return 6
187
- }
188
- for _, privateFile := range []string{checked, wrightState} {
189
- if err := os.Remove(privateFile); err != nil && !os.IsNotExist(err) {
190
- fmt.Fprintf(stderr, "reconcile: cannot arm refreshed slice window: %v\n", err)
191
- return 6
192
- }
193
- }
194
- fmt.Fprintf(stdout, "reconcile: dispatch snapshot refreshed for %s; original slice baseline retained.\n", slug)
195
- return 0
196
- }
197
-
198
- if err := closeWindow(); err != nil {
199
- fmt.Fprintf(stderr, "reconcile: cannot clear stale slice window: %v\n", err)
200
- return 6
201
- }
202
-
203
- allowlistPath, err := wrightAllowlistPath(gitRoot, d)
204
- if err != nil {
205
- fmt.Fprintf(stderr, "reconcile: invalid wright allowlist location: %v\n", err)
206
- return 6
207
- }
208
- allowed, err := readWrightAllowlist(gitRoot, allowlistPath)
209
- if err != nil {
210
- fmt.Fprintf(stderr, "reconcile: invalid wright allowlist %s: %v\n", allowlistPath, err)
211
- return 6
212
- }
213
-
214
- tree, err := worktreeTree(gitRoot, objects, root)
215
- if err != nil {
216
- _ = closeWindow()
217
- fmt.Fprintf(stderr, "reconcile: cannot snapshot worktree: %v\n", err)
218
- return 6
219
- }
220
- if err := os.WriteFile(base, []byte(tree+"\n"), 0o644); err != nil {
221
- _ = closeWindow()
222
- fmt.Fprintf(stderr, "reconcile: cannot write snapshot: %v\n", err)
223
- return 6
224
- }
225
- if err := os.WriteFile(capturedAllowlist, renderWrightAllowlist(allowed), 0o600); err != nil {
226
- _ = closeWindow()
227
- fmt.Fprintf(stderr, "reconcile: cannot capture wright allowlist: %v\n", err)
228
- return 6
229
- }
230
- state, err := captureReconcileDevritesState(root, devritesSnapshot, objects, checked, wrightState)
231
- if err != nil {
232
- _ = closeWindow()
233
- fmt.Fprintf(stderr, "reconcile: cannot snapshot .devrites: %v\n", err)
234
- return 6
235
- }
236
- if err := writeDevritesState(devritesSnapshot, state); err != nil {
237
- _ = closeWindow()
238
- fmt.Fprintf(stderr, "reconcile: cannot write .devrites snapshot: %v\n", err)
239
- return 6
240
- }
241
- fmt.Fprintf(stdout, "reconcile: snapshot captured for %s.\n", slug)
242
- return 0
243
-
244
- case "abort":
245
- baseTree, env, activeObjects, active, err := loadReconcileBaseline(gitRoot, d)
246
- if err != nil {
247
- fmt.Fprintf(stderr, "reconcile: invalid snapshot: %v\n", err)
248
- return 6
249
- }
250
- if !active {
251
- fmt.Fprintln(stderr, `reconcile: no active snapshot to abort.`)
252
- return 6
253
- }
254
- allowed, err := readWrightAllowlist(gitRoot, capturedAllowlist)
255
- if err != nil {
256
- fmt.Fprintf(stderr, "reconcile: invalid captured allowlist: %v\n", err)
257
- return 6
258
- }
259
- allowlistBytes := renderWrightAllowlist(allowed)
260
- allowlistDigest := sha256.Sum256(allowlistBytes)
261
-
262
- nowTree, err := worktreeTree(gitRoot, activeObjects, root)
263
- if err != nil {
264
- fmt.Fprintf(stderr, "reconcile: cannot capture worktree for abort: %v\n", err)
265
- return 6
266
- }
267
- changed, err := changedTreePaths(gitRoot, env, baseTree, nowTree)
268
- if err != nil {
269
- fmt.Fprintf(stderr, "reconcile: cannot compare abort baseline: %v\n", err)
270
- return 6
271
- }
272
- changed = sourceTreePaths(changed)
273
- if err := restoreTreePaths(gitRoot, env, baseTree, changed); err != nil {
274
- fmt.Fprintf(stderr, "reconcile: cannot restore rejected writer source delta: %v\n", err)
275
- return 6
276
- }
277
-
278
- restoredTree, err := worktreeTree(gitRoot, activeObjects, root)
279
- if err != nil {
280
- fmt.Fprintf(stderr, "reconcile: cannot verify aborted worktree: %v\n", err)
281
- return 6
282
- }
283
- remaining, err := changedTreePaths(gitRoot, env, baseTree, restoredTree)
284
- if err != nil {
285
- fmt.Fprintf(stderr, "reconcile: cannot verify abort baseline: %v\n", err)
286
- return 6
287
- }
288
- if remaining = sourceTreePaths(remaining); len(remaining) != 0 {
289
- fmt.Fprintln(stderr, "reconcile: rejected writer source delta was not fully restored; retained window left open.")
290
- return 6
291
- }
292
- manifestDigest, entryCount, err := sourceTreeManifest(gitRoot, env, baseTree)
293
- if err != nil {
294
- fmt.Fprintf(stderr, "reconcile: cannot fingerprint restored source baseline: %v\n", err)
295
- return 6
296
- }
297
- restoredDigest, restoredEntryCount, err := sourceTreeManifest(gitRoot, env, restoredTree)
298
- if err != nil {
299
- fmt.Fprintf(stderr, "reconcile: cannot fingerprint restored worktree: %v\n", err)
300
- return 6
301
- }
302
- if restoredDigest != manifestDigest || restoredEntryCount != entryCount {
303
- fmt.Fprintln(stderr, "reconcile: restored source manifest does not match the original baseline; retained window left open.")
304
- return 6
305
- }
306
- receipt := reconcileAbortReceipt{
307
- SchemaVersion: reconcileAbortSchema,
308
- Slug: slug,
309
- BaselineTree: baseTree,
310
- SourceManifestSHA256: manifestDigest,
311
- SourceEntryCount: entryCount,
312
- CapturedAllowlistSHA256: hex.EncodeToString(allowlistDigest[:]),
313
- RestoredPaths: changed,
314
- WindowClosed: true,
315
- }
316
- receiptData, receiptName, err := renderReconcileAbortReceipt(receipt)
317
- if err != nil {
318
- fmt.Fprintf(stderr, "reconcile: cannot render abort receipt: %v\n", err)
319
- return 6
320
- }
321
- receiptPath := filepath.Join(d, receiptName)
322
- pendingReceipt, err := stageContentAddressedReceipt(receiptPath, receiptData)
323
- if err != nil {
324
- fmt.Fprintf(stderr, "reconcile: cannot stage abort receipt: %v\n", err)
325
- return 6
326
- }
327
- if pendingReceipt != "" {
328
- defer func() { _ = os.Remove(pendingReceipt) }()
329
- }
330
- if err := commitContentAddressedReceipt(receiptPath, pendingReceipt, receiptData); err != nil {
331
- fmt.Fprintf(stderr, "reconcile: source baseline restored, but cannot persist abort receipt; retained window left open: %v\n", err)
332
- return 6
333
- }
334
- if err := closeWindow(); err != nil {
335
- fmt.Fprintf(stderr, "reconcile: source baseline restored and receipt persisted, but cannot close rejected slice window: %v\n", err)
336
- return 6
337
- }
338
- fmt.Fprintf(stdout, "reconcile: aborted rejected slice window for %s; restored %d source path(s); receipt %s.\n", slug, len(changed), receiptName)
339
- return 0
340
-
341
- case "restore-check":
342
- _, env, activeObjects, active, err := loadReconcileBaseline(gitRoot, d)
343
- if err != nil {
344
- fmt.Fprintf(stderr, "reconcile: invalid snapshot: %v\n", err)
345
- return 6
346
- }
347
- if !active {
348
- fmt.Fprintln(stderr, `reconcile: no snapshot (.reconcile-base/.reconcile-objects): nothing can be restored.`)
349
- return 6
350
- }
351
- checkedData, err := os.ReadFile(checked)
352
- if err != nil {
353
- fmt.Fprintln(stderr, `reconcile: active slice window has no clean check marker; nothing can be restored.`)
354
- return 6
355
- }
356
- checkedTree := strings.TrimSpace(string(checkedData))
357
- if checkedTree == "" || strings.ContainsAny(checkedTree, " \t\r\n") {
358
- fmt.Fprintf(stderr, "reconcile: invalid %s: expected one tree id\n", reconcileCheckedName)
359
- return 6
360
- }
361
- nowTree, err := worktreeTree(gitRoot, activeObjects, root)
362
- if err != nil {
363
- fmt.Fprintf(stderr, "reconcile: cannot capture worktree for restore: %v\n", err)
364
- return 6
365
- }
366
- changed, err := restoreTreeDelta(gitRoot, env, checkedTree, nowTree)
367
- if err != nil {
368
- fmt.Fprintf(stderr, "reconcile: cannot restore post-check source drift: %v\n", err)
369
- return 6
370
- }
371
- restoredTree, err := worktreeTree(gitRoot, activeObjects, root)
372
- if err != nil {
373
- fmt.Fprintf(stderr, "reconcile: cannot verify restored worktree: %v\n", err)
374
- return 6
375
- }
376
- if restoredTree != checkedTree {
377
- fmt.Fprintln(stderr, "reconcile: restored worktree does not match the last clean check; retained window left open.")
378
- return 6
379
- }
380
- if len(changed) == 0 {
381
- fmt.Fprintln(stdout, "reconcile: no post-check source drift to restore.")
382
- return 0
383
- }
384
- fmt.Fprintln(stdout, "reconcile: restored only the post-check source delta:")
385
- for _, changedPath := range changed {
386
- fmt.Fprintf(stdout, " - %s\n", changedPath)
387
- }
388
- return 0
389
-
390
- case "check":
391
- baseTree, env, _, active, err := loadReconcileBaseline(gitRoot, d)
392
- if err != nil {
393
- fmt.Fprintf(stderr, "reconcile: invalid snapshot: %v\n", err)
394
- return 6
395
- }
396
- if !active {
397
- fmt.Fprintln(stderr, `reconcile: no snapshot (.reconcile-base/.reconcile-objects): run "devrites-engine reconcile snapshot" before dispatch.`)
398
- return 6
399
- }
400
-
401
- allowed, err := readWrightAllowlist(gitRoot, capturedAllowlist)
402
- if err != nil {
403
- fmt.Fprintf(stderr, "reconcile: invalid captured allowlist: %v\n", err)
404
- return 6
405
- }
406
- beforeDevritesPath := devritesSnapshot
407
- if _, err := os.Lstat(wrightState); err == nil {
408
- beforeDevritesPath = wrightState
409
- } else if !os.IsNotExist(err) {
410
- fmt.Fprintf(stderr, "reconcile: invalid wright-start .devrites snapshot: %v\n", err)
411
- return 6
412
- }
413
- beforeDevrites, err := readDevritesState(beforeDevritesPath)
414
- if err != nil {
415
- fmt.Fprintf(stderr, "reconcile: invalid .devrites snapshot: %v\n", err)
416
- return 6
417
- }
418
- beforeDevrites = withoutRootOwnedOperationalState(beforeDevrites)
419
- afterDevrites, err := captureReconcileDevritesState(root, devritesSnapshot, objects, checked, wrightState)
420
- if err != nil {
421
- fmt.Fprintf(stderr, "reconcile: cannot compare .devrites: %v\n", err)
422
- return 6
423
- }
424
-
425
- nowTree, err := worktreeTree(gitRoot, objects, root)
426
- if err != nil {
427
- fmt.Fprintf(stderr, "reconcile: cannot capture worktree: %v\n", err)
428
- return 6
429
- }
430
- if checkedData, checkedErr := os.ReadFile(checked); checkedErr == nil {
431
- checkedTree := strings.TrimSpace(string(checkedData))
432
- if checkedTree == "" || strings.ContainsAny(checkedTree, " \t\r\n") {
433
- fmt.Fprintf(stderr, "reconcile: invalid %s: expected one tree id\n", reconcileCheckedName)
434
- return 6
435
- }
436
- if checkedTree != nowTree {
437
- changedAfterCheck, err := changedTreePaths(gitRoot, env, checkedTree, nowTree)
438
- if err != nil {
439
- fmt.Fprintf(stderr, "reconcile: cannot compare with the last clean check: %v\n", err)
440
- return 6
441
- }
442
- sort.Strings(changedAfterCheck)
443
- fmt.Fprintln(stderr, "reconcile: STOP: source changed after the last clean check; root-owned artifact gates must not edit source:")
444
- for _, changedPath := range changedAfterCheck {
445
- fmt.Fprintf(stderr, " - %s\n", changedPath)
446
- }
447
- return 5
448
- }
449
- } else if !os.IsNotExist(checkedErr) {
450
- fmt.Fprintf(stderr, "reconcile: cannot read %s: %v\n", reconcileCheckedName, checkedErr)
451
- return 6
452
- }
453
- changed, err := changedTreePaths(gitRoot, env, baseTree, nowTree)
454
- if err != nil {
455
- fmt.Fprintf(stderr, "reconcile: cannot compare worktree: %v\n", err)
456
- return 6
457
- }
458
-
459
- violations := map[string]struct{}{}
460
- for _, changedPath := range changed {
461
- // Canonical DevRites state is fingerprinted below. It may be tracked
462
- // or unignored, so do not treat the same path as source as well.
463
- if changedPath == ".devrites" || strings.HasPrefix(changedPath, reconcileDevritesPathPrefix) {
464
- continue
465
- }
466
- if _, ok := allowed[changedPath]; !ok {
467
- violations[changedPath] = struct{}{}
468
- }
469
- }
470
- for _, changedPath := range changedDevritesPaths(beforeDevrites, afterDevrites) {
471
- violations[reconcileDevritesPathPrefix+changedPath] = struct{}{}
472
- }
473
-
474
- if len(violations) > 0 {
475
- paths := make([]string, 0, len(violations))
476
- for changedPath := range violations {
477
- paths = append(paths, changedPath)
478
- }
479
- sort.Strings(paths)
480
- fmt.Fprintf(stderr, "reconcile: STOP: %d path(s) changed outside the orchestrator-authorized wright allowlist (A1 breach):\n", len(paths))
481
- for _, changedPath := range paths {
482
- fmt.Fprintf(stderr, " - %s\n", changedPath)
483
- }
484
- fmt.Fprintln(stderr, "Reject this writer result. Preserve pre-snapshot user changes, restore only the unauthorized slice delta, then re-dispatch with an exact allowlist.")
485
- return 5
486
- }
487
- if err := os.WriteFile(checked, []byte(nowTree+"\n"), 0o600); err != nil {
488
- fmt.Fprintf(stderr, "reconcile: cannot record clean check: %v\n", err)
489
- return 6
490
- }
491
- fmt.Fprintln(stdout, `reconcile: OK: every slice change matches the orchestrator allowlist; baseline retained for post-slice gates (run "reconcile close" afterward).`)
492
- return 0
493
-
494
- default:
495
- fmt.Fprintln(stderr, "reconcile: usage: devrites-engine reconcile <snapshot|check|restore-check|abort|close> [slug]")
496
- return 6
497
- }
498
- }
499
-
500
- // CaptureReconcileWrightBoundary records canonical state at the confirmed
501
- // wright start. The original source baseline remains untouched; reconciliation
502
- // can therefore distinguish root recovery records that predate the writer from
503
- // canonical mutations made while the writer is active.
504
- func CaptureReconcileWrightBoundary(root, slug string) error {
505
- d := featureDir(root, slug)
506
- if slug == "" || !isDir(d) {
507
- return fmt.Errorf("no active workspace")
508
- }
509
- base := filepath.Join(d, reconcileBaseName)
510
- capturedAllowlist := filepath.Join(d, reconcileAllowlistName)
511
- devritesSnapshot := filepath.Join(d, reconcileDevritesName)
512
- objects := filepath.Join(d, reconcileObjectsName)
513
- checked := filepath.Join(d, reconcileCheckedName)
514
- wrightState := filepath.Join(d, reconcileWrightStateName)
515
-
516
- for _, filename := range []string{base, capturedAllowlist} {
517
- info, err := os.Lstat(filename)
518
- if err != nil || !info.Mode().IsRegular() {
519
- if err == nil {
520
- err = fmt.Errorf("not a regular file")
521
- }
522
- return fmt.Errorf("invalid reconcile window %s: %w", filepath.Base(filename), err)
523
- }
524
- }
525
- if !isDir(objects) {
526
- return fmt.Errorf("invalid reconcile window %s: not a directory", filepath.Base(objects))
527
- }
528
- if _, err := readDevritesState(devritesSnapshot); err != nil {
529
- return fmt.Errorf("invalid reconcile window %s: %w", filepath.Base(devritesSnapshot), err)
530
- }
531
- state, err := captureReconcileDevritesState(
532
- root,
533
- devritesSnapshot,
534
- objects,
535
- checked,
536
- wrightState,
537
- )
538
- if err != nil {
539
- return err
540
- }
541
- return writeDevritesState(wrightState, state)
542
- }
543
-
544
- func wrightAllowlistPath(gitRoot, workspace string) (string, error) {
545
- configured := os.Getenv(wrightAllowlistFileEnv)
546
- var filename string
547
- if configured == "" {
548
- filename = filepath.Join(workspace, defaultWrightAllowlistName)
549
- } else if filepath.IsAbs(configured) {
550
- filename = filepath.Clean(configured)
551
- } else {
552
- filename = filepath.Join(gitRoot, filepath.FromSlash(configured))
553
- }
554
- if !safepath.WithinResolved(filename, workspace) {
555
- return "", fmt.Errorf("path must remain inside the active workspace")
556
- }
557
- return filename, nil
558
- }
559
-
560
- func readWrightAllowlist(gitRoot, filename string) (map[string]struct{}, error) {
561
- data, err := os.ReadFile(filename)
562
- if err != nil {
563
- return nil, err
564
- }
565
- return parseWrightAllowlist(gitRoot, data)
566
- }
567
-
568
- func parseWrightAllowlist(gitRoot string, data []byte) (map[string]struct{}, error) {
569
- allowed := map[string]struct{}{}
570
- for lineNumber, candidate := range splitLinesNoTrailing(data) {
571
- line := lineNumber + 1
572
- switch {
573
- case candidate == "":
574
- return nil, fmt.Errorf("line %d is empty", line)
575
- case strings.TrimSpace(candidate) != candidate:
576
- return nil, fmt.Errorf("line %d is not normalized: %q", line, candidate)
577
- case strings.Contains(candidate, `\`):
578
- return nil, fmt.Errorf("line %d uses a non-Git path separator: %q", line, candidate)
579
- case filepath.IsAbs(candidate) || path.IsAbs(candidate) || windowsAbsolutePath(candidate):
580
- return nil, fmt.Errorf("line %d is absolute: %q", line, candidate)
581
- case candidate == ".." || strings.HasPrefix(candidate, "../"):
582
- return nil, fmt.Errorf("line %d traverses outside the repository: %q", line, candidate)
583
- case candidate == "." || path.Clean(candidate) != candidate:
584
- return nil, fmt.Errorf("line %d is not normalized: %q", line, candidate)
585
- case candidate == ".devrites" || strings.HasPrefix(candidate, reconcileDevritesPathPrefix):
586
- return nil, fmt.Errorf("line %d targets orchestrator-owned .devrites state: %q", line, candidate)
587
- }
588
- if _, duplicate := allowed[candidate]; duplicate {
589
- return nil, fmt.Errorf("line %d duplicates %q", line, candidate)
590
- }
591
- fullPath := filepath.Join(gitRoot, filepath.FromSlash(candidate))
592
- if !safepath.WithinResolved(fullPath, gitRoot) {
593
- return nil, fmt.Errorf("line %d escapes the repository through a symlink: %q", line, candidate)
594
- }
595
- info, err := os.Stat(fullPath)
596
- switch {
597
- case err == nil && info.IsDir():
598
- return nil, fmt.Errorf("line %d names a directory, not a file: %q", line, candidate)
599
- case err != nil && !os.IsNotExist(err):
600
- return nil, fmt.Errorf("line %d cannot inspect %q: %w", line, candidate, err)
601
- }
602
- allowed[candidate] = struct{}{}
603
- }
604
- return allowed, nil
605
- }
606
-
607
- func windowsAbsolutePath(candidate string) bool {
608
- return len(candidate) >= 3 &&
609
- ((candidate[0] >= 'A' && candidate[0] <= 'Z') || (candidate[0] >= 'a' && candidate[0] <= 'z')) &&
610
- candidate[1] == ':' && (candidate[2] == '/' || candidate[2] == '\\')
611
- }
612
-
613
- func renderWrightAllowlist(allowed map[string]struct{}) []byte {
614
- paths := make([]string, 0, len(allowed))
615
- for allowedPath := range allowed {
616
- paths = append(paths, allowedPath)
617
- }
618
- sort.Strings(paths)
619
- if len(paths) == 0 {
620
- return nil
621
- }
622
- return []byte(strings.Join(paths, "\n") + "\n")
623
- }
624
-
625
- type devritesStateEntry struct {
626
- Path string `json:"path"`
627
- Fingerprint string `json:"fingerprint"`
628
- }
629
-
630
- // captureReconcileDevritesState keeps Forge's typed lifecycle manifest private
631
- // while continuing to fingerprint every other .forge path, including the human
632
- // forge-report.md. A manifest is ignored only after the Forge owner validates
633
- // its unique run, derived path, and primary root.
634
- func captureReconcileDevritesState(root string, ignoredPaths ...string) ([]devritesStateEntry, error) {
635
- manifests, err := validatedForgeManifestPaths(root)
636
- if err != nil {
637
- return nil, err
638
- }
639
- return captureDevritesState(root, append(ignoredPaths, manifests...)...)
640
- }
641
-
642
- func validatedForgeManifestPaths(root string) ([]string, error) {
643
- work := filepath.Join(root, "work")
644
- info, err := os.Lstat(work)
645
- if os.IsNotExist(err) {
646
- return nil, nil
647
- }
648
- if err != nil {
649
- return nil, err
650
- }
651
- if !info.IsDir() || info.Mode()&os.ModeSymlink != 0 {
652
- return nil, nil
653
- }
654
- slugs, err := os.ReadDir(work)
655
- if err != nil {
656
- return nil, err
657
- }
658
- var paths []string
659
- for _, slug := range slugs {
660
- if !slug.IsDir() || slug.Type()&os.ModeSymlink != 0 {
661
- continue
662
- }
663
- forgeDir := filepath.Join(work, slug.Name(), ".forge")
664
- forgeInfo, err := os.Lstat(forgeDir)
665
- if os.IsNotExist(err) {
666
- continue
667
- }
668
- if err != nil {
669
- return nil, err
670
- }
671
- if !forgeInfo.IsDir() || forgeInfo.Mode()&os.ModeSymlink != 0 {
672
- continue
673
- }
674
- runs, err := os.ReadDir(forgeDir)
675
- if err != nil {
676
- return nil, err
677
- }
678
- for _, run := range runs {
679
- if !run.IsDir() || run.Type()&os.ModeSymlink != 0 {
680
- continue
681
- }
682
- manifestPath := filepath.Join(forgeDir, run.Name(), "manifest.json")
683
- manifestInfo, err := os.Lstat(manifestPath)
684
- if os.IsNotExist(err) {
685
- continue
686
- }
687
- if err != nil {
688
- return nil, err
689
- }
690
- if !manifestInfo.Mode().IsRegular() {
691
- continue
692
- }
693
- manifest, validatedPath, err := forge.Load(filepath.Dir(root), run.Name())
694
- if err != nil {
695
- return nil, fmt.Errorf("reconcile: invalid Forge manifest %s: %w", manifestPath, err)
696
- }
697
- if filepath.Clean(validatedPath) != filepath.Clean(manifestPath) || manifest.FeatureSlug != slug.Name() {
698
- return nil, fmt.Errorf("reconcile: Forge manifest ownership mismatch: %s", manifestPath)
699
- }
700
- paths = append(paths, validatedPath)
701
- }
702
- }
703
- sort.Strings(paths)
704
- return paths, nil
705
- }
706
-
707
- func captureDevritesState(root string, ignoredPaths ...string) ([]devritesStateEntry, error) {
708
- ignored := make(map[string]struct{}, len(ignoredPaths))
709
- for _, ignoredPath := range ignoredPaths {
710
- rel, err := filepath.Rel(root, ignoredPath)
711
- if err != nil {
712
- return nil, err
713
- }
714
- ignored[filepath.Clean(rel)] = struct{}{}
715
- }
716
-
717
- var state []devritesStateEntry
718
- err := filepath.WalkDir(root, func(filename string, entry os.DirEntry, walkErr error) error {
719
- if walkErr != nil {
720
- return walkErr
721
- }
722
- if filename == root {
723
- return nil
724
- }
725
- rel, err := filepath.Rel(root, filename)
726
- if err != nil {
727
- return err
728
- }
729
- rel = filepath.Clean(rel)
730
- if _, skip := ignored[rel]; skip {
731
- if entry.IsDir() {
732
- return filepath.SkipDir
733
- }
734
- return nil
735
- }
736
- operational, err := reconcileRootOwnedOperationalFile(filename, rel, entry)
737
- if err != nil {
738
- return err
739
- }
740
- if operational {
741
- return nil
742
- }
743
-
744
- fingerprint, err := devritesFingerprint(filename, entry)
745
- if err != nil {
746
- return err
747
- }
748
- state = append(state, devritesStateEntry{
749
- Path: filepath.ToSlash(rel),
750
- Fingerprint: fingerprint,
751
- })
752
- return nil
753
- })
754
- if err != nil {
755
- return nil, err
756
- }
757
- sort.Slice(state, func(i, j int) bool { return state[i].Path < state[j].Path })
758
- return state, nil
759
- }
760
-
761
- // reconcileRootOwnedOperationalFile excludes engine/root-owned operational
762
- // records from the writer delta. Their owning hooks/gates validate them where
763
- // needed; root activity must not be attributed to the slice writer.
764
- func reconcileRootOwnedOperationalFile(filename, rel string, entry os.DirEntry) (bool, error) {
765
- if !entry.Type().IsRegular() || !reconcileRootOwnedOperationalPath(rel) {
766
- return false, nil
767
- }
768
- rel = filepath.ToSlash(filepath.Clean(rel))
769
- parts := strings.Split(rel, "/")
770
- if len(parts) == 3 && parts[2] == recoveryAttemptsFile {
771
- if _, err := readRecoveryAttempts(filename); err != nil {
772
- return false, fmt.Errorf("validate root-owned recovery ledger %s: %w", rel, err)
773
- }
774
- }
775
- if len(parts) == 3 && reconcileAbortReceiptName.MatchString(parts[2]) {
776
- data, err := os.ReadFile(filename)
777
- if err != nil {
778
- return false, fmt.Errorf("read reconcile abort receipt %s: %w", rel, err)
779
- }
780
- match := reconcileAbortReceiptName.FindStringSubmatch(parts[2])
781
- digest := sha256.Sum256(data)
782
- if hex.EncodeToString(digest[:]) != match[1] {
783
- return false, fmt.Errorf("validate reconcile abort receipt %s: content digest does not match filename", rel)
784
- }
785
- var receipt reconcileAbortReceipt
786
- if err := json.Unmarshal(data, &receipt); err != nil {
787
- return false, fmt.Errorf("validate reconcile abort receipt %s: %w", rel, err)
788
- }
789
- if receipt.SchemaVersion != reconcileAbortSchema || !receipt.WindowClosed ||
790
- receipt.Slug != parts[1] || !reconcileObjectID.MatchString(receipt.BaselineTree) ||
791
- !reconcileSHA256.MatchString(receipt.SourceManifestSHA256) ||
792
- !reconcileSHA256.MatchString(receipt.CapturedAllowlistSHA256) ||
793
- receipt.SourceEntryCount < 0 || !validReceiptPaths(receipt.RestoredPaths) {
794
- return false, fmt.Errorf("validate reconcile abort receipt %s: invalid receipt fields", rel)
795
- }
796
- }
797
- return true, nil
798
- }
799
-
800
- func validReceiptPaths(paths []string) bool {
801
- previous := ""
802
- for _, receiptPath := range paths {
803
- if receiptPath == "" || receiptPath == "." || receiptPath == ".devrites" ||
804
- strings.HasPrefix(receiptPath, reconcileDevritesPathPrefix) ||
805
- path.Clean(receiptPath) != receiptPath ||
806
- (previous != "" && receiptPath <= previous) {
807
- return false
808
- }
809
- previous = receiptPath
810
- }
811
- return true
812
- }
813
-
814
- func reconcileRootOwnedOperationalPath(rel string) bool {
815
- rel = filepath.ToSlash(filepath.Clean(rel))
816
- if rel == "timeline.jsonl" {
817
- return true
818
- }
819
- parts := strings.Split(rel, "/")
820
- if len(parts) != 3 ||
821
- (parts[0] != "work" && parts[0] != "features") ||
822
- !slugToken.MatchString(parts[1]) {
823
- return false
824
- }
825
- switch parts[2] {
826
- case "events.jsonl",
827
- recoveryAttemptsFile,
828
- ".red",
829
- "handoff.md",
830
- ".a1-guard.log",
831
- ".reviewer-ro.log",
832
- ".stop-gate.log",
833
- ".wright-scope.log":
834
- return true
835
- default:
836
- return reconcileAbortReceiptName.MatchString(parts[2])
837
- }
838
- }
839
-
840
- func withoutRootOwnedOperationalState(state []devritesStateEntry) []devritesStateEntry {
841
- filtered := state[:0]
842
- for _, item := range state {
843
- if strings.HasPrefix(item.Fingerprint, "file:") &&
844
- reconcileRootOwnedOperationalPath(item.Path) {
845
- continue
846
- }
847
- filtered = append(filtered, item)
848
- }
849
- return filtered
850
- }
851
-
852
- func devritesFingerprint(filename string, entry os.DirEntry) (string, error) {
853
- info, err := entry.Info()
854
- if err != nil {
855
- return "", err
856
- }
857
- mode := info.Mode()
858
- switch {
859
- case mode.IsRegular():
860
- data, err := os.ReadFile(filename)
861
- if err != nil {
862
- return "", err
863
- }
864
- sum := sha256.Sum256(data)
865
- return fmt.Sprintf("file:%#o:%s", mode.Perm(), hex.EncodeToString(sum[:])), nil
866
- case mode.IsDir():
867
- return fmt.Sprintf("dir:%#o", mode.Perm()), nil
868
- case mode&os.ModeSymlink != 0:
869
- target, err := os.Readlink(filename)
870
- if err != nil {
871
- return "", err
872
- }
873
- sum := sha256.Sum256([]byte(target))
874
- return fmt.Sprintf("symlink:%s", hex.EncodeToString(sum[:])), nil
875
- default:
876
- return fmt.Sprintf("other:%s", mode.String()), nil
877
- }
878
- }
879
-
880
- func writeDevritesState(filename string, state []devritesStateEntry) error {
881
- data, err := json.Marshal(state)
882
- if err != nil {
883
- return err
884
- }
885
- data = append(data, '\n')
886
- return os.WriteFile(filename, data, 0o600)
887
- }
888
-
889
- func readDevritesState(filename string) ([]devritesStateEntry, error) {
890
- data, err := os.ReadFile(filename)
891
- if err != nil {
892
- return nil, err
893
- }
894
- var state []devritesStateEntry
895
- if err := json.Unmarshal(data, &state); err != nil {
896
- return nil, err
897
- }
898
- seen := map[string]struct{}{}
899
- for i, entry := range state {
900
- if entry.Path == "" || entry.Fingerprint == "" ||
901
- path.Clean(entry.Path) != entry.Path || path.IsAbs(entry.Path) ||
902
- entry.Path == ".." || strings.HasPrefix(entry.Path, "../") || strings.Contains(entry.Path, `\`) {
903
- return nil, fmt.Errorf("entry %d is invalid", i+1)
904
- }
905
- if _, duplicate := seen[entry.Path]; duplicate {
906
- return nil, fmt.Errorf("entry %d duplicates %q", i+1, entry.Path)
907
- }
908
- seen[entry.Path] = struct{}{}
909
- }
910
- return state, nil
911
- }
912
-
913
- func changedDevritesPaths(before, after []devritesStateEntry) []string {
914
- beforeMap := make(map[string]string, len(before))
915
- afterMap := make(map[string]string, len(after))
916
- for _, entry := range before {
917
- beforeMap[entry.Path] = entry.Fingerprint
918
- }
919
- for _, entry := range after {
920
- afterMap[entry.Path] = entry.Fingerprint
921
- }
922
- changed := map[string]struct{}{}
923
- for statePath, fingerprint := range beforeMap {
924
- if afterMap[statePath] != fingerprint {
925
- changed[statePath] = struct{}{}
926
- }
927
- }
928
- for statePath, fingerprint := range afterMap {
929
- if beforeMap[statePath] != fingerprint {
930
- changed[statePath] = struct{}{}
931
- }
932
- }
933
- paths := make([]string, 0, len(changed))
934
- for statePath := range changed {
935
- paths = append(paths, statePath)
936
- }
937
- sort.Strings(paths)
938
- return paths
939
- }
940
-
941
- // restoreTreeDelta materializes the last clean tree in a private directory and
942
- // copies back only paths that changed afterward. It never touches the real Git
943
- // index and rejects parent symlink escapes before deleting or writing.
944
- func restoreTreeDelta(gitRoot string, env []string, checkedTree, currentTree string) ([]string, error) {
945
- changed, err := changedTreePaths(gitRoot, env, checkedTree, currentTree)
946
- if err != nil {
947
- return nil, err
948
- }
949
- sort.Strings(changed)
950
- if len(changed) == 0 {
951
- return nil, nil
952
- }
953
- if err := restoreTreePaths(gitRoot, env, checkedTree, changed); err != nil {
954
- return nil, err
955
- }
956
- return changed, nil
957
- }
958
-
959
- func restoreTreePaths(gitRoot string, env []string, targetTree string, changed []string) error {
960
- materialized, err := os.MkdirTemp("", "devrites-reconcile-restore-*")
961
- if err != nil {
962
- return fmt.Errorf("create restore directory: %w", err)
963
- }
964
- defer func() { _ = os.RemoveAll(materialized) }()
965
- materializedTree := filepath.Join(materialized, "tree")
966
- if err := os.MkdirAll(materializedTree, 0o700); err != nil {
967
- return fmt.Errorf("create restore tree: %w", err)
968
- }
969
-
970
- type restoreEntry struct {
971
- path, target, parent, source string
972
- mode os.FileMode
973
- symlink, exists bool
974
- }
975
- entries := make([]restoreEntry, 0, len(changed))
976
- for _, changedPath := range changed {
977
- if changedPath == "." || changedPath == ".devrites" ||
978
- strings.HasPrefix(changedPath, reconcileDevritesPathPrefix) ||
979
- path.Clean(changedPath) != changedPath {
980
- return fmt.Errorf("unsafe restore path %q", changedPath)
981
- }
982
- target := filepath.Join(gitRoot, filepath.FromSlash(changedPath))
983
- targetParent := filepath.Dir(target)
984
- if !safepath.WithinResolved(targetParent, gitRoot) {
985
- return fmt.Errorf("restore parent escapes repository through a symlink: %s", changedPath)
986
- }
987
- if targetInfo, targetErr := os.Lstat(target); targetErr == nil {
988
- if targetInfo.IsDir() {
989
- return fmt.Errorf("refusing to remove live directory at restore path %s", changedPath)
990
- }
991
- } else if !os.IsNotExist(targetErr) {
992
- return fmt.Errorf("inspect restore target %s: %w", changedPath, targetErr)
993
- }
994
-
995
- treeEntry, err := reconcileGitOutput(
996
- gitRoot, env, "ls-tree", "-z", targetTree, "--", ":(top,literal)"+changedPath,
997
- )
998
- if err != nil {
999
- return err
1000
- }
1001
- if len(treeEntry) == 0 {
1002
- entries = append(entries, restoreEntry{path: changedPath, target: target, parent: targetParent})
1003
- continue
1004
- }
1005
- record := strings.TrimSuffix(string(treeEntry), "\x00")
1006
- if strings.Contains(record, "\x00") {
1007
- return fmt.Errorf("ambiguous tree entry for %s", changedPath)
1008
- }
1009
- header, entryPath, ok := strings.Cut(record, "\t")
1010
- fields := strings.Fields(header)
1011
- if !ok || len(fields) != 3 || entryPath != changedPath {
1012
- return fmt.Errorf("invalid tree entry for %s", changedPath)
1013
- }
1014
- if fields[0] == "160000" || fields[1] == "commit" {
1015
- return fmt.Errorf("cannot safely restore changed submodule worktree %s", changedPath)
1016
- }
1017
- entry := restoreEntry{
1018
- path: changedPath,
1019
- target: target,
1020
- parent: targetParent,
1021
- source: filepath.Join(materializedTree, filepath.FromSlash(changedPath)),
1022
- exists: true,
1023
- symlink: fields[0] == "120000",
1024
- }
1025
- switch fields[0] {
1026
- case "100644":
1027
- entry.mode = 0o644
1028
- case "100755":
1029
- entry.mode = 0o755
1030
- case "120000":
1031
- default:
1032
- return fmt.Errorf("unsupported tree mode %s for %s", fields[0], changedPath)
1033
- }
1034
- if err := os.MkdirAll(filepath.Dir(entry.source), 0o700); err != nil {
1035
- return fmt.Errorf("create materialized parent for %s: %w", changedPath, err)
1036
- }
1037
- source, err := os.OpenFile(entry.source, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o600)
1038
- if err != nil {
1039
- return fmt.Errorf("create materialized source %s: %w", changedPath, err)
1040
- }
1041
- writeErr := runGitCommandToWriter(gitRoot, env, source, "cat-file", "blob", fields[2])
1042
- closeErr := source.Close()
1043
- if writeErr != nil {
1044
- return fmt.Errorf("materialize source %s: %w", changedPath, writeErr)
1045
- }
1046
- if closeErr != nil {
1047
- return fmt.Errorf("close materialized source %s: %w", changedPath, closeErr)
1048
- }
1049
- entries = append(entries, entry)
1050
- }
1051
-
1052
- for _, entry := range entries {
1053
- if err := os.RemoveAll(entry.target); err != nil {
1054
- return fmt.Errorf("remove source path %s: %w", entry.path, err)
1055
- }
1056
- if !entry.exists {
1057
- continue
1058
- }
1059
- if err := os.MkdirAll(entry.parent, 0o755); err != nil {
1060
- return fmt.Errorf("create restore parent for %s: %w", entry.path, err)
1061
- }
1062
- if entry.symlink {
1063
- link, err := os.ReadFile(entry.source)
1064
- if err != nil {
1065
- return fmt.Errorf("read clean symlink %s: %w", entry.path, err)
1066
- }
1067
- if err := os.Symlink(string(link), entry.target); err != nil {
1068
- return fmt.Errorf("restore symlink %s: %w", entry.path, err)
1069
- }
1070
- continue
1071
- }
1072
- source, err := os.Open(entry.source)
1073
- if err != nil {
1074
- return fmt.Errorf("open clean file %s: %w", entry.path, err)
1075
- }
1076
- target, err := os.OpenFile(entry.target, os.O_WRONLY|os.O_CREATE|os.O_EXCL, entry.mode)
1077
- if err != nil {
1078
- _ = source.Close()
1079
- return fmt.Errorf("create restored file %s: %w", entry.path, err)
1080
- }
1081
- _, copyErr := io.Copy(target, source)
1082
- sourceCloseErr := source.Close()
1083
- targetCloseErr := target.Close()
1084
- if copyErr != nil {
1085
- return fmt.Errorf("restore file %s: %w", entry.path, copyErr)
1086
- }
1087
- if sourceCloseErr != nil {
1088
- return fmt.Errorf("close clean file %s: %w", entry.path, sourceCloseErr)
1089
- }
1090
- if targetCloseErr != nil {
1091
- return fmt.Errorf("close restored file %s: %w", entry.path, targetCloseErr)
1092
- }
1093
- }
1094
- return nil
1095
- }
1096
-
1097
- func sourceTreePaths(paths []string) []string {
1098
- source := make([]string, 0, len(paths))
1099
- for _, changedPath := range paths {
1100
- if changedPath == ".devrites" || strings.HasPrefix(changedPath, reconcileDevritesPathPrefix) {
1101
- continue
1102
- }
1103
- source = append(source, changedPath)
1104
- }
1105
- sort.Strings(source)
1106
- return source
1107
- }
1108
-
1109
- func sourceTreeManifest(gitRoot string, env []string, tree string) (string, int, error) {
1110
- out, err := reconcileGitOutput(gitRoot, env, "ls-tree", "-r", "-z", "--full-tree", tree)
1111
- if err != nil {
1112
- return "", 0, err
1113
- }
1114
- hash := sha256.New()
1115
- count := 0
1116
- for _, entry := range strings.Split(string(out), "\x00") {
1117
- if entry == "" {
1118
- continue
1119
- }
1120
- tab := strings.IndexByte(entry, '\t')
1121
- if tab < 0 {
1122
- return "", 0, fmt.Errorf("invalid ls-tree entry")
1123
- }
1124
- entryPath := entry[tab+1:]
1125
- if entryPath == ".devrites" || strings.HasPrefix(entryPath, reconcileDevritesPathPrefix) {
1126
- continue
1127
- }
1128
- _, _ = hash.Write([]byte(entry))
1129
- _, _ = hash.Write([]byte{0})
1130
- count++
1131
- }
1132
- return hex.EncodeToString(hash.Sum(nil)), count, nil
1133
- }
1134
-
1135
- func renderReconcileAbortReceipt(receipt reconcileAbortReceipt) ([]byte, string, error) {
1136
- data, err := json.MarshalIndent(receipt, "", " ")
1137
- if err != nil {
1138
- return nil, "", err
1139
- }
1140
- data = append(data, '\n')
1141
- digest := sha256.Sum256(data)
1142
- name := fmt.Sprintf(".reconcile-abort-%x.json", digest)
1143
- return data, name, nil
1144
- }
1145
-
1146
- func stageContentAddressedReceipt(filename string, data []byte) (string, error) {
1147
- existing, readErr := os.ReadFile(filename)
1148
- if readErr == nil {
1149
- if string(existing) != string(data) {
1150
- return "", fmt.Errorf("existing content-addressed receipt does not match %s", filepath.Base(filename))
1151
- }
1152
- return "", nil
1153
- }
1154
- if !os.IsNotExist(readErr) {
1155
- return "", readErr
1156
- }
1157
- pending := strings.TrimSuffix(filename, ".json") + ".pending"
1158
- file, err := os.OpenFile(pending, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o600)
1159
- if err != nil {
1160
- return "", err
1161
- }
1162
- if _, err := file.Write(data); err != nil {
1163
- _ = file.Close()
1164
- _ = os.Remove(pending)
1165
- return "", err
1166
- }
1167
- if err := file.Sync(); err != nil {
1168
- _ = file.Close()
1169
- _ = os.Remove(pending)
1170
- return "", err
1171
- }
1172
- if err := file.Close(); err != nil {
1173
- _ = os.Remove(pending)
1174
- return "", err
1175
- }
1176
- if err := os.Chmod(pending, 0o444); err != nil {
1177
- _ = os.Remove(pending)
1178
- return "", err
1179
- }
1180
- return pending, nil
1181
- }
1182
-
1183
- func commitContentAddressedReceipt(filename, pending string, data []byte) error {
1184
- if pending == "" {
1185
- return nil
1186
- }
1187
- if err := os.Rename(pending, filename); err != nil {
1188
- return err
1189
- }
1190
- persisted, err := os.ReadFile(filename)
1191
- if err != nil {
1192
- return err
1193
- }
1194
- if string(persisted) != string(data) {
1195
- return fmt.Errorf("persisted abort receipt does not match staged content")
1196
- }
1197
- return nil
1198
- }
1199
-
1200
- // worktreeTree captures the working tree (tracked + untracked, .gitignore
1201
- // honored) as a git tree object via a throwaway index, excluding separately
1202
- // fingerprinted DevRites roots. Seeding from HEAD keeps committed paths tracked
1203
- // even when a later ignore rule matches them. The user's real index is never
1204
- // touched.
1205
- //
1206
- // A failing "git add -A" must not be ignored: write-tree would still succeed
1207
- // against the seeded throwaway index. Both the snapshot and the check could then
1208
- // record the same stale tree, and the gate would report OK on precisely the A1
1209
- // breach it exists to catch. Surface the error and let the caller fail closed.
1210
- func worktreeTree(gitRoot, objectDir string, excludedRoots ...string) (string, error) {
1211
- idx := filepath.Join(os.TempDir(), fmt.Sprintf("devrites-reconcile-%d.idx", os.Getpid()))
1212
- _ = os.Remove(idx)
1213
- defer func() { _ = os.Remove(idx) }()
1214
- if err := os.MkdirAll(objectDir, 0o700); err != nil {
1215
- return "", fmt.Errorf("create object directory: %w", err)
1216
- }
1217
- env, err := reconcileGitEnv(gitRoot, objectDir)
1218
- if err != nil {
1219
- return "", err
1220
- }
1221
- env = append(env, "GIT_INDEX_FILE="+idx)
1222
-
1223
- _, readErr := runGitCommand(gitRoot, env, "read-tree", "HEAD")
1224
- if readErr != nil {
1225
- readFailure := fmt.Errorf("git read-tree HEAD: %w", readErr)
1226
- // An unborn branch is the one valid reason HEAD cannot seed the index.
1227
- // Distinguish it from a corrupt or unreadable existing ref so the gate
1228
- // still fails closed on real repository errors.
1229
- refOut, symbolicErr := runGitCommand(gitRoot, env, "symbolic-ref", "--quiet", "HEAD")
1230
- if symbolicErr != nil {
1231
- return "", readFailure
1232
- }
1233
- ref := strings.TrimSpace(string(refOut))
1234
- if _, err := runGitCommand(gitRoot, env, "show-ref", "--verify", "--quiet", ref); err == nil {
1235
- return "", readFailure
1236
- } else if code, ok := gitErrorExitCode(err); !ok || code != 1 {
1237
- return "", fmt.Errorf("inspect HEAD ref %s: %w", ref, err)
1238
- }
1239
-
1240
- if _, err := runGitCommand(gitRoot, env, "read-tree", "--empty"); err != nil {
1241
- return "", fmt.Errorf("git read-tree --empty: %w", err)
1242
- }
1243
- }
1244
-
1245
- if _, err := runGitCommand(gitRoot, env, "add", "-A"); err != nil {
1246
- return "", fmt.Errorf("git add -A: %w", err)
1247
- }
1248
- for _, excludedRoot := range excludedRoots {
1249
- rel, err := filepath.Rel(gitRoot, excludedRoot)
1250
- if err != nil || rel == "." || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
1251
- continue
1252
- }
1253
- if _, err := runGitCommand(
1254
- gitRoot, env, "rm", "-r", "--cached", "--ignore-unmatch", "--",
1255
- ":(top,literal)"+filepath.ToSlash(rel),
1256
- ); err != nil {
1257
- return "", fmt.Errorf("exclude %s from worktree snapshot: %w", rel, err)
1258
- }
1259
- }
1260
- if err := rewriteIndexWithRawWorktreeBytes(gitRoot, env); err != nil {
1261
- return "", err
1262
- }
1263
-
1264
- out, err := runGitCommand(gitRoot, env, "write-tree")
1265
- if err != nil {
1266
- return "", fmt.Errorf("git write-tree: %w", err)
1267
- }
1268
- tree := strings.TrimSpace(string(out))
1269
- if tree == "" {
1270
- return "", fmt.Errorf("git write-tree: empty tree sha")
1271
- }
1272
- return tree, nil
1273
- }
1274
-
1275
- func rewriteIndexWithRawWorktreeBytes(gitRoot string, env []string) error {
1276
- out, err := runGitCommand(gitRoot, env, "ls-files", "--stage", "-z")
1277
- if err != nil {
1278
- return fmt.Errorf("list worktree snapshot entries: %w", err)
1279
- }
1280
- type rawEntry struct {
1281
- mode, path string
1282
- }
1283
- var batch, individual []rawEntry
1284
- for _, record := range strings.Split(string(out), "\x00") {
1285
- if record == "" {
1286
- continue
1287
- }
1288
- header, filename, ok := strings.Cut(record, "\t")
1289
- fields := strings.Fields(header)
1290
- if !ok || len(fields) != 3 || fields[2] != "0" {
1291
- return fmt.Errorf("invalid worktree snapshot index entry")
1292
- }
1293
- if fields[0] != "100644" && fields[0] != "100755" {
1294
- continue
1295
- }
1296
- entry := rawEntry{mode: fields[0], path: filename}
1297
- if strings.ContainsRune(filename, '\n') {
1298
- individual = append(individual, entry)
1299
- } else {
1300
- batch = append(batch, entry)
1301
- }
1302
- }
1303
-
1304
- var indexInfo strings.Builder
1305
- if len(batch) > 0 {
1306
- var paths strings.Builder
1307
- for _, entry := range batch {
1308
- paths.WriteString(entry.path)
1309
- paths.WriteByte('\n')
1310
- }
1311
- hashes, err := runGitCommandInput(
1312
- gitRoot, env, []byte(paths.String()),
1313
- "hash-object", "-w", "--no-filters", "--stdin-paths",
1314
- )
1315
- if err != nil {
1316
- return fmt.Errorf("hash raw worktree files: %w", err)
1317
- }
1318
- objectIDs := strings.Fields(string(hashes))
1319
- if len(objectIDs) != len(batch) {
1320
- return fmt.Errorf("hash raw worktree files: got %d object ids for %d paths", len(objectIDs), len(batch))
1321
- }
1322
- for i, entry := range batch {
1323
- if !reconcileObjectID.MatchString(objectIDs[i]) {
1324
- return fmt.Errorf("hash raw worktree file %s: invalid object id", entry.path)
1325
- }
1326
- fmt.Fprintf(&indexInfo, "%s %s\t%s\x00", entry.mode, objectIDs[i], entry.path)
1327
- }
1328
- }
1329
- for _, entry := range individual {
1330
- hash, err := runGitCommand(gitRoot, env, "hash-object", "-w", "--no-filters", "--", entry.path)
1331
- if err != nil {
1332
- return fmt.Errorf("hash raw worktree file %s: %w", entry.path, err)
1333
- }
1334
- objectID := strings.TrimSpace(string(hash))
1335
- if !reconcileObjectID.MatchString(objectID) {
1336
- return fmt.Errorf("hash raw worktree file %s: invalid object id", entry.path)
1337
- }
1338
- fmt.Fprintf(&indexInfo, "%s %s\t%s\x00", entry.mode, objectID, entry.path)
1339
- }
1340
- if indexInfo.Len() == 0 {
1341
- return nil
1342
- }
1343
- if _, err := runGitCommandInput(
1344
- gitRoot, env, []byte(indexInfo.String()),
1345
- "update-index", "-z", "--index-info",
1346
- ); err != nil {
1347
- return fmt.Errorf("record raw worktree files: %w", err)
1348
- }
1349
- return nil
1350
- }
1351
-
1352
- func reconcileGitEnv(gitRoot, objectDir string) ([]string, error) {
1353
- out, err := runGitCommand(gitRoot, nil, "rev-parse", "--git-common-dir")
1354
- if err != nil {
1355
- return nil, fmt.Errorf("git rev-parse --git-common-dir: %w", err)
1356
- }
1357
- commonDir := strings.TrimSpace(string(out))
1358
- if !filepath.IsAbs(commonDir) {
1359
- commonDir = filepath.Join(gitRoot, commonDir)
1360
- }
1361
- alternates := []string{filepath.Join(commonDir, "objects")}
1362
- if inherited := os.Getenv("GIT_OBJECT_DIRECTORY"); inherited != "" {
1363
- alternates = append(alternates, inherited)
1364
- }
1365
- if inherited := os.Getenv("GIT_ALTERNATE_OBJECT_DIRECTORIES"); inherited != "" {
1366
- alternates = append(alternates, inherited)
1367
- }
1368
- return append(os.Environ(),
1369
- "GIT_OBJECT_DIRECTORY="+objectDir,
1370
- "GIT_ALTERNATE_OBJECT_DIRECTORIES="+strings.Join(alternates, string(os.PathListSeparator)),
1371
- ), nil
1372
- }
1373
-
1374
- // loadReconcileBaseline returns the retained slice baseline and its private Git
1375
- // environment. With no lifecycle state at all it reports active=false so gates
1376
- // invoked outside a slice can compare against HEAD. A partial or corrupt
1377
- // lifecycle never silently falls back to HEAD.
1378
- func loadReconcileBaseline(gitRoot, workspace string) (baseTree string, env []string, objectDir string, active bool, err error) {
1379
- basePath := filepath.Join(workspace, reconcileBaseName)
1380
- objectDir = filepath.Join(workspace, reconcileObjectsName)
1381
-
1382
- baseInfo, baseErr := os.Lstat(basePath)
1383
- objectsInfo, objectsErr := os.Lstat(objectDir)
1384
- baseMissing := os.IsNotExist(baseErr)
1385
- objectsMissing := os.IsNotExist(objectsErr)
1386
- if baseMissing && objectsMissing {
1387
- return "", nil, "", false, nil
1388
- }
1389
- if baseErr != nil && !baseMissing {
1390
- return "", nil, "", false, fmt.Errorf("cannot inspect %s: %w", reconcileBaseName, baseErr)
1391
- }
1392
- if objectsErr != nil && !objectsMissing {
1393
- return "", nil, "", false, fmt.Errorf("cannot inspect %s: %w", reconcileObjectsName, objectsErr)
1394
- }
1395
- if baseMissing || objectsMissing {
1396
- return "", nil, "", false, fmt.Errorf("partial lifecycle: %s and %s must both exist", reconcileBaseName, reconcileObjectsName)
1397
- }
1398
- if !baseInfo.Mode().IsRegular() {
1399
- return "", nil, "", false, fmt.Errorf("%s is not a regular file", reconcileBaseName)
1400
- }
1401
- if !objectsInfo.IsDir() {
1402
- return "", nil, "", false, fmt.Errorf("%s is not a directory", reconcileObjectsName)
1403
- }
1404
-
1405
- data, err := os.ReadFile(basePath)
1406
- if err != nil {
1407
- return "", nil, "", false, fmt.Errorf("read %s: %w", reconcileBaseName, err)
1408
- }
1409
- baseTree = strings.TrimSpace(string(data))
1410
- if baseTree == "" || strings.ContainsAny(baseTree, " \t\r\n") {
1411
- return "", nil, "", false, fmt.Errorf("%s does not contain one tree id", reconcileBaseName)
1412
- }
1413
- env, err = reconcileGitEnv(gitRoot, objectDir)
1414
- if err != nil {
1415
- return "", nil, "", false, err
1416
- }
1417
- if _, err := reconcileGitOutput(gitRoot, env, "cat-file", "-e", baseTree+"^{tree}"); err != nil {
1418
- return "", nil, "", false, fmt.Errorf("%s tree is unavailable: %w", reconcileBaseName, err)
1419
- }
1420
- return baseTree, env, objectDir, true, nil
1421
- }
1422
-
1423
- type sliceTreeRange struct {
1424
- base string
1425
- current string
1426
- env []string
1427
- cleanup func()
1428
- }
1429
-
1430
- // captureSliceTreeRange returns an immutable base→current pair. During an open
1431
- // reconcile window it reuses the retained private object database. For a direct
1432
- // standalone gate it captures the current dirty tree in a temporary private
1433
- // object database and compares it with HEAD.
1434
- func captureSliceTreeRange(gitRoot, devritesRoot, workspace string) (sliceTreeRange, error) {
1435
- base, _, objectDir, active, err := loadReconcileBaseline(gitRoot, workspace)
1436
- if err != nil {
1437
- return sliceTreeRange{}, err
1438
- }
1439
- cleanup := func() {}
1440
- if !active {
1441
- objectDir, err = os.MkdirTemp("", "devrites-gate-objects-*")
1442
- if err != nil {
1443
- return sliceTreeRange{}, fmt.Errorf("create temporary object database: %w", err)
1444
- }
1445
- cleanup = func() { _ = os.RemoveAll(objectDir) }
1446
- base = "HEAD"
1447
- }
1448
- current, err := worktreeTree(gitRoot, objectDir, devritesRoot)
1449
- if err != nil {
1450
- cleanup()
1451
- return sliceTreeRange{}, err
1452
- }
1453
- env, err := reconcileGitEnv(gitRoot, objectDir)
1454
- if err != nil {
1455
- cleanup()
1456
- return sliceTreeRange{}, err
1457
- }
1458
- return sliceTreeRange{base: base, current: current, env: env, cleanup: cleanup}, nil
1459
- }
1460
-
1461
- func changedTreePaths(gitRoot string, env []string, baseTree, currentTree string) ([]string, error) {
1462
- out, err := reconcileGitOutput(
1463
- gitRoot,
1464
- env,
1465
- "diff", "--name-only", "--no-renames", "-z", baseTree, currentTree, "--",
1466
- )
1467
- if err != nil {
1468
- return nil, err
1469
- }
1470
- var paths []string
1471
- for rawPath := range strings.SplitSeq(string(out), "\x00") {
1472
- if rawPath != "" {
1473
- paths = append(paths, filepath.ToSlash(filepath.Clean(rawPath)))
1474
- }
1475
- }
1476
- return paths, nil
1477
- }
1478
-
1479
- func treeFileContent(gitRoot string, env []string, tree, filename string) ([]byte, bool, error) {
1480
- entry, err := reconcileGitOutput(gitRoot, env, "ls-tree", "-z", tree, "--", ":(literal)"+filename)
1481
- if err != nil {
1482
- return nil, false, err
1483
- }
1484
- if len(entry) == 0 {
1485
- return nil, false, nil
1486
- }
1487
- out, err := reconcileGitOutput(gitRoot, env, "show", tree+":"+filename)
1488
- if err != nil {
1489
- return nil, false, err
1490
- }
1491
- return out, true, nil
1492
- }
1493
-
1494
- func reconcileGitOutput(gitRoot string, env []string, args ...string) ([]byte, error) {
1495
- return runGitCommand(gitRoot, env, args...)
1496
- }