devrites 3.0.6 → 3.1.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 (472) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +67 -45
  3. package/SECURITY.md +31 -29
  4. package/docs/adr/0001-go-engine-as-control-plane.md +1 -1
  5. package/docs/adr/0002-dual-host-harness.md +1 -1
  6. package/docs/adr/0004-state-schema-phases-sections.md +1 -2
  7. package/docs/adr/0006-clock-seam-and-engine-ci-gates.md +3 -4
  8. package/docs/adr/0009-prebuild-decision-coverage-and-readiness.md +67 -0
  9. package/docs/adr/0010-agent-first-fresh-context-orchestration.md +71 -0
  10. package/docs/adr/0011-define-plan-transition-rights.md +28 -0
  11. package/docs/adr/README.md +3 -0
  12. package/docs/agents/triage-labels.md +7 -7
  13. package/docs/architecture.md +157 -140
  14. package/docs/cli.md +41 -12
  15. package/docs/command-map.md +49 -35
  16. package/docs/engine/agent-contract.md +92 -15
  17. package/docs/engine/commands.md +242 -72
  18. package/docs/engine/state-schema.md +52 -23
  19. package/docs/engine/workspace-schema.md +94 -15
  20. package/docs/extensions.md +1 -1
  21. package/docs/flow.md +80 -50
  22. package/docs/harness-compliance.md +29 -5
  23. package/docs/orchestration.md +107 -79
  24. package/docs/quick-reference.md +7 -3
  25. package/docs/release.md +4 -3
  26. package/docs/skills.md +64 -38
  27. package/docs/usage.md +57 -38
  28. package/engine/cmd/releasepack/main.go +219 -0
  29. package/engine/cmd/releasepack/main_test.go +170 -0
  30. package/engine/commands.go +170 -23
  31. package/engine/git_guard.go +187 -0
  32. package/engine/git_guard_test.go +283 -0
  33. package/engine/hookpolicy.go +53 -55
  34. package/engine/hookpolicy_test.go +91 -1
  35. package/engine/hooks.go +296 -75
  36. package/engine/hooks_events_test.go +276 -6
  37. package/engine/hooks_workspace.go +640 -159
  38. package/engine/internal/devritespaths/paths.go +65 -10
  39. package/engine/internal/devritespaths/paths_test.go +110 -0
  40. package/engine/internal/doctor/doctor.go +153 -23
  41. package/engine/internal/doctor/doctor_test.go +74 -0
  42. package/engine/internal/forge/forge.go +940 -0
  43. package/engine/internal/forge/forge_test.go +576 -0
  44. package/engine/internal/forge/git.go +245 -0
  45. package/engine/internal/forge/liveness_unix.go +48 -0
  46. package/engine/internal/forge/liveness_windows.go +67 -0
  47. package/engine/internal/forge/manifest.go +402 -0
  48. package/engine/internal/gate/gate.go +90 -71
  49. package/engine/internal/gate/gate_test.go +71 -2
  50. package/engine/internal/harness/compliance.go +18 -24
  51. package/engine/internal/harness/harness.go +37 -44
  52. package/engine/internal/harness/harness_test.go +21 -5
  53. package/engine/internal/install/install.go +575 -58
  54. package/engine/internal/install/install_test.go +490 -9
  55. package/engine/internal/iohooks/iohooks.go +350 -59
  56. package/engine/internal/iohooks/iohooks_test.go +421 -1
  57. package/engine/internal/lib/buildreadiness.go +35 -18
  58. package/engine/internal/lib/clarifyreturn.go +91 -0
  59. package/engine/internal/lib/clarifyreturn_test.go +123 -0
  60. package/engine/internal/lib/context.go +54 -15
  61. package/engine/internal/lib/cursor_compat_test.go +8 -0
  62. package/engine/internal/lib/extensions.go +2 -3
  63. package/engine/internal/lib/gitauthority.go +601 -0
  64. package/engine/internal/lib/gitauthority_test.go +346 -0
  65. package/engine/internal/lib/jsonout.go +25 -11
  66. package/engine/internal/lib/jsonout_test.go +16 -0
  67. package/engine/internal/lib/lanes.go +14 -8
  68. package/engine/internal/lib/observability_test.go +358 -0
  69. package/engine/internal/lib/packageexistence.go +151 -35
  70. package/engine/internal/lib/packageexistence_test.go +163 -0
  71. package/engine/internal/lib/progress.go +11 -9
  72. package/engine/internal/lib/provenance.go +462 -0
  73. package/engine/internal/lib/provenance_test.go +154 -0
  74. package/engine/internal/lib/readiness_contract.json +111 -0
  75. package/engine/internal/lib/readinessartifact.go +510 -0
  76. package/engine/internal/lib/readinessartifact_test.go +321 -0
  77. package/engine/internal/lib/reconcile.go +712 -88
  78. package/engine/internal/lib/reconcile_test.go +335 -16
  79. package/engine/internal/lib/recoveryattempts.go +298 -0
  80. package/engine/internal/lib/recoveryattempts_test.go +215 -0
  81. package/engine/internal/lib/resolve.go +23 -19
  82. package/engine/internal/lib/runbook_context_test.go +20 -0
  83. package/engine/internal/lib/session.go +701 -9
  84. package/engine/internal/lib/session_test.go +80 -13
  85. package/engine/internal/lib/testintegrity.go +33 -37
  86. package/engine/internal/lib/testintegrity_test.go +63 -1
  87. package/engine/internal/migrate/migrate.go +81 -1
  88. package/engine/internal/migrate/migrate_test.go +22 -1
  89. package/engine/internal/reason/reason.go +180 -0
  90. package/engine/internal/reason/reason_test.go +35 -0
  91. package/engine/internal/rootfacts/facts.go +466 -0
  92. package/engine/internal/rootfacts/facts_test.go +306 -0
  93. package/engine/internal/safepath/safepath.go +55 -0
  94. package/engine/internal/safepath/safepath_test.go +69 -0
  95. package/engine/internal/safepath/safepath_windows_test.go +26 -0
  96. package/engine/internal/state/clarify_transition.go +165 -0
  97. package/engine/internal/state/clarify_transition_test.go +130 -0
  98. package/engine/internal/state/cmd/workflowmanifest/main.go +12 -8
  99. package/engine/internal/state/cursor.go +58 -0
  100. package/engine/internal/state/cursor_test.go +42 -1
  101. package/engine/internal/state/feature.go +35 -48
  102. package/engine/internal/state/schema.go +90 -37
  103. package/engine/internal/state/snapshot.go +18 -2
  104. package/engine/internal/state/state_test.go +187 -7
  105. package/engine/internal/state/status.go +48 -11
  106. package/engine/internal/state/workflow_manifest.json +231 -50
  107. package/engine/internal/toolpolicy/classifier.go +533 -0
  108. package/engine/internal/toolpolicy/classifier_test.go +424 -0
  109. package/engine/internal/toolpolicy/git.go +616 -0
  110. package/engine/internal/toolpolicy/scanner.go +382 -0
  111. package/engine/main.go +160 -77
  112. package/engine/observability_cli_test.go +52 -0
  113. package/engine/root_routing_test.go +277 -0
  114. package/engine/testdata/golden/TestParityBuildReadiness/arg=approved.golden +1 -1
  115. package/engine/testdata/golden/TestParityBuildReadiness/arg=emptystatus.golden +1 -1
  116. package/engine/testdata/golden/TestParityBuildReadiness/arg=noclarify.golden +1 -0
  117. package/engine/testdata/golden/TestParityBuildReadiness/arg=novet.golden +1 -0
  118. package/engine/testdata/golden/TestParityBuildReadiness/arg=stalevet.golden +1 -0
  119. package/engine/testdata/golden/TestParityBuildReadiness/arg=trailhash.golden +1 -1
  120. package/engine/testdata/golden/TestParityBuildReadiness/arg=trailpipe.golden +1 -1
  121. package/engine/testdata/golden/TestParityBuildReadiness/arg=vetnotready.golden +1 -0
  122. package/engine/testdata/golden/TestParityProgress/arg=allbuilt.golden +1 -1
  123. package/engine/testdata/golden/TestParityProgress/arg=done.golden +1 -1
  124. package/engine/testdata/golden/TestParityProgress/arg=mid.golden +1 -1
  125. package/engine/testdata/golden/TestParityProgress/arg=nophase.golden +1 -1
  126. package/engine/testdata/golden/TestParityProgress/arg=noslice.golden +1 -1
  127. package/engine/testdata/golden/TestParityProgress/arg=plan.golden +1 -1
  128. package/engine/testdata/golden/TestParityProgress/arg=seal.golden +1 -1
  129. package/engine/testdata/golden/TestParityReconcile/check-clean.golden +1 -1
  130. package/engine/testdata/golden/TestParityReconcile/inline-fallback.golden +1 -0
  131. package/engine/testdata/golden/TestParityReconcile/snapshot-no-allowlist.golden +1 -0
  132. package/engine/testdata/golden/TestParityWrightScope/devrites-edit-denied.golden +2 -0
  133. package/engine/testdata/golden/TestParityWrightScope/out-of-scope-enforce-denies.golden +1 -1
  134. package/engine/tests/adr_0011_define_plan_test.go +30 -0
  135. package/engine/tests/budget_test.go +19 -8
  136. package/engine/tests/concurrency_cli_test.go +7 -1
  137. package/engine/tests/doctor_cli_test.go +148 -2
  138. package/engine/tests/forge_cli_test.go +463 -0
  139. package/engine/tests/gate_test.go +63 -8
  140. package/engine/tests/hook_test.go +260 -8
  141. package/engine/tests/hooks_io_test.go +94 -3
  142. package/engine/tests/json_contract_test.go +127 -2
  143. package/engine/tests/migrate_cli_test.go +2 -2
  144. package/engine/tests/parity_buildreadiness_test.go +141 -10
  145. package/engine/tests/parity_learnings_test.go +16 -17
  146. package/engine/tests/parity_reconcile_test.go +20 -17
  147. package/engine/tests/parity_resolve_test.go +12 -11
  148. package/engine/tests/parity_test.go +21 -17
  149. package/pack/.claude/agents/devrites-code-reviewer.md +62 -48
  150. package/pack/.claude/agents/devrites-devex-reviewer.md +69 -53
  151. package/pack/.claude/agents/devrites-doubt-reviewer.md +29 -22
  152. package/pack/.claude/agents/devrites-evidence-scout.md +69 -0
  153. package/pack/.claude/agents/devrites-forge-judge.md +74 -61
  154. package/pack/.claude/agents/devrites-frontend-reviewer.md +48 -39
  155. package/pack/.claude/agents/devrites-performance-reviewer.md +49 -40
  156. package/pack/.claude/agents/devrites-plan-drafter.md +71 -0
  157. package/pack/.claude/agents/devrites-plan-reviewer.md +80 -47
  158. package/pack/.claude/agents/devrites-proof-runner.md +74 -0
  159. package/pack/.claude/agents/devrites-retrospector.md +48 -45
  160. package/pack/.claude/agents/devrites-security-auditor.md +46 -36
  161. package/pack/.claude/agents/devrites-simplifier-reviewer.md +50 -46
  162. package/pack/.claude/agents/devrites-slice-wright.md +153 -165
  163. package/pack/.claude/agents/devrites-spec-reviewer.md +34 -32
  164. package/pack/.claude/agents/devrites-strategy-reviewer.md +63 -34
  165. package/pack/.claude/agents/devrites-test-analyst.md +40 -30
  166. package/pack/.claude/settings.json +2 -1
  167. package/pack/.claude/skills/devrites-audit/SKILL.md +40 -61
  168. package/pack/.claude/skills/devrites-debug-recovery/SKILL.md +24 -13
  169. package/pack/.claude/skills/devrites-debug-recovery/reference/build-the-loop.md +24 -21
  170. package/pack/.claude/skills/devrites-debug-recovery/reference/cleanup-and-classify.md +34 -6
  171. package/pack/.claude/skills/devrites-debug-recovery/reference/instrumentation.md +2 -2
  172. package/pack/.claude/skills/devrites-doubt/SKILL.md +25 -17
  173. package/pack/.claude/skills/devrites-interview/SKILL.md +53 -52
  174. package/pack/.claude/skills/devrites-lib/SKILL.md +11 -9
  175. package/pack/.claude/skills/devrites-lib/reference/intent-map.md +3 -2
  176. package/pack/.claude/skills/devrites-lib/reference/parallel-dispatch.md +82 -137
  177. package/pack/.claude/skills/devrites-lib/reference/reply-contract.md +8 -1
  178. package/pack/.claude/skills/devrites-lib/reference/standards/README.md +38 -55
  179. package/pack/.claude/skills/devrites-lib/reference/standards/afk-hitl.md +53 -27
  180. package/pack/.claude/skills/devrites-lib/reference/standards/agents.md +198 -190
  181. package/pack/.claude/skills/devrites-lib/reference/standards/anti-patterns.md +5 -15
  182. package/pack/.claude/skills/devrites-lib/reference/standards/ci-cd.md +27 -58
  183. package/pack/.claude/skills/devrites-lib/reference/standards/code-review.md +16 -52
  184. package/pack/.claude/skills/devrites-lib/reference/standards/coding-style.md +2 -10
  185. package/pack/.claude/skills/devrites-lib/reference/standards/context-hygiene.md +18 -61
  186. package/pack/.claude/skills/devrites-lib/reference/standards/core.md +23 -18
  187. package/pack/.claude/skills/devrites-lib/reference/standards/deprecation.md +17 -57
  188. package/pack/.claude/skills/devrites-lib/reference/standards/development-workflow.md +4 -33
  189. package/pack/.claude/skills/devrites-lib/reference/standards/git-workflow.md +4 -20
  190. package/pack/.claude/skills/devrites-lib/reference/standards/hooks.md +3 -14
  191. package/pack/.claude/skills/devrites-lib/reference/standards/patterns.md +9 -25
  192. package/pack/.claude/skills/devrites-lib/reference/standards/performance.md +0 -9
  193. package/pack/.claude/skills/devrites-lib/reference/standards/principles.md +1 -3
  194. package/pack/.claude/skills/devrites-lib/reference/standards/security.md +17 -7
  195. package/pack/.claude/skills/devrites-lib/reference/standards/testing.md +1 -27
  196. package/pack/.claude/skills/devrites-lib/reference/standards/tooling.md +46 -50
  197. package/pack/.claude/skills/devrites-lib/reference/workspace-artifact-schema.md +23 -10
  198. package/pack/.claude/skills/devrites-source-driven/SKILL.md +23 -22
  199. package/pack/.claude/skills/rite/SKILL.md +8 -6
  200. package/pack/.claude/skills/rite/reference/menu.md +8 -6
  201. package/pack/.claude/skills/rite-adopt/SKILL.md +33 -37
  202. package/pack/.claude/skills/rite-autocomplete/SKILL.md +33 -28
  203. package/pack/.claude/skills/rite-autocomplete/reference/loop.md +21 -21
  204. package/pack/.claude/skills/rite-autocomplete/reference/stop-conditions.md +15 -6
  205. package/pack/.claude/skills/rite-build/SKILL.md +35 -30
  206. package/pack/.claude/skills/rite-build/reference/afk-discipline.md +38 -38
  207. package/pack/.claude/skills/rite-build/reference/evidence-standard.md +10 -0
  208. package/pack/.claude/skills/rite-build/reference/forge.md +186 -156
  209. package/pack/.claude/skills/rite-build/reference/one-slice-cycle.md +4 -3
  210. package/pack/.claude/skills/rite-build/reference/phase-contract.md +96 -175
  211. package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +129 -134
  212. package/pack/.claude/skills/rite-clarify/SKILL.md +89 -0
  213. package/pack/.claude/skills/rite-clarify/reference/decision-coverage.md +59 -0
  214. package/pack/.claude/skills/rite-converge/SKILL.md +35 -25
  215. package/pack/.claude/skills/rite-define/SKILL.md +57 -32
  216. package/pack/.claude/skills/rite-define/reference/gates.md +16 -15
  217. package/pack/.claude/skills/rite-define/reference/plan-template.md +16 -8
  218. package/pack/.claude/skills/rite-frame/reference/failure-modes.md +22 -24
  219. package/pack/.claude/skills/rite-plan/SKILL.md +44 -16
  220. package/pack/.claude/skills/rite-plan/reference/task-breakdown.md +4 -0
  221. package/pack/.claude/skills/rite-polish/SKILL.md +27 -23
  222. package/pack/.claude/skills/rite-prototype/SKILL.md +25 -26
  223. package/pack/.claude/skills/rite-prove/SKILL.md +27 -17
  224. package/pack/.claude/skills/rite-resolve/SKILL.md +12 -11
  225. package/pack/.claude/skills/rite-resolve/reference/answer-protocol.md +3 -0
  226. package/pack/.claude/skills/rite-review/SKILL.md +37 -28
  227. package/pack/.claude/skills/rite-seal/reference/phase-contract.md +27 -92
  228. package/pack/.claude/skills/rite-seal/reference/risk-and-rollback.md +13 -0
  229. package/pack/.claude/skills/rite-ship/reference/design-memory.md +31 -36
  230. package/pack/.claude/skills/rite-spec/SKILL.md +84 -135
  231. package/pack/.claude/skills/rite-spec/reference/investigation.md +37 -33
  232. package/pack/.claude/skills/rite-spec/reference/question-protocol.md +6 -2
  233. package/pack/.claude/skills/rite-spec/reference/spec-checklists.md +25 -24
  234. package/pack/.claude/skills/rite-spec/reference/spec-template.md +9 -2
  235. package/pack/.claude/skills/rite-spec/reference/state-workspace.md +13 -3
  236. package/pack/.claude/skills/rite-temper/SKILL.md +62 -47
  237. package/pack/.claude/skills/rite-temper/reference/review-dimensions.md +24 -22
  238. package/pack/.claude/skills/rite-vet/SKILL.md +110 -113
  239. package/pack/.claude/skills/rite-vet/reference/artifacts.md +42 -9
  240. package/pack/.claude/skills/rite-vet/reference/review-axes.md +39 -37
  241. package/pack/generated/claude/agents/devrites-code-reviewer.md +62 -48
  242. package/pack/generated/claude/agents/devrites-devex-reviewer.md +69 -53
  243. package/pack/generated/claude/agents/devrites-doubt-reviewer.md +29 -22
  244. package/pack/generated/claude/agents/devrites-evidence-scout.md +69 -0
  245. package/pack/generated/claude/agents/devrites-forge-judge.md +74 -61
  246. package/pack/generated/claude/agents/devrites-frontend-reviewer.md +48 -39
  247. package/pack/generated/claude/agents/devrites-performance-reviewer.md +49 -40
  248. package/pack/generated/claude/agents/devrites-plan-drafter.md +71 -0
  249. package/pack/generated/claude/agents/devrites-plan-reviewer.md +80 -47
  250. package/pack/generated/claude/agents/devrites-proof-runner.md +74 -0
  251. package/pack/generated/claude/agents/devrites-retrospector.md +48 -45
  252. package/pack/generated/claude/agents/devrites-security-auditor.md +46 -36
  253. package/pack/generated/claude/agents/devrites-simplifier-reviewer.md +50 -46
  254. package/pack/generated/claude/agents/devrites-slice-wright.md +153 -165
  255. package/pack/generated/claude/agents/devrites-spec-reviewer.md +34 -32
  256. package/pack/generated/claude/agents/devrites-strategy-reviewer.md +63 -34
  257. package/pack/generated/claude/agents/devrites-test-analyst.md +40 -30
  258. package/pack/generated/claude/settings.json +2 -1
  259. package/pack/generated/claude/skills/devrites-audit/SKILL.md +40 -61
  260. package/pack/generated/claude/skills/devrites-debug-recovery/SKILL.md +24 -13
  261. package/pack/generated/claude/skills/devrites-debug-recovery/reference/build-the-loop.md +24 -21
  262. package/pack/generated/claude/skills/devrites-debug-recovery/reference/cleanup-and-classify.md +34 -6
  263. package/pack/generated/claude/skills/devrites-debug-recovery/reference/instrumentation.md +2 -2
  264. package/pack/generated/claude/skills/devrites-doubt/SKILL.md +25 -17
  265. package/pack/generated/claude/skills/devrites-interview/SKILL.md +53 -52
  266. package/pack/generated/claude/skills/devrites-lib/SKILL.md +11 -9
  267. package/pack/generated/claude/skills/devrites-lib/reference/intent-map.md +3 -2
  268. package/pack/generated/claude/skills/devrites-lib/reference/parallel-dispatch.md +82 -137
  269. package/pack/generated/claude/skills/devrites-lib/reference/reply-contract.md +8 -1
  270. package/pack/generated/claude/skills/devrites-lib/reference/standards/README.md +38 -55
  271. package/pack/generated/claude/skills/devrites-lib/reference/standards/afk-hitl.md +53 -27
  272. package/pack/generated/claude/skills/devrites-lib/reference/standards/agents.md +198 -190
  273. package/pack/generated/claude/skills/devrites-lib/reference/standards/anti-patterns.md +5 -15
  274. package/pack/generated/claude/skills/devrites-lib/reference/standards/ci-cd.md +27 -58
  275. package/pack/generated/claude/skills/devrites-lib/reference/standards/code-review.md +16 -52
  276. package/pack/generated/claude/skills/devrites-lib/reference/standards/coding-style.md +2 -10
  277. package/pack/generated/claude/skills/devrites-lib/reference/standards/context-hygiene.md +18 -61
  278. package/pack/generated/claude/skills/devrites-lib/reference/standards/core.md +23 -18
  279. package/pack/generated/claude/skills/devrites-lib/reference/standards/deprecation.md +17 -57
  280. package/pack/generated/claude/skills/devrites-lib/reference/standards/development-workflow.md +4 -33
  281. package/pack/generated/claude/skills/devrites-lib/reference/standards/git-workflow.md +4 -20
  282. package/pack/generated/claude/skills/devrites-lib/reference/standards/hooks.md +3 -14
  283. package/pack/generated/claude/skills/devrites-lib/reference/standards/patterns.md +9 -25
  284. package/pack/generated/claude/skills/devrites-lib/reference/standards/performance.md +0 -9
  285. package/pack/generated/claude/skills/devrites-lib/reference/standards/principles.md +1 -3
  286. package/pack/generated/claude/skills/devrites-lib/reference/standards/security.md +17 -7
  287. package/pack/generated/claude/skills/devrites-lib/reference/standards/testing.md +1 -27
  288. package/pack/generated/claude/skills/devrites-lib/reference/standards/tooling.md +46 -50
  289. package/pack/generated/claude/skills/devrites-lib/reference/workspace-artifact-schema.md +23 -10
  290. package/pack/generated/claude/skills/devrites-source-driven/SKILL.md +23 -22
  291. package/pack/generated/claude/skills/rite/SKILL.md +8 -6
  292. package/pack/generated/claude/skills/rite/reference/menu.md +8 -6
  293. package/pack/generated/claude/skills/rite-adopt/SKILL.md +33 -37
  294. package/pack/generated/claude/skills/rite-autocomplete/SKILL.md +33 -28
  295. package/pack/generated/claude/skills/rite-autocomplete/reference/loop.md +21 -21
  296. package/pack/generated/claude/skills/rite-autocomplete/reference/stop-conditions.md +15 -6
  297. package/pack/generated/claude/skills/rite-build/SKILL.md +35 -30
  298. package/pack/generated/claude/skills/rite-build/reference/afk-discipline.md +38 -38
  299. package/pack/generated/claude/skills/rite-build/reference/evidence-standard.md +10 -0
  300. package/pack/generated/claude/skills/rite-build/reference/forge.md +186 -156
  301. package/pack/generated/claude/skills/rite-build/reference/one-slice-cycle.md +4 -3
  302. package/pack/generated/claude/skills/rite-build/reference/phase-contract.md +96 -175
  303. package/pack/generated/claude/skills/rite-build/reference/wright-dispatch.md +129 -134
  304. package/pack/generated/claude/skills/rite-clarify/SKILL.md +89 -0
  305. package/pack/generated/claude/skills/rite-clarify/reference/decision-coverage.md +59 -0
  306. package/pack/generated/claude/skills/rite-converge/SKILL.md +35 -25
  307. package/pack/generated/claude/skills/rite-define/SKILL.md +57 -32
  308. package/pack/generated/claude/skills/rite-define/reference/gates.md +16 -15
  309. package/pack/generated/claude/skills/rite-define/reference/plan-template.md +16 -8
  310. package/pack/generated/claude/skills/rite-frame/reference/failure-modes.md +22 -24
  311. package/pack/generated/claude/skills/rite-plan/SKILL.md +44 -16
  312. package/pack/generated/claude/skills/rite-plan/reference/task-breakdown.md +4 -0
  313. package/pack/generated/claude/skills/rite-polish/SKILL.md +27 -23
  314. package/pack/generated/claude/skills/rite-prototype/SKILL.md +25 -26
  315. package/pack/generated/claude/skills/rite-prove/SKILL.md +27 -17
  316. package/pack/generated/claude/skills/rite-resolve/SKILL.md +12 -11
  317. package/pack/generated/claude/skills/rite-resolve/reference/answer-protocol.md +3 -0
  318. package/pack/generated/claude/skills/rite-review/SKILL.md +37 -28
  319. package/pack/generated/claude/skills/rite-seal/reference/phase-contract.md +27 -92
  320. package/pack/generated/claude/skills/rite-seal/reference/risk-and-rollback.md +13 -0
  321. package/pack/generated/claude/skills/rite-ship/reference/design-memory.md +31 -36
  322. package/pack/generated/claude/skills/rite-spec/SKILL.md +84 -135
  323. package/pack/generated/claude/skills/rite-spec/reference/investigation.md +37 -33
  324. package/pack/generated/claude/skills/rite-spec/reference/question-protocol.md +6 -2
  325. package/pack/generated/claude/skills/rite-spec/reference/spec-checklists.md +25 -24
  326. package/pack/generated/claude/skills/rite-spec/reference/spec-template.md +9 -2
  327. package/pack/generated/claude/skills/rite-spec/reference/state-workspace.md +13 -3
  328. package/pack/generated/claude/skills/rite-temper/SKILL.md +62 -47
  329. package/pack/generated/claude/skills/rite-temper/reference/review-dimensions.md +24 -22
  330. package/pack/generated/claude/skills/rite-vet/SKILL.md +110 -113
  331. package/pack/generated/claude/skills/rite-vet/reference/artifacts.md +42 -9
  332. package/pack/generated/claude/skills/rite-vet/reference/review-axes.md +39 -37
  333. package/pack/generated/codex/AGENTS.md +5 -2
  334. package/pack/generated/codex/agents/devrites-code-reviewer.toml +69 -47
  335. package/pack/generated/codex/agents/devrites-devex-reviewer.toml +75 -51
  336. package/pack/generated/codex/agents/devrites-doubt-reviewer.toml +35 -20
  337. package/pack/generated/codex/agents/devrites-evidence-scout.toml +75 -0
  338. package/pack/generated/codex/agents/devrites-forge-judge.toml +80 -59
  339. package/pack/generated/codex/agents/devrites-frontend-reviewer.toml +54 -37
  340. package/pack/generated/codex/agents/devrites-performance-reviewer.toml +55 -38
  341. package/pack/generated/codex/agents/devrites-plan-drafter.toml +77 -0
  342. package/pack/generated/codex/agents/devrites-plan-reviewer.toml +82 -47
  343. package/pack/generated/codex/agents/devrites-proof-runner.toml +80 -0
  344. package/pack/generated/codex/agents/devrites-retrospector.toml +54 -43
  345. package/pack/generated/codex/agents/devrites-security-auditor.toml +53 -35
  346. package/pack/generated/codex/agents/devrites-simplifier-reviewer.toml +56 -44
  347. package/pack/generated/codex/agents/devrites-slice-wright.toml +159 -163
  348. package/pack/generated/codex/agents/devrites-spec-reviewer.toml +40 -30
  349. package/pack/generated/codex/agents/devrites-strategy-reviewer.toml +65 -34
  350. package/pack/generated/codex/agents/devrites-test-analyst.toml +46 -28
  351. package/pack/generated/codex/hooks.json +4 -14
  352. package/pack/generated/codex/skills/devrites-api-interface/SKILL.md +7 -3
  353. package/pack/generated/codex/skills/devrites-audit/SKILL.md +47 -64
  354. package/pack/generated/codex/skills/devrites-browser-proof/SKILL.md +7 -3
  355. package/pack/generated/codex/skills/devrites-debug-recovery/SKILL.md +31 -16
  356. package/pack/generated/codex/skills/devrites-debug-recovery/reference/build-the-loop.md +24 -21
  357. package/pack/generated/codex/skills/devrites-debug-recovery/reference/cleanup-and-classify.md +34 -6
  358. package/pack/generated/codex/skills/devrites-debug-recovery/reference/instrumentation.md +2 -2
  359. package/pack/generated/codex/skills/devrites-doubt/SKILL.md +32 -20
  360. package/pack/generated/codex/skills/devrites-frontend-craft/SKILL.md +7 -3
  361. package/pack/generated/codex/skills/devrites-interview/SKILL.md +60 -55
  362. package/pack/generated/codex/skills/devrites-lib/SKILL.md +18 -12
  363. package/pack/generated/codex/skills/devrites-lib/reference/intent-map.md +3 -2
  364. package/pack/generated/codex/skills/devrites-lib/reference/parallel-dispatch.md +82 -137
  365. package/pack/generated/codex/skills/devrites-lib/reference/reply-contract.md +8 -1
  366. package/pack/generated/codex/skills/devrites-lib/reference/standards/README.md +38 -55
  367. package/pack/generated/codex/skills/devrites-lib/reference/standards/afk-hitl.md +53 -27
  368. package/pack/generated/codex/skills/devrites-lib/reference/standards/agents.md +198 -190
  369. package/pack/generated/codex/skills/devrites-lib/reference/standards/anti-patterns.md +5 -15
  370. package/pack/generated/codex/skills/devrites-lib/reference/standards/ci-cd.md +27 -58
  371. package/pack/generated/codex/skills/devrites-lib/reference/standards/code-review.md +16 -52
  372. package/pack/generated/codex/skills/devrites-lib/reference/standards/coding-style.md +2 -10
  373. package/pack/generated/codex/skills/devrites-lib/reference/standards/context-hygiene.md +18 -61
  374. package/pack/generated/codex/skills/devrites-lib/reference/standards/core.md +23 -18
  375. package/pack/generated/codex/skills/devrites-lib/reference/standards/deprecation.md +17 -57
  376. package/pack/generated/codex/skills/devrites-lib/reference/standards/development-workflow.md +4 -33
  377. package/pack/generated/codex/skills/devrites-lib/reference/standards/git-workflow.md +4 -20
  378. package/pack/generated/codex/skills/devrites-lib/reference/standards/hooks.md +3 -14
  379. package/pack/generated/codex/skills/devrites-lib/reference/standards/patterns.md +9 -25
  380. package/pack/generated/codex/skills/devrites-lib/reference/standards/performance.md +0 -9
  381. package/pack/generated/codex/skills/devrites-lib/reference/standards/principles.md +1 -3
  382. package/pack/generated/codex/skills/devrites-lib/reference/standards/security.md +17 -7
  383. package/pack/generated/codex/skills/devrites-lib/reference/standards/testing.md +1 -27
  384. package/pack/generated/codex/skills/devrites-lib/reference/standards/tooling.md +46 -50
  385. package/pack/generated/codex/skills/devrites-lib/reference/workspace-artifact-schema.md +23 -10
  386. package/pack/generated/codex/skills/devrites-prose-craft/SKILL.md +7 -3
  387. package/pack/generated/codex/skills/devrites-refresh-indexes/SKILL.md +7 -3
  388. package/pack/generated/codex/skills/devrites-source-driven/SKILL.md +29 -24
  389. package/pack/generated/codex/skills/devrites-ux-shape/SKILL.md +7 -3
  390. package/pack/generated/codex/skills/rite/SKILL.md +19 -13
  391. package/pack/generated/codex/skills/rite/reference/menu.md +8 -6
  392. package/pack/generated/codex/skills/rite-adopt/SKILL.md +40 -40
  393. package/pack/generated/codex/skills/rite-autocomplete/SKILL.md +40 -31
  394. package/pack/generated/codex/skills/rite-autocomplete/reference/loop.md +21 -21
  395. package/pack/generated/codex/skills/rite-autocomplete/reference/stop-conditions.md +15 -6
  396. package/pack/generated/codex/skills/rite-build/SKILL.md +42 -33
  397. package/pack/generated/codex/skills/rite-build/reference/afk-discipline.md +38 -38
  398. package/pack/generated/codex/skills/rite-build/reference/evidence-standard.md +10 -0
  399. package/pack/generated/codex/skills/rite-build/reference/forge.md +186 -156
  400. package/pack/generated/codex/skills/rite-build/reference/one-slice-cycle.md +4 -3
  401. package/pack/generated/codex/skills/rite-build/reference/phase-contract.md +96 -175
  402. package/pack/generated/codex/skills/rite-build/reference/wright-dispatch.md +129 -134
  403. package/pack/generated/codex/skills/rite-clarify/SKILL.md +105 -0
  404. package/pack/generated/codex/skills/rite-clarify/reference/decision-coverage.md +59 -0
  405. package/pack/generated/codex/skills/rite-converge/SKILL.md +42 -28
  406. package/pack/generated/codex/skills/rite-customize/SKILL.md +7 -3
  407. package/pack/generated/codex/skills/rite-define/SKILL.md +64 -35
  408. package/pack/generated/codex/skills/rite-define/reference/gates.md +16 -15
  409. package/pack/generated/codex/skills/rite-define/reference/plan-template.md +16 -8
  410. package/pack/generated/codex/skills/rite-doctor/SKILL.md +7 -3
  411. package/pack/generated/codex/skills/rite-dogfood/SKILL.md +7 -3
  412. package/pack/generated/codex/skills/rite-explain/SKILL.md +7 -3
  413. package/pack/generated/codex/skills/rite-frame/SKILL.md +7 -3
  414. package/pack/generated/codex/skills/rite-frame/reference/failure-modes.md +22 -24
  415. package/pack/generated/codex/skills/rite-handoff/SKILL.md +7 -3
  416. package/pack/generated/codex/skills/rite-learn/SKILL.md +7 -3
  417. package/pack/generated/codex/skills/rite-plan/SKILL.md +51 -19
  418. package/pack/generated/codex/skills/rite-plan/reference/task-breakdown.md +5 -1
  419. package/pack/generated/codex/skills/rite-polish/SKILL.md +34 -26
  420. package/pack/generated/codex/skills/rite-pov/SKILL.md +7 -3
  421. package/pack/generated/codex/skills/rite-pr-feedback/SKILL.md +7 -3
  422. package/pack/generated/codex/skills/rite-pressure-test/SKILL.md +7 -3
  423. package/pack/generated/codex/skills/rite-prototype/SKILL.md +32 -29
  424. package/pack/generated/codex/skills/rite-prove/SKILL.md +34 -20
  425. package/pack/generated/codex/skills/rite-quick/SKILL.md +7 -3
  426. package/pack/generated/codex/skills/rite-resolve/SKILL.md +19 -14
  427. package/pack/generated/codex/skills/rite-resolve/reference/answer-protocol.md +3 -0
  428. package/pack/generated/codex/skills/rite-review/SKILL.md +44 -31
  429. package/pack/generated/codex/skills/rite-seal/SKILL.md +7 -3
  430. package/pack/generated/codex/skills/rite-seal/reference/phase-contract.md +27 -92
  431. package/pack/generated/codex/skills/rite-seal/reference/risk-and-rollback.md +13 -0
  432. package/pack/generated/codex/skills/rite-ship/SKILL.md +7 -3
  433. package/pack/generated/codex/skills/rite-ship/reference/design-memory.md +31 -36
  434. package/pack/generated/codex/skills/rite-spec/SKILL.md +91 -138
  435. package/pack/generated/codex/skills/rite-spec/reference/investigation.md +37 -33
  436. package/pack/generated/codex/skills/rite-spec/reference/question-protocol.md +6 -2
  437. package/pack/generated/codex/skills/rite-spec/reference/spec-checklists.md +25 -24
  438. package/pack/generated/codex/skills/rite-spec/reference/spec-template.md +9 -2
  439. package/pack/generated/codex/skills/rite-spec/reference/state-workspace.md +13 -3
  440. package/pack/generated/codex/skills/rite-status/SKILL.md +7 -3
  441. package/pack/generated/codex/skills/rite-temper/SKILL.md +69 -50
  442. package/pack/generated/codex/skills/rite-temper/reference/review-dimensions.md +24 -22
  443. package/pack/generated/codex/skills/rite-vet/SKILL.md +117 -116
  444. package/pack/generated/codex/skills/rite-vet/reference/artifacts.md +42 -9
  445. package/pack/generated/codex/skills/rite-vet/reference/review-axes.md +40 -38
  446. package/pack/generated/codex/skills/rite-zoom-out/SKILL.md +7 -3
  447. package/package.json +1 -1
  448. package/scripts/build-release-tarball.sh +32 -15
  449. package/scripts/check-authority-drift.py +125 -0
  450. package/scripts/check-instruction-size-baseline.mjs +19 -11
  451. package/scripts/check-invocation-integrity.py +2 -0
  452. package/scripts/codex-generate.sh +69 -33
  453. package/scripts/grade-feature.sh +121 -40
  454. package/scripts/live-hosts/agent-result.schema.json +230 -0
  455. package/scripts/live-hosts/claude.sh +87 -0
  456. package/scripts/live-hosts/codex.sh +81 -0
  457. package/scripts/live-hosts/common.sh +113 -0
  458. package/scripts/live-hosts/fake-host.py +264 -0
  459. package/scripts/live-hosts/host-transport.py +287 -0
  460. package/scripts/release-check.sh +5 -1
  461. package/scripts/run-agent-contract-evals.py +1380 -0
  462. package/scripts/run-behavioral-evals.sh +24 -30
  463. package/scripts/run-evals.sh +1 -5
  464. package/scripts/run-live-behavioral-evals.py +1274 -144
  465. package/scripts/run-outcome-evals.sh +414 -88
  466. package/scripts/run-tests.mjs +30 -2
  467. package/scripts/skills-inventory.mjs +1 -1
  468. package/scripts/validate-workflow-security.py +39 -20
  469. package/scripts/validate-workspace-schema.py +362 -10
  470. package/scripts/validate.sh +21 -15
  471. package/engine/testdata/golden/TestParityWrightScope/devrites-edit-allowed.golden +0 -1
  472. /package/engine/testdata/golden/{TestParityReconcile/check-no-claimed.golden → TestParityBuildReadiness/arg=clarifyopen.golden} +0 -0
@@ -4,14 +4,19 @@ import (
4
4
  "archive/tar"
5
5
  "bytes"
6
6
  "compress/gzip"
7
+ "crypto/sha256"
7
8
  "encoding/json"
8
9
  "fmt"
9
10
  "io/fs"
11
+ "net/http"
12
+ "net/http/httptest"
10
13
  "os"
14
+ "os/exec"
11
15
  "path/filepath"
12
16
  "runtime"
13
17
  "strings"
14
18
  "testing"
19
+ "time"
15
20
 
16
21
  "github.com/devrites/devrites/internal/testutil"
17
22
  )
@@ -254,7 +259,7 @@ func TestInstallKeepsClaudeMergeOwnershipAcrossReinstall(t *testing.T) {
254
259
  if !exists(filepath.Join(target, filepath.FromSlash(markerRel))) {
255
260
  t.Fatal("first install did not create the Claude hook merge marker")
256
261
  }
257
- if !readManifest(filepath.Join(target, ManifestName))[markerRel] {
262
+ if _, ok := readManifest(filepath.Join(target, ManifestName))[markerRel]; !ok {
258
263
  t.Fatalf("first install did not record the Claude hook merge marker:\n%s", testutil.ReadFile(t, filepath.Join(target, ManifestName)))
259
264
  }
260
265
  runInstall(t, target, payload, func(o *Options) {})
@@ -325,18 +330,23 @@ func TestInstallBinaryUsesEngineHandoff(t *testing.T) {
325
330
  payload := testPayload(t)
326
331
  target := t.TempDir()
327
332
  binDir := t.TempDir()
328
- engine := filepath.Join(t.TempDir(), "devrites-engine")
329
- engineBody := "#!/bin/sh\nif [ \"$1\" = version ]; then echo 1.2.3; exit 0; fi\necho handoff\n"
330
- testutil.WriteExecutable(t, engine, engineBody)
333
+ engine := buildVersionBinary(t, "1.2.3")
334
+ engineBody, err := os.ReadFile(engine)
335
+ if err != nil {
336
+ t.Fatal(err)
337
+ }
331
338
  t.Setenv("DEVRITES_ENGINE_CLI", engine)
332
339
  t.Setenv("DEVRITES_BIN_DIR", binDir)
333
340
  t.Setenv("DEVRITES_REF", "v1.2.3")
341
+ conflictDir := t.TempDir()
342
+ testutil.WriteExecutable(t, filepath.Join(conflictDir, engineBinaryName()), "conflict\n")
343
+ t.Setenv("PATH", conflictDir)
334
344
 
335
345
  runInstall(t, target, payload, func(o *Options) {})
336
346
 
337
- installed := filepath.Join(binDir, "devrites-engine")
338
- if got := testutil.ReadFile(t, installed); got != engineBody {
339
- t.Fatalf("installed binary did not come from DEVRITES_ENGINE_CLI handoff:\n%s", got)
347
+ installed := filepath.Join(binDir, engineBinaryName())
348
+ if got, err := os.ReadFile(installed); err != nil || !bytes.Equal(got, engineBody) {
349
+ t.Fatalf("installed binary did not come from DEVRITES_ENGINE_CLI handoff: %v", err)
340
350
  }
341
351
  info, err := os.Stat(installed)
342
352
  if err != nil {
@@ -347,12 +357,116 @@ func TestInstallBinaryUsesEngineHandoff(t *testing.T) {
347
357
  }
348
358
  }
349
359
 
360
+ func TestAcquireBinaryBuildsWithoutVCSMetadata(t *testing.T) {
361
+ t.Setenv("DEVRITES_ENGINE_CLI", "")
362
+ t.Setenv("DEVRITES_UPDATE_BUNDLE", "")
363
+ t.Setenv("GOFLAGS", "-buildvcs=true")
364
+ source := t.TempDir()
365
+ if err := os.Mkdir(filepath.Join(source, ".git"), 0o755); err != nil {
366
+ t.Fatal(err)
367
+ }
368
+ testutil.WriteFile(t, filepath.Join(source, "engine", "go.mod"), "module github.com/devrites/devrites\n\ngo 1.23\n")
369
+ testutil.WriteFile(t, filepath.Join(source, "engine", "main.go"), "package main\nfunc main() {}\n")
370
+
371
+ r := runner{source: source}
372
+ staged, cleanup, err := r.acquireBinary("v1.2.3", githubBaseURL)
373
+ defer cleanup()
374
+ if err != nil {
375
+ t.Fatalf("acquireBinary with unavailable VCS metadata: %v", err)
376
+ }
377
+ if !exists(staged) {
378
+ t.Fatalf("acquireBinary did not build %s", staged)
379
+ }
380
+ }
381
+
382
+ func TestAcquireBinaryPrefersVerifiedReleaseOverStaleHandoff(t *testing.T) {
383
+ stale := filepath.Join(t.TempDir(), "devrites-engine")
384
+ testutil.WriteExecutable(t, stale, "#!/bin/sh\nif [ \"$1\" = version ]; then echo v1.2.2; fi\n")
385
+ t.Setenv("DEVRITES_ENGINE_CLI", stale)
386
+ t.Setenv("DEVRITES_UPDATE_BUNDLE", "")
387
+ t.Setenv("DEVRITES_REPO", "owner/repo")
388
+ tag := "v1.2.3"
389
+ asset := fmt.Sprintf("devrites-%s-%s", runtime.GOOS, runtime.GOARCH)
390
+ if runtime.GOOS == "windows" {
391
+ asset += ".exe"
392
+ }
393
+ path := "/owner/repo/releases/download/" + tag + "/" + asset
394
+ body := []byte("verified release binary")
395
+ sum := fmt.Sprintf("%x %s\n", sha256.Sum256(body), asset)
396
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
397
+ switch req.URL.Path {
398
+ case path:
399
+ _, _ = w.Write(body)
400
+ case path + ".sha256":
401
+ _, _ = w.Write([]byte(sum))
402
+ default:
403
+ http.NotFound(w, req)
404
+ }
405
+ }))
406
+ defer server.Close()
407
+
408
+ r := runner{releaseBinaryTag: tag}
409
+ staged, cleanup, err := r.acquireBinary(tag, server.URL)
410
+ defer cleanup()
411
+ if err != nil {
412
+ t.Fatalf("acquire verified release binary: %v", err)
413
+ }
414
+ got, err := os.ReadFile(staged)
415
+ if err != nil {
416
+ t.Fatal(err)
417
+ }
418
+ if !bytes.Equal(got, body) {
419
+ t.Fatalf("acquired binary = %q, want %q", got, body)
420
+ }
421
+ }
422
+
423
+ func TestAcquireBinaryRejectsReleaseChecksumMismatch(t *testing.T) {
424
+ t.Setenv("DEVRITES_ENGINE_CLI", "")
425
+ t.Setenv("DEVRITES_UPDATE_BUNDLE", "")
426
+ t.Setenv("DEVRITES_REPO", "owner/repo")
427
+ tag := "v1.2.3"
428
+ source := t.TempDir()
429
+ testutil.WriteFile(t, filepath.Join(source, "engine", "go.mod"), "module github.com/devrites/devrites\n\ngo 1.23\n")
430
+ testutil.WriteFile(t, filepath.Join(source, "engine", "main.go"), "package main\nfunc main() {}\n")
431
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
432
+ if strings.HasSuffix(req.URL.Path, ".sha256") {
433
+ _, _ = w.Write([]byte("deadbeef\n"))
434
+ return
435
+ }
436
+ _, _ = w.Write([]byte("tampered release binary"))
437
+ }))
438
+ defer server.Close()
439
+
440
+ r := runner{source: source, releaseBinaryTag: tag}
441
+ _, cleanup, err := r.acquireBinary(tag, server.URL)
442
+ defer cleanup()
443
+ if err == nil || !strings.Contains(err.Error(), "checksum mismatch") {
444
+ t.Fatalf("acquireBinary error = %v, want checksum mismatch", err)
445
+ }
446
+ }
447
+
448
+ func TestInstallBinaryFailsWhenPreparedUpdateCannotBeWritten(t *testing.T) {
449
+ prepared := filepath.Join(t.TempDir(), "devrites-engine")
450
+ testutil.WriteExecutable(t, prepared, "#!/bin/sh\nif [ \"$1\" = version ]; then echo 1.2.3; fi\n")
451
+ blocked := filepath.Join(t.TempDir(), "not-a-directory")
452
+ testutil.WriteFile(t, blocked, "file\n")
453
+ t.Setenv("DEVRITES_BIN_DIR", filepath.Join(blocked, "bin"))
454
+ t.Setenv("DEVRITES_REF", "v1.2.3")
455
+
456
+ r := runner{
457
+ opts: DefaultOptions(ModeInstall),
458
+ preparedBinary: prepared,
459
+ }
460
+ if err := r.installBinary(); err == nil {
461
+ t.Fatal("installBinary accepted a prepared update it could not write")
462
+ }
463
+ }
464
+
350
465
  func TestInstallBinaryWarnsWhenHooksCannotResolveEngine(t *testing.T) {
351
466
  payload := testPayload(t)
352
467
  target := t.TempDir()
353
468
  binDir := t.TempDir()
354
- engine := filepath.Join(t.TempDir(), "devrites-engine")
355
- testutil.WriteExecutable(t, engine, "#!/bin/sh\nif [ \"$1\" = version ]; then echo 1.2.3; exit 0; fi\n")
469
+ engine := buildVersionBinary(t, "1.2.3")
356
470
  pathDir := t.TempDir()
357
471
  var stderr bytes.Buffer
358
472
 
@@ -370,6 +484,18 @@ func TestInstallBinaryWarnsWhenHooksCannotResolveEngine(t *testing.T) {
370
484
  }
371
485
  }
372
486
 
487
+ func buildVersionBinary(t *testing.T, version string) string {
488
+ t.Helper()
489
+ dir := t.TempDir()
490
+ source := filepath.Join(dir, "main.go")
491
+ testutil.WriteFile(t, source, fmt.Sprintf("package main\nimport \"fmt\"\nfunc main() { fmt.Println(%q) }\n", version))
492
+ binary := filepath.Join(dir, engineBinaryName())
493
+ if out, err := exec.Command("go", "build", "-o", binary, source).CombinedOutput(); err != nil {
494
+ t.Fatalf("build test engine: %v\n%s", err, out)
495
+ }
496
+ return binary
497
+ }
498
+
373
499
  func TestBinaryCandidatesSkipsUnsetBinDir(t *testing.T) {
374
500
  t.Setenv("DEVRITES_BIN_DIR", "")
375
501
 
@@ -451,6 +577,361 @@ printf '{"hooks":{"Stop":[{"hooks":[{"type":"command","command":"devrites-engine
451
577
  }
452
578
  }
453
579
 
580
+ func TestManagedFilePolicy(t *testing.T) {
581
+ t.Setenv("DEVRITES_NO_BINARY", "1")
582
+ const rel = ".claude/skills/rite/SKILL.md"
583
+
584
+ t.Run("refresh preserves customized unless forced", func(t *testing.T) {
585
+ payload := testPayload(t)
586
+ target := t.TempDir()
587
+ runInstall(t, target, payload, func(o *Options) {})
588
+ dest := filepath.Join(target, filepath.FromSlash(rel))
589
+ testutil.WriteFile(t, dest, "customized\n")
590
+ testutil.WriteFile(t, filepath.Join(payload, "claude", "skills", "rite", "SKILL.md"), "new\n")
591
+
592
+ err := applyInstall(target, payload, false, false, nil)
593
+ if err == nil || !strings.Contains(err.Error(), "--force") {
594
+ t.Fatalf("customized refresh error = %v, want --force remediation", err)
595
+ }
596
+ if got := testutil.ReadFile(t, dest); got != "customized\n" {
597
+ t.Fatalf("default refresh changed customized file: %q", got)
598
+ }
599
+
600
+ var out bytes.Buffer
601
+ if err := applyInstall(target, payload, true, true, &out); err != nil {
602
+ t.Fatal(err)
603
+ }
604
+ if !strings.Contains(out.String(), "[overwrite(force-customized)] "+rel) {
605
+ t.Fatalf("forced dry-run did not predict overwrite:\n%s", out.String())
606
+ }
607
+ if got := testutil.ReadFile(t, dest); got != "customized\n" {
608
+ t.Fatalf("dry-run changed customized file: %q", got)
609
+ }
610
+
611
+ if err := applyInstall(target, payload, true, false, nil); err != nil {
612
+ t.Fatal(err)
613
+ }
614
+ if got := testutil.ReadFile(t, dest); got != "new\n" {
615
+ t.Fatalf("forced refresh = %q, want new payload", got)
616
+ }
617
+ })
618
+
619
+ t.Run("legacy manifest requires force", func(t *testing.T) {
620
+ payload := testPayload(t)
621
+ target := t.TempDir()
622
+ runInstall(t, target, payload, func(o *Options) {})
623
+ manifest := filepath.Join(target, ManifestName)
624
+ lines := strings.Split(testutil.ReadFile(t, manifest), "\n")
625
+ var next []string
626
+ for _, line := range lines {
627
+ if !strings.HasPrefix(line, "# managed: "+rel+" ") {
628
+ next = append(next, line)
629
+ }
630
+ }
631
+ testutil.WriteFile(t, manifest, strings.Join(next, "\n"))
632
+
633
+ err := applyInstall(target, payload, false, false, nil)
634
+ if err == nil || !strings.Contains(err.Error(), "legacy manifest entry") {
635
+ t.Fatalf("legacy refresh error = %v", err)
636
+ }
637
+ if err := applyInstall(target, payload, true, false, nil); err != nil {
638
+ t.Fatalf("forced legacy refresh: %v", err)
639
+ }
640
+ })
641
+
642
+ t.Run("missing is recreated and absent uninstall is a no-op", func(t *testing.T) {
643
+ payload := testPayload(t)
644
+ target := t.TempDir()
645
+ runInstall(t, target, payload, func(o *Options) {})
646
+ dest := filepath.Join(target, filepath.FromSlash(rel))
647
+ if err := os.Remove(dest); err != nil {
648
+ t.Fatal(err)
649
+ }
650
+ if err := applyInstall(target, payload, false, false, nil); err != nil {
651
+ t.Fatal(err)
652
+ }
653
+ if !exists(dest) {
654
+ t.Fatal("refresh did not recreate missing managed file")
655
+ }
656
+ if err := os.Remove(dest); err != nil {
657
+ t.Fatal(err)
658
+ }
659
+ runUninstall(t, target)
660
+ })
661
+
662
+ t.Run("foreign is preserved unless forced", func(t *testing.T) {
663
+ payload := testPayload(t)
664
+ target := t.TempDir()
665
+ dest := filepath.Join(target, filepath.FromSlash(rel))
666
+ testutil.WriteFile(t, dest, "mine\n")
667
+ runInstall(t, target, payload, func(o *Options) {})
668
+ if got := testutil.ReadFile(t, dest); got != "mine\n" {
669
+ t.Fatalf("default install overwrote foreign file: %q", got)
670
+ }
671
+ if err := applyInstall(target, payload, true, false, nil); err != nil {
672
+ t.Fatal(err)
673
+ }
674
+ if got := testutil.ReadFile(t, dest); got != "claude rite\n" {
675
+ t.Fatalf("forced install did not replace foreign file: %q", got)
676
+ }
677
+ })
678
+ }
679
+
680
+ func TestManagedPruneAndUninstallPolicy(t *testing.T) {
681
+ t.Setenv("DEVRITES_NO_BINARY", "1")
682
+ const staleRel = ".claude/skills/stale/SKILL.md"
683
+
684
+ t.Run("prune", func(t *testing.T) {
685
+ payload := testPayload(t)
686
+ target := t.TempDir()
687
+ runInstall(t, target, payload, func(o *Options) {})
688
+ stale := filepath.Join(target, filepath.FromSlash(staleRel))
689
+ testutil.WriteFile(t, stale, "owned\n")
690
+ addManifestRecord(t, filepath.Join(target, ManifestName), staleRel, []byte("owned\n"))
691
+ testutil.WriteFile(t, stale, "customized\n")
692
+
693
+ err := applyInstall(target, payload, false, false, nil)
694
+ if err == nil || !strings.Contains(err.Error(), staleRel) || !exists(stale) {
695
+ t.Fatalf("default prune error = %v, exists = %t", err, exists(stale))
696
+ }
697
+ var out bytes.Buffer
698
+ if err := applyInstall(target, payload, true, true, &out); err != nil {
699
+ t.Fatal(err)
700
+ }
701
+ if !strings.Contains(out.String(), "[prune(force-customized)] "+staleRel) || !exists(stale) {
702
+ t.Fatalf("forced prune dry-run mismatch:\n%s", out.String())
703
+ }
704
+ if err := applyInstall(target, payload, true, false, nil); err != nil {
705
+ t.Fatal(err)
706
+ }
707
+ if exists(stale) {
708
+ t.Fatal("forced prune kept customized dropped file")
709
+ }
710
+ })
711
+
712
+ t.Run("uninstall", func(t *testing.T) {
713
+ payload := testPayload(t)
714
+ target := t.TempDir()
715
+ runInstall(t, target, payload, func(o *Options) {})
716
+ dest := filepath.Join(target, ".claude", "skills", "rite", "SKILL.md")
717
+ testutil.WriteFile(t, dest, "customized\n")
718
+
719
+ opts := DefaultOptions(ModeUninstall)
720
+ opts.Target = target
721
+ opts.KeepBinary = true
722
+ opts.Stdout = &bytes.Buffer{}
723
+ opts.Stderr = &bytes.Buffer{}
724
+ err := Apply(opts)
725
+ if err == nil || !strings.Contains(err.Error(), "--force") || !exists(dest) {
726
+ t.Fatalf("default uninstall error = %v, exists = %t", err, exists(dest))
727
+ }
728
+
729
+ var out bytes.Buffer
730
+ opts.Force = true
731
+ opts.DryRun = true
732
+ opts.Stdout = &out
733
+ if err := Apply(opts); err != nil {
734
+ t.Fatal(err)
735
+ }
736
+ if !strings.Contains(out.String(), "[remove(force-customized)] .claude/skills/rite/SKILL.md") || !exists(dest) {
737
+ t.Fatalf("forced uninstall dry-run mismatch:\n%s", out.String())
738
+ }
739
+ opts.DryRun = false
740
+ if err := Apply(opts); err != nil {
741
+ t.Fatal(err)
742
+ }
743
+ if exists(dest) {
744
+ t.Fatal("forced uninstall kept customized managed file")
745
+ }
746
+ })
747
+ }
748
+
749
+ func TestManagedPathsRejectLinksAndRaces(t *testing.T) {
750
+ t.Setenv("DEVRITES_NO_BINARY", "1")
751
+ if runtime.GOOS == "windows" {
752
+ t.Skip("creating symlinks requires privileges on Windows; the same confinement code runs there")
753
+ }
754
+ for _, tc := range []struct {
755
+ name string
756
+ link func(target, outside string) error
757
+ }{
758
+ {
759
+ name: "final symlink",
760
+ link: func(target, outside string) error {
761
+ dest := filepath.Join(target, ".claude", "skills", "rite", "SKILL.md")
762
+ if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
763
+ return err
764
+ }
765
+ return os.Symlink(filepath.Join(outside, "sentinel"), dest)
766
+ },
767
+ },
768
+ {
769
+ name: "ancestor symlink",
770
+ link: func(target, outside string) error {
771
+ return os.Symlink(outside, filepath.Join(target, ".agents"))
772
+ },
773
+ },
774
+ } {
775
+ t.Run(tc.name, func(t *testing.T) {
776
+ payload := testPayload(t)
777
+ target := t.TempDir()
778
+ outside := t.TempDir()
779
+ testutil.WriteFile(t, filepath.Join(outside, "sentinel"), "outside\n")
780
+ if err := tc.link(target, outside); err != nil {
781
+ t.Fatal(err)
782
+ }
783
+ err := applyInstall(target, payload, true, false, nil)
784
+ if err == nil || !strings.Contains(err.Error(), "refusing") {
785
+ t.Fatalf("linked install error = %v", err)
786
+ }
787
+ if got := testutil.ReadFile(t, filepath.Join(outside, "sentinel")); got != "outside\n" {
788
+ t.Fatalf("linked install changed outside file: %q", got)
789
+ }
790
+ })
791
+ }
792
+
793
+ t.Run("recheck detects change", func(t *testing.T) {
794
+ target := t.TempDir()
795
+ const rel = "managed.txt"
796
+ testutil.WriteFile(t, filepath.Join(target, rel), "before\n")
797
+ r := runner{target: target, preflight: map[string]pathSnapshot{}}
798
+ if _, err := r.rememberPath(rel); err != nil {
799
+ t.Fatal(err)
800
+ }
801
+ testutil.WriteFile(t, filepath.Join(target, rel), "after\n")
802
+ if err := r.recheckPath(rel); err == nil || !strings.Contains(err.Error(), "changed after preflight") {
803
+ t.Fatalf("recheck error = %v", err)
804
+ }
805
+ })
806
+ }
807
+
808
+ func TestVerifyEngineBinary(t *testing.T) {
809
+ if runtime.GOOS == "windows" {
810
+ t.Skip("shell fixtures are Unix-only")
811
+ }
812
+ for _, tc := range []struct {
813
+ name string
814
+ body string
815
+ want string
816
+ ok bool
817
+ }{
818
+ {name: "exact", body: "echo v1.2.3", want: "1.2.3", ok: true},
819
+ {name: "wrong", body: "echo 1.2.4", want: "1.2.3"},
820
+ {name: "dev", body: "echo dev", want: "1.2.3"},
821
+ {name: "multiline", body: "printf '1.2.3\\nextra\\n'", want: "1.2.3"},
822
+ {name: "empty", body: ":", want: "1.2.3"},
823
+ } {
824
+ t.Run(tc.name, func(t *testing.T) {
825
+ path := filepath.Join(t.TempDir(), "engine")
826
+ testutil.WriteExecutable(t, path, "#!/bin/sh\n"+tc.body+"\n")
827
+ _, err := verifyEngineBinary(path, tc.want, time.Second)
828
+ if (err == nil) != tc.ok {
829
+ t.Fatalf("verifyEngineBinary error = %v, ok = %t", err, tc.ok)
830
+ }
831
+ })
832
+ }
833
+ t.Run("timeout", func(t *testing.T) {
834
+ path := filepath.Join(t.TempDir(), "engine")
835
+ testutil.WriteExecutable(t, path, "#!/bin/sh\nsleep 2\n")
836
+ _, err := verifyEngineBinary(path, "1.2.3", 20*time.Millisecond)
837
+ if err == nil || !strings.Contains(err.Error(), "timed out") {
838
+ t.Fatalf("timeout error = %v", err)
839
+ }
840
+ })
841
+ }
842
+
843
+ func TestInstallBinaryRollsBackVerificationFailure(t *testing.T) {
844
+ if runtime.GOOS == "windows" {
845
+ t.Skip("shell fixture is Unix-only; backup/restore portability is covered separately")
846
+ }
847
+ for _, hadOld := range []bool{false, true} {
848
+ t.Run(fmt.Sprintf("had-old-%t", hadOld), func(t *testing.T) {
849
+ binDir := t.TempDir()
850
+ dest := filepath.Join(binDir, "devrites-engine")
851
+ old := "#!/bin/sh\nif [ \"$1\" = version ]; then echo 1.0.0; fi\n"
852
+ if hadOld {
853
+ testutil.WriteExecutable(t, dest, old)
854
+ if err := os.Chmod(dest, 0o700); err != nil {
855
+ t.Fatal(err)
856
+ }
857
+ }
858
+ staged := filepath.Join(t.TempDir(), "devrites-engine")
859
+ body := fmt.Sprintf("#!/bin/sh\nif [ \"$1\" = version ]; then case \"$0\" in %q) echo 1.2.4;; *) echo 1.2.3;; esac; fi\n", dest)
860
+ testutil.WriteExecutable(t, staged, body)
861
+ t.Setenv("DEVRITES_BIN_DIR", binDir)
862
+ t.Setenv("DEVRITES_REF", "v1.2.3")
863
+ t.Setenv("PATH", t.TempDir())
864
+ r := runner{opts: DefaultOptions(ModeInstall), preparedBinary: staged}
865
+
866
+ err := r.installBinary()
867
+ wantOutcome := "bad binary removed"
868
+ if hadOld {
869
+ wantOutcome = "previous binary restored"
870
+ }
871
+ if err == nil || !strings.Contains(err.Error(), wantOutcome) {
872
+ t.Fatalf("installBinary error = %v", err)
873
+ }
874
+ if hadOld {
875
+ if got := testutil.ReadFile(t, dest); got != old {
876
+ t.Fatalf("rollback bytes = %q, want old binary", got)
877
+ }
878
+ info, statErr := os.Stat(dest)
879
+ if statErr != nil {
880
+ t.Fatal(statErr)
881
+ }
882
+ if info.Mode().Perm() != 0o700 {
883
+ t.Fatalf("rollback mode = %v, want 0700", info.Mode().Perm())
884
+ }
885
+ } else if exists(dest) {
886
+ t.Fatal("failed first install left bad binary")
887
+ }
888
+ })
889
+ }
890
+ }
891
+
892
+ func TestBackupRestoreBinaryIsPlatformSafe(t *testing.T) {
893
+ dest := filepath.Join(t.TempDir(), "devrites-engine")
894
+ testutil.WriteFile(t, dest, "old\n")
895
+ if err := os.Chmod(dest, 0o600); err != nil {
896
+ t.Fatal(err)
897
+ }
898
+ backup, mode, hadOld, err := backupBinary(dest)
899
+ if err != nil {
900
+ t.Fatal(err)
901
+ }
902
+ defer os.Remove(backup)
903
+ if filepath.Dir(backup) != filepath.Dir(dest) {
904
+ t.Fatalf("backup %s is not beside %s", backup, dest)
905
+ }
906
+ testutil.WriteFile(t, dest, "bad\n")
907
+ if err := restoreBinary(dest, backup, mode, hadOld); err != nil {
908
+ t.Fatal(err)
909
+ }
910
+ if got := testutil.ReadFile(t, dest); got != "old\n" {
911
+ t.Fatalf("restored bytes = %q", got)
912
+ }
913
+ }
914
+
915
+ func applyInstall(target, payload string, force, dryRun bool, out *bytes.Buffer) error {
916
+ opts := DefaultOptions(ModeInstall)
917
+ opts.Target = target
918
+ opts.PayloadDir = payload
919
+ opts.Force = force
920
+ opts.DryRun = dryRun
921
+ if out == nil {
922
+ out = &bytes.Buffer{}
923
+ }
924
+ opts.Stdout = out
925
+ opts.Stderr = &bytes.Buffer{}
926
+ return Apply(opts)
927
+ }
928
+
929
+ func addManifestRecord(t *testing.T, manifest, rel string, data []byte) {
930
+ t.Helper()
931
+ hash := fmt.Sprintf("sha256:%x", sha256.Sum256(data))
932
+ testutil.AppendFile(t, manifest, "# managed: "+rel+" "+hash+"\n"+rel+"\n")
933
+ }
934
+
454
935
  func runInstall(t *testing.T, target, payload string, mutate func(*Options)) {
455
936
  t.Helper()
456
937
  opts := DefaultOptions(ModeInstall)