devrites 3.0.7 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (484) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +82 -50
  3. package/SECURITY.md +31 -29
  4. package/docs/adr/0001-go-engine-as-control-plane.md +1 -1
  5. package/docs/adr/0002-dual-host-harness.md +1 -1
  6. package/docs/adr/0004-state-schema-phases-sections.md +1 -2
  7. package/docs/adr/0006-clock-seam-and-engine-ci-gates.md +3 -4
  8. package/docs/adr/0009-prebuild-decision-coverage-and-readiness.md +67 -0
  9. package/docs/adr/0010-agent-first-fresh-context-orchestration.md +71 -0
  10. package/docs/adr/0011-define-plan-transition-rights.md +28 -0
  11. package/docs/adr/0012-semantic-workspace-upgrades.md +77 -0
  12. package/docs/adr/README.md +4 -0
  13. package/docs/agents/triage-labels.md +7 -7
  14. package/docs/architecture.md +179 -141
  15. package/docs/cli.md +47 -12
  16. package/docs/command-map.md +61 -38
  17. package/docs/engine/agent-contract.md +92 -15
  18. package/docs/engine/commands.md +248 -72
  19. package/docs/engine/state-schema.md +52 -23
  20. package/docs/engine/workspace-schema.md +103 -15
  21. package/docs/extensions.md +1 -1
  22. package/docs/flow.md +86 -51
  23. package/docs/harness-compliance.md +29 -5
  24. package/docs/orchestration.md +107 -79
  25. package/docs/quick-reference.md +11 -3
  26. package/docs/release.md +4 -3
  27. package/docs/skills.md +67 -38
  28. package/docs/usage.md +84 -39
  29. package/engine/cmd/releasepack/main.go +219 -0
  30. package/engine/cmd/releasepack/main_test.go +170 -0
  31. package/engine/commands.go +170 -23
  32. package/engine/git_guard.go +187 -0
  33. package/engine/git_guard_test.go +283 -0
  34. package/engine/hookpolicy.go +53 -55
  35. package/engine/hookpolicy_test.go +91 -1
  36. package/engine/hooks.go +296 -75
  37. package/engine/hooks_events_test.go +276 -6
  38. package/engine/hooks_workspace.go +640 -159
  39. package/engine/internal/devritespaths/paths.go +65 -10
  40. package/engine/internal/devritespaths/paths_test.go +110 -0
  41. package/engine/internal/doctor/doctor.go +153 -23
  42. package/engine/internal/doctor/doctor_test.go +74 -0
  43. package/engine/internal/forge/forge.go +940 -0
  44. package/engine/internal/forge/forge_test.go +576 -0
  45. package/engine/internal/forge/git.go +245 -0
  46. package/engine/internal/forge/liveness_unix.go +48 -0
  47. package/engine/internal/forge/liveness_windows.go +67 -0
  48. package/engine/internal/forge/manifest.go +402 -0
  49. package/engine/internal/gate/gate.go +90 -71
  50. package/engine/internal/gate/gate_test.go +71 -2
  51. package/engine/internal/harness/compliance.go +18 -24
  52. package/engine/internal/harness/harness.go +37 -44
  53. package/engine/internal/harness/harness_test.go +21 -5
  54. package/engine/internal/install/install.go +486 -38
  55. package/engine/internal/install/install_test.go +383 -10
  56. package/engine/internal/iohooks/iohooks.go +350 -59
  57. package/engine/internal/iohooks/iohooks_test.go +421 -1
  58. package/engine/internal/lib/buildreadiness.go +43 -18
  59. package/engine/internal/lib/clarifyreturn.go +91 -0
  60. package/engine/internal/lib/clarifyreturn_test.go +123 -0
  61. package/engine/internal/lib/context.go +54 -15
  62. package/engine/internal/lib/cursor_compat_test.go +8 -0
  63. package/engine/internal/lib/extensions.go +2 -3
  64. package/engine/internal/lib/gitauthority.go +601 -0
  65. package/engine/internal/lib/gitauthority_test.go +346 -0
  66. package/engine/internal/lib/jsonout.go +25 -11
  67. package/engine/internal/lib/jsonout_test.go +16 -0
  68. package/engine/internal/lib/lanes.go +14 -8
  69. package/engine/internal/lib/observability_test.go +358 -0
  70. package/engine/internal/lib/packageexistence.go +151 -35
  71. package/engine/internal/lib/packageexistence_test.go +163 -0
  72. package/engine/internal/lib/progress.go +11 -9
  73. package/engine/internal/lib/provenance.go +462 -0
  74. package/engine/internal/lib/provenance_test.go +154 -0
  75. package/engine/internal/lib/readiness_contract.json +118 -0
  76. package/engine/internal/lib/readinessartifact.go +533 -0
  77. package/engine/internal/lib/readinessartifact_test.go +422 -0
  78. package/engine/internal/lib/reconcile.go +712 -88
  79. package/engine/internal/lib/reconcile_test.go +335 -16
  80. package/engine/internal/lib/recoveryattempts.go +298 -0
  81. package/engine/internal/lib/recoveryattempts_test.go +215 -0
  82. package/engine/internal/lib/resolve.go +23 -19
  83. package/engine/internal/lib/runbook_context_test.go +20 -0
  84. package/engine/internal/lib/session.go +701 -9
  85. package/engine/internal/lib/session_test.go +80 -13
  86. package/engine/internal/lib/testintegrity.go +33 -37
  87. package/engine/internal/lib/testintegrity_test.go +63 -1
  88. package/engine/internal/migrate/migrate.go +81 -1
  89. package/engine/internal/migrate/migrate_test.go +22 -1
  90. package/engine/internal/reason/reason.go +180 -0
  91. package/engine/internal/reason/reason_test.go +35 -0
  92. package/engine/internal/rootfacts/facts.go +466 -0
  93. package/engine/internal/rootfacts/facts_test.go +306 -0
  94. package/engine/internal/safepath/safepath.go +55 -0
  95. package/engine/internal/safepath/safepath_test.go +69 -0
  96. package/engine/internal/safepath/safepath_windows_test.go +26 -0
  97. package/engine/internal/state/clarify_transition.go +165 -0
  98. package/engine/internal/state/clarify_transition_test.go +130 -0
  99. package/engine/internal/state/cmd/workflowmanifest/main.go +12 -8
  100. package/engine/internal/state/cursor.go +58 -0
  101. package/engine/internal/state/cursor_test.go +42 -1
  102. package/engine/internal/state/feature.go +35 -48
  103. package/engine/internal/state/schema.go +90 -37
  104. package/engine/internal/state/snapshot.go +18 -2
  105. package/engine/internal/state/state_test.go +187 -7
  106. package/engine/internal/state/status.go +48 -11
  107. package/engine/internal/state/workflow_manifest.json +231 -50
  108. package/engine/internal/toolpolicy/classifier.go +533 -0
  109. package/engine/internal/toolpolicy/classifier_test.go +424 -0
  110. package/engine/internal/toolpolicy/git.go +616 -0
  111. package/engine/internal/toolpolicy/scanner.go +382 -0
  112. package/engine/main.go +160 -77
  113. package/engine/observability_cli_test.go +52 -0
  114. package/engine/root_routing_test.go +277 -0
  115. package/engine/testdata/golden/TestParityBuildReadiness/arg=approved.golden +1 -1
  116. package/engine/testdata/golden/TestParityBuildReadiness/arg=emptystatus.golden +1 -1
  117. package/engine/testdata/golden/TestParityBuildReadiness/arg=legacycontract.golden +1 -0
  118. package/engine/testdata/golden/TestParityBuildReadiness/arg=noclarify.golden +1 -0
  119. package/engine/testdata/golden/TestParityBuildReadiness/arg=novet.golden +1 -0
  120. package/engine/testdata/golden/TestParityBuildReadiness/arg=stalevet.golden +1 -0
  121. package/engine/testdata/golden/TestParityBuildReadiness/arg=trailhash.golden +1 -1
  122. package/engine/testdata/golden/TestParityBuildReadiness/arg=trailpipe.golden +1 -1
  123. package/engine/testdata/golden/TestParityBuildReadiness/arg=vetnotready.golden +1 -0
  124. package/engine/testdata/golden/TestParityProgress/arg=allbuilt.golden +1 -1
  125. package/engine/testdata/golden/TestParityProgress/arg=done.golden +1 -1
  126. package/engine/testdata/golden/TestParityProgress/arg=mid.golden +1 -1
  127. package/engine/testdata/golden/TestParityProgress/arg=nophase.golden +1 -1
  128. package/engine/testdata/golden/TestParityProgress/arg=noslice.golden +1 -1
  129. package/engine/testdata/golden/TestParityProgress/arg=plan.golden +1 -1
  130. package/engine/testdata/golden/TestParityProgress/arg=seal.golden +1 -1
  131. package/engine/testdata/golden/TestParityReconcile/check-clean.golden +1 -1
  132. package/engine/testdata/golden/TestParityReconcile/inline-fallback.golden +1 -0
  133. package/engine/testdata/golden/TestParityReconcile/snapshot-no-allowlist.golden +1 -0
  134. package/engine/testdata/golden/TestParityWrightScope/devrites-edit-denied.golden +2 -0
  135. package/engine/testdata/golden/TestParityWrightScope/out-of-scope-enforce-denies.golden +1 -1
  136. package/engine/tests/adr_0011_define_plan_test.go +30 -0
  137. package/engine/tests/budget_test.go +19 -8
  138. package/engine/tests/concurrency_cli_test.go +7 -1
  139. package/engine/tests/doctor_cli_test.go +148 -2
  140. package/engine/tests/forge_cli_test.go +463 -0
  141. package/engine/tests/gate_test.go +63 -8
  142. package/engine/tests/hook_test.go +260 -8
  143. package/engine/tests/hooks_io_test.go +94 -3
  144. package/engine/tests/json_contract_test.go +127 -2
  145. package/engine/tests/migrate_cli_test.go +2 -2
  146. package/engine/tests/parity_buildreadiness_test.go +167 -10
  147. package/engine/tests/parity_learnings_test.go +16 -17
  148. package/engine/tests/parity_reconcile_test.go +20 -17
  149. package/engine/tests/parity_resolve_test.go +12 -11
  150. package/engine/tests/parity_test.go +21 -17
  151. package/pack/.claude/agents/devrites-code-reviewer.md +62 -48
  152. package/pack/.claude/agents/devrites-devex-reviewer.md +69 -53
  153. package/pack/.claude/agents/devrites-doubt-reviewer.md +29 -22
  154. package/pack/.claude/agents/devrites-evidence-scout.md +69 -0
  155. package/pack/.claude/agents/devrites-forge-judge.md +74 -61
  156. package/pack/.claude/agents/devrites-frontend-reviewer.md +48 -39
  157. package/pack/.claude/agents/devrites-performance-reviewer.md +49 -40
  158. package/pack/.claude/agents/devrites-plan-drafter.md +73 -0
  159. package/pack/.claude/agents/devrites-plan-reviewer.md +80 -47
  160. package/pack/.claude/agents/devrites-proof-runner.md +74 -0
  161. package/pack/.claude/agents/devrites-retrospector.md +48 -45
  162. package/pack/.claude/agents/devrites-security-auditor.md +46 -36
  163. package/pack/.claude/agents/devrites-simplifier-reviewer.md +50 -46
  164. package/pack/.claude/agents/devrites-slice-wright.md +153 -165
  165. package/pack/.claude/agents/devrites-spec-reviewer.md +34 -32
  166. package/pack/.claude/agents/devrites-strategy-reviewer.md +63 -34
  167. package/pack/.claude/agents/devrites-test-analyst.md +40 -30
  168. package/pack/.claude/agents/devrites-upgrade-planner.md +100 -0
  169. package/pack/.claude/settings.json +2 -1
  170. package/pack/.claude/skills/devrites-audit/SKILL.md +40 -61
  171. package/pack/.claude/skills/devrites-debug-recovery/SKILL.md +24 -13
  172. package/pack/.claude/skills/devrites-debug-recovery/reference/build-the-loop.md +24 -21
  173. package/pack/.claude/skills/devrites-debug-recovery/reference/cleanup-and-classify.md +34 -6
  174. package/pack/.claude/skills/devrites-debug-recovery/reference/instrumentation.md +2 -2
  175. package/pack/.claude/skills/devrites-doubt/SKILL.md +25 -17
  176. package/pack/.claude/skills/devrites-interview/SKILL.md +53 -52
  177. package/pack/.claude/skills/devrites-lib/SKILL.md +12 -9
  178. package/pack/.claude/skills/devrites-lib/reference/intent-map.md +4 -2
  179. package/pack/.claude/skills/devrites-lib/reference/parallel-dispatch.md +82 -137
  180. package/pack/.claude/skills/devrites-lib/reference/reply-contract.md +8 -1
  181. package/pack/.claude/skills/devrites-lib/reference/standards/README.md +38 -55
  182. package/pack/.claude/skills/devrites-lib/reference/standards/afk-hitl.md +53 -27
  183. package/pack/.claude/skills/devrites-lib/reference/standards/agents.md +200 -190
  184. package/pack/.claude/skills/devrites-lib/reference/standards/anti-patterns.md +5 -15
  185. package/pack/.claude/skills/devrites-lib/reference/standards/ci-cd.md +27 -58
  186. package/pack/.claude/skills/devrites-lib/reference/standards/code-review.md +16 -52
  187. package/pack/.claude/skills/devrites-lib/reference/standards/coding-style.md +2 -10
  188. package/pack/.claude/skills/devrites-lib/reference/standards/context-hygiene.md +18 -61
  189. package/pack/.claude/skills/devrites-lib/reference/standards/core.md +23 -18
  190. package/pack/.claude/skills/devrites-lib/reference/standards/deprecation.md +17 -57
  191. package/pack/.claude/skills/devrites-lib/reference/standards/development-workflow.md +4 -33
  192. package/pack/.claude/skills/devrites-lib/reference/standards/git-workflow.md +4 -20
  193. package/pack/.claude/skills/devrites-lib/reference/standards/hooks.md +3 -14
  194. package/pack/.claude/skills/devrites-lib/reference/standards/patterns.md +9 -25
  195. package/pack/.claude/skills/devrites-lib/reference/standards/performance.md +0 -9
  196. package/pack/.claude/skills/devrites-lib/reference/standards/principles.md +1 -3
  197. package/pack/.claude/skills/devrites-lib/reference/standards/security.md +17 -7
  198. package/pack/.claude/skills/devrites-lib/reference/standards/testing.md +1 -27
  199. package/pack/.claude/skills/devrites-lib/reference/standards/tooling.md +46 -50
  200. package/pack/.claude/skills/devrites-lib/reference/workspace-artifact-schema.md +40 -10
  201. package/pack/.claude/skills/devrites-source-driven/SKILL.md +23 -22
  202. package/pack/.claude/skills/rite/SKILL.md +10 -6
  203. package/pack/.claude/skills/rite/reference/menu.md +13 -7
  204. package/pack/.claude/skills/rite-adopt/SKILL.md +33 -37
  205. package/pack/.claude/skills/rite-autocomplete/SKILL.md +33 -28
  206. package/pack/.claude/skills/rite-autocomplete/reference/loop.md +21 -21
  207. package/pack/.claude/skills/rite-autocomplete/reference/stop-conditions.md +15 -6
  208. package/pack/.claude/skills/rite-build/SKILL.md +35 -30
  209. package/pack/.claude/skills/rite-build/reference/afk-discipline.md +41 -39
  210. package/pack/.claude/skills/rite-build/reference/evidence-standard.md +10 -0
  211. package/pack/.claude/skills/rite-build/reference/forge.md +186 -156
  212. package/pack/.claude/skills/rite-build/reference/one-slice-cycle.md +4 -3
  213. package/pack/.claude/skills/rite-build/reference/phase-contract.md +96 -175
  214. package/pack/.claude/skills/rite-build/reference/spec-drift-guard.md +10 -4
  215. package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +129 -134
  216. package/pack/.claude/skills/rite-clarify/SKILL.md +90 -0
  217. package/pack/.claude/skills/rite-clarify/reference/decision-coverage.md +61 -0
  218. package/pack/.claude/skills/rite-converge/SKILL.md +35 -25
  219. package/pack/.claude/skills/rite-define/SKILL.md +58 -32
  220. package/pack/.claude/skills/rite-define/reference/gates.md +16 -15
  221. package/pack/.claude/skills/rite-define/reference/plan-template.md +18 -8
  222. package/pack/.claude/skills/rite-frame/reference/failure-modes.md +22 -24
  223. package/pack/.claude/skills/rite-plan/SKILL.md +47 -16
  224. package/pack/.claude/skills/rite-plan/reference/task-breakdown.md +4 -0
  225. package/pack/.claude/skills/rite-polish/SKILL.md +27 -23
  226. package/pack/.claude/skills/rite-prototype/SKILL.md +25 -26
  227. package/pack/.claude/skills/rite-prove/SKILL.md +27 -17
  228. package/pack/.claude/skills/rite-resolve/SKILL.md +12 -11
  229. package/pack/.claude/skills/rite-resolve/reference/answer-protocol.md +3 -0
  230. package/pack/.claude/skills/rite-review/SKILL.md +37 -28
  231. package/pack/.claude/skills/rite-seal/reference/phase-contract.md +27 -92
  232. package/pack/.claude/skills/rite-seal/reference/risk-and-rollback.md +13 -0
  233. package/pack/.claude/skills/rite-ship/reference/design-memory.md +31 -36
  234. package/pack/.claude/skills/rite-spec/SKILL.md +84 -135
  235. package/pack/.claude/skills/rite-spec/reference/investigation.md +37 -33
  236. package/pack/.claude/skills/rite-spec/reference/question-protocol.md +6 -2
  237. package/pack/.claude/skills/rite-spec/reference/spec-checklists.md +25 -24
  238. package/pack/.claude/skills/rite-spec/reference/spec-template.md +9 -2
  239. package/pack/.claude/skills/rite-spec/reference/state-workspace.md +13 -3
  240. package/pack/.claude/skills/rite-temper/SKILL.md +62 -47
  241. package/pack/.claude/skills/rite-temper/reference/review-dimensions.md +24 -22
  242. package/pack/.claude/skills/rite-upgrade/SKILL.md +125 -0
  243. package/pack/.claude/skills/rite-vet/SKILL.md +114 -113
  244. package/pack/.claude/skills/rite-vet/reference/artifacts.md +50 -9
  245. package/pack/.claude/skills/rite-vet/reference/review-axes.md +39 -37
  246. package/pack/generated/claude/agents/devrites-code-reviewer.md +62 -48
  247. package/pack/generated/claude/agents/devrites-devex-reviewer.md +69 -53
  248. package/pack/generated/claude/agents/devrites-doubt-reviewer.md +29 -22
  249. package/pack/generated/claude/agents/devrites-evidence-scout.md +69 -0
  250. package/pack/generated/claude/agents/devrites-forge-judge.md +74 -61
  251. package/pack/generated/claude/agents/devrites-frontend-reviewer.md +48 -39
  252. package/pack/generated/claude/agents/devrites-performance-reviewer.md +49 -40
  253. package/pack/generated/claude/agents/devrites-plan-drafter.md +73 -0
  254. package/pack/generated/claude/agents/devrites-plan-reviewer.md +80 -47
  255. package/pack/generated/claude/agents/devrites-proof-runner.md +74 -0
  256. package/pack/generated/claude/agents/devrites-retrospector.md +48 -45
  257. package/pack/generated/claude/agents/devrites-security-auditor.md +46 -36
  258. package/pack/generated/claude/agents/devrites-simplifier-reviewer.md +50 -46
  259. package/pack/generated/claude/agents/devrites-slice-wright.md +153 -165
  260. package/pack/generated/claude/agents/devrites-spec-reviewer.md +34 -32
  261. package/pack/generated/claude/agents/devrites-strategy-reviewer.md +63 -34
  262. package/pack/generated/claude/agents/devrites-test-analyst.md +40 -30
  263. package/pack/generated/claude/agents/devrites-upgrade-planner.md +100 -0
  264. package/pack/generated/claude/settings.json +2 -1
  265. package/pack/generated/claude/skills/devrites-audit/SKILL.md +40 -61
  266. package/pack/generated/claude/skills/devrites-debug-recovery/SKILL.md +24 -13
  267. package/pack/generated/claude/skills/devrites-debug-recovery/reference/build-the-loop.md +24 -21
  268. package/pack/generated/claude/skills/devrites-debug-recovery/reference/cleanup-and-classify.md +34 -6
  269. package/pack/generated/claude/skills/devrites-debug-recovery/reference/instrumentation.md +2 -2
  270. package/pack/generated/claude/skills/devrites-doubt/SKILL.md +25 -17
  271. package/pack/generated/claude/skills/devrites-interview/SKILL.md +53 -52
  272. package/pack/generated/claude/skills/devrites-lib/SKILL.md +12 -9
  273. package/pack/generated/claude/skills/devrites-lib/reference/intent-map.md +4 -2
  274. package/pack/generated/claude/skills/devrites-lib/reference/parallel-dispatch.md +82 -137
  275. package/pack/generated/claude/skills/devrites-lib/reference/reply-contract.md +8 -1
  276. package/pack/generated/claude/skills/devrites-lib/reference/standards/README.md +38 -55
  277. package/pack/generated/claude/skills/devrites-lib/reference/standards/afk-hitl.md +53 -27
  278. package/pack/generated/claude/skills/devrites-lib/reference/standards/agents.md +200 -190
  279. package/pack/generated/claude/skills/devrites-lib/reference/standards/anti-patterns.md +5 -15
  280. package/pack/generated/claude/skills/devrites-lib/reference/standards/ci-cd.md +27 -58
  281. package/pack/generated/claude/skills/devrites-lib/reference/standards/code-review.md +16 -52
  282. package/pack/generated/claude/skills/devrites-lib/reference/standards/coding-style.md +2 -10
  283. package/pack/generated/claude/skills/devrites-lib/reference/standards/context-hygiene.md +18 -61
  284. package/pack/generated/claude/skills/devrites-lib/reference/standards/core.md +23 -18
  285. package/pack/generated/claude/skills/devrites-lib/reference/standards/deprecation.md +17 -57
  286. package/pack/generated/claude/skills/devrites-lib/reference/standards/development-workflow.md +4 -33
  287. package/pack/generated/claude/skills/devrites-lib/reference/standards/git-workflow.md +4 -20
  288. package/pack/generated/claude/skills/devrites-lib/reference/standards/hooks.md +3 -14
  289. package/pack/generated/claude/skills/devrites-lib/reference/standards/patterns.md +9 -25
  290. package/pack/generated/claude/skills/devrites-lib/reference/standards/performance.md +0 -9
  291. package/pack/generated/claude/skills/devrites-lib/reference/standards/principles.md +1 -3
  292. package/pack/generated/claude/skills/devrites-lib/reference/standards/security.md +17 -7
  293. package/pack/generated/claude/skills/devrites-lib/reference/standards/testing.md +1 -27
  294. package/pack/generated/claude/skills/devrites-lib/reference/standards/tooling.md +46 -50
  295. package/pack/generated/claude/skills/devrites-lib/reference/workspace-artifact-schema.md +40 -10
  296. package/pack/generated/claude/skills/devrites-source-driven/SKILL.md +23 -22
  297. package/pack/generated/claude/skills/rite/SKILL.md +10 -6
  298. package/pack/generated/claude/skills/rite/reference/menu.md +13 -7
  299. package/pack/generated/claude/skills/rite-adopt/SKILL.md +33 -37
  300. package/pack/generated/claude/skills/rite-autocomplete/SKILL.md +33 -28
  301. package/pack/generated/claude/skills/rite-autocomplete/reference/loop.md +21 -21
  302. package/pack/generated/claude/skills/rite-autocomplete/reference/stop-conditions.md +15 -6
  303. package/pack/generated/claude/skills/rite-build/SKILL.md +35 -30
  304. package/pack/generated/claude/skills/rite-build/reference/afk-discipline.md +41 -39
  305. package/pack/generated/claude/skills/rite-build/reference/evidence-standard.md +10 -0
  306. package/pack/generated/claude/skills/rite-build/reference/forge.md +186 -156
  307. package/pack/generated/claude/skills/rite-build/reference/one-slice-cycle.md +4 -3
  308. package/pack/generated/claude/skills/rite-build/reference/phase-contract.md +96 -175
  309. package/pack/generated/claude/skills/rite-build/reference/spec-drift-guard.md +10 -4
  310. package/pack/generated/claude/skills/rite-build/reference/wright-dispatch.md +129 -134
  311. package/pack/generated/claude/skills/rite-clarify/SKILL.md +90 -0
  312. package/pack/generated/claude/skills/rite-clarify/reference/decision-coverage.md +61 -0
  313. package/pack/generated/claude/skills/rite-converge/SKILL.md +35 -25
  314. package/pack/generated/claude/skills/rite-define/SKILL.md +58 -32
  315. package/pack/generated/claude/skills/rite-define/reference/gates.md +16 -15
  316. package/pack/generated/claude/skills/rite-define/reference/plan-template.md +18 -8
  317. package/pack/generated/claude/skills/rite-frame/reference/failure-modes.md +22 -24
  318. package/pack/generated/claude/skills/rite-plan/SKILL.md +47 -16
  319. package/pack/generated/claude/skills/rite-plan/reference/task-breakdown.md +4 -0
  320. package/pack/generated/claude/skills/rite-polish/SKILL.md +27 -23
  321. package/pack/generated/claude/skills/rite-prototype/SKILL.md +25 -26
  322. package/pack/generated/claude/skills/rite-prove/SKILL.md +27 -17
  323. package/pack/generated/claude/skills/rite-resolve/SKILL.md +12 -11
  324. package/pack/generated/claude/skills/rite-resolve/reference/answer-protocol.md +3 -0
  325. package/pack/generated/claude/skills/rite-review/SKILL.md +37 -28
  326. package/pack/generated/claude/skills/rite-seal/reference/phase-contract.md +27 -92
  327. package/pack/generated/claude/skills/rite-seal/reference/risk-and-rollback.md +13 -0
  328. package/pack/generated/claude/skills/rite-ship/reference/design-memory.md +31 -36
  329. package/pack/generated/claude/skills/rite-spec/SKILL.md +84 -135
  330. package/pack/generated/claude/skills/rite-spec/reference/investigation.md +37 -33
  331. package/pack/generated/claude/skills/rite-spec/reference/question-protocol.md +6 -2
  332. package/pack/generated/claude/skills/rite-spec/reference/spec-checklists.md +25 -24
  333. package/pack/generated/claude/skills/rite-spec/reference/spec-template.md +9 -2
  334. package/pack/generated/claude/skills/rite-spec/reference/state-workspace.md +13 -3
  335. package/pack/generated/claude/skills/rite-temper/SKILL.md +62 -47
  336. package/pack/generated/claude/skills/rite-temper/reference/review-dimensions.md +24 -22
  337. package/pack/generated/claude/skills/rite-upgrade/SKILL.md +125 -0
  338. package/pack/generated/claude/skills/rite-vet/SKILL.md +114 -113
  339. package/pack/generated/claude/skills/rite-vet/reference/artifacts.md +50 -9
  340. package/pack/generated/claude/skills/rite-vet/reference/review-axes.md +39 -37
  341. package/pack/generated/codex/AGENTS.md +5 -2
  342. package/pack/generated/codex/agents/devrites-code-reviewer.toml +69 -47
  343. package/pack/generated/codex/agents/devrites-devex-reviewer.toml +75 -51
  344. package/pack/generated/codex/agents/devrites-doubt-reviewer.toml +35 -20
  345. package/pack/generated/codex/agents/devrites-evidence-scout.toml +75 -0
  346. package/pack/generated/codex/agents/devrites-forge-judge.toml +80 -59
  347. package/pack/generated/codex/agents/devrites-frontend-reviewer.toml +54 -37
  348. package/pack/generated/codex/agents/devrites-performance-reviewer.toml +55 -38
  349. package/pack/generated/codex/agents/devrites-plan-drafter.toml +79 -0
  350. package/pack/generated/codex/agents/devrites-plan-reviewer.toml +82 -47
  351. package/pack/generated/codex/agents/devrites-proof-runner.toml +80 -0
  352. package/pack/generated/codex/agents/devrites-retrospector.toml +54 -43
  353. package/pack/generated/codex/agents/devrites-security-auditor.toml +53 -35
  354. package/pack/generated/codex/agents/devrites-simplifier-reviewer.toml +56 -44
  355. package/pack/generated/codex/agents/devrites-slice-wright.toml +159 -163
  356. package/pack/generated/codex/agents/devrites-spec-reviewer.toml +40 -30
  357. package/pack/generated/codex/agents/devrites-strategy-reviewer.toml +65 -34
  358. package/pack/generated/codex/agents/devrites-test-analyst.toml +46 -28
  359. package/pack/generated/codex/agents/devrites-upgrade-planner.toml +106 -0
  360. package/pack/generated/codex/hooks.json +4 -14
  361. package/pack/generated/codex/skills/devrites-api-interface/SKILL.md +7 -3
  362. package/pack/generated/codex/skills/devrites-audit/SKILL.md +47 -64
  363. package/pack/generated/codex/skills/devrites-browser-proof/SKILL.md +7 -3
  364. package/pack/generated/codex/skills/devrites-debug-recovery/SKILL.md +31 -16
  365. package/pack/generated/codex/skills/devrites-debug-recovery/reference/build-the-loop.md +24 -21
  366. package/pack/generated/codex/skills/devrites-debug-recovery/reference/cleanup-and-classify.md +34 -6
  367. package/pack/generated/codex/skills/devrites-debug-recovery/reference/instrumentation.md +2 -2
  368. package/pack/generated/codex/skills/devrites-doubt/SKILL.md +32 -20
  369. package/pack/generated/codex/skills/devrites-frontend-craft/SKILL.md +7 -3
  370. package/pack/generated/codex/skills/devrites-interview/SKILL.md +60 -55
  371. package/pack/generated/codex/skills/devrites-lib/SKILL.md +19 -12
  372. package/pack/generated/codex/skills/devrites-lib/reference/intent-map.md +4 -2
  373. package/pack/generated/codex/skills/devrites-lib/reference/parallel-dispatch.md +82 -137
  374. package/pack/generated/codex/skills/devrites-lib/reference/reply-contract.md +8 -1
  375. package/pack/generated/codex/skills/devrites-lib/reference/standards/README.md +38 -55
  376. package/pack/generated/codex/skills/devrites-lib/reference/standards/afk-hitl.md +53 -27
  377. package/pack/generated/codex/skills/devrites-lib/reference/standards/agents.md +200 -190
  378. package/pack/generated/codex/skills/devrites-lib/reference/standards/anti-patterns.md +5 -15
  379. package/pack/generated/codex/skills/devrites-lib/reference/standards/ci-cd.md +27 -58
  380. package/pack/generated/codex/skills/devrites-lib/reference/standards/code-review.md +16 -52
  381. package/pack/generated/codex/skills/devrites-lib/reference/standards/coding-style.md +2 -10
  382. package/pack/generated/codex/skills/devrites-lib/reference/standards/context-hygiene.md +18 -61
  383. package/pack/generated/codex/skills/devrites-lib/reference/standards/core.md +23 -18
  384. package/pack/generated/codex/skills/devrites-lib/reference/standards/deprecation.md +17 -57
  385. package/pack/generated/codex/skills/devrites-lib/reference/standards/development-workflow.md +4 -33
  386. package/pack/generated/codex/skills/devrites-lib/reference/standards/git-workflow.md +4 -20
  387. package/pack/generated/codex/skills/devrites-lib/reference/standards/hooks.md +3 -14
  388. package/pack/generated/codex/skills/devrites-lib/reference/standards/patterns.md +9 -25
  389. package/pack/generated/codex/skills/devrites-lib/reference/standards/performance.md +0 -9
  390. package/pack/generated/codex/skills/devrites-lib/reference/standards/principles.md +1 -3
  391. package/pack/generated/codex/skills/devrites-lib/reference/standards/security.md +17 -7
  392. package/pack/generated/codex/skills/devrites-lib/reference/standards/testing.md +1 -27
  393. package/pack/generated/codex/skills/devrites-lib/reference/standards/tooling.md +46 -50
  394. package/pack/generated/codex/skills/devrites-lib/reference/workspace-artifact-schema.md +40 -10
  395. package/pack/generated/codex/skills/devrites-prose-craft/SKILL.md +7 -3
  396. package/pack/generated/codex/skills/devrites-refresh-indexes/SKILL.md +7 -3
  397. package/pack/generated/codex/skills/devrites-source-driven/SKILL.md +29 -24
  398. package/pack/generated/codex/skills/devrites-ux-shape/SKILL.md +7 -3
  399. package/pack/generated/codex/skills/rite/SKILL.md +21 -13
  400. package/pack/generated/codex/skills/rite/reference/menu.md +13 -7
  401. package/pack/generated/codex/skills/rite-adopt/SKILL.md +40 -40
  402. package/pack/generated/codex/skills/rite-autocomplete/SKILL.md +40 -31
  403. package/pack/generated/codex/skills/rite-autocomplete/reference/loop.md +21 -21
  404. package/pack/generated/codex/skills/rite-autocomplete/reference/stop-conditions.md +15 -6
  405. package/pack/generated/codex/skills/rite-build/SKILL.md +42 -33
  406. package/pack/generated/codex/skills/rite-build/reference/afk-discipline.md +41 -39
  407. package/pack/generated/codex/skills/rite-build/reference/evidence-standard.md +10 -0
  408. package/pack/generated/codex/skills/rite-build/reference/forge.md +186 -156
  409. package/pack/generated/codex/skills/rite-build/reference/one-slice-cycle.md +4 -3
  410. package/pack/generated/codex/skills/rite-build/reference/phase-contract.md +96 -175
  411. package/pack/generated/codex/skills/rite-build/reference/spec-drift-guard.md +10 -4
  412. package/pack/generated/codex/skills/rite-build/reference/wright-dispatch.md +129 -134
  413. package/pack/generated/codex/skills/rite-clarify/SKILL.md +106 -0
  414. package/pack/generated/codex/skills/rite-clarify/reference/decision-coverage.md +61 -0
  415. package/pack/generated/codex/skills/rite-converge/SKILL.md +42 -28
  416. package/pack/generated/codex/skills/rite-customize/SKILL.md +7 -3
  417. package/pack/generated/codex/skills/rite-define/SKILL.md +65 -35
  418. package/pack/generated/codex/skills/rite-define/reference/gates.md +16 -15
  419. package/pack/generated/codex/skills/rite-define/reference/plan-template.md +18 -8
  420. package/pack/generated/codex/skills/rite-doctor/SKILL.md +7 -3
  421. package/pack/generated/codex/skills/rite-dogfood/SKILL.md +7 -3
  422. package/pack/generated/codex/skills/rite-explain/SKILL.md +7 -3
  423. package/pack/generated/codex/skills/rite-frame/SKILL.md +7 -3
  424. package/pack/generated/codex/skills/rite-frame/reference/failure-modes.md +22 -24
  425. package/pack/generated/codex/skills/rite-handoff/SKILL.md +7 -3
  426. package/pack/generated/codex/skills/rite-learn/SKILL.md +7 -3
  427. package/pack/generated/codex/skills/rite-plan/SKILL.md +54 -19
  428. package/pack/generated/codex/skills/rite-plan/reference/task-breakdown.md +5 -1
  429. package/pack/generated/codex/skills/rite-polish/SKILL.md +34 -26
  430. package/pack/generated/codex/skills/rite-pov/SKILL.md +7 -3
  431. package/pack/generated/codex/skills/rite-pr-feedback/SKILL.md +7 -3
  432. package/pack/generated/codex/skills/rite-pressure-test/SKILL.md +7 -3
  433. package/pack/generated/codex/skills/rite-prototype/SKILL.md +32 -29
  434. package/pack/generated/codex/skills/rite-prove/SKILL.md +34 -20
  435. package/pack/generated/codex/skills/rite-quick/SKILL.md +7 -3
  436. package/pack/generated/codex/skills/rite-resolve/SKILL.md +19 -14
  437. package/pack/generated/codex/skills/rite-resolve/reference/answer-protocol.md +3 -0
  438. package/pack/generated/codex/skills/rite-review/SKILL.md +44 -31
  439. package/pack/generated/codex/skills/rite-seal/SKILL.md +7 -3
  440. package/pack/generated/codex/skills/rite-seal/reference/phase-contract.md +27 -92
  441. package/pack/generated/codex/skills/rite-seal/reference/risk-and-rollback.md +13 -0
  442. package/pack/generated/codex/skills/rite-ship/SKILL.md +7 -3
  443. package/pack/generated/codex/skills/rite-ship/reference/design-memory.md +31 -36
  444. package/pack/generated/codex/skills/rite-spec/SKILL.md +91 -138
  445. package/pack/generated/codex/skills/rite-spec/reference/investigation.md +37 -33
  446. package/pack/generated/codex/skills/rite-spec/reference/question-protocol.md +6 -2
  447. package/pack/generated/codex/skills/rite-spec/reference/spec-checklists.md +25 -24
  448. package/pack/generated/codex/skills/rite-spec/reference/spec-template.md +9 -2
  449. package/pack/generated/codex/skills/rite-spec/reference/state-workspace.md +13 -3
  450. package/pack/generated/codex/skills/rite-status/SKILL.md +7 -3
  451. package/pack/generated/codex/skills/rite-temper/SKILL.md +69 -50
  452. package/pack/generated/codex/skills/rite-temper/reference/review-dimensions.md +24 -22
  453. package/pack/generated/codex/skills/rite-upgrade/SKILL.md +141 -0
  454. package/pack/generated/codex/skills/rite-upgrade/agents/openai.yaml +2 -0
  455. package/pack/generated/codex/skills/rite-vet/SKILL.md +121 -116
  456. package/pack/generated/codex/skills/rite-vet/reference/artifacts.md +50 -9
  457. package/pack/generated/codex/skills/rite-vet/reference/review-axes.md +40 -38
  458. package/pack/generated/codex/skills/rite-zoom-out/SKILL.md +7 -3
  459. package/package.json +1 -1
  460. package/scripts/build-release-tarball.sh +32 -15
  461. package/scripts/check-authority-drift.py +125 -0
  462. package/scripts/check-instruction-size-baseline.mjs +19 -11
  463. package/scripts/check-invocation-integrity.py +2 -0
  464. package/scripts/codex-generate.sh +69 -33
  465. package/scripts/grade-feature.sh +121 -40
  466. package/scripts/live-hosts/agent-result.schema.json +230 -0
  467. package/scripts/live-hosts/claude.sh +87 -0
  468. package/scripts/live-hosts/codex.sh +81 -0
  469. package/scripts/live-hosts/common.sh +113 -0
  470. package/scripts/live-hosts/fake-host.py +264 -0
  471. package/scripts/live-hosts/host-transport.py +287 -0
  472. package/scripts/release-check.sh +5 -1
  473. package/scripts/run-agent-contract-evals.py +1380 -0
  474. package/scripts/run-behavioral-evals.sh +24 -30
  475. package/scripts/run-evals.sh +1 -5
  476. package/scripts/run-live-behavioral-evals.py +1274 -144
  477. package/scripts/run-outcome-evals.sh +445 -88
  478. package/scripts/run-tests.mjs +30 -2
  479. package/scripts/skills-inventory.mjs +1 -1
  480. package/scripts/validate-workflow-security.py +39 -20
  481. package/scripts/validate-workspace-schema.py +362 -10
  482. package/scripts/validate.sh +21 -15
  483. package/engine/testdata/golden/TestParityWrightScope/devrites-edit-allowed.golden +0 -1
  484. /package/engine/testdata/golden/{TestParityReconcile/check-no-claimed.golden → TestParityBuildReadiness/arg=clarifyopen.golden} +0 -0
@@ -0,0 +1,283 @@
1
+ package main
2
+
3
+ import (
4
+ "bytes"
5
+ "encoding/json"
6
+ "os"
7
+ "path/filepath"
8
+ "strings"
9
+ "testing"
10
+
11
+ "github.com/devrites/devrites/internal/harness"
12
+ "github.com/devrites/devrites/internal/lib"
13
+ "github.com/devrites/devrites/internal/reason"
14
+ "github.com/devrites/devrites/internal/toolpolicy"
15
+ )
16
+
17
+ func TestGitClassifierReasonIDsBelongToDiagnosticCatalog(t *testing.T) {
18
+ ids := []toolpolicy.ReasonID{
19
+ toolpolicy.ReasonInputTooLarge,
20
+ toolpolicy.ReasonAmbiguousSyntax,
21
+ toolpolicy.ReasonAmbiguousDynamic,
22
+ toolpolicy.ReasonAmbiguousAlias,
23
+ toolpolicy.ReasonAmbiguousGlobalOption,
24
+ toolpolicy.ReasonAmbiguousStdin,
25
+ toolpolicy.ReasonDestructiveReset,
26
+ toolpolicy.ReasonDestructiveClean,
27
+ toolpolicy.ReasonDestructiveCheckout,
28
+ toolpolicy.ReasonDestructiveRestore,
29
+ toolpolicy.ReasonDestructiveSwitch,
30
+ toolpolicy.ReasonDestructiveRemove,
31
+ toolpolicy.ReasonDestructiveBranch,
32
+ toolpolicy.ReasonDestructiveTag,
33
+ toolpolicy.ReasonDestructiveUpdateRef,
34
+ toolpolicy.ReasonDestructiveStash,
35
+ toolpolicy.ReasonDestructiveReflog,
36
+ toolpolicy.ReasonDestructivePrune,
37
+ toolpolicy.ReasonDestructiveHistory,
38
+ toolpolicy.ReasonDestructivePushForce,
39
+ toolpolicy.ReasonDestructivePushDelete,
40
+ toolpolicy.ReasonDestructiveWorktree,
41
+ toolpolicy.ReasonDestructiveRefDeletion,
42
+ }
43
+ for _, id := range ids {
44
+ if _, err := reason.Parse(string(id)); err != nil {
45
+ t.Errorf("%s: %v", id, err)
46
+ }
47
+ }
48
+ }
49
+
50
+ func TestGitGuardOrdinaryGitPassesSilently(t *testing.T) {
51
+ for _, command := range []string{
52
+ "git status --short",
53
+ "git diff -- reset",
54
+ "git add file && git commit -m safe",
55
+ "printf '%s\\n' 'git reset --hard HEAD'",
56
+ } {
57
+ var stdout, stderr bytes.Buffer
58
+ if code := hookGitGuard(harness.Claude, gitGuardInput("Bash", command), &stdout, &stderr); code != 0 {
59
+ t.Fatalf("%q code=%d", command, code)
60
+ }
61
+ if stdout.Len() != 0 || stderr.Len() != 0 {
62
+ t.Fatalf("%q emitted stdout=%q stderr=%q", command, stdout.String(), stderr.String())
63
+ }
64
+ }
65
+ }
66
+
67
+ func TestGitGuardAmbiguousHighImpactAlwaysDenies(t *testing.T) {
68
+ for _, host := range []harness.Harness{harness.Claude, harness.Codex} {
69
+ t.Run(string(host), func(t *testing.T) {
70
+ var stdout, stderr bytes.Buffer
71
+ if code := hookGitGuard(host, gitGuardInput("Bash", "eval 'git reset --hard HEAD'"), &stdout, &stderr); code != 0 {
72
+ t.Fatalf("code=%d", code)
73
+ }
74
+ if !strings.Contains(stdout.String(), `"permissionDecision":"deny"`) ||
75
+ !strings.Contains(stdout.String(), string(reason.GitAmbiguousDynamic)) ||
76
+ !strings.Contains(stdout.String(), "direct, literal git command") {
77
+ t.Fatalf("stdout=%q", stdout.String())
78
+ }
79
+ if strings.Contains(stdout.String(), "reset --hard") || stderr.Len() != 0 {
80
+ t.Fatalf("guard leaked command or stderr: stdout=%q stderr=%q", stdout.String(), stderr.String())
81
+ }
82
+ })
83
+ }
84
+ }
85
+
86
+ func TestGitGuardOpensResolvesAndConsumesExactQuestionForBothHosts(t *testing.T) {
87
+ var hostReasons []string
88
+ for _, host := range []harness.Harness{harness.Claude, harness.Codex} {
89
+ t.Run(string(host), func(t *testing.T) {
90
+ root := makeHookWorkspace(t)
91
+ work := filepath.Join(root, "work", "demo")
92
+ if err := os.WriteFile(filepath.Join(work, "questions.md"), []byte("# Questions\n\nNone.\n"), 0o644); err != nil {
93
+ t.Fatal(err)
94
+ }
95
+ t.Setenv("DEVRITES_NOW", "2026-07-23T10:00:00Z")
96
+ const command = "git reset --hard SECRET-REF"
97
+
98
+ var denied, deniedErr bytes.Buffer
99
+ if code := hookGitGuard(host, gitGuardInput("Bash", command), &denied, &deniedErr); code != 0 {
100
+ t.Fatalf("pending code=%d", code)
101
+ }
102
+ if !strings.Contains(denied.String(), `"permissionDecision":"deny"`) ||
103
+ !strings.Contains(denied.String(), string(reason.GitAuthorityPending)) {
104
+ t.Fatalf("pending stdout=%q", denied.String())
105
+ }
106
+ questionData, err := os.ReadFile(filepath.Join(work, "questions.md"))
107
+ if err != nil {
108
+ t.Fatal(err)
109
+ }
110
+ if bytes.Contains(questionData, []byte("SECRET-REF")) || bytes.Contains(questionData, []byte("reset --hard")) {
111
+ t.Fatalf("question leaked command: %s", questionData)
112
+ }
113
+ qid := gitGuardQuestionID(t, questionData)
114
+
115
+ t.Setenv("DEVRITES_NOW", "2026-07-23T10:01:00Z")
116
+ var resolveOut, resolveErr bytes.Buffer
117
+ if code := lib.Resolve(root, []string{qid, lib.GitAuthorityAnswer}, &resolveOut, &resolveErr); code != 0 {
118
+ t.Fatalf("resolve code=%d stderr=%q", code, resolveErr.String())
119
+ }
120
+
121
+ var allowed, allowedErr bytes.Buffer
122
+ if code := hookGitGuard(host, gitGuardInput("Bash", command), &allowed, &allowedErr); code != 0 {
123
+ t.Fatalf("allow code=%d", code)
124
+ }
125
+ if !strings.Contains(allowed.String(), `"permissionDecision":"allow"`) ||
126
+ !strings.Contains(allowed.String(), string(reason.GitAuthorityGranted)) {
127
+ t.Fatalf("allow stdout=%q", allowed.String())
128
+ }
129
+ ledger, err := os.ReadFile(filepath.Join(work, lib.GitAuthorityLedgerFile))
130
+ if err != nil {
131
+ t.Fatal(err)
132
+ }
133
+ if bytes.Contains(ledger, []byte("SECRET-REF")) || bytes.Contains(ledger, []byte("reset --hard")) {
134
+ t.Fatalf("ledger leaked command: %s", ledger)
135
+ }
136
+ timeline, err := os.ReadFile(filepath.Join(root, "timeline.jsonl"))
137
+ if err != nil {
138
+ t.Fatal(err)
139
+ }
140
+ if bytes.Contains(timeline, []byte("SECRET-REF")) || bytes.Contains(timeline, []byte("reset --hard")) {
141
+ t.Fatalf("timeline leaked command: %s", timeline)
142
+ }
143
+ events := readV1HookEvents(t, root)
144
+ if len(events) != 2 {
145
+ t.Fatalf("events=%+v", events)
146
+ }
147
+ for _, event := range events {
148
+ if event.Boundary != lib.BoundaryGitPolicy || event.Event != "git-guard" ||
149
+ event.Host != lib.EventHost(host) || event.GuardStrength != lib.GuardEnforced {
150
+ t.Fatalf("event=%+v", event)
151
+ }
152
+ }
153
+ hostReasons = append(hostReasons, string(events[0].ReasonID)+"/"+string(events[0].Outcome))
154
+ })
155
+ }
156
+ if len(hostReasons) != 2 || hostReasons[0] != hostReasons[1] {
157
+ t.Fatalf("host policy drift: %v", hostReasons)
158
+ }
159
+ }
160
+
161
+ func TestGitGuardWrongDigestAndNoWorkspaceDeny(t *testing.T) {
162
+ root := makeHookWorkspace(t)
163
+ work := filepath.Join(root, "work", "demo")
164
+ if err := os.WriteFile(filepath.Join(work, "questions.md"), []byte("# Questions\n\nNone.\n"), 0o644); err != nil {
165
+ t.Fatal(err)
166
+ }
167
+ t.Setenv("DEVRITES_NOW", "2026-07-23T10:00:00Z")
168
+
169
+ var first bytes.Buffer
170
+ hookGitGuard(harness.Claude, gitGuardInput("Bash", "git reset --hard HEAD"), &first, &bytes.Buffer{})
171
+ qdata, err := os.ReadFile(filepath.Join(work, "questions.md"))
172
+ if err != nil {
173
+ t.Fatal(err)
174
+ }
175
+ qid := gitGuardQuestionID(t, qdata)
176
+ var resolveOut, resolveErr bytes.Buffer
177
+ if code := lib.Resolve(root, []string{qid, lib.GitAuthorityAnswer}, &resolveOut, &resolveErr); code != 0 {
178
+ t.Fatalf("resolve code=%d stderr=%q", code, resolveErr.String())
179
+ }
180
+ var mismatch bytes.Buffer
181
+ hookGitGuard(harness.Claude, gitGuardInput("Bash", "git clean -fd"), &mismatch, &bytes.Buffer{})
182
+ if !strings.Contains(mismatch.String(), `"permissionDecision":"deny"`) ||
183
+ strings.Contains(mismatch.String(), `"permissionDecision":"allow"`) {
184
+ t.Fatalf("wrong digest stdout=%q", mismatch.String())
185
+ }
186
+
187
+ t.Setenv("DEVRITES_ROOT", filepath.Join(t.TempDir(), ".devrites"))
188
+ if err := os.MkdirAll(os.Getenv("DEVRITES_ROOT"), 0o755); err != nil {
189
+ t.Fatal(err)
190
+ }
191
+ var absent bytes.Buffer
192
+ hookGitGuard(harness.Claude, gitGuardInput("Bash", "git reset --hard HEAD"), &absent, &bytes.Buffer{})
193
+ if !strings.Contains(absent.String(), string(reason.GitWorkspaceUnavailable)) ||
194
+ !strings.Contains(absent.String(), `"permissionDecision":"deny"`) {
195
+ t.Fatalf("no-workspace stdout=%q", absent.String())
196
+ }
197
+ }
198
+
199
+ func TestGitGuardEventFailureNeverChangesConsumedAllow(t *testing.T) {
200
+ root := makeHookWorkspace(t)
201
+ work := filepath.Join(root, "work", "demo")
202
+ if err := os.WriteFile(filepath.Join(work, "questions.md"), []byte("# Questions\n\nNone.\n"), 0o644); err != nil {
203
+ t.Fatal(err)
204
+ }
205
+ t.Setenv("DEVRITES_NOW", "2026-07-23T10:00:00Z")
206
+ const command = "git reset --hard HEAD"
207
+ var pending bytes.Buffer
208
+ hookGitGuard(harness.Claude, gitGuardInput("Bash", command), &pending, &bytes.Buffer{})
209
+ qdata, err := os.ReadFile(filepath.Join(work, "questions.md"))
210
+ if err != nil {
211
+ t.Fatal(err)
212
+ }
213
+ qid := gitGuardQuestionID(t, qdata)
214
+ var resolveOut, resolveErr bytes.Buffer
215
+ if code := lib.Resolve(root, []string{qid, lib.GitAuthorityAnswer}, &resolveOut, &resolveErr); code != 0 {
216
+ t.Fatalf("resolve code=%d stderr=%q", code, resolveErr.String())
217
+ }
218
+ timeline := filepath.Join(root, "timeline.jsonl")
219
+ if err := os.Remove(timeline); err != nil {
220
+ t.Fatal(err)
221
+ }
222
+ if err := os.Mkdir(timeline, 0o755); err != nil {
223
+ t.Fatal(err)
224
+ }
225
+
226
+ var allowed bytes.Buffer
227
+ hookGitGuard(harness.Claude, gitGuardInput("Bash", command), &allowed, &bytes.Buffer{})
228
+ if !strings.Contains(allowed.String(), `"permissionDecision":"allow"`) {
229
+ t.Fatalf("telemetry failure changed allow: %q", allowed.String())
230
+ }
231
+ if _, err := os.Stat(filepath.Join(work, lib.GitAuthorityLedgerFile)); err != nil {
232
+ t.Fatalf("grant was not consumed before telemetry: %v", err)
233
+ }
234
+ }
235
+
236
+ func TestGitGuardDispatchAndKillPath(t *testing.T) {
237
+ root := makeHookWorkspace(t)
238
+ if err := os.WriteFile(filepath.Join(root, "work", "demo", "questions.md"), []byte("# Questions\n\nNone.\n"), 0o644); err != nil {
239
+ t.Fatal(err)
240
+ }
241
+ payload, _ := json.Marshal(map[string]any{
242
+ "tool_name": "Bash",
243
+ "tool_input": map[string]string{"command": "git reset --hard HEAD"},
244
+ })
245
+
246
+ t.Setenv("DEVRITES_HOOK_PROFILE", "standard")
247
+ t.Setenv("DEVRITES_DISABLED_HOOKS", "git-guard")
248
+ var disabled bytes.Buffer
249
+ if code := run([]string{"hook", "git-guard", "--harness=claude"}, bytes.NewReader(payload), &disabled, &bytes.Buffer{}); code != 0 {
250
+ t.Fatalf("disabled code=%d", code)
251
+ }
252
+ if disabled.Len() != 0 {
253
+ t.Fatalf("disabled hook blocked: %q", disabled.String())
254
+ }
255
+
256
+ t.Setenv("DEVRITES_DISABLED_HOOKS", "")
257
+ var enabled bytes.Buffer
258
+ if code := run([]string{"hook", "git-guard", "--harness=claude"}, bytes.NewReader(payload), &enabled, &bytes.Buffer{}); code != 0 {
259
+ t.Fatalf("enabled code=%d", code)
260
+ }
261
+ if !strings.Contains(enabled.String(), `"permissionDecision":"deny"`) {
262
+ t.Fatalf("enabled hook did not deny: %q", enabled.String())
263
+ }
264
+ }
265
+
266
+ func gitGuardInput(tool, command string) *bytes.Reader {
267
+ data, _ := json.Marshal(map[string]any{
268
+ "tool_name": tool,
269
+ "tool_input": map[string]string{"command": command},
270
+ })
271
+ return bytes.NewReader(data)
272
+ }
273
+
274
+ func gitGuardQuestionID(t *testing.T, data []byte) string {
275
+ t.Helper()
276
+ for _, line := range strings.Split(string(data), "\n") {
277
+ if strings.HasPrefix(line, "## q-") {
278
+ return strings.TrimPrefix(line, "## ")
279
+ }
280
+ }
281
+ t.Fatalf("question id missing:\n%s", data)
282
+ return ""
283
+ }
@@ -5,65 +5,65 @@ import (
5
5
  "strings"
6
6
  )
7
7
 
8
- // Hook control plane.
9
- //
10
- // Every wired hook has a canonical id: its `hook <name>` CLI verb (allow,
11
- // orient, stop-gate, …). That id is the addressable handle for two orthogonal
12
- // runtime controls, both read from the environment so an operator tunes safety
13
- // vs speed without editing any JSON:
8
+ // Hook names are also their canonical IDs. Two environment settings control
9
+ // whether a hook runs:
14
10
  //
15
11
  // DEVRITES_HOOK_PROFILE=minimal|standard|strict which hooks run at all
16
- // DEVRITES_DISABLED_HOOKS="stop-gate,a1-guard" an explicit per-id kill list
12
+ // DEVRITES_DISABLED_HOOKS="stop-gate,a1-guard" hook IDs to disable
17
13
  //
18
- // The profile is a floor: a hook runs when its tier is at or below the active
19
- // profile AND it is not in the kill list. Everything else short-circuits to a
20
- // clean fail-open no-op in cmdHook: identical to the inline `command -v … ||
21
- // exit 0` guard, so a disabled hook is indistinguishable from an absent binary.
14
+ // A hook runs when its tier is at or below the selected profile and its ID is
15
+ // not disabled. Disabled hooks return success without doing anything, just as
16
+ // they would if the binary were absent.
22
17
  //
23
- // A SEPARATE axis is enforce-vs-observe: the OBSERVE-by-default guards (a1-guard,
24
- // stop-gate, reviewer-readonly) only ever log until their specific
25
- // DEVRITES_*=enforce var is set. The `strict` profile is the ergonomic shortcut
26
- // that flips ALL of them on at once (see hookEnforce), so a hardened session is
27
- // one env var, not four.
18
+ // Enforcement is separate from activation. Main-thread guards observe by
19
+ // default until their DEVRITES_*=enforce variable is set. The strict profile
20
+ // enforces every legacy observe path. reviewer-readonly and wright-scope always
21
+ // enforce policy for declared DevRites leaf agents.
28
22
 
29
23
  // hookTier is the minimum profile level at which a hook is active.
30
24
  type hookTier int
31
25
 
32
26
  const (
33
- tierMinimal hookTier = iota // orientation-only; never blocks or mutates flow
34
- tierStandard // the default working set: gates, guards, caches
35
- tierStrict // reserved for experimental/aggressive hooks
27
+ tierMinimal hookTier = iota // orientation only; never blocks or changes flow
28
+ tierStandard // default gates, guards, and caches
29
+ tierStrict // experimental or aggressive hooks
36
30
  )
37
31
 
38
- // hookRegistry maps each canonical hook id to the profile tier that first
39
- // activates it. A name absent from the registry is treated as tierMinimal
40
- // (always active): fail-open: an unrecognised hook is never silently disabled
41
- // by the profile mechanism.
42
- var hookRegistry = map[string]hookTier{
43
- // minimal: orientation and auto-approval, which never interrupt work.
44
- "allow": tierMinimal,
45
- "orient": tierMinimal,
46
- "subagent-orient": tierMinimal,
47
- "cursor": tierMinimal,
48
- "statusline": tierMinimal,
32
+ type hookDefinition struct {
33
+ tier hookTier
34
+ canBlock bool
35
+ }
36
+
37
+ // hookRegistry records each hook's tier and whether it can block. The blocker
38
+ // audit derives its cases from canBlock, so a new blocker must cover reentry,
39
+ // malformed input, bounds, and the disabled path. Unknown hook names stay
40
+ // active instead of being silently disabled.
41
+ var hookRegistry = map[string]hookDefinition{
42
+ // Minimal hooks provide orientation and automatic approval without
43
+ // interrupting work.
44
+ "allow": {tier: tierMinimal},
45
+ "orient": {tier: tierMinimal},
46
+ "subagent-orient": {tier: tierMinimal},
47
+ "cursor": {tier: tierMinimal},
48
+ "statusline": {tier: tierMinimal},
49
49
 
50
- // standard: the default gates, guards, sentinels and caches.
51
- "a1-guard": tierStandard,
52
- "stop-gate": tierStandard,
53
- "reviewer-readonly": tierStandard,
54
- "wright-scope": tierStandard,
55
- "redwatch": tierStandard,
56
- "source-cache-pre": tierStandard,
57
- "source-cache-post": tierStandard,
58
- "refresh-indexes": tierStandard,
59
- "event": tierStandard,
60
- "auq": tierStandard,
61
- "handoff-snapshot": tierStandard,
50
+ // Standard hooks add the default gates, guards, sentinels, and caches.
51
+ "a1-guard": {tier: tierStandard, canBlock: true},
52
+ "git-guard": {tier: tierStandard, canBlock: true},
53
+ "stop-gate": {tier: tierStandard, canBlock: true},
54
+ "reviewer-readonly": {tier: tierStandard, canBlock: true},
55
+ "wright-scope": {tier: tierStandard, canBlock: true},
56
+ "redwatch": {tier: tierStandard},
57
+ "source-cache-pre": {tier: tierStandard},
58
+ "source-cache-post": {tier: tierStandard},
59
+ "refresh-indexes": {tier: tierStandard},
60
+ "event": {tier: tierStandard},
61
+ "auq": {tier: tierStandard},
62
+ "handoff-snapshot": {tier: tierStandard},
62
63
  }
63
64
 
64
- // hookProfileTier resolves DEVRITES_HOOK_PROFILE to a tier, defaulting to
65
- // standard on empty or unrecognised input (fail-open: a typo never silently
66
- // strips the working set below default).
65
+ // hookProfileTier resolves DEVRITES_HOOK_PROFILE. Empty or unrecognized input
66
+ // uses the standard tier so a typo cannot disable the default hooks.
67
67
  func hookProfileTier() hookTier {
68
68
  switch strings.ToLower(strings.TrimSpace(os.Getenv("DEVRITES_HOOK_PROFILE"))) {
69
69
  case "minimal":
@@ -75,21 +75,20 @@ func hookProfileTier() hookTier {
75
75
  }
76
76
  }
77
77
 
78
- // hookActive reports whether a hook id may run under the current profile and
79
- // kill list. It is the single gate consulted at the top of cmdHook.
78
+ // hookActive reports whether the current profile and disabled list allow a hook
79
+ // ID to run. cmdHook checks it before dispatch.
80
80
  func hookActive(name string) bool {
81
81
  if hookDisabled(name) {
82
82
  return false
83
83
  }
84
- tier, known := hookRegistry[name]
84
+ def, known := hookRegistry[name]
85
85
  if !known {
86
- return true // unknown id: never disabled by profile
86
+ return true // Profiles do not disable unknown IDs.
87
87
  }
88
- return tier <= hookProfileTier()
88
+ return def.tier <= hookProfileTier()
89
89
  }
90
90
 
91
- // hookDisabled reports whether a hook id appears in the DEVRITES_DISABLED_HOOKS
92
- // comma-separated kill list.
91
+ // hookDisabled reports whether DEVRITES_DISABLED_HOOKS contains the hook ID.
93
92
  func hookDisabled(name string) bool {
94
93
  list := os.Getenv("DEVRITES_DISABLED_HOOKS")
95
94
  if list == "" {
@@ -103,9 +102,8 @@ func hookDisabled(name string) bool {
103
102
  return false
104
103
  }
105
104
 
106
- // hookEnforce reports whether an OBSERVE-default guard should block rather than
107
- // log. It is true when the guard's own DEVRITES_*=enforce var is set, OR when
108
- // the strict profile is active: strict enforces every guard at once.
105
+ // hookEnforce reports whether a guard that normally observes should block. Its
106
+ // own enforce variable or the strict profile enables blocking.
109
107
  func hookEnforce(specificVar string) bool {
110
108
  if os.Getenv(specificVar) == "enforce" {
111
109
  return true
@@ -1,6 +1,10 @@
1
1
  package main
2
2
 
3
- import "testing"
3
+ import (
4
+ "bytes"
5
+ "strings"
6
+ "testing"
7
+ )
4
8
 
5
9
  func TestHookActiveProfileTiers(t *testing.T) {
6
10
  cases := []struct {
@@ -62,3 +66,89 @@ func TestHookEnforce(t *testing.T) {
62
66
  t.Error("strict profile should enforce every guard")
63
67
  }
64
68
  }
69
+
70
+ func TestBlockingHookExitAudit(t *testing.T) {
71
+ type exitContract struct {
72
+ reentry string
73
+ malformed string
74
+ bound string
75
+ killPath string
76
+ }
77
+ audit := map[string]exitContract{
78
+ "stop-gate": {
79
+ reentry: "explicit stop_hook_active=true lets the re-entered Stop complete",
80
+ malformed: "empty, malformed, missing, null, or non-boolean stop_hook_active fails open",
81
+ bound: "one block before the host re-enters with stop_hook_active=true",
82
+ killPath: "DEVRITES_DISABLED_HOOKS=stop-gate or DEVRITES_HOOK_PROFILE=minimal",
83
+ },
84
+ "reviewer-readonly": {
85
+ reentry: "not applicable: each PreToolUse decision applies to one tool request",
86
+ malformed: "missing or malformed tool identity fails open",
87
+ bound: "one synchronous deny; no retry or subprocess",
88
+ killPath: "DEVRITES_DISABLED_HOOKS=reviewer-readonly or DEVRITES_HOOK_PROFILE=minimal",
89
+ },
90
+ "a1-guard": {
91
+ reentry: "not applicable: each PreToolUse decision applies to one tool request",
92
+ malformed: "missing or malformed tool identity fails open",
93
+ bound: "one synchronous deny; no retry or subprocess",
94
+ killPath: "DEVRITES_DISABLED_HOOKS=a1-guard or DEVRITES_HOOK_PROFILE=minimal",
95
+ },
96
+ "wright-scope": {
97
+ reentry: "not applicable: each PreToolUse decision applies to one tool request",
98
+ malformed: "missing or malformed tool identity fails open",
99
+ bound: "one synchronous deny; no retry or subprocess",
100
+ killPath: "DEVRITES_DISABLED_HOOKS=wright-scope or DEVRITES_HOOK_PROFILE=minimal",
101
+ },
102
+ "git-guard": {
103
+ reentry: "not applicable: each PreToolUse decision applies to one tool request",
104
+ malformed: "missing or malformed structured tool input fails open",
105
+ bound: "one synchronous deny or one consumed one-shot allow; no retry or subprocess",
106
+ killPath: "DEVRITES_DISABLED_HOOKS=git-guard or DEVRITES_HOOK_PROFILE=minimal",
107
+ },
108
+ }
109
+
110
+ for name, def := range hookRegistry {
111
+ if !def.canBlock {
112
+ continue
113
+ }
114
+ contract, ok := audit[name]
115
+ if !ok {
116
+ t.Errorf("production blocker %q has no exit audit row", name)
117
+ continue
118
+ }
119
+ if contract.reentry == "" || contract.malformed == "" || contract.bound == "" || contract.killPath == "" {
120
+ t.Errorf("blocker %q has an incomplete exit audit row: %#v", name, contract)
121
+ }
122
+ }
123
+ for name := range audit {
124
+ def, ok := hookRegistry[name]
125
+ if !ok || !def.canBlock {
126
+ t.Errorf("exit audit row %q is not a production blocker", name)
127
+ }
128
+ }
129
+
130
+ for name := range audit {
131
+ t.Run(name, func(t *testing.T) {
132
+ t.Setenv("DEVRITES_HOOK_PROFILE", "standard")
133
+ t.Setenv("DEVRITES_DISABLED_HOOKS", name)
134
+ if hookActive(name) {
135
+ t.Fatal("per-hook kill list did not disable blocker")
136
+ }
137
+
138
+ t.Setenv("DEVRITES_DISABLED_HOOKS", "")
139
+ t.Setenv("DEVRITES_HOOK_PROFILE", "minimal")
140
+ if hookActive(name) {
141
+ t.Fatal("minimal profile did not disable blocker")
142
+ }
143
+
144
+ t.Setenv("DEVRITES_HOOK_PROFILE", "standard")
145
+ var stdout, stderr bytes.Buffer
146
+ if code := run([]string{"hook", name, "--harness=claude"}, strings.NewReader("not-json"), &stdout, &stderr); code != exitOK {
147
+ t.Fatalf("malformed input exit = %d, want 0 (stderr %q)", code, stderr.String())
148
+ }
149
+ if stdout.Len() != 0 {
150
+ t.Fatalf("malformed input emitted a blocking decision: %q", stdout.String())
151
+ }
152
+ })
153
+ }
154
+ }