devrites 6.0.4 → 6.1.1

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 (800) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +24 -9
  3. package/SECURITY.md +9 -6
  4. package/bin/devrites.mjs +28 -12
  5. package/docs/adr/0035-claude-root-leaves-plan-mode.md +33 -0
  6. package/docs/adr/0036-archive-recall-is-a-read-time-query.md +32 -0
  7. package/docs/adr/README.md +4 -2
  8. package/docs/architecture.md +3 -1
  9. package/docs/cli.md +25 -2
  10. package/docs/command-map.md +10 -8
  11. package/docs/engine/commands.md +6 -3
  12. package/docs/harness-compliance.md +2 -2
  13. package/docs/orchestration.md +5 -4
  14. package/docs/release.md +4 -4
  15. package/docs/skills.md +2 -2
  16. package/engine/commands.go +1 -1
  17. package/engine/help.go +10 -6
  18. package/engine/internal/acceptance/cli.go +4 -1
  19. package/engine/internal/acceptance/exec.go +11 -11
  20. package/engine/internal/devritespaths/paths.go +5 -0
  21. package/engine/internal/hostpack/hostpack.go +48 -3
  22. package/engine/internal/install/apply.go +68 -1
  23. package/engine/internal/install/binary.go +4 -0
  24. package/engine/internal/install/install.go +25 -15
  25. package/engine/internal/install/merge.go +287 -123
  26. package/engine/internal/install/preflight.go +71 -1
  27. package/engine/internal/install/uninstall.go +7 -1
  28. package/engine/internal/install/update.go +7 -3
  29. package/engine/internal/lib/claims.go +30 -14
  30. package/engine/internal/lib/cli_observe.go +6 -0
  31. package/engine/internal/lib/context.go +24 -7
  32. package/engine/internal/lib/diffscope.go +79 -3
  33. package/engine/internal/lib/dispatch.go +34 -7
  34. package/engine/internal/lib/drift.go +9 -2
  35. package/engine/internal/lib/handoff.go +14 -5
  36. package/engine/internal/lib/metrics.go +29 -9
  37. package/engine/internal/lib/migrate.go +30 -12
  38. package/engine/internal/lib/next.go +22 -2
  39. package/engine/internal/lib/open_visual.go +3 -1
  40. package/engine/internal/lib/recall.go +355 -0
  41. package/engine/internal/lib/regression.go +20 -0
  42. package/engine/internal/lib/resolve.go +96 -53
  43. package/engine/internal/lib/secretscan.go +24 -1
  44. package/engine/internal/lib/taskgraph.go +31 -1
  45. package/engine/internal/lib/util.go +18 -7
  46. package/engine/internal/lib/windows.go +68 -15
  47. package/engine/internal/markdowntext/markdowntext.go +45 -6
  48. package/engine/internal/notes/cli.go +5 -3
  49. package/engine/internal/notes/notes.go +8 -3
  50. package/engine/internal/overhaul/admit/run.go +16 -12
  51. package/engine/internal/overhaul/overhaul.go +1 -0
  52. package/engine/internal/overhaul/records/check.go +52 -9
  53. package/engine/internal/overhaul/render/run.go +3 -2
  54. package/engine/internal/overhaul/snapshot/run.go +2 -2
  55. package/engine/internal/parallel/git.go +5 -6
  56. package/engine/internal/parallel/ops.go +3 -0
  57. package/engine/internal/parallel/pathdisjoint.go +9 -4
  58. package/engine/internal/release/release.go +11 -9
  59. package/engine/internal/safepath/safepath.go +17 -1
  60. package/engine/internal/state/budgets.go +8 -4
  61. package/engine/internal/state/feature.go +3 -1
  62. package/engine/internal/state/observation.go +12 -7
  63. package/engine/internal/state/taskgraph.go +67 -12
  64. package/engine/internal/state/write.go +7 -1
  65. package/engine/main.go +5 -1
  66. package/pack/.claude/agents/_shared/composition-overhaul.md +2 -0
  67. package/pack/.claude/agents/devrites-devex-reviewer.md +5 -0
  68. package/pack/.claude/agents/devrites-evidence-scout.md +4 -0
  69. package/pack/.claude/agents/devrites-plan-reviewer.md +4 -1
  70. package/pack/.claude/agents/overhaul-implementer.md +1 -0
  71. package/pack/.claude/settings.json +4 -4
  72. package/pack/.claude/skills/devrites-api-interface/SKILL.md +1 -1
  73. package/pack/.claude/skills/devrites-audit/SKILL.md +1 -1
  74. package/pack/.claude/skills/devrites-browser-proof/SKILL.md +1 -1
  75. package/pack/.claude/skills/devrites-debug-recovery/SKILL.md +8 -6
  76. package/pack/.claude/skills/devrites-doubt/SKILL.md +1 -1
  77. package/pack/.claude/skills/devrites-frontend-craft/SKILL.md +1 -1
  78. package/pack/.claude/skills/devrites-interview/SKILL.md +1 -1
  79. package/pack/.claude/skills/devrites-lib/reference/intent-map.md +3 -3
  80. package/pack/.claude/skills/devrites-lib/reference/parallel-dispatch.md +4 -3
  81. package/pack/.claude/skills/devrites-lib/reference/reply-contract.md +12 -15
  82. package/pack/.claude/skills/devrites-lib/reference/standards/afk-hitl.md +30 -10
  83. package/pack/.claude/skills/devrites-lib/reference/standards/ci-cd.md +2 -1
  84. package/pack/.claude/skills/devrites-lib/reference/standards/code-review.md +1 -1
  85. package/pack/.claude/skills/devrites-lib/reference/standards/core.md +1 -1
  86. package/pack/.claude/skills/devrites-lib/reference/standards/data-integrity.md +4 -0
  87. package/pack/.claude/skills/devrites-lib/reference/standards/developer-experience.md +5 -0
  88. package/pack/.claude/skills/devrites-lib/reference/standards/loop-operations.md +3 -0
  89. package/pack/.claude/skills/devrites-lib/reference/standards/one-shot-actions.md +7 -6
  90. package/pack/.claude/skills/devrites-lib/reference/standards/performance.md +2 -0
  91. package/pack/.claude/skills/devrites-lib/reference/standards/skill-authoring.md +13 -1
  92. package/pack/.claude/skills/devrites-lib/reference/standards/testing.md +13 -0
  93. package/pack/.claude/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -6
  94. package/pack/.claude/skills/devrites-prose-craft/SKILL.md +1 -1
  95. package/pack/.claude/skills/devrites-source-driven/SKILL.md +1 -1
  96. package/pack/.claude/skills/devrites-ux-shape/SKILL.md +1 -1
  97. package/pack/.claude/skills/overhaul/references/orchestration.md +6 -2
  98. package/pack/.claude/skills/overhaul/references/records.md +8 -3
  99. package/pack/.claude/skills/rite/SKILL.md +4 -3
  100. package/pack/.claude/skills/rite/reference/menu.md +6 -5
  101. package/pack/.claude/skills/rite-adopt/SKILL.md +4 -3
  102. package/pack/.claude/skills/rite-autocomplete/SKILL.md +17 -14
  103. package/pack/.claude/skills/rite-autocomplete/reference/loop.md +9 -6
  104. package/pack/.claude/skills/rite-autocomplete/reference/stop-conditions.md +25 -21
  105. package/pack/.claude/skills/rite-build/reference/afk-discipline.md +6 -3
  106. package/pack/.claude/skills/rite-build/reference/parallel-batch.md +5 -5
  107. package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +19 -0
  108. package/pack/.claude/skills/rite-converge/SKILL.md +4 -3
  109. package/pack/.claude/skills/rite-customize/SKILL.md +1 -1
  110. package/pack/.claude/skills/rite-define/SKILL.md +3 -2
  111. package/pack/.claude/skills/rite-doctor/SKILL.md +12 -6
  112. package/pack/.claude/skills/rite-dogfood/SKILL.md +2 -2
  113. package/pack/.claude/skills/rite-explain/SKILL.md +1 -1
  114. package/pack/.claude/skills/rite-fast/SKILL.md +3 -1
  115. package/pack/.claude/skills/rite-handoff/SKILL.md +4 -3
  116. package/pack/.claude/skills/rite-learn/SKILL.md +2 -2
  117. package/pack/.claude/skills/rite-plan/SKILL.md +1 -1
  118. package/pack/.claude/skills/rite-plan/reference/replan-and-repair.md +2 -0
  119. package/pack/.claude/skills/rite-plan/reference/slicing.md +3 -1
  120. package/pack/.claude/skills/rite-pov/SKILL.md +1 -1
  121. package/pack/.claude/skills/rite-pr-feedback/SKILL.md +5 -4
  122. package/pack/.claude/skills/rite-pressure-test/SKILL.md +7 -5
  123. package/pack/.claude/skills/rite-prototype/SKILL.md +1 -1
  124. package/pack/.claude/skills/rite-prove/SKILL.md +4 -2
  125. package/pack/.claude/skills/rite-prove/reference/failure-triage.md +2 -1
  126. package/pack/.claude/skills/rite-quick/SKILL.md +10 -5
  127. package/pack/.claude/skills/rite-resolve/SKILL.md +3 -3
  128. package/pack/.claude/skills/rite-resolve/reference/answer-protocol.md +4 -5
  129. package/pack/.claude/skills/rite-review/SKILL.md +2 -1
  130. package/pack/.claude/skills/rite-seal/SKILL.md +3 -3
  131. package/pack/.claude/skills/rite-seal/reference/go-no-go.md +2 -0
  132. package/pack/.claude/skills/rite-seal/reference/seal-template.md +5 -2
  133. package/pack/.claude/skills/rite-ship/reference/close-out.md +3 -2
  134. package/pack/.claude/skills/rite-spec/SKILL.md +4 -1
  135. package/pack/.claude/skills/rite-spec/reference/state-workspace.md +2 -2
  136. package/pack/.claude/skills/rite-status/SKILL.md +1 -1
  137. package/pack/.claude/skills/rite-temper/reference/significance.md +2 -0
  138. package/pack/.claude/skills/rite-upgrade/SKILL.md +4 -2
  139. package/pack/.claude/skills/rite-vet/SKILL.md +3 -2
  140. package/pack/.claude/skills/rite-vet/reference/artifacts.md +1 -1
  141. package/pack/.claude/skills/rite-watch-pr/SKILL.md +1 -1
  142. package/pack/.claude/skills/rite-zoom-out/SKILL.md +1 -1
  143. package/pack/generated/claude/agents/devrites-devex-reviewer.md +5 -0
  144. package/pack/generated/claude/agents/devrites-evidence-scout.md +4 -0
  145. package/pack/generated/claude/agents/devrites-plan-reviewer.md +4 -1
  146. package/pack/generated/claude/agents/overhaul-backend-engineer.md +2 -0
  147. package/pack/generated/claude/agents/overhaul-boundary.md +2 -0
  148. package/pack/generated/claude/agents/overhaul-craft.md +2 -0
  149. package/pack/generated/claude/agents/overhaul-frontend-engineer.md +2 -0
  150. package/pack/generated/claude/agents/overhaul-implementer.md +3 -0
  151. package/pack/generated/claude/agents/overhaul-recon.md +2 -0
  152. package/pack/generated/claude/agents/overhaul-specialist.md +2 -0
  153. package/pack/generated/claude/agents/overhaul-verifier.md +2 -0
  154. package/pack/generated/claude/settings.json +4 -4
  155. package/pack/generated/claude/skills/devrites-api-interface/SKILL.md +1 -1
  156. package/pack/generated/claude/skills/devrites-audit/SKILL.md +1 -1
  157. package/pack/generated/claude/skills/devrites-browser-proof/SKILL.md +1 -1
  158. package/pack/generated/claude/skills/devrites-debug-recovery/SKILL.md +8 -6
  159. package/pack/generated/claude/skills/devrites-doubt/SKILL.md +1 -1
  160. package/pack/generated/claude/skills/devrites-frontend-craft/SKILL.md +1 -1
  161. package/pack/generated/claude/skills/devrites-interview/SKILL.md +1 -1
  162. package/pack/generated/claude/skills/devrites-lib/reference/intent-map.md +3 -3
  163. package/pack/generated/claude/skills/devrites-lib/reference/parallel-dispatch.md +4 -3
  164. package/pack/generated/claude/skills/devrites-lib/reference/reply-contract.md +12 -15
  165. package/pack/generated/claude/skills/devrites-lib/reference/standards/afk-hitl.md +30 -10
  166. package/pack/generated/claude/skills/devrites-lib/reference/standards/ci-cd.md +2 -1
  167. package/pack/generated/claude/skills/devrites-lib/reference/standards/code-review.md +1 -1
  168. package/pack/generated/claude/skills/devrites-lib/reference/standards/core.md +1 -1
  169. package/pack/generated/claude/skills/devrites-lib/reference/standards/data-integrity.md +4 -0
  170. package/pack/generated/claude/skills/devrites-lib/reference/standards/developer-experience.md +5 -0
  171. package/pack/generated/claude/skills/devrites-lib/reference/standards/loop-operations.md +3 -0
  172. package/pack/generated/claude/skills/devrites-lib/reference/standards/one-shot-actions.md +7 -6
  173. package/pack/generated/claude/skills/devrites-lib/reference/standards/performance.md +2 -0
  174. package/pack/generated/claude/skills/devrites-lib/reference/standards/skill-authoring.md +13 -1
  175. package/pack/generated/claude/skills/devrites-lib/reference/standards/testing.md +13 -0
  176. package/pack/generated/claude/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -6
  177. package/pack/generated/claude/skills/devrites-prose-craft/SKILL.md +1 -1
  178. package/pack/generated/claude/skills/devrites-source-driven/SKILL.md +1 -1
  179. package/pack/generated/claude/skills/devrites-ux-shape/SKILL.md +1 -1
  180. package/pack/generated/claude/skills/overhaul/references/orchestration.md +6 -2
  181. package/pack/generated/claude/skills/overhaul/references/records.md +8 -3
  182. package/pack/generated/claude/skills/rite/SKILL.md +4 -3
  183. package/pack/generated/claude/skills/rite/reference/menu.md +6 -5
  184. package/pack/generated/claude/skills/rite-adopt/SKILL.md +4 -3
  185. package/pack/generated/claude/skills/rite-autocomplete/SKILL.md +17 -14
  186. package/pack/generated/claude/skills/rite-autocomplete/reference/loop.md +9 -6
  187. package/pack/generated/claude/skills/rite-autocomplete/reference/stop-conditions.md +25 -21
  188. package/pack/generated/claude/skills/rite-build/reference/afk-discipline.md +6 -3
  189. package/pack/generated/claude/skills/rite-build/reference/parallel-batch.md +5 -5
  190. package/pack/generated/claude/skills/rite-build/reference/wright-dispatch.md +19 -0
  191. package/pack/generated/claude/skills/rite-converge/SKILL.md +4 -3
  192. package/pack/generated/claude/skills/rite-customize/SKILL.md +1 -1
  193. package/pack/generated/claude/skills/rite-define/SKILL.md +3 -2
  194. package/pack/generated/claude/skills/rite-doctor/SKILL.md +12 -6
  195. package/pack/generated/claude/skills/rite-dogfood/SKILL.md +2 -2
  196. package/pack/generated/claude/skills/rite-explain/SKILL.md +1 -1
  197. package/pack/generated/claude/skills/rite-fast/SKILL.md +3 -1
  198. package/pack/generated/claude/skills/rite-handoff/SKILL.md +4 -3
  199. package/pack/generated/claude/skills/rite-learn/SKILL.md +2 -2
  200. package/pack/generated/claude/skills/rite-plan/SKILL.md +1 -1
  201. package/pack/generated/claude/skills/rite-plan/reference/replan-and-repair.md +2 -0
  202. package/pack/generated/claude/skills/rite-plan/reference/slicing.md +3 -1
  203. package/pack/generated/claude/skills/rite-pov/SKILL.md +1 -1
  204. package/pack/generated/claude/skills/rite-pr-feedback/SKILL.md +5 -4
  205. package/pack/generated/claude/skills/rite-pressure-test/SKILL.md +7 -5
  206. package/pack/generated/claude/skills/rite-prototype/SKILL.md +1 -1
  207. package/pack/generated/claude/skills/rite-prove/SKILL.md +4 -2
  208. package/pack/generated/claude/skills/rite-prove/reference/failure-triage.md +2 -1
  209. package/pack/generated/claude/skills/rite-quick/SKILL.md +10 -5
  210. package/pack/generated/claude/skills/rite-resolve/SKILL.md +3 -3
  211. package/pack/generated/claude/skills/rite-resolve/reference/answer-protocol.md +4 -5
  212. package/pack/generated/claude/skills/rite-review/SKILL.md +2 -1
  213. package/pack/generated/claude/skills/rite-seal/SKILL.md +3 -3
  214. package/pack/generated/claude/skills/rite-seal/reference/go-no-go.md +2 -0
  215. package/pack/generated/claude/skills/rite-seal/reference/seal-template.md +5 -2
  216. package/pack/generated/claude/skills/rite-ship/reference/close-out.md +3 -2
  217. package/pack/generated/claude/skills/rite-spec/SKILL.md +4 -1
  218. package/pack/generated/claude/skills/rite-spec/reference/state-workspace.md +2 -2
  219. package/pack/generated/claude/skills/rite-status/SKILL.md +1 -1
  220. package/pack/generated/claude/skills/rite-temper/reference/significance.md +2 -0
  221. package/pack/generated/claude/skills/rite-upgrade/SKILL.md +4 -2
  222. package/pack/generated/claude/skills/rite-vet/SKILL.md +3 -2
  223. package/pack/generated/claude/skills/rite-vet/reference/artifacts.md +1 -1
  224. package/pack/generated/claude/skills/rite-watch-pr/SKILL.md +1 -1
  225. package/pack/generated/claude/skills/rite-zoom-out/SKILL.md +1 -1
  226. package/pack/generated/codex/agents/devrites-code-reviewer.toml +7 -7
  227. package/pack/generated/codex/agents/devrites-devex-reviewer.toml +6 -1
  228. package/pack/generated/codex/agents/devrites-evidence-scout.toml +4 -0
  229. package/pack/generated/codex/agents/devrites-frontend-reviewer.toml +1 -1
  230. package/pack/generated/codex/agents/devrites-plan-drafter.toml +3 -3
  231. package/pack/generated/codex/agents/devrites-plan-reviewer.toml +5 -2
  232. package/pack/generated/codex/agents/devrites-proof-runner.toml +2 -2
  233. package/pack/generated/codex/agents/devrites-security-auditor.toml +1 -1
  234. package/pack/generated/codex/agents/devrites-simplifier-reviewer.toml +1 -1
  235. package/pack/generated/codex/agents/devrites-slice-wright.toml +5 -5
  236. package/pack/generated/codex/agents/devrites-test-analyst.toml +5 -5
  237. package/pack/generated/codex/agents/devrites-upgrade-planner.toml +1 -1
  238. package/pack/generated/codex/agents/fast-builder.toml +3 -3
  239. package/pack/generated/codex/agents/fast-critic.toml +3 -3
  240. package/pack/generated/codex/agents/overhaul-backend-engineer.toml +2 -0
  241. package/pack/generated/codex/agents/overhaul-boundary.toml +2 -0
  242. package/pack/generated/codex/agents/overhaul-craft.toml +2 -0
  243. package/pack/generated/codex/agents/overhaul-frontend-engineer.toml +2 -0
  244. package/pack/generated/codex/agents/overhaul-implementer.toml +2 -0
  245. package/pack/generated/codex/agents/overhaul-recon.toml +2 -0
  246. package/pack/generated/codex/agents/overhaul-specialist.toml +2 -0
  247. package/pack/generated/codex/agents/overhaul-verifier.toml +2 -0
  248. package/pack/generated/codex/skills/devrites-api-interface/SKILL.md +1 -1
  249. package/pack/generated/codex/skills/devrites-audit/SKILL.md +1 -1
  250. package/pack/generated/codex/skills/devrites-browser-proof/SKILL.md +1 -1
  251. package/pack/generated/codex/skills/devrites-debug-recovery/SKILL.md +8 -6
  252. package/pack/generated/codex/skills/devrites-doubt/SKILL.md +1 -1
  253. package/pack/generated/codex/skills/devrites-frontend-craft/SKILL.md +1 -1
  254. package/pack/generated/codex/skills/devrites-interview/SKILL.md +1 -1
  255. package/pack/generated/codex/skills/devrites-lib/reference/intent-map.md +3 -3
  256. package/pack/generated/codex/skills/devrites-lib/reference/parallel-dispatch.md +6 -5
  257. package/pack/generated/codex/skills/devrites-lib/reference/reply-contract.md +12 -15
  258. package/pack/generated/codex/skills/devrites-lib/reference/standards/afk-hitl.md +30 -10
  259. package/pack/generated/codex/skills/devrites-lib/reference/standards/agents.md +1 -1
  260. package/pack/generated/codex/skills/devrites-lib/reference/standards/ci-cd.md +2 -1
  261. package/pack/generated/codex/skills/devrites-lib/reference/standards/code-review.md +1 -1
  262. package/pack/generated/codex/skills/devrites-lib/reference/standards/core.md +2 -2
  263. package/pack/generated/codex/skills/devrites-lib/reference/standards/data-integrity.md +4 -0
  264. package/pack/generated/codex/skills/devrites-lib/reference/standards/developer-experience.md +5 -0
  265. package/pack/generated/codex/skills/devrites-lib/reference/standards/loop-operations.md +3 -0
  266. package/pack/generated/codex/skills/devrites-lib/reference/standards/one-shot-actions.md +7 -6
  267. package/pack/generated/codex/skills/devrites-lib/reference/standards/performance.md +2 -0
  268. package/pack/generated/codex/skills/devrites-lib/reference/standards/skill-authoring.md +13 -1
  269. package/pack/generated/codex/skills/devrites-lib/reference/standards/testing.md +13 -0
  270. package/pack/generated/codex/skills/devrites-lib/reference/workspace-artifact-schema.md +11 -7
  271. package/pack/generated/codex/skills/devrites-prose-craft/SKILL.md +1 -1
  272. package/pack/generated/codex/skills/devrites-source-driven/SKILL.md +1 -1
  273. package/pack/generated/codex/skills/devrites-ux-shape/SKILL.md +1 -1
  274. package/pack/generated/codex/skills/overhaul/SKILL.md +15 -15
  275. package/pack/generated/codex/skills/overhaul/references/audit-domains.md +2 -2
  276. package/pack/generated/codex/skills/overhaul/references/orchestration.md +11 -7
  277. package/pack/generated/codex/skills/overhaul/references/records.md +8 -3
  278. package/pack/generated/codex/skills/rite/SKILL.md +4 -3
  279. package/pack/generated/codex/skills/rite/reference/menu.md +6 -5
  280. package/pack/generated/codex/skills/rite-adopt/SKILL.md +4 -3
  281. package/pack/generated/codex/skills/rite-autocomplete/SKILL.md +18 -15
  282. package/pack/generated/codex/skills/rite-autocomplete/reference/loop.md +9 -6
  283. package/pack/generated/codex/skills/rite-autocomplete/reference/stop-conditions.md +25 -21
  284. package/pack/generated/codex/skills/rite-build/SKILL.md +2 -2
  285. package/pack/generated/codex/skills/rite-build/reference/afk-discipline.md +6 -3
  286. package/pack/generated/codex/skills/rite-build/reference/parallel-batch.md +6 -6
  287. package/pack/generated/codex/skills/rite-build/reference/wright-dispatch.md +20 -1
  288. package/pack/generated/codex/skills/rite-clarify/SKILL.md +1 -1
  289. package/pack/generated/codex/skills/rite-converge/SKILL.md +5 -4
  290. package/pack/generated/codex/skills/rite-customize/SKILL.md +1 -1
  291. package/pack/generated/codex/skills/rite-define/SKILL.md +4 -3
  292. package/pack/generated/codex/skills/rite-doctor/SKILL.md +12 -6
  293. package/pack/generated/codex/skills/rite-dogfood/SKILL.md +2 -2
  294. package/pack/generated/codex/skills/rite-explain/SKILL.md +1 -1
  295. package/pack/generated/codex/skills/rite-fast/SKILL.md +8 -6
  296. package/pack/generated/codex/skills/rite-frame/SKILL.md +1 -1
  297. package/pack/generated/codex/skills/rite-handoff/SKILL.md +4 -3
  298. package/pack/generated/codex/skills/rite-learn/SKILL.md +2 -2
  299. package/pack/generated/codex/skills/rite-plan/SKILL.md +2 -2
  300. package/pack/generated/codex/skills/rite-plan/reference/replan-and-repair.md +2 -0
  301. package/pack/generated/codex/skills/rite-plan/reference/slicing.md +3 -1
  302. package/pack/generated/codex/skills/rite-polish/SKILL.md +2 -2
  303. package/pack/generated/codex/skills/rite-pov/SKILL.md +1 -1
  304. package/pack/generated/codex/skills/rite-pr-feedback/SKILL.md +5 -4
  305. package/pack/generated/codex/skills/rite-pressure-test/SKILL.md +7 -5
  306. package/pack/generated/codex/skills/rite-prototype/SKILL.md +1 -1
  307. package/pack/generated/codex/skills/rite-prove/SKILL.md +5 -3
  308. package/pack/generated/codex/skills/rite-prove/reference/failure-triage.md +2 -1
  309. package/pack/generated/codex/skills/rite-quick/SKILL.md +11 -6
  310. package/pack/generated/codex/skills/rite-resolve/SKILL.md +3 -3
  311. package/pack/generated/codex/skills/rite-resolve/reference/answer-protocol.md +4 -5
  312. package/pack/generated/codex/skills/rite-review/SKILL.md +3 -2
  313. package/pack/generated/codex/skills/rite-seal/SKILL.md +4 -4
  314. package/pack/generated/codex/skills/rite-seal/reference/go-no-go.md +2 -0
  315. package/pack/generated/codex/skills/rite-seal/reference/seal-template.md +5 -2
  316. package/pack/generated/codex/skills/rite-ship/SKILL.md +1 -1
  317. package/pack/generated/codex/skills/rite-ship/reference/close-out.md +3 -2
  318. package/pack/generated/codex/skills/rite-spec/SKILL.md +5 -2
  319. package/pack/generated/codex/skills/rite-spec/reference/state-workspace.md +2 -2
  320. package/pack/generated/codex/skills/rite-status/SKILL.md +1 -1
  321. package/pack/generated/codex/skills/rite-temper/SKILL.md +2 -2
  322. package/pack/generated/codex/skills/rite-temper/reference/significance.md +2 -0
  323. package/pack/generated/codex/skills/rite-upgrade/SKILL.md +4 -2
  324. package/pack/generated/codex/skills/rite-vet/SKILL.md +4 -3
  325. package/pack/generated/codex/skills/rite-vet/reference/artifacts.md +1 -1
  326. package/pack/generated/codex/skills/rite-watch-pr/SKILL.md +1 -1
  327. package/pack/generated/codex/skills/rite-zoom-out/SKILL.md +1 -1
  328. package/pack/generated/devin/AGENTS.md +1 -1
  329. package/pack/generated/devin/agents/devrites-devex-reviewer.md +5 -0
  330. package/pack/generated/devin/agents/devrites-evidence-scout.md +4 -0
  331. package/pack/generated/devin/agents/devrites-plan-reviewer.md +4 -1
  332. package/pack/generated/devin/agents/overhaul-backend-engineer.md +2 -0
  333. package/pack/generated/devin/agents/overhaul-boundary.md +2 -0
  334. package/pack/generated/devin/agents/overhaul-craft.md +2 -0
  335. package/pack/generated/devin/agents/overhaul-frontend-engineer.md +2 -0
  336. package/pack/generated/devin/agents/overhaul-implementer.md +2 -0
  337. package/pack/generated/devin/agents/overhaul-recon.md +2 -0
  338. package/pack/generated/devin/agents/overhaul-specialist.md +2 -0
  339. package/pack/generated/devin/agents/overhaul-verifier.md +2 -0
  340. package/pack/generated/devin/skills/devrites-api-interface/SKILL.md +1 -1
  341. package/pack/generated/devin/skills/devrites-audit/SKILL.md +1 -1
  342. package/pack/generated/devin/skills/devrites-browser-proof/SKILL.md +1 -1
  343. package/pack/generated/devin/skills/devrites-debug-recovery/SKILL.md +8 -6
  344. package/pack/generated/devin/skills/devrites-doubt/SKILL.md +1 -1
  345. package/pack/generated/devin/skills/devrites-frontend-craft/SKILL.md +1 -1
  346. package/pack/generated/devin/skills/devrites-interview/SKILL.md +2 -2
  347. package/pack/generated/devin/skills/devrites-lib/reference/intent-map.md +3 -3
  348. package/pack/generated/devin/skills/devrites-lib/reference/parallel-dispatch.md +6 -5
  349. package/pack/generated/devin/skills/devrites-lib/reference/reply-contract.md +12 -15
  350. package/pack/generated/devin/skills/devrites-lib/reference/standards/afk-hitl.md +33 -13
  351. package/pack/generated/devin/skills/devrites-lib/reference/standards/agents.md +1 -1
  352. package/pack/generated/devin/skills/devrites-lib/reference/standards/ci-cd.md +2 -1
  353. package/pack/generated/devin/skills/devrites-lib/reference/standards/code-review.md +1 -1
  354. package/pack/generated/devin/skills/devrites-lib/reference/standards/core.md +2 -2
  355. package/pack/generated/devin/skills/devrites-lib/reference/standards/data-integrity.md +4 -0
  356. package/pack/generated/devin/skills/devrites-lib/reference/standards/developer-experience.md +5 -0
  357. package/pack/generated/devin/skills/devrites-lib/reference/standards/loop-operations.md +3 -0
  358. package/pack/generated/devin/skills/devrites-lib/reference/standards/one-shot-actions.md +7 -6
  359. package/pack/generated/devin/skills/devrites-lib/reference/standards/performance.md +2 -0
  360. package/pack/generated/devin/skills/devrites-lib/reference/standards/skill-authoring.md +13 -1
  361. package/pack/generated/devin/skills/devrites-lib/reference/standards/testing.md +13 -0
  362. package/pack/generated/devin/skills/devrites-lib/reference/workspace-artifact-schema.md +11 -7
  363. package/pack/generated/devin/skills/devrites-prose-craft/SKILL.md +1 -1
  364. package/pack/generated/devin/skills/devrites-source-driven/SKILL.md +1 -1
  365. package/pack/generated/devin/skills/devrites-ux-shape/SKILL.md +1 -1
  366. package/pack/generated/devin/skills/overhaul/SKILL.md +15 -15
  367. package/pack/generated/devin/skills/overhaul/references/audit-domains.md +2 -2
  368. package/pack/generated/devin/skills/overhaul/references/orchestration.md +11 -7
  369. package/pack/generated/devin/skills/overhaul/references/records.md +8 -3
  370. package/pack/generated/devin/skills/rite/SKILL.md +4 -3
  371. package/pack/generated/devin/skills/rite/reference/menu.md +6 -5
  372. package/pack/generated/devin/skills/rite-adopt/SKILL.md +4 -3
  373. package/pack/generated/devin/skills/rite-autocomplete/SKILL.md +19 -16
  374. package/pack/generated/devin/skills/rite-autocomplete/reference/decision-policy.md +1 -1
  375. package/pack/generated/devin/skills/rite-autocomplete/reference/loop.md +9 -6
  376. package/pack/generated/devin/skills/rite-autocomplete/reference/stop-conditions.md +26 -22
  377. package/pack/generated/devin/skills/rite-build/SKILL.md +2 -2
  378. package/pack/generated/devin/skills/rite-build/reference/afk-discipline.md +6 -3
  379. package/pack/generated/devin/skills/rite-build/reference/checkpoint-protocol.md +2 -2
  380. package/pack/generated/devin/skills/rite-build/reference/parallel-batch.md +6 -6
  381. package/pack/generated/devin/skills/rite-build/reference/wright-dispatch.md +20 -1
  382. package/pack/generated/devin/skills/rite-clarify/SKILL.md +1 -1
  383. package/pack/generated/devin/skills/rite-converge/SKILL.md +5 -4
  384. package/pack/generated/devin/skills/rite-customize/SKILL.md +1 -1
  385. package/pack/generated/devin/skills/rite-define/SKILL.md +4 -3
  386. package/pack/generated/devin/skills/rite-doctor/SKILL.md +12 -6
  387. package/pack/generated/devin/skills/rite-dogfood/SKILL.md +2 -2
  388. package/pack/generated/devin/skills/rite-explain/SKILL.md +3 -3
  389. package/pack/generated/devin/skills/rite-explain/reference/intake.md +1 -1
  390. package/pack/generated/devin/skills/rite-fast/SKILL.md +8 -6
  391. package/pack/generated/devin/skills/rite-frame/SKILL.md +1 -1
  392. package/pack/generated/devin/skills/rite-handoff/SKILL.md +4 -3
  393. package/pack/generated/devin/skills/rite-learn/SKILL.md +2 -2
  394. package/pack/generated/devin/skills/rite-plan/SKILL.md +2 -2
  395. package/pack/generated/devin/skills/rite-plan/reference/replan-and-repair.md +2 -0
  396. package/pack/generated/devin/skills/rite-plan/reference/slicing.md +3 -1
  397. package/pack/generated/devin/skills/rite-polish/SKILL.md +2 -2
  398. package/pack/generated/devin/skills/rite-pov/SKILL.md +1 -1
  399. package/pack/generated/devin/skills/rite-pr-feedback/SKILL.md +5 -4
  400. package/pack/generated/devin/skills/rite-pressure-test/SKILL.md +7 -5
  401. package/pack/generated/devin/skills/rite-prototype/SKILL.md +1 -1
  402. package/pack/generated/devin/skills/rite-prove/SKILL.md +5 -3
  403. package/pack/generated/devin/skills/rite-prove/reference/failure-triage.md +2 -1
  404. package/pack/generated/devin/skills/rite-quick/SKILL.md +11 -6
  405. package/pack/generated/devin/skills/rite-resolve/SKILL.md +4 -4
  406. package/pack/generated/devin/skills/rite-resolve/reference/answer-protocol.md +4 -5
  407. package/pack/generated/devin/skills/rite-review/SKILL.md +3 -2
  408. package/pack/generated/devin/skills/rite-seal/SKILL.md +4 -4
  409. package/pack/generated/devin/skills/rite-seal/reference/go-no-go.md +2 -0
  410. package/pack/generated/devin/skills/rite-seal/reference/seal-template.md +5 -2
  411. package/pack/generated/devin/skills/rite-ship/SKILL.md +1 -1
  412. package/pack/generated/devin/skills/rite-ship/reference/close-out.md +3 -2
  413. package/pack/generated/devin/skills/rite-spec/SKILL.md +5 -2
  414. package/pack/generated/devin/skills/rite-spec/reference/investigation.md +1 -1
  415. package/pack/generated/devin/skills/rite-spec/reference/state-workspace.md +2 -2
  416. package/pack/generated/devin/skills/rite-status/SKILL.md +1 -1
  417. package/pack/generated/devin/skills/rite-temper/SKILL.md +3 -3
  418. package/pack/generated/devin/skills/rite-temper/reference/scope-modes.md +1 -1
  419. package/pack/generated/devin/skills/rite-temper/reference/significance.md +3 -1
  420. package/pack/generated/devin/skills/rite-upgrade/SKILL.md +4 -2
  421. package/pack/generated/devin/skills/rite-vet/SKILL.md +4 -3
  422. package/pack/generated/devin/skills/rite-vet/reference/artifacts.md +1 -1
  423. package/pack/generated/devin/skills/rite-vet/reference/review-axes.md +2 -2
  424. package/pack/generated/devin/skills/rite-watch-pr/SKILL.md +1 -1
  425. package/pack/generated/devin/skills/rite-zoom-out/SKILL.md +1 -1
  426. package/pack/generated/omp/agents/devrites-code-reviewer.md +7 -7
  427. package/pack/generated/omp/agents/devrites-devex-reviewer.md +6 -1
  428. package/pack/generated/omp/agents/devrites-evidence-scout.md +4 -0
  429. package/pack/generated/omp/agents/devrites-frontend-reviewer.md +1 -1
  430. package/pack/generated/omp/agents/devrites-plan-drafter.md +3 -3
  431. package/pack/generated/omp/agents/devrites-plan-reviewer.md +5 -2
  432. package/pack/generated/omp/agents/devrites-proof-runner.md +2 -2
  433. package/pack/generated/omp/agents/devrites-security-auditor.md +1 -1
  434. package/pack/generated/omp/agents/devrites-simplifier-reviewer.md +1 -1
  435. package/pack/generated/omp/agents/devrites-slice-wright.md +5 -5
  436. package/pack/generated/omp/agents/devrites-test-analyst.md +5 -5
  437. package/pack/generated/omp/agents/devrites-upgrade-planner.md +1 -1
  438. package/pack/generated/omp/agents/fast-builder.md +3 -3
  439. package/pack/generated/omp/agents/fast-critic.md +3 -3
  440. package/pack/generated/omp/agents/overhaul-backend-engineer.md +2 -0
  441. package/pack/generated/omp/agents/overhaul-boundary.md +2 -0
  442. package/pack/generated/omp/agents/overhaul-craft.md +2 -0
  443. package/pack/generated/omp/agents/overhaul-frontend-engineer.md +2 -0
  444. package/pack/generated/omp/agents/overhaul-implementer.md +2 -0
  445. package/pack/generated/omp/agents/overhaul-recon.md +2 -0
  446. package/pack/generated/omp/agents/overhaul-specialist.md +2 -0
  447. package/pack/generated/omp/agents/overhaul-verifier.md +2 -0
  448. package/pack/generated/omp/commands/rite-resolve.md +1 -1
  449. package/pack/generated/omp/skills/devrites-api-interface/SKILL.md +4 -4
  450. package/pack/generated/omp/skills/devrites-audit/SKILL.md +5 -5
  451. package/pack/generated/omp/skills/devrites-browser-proof/SKILL.md +5 -5
  452. package/pack/generated/omp/skills/devrites-browser-proof/reference/browser-performance.md +1 -1
  453. package/pack/generated/omp/skills/devrites-browser-proof/reference/visual-verdict.md +1 -1
  454. package/pack/generated/omp/skills/devrites-debug-recovery/SKILL.md +12 -10
  455. package/pack/generated/omp/skills/devrites-debug-recovery/reference/cleanup-and-classify.md +1 -1
  456. package/pack/generated/omp/skills/devrites-doubt/SKILL.md +6 -6
  457. package/pack/generated/omp/skills/devrites-frontend-craft/SKILL.md +3 -3
  458. package/pack/generated/omp/skills/devrites-frontend-craft/reference/craft.md +1 -1
  459. package/pack/generated/omp/skills/devrites-frontend-craft/reference/design-references.md +4 -4
  460. package/pack/generated/omp/skills/devrites-frontend-craft/reference/fullstack.md +1 -1
  461. package/pack/generated/omp/skills/devrites-frontend-craft/reference/quality-standards.md +8 -8
  462. package/pack/generated/omp/skills/devrites-frontend-craft/reference/reuse-first.md +2 -2
  463. package/pack/generated/omp/skills/devrites-frontend-craft/reference/shape.md +2 -2
  464. package/pack/generated/omp/skills/devrites-interview/SKILL.md +3 -3
  465. package/pack/generated/omp/skills/devrites-lib/reference/intent-map.md +4 -4
  466. package/pack/generated/omp/skills/devrites-lib/reference/parallel-dispatch.md +8 -7
  467. package/pack/generated/omp/skills/devrites-lib/reference/reply-contract.md +12 -15
  468. package/pack/generated/omp/skills/devrites-lib/reference/standards/afk-hitl.md +38 -18
  469. package/pack/generated/omp/skills/devrites-lib/reference/standards/agents.md +6 -6
  470. package/pack/generated/omp/skills/devrites-lib/reference/standards/browser-proof-checklist.md +2 -2
  471. package/pack/generated/omp/skills/devrites-lib/reference/standards/ci-cd.md +2 -1
  472. package/pack/generated/omp/skills/devrites-lib/reference/standards/code-review.md +3 -3
  473. package/pack/generated/omp/skills/devrites-lib/reference/standards/coding-style.md +1 -1
  474. package/pack/generated/omp/skills/devrites-lib/reference/standards/context-hygiene.md +2 -2
  475. package/pack/generated/omp/skills/devrites-lib/reference/standards/core.md +4 -4
  476. package/pack/generated/omp/skills/devrites-lib/reference/standards/data-integrity.md +4 -0
  477. package/pack/generated/omp/skills/devrites-lib/reference/standards/debug-recovery.md +1 -1
  478. package/pack/generated/omp/skills/devrites-lib/reference/standards/definition-of-done.md +1 -1
  479. package/pack/generated/omp/skills/devrites-lib/reference/standards/developer-experience.md +8 -3
  480. package/pack/generated/omp/skills/devrites-lib/reference/standards/glossary.md +3 -3
  481. package/pack/generated/omp/skills/devrites-lib/reference/standards/loop-operations.md +3 -0
  482. package/pack/generated/omp/skills/devrites-lib/reference/standards/one-shot-actions.md +7 -6
  483. package/pack/generated/omp/skills/devrites-lib/reference/standards/performance.md +3 -1
  484. package/pack/generated/omp/skills/devrites-lib/reference/standards/prose-style.md +4 -4
  485. package/pack/generated/omp/skills/devrites-lib/reference/standards/release/ship-checklist.md +1 -1
  486. package/pack/generated/omp/skills/devrites-lib/reference/standards/review/README.md +1 -1
  487. package/pack/generated/omp/skills/devrites-lib/reference/standards/review/ci-workflows.md +1 -1
  488. package/pack/generated/omp/skills/devrites-lib/reference/standards/review/react.md +1 -1
  489. package/pack/generated/omp/skills/devrites-lib/reference/standards/review/stack-profile.md +5 -5
  490. package/pack/generated/omp/skills/devrites-lib/reference/standards/skill-authoring.md +15 -3
  491. package/pack/generated/omp/skills/devrites-lib/reference/standards/spec-grammar.md +3 -3
  492. package/pack/generated/omp/skills/devrites-lib/reference/standards/testing.md +14 -1
  493. package/pack/generated/omp/skills/devrites-lib/reference/standards/tooling.md +1 -1
  494. package/pack/generated/omp/skills/devrites-lib/reference/standards/verification-methods.md +1 -1
  495. package/pack/generated/omp/skills/devrites-lib/reference/visual-playbooks/index.md +2 -2
  496. package/pack/generated/omp/skills/devrites-lib/reference/workspace-artifact-schema.md +12 -8
  497. package/pack/generated/omp/skills/devrites-prose-craft/SKILL.md +4 -4
  498. package/pack/generated/omp/skills/devrites-prose-craft/reference/examples.md +1 -1
  499. package/pack/generated/omp/skills/devrites-source-driven/SKILL.md +3 -3
  500. package/pack/generated/omp/skills/devrites-ux-shape/SKILL.md +10 -10
  501. package/pack/generated/omp/skills/devrites-ux-shape/reference/brief-template.md +3 -3
  502. package/pack/generated/omp/skills/devrites-ux-shape/reference/visual-direction-probe.md +1 -1
  503. package/pack/generated/omp/skills/overhaul/references/orchestration.md +6 -2
  504. package/pack/generated/omp/skills/overhaul/references/records.md +8 -3
  505. package/pack/generated/omp/skills/rite/SKILL.md +8 -7
  506. package/pack/generated/omp/skills/rite/reference/menu.md +6 -5
  507. package/pack/generated/omp/skills/rite-adopt/SKILL.md +6 -5
  508. package/pack/generated/omp/skills/rite-adopt/reference/anti-patterns.md +1 -1
  509. package/pack/generated/omp/skills/rite-autocomplete/SKILL.md +22 -19
  510. package/pack/generated/omp/skills/rite-autocomplete/reference/decision-policy.md +1 -1
  511. package/pack/generated/omp/skills/rite-autocomplete/reference/loop.md +11 -8
  512. package/pack/generated/omp/skills/rite-autocomplete/reference/stop-conditions.md +25 -21
  513. package/pack/generated/omp/skills/rite-build/SKILL.md +7 -7
  514. package/pack/generated/omp/skills/rite-build/reference/afk-discipline.md +7 -4
  515. package/pack/generated/omp/skills/rite-build/reference/anti-patterns.md +1 -1
  516. package/pack/generated/omp/skills/rite-build/reference/checkpoint-protocol.md +2 -2
  517. package/pack/generated/omp/skills/rite-build/reference/checkpoint.md +2 -2
  518. package/pack/generated/omp/skills/rite-build/reference/frontend-trigger.md +1 -1
  519. package/pack/generated/omp/skills/rite-build/reference/parallel-batch.md +9 -9
  520. package/pack/generated/omp/skills/rite-build/reference/phase-contract.md +6 -6
  521. package/pack/generated/omp/skills/rite-build/reference/tdd.md +1 -1
  522. package/pack/generated/omp/skills/rite-build/reference/wright-dispatch.md +27 -8
  523. package/pack/generated/omp/skills/rite-clarify/SKILL.md +4 -4
  524. package/pack/generated/omp/skills/rite-clarify/reference/anti-patterns.md +1 -1
  525. package/pack/generated/omp/skills/rite-converge/SKILL.md +16 -15
  526. package/pack/generated/omp/skills/rite-converge/reference/convergence-assessment.md +4 -4
  527. package/pack/generated/omp/skills/rite-customize/SKILL.md +2 -2
  528. package/pack/generated/omp/skills/rite-define/SKILL.md +25 -24
  529. package/pack/generated/omp/skills/rite-define/reference/anti-patterns.md +2 -2
  530. package/pack/generated/omp/skills/rite-define/reference/gates.md +2 -2
  531. package/pack/generated/omp/skills/rite-doctor/SKILL.md +12 -6
  532. package/pack/generated/omp/skills/rite-dogfood/SKILL.md +5 -5
  533. package/pack/generated/omp/skills/rite-explain/SKILL.md +7 -7
  534. package/pack/generated/omp/skills/rite-explain/reference/intake.md +1 -1
  535. package/pack/generated/omp/skills/rite-fast/SKILL.md +12 -10
  536. package/pack/generated/omp/skills/rite-fast/reference/artifacts.md +1 -1
  537. package/pack/generated/omp/skills/rite-frame/SKILL.md +6 -6
  538. package/pack/generated/omp/skills/rite-frame/reference/failure-modes.md +4 -4
  539. package/pack/generated/omp/skills/rite-handoff/SKILL.md +7 -6
  540. package/pack/generated/omp/skills/rite-learn/SKILL.md +6 -6
  541. package/pack/generated/omp/skills/rite-plan/SKILL.md +9 -9
  542. package/pack/generated/omp/skills/rite-plan/reference/anti-patterns.md +1 -1
  543. package/pack/generated/omp/skills/rite-plan/reference/replan-and-repair.md +3 -1
  544. package/pack/generated/omp/skills/rite-plan/reference/slicing.md +6 -4
  545. package/pack/generated/omp/skills/rite-plan/reference/task-breakdown.md +1 -1
  546. package/pack/generated/omp/skills/rite-polish/SKILL.md +6 -6
  547. package/pack/generated/omp/skills/rite-polish/reference/anti-ai-slop.md +9 -9
  548. package/pack/generated/omp/skills/rite-polish/reference/anti-patterns.md +1 -1
  549. package/pack/generated/omp/skills/rite-polish/reference/backend-polish.md +2 -2
  550. package/pack/generated/omp/skills/rite-polish/reference/browser-polish-evidence.md +1 -1
  551. package/pack/generated/omp/skills/rite-polish/reference/code.md +8 -8
  552. package/pack/generated/omp/skills/rite-polish/reference/design-memory.md +3 -3
  553. package/pack/generated/omp/skills/rite-polish/reference/design-system-discovery.md +1 -1
  554. package/pack/generated/omp/skills/rite-polish/reference/harden-checklist.md +1 -1
  555. package/pack/generated/omp/skills/rite-polish/reference/ledger.md +3 -3
  556. package/pack/generated/omp/skills/rite-polish/reference/ui.md +1 -1
  557. package/pack/generated/omp/skills/rite-pov/SKILL.md +2 -2
  558. package/pack/generated/omp/skills/rite-pr-feedback/SKILL.md +6 -5
  559. package/pack/generated/omp/skills/rite-pressure-test/SKILL.md +9 -7
  560. package/pack/generated/omp/skills/rite-prototype/SKILL.md +1 -1
  561. package/pack/generated/omp/skills/rite-prove/SKILL.md +17 -15
  562. package/pack/generated/omp/skills/rite-prove/reference/acceptance-proof.md +7 -7
  563. package/pack/generated/omp/skills/rite-prove/reference/anti-patterns.md +1 -1
  564. package/pack/generated/omp/skills/rite-prove/reference/browser-proof.md +3 -3
  565. package/pack/generated/omp/skills/rite-prove/reference/failure-triage.md +4 -3
  566. package/pack/generated/omp/skills/rite-quick/SKILL.md +19 -14
  567. package/pack/generated/omp/skills/rite-resolve/SKILL.md +6 -6
  568. package/pack/generated/omp/skills/rite-resolve/reference/answer-protocol.md +4 -5
  569. package/pack/generated/omp/skills/rite-review/SKILL.md +23 -22
  570. package/pack/generated/omp/skills/rite-review/reference/anti-patterns.md +1 -1
  571. package/pack/generated/omp/skills/rite-review/reference/cognitive-load.md +2 -2
  572. package/pack/generated/omp/skills/rite-review/reference/nielsen-heuristics.md +1 -1
  573. package/pack/generated/omp/skills/rite-review/reference/performance-checklist.md +1 -1
  574. package/pack/generated/omp/skills/rite-review/reference/performance-review.md +1 -1
  575. package/pack/generated/omp/skills/rite-review/reference/security-review.md +2 -2
  576. package/pack/generated/omp/skills/rite-seal/SKILL.md +11 -11
  577. package/pack/generated/omp/skills/rite-seal/reference/anti-patterns.md +1 -1
  578. package/pack/generated/omp/skills/rite-seal/reference/final-evidence.md +3 -3
  579. package/pack/generated/omp/skills/rite-seal/reference/go-no-go.md +3 -1
  580. package/pack/generated/omp/skills/rite-seal/reference/output.md +1 -1
  581. package/pack/generated/omp/skills/rite-seal/reference/phase-contract.md +4 -4
  582. package/pack/generated/omp/skills/rite-seal/reference/seal-template.md +5 -2
  583. package/pack/generated/omp/skills/rite-ship/SKILL.md +7 -7
  584. package/pack/generated/omp/skills/rite-ship/reference/anti-patterns.md +1 -1
  585. package/pack/generated/omp/skills/rite-ship/reference/close-out.md +5 -4
  586. package/pack/generated/omp/skills/rite-ship/reference/git-ship.md +2 -2
  587. package/pack/generated/omp/skills/rite-ship/reference/rollout.md +3 -3
  588. package/pack/generated/omp/skills/rite-spec/SKILL.md +17 -14
  589. package/pack/generated/omp/skills/rite-spec/reference/anti-patterns.md +1 -1
  590. package/pack/generated/omp/skills/rite-spec/reference/investigation.md +3 -3
  591. package/pack/generated/omp/skills/rite-spec/reference/question-protocol.md +1 -1
  592. package/pack/generated/omp/skills/rite-spec/reference/spec-template.md +2 -2
  593. package/pack/generated/omp/skills/rite-spec/reference/state-workspace.md +10 -10
  594. package/pack/generated/omp/skills/rite-status/SKILL.md +2 -2
  595. package/pack/generated/omp/skills/rite-temper/SKILL.md +13 -13
  596. package/pack/generated/omp/skills/rite-temper/reference/anti-patterns.md +2 -2
  597. package/pack/generated/omp/skills/rite-temper/reference/review-dimensions.md +3 -3
  598. package/pack/generated/omp/skills/rite-temper/reference/scope-modes.md +3 -3
  599. package/pack/generated/omp/skills/rite-temper/reference/significance.md +4 -2
  600. package/pack/generated/omp/skills/rite-upgrade/SKILL.md +7 -5
  601. package/pack/generated/omp/skills/rite-vet/SKILL.md +14 -13
  602. package/pack/generated/omp/skills/rite-vet/reference/anti-patterns.md +2 -2
  603. package/pack/generated/omp/skills/rite-vet/reference/artifacts.md +3 -3
  604. package/pack/generated/omp/skills/rite-vet/reference/depth.md +3 -3
  605. package/pack/generated/omp/skills/rite-vet/reference/review-axes.md +5 -5
  606. package/pack/generated/omp/skills/rite-watch-pr/SKILL.md +2 -2
  607. package/pack/generated/omp/skills/rite-zoom-out/SKILL.md +1 -1
  608. package/pack/generated/pi/agents/devrites-devex-reviewer.md +5 -0
  609. package/pack/generated/pi/agents/devrites-evidence-scout.md +4 -0
  610. package/pack/generated/pi/agents/devrites-plan-reviewer.md +4 -1
  611. package/pack/generated/pi/agents/overhaul-backend-engineer.md +2 -0
  612. package/pack/generated/pi/agents/overhaul-boundary.md +2 -0
  613. package/pack/generated/pi/agents/overhaul-craft.md +2 -0
  614. package/pack/generated/pi/agents/overhaul-frontend-engineer.md +2 -0
  615. package/pack/generated/pi/agents/overhaul-implementer.md +2 -0
  616. package/pack/generated/pi/agents/overhaul-recon.md +2 -0
  617. package/pack/generated/pi/agents/overhaul-specialist.md +2 -0
  618. package/pack/generated/pi/agents/overhaul-verifier.md +2 -0
  619. package/pack/generated/pi/prompts/rite-resolve.md +1 -1
  620. package/pack/generated/pi/skills/devrites-api-interface/SKILL.md +1 -1
  621. package/pack/generated/pi/skills/devrites-audit/SKILL.md +1 -1
  622. package/pack/generated/pi/skills/devrites-browser-proof/SKILL.md +1 -1
  623. package/pack/generated/pi/skills/devrites-debug-recovery/SKILL.md +8 -6
  624. package/pack/generated/pi/skills/devrites-doubt/SKILL.md +1 -1
  625. package/pack/generated/pi/skills/devrites-frontend-craft/SKILL.md +1 -1
  626. package/pack/generated/pi/skills/devrites-interview/SKILL.md +1 -1
  627. package/pack/generated/pi/skills/devrites-lib/reference/intent-map.md +3 -3
  628. package/pack/generated/pi/skills/devrites-lib/reference/parallel-dispatch.md +6 -5
  629. package/pack/generated/pi/skills/devrites-lib/reference/reply-contract.md +12 -15
  630. package/pack/generated/pi/skills/devrites-lib/reference/standards/afk-hitl.md +30 -10
  631. package/pack/generated/pi/skills/devrites-lib/reference/standards/agents.md +1 -1
  632. package/pack/generated/pi/skills/devrites-lib/reference/standards/ci-cd.md +2 -1
  633. package/pack/generated/pi/skills/devrites-lib/reference/standards/code-review.md +1 -1
  634. package/pack/generated/pi/skills/devrites-lib/reference/standards/core.md +2 -2
  635. package/pack/generated/pi/skills/devrites-lib/reference/standards/data-integrity.md +4 -0
  636. package/pack/generated/pi/skills/devrites-lib/reference/standards/developer-experience.md +5 -0
  637. package/pack/generated/pi/skills/devrites-lib/reference/standards/loop-operations.md +3 -0
  638. package/pack/generated/pi/skills/devrites-lib/reference/standards/one-shot-actions.md +7 -6
  639. package/pack/generated/pi/skills/devrites-lib/reference/standards/performance.md +2 -0
  640. package/pack/generated/pi/skills/devrites-lib/reference/standards/skill-authoring.md +13 -1
  641. package/pack/generated/pi/skills/devrites-lib/reference/standards/testing.md +13 -0
  642. package/pack/generated/pi/skills/devrites-lib/reference/workspace-artifact-schema.md +11 -7
  643. package/pack/generated/pi/skills/devrites-prose-craft/SKILL.md +1 -1
  644. package/pack/generated/pi/skills/devrites-source-driven/SKILL.md +1 -1
  645. package/pack/generated/pi/skills/devrites-ux-shape/SKILL.md +1 -1
  646. package/pack/generated/pi/skills/overhaul/SKILL.md +15 -15
  647. package/pack/generated/pi/skills/overhaul/references/audit-domains.md +2 -2
  648. package/pack/generated/pi/skills/overhaul/references/orchestration.md +11 -7
  649. package/pack/generated/pi/skills/overhaul/references/records.md +8 -3
  650. package/pack/generated/pi/skills/rite/SKILL.md +4 -3
  651. package/pack/generated/pi/skills/rite/reference/menu.md +6 -5
  652. package/pack/generated/pi/skills/rite-adopt/SKILL.md +4 -3
  653. package/pack/generated/pi/skills/rite-autocomplete/SKILL.md +18 -15
  654. package/pack/generated/pi/skills/rite-autocomplete/reference/loop.md +9 -6
  655. package/pack/generated/pi/skills/rite-autocomplete/reference/stop-conditions.md +25 -21
  656. package/pack/generated/pi/skills/rite-build/SKILL.md +2 -2
  657. package/pack/generated/pi/skills/rite-build/reference/afk-discipline.md +6 -3
  658. package/pack/generated/pi/skills/rite-build/reference/parallel-batch.md +6 -6
  659. package/pack/generated/pi/skills/rite-build/reference/wright-dispatch.md +20 -1
  660. package/pack/generated/pi/skills/rite-clarify/SKILL.md +1 -1
  661. package/pack/generated/pi/skills/rite-converge/SKILL.md +5 -4
  662. package/pack/generated/pi/skills/rite-customize/SKILL.md +1 -1
  663. package/pack/generated/pi/skills/rite-define/SKILL.md +4 -3
  664. package/pack/generated/pi/skills/rite-doctor/SKILL.md +12 -6
  665. package/pack/generated/pi/skills/rite-dogfood/SKILL.md +2 -2
  666. package/pack/generated/pi/skills/rite-explain/SKILL.md +1 -1
  667. package/pack/generated/pi/skills/rite-fast/SKILL.md +8 -6
  668. package/pack/generated/pi/skills/rite-frame/SKILL.md +1 -1
  669. package/pack/generated/pi/skills/rite-handoff/SKILL.md +4 -3
  670. package/pack/generated/pi/skills/rite-learn/SKILL.md +2 -2
  671. package/pack/generated/pi/skills/rite-plan/SKILL.md +2 -2
  672. package/pack/generated/pi/skills/rite-plan/reference/replan-and-repair.md +2 -0
  673. package/pack/generated/pi/skills/rite-plan/reference/slicing.md +3 -1
  674. package/pack/generated/pi/skills/rite-polish/SKILL.md +2 -2
  675. package/pack/generated/pi/skills/rite-pov/SKILL.md +1 -1
  676. package/pack/generated/pi/skills/rite-pr-feedback/SKILL.md +5 -4
  677. package/pack/generated/pi/skills/rite-pressure-test/SKILL.md +7 -5
  678. package/pack/generated/pi/skills/rite-prototype/SKILL.md +1 -1
  679. package/pack/generated/pi/skills/rite-prove/SKILL.md +5 -3
  680. package/pack/generated/pi/skills/rite-prove/reference/failure-triage.md +2 -1
  681. package/pack/generated/pi/skills/rite-quick/SKILL.md +11 -6
  682. package/pack/generated/pi/skills/rite-resolve/SKILL.md +3 -3
  683. package/pack/generated/pi/skills/rite-resolve/reference/answer-protocol.md +4 -5
  684. package/pack/generated/pi/skills/rite-review/SKILL.md +3 -2
  685. package/pack/generated/pi/skills/rite-seal/SKILL.md +4 -4
  686. package/pack/generated/pi/skills/rite-seal/reference/go-no-go.md +2 -0
  687. package/pack/generated/pi/skills/rite-seal/reference/seal-template.md +5 -2
  688. package/pack/generated/pi/skills/rite-ship/SKILL.md +1 -1
  689. package/pack/generated/pi/skills/rite-ship/reference/close-out.md +3 -2
  690. package/pack/generated/pi/skills/rite-spec/SKILL.md +5 -2
  691. package/pack/generated/pi/skills/rite-spec/reference/state-workspace.md +2 -2
  692. package/pack/generated/pi/skills/rite-status/SKILL.md +1 -1
  693. package/pack/generated/pi/skills/rite-temper/SKILL.md +2 -2
  694. package/pack/generated/pi/skills/rite-temper/reference/significance.md +2 -0
  695. package/pack/generated/pi/skills/rite-upgrade/SKILL.md +4 -2
  696. package/pack/generated/pi/skills/rite-vet/SKILL.md +4 -3
  697. package/pack/generated/pi/skills/rite-vet/reference/artifacts.md +1 -1
  698. package/pack/generated/pi/skills/rite-watch-pr/SKILL.md +1 -1
  699. package/pack/generated/pi/skills/rite-zoom-out/SKILL.md +1 -1
  700. package/package.json +3 -2
  701. package/scripts/build-host-artifacts.sh +13 -0
  702. package/scripts/codex-generate.sh +5 -1
  703. package/scripts/devin-generate.sh +4 -3
  704. package/scripts/install-lib.sh +5 -0
  705. package/scripts/omp-generate.sh +23 -1
  706. package/scripts/pi-generate.sh +2 -2
  707. package/engine/cmd/releasepack/main_test.go +0 -239
  708. package/engine/harnessmatrix_test.go +0 -46
  709. package/engine/help_test.go +0 -241
  710. package/engine/internal/acceptance/acceptance_test.go +0 -946
  711. package/engine/internal/devritespaths/paths_test.go +0 -128
  712. package/engine/internal/fsutil/copy_test.go +0 -154
  713. package/engine/internal/gate/gate_test.go +0 -865
  714. package/engine/internal/gate/readiness_binding_test.go +0 -443
  715. package/engine/internal/gitenv/env_test.go +0 -48
  716. package/engine/internal/hostpack/hostpack_test.go +0 -269
  717. package/engine/internal/install/install_test.go +0 -1741
  718. package/engine/internal/install/workspace_advisory_test.go +0 -54
  719. package/engine/internal/lib/candidate_test.go +0 -506
  720. package/engine/internal/lib/claims_test.go +0 -267
  721. package/engine/internal/lib/closeout_test.go +0 -266
  722. package/engine/internal/lib/context_next_test.go +0 -912
  723. package/engine/internal/lib/cursor_compat_test.go +0 -65
  724. package/engine/internal/lib/detect_test.go +0 -118
  725. package/engine/internal/lib/drift_test.go +0 -135
  726. package/engine/internal/lib/dup_test.go +0 -666
  727. package/engine/internal/lib/environment_report_test.go +0 -54
  728. package/engine/internal/lib/evidencefresh_test.go +0 -120
  729. package/engine/internal/lib/handoff_test.go +0 -83
  730. package/engine/internal/lib/manifest_union_test.go +0 -251
  731. package/engine/internal/lib/metrics_test.go +0 -28
  732. package/engine/internal/lib/migrate_test.go +0 -213
  733. package/engine/internal/lib/observe_budgets_test.go +0 -428
  734. package/engine/internal/lib/open_visual_test.go +0 -291
  735. package/engine/internal/lib/regression_test.go +0 -282
  736. package/engine/internal/lib/resolve_remediation_test.go +0 -192
  737. package/engine/internal/lib/secretscan_test.go +0 -556
  738. package/engine/internal/lib/slicecheck_test.go +0 -170
  739. package/engine/internal/lib/taskgraph_test.go +0 -353
  740. package/engine/internal/lib/triggers_test.go +0 -23
  741. package/engine/internal/lib/util_test.go +0 -148
  742. package/engine/internal/lib/visual_outline_test.go +0 -189
  743. package/engine/internal/lib/windows_test.go +0 -420
  744. package/engine/internal/lib/workspace_read_test.go +0 -102
  745. package/engine/internal/markdowntext/markdowntext_fuzz_test.go +0 -36
  746. package/engine/internal/markdowntext/markdowntext_test.go +0 -58
  747. package/engine/internal/markdowntext/testdata/structural.json +0 -277
  748. package/engine/internal/notes/notes_test.go +0 -545
  749. package/engine/internal/overhaul/admit/admit_test.go +0 -204
  750. package/engine/internal/overhaul/admit/packet_test.go +0 -223
  751. package/engine/internal/overhaul/bench/run_test.go +0 -316
  752. package/engine/internal/overhaul/ovio/ovio_test.go +0 -75
  753. package/engine/internal/overhaul/packet/packet_test.go +0 -263
  754. package/engine/internal/overhaul/records/init_test.go +0 -96
  755. package/engine/internal/overhaul/records/records_test.go +0 -696
  756. package/engine/internal/overhaul/records/run_test.go +0 -77
  757. package/engine/internal/overhaul/render/run_test.go +0 -928
  758. package/engine/internal/overhaul/score/run_test.go +0 -636
  759. package/engine/internal/overhaul/snapshot/run_test.go +0 -718
  760. package/engine/internal/parallel/cli_test.go +0 -91
  761. package/engine/internal/parallel/ops_test.go +0 -939
  762. package/engine/internal/parallel/parallel_test.go +0 -645
  763. package/engine/internal/parallel/pathdisjoint_fuzz_test.go +0 -43
  764. package/engine/internal/parallel/pathdisjoint_test.go +0 -193
  765. package/engine/internal/parallel/select_test.go +0 -95
  766. package/engine/internal/parallel/testdata/fuzz/FuzzPathDisjoint/a449b81b4711b7b4 +0 -3
  767. package/engine/internal/reason/reason_test.go +0 -37
  768. package/engine/internal/release/release_test.go +0 -181
  769. package/engine/internal/rootfacts/facts_test.go +0 -364
  770. package/engine/internal/safepath/safepath_fuzz_test.go +0 -21
  771. package/engine/internal/safepath/safepath_test.go +0 -69
  772. package/engine/internal/safepath/safepath_windows_test.go +0 -26
  773. package/engine/internal/state/acceptance_test.go +0 -57
  774. package/engine/internal/state/budgets_test.go +0 -135
  775. package/engine/internal/state/cmd/workflowmanifest/main_test.go +0 -96
  776. package/engine/internal/state/concurrency_test.go +0 -184
  777. package/engine/internal/state/cursor_fuzz_test.go +0 -80
  778. package/engine/internal/state/cursor_test.go +0 -276
  779. package/engine/internal/state/observation_test.go +0 -961
  780. package/engine/internal/state/observation_unix_test.go +0 -68
  781. package/engine/internal/state/state_test.go +0 -986
  782. package/engine/internal/state/taskgraph_fuzz_test.go +0 -38
  783. package/engine/internal/state/taskgraph_test.go +0 -43
  784. package/engine/internal/state/testdata/fuzz/FuzzCursorRoundTrip/114b28f68c1fa4db +0 -4
  785. package/engine/internal/state/testdata/fuzz/FuzzCursorRoundTrip/378a0f2218e812ea +0 -4
  786. package/engine/internal/state/testdata/fuzz/FuzzCursorRoundTrip/39eaa26fcb481e3c +0 -4
  787. package/engine/internal/state/testdata/fuzz/FuzzCursorRoundTrip/776ca1dc0143acf0 +0 -4
  788. package/engine/internal/state/testdata/fuzz/FuzzCursorRoundTrip/9295930c1ebb81df +0 -4
  789. package/engine/internal/state/testdata/fuzz/FuzzCursorRoundTrip/a60de309dc33be0d +0 -4
  790. package/engine/internal/state/testdata/fuzz/FuzzCursorRoundTrip/f04dc7b06ef7c99f +0 -4
  791. package/engine/internal/state/testdata/fuzz/FuzzCursorRoundTrip/f3000ac510878bd6 +0 -4
  792. package/engine/internal/state/testdata/fuzz/FuzzWorkspaceSchemaRow/a75aabb9f2a847d5 +0 -2
  793. package/engine/internal/state/workspaceschema_fuzz_test.go +0 -27
  794. package/engine/internal/state/workspaceschema_test.go +0 -101
  795. package/engine/internal/state/write_test.go +0 -136
  796. package/engine/internal/version/version_test.go +0 -29
  797. package/engine/internal/workflow/commands_test.go +0 -18
  798. package/engine/note_routing_test.go +0 -95
  799. package/engine/observation_contract_test.go +0 -175
  800. package/engine/root_routing_test.go +0 -340
@@ -1,1741 +0,0 @@
1
- package install
2
-
3
- import (
4
- "bytes"
5
- "context"
6
- "crypto/sha256"
7
- "fmt"
8
- "os"
9
- "os/exec"
10
- "path/filepath"
11
- "runtime"
12
- "slices"
13
- "strings"
14
-
15
- "strconv"
16
- "testing"
17
- "time"
18
-
19
- "github.com/devrites/devrites/internal/hostpack"
20
- "github.com/devrites/devrites/internal/release"
21
- "github.com/devrites/devrites/internal/testutil"
22
- )
23
-
24
- func TestInstallDryRunWritesNothing(t *testing.T) {
25
- t.Setenv("DEVRITES_NO_BINARY", "1")
26
- payload := testPayload(t)
27
- target := t.TempDir()
28
- var out bytes.Buffer
29
-
30
- opts := DefaultOptions(ModeInstall)
31
- opts.Target = target
32
- opts.PayloadDir = payload
33
- opts.DryRun = true
34
- opts.Stdout = &out
35
- opts.Stderr = &bytes.Buffer{}
36
-
37
- if err := Apply(opts); err != nil {
38
- t.Fatal(err)
39
- }
40
- if exists(filepath.Join(target, ".claude")) {
41
- t.Fatal("dry-run created .claude")
42
- }
43
- if !strings.Contains(out.String(), "[install] .claude/skills/rite/SKILL.md") {
44
- t.Fatalf("dry-run output missing planned install:\n%s", out.String())
45
- }
46
- if strings.Contains(out.String(), "Next:") {
47
- t.Fatalf("dry-run output claims a next move:\n%s", out.String())
48
- }
49
- }
50
-
51
- func TestInstallPrintsFirstMoveForInstalledHosts(t *testing.T) {
52
- t.Setenv("DEVRITES_NO_BINARY", "1")
53
-
54
- for _, tc := range []struct {
55
- name string
56
- withCodex bool
57
- withOmp bool
58
- withPi bool
59
- withDevin bool
60
- want string
61
- }{
62
- {name: "Claude, Codex, and OMP", withCodex: true, withOmp: true, want: "Next: reopen the project, then run /rite (Claude) or $rite (Codex) or /skill:rite (omp/pi)."},
63
- {name: "Claude and Codex", withCodex: true, withOmp: false, withPi: false, want: "Next: reopen the project, then run /rite (Claude) or $rite (Codex)."},
64
- {name: "Claude, Codex, and Devin", withCodex: true, withDevin: true, want: "Next: reopen the project, then run /rite (Claude/Devin) or $rite (Codex)."},
65
- {name: "Claude and OMP", withCodex: false, withOmp: true, withPi: false, want: "Next: reopen the project, then run /rite or /skill:rite."},
66
- {name: "Claude and pi", withCodex: false, withOmp: false, withPi: true, want: "Next: reopen the project, then run /rite or /skill:rite."},
67
- {name: "Claude and Devin", withCodex: false, withDevin: true, want: "Next: reopen the project, then run /rite."},
68
- {name: "Claude only", withCodex: false, withOmp: false, withPi: false, want: "Next: reopen the project, then run /rite."},
69
- } {
70
- t.Run(tc.name, func(t *testing.T) {
71
- var out bytes.Buffer
72
- opts := DefaultOptions(ModeInstall)
73
- opts.Target = t.TempDir()
74
- opts.PayloadDir = testPayload(t)
75
- opts.WithCodex = tc.withCodex
76
- opts.WithOmp = tc.withOmp
77
- opts.WithPi = tc.withPi
78
- opts.WithDevin = tc.withDevin
79
- opts.Stdout = &out
80
- opts.Stderr = &bytes.Buffer{}
81
-
82
- if err := Apply(opts); err != nil {
83
- t.Fatal(err)
84
- }
85
- if !strings.Contains(out.String(), tc.want) {
86
- t.Fatalf("install output missing first move %q:\n%s", tc.want, out.String())
87
- }
88
- })
89
- }
90
- }
91
-
92
- func TestInstallManifestConflictAndPrune(t *testing.T) {
93
- t.Setenv("DEVRITES_NO_BINARY", "1")
94
- payload := testPayload(t)
95
- target := t.TempDir()
96
-
97
- runInstall(t, target, payload, func(o *Options) {})
98
- testutil.WriteFile(t, filepath.Join(target, ".claude", "skills", "foreign", "SKILL.md"), "mine\n")
99
- runInstall(t, target, payload, func(o *Options) {})
100
- if got := testutil.ReadFile(t, filepath.Join(target, ".claude", "skills", "foreign", "SKILL.md")); got != "mine\n" {
101
- t.Fatalf("unmanaged file clobbered: %q", got)
102
- }
103
-
104
- stale := filepath.Join(target, ".claude", "skills", "stale", "SKILL.md")
105
- testutil.WriteFile(t, stale, "stale\n")
106
- testutil.AppendFile(t, filepath.Join(target, ManifestName), ".claude/skills/stale/SKILL.md\n")
107
- runInstall(t, target, payload, func(o *Options) { o.Force = true })
108
- if exists(stale) {
109
- t.Fatal("managed stale file was not pruned")
110
- }
111
- if !exists(filepath.Join(target, ".devrites", "ACTIVE")) {
112
- t.Fatal("ACTIVE not seeded")
113
- }
114
- if strings.Contains(testutil.ReadFile(t, filepath.Join(target, ManifestName)), ".devrites/ACTIVE") {
115
- t.Fatal("ACTIVE should not be manifest-managed")
116
- }
117
- }
118
-
119
- func TestMarkerMergeAndUninstallPreserveUserContent(t *testing.T) {
120
- t.Setenv("DEVRITES_NO_BINARY", "1")
121
- payload := testPayload(t)
122
- target := t.TempDir()
123
- testutil.WriteFile(t, filepath.Join(target, "AGENTS.md"), "user guidance\n")
124
- testutil.WriteFile(t, filepath.Join(target, ".codex", "config.toml"), "model = \"x\"\n")
125
- userHooks := `{"hooks":{"Stop":[{"hooks":[{"type":"command","command":"echo user"}]}]}}` + "\n"
126
- testutil.WriteFile(t, filepath.Join(target, ".codex", "hooks.json"), userHooks)
127
-
128
- runInstall(t, target, payload, func(o *Options) {})
129
- runInstall(t, target, payload, func(o *Options) {})
130
- agents := testutil.ReadFile(t, filepath.Join(target, "AGENTS.md"))
131
- if strings.Count(agents, "<!-- BEGIN DEVRITES CODEX -->") != 1 {
132
- t.Fatalf("AGENTS marker duplicated:\n%s", agents)
133
- }
134
- if strings.Count(agents, "<!-- BEGIN DEVRITES DEVIN -->") != 1 {
135
- t.Fatalf("AGENTS Devin marker missing or duplicated:\n%s", agents)
136
- }
137
- if !strings.Contains(agents, "user guidance") {
138
- t.Fatal("AGENTS user content lost")
139
- }
140
- config := testutil.ReadFile(t, filepath.Join(target, ".codex", "config.toml"))
141
- if strings.Count(config, "# BEGIN DEVRITES CODEX PERMISSIONS") != 1 ||
142
- !strings.Contains(config, `default_permissions = "devrites-orchestrator"`) ||
143
- !strings.Contains(config, `model = "x"`) {
144
- t.Fatalf("config preservation wrong:\n%s", config)
145
- }
146
- if hooks := testutil.ReadFile(t, filepath.Join(target, ".codex", "hooks.json")); hooks != userHooks {
147
- t.Fatalf("installer changed native Codex hooks:\n%s", hooks)
148
- }
149
-
150
- runUninstall(t, target)
151
- if got := testutil.ReadFile(t, filepath.Join(target, "AGENTS.md")); !strings.Contains(got, "user guidance") || strings.Contains(got, "DEVRITES CODEX") || strings.Contains(got, "DEVRITES DEVIN") {
152
- t.Fatalf("AGENTS uninstall preservation wrong:\n%s", got)
153
- }
154
- if exists(filepath.Join(target, ".devin")) {
155
- t.Fatal("uninstall left .devin behind")
156
- }
157
- if got := testutil.ReadFile(t, filepath.Join(target, ".codex", "config.toml")); !strings.Contains(got, `model = "x"`) || strings.Contains(got, "DEVRITES CODEX") {
158
- t.Fatalf("config uninstall preservation wrong:\n%s", got)
159
- }
160
- if got := testutil.ReadFile(t, filepath.Join(target, ".codex", "hooks.json")); got != userHooks {
161
- t.Fatalf("hooks uninstall preservation wrong:\n%s", got)
162
- }
163
- if !exists(filepath.Join(target, ".devrites", "ACTIVE")) {
164
- t.Fatal("uninstall removed ACTIVE")
165
- }
166
- }
167
-
168
- func TestMarkerMergeDryRunReportsUnreadableTarget(t *testing.T) {
169
- payload := t.TempDir()
170
- if err := os.WriteFile(filepath.Join(payload, "block.md"), []byte("DevRites\n"), 0o644); err != nil {
171
- t.Fatal(err)
172
- }
173
- target := t.TempDir()
174
- if err := os.Mkdir(filepath.Join(target, "AGENTS.md"), 0o755); err != nil {
175
- t.Fatal(err)
176
- }
177
- r := runner{
178
- opts: Options{DryRun: true, Stdout: &bytes.Buffer{}},
179
- target: target,
180
- payloadFS: os.DirFS(payload),
181
- }
182
- err := r.mergeMarkerFile(hostpack.MarkerMerge{
183
- TargetRel: "AGENTS.md",
184
- PayloadRel: "block.md",
185
- Begin: "<!-- BEGIN -->",
186
- End: "<!-- END -->",
187
- })
188
- if err == nil || !strings.Contains(err.Error(), "cannot read AGENTS.md") {
189
- t.Fatalf("mergeMarkerFile error = %v, want target read error", err)
190
- }
191
- }
192
-
193
- func TestUninstallReportsUnreadableManifest(t *testing.T) {
194
- target := t.TempDir()
195
- if err := os.Mkdir(filepath.Dir(filepath.Join(target, ManifestName)), 0o755); err != nil {
196
- t.Fatal(err)
197
- }
198
- if err := os.Mkdir(filepath.Join(target, ManifestName), 0o755); err != nil {
199
- t.Fatal(err)
200
- }
201
- opts := DefaultOptions(ModeUninstall)
202
- opts.Target = target
203
- err := Apply(opts)
204
- if err == nil || !strings.Contains(err.Error(), "read manifest") {
205
- t.Fatalf("Apply error = %v, want manifest read error", err)
206
- }
207
- if info, statErr := os.Stat(filepath.Join(target, ManifestName)); statErr != nil || !info.IsDir() {
208
- t.Fatalf("manifest was replaced: info=%v err=%v", info, statErr)
209
- }
210
- }
211
-
212
- func TestInstallMergesClaudePermissionsIntoExistingSettings(t *testing.T) {
213
- t.Setenv("DEVRITES_NO_BINARY", "1")
214
- payload := testPayload(t)
215
- target := t.TempDir()
216
- testutil.WriteFile(t, filepath.Join(target, ".claude", "settings.json"), `{
217
- "$comment": "keep my local notes",
218
- "theme": "dark",
219
- "statusLine": {"type":"command","command":"DEVRITES_THEME=dark echo user-status"},
220
- "permissions": {
221
- "defaultMode": "plan",
222
- "allow": ["Bash(user-tool *)", "Bash(git checkout *)", "Bash(devrites-engine retired-cmd *)"],
223
- "ask": ["Bash(rm *)"]
224
- },
225
- "hooks": {"Stop":[{"hooks":[
226
- {"type":"command","command":"echo user-stop"},
227
- {"type":"command","command":"devrites-engine hook old-gate --harness=claude"}
228
- ]}]}
229
- }`+"\n")
230
-
231
- var stderr bytes.Buffer
232
- runInstall(t, target, payload, func(o *Options) { o.Stderr = &stderr })
233
- runInstall(t, target, payload, func(o *Options) { o.Stderr = &stderr })
234
-
235
- settings := testutil.ReadFile(t, filepath.Join(target, ".claude", "settings.json"))
236
- for _, preserved := range []string{"keep my local notes", `"theme": "dark"`, "echo user-status", "echo user-stop", "Bash(user-tool *)", "Bash(git checkout *)", "Bash(rm *)"} {
237
- if !strings.Contains(settings, preserved) {
238
- t.Fatalf("Claude settings lost user content %q:\n%s", preserved, settings)
239
- }
240
- }
241
- if strings.Count(settings, "Bash(devrites-engine check readiness *)") != 1 {
242
- t.Fatalf("Claude permission was not merged exactly once:\n%s", settings)
243
- }
244
- for _, managed := range []string{"mcp__codegraph__*", "Bash(git diff *)", "Edit(.devrites/**)"} {
245
- if !strings.Contains(settings, managed) {
246
- t.Fatalf("Claude settings merge missed managed rule %q:\n%s", managed, settings)
247
- }
248
- }
249
- if strings.Contains(settings, "retired-cmd") {
250
- t.Fatalf("Claude settings merge kept a retired engine rule:\n%s", settings)
251
- }
252
- if strings.Contains(settings, "devrites-engine hook") {
253
- t.Fatalf("installer retained a legacy DevRites hook:\n%s", settings)
254
- }
255
- manifest := testutil.ReadFile(t, filepath.Join(target, ManifestName))
256
- if !strings.Contains(manifest, "\n.claude/devrites.claude-hooks-merge\n") {
257
- t.Fatalf("Claude settings merge marker missing:\n%s", manifest)
258
- }
259
- marker := testutil.ReadFile(t, filepath.Join(target, ".claude", "devrites.claude-hooks-merge"))
260
- if !strings.Contains(marker, "default-mode=preexisting") {
261
- t.Fatalf("Claude settings marker lost pre-existing plan-mode ownership:\n%s", marker)
262
- }
263
-
264
- runUninstall(t, target)
265
- settings = testutil.ReadFile(t, filepath.Join(target, ".claude", "settings.json"))
266
- for _, preserved := range []string{"keep my local notes", `"theme": "dark"`, "echo user-status", "echo user-stop", "Bash(user-tool *)", "Bash(git checkout *)", "Bash(rm *)", `"defaultMode": "plan"`} {
267
- if !strings.Contains(settings, preserved) {
268
- t.Fatalf("Claude settings uninstall lost user content %q:\n%s", preserved, settings)
269
- }
270
- }
271
- if strings.Contains(settings, "Bash(devrites-engine ") || strings.Contains(settings, "devrites-engine hook") {
272
- t.Fatalf("Claude settings uninstall left DevRites configuration:\n%s", settings)
273
- }
274
- for _, managed := range []string{"mcp__codegraph__*", "Bash(git diff *)", "Edit(.devrites/**)"} {
275
- if strings.Contains(settings, managed) {
276
- t.Fatalf("Claude settings uninstall left managed non-engine rule %q:\n%s", managed, settings)
277
- }
278
- }
279
- }
280
-
281
- func TestInstallKeepsClaudeMergeOwnershipAcrossReinstall(t *testing.T) {
282
- t.Setenv("DEVRITES_NO_BINARY", "1")
283
- payload := testPayload(t)
284
- target := t.TempDir()
285
- settingsPath := filepath.Join(target, ".claude", "settings.json")
286
- testutil.WriteFile(t, settingsPath, "{}\n")
287
-
288
- runInstall(t, target, payload, func(o *Options) {})
289
- markerRel := ".claude/devrites.claude-hooks-merge"
290
- if !exists(filepath.Join(target, filepath.FromSlash(markerRel))) {
291
- t.Fatal("first install did not create the Claude settings merge marker")
292
- }
293
- manifestRecords, err := readManifest(filepath.Join(target, ManifestName))
294
- if err != nil {
295
- t.Fatal(err)
296
- }
297
- if _, ok := manifestRecords[markerRel]; !ok {
298
- t.Fatalf("first install did not record the Claude hook merge marker:\n%s", testutil.ReadFile(t, filepath.Join(target, ManifestName)))
299
- }
300
- runInstall(t, target, payload, func(o *Options) {})
301
-
302
- settings := testutil.ReadFile(t, settingsPath)
303
- if strings.Count(settings, `"defaultMode": "plan"`) != 1 ||
304
- strings.Count(settings, "Bash(devrites-engine check readiness *)") != 1 {
305
- t.Fatalf("Claude permissions lost merge ownership on reinstall (marker exists: %t):\nsettings:\n%s\nmanifest:\n%s",
306
- exists(filepath.Join(target, filepath.FromSlash(markerRel))), settings, testutil.ReadFile(t, filepath.Join(target, ManifestName)))
307
- }
308
- if marker := testutil.ReadFile(t, filepath.Join(target, filepath.FromSlash(markerRel))); !strings.Contains(marker, "default-mode=added") {
309
- t.Fatalf("Claude settings marker did not record owned plan mode:\n%s", marker)
310
- }
311
- manifest := testutil.ReadFile(t, filepath.Join(target, ManifestName))
312
- if !strings.Contains(manifest, "\n.claude/devrites.claude-hooks-merge\n") {
313
- t.Fatalf("Claude settings merge marker missing after reinstall:\n%s", manifest)
314
- }
315
-
316
- runUninstall(t, target)
317
- if got := testutil.ReadFile(t, settingsPath); got != "{}\n" {
318
- t.Fatalf("uninstall did not preserve the pre-existing empty settings file: %q", got)
319
- }
320
- }
321
-
322
- func TestInstallRejectsConflictingClaudeDefaultMode(t *testing.T) {
323
- t.Setenv("DEVRITES_NO_BINARY", "1")
324
- payload := testPayload(t)
325
- target := t.TempDir()
326
- settingsPath := filepath.Join(target, ".claude", "settings.json")
327
- original := `{"permissions":{"defaultMode":"acceptEdits"},"theme":"dark"}` + "\n"
328
- testutil.WriteFile(t, settingsPath, original)
329
-
330
- err := applyInstall(target, payload, false, false, &bytes.Buffer{})
331
- if err == nil || !strings.Contains(err.Error(), "requires plan mode") {
332
- t.Fatalf("install error = %v, want conflicting default-mode rejection", err)
333
- }
334
- if got := testutil.ReadFile(t, settingsPath); got != original {
335
- t.Fatalf("failed install changed Claude settings:\n%s", got)
336
- }
337
- if exists(filepath.Join(target, ".claude", "devrites.claude-hooks-merge")) {
338
- t.Fatal("failed install created a Claude settings marker")
339
- }
340
- }
341
-
342
- func TestGeneratedPayloadInstallsVerbatim(t *testing.T) {
343
- t.Setenv("DEVRITES_NO_BINARY", "1")
344
- payload := testPayload(t)
345
- target := t.TempDir()
346
- sentinel := "generated sentinel"
347
- testutil.WriteFile(t, filepath.Join(payload, "codex", "skills", "rite", "SKILL.md"), sentinel+"\n")
348
- testutil.WriteFile(t, filepath.Join(payload, "claude", "workflows", "devrites-readonly-review.js"), sentinel+"\n")
349
-
350
- runInstall(t, target, payload, func(o *Options) {})
351
- if got := testutil.ReadFile(t, filepath.Join(target, ".agents", "skills", "rite", "SKILL.md")); got != sentinel+"\n" {
352
- t.Fatalf("codex payload was not installed verbatim: %q", got)
353
- }
354
- if got := testutil.ReadFile(t, filepath.Join(target, ".claude", "workflows", "devrites-readonly-review.js")); got != sentinel+"\n" {
355
- t.Fatalf("Claude workflow payload was not installed verbatim: %q", got)
356
- }
357
- }
358
-
359
- func TestInstallBinaryUsesEngineHandoff(t *testing.T) {
360
- t.Setenv("DEVRITES_NO_BINARY", "")
361
- payload := testPayload(t)
362
- target := t.TempDir()
363
- binDir := t.TempDir()
364
- engine := buildVersionBinary(t, "1.2.3")
365
- engineBody, err := os.ReadFile(engine)
366
- if err != nil {
367
- t.Fatal(err)
368
- }
369
- t.Setenv("DEVRITES_ENGINE_CLI", engine)
370
- t.Setenv("DEVRITES_BIN_DIR", binDir)
371
- t.Setenv("DEVRITES_REF", "v1.2.3")
372
- conflictDir := t.TempDir()
373
- testutil.WriteExecutable(t, filepath.Join(conflictDir, engineBinaryName()), "conflict\n")
374
- t.Setenv("PATH", conflictDir)
375
-
376
- runInstall(t, target, payload, func(o *Options) {})
377
-
378
- installed := filepath.Join(binDir, engineBinaryName())
379
- if got, err := os.ReadFile(installed); err != nil || !bytes.Equal(got, engineBody) {
380
- t.Fatalf("installed binary did not come from DEVRITES_ENGINE_CLI handoff: %v", err)
381
- }
382
- info, err := os.Stat(installed)
383
- if err != nil {
384
- t.Fatal(err)
385
- }
386
- if runtime.GOOS != "windows" && info.Mode()&0o111 == 0 {
387
- t.Fatalf("installed binary is not executable: %v", info.Mode())
388
- }
389
- }
390
-
391
- func TestAcquireBinaryRequiresCompatibleEngineHandoff(t *testing.T) {
392
- engine := buildVersionBinary(t, "1.2.2")
393
- t.Setenv("DEVRITES_ENGINE_CLI", engine)
394
-
395
- r := runner{}
396
- _, cleanup, err := r.acquireBinary("v1.2.3")
397
- defer cleanup()
398
- if err == nil || !strings.Contains(err.Error(), "version mismatch") {
399
- t.Fatalf("acquireBinary error = %v, want handoff version mismatch", err)
400
- }
401
- }
402
-
403
- func TestInstallBinaryRejectsIncompatibleEngineHandoff(t *testing.T) {
404
- t.Setenv("DEVRITES_NO_BINARY", "")
405
- engine := buildVersionBinary(t, "1.2.2")
406
- t.Setenv("DEVRITES_ENGINE_CLI", engine)
407
- t.Setenv("DEVRITES_BIN_DIR", t.TempDir())
408
- t.Setenv("DEVRITES_REF", "v1.2.3")
409
-
410
- r := runner{opts: DefaultOptions(ModeInstall)}
411
- if err := r.installBinary(); err == nil || !strings.Contains(err.Error(), "version mismatch") {
412
- t.Fatalf("installBinary error = %v, want handoff version mismatch", err)
413
- }
414
- }
415
-
416
- func TestInstallBinaryRejectsMissingConfiguredEngineHandoff(t *testing.T) {
417
- t.Setenv("DEVRITES_NO_BINARY", "")
418
- handoff := filepath.Join(t.TempDir(), "missing-devrites-engine")
419
- t.Setenv("DEVRITES_ENGINE_CLI", handoff)
420
- t.Setenv("DEVRITES_BIN_DIR", t.TempDir())
421
- t.Setenv("DEVRITES_REF", "v1.2.3")
422
-
423
- r := runner{opts: DefaultOptions(ModeInstall)}
424
- if err := r.installBinary(); err == nil || !strings.Contains(err.Error(), handoff) {
425
- t.Fatalf("installBinary error = %v, want missing configured handoff error", err)
426
- }
427
- }
428
-
429
- func TestAcquireBinaryRejectsMissingEngineHandoff(t *testing.T) {
430
- t.Setenv("DEVRITES_ENGINE_CLI", "")
431
-
432
- r := runner{}
433
- _, cleanup, err := r.acquireBinary("v1.2.3")
434
- defer cleanup()
435
- if err == nil || !strings.Contains(err.Error(), "DEVRITES_ENGINE_CLI") {
436
- t.Fatalf("acquireBinary error = %v, want missing handoff error", err)
437
- }
438
- }
439
-
440
- func TestInstallBinaryFailsWhenPreparedUpdateCannotBeWritten(t *testing.T) {
441
- t.Setenv("DEVRITES_NO_BINARY", "")
442
- prepared := filepath.Join(t.TempDir(), "devrites-engine")
443
- testutil.WriteExecutable(t, prepared, "#!/bin/sh\nif [ \"$1\" = version ]; then echo 1.2.3; fi\n")
444
- blocked := filepath.Join(t.TempDir(), "not-a-directory")
445
- testutil.WriteFile(t, blocked, "file\n")
446
- t.Setenv("DEVRITES_BIN_DIR", filepath.Join(blocked, "bin"))
447
- t.Setenv("DEVRITES_REF", "v1.2.3")
448
-
449
- r := runner{
450
- opts: DefaultOptions(ModeInstall),
451
- preparedBinary: prepared,
452
- }
453
- if err := r.installBinary(); err == nil {
454
- t.Fatal("installBinary accepted a prepared update it could not write")
455
- }
456
- }
457
-
458
- func TestInstallBinaryWithoutBinaryReportsSkip(t *testing.T) {
459
- t.Setenv("DEVRITES_NO_BINARY", "")
460
- var stdout bytes.Buffer
461
- opts := DefaultOptions(ModeInstall)
462
- opts.WithBinary = false
463
- opts.Stdout = &stdout
464
-
465
- r := runner{opts: opts}
466
- if err := r.installBinary(); err != nil {
467
- t.Fatalf("installBinary() error = %v", err)
468
- }
469
-
470
- output := stdout.String()
471
- if output != " engine binary: skipped (--no-binary).\n" {
472
- t.Fatalf("stdout = %q, want binary skip diagnostic", output)
473
- }
474
- }
475
-
476
- func TestInstallBinaryEnvSkipNamesEnvVar(t *testing.T) {
477
- t.Setenv("DEVRITES_NO_BINARY", "1")
478
- var stdout bytes.Buffer
479
- opts := DefaultOptions(ModeInstall)
480
- opts.WithBinary = true
481
- opts.Stdout = &stdout
482
-
483
- r := runner{opts: opts}
484
- if err := r.installBinary(); err != nil {
485
- t.Fatalf("installBinary() error = %v", err)
486
- }
487
-
488
- if got, want := stdout.String(), " engine binary: skipped (DEVRITES_NO_BINARY=1).\n"; got != want {
489
- t.Fatalf("stdout = %q, want %q", got, want)
490
- }
491
- }
492
-
493
- func TestInstallBinaryDoesNotRequirePATH(t *testing.T) {
494
- payload := testPayload(t)
495
- target := t.TempDir()
496
- binDir := t.TempDir()
497
- engine := buildVersionBinary(t, "1.2.3")
498
- pathDir := t.TempDir()
499
- var stderr bytes.Buffer
500
-
501
- t.Setenv("DEVRITES_ENGINE_CLI", engine)
502
- t.Setenv("DEVRITES_BIN_DIR", binDir)
503
- t.Setenv("DEVRITES_REF", "v1.2.3")
504
- t.Setenv("PATH", pathDir)
505
-
506
- runInstall(t, target, payload, func(o *Options) {
507
- o.Stderr = &stderr
508
- })
509
-
510
- if stderr.Len() != 0 {
511
- t.Fatalf("stderr = %q, want no PATH warning", stderr.String())
512
- }
513
- }
514
-
515
- func TestBinaryInstallFailureWarnsAndContinues(t *testing.T) {
516
- t.Setenv("DEVRITES_ENGINE_CLI", "")
517
- var stderr bytes.Buffer
518
- opts := DefaultOptions(ModeInstall)
519
- opts.Stderr = &stderr
520
-
521
- r := runner{opts: opts}
522
- if err := r.binaryInstallFailure(fmt.Errorf("handoff unavailable")); err != nil {
523
- t.Fatalf("binaryInstallFailure() error = %v, want nil", err)
524
- }
525
-
526
- warning := stderr.String()
527
- if !strings.Contains(warning, "handoff unavailable") {
528
- t.Fatalf("warning = %q, want handoff failure", warning)
529
- }
530
- if !strings.Contains(warning, "continuing without it") {
531
- t.Fatalf("warning = %q, want non-blocking fallback", warning)
532
- }
533
- }
534
-
535
- func buildVersionBinary(t *testing.T, version string) string {
536
- t.Helper()
537
- dir := t.TempDir()
538
- source := filepath.Join(dir, "main.go")
539
- testutil.WriteFile(t, source, fmt.Sprintf("package main\nimport \"fmt\"\nfunc main() { fmt.Println(%q) }\n", version))
540
- binary := filepath.Join(dir, engineBinaryName())
541
- if out, err := exec.Command("go", "build", "-o", binary, source).CombinedOutput(); err != nil {
542
- t.Fatalf("build test engine: %v\n%s", err, out)
543
- }
544
- return binary
545
- }
546
-
547
- func buildUpdateHandoffBinary(t *testing.T, version string) string {
548
- t.Helper()
549
- dir := t.TempDir()
550
- source := filepath.Join(dir, "main.go")
551
- testutil.WriteFile(t, source, fmt.Sprintf(`package main
552
- import (
553
- "fmt"
554
- "os"
555
- "strings"
556
- )
557
- func main() {
558
- if len(os.Args) == 2 && os.Args[1] == "version" {
559
- fmt.Println(%q)
560
- return
561
- }
562
- output := strings.Join(os.Args[1:], "\n") + "\n" +
563
- "DEVRITES_UPDATE_HANDOFF=" + os.Getenv("DEVRITES_UPDATE_HANDOFF") + "\n" +
564
- "DEVRITES_ENGINE_CLI=" + os.Getenv("DEVRITES_ENGINE_CLI") + "\n"
565
- if err := os.WriteFile(os.Getenv("DEVRITES_TEST_UPDATE_HANDOFF"), []byte(output), 0600); err != nil {
566
- panic(err)
567
- }
568
- }
569
- `, version))
570
- binary := filepath.Join(dir, engineBinaryName())
571
- if out, err := exec.Command("go", "build", "-o", binary, source).CombinedOutput(); err != nil {
572
- t.Fatalf("build update handoff engine: %v\n%s", err, out)
573
- }
574
- return binary
575
- }
576
-
577
- func TestBinaryCandidatesSkipsUnsetBinDir(t *testing.T) {
578
- t.Setenv("DEVRITES_BIN_DIR", "")
579
-
580
- for _, candidate := range binaryCandidates() {
581
- if candidate == "devrites-engine" {
582
- t.Fatalf("unset DEVRITES_BIN_DIR produced relative candidate %q", candidate)
583
- }
584
- }
585
- }
586
-
587
- func TestUpdateRejectsRemovedAcquisitionFlags(t *testing.T) {
588
- for _, arg := range []string{"--to=v2.0.0", "--pre"} {
589
- t.Run(arg, func(t *testing.T) {
590
- var stderr bytes.Buffer
591
- if code := Run([]string{arg}, &bytes.Buffer{}, &stderr, ModeUpdate); code != 2 {
592
- t.Fatalf("Run(%q) code = %d, want 2", arg, code)
593
- }
594
- if !strings.Contains(stderr.String(), "flag provided but not defined") {
595
- t.Fatalf("Run(%q) stderr = %q, want unknown flag error", arg, stderr.String())
596
- }
597
- })
598
- }
599
- }
600
-
601
- func TestUpdateInstallsLocalPayload(t *testing.T) {
602
- t.Setenv("DEVRITES_NO_BINARY", "1")
603
- oldPayload := testPayload(t)
604
- target := t.TempDir()
605
- oldSource := testSource(t, "1.0.0")
606
- runInstall(t, target, oldPayload, func(o *Options) {
607
- o.SourceDir = oldSource
608
- o.WithAgents = false
609
- })
610
-
611
- source := testSource(t, "2.0.0")
612
- payload := filepath.Join(t.TempDir(), "generated")
613
- writeTestPayload(t, payload)
614
- testutil.WriteFile(t, filepath.Join(payload, "codex", "skills", "rite", "SKILL.md"), "updated rite\n")
615
-
616
- opts := DefaultOptions(ModeUpdate)
617
- opts.Target = target
618
- opts.SourceDir = source
619
- opts.PayloadDir = payload
620
- opts.Stdout = &bytes.Buffer{}
621
- opts.Stderr = &bytes.Buffer{}
622
- check := opts
623
- check.UpdateCheck = true
624
- check.PayloadDir = ""
625
- if err := Apply(check); err == nil || !strings.Contains(err.Error(), "update available: 1.0.0 -> 2.0.0") {
626
- t.Fatalf("update check error = %v, want local candidate report", err)
627
- }
628
- if got := manifestHeader(filepath.Join(target, ManifestName), "devrites-version"); got != "1.0.0" {
629
- t.Fatalf("update check changed manifest version to %q", got)
630
- }
631
- if err := Apply(opts); err != nil {
632
- t.Fatal(err)
633
- }
634
-
635
- if got := testutil.ReadFile(t, filepath.Join(target, ".agents", "skills", "rite", "SKILL.md")); got != "updated rite\n" {
636
- t.Fatalf("update did not install local payload: %q", got)
637
- }
638
- if got := manifestHeader(filepath.Join(target, ManifestName), "devrites-version"); got != "2.0.0" {
639
- t.Fatalf("manifest version = %q, want 2.0.0", got)
640
- }
641
- if exists(filepath.Join(target, ".claude", "agents")) {
642
- t.Fatal("update did not replay --no-agents from the manifest")
643
- }
644
- }
645
-
646
- func TestUpdateAcquiresLatestReleaseAndHandsOff(t *testing.T) {
647
- t.Setenv("DEVRITES_NO_BINARY", "1")
648
- target := t.TempDir()
649
- runInstall(t, target, testPayload(t), func(o *Options) {
650
- o.SourceDir = testSource(t, "1.0.0")
651
- })
652
- source := testSource(t, "2.0.0")
653
- payload := testPayload(t)
654
- engine := buildUpdateHandoffBinary(t, "2.0.0")
655
- marker := filepath.Join(t.TempDir(), "handoff.txt")
656
- t.Setenv("DEVRITES_TEST_UPDATE_HANDOFF", marker)
657
-
658
- oldResolve, oldAcquire := resolveLatestRelease, acquireRelease
659
- resolveLatestRelease = func(context.Context, string) (string, error) { return "v2.0.0", nil }
660
- cleaned := false
661
- acquireRelease = func(context.Context, string, string) (release.Candidate, func(), error) {
662
- return release.Candidate{
663
- SourceDir: source,
664
- PayloadDir: payload,
665
- EnginePath: engine,
666
- BundleURL: "https://example.invalid/devrites-v2.0.0.tar.gz",
667
- }, func() { cleaned = true }, nil
668
- }
669
- t.Cleanup(func() {
670
- resolveLatestRelease, acquireRelease = oldResolve, oldAcquire
671
- })
672
-
673
- var stdout, stderr bytes.Buffer
674
- opts := DefaultOptions(ModeUpdate)
675
- opts.Target = target
676
- opts.Stdout = &stdout
677
- opts.Stderr = &stderr
678
- if err := Apply(opts); err != nil {
679
- t.Fatalf("remote update: %v\n%s", err, stderr.String())
680
- }
681
- if !cleaned {
682
- t.Fatal("remote update did not clean acquired release")
683
- }
684
- handoff := testutil.ReadFile(t, marker)
685
- for _, want := range []string{
686
- "update\n",
687
- "--target\n" + target + "\n",
688
- "--source-dir\n" + source + "\n",
689
- "--payload-dir\n" + payload + "\n",
690
- "DEVRITES_UPDATE_HANDOFF=1\n",
691
- "DEVRITES_ENGINE_CLI=" + engine + "\n",
692
- } {
693
- if !strings.Contains(handoff, want) {
694
- t.Fatalf("handoff missing %q:\n%s", want, handoff)
695
- }
696
- }
697
- if !strings.Contains(stdout.String(), "latest: 2.0.0") || !strings.Contains(stdout.String(), "bundle: https://example.invalid/") {
698
- t.Fatalf("remote update output:\n%s", stdout.String())
699
- }
700
- }
701
-
702
- func TestRemoteUpdateCheckDoesNotDownload(t *testing.T) {
703
- target := t.TempDir()
704
- testutil.WriteFile(t, filepath.Join(target, ManifestName), "# devrites-version: 1.0.0\n")
705
- oldResolve, oldAcquire := resolveLatestRelease, acquireRelease
706
- resolveLatestRelease = func(context.Context, string) (string, error) { return "v2.0.0", nil }
707
- acquireRelease = func(context.Context, string, string) (release.Candidate, func(), error) {
708
- t.Fatal("--check downloaded release assets")
709
- return release.Candidate{}, func() {}, nil
710
- }
711
- t.Cleanup(func() {
712
- resolveLatestRelease, acquireRelease = oldResolve, oldAcquire
713
- })
714
- opts := DefaultOptions(ModeUpdate)
715
- opts.Target = target
716
- opts.UpdateCheck = true
717
- opts.Stdout = &bytes.Buffer{}
718
- opts.Stderr = &bytes.Buffer{}
719
- if err := Apply(opts); err == nil || !strings.Contains(err.Error(), "update available: 1.0.0 -> 2.0.0") {
720
- t.Fatalf("remote update check error = %v", err)
721
- }
722
- }
723
-
724
- func TestUpdateCleansLegacyCodexHooks(t *testing.T) {
725
- t.Setenv("DEVRITES_NO_BINARY", "1")
726
- payload := testPayload(t)
727
- source := testSource(t, "4.0.0")
728
-
729
- t.Run("preserves user hooks", func(t *testing.T) {
730
- target := t.TempDir()
731
- hooks := `{
732
- "$comment": "DevRites hooks for Codex. Project hooks load only after trust.",
733
- "hooks": {"Stop":[{"hooks":[
734
- {"type":"command","command":"devrites-engine hook stop-gate --harness=codex"},
735
- {"type":"command","command":"echo user"}
736
- ]}]}
737
- }` + "\n"
738
- seedLegacyCodexHooksInstall(t, target, hooks)
739
-
740
- runLocalUpdate(t, target, source, payload)
741
-
742
- if exists(filepath.Join(target, ".claude", "devrites.codex-hooks-merge")) {
743
- t.Fatal("update kept the legacy Codex hooks marker")
744
- }
745
- got := testutil.ReadFile(t, filepath.Join(target, ".codex", "hooks.json"))
746
- if strings.Contains(got, "devrites-engine hook") || strings.Contains(got, "DevRites hooks") || !strings.Contains(got, "echo user") {
747
- t.Fatalf("legacy Codex hook cleanup mismatch:\n%s", got)
748
- }
749
- })
750
-
751
- t.Run("removes empty hooks file", func(t *testing.T) {
752
- target := t.TempDir()
753
- hooks := `{"hooks":{"Stop":[{"hooks":[{"type":"command","command":"devrites-engine hook stop-gate --harness=codex"}]}]}}` + "\n"
754
- seedLegacyCodexHooksInstall(t, target, hooks)
755
-
756
- runLocalUpdate(t, target, source, payload)
757
-
758
- if exists(filepath.Join(target, ".codex", "hooks.json")) {
759
- t.Fatal("update kept an empty legacy Codex hooks file")
760
- }
761
- })
762
-
763
- t.Run("preserves unrecognized user hook shape", func(t *testing.T) {
764
- target := t.TempDir()
765
- seedLegacyCodexHooksInstall(t, target, `{"hooks":"user-defined"}`+"\n")
766
-
767
- runLocalUpdate(t, target, source, payload)
768
-
769
- got := testutil.ReadFile(t, filepath.Join(target, ".codex", "hooks.json"))
770
- if !strings.Contains(got, `"hooks": "user-defined"`) {
771
- t.Fatalf("legacy cleanup removed unrecognized user hooks: %s", got)
772
- }
773
- })
774
-
775
- t.Run("malformed hooks keep ownership marker", func(t *testing.T) {
776
- target := t.TempDir()
777
- seedLegacyCodexHooksInstall(t, target, "{\n")
778
-
779
- err := applyUpdate(target, source, payload)
780
- if err == nil || !strings.Contains(err.Error(), "load hooks config") {
781
- t.Fatalf("update error = %v, want malformed legacy hooks error", err)
782
- }
783
- if !exists(filepath.Join(target, ".claude", "devrites.codex-hooks-merge")) {
784
- t.Fatal("failed cleanup removed the legacy ownership marker")
785
- }
786
- if got := testutil.ReadFile(t, filepath.Join(target, ".codex", "hooks.json")); got != "{\n" {
787
- t.Fatalf("failed cleanup changed hooks: %q", got)
788
- }
789
- })
790
-
791
- t.Run("uninstall preserves user hooks", func(t *testing.T) {
792
- target := t.TempDir()
793
- hooks := `{"hooks":{"Stop":[{"hooks":[{"type":"command","command":"devrites-engine hook stop-gate"},{"type":"command","command":"echo user"}]}]}}` + "\n"
794
- seedLegacyCodexHooksInstall(t, target, hooks)
795
-
796
- runUninstall(t, target)
797
-
798
- got := testutil.ReadFile(t, filepath.Join(target, ".codex", "hooks.json"))
799
- if strings.Contains(got, "devrites-engine hook") || !strings.Contains(got, "echo user") {
800
- t.Fatalf("legacy Codex hook uninstall mismatch:\n%s", got)
801
- }
802
- })
803
- }
804
-
805
- func TestLegacyCodexHooksMergeIsCleanupOnly(t *testing.T) {
806
- legacy := hostpack.LegacyCodexHooksMerge
807
- merge, ok := hostpack.ManagedMergeForMarker(legacy.MarkerRel)
808
- if !ok || merge != legacy {
809
- t.Fatalf("legacy marker lookup = %#v, %t", merge, ok)
810
- }
811
- if slices.Contains(hostpack.RequiredPayload(true, true, true, true), "codex/hooks.json") {
812
- t.Fatal("legacy Codex hooks entered the required payload")
813
- }
814
- r := runner{opts: DefaultOptions(ModeInstall), payloadFS: os.DirFS(testPayload(t))}
815
- desired, err := r.desiredInstallPaths()
816
- if err != nil {
817
- t.Fatal(err)
818
- }
819
- if desired[legacy.MarkerRel] || desired[legacy.TargetRel] || slices.Contains(r.installMergeTargets(), legacy.TargetRel) {
820
- t.Fatalf("legacy Codex hooks entered active install paths: desired=%v merge-targets=%v", desired, r.installMergeTargets())
821
- }
822
- }
823
-
824
- func TestUpdateRejectsMissingGeneratedPayload(t *testing.T) {
825
- t.Setenv("DEVRITES_NO_BINARY", "1")
826
- oldPayload := testPayload(t)
827
- target := t.TempDir()
828
- runInstall(t, target, oldPayload, func(o *Options) { o.SourceDir = testSource(t, "1.0.0") })
829
-
830
- source := testSource(t, "2.1.0")
831
- payload := filepath.Join(t.TempDir(), "generated")
832
- builderMarker := filepath.Join(t.TempDir(), "builder-invoked")
833
- t.Setenv("DEVRITES_TEST_BUILDER_MARKER", builderMarker)
834
- testutil.WriteExecutable(t, filepath.Join(source, "scripts", "build-host-artifacts.sh"), `#!/usr/bin/env bash
835
- touch "${DEVRITES_TEST_BUILDER_MARKER:?}"
836
- exit 42
837
- `)
838
-
839
- opts := DefaultOptions(ModeUpdate)
840
- opts.Target = target
841
- opts.SourceDir = source
842
- opts.PayloadDir = payload
843
- opts.Stdout = &bytes.Buffer{}
844
- opts.Stderr = &bytes.Buffer{}
845
- err := Apply(opts)
846
- if err == nil ||
847
- !strings.Contains(err.Error(), "local update payload") ||
848
- !strings.Contains(err.Error(), "missing claude/skills") ||
849
- !strings.Contains(err.Error(), payload) {
850
- t.Fatalf("update error = %v, want actionable missing payload error", err)
851
- }
852
- if exists(builderMarker) {
853
- t.Fatal("update invoked the host artifact builder")
854
- }
855
- if got := testutil.ReadFile(t, filepath.Join(target, ".agents", "skills", "rite", "SKILL.md")); got != "codex rite\n" {
856
- t.Fatalf("rejected update changed installed payload: %q", got)
857
- }
858
- if got := manifestHeader(filepath.Join(target, ManifestName), "devrites-version"); got != "1.0.0" {
859
- t.Fatalf("manifest version = %q, want 1.0.0 after rejected update", got)
860
- }
861
- }
862
-
863
- func TestManagedFilePolicy(t *testing.T) {
864
- t.Setenv("DEVRITES_NO_BINARY", "1")
865
- const rel = ".claude/skills/rite/SKILL.md"
866
-
867
- t.Run("refresh preserves customized unless forced", func(t *testing.T) {
868
- payload := testPayload(t)
869
- target := t.TempDir()
870
- runInstall(t, target, payload, func(o *Options) {})
871
- dest := filepath.Join(target, filepath.FromSlash(rel))
872
- testutil.WriteFile(t, dest, "customized\n")
873
- testutil.WriteFile(t, filepath.Join(payload, "claude", "skills", "rite", "SKILL.md"), "new\n")
874
-
875
- err := applyInstall(target, payload, false, false, nil)
876
- if err == nil || !strings.Contains(err.Error(), "--force") {
877
- t.Fatalf("customized refresh error = %v, want --force remediation", err)
878
- }
879
- if got := testutil.ReadFile(t, dest); got != "customized\n" {
880
- t.Fatalf("default refresh changed customized file: %q", got)
881
- }
882
-
883
- var out bytes.Buffer
884
- if err := applyInstall(target, payload, true, true, &out); err != nil {
885
- t.Fatal(err)
886
- }
887
- if !strings.Contains(out.String(), "[overwrite(force-customized)] "+rel) {
888
- t.Fatalf("forced dry-run did not predict overwrite:\n%s", out.String())
889
- }
890
- if got := testutil.ReadFile(t, dest); got != "customized\n" {
891
- t.Fatalf("dry-run changed customized file: %q", got)
892
- }
893
-
894
- if err := applyInstall(target, payload, true, false, nil); err != nil {
895
- t.Fatal(err)
896
- }
897
- if got := testutil.ReadFile(t, dest); got != "new\n" {
898
- t.Fatalf("forced refresh = %q, want new payload", got)
899
- }
900
- })
901
-
902
- t.Run("legacy manifest requires force", func(t *testing.T) {
903
- payload := testPayload(t)
904
- target := t.TempDir()
905
- runInstall(t, target, payload, func(o *Options) {})
906
- manifest := filepath.Join(target, ManifestName)
907
- lines := strings.Split(testutil.ReadFile(t, manifest), "\n")
908
- var next []string
909
- for _, line := range lines {
910
- if !strings.HasPrefix(line, "# managed: "+rel+" ") {
911
- next = append(next, line)
912
- }
913
- }
914
- testutil.WriteFile(t, manifest, strings.Join(next, "\n"))
915
-
916
- err := applyInstall(target, payload, false, false, nil)
917
- if err == nil || !strings.Contains(err.Error(), "legacy manifest entry") {
918
- t.Fatalf("legacy refresh error = %v", err)
919
- }
920
- if err := applyInstall(target, payload, true, false, nil); err != nil {
921
- t.Fatalf("forced legacy refresh: %v", err)
922
- }
923
- })
924
-
925
- t.Run("missing is recreated and absent uninstall is a no-op", func(t *testing.T) {
926
- payload := testPayload(t)
927
- target := t.TempDir()
928
- runInstall(t, target, payload, func(o *Options) {})
929
- dest := filepath.Join(target, filepath.FromSlash(rel))
930
- if err := os.Remove(dest); err != nil {
931
- t.Fatal(err)
932
- }
933
- if err := applyInstall(target, payload, false, false, nil); err != nil {
934
- t.Fatal(err)
935
- }
936
- if !exists(dest) {
937
- t.Fatal("refresh did not recreate missing managed file")
938
- }
939
- if err := os.Remove(dest); err != nil {
940
- t.Fatal(err)
941
- }
942
- runUninstall(t, target)
943
- })
944
-
945
- t.Run("foreign is preserved unless forced", func(t *testing.T) {
946
- payload := testPayload(t)
947
- target := t.TempDir()
948
- dest := filepath.Join(target, filepath.FromSlash(rel))
949
- testutil.WriteFile(t, dest, "mine\n")
950
- runInstall(t, target, payload, func(o *Options) {})
951
- if got := testutil.ReadFile(t, dest); got != "mine\n" {
952
- t.Fatalf("default install overwrote foreign file: %q", got)
953
- }
954
- if err := applyInstall(target, payload, true, false, nil); err != nil {
955
- t.Fatal(err)
956
- }
957
- if got := testutil.ReadFile(t, dest); got != "claude rite\n" {
958
- t.Fatalf("forced install did not replace foreign file: %q", got)
959
- }
960
- })
961
- }
962
-
963
- func TestManagedPruneAndUninstallPolicy(t *testing.T) {
964
- t.Setenv("DEVRITES_NO_BINARY", "1")
965
- const staleRel = ".claude/skills/stale/SKILL.md"
966
-
967
- t.Run("prune", func(t *testing.T) {
968
- payload := testPayload(t)
969
- target := t.TempDir()
970
- runInstall(t, target, payload, func(o *Options) {})
971
- stale := filepath.Join(target, filepath.FromSlash(staleRel))
972
- testutil.WriteFile(t, stale, "owned\n")
973
- addManifestRecord(t, filepath.Join(target, ManifestName), staleRel, []byte("owned\n"))
974
- testutil.WriteFile(t, stale, "customized\n")
975
-
976
- err := applyInstall(target, payload, false, false, nil)
977
- if err == nil || !strings.Contains(err.Error(), staleRel) || !exists(stale) {
978
- t.Fatalf("default prune error = %v, exists = %t", err, exists(stale))
979
- }
980
- var out bytes.Buffer
981
- if err := applyInstall(target, payload, true, true, &out); err != nil {
982
- t.Fatal(err)
983
- }
984
- if !strings.Contains(out.String(), "[prune(force-customized)] "+staleRel) || !exists(stale) {
985
- t.Fatalf("forced prune dry-run mismatch:\n%s", out.String())
986
- }
987
- if err := applyInstall(target, payload, true, false, nil); err != nil {
988
- t.Fatal(err)
989
- }
990
- if exists(stale) {
991
- t.Fatal("forced prune kept customized dropped file")
992
- }
993
- })
994
-
995
- t.Run("uninstall", func(t *testing.T) {
996
- payload := testPayload(t)
997
- target := t.TempDir()
998
- runInstall(t, target, payload, func(o *Options) {})
999
- dest := filepath.Join(target, ".claude", "skills", "rite", "SKILL.md")
1000
- testutil.WriteFile(t, dest, "customized\n")
1001
-
1002
- opts := DefaultOptions(ModeUninstall)
1003
- opts.Target = target
1004
- opts.KeepBinary = true
1005
- opts.Stdout = &bytes.Buffer{}
1006
- opts.Stderr = &bytes.Buffer{}
1007
- err := Apply(opts)
1008
- if err == nil || !strings.Contains(err.Error(), "--force") || !exists(dest) {
1009
- t.Fatalf("default uninstall error = %v, exists = %t", err, exists(dest))
1010
- }
1011
-
1012
- var out bytes.Buffer
1013
- opts.Force = true
1014
- opts.DryRun = true
1015
- opts.Stdout = &out
1016
- if err := Apply(opts); err != nil {
1017
- t.Fatal(err)
1018
- }
1019
- if !strings.Contains(out.String(), "[remove(force-customized)] .claude/skills/rite/SKILL.md") || !exists(dest) {
1020
- t.Fatalf("forced uninstall dry-run mismatch:\n%s", out.String())
1021
- }
1022
- opts.DryRun = false
1023
- if err := Apply(opts); err != nil {
1024
- t.Fatal(err)
1025
- }
1026
- if exists(dest) {
1027
- t.Fatal("forced uninstall kept customized managed file")
1028
- }
1029
- })
1030
- }
1031
-
1032
- func TestManagedPathsRejectLinksAndRaces(t *testing.T) {
1033
- t.Setenv("DEVRITES_NO_BINARY", "1")
1034
- if runtime.GOOS == "windows" {
1035
- t.Skip("creating symlinks requires privileges on Windows; the same confinement code runs there")
1036
- }
1037
- for _, tc := range []struct {
1038
- name string
1039
- link func(target, outside string) error
1040
- }{
1041
- {
1042
- name: "final symlink",
1043
- link: func(target, outside string) error {
1044
- dest := filepath.Join(target, ".claude", "skills", "rite", "SKILL.md")
1045
- if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
1046
- return err
1047
- }
1048
- return os.Symlink(filepath.Join(outside, "sentinel"), dest)
1049
- },
1050
- },
1051
- {
1052
- name: "ancestor symlink",
1053
- link: func(target, outside string) error {
1054
- return os.Symlink(outside, filepath.Join(target, ".agents"))
1055
- },
1056
- },
1057
- } {
1058
- t.Run(tc.name, func(t *testing.T) {
1059
- payload := testPayload(t)
1060
- target := t.TempDir()
1061
- outside := t.TempDir()
1062
- testutil.WriteFile(t, filepath.Join(outside, "sentinel"), "outside\n")
1063
- if err := tc.link(target, outside); err != nil {
1064
- t.Fatal(err)
1065
- }
1066
- err := applyInstall(target, payload, true, false, nil)
1067
- if err == nil || !strings.Contains(err.Error(), "refusing") {
1068
- t.Fatalf("linked install error = %v", err)
1069
- }
1070
- if got := testutil.ReadFile(t, filepath.Join(outside, "sentinel")); got != "outside\n" {
1071
- t.Fatalf("linked install changed outside file: %q", got)
1072
- }
1073
- })
1074
- }
1075
-
1076
- t.Run("recheck detects change", func(t *testing.T) {
1077
- target := t.TempDir()
1078
- const rel = "managed.txt"
1079
- testutil.WriteFile(t, filepath.Join(target, rel), "before\n")
1080
- r := runner{target: target, preflight: map[string]pathSnapshot{}}
1081
- if _, err := r.rememberPath(rel); err != nil {
1082
- t.Fatal(err)
1083
- }
1084
- testutil.WriteFile(t, filepath.Join(target, rel), "after\n")
1085
- if err := r.recheckPath(rel); err == nil || !strings.Contains(err.Error(), "changed after preflight") {
1086
- t.Fatalf("recheck error = %v", err)
1087
- }
1088
- })
1089
- }
1090
-
1091
- func TestVerifyEngineBinary(t *testing.T) {
1092
- if runtime.GOOS == "windows" {
1093
- t.Skip("shell fixtures are Unix-only")
1094
- }
1095
- for _, tc := range []struct {
1096
- name string
1097
- body string
1098
- want string
1099
- ok bool
1100
- }{
1101
- {name: "exact", body: "echo v1.2.3", want: "1.2.3", ok: true},
1102
- {name: "wrong", body: "echo 1.2.4", want: "1.2.3"},
1103
- {name: "dev", body: "echo dev", want: "1.2.3"},
1104
- {name: "multiline", body: "printf '1.2.3\\nextra\\n'", want: "1.2.3"},
1105
- {name: "empty", body: ":", want: "1.2.3"},
1106
- } {
1107
- t.Run(tc.name, func(t *testing.T) {
1108
- path := filepath.Join(t.TempDir(), "engine")
1109
- testutil.WriteExecutable(t, path, "#!/bin/sh\n"+tc.body+"\n")
1110
- _, err := verifyEngineBinary(path, tc.want, 5*time.Second)
1111
- if (err == nil) != tc.ok {
1112
- t.Fatalf("verifyEngineBinary error = %v, ok = %t", err, tc.ok)
1113
- }
1114
- })
1115
- }
1116
- t.Run("timeout", func(t *testing.T) {
1117
- path := filepath.Join(t.TempDir(), "engine")
1118
- testutil.WriteExecutable(t, path, "#!/bin/sh\nsleep 2\n")
1119
- _, err := verifyEngineBinary(path, "1.2.3", 20*time.Millisecond)
1120
- if err == nil || !strings.Contains(err.Error(), "timed out") {
1121
- t.Fatalf("timeout error = %v", err)
1122
- }
1123
- })
1124
- }
1125
-
1126
- func TestInstallBinaryRollsBackVerificationFailure(t *testing.T) {
1127
- t.Setenv("DEVRITES_NO_BINARY", "")
1128
- if runtime.GOOS == "windows" {
1129
- t.Skip("shell fixture is Unix-only; backup/restore portability is covered separately")
1130
- }
1131
- for _, hadOld := range []bool{false, true} {
1132
- t.Run(fmt.Sprintf("had-old-%t", hadOld), func(t *testing.T) {
1133
- binDir := t.TempDir()
1134
- dest := filepath.Join(binDir, "devrites-engine")
1135
- old := "#!/bin/sh\nif [ \"$1\" = version ]; then echo 1.0.0; fi\n"
1136
- if hadOld {
1137
- testutil.WriteExecutable(t, dest, old)
1138
- if err := os.Chmod(dest, 0o700); err != nil {
1139
- t.Fatal(err)
1140
- }
1141
- }
1142
- staged := filepath.Join(t.TempDir(), "devrites-engine")
1143
- body := fmt.Sprintf("#!/bin/sh\nif [ \"$1\" = version ]; then case \"$0\" in %q) echo 1.2.4;; *) echo 1.2.3;; esac; fi\n", dest)
1144
- testutil.WriteExecutable(t, staged, body)
1145
- t.Setenv("DEVRITES_BIN_DIR", binDir)
1146
- t.Setenv("DEVRITES_REF", "v1.2.3")
1147
- t.Setenv("PATH", t.TempDir())
1148
- r := runner{opts: DefaultOptions(ModeInstall), preparedBinary: staged}
1149
-
1150
- err := r.installBinary()
1151
- wantOutcome := "bad binary removed"
1152
- if hadOld {
1153
- wantOutcome = "previous binary restored"
1154
- }
1155
- if err == nil || !strings.Contains(err.Error(), wantOutcome) {
1156
- t.Fatalf("installBinary error = %v", err)
1157
- }
1158
- if hadOld {
1159
- if got := testutil.ReadFile(t, dest); got != old {
1160
- t.Fatalf("rollback bytes = %q, want old binary", got)
1161
- }
1162
- info, statErr := os.Stat(dest)
1163
- if statErr != nil {
1164
- t.Fatal(statErr)
1165
- }
1166
- if info.Mode().Perm() != 0o700 {
1167
- t.Fatalf("rollback mode = %v, want 0700", info.Mode().Perm())
1168
- }
1169
- } else if exists(dest) {
1170
- t.Fatal("failed first install left bad binary")
1171
- }
1172
- })
1173
- }
1174
- }
1175
-
1176
- func TestBackupRestoreBinaryIsPlatformSafe(t *testing.T) {
1177
- dest := filepath.Join(t.TempDir(), "devrites-engine")
1178
- testutil.WriteFile(t, dest, "old\n")
1179
- if err := os.Chmod(dest, 0o600); err != nil {
1180
- t.Fatal(err)
1181
- }
1182
- backup, mode, hadOld, err := backupBinary(dest)
1183
- if err != nil {
1184
- t.Fatal(err)
1185
- }
1186
- defer func() { _ = os.Remove(backup) }()
1187
- if filepath.Dir(backup) != filepath.Dir(dest) {
1188
- t.Fatalf("backup %s is not beside %s", backup, dest)
1189
- }
1190
- testutil.WriteFile(t, dest, "bad\n")
1191
- if err := restoreBinary(dest, backup, mode, hadOld); err != nil {
1192
- t.Fatal(err)
1193
- }
1194
- if got := testutil.ReadFile(t, dest); got != "old\n" {
1195
- t.Fatalf("restored bytes = %q", got)
1196
- }
1197
- }
1198
-
1199
- func applyInstall(target, payload string, force, dryRun bool, out *bytes.Buffer) error {
1200
- opts := DefaultOptions(ModeInstall)
1201
- opts.Target = target
1202
- opts.PayloadDir = payload
1203
- opts.Force = force
1204
- opts.DryRun = dryRun
1205
- if out == nil {
1206
- out = &bytes.Buffer{}
1207
- }
1208
- opts.Stdout = out
1209
- opts.Stderr = &bytes.Buffer{}
1210
- return Apply(opts)
1211
- }
1212
-
1213
- func applyUpdate(target, source, payload string) error {
1214
- opts := DefaultOptions(ModeUpdate)
1215
- opts.Target = target
1216
- opts.SourceDir = source
1217
- opts.PayloadDir = payload
1218
- opts.Stdout = &bytes.Buffer{}
1219
- opts.Stderr = &bytes.Buffer{}
1220
- return Apply(opts)
1221
- }
1222
-
1223
- func runLocalUpdate(t *testing.T, target, source, payload string) {
1224
- t.Helper()
1225
- if err := applyUpdate(target, source, payload); err != nil {
1226
- t.Fatal(err)
1227
- }
1228
- }
1229
-
1230
- func seedLegacyCodexHooksInstall(t *testing.T, target, hooks string) {
1231
- t.Helper()
1232
- markerRel := ".claude/devrites.codex-hooks-merge"
1233
- marker := []byte(".codex/hooks.json contains DevRites managed hook entries.\n")
1234
- testutil.WriteFile(t, filepath.Join(target, ".codex", "hooks.json"), hooks)
1235
- testutil.WriteFile(t, filepath.Join(target, filepath.FromSlash(markerRel)), string(marker))
1236
- testutil.WriteFile(t, filepath.Join(target, ManifestName), "# DevRites install manifest - do not edit by hand.\n# devrites-version: 3.2.27\n# devrites-flags: \n")
1237
- addManifestRecord(t, filepath.Join(target, ManifestName), markerRel, marker)
1238
- }
1239
-
1240
- func addManifestRecord(t *testing.T, manifest, rel string, data []byte) {
1241
- t.Helper()
1242
- hash := fmt.Sprintf("sha256:%x", sha256.Sum256(data))
1243
- testutil.AppendFile(t, manifest, "# managed: "+rel+" "+hash+"\n"+rel+"\n")
1244
- }
1245
-
1246
- func runInstall(t *testing.T, target, payload string, mutate func(*Options)) {
1247
- t.Helper()
1248
- opts := DefaultOptions(ModeInstall)
1249
- opts.Target = target
1250
- opts.PayloadDir = payload
1251
- opts.Stdout = &bytes.Buffer{}
1252
- opts.Stderr = &bytes.Buffer{}
1253
- mutate(&opts)
1254
- if err := Apply(opts); err != nil {
1255
- t.Fatal(err)
1256
- }
1257
- }
1258
-
1259
- func TestUninstallNoBinaryEnvKeepsGlobalBinary(t *testing.T) {
1260
- t.Setenv("DEVRITES_NO_BINARY", "1")
1261
- home := t.TempDir()
1262
- t.Setenv("HOME", home)
1263
- t.Setenv("DEVRITES_BIN_DIR", t.TempDir())
1264
- bin := filepath.Join(home, ".local", "bin", engineBinaryName())
1265
- testutil.WriteFile(t, bin, "engine\n")
1266
- target := t.TempDir()
1267
- runInstall(t, target, testPayload(t), func(*Options) {})
1268
-
1269
- opts := DefaultOptions(ModeUninstall)
1270
- opts.Target = target
1271
- opts.Stdout = &bytes.Buffer{}
1272
- opts.Stderr = &bytes.Buffer{}
1273
- if err := Apply(opts); err != nil {
1274
- t.Fatal(err)
1275
- }
1276
- if _, err := os.Stat(bin); err != nil {
1277
- t.Fatalf("uninstall with DEVRITES_NO_BINARY=1 removed global binary: %v", err)
1278
- }
1279
- }
1280
-
1281
- func runUninstall(t *testing.T, target string) {
1282
- t.Helper()
1283
- opts := DefaultOptions(ModeUninstall)
1284
- opts.Target = target
1285
- opts.KeepBinary = true
1286
- opts.Stdout = &bytes.Buffer{}
1287
- opts.Stderr = &bytes.Buffer{}
1288
- if err := Apply(opts); err != nil {
1289
- t.Fatal(err)
1290
- }
1291
- }
1292
-
1293
- func testPayload(t *testing.T) string {
1294
- t.Helper()
1295
- root := t.TempDir()
1296
- writeTestPayload(t, root)
1297
- return root
1298
- }
1299
-
1300
- func writeTestPayload(t *testing.T, root string) {
1301
- t.Helper()
1302
- testutil.WriteFile(t, filepath.Join(root, "claude", "skills", "rite", "SKILL.md"), "claude rite\n")
1303
- testutil.WriteFile(t, filepath.Join(root, "claude", "agents", "devrites-code-reviewer.md"), "agent\n")
1304
- testutil.WriteFile(t, filepath.Join(root, "claude", "workflows", "devrites-readonly-review.js"), "return { read_only: true }\n")
1305
- testutil.WriteFile(t, filepath.Join(root, "claude", "settings.json"), `{
1306
- "permissions": {
1307
- "defaultMode": "plan",
1308
- "allow": ["Bash(devrites-engine check readiness *)", "mcp__codegraph__*", "Bash(git diff *)", "Edit(.devrites/**)"]
1309
- }
1310
- }`+"\n")
1311
- testutil.WriteFile(t, filepath.Join(root, "codex", "skills", "rite", "SKILL.md"), "codex rite\n")
1312
- testutil.WriteFile(t, filepath.Join(root, "codex", "agents", "devrites-code-reviewer.toml"), "name = \"devrites-code-reviewer\"\n")
1313
- testutil.WriteFile(t, filepath.Join(root, "codex", "AGENTS.md"), "<!-- BEGIN DEVRITES CODEX -->\nDevRites\n<!-- END DEVRITES CODEX -->\n")
1314
- testutil.WriteFile(t, filepath.Join(root, "codex", "config.toml"), `# BEGIN DEVRITES CODEX PERMISSIONS
1315
- default_permissions = "devrites-orchestrator"
1316
- [permissions.devrites-orchestrator]
1317
- extends = ":workspace"
1318
- # END DEVRITES CODEX PERMISSIONS
1319
- `)
1320
- testutil.WriteFile(t, filepath.Join(root, "omp", "skills", "rite", "SKILL.md"), "omp rite\n")
1321
- testutil.WriteFile(t, filepath.Join(root, "omp", "agents", "devrites-code-reviewer.md"), "omp agent\n")
1322
- testutil.WriteFile(t, filepath.Join(root, "omp", "commands", "rite.md"), "omp command\n")
1323
- testutil.WriteFile(t, filepath.Join(root, "omp", ".omp-plugin", "plugin.json"), "{}\n")
1324
- testutil.WriteFile(t, filepath.Join(root, "pi", "skills", "rite", "SKILL.md"), "pi rite\n")
1325
- testutil.WriteFile(t, filepath.Join(root, "pi", "agents", "devrites-code-reviewer.md"), "pi agent\n")
1326
- testutil.WriteFile(t, filepath.Join(root, "pi", "prompts", "rite.md"), "pi prompt\n")
1327
- testutil.WriteFile(t, filepath.Join(root, "pi", "AGENTS.md"), "<!-- BEGIN DEVRITES PI -->\nDevRites\n<!-- END DEVRITES PI -->\n")
1328
- testutil.WriteFile(t, filepath.Join(root, "devin", "skills", "rite", "SKILL.md"), "devin rite\n")
1329
- testutil.WriteFile(t, filepath.Join(root, "devin", "agents", "devrites-code-reviewer.md"), "devin agent\n")
1330
- testutil.WriteFile(t, filepath.Join(root, "devin", "AGENTS.md"), "<!-- BEGIN DEVRITES DEVIN -->\nDevRites\n<!-- END DEVRITES DEVIN -->\n")
1331
- }
1332
-
1333
- func testSource(t *testing.T, version string) string {
1334
- t.Helper()
1335
- root := t.TempDir()
1336
- testutil.WriteFile(t, filepath.Join(root, "package.json"), `{"version":"`+version+`"}`+"\n")
1337
- return root
1338
- }
1339
-
1340
- func manifestEntries(t *testing.T, manifest string) []string {
1341
- t.Helper()
1342
- var out []string
1343
- for _, line := range strings.Split(testutil.ReadFile(t, manifest), "\n") {
1344
- line = strings.TrimSpace(line)
1345
- if line == "" || strings.HasPrefix(line, "#") {
1346
- continue
1347
- }
1348
- out = append(out, line)
1349
- }
1350
- return out
1351
- }
1352
-
1353
- func slicesContains(haystack []string, needle string) bool {
1354
- for _, s := range haystack {
1355
- if s == needle {
1356
- return true
1357
- }
1358
- }
1359
- return false
1360
- }
1361
-
1362
- func summaryCount(out, key string) (int, error) {
1363
- for _, line := range strings.Split(out, "\n") {
1364
- i := strings.Index(line, key)
1365
- if i < 0 {
1366
- continue
1367
- }
1368
- fields := strings.Fields(line[i+len(key):])
1369
- if len(fields) == 0 {
1370
- continue
1371
- }
1372
- n, err := strconv.Atoi(fields[0])
1373
- if err != nil {
1374
- return 0, fmt.Errorf("parse %s count %q: %w", key, fields[0], err)
1375
- }
1376
- return n, nil
1377
- }
1378
- return 0, fmt.Errorf("install output has no %s counter:\n%s", key, out)
1379
- }
1380
-
1381
- func TestInstallAdoptsResidueAfterLostManifest(t *testing.T) {
1382
- t.Setenv("DEVRITES_NO_BINARY", "1")
1383
- payload := testPayload(t)
1384
- target := t.TempDir()
1385
- runInstall(t, target, payload, func(*Options) {})
1386
-
1387
- first := manifestEntries(t, filepath.Join(target, ManifestName))
1388
- if len(first) == 0 {
1389
- t.Fatal("first install recorded no managed paths")
1390
- }
1391
-
1392
- // Exactly the state a run that dies before writeManifest leaves behind.
1393
- if err := os.Remove(filepath.Join(target, ManifestName)); err != nil {
1394
- t.Fatal(err)
1395
- }
1396
-
1397
- var out, errOut bytes.Buffer
1398
- opts := DefaultOptions(ModeInstall)
1399
- opts.Target = target
1400
- opts.PayloadDir = payload
1401
- opts.Stdout = &out
1402
- opts.Stderr = &errOut
1403
- if err := Apply(opts); err != nil {
1404
- t.Fatalf("reinstall after manifest loss returned %v; want the tree adopted, not refused\nstdout:\n%s\nstderr:\n%s", err, out.String(), errOut.String())
1405
- }
1406
- if !strings.Contains(out.String(), "DevRites installed") {
1407
- t.Fatalf("reinstall did not report an install:\n%s", out.String())
1408
- }
1409
- if got := strings.Count(errOut.String(), "not DevRites-managed"); got != 0 {
1410
- t.Fatalf("reinstall rejected %d payload-identical files as foreign:\n%s", got, errOut.String())
1411
- }
1412
-
1413
- installed, err := summaryCount(out.String(), "installed:")
1414
- if err != nil {
1415
- t.Fatal(err)
1416
- }
1417
- skipped, err := summaryCount(out.String(), "skipped(conflict):")
1418
- if err != nil {
1419
- t.Fatal(err)
1420
- }
1421
- if installed != len(first) || skipped != 0 {
1422
- t.Fatalf("reinstall installed %d of %d payload files and skipped %d; want the whole tree adopted\n%s", installed, len(first), skipped, out.String())
1423
- }
1424
-
1425
- second := manifestEntries(t, filepath.Join(target, ManifestName))
1426
- if len(second) != len(first) {
1427
- t.Fatalf("reinstall manifest records %d paths, want the %d the first install owned", len(second), len(first))
1428
- }
1429
- for _, rel := range first {
1430
- if !slicesContains(second, rel) {
1431
- t.Fatalf("reinstall manifest dropped %s", rel)
1432
- }
1433
- }
1434
-
1435
- // The orphan leg: every file the crashed run left on disk must be removable
1436
- // again. This asserts against the first manifest on purpose, so a reinstall
1437
- // that records nothing cannot pass it vacuously.
1438
- opts = DefaultOptions(ModeUninstall)
1439
- opts.Target = target
1440
- opts.KeepBinary = true
1441
- opts.Stdout = &bytes.Buffer{}
1442
- opts.Stderr = &bytes.Buffer{}
1443
- if err := Apply(opts); err != nil {
1444
- t.Fatalf("uninstall after adopted reinstall returned %v", err)
1445
- }
1446
- for _, rel := range first {
1447
- if exists(filepath.Join(target, filepath.FromSlash(rel))) {
1448
- t.Fatalf("uninstall orphaned %s", rel)
1449
- }
1450
- }
1451
- }
1452
-
1453
- func TestReinstallAfterLostManifestSkipsDifferingFile(t *testing.T) {
1454
- t.Setenv("DEVRITES_NO_BINARY", "1")
1455
- payload := testPayload(t)
1456
- target := t.TempDir()
1457
- runInstall(t, target, payload, func(*Options) {})
1458
-
1459
- var foreign string
1460
- for _, rel := range manifestEntries(t, filepath.Join(target, ManifestName)) {
1461
- if strings.HasSuffix(rel, "agents/devrites-code-reviewer.md") {
1462
- foreign = rel
1463
- }
1464
- }
1465
- if foreign == "" {
1466
- t.Fatal("first install did not record the agent file")
1467
- }
1468
- path := filepath.Join(target, filepath.FromSlash(foreign))
1469
- if err := os.Remove(filepath.Join(target, ManifestName)); err != nil {
1470
- t.Fatal(err)
1471
- }
1472
- const mine = "user-authored bytes\n"
1473
- testutil.WriteFile(t, path, mine)
1474
-
1475
- var out, errOut bytes.Buffer
1476
- opts := DefaultOptions(ModeInstall)
1477
- opts.Target = target
1478
- opts.PayloadDir = payload
1479
- opts.Stdout = &out
1480
- opts.Stderr = &errOut
1481
- if err := Apply(opts); err != nil {
1482
- t.Fatalf("reinstall returned %v\n%s", err, errOut.String())
1483
- }
1484
-
1485
- if got := testutil.ReadFile(t, path); got != mine {
1486
- t.Fatalf("reinstall rewrote a file whose bytes differ from the payload: %q", got)
1487
- }
1488
- if slicesContains(manifestEntries(t, filepath.Join(target, ManifestName)), foreign) {
1489
- t.Fatalf("manifest adopted %s although its bytes differ from the payload", foreign)
1490
- }
1491
- if !strings.Contains(errOut.String(), "skip "+foreign) {
1492
- t.Fatalf("reinstall did not warn about skipping %s:\n%s", foreign, errOut.String())
1493
- }
1494
-
1495
- opts = DefaultOptions(ModeUninstall)
1496
- opts.Target = target
1497
- opts.KeepBinary = true
1498
- opts.Stdout = &bytes.Buffer{}
1499
- opts.Stderr = &bytes.Buffer{}
1500
- if err := Apply(opts); err != nil {
1501
- t.Fatalf("uninstall returned %v", err)
1502
- }
1503
- if got := testutil.ReadFile(t, path); got != mine {
1504
- t.Fatalf("uninstall removed or changed the unmanaged file: %q", got)
1505
- }
1506
- }
1507
-
1508
- func TestApplyReportsSkippedEngineBinary(t *testing.T) {
1509
- t.Setenv("DEVRITES_NO_BINARY", "")
1510
- t.Setenv("DEVRITES_ENGINE_CLI", "")
1511
- t.Setenv("HOME", t.TempDir())
1512
- t.Setenv("DEVRITES_BIN_DIR", t.TempDir())
1513
- var out, errOut bytes.Buffer
1514
- opts := DefaultOptions(ModeInstall)
1515
- opts.Target = t.TempDir()
1516
- opts.PayloadDir = testPayload(t)
1517
- opts.WithBinary = true
1518
- opts.Stdout = &out
1519
- opts.Stderr = &errOut
1520
- if err := Apply(opts); err != nil {
1521
- t.Fatalf("Apply() error = %v, want nil", err)
1522
- }
1523
- if !strings.Contains(out.String(), "DevRites installed") {
1524
- t.Fatalf("stdout = %q, want install success", out.String())
1525
- }
1526
- if !strings.Contains(out.String(), "engine binary: skipped (no DEVRITES_ENGINE_CLI handoff)") {
1527
- t.Fatalf("stdout = %q, want the skipped engine binary and its reason in the summary", out.String())
1528
- }
1529
- }
1530
-
1531
- func TestApplySkippedEngineBinaryKeepsExistingBinary(t *testing.T) {
1532
- t.Setenv("DEVRITES_NO_BINARY", "")
1533
- t.Setenv("DEVRITES_ENGINE_CLI", "")
1534
- t.Setenv("HOME", t.TempDir())
1535
- binDir := t.TempDir()
1536
- t.Setenv("DEVRITES_BIN_DIR", binDir)
1537
- old := filepath.Join(binDir, engineBinaryName())
1538
- if err := os.WriteFile(old, []byte("old engine"), 0o755); err != nil {
1539
- t.Fatal(err)
1540
- }
1541
- var out, errOut bytes.Buffer
1542
- opts := DefaultOptions(ModeInstall)
1543
- opts.Target = t.TempDir()
1544
- opts.PayloadDir = testPayload(t)
1545
- opts.WithBinary = true
1546
- opts.Stdout = &out
1547
- opts.Stderr = &errOut
1548
- if err := Apply(opts); err != nil {
1549
- t.Fatalf("Apply() error = %v, want nil", err)
1550
- }
1551
- if strings.Contains(out.String(), "will not be on PATH") {
1552
- t.Fatalf("stdout = %q, must not claim the command is missing while an older binary is kept", out.String())
1553
- }
1554
- if !strings.Contains(out.String(), "existing devrites-engine binary was kept") {
1555
- t.Fatalf("stdout = %q, want the kept existing binary reported", out.String())
1556
- }
1557
- if got := testutil.ReadFile(t, old); got != "old engine" {
1558
- t.Fatalf("existing binary changed: %q", got)
1559
- }
1560
- }
1561
-
1562
- func installForSwap(t *testing.T) (Options, string, string) {
1563
- t.Helper()
1564
- t.Setenv("DEVRITES_NO_BINARY", "1")
1565
- target := filepath.Join(t.TempDir(), "target")
1566
- if err := os.MkdirAll(target, 0o755); err != nil {
1567
- t.Fatal(err)
1568
- }
1569
- opts := DefaultOptions(ModeInstall)
1570
- opts.Target = target
1571
- opts.PayloadDir = testPayload(t)
1572
- opts.Stdout = &bytes.Buffer{}
1573
- opts.Stderr = &bytes.Buffer{}
1574
- if err := Apply(opts); err != nil {
1575
- t.Fatal(err)
1576
- }
1577
- outside := filepath.Join(filepath.Dir(target), "outside")
1578
- if err := os.MkdirAll(outside, 0o755); err != nil {
1579
- t.Fatal(err)
1580
- }
1581
- return opts, target, outside
1582
- }
1583
-
1584
- // swapDirAfterRecheck replaces dir with a symlink to outside once rel has been
1585
- // rechecked, the way a concurrent process could.
1586
- func swapDirAfterRecheck(t *testing.T, rel, dir, outside string) *bool {
1587
- t.Helper()
1588
- swapped := new(bool)
1589
- afterRecheckPath = func(r string) {
1590
- if *swapped || r != rel {
1591
- return
1592
- }
1593
- if err := os.Rename(dir, dir+".real"); err != nil {
1594
- t.Fatal(err)
1595
- }
1596
- if err := os.Symlink(outside, dir); err != nil {
1597
- t.Fatal(err)
1598
- }
1599
- *swapped = true
1600
- }
1601
- t.Cleanup(func() { afterRecheckPath = nil })
1602
- return swapped
1603
- }
1604
-
1605
- func TestUninstallStripAfterRecheckStaysInTarget(t *testing.T) {
1606
- opts, target, outside := installForSwap(t)
1607
- cfg := filepath.Join(target, ".codex", "config.toml")
1608
- orig, err := os.ReadFile(cfg)
1609
- if err != nil {
1610
- t.Fatal(err)
1611
- }
1612
- withUser := append([]byte("model = \"user\"\n"), orig...)
1613
- if err := os.WriteFile(cfg, withUser, 0o644); err != nil {
1614
- t.Fatal(err)
1615
- }
1616
- outsideCfg := filepath.Join(outside, "config.toml")
1617
- if err := os.WriteFile(outsideCfg, withUser, 0o600); err != nil {
1618
- t.Fatal(err)
1619
- }
1620
- swapped := swapDirAfterRecheck(t, ".codex/config.toml", filepath.Join(target, ".codex"), outside)
1621
- opts.Mode = ModeUninstall
1622
- _ = Apply(opts)
1623
- if !*swapped {
1624
- t.Fatal("swap never happened")
1625
- }
1626
- after, err := os.ReadFile(outsideCfg)
1627
- if err != nil || !bytes.Equal(after, withUser) {
1628
- t.Fatalf("uninstall rewrote a file outside the target: err=%v content=%q", err, after)
1629
- }
1630
- }
1631
-
1632
- func TestUninstallRemoveAfterRecheckStaysInTarget(t *testing.T) {
1633
- opts, target, outside := installForSwap(t)
1634
- victim := filepath.Join(outside, "devrites-code-reviewer.toml")
1635
- if err := os.WriteFile(victim, []byte("precious\n"), 0o644); err != nil {
1636
- t.Fatal(err)
1637
- }
1638
- swapped := swapDirAfterRecheck(t, ".codex/agents/devrites-code-reviewer.toml", filepath.Join(target, ".codex", "agents"), outside)
1639
- opts.Mode = ModeUninstall
1640
- _ = Apply(opts)
1641
- if !*swapped {
1642
- t.Fatal("swap never happened")
1643
- }
1644
- if _, err := os.Stat(victim); err != nil {
1645
- t.Fatalf("uninstall removed a file outside the target: %v", err)
1646
- }
1647
- }
1648
-
1649
- func TestUninstallDryRunUnbalancedMarker(t *testing.T) {
1650
- opts, target, _ := installForSwap(t)
1651
- agents := filepath.Join(target, "AGENTS.md")
1652
- data, err := os.ReadFile(agents)
1653
- if err != nil {
1654
- t.Fatal(err)
1655
- }
1656
- broken := strings.Replace(string(data), "<!-- END DEVRITES CODEX -->\n", "", 1)
1657
- if broken == string(data) {
1658
- t.Fatalf("no END marker in %q", data)
1659
- }
1660
- if err := os.WriteFile(agents, []byte(broken+"user tail\n"), 0o644); err != nil {
1661
- t.Fatal(err)
1662
- }
1663
- opts.Mode = ModeUninstall
1664
- dry := opts
1665
- dry.DryRun = true
1666
- dryErr := Apply(dry)
1667
- realErr := Apply(opts)
1668
- if realErr == nil {
1669
- t.Fatal("real uninstall accepted an unterminated marker block")
1670
- }
1671
- if dryErr == nil || dryErr.Error() != realErr.Error() {
1672
- t.Fatalf("dry-run err=%v, real uninstall err=%v; want the same error", dryErr, realErr)
1673
- }
1674
- }
1675
-
1676
- func hashTree(t *testing.T, root string) string {
1677
- t.Helper()
1678
- h := sha256.New()
1679
- err := filepath.WalkDir(root, func(p string, d os.DirEntry, err error) error {
1680
- if err != nil {
1681
- return err
1682
- }
1683
- rel, _ := filepath.Rel(root, p)
1684
- fmt.Fprintf(h, "%s|%v\n", rel, d.Type())
1685
- if d.Type().IsRegular() {
1686
- data, err := os.ReadFile(p)
1687
- if err != nil {
1688
- return err
1689
- }
1690
- h.Write(data)
1691
- }
1692
- return nil
1693
- })
1694
- if err != nil {
1695
- t.Fatal(err)
1696
- }
1697
- return fmt.Sprintf("%x", h.Sum(nil))
1698
- }
1699
-
1700
- func TestUninstallDryRunMatchesRealMarkerErrors(t *testing.T) {
1701
- const (
1702
- piBegin = "<!-- BEGIN DEVRITES PI -->\n"
1703
- piEnd = "<!-- END DEVRITES PI -->\n"
1704
- codexBegin = "<!-- BEGIN DEVRITES CODEX -->\n"
1705
- codexEnd = "<!-- END DEVRITES CODEX -->\n"
1706
- )
1707
- for _, tc := range []struct {
1708
- name string
1709
- agents string
1710
- wantErr bool
1711
- }{
1712
- {name: "balanced", agents: "user\n" + codexBegin + "x\n" + codexEnd + piBegin + "y\n" + piEnd},
1713
- {name: "only blocks", agents: codexBegin + "x\n" + codexEnd},
1714
- {name: "unterminated codex", agents: codexBegin + "x\nuser tail\n", wantErr: true},
1715
- {name: "unterminated pi", agents: piBegin + "x\nuser tail\n", wantErr: true},
1716
- {name: "crossed", agents: "u\n" + piBegin + "A\n" + codexBegin + "B\n" + piEnd + "C\n" + codexEnd + "v\n", wantErr: true},
1717
- {name: "crossed other way", agents: codexBegin + "A\n" + piBegin + "B\n" + codexEnd + "C\n" + piEnd},
1718
- } {
1719
- t.Run(tc.name, func(t *testing.T) {
1720
- opts, target, _ := installForSwap(t)
1721
- if err := os.WriteFile(filepath.Join(target, "AGENTS.md"), []byte(tc.agents), 0o644); err != nil {
1722
- t.Fatal(err)
1723
- }
1724
- opts.Mode = ModeUninstall
1725
- dry := opts
1726
- dry.DryRun = true
1727
- before := hashTree(t, target)
1728
- dryErr := Apply(dry)
1729
- if hashTree(t, target) != before {
1730
- t.Fatal("dry-run changed the target tree")
1731
- }
1732
- realErr := Apply(opts)
1733
- if (realErr != nil) != tc.wantErr {
1734
- t.Fatalf("real uninstall err=%v, want error=%v", realErr, tc.wantErr)
1735
- }
1736
- if fmt.Sprint(dryErr) != fmt.Sprint(realErr) {
1737
- t.Fatalf("dry-run err=%v, real uninstall err=%v; want the same error", dryErr, realErr)
1738
- }
1739
- })
1740
- }
1741
- }