devrites 3.0.7 → 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 +12 -0
  2. package/README.md +67 -47
  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 +486 -38
  54. package/engine/internal/install/install_test.go +383 -10
  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
@@ -0,0 +1,601 @@
1
+ package lib
2
+
3
+ import (
4
+ "bytes"
5
+ "encoding/json"
6
+ "fmt"
7
+ "io"
8
+ "os"
9
+ "path/filepath"
10
+ "regexp"
11
+ "runtime"
12
+ "sort"
13
+ "strings"
14
+ "time"
15
+
16
+ "github.com/devrites/devrites/internal/reason"
17
+ "github.com/devrites/devrites/internal/state"
18
+ "github.com/devrites/devrites/internal/toolpolicy"
19
+ )
20
+
21
+ const (
22
+ GitAuthoritySchemaV1 = "devrites-git-authority/v1"
23
+ GitAuthorityConsumptionSchemaV1 = "devrites-git-authority-consumption/v1"
24
+ GitAuthorityKind = "destructive-git-once"
25
+ GitAuthorityAnswer = "Authorize once"
26
+ GitAuthorityLedgerFile = ".git-authority-consumption.jsonl"
27
+ GitAuthorityTTL = 15 * time.Minute
28
+
29
+ maxGitAuthorityFileBytes = 1 << 20
30
+ maxGitAuthorityLedgerBytes = 256 << 10
31
+ maxGitAuthorityReasons = 32
32
+ )
33
+
34
+ var (
35
+ gitAuthorityDigestRE = regexp.MustCompile(`^drv-git-op-v1:sha256:[0-9a-f]{64}$`)
36
+ gitAuthorityQIDRE = regexp.MustCompile(`^q-[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{3}$`)
37
+ )
38
+
39
+ // GitAuthorityDecision is deliberately metadata-only. It never carries the
40
+ // command, normalized tokens, paths, refs, or a filesystem error.
41
+ type GitAuthorityDecision struct {
42
+ Allowed bool
43
+ Opened bool
44
+ QuestionID string
45
+ ReasonID reason.ID
46
+ }
47
+
48
+ type gitAuthorityQuestion struct {
49
+ QID string
50
+ Status string
51
+ Digest string
52
+ ClassifierReasonIDs []string
53
+ RequestedAt time.Time
54
+ ExpiresAt time.Time
55
+ AnsweredAt time.Time
56
+ Answer string
57
+ }
58
+
59
+ type gitAuthorityConsumption struct {
60
+ Schema string `json:"schema"`
61
+ Kind string `json:"kind"`
62
+ QuestionID string `json:"question_id"`
63
+ OperationDigest string `json:"operation_digest"`
64
+ ClassifierReasonIDs []string `json:"classifier_reason_ids"`
65
+ ConsumedAt string `json:"consumed_at"`
66
+ }
67
+
68
+ // AuthorizeGitOperation consumes an exact answered grant or opens one
69
+ // idempotent escalating question. Callers must invoke it only for an
70
+ // unambiguous destructive classifier result.
71
+ func AuthorizeGitOperation(root, slug, digest string, classifierReasonIDs []toolpolicy.ReasonID) GitAuthorityDecision {
72
+ reasons, ok := canonicalGitAuthorityReasons(classifierReasonIDs)
73
+ if !ok || !gitAuthorityDigestRE.MatchString(digest) || strings.TrimSpace(slug) == "" {
74
+ return GitAuthorityDecision{ReasonID: reason.GitAuthorityCorrupt}
75
+ }
76
+
77
+ work := featureDir(root, slug)
78
+ if !regularDirectory(work) {
79
+ return GitAuthorityDecision{ReasonID: reason.GitWorkspaceUnavailable}
80
+ }
81
+ qfile := filepath.Join(work, "questions.md")
82
+ sfile := filepath.Join(work, "state.md")
83
+ if !regularFileNoSymlink(qfile) || !regularFileNoSymlink(sfile) {
84
+ return GitAuthorityDecision{ReasonID: reason.GitWorkspaceUnavailable}
85
+ }
86
+
87
+ decision := GitAuthorityDecision{ReasonID: reason.GitAuthorityUnavailable}
88
+ if err := state.WithFeatureLock(root, slug, func() error {
89
+ var err error
90
+ decision, err = authorizeGitOperationLocked(root, slug, qfile, sfile, digest, reasons)
91
+ return err
92
+ }); err != nil {
93
+ return GitAuthorityDecision{ReasonID: reason.GitAuthorityUnavailable}
94
+ }
95
+ return decision
96
+ }
97
+
98
+ func authorizeGitOperationLocked(root, slug, qfile, sfile, digest string, reasons []string) (GitAuthorityDecision, error) {
99
+ now := clockNow().UTC().Truncate(time.Second)
100
+ qdata, err := readBoundedRegularFile(qfile, maxGitAuthorityFileBytes)
101
+ if err != nil {
102
+ return GitAuthorityDecision{ReasonID: reason.GitAuthorityCorrupt}, nil
103
+ }
104
+ questions, err := parseGitAuthorityQuestions(qdata)
105
+ if err != nil {
106
+ return GitAuthorityDecision{ReasonID: reason.GitAuthorityCorrupt}, nil
107
+ }
108
+
109
+ ledgerPath := filepath.Join(featureDir(root, slug), GitAuthorityLedgerFile)
110
+ ledger, ledgerData, err := readGitAuthorityLedger(ledgerPath)
111
+ if err != nil {
112
+ return GitAuthorityDecision{ReasonID: reason.GitAuthorityCorrupt}, nil
113
+ }
114
+ consumed := make(map[string]bool, len(ledger))
115
+ for _, entry := range ledger {
116
+ consumed[entry.QuestionID+"\x00"+entry.OperationDigest] = true
117
+ }
118
+
119
+ reopenReason := reason.ID("")
120
+ for i := len(questions) - 1; i >= 0; i-- {
121
+ q := questions[i]
122
+ if q.Digest != digest {
123
+ continue
124
+ }
125
+ if !equalStrings(q.ClassifierReasonIDs, reasons) {
126
+ return GitAuthorityDecision{ReasonID: reason.GitAuthorityCorrupt}, nil
127
+ }
128
+ if q.RequestedAt.After(now) {
129
+ return GitAuthorityDecision{ReasonID: reason.GitAuthorityCorrupt}, nil
130
+ }
131
+ fresh := now.Before(q.ExpiresAt)
132
+ switch q.Status {
133
+ case "dropped":
134
+ return GitAuthorityDecision{QuestionID: q.QID, ReasonID: reason.GitAuthorityRefused}, nil
135
+ case "answered":
136
+ if strings.TrimSpace(q.Answer) != GitAuthorityAnswer {
137
+ return GitAuthorityDecision{QuestionID: q.QID, ReasonID: reason.GitAuthorityRefused}, nil
138
+ }
139
+ if q.AnsweredAt.Before(q.RequestedAt) || q.AnsweredAt.After(now) {
140
+ return GitAuthorityDecision{QuestionID: q.QID, ReasonID: reason.GitAuthorityCorrupt}, nil
141
+ }
142
+ if !fresh || q.AnsweredAt.After(q.ExpiresAt) {
143
+ reopenReason = reason.GitAuthorityExpired
144
+ break
145
+ }
146
+ if consumed[q.QID+"\x00"+digest] {
147
+ reopenReason = reason.GitAuthorityReplayed
148
+ break
149
+ }
150
+ entry := gitAuthorityConsumption{
151
+ Schema: GitAuthorityConsumptionSchemaV1,
152
+ Kind: GitAuthorityKind,
153
+ QuestionID: q.QID,
154
+ OperationDigest: digest,
155
+ ClassifierReasonIDs: append([]string(nil), reasons...),
156
+ ConsumedAt: formatGitAuthorityTime(now),
157
+ }
158
+ if err := appendGitAuthorityConsumption(ledgerPath, ledgerData, entry); err != nil {
159
+ return GitAuthorityDecision{QuestionID: q.QID, ReasonID: reason.GitAuthorityUnavailable}, nil
160
+ }
161
+ return GitAuthorityDecision{Allowed: true, QuestionID: q.QID, ReasonID: reason.GitAuthorityGranted}, nil
162
+ case "open":
163
+ if fresh {
164
+ if err := ensureGitAuthorityAwaiting(sfile, q.QID); err != nil {
165
+ return GitAuthorityDecision{QuestionID: q.QID, ReasonID: reason.GitAuthorityUnavailable}, nil
166
+ }
167
+ return GitAuthorityDecision{QuestionID: q.QID, ReasonID: reason.GitAuthorityPending}, nil
168
+ }
169
+ reopenReason = reason.GitAuthorityExpired
170
+ }
171
+ break
172
+ }
173
+
174
+ qid, err := nextQuestionID(qdata, now)
175
+ if err != nil {
176
+ return GitAuthorityDecision{ReasonID: reason.GitAuthorityUnavailable}, nil
177
+ }
178
+ request := renderGitAuthorityQuestion(qdata, qid, digest, reasons, now)
179
+ if err := state.AtomicWrite(qfile, request, 0o644); err != nil {
180
+ return GitAuthorityDecision{ReasonID: reason.GitAuthorityUnavailable}, nil
181
+ }
182
+ if err := ensureGitAuthorityAwaiting(sfile, qid); err != nil {
183
+ return GitAuthorityDecision{QuestionID: qid, ReasonID: reason.GitAuthorityUnavailable}, nil
184
+ }
185
+ if reopenReason == "" {
186
+ reopenReason = reason.GitAuthorityPending
187
+ }
188
+ return GitAuthorityDecision{
189
+ Opened: true,
190
+ QuestionID: qid,
191
+ ReasonID: reopenReason,
192
+ }, nil
193
+ }
194
+
195
+ func canonicalGitAuthorityReasons(ids []toolpolicy.ReasonID) ([]string, bool) {
196
+ if len(ids) == 0 || len(ids) > maxGitAuthorityReasons {
197
+ return nil, false
198
+ }
199
+ seen := make(map[string]bool, len(ids))
200
+ out := make([]string, 0, len(ids))
201
+ for _, raw := range ids {
202
+ value := string(raw)
203
+ parsed, err := reason.Parse(value)
204
+ if err != nil || !strings.HasPrefix(string(parsed), "DRV-GIT-DESTRUCTIVE-") {
205
+ return nil, false
206
+ }
207
+ if !seen[value] {
208
+ seen[value] = true
209
+ out = append(out, value)
210
+ }
211
+ }
212
+ sort.Strings(out)
213
+ return out, len(out) > 0
214
+ }
215
+
216
+ func parseGitAuthorityQuestions(data []byte) ([]gitAuthorityQuestion, error) {
217
+ lines := splitLinesNoTrailing(data)
218
+ var out []gitAuthorityQuestion
219
+ for i := 0; i < len(lines); {
220
+ if !qHeaderRe.MatchString(lines[i]) {
221
+ i++
222
+ continue
223
+ }
224
+ header := strings.TrimSpace(strings.TrimPrefix(lines[i], "## "))
225
+ start := i
226
+ i++
227
+ for i < len(lines) && !qHeaderRe.MatchString(lines[i]) {
228
+ i++
229
+ }
230
+ fields, duplicate, unknown := gitAuthorityQuestionFields(lines[start+1 : i])
231
+ authority := fields["schema"] == GitAuthoritySchemaV1 || fields["kind"] == GitAuthorityKind
232
+ if !authority {
233
+ continue
234
+ }
235
+ if duplicate != "" || unknown != "" || fields["schema"] != GitAuthoritySchemaV1 ||
236
+ fields["kind"] != GitAuthorityKind || !gitAuthorityQIDRE.MatchString(header) {
237
+ return nil, fmt.Errorf("invalid Git authority question")
238
+ }
239
+ q, err := validateGitAuthorityQuestion(header, fields)
240
+ if err != nil {
241
+ return nil, err
242
+ }
243
+ out = append(out, q)
244
+ }
245
+ return out, nil
246
+ }
247
+
248
+ func normalizeGitAuthorityResolution(data []byte, qid, status, answer string) (string, error) {
249
+ lines := splitLinesNoTrailing(data)
250
+ target := regexp.MustCompile(`^## ` + regexp.QuoteMeta(qid) + `([[:space:]]|$)`)
251
+ for i := 0; i < len(lines); i++ {
252
+ if !target.MatchString(lines[i]) {
253
+ continue
254
+ }
255
+ start := i + 1
256
+ i++
257
+ for i < len(lines) && !qHeaderRe.MatchString(lines[i]) {
258
+ i++
259
+ }
260
+ fields, duplicate, unknown := gitAuthorityQuestionFields(lines[start:i])
261
+ authority := fields["schema"] == GitAuthoritySchemaV1 || fields["kind"] == GitAuthorityKind
262
+ if !authority {
263
+ return answer, nil
264
+ }
265
+ if duplicate != "" || unknown != "" || fields["schema"] != GitAuthoritySchemaV1 ||
266
+ fields["kind"] != GitAuthorityKind {
267
+ return "", fmt.Errorf("git authority question is invalid")
268
+ }
269
+ q, err := validateGitAuthorityQuestion(qid, fields)
270
+ if err != nil {
271
+ return "", fmt.Errorf("git authority question is invalid")
272
+ }
273
+ if q.Status != "open" {
274
+ return answer, nil
275
+ }
276
+ switch status {
277
+ case "answered":
278
+ if strings.TrimSpace(answer) != GitAuthorityAnswer {
279
+ return "", fmt.Errorf(`git authority answer must exactly equal %q`, GitAuthorityAnswer)
280
+ }
281
+ return GitAuthorityAnswer, nil
282
+ case "dropped":
283
+ return "refused", nil
284
+ }
285
+ return "", fmt.Errorf("unsupported Git authority resolution")
286
+ }
287
+ return answer, nil
288
+ }
289
+
290
+ func gitAuthorityQuestionFields(lines []string) (map[string]string, string, string) {
291
+ allowed := map[string]bool{
292
+ "status": true, "gate": true, "schema": true, "kind": true,
293
+ "operation_digest": true, "classifier_reason_ids": true,
294
+ "requested_at": true, "expires_at": true, "answer_contract": true,
295
+ "answered_at": true, "answer": true,
296
+ }
297
+ fields := make(map[string]string)
298
+ var duplicate, unknown string
299
+ for _, line := range lines {
300
+ line = strings.TrimSpace(line)
301
+ if line == "" {
302
+ continue
303
+ }
304
+ key, value, ok := strings.Cut(line, ":")
305
+ key = strings.TrimSpace(key)
306
+ if !ok || !allowed[key] {
307
+ if unknown == "" {
308
+ unknown = line
309
+ }
310
+ continue
311
+ }
312
+ if _, exists := fields[key]; exists {
313
+ if duplicate == "" {
314
+ duplicate = key
315
+ }
316
+ continue
317
+ }
318
+ fields[key] = strings.TrimSpace(value)
319
+ }
320
+ return fields, duplicate, unknown
321
+ }
322
+
323
+ func validateGitAuthorityQuestion(qid string, fields map[string]string) (gitAuthorityQuestion, error) {
324
+ q := gitAuthorityQuestion{
325
+ QID: qid,
326
+ Status: fields["status"],
327
+ Digest: fields["operation_digest"],
328
+ Answer: fields["answer"],
329
+ }
330
+ if fields["gate"] != "escalating" || fields["answer_contract"] != GitAuthorityAnswer ||
331
+ !gitAuthorityDigestRE.MatchString(q.Digest) {
332
+ return q, fmt.Errorf("invalid Git authority contract")
333
+ }
334
+ reasons, err := parseGitAuthorityReasonList(fields["classifier_reason_ids"])
335
+ if err != nil {
336
+ return q, err
337
+ }
338
+ q.ClassifierReasonIDs = reasons
339
+ q.RequestedAt, err = time.Parse(time.RFC3339, fields["requested_at"])
340
+ if err != nil {
341
+ return q, fmt.Errorf("invalid Git authority request time")
342
+ }
343
+ q.ExpiresAt, err = time.Parse(time.RFC3339, fields["expires_at"])
344
+ if err != nil || !q.ExpiresAt.Equal(q.RequestedAt.Add(GitAuthorityTTL)) {
345
+ return q, fmt.Errorf("invalid Git authority expiry contract")
346
+ }
347
+ switch q.Status {
348
+ case "open":
349
+ if fields["answered_at"] != "" || fields["answer"] != "" {
350
+ return q, fmt.Errorf("open Git authority contains an answer")
351
+ }
352
+ case "answered", "dropped":
353
+ q.AnsweredAt, err = time.Parse(time.RFC3339, fields["answered_at"])
354
+ if err != nil || fields["answer"] == "" {
355
+ return q, fmt.Errorf("terminal Git authority is incomplete")
356
+ }
357
+ default:
358
+ return q, fmt.Errorf("invalid Git authority status")
359
+ }
360
+ return q, nil
361
+ }
362
+
363
+ func parseGitAuthorityReasonList(value string) ([]string, error) {
364
+ if value == "" {
365
+ return nil, fmt.Errorf("missing classifier reasons")
366
+ }
367
+ parts := strings.Split(value, ",")
368
+ if len(parts) > maxGitAuthorityReasons {
369
+ return nil, fmt.Errorf("too many classifier reasons")
370
+ }
371
+ for i, part := range parts {
372
+ if strings.TrimSpace(part) != part || part == "" {
373
+ return nil, fmt.Errorf("non-canonical classifier reasons")
374
+ }
375
+ parsed, err := reason.Parse(part)
376
+ if err != nil || !strings.HasPrefix(string(parsed), "DRV-GIT-DESTRUCTIVE-") {
377
+ return nil, fmt.Errorf("unknown classifier reason")
378
+ }
379
+ if i > 0 && parts[i-1] >= part {
380
+ return nil, fmt.Errorf("classifier reasons must be unique and sorted")
381
+ }
382
+ }
383
+ return parts, nil
384
+ }
385
+
386
+ func renderGitAuthorityQuestion(existing []byte, qid, digest string, reasons []string, now time.Time) []byte {
387
+ record := []string{
388
+ "## " + qid,
389
+ "status: open",
390
+ "gate: escalating",
391
+ "schema: " + GitAuthoritySchemaV1,
392
+ "kind: " + GitAuthorityKind,
393
+ "operation_digest: " + digest,
394
+ "classifier_reason_ids: " + strings.Join(reasons, ","),
395
+ "requested_at: " + formatGitAuthorityTime(now),
396
+ "expires_at: " + formatGitAuthorityTime(now.Add(GitAuthorityTTL)),
397
+ "answer_contract: " + GitAuthorityAnswer,
398
+ }
399
+ lines := splitLinesNoTrailing(existing)
400
+ hasQuestions := false
401
+ for _, line := range lines {
402
+ if qHeaderRe.MatchString(line) {
403
+ hasQuestions = true
404
+ break
405
+ }
406
+ }
407
+ if !hasQuestions {
408
+ filtered := lines[:0]
409
+ for _, line := range lines {
410
+ if strings.EqualFold(strings.TrimSpace(line), "None.") {
411
+ continue
412
+ }
413
+ filtered = append(filtered, line)
414
+ }
415
+ lines = filtered
416
+ }
417
+ lines = trimTrailingBlankLines(lines)
418
+ if len(lines) > 0 {
419
+ lines = append(lines, "")
420
+ }
421
+ lines = append(lines, record...)
422
+ return joinRecords(lines)
423
+ }
424
+
425
+ func nextQuestionID(data []byte, now time.Time) (string, error) {
426
+ today := now.Format("2006-01-02")
427
+ countRE := regexp.MustCompile(`(?m)^## q-` + regexp.QuoteMeta(today) + `-`)
428
+ used := len(countRE.FindAllIndex(data, -1))
429
+ qid := fmt.Sprintf("q-%s-%03d", today, used+1)
430
+ collisionRE := regexp.MustCompile(`(?m)^## ` + regexp.QuoteMeta(qid) + `([[:space:]]|$)`)
431
+ if collisionRE.Match(data) {
432
+ return qid, fmt.Errorf("question id collision")
433
+ }
434
+ return qid, nil
435
+ }
436
+
437
+ func ensureGitAuthorityAwaiting(sfile, qid string) error {
438
+ data, err := readBoundedRegularFile(sfile, maxGitAuthorityFileBytes)
439
+ if err != nil {
440
+ return err
441
+ }
442
+ lines := splitLinesNoTrailing(data)
443
+ if current := awaitingQuestionID(lines); current != "" && current != qid {
444
+ return fmt.Errorf("another question is already awaiting")
445
+ }
446
+ if awaitingQuestionID(lines) == "" {
447
+ block := []string{"## Awaiting human", "- qid: " + qid, ""}
448
+ insert := len(lines)
449
+ for i, line := range lines {
450
+ if strings.EqualFold(strings.TrimSpace(line), "## Log") {
451
+ insert = i
452
+ break
453
+ }
454
+ }
455
+ prefix := append([]string(nil), lines[:insert]...)
456
+ if len(prefix) > 0 && strings.TrimSpace(prefix[len(prefix)-1]) != "" {
457
+ prefix = append(prefix, "")
458
+ }
459
+ lines = append(prefix, append(block, lines[insert:]...)...)
460
+ }
461
+ lines = state.UpsertCursorField(lines, state.CursorStatus, "awaiting_human")
462
+ lines = state.UpsertCursorField(lines, state.CursorNextAction, `/rite-resolve `+qid+` "`+GitAuthorityAnswer+`"`)
463
+ return state.AtomicWrite(sfile, joinRecords(trimTrailingBlankLines(lines)), 0o644)
464
+ }
465
+
466
+ func awaitingQuestionID(lines []string) string {
467
+ inAwaiting := false
468
+ for _, line := range lines {
469
+ trimmed := strings.TrimSpace(line)
470
+ if strings.HasPrefix(trimmed, "## ") {
471
+ if inAwaiting {
472
+ break
473
+ }
474
+ inAwaiting = strings.EqualFold(strings.TrimSpace(strings.TrimPrefix(trimmed, "## ")), "Awaiting human")
475
+ continue
476
+ }
477
+ if inAwaiting {
478
+ if qid, ok := state.CursorField([]string{line}, state.CursorQuestionID); ok {
479
+ return qid
480
+ }
481
+ }
482
+ }
483
+ return ""
484
+ }
485
+
486
+ func readGitAuthorityLedger(path string) ([]gitAuthorityConsumption, []byte, error) {
487
+ info, err := os.Lstat(path)
488
+ if os.IsNotExist(err) {
489
+ return nil, nil, nil
490
+ }
491
+ if err != nil || !info.Mode().IsRegular() || !gitAuthorityLedgerModeOK(info.Mode()) {
492
+ return nil, nil, fmt.Errorf("invalid Git authority ledger")
493
+ }
494
+ if info.Size() > maxGitAuthorityLedgerBytes {
495
+ return nil, nil, fmt.Errorf("git authority ledger exceeds bound")
496
+ }
497
+ data, err := os.ReadFile(path)
498
+ if err != nil {
499
+ return nil, nil, err
500
+ }
501
+ var out []gitAuthorityConsumption
502
+ for _, line := range bytes.Split(data, []byte("\n")) {
503
+ if len(line) == 0 {
504
+ continue
505
+ }
506
+ var entry gitAuthorityConsumption
507
+ decoder := json.NewDecoder(bytes.NewReader(line))
508
+ decoder.DisallowUnknownFields()
509
+ if err := decoder.Decode(&entry); err != nil {
510
+ return nil, nil, fmt.Errorf("invalid Git authority ledger entry")
511
+ }
512
+ var extra any
513
+ if decoder.Decode(&extra) != io.EOF {
514
+ return nil, nil, fmt.Errorf("invalid Git authority ledger trailing data")
515
+ }
516
+ if err := validateGitAuthorityConsumption(entry); err != nil {
517
+ return nil, nil, err
518
+ }
519
+ out = append(out, entry)
520
+ }
521
+ return out, data, nil
522
+ }
523
+
524
+ func gitAuthorityLedgerModeOK(mode os.FileMode) bool {
525
+ // Windows exposes inherited ACLs rather than POSIX group/other mode bits;
526
+ // Chmod(0600) is still requested at every atomic write.
527
+ return runtime.GOOS == "windows" || mode.Perm() == 0o600
528
+ }
529
+
530
+ func validateGitAuthorityConsumption(entry gitAuthorityConsumption) error {
531
+ if entry.Schema != GitAuthorityConsumptionSchemaV1 || entry.Kind != GitAuthorityKind ||
532
+ !gitAuthorityQIDRE.MatchString(entry.QuestionID) ||
533
+ !gitAuthorityDigestRE.MatchString(entry.OperationDigest) {
534
+ return fmt.Errorf("invalid Git authority ledger contract")
535
+ }
536
+ if _, err := parseGitAuthorityReasonList(strings.Join(entry.ClassifierReasonIDs, ",")); err != nil {
537
+ return err
538
+ }
539
+ if _, err := time.Parse(time.RFC3339, entry.ConsumedAt); err != nil {
540
+ return fmt.Errorf("invalid Git authority consumption time")
541
+ }
542
+ return nil
543
+ }
544
+
545
+ func appendGitAuthorityConsumption(path string, current []byte, entry gitAuthorityConsumption) error {
546
+ line, err := json.Marshal(entry)
547
+ if err != nil {
548
+ return err
549
+ }
550
+ out := append([]byte(nil), bytes.TrimRight(current, "\n")...)
551
+ if len(out) > 0 {
552
+ out = append(out, '\n')
553
+ }
554
+ out = append(out, line...)
555
+ out = append(out, '\n')
556
+ if len(out) > maxGitAuthorityLedgerBytes {
557
+ return fmt.Errorf("git authority ledger exceeds bound")
558
+ }
559
+ return state.AtomicWrite(path, out, 0o600)
560
+ }
561
+
562
+ func readBoundedRegularFile(path string, limit int64) ([]byte, error) {
563
+ info, err := os.Lstat(path)
564
+ if err != nil || !info.Mode().IsRegular() || info.Size() > limit {
565
+ return nil, fmt.Errorf("invalid bounded regular file")
566
+ }
567
+ return os.ReadFile(path)
568
+ }
569
+
570
+ func regularFileNoSymlink(path string) bool {
571
+ info, err := os.Lstat(path)
572
+ return err == nil && info.Mode().IsRegular()
573
+ }
574
+
575
+ func regularDirectory(path string) bool {
576
+ info, err := os.Lstat(path)
577
+ return err == nil && info.IsDir()
578
+ }
579
+
580
+ func formatGitAuthorityTime(value time.Time) string {
581
+ return value.UTC().Format(time.RFC3339)
582
+ }
583
+
584
+ func equalStrings(a, b []string) bool {
585
+ if len(a) != len(b) {
586
+ return false
587
+ }
588
+ for i := range a {
589
+ if a[i] != b[i] {
590
+ return false
591
+ }
592
+ }
593
+ return true
594
+ }
595
+
596
+ func trimTrailingBlankLines(lines []string) []string {
597
+ for len(lines) > 0 && strings.TrimSpace(lines[len(lines)-1]) == "" {
598
+ lines = lines[:len(lines)-1]
599
+ }
600
+ return lines
601
+ }