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
@@ -6,6 +6,7 @@ import (
6
6
  "compress/gzip"
7
7
  "context"
8
8
  "crypto/sha256"
9
+ "encoding/hex"
9
10
  "encoding/json"
10
11
  "errors"
11
12
  "flag"
@@ -26,6 +27,7 @@ import (
26
27
  "github.com/devrites/devrites/internal/fsutil"
27
28
  "github.com/devrites/devrites/internal/hostpack"
28
29
  "github.com/devrites/devrites/internal/iohooks"
30
+ "github.com/devrites/devrites/internal/safepath"
29
31
  "github.com/devrites/devrites/internal/version"
30
32
  )
31
33
 
@@ -78,12 +80,22 @@ type runner struct {
78
80
  source string
79
81
  manifest []string
80
82
  records map[string]string
81
- prev map[string]bool
83
+ prev map[string]managedRecord
84
+ preflight map[string]pathSnapshot
82
85
  stats stats
83
86
  releaseBinaryTag string
84
87
  preparedBinary string
85
88
  }
86
89
 
90
+ type managedRecord struct {
91
+ Hash string
92
+ }
93
+
94
+ type pathSnapshot struct {
95
+ missing bool
96
+ hash string
97
+ }
98
+
87
99
  func DefaultOptions(mode Mode) Options {
88
100
  return Options{
89
101
  Mode: mode,
@@ -213,7 +225,7 @@ func flagWasSet(flags *flag.FlagSet, name string) bool {
213
225
  func usage(mode Mode) string {
214
226
  switch mode {
215
227
  case ModeUninstall:
216
- return "usage: devrites-engine uninstall [--target DIR] [--dry-run] [--keep-binary]\n"
228
+ return "usage: devrites-engine uninstall [--target DIR] [--dry-run] [--force] [--keep-binary]\n"
217
229
  case ModeUpdate:
218
230
  return "usage: devrites-engine update [--target DIR] [--dry-run] [--force] [--check] [--to TAG] [install flags]\n"
219
231
  default:
@@ -277,7 +289,16 @@ func newRunner(opts Options) (*runner, error) {
277
289
  if payload != "" {
278
290
  payload, _ = filepath.Abs(payload)
279
291
  }
280
- r := &runner{opts: opts, target: target, payload: payload, payloadFS: os.DirFS(payload), source: source, prev: map[string]bool{}, records: map[string]string{}}
292
+ r := &runner{
293
+ opts: opts,
294
+ target: target,
295
+ payload: payload,
296
+ payloadFS: os.DirFS(payload),
297
+ source: source,
298
+ prev: map[string]managedRecord{},
299
+ records: map[string]string{},
300
+ preflight: map[string]pathSnapshot{},
301
+ }
281
302
  if opts.Mode != ModeUninstall {
282
303
  if err := r.validatePayload(); err != nil {
283
304
  return nil, fmt.Errorf("validate payload: %w", err)
@@ -343,6 +364,14 @@ func (r *runner) install() error {
343
364
  fmt.Fprintln(r.opts.Stdout, " (dry run - no changes will be made)")
344
365
  }
345
366
  fmt.Fprintln(r.opts.Stdout)
367
+ if r.preparedBinary != "" {
368
+ if _, err := verifyEngineBinary(r.preparedBinary, r.binaryTag(), 30*time.Second); err != nil {
369
+ return fmt.Errorf("verify staged engine binary %s: %w", r.preparedBinary, err)
370
+ }
371
+ }
372
+ if err := r.preflightInstall(); err != nil {
373
+ return err
374
+ }
346
375
 
347
376
  for _, tree := range hostpack.InstallTrees(r.opts.WithSkills, r.opts.WithAgents, r.opts.WithCodex) {
348
377
  if err := r.installTree(tree.PayloadPrefix, tree.TargetPrefix); err != nil {
@@ -424,12 +453,233 @@ func (r *runner) installFile(src, rel string) error {
424
453
  return r.installData(data, rel)
425
454
  }
426
455
 
456
+ func (r *runner) preflightInstall() error {
457
+ desired, err := r.desiredInstallPaths()
458
+ if err != nil {
459
+ return fmt.Errorf("preflight install paths: %w", err)
460
+ }
461
+ var conflicts []string
462
+ for rel := range desired {
463
+ snapshot, err := r.rememberPath(rel)
464
+ if err != nil {
465
+ return err
466
+ }
467
+ record, managed := r.prev[rel]
468
+ if managed && !snapshot.missing {
469
+ if kind := managedConflict(record, snapshot); kind != "" && !r.opts.Force {
470
+ conflicts = append(conflicts, fmt.Sprintf("%s: %s", kind, rel))
471
+ }
472
+ }
473
+ }
474
+ for rel, record := range r.prev {
475
+ if desired[rel] || hostpack.PreserveOnPrune(rel) {
476
+ continue
477
+ }
478
+ snapshot, err := r.rememberPath(rel)
479
+ if err != nil {
480
+ return err
481
+ }
482
+ if !snapshot.missing {
483
+ if kind := managedConflict(record, snapshot); kind != "" && !r.opts.Force {
484
+ conflicts = append(conflicts, fmt.Sprintf("%s: %s", kind, rel))
485
+ }
486
+ }
487
+ if merge, ok := hostpack.ManagedMergeForMarker(rel); ok {
488
+ if _, err := r.rememberPath(merge.TargetRel); err != nil {
489
+ return err
490
+ }
491
+ }
492
+ }
493
+ for _, rel := range r.installMergeTargets() {
494
+ if _, err := r.rememberPath(rel); err != nil {
495
+ return err
496
+ }
497
+ }
498
+ if _, err := r.rememberPath(ManifestName); err != nil {
499
+ return err
500
+ }
501
+ return managedConflictError(conflicts)
502
+ }
503
+
504
+ func (r *runner) preflightUninstall(entries []string) error {
505
+ var conflicts []string
506
+ for _, rel := range entries {
507
+ if merge, ok := hostpack.ManagedMergeForMarker(rel); ok {
508
+ if _, err := r.rememberPath(merge.TargetRel); err != nil {
509
+ return err
510
+ }
511
+ }
512
+ if !hostpack.ShouldRemoveOnUninstall(rel, entries) {
513
+ continue
514
+ }
515
+ snapshot, err := r.rememberPath(rel)
516
+ if err != nil {
517
+ return err
518
+ }
519
+ if snapshot.missing {
520
+ continue
521
+ }
522
+ if kind := managedConflict(r.prev[rel], snapshot); kind != "" && !r.opts.Force {
523
+ conflicts = append(conflicts, fmt.Sprintf("%s: %s", kind, rel))
524
+ }
525
+ }
526
+ if _, err := r.rememberPath(ManifestName); err != nil {
527
+ return err
528
+ }
529
+ return managedConflictError(conflicts)
530
+ }
531
+
532
+ func (r *runner) desiredInstallPaths() (map[string]bool, error) {
533
+ out := map[string]bool{".devrites/README.md": true}
534
+ for _, tree := range hostpack.InstallTrees(r.opts.WithSkills, r.opts.WithAgents, r.opts.WithCodex) {
535
+ err := fs.WalkDir(r.payloadFS, tree.PayloadPrefix, func(path string, d fs.DirEntry, err error) error {
536
+ if err != nil {
537
+ return err
538
+ }
539
+ if d.IsDir() {
540
+ return nil
541
+ }
542
+ rel, err := filepath.Rel(tree.PayloadPrefix, path)
543
+ if err != nil {
544
+ return err
545
+ }
546
+ out[filepath.ToSlash(filepath.Join(tree.TargetPrefix, rel))] = true
547
+ return nil
548
+ })
549
+ if err != nil {
550
+ return nil, err
551
+ }
552
+ }
553
+ if r.opts.WithSkills && r.opts.AliasMode == "all" {
554
+ for _, alias := range hostpack.Aliases {
555
+ for _, rel := range hostpack.AliasTargets(alias, r.opts.WithCodex) {
556
+ out[rel] = true
557
+ }
558
+ }
559
+ }
560
+ if r.opts.WithSkills && r.opts.WithCodex {
561
+ out[hostpack.CodexAgentsMerge.MarkerRel] = true
562
+ out[hostpack.CodexHooksMerge.MarkerRel] = true
563
+ }
564
+ if r.opts.WithSkills {
565
+ out[hostpack.ClaudeSettingsMerge.MarkerRel] = true
566
+ }
567
+ return out, nil
568
+ }
569
+
570
+ func (r *runner) installMergeTargets() []string {
571
+ var out []string
572
+ if r.opts.WithSkills {
573
+ out = append(out, hostpack.ClaudeSettingsMerge.TargetRel)
574
+ if r.opts.WithCodex {
575
+ out = append(out, hostpack.CodexAgentsMerge.TargetRel, hostpack.CodexHooksMerge.TargetRel)
576
+ }
577
+ }
578
+ return out
579
+ }
580
+
581
+ func (r *runner) rememberPath(rel string) (pathSnapshot, error) {
582
+ rel = filepath.ToSlash(rel)
583
+ if snapshot, ok := r.preflight[rel]; ok {
584
+ return snapshot, nil
585
+ }
586
+ snapshot, err := inspectManagedPath(r.target, rel)
587
+ if err != nil {
588
+ return pathSnapshot{}, err
589
+ }
590
+ r.preflight[rel] = snapshot
591
+ return snapshot, nil
592
+ }
593
+
594
+ func (r *runner) recheckPath(rel string) error {
595
+ rel = filepath.ToSlash(rel)
596
+ before, ok := r.preflight[rel]
597
+ if !ok {
598
+ return fmt.Errorf("path %s was not preflighted", rel)
599
+ }
600
+ now, err := inspectManagedPath(r.target, rel)
601
+ if err != nil {
602
+ return err
603
+ }
604
+ if now != before {
605
+ return fmt.Errorf("refusing to change %s: file changed after preflight; retry the operation", rel)
606
+ }
607
+ return nil
608
+ }
609
+
610
+ func inspectManagedPath(target, rel string) (pathSnapshot, error) {
611
+ native := filepath.FromSlash(rel)
612
+ if !filepath.IsLocal(native) {
613
+ return pathSnapshot{}, fmt.Errorf("refusing unsafe managed path %q", rel)
614
+ }
615
+ dest := filepath.Join(target, native)
616
+ if !safepath.WithinResolved(dest, target) {
617
+ return pathSnapshot{}, fmt.Errorf("refusing managed path outside target: %s", rel)
618
+ }
619
+ walk := target
620
+ parts := strings.Split(native, string(filepath.Separator))
621
+ for _, part := range parts {
622
+ walk = filepath.Join(walk, part)
623
+ info, err := os.Lstat(walk)
624
+ if os.IsNotExist(err) {
625
+ return pathSnapshot{missing: true}, nil
626
+ }
627
+ if err != nil {
628
+ return pathSnapshot{}, fmt.Errorf("inspect managed path %s: %w", rel, err)
629
+ }
630
+ if info.Mode()&os.ModeSymlink != 0 {
631
+ return pathSnapshot{}, fmt.Errorf("refusing symlink or junction in managed path: %s", rel)
632
+ }
633
+ }
634
+ info, err := os.Lstat(dest)
635
+ if os.IsNotExist(err) {
636
+ return pathSnapshot{missing: true}, nil
637
+ }
638
+ if err != nil {
639
+ return pathSnapshot{}, fmt.Errorf("inspect managed path %s: %w", rel, err)
640
+ }
641
+ if !info.Mode().IsRegular() {
642
+ return pathSnapshot{}, fmt.Errorf("refusing non-regular managed path: %s", rel)
643
+ }
644
+ f, err := os.Open(dest)
645
+ if err != nil {
646
+ return pathSnapshot{}, fmt.Errorf("read managed path %s: %w", rel, err)
647
+ }
648
+ defer f.Close()
649
+ sum := sha256.New()
650
+ if _, err := io.Copy(sum, f); err != nil {
651
+ return pathSnapshot{}, fmt.Errorf("hash managed path %s: %w", rel, err)
652
+ }
653
+ return pathSnapshot{hash: "sha256:" + hex.EncodeToString(sum.Sum(nil))}, nil
654
+ }
655
+
656
+ func managedConflict(record managedRecord, snapshot pathSnapshot) string {
657
+ if record.Hash == "" {
658
+ return "legacy manifest entry (no hash)"
659
+ }
660
+ if record.Hash != snapshot.hash {
661
+ return "customized managed file"
662
+ }
663
+ return ""
664
+ }
665
+
666
+ func managedConflictError(conflicts []string) error {
667
+ if len(conflicts) == 0 {
668
+ return nil
669
+ }
670
+ sort.Strings(conflicts)
671
+ return fmt.Errorf("managed files differ from the install manifest:\n %s\nrerun with --force to replace or remove these files", strings.Join(conflicts, "\n "))
672
+ }
673
+
427
674
  func (r *runner) installData(data []byte, rel string) error {
428
675
  dest := filepath.Join(r.target, filepath.FromSlash(rel))
429
676
  action := "install"
430
677
  if exists(dest) {
678
+ record, managed := r.prev[rel]
431
679
  switch {
432
- case r.prev[rel]:
680
+ case managed && managedConflict(record, r.preflight[filepath.ToSlash(rel)]) != "" && r.opts.Force:
681
+ action = "overwrite(force-customized)"
682
+ case managed:
433
683
  action = "overwrite"
434
684
  case r.opts.Force:
435
685
  action = "overwrite(force)"
@@ -445,8 +695,13 @@ func (r *runner) installData(data []byte, rel string) error {
445
695
  }
446
696
  if r.opts.DryRun {
447
697
  fmt.Fprintf(r.opts.Stdout, " [%s] %s\n", action, rel)
448
- } else if err := fsutil.WriteFileAtomic(dest, data, 0o644); err != nil {
449
- return fmt.Errorf("cannot write %s: %w", rel, err)
698
+ } else {
699
+ if err := r.recheckPath(rel); err != nil {
700
+ return err
701
+ }
702
+ if err := fsutil.WriteFileAtomic(dest, data, 0o644); err != nil {
703
+ return fmt.Errorf("cannot write %s: %w", rel, err)
704
+ }
450
705
  }
451
706
  r.addManifest(rel)
452
707
  r.addInstallRecord(rel, data)
@@ -492,6 +747,9 @@ func (r *runner) mergeMarkerFile(merge hostpack.MarkerMerge) error {
492
747
  }
493
748
  fmt.Fprintf(r.opts.Stdout, " [merge] %s (%s)\n", merge.TargetRel, verb)
494
749
  } else {
750
+ if err := r.recheckPath(merge.TargetRel); err != nil {
751
+ return err
752
+ }
495
753
  next := block
496
754
  if current, err := os.ReadFile(dest); err == nil {
497
755
  next = hostpack.MergeMarkerBlock(current, block, merge.Begin, merge.End)
@@ -513,6 +771,9 @@ func (r *runner) mergeJSONHooks(merge hostpack.JSONMerge) error {
513
771
  fmt.Fprintf(r.opts.Stdout, " [merge] %s\n", merge.DryRunText)
514
772
  return r.installMarker(merge.MarkerRel, merge.MarkerText)
515
773
  }
774
+ if err := r.recheckPath(merge.TargetRel); err != nil {
775
+ return err
776
+ }
516
777
  next := devrites
517
778
  if current, err := readJSON(dest); err == nil {
518
779
  if currentStatus, exists := current["statusLine"]; exists && !isDevritesHookCommand(currentStatus) {
@@ -546,7 +807,7 @@ func (r *runner) seedClaudeSettings() error {
546
807
  if err != nil {
547
808
  return fmt.Errorf("load Claude settings payload: %w", err)
548
809
  }
549
- managed := r.prev[merge.MarkerRel]
810
+ _, managed := r.prev[merge.MarkerRel]
550
811
  if reflect.DeepEqual(current, devrites) {
551
812
  if managed {
552
813
  return r.installMarker(merge.MarkerRel, merge.MarkerText)
@@ -562,6 +823,9 @@ func (r *runner) seedClaudeSettings() error {
562
823
  if err != nil {
563
824
  return fmt.Errorf("read payload claude/settings.json: %w", err)
564
825
  }
826
+ if err := r.recheckPath(rel); err != nil {
827
+ return err
828
+ }
565
829
  return fsutil.WriteFileAtomic(dest, data, 0o644)
566
830
  }
567
831
  return r.mergeJSONHooks(merge)
@@ -574,6 +838,9 @@ func (r *runner) seedClaudeSettings() error {
574
838
  if err != nil {
575
839
  return fmt.Errorf("read payload claude/settings.json: %w", err)
576
840
  }
841
+ if err := r.recheckPath(rel); err != nil {
842
+ return err
843
+ }
577
844
  return fsutil.WriteFileAtomic(dest, data, 0o644)
578
845
  }
579
846
 
@@ -627,10 +894,20 @@ func (r *runner) pruneDropped() error {
627
894
  if !exists(dead) {
628
895
  continue
629
896
  }
897
+ action := "prune"
898
+ if managedConflict(r.prev[rel], r.preflight[rel]) != "" && r.opts.Force {
899
+ action = "prune(force-customized)"
900
+ }
630
901
  if r.opts.DryRun {
631
- fmt.Fprintf(r.opts.Stdout, " [prune] %s (dropped from pack)\n", rel)
902
+ fmt.Fprintf(r.opts.Stdout, " [%s] %s (dropped from pack)\n", action, rel)
632
903
  } else {
904
+ if err := r.recheckPath(rel); err != nil {
905
+ return err
906
+ }
633
907
  if merge, ok := hostpack.ManagedMergeForMarker(rel); ok {
908
+ if err := r.recheckPath(merge.TargetRel); err != nil {
909
+ return err
910
+ }
634
911
  if merge.TargetRel == hostpack.CodexHooksMerge.TargetRel || merge.TargetRel == hostpack.ClaudeSettingsMerge.TargetRel {
635
912
  preserveEmpty := merge.TargetRel == hostpack.ClaudeSettingsMerge.TargetRel
636
913
  _ = r.stripHooksPath(filepath.Join(r.target, filepath.FromSlash(merge.TargetRel)), preserveEmpty)
@@ -642,7 +919,7 @@ func (r *runner) pruneDropped() error {
642
919
  return fmt.Errorf("remove %s: %w", rel, err)
643
920
  }
644
921
  pruneEmptyDirs(filepath.Dir(dead), r.target)
645
- fmt.Fprintf(r.opts.Stdout, " [prune] %s\n", rel)
922
+ fmt.Fprintf(r.opts.Stdout, " [%s] %s\n", action, rel)
646
923
  }
647
924
  r.stats.pruned++
648
925
  }
@@ -666,6 +943,9 @@ func (r *runner) writeManifest() error {
666
943
  for _, rel := range r.manifest {
667
944
  b.WriteString(rel + "\n")
668
945
  }
946
+ if err := r.recheckPath(ManifestName); err != nil {
947
+ return err
948
+ }
669
949
  return fsutil.WriteFileAtomic(filepath.Join(r.target, ManifestName), []byte(b.String()), 0o644)
670
950
  }
671
951
 
@@ -705,6 +985,9 @@ func (r *runner) uninstall() error {
705
985
  fmt.Fprintln(r.opts.Stdout, " (dry run - no changes)")
706
986
  }
707
987
  fmt.Fprintln(r.opts.Stdout)
988
+ if err := r.preflightUninstall(entries); err != nil {
989
+ return err
990
+ }
708
991
 
709
992
  for _, rel := range entries {
710
993
  merge, ok := hostpack.ManagedMergeForMarker(rel)
@@ -715,6 +998,12 @@ func (r *runner) uninstall() error {
715
998
  fmt.Fprintf(r.opts.Stdout, " [merge-remove] %s\n", merge.DryRun)
716
999
  continue
717
1000
  }
1001
+ if err := r.recheckPath(rel); err != nil {
1002
+ return err
1003
+ }
1004
+ if err := r.recheckPath(merge.TargetRel); err != nil {
1005
+ return err
1006
+ }
718
1007
  if merge.TargetRel == hostpack.CodexHooksMerge.TargetRel || merge.TargetRel == hostpack.ClaudeSettingsMerge.TargetRel {
719
1008
  preserveEmpty := merge.TargetRel == hostpack.ClaudeSettingsMerge.TargetRel
720
1009
  if err := r.stripHooksPath(filepath.Join(r.target, filepath.FromSlash(merge.TargetRel)), preserveEmpty); err != nil {
@@ -734,10 +1023,19 @@ func (r *runner) uninstall() error {
734
1023
  }
735
1024
  dest := filepath.Join(r.target, filepath.FromSlash(rel))
736
1025
  if exists(dest) {
1026
+ action := "remove"
1027
+ if managedConflict(r.prev[rel], r.preflight[rel]) != "" && r.opts.Force {
1028
+ action = "remove(force-customized)"
1029
+ }
737
1030
  if r.opts.DryRun {
738
- fmt.Fprintf(r.opts.Stdout, " [remove] %s\n", rel)
739
- } else if err := os.Remove(dest); err != nil && !os.IsNotExist(err) {
740
- return fmt.Errorf("remove %s: %w", rel, err)
1031
+ fmt.Fprintf(r.opts.Stdout, " [%s] %s\n", action, rel)
1032
+ } else {
1033
+ if err := r.recheckPath(rel); err != nil {
1034
+ return err
1035
+ }
1036
+ if err := os.Remove(dest); err != nil && !os.IsNotExist(err) {
1037
+ return fmt.Errorf("remove %s: %w", rel, err)
1038
+ }
741
1039
  }
742
1040
  dirs = append(dirs, filepath.Dir(dest))
743
1041
  r.stats.removed++
@@ -748,6 +1046,9 @@ func (r *runner) uninstall() error {
748
1046
  if r.opts.DryRun {
749
1047
  fmt.Fprintf(r.opts.Stdout, " [remove] %s\n", ManifestName)
750
1048
  } else {
1049
+ if err := r.recheckPath(ManifestName); err != nil {
1050
+ return err
1051
+ }
751
1052
  _ = os.Remove(mf)
752
1053
  dirs = append(dirs, filepath.Dir(mf))
753
1054
  for _, d := range dirs {
@@ -849,7 +1150,7 @@ func runUpdate(opts Options) error {
849
1150
  next.Target = target
850
1151
  next.PayloadDir = payload
851
1152
  next.SourceDir = source
852
- next.Force = true
1153
+ next.Force = opts.Force
853
1154
  if opts.DryRun {
854
1155
  next.DryRun = true
855
1156
  }
@@ -1302,10 +1603,7 @@ func (r *runner) installBinary() error {
1302
1603
  fmt.Fprintln(r.opts.Stdout, " would install the global devrites-engine control-plane binary")
1303
1604
  return nil
1304
1605
  }
1305
- tag := os.Getenv("DEVRITES_REF")
1306
- if tag == "" {
1307
- tag = "v" + strings.TrimPrefix(installedVersion(r.source), "v")
1308
- }
1606
+ tag := r.binaryTag()
1309
1607
  incoming := strings.TrimPrefix(tag, "v")
1310
1608
  dest := binaryDest()
1311
1609
  if exists(dest) {
@@ -1323,17 +1621,39 @@ func (r *runner) installBinary() error {
1323
1621
  }
1324
1622
  }
1325
1623
  defer cleanup()
1624
+ if _, err := verifyEngineBinary(staged, tag, 30*time.Second); err != nil {
1625
+ return fmt.Errorf("verify staged engine binary %s: %w", staged, err)
1626
+ }
1326
1627
  if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
1327
- return r.binaryInstallFailure(err)
1628
+ return err
1328
1629
  }
1329
1630
  data, err := os.ReadFile(staged)
1330
1631
  if err != nil {
1331
- return r.binaryInstallFailure(err)
1632
+ return err
1633
+ }
1634
+ backup, oldMode, hadOld, err := backupBinary(dest)
1635
+ if err != nil {
1636
+ return err
1637
+ }
1638
+ if backup != "" {
1639
+ defer os.Remove(backup)
1332
1640
  }
1333
1641
  if err := fsutil.WriteFileAtomic(dest, data, 0o755); err != nil {
1334
- return r.binaryInstallFailure(err)
1642
+ if restoreErr := restoreBinary(dest, backup, oldMode, hadOld); restoreErr != nil {
1643
+ return fmt.Errorf("install binary: %v; restore previous binary: %w", err, restoreErr)
1644
+ }
1645
+ return err
1335
1646
  }
1336
1647
  _ = os.Chmod(dest, 0o755)
1648
+ if _, err := verifyEngineBinary(dest, tag, 30*time.Second); err != nil {
1649
+ if restoreErr := restoreBinary(dest, backup, oldMode, hadOld); restoreErr != nil {
1650
+ return fmt.Errorf("verify installed engine binary: %v; restore previous binary: %w", err, restoreErr)
1651
+ }
1652
+ if hadOld {
1653
+ return fmt.Errorf("verify installed engine binary: %w (previous binary restored)", err)
1654
+ }
1655
+ return fmt.Errorf("verify installed engine binary: %w (bad binary removed)", err)
1656
+ }
1337
1657
  fmt.Fprintf(r.opts.Stdout, " engine binary: installed %s\n", dest)
1338
1658
  if !binaryReachableFromPATH(dest) {
1339
1659
  fmt.Fprintf(r.opts.Stderr, "warning: engine binary: %s is not on PATH; installed hooks will fail open until devrites-engine is reachable.\n", dest)
@@ -1341,6 +1661,61 @@ func (r *runner) installBinary() error {
1341
1661
  return nil
1342
1662
  }
1343
1663
 
1664
+ func (r *runner) binaryTag() string {
1665
+ if r.releaseBinaryTag != "" {
1666
+ return r.releaseBinaryTag
1667
+ }
1668
+ if tag := os.Getenv("DEVRITES_REF"); tag != "" {
1669
+ return tag
1670
+ }
1671
+ return "v" + strings.TrimPrefix(installedVersion(r.source), "v")
1672
+ }
1673
+
1674
+ func backupBinary(dest string) (string, fs.FileMode, bool, error) {
1675
+ info, err := os.Lstat(dest)
1676
+ if os.IsNotExist(err) {
1677
+ return "", 0, false, nil
1678
+ }
1679
+ if err != nil {
1680
+ return "", 0, false, fmt.Errorf("inspect existing engine binary: %w", err)
1681
+ }
1682
+ if !info.Mode().IsRegular() {
1683
+ return "", 0, false, fmt.Errorf("refusing to replace non-regular engine binary: %s", dest)
1684
+ }
1685
+ data, err := os.ReadFile(dest)
1686
+ if err != nil {
1687
+ return "", 0, false, fmt.Errorf("read existing engine binary: %w", err)
1688
+ }
1689
+ f, err := os.CreateTemp(filepath.Dir(dest), "."+filepath.Base(dest)+".backup-*")
1690
+ if err != nil {
1691
+ return "", 0, false, fmt.Errorf("create engine binary backup: %w", err)
1692
+ }
1693
+ backup := f.Name()
1694
+ if err := f.Close(); err != nil {
1695
+ _ = os.Remove(backup)
1696
+ return "", 0, false, fmt.Errorf("create engine binary backup: %w", err)
1697
+ }
1698
+ if err := fsutil.WriteFileAtomic(backup, data, info.Mode().Perm()); err != nil {
1699
+ _ = os.Remove(backup)
1700
+ return "", 0, false, fmt.Errorf("write engine binary backup: %w", err)
1701
+ }
1702
+ return backup, info.Mode().Perm(), true, nil
1703
+ }
1704
+
1705
+ func restoreBinary(dest, backup string, mode fs.FileMode, hadOld bool) error {
1706
+ if !hadOld {
1707
+ if err := os.Remove(dest); err != nil && !os.IsNotExist(err) {
1708
+ return err
1709
+ }
1710
+ return nil
1711
+ }
1712
+ data, err := os.ReadFile(backup)
1713
+ if err != nil {
1714
+ return err
1715
+ }
1716
+ return fsutil.WriteFileAtomic(dest, data, mode)
1717
+ }
1718
+
1344
1719
  func (r *runner) binaryInstallFailure(err error) error {
1345
1720
  if r.preparedBinary != "" {
1346
1721
  return err
@@ -1364,7 +1739,7 @@ func (r *runner) acquireBinary(tag, baseURL string) (string, func(), error) {
1364
1739
  return "", func() {}, fmt.Errorf("create temp dir: %w", err)
1365
1740
  }
1366
1741
  cleanup := func() { _ = os.RemoveAll(tmp) }
1367
- staged := filepath.Join(tmp, "devrites-engine")
1742
+ staged := filepath.Join(tmp, engineBinaryName())
1368
1743
  var releaseErr error
1369
1744
  if r.releaseBinaryTag != "" && os.Getenv("DEVRITES_UPDATE_BUNDLE") == "" {
1370
1745
  repo := os.Getenv("DEVRITES_REPO")
@@ -1416,6 +1791,9 @@ func downloadReleaseBinary(baseURL, repo, tag, dest string) (bool, error) {
1416
1791
  if err := verifySHA256(dest, sumPath); err != nil {
1417
1792
  return true, fmt.Errorf("could not verify release binary %s: %w", asset, err)
1418
1793
  }
1794
+ if err := os.Chmod(dest, 0o755); err != nil && runtime.GOOS != "windows" {
1795
+ return true, fmt.Errorf("make release binary executable: %w", err)
1796
+ }
1419
1797
  return true, nil
1420
1798
  }
1421
1799
 
@@ -1442,24 +1820,31 @@ func (r *runner) removeBinary() error {
1442
1820
 
1443
1821
  func binaryDest() string {
1444
1822
  if dir := os.Getenv("DEVRITES_BIN_DIR"); dir != "" {
1445
- return filepath.Join(dir, "devrites-engine")
1823
+ return filepath.Join(dir, engineBinaryName())
1446
1824
  }
1447
1825
  if home, err := os.UserHomeDir(); err == nil {
1448
- return filepath.Join(home, ".local", "bin", "devrites-engine")
1826
+ return filepath.Join(home, ".local", "bin", engineBinaryName())
1449
1827
  }
1450
- return "/usr/local/bin/devrites-engine"
1828
+ return filepath.Join("/usr/local/bin", engineBinaryName())
1829
+ }
1830
+
1831
+ func engineBinaryName() string {
1832
+ if runtime.GOOS == "windows" {
1833
+ return "devrites-engine.exe"
1834
+ }
1835
+ return "devrites-engine"
1451
1836
  }
1452
1837
 
1453
1838
  func binaryCandidates() []string {
1454
1839
  candidates := []string{}
1455
1840
  if dir := os.Getenv("DEVRITES_BIN_DIR"); dir != "" {
1456
- candidates = append(candidates, filepath.Join(dir, "devrites-engine"))
1841
+ candidates = append(candidates, filepath.Join(dir, engineBinaryName()))
1457
1842
  }
1458
1843
  home, _ := os.UserHomeDir()
1459
1844
  if home != "" {
1460
- candidates = append(candidates, filepath.Join(home, ".local", "bin", "devrites-engine"))
1845
+ candidates = append(candidates, filepath.Join(home, ".local", "bin", engineBinaryName()))
1461
1846
  }
1462
- return append(candidates, "/usr/local/bin/devrites-engine")
1847
+ return append(candidates, filepath.Join("/usr/local/bin", engineBinaryName()))
1463
1848
  }
1464
1849
 
1465
1850
  func binaryReachableFromPATH(dest string) bool {
@@ -1493,31 +1878,81 @@ func pathContainsDir(dir string) bool {
1493
1878
  }
1494
1879
 
1495
1880
  func engineVersion(path string) string {
1496
- ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
1497
- defer cancel()
1498
- out, err := exec.CommandContext(ctx, path, "version").Output()
1881
+ got, err := readEngineVersion(path, 30*time.Second)
1499
1882
  if err != nil {
1500
1883
  return ""
1501
1884
  }
1502
- return strings.TrimSpace(string(out))
1885
+ return got
1503
1886
  }
1504
1887
 
1505
- func readManifest(path string) map[string]bool {
1506
- out := map[string]bool{}
1507
- for _, rel := range readManifestList(path) {
1508
- out[rel] = true
1888
+ func verifyEngineBinary(path, want string, timeout time.Duration) (string, error) {
1889
+ want = strings.TrimPrefix(strings.TrimSpace(want), "v")
1890
+ if want == "" || want == "dev" || !semverLike(want) {
1891
+ return "", fmt.Errorf("invalid requested version %q", want)
1509
1892
  }
1510
- return out
1893
+ got, err := readEngineVersion(path, timeout)
1894
+ if err != nil {
1895
+ return "", err
1896
+ }
1897
+ if strings.TrimPrefix(got, "v") != want {
1898
+ return "", fmt.Errorf("version mismatch: got %s want %s", got, want)
1899
+ }
1900
+ return got, nil
1901
+ }
1902
+
1903
+ func readEngineVersion(path string, timeout time.Duration) (string, error) {
1904
+ ctx, cancel := context.WithTimeout(context.Background(), timeout)
1905
+ defer cancel()
1906
+ out, err := exec.CommandContext(ctx, path, "version").Output()
1907
+ if err != nil {
1908
+ if errors.Is(ctx.Err(), context.DeadlineExceeded) {
1909
+ return "", fmt.Errorf("version command timed out after %s", timeout)
1910
+ }
1911
+ return "", fmt.Errorf("run exact path %s version: %w", path, err)
1912
+ }
1913
+ line := strings.TrimSuffix(string(out), "\n")
1914
+ line = strings.TrimSuffix(line, "\r")
1915
+ if strings.ContainsAny(line, "\r\n") {
1916
+ return "", fmt.Errorf("invalid multi-line version output")
1917
+ }
1918
+ line = strings.TrimSpace(line)
1919
+ if line == "" || line == "dev" || !semverLike(line) {
1920
+ return "", fmt.Errorf("invalid version output %q", line)
1921
+ }
1922
+ return line, nil
1923
+ }
1924
+
1925
+ func readManifest(path string) map[string]managedRecord {
1926
+ records, _ := parseManifest(path)
1927
+ return records
1511
1928
  }
1512
1929
 
1513
1930
  func readManifestList(path string) []string {
1931
+ _, entries := parseManifest(path)
1932
+ return entries
1933
+ }
1934
+
1935
+ func parseManifest(path string) (map[string]managedRecord, []string) {
1514
1936
  data, err := os.ReadFile(path)
1515
1937
  if err != nil {
1516
- return nil
1938
+ return map[string]managedRecord{}, nil
1517
1939
  }
1940
+ hashes := map[string]string{}
1518
1941
  var out []string
1519
1942
  for _, line := range strings.Split(string(data), "\n") {
1520
1943
  line = strings.TrimSpace(line)
1944
+ if strings.HasPrefix(line, "# managed: ") {
1945
+ record := strings.TrimPrefix(line, "# managed: ")
1946
+ i := strings.LastIndex(record, " sha256:")
1947
+ if i > 0 {
1948
+ rel := strings.TrimSpace(record[:i])
1949
+ hash := "sha256:" + strings.ToLower(strings.TrimSpace(record[i+len(" sha256:"):]))
1950
+ if filepath.IsLocal(filepath.FromSlash(rel)) && validManagedHash(hash) {
1951
+ hashes[rel] = hash
1952
+ }
1953
+ }
1954
+ continue
1955
+ }
1521
1956
  if line == "" || strings.HasPrefix(line, "#") {
1522
1957
  continue
1523
1958
  }
@@ -1528,7 +1963,20 @@ func readManifestList(path string) []string {
1528
1963
  }
1529
1964
  out = append(out, line)
1530
1965
  }
1531
- return out
1966
+ records := make(map[string]managedRecord, len(out))
1967
+ for _, rel := range out {
1968
+ records[rel] = managedRecord{Hash: hashes[rel]}
1969
+ }
1970
+ return records, out
1971
+ }
1972
+
1973
+ func validManagedHash(hash string) bool {
1974
+ const prefix = "sha256:"
1975
+ if !strings.HasPrefix(hash, prefix) || len(hash) != len(prefix)+sha256.Size*2 {
1976
+ return false
1977
+ }
1978
+ _, err := hex.DecodeString(strings.TrimPrefix(hash, prefix))
1979
+ return err == nil
1532
1980
  }
1533
1981
 
1534
1982
  func manifestHeader(path, key string) string {