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
@@ -6,6 +6,8 @@ import (
6
6
  "os"
7
7
  "path/filepath"
8
8
  "strings"
9
+
10
+ "github.com/devrites/devrites/internal/safepath"
9
11
  )
10
12
 
11
13
  const (
@@ -19,22 +21,59 @@ const (
19
21
  ManifestName = ".claude/devrites.manifest"
20
22
  )
21
23
 
22
- // WorkspaceOverride resolves DEVRITES_WORKSPACE relative to the project
23
- // directory that contains the .devrites root.
24
+ // WorkspaceOverride resolves a valid DEVRITES_WORKSPACE. Invalid overrides are
25
+ // ignored here so path construction stays inside root; callers that surface
26
+ // diagnostics use WorkspaceOverrideChecked.
24
27
  func WorkspaceOverride(root, slug string) string {
28
+ path, _ := WorkspaceOverrideChecked(root, slug)
29
+ return path
30
+ }
31
+
32
+ // WorkspaceOverrideChecked resolves DEVRITES_WORKSPACE relative to the project
33
+ // and proves its physical path remains inside the selected .devrites root.
34
+ func WorkspaceOverrideChecked(root, slug string) (string, error) {
25
35
  raw := strings.TrimSpace(os.Getenv("DEVRITES_WORKSPACE"))
26
36
  if raw == "" {
27
- return ""
37
+ return "", nil
28
38
  }
29
39
  path := raw
30
40
  if !filepath.IsAbs(path) {
31
41
  path = filepath.Join(filepath.Dir(root), path)
32
42
  }
33
- path = filepath.Clean(path)
34
- if slug == "" || filepath.Base(path) == slug {
35
- return path
43
+ abs, err := filepath.Abs(path)
44
+ if err != nil {
45
+ return "", fmt.Errorf("DRV-WORKSPACE-INVALID: resolve DEVRITES_WORKSPACE: %w", err)
36
46
  }
37
- return ""
47
+ path, err = safepath.ResolveExisting(abs)
48
+ if err != nil {
49
+ return "", fmt.Errorf("DRV-WORKSPACE-INVALID: resolve DEVRITES_WORKSPACE %q: %w", raw, err)
50
+ }
51
+ if !safepath.WithinResolved(path, root) {
52
+ return "", fmt.Errorf("DRV-WORKSPACE-OUTSIDE-ROOT: DEVRITES_WORKSPACE %q is outside %q; run `unset DEVRITES_WORKSPACE`", raw, root)
53
+ }
54
+ resolvedRoot, err := safepath.ResolveExisting(root)
55
+ if err != nil {
56
+ return "", fmt.Errorf("DRV-WORKSPACE-INVALID: resolve DevRites root %q: %w", root, err)
57
+ }
58
+ sameRoot := path == resolvedRoot
59
+ if pathInfo, pathErr := os.Stat(path); pathErr == nil {
60
+ if rootInfo, rootErr := os.Stat(resolvedRoot); rootErr == nil {
61
+ sameRoot = os.SameFile(pathInfo, rootInfo)
62
+ }
63
+ }
64
+ rel, relErr := filepath.Rel(resolvedRoot, path)
65
+ parts := strings.FieldsFunc(rel, func(r rune) bool {
66
+ return r == '/' || r == '\\'
67
+ })
68
+ canonicalWorkspace := relErr == nil && len(parts) == 2 &&
69
+ (parts[0] == "work" || parts[0] == "features")
70
+ if sameRoot || !canonicalWorkspace || !validSlug(filepath.Base(path)) {
71
+ return "", fmt.Errorf("DRV-WORKSPACE-INVALID: DEVRITES_WORKSPACE %q is not a feature workspace; run `unset DEVRITES_WORKSPACE`", raw)
72
+ }
73
+ if slug != "" && filepath.Base(path) != slug {
74
+ return "", fmt.Errorf("DRV-WORKSPACE-SLUG-MISMATCH: DEVRITES_WORKSPACE basename %q does not match feature %q; run `unset DEVRITES_WORKSPACE`", filepath.Base(path), slug)
75
+ }
76
+ return path, nil
38
77
  }
39
78
 
40
79
  // FeatureDir is the per-feature workspace directory under root. work/ is
@@ -57,20 +96,36 @@ func FeatureDir(root, slug string) string {
57
96
  // ActiveSlug reads and trims the active-feature pointer. A missing pointer file
58
97
  // is not an error; it yields the empty slug.
59
98
  func ActiveSlug(root string) (string, error) {
60
- if ws := WorkspaceOverride(root, ""); ws != "" {
99
+ if ws, err := WorkspaceOverrideChecked(root, ""); err != nil {
100
+ return "", err
101
+ } else if ws != "" {
61
102
  return filepath.Base(ws), nil
62
103
  }
63
- raw, err := os.ReadFile(filepath.Join(root, "ACTIVE"))
104
+ activePath := filepath.Join(root, "ACTIVE")
105
+ if info, err := os.Lstat(activePath); err == nil && info.Mode()&os.ModeSymlink != 0 {
106
+ return "", fmt.Errorf("DRV-ACTIVE-SYMLINK: ACTIVE is a symlink; run `rm -f %q`", activePath)
107
+ }
108
+ raw, err := os.ReadFile(activePath)
64
109
  if errors.Is(err, os.ErrNotExist) {
65
110
  return "", nil
66
111
  }
67
112
  if err != nil {
68
113
  return "", fmt.Errorf("read active feature: %w", err)
69
114
  }
70
- return strings.TrimSpace(string(raw)), nil
115
+ slug := strings.TrimSpace(string(raw))
116
+ if slug != "" && !validSlug(slug) {
117
+ return "", fmt.Errorf("DRV-ACTIVE-INVALID: ACTIVE value %q is not a feature slug; run `rm -f %q`", slug, activePath)
118
+ }
119
+ return slug, nil
71
120
  }
72
121
 
73
122
  func isDir(path string) bool {
74
123
  info, err := os.Stat(path)
75
124
  return err == nil && info.IsDir()
76
125
  }
126
+
127
+ func validSlug(slug string) bool {
128
+ return slug != "" && slug != "." && slug != ".." &&
129
+ filepath.Base(slug) == slug &&
130
+ !strings.ContainsAny(slug, `/\`)
131
+ }
@@ -0,0 +1,110 @@
1
+ package devritespaths
2
+
3
+ import (
4
+ "os"
5
+ "path/filepath"
6
+ "strings"
7
+ "testing"
8
+ )
9
+
10
+ func TestWorkspaceOverrideCheckedRequiresPhysicalRootContainment(t *testing.T) {
11
+ project := t.TempDir()
12
+ root := filepath.Join(project, DevritesRootName)
13
+ workspace := filepath.Join(root, "work", "feature")
14
+ if err := os.MkdirAll(workspace, 0o755); err != nil {
15
+ t.Fatal(err)
16
+ }
17
+
18
+ t.Setenv("DEVRITES_WORKSPACE", workspace)
19
+ got, err := WorkspaceOverrideChecked(root, "feature")
20
+ if err != nil {
21
+ t.Fatal(err)
22
+ }
23
+ want, err := filepath.EvalSymlinks(workspace)
24
+ if err != nil {
25
+ t.Fatal(err)
26
+ }
27
+ if got != want {
28
+ t.Fatalf("WorkspaceOverrideChecked() = %q, want canonical %q", got, want)
29
+ }
30
+ t.Setenv("DEVRITES_WORKSPACE", filepath.Join(DevritesRootName, "work", "feature"))
31
+ if got, err := WorkspaceOverrideChecked(root, "feature"); err != nil || got != want {
32
+ t.Fatalf("relative WorkspaceOverrideChecked() = %q, %v; want %q", got, err, want)
33
+ }
34
+
35
+ outside := filepath.Join(t.TempDir(), "feature")
36
+ t.Setenv("DEVRITES_WORKSPACE", outside)
37
+ if _, err := WorkspaceOverrideChecked(root, "feature"); err == nil || !strings.Contains(err.Error(), "DRV-WORKSPACE-OUTSIDE-ROOT") {
38
+ t.Fatalf("outside workspace error = %v, want stable containment diagnostic", err)
39
+ }
40
+ if got := WorkspaceOverride(root, "feature"); got != "" {
41
+ t.Fatalf("unsafe compatibility result = %q, want empty", got)
42
+ }
43
+
44
+ t.Setenv("DEVRITES_WORKSPACE", root)
45
+ if _, err := WorkspaceOverrideChecked(root, ""); err == nil || !strings.Contains(err.Error(), "DRV-WORKSPACE-INVALID") {
46
+ t.Fatalf("root-as-workspace error = %v", err)
47
+ }
48
+
49
+ arbitrary := filepath.Join(root, "extensions", "feature")
50
+ if err := os.MkdirAll(arbitrary, 0o755); err != nil {
51
+ t.Fatal(err)
52
+ }
53
+ t.Setenv("DEVRITES_WORKSPACE", arbitrary)
54
+ if _, err := WorkspaceOverrideChecked(root, ""); err == nil || !strings.Contains(err.Error(), "DRV-WORKSPACE-INVALID") {
55
+ t.Fatalf("non-feature path error = %v", err)
56
+ }
57
+ }
58
+
59
+ func TestWorkspaceOverrideCheckedRejectsSymlinkEscapeAndSlugMismatch(t *testing.T) {
60
+ project := t.TempDir()
61
+ root := filepath.Join(project, DevritesRootName)
62
+ if err := os.MkdirAll(filepath.Join(root, "work"), 0o755); err != nil {
63
+ t.Fatal(err)
64
+ }
65
+ outside := t.TempDir()
66
+ link := filepath.Join(root, "work", "feature")
67
+ if err := os.Symlink(outside, link); err != nil {
68
+ t.Skipf("symlinks unavailable: %v", err)
69
+ }
70
+ t.Setenv("DEVRITES_WORKSPACE", link)
71
+ if _, err := WorkspaceOverrideChecked(root, "feature"); err == nil || !strings.Contains(err.Error(), "DRV-WORKSPACE-OUTSIDE-ROOT") {
72
+ t.Fatalf("symlink escape error = %v", err)
73
+ }
74
+
75
+ inside := filepath.Join(root, "work", "other")
76
+ if err := os.MkdirAll(inside, 0o755); err != nil {
77
+ t.Fatal(err)
78
+ }
79
+ t.Setenv("DEVRITES_WORKSPACE", inside)
80
+ if _, err := WorkspaceOverrideChecked(root, "feature"); err == nil || !strings.Contains(err.Error(), "DRV-WORKSPACE-SLUG-MISMATCH") {
81
+ t.Fatalf("slug mismatch error = %v", err)
82
+ }
83
+ }
84
+
85
+ func TestActiveSlugRejectsPathTraversal(t *testing.T) {
86
+ root := filepath.Join(t.TempDir(), DevritesRootName)
87
+ if err := os.MkdirAll(root, 0o755); err != nil {
88
+ t.Fatal(err)
89
+ }
90
+ if err := os.WriteFile(filepath.Join(root, "ACTIVE"), []byte("../../outside\n"), 0o644); err != nil {
91
+ t.Fatal(err)
92
+ }
93
+ if _, err := ActiveSlug(root); err == nil || !strings.Contains(err.Error(), "DRV-ACTIVE-INVALID") {
94
+ t.Fatalf("ActiveSlug error = %v, want stable invalid-pointer diagnostic", err)
95
+ }
96
+
97
+ if err := os.Remove(filepath.Join(root, "ACTIVE")); err != nil {
98
+ t.Fatal(err)
99
+ }
100
+ target := filepath.Join(t.TempDir(), "ACTIVE")
101
+ if err := os.WriteFile(target, []byte("feature\n"), 0o644); err != nil {
102
+ t.Fatal(err)
103
+ }
104
+ if err := os.Symlink(target, filepath.Join(root, "ACTIVE")); err != nil {
105
+ t.Skipf("symlinks unavailable: %v", err)
106
+ }
107
+ if _, err := ActiveSlug(root); err == nil || !strings.Contains(err.Error(), "DRV-ACTIVE-SYMLINK") {
108
+ t.Fatalf("ActiveSlug symlink error = %v, want stable pointer diagnostic", err)
109
+ }
110
+ }
@@ -1,22 +1,24 @@
1
- // Package doctor reports the version triangle: the engine binary, the installed
2
- // DevRites pack, and the on-disk .devrites state schema, and a clear skew
3
- // verdict. Version skew is a leading cause of silent breakage; doctor turns it
4
- // into one legible line. Everything here is a read; doctor never mutates state.
1
+ // Package doctor compares the engine, installed pack, and workspace schema
2
+ // versions. It also reports root and installation problems without changing
3
+ // workspace state.
5
4
  package doctor
6
5
 
7
6
  import (
7
+ "crypto/sha256"
8
8
  "encoding/json"
9
9
  "fmt"
10
+ "io/fs"
10
11
  "os"
11
12
  "path/filepath"
12
13
  "strings"
13
14
 
15
+ "github.com/devrites/devrites/internal/rootfacts"
14
16
  "github.com/devrites/devrites/internal/state"
15
17
  "github.com/devrites/devrites/internal/version"
16
18
  )
17
19
 
18
- // Unknown is the pack value reported when the installed pack version can't be
19
- // discovered: skew against an unknown pack is not asserted.
20
+ // Unknown is reported when the installed pack version cannot be found. Doctor
21
+ // does not report version skew against this value.
20
22
  const Unknown = "unknown"
21
23
 
22
24
  // Report is the resolved version triangle plus its verdict.
@@ -27,28 +29,45 @@ type Report struct {
27
29
  BinarySchema int // the schema version this binary understands
28
30
  Verdict string // one-line human summary
29
31
  Checks []string // advisory install/host-artifact drift checks
30
- Refuse bool // true iff state schema is a newer major than the binary supports
32
+ Refuse bool // true when topology or state schema makes action unsafe
33
+ Root rootfacts.Facts
31
34
  }
32
35
 
33
- // Diagnose resolves the triangle for the project rooted at projectDir with the
34
- // .devrites directory at root, and computes the skew verdict.
36
+ // Diagnose compares versions for projectDir and its .devrites root.
35
37
  //
36
- // - State schema newer (higher major) than the binary supports Refuse: the
37
- // binary would not safely parse it, so it reports that rather than guessing.
38
- // - Binary older than the installed pack a warning, never a block: an older
39
- // binary can still run; the user is just told to update.
40
- // - Otherwise → ok. Additive changes (older state read by a newer binary) are
41
- // always fine.
38
+ // - A state schema newer than the binary causes a refusal because the binary
39
+ // cannot safely parse it.
40
+ // - A binary older than the pack produces a warning but does not block.
41
+ // - All other comparable versions are compatible. A newer binary can read
42
+ // older state because schema changes are additive.
42
43
  func Diagnose(projectDir, root string) (*Report, error) {
43
- stateSchema, err := state.MaxDeclaredSchemaVersion(root)
44
- if err != nil {
45
- return nil, fmt.Errorf("read state schema: %w", err)
46
- }
47
- if stateSchema == 0 {
48
- // Nothing on disk declares a version: treat it as the current schema;
49
- // undeclared state is read as the engine's own version.
50
- stateSchema = state.SchemaVersion
44
+ return DiagnoseFacts(rootfacts.Facts{
45
+ LexicalProject: projectDir,
46
+ PhysicalProject: projectDir,
47
+ LexicalRoot: root,
48
+ PhysicalRoot: root,
49
+ SelectionReason: "provided",
50
+ Hazards: []rootfacts.Hazard{},
51
+ })
52
+ }
53
+
54
+ // DiagnoseFacts reports the same canonical root facts used by state selection
55
+ // and context show.
56
+ func DiagnoseFacts(facts rootfacts.Facts) (*Report, error) {
57
+ stateSchema := state.SchemaVersion
58
+ if facts.PhysicalRoot != "" {
59
+ var err error
60
+ stateSchema, err = state.MaxDeclaredSchemaVersion(facts.PhysicalRoot)
61
+ if err != nil {
62
+ return nil, fmt.Errorf("read state schema: %w", err)
63
+ }
64
+ if stateSchema == 0 {
65
+ // Nothing on disk declares a version: treat it as the current schema;
66
+ // undeclared state is read as the engine's own version.
67
+ stateSchema = state.SchemaVersion
68
+ }
51
69
  }
70
+ projectDir := facts.PhysicalProject
52
71
 
53
72
  r := &Report{
54
73
  Binary: version.Version,
@@ -56,9 +75,13 @@ func Diagnose(projectDir, root string) (*Report, error) {
56
75
  StateSchema: stateSchema,
57
76
  BinarySchema: state.SchemaVersion,
58
77
  Checks: driftChecks(projectDir),
78
+ Root: facts,
59
79
  }
60
80
 
61
81
  switch {
82
+ case facts.Refuses():
83
+ r.Refuse = true
84
+ r.Verdict = "REFUSE: root or workspace selection is unsafe; apply the reported remediation"
62
85
  case r.StateSchema > r.BinarySchema:
63
86
  r.Refuse = true
64
87
  r.Verdict = fmt.Sprintf("REFUSE: state schema v%d is newer than this binary supports (v%d): update devrites",
@@ -66,6 +89,8 @@ func Diagnose(projectDir, root string) (*Report, error) {
66
89
  case r.packSkew():
67
90
  r.Verdict = fmt.Sprintf("WARN: binary %s is older than the installed pack %s: update the devrites-engine binary",
68
91
  r.Binary, r.Pack)
92
+ case len(facts.Hazards) > 0:
93
+ r.Verdict = "WARN: workspace or repository hazards need attention"
69
94
  default:
70
95
  r.Verdict = "ok: binary, pack, and state schema are compatible"
71
96
  }
@@ -84,10 +109,46 @@ func (r *Report) packSkew() bool {
84
109
  // Render produces the deterministic doctor report with a trailing newline.
85
110
  func (r *Report) Render() string {
86
111
  var b strings.Builder
112
+ physicalRoot := r.Root.PhysicalRoot
113
+ if physicalRoot == "" {
114
+ physicalRoot = "none"
115
+ }
116
+ physicalProject := r.Root.PhysicalProject
117
+ if physicalProject == "" {
118
+ physicalProject = "unknown"
119
+ }
120
+ fmt.Fprintf(&b, "project: %s\n", physicalProject)
121
+ fmt.Fprintf(&b, "root: %s\n", physicalRoot)
122
+ fmt.Fprintf(&b, "root-selection: %s\n", r.Root.SelectionReason)
123
+ if r.Root.LexicalProject != "" && r.Root.LexicalProject != r.Root.PhysicalProject {
124
+ fmt.Fprintf(&b, "project-lexical: %s\n", r.Root.LexicalProject)
125
+ }
126
+ if r.Root.LexicalRoot != "" && r.Root.LexicalRoot != r.Root.PhysicalRoot {
127
+ fmt.Fprintf(&b, "root-lexical: %s\n", r.Root.LexicalRoot)
128
+ }
129
+ if r.Root.Git.TopLevel == "" {
130
+ fmt.Fprintln(&b, "git: none")
131
+ } else {
132
+ fmt.Fprintf(&b, "git: top=%s dir=%s common=%s linked-worktree=%t submodule=%t\n",
133
+ r.Root.Git.TopLevel, r.Root.Git.Dir, r.Root.Git.CommonDir,
134
+ r.Root.Git.LinkedWorktree, r.Root.Git.Submodule)
135
+ if r.Root.Git.Superproject != "" {
136
+ fmt.Fprintf(&b, "git-superproject: %s\n", r.Root.Git.Superproject)
137
+ }
138
+ }
87
139
  fmt.Fprintf(&b, "binary: %s\n", r.Binary)
88
140
  fmt.Fprintf(&b, "pack: %s\n", r.Pack)
89
141
  fmt.Fprintf(&b, "state-schema: v%d (binary supports v%d)\n", r.StateSchema, r.BinarySchema)
90
142
  fmt.Fprintf(&b, "verdict: %s\n", r.Verdict)
143
+ if len(r.Root.Hazards) == 0 {
144
+ fmt.Fprintln(&b, "hazards: ok")
145
+ } else {
146
+ fmt.Fprintln(&b, "hazards:")
147
+ for _, hazard := range r.Root.Hazards {
148
+ fmt.Fprintf(&b, " - [%s] %s: %s\n", hazard.ID, strings.ToUpper(hazard.Severity), hazard.Message)
149
+ fmt.Fprintf(&b, " fix: %s\n", hazard.Remediation)
150
+ }
151
+ }
91
152
  if len(r.Checks) == 0 {
92
153
  fmt.Fprintln(&b, "checks: ok")
93
154
  } else {
@@ -142,9 +203,78 @@ func driftChecks(projectDir string) []string {
142
203
  if firstLine(filepath.Join(projectDir, ".claude", "devrites.version")) == "" && isDir(filepath.Join(projectDir, ".claude", "skills")) {
143
204
  checks = append(checks, "WARN: installed skills have no .claude/devrites.version marker")
144
205
  }
206
+ if generatedClaudeDrift(projectDir) {
207
+ checks = append(checks, "[DRV-GENERATED-DRIFT] WARN: pack/generated/claude differs from canonical pack/.claude; run `bash scripts/build-host-artifacts.sh`")
208
+ }
145
209
  return checks
146
210
  }
147
211
 
212
+ func generatedClaudeDrift(projectDir string) bool {
213
+ canonical := filepath.Join(projectDir, "pack", ".claude")
214
+ generated := filepath.Join(projectDir, "pack", "generated", "claude")
215
+ if !isDir(canonical) {
216
+ return false
217
+ }
218
+ want, err := fileDigests(canonical, func(rel string) bool {
219
+ return rel == filepath.Join("agents", ".impeccable") ||
220
+ strings.HasPrefix(rel, filepath.Join("agents", ".impeccable")+string(filepath.Separator))
221
+ })
222
+ if err != nil {
223
+ return true
224
+ }
225
+ got, err := fileDigests(generated, nil)
226
+ if err != nil || len(want) != len(got) {
227
+ return true
228
+ }
229
+ for path, digest := range want {
230
+ if got[path] != digest {
231
+ return true
232
+ }
233
+ }
234
+ return false
235
+ }
236
+
237
+ func fileDigests(root string, skip func(string) bool) (map[string][sha256.Size]byte, error) {
238
+ rootDir, err := os.OpenRoot(root)
239
+ if err != nil {
240
+ return nil, err
241
+ }
242
+ defer func() { _ = rootDir.Close() }()
243
+
244
+ files := map[string][sha256.Size]byte{}
245
+ err = filepath.WalkDir(root, func(path string, entry fs.DirEntry, err error) error {
246
+ if err != nil {
247
+ return err
248
+ }
249
+ rel, err := filepath.Rel(root, path)
250
+ if err != nil {
251
+ return err
252
+ }
253
+ if rel == "." {
254
+ return nil
255
+ }
256
+ if skip != nil && skip(rel) {
257
+ if entry.IsDir() {
258
+ return filepath.SkipDir
259
+ }
260
+ return nil
261
+ }
262
+ if entry.IsDir() {
263
+ return nil
264
+ }
265
+ if entry.Type()&os.ModeSymlink != 0 || !entry.Type().IsRegular() {
266
+ return fmt.Errorf("generated tree contains non-regular file %s", rel)
267
+ }
268
+ body, err := rootDir.ReadFile(rel)
269
+ if err != nil {
270
+ return err
271
+ }
272
+ files[filepath.ToSlash(rel)] = sha256.Sum256(body)
273
+ return nil
274
+ })
275
+ return files, err
276
+ }
277
+
148
278
  func readString(path string) string {
149
279
  raw, err := os.ReadFile(path)
150
280
  if err != nil {
@@ -6,6 +6,7 @@ import (
6
6
  "strings"
7
7
  "testing"
8
8
 
9
+ "github.com/devrites/devrites/internal/rootfacts"
9
10
  "github.com/devrites/devrites/internal/version"
10
11
  )
11
12
 
@@ -148,3 +149,76 @@ func TestDiagnoseReportsHostArtifactDrift(t *testing.T) {
148
149
  }
149
150
  }
150
151
  }
152
+
153
+ func TestDiagnoseFactsReportsCanonicalRootAndStaleActive(t *testing.T) {
154
+ projectDir := t.TempDir()
155
+ root := filepath.Join(projectDir, ".devrites")
156
+ if err := os.MkdirAll(root, 0o755); err != nil {
157
+ t.Fatal(err)
158
+ }
159
+ if err := os.WriteFile(filepath.Join(root, "ACTIVE"), []byte("ghost\n"), 0o644); err != nil {
160
+ t.Fatal(err)
161
+ }
162
+ facts, err := rootfacts.ResolveFrom(projectDir, projectDir)
163
+ if err != nil {
164
+ t.Fatal(err)
165
+ }
166
+ r, err := DiagnoseFacts(facts)
167
+ if err != nil {
168
+ t.Fatal(err)
169
+ }
170
+ out := r.Render()
171
+ for _, want := range []string{
172
+ "root: " + facts.PhysicalRoot,
173
+ "root-selection: DEVRITES_ROOT",
174
+ "[DRV-ACTIVE-STALE]",
175
+ "fix: rm -f",
176
+ } {
177
+ if !strings.Contains(out, want) {
178
+ t.Fatalf("doctor output missing %q:\n%s", want, out)
179
+ }
180
+ }
181
+ if r.Refuse {
182
+ t.Fatal("stale ACTIVE should warn, not turn a read-only doctor run into a refusal")
183
+ }
184
+ }
185
+
186
+ func TestDiagnoseReportsCanonicalGeneratedResidue(t *testing.T) {
187
+ projectDir := t.TempDir()
188
+ canonical := filepath.Join(projectDir, "pack", ".claude")
189
+ generated := filepath.Join(projectDir, "pack", "generated", "claude")
190
+ for _, dir := range []string{canonical, generated} {
191
+ if err := os.MkdirAll(dir, 0o755); err != nil {
192
+ t.Fatal(err)
193
+ }
194
+ }
195
+ for _, path := range []string{
196
+ filepath.Join(canonical, "settings.json"),
197
+ filepath.Join(generated, "settings.json"),
198
+ } {
199
+ if err := os.WriteFile(path, []byte(`{"hooks":{}}`), 0o644); err != nil {
200
+ t.Fatal(err)
201
+ }
202
+ }
203
+ root := t.TempDir()
204
+ writeFeature(t, root, "f", 1)
205
+ aligned, err := Diagnose(projectDir, root)
206
+ if err != nil {
207
+ t.Fatal(err)
208
+ }
209
+ if strings.Contains(aligned.Render(), "DRV-GENERATED-DRIFT") {
210
+ t.Fatalf("aligned generated tree reported drift:\n%s", aligned.Render())
211
+ }
212
+
213
+ if err := os.WriteFile(filepath.Join(generated, "legacy.json"), []byte("{}"), 0o644); err != nil {
214
+ t.Fatal(err)
215
+ }
216
+ drifted, err := Diagnose(projectDir, root)
217
+ if err != nil {
218
+ t.Fatal(err)
219
+ }
220
+ if !strings.Contains(drifted.Render(), "[DRV-GENERATED-DRIFT]") ||
221
+ !strings.Contains(drifted.Render(), "bash scripts/build-host-artifacts.sh") {
222
+ t.Fatalf("generated residue not diagnosed:\n%s", drifted.Render())
223
+ }
224
+ }