devrites 3.0.6 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (472) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +67 -45
  3. package/SECURITY.md +31 -29
  4. package/docs/adr/0001-go-engine-as-control-plane.md +1 -1
  5. package/docs/adr/0002-dual-host-harness.md +1 -1
  6. package/docs/adr/0004-state-schema-phases-sections.md +1 -2
  7. package/docs/adr/0006-clock-seam-and-engine-ci-gates.md +3 -4
  8. package/docs/adr/0009-prebuild-decision-coverage-and-readiness.md +67 -0
  9. package/docs/adr/0010-agent-first-fresh-context-orchestration.md +71 -0
  10. package/docs/adr/0011-define-plan-transition-rights.md +28 -0
  11. package/docs/adr/README.md +3 -0
  12. package/docs/agents/triage-labels.md +7 -7
  13. package/docs/architecture.md +157 -140
  14. package/docs/cli.md +41 -12
  15. package/docs/command-map.md +49 -35
  16. package/docs/engine/agent-contract.md +92 -15
  17. package/docs/engine/commands.md +242 -72
  18. package/docs/engine/state-schema.md +52 -23
  19. package/docs/engine/workspace-schema.md +94 -15
  20. package/docs/extensions.md +1 -1
  21. package/docs/flow.md +80 -50
  22. package/docs/harness-compliance.md +29 -5
  23. package/docs/orchestration.md +107 -79
  24. package/docs/quick-reference.md +7 -3
  25. package/docs/release.md +4 -3
  26. package/docs/skills.md +64 -38
  27. package/docs/usage.md +57 -38
  28. package/engine/cmd/releasepack/main.go +219 -0
  29. package/engine/cmd/releasepack/main_test.go +170 -0
  30. package/engine/commands.go +170 -23
  31. package/engine/git_guard.go +187 -0
  32. package/engine/git_guard_test.go +283 -0
  33. package/engine/hookpolicy.go +53 -55
  34. package/engine/hookpolicy_test.go +91 -1
  35. package/engine/hooks.go +296 -75
  36. package/engine/hooks_events_test.go +276 -6
  37. package/engine/hooks_workspace.go +640 -159
  38. package/engine/internal/devritespaths/paths.go +65 -10
  39. package/engine/internal/devritespaths/paths_test.go +110 -0
  40. package/engine/internal/doctor/doctor.go +153 -23
  41. package/engine/internal/doctor/doctor_test.go +74 -0
  42. package/engine/internal/forge/forge.go +940 -0
  43. package/engine/internal/forge/forge_test.go +576 -0
  44. package/engine/internal/forge/git.go +245 -0
  45. package/engine/internal/forge/liveness_unix.go +48 -0
  46. package/engine/internal/forge/liveness_windows.go +67 -0
  47. package/engine/internal/forge/manifest.go +402 -0
  48. package/engine/internal/gate/gate.go +90 -71
  49. package/engine/internal/gate/gate_test.go +71 -2
  50. package/engine/internal/harness/compliance.go +18 -24
  51. package/engine/internal/harness/harness.go +37 -44
  52. package/engine/internal/harness/harness_test.go +21 -5
  53. package/engine/internal/install/install.go +575 -58
  54. package/engine/internal/install/install_test.go +490 -9
  55. package/engine/internal/iohooks/iohooks.go +350 -59
  56. package/engine/internal/iohooks/iohooks_test.go +421 -1
  57. package/engine/internal/lib/buildreadiness.go +35 -18
  58. package/engine/internal/lib/clarifyreturn.go +91 -0
  59. package/engine/internal/lib/clarifyreturn_test.go +123 -0
  60. package/engine/internal/lib/context.go +54 -15
  61. package/engine/internal/lib/cursor_compat_test.go +8 -0
  62. package/engine/internal/lib/extensions.go +2 -3
  63. package/engine/internal/lib/gitauthority.go +601 -0
  64. package/engine/internal/lib/gitauthority_test.go +346 -0
  65. package/engine/internal/lib/jsonout.go +25 -11
  66. package/engine/internal/lib/jsonout_test.go +16 -0
  67. package/engine/internal/lib/lanes.go +14 -8
  68. package/engine/internal/lib/observability_test.go +358 -0
  69. package/engine/internal/lib/packageexistence.go +151 -35
  70. package/engine/internal/lib/packageexistence_test.go +163 -0
  71. package/engine/internal/lib/progress.go +11 -9
  72. package/engine/internal/lib/provenance.go +462 -0
  73. package/engine/internal/lib/provenance_test.go +154 -0
  74. package/engine/internal/lib/readiness_contract.json +111 -0
  75. package/engine/internal/lib/readinessartifact.go +510 -0
  76. package/engine/internal/lib/readinessartifact_test.go +321 -0
  77. package/engine/internal/lib/reconcile.go +712 -88
  78. package/engine/internal/lib/reconcile_test.go +335 -16
  79. package/engine/internal/lib/recoveryattempts.go +298 -0
  80. package/engine/internal/lib/recoveryattempts_test.go +215 -0
  81. package/engine/internal/lib/resolve.go +23 -19
  82. package/engine/internal/lib/runbook_context_test.go +20 -0
  83. package/engine/internal/lib/session.go +701 -9
  84. package/engine/internal/lib/session_test.go +80 -13
  85. package/engine/internal/lib/testintegrity.go +33 -37
  86. package/engine/internal/lib/testintegrity_test.go +63 -1
  87. package/engine/internal/migrate/migrate.go +81 -1
  88. package/engine/internal/migrate/migrate_test.go +22 -1
  89. package/engine/internal/reason/reason.go +180 -0
  90. package/engine/internal/reason/reason_test.go +35 -0
  91. package/engine/internal/rootfacts/facts.go +466 -0
  92. package/engine/internal/rootfacts/facts_test.go +306 -0
  93. package/engine/internal/safepath/safepath.go +55 -0
  94. package/engine/internal/safepath/safepath_test.go +69 -0
  95. package/engine/internal/safepath/safepath_windows_test.go +26 -0
  96. package/engine/internal/state/clarify_transition.go +165 -0
  97. package/engine/internal/state/clarify_transition_test.go +130 -0
  98. package/engine/internal/state/cmd/workflowmanifest/main.go +12 -8
  99. package/engine/internal/state/cursor.go +58 -0
  100. package/engine/internal/state/cursor_test.go +42 -1
  101. package/engine/internal/state/feature.go +35 -48
  102. package/engine/internal/state/schema.go +90 -37
  103. package/engine/internal/state/snapshot.go +18 -2
  104. package/engine/internal/state/state_test.go +187 -7
  105. package/engine/internal/state/status.go +48 -11
  106. package/engine/internal/state/workflow_manifest.json +231 -50
  107. package/engine/internal/toolpolicy/classifier.go +533 -0
  108. package/engine/internal/toolpolicy/classifier_test.go +424 -0
  109. package/engine/internal/toolpolicy/git.go +616 -0
  110. package/engine/internal/toolpolicy/scanner.go +382 -0
  111. package/engine/main.go +160 -77
  112. package/engine/observability_cli_test.go +52 -0
  113. package/engine/root_routing_test.go +277 -0
  114. package/engine/testdata/golden/TestParityBuildReadiness/arg=approved.golden +1 -1
  115. package/engine/testdata/golden/TestParityBuildReadiness/arg=emptystatus.golden +1 -1
  116. package/engine/testdata/golden/TestParityBuildReadiness/arg=noclarify.golden +1 -0
  117. package/engine/testdata/golden/TestParityBuildReadiness/arg=novet.golden +1 -0
  118. package/engine/testdata/golden/TestParityBuildReadiness/arg=stalevet.golden +1 -0
  119. package/engine/testdata/golden/TestParityBuildReadiness/arg=trailhash.golden +1 -1
  120. package/engine/testdata/golden/TestParityBuildReadiness/arg=trailpipe.golden +1 -1
  121. package/engine/testdata/golden/TestParityBuildReadiness/arg=vetnotready.golden +1 -0
  122. package/engine/testdata/golden/TestParityProgress/arg=allbuilt.golden +1 -1
  123. package/engine/testdata/golden/TestParityProgress/arg=done.golden +1 -1
  124. package/engine/testdata/golden/TestParityProgress/arg=mid.golden +1 -1
  125. package/engine/testdata/golden/TestParityProgress/arg=nophase.golden +1 -1
  126. package/engine/testdata/golden/TestParityProgress/arg=noslice.golden +1 -1
  127. package/engine/testdata/golden/TestParityProgress/arg=plan.golden +1 -1
  128. package/engine/testdata/golden/TestParityProgress/arg=seal.golden +1 -1
  129. package/engine/testdata/golden/TestParityReconcile/check-clean.golden +1 -1
  130. package/engine/testdata/golden/TestParityReconcile/inline-fallback.golden +1 -0
  131. package/engine/testdata/golden/TestParityReconcile/snapshot-no-allowlist.golden +1 -0
  132. package/engine/testdata/golden/TestParityWrightScope/devrites-edit-denied.golden +2 -0
  133. package/engine/testdata/golden/TestParityWrightScope/out-of-scope-enforce-denies.golden +1 -1
  134. package/engine/tests/adr_0011_define_plan_test.go +30 -0
  135. package/engine/tests/budget_test.go +19 -8
  136. package/engine/tests/concurrency_cli_test.go +7 -1
  137. package/engine/tests/doctor_cli_test.go +148 -2
  138. package/engine/tests/forge_cli_test.go +463 -0
  139. package/engine/tests/gate_test.go +63 -8
  140. package/engine/tests/hook_test.go +260 -8
  141. package/engine/tests/hooks_io_test.go +94 -3
  142. package/engine/tests/json_contract_test.go +127 -2
  143. package/engine/tests/migrate_cli_test.go +2 -2
  144. package/engine/tests/parity_buildreadiness_test.go +141 -10
  145. package/engine/tests/parity_learnings_test.go +16 -17
  146. package/engine/tests/parity_reconcile_test.go +20 -17
  147. package/engine/tests/parity_resolve_test.go +12 -11
  148. package/engine/tests/parity_test.go +21 -17
  149. package/pack/.claude/agents/devrites-code-reviewer.md +62 -48
  150. package/pack/.claude/agents/devrites-devex-reviewer.md +69 -53
  151. package/pack/.claude/agents/devrites-doubt-reviewer.md +29 -22
  152. package/pack/.claude/agents/devrites-evidence-scout.md +69 -0
  153. package/pack/.claude/agents/devrites-forge-judge.md +74 -61
  154. package/pack/.claude/agents/devrites-frontend-reviewer.md +48 -39
  155. package/pack/.claude/agents/devrites-performance-reviewer.md +49 -40
  156. package/pack/.claude/agents/devrites-plan-drafter.md +71 -0
  157. package/pack/.claude/agents/devrites-plan-reviewer.md +80 -47
  158. package/pack/.claude/agents/devrites-proof-runner.md +74 -0
  159. package/pack/.claude/agents/devrites-retrospector.md +48 -45
  160. package/pack/.claude/agents/devrites-security-auditor.md +46 -36
  161. package/pack/.claude/agents/devrites-simplifier-reviewer.md +50 -46
  162. package/pack/.claude/agents/devrites-slice-wright.md +153 -165
  163. package/pack/.claude/agents/devrites-spec-reviewer.md +34 -32
  164. package/pack/.claude/agents/devrites-strategy-reviewer.md +63 -34
  165. package/pack/.claude/agents/devrites-test-analyst.md +40 -30
  166. package/pack/.claude/settings.json +2 -1
  167. package/pack/.claude/skills/devrites-audit/SKILL.md +40 -61
  168. package/pack/.claude/skills/devrites-debug-recovery/SKILL.md +24 -13
  169. package/pack/.claude/skills/devrites-debug-recovery/reference/build-the-loop.md +24 -21
  170. package/pack/.claude/skills/devrites-debug-recovery/reference/cleanup-and-classify.md +34 -6
  171. package/pack/.claude/skills/devrites-debug-recovery/reference/instrumentation.md +2 -2
  172. package/pack/.claude/skills/devrites-doubt/SKILL.md +25 -17
  173. package/pack/.claude/skills/devrites-interview/SKILL.md +53 -52
  174. package/pack/.claude/skills/devrites-lib/SKILL.md +11 -9
  175. package/pack/.claude/skills/devrites-lib/reference/intent-map.md +3 -2
  176. package/pack/.claude/skills/devrites-lib/reference/parallel-dispatch.md +82 -137
  177. package/pack/.claude/skills/devrites-lib/reference/reply-contract.md +8 -1
  178. package/pack/.claude/skills/devrites-lib/reference/standards/README.md +38 -55
  179. package/pack/.claude/skills/devrites-lib/reference/standards/afk-hitl.md +53 -27
  180. package/pack/.claude/skills/devrites-lib/reference/standards/agents.md +198 -190
  181. package/pack/.claude/skills/devrites-lib/reference/standards/anti-patterns.md +5 -15
  182. package/pack/.claude/skills/devrites-lib/reference/standards/ci-cd.md +27 -58
  183. package/pack/.claude/skills/devrites-lib/reference/standards/code-review.md +16 -52
  184. package/pack/.claude/skills/devrites-lib/reference/standards/coding-style.md +2 -10
  185. package/pack/.claude/skills/devrites-lib/reference/standards/context-hygiene.md +18 -61
  186. package/pack/.claude/skills/devrites-lib/reference/standards/core.md +23 -18
  187. package/pack/.claude/skills/devrites-lib/reference/standards/deprecation.md +17 -57
  188. package/pack/.claude/skills/devrites-lib/reference/standards/development-workflow.md +4 -33
  189. package/pack/.claude/skills/devrites-lib/reference/standards/git-workflow.md +4 -20
  190. package/pack/.claude/skills/devrites-lib/reference/standards/hooks.md +3 -14
  191. package/pack/.claude/skills/devrites-lib/reference/standards/patterns.md +9 -25
  192. package/pack/.claude/skills/devrites-lib/reference/standards/performance.md +0 -9
  193. package/pack/.claude/skills/devrites-lib/reference/standards/principles.md +1 -3
  194. package/pack/.claude/skills/devrites-lib/reference/standards/security.md +17 -7
  195. package/pack/.claude/skills/devrites-lib/reference/standards/testing.md +1 -27
  196. package/pack/.claude/skills/devrites-lib/reference/standards/tooling.md +46 -50
  197. package/pack/.claude/skills/devrites-lib/reference/workspace-artifact-schema.md +23 -10
  198. package/pack/.claude/skills/devrites-source-driven/SKILL.md +23 -22
  199. package/pack/.claude/skills/rite/SKILL.md +8 -6
  200. package/pack/.claude/skills/rite/reference/menu.md +8 -6
  201. package/pack/.claude/skills/rite-adopt/SKILL.md +33 -37
  202. package/pack/.claude/skills/rite-autocomplete/SKILL.md +33 -28
  203. package/pack/.claude/skills/rite-autocomplete/reference/loop.md +21 -21
  204. package/pack/.claude/skills/rite-autocomplete/reference/stop-conditions.md +15 -6
  205. package/pack/.claude/skills/rite-build/SKILL.md +35 -30
  206. package/pack/.claude/skills/rite-build/reference/afk-discipline.md +38 -38
  207. package/pack/.claude/skills/rite-build/reference/evidence-standard.md +10 -0
  208. package/pack/.claude/skills/rite-build/reference/forge.md +186 -156
  209. package/pack/.claude/skills/rite-build/reference/one-slice-cycle.md +4 -3
  210. package/pack/.claude/skills/rite-build/reference/phase-contract.md +96 -175
  211. package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +129 -134
  212. package/pack/.claude/skills/rite-clarify/SKILL.md +89 -0
  213. package/pack/.claude/skills/rite-clarify/reference/decision-coverage.md +59 -0
  214. package/pack/.claude/skills/rite-converge/SKILL.md +35 -25
  215. package/pack/.claude/skills/rite-define/SKILL.md +57 -32
  216. package/pack/.claude/skills/rite-define/reference/gates.md +16 -15
  217. package/pack/.claude/skills/rite-define/reference/plan-template.md +16 -8
  218. package/pack/.claude/skills/rite-frame/reference/failure-modes.md +22 -24
  219. package/pack/.claude/skills/rite-plan/SKILL.md +44 -16
  220. package/pack/.claude/skills/rite-plan/reference/task-breakdown.md +4 -0
  221. package/pack/.claude/skills/rite-polish/SKILL.md +27 -23
  222. package/pack/.claude/skills/rite-prototype/SKILL.md +25 -26
  223. package/pack/.claude/skills/rite-prove/SKILL.md +27 -17
  224. package/pack/.claude/skills/rite-resolve/SKILL.md +12 -11
  225. package/pack/.claude/skills/rite-resolve/reference/answer-protocol.md +3 -0
  226. package/pack/.claude/skills/rite-review/SKILL.md +37 -28
  227. package/pack/.claude/skills/rite-seal/reference/phase-contract.md +27 -92
  228. package/pack/.claude/skills/rite-seal/reference/risk-and-rollback.md +13 -0
  229. package/pack/.claude/skills/rite-ship/reference/design-memory.md +31 -36
  230. package/pack/.claude/skills/rite-spec/SKILL.md +84 -135
  231. package/pack/.claude/skills/rite-spec/reference/investigation.md +37 -33
  232. package/pack/.claude/skills/rite-spec/reference/question-protocol.md +6 -2
  233. package/pack/.claude/skills/rite-spec/reference/spec-checklists.md +25 -24
  234. package/pack/.claude/skills/rite-spec/reference/spec-template.md +9 -2
  235. package/pack/.claude/skills/rite-spec/reference/state-workspace.md +13 -3
  236. package/pack/.claude/skills/rite-temper/SKILL.md +62 -47
  237. package/pack/.claude/skills/rite-temper/reference/review-dimensions.md +24 -22
  238. package/pack/.claude/skills/rite-vet/SKILL.md +110 -113
  239. package/pack/.claude/skills/rite-vet/reference/artifacts.md +42 -9
  240. package/pack/.claude/skills/rite-vet/reference/review-axes.md +39 -37
  241. package/pack/generated/claude/agents/devrites-code-reviewer.md +62 -48
  242. package/pack/generated/claude/agents/devrites-devex-reviewer.md +69 -53
  243. package/pack/generated/claude/agents/devrites-doubt-reviewer.md +29 -22
  244. package/pack/generated/claude/agents/devrites-evidence-scout.md +69 -0
  245. package/pack/generated/claude/agents/devrites-forge-judge.md +74 -61
  246. package/pack/generated/claude/agents/devrites-frontend-reviewer.md +48 -39
  247. package/pack/generated/claude/agents/devrites-performance-reviewer.md +49 -40
  248. package/pack/generated/claude/agents/devrites-plan-drafter.md +71 -0
  249. package/pack/generated/claude/agents/devrites-plan-reviewer.md +80 -47
  250. package/pack/generated/claude/agents/devrites-proof-runner.md +74 -0
  251. package/pack/generated/claude/agents/devrites-retrospector.md +48 -45
  252. package/pack/generated/claude/agents/devrites-security-auditor.md +46 -36
  253. package/pack/generated/claude/agents/devrites-simplifier-reviewer.md +50 -46
  254. package/pack/generated/claude/agents/devrites-slice-wright.md +153 -165
  255. package/pack/generated/claude/agents/devrites-spec-reviewer.md +34 -32
  256. package/pack/generated/claude/agents/devrites-strategy-reviewer.md +63 -34
  257. package/pack/generated/claude/agents/devrites-test-analyst.md +40 -30
  258. package/pack/generated/claude/settings.json +2 -1
  259. package/pack/generated/claude/skills/devrites-audit/SKILL.md +40 -61
  260. package/pack/generated/claude/skills/devrites-debug-recovery/SKILL.md +24 -13
  261. package/pack/generated/claude/skills/devrites-debug-recovery/reference/build-the-loop.md +24 -21
  262. package/pack/generated/claude/skills/devrites-debug-recovery/reference/cleanup-and-classify.md +34 -6
  263. package/pack/generated/claude/skills/devrites-debug-recovery/reference/instrumentation.md +2 -2
  264. package/pack/generated/claude/skills/devrites-doubt/SKILL.md +25 -17
  265. package/pack/generated/claude/skills/devrites-interview/SKILL.md +53 -52
  266. package/pack/generated/claude/skills/devrites-lib/SKILL.md +11 -9
  267. package/pack/generated/claude/skills/devrites-lib/reference/intent-map.md +3 -2
  268. package/pack/generated/claude/skills/devrites-lib/reference/parallel-dispatch.md +82 -137
  269. package/pack/generated/claude/skills/devrites-lib/reference/reply-contract.md +8 -1
  270. package/pack/generated/claude/skills/devrites-lib/reference/standards/README.md +38 -55
  271. package/pack/generated/claude/skills/devrites-lib/reference/standards/afk-hitl.md +53 -27
  272. package/pack/generated/claude/skills/devrites-lib/reference/standards/agents.md +198 -190
  273. package/pack/generated/claude/skills/devrites-lib/reference/standards/anti-patterns.md +5 -15
  274. package/pack/generated/claude/skills/devrites-lib/reference/standards/ci-cd.md +27 -58
  275. package/pack/generated/claude/skills/devrites-lib/reference/standards/code-review.md +16 -52
  276. package/pack/generated/claude/skills/devrites-lib/reference/standards/coding-style.md +2 -10
  277. package/pack/generated/claude/skills/devrites-lib/reference/standards/context-hygiene.md +18 -61
  278. package/pack/generated/claude/skills/devrites-lib/reference/standards/core.md +23 -18
  279. package/pack/generated/claude/skills/devrites-lib/reference/standards/deprecation.md +17 -57
  280. package/pack/generated/claude/skills/devrites-lib/reference/standards/development-workflow.md +4 -33
  281. package/pack/generated/claude/skills/devrites-lib/reference/standards/git-workflow.md +4 -20
  282. package/pack/generated/claude/skills/devrites-lib/reference/standards/hooks.md +3 -14
  283. package/pack/generated/claude/skills/devrites-lib/reference/standards/patterns.md +9 -25
  284. package/pack/generated/claude/skills/devrites-lib/reference/standards/performance.md +0 -9
  285. package/pack/generated/claude/skills/devrites-lib/reference/standards/principles.md +1 -3
  286. package/pack/generated/claude/skills/devrites-lib/reference/standards/security.md +17 -7
  287. package/pack/generated/claude/skills/devrites-lib/reference/standards/testing.md +1 -27
  288. package/pack/generated/claude/skills/devrites-lib/reference/standards/tooling.md +46 -50
  289. package/pack/generated/claude/skills/devrites-lib/reference/workspace-artifact-schema.md +23 -10
  290. package/pack/generated/claude/skills/devrites-source-driven/SKILL.md +23 -22
  291. package/pack/generated/claude/skills/rite/SKILL.md +8 -6
  292. package/pack/generated/claude/skills/rite/reference/menu.md +8 -6
  293. package/pack/generated/claude/skills/rite-adopt/SKILL.md +33 -37
  294. package/pack/generated/claude/skills/rite-autocomplete/SKILL.md +33 -28
  295. package/pack/generated/claude/skills/rite-autocomplete/reference/loop.md +21 -21
  296. package/pack/generated/claude/skills/rite-autocomplete/reference/stop-conditions.md +15 -6
  297. package/pack/generated/claude/skills/rite-build/SKILL.md +35 -30
  298. package/pack/generated/claude/skills/rite-build/reference/afk-discipline.md +38 -38
  299. package/pack/generated/claude/skills/rite-build/reference/evidence-standard.md +10 -0
  300. package/pack/generated/claude/skills/rite-build/reference/forge.md +186 -156
  301. package/pack/generated/claude/skills/rite-build/reference/one-slice-cycle.md +4 -3
  302. package/pack/generated/claude/skills/rite-build/reference/phase-contract.md +96 -175
  303. package/pack/generated/claude/skills/rite-build/reference/wright-dispatch.md +129 -134
  304. package/pack/generated/claude/skills/rite-clarify/SKILL.md +89 -0
  305. package/pack/generated/claude/skills/rite-clarify/reference/decision-coverage.md +59 -0
  306. package/pack/generated/claude/skills/rite-converge/SKILL.md +35 -25
  307. package/pack/generated/claude/skills/rite-define/SKILL.md +57 -32
  308. package/pack/generated/claude/skills/rite-define/reference/gates.md +16 -15
  309. package/pack/generated/claude/skills/rite-define/reference/plan-template.md +16 -8
  310. package/pack/generated/claude/skills/rite-frame/reference/failure-modes.md +22 -24
  311. package/pack/generated/claude/skills/rite-plan/SKILL.md +44 -16
  312. package/pack/generated/claude/skills/rite-plan/reference/task-breakdown.md +4 -0
  313. package/pack/generated/claude/skills/rite-polish/SKILL.md +27 -23
  314. package/pack/generated/claude/skills/rite-prototype/SKILL.md +25 -26
  315. package/pack/generated/claude/skills/rite-prove/SKILL.md +27 -17
  316. package/pack/generated/claude/skills/rite-resolve/SKILL.md +12 -11
  317. package/pack/generated/claude/skills/rite-resolve/reference/answer-protocol.md +3 -0
  318. package/pack/generated/claude/skills/rite-review/SKILL.md +37 -28
  319. package/pack/generated/claude/skills/rite-seal/reference/phase-contract.md +27 -92
  320. package/pack/generated/claude/skills/rite-seal/reference/risk-and-rollback.md +13 -0
  321. package/pack/generated/claude/skills/rite-ship/reference/design-memory.md +31 -36
  322. package/pack/generated/claude/skills/rite-spec/SKILL.md +84 -135
  323. package/pack/generated/claude/skills/rite-spec/reference/investigation.md +37 -33
  324. package/pack/generated/claude/skills/rite-spec/reference/question-protocol.md +6 -2
  325. package/pack/generated/claude/skills/rite-spec/reference/spec-checklists.md +25 -24
  326. package/pack/generated/claude/skills/rite-spec/reference/spec-template.md +9 -2
  327. package/pack/generated/claude/skills/rite-spec/reference/state-workspace.md +13 -3
  328. package/pack/generated/claude/skills/rite-temper/SKILL.md +62 -47
  329. package/pack/generated/claude/skills/rite-temper/reference/review-dimensions.md +24 -22
  330. package/pack/generated/claude/skills/rite-vet/SKILL.md +110 -113
  331. package/pack/generated/claude/skills/rite-vet/reference/artifacts.md +42 -9
  332. package/pack/generated/claude/skills/rite-vet/reference/review-axes.md +39 -37
  333. package/pack/generated/codex/AGENTS.md +5 -2
  334. package/pack/generated/codex/agents/devrites-code-reviewer.toml +69 -47
  335. package/pack/generated/codex/agents/devrites-devex-reviewer.toml +75 -51
  336. package/pack/generated/codex/agents/devrites-doubt-reviewer.toml +35 -20
  337. package/pack/generated/codex/agents/devrites-evidence-scout.toml +75 -0
  338. package/pack/generated/codex/agents/devrites-forge-judge.toml +80 -59
  339. package/pack/generated/codex/agents/devrites-frontend-reviewer.toml +54 -37
  340. package/pack/generated/codex/agents/devrites-performance-reviewer.toml +55 -38
  341. package/pack/generated/codex/agents/devrites-plan-drafter.toml +77 -0
  342. package/pack/generated/codex/agents/devrites-plan-reviewer.toml +82 -47
  343. package/pack/generated/codex/agents/devrites-proof-runner.toml +80 -0
  344. package/pack/generated/codex/agents/devrites-retrospector.toml +54 -43
  345. package/pack/generated/codex/agents/devrites-security-auditor.toml +53 -35
  346. package/pack/generated/codex/agents/devrites-simplifier-reviewer.toml +56 -44
  347. package/pack/generated/codex/agents/devrites-slice-wright.toml +159 -163
  348. package/pack/generated/codex/agents/devrites-spec-reviewer.toml +40 -30
  349. package/pack/generated/codex/agents/devrites-strategy-reviewer.toml +65 -34
  350. package/pack/generated/codex/agents/devrites-test-analyst.toml +46 -28
  351. package/pack/generated/codex/hooks.json +4 -14
  352. package/pack/generated/codex/skills/devrites-api-interface/SKILL.md +7 -3
  353. package/pack/generated/codex/skills/devrites-audit/SKILL.md +47 -64
  354. package/pack/generated/codex/skills/devrites-browser-proof/SKILL.md +7 -3
  355. package/pack/generated/codex/skills/devrites-debug-recovery/SKILL.md +31 -16
  356. package/pack/generated/codex/skills/devrites-debug-recovery/reference/build-the-loop.md +24 -21
  357. package/pack/generated/codex/skills/devrites-debug-recovery/reference/cleanup-and-classify.md +34 -6
  358. package/pack/generated/codex/skills/devrites-debug-recovery/reference/instrumentation.md +2 -2
  359. package/pack/generated/codex/skills/devrites-doubt/SKILL.md +32 -20
  360. package/pack/generated/codex/skills/devrites-frontend-craft/SKILL.md +7 -3
  361. package/pack/generated/codex/skills/devrites-interview/SKILL.md +60 -55
  362. package/pack/generated/codex/skills/devrites-lib/SKILL.md +18 -12
  363. package/pack/generated/codex/skills/devrites-lib/reference/intent-map.md +3 -2
  364. package/pack/generated/codex/skills/devrites-lib/reference/parallel-dispatch.md +82 -137
  365. package/pack/generated/codex/skills/devrites-lib/reference/reply-contract.md +8 -1
  366. package/pack/generated/codex/skills/devrites-lib/reference/standards/README.md +38 -55
  367. package/pack/generated/codex/skills/devrites-lib/reference/standards/afk-hitl.md +53 -27
  368. package/pack/generated/codex/skills/devrites-lib/reference/standards/agents.md +198 -190
  369. package/pack/generated/codex/skills/devrites-lib/reference/standards/anti-patterns.md +5 -15
  370. package/pack/generated/codex/skills/devrites-lib/reference/standards/ci-cd.md +27 -58
  371. package/pack/generated/codex/skills/devrites-lib/reference/standards/code-review.md +16 -52
  372. package/pack/generated/codex/skills/devrites-lib/reference/standards/coding-style.md +2 -10
  373. package/pack/generated/codex/skills/devrites-lib/reference/standards/context-hygiene.md +18 -61
  374. package/pack/generated/codex/skills/devrites-lib/reference/standards/core.md +23 -18
  375. package/pack/generated/codex/skills/devrites-lib/reference/standards/deprecation.md +17 -57
  376. package/pack/generated/codex/skills/devrites-lib/reference/standards/development-workflow.md +4 -33
  377. package/pack/generated/codex/skills/devrites-lib/reference/standards/git-workflow.md +4 -20
  378. package/pack/generated/codex/skills/devrites-lib/reference/standards/hooks.md +3 -14
  379. package/pack/generated/codex/skills/devrites-lib/reference/standards/patterns.md +9 -25
  380. package/pack/generated/codex/skills/devrites-lib/reference/standards/performance.md +0 -9
  381. package/pack/generated/codex/skills/devrites-lib/reference/standards/principles.md +1 -3
  382. package/pack/generated/codex/skills/devrites-lib/reference/standards/security.md +17 -7
  383. package/pack/generated/codex/skills/devrites-lib/reference/standards/testing.md +1 -27
  384. package/pack/generated/codex/skills/devrites-lib/reference/standards/tooling.md +46 -50
  385. package/pack/generated/codex/skills/devrites-lib/reference/workspace-artifact-schema.md +23 -10
  386. package/pack/generated/codex/skills/devrites-prose-craft/SKILL.md +7 -3
  387. package/pack/generated/codex/skills/devrites-refresh-indexes/SKILL.md +7 -3
  388. package/pack/generated/codex/skills/devrites-source-driven/SKILL.md +29 -24
  389. package/pack/generated/codex/skills/devrites-ux-shape/SKILL.md +7 -3
  390. package/pack/generated/codex/skills/rite/SKILL.md +19 -13
  391. package/pack/generated/codex/skills/rite/reference/menu.md +8 -6
  392. package/pack/generated/codex/skills/rite-adopt/SKILL.md +40 -40
  393. package/pack/generated/codex/skills/rite-autocomplete/SKILL.md +40 -31
  394. package/pack/generated/codex/skills/rite-autocomplete/reference/loop.md +21 -21
  395. package/pack/generated/codex/skills/rite-autocomplete/reference/stop-conditions.md +15 -6
  396. package/pack/generated/codex/skills/rite-build/SKILL.md +42 -33
  397. package/pack/generated/codex/skills/rite-build/reference/afk-discipline.md +38 -38
  398. package/pack/generated/codex/skills/rite-build/reference/evidence-standard.md +10 -0
  399. package/pack/generated/codex/skills/rite-build/reference/forge.md +186 -156
  400. package/pack/generated/codex/skills/rite-build/reference/one-slice-cycle.md +4 -3
  401. package/pack/generated/codex/skills/rite-build/reference/phase-contract.md +96 -175
  402. package/pack/generated/codex/skills/rite-build/reference/wright-dispatch.md +129 -134
  403. package/pack/generated/codex/skills/rite-clarify/SKILL.md +105 -0
  404. package/pack/generated/codex/skills/rite-clarify/reference/decision-coverage.md +59 -0
  405. package/pack/generated/codex/skills/rite-converge/SKILL.md +42 -28
  406. package/pack/generated/codex/skills/rite-customize/SKILL.md +7 -3
  407. package/pack/generated/codex/skills/rite-define/SKILL.md +64 -35
  408. package/pack/generated/codex/skills/rite-define/reference/gates.md +16 -15
  409. package/pack/generated/codex/skills/rite-define/reference/plan-template.md +16 -8
  410. package/pack/generated/codex/skills/rite-doctor/SKILL.md +7 -3
  411. package/pack/generated/codex/skills/rite-dogfood/SKILL.md +7 -3
  412. package/pack/generated/codex/skills/rite-explain/SKILL.md +7 -3
  413. package/pack/generated/codex/skills/rite-frame/SKILL.md +7 -3
  414. package/pack/generated/codex/skills/rite-frame/reference/failure-modes.md +22 -24
  415. package/pack/generated/codex/skills/rite-handoff/SKILL.md +7 -3
  416. package/pack/generated/codex/skills/rite-learn/SKILL.md +7 -3
  417. package/pack/generated/codex/skills/rite-plan/SKILL.md +51 -19
  418. package/pack/generated/codex/skills/rite-plan/reference/task-breakdown.md +5 -1
  419. package/pack/generated/codex/skills/rite-polish/SKILL.md +34 -26
  420. package/pack/generated/codex/skills/rite-pov/SKILL.md +7 -3
  421. package/pack/generated/codex/skills/rite-pr-feedback/SKILL.md +7 -3
  422. package/pack/generated/codex/skills/rite-pressure-test/SKILL.md +7 -3
  423. package/pack/generated/codex/skills/rite-prototype/SKILL.md +32 -29
  424. package/pack/generated/codex/skills/rite-prove/SKILL.md +34 -20
  425. package/pack/generated/codex/skills/rite-quick/SKILL.md +7 -3
  426. package/pack/generated/codex/skills/rite-resolve/SKILL.md +19 -14
  427. package/pack/generated/codex/skills/rite-resolve/reference/answer-protocol.md +3 -0
  428. package/pack/generated/codex/skills/rite-review/SKILL.md +44 -31
  429. package/pack/generated/codex/skills/rite-seal/SKILL.md +7 -3
  430. package/pack/generated/codex/skills/rite-seal/reference/phase-contract.md +27 -92
  431. package/pack/generated/codex/skills/rite-seal/reference/risk-and-rollback.md +13 -0
  432. package/pack/generated/codex/skills/rite-ship/SKILL.md +7 -3
  433. package/pack/generated/codex/skills/rite-ship/reference/design-memory.md +31 -36
  434. package/pack/generated/codex/skills/rite-spec/SKILL.md +91 -138
  435. package/pack/generated/codex/skills/rite-spec/reference/investigation.md +37 -33
  436. package/pack/generated/codex/skills/rite-spec/reference/question-protocol.md +6 -2
  437. package/pack/generated/codex/skills/rite-spec/reference/spec-checklists.md +25 -24
  438. package/pack/generated/codex/skills/rite-spec/reference/spec-template.md +9 -2
  439. package/pack/generated/codex/skills/rite-spec/reference/state-workspace.md +13 -3
  440. package/pack/generated/codex/skills/rite-status/SKILL.md +7 -3
  441. package/pack/generated/codex/skills/rite-temper/SKILL.md +69 -50
  442. package/pack/generated/codex/skills/rite-temper/reference/review-dimensions.md +24 -22
  443. package/pack/generated/codex/skills/rite-vet/SKILL.md +117 -116
  444. package/pack/generated/codex/skills/rite-vet/reference/artifacts.md +42 -9
  445. package/pack/generated/codex/skills/rite-vet/reference/review-axes.md +40 -38
  446. package/pack/generated/codex/skills/rite-zoom-out/SKILL.md +7 -3
  447. package/package.json +1 -1
  448. package/scripts/build-release-tarball.sh +32 -15
  449. package/scripts/check-authority-drift.py +125 -0
  450. package/scripts/check-instruction-size-baseline.mjs +19 -11
  451. package/scripts/check-invocation-integrity.py +2 -0
  452. package/scripts/codex-generate.sh +69 -33
  453. package/scripts/grade-feature.sh +121 -40
  454. package/scripts/live-hosts/agent-result.schema.json +230 -0
  455. package/scripts/live-hosts/claude.sh +87 -0
  456. package/scripts/live-hosts/codex.sh +81 -0
  457. package/scripts/live-hosts/common.sh +113 -0
  458. package/scripts/live-hosts/fake-host.py +264 -0
  459. package/scripts/live-hosts/host-transport.py +287 -0
  460. package/scripts/release-check.sh +5 -1
  461. package/scripts/run-agent-contract-evals.py +1380 -0
  462. package/scripts/run-behavioral-evals.sh +24 -30
  463. package/scripts/run-evals.sh +1 -5
  464. package/scripts/run-live-behavioral-evals.py +1274 -144
  465. package/scripts/run-outcome-evals.sh +414 -88
  466. package/scripts/run-tests.mjs +30 -2
  467. package/scripts/skills-inventory.mjs +1 -1
  468. package/scripts/validate-workflow-security.py +39 -20
  469. package/scripts/validate-workspace-schema.py +362 -10
  470. package/scripts/validate.sh +21 -15
  471. package/engine/testdata/golden/TestParityWrightScope/devrites-edit-allowed.golden +0 -1
  472. /package/engine/testdata/golden/{TestParityReconcile/check-no-claimed.golden → TestParityBuildReadiness/arg=clarifyopen.golden} +0 -0
@@ -1,6 +1,8 @@
1
1
  package lib
2
2
 
3
3
  import (
4
+ "bufio"
5
+ "bytes"
4
6
  "crypto/sha256"
5
7
  "encoding/hex"
6
8
  "encoding/json"
@@ -8,10 +10,14 @@ import (
8
10
  "io"
9
11
  "os"
10
12
  "path/filepath"
13
+ "sort"
11
14
  "strconv"
12
15
  "strings"
16
+ "time"
13
17
 
14
18
  "github.com/devrites/devrites/internal/fsutil"
19
+ "github.com/devrites/devrites/internal/reason"
20
+ "github.com/devrites/devrites/internal/state"
15
21
  )
16
22
 
17
23
  type timelineEntry struct {
@@ -39,12 +45,21 @@ type reviewFingerprint struct {
39
45
  Text string `json:"text"`
40
46
  }
41
47
 
42
- // Timeline is the append-only session trace: what happened, which gate/skill did
43
- // it, and which durable decision or state transition resulted.
48
+ // Timeline writes privacy-bounded v1 trace facts, reads a bounded local report,
49
+ // and purges only exact telemetry selections. Events never own lifecycle state.
44
50
  func Timeline(root string, args []string, stdout, stderr io.Writer) int {
45
51
  switch argAt(args, 0) {
46
52
  case "log":
47
53
  entry := timelineEntry{TS: nowUTC()}
54
+ var (
55
+ mode ExecutionMode
56
+ strength GuardStrength
57
+ reasonID reason.ID
58
+ host = HostEngine
59
+ ruleIDs []reason.ID
60
+ evidence []string
61
+ typed bool
62
+ )
48
63
  rest := args[1:]
49
64
  if len(rest) > 0 && !strings.HasPrefix(rest[0], "--") {
50
65
  entry.Event = rest[0]
@@ -108,16 +123,98 @@ func Timeline(root string, args []string, stdout, stderr io.Writer) int {
108
123
  return 2
109
124
  }
110
125
  entry.Note = v
126
+ case "--execution-mode":
127
+ v, ok := next(rest[i])
128
+ if !ok {
129
+ return 2
130
+ }
131
+ mode, typed = ExecutionMode(v), true
132
+ case "--guard-strength":
133
+ v, ok := next(rest[i])
134
+ if !ok {
135
+ return 2
136
+ }
137
+ strength, typed = GuardStrength(v), true
138
+ case "--reason-id":
139
+ v, ok := next(rest[i])
140
+ if !ok {
141
+ return 2
142
+ }
143
+ parsed, err := reason.Parse(v)
144
+ if err != nil {
145
+ fmt.Fprintf(stderr, "timeline: %v\n", err)
146
+ return 2
147
+ }
148
+ reasonID, typed = parsed, true
149
+ case "--rule-id":
150
+ v, ok := next(rest[i])
151
+ if !ok {
152
+ return 2
153
+ }
154
+ parsed, err := reason.Parse(v)
155
+ if err != nil {
156
+ fmt.Fprintf(stderr, "timeline: %v\n", err)
157
+ return 2
158
+ }
159
+ ruleIDs, typed = append(ruleIDs, parsed), true
160
+ case "--evidence":
161
+ v, ok := next(rest[i])
162
+ if !ok {
163
+ return 2
164
+ }
165
+ evidence, typed = append(evidence, v), true
166
+ case "--host":
167
+ v, ok := next(rest[i])
168
+ if !ok {
169
+ return 2
170
+ }
171
+ host, typed = EventHost(v), true
111
172
  default:
112
173
  fmt.Fprintf(stderr, "timeline: unknown option %s\n", rest[i])
113
174
  return 2
114
175
  }
115
176
  }
116
177
  if entry.Event == "" {
117
- fmt.Fprintln(stderr, "usage: devrites-engine timeline log <event> [--skill S] [--slug S] [--outcome O] [--decision D] [--from A --to B] [--note N]")
178
+ fmt.Fprintln(stderr, timelineLogUsage)
179
+ return 2
180
+ }
181
+ if !typed {
182
+ fmt.Fprintln(stderr, "timeline: legacy free-text writes are disabled; provide typed v1 mode, guard, and reason facts")
118
183
  return 2
119
184
  }
120
- if err := appendJSONLine(filepath.Join(root, "timeline.jsonl"), entry); err != nil {
185
+ if mode == "" || strength == "" || reasonID == "" {
186
+ fmt.Fprintln(stderr, "timeline: typed records require --execution-mode, --guard-strength, and --reason-id")
187
+ return 2
188
+ }
189
+ if entry.Skill != "" || entry.Decision != "" || entry.Note != "" {
190
+ fmt.Fprintln(stderr, "timeline: typed records do not retain --skill, --decision, or --note free text")
191
+ return 2
192
+ }
193
+ ev := NewEventV1(BoundaryAgentDispatch, entry.Event, reasonID)
194
+ ev.ExecutionMode = mode
195
+ ev.GuardStrength = strength
196
+ ev.Host = host
197
+ ev.Outcome = OutcomeRecorded
198
+ if entry.Outcome != "" {
199
+ ev.Outcome = EventOutcome(entry.Outcome)
200
+ }
201
+ if entry.From != "" {
202
+ ev.PhaseBefore = state.Phase(entry.From)
203
+ }
204
+ if entry.To != "" {
205
+ ev.PhaseAfter = state.Phase(entry.To)
206
+ }
207
+ ev.RuleIDs = append(ev.RuleIDs, ruleIDs...)
208
+ ev.EvidencePaths = append([]string(nil), evidence...)
209
+ if err := BindEventWorkspace(root, entry.Slug, &ev); err != nil {
210
+ fmt.Fprintf(stderr, "timeline: %v\n", err)
211
+ return 2
212
+ }
213
+ if err := ValidateEventV1(ev); err != nil {
214
+ fmt.Fprintf(stderr, "timeline: %v\n", err)
215
+ return 2
216
+ }
217
+ if err := AppendEventV1(root, ev); err != nil {
121
218
  fmt.Fprintf(stderr, "timeline: %v\n", err)
122
219
  return 1
123
220
  }
@@ -135,10 +232,591 @@ func Timeline(root string, args []string, stdout, stderr io.Writer) int {
135
232
  }
136
233
  printTail(filepath.Join(root, "timeline.jsonl"), limit, "timeline", stdout)
137
234
  return 0
235
+ case "report":
236
+ return timelineReport(root, args[1:], stdout, stderr)
237
+ case "purge":
238
+ return timelinePurge(root, args[1:], stdout, stderr)
138
239
  default:
139
- fmt.Fprintln(stderr, "usage: devrites-engine timeline log|list [...]")
240
+ fmt.Fprintln(stderr, "usage: devrites-engine timeline log|list|report|purge [...]")
241
+ return 2
242
+ }
243
+ }
244
+
245
+ const timelineLogUsage = "usage: devrites-engine timeline log <event> [--slug S] [--outcome O] [--from A --to B] --execution-mode named|generic|inline|none --guard-strength enforced|observed|unavailable|bypassed|n/a --reason-id DRV-* [--host engine|claude|codex] [--rule-id DRV-*] [--evidence PATH]"
246
+
247
+ const (
248
+ telemetryTailBytes int64 = 4 << 20
249
+ telemetryMaxTailEvents = 4096
250
+ telemetryStatusTailBytes int64 = 256 << 10
251
+ telemetryStatusTailEvents = 256
252
+ telemetryMaxLineBytes = 64 << 10
253
+ telemetryMaxStorageBytes int64 = 16 << 20
254
+ telemetryMaxPurgeBytes int64 = 16 << 20
255
+ )
256
+
257
+ const observabilityReportSchema = "devrites-observability/v1"
258
+
259
+ type telemetryReadStats struct {
260
+ LegacyIgnored int `json:"legacy_ignored"`
261
+ CorruptIgnored int `json:"corrupt_ignored"`
262
+ TailTruncated bool `json:"tail_truncated"`
263
+ ReadDegraded bool `json:"read_degraded"`
264
+ }
265
+
266
+ type observabilityReport struct {
267
+ Schema string `json:"schema"`
268
+ RunID string `json:"run_id,omitempty"`
269
+ Workspace string `json:"workspace,omitempty"`
270
+ Events int `json:"events"`
271
+ TraceStarted bool `json:"trace_started"`
272
+ TraceFinished bool `json:"trace_finished"`
273
+ StartedAt string `json:"started_at,omitempty"`
274
+ FinishedAt string `json:"finished_at,omitempty"`
275
+ DurationSeconds int64 `json:"duration_seconds"`
276
+ PhaseDurations map[string]int64 `json:"phase_durations_seconds"`
277
+ Retries int `json:"retries"`
278
+ HumanWaits int `json:"human_waits"`
279
+ Interruptions int `json:"interruptions"`
280
+ Resumes int `json:"resumes"`
281
+ LinkedResumes int `json:"linked_resumes"`
282
+ OpenInterruptions int `json:"open_interruptions"`
283
+ ExecutionMode ExecutionMode `json:"execution_mode,omitempty"`
284
+ GuardStrength GuardStrength `json:"guard_strength,omitempty"`
285
+ Host EventHost `json:"host,omitempty"`
286
+ LastFailedGateReason reason.ID `json:"last_failed_gate_reason,omitempty"`
287
+ LastFailedGatePhase state.Phase `json:"last_failed_gate_phase,omitempty"`
288
+ StaleEvidence int `json:"stale_evidence"`
289
+ Degradations int `json:"degradations"`
290
+ FinalOutcome string `json:"final_outcome"`
291
+ TelemetryRead telemetryReadStats `json:"telemetry_read"`
292
+ }
293
+
294
+ func timelineReport(root string, args []string, stdout, stderr io.Writer) int {
295
+ runID := ""
296
+ wantJSON := false
297
+ for i := 0; i < len(args); i++ {
298
+ switch args[i] {
299
+ case "--json":
300
+ wantJSON = true
301
+ case "--run":
302
+ i++
303
+ if i >= len(args) || runID != "" {
304
+ fmt.Fprintln(stderr, "timeline report: --run needs one opaque run id")
305
+ return 2
306
+ }
307
+ runID = args[i]
308
+ if !runIDToken.MatchString(runID) {
309
+ fmt.Fprintln(stderr, "timeline report: --run must be an opaque drv-run-v1 token")
310
+ return 2
311
+ }
312
+ default:
313
+ fmt.Fprintf(stderr, "timeline report: unknown option %s\n", args[i])
314
+ return 2
315
+ }
316
+ }
317
+
318
+ events, stats := readTelemetryTail(filepath.Join(root, "timeline.jsonl"))
319
+ report := summarizeObservability(events, stats, runID)
320
+ if wantJSON {
321
+ if err := json.NewEncoder(stdout).Encode(report); err != nil {
322
+ fmt.Fprintf(stderr, "timeline report: %v\n", err)
323
+ return 1
324
+ }
325
+ return 0
326
+ }
327
+ printObservabilityReport(report, stdout)
328
+ return 0
329
+ }
330
+
331
+ func readTelemetryTail(path string) ([]EventV1, telemetryReadStats) {
332
+ return readTelemetryTailBounded(path, telemetryTailBytes, telemetryMaxTailEvents)
333
+ }
334
+
335
+ func readTelemetryTailBounded(path string, maxBytes int64, maxEvents int) ([]EventV1, telemetryReadStats) {
336
+ var stats telemetryReadStats
337
+ info, err := os.Lstat(path)
338
+ if os.IsNotExist(err) {
339
+ return nil, stats
340
+ }
341
+ if err != nil || !info.Mode().IsRegular() || info.Mode()&os.ModeSymlink != 0 {
342
+ stats.ReadDegraded = true
343
+ return nil, stats
344
+ }
345
+ f, err := os.Open(path)
346
+ if err != nil {
347
+ stats.ReadDegraded = true
348
+ return nil, stats
349
+ }
350
+ defer f.Close()
351
+
352
+ start := int64(0)
353
+ if info.Size() > maxBytes {
354
+ start = info.Size() - maxBytes
355
+ stats.TailTruncated = true
356
+ }
357
+ if _, err := f.Seek(start, io.SeekStart); err != nil {
358
+ stats.ReadDegraded = true
359
+ return nil, stats
360
+ }
361
+ reader := bufio.NewReader(io.LimitReader(f, maxBytes))
362
+ if start > 0 {
363
+ for {
364
+ _, err := reader.ReadSlice('\n')
365
+ if err == bufio.ErrBufferFull {
366
+ continue
367
+ }
368
+ if err != nil && err != io.EOF {
369
+ stats.ReadDegraded = true
370
+ }
371
+ break
372
+ }
373
+ }
374
+
375
+ scanner := bufio.NewScanner(reader)
376
+ scanner.Buffer(make([]byte, 4096), telemetryMaxLineBytes)
377
+ ring := make([]EventV1, maxEvents)
378
+ total := 0
379
+ for scanner.Scan() {
380
+ line := bytes.TrimSpace(scanner.Bytes())
381
+ if len(line) == 0 {
382
+ continue
383
+ }
384
+ var envelope struct {
385
+ Schema string `json:"schema"`
386
+ }
387
+ if err := json.Unmarshal(line, &envelope); err != nil {
388
+ stats.CorruptIgnored++
389
+ continue
390
+ }
391
+ if envelope.Schema != EventSchemaV1 {
392
+ stats.LegacyIgnored++
393
+ continue
394
+ }
395
+ var event EventV1
396
+ if err := json.Unmarshal(line, &event); err != nil || ValidateEventV1(event) != nil {
397
+ stats.CorruptIgnored++
398
+ continue
399
+ }
400
+ ring[total%maxEvents] = event
401
+ total++
402
+ }
403
+ if scanner.Err() != nil {
404
+ stats.ReadDegraded = true
405
+ }
406
+ if total > maxEvents {
407
+ stats.TailTruncated = true
408
+ out := make([]EventV1, maxEvents)
409
+ cursor := total % maxEvents
410
+ for i := range out {
411
+ out[i] = ring[(cursor+i)%maxEvents]
412
+ }
413
+ return out, stats
414
+ }
415
+ return append([]EventV1(nil), ring[:total]...), stats
416
+ }
417
+
418
+ func summarizeObservability(events []EventV1, stats telemetryReadStats, runID string) observabilityReport {
419
+ report := observabilityReport{
420
+ Schema: observabilityReportSchema,
421
+ PhaseDurations: map[string]int64{},
422
+ FinalOutcome: "unfinished",
423
+ TelemetryRead: stats,
424
+ }
425
+ if runID == "" {
426
+ runID = latestObservedRun(events)
427
+ }
428
+ report.RunID = runID
429
+ if runID == "" {
430
+ return report
431
+ }
432
+
433
+ selected := make([]EventV1, 0, len(events))
434
+ for _, event := range events {
435
+ if event.RunID == runID {
436
+ selected = append(selected, event)
437
+ }
438
+ }
439
+ sort.SliceStable(selected, func(i, j int) bool {
440
+ left, _ := time.Parse(time.RFC3339, selected[i].TS)
441
+ right, _ := time.Parse(time.RFC3339, selected[j].TS)
442
+ return left.Before(right)
443
+ })
444
+ for i, event := range selected {
445
+ if event.Event == "run-started" {
446
+ selected = selected[i:]
447
+ break
448
+ }
449
+ }
450
+ report.Events = len(selected)
451
+ if len(selected) == 0 {
452
+ return report
453
+ }
454
+
455
+ firstAt, _ := time.Parse(time.RFC3339, selected[0].TS)
456
+ lastAt := firstAt
457
+ report.StartedAt = selected[0].TS
458
+ for i, event := range selected {
459
+ at, _ := time.Parse(time.RFC3339, event.TS)
460
+ if at.After(lastAt) {
461
+ lastAt = at
462
+ }
463
+ if event.Workspace != "" {
464
+ report.Workspace = event.Workspace
465
+ }
466
+ if event.Event == "run-started" && !report.TraceStarted {
467
+ report.TraceStarted = true
468
+ report.StartedAt = event.TS
469
+ firstAt = at
470
+ }
471
+ if event.Event == "run-finished" {
472
+ report.TraceFinished = true
473
+ report.FinishedAt = event.TS
474
+ report.FinalOutcome = string(event.Outcome)
475
+ }
476
+ if event.ExecutionMode != ExecutionNone {
477
+ report.ExecutionMode = event.ExecutionMode
478
+ }
479
+ if event.GuardStrength != GuardNA {
480
+ report.GuardStrength = event.GuardStrength
481
+ }
482
+ if event.Host != "" {
483
+ report.Host = event.Host
484
+ }
485
+ switch event.Event {
486
+ case "retry", "recovery-retry":
487
+ report.Retries++
488
+ case "evidence-stale":
489
+ report.StaleEvidence++
490
+ }
491
+ humanWait := event.Event == "human-wait-started" ||
492
+ event.Event == "human-wait-resumed" ||
493
+ event.ReasonID == reason.HookStopUnsurfacedHumanGate
494
+ if humanWait {
495
+ report.HumanWaits++
496
+ }
497
+ if event.Event == "run-interrupted" ||
498
+ (event.ReasonID == reason.HookStopUnsurfacedHumanGate && event.Event != "human-wait-resumed") {
499
+ report.Interruptions++
500
+ }
501
+ if event.Event == "run-resumed" || event.Event == "human-wait-resumed" {
502
+ report.Resumes++
503
+ }
504
+ if event.Boundary == BoundaryLifecycleGate && failedEventOutcome(event.Outcome) {
505
+ report.LastFailedGateReason = event.ReasonID
506
+ report.LastFailedGatePhase = eventPhase(event)
507
+ }
508
+ if degradedEvent(event) {
509
+ report.Degradations++
510
+ }
511
+ if i+1 < len(selected) {
512
+ nextAt, _ := time.Parse(time.RFC3339, selected[i+1].TS)
513
+ if nextAt.After(at) {
514
+ if phase := eventPhase(event); phase != "" {
515
+ report.PhaseDurations[string(phase)] += int64(nextAt.Sub(at) / time.Second)
516
+ }
517
+ }
518
+ }
519
+ }
520
+ report.DurationSeconds = int64(lastAt.Sub(firstAt) / time.Second)
521
+ report.LinkedResumes = min(report.Interruptions, report.Resumes)
522
+ report.OpenInterruptions = report.Interruptions - report.LinkedResumes
523
+ return report
524
+ }
525
+
526
+ func latestObservedRun(events []EventV1) string {
527
+ for i := len(events) - 1; i >= 0; i-- {
528
+ if events[i].Event == "run-started" {
529
+ return events[i].RunID
530
+ }
531
+ }
532
+ for i := len(events) - 1; i >= 0; i-- {
533
+ if events[i].Boundary != BoundaryRootSelection {
534
+ return events[i].RunID
535
+ }
536
+ }
537
+ return ""
538
+ }
539
+
540
+ func eventPhase(event EventV1) state.Phase {
541
+ if event.PhaseAfter != "" {
542
+ return event.PhaseAfter
543
+ }
544
+ return event.PhaseBefore
545
+ }
546
+
547
+ func failedEventOutcome(outcome EventOutcome) bool {
548
+ return outcome == OutcomeBlocked || outcome == OutcomeDenied ||
549
+ outcome == OutcomeUnavailable || outcome == OutcomeWarning
550
+ }
551
+
552
+ func degradedEvent(event EventV1) bool {
553
+ return event.Event == "degraded" ||
554
+ event.ExecutionMode == ExecutionGeneric ||
555
+ event.ExecutionMode == ExecutionInline ||
556
+ event.GuardStrength == GuardUnavailable ||
557
+ event.GuardStrength == GuardBypassed ||
558
+ event.Outcome == OutcomeUnavailable ||
559
+ event.Outcome == OutcomeBypassed
560
+ }
561
+
562
+ func printObservabilityReport(report observabilityReport, stdout io.Writer) {
563
+ if report.Events == 0 {
564
+ fmt.Fprintf(stdout,
565
+ "timeline report: no valid %s rows in the bounded tail (legacy:%d corrupt:%d degraded:%t truncated:%t).\n",
566
+ EventSchemaV1, report.TelemetryRead.LegacyIgnored, report.TelemetryRead.CorruptIgnored,
567
+ report.TelemetryRead.ReadDegraded, report.TelemetryRead.TailTruncated)
568
+ return
569
+ }
570
+ fmt.Fprintf(stdout, "timeline report: run %s · %d v1 events · duration %s\n",
571
+ report.RunID, report.Events, (time.Duration(report.DurationSeconds) * time.Second).String())
572
+ if len(report.PhaseDurations) > 0 {
573
+ phases := make([]string, 0, len(report.PhaseDurations))
574
+ for phase := range report.PhaseDurations {
575
+ phases = append(phases, phase)
576
+ }
577
+ sort.Strings(phases)
578
+ fmt.Fprint(stdout, "phases:")
579
+ for _, phase := range phases {
580
+ fmt.Fprintf(stdout, " %s=%s", phase, (time.Duration(report.PhaseDurations[phase]) * time.Second).String())
581
+ }
582
+ fmt.Fprintln(stdout)
583
+ }
584
+ fmt.Fprintf(stdout, "recovery: retries:%d waits:%d interruptions:%d resumes:%d linked:%d open:%d\n",
585
+ report.Retries, report.HumanWaits, report.Interruptions, report.Resumes,
586
+ report.LinkedResumes, report.OpenInterruptions)
587
+ fmt.Fprintf(stdout, "active: mode:%s guard:%s host:%s\n",
588
+ defaultText(string(report.ExecutionMode), "none"),
589
+ defaultText(string(report.GuardStrength), "n/a"),
590
+ defaultText(string(report.Host), "unknown"))
591
+ if report.LastFailedGateReason != "" {
592
+ fmt.Fprintf(stdout, "last failed gate: %s phase:%s\n",
593
+ report.LastFailedGateReason, defaultText(string(report.LastFailedGatePhase), "unknown"))
594
+ } else {
595
+ fmt.Fprintln(stdout, "last failed gate: none")
596
+ }
597
+ fmt.Fprintf(stdout, "evidence: stale:%d · degradation:%d · outcome:%s\n",
598
+ report.StaleEvidence, report.Degradations, report.FinalOutcome)
599
+ fmt.Fprintf(stdout, "compatibility: legacy:%d corrupt:%d ignored · degraded:%t truncated:%t\n",
600
+ report.TelemetryRead.LegacyIgnored, report.TelemetryRead.CorruptIgnored,
601
+ report.TelemetryRead.ReadDegraded, report.TelemetryRead.TailTruncated)
602
+ }
603
+
604
+ func defaultText(value, fallback string) string {
605
+ if value == "" {
606
+ return fallback
607
+ }
608
+ return value
609
+ }
610
+
611
+ // ObservabilityStatus derives compact display-only facts from the same v1 rows
612
+ // used by timeline report. It never advances or overrides lifecycle state.
613
+ func ObservabilityStatus(root, slug string) string {
614
+ if slug == "" {
615
+ return ""
616
+ }
617
+ events, stats := readTelemetryTailBounded(
618
+ filepath.Join(root, "timeline.jsonl"),
619
+ telemetryStatusTailBytes,
620
+ telemetryStatusTailEvents,
621
+ )
622
+ work := ".devrites/work/" + slug
623
+ legacy := ".devrites/features/" + slug
624
+ filtered := make([]EventV1, 0, len(events))
625
+ for _, event := range events {
626
+ if event.Workspace == work || event.Workspace == legacy {
627
+ filtered = append(filtered, event)
628
+ }
629
+ }
630
+ report := summarizeObservability(filtered, stats, "")
631
+ if report.Events == 0 {
632
+ return ""
633
+ }
634
+ parts := []string{"obs"}
635
+ if report.ExecutionMode != "" {
636
+ parts = append(parts, "mode:"+string(report.ExecutionMode))
637
+ }
638
+ if report.HumanWaits > 0 {
639
+ parts = append(parts, "waits:"+strconv.Itoa(report.HumanWaits))
640
+ }
641
+ if report.LastFailedGateReason != "" {
642
+ parts = append(parts, "gate:"+string(report.LastFailedGateReason))
643
+ }
644
+ if report.StaleEvidence > 0 {
645
+ parts = append(parts, "stale:"+strconv.Itoa(report.StaleEvidence))
646
+ }
647
+ if report.Degradations > 0 {
648
+ parts = append(parts, "degraded:"+strconv.Itoa(report.Degradations))
649
+ }
650
+ if report.FinalOutcome != "unfinished" {
651
+ parts = append(parts, "outcome:"+report.FinalOutcome)
652
+ }
653
+ if len(parts) == 1 {
654
+ return ""
655
+ }
656
+ return strings.Join(parts, " ")
657
+ }
658
+
659
+ func timelinePurge(root string, args []string, stdout, stderr io.Writer) int {
660
+ var (
661
+ before *time.Time
662
+ runID string
663
+ )
664
+ for i := 0; i < len(args); i++ {
665
+ switch args[i] {
666
+ case "--before":
667
+ i++
668
+ if i >= len(args) || before != nil {
669
+ fmt.Fprintln(stderr, "timeline purge: --before needs one RFC3339 timestamp")
670
+ return 2
671
+ }
672
+ parsed, err := time.Parse(time.RFC3339, args[i])
673
+ if err != nil {
674
+ fmt.Fprintln(stderr, "timeline purge: --before must be RFC3339")
675
+ return 2
676
+ }
677
+ before = &parsed
678
+ case "--run":
679
+ i++
680
+ if i >= len(args) || runID != "" {
681
+ fmt.Fprintln(stderr, "timeline purge: --run needs one opaque run id")
682
+ return 2
683
+ }
684
+ runID = args[i]
685
+ if !runIDToken.MatchString(runID) {
686
+ fmt.Fprintln(stderr, "timeline purge: --run must be an opaque drv-run-v1 token")
687
+ return 2
688
+ }
689
+ default:
690
+ fmt.Fprintf(stderr, "timeline purge: unknown option %s\n", args[i])
691
+ return 2
692
+ }
693
+ }
694
+ if before == nil && runID == "" {
695
+ fmt.Fprintln(stderr, "timeline purge: provide --before and/or --run")
140
696
  return 2
141
697
  }
698
+
699
+ targets, err := telemetryTargets(root)
700
+ if err != nil {
701
+ fmt.Fprintf(stderr, "timeline purge: %v\n", err)
702
+ return 1
703
+ }
704
+ removed, changed := 0, 0
705
+ for _, target := range targets {
706
+ n, err := purgeTelemetryFile(root, target, func(event EventV1) bool {
707
+ if runID != "" && event.RunID != runID {
708
+ return false
709
+ }
710
+ if before != nil {
711
+ at, _ := time.Parse(time.RFC3339, event.TS)
712
+ if !at.Before(*before) {
713
+ return false
714
+ }
715
+ }
716
+ return true
717
+ })
718
+ if err != nil {
719
+ fmt.Fprintf(stderr, "timeline purge: %v\n", err)
720
+ return 1
721
+ }
722
+ if n > 0 {
723
+ changed++
724
+ removed += n
725
+ }
726
+ }
727
+ fmt.Fprintf(stdout, "timeline: purged %d v1 event(s) from %d telemetry file(s).\n", removed, changed)
728
+ return 0
729
+ }
730
+
731
+ func telemetryTargets(root string) ([]string, error) {
732
+ targets := []string{filepath.Join(root, "timeline.jsonl")}
733
+ for _, owner := range []string{"work", "features"} {
734
+ base := filepath.Join(root, owner)
735
+ info, err := os.Lstat(base)
736
+ if os.IsNotExist(err) {
737
+ continue
738
+ }
739
+ if err != nil {
740
+ return nil, fmt.Errorf("inspect telemetry owner %s: %w", owner, err)
741
+ }
742
+ if info.Mode()&os.ModeSymlink != 0 || !info.IsDir() {
743
+ return nil, fmt.Errorf("telemetry owner %s is not a real directory", owner)
744
+ }
745
+ entries, err := os.ReadDir(base)
746
+ if err != nil {
747
+ return nil, fmt.Errorf("read telemetry owner %s: %w", owner, err)
748
+ }
749
+ for _, entry := range entries {
750
+ if entry.Type()&os.ModeSymlink != 0 || !entry.IsDir() ||
751
+ !slugToken.MatchString(entry.Name()) {
752
+ continue
753
+ }
754
+ targets = append(targets, filepath.Join(base, entry.Name(), "events.jsonl"))
755
+ }
756
+ }
757
+ return targets, nil
758
+ }
759
+
760
+ func purgeTelemetryFile(root, path string, remove func(EventV1) bool) (int, error) {
761
+ info, err := os.Lstat(path)
762
+ if os.IsNotExist(err) {
763
+ return 0, nil
764
+ }
765
+ if err != nil {
766
+ return 0, fmt.Errorf("inspect %s: %w", filepath.Base(path), err)
767
+ }
768
+ if err := validateEventLineTarget(path, root); err != nil {
769
+ return 0, err
770
+ }
771
+ if !info.Mode().IsRegular() || info.Mode()&os.ModeSymlink != 0 {
772
+ return 0, fmt.Errorf("%s is not a regular telemetry file", filepath.Base(path))
773
+ }
774
+ if info.Size() > telemetryMaxPurgeBytes {
775
+ return 0, fmt.Errorf("%s exceeds the 16 MiB purge bound; delete this telemetry file manually", filepath.Base(path))
776
+ }
777
+ data, err := os.ReadFile(path)
778
+ if err != nil {
779
+ return 0, fmt.Errorf("read %s: %w", filepath.Base(path), err)
780
+ }
781
+ scanner := bufio.NewScanner(bytes.NewReader(data))
782
+ scanner.Buffer(make([]byte, 4096), telemetryMaxLineBytes)
783
+ var out bytes.Buffer
784
+ out.Grow(len(data))
785
+ removed := 0
786
+ for scanner.Scan() {
787
+ line := append([]byte(nil), scanner.Bytes()...)
788
+ drop := false
789
+ var envelope struct {
790
+ Schema string `json:"schema"`
791
+ }
792
+ if json.Unmarshal(line, &envelope) == nil && envelope.Schema == EventSchemaV1 {
793
+ var event EventV1
794
+ if json.Unmarshal(line, &event) == nil && ValidateEventV1(event) == nil {
795
+ drop = remove(event)
796
+ }
797
+ }
798
+ if drop {
799
+ removed++
800
+ continue
801
+ }
802
+ out.Write(line)
803
+ out.WriteByte('\n')
804
+ }
805
+ if err := scanner.Err(); err != nil {
806
+ return 0, fmt.Errorf("%s contains an oversized telemetry row; original preserved", filepath.Base(path))
807
+ }
808
+ if removed == 0 {
809
+ return 0, nil
810
+ }
811
+ current, err := os.Lstat(path)
812
+ if err != nil || !os.SameFile(info, current) ||
813
+ current.Size() != info.Size() || !current.ModTime().Equal(info.ModTime()) {
814
+ return 0, fmt.Errorf("%s changed during purge; original preserved", filepath.Base(path))
815
+ }
816
+ if err := fsutil.WriteFileAtomic(path, out.Bytes(), info.Mode().Perm()); err != nil {
817
+ return 0, fmt.Errorf("rewrite %s: %w", filepath.Base(path), err)
818
+ }
819
+ return removed, nil
142
820
  }
143
821
 
144
822
  // Health records one compact score (legacy record/list) or runs the project code-health dashboard.
@@ -297,21 +975,35 @@ func appendJSONLine(path string, v any) error {
297
975
  if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
298
976
  return fmt.Errorf("create log dir: %w", err)
299
977
  }
978
+ b, err := json.Marshal(v)
979
+ if err != nil {
980
+ return fmt.Errorf("marshal entry: %w", err)
981
+ }
982
+ if telemetryLogPath(path) {
983
+ if info, err := os.Stat(path); err == nil {
984
+ if info.Size()+int64(len(b))+1 > telemetryMaxStorageBytes {
985
+ return fmt.Errorf("telemetry log reached the 16 MiB local retention bound; run timeline purge")
986
+ }
987
+ } else if !os.IsNotExist(err) {
988
+ return fmt.Errorf("inspect telemetry log: %w", err)
989
+ }
990
+ }
300
991
  f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
301
992
  if err != nil {
302
993
  return fmt.Errorf("append log: %w", err)
303
994
  }
304
995
  defer f.Close()
305
- b, err := json.Marshal(v)
306
- if err != nil {
307
- return fmt.Errorf("marshal entry: %w", err)
308
- }
309
996
  if _, err := f.Write(append(b, '\n')); err != nil {
310
997
  return fmt.Errorf("append log: %w", err)
311
998
  }
312
999
  return nil
313
1000
  }
314
1001
 
1002
+ func telemetryLogPath(path string) bool {
1003
+ base := filepath.Base(path)
1004
+ return base == "timeline.jsonl" || base == "events.jsonl"
1005
+ }
1006
+
315
1007
  func printTail(path string, limit int, label string, stdout io.Writer) {
316
1008
  data, err := os.ReadFile(path)
317
1009
  if err != nil {