devrites 3.0.7 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (472) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +67 -47
  3. package/SECURITY.md +31 -29
  4. package/docs/adr/0001-go-engine-as-control-plane.md +1 -1
  5. package/docs/adr/0002-dual-host-harness.md +1 -1
  6. package/docs/adr/0004-state-schema-phases-sections.md +1 -2
  7. package/docs/adr/0006-clock-seam-and-engine-ci-gates.md +3 -4
  8. package/docs/adr/0009-prebuild-decision-coverage-and-readiness.md +67 -0
  9. package/docs/adr/0010-agent-first-fresh-context-orchestration.md +71 -0
  10. package/docs/adr/0011-define-plan-transition-rights.md +28 -0
  11. package/docs/adr/README.md +3 -0
  12. package/docs/agents/triage-labels.md +7 -7
  13. package/docs/architecture.md +157 -140
  14. package/docs/cli.md +41 -12
  15. package/docs/command-map.md +49 -35
  16. package/docs/engine/agent-contract.md +92 -15
  17. package/docs/engine/commands.md +242 -72
  18. package/docs/engine/state-schema.md +52 -23
  19. package/docs/engine/workspace-schema.md +94 -15
  20. package/docs/extensions.md +1 -1
  21. package/docs/flow.md +80 -50
  22. package/docs/harness-compliance.md +29 -5
  23. package/docs/orchestration.md +107 -79
  24. package/docs/quick-reference.md +7 -3
  25. package/docs/release.md +4 -3
  26. package/docs/skills.md +64 -38
  27. package/docs/usage.md +57 -38
  28. package/engine/cmd/releasepack/main.go +219 -0
  29. package/engine/cmd/releasepack/main_test.go +170 -0
  30. package/engine/commands.go +170 -23
  31. package/engine/git_guard.go +187 -0
  32. package/engine/git_guard_test.go +283 -0
  33. package/engine/hookpolicy.go +53 -55
  34. package/engine/hookpolicy_test.go +91 -1
  35. package/engine/hooks.go +296 -75
  36. package/engine/hooks_events_test.go +276 -6
  37. package/engine/hooks_workspace.go +640 -159
  38. package/engine/internal/devritespaths/paths.go +65 -10
  39. package/engine/internal/devritespaths/paths_test.go +110 -0
  40. package/engine/internal/doctor/doctor.go +153 -23
  41. package/engine/internal/doctor/doctor_test.go +74 -0
  42. package/engine/internal/forge/forge.go +940 -0
  43. package/engine/internal/forge/forge_test.go +576 -0
  44. package/engine/internal/forge/git.go +245 -0
  45. package/engine/internal/forge/liveness_unix.go +48 -0
  46. package/engine/internal/forge/liveness_windows.go +67 -0
  47. package/engine/internal/forge/manifest.go +402 -0
  48. package/engine/internal/gate/gate.go +90 -71
  49. package/engine/internal/gate/gate_test.go +71 -2
  50. package/engine/internal/harness/compliance.go +18 -24
  51. package/engine/internal/harness/harness.go +37 -44
  52. package/engine/internal/harness/harness_test.go +21 -5
  53. package/engine/internal/install/install.go +486 -38
  54. package/engine/internal/install/install_test.go +383 -10
  55. package/engine/internal/iohooks/iohooks.go +350 -59
  56. package/engine/internal/iohooks/iohooks_test.go +421 -1
  57. package/engine/internal/lib/buildreadiness.go +35 -18
  58. package/engine/internal/lib/clarifyreturn.go +91 -0
  59. package/engine/internal/lib/clarifyreturn_test.go +123 -0
  60. package/engine/internal/lib/context.go +54 -15
  61. package/engine/internal/lib/cursor_compat_test.go +8 -0
  62. package/engine/internal/lib/extensions.go +2 -3
  63. package/engine/internal/lib/gitauthority.go +601 -0
  64. package/engine/internal/lib/gitauthority_test.go +346 -0
  65. package/engine/internal/lib/jsonout.go +25 -11
  66. package/engine/internal/lib/jsonout_test.go +16 -0
  67. package/engine/internal/lib/lanes.go +14 -8
  68. package/engine/internal/lib/observability_test.go +358 -0
  69. package/engine/internal/lib/packageexistence.go +151 -35
  70. package/engine/internal/lib/packageexistence_test.go +163 -0
  71. package/engine/internal/lib/progress.go +11 -9
  72. package/engine/internal/lib/provenance.go +462 -0
  73. package/engine/internal/lib/provenance_test.go +154 -0
  74. package/engine/internal/lib/readiness_contract.json +111 -0
  75. package/engine/internal/lib/readinessartifact.go +510 -0
  76. package/engine/internal/lib/readinessartifact_test.go +321 -0
  77. package/engine/internal/lib/reconcile.go +712 -88
  78. package/engine/internal/lib/reconcile_test.go +335 -16
  79. package/engine/internal/lib/recoveryattempts.go +298 -0
  80. package/engine/internal/lib/recoveryattempts_test.go +215 -0
  81. package/engine/internal/lib/resolve.go +23 -19
  82. package/engine/internal/lib/runbook_context_test.go +20 -0
  83. package/engine/internal/lib/session.go +701 -9
  84. package/engine/internal/lib/session_test.go +80 -13
  85. package/engine/internal/lib/testintegrity.go +33 -37
  86. package/engine/internal/lib/testintegrity_test.go +63 -1
  87. package/engine/internal/migrate/migrate.go +81 -1
  88. package/engine/internal/migrate/migrate_test.go +22 -1
  89. package/engine/internal/reason/reason.go +180 -0
  90. package/engine/internal/reason/reason_test.go +35 -0
  91. package/engine/internal/rootfacts/facts.go +466 -0
  92. package/engine/internal/rootfacts/facts_test.go +306 -0
  93. package/engine/internal/safepath/safepath.go +55 -0
  94. package/engine/internal/safepath/safepath_test.go +69 -0
  95. package/engine/internal/safepath/safepath_windows_test.go +26 -0
  96. package/engine/internal/state/clarify_transition.go +165 -0
  97. package/engine/internal/state/clarify_transition_test.go +130 -0
  98. package/engine/internal/state/cmd/workflowmanifest/main.go +12 -8
  99. package/engine/internal/state/cursor.go +58 -0
  100. package/engine/internal/state/cursor_test.go +42 -1
  101. package/engine/internal/state/feature.go +35 -48
  102. package/engine/internal/state/schema.go +90 -37
  103. package/engine/internal/state/snapshot.go +18 -2
  104. package/engine/internal/state/state_test.go +187 -7
  105. package/engine/internal/state/status.go +48 -11
  106. package/engine/internal/state/workflow_manifest.json +231 -50
  107. package/engine/internal/toolpolicy/classifier.go +533 -0
  108. package/engine/internal/toolpolicy/classifier_test.go +424 -0
  109. package/engine/internal/toolpolicy/git.go +616 -0
  110. package/engine/internal/toolpolicy/scanner.go +382 -0
  111. package/engine/main.go +160 -77
  112. package/engine/observability_cli_test.go +52 -0
  113. package/engine/root_routing_test.go +277 -0
  114. package/engine/testdata/golden/TestParityBuildReadiness/arg=approved.golden +1 -1
  115. package/engine/testdata/golden/TestParityBuildReadiness/arg=emptystatus.golden +1 -1
  116. package/engine/testdata/golden/TestParityBuildReadiness/arg=noclarify.golden +1 -0
  117. package/engine/testdata/golden/TestParityBuildReadiness/arg=novet.golden +1 -0
  118. package/engine/testdata/golden/TestParityBuildReadiness/arg=stalevet.golden +1 -0
  119. package/engine/testdata/golden/TestParityBuildReadiness/arg=trailhash.golden +1 -1
  120. package/engine/testdata/golden/TestParityBuildReadiness/arg=trailpipe.golden +1 -1
  121. package/engine/testdata/golden/TestParityBuildReadiness/arg=vetnotready.golden +1 -0
  122. package/engine/testdata/golden/TestParityProgress/arg=allbuilt.golden +1 -1
  123. package/engine/testdata/golden/TestParityProgress/arg=done.golden +1 -1
  124. package/engine/testdata/golden/TestParityProgress/arg=mid.golden +1 -1
  125. package/engine/testdata/golden/TestParityProgress/arg=nophase.golden +1 -1
  126. package/engine/testdata/golden/TestParityProgress/arg=noslice.golden +1 -1
  127. package/engine/testdata/golden/TestParityProgress/arg=plan.golden +1 -1
  128. package/engine/testdata/golden/TestParityProgress/arg=seal.golden +1 -1
  129. package/engine/testdata/golden/TestParityReconcile/check-clean.golden +1 -1
  130. package/engine/testdata/golden/TestParityReconcile/inline-fallback.golden +1 -0
  131. package/engine/testdata/golden/TestParityReconcile/snapshot-no-allowlist.golden +1 -0
  132. package/engine/testdata/golden/TestParityWrightScope/devrites-edit-denied.golden +2 -0
  133. package/engine/testdata/golden/TestParityWrightScope/out-of-scope-enforce-denies.golden +1 -1
  134. package/engine/tests/adr_0011_define_plan_test.go +30 -0
  135. package/engine/tests/budget_test.go +19 -8
  136. package/engine/tests/concurrency_cli_test.go +7 -1
  137. package/engine/tests/doctor_cli_test.go +148 -2
  138. package/engine/tests/forge_cli_test.go +463 -0
  139. package/engine/tests/gate_test.go +63 -8
  140. package/engine/tests/hook_test.go +260 -8
  141. package/engine/tests/hooks_io_test.go +94 -3
  142. package/engine/tests/json_contract_test.go +127 -2
  143. package/engine/tests/migrate_cli_test.go +2 -2
  144. package/engine/tests/parity_buildreadiness_test.go +141 -10
  145. package/engine/tests/parity_learnings_test.go +16 -17
  146. package/engine/tests/parity_reconcile_test.go +20 -17
  147. package/engine/tests/parity_resolve_test.go +12 -11
  148. package/engine/tests/parity_test.go +21 -17
  149. package/pack/.claude/agents/devrites-code-reviewer.md +62 -48
  150. package/pack/.claude/agents/devrites-devex-reviewer.md +69 -53
  151. package/pack/.claude/agents/devrites-doubt-reviewer.md +29 -22
  152. package/pack/.claude/agents/devrites-evidence-scout.md +69 -0
  153. package/pack/.claude/agents/devrites-forge-judge.md +74 -61
  154. package/pack/.claude/agents/devrites-frontend-reviewer.md +48 -39
  155. package/pack/.claude/agents/devrites-performance-reviewer.md +49 -40
  156. package/pack/.claude/agents/devrites-plan-drafter.md +71 -0
  157. package/pack/.claude/agents/devrites-plan-reviewer.md +80 -47
  158. package/pack/.claude/agents/devrites-proof-runner.md +74 -0
  159. package/pack/.claude/agents/devrites-retrospector.md +48 -45
  160. package/pack/.claude/agents/devrites-security-auditor.md +46 -36
  161. package/pack/.claude/agents/devrites-simplifier-reviewer.md +50 -46
  162. package/pack/.claude/agents/devrites-slice-wright.md +153 -165
  163. package/pack/.claude/agents/devrites-spec-reviewer.md +34 -32
  164. package/pack/.claude/agents/devrites-strategy-reviewer.md +63 -34
  165. package/pack/.claude/agents/devrites-test-analyst.md +40 -30
  166. package/pack/.claude/settings.json +2 -1
  167. package/pack/.claude/skills/devrites-audit/SKILL.md +40 -61
  168. package/pack/.claude/skills/devrites-debug-recovery/SKILL.md +24 -13
  169. package/pack/.claude/skills/devrites-debug-recovery/reference/build-the-loop.md +24 -21
  170. package/pack/.claude/skills/devrites-debug-recovery/reference/cleanup-and-classify.md +34 -6
  171. package/pack/.claude/skills/devrites-debug-recovery/reference/instrumentation.md +2 -2
  172. package/pack/.claude/skills/devrites-doubt/SKILL.md +25 -17
  173. package/pack/.claude/skills/devrites-interview/SKILL.md +53 -52
  174. package/pack/.claude/skills/devrites-lib/SKILL.md +11 -9
  175. package/pack/.claude/skills/devrites-lib/reference/intent-map.md +3 -2
  176. package/pack/.claude/skills/devrites-lib/reference/parallel-dispatch.md +82 -137
  177. package/pack/.claude/skills/devrites-lib/reference/reply-contract.md +8 -1
  178. package/pack/.claude/skills/devrites-lib/reference/standards/README.md +38 -55
  179. package/pack/.claude/skills/devrites-lib/reference/standards/afk-hitl.md +53 -27
  180. package/pack/.claude/skills/devrites-lib/reference/standards/agents.md +198 -190
  181. package/pack/.claude/skills/devrites-lib/reference/standards/anti-patterns.md +5 -15
  182. package/pack/.claude/skills/devrites-lib/reference/standards/ci-cd.md +27 -58
  183. package/pack/.claude/skills/devrites-lib/reference/standards/code-review.md +16 -52
  184. package/pack/.claude/skills/devrites-lib/reference/standards/coding-style.md +2 -10
  185. package/pack/.claude/skills/devrites-lib/reference/standards/context-hygiene.md +18 -61
  186. package/pack/.claude/skills/devrites-lib/reference/standards/core.md +23 -18
  187. package/pack/.claude/skills/devrites-lib/reference/standards/deprecation.md +17 -57
  188. package/pack/.claude/skills/devrites-lib/reference/standards/development-workflow.md +4 -33
  189. package/pack/.claude/skills/devrites-lib/reference/standards/git-workflow.md +4 -20
  190. package/pack/.claude/skills/devrites-lib/reference/standards/hooks.md +3 -14
  191. package/pack/.claude/skills/devrites-lib/reference/standards/patterns.md +9 -25
  192. package/pack/.claude/skills/devrites-lib/reference/standards/performance.md +0 -9
  193. package/pack/.claude/skills/devrites-lib/reference/standards/principles.md +1 -3
  194. package/pack/.claude/skills/devrites-lib/reference/standards/security.md +17 -7
  195. package/pack/.claude/skills/devrites-lib/reference/standards/testing.md +1 -27
  196. package/pack/.claude/skills/devrites-lib/reference/standards/tooling.md +46 -50
  197. package/pack/.claude/skills/devrites-lib/reference/workspace-artifact-schema.md +23 -10
  198. package/pack/.claude/skills/devrites-source-driven/SKILL.md +23 -22
  199. package/pack/.claude/skills/rite/SKILL.md +8 -6
  200. package/pack/.claude/skills/rite/reference/menu.md +8 -6
  201. package/pack/.claude/skills/rite-adopt/SKILL.md +33 -37
  202. package/pack/.claude/skills/rite-autocomplete/SKILL.md +33 -28
  203. package/pack/.claude/skills/rite-autocomplete/reference/loop.md +21 -21
  204. package/pack/.claude/skills/rite-autocomplete/reference/stop-conditions.md +15 -6
  205. package/pack/.claude/skills/rite-build/SKILL.md +35 -30
  206. package/pack/.claude/skills/rite-build/reference/afk-discipline.md +38 -38
  207. package/pack/.claude/skills/rite-build/reference/evidence-standard.md +10 -0
  208. package/pack/.claude/skills/rite-build/reference/forge.md +186 -156
  209. package/pack/.claude/skills/rite-build/reference/one-slice-cycle.md +4 -3
  210. package/pack/.claude/skills/rite-build/reference/phase-contract.md +96 -175
  211. package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +129 -134
  212. package/pack/.claude/skills/rite-clarify/SKILL.md +89 -0
  213. package/pack/.claude/skills/rite-clarify/reference/decision-coverage.md +59 -0
  214. package/pack/.claude/skills/rite-converge/SKILL.md +35 -25
  215. package/pack/.claude/skills/rite-define/SKILL.md +57 -32
  216. package/pack/.claude/skills/rite-define/reference/gates.md +16 -15
  217. package/pack/.claude/skills/rite-define/reference/plan-template.md +16 -8
  218. package/pack/.claude/skills/rite-frame/reference/failure-modes.md +22 -24
  219. package/pack/.claude/skills/rite-plan/SKILL.md +44 -16
  220. package/pack/.claude/skills/rite-plan/reference/task-breakdown.md +4 -0
  221. package/pack/.claude/skills/rite-polish/SKILL.md +27 -23
  222. package/pack/.claude/skills/rite-prototype/SKILL.md +25 -26
  223. package/pack/.claude/skills/rite-prove/SKILL.md +27 -17
  224. package/pack/.claude/skills/rite-resolve/SKILL.md +12 -11
  225. package/pack/.claude/skills/rite-resolve/reference/answer-protocol.md +3 -0
  226. package/pack/.claude/skills/rite-review/SKILL.md +37 -28
  227. package/pack/.claude/skills/rite-seal/reference/phase-contract.md +27 -92
  228. package/pack/.claude/skills/rite-seal/reference/risk-and-rollback.md +13 -0
  229. package/pack/.claude/skills/rite-ship/reference/design-memory.md +31 -36
  230. package/pack/.claude/skills/rite-spec/SKILL.md +84 -135
  231. package/pack/.claude/skills/rite-spec/reference/investigation.md +37 -33
  232. package/pack/.claude/skills/rite-spec/reference/question-protocol.md +6 -2
  233. package/pack/.claude/skills/rite-spec/reference/spec-checklists.md +25 -24
  234. package/pack/.claude/skills/rite-spec/reference/spec-template.md +9 -2
  235. package/pack/.claude/skills/rite-spec/reference/state-workspace.md +13 -3
  236. package/pack/.claude/skills/rite-temper/SKILL.md +62 -47
  237. package/pack/.claude/skills/rite-temper/reference/review-dimensions.md +24 -22
  238. package/pack/.claude/skills/rite-vet/SKILL.md +110 -113
  239. package/pack/.claude/skills/rite-vet/reference/artifacts.md +42 -9
  240. package/pack/.claude/skills/rite-vet/reference/review-axes.md +39 -37
  241. package/pack/generated/claude/agents/devrites-code-reviewer.md +62 -48
  242. package/pack/generated/claude/agents/devrites-devex-reviewer.md +69 -53
  243. package/pack/generated/claude/agents/devrites-doubt-reviewer.md +29 -22
  244. package/pack/generated/claude/agents/devrites-evidence-scout.md +69 -0
  245. package/pack/generated/claude/agents/devrites-forge-judge.md +74 -61
  246. package/pack/generated/claude/agents/devrites-frontend-reviewer.md +48 -39
  247. package/pack/generated/claude/agents/devrites-performance-reviewer.md +49 -40
  248. package/pack/generated/claude/agents/devrites-plan-drafter.md +71 -0
  249. package/pack/generated/claude/agents/devrites-plan-reviewer.md +80 -47
  250. package/pack/generated/claude/agents/devrites-proof-runner.md +74 -0
  251. package/pack/generated/claude/agents/devrites-retrospector.md +48 -45
  252. package/pack/generated/claude/agents/devrites-security-auditor.md +46 -36
  253. package/pack/generated/claude/agents/devrites-simplifier-reviewer.md +50 -46
  254. package/pack/generated/claude/agents/devrites-slice-wright.md +153 -165
  255. package/pack/generated/claude/agents/devrites-spec-reviewer.md +34 -32
  256. package/pack/generated/claude/agents/devrites-strategy-reviewer.md +63 -34
  257. package/pack/generated/claude/agents/devrites-test-analyst.md +40 -30
  258. package/pack/generated/claude/settings.json +2 -1
  259. package/pack/generated/claude/skills/devrites-audit/SKILL.md +40 -61
  260. package/pack/generated/claude/skills/devrites-debug-recovery/SKILL.md +24 -13
  261. package/pack/generated/claude/skills/devrites-debug-recovery/reference/build-the-loop.md +24 -21
  262. package/pack/generated/claude/skills/devrites-debug-recovery/reference/cleanup-and-classify.md +34 -6
  263. package/pack/generated/claude/skills/devrites-debug-recovery/reference/instrumentation.md +2 -2
  264. package/pack/generated/claude/skills/devrites-doubt/SKILL.md +25 -17
  265. package/pack/generated/claude/skills/devrites-interview/SKILL.md +53 -52
  266. package/pack/generated/claude/skills/devrites-lib/SKILL.md +11 -9
  267. package/pack/generated/claude/skills/devrites-lib/reference/intent-map.md +3 -2
  268. package/pack/generated/claude/skills/devrites-lib/reference/parallel-dispatch.md +82 -137
  269. package/pack/generated/claude/skills/devrites-lib/reference/reply-contract.md +8 -1
  270. package/pack/generated/claude/skills/devrites-lib/reference/standards/README.md +38 -55
  271. package/pack/generated/claude/skills/devrites-lib/reference/standards/afk-hitl.md +53 -27
  272. package/pack/generated/claude/skills/devrites-lib/reference/standards/agents.md +198 -190
  273. package/pack/generated/claude/skills/devrites-lib/reference/standards/anti-patterns.md +5 -15
  274. package/pack/generated/claude/skills/devrites-lib/reference/standards/ci-cd.md +27 -58
  275. package/pack/generated/claude/skills/devrites-lib/reference/standards/code-review.md +16 -52
  276. package/pack/generated/claude/skills/devrites-lib/reference/standards/coding-style.md +2 -10
  277. package/pack/generated/claude/skills/devrites-lib/reference/standards/context-hygiene.md +18 -61
  278. package/pack/generated/claude/skills/devrites-lib/reference/standards/core.md +23 -18
  279. package/pack/generated/claude/skills/devrites-lib/reference/standards/deprecation.md +17 -57
  280. package/pack/generated/claude/skills/devrites-lib/reference/standards/development-workflow.md +4 -33
  281. package/pack/generated/claude/skills/devrites-lib/reference/standards/git-workflow.md +4 -20
  282. package/pack/generated/claude/skills/devrites-lib/reference/standards/hooks.md +3 -14
  283. package/pack/generated/claude/skills/devrites-lib/reference/standards/patterns.md +9 -25
  284. package/pack/generated/claude/skills/devrites-lib/reference/standards/performance.md +0 -9
  285. package/pack/generated/claude/skills/devrites-lib/reference/standards/principles.md +1 -3
  286. package/pack/generated/claude/skills/devrites-lib/reference/standards/security.md +17 -7
  287. package/pack/generated/claude/skills/devrites-lib/reference/standards/testing.md +1 -27
  288. package/pack/generated/claude/skills/devrites-lib/reference/standards/tooling.md +46 -50
  289. package/pack/generated/claude/skills/devrites-lib/reference/workspace-artifact-schema.md +23 -10
  290. package/pack/generated/claude/skills/devrites-source-driven/SKILL.md +23 -22
  291. package/pack/generated/claude/skills/rite/SKILL.md +8 -6
  292. package/pack/generated/claude/skills/rite/reference/menu.md +8 -6
  293. package/pack/generated/claude/skills/rite-adopt/SKILL.md +33 -37
  294. package/pack/generated/claude/skills/rite-autocomplete/SKILL.md +33 -28
  295. package/pack/generated/claude/skills/rite-autocomplete/reference/loop.md +21 -21
  296. package/pack/generated/claude/skills/rite-autocomplete/reference/stop-conditions.md +15 -6
  297. package/pack/generated/claude/skills/rite-build/SKILL.md +35 -30
  298. package/pack/generated/claude/skills/rite-build/reference/afk-discipline.md +38 -38
  299. package/pack/generated/claude/skills/rite-build/reference/evidence-standard.md +10 -0
  300. package/pack/generated/claude/skills/rite-build/reference/forge.md +186 -156
  301. package/pack/generated/claude/skills/rite-build/reference/one-slice-cycle.md +4 -3
  302. package/pack/generated/claude/skills/rite-build/reference/phase-contract.md +96 -175
  303. package/pack/generated/claude/skills/rite-build/reference/wright-dispatch.md +129 -134
  304. package/pack/generated/claude/skills/rite-clarify/SKILL.md +89 -0
  305. package/pack/generated/claude/skills/rite-clarify/reference/decision-coverage.md +59 -0
  306. package/pack/generated/claude/skills/rite-converge/SKILL.md +35 -25
  307. package/pack/generated/claude/skills/rite-define/SKILL.md +57 -32
  308. package/pack/generated/claude/skills/rite-define/reference/gates.md +16 -15
  309. package/pack/generated/claude/skills/rite-define/reference/plan-template.md +16 -8
  310. package/pack/generated/claude/skills/rite-frame/reference/failure-modes.md +22 -24
  311. package/pack/generated/claude/skills/rite-plan/SKILL.md +44 -16
  312. package/pack/generated/claude/skills/rite-plan/reference/task-breakdown.md +4 -0
  313. package/pack/generated/claude/skills/rite-polish/SKILL.md +27 -23
  314. package/pack/generated/claude/skills/rite-prototype/SKILL.md +25 -26
  315. package/pack/generated/claude/skills/rite-prove/SKILL.md +27 -17
  316. package/pack/generated/claude/skills/rite-resolve/SKILL.md +12 -11
  317. package/pack/generated/claude/skills/rite-resolve/reference/answer-protocol.md +3 -0
  318. package/pack/generated/claude/skills/rite-review/SKILL.md +37 -28
  319. package/pack/generated/claude/skills/rite-seal/reference/phase-contract.md +27 -92
  320. package/pack/generated/claude/skills/rite-seal/reference/risk-and-rollback.md +13 -0
  321. package/pack/generated/claude/skills/rite-ship/reference/design-memory.md +31 -36
  322. package/pack/generated/claude/skills/rite-spec/SKILL.md +84 -135
  323. package/pack/generated/claude/skills/rite-spec/reference/investigation.md +37 -33
  324. package/pack/generated/claude/skills/rite-spec/reference/question-protocol.md +6 -2
  325. package/pack/generated/claude/skills/rite-spec/reference/spec-checklists.md +25 -24
  326. package/pack/generated/claude/skills/rite-spec/reference/spec-template.md +9 -2
  327. package/pack/generated/claude/skills/rite-spec/reference/state-workspace.md +13 -3
  328. package/pack/generated/claude/skills/rite-temper/SKILL.md +62 -47
  329. package/pack/generated/claude/skills/rite-temper/reference/review-dimensions.md +24 -22
  330. package/pack/generated/claude/skills/rite-vet/SKILL.md +110 -113
  331. package/pack/generated/claude/skills/rite-vet/reference/artifacts.md +42 -9
  332. package/pack/generated/claude/skills/rite-vet/reference/review-axes.md +39 -37
  333. package/pack/generated/codex/AGENTS.md +5 -2
  334. package/pack/generated/codex/agents/devrites-code-reviewer.toml +69 -47
  335. package/pack/generated/codex/agents/devrites-devex-reviewer.toml +75 -51
  336. package/pack/generated/codex/agents/devrites-doubt-reviewer.toml +35 -20
  337. package/pack/generated/codex/agents/devrites-evidence-scout.toml +75 -0
  338. package/pack/generated/codex/agents/devrites-forge-judge.toml +80 -59
  339. package/pack/generated/codex/agents/devrites-frontend-reviewer.toml +54 -37
  340. package/pack/generated/codex/agents/devrites-performance-reviewer.toml +55 -38
  341. package/pack/generated/codex/agents/devrites-plan-drafter.toml +77 -0
  342. package/pack/generated/codex/agents/devrites-plan-reviewer.toml +82 -47
  343. package/pack/generated/codex/agents/devrites-proof-runner.toml +80 -0
  344. package/pack/generated/codex/agents/devrites-retrospector.toml +54 -43
  345. package/pack/generated/codex/agents/devrites-security-auditor.toml +53 -35
  346. package/pack/generated/codex/agents/devrites-simplifier-reviewer.toml +56 -44
  347. package/pack/generated/codex/agents/devrites-slice-wright.toml +159 -163
  348. package/pack/generated/codex/agents/devrites-spec-reviewer.toml +40 -30
  349. package/pack/generated/codex/agents/devrites-strategy-reviewer.toml +65 -34
  350. package/pack/generated/codex/agents/devrites-test-analyst.toml +46 -28
  351. package/pack/generated/codex/hooks.json +4 -14
  352. package/pack/generated/codex/skills/devrites-api-interface/SKILL.md +7 -3
  353. package/pack/generated/codex/skills/devrites-audit/SKILL.md +47 -64
  354. package/pack/generated/codex/skills/devrites-browser-proof/SKILL.md +7 -3
  355. package/pack/generated/codex/skills/devrites-debug-recovery/SKILL.md +31 -16
  356. package/pack/generated/codex/skills/devrites-debug-recovery/reference/build-the-loop.md +24 -21
  357. package/pack/generated/codex/skills/devrites-debug-recovery/reference/cleanup-and-classify.md +34 -6
  358. package/pack/generated/codex/skills/devrites-debug-recovery/reference/instrumentation.md +2 -2
  359. package/pack/generated/codex/skills/devrites-doubt/SKILL.md +32 -20
  360. package/pack/generated/codex/skills/devrites-frontend-craft/SKILL.md +7 -3
  361. package/pack/generated/codex/skills/devrites-interview/SKILL.md +60 -55
  362. package/pack/generated/codex/skills/devrites-lib/SKILL.md +18 -12
  363. package/pack/generated/codex/skills/devrites-lib/reference/intent-map.md +3 -2
  364. package/pack/generated/codex/skills/devrites-lib/reference/parallel-dispatch.md +82 -137
  365. package/pack/generated/codex/skills/devrites-lib/reference/reply-contract.md +8 -1
  366. package/pack/generated/codex/skills/devrites-lib/reference/standards/README.md +38 -55
  367. package/pack/generated/codex/skills/devrites-lib/reference/standards/afk-hitl.md +53 -27
  368. package/pack/generated/codex/skills/devrites-lib/reference/standards/agents.md +198 -190
  369. package/pack/generated/codex/skills/devrites-lib/reference/standards/anti-patterns.md +5 -15
  370. package/pack/generated/codex/skills/devrites-lib/reference/standards/ci-cd.md +27 -58
  371. package/pack/generated/codex/skills/devrites-lib/reference/standards/code-review.md +16 -52
  372. package/pack/generated/codex/skills/devrites-lib/reference/standards/coding-style.md +2 -10
  373. package/pack/generated/codex/skills/devrites-lib/reference/standards/context-hygiene.md +18 -61
  374. package/pack/generated/codex/skills/devrites-lib/reference/standards/core.md +23 -18
  375. package/pack/generated/codex/skills/devrites-lib/reference/standards/deprecation.md +17 -57
  376. package/pack/generated/codex/skills/devrites-lib/reference/standards/development-workflow.md +4 -33
  377. package/pack/generated/codex/skills/devrites-lib/reference/standards/git-workflow.md +4 -20
  378. package/pack/generated/codex/skills/devrites-lib/reference/standards/hooks.md +3 -14
  379. package/pack/generated/codex/skills/devrites-lib/reference/standards/patterns.md +9 -25
  380. package/pack/generated/codex/skills/devrites-lib/reference/standards/performance.md +0 -9
  381. package/pack/generated/codex/skills/devrites-lib/reference/standards/principles.md +1 -3
  382. package/pack/generated/codex/skills/devrites-lib/reference/standards/security.md +17 -7
  383. package/pack/generated/codex/skills/devrites-lib/reference/standards/testing.md +1 -27
  384. package/pack/generated/codex/skills/devrites-lib/reference/standards/tooling.md +46 -50
  385. package/pack/generated/codex/skills/devrites-lib/reference/workspace-artifact-schema.md +23 -10
  386. package/pack/generated/codex/skills/devrites-prose-craft/SKILL.md +7 -3
  387. package/pack/generated/codex/skills/devrites-refresh-indexes/SKILL.md +7 -3
  388. package/pack/generated/codex/skills/devrites-source-driven/SKILL.md +29 -24
  389. package/pack/generated/codex/skills/devrites-ux-shape/SKILL.md +7 -3
  390. package/pack/generated/codex/skills/rite/SKILL.md +19 -13
  391. package/pack/generated/codex/skills/rite/reference/menu.md +8 -6
  392. package/pack/generated/codex/skills/rite-adopt/SKILL.md +40 -40
  393. package/pack/generated/codex/skills/rite-autocomplete/SKILL.md +40 -31
  394. package/pack/generated/codex/skills/rite-autocomplete/reference/loop.md +21 -21
  395. package/pack/generated/codex/skills/rite-autocomplete/reference/stop-conditions.md +15 -6
  396. package/pack/generated/codex/skills/rite-build/SKILL.md +42 -33
  397. package/pack/generated/codex/skills/rite-build/reference/afk-discipline.md +38 -38
  398. package/pack/generated/codex/skills/rite-build/reference/evidence-standard.md +10 -0
  399. package/pack/generated/codex/skills/rite-build/reference/forge.md +186 -156
  400. package/pack/generated/codex/skills/rite-build/reference/one-slice-cycle.md +4 -3
  401. package/pack/generated/codex/skills/rite-build/reference/phase-contract.md +96 -175
  402. package/pack/generated/codex/skills/rite-build/reference/wright-dispatch.md +129 -134
  403. package/pack/generated/codex/skills/rite-clarify/SKILL.md +105 -0
  404. package/pack/generated/codex/skills/rite-clarify/reference/decision-coverage.md +59 -0
  405. package/pack/generated/codex/skills/rite-converge/SKILL.md +42 -28
  406. package/pack/generated/codex/skills/rite-customize/SKILL.md +7 -3
  407. package/pack/generated/codex/skills/rite-define/SKILL.md +64 -35
  408. package/pack/generated/codex/skills/rite-define/reference/gates.md +16 -15
  409. package/pack/generated/codex/skills/rite-define/reference/plan-template.md +16 -8
  410. package/pack/generated/codex/skills/rite-doctor/SKILL.md +7 -3
  411. package/pack/generated/codex/skills/rite-dogfood/SKILL.md +7 -3
  412. package/pack/generated/codex/skills/rite-explain/SKILL.md +7 -3
  413. package/pack/generated/codex/skills/rite-frame/SKILL.md +7 -3
  414. package/pack/generated/codex/skills/rite-frame/reference/failure-modes.md +22 -24
  415. package/pack/generated/codex/skills/rite-handoff/SKILL.md +7 -3
  416. package/pack/generated/codex/skills/rite-learn/SKILL.md +7 -3
  417. package/pack/generated/codex/skills/rite-plan/SKILL.md +51 -19
  418. package/pack/generated/codex/skills/rite-plan/reference/task-breakdown.md +5 -1
  419. package/pack/generated/codex/skills/rite-polish/SKILL.md +34 -26
  420. package/pack/generated/codex/skills/rite-pov/SKILL.md +7 -3
  421. package/pack/generated/codex/skills/rite-pr-feedback/SKILL.md +7 -3
  422. package/pack/generated/codex/skills/rite-pressure-test/SKILL.md +7 -3
  423. package/pack/generated/codex/skills/rite-prototype/SKILL.md +32 -29
  424. package/pack/generated/codex/skills/rite-prove/SKILL.md +34 -20
  425. package/pack/generated/codex/skills/rite-quick/SKILL.md +7 -3
  426. package/pack/generated/codex/skills/rite-resolve/SKILL.md +19 -14
  427. package/pack/generated/codex/skills/rite-resolve/reference/answer-protocol.md +3 -0
  428. package/pack/generated/codex/skills/rite-review/SKILL.md +44 -31
  429. package/pack/generated/codex/skills/rite-seal/SKILL.md +7 -3
  430. package/pack/generated/codex/skills/rite-seal/reference/phase-contract.md +27 -92
  431. package/pack/generated/codex/skills/rite-seal/reference/risk-and-rollback.md +13 -0
  432. package/pack/generated/codex/skills/rite-ship/SKILL.md +7 -3
  433. package/pack/generated/codex/skills/rite-ship/reference/design-memory.md +31 -36
  434. package/pack/generated/codex/skills/rite-spec/SKILL.md +91 -138
  435. package/pack/generated/codex/skills/rite-spec/reference/investigation.md +37 -33
  436. package/pack/generated/codex/skills/rite-spec/reference/question-protocol.md +6 -2
  437. package/pack/generated/codex/skills/rite-spec/reference/spec-checklists.md +25 -24
  438. package/pack/generated/codex/skills/rite-spec/reference/spec-template.md +9 -2
  439. package/pack/generated/codex/skills/rite-spec/reference/state-workspace.md +13 -3
  440. package/pack/generated/codex/skills/rite-status/SKILL.md +7 -3
  441. package/pack/generated/codex/skills/rite-temper/SKILL.md +69 -50
  442. package/pack/generated/codex/skills/rite-temper/reference/review-dimensions.md +24 -22
  443. package/pack/generated/codex/skills/rite-vet/SKILL.md +117 -116
  444. package/pack/generated/codex/skills/rite-vet/reference/artifacts.md +42 -9
  445. package/pack/generated/codex/skills/rite-vet/reference/review-axes.md +40 -38
  446. package/pack/generated/codex/skills/rite-zoom-out/SKILL.md +7 -3
  447. package/package.json +1 -1
  448. package/scripts/build-release-tarball.sh +32 -15
  449. package/scripts/check-authority-drift.py +125 -0
  450. package/scripts/check-instruction-size-baseline.mjs +19 -11
  451. package/scripts/check-invocation-integrity.py +2 -0
  452. package/scripts/codex-generate.sh +69 -33
  453. package/scripts/grade-feature.sh +121 -40
  454. package/scripts/live-hosts/agent-result.schema.json +230 -0
  455. package/scripts/live-hosts/claude.sh +87 -0
  456. package/scripts/live-hosts/codex.sh +81 -0
  457. package/scripts/live-hosts/common.sh +113 -0
  458. package/scripts/live-hosts/fake-host.py +264 -0
  459. package/scripts/live-hosts/host-transport.py +287 -0
  460. package/scripts/release-check.sh +5 -1
  461. package/scripts/run-agent-contract-evals.py +1380 -0
  462. package/scripts/run-behavioral-evals.sh +24 -30
  463. package/scripts/run-evals.sh +1 -5
  464. package/scripts/run-live-behavioral-evals.py +1274 -144
  465. package/scripts/run-outcome-evals.sh +414 -88
  466. package/scripts/run-tests.mjs +30 -2
  467. package/scripts/skills-inventory.mjs +1 -1
  468. package/scripts/validate-workflow-security.py +39 -20
  469. package/scripts/validate-workspace-schema.py +362 -10
  470. package/scripts/validate.sh +21 -15
  471. package/engine/testdata/golden/TestParityWrightScope/devrites-edit-allowed.golden +0 -1
  472. /package/engine/testdata/golden/{TestParityReconcile/check-no-claimed.golden → TestParityBuildReadiness/arg=clarifyopen.golden} +0 -0
@@ -1,11 +1,8 @@
1
+ // Package iohooks contains hooks that access the network or start processes. The
2
+ // source cache revalidates citations with conditional HEAD requests, and the
3
+ // index refresher starts background reindexing. Errors do not block the caller.
1
4
  package iohooks
2
5
 
3
- // IO hooks: the ported bash hooks that do network or process-orchestration IO:
4
- // the source-citation cache (a conditional-HEAD revalidation cache) and the
5
- // code-index refresher (a detached background reindex). Unlike the deterministic
6
- // control-plane hooks these reach the network / spawn processes; they are kept in
7
- // their own file to mark that boundary. Every one is fail-open and never blocks.
8
-
9
6
  import (
10
7
  "context"
11
8
  "crypto/sha256"
@@ -15,27 +12,69 @@ import (
15
12
  "fmt"
16
13
  "io"
17
14
  "net/http"
15
+ "net/netip"
16
+ "net/url"
18
17
  "os"
19
18
  "os/exec"
20
19
  "path/filepath"
20
+ "regexp"
21
21
  "strings"
22
22
  "time"
23
23
 
24
24
  "github.com/devrites/devrites/internal/fsutil"
25
+ "github.com/devrites/devrites/internal/harness"
25
26
  )
26
27
 
27
- // exitOK is the fail-open success code these hooks return.
28
- const exitOK = 0
28
+ const (
29
+ // Current release tarballs are under 2 MiB and host binaries are about 13 MiB.
30
+ // The 64 MiB cap allows growth while keeping temporary files bounded.
31
+ maxInstallDownloadBytes = int64(64) << 20
32
+ // Release JSON is metadata rather than an asset, so it gets a smaller cap.
33
+ maxInstallJSONBytes = int64(4) << 20
34
+ // WebFetch results are untrusted JSON. One MiB covers the current reading cache
35
+ // while bounding the optional warning scan.
36
+ maxSourcePostPayloadBytes = int64(1) << 20
37
+
38
+ // exitOK is the fail-open success code these hooks return.
39
+ exitOK = 0
40
+
41
+ ingestWarningMode = "warn"
42
+
43
+ ingestReasonHiddenControl = "ingest_hidden_control"
44
+ ingestReasonInstructionRedirect = "ingest_instruction_redirect"
45
+ ingestReasonCredentialShape = "ingest_credential_shape" // #nosec G101 -- finding category, not a credential
46
+ )
29
47
 
30
48
  var (
31
- installHTTPClient = &http.Client{Timeout: 30 * time.Second}
49
+ installHTTPClient = &http.Client{
50
+ Timeout: 30 * time.Second,
51
+ CheckRedirect: checkInstallRedirect,
52
+ }
32
53
  sourceCacheHTTPClient = &http.Client{Timeout: 5 * time.Second}
54
+
55
+ ingestInstructionPatterns = []*regexp.Regexp{
56
+ regexp.MustCompile(`(?i)\b(?:ignore|disregard|forget|override)\s+(?:all\s+)?(?:previous|prior|earlier|above)\s+(?:instructions?|directives?|rules?)\b`),
57
+ regexp.MustCompile(`(?i)\b(?:follow|obey|execute)\s+(?:only\s+)?(?:these|the following|my)\s+(?:instructions?|directives?|commands?)\s+(?:instead|now)\b`),
58
+ }
59
+ ingestCredentialPatterns = []struct {
60
+ re *regexp.Regexp
61
+ accept func(string) bool
62
+ }{
63
+ {regexp.MustCompile(`AKIA[0-9A-Z]{16}`), func(s string) bool {
64
+ return !strings.HasSuffix(s, "EXAMPLE")
65
+ }},
66
+ {regexp.MustCompile(`gh[pousr]_[A-Za-z0-9]{36,255}`), variedCredential},
67
+ {regexp.MustCompile(`AIza[0-9A-Za-z_-]{35}`), variedCredential},
68
+ {regexp.MustCompile(`(?s)-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----[\r\n]+[A-Za-z0-9+/=\r\n]{64,}-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----`), func(string) bool {
69
+ return true
70
+ }},
71
+ }
33
72
  )
34
73
 
35
74
  // ---- source-citation cache -------------------------------------------------
36
75
 
37
- // sourceCacheEntry is the on-disk cache record: a page's post-processed reading
38
- // plus the origin validators that let a later 304 prove it is still fresh.
76
+ // sourceCacheEntry stores a processed page and the validators needed to confirm
77
+ // it with a later 304 response.
39
78
  type sourceCacheEntry struct {
40
79
  URL string `json:"url"`
41
80
  Prompt string `json:"prompt"`
@@ -47,9 +86,9 @@ type sourceCacheEntry struct {
47
86
 
48
87
  func sourceCacheOff() bool { return os.Getenv("DEVRITES_SOURCE_CACHE") == "off" }
49
88
 
50
- // ioProjectDir is the repo root the IO hooks key off: CLAUDE_PROJECT_DIR or CWD,
51
- // like the shell hooks. (The cache is project-global under .devrites/, not
52
- // feature-scoped, so it does not use the feature-dir resolver.)
89
+ // ioProjectDir returns the repository root from CLAUDE_PROJECT_DIR or the current
90
+ // directory, matching the shell hooks. The cache is project wide under
91
+ // .devrites, so it does not use the feature directory resolver.
53
92
  func ioProjectDir() string {
54
93
  if d := os.Getenv("CLAUDE_PROJECT_DIR"); d != "" {
55
94
  return d
@@ -68,11 +107,10 @@ func sourceCachePath(url string) string {
68
107
  return filepath.Join(ioProjectDir(), ".devrites", "source-cache", key+".json")
69
108
  }
70
109
 
71
- // hookSourceCachePre serves a cached page reading ONLY after revalidating it with a
72
- // conditional HEAD that returns 304: a fresh verification, not a memory read.
73
- // Ported from devrites-source-cache-pre.sh. Cache hit the reading on stderr +
74
- // exit 2 (Claude hands that to the agent in place of the fetch); miss/uncertain →
75
- // exit 0 (the real WebFetch proceeds). Fail-open on every uncertainty.
110
+ // hookSourceCachePre serves a cached page only after a conditional HEAD request
111
+ // returns 304. A cache hit writes the reading to stderr and exits 2 so Claude
112
+ // uses it instead of fetching. A miss or uncertain result exits 0 and lets
113
+ // WebFetch continue. This matches devrites-source-cache-pre.sh.
76
114
  func SourceCachePre(stdin io.Reader, stdout, stderr io.Writer) int {
77
115
  if sourceCacheOff() {
78
116
  return exitOK
@@ -95,7 +133,7 @@ func SourceCachePre(stdin io.Reader, stdout, stderr io.Writer) int {
95
133
  if json.Unmarshal(raw, &e) != nil {
96
134
  return exitOK
97
135
  }
98
- // No validator on the stored entry freshness is unprovable never serve.
136
+ // An entry without a validator cannot be revalidated.
99
137
  if e.ETag == "" && e.LastModified == "" {
100
138
  return exitOK
101
139
  }
@@ -112,11 +150,11 @@ func SourceCachePre(stdin io.Reader, stdout, stderr io.Writer) int {
112
150
  }
113
151
  resp, err := headURL(url, headers)
114
152
  if err != nil {
115
- return exitOK // slow/dead server must not stall the turn
153
+ return exitOK // A slow or unavailable server does not stall the turn.
116
154
  }
117
155
  _ = resp.Body.Close()
118
156
  if resp.StatusCode != http.StatusNotModified || e.Content == "" {
119
- return exitOK // changed / inconclusive let WebFetch re-fetch
157
+ return exitOK // Let WebFetch handle changed or inconclusive results.
120
158
  }
121
159
 
122
160
  fetched := e.FetchedAt
@@ -131,19 +169,20 @@ func SourceCachePre(stdin io.Reader, stdout, stderr io.Writer) int {
131
169
  fmt.Fprintln(stderr, "----- BEGIN CACHED CONTENT -----")
132
170
  fmt.Fprintf(stderr, "%s\n", e.Content)
133
171
  fmt.Fprintln(stderr, "----- END CACHED CONTENT -----")
134
- return 2 // the cache-hit channel: stderr replaces the fetch
172
+ return 2 // Claude uses stderr as the fetch result on a cache hit.
135
173
  }
136
174
 
137
- // hookSourceCachePost stores a completed WebFetch keyed on sha256(url) together with
138
- // the origin's validator headers, but only when the origin supplies a validator
139
- // (else a later 304 could never prove freshness). Ported from
140
- // devrites-source-cache-post.sh. Always exit 0.
141
- func SourceCachePost(stdin io.Reader, stdout, stderr io.Writer) int {
142
- if sourceCacheOff() {
175
+ // SourceCachePost stores a completed WebFetch under sha256(url) after a bounded
176
+ // read. For Claude, the optional warning scan runs before the cache write and
177
+ // returns typed metadata. The hook always exits 0.
178
+ func SourceCachePost(h harness.Harness, stdin io.Reader, stdout, stderr io.Writer) int {
179
+ warn := h == harness.Claude && os.Getenv("DEVRITES_INGEST_WARNING") == ingestWarningMode
180
+ cacheOff := sourceCacheOff()
181
+ if cacheOff && !warn {
143
182
  return exitOK
144
183
  }
145
- data, err := io.ReadAll(stdin)
146
- if err != nil {
184
+ data, err := io.ReadAll(io.LimitReader(stdin, maxSourcePostPayloadBytes+1))
185
+ if err != nil || int64(len(data)) > maxSourcePostPayloadBytes {
147
186
  return exitOK
148
187
  }
149
188
  payload := parseWebFetchPayload(data)
@@ -155,11 +194,22 @@ func SourceCachePost(stdin io.Reader, stdout, stderr io.Writer) int {
155
194
  if content == "" {
156
195
  return exitOK
157
196
  }
197
+ if warn {
198
+ findings := scanIngestContent(content, ingestOrigin(payload.ToolInput.URL))
199
+ if len(findings) > 0 {
200
+ _ = os.Remove(sourceCachePath(url))
201
+ writeIngestWarning(h, findings, stdout)
202
+ return exitOK
203
+ }
204
+ }
205
+ if cacheOff {
206
+ return exitOK
207
+ }
158
208
  prompt := payload.ToolInput.Prompt
159
209
 
160
210
  etag, lastMod := fetchValidators(url)
161
211
  entryPath := sourceCachePath(url)
162
- // No validator we could never prove freshness later → don't cache; clear stale.
212
+ // Clear stale data when no validator can support later revalidation.
163
213
  if etag == "" && lastMod == "" {
164
214
  _ = os.Remove(entryPath)
165
215
  return exitOK
@@ -180,8 +230,168 @@ func SourceCachePost(stdin io.Reader, stdout, stderr io.Writer) int {
180
230
  return exitOK
181
231
  }
182
232
 
183
- // fetchValidators does a HEAD to the origin (following redirects) and returns the
184
- // FINAL response's ETag / Last-Modified, empty on any failure.
233
+ type ingestFinding struct {
234
+ ReasonID string `json:"reason_id"`
235
+ Class string `json:"class"`
236
+ Severity string `json:"severity"`
237
+ Offset int `json:"offset"`
238
+ Origin string `json:"origin"`
239
+ CacheSkipped bool `json:"cache_skipped"`
240
+ }
241
+
242
+ func scanIngestContent(content, origin string) []ingestFinding {
243
+ findings := make([]ingestFinding, 0, 3)
244
+ appendFinding := func(reasonID, class string, offset int) {
245
+ findings = append(findings, ingestFinding{
246
+ ReasonID: reasonID,
247
+ Class: class,
248
+ Severity: "high",
249
+ Offset: offset,
250
+ Origin: origin,
251
+ CacheSkipped: true,
252
+ })
253
+ }
254
+ if offset, ok := hiddenControlOffset(content); ok {
255
+ appendFinding(ingestReasonHiddenControl, "hidden_control", offset)
256
+ }
257
+ if offset, ok := instructionRedirectOffset(content); ok {
258
+ appendFinding(ingestReasonInstructionRedirect, "instruction_redirect", offset)
259
+ }
260
+ if offset, ok := credentialOffset(content); ok {
261
+ appendFinding(ingestReasonCredentialShape, "credential_shape", offset)
262
+ }
263
+ return findings
264
+ }
265
+
266
+ func hiddenControlOffset(content string) (int, bool) {
267
+ for offset, r := range content {
268
+ switch {
269
+ case r >= '\u202a' && r <= '\u202e':
270
+ return offset, true
271
+ case r >= '\u2066' && r <= '\u2069':
272
+ return offset, true
273
+ case r == '\u200b' || r == '\u2060' || r == '\ufeff':
274
+ return offset, true
275
+ }
276
+ }
277
+ return 0, false
278
+ }
279
+
280
+ func instructionRedirectOffset(content string) (int, bool) {
281
+ best := -1
282
+ for _, pattern := range ingestInstructionPatterns {
283
+ for cursor := 0; cursor < len(content); {
284
+ loc := pattern.FindStringIndex(content[cursor:])
285
+ if loc == nil {
286
+ break
287
+ }
288
+ start, end := cursor+loc[0], cursor+loc[1]
289
+ if !instructionExample(content, start, end) && (best < 0 || start < best) {
290
+ best = start
291
+ }
292
+ cursor = end
293
+ }
294
+ }
295
+ return best, best >= 0
296
+ }
297
+
298
+ func instructionExample(content string, start, end int) bool {
299
+ lineStart := strings.LastIndexByte(content[:start], '\n') + 1
300
+ beforeStart := lineStart
301
+ if start-beforeStart > 160 {
302
+ beforeStart = start - 160
303
+ }
304
+ afterEnd := len(content)
305
+ if end+160 < afterEnd {
306
+ afterEnd = end + 160
307
+ }
308
+ if n := strings.IndexByte(content[end:afterEnd], '\n'); n >= 0 {
309
+ afterEnd = end + n
310
+ }
311
+ before, after := content[beforeStart:start], content[end:afterEnd]
312
+ for _, quote := range []byte{'`', '"', '\''} {
313
+ if strings.LastIndexByte(before, quote) >= 0 && strings.IndexByte(after, quote) >= 0 {
314
+ return true
315
+ }
316
+ }
317
+ lowerBefore := strings.ToLower(before)
318
+ trimmedBefore := strings.TrimSpace(lowerBefore)
319
+ if strings.HasPrefix(trimmedBefore, "//") || strings.HasPrefix(trimmedBefore, "# ") {
320
+ return true
321
+ }
322
+ for _, marker := range []string{
323
+ "do not ", "don't ", "never ", "example", "pattern", "detect",
324
+ "security guidance", "prompt injection",
325
+ } {
326
+ if strings.Contains(lowerBefore, marker) {
327
+ return true
328
+ }
329
+ }
330
+ return false
331
+ }
332
+
333
+ func credentialOffset(content string) (int, bool) {
334
+ best := -1
335
+ for _, pattern := range ingestCredentialPatterns {
336
+ for cursor := 0; cursor < len(content); {
337
+ loc := pattern.re.FindStringIndex(content[cursor:])
338
+ if loc == nil {
339
+ break
340
+ }
341
+ start, end := cursor+loc[0], cursor+loc[1]
342
+ if pattern.accept(content[start:end]) && (best < 0 || start < best) {
343
+ best = start
344
+ break
345
+ }
346
+ cursor = end
347
+ }
348
+ }
349
+ return best, best >= 0
350
+ }
351
+
352
+ func variedCredential(s string) bool {
353
+ var lower, upper, digit bool
354
+ for _, r := range s {
355
+ switch {
356
+ case r >= 'a' && r <= 'z':
357
+ lower = true
358
+ case r >= 'A' && r <= 'Z':
359
+ upper = true
360
+ case r >= '0' && r <= '9':
361
+ digit = true
362
+ }
363
+ }
364
+ return lower && upper && digit
365
+ }
366
+
367
+ func ingestOrigin(rawURL string) string {
368
+ origin := "claude/WebFetch/PostToolUse"
369
+ if u, err := url.Parse(rawURL); err == nil && u.Hostname() != "" {
370
+ host := strings.ToLower(u.Hostname())
371
+ if len(host) <= 253 && strings.IndexFunc(host, func(r rune) bool {
372
+ return !((r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '.' || r == '-' || r == ':')
373
+ }) < 0 {
374
+ origin += "@" + host
375
+ }
376
+ }
377
+ return origin
378
+ }
379
+
380
+ func writeIngestWarning(h harness.Harness, findings []ingestFinding, stdout io.Writer) {
381
+ metadata, err := json.Marshal(findings)
382
+ if err != nil {
383
+ return
384
+ }
385
+ context := "DevRites experimental ingestion warning: suspicious untrusted WebFetch content detected. Review this metadata before using the result: " + string(metadata)
386
+ envelope, err := h.PostToolContext(context)
387
+ if err == nil {
388
+ fmt.Fprintln(stdout, envelope)
389
+ }
390
+ }
391
+
392
+ // fetchValidators sends a HEAD request to the origin, follows redirects, and
393
+ // returns the final response's ETag and Last-Modified values. Failures return
394
+ // empty values.
185
395
  func fetchValidators(url string) (etag, lastMod string) {
186
396
  resp, err := headURL(url, nil)
187
397
  if err != nil {
@@ -205,25 +415,42 @@ func headURL(url string, headers http.Header) (*http.Response, error) {
205
415
  }
206
416
 
207
417
  func FetchJSON(url string, out any) error {
208
- resp, err := installHTTPClient.Get(url)
418
+ resp, err := installGet(url)
209
419
  if err != nil {
210
420
  return fmt.Errorf("fetch JSON: %w", err)
211
421
  }
212
422
  defer resp.Body.Close()
213
423
  if resp.StatusCode < 200 || resp.StatusCode > 299 {
214
- return fmt.Errorf("%s returned %s", url, resp.Status)
424
+ return fmt.Errorf("%s returned %s", resp.Request.URL.Redacted(), resp.Status)
425
+ }
426
+ if resp.ContentLength > maxInstallJSONBytes {
427
+ return fmt.Errorf("JSON response exceeds %d bytes", maxInstallJSONBytes)
215
428
  }
216
- return json.NewDecoder(resp.Body).Decode(out)
429
+ body, err := io.ReadAll(io.LimitReader(resp.Body, maxInstallJSONBytes+1))
430
+ if err != nil {
431
+ return fmt.Errorf("read JSON response: %w", err)
432
+ }
433
+ if int64(len(body)) > maxInstallJSONBytes {
434
+ return fmt.Errorf("JSON response exceeds %d bytes", maxInstallJSONBytes)
435
+ }
436
+ if err := json.Unmarshal(body, out); err != nil {
437
+ return fmt.Errorf("decode JSON response: %w", err)
438
+ }
439
+ return nil
217
440
  }
218
441
 
219
442
  func DownloadFile(url, path string) error {
220
- resp, err := installHTTPClient.Get(url)
443
+ resp, err := installGet(url)
221
444
  if err != nil {
222
445
  return fmt.Errorf("download to %s: %w", path, err)
223
446
  }
224
447
  if resp.StatusCode < 200 || resp.StatusCode > 299 {
225
448
  _ = resp.Body.Close()
226
- return fmt.Errorf("%s returned %s", url, resp.Status)
449
+ return fmt.Errorf("%s returned %s", resp.Request.URL.Redacted(), resp.Status)
450
+ }
451
+ if resp.ContentLength > maxInstallDownloadBytes {
452
+ _ = resp.Body.Close()
453
+ return fmt.Errorf("download exceeds %d bytes", maxInstallDownloadBytes)
227
454
  }
228
455
  dir := filepath.Dir(path)
229
456
  out, err := os.CreateTemp(dir, "."+filepath.Base(path)+".download-*")
@@ -239,12 +466,17 @@ func DownloadFile(url, path string) error {
239
466
  _ = os.Remove(tmp)
240
467
  }
241
468
  }()
242
- if _, err := io.Copy(out, resp.Body); err != nil {
469
+ written, err := io.Copy(out, io.LimitReader(resp.Body, maxInstallDownloadBytes+1))
470
+ if err != nil {
243
471
  _ = resp.Body.Close()
244
472
  return fmt.Errorf("write %s: %w", path, err)
245
473
  }
474
+ if written > maxInstallDownloadBytes {
475
+ _ = resp.Body.Close()
476
+ return fmt.Errorf("download exceeds %d bytes", maxInstallDownloadBytes)
477
+ }
246
478
  if err := resp.Body.Close(); err != nil {
247
- return fmt.Errorf("close download %s: %w", url, err)
479
+ return fmt.Errorf("close download %s: %w", resp.Request.URL.Redacted(), err)
248
480
  }
249
481
  if err := out.Chmod(0o644); err != nil {
250
482
  return fmt.Errorf("chmod %s: %w", path, err)
@@ -262,6 +494,66 @@ func DownloadFile(url, path string) error {
262
494
  return nil
263
495
  }
264
496
 
497
+ func installGet(rawURL string) (*http.Response, error) {
498
+ req, err := http.NewRequest(http.MethodGet, rawURL, nil)
499
+ if err != nil {
500
+ return nil, fmt.Errorf("build install request: %w", err)
501
+ }
502
+ if err := validateInstallURL(req.URL); err != nil {
503
+ return nil, err
504
+ }
505
+ return installHTTPClient.Do(req)
506
+ }
507
+
508
+ func validateInstallURL(u *url.URL) error {
509
+ if u == nil || u.Hostname() == "" {
510
+ return errors.New("install URL has no host")
511
+ }
512
+ switch strings.ToLower(u.Scheme) {
513
+ case "https":
514
+ return nil
515
+ case "http":
516
+ if ip, err := netip.ParseAddr(u.Hostname()); err == nil && ip.Unmap().IsLoopback() {
517
+ return nil
518
+ }
519
+ }
520
+ return fmt.Errorf("install URL must use HTTPS (HTTP is allowed only for literal loopback test addresses): %s", u.Redacted())
521
+ }
522
+
523
+ func checkInstallRedirect(req *http.Request, via []*http.Request) error {
524
+ if len(via) >= 10 {
525
+ return errors.New("stopped after 10 redirects")
526
+ }
527
+ if err := validateInstallURL(req.URL); err != nil {
528
+ return err
529
+ }
530
+ if len(via) == 0 {
531
+ return nil
532
+ }
533
+ if !isLoopbackURL(via[0].URL) && isLoopbackURL(req.URL) {
534
+ return fmt.Errorf("remote install URL cannot redirect to loopback: %s", req.URL.Redacted())
535
+ }
536
+ if !strings.EqualFold(via[len(via)-1].URL.Host, req.URL.Host) {
537
+ req.URL.User = nil
538
+ for _, name := range []string{"Authorization", "Proxy-Authorization", "Cookie"} {
539
+ req.Header.Del(name)
540
+ }
541
+ }
542
+ return nil
543
+ }
544
+
545
+ func isLoopbackURL(u *url.URL) bool {
546
+ if u == nil {
547
+ return false
548
+ }
549
+ host := strings.TrimSuffix(strings.ToLower(u.Hostname()), ".")
550
+ if host == "localhost" || strings.HasSuffix(host, ".localhost") {
551
+ return true
552
+ }
553
+ ip, err := netip.ParseAddr(host)
554
+ return err == nil && ip.Unmap().IsLoopback()
555
+ }
556
+
265
557
  type webFetchPayload struct {
266
558
  ToolInput struct {
267
559
  URL string `json:"url"`
@@ -314,17 +606,16 @@ var refreshExcludedDirs = map[string]bool{
314
606
  ".research": true, ".cursor": true, "dist": true, "build": true,
315
607
  }
316
608
 
317
- // hookRefreshIndexes keeps the optional code-intelligence indexes fresh after code
318
- // changes. Ported from devrites-refresh-indexes.sh. Trigger mode (no args) guards on
319
- // availability + a change-stamp, then spawns a DETACHED worker so Stop returns at
320
- // once; `--worker ROOT` is that detached work; `--force [ROOT]` runs it
321
- // synchronously and prints a report. Silent when no index tracks the repo;
322
- // fail-open, never blocks.
609
+ // hookRefreshIndexes refreshes optional code indexes after source changes. With
610
+ // no arguments it checks availability and a change stamp, then starts a detached
611
+ // worker so Stop can return. `--worker ROOT` runs that work, while
612
+ // `--force [ROOT]` runs it synchronously and prints a report. This matches
613
+ // devrites-refresh-indexes.sh and stays silent when no index tracks the repo.
323
614
  func RefreshIndexes(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
324
615
  if os.Getenv("DEVRITES_REFRESH_INDEXES") == "off" {
325
616
  return exitOK
326
617
  }
327
- // Worker branch: do the work, release the lock, exit.
618
+ // Run the detached worker, then release its lock.
328
619
  if len(args) >= 1 && args[0] == "--worker" {
329
620
  root := argOrProject(args, 1)
330
621
  st := computeRefreshState(root)
@@ -341,21 +632,21 @@ func RefreshIndexes(args []string, stdin io.Reader, stdout, stderr io.Writer) in
341
632
  root := argOrProject(rest, 0)
342
633
  st := computeRefreshState(root)
343
634
 
344
- // Availability guard: nothing to do unless at least one index tracks this repo.
635
+ // Skip repositories that no configured index tracks.
345
636
  if !dirExists(filepath.Join(root, ".codegraph")) &&
346
637
  !dirExists(filepath.Join(root, "graphify-out")) &&
347
638
  !cbmRegistered(root) {
348
639
  return exitOK
349
640
  }
350
- // Change guard (trigger mode): skip if nothing changed since the last refresh.
641
+ // In trigger mode, skip unchanged repositories.
351
642
  if !force && fileExists(st.stamp) && !repoChangedSince(root, st.stamp) {
352
643
  return exitOK
353
644
  }
354
- // Lock: never run two refreshes at once; steal a lock older than 30 minutes.
645
+ // Allow one refresh at a time and replace locks older than 30 minutes.
355
646
  if !acquireRefreshLock(st.lock) {
356
647
  return exitOK
357
648
  }
358
- // Debounce: stamp now so a no-op turn doesn't re-trigger.
649
+ // Stamp the attempt so a no-op turn does not trigger it again.
359
650
  _ = os.WriteFile(st.stamp, []byte(""), 0o644)
360
651
 
361
652
  if force {
@@ -363,7 +654,7 @@ func RefreshIndexes(args []string, stdin io.Reader, stdout, stderr io.Writer) in
363
654
  _ = os.RemoveAll(st.lock)
364
655
  return exitOK
365
656
  }
366
- // Detached worker: Stop returns immediately.
657
+ // Start a detached worker so Stop can return immediately.
367
658
  spawnRefreshWorker(root, st)
368
659
  return exitOK
369
660
  }
@@ -449,8 +740,8 @@ func spawnRefreshWorker(root string, st refreshState) {
449
740
  }
450
741
  }
451
742
 
452
- // refreshWorker runs the actual reindex for each optional tool that is installed
453
- // AND tracks this repo, each under a bounded timeout. Missing tool = no-op.
743
+ // refreshWorker runs each installed indexer that tracks the repository, with a
744
+ // timeout for each command.
454
745
  func refreshWorker(root string, st refreshState, out io.Writer) {
455
746
  fmt.Fprintf(out, "[%s] refresh start: %s\n", nowStamp(), root)
456
747
  if cbmRegistered(root) {
@@ -469,8 +760,8 @@ func refreshWorker(root string, st refreshState, out io.Writer) {
469
760
  fmt.Fprintf(out, "[%s] refresh done\n", nowStamp())
470
761
  }
471
762
 
472
- // runIndexTool runs one external index command under a timeout, tee-ing its combined
473
- // output; a failure or timeout is reported, never fatal.
763
+ // runIndexTool runs one index command with a timeout and copies its combined
764
+ // output. Failures are reported but do not stop other indexers.
474
765
  func runIndexTool(out io.Writer, timeout time.Duration, name string, args ...string) {
475
766
  ctx, cancel := context.WithTimeout(context.Background(), timeout)
476
767
  defer cancel()
@@ -505,8 +796,8 @@ func runIndexTool(out io.Writer, timeout time.Duration, name string, args ...str
505
796
  }
506
797
  }
507
798
 
508
- // cbmRegistered reports whether codebase-memory-mcp is installed AND tracks root:
509
- // a cheap repo-local marker first, then the project registry.
799
+ // cbmRegistered reports whether codebase-memory-mcp is installed and tracks
800
+ // root. It checks a repository marker before consulting the project registry.
510
801
  func cbmRegistered(root string) bool {
511
802
  if !hasTool("codebase-memory-mcp") {
512
803
  return false