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
@@ -2,30 +2,32 @@ package lib
2
2
 
3
3
  import (
4
4
  "bytes"
5
+ "encoding/json"
5
6
  "os"
6
7
  "path/filepath"
7
8
  "strings"
8
9
  "testing"
10
+
11
+ "github.com/devrites/devrites/internal/reason"
9
12
  )
10
13
 
11
- func TestTimelineLogAndList(t *testing.T) {
14
+ func TestTimelineLegacyWritesRejectedAndReadsPreserved(t *testing.T) {
12
15
  t.Setenv("DEVRITES_NOW", "2026-07-09T10:11:12Z")
13
16
  root := t.TempDir()
14
17
  stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{}
15
-
16
- code := Timeline(root, []string{"log", "completed", "--skill", "rite-review", "--slug", "auth", "--outcome", "ok", "--decision", "ship"}, stdout, stderr)
17
- if code != 0 {
18
- t.Fatalf("timeline log failed: code=%d stdout=%q stderr=%q", code, stdout.String(), stderr.String())
19
- }
20
- data, err := os.ReadFile(filepath.Join(root, "timeline.jsonl"))
21
- if err != nil {
18
+ legacy := []byte(`{"ts":"2026-07-09T10:11:12Z","event":"completed","decision":"ship"}` + "\n")
19
+ path := filepath.Join(root, "timeline.jsonl")
20
+ if err := os.WriteFile(path, legacy, 0o644); err != nil {
22
21
  t.Fatal(err)
23
22
  }
24
- got := string(data)
25
- for _, want := range []string{`"ts":"2026-07-09T10:11:12Z"`, `"event":"completed"`, `"skill":"rite-review"`, `"slug":"auth"`, `"decision":"ship"`} {
26
- if !strings.Contains(got, want) {
27
- t.Fatalf("timeline entry missing %s: %s", want, got)
28
- }
23
+
24
+ code := Timeline(root, []string{"log", "completed", "--skill", "rite-review", "--slug", "auth", "--outcome", "ok", "--decision", "PRIVATE_RESULT"}, stdout, stderr)
25
+ if code != 2 || !strings.Contains(stderr.String(), "legacy free-text writes are disabled") {
26
+ t.Fatalf("legacy timeline write code=%d stdout=%q stderr=%q", code, stdout.String(), stderr.String())
27
+ }
28
+ data, err := os.ReadFile(path)
29
+ if err != nil || !bytes.Equal(data, legacy) {
30
+ t.Fatalf("legacy write changed timeline: data=%q err=%v", data, err)
29
31
  }
30
32
 
31
33
  stdout.Reset()
@@ -44,6 +46,71 @@ func TestTimelineLogAndList(t *testing.T) {
44
46
  }
45
47
  }
46
48
 
49
+ func TestTimelineTypedFactsAreValidatedAndVersioned(t *testing.T) {
50
+ t.Setenv("DEVRITES_NOW", "2026-07-09T10:11:12Z")
51
+ t.Setenv("DEVRITES_RUN_ID", testRunID)
52
+ project := t.TempDir()
53
+ root := filepath.Join(project, ".devrites")
54
+ workspace := filepath.Join(root, "work", "auth")
55
+ if err := os.MkdirAll(workspace, 0o755); err != nil {
56
+ t.Fatal(err)
57
+ }
58
+ if err := os.WriteFile(filepath.Join(workspace, "state.md"), []byte("| phase | build |\n"), 0o644); err != nil {
59
+ t.Fatal(err)
60
+ }
61
+ t.Setenv("DEVRITES_ROOT", root)
62
+ stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{}
63
+ code := Timeline(root, []string{
64
+ "log", "review-dispatch", "--slug", "auth",
65
+ "--execution-mode", "generic",
66
+ "--guard-strength", "observed",
67
+ "--reason-id", string(reason.AgentGenericFallback),
68
+ "--host", "codex",
69
+ "--evidence", ".devrites/work/auth/plan.md",
70
+ }, stdout, stderr)
71
+ if code != 0 {
72
+ t.Fatalf("timeline log code=%d stdout=%q stderr=%q", code, stdout.String(), stderr.String())
73
+ }
74
+ raw, err := os.ReadFile(filepath.Join(root, "timeline.jsonl"))
75
+ if err != nil {
76
+ t.Fatal(err)
77
+ }
78
+ var got EventV1
79
+ if err := json.Unmarshal(raw, &got); err != nil {
80
+ t.Fatal(err)
81
+ }
82
+ if got.Schema != EventSchemaV1 || got.ExecutionMode != ExecutionGeneric ||
83
+ got.GuardStrength != GuardObserved || got.ReasonID != reason.AgentGenericFallback ||
84
+ got.Host != HostCodex || got.Workspace != ".devrites/work/auth" {
85
+ t.Fatalf("typed event = %+v", got)
86
+ }
87
+
88
+ stderr.Reset()
89
+ if code := Timeline(root, []string{
90
+ "log", "bad", "--execution-mode", "named", "--guard-strength", "enforced",
91
+ "--reason-id", "DRV-UNKNOWN",
92
+ }, &bytes.Buffer{}, stderr); code != 2 {
93
+ t.Fatalf("unknown reason code=%d stderr=%q", code, stderr.String())
94
+ }
95
+ }
96
+
97
+ func TestTimelineListPreservesAndDistinguishesLegacyAndCorruptRows(t *testing.T) {
98
+ root := t.TempDir()
99
+ legacy := `{"ts":"2026-01-01T00:00:00Z","event":"legacy"}`
100
+ corrupt := `{not-json}`
101
+ if err := os.WriteFile(filepath.Join(root, "timeline.jsonl"), []byte(legacy+"\n"+corrupt+"\n"), 0o644); err != nil {
102
+ t.Fatal(err)
103
+ }
104
+ stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{}
105
+ if code := Timeline(root, []string{"list"}, stdout, stderr); code != 0 {
106
+ t.Fatalf("timeline list code=%d stderr=%q", code, stderr.String())
107
+ }
108
+ if got := stdout.String(); !strings.Contains(got, legacy) || !strings.Contains(got, corrupt) ||
109
+ strings.Contains(legacy, `"schema":"`+EventSchemaV1+`"`) {
110
+ t.Fatalf("legacy/corrupt history changed: %q", got)
111
+ }
112
+ }
113
+
47
114
  func TestHealthRecordValidatesScore(t *testing.T) {
48
115
  t.Setenv("DEVRITES_NOW", "2026-07-09")
49
116
  root := t.TempDir()
@@ -4,7 +4,6 @@ import (
4
4
  "fmt"
5
5
  "io"
6
6
  "os"
7
- "os/exec"
8
7
  "path/filepath"
9
8
  "regexp"
10
9
  "strings"
@@ -18,14 +17,13 @@ var (
18
17
  assertMarkers = regexp.MustCompile(`assert|expect\(|\.should\b|\brequire\.|EXPECT_|ASSERT_|XCTAssert|t\.Error|t\.Fatal`)
19
18
  )
20
19
 
21
- // TestIntegrity guards against reaching green by weakening tests instead of fixing
22
- // code. It diffs the test files against the slice's base tree (the reconcile
23
- // snapshot if present, else HEAD) and flags any test file that was deleted, gained
24
- // a skip/focus marker, or lost assertions. Read-only; the workspace is
25
- // <root>/features/<slug>.
20
+ // TestIntegrity detects tests that were weakened instead of fixing the code. It
21
+ // compares test files in the retained reconcile tree with the current tree, or
22
+ // uses HEAD as the base outside a slice lifecycle. It does not modify the
23
+ // workspace at <root>/features/<slug>.
26
24
  //
27
25
  // 0 clean, or skipped because this is not a git repo
28
- // 2 no active workspace / bad args
26
+ // 2 no active workspace, bad args, or missing/corrupt baseline state
29
27
  // 3 WEAKENED: a test was deleted, skipped, or de-asserted since the base
30
28
  func TestIntegrity(root string, args []string, stdout, stderr io.Writer) int {
31
29
  // Accept both `test-integrity [slug]` and the `check [slug]` alias; a leading
@@ -56,23 +54,26 @@ func TestIntegrity(root string, args []string, stdout, stderr io.Writer) int {
56
54
  cwd, _ := os.Getwd()
57
55
  gitRoot := gitToplevel(cwd)
58
56
  if gitRoot == "" {
59
- fmt.Fprintln(stderr, "test-integrity: not a git repo: gate skipped, eyeball the test diff by hand.")
57
+ fmt.Fprintln(stderr, "test-integrity: not a git repo: gate skipped; inspect the test diff manually.")
60
58
  return 0
61
59
  }
62
60
 
63
- baseTree := ""
64
- if b, err := os.ReadFile(filepath.Join(d, ".reconcile-base")); err == nil {
65
- baseTree = strings.TrimSpace(string(b))
61
+ trees, err := captureSliceTreeRange(gitRoot, root, d)
62
+ if err != nil {
63
+ fmt.Fprintf(stderr, "test-integrity: cannot capture slice range: %v\n", err)
64
+ return 2
66
65
  }
67
- ref := baseTree
68
- if ref == "" {
69
- ref = "HEAD"
66
+ defer trees.cleanup()
67
+ changedPaths, err := changedTreePaths(gitRoot, trees.env, trees.base, trees.current)
68
+ if err != nil {
69
+ fmt.Fprintf(stderr, "test-integrity: cannot compare slice trees: %v\n", err)
70
+ return 2
70
71
  }
71
72
 
72
73
  weak := 0
73
74
  srcChanged, testChanged := 0, 0
74
75
  var report strings.Builder
75
- for _, f := range gitDiffNames(gitRoot, ref) {
76
+ for _, f := range changedPaths {
76
77
  if f == "" {
77
78
  continue
78
79
  }
@@ -83,19 +84,25 @@ func TestIntegrity(root string, args []string, stdout, stderr io.Writer) int {
83
84
  continue
84
85
  }
85
86
  testChanged++
86
- old, ok := gitShow(gitRoot, ref+":"+f)
87
- if !ok || old == "" {
87
+ oldBytes, oldExists, err := treeFileContent(gitRoot, trees.env, trees.base, f)
88
+ if err != nil {
89
+ fmt.Fprintf(stderr, "test-integrity: cannot read baseline %s: %v\n", f, err)
90
+ return 2
91
+ }
92
+ if !oldExists || len(oldBytes) == 0 {
88
93
  continue // new test file: adding tests is never a weakening
89
94
  }
90
- if !isFile(filepath.Join(gitRoot, f)) {
95
+ newBytes, newExists, err := treeFileContent(gitRoot, trees.env, trees.current, f)
96
+ if err != nil {
97
+ fmt.Fprintf(stderr, "test-integrity: cannot read current %s: %v\n", f, err)
98
+ return 2
99
+ }
100
+ if !newExists {
91
101
  weak++
92
102
  fmt.Fprintf(&report, " - %s: test file DELETED\n", f)
93
103
  continue
94
104
  }
95
- newBytes, err := os.ReadFile(filepath.Join(gitRoot, f))
96
- if err != nil {
97
- continue
98
- }
105
+ old := string(oldBytes)
99
106
  now := string(newBytes)
100
107
  oldSkips, newSkips := countMatches(skipMarkers, old), countMatches(skipMarkers, now)
101
108
  oldAsserts, newAsserts := countMatches(assertMarkers, old), countMatches(assertMarkers, now)
@@ -109,10 +116,9 @@ func TestIntegrity(root string, args []string, stdout, stderr io.Writer) int {
109
116
  }
110
117
  }
111
118
 
112
- // Verification-gap advisory: source changed but no test file did. Not a
113
- // weakening and never a verdict on its own: a pointer to run the
114
- // verification-gap trace (testing.md), since a green suite can pass identically
115
- // with the change reverted. Printed on both the clean and weakened paths.
119
+ // When source changes without a test change, print the verification-gap
120
+ // advisory from testing.md. This is not a weakening verdict and appears on
121
+ // both clean and weakened paths.
116
122
  gapAdvisory := ""
117
123
  if srcChanged > 0 && testChanged == 0 {
118
124
  gapAdvisory = fmt.Sprintf("test-integrity: advisory: %d source file(s) changed, 0 test file(s) touched; "+
@@ -122,7 +128,7 @@ func TestIntegrity(root string, args []string, stdout, stderr io.Writer) int {
122
128
  if weak > 0 {
123
129
  fmt.Fprintf(stderr, "test-integrity: WEAKENED: %d test file(s) lost coverage since the slice base:\n", weak)
124
130
  fmt.Fprint(stderr, report.String())
125
- fmt.Fprintln(stderr, "A test weakened to pass a gate is a Critical finding. Revert it; fix the code or raise a blocking question.")
131
+ fmt.Fprintln(stderr, "Weakening a test to pass the gate is a Critical finding. Restore the test, then fix the code or raise a blocking question.")
126
132
  if gapAdvisory != "" {
127
133
  fmt.Fprintln(stderr, gapAdvisory)
128
134
  }
@@ -191,13 +197,3 @@ func isTestFile(f string) bool {
191
197
  func countMatches(re *regexp.Regexp, text string) int {
192
198
  return len(re.FindAllStringIndex(text, -1))
193
199
  }
194
-
195
- // gitShow returns the content of an object (e.g. "HEAD:path"), and false when it
196
- // does not exist at that ref.
197
- func gitShow(gitRoot, spec string) (string, bool) {
198
- out, err := exec.Command("git", "-C", gitRoot, "show", spec).Output()
199
- if err != nil {
200
- return "", false
201
- }
202
- return string(out), true
203
- }
@@ -1,6 +1,12 @@
1
1
  package lib
2
2
 
3
- import "testing"
3
+ import (
4
+ "bytes"
5
+ "os"
6
+ "path/filepath"
7
+ "strings"
8
+ "testing"
9
+ )
4
10
 
5
11
  func TestIsTestFileClassifiesTests(t *testing.T) {
6
12
  cases := []struct {
@@ -70,3 +76,59 @@ func TestIsSourceFileRecognisesCode(t *testing.T) {
70
76
  }
71
77
  }
72
78
  }
79
+
80
+ func TestIntegrityDetectsDeletionFromDirtySliceBaseline(t *testing.T) {
81
+ gitRoot := newGitRepo(t)
82
+ root := workspace(t, "feat")
83
+ testPath := "tests/preexisting_test.go"
84
+ writeFile(t, filepath.Join(gitRoot, testPath), "package tests\n\nfunc TestBaseline(t *testing.T) { t.Fatal(\"assert\") }\n")
85
+ writeWrightAllowlist(t, root, "feat", testPath)
86
+ if code, out := runReconcile(t, root, "snapshot", "feat"); code != 0 {
87
+ t.Fatalf("snapshot = %d, want 0\n%s", code, out)
88
+ }
89
+ if err := os.Remove(filepath.Join(gitRoot, testPath)); err != nil {
90
+ t.Fatal(err)
91
+ }
92
+ if code, out := runReconcile(t, root, "check", "feat"); code != 0 {
93
+ t.Fatalf("reconcile check = %d, want 0\n%s", code, out)
94
+ }
95
+
96
+ var stdout, stderr bytes.Buffer
97
+ code := TestIntegrity(root, []string{"feat"}, &stdout, &stderr)
98
+ if code != 3 {
99
+ t.Fatalf("test-integrity = %d, want 3\nstdout: %s\nstderr: %s", code, stdout.String(), stderr.String())
100
+ }
101
+ if !strings.Contains(stderr.String(), testPath+": test file DELETED") {
102
+ t.Fatalf("deleted dirty-baseline test missing from finding:\n%s", stderr.String())
103
+ }
104
+ }
105
+
106
+ func TestIntegrityFailsClosedOnPartialReconcileBaseline(t *testing.T) {
107
+ newGitRepo(t)
108
+ root := workspace(t, "feat")
109
+ writeFile(t, filepath.Join(featureDir(root, "feat"), reconcileBaseName), "deadbeef\n")
110
+
111
+ var stdout, stderr bytes.Buffer
112
+ code := TestIntegrity(root, []string{"feat"}, &stdout, &stderr)
113
+ if code != 2 {
114
+ t.Fatalf("test-integrity = %d, want 2\nstdout: %s\nstderr: %s", code, stdout.String(), stderr.String())
115
+ }
116
+ if !strings.Contains(stderr.String(), "partial lifecycle") {
117
+ t.Fatalf("missing fail-closed lifecycle diagnostic:\n%s", stderr.String())
118
+ }
119
+ }
120
+
121
+ func TestIntegrityAdvisesWhenSourceChangesWithoutTests(t *testing.T) {
122
+ gitRoot := newGitRepo(t)
123
+ root := workspace(t, "feat")
124
+ writeFile(t, filepath.Join(gitRoot, "src", "feature.go"), "package src\n")
125
+
126
+ var stdout, stderr bytes.Buffer
127
+ code := TestIntegrity(root, []string{"feat"}, &stdout, &stderr)
128
+ if code != 0 {
129
+ t.Fatalf("test-integrity = %d, want 0\nstdout: %s\nstderr: %s", code, stdout.String(), stderr.String())
130
+ }
131
+ if !strings.Contains(stdout.String(), "source file(s) changed, 0 test file(s) touched") {
132
+ t.Fatalf("missing verification-gap advisory:\n%s", stdout.String())
133
+ }
134
+ }
@@ -11,6 +11,7 @@ import (
11
11
  "path/filepath"
12
12
  "slices"
13
13
  "sort"
14
+ "strconv"
14
15
  "strings"
15
16
  "time"
16
17
 
@@ -93,6 +94,9 @@ func needsNormalizeFeature(dir string) bool {
93
94
  if !regularFileExists(filepath.Join(dir, state.WorkspaceMapFile)) {
94
95
  return true
95
96
  }
97
+ if version := declaredSchemaVersion(dir); version < state.SchemaVersion {
98
+ return true
99
+ }
96
100
  return regularFileExists(filepath.Join(dir, "proof.md")) && !regularFileExists(filepath.Join(dir, state.EvidenceFile)) ||
97
101
  regularFileExists(filepath.Join(dir, "status.md")) && !regularFileExists(filepath.Join(dir, state.LedgerFile))
98
102
  }
@@ -114,12 +118,88 @@ func normalizeFeatureDir(dir, slug string) error {
114
118
  return fmt.Errorf("write workspace index: %w", err)
115
119
  }
116
120
  }
121
+ phase := derivePhase(filepath.Join(dir, state.LedgerFile))
122
+ if err := upgradeWorkspaceMapSchema(filepath.Join(dir, state.WorkspaceMapFile), slug, phase); err != nil {
123
+ return fmt.Errorf("upgrade workspace schema: %w", err)
124
+ }
117
125
  if err := copyAliasFile(dir, "proof.md", state.EvidenceFile); err != nil {
118
126
  return fmt.Errorf("copy proof.md to evidence.md: %w", err)
119
127
  }
120
128
  return copyAliasFile(dir, "status.md", state.LedgerFile)
121
129
  }
122
130
 
131
+ func declaredSchemaVersion(dir string) int {
132
+ for _, name := range state.WorkspaceMapFiles() {
133
+ raw, err := os.ReadFile(filepath.Join(dir, name))
134
+ if err != nil {
135
+ continue
136
+ }
137
+ lines := strings.Split(string(raw), "\n")
138
+ if len(lines) == 0 || strings.TrimSpace(lines[0]) != "---" {
139
+ return 0
140
+ }
141
+ for _, line := range lines[1:] {
142
+ if strings.TrimSpace(line) == "---" {
143
+ break
144
+ }
145
+ key, value, ok := strings.Cut(line, ":")
146
+ if ok && strings.TrimSpace(key) == "schemaVersion" {
147
+ version, _ := strconv.Atoi(strings.TrimSpace(value))
148
+ return version
149
+ }
150
+ }
151
+ return 0
152
+ }
153
+ return 0
154
+ }
155
+
156
+ // upgradeWorkspaceMapSchema advances only the compatibility declaration. It
157
+ // never creates decision coverage, readiness, or proof artifacts: those must be
158
+ // earned by their owning phases.
159
+ func upgradeWorkspaceMapSchema(path, slug string, phase state.Phase) error {
160
+ raw, err := os.ReadFile(path)
161
+ if err != nil {
162
+ return err
163
+ }
164
+ version := declaredSchemaVersion(filepath.Dir(path))
165
+ if version > state.SchemaVersion {
166
+ return fmt.Errorf("schemaVersion %d is newer than this engine supports (%d)", version, state.SchemaVersion)
167
+ }
168
+ if version == state.SchemaVersion {
169
+ return nil
170
+ }
171
+
172
+ text := string(raw)
173
+ if !strings.HasPrefix(text, "---\n") {
174
+ prefix := fmt.Sprintf("---\nslug: %s\nphase: %s\nschemaVersion: %d\n---\n\n", slug, phase, state.SchemaVersion)
175
+ return state.AtomicWrite(path, []byte(prefix+text), 0o644)
176
+ }
177
+
178
+ lines := strings.Split(text, "\n")
179
+ end := -1
180
+ schemaLine := -1
181
+ for i := 1; i < len(lines); i++ {
182
+ if strings.TrimSpace(lines[i]) == "---" {
183
+ end = i
184
+ break
185
+ }
186
+ key, _, ok := strings.Cut(lines[i], ":")
187
+ if ok && strings.TrimSpace(key) == "schemaVersion" {
188
+ schemaLine = i
189
+ }
190
+ }
191
+ if end < 0 {
192
+ return fmt.Errorf("workspace map has an unclosed frontmatter block")
193
+ }
194
+ value := fmt.Sprintf("schemaVersion: %d", state.SchemaVersion)
195
+ if schemaLine >= 0 {
196
+ lines[schemaLine] = value
197
+ } else {
198
+ lines = append(lines[:end], append([]string{value}, lines[end:]...)...)
199
+ }
200
+ return state.AtomicWrite(path, []byte(strings.Join(lines, "\n")), 0o644)
201
+ }
202
+
123
203
  func copyAliasFile(dir, alias, canonical string) error {
124
204
  src := filepath.Join(dir, alias)
125
205
  dst := filepath.Join(dir, canonical)
@@ -133,7 +213,7 @@ func copyAliasFile(dir, alias, canonical string) error {
133
213
  return state.AtomicWrite(dst, data, 0o644)
134
214
  }
135
215
 
136
- // workspaceIndex renders the compact map added to a normalized workspace.
216
+ // workspaceIndex renders the compact map used by a normalized workspace.
137
217
  func workspaceIndex(slug string, phase state.Phase) string {
138
218
  return fmt.Sprintf(`---
139
219
  slug: %s
@@ -37,6 +37,7 @@ func TestRunNormalizesCanonicalWorkLayout(t *testing.T) {
37
37
  assertFile(t, root, "work/alpha/evidence.md", "proof\n")
38
38
  assertFile(t, root, "work/alpha/state.md", "status: done - shipped\n")
39
39
  assertFileContains(t, root, "work/alpha/README.md", "phase: done")
40
+ assertFileContains(t, root, "work/alpha/README.md", "schemaVersion: 2")
40
41
  assertMissing(t, root, "work/alpha/proof.md")
41
42
  assertMissing(t, root, "work/alpha/status.md")
42
43
  assertFile(t, root, "work/alpha/review.md", "review\n")
@@ -72,7 +73,8 @@ func TestRunNormalizesLiveFeatureAliases(t *testing.T) {
72
73
  if got := strings.Join(res.Migrated, ","); got != "beta" {
73
74
  t.Fatalf("Run normalized=%q, want beta", got)
74
75
  }
75
- assertFile(t, root, "features/beta/README.md", "# Beta\n")
76
+ assertFileContains(t, root, "features/beta/README.md", "schemaVersion: 2")
77
+ assertFileContains(t, root, "features/beta/README.md", "# Beta")
76
78
  assertFile(t, root, "features/beta/state.md", "- Phase: prove\n")
77
79
  assertFile(t, root, "features/beta/evidence.md", "evidence\n")
78
80
  assertFile(t, root, "features/beta/status.md", "- Phase: prove\n")
@@ -87,6 +89,24 @@ func TestRunNormalizesLiveFeatureAliases(t *testing.T) {
87
89
  }
88
90
  }
89
91
 
92
+ func TestRunUpgradesV1DeclarationWithoutFabricatingReadiness(t *testing.T) {
93
+ root := t.TempDir()
94
+ testutil.WriteFile(t, filepath.Join(root, "work/old/README.md"), "---\nphase: vet\nschemaVersion: 1\n---\n\nOld workspace.\n")
95
+ testutil.WriteFile(t, filepath.Join(root, "work/old/state.md"), "| phase | vet |\n")
96
+
97
+ result, err := Run(root)
98
+ if err != nil {
99
+ t.Fatal(err)
100
+ }
101
+ if result.Skipped || strings.Join(result.Migrated, ",") != "old" {
102
+ t.Fatalf("result=%+v, want old migrated", result)
103
+ }
104
+ assertFileContains(t, root, "work/old/README.md", "schemaVersion: 2")
105
+ for _, name := range []string{"decision-coverage.md", "eng-review.md", "test-plan.md"} {
106
+ assertMissing(t, root, filepath.Join("work/old", name))
107
+ }
108
+ }
109
+
90
110
  func TestMapLegacyPhase(t *testing.T) {
91
111
  for _, phase := range state.LifecyclePhases() {
92
112
  got, ok := mapLegacyPhase(string(phase))
@@ -101,6 +121,7 @@ func TestMapLegacyPhase(t *testing.T) {
101
121
  ok bool
102
122
  }{
103
123
  {word: "specced", want: state.PhaseSpec, ok: true},
124
+ {word: "clarified", want: state.PhaseClarify, ok: true},
104
125
  {word: "in-progress", want: state.PhaseBuild, ok: true},
105
126
  {word: "reviewing", want: state.PhaseReview, ok: true},
106
127
  {word: "done - shipped", want: state.PhaseDone, ok: true},
@@ -0,0 +1,180 @@
1
+ // Package reason owns stable machine-readable decision identifiers.
2
+ package reason
3
+
4
+ import (
5
+ "fmt"
6
+ "sort"
7
+ )
8
+
9
+ // ID identifies one rule outcome independently of its human wording.
10
+ type ID string
11
+
12
+ const (
13
+ RootSelected ID = "DRV-ROOT-SELECTED"
14
+
15
+ GateReadinessPassed ID = "DRV-GATE-READINESS-PASSED" // #nosec G101 -- stable decision ID, not a credential
16
+ GateReadinessMissing ID = "DRV-GATE-READINESS-MISSING"
17
+ GateSealPassed ID = "DRV-GATE-SEAL-PASSED" // #nosec G101 -- stable decision ID, not a credential
18
+ GateSealMissing ID = "DRV-GATE-SEAL-MISSING"
19
+
20
+ HookDisabled ID = "DRV-HOOK-DISABLED"
21
+ HookAllowApproved ID = "DRV-HOOK-ALLOW-APPROVED"
22
+ HookStopClear ID = "DRV-HOOK-STOP-CLEAR"
23
+ HookStopInputInvalid ID = "DRV-HOOK-STOP-INPUT-INVALID"
24
+ HookStopReentry ID = "DRV-HOOK-STOP-REENTRY"
25
+ HookStopWorkspaceUnavailable ID = "DRV-HOOK-STOP-WORKSPACE-UNAVAILABLE" // #nosec G101 -- stable decision ID, not a credential
26
+ HookStopRed ID = "DRV-HOOK-STOP-RED"
27
+ HookStopUnsurfacedHumanGate ID = "DRV-HOOK-STOP-UNSURFACED-HUMAN-GATE"
28
+ HookStopMissingProof ID = "DRV-HOOK-STOP-MISSING-PROOF"
29
+ HookReviewerReadonlyDenied ID = "DRV-HOOK-REVIEWER-READONLY-DENIED"
30
+ HookReviewerReadonlyObserved ID = "DRV-HOOK-REVIEWER-READONLY-OBSERVED"
31
+ HookA1Denied ID = "DRV-HOOK-A1-DENIED"
32
+ HookA1Observed ID = "DRV-HOOK-A1-OBSERVED"
33
+ HookWrightScopeDenied ID = "DRV-HOOK-WRIGHT-SCOPE-DENIED"
34
+ HookWrightScopeObserved ID = "DRV-HOOK-WRIGHT-SCOPE-OBSERVED"
35
+ HookWrightForbiddenDenied ID = "DRV-HOOK-WRIGHT-FORBIDDEN-DENIED"
36
+ HookForgeBindingDenied ID = "DRV-HOOK-FORGE-BINDING-DENIED"
37
+ HookIngestWarning ID = "DRV-HOOK-INGEST-WARNING"
38
+
39
+ GitAuthorityPending ID = "DRV-GIT-AUTHORITY-PENDING"
40
+ GitAuthorityGranted ID = "DRV-GIT-AUTHORITY-GRANTED"
41
+ GitAuthorityExpired ID = "DRV-GIT-AUTHORITY-EXPIRED"
42
+ GitAuthorityRefused ID = "DRV-GIT-AUTHORITY-REFUSED"
43
+ GitAuthorityReplayed ID = "DRV-GIT-AUTHORITY-REPLAYED"
44
+ GitAuthorityCorrupt ID = "DRV-GIT-AUTHORITY-CORRUPT"
45
+ GitAuthorityUnavailable ID = "DRV-GIT-AUTHORITY-UNAVAILABLE"
46
+ GitWorkspaceUnavailable ID = "DRV-GIT-WORKSPACE-UNAVAILABLE"
47
+
48
+ GitInputTooLarge ID = "DRV-GIT-INPUT-TOO-LARGE"
49
+ GitAmbiguousSyntax ID = "DRV-GIT-AMBIGUOUS-SYNTAX"
50
+ GitAmbiguousDynamic ID = "DRV-GIT-AMBIGUOUS-DYNAMIC"
51
+ GitAmbiguousAlias ID = "DRV-GIT-AMBIGUOUS-ALIAS"
52
+ GitAmbiguousGlobalOption ID = "DRV-GIT-AMBIGUOUS-GLOBAL-OPTION"
53
+ GitAmbiguousStdin ID = "DRV-GIT-AMBIGUOUS-STDIN"
54
+ GitDestructiveReset ID = "DRV-GIT-DESTRUCTIVE-RESET"
55
+ GitDestructiveClean ID = "DRV-GIT-DESTRUCTIVE-CLEAN"
56
+ GitDestructiveCheckout ID = "DRV-GIT-DESTRUCTIVE-CHECKOUT"
57
+ GitDestructiveRestore ID = "DRV-GIT-DESTRUCTIVE-RESTORE"
58
+ GitDestructiveSwitch ID = "DRV-GIT-DESTRUCTIVE-SWITCH"
59
+ GitDestructiveRemove ID = "DRV-GIT-DESTRUCTIVE-RM"
60
+ GitDestructiveBranch ID = "DRV-GIT-DESTRUCTIVE-BRANCH"
61
+ GitDestructiveTag ID = "DRV-GIT-DESTRUCTIVE-TAG"
62
+ GitDestructiveUpdateRef ID = "DRV-GIT-DESTRUCTIVE-UPDATE-REF"
63
+ GitDestructiveStash ID = "DRV-GIT-DESTRUCTIVE-STASH"
64
+ GitDestructiveReflog ID = "DRV-GIT-DESTRUCTIVE-REFLOG"
65
+ GitDestructivePrune ID = "DRV-GIT-DESTRUCTIVE-PRUNE"
66
+ GitDestructiveHistory ID = "DRV-GIT-DESTRUCTIVE-HISTORY"
67
+ GitDestructivePushForce ID = "DRV-GIT-DESTRUCTIVE-PUSH-FORCE"
68
+ GitDestructivePushDelete ID = "DRV-GIT-DESTRUCTIVE-PUSH-DELETE"
69
+ GitDestructiveWorktree ID = "DRV-GIT-DESTRUCTIVE-WORKTREE"
70
+ GitDestructiveRefDeletion ID = "DRV-GIT-DESTRUCTIVE-REF-DELETE"
71
+
72
+ AgentNamed ID = "DRV-AGENT-NAMED"
73
+ AgentGenericFallback ID = "DRV-AGENT-GENERIC-FALLBACK"
74
+ AgentInlineFallback ID = "DRV-AGENT-INLINE-FALLBACK"
75
+ AgentUnavailable ID = "DRV-AGENT-UNAVAILABLE"
76
+ AgentResultAccepted ID = "DRV-AGENT-RESULT-ACCEPTED"
77
+ AgentResultMalformed ID = "DRV-AGENT-RESULT-MALFORMED"
78
+ AgentResultStale ID = "DRV-AGENT-RESULT-STALE"
79
+ AgentResultIdentityMismatch ID = "DRV-AGENT-RESULT-IDENTITY-MISMATCH"
80
+ AgentResultOutOfScope ID = "DRV-AGENT-RESULT-OUT-OF-SCOPE"
81
+ )
82
+
83
+ var catalog = []ID{
84
+ RootSelected,
85
+ GateReadinessPassed,
86
+ GateReadinessMissing,
87
+ GateSealPassed,
88
+ GateSealMissing,
89
+ HookDisabled,
90
+ HookAllowApproved,
91
+ HookStopClear,
92
+ HookStopInputInvalid,
93
+ HookStopReentry,
94
+ HookStopWorkspaceUnavailable,
95
+ HookStopRed,
96
+ HookStopUnsurfacedHumanGate,
97
+ HookStopMissingProof,
98
+ HookReviewerReadonlyDenied,
99
+ HookReviewerReadonlyObserved,
100
+ HookA1Denied,
101
+ HookA1Observed,
102
+ HookWrightScopeDenied,
103
+ HookWrightScopeObserved,
104
+ HookWrightForbiddenDenied,
105
+ HookForgeBindingDenied,
106
+ HookIngestWarning,
107
+ GitAuthorityPending,
108
+ GitAuthorityGranted,
109
+ GitAuthorityExpired,
110
+ GitAuthorityRefused,
111
+ GitAuthorityReplayed,
112
+ GitAuthorityCorrupt,
113
+ GitAuthorityUnavailable,
114
+ GitWorkspaceUnavailable,
115
+ GitInputTooLarge,
116
+ GitAmbiguousSyntax,
117
+ GitAmbiguousDynamic,
118
+ GitAmbiguousAlias,
119
+ GitAmbiguousGlobalOption,
120
+ GitAmbiguousStdin,
121
+ GitDestructiveReset,
122
+ GitDestructiveClean,
123
+ GitDestructiveCheckout,
124
+ GitDestructiveRestore,
125
+ GitDestructiveSwitch,
126
+ GitDestructiveRemove,
127
+ GitDestructiveBranch,
128
+ GitDestructiveTag,
129
+ GitDestructiveUpdateRef,
130
+ GitDestructiveStash,
131
+ GitDestructiveReflog,
132
+ GitDestructivePrune,
133
+ GitDestructiveHistory,
134
+ GitDestructivePushForce,
135
+ GitDestructivePushDelete,
136
+ GitDestructiveWorktree,
137
+ GitDestructiveRefDeletion,
138
+ AgentNamed,
139
+ AgentGenericFallback,
140
+ AgentInlineFallback,
141
+ AgentUnavailable,
142
+ AgentResultAccepted,
143
+ AgentResultMalformed,
144
+ AgentResultStale,
145
+ AgentResultIdentityMismatch,
146
+ AgentResultOutOfScope,
147
+ }
148
+
149
+ var known = func() map[ID]struct{} {
150
+ out := make(map[ID]struct{}, len(catalog))
151
+ for _, id := range catalog {
152
+ if _, duplicate := out[id]; duplicate {
153
+ panic("duplicate reason id " + id)
154
+ }
155
+ out[id] = struct{}{}
156
+ }
157
+ return out
158
+ }()
159
+
160
+ // Parse accepts only identifiers in the frozen catalog.
161
+ func Parse(value string) (ID, error) {
162
+ id := ID(value)
163
+ if _, ok := known[id]; !ok {
164
+ return "", fmt.Errorf("unknown reason id %q", value)
165
+ }
166
+ return id, nil
167
+ }
168
+
169
+ // Known reports whether id belongs to the frozen catalog.
170
+ func Known(id ID) bool {
171
+ _, ok := known[id]
172
+ return ok
173
+ }
174
+
175
+ // All returns the catalog in lexical order for generators and tests.
176
+ func All() []ID {
177
+ out := append([]ID(nil), catalog...)
178
+ sort.Slice(out, func(i, j int) bool { return out[i] < out[j] })
179
+ return out
180
+ }