@khoavu882/doflow 2.0.0-beta.4 → 2.0.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 (497) hide show
  1. package/README.md +12 -11
  2. package/bin/doflow.js +21 -11
  3. package/core/.antigravity-plugin/plugin.json +1 -3
  4. package/core/.claude-plugin/plugin.json +1 -1
  5. package/core/.codex-plugin/plugin.json +1 -1
  6. package/core/.plugin/plugin.json +1 -1
  7. package/core/harnesses/claude/hooks/lib.sh +43 -24
  8. package/core/harnesses/claude/hooks/mcp-tool-guard.sh +7 -17
  9. package/core/harnesses/claude/hooks/permission-audit.sh +3 -0
  10. package/core/harnesses/claude/hooks/post-compact.sh +35 -32
  11. package/core/harnesses/claude/hooks/post-edit-lint.sh +2 -0
  12. package/core/harnesses/claude/hooks/post-shell-observe.sh +48 -0
  13. package/core/harnesses/claude/hooks/post-tool-failure.sh +3 -0
  14. package/core/harnesses/claude/hooks/pre-bash-guard.sh +7 -17
  15. package/core/harnesses/claude/hooks/pre-compact.sh +23 -27
  16. package/core/harnesses/claude/hooks/pre-implement-gate.sh +7 -18
  17. package/core/harnesses/claude/hooks/session-end.sh +23 -10
  18. package/core/harnesses/claude/hooks/session-start.sh +5 -3
  19. package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
  20. package/core/harnesses/claude/hooks/stop-check.sh +5 -3
  21. package/core/harnesses/claude/hooks/subagent-audit.sh +5 -5
  22. package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -3
  23. package/core/harnesses/claude/settings/settings.json +18 -0
  24. package/core/harnesses/claude/statusline/doflow-statusline.sh +1 -0
  25. package/core/harnesses/codex/agents/core-implementer.toml +1 -0
  26. package/core/harnesses/codex/agents/delivery-lead.toml +10 -0
  27. package/core/harnesses/codex/agents/quality-guardian.toml +1 -1
  28. package/core/harnesses/codex/agents/research-writer.toml +1 -1
  29. package/core/harnesses/codex/agents/reviewer.toml +10 -0
  30. package/core/harnesses/codex/agents/spec-analyst.toml +1 -1
  31. package/core/harnesses/codex/agents/system-architect.toml +1 -1
  32. package/core/harnesses/codex/hooks/lib.sh +44 -25
  33. package/core/harnesses/codex/hooks/mcp-tool-guard.sh +7 -15
  34. package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
  35. package/core/harnesses/codex/hooks/pre-bash-guard.sh +7 -15
  36. package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
  37. package/core/harnesses/codex/hooks/pre-implement-gate.sh +7 -16
  38. package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
  39. package/core/harnesses/codex/hooks/session-start.sh +5 -3
  40. package/core/harnesses/codex/hooks/stop-check.sh +6 -5
  41. package/core/harnesses/codex/hooks/subagent-audit.sh +18 -6
  42. package/core/harnesses/codex/hooks/user-prompt-submit.sh +5 -4
  43. package/core/harnesses/copilot/hooks/doflow-lib.sh +93 -0
  44. package/core/harnesses/copilot/hooks/doflow-mcp-tool-guard.sh +9 -0
  45. package/core/harnesses/copilot/hooks/doflow-pre-bash-guard.sh +9 -0
  46. package/core/harnesses/copilot/hooks/doflow-pre-implement-gate.sh +9 -0
  47. package/core/harnesses/copilot/hooks/doflow-session-start.sh +9 -0
  48. package/core/harnesses/copilot/hooks/doflow-stop-check.sh +9 -0
  49. package/core/harnesses/copilot/hooks/doflow-subagent-start.sh +9 -0
  50. package/core/harnesses/copilot/hooks/doflow-subagent-stop.sh +9 -0
  51. package/core/harnesses/copilot/hooks/doflow.json +57 -0
  52. package/core/harnesses/kiro/agents/doflow.json +1 -1
  53. package/core/harnesses/kiro/hooks/lib.sh +26 -6
  54. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +21 -5
  55. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +21 -5
  56. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +21 -5
  57. package/core/harnesses/kiro/hooks/session-start.sh +12 -5
  58. package/core/harnesses/kiro/hooks/stop-check.sh +22 -30
  59. package/core/harnesses/pi/extensions/doflow-guard.js +167 -0
  60. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +27 -12
  61. package/core/harnesses/shared/hooks/adapters/antigravity.js +14 -57
  62. package/core/harnesses/shared/hooks/bridge/bridge.sh +277 -0
  63. package/core/harnesses/shared/hooks/bridge/envelope.jq +129 -0
  64. package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
  65. package/core/harnesses/shared/hooks/bridge/respond.sh +70 -0
  66. package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +28 -14
  67. package/core/harnesses/shared/hooks/policies/capture-failure.sh +51 -16
  68. package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
  69. package/core/harnesses/shared/hooks/policies/contract.sh +78 -0
  70. package/core/harnesses/shared/hooks/policies/deny-json.sh +35 -9
  71. package/core/harnesses/shared/hooks/policies/emission-rules.sh +37 -0
  72. package/core/harnesses/shared/hooks/policies/emission.jq +209 -0
  73. package/core/harnesses/shared/hooks/policies/emission.sh +383 -0
  74. package/core/harnesses/shared/hooks/policies/identity.sh +177 -0
  75. package/core/harnesses/shared/hooks/policies/lib.sh +65 -40
  76. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +48 -35
  77. package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +284 -33
  78. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +180 -110
  79. package/core/harnesses/shared/hooks/policies/session-context.sh +66 -7
  80. package/core/harnesses/shared/hooks/policies/stop-check.sh +61 -23
  81. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +39 -6
  82. package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
  83. package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +97 -159
  84. package/core/harnesses/shared/hooks/stream-hook-runner.js +114 -187
  85. package/core/harnesses/shared/locator/doflow-run +2 -3
  86. package/core/registry/advice.json +3 -3
  87. package/core/registry/assets.json +72 -80
  88. package/core/registry/capabilities.json +12 -0
  89. package/core/registry/contracts.json +60 -33
  90. package/core/registry/contribution-points.json +32 -0
  91. package/core/registry/gates.json +32 -0
  92. package/core/registry/guidance-caps.json +40 -0
  93. package/core/registry/harnesses.json +1542 -277
  94. package/core/registry/lifecycle.json +2837 -37
  95. package/core/registry/mcp.json +5 -1
  96. package/core/registry/models.json +7 -1
  97. package/core/registry/package-templates/README.md +19 -0
  98. package/core/registry/package-templates/agent/files/agent.md +9 -0
  99. package/core/registry/package-templates/agent/template.json +4 -0
  100. package/core/registry/package-templates/capability-provider/files/provider.json +9 -0
  101. package/core/registry/package-templates/capability-provider/template.json +11 -0
  102. package/core/registry/package-templates/debate-preset/files/preset.json +26 -0
  103. package/core/registry/package-templates/debate-preset/template.json +4 -0
  104. package/core/registry/package-templates/review-preset/files/preset.json +30 -0
  105. package/core/registry/package-templates/review-preset/template.json +4 -0
  106. package/core/registry/package-templates/skill/files/SKILL.md +8 -0
  107. package/core/registry/package-templates/skill/template.json +4 -0
  108. package/core/registry/package-templates/worker/files/worker.json +92 -0
  109. package/core/registry/package-templates/worker/template.json +11 -0
  110. package/core/registry/packages/doflow/doflow-package.json +15 -0
  111. package/core/registry/packages/doflow/presets/debate-3.json +26 -0
  112. package/core/registry/protocols.json +61 -0
  113. package/core/registry/routes.json +17 -0
  114. package/core/registry/rules.json +431 -0
  115. package/core/registry/settings.json +276 -0
  116. package/core/registry/storage.json +701 -30
  117. package/core/registry/telemetry.json +236 -0
  118. package/core/registry/transcripts.json +142 -0
  119. package/core/registry/verification.json +76 -1
  120. package/core/registry/workflows.json +55 -0
  121. package/core/shared/agent-specs/core-implementer.md +3 -0
  122. package/core/shared/agent-specs/delivery-lead.md +68 -0
  123. package/core/shared/agent-specs/quality-guardian.md +5 -1
  124. package/core/shared/agent-specs/research-writer.md +10 -3
  125. package/core/shared/agent-specs/reviewer.md +53 -0
  126. package/core/shared/agent-specs/spec-analyst.md +5 -6
  127. package/core/shared/agent-specs/system-architect.md +5 -1
  128. package/core/shared/guidance/DOFLOW_CORE.md +1 -5
  129. package/core/shared/guidance/modes/MODE_DeepResearch.md +1 -1
  130. package/core/shared/guidance/modes/MODE_Orchestration.md +4 -3
  131. package/core/shared/guidance/modes/MODE_Task_Management.md +14 -14
  132. package/core/shared/guidance/pointers/codex.md +1 -4
  133. package/core/shared/guidance/references/ARTIFACT_FORMAT.md +30 -16
  134. package/core/shared/guidance/references/ARTIFACT_VOICE.md +3 -2
  135. package/core/shared/guidance/references/CONSTITUTION_BASE.md +17 -15
  136. package/core/shared/guidance/references/DIAGRAM_GUIDE.md +1 -1
  137. package/core/shared/guidance/references/DOFLOW_CHAIN.md +17 -19
  138. package/core/shared/guidance/references/EVIDENCE_LEDGER.md +7 -8
  139. package/core/shared/guidance/references/MODEL_SELECTION.md +5 -3
  140. package/core/shared/guidance/references/ORCHESTRATION.md +1 -2
  141. package/core/shared/guidance/rules/RULES.md +45 -0
  142. package/core/shared/scripts/doflow/bash/do-args.sh +37 -0
  143. package/core/shared/scripts/doflow/bash/do-exec-paths.sh +4 -0
  144. package/core/shared/scripts/doflow/bash/do-git-state.sh +15 -4
  145. package/core/shared/scripts/doflow/bash/do-parallel-check.sh +4 -0
  146. package/core/shared/scripts/doflow/bash/do-paths.sh +40 -2
  147. package/core/shared/scripts/doflow/bash/do-prereqs.sh +2 -1
  148. package/core/shared/scripts/doflow/bash/do-review-package.sh +4 -0
  149. package/core/shared/scripts/doflow/bash/do-task-brief.sh +58 -16
  150. package/core/shared/scripts/doflow/bash/render-audit.sh +26 -2
  151. package/core/shared/scripts/doflow/bash/render-diagrams.sh +1 -0
  152. package/core/shared/scripts/doflow/bash/render-puml.sh +2 -1
  153. package/core/shared/scripts/doflow/bash/validate-artifacts.sh +48 -4
  154. package/core/shared/scripts/doflow/bin/doflow-run +154 -35
  155. package/core/shared/skills/do/SKILL.md +12 -10
  156. package/core/shared/skills/do/references/advice_calls.md +26 -0
  157. package/core/shared/skills/do/references/pm_routing.md +1 -2
  158. package/core/shared/skills/do/references/program_mode.md +31 -0
  159. package/core/shared/skills/do-brainstorm/SKILL.md +20 -74
  160. package/core/shared/skills/do-brainstorm/references/handoff_branches.md +35 -0
  161. package/core/shared/skills/do-brainstorm/references/intent_seed.md +21 -0
  162. package/core/shared/skills/do-brainstorm/references/round_log.md +17 -0
  163. package/core/shared/skills/do-code-review/SKILL.md +41 -144
  164. package/core/shared/skills/do-code-review/content-types/markdown.md +24 -2
  165. package/core/shared/skills/do-code-review/references/tools.md +90 -0
  166. package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +549 -7
  167. package/core/shared/skills/do-design/SKILL.md +24 -23
  168. package/core/shared/skills/do-diagnose/SKILL.md +14 -4
  169. package/core/shared/skills/do-document/SKILL.md +11 -1
  170. package/core/shared/skills/do-document/references/deep_research.md +1 -2
  171. package/core/shared/skills/do-document/references/embedded-research.md +18 -6
  172. package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
  173. package/core/shared/skills/do-execute-plan/SKILL.md +17 -59
  174. package/core/shared/skills/do-execute-plan/references/handoff_record.md +7 -2
  175. package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +13 -0
  176. package/core/shared/skills/do-execute-plan/references/readiness_gate.md +28 -1
  177. package/core/shared/skills/do-execute-plan/references/run_position.md +13 -1
  178. package/core/shared/skills/do-execute-plan/references/scaffold.md +17 -9
  179. package/core/shared/skills/do-execute-plan/references/state_checkpoint.md +11 -0
  180. package/core/shared/skills/do-execute-plan/references/task_attempts.md +78 -24
  181. package/core/shared/skills/do-flow/SKILL.md +8 -9
  182. package/core/shared/skills/do-flow/references/task_classes.md +2 -2
  183. package/core/shared/skills/do-git/SKILL.md +4 -0
  184. package/core/shared/skills/do-implement/SKILL.md +7 -9
  185. package/core/shared/skills/do-plan/SKILL.md +29 -19
  186. package/core/shared/skills/do-test/SKILL.md +7 -4
  187. package/core/shared/templates/doflow/constitution-template.md +9 -3
  188. package/core/shared/templates/doflow/data-model-template.md +1 -1
  189. package/core/shared/templates/doflow/design-template.md +113 -16
  190. package/core/shared/templates/doflow/plan-template.md +11 -10
  191. package/core/shared/templates/doflow/question-log-template.md +14 -2
  192. package/core/shared/templates/doflow/requirement-template.md +1 -1
  193. package/core/worker-evidence/antigravity/checks.json +14 -0
  194. package/core/worker-evidence/codex/2026-10-18-cli-check.json +107 -0
  195. package/core/worker-evidence/codex/2026-10-18-live-run-edit.json +73 -0
  196. package/core/worker-evidence/codex/2026-10-18-live-run-read-only.json +62 -0
  197. package/core/worker-evidence/codex/2026-10-24-cli-check.json +126 -0
  198. package/core/worker-evidence/codex/checks.json +38 -0
  199. package/package.json +10 -6
  200. package/src/adapters/antigravity/index.js +226 -113
  201. package/src/adapters/claude/index.js +39 -5
  202. package/src/adapters/codex/agents.js +25 -15
  203. package/src/adapters/codex/hooks.js +40 -5
  204. package/src/adapters/codex/index.js +100 -22
  205. package/src/adapters/codex/native-projection.js +6 -2
  206. package/src/adapters/copilot/index.js +58 -23
  207. package/src/adapters/copy-tree.js +294 -48
  208. package/src/adapters/guidance-manifest.js +71 -0
  209. package/src/adapters/guidance-pointer.js +19 -0
  210. package/src/adapters/hook-commands.js +6 -10
  211. package/src/adapters/index.js +1 -0
  212. package/src/adapters/kiro/index.js +38 -13
  213. package/src/adapters/opencode/index.js +8 -8
  214. package/src/adapters/pi/hooks.js +120 -0
  215. package/src/adapters/pi/index.js +48 -16
  216. package/src/adapters/resolve-source.js +26 -2
  217. package/src/cli/commands/install.js +3 -2
  218. package/src/cli/commands/reconcile.js +53 -9
  219. package/src/cli/commands/remove.js +2 -2
  220. package/src/cli/commands/rollback.js +6 -3
  221. package/src/cli/commands/self-update.js +18 -9
  222. package/src/cli/commands/status.js +21 -2
  223. package/src/cli/commands/ui.js +99 -0
  224. package/src/cli/commands/update.js +3 -2
  225. package/src/cli/index.js +338 -33
  226. package/src/cli/runtime-commands.js +243 -52
  227. package/src/cli/shared.js +0 -2
  228. package/src/helper/git.js +1 -1
  229. package/src/helper/guardrails.js +1 -1
  230. package/src/helper/marker-merge.js +31 -13
  231. package/src/helper/package-ids.js +37 -0
  232. package/src/helper/semver.js +94 -0
  233. package/src/install/backup.js +105 -15
  234. package/src/install/context.js +1 -1
  235. package/src/install/targets.js +31 -16
  236. package/src/lifecycle/coverage.js +74 -0
  237. package/src/lifecycle/hook-step.js +61 -0
  238. package/src/lifecycle/hook-tier.js +160 -0
  239. package/src/lifecycle/index.js +434 -86
  240. package/src/lifecycle/retired-harnesses.js +318 -0
  241. package/src/lifecycle/view.js +35 -15
  242. package/src/registry/index.js +686 -32
  243. package/src/registry/worker-ids.js +45 -0
  244. package/src/release/build-package.js +0 -3
  245. package/src/release/docs-blocks.js +492 -0
  246. package/src/release/preflight.js +425 -0
  247. package/src/release/rehearse.js +401 -0
  248. package/src/release/release-workflow.js +733 -0
  249. package/src/release/sync-plugin-versions.js +43 -7
  250. package/src/runtime/advice/bench-gate.js +25 -0
  251. package/src/runtime/advice/cli.js +59 -16
  252. package/src/runtime/advice/client.js +32 -9
  253. package/src/runtime/advice/config.js +98 -133
  254. package/src/runtime/advice/doctor.js +1 -1
  255. package/src/runtime/advice/elevation.js +97 -0
  256. package/src/runtime/advice/endpoint-guard.js +9 -3
  257. package/src/runtime/advice/mode.js +37 -0
  258. package/src/runtime/advice/record.js +18 -5
  259. package/src/runtime/advice/registry.js +4 -1
  260. package/src/runtime/advice/shadow-config.js +229 -0
  261. package/src/runtime/advice/shadow-dispatcher.js +276 -0
  262. package/src/runtime/brief-rules.js +81 -0
  263. package/src/runtime/brief-tools.js +111 -34
  264. package/src/runtime/candidate.js +514 -0
  265. package/src/runtime/capabilities/executor.js +450 -0
  266. package/src/runtime/capabilities/loopback.js +41 -0
  267. package/src/runtime/capabilities/sanitizer.js +88 -0
  268. package/src/runtime/capability-router.js +135 -3
  269. package/src/runtime/checkouts.js +73 -11
  270. package/src/runtime/claims.js +27 -12
  271. package/src/runtime/cli-result.js +11 -4
  272. package/src/runtime/cli.js +221 -277
  273. package/src/runtime/config-home.js +42 -0
  274. package/src/runtime/context/arbitrate.js +767 -0
  275. package/src/runtime/context/budget.js +203 -0
  276. package/src/runtime/context/compose.js +1367 -0
  277. package/src/runtime/context/delivery-store.js +100 -0
  278. package/src/runtime/context/emission-rules.js +118 -0
  279. package/src/runtime/context/identity.js +227 -0
  280. package/src/runtime/context/mandatory.js +594 -0
  281. package/src/runtime/context/pointer.js +142 -0
  282. package/src/runtime/context/primary-command.js +117 -0
  283. package/src/runtime/context/primary.js +344 -0
  284. package/src/runtime/context/summary-store.js +655 -0
  285. package/src/runtime/context-pack.js +1106 -245
  286. package/src/runtime/decision-register.js +13 -4
  287. package/src/runtime/dispatch/admission.js +392 -0
  288. package/src/runtime/dispatch/attempt-end.js +274 -0
  289. package/src/runtime/dispatch/command.js +628 -0
  290. package/src/runtime/dispatch/controls.js +904 -0
  291. package/src/runtime/dispatch/envelope.js +276 -0
  292. package/src/runtime/dispatch/grants.js +310 -0
  293. package/src/runtime/dispatch/integrate.js +617 -0
  294. package/src/runtime/dispatch/loop.js +1835 -0
  295. package/src/runtime/dispatch/store.js +352 -0
  296. package/src/runtime/dispatch/verify-run.js +201 -0
  297. package/src/runtime/evidence-fields.js +39 -0
  298. package/src/runtime/evidence-ledger.js +41 -7
  299. package/src/runtime/failure/capture.js +3 -1
  300. package/src/runtime/failure/cli.js +25 -9
  301. package/src/runtime/failure/home.js +57 -30
  302. package/src/runtime/failure/store.js +7 -2
  303. package/src/runtime/feature-pointer.js +174 -0
  304. package/src/runtime/feature-resolve.js +13 -2
  305. package/src/runtime/fleet/command.js +77 -0
  306. package/src/runtime/fleet/projects.js +178 -0
  307. package/src/runtime/fleet/tree.js +383 -0
  308. package/src/runtime/freshness.js +77 -10
  309. package/src/runtime/git-memo.js +46 -0
  310. package/src/runtime/guidance/ablation.js +427 -0
  311. package/src/runtime/health.js +565 -17
  312. package/src/runtime/history-compactor.js +13 -3
  313. package/src/runtime/implementation-gate.js +277 -22
  314. package/src/runtime/inventory/coverage.js +1 -1
  315. package/src/runtime/inventory/index.js +1 -1
  316. package/src/runtime/inventory/precedence.js +2 -2
  317. package/src/runtime/inventory/siblings.js +6 -4
  318. package/src/runtime/knowledge/code-corpus.js +277 -0
  319. package/src/runtime/knowledge/code-lexical.js +130 -0
  320. package/src/runtime/knowledge/code-retrieval.js +307 -0
  321. package/src/runtime/knowledge/code-validation.js +106 -0
  322. package/src/runtime/knowledge/gp3-gate.js +74 -0
  323. package/src/runtime/knowledge/graph-adapter.js +288 -0
  324. package/src/runtime/knowledge/graph-fallback.js +394 -0
  325. package/src/runtime/knowledge/hybrid.js +136 -8
  326. package/src/runtime/knowledge/retrieval.js +34 -1
  327. package/src/runtime/knowledge/rtk-compress.js +77 -0
  328. package/src/runtime/knowledge/semble-bridge.py +106 -0
  329. package/src/runtime/knowledge/semble-lifecycle.js +114 -0
  330. package/src/runtime/knowledge/semble-provider.js +365 -0
  331. package/src/runtime/lifecycle/cli.js +16 -6
  332. package/src/runtime/lifecycle/event-store.js +8 -6
  333. package/src/runtime/lifecycle/followup.js +6 -1
  334. package/src/runtime/lifecycle/overview.js +24 -1
  335. package/src/runtime/lifecycle/report-store.js +8 -10
  336. package/src/runtime/lifecycle/retention.js +95 -31
  337. package/src/runtime/lifecycle/root.js +9 -1
  338. package/src/runtime/lifecycle/status.js +34 -17
  339. package/src/runtime/lifecycle/store-upkeep.js +56 -7
  340. package/src/runtime/mask.js +134 -7
  341. package/src/runtime/mcp-consultations.js +40 -6
  342. package/src/runtime/model-router.js +79 -7
  343. package/src/runtime/models/catalogue.js +191 -0
  344. package/src/runtime/models/command.js +153 -0
  345. package/src/runtime/off-words.js +12 -0
  346. package/src/runtime/outcome.js +3 -3
  347. package/src/runtime/packages/command.js +1009 -0
  348. package/src/runtime/packages/contributions.js +116 -0
  349. package/src/runtime/packages/digest.js +132 -0
  350. package/src/runtime/packages/fetch.js +182 -0
  351. package/src/runtime/packages/grants.js +192 -0
  352. package/src/runtime/packages/index-file.js +134 -0
  353. package/src/runtime/packages/instruction.js +90 -0
  354. package/src/runtime/packages/manifest.js +331 -0
  355. package/src/runtime/packages/points.js +477 -0
  356. package/src/runtime/packages/programs.js +216 -0
  357. package/src/runtime/packages/scaffold.js +277 -0
  358. package/src/runtime/packages/source.js +90 -0
  359. package/src/runtime/packages/state.js +164 -0
  360. package/src/runtime/packages/store.js +449 -0
  361. package/src/runtime/packages/tar.js +106 -0
  362. package/src/runtime/packages/view.js +406 -0
  363. package/src/runtime/plan-check.js +59 -0
  364. package/src/runtime/plan-content.js +324 -0
  365. package/src/runtime/policy-init.js +118 -0
  366. package/src/runtime/profiles/doctor.js +96 -0
  367. package/src/runtime/profiles/resolve.js +244 -0
  368. package/src/runtime/profiles/rollback.js +254 -0
  369. package/src/runtime/profiles/sampler.js +41 -0
  370. package/src/runtime/readiness-record.js +16 -11
  371. package/src/runtime/readiness.js +127 -16
  372. package/src/runtime/research-request.js +50 -25
  373. package/src/runtime/retrieval-plan.js +402 -73
  374. package/src/runtime/retrieval-record.js +293 -0
  375. package/src/runtime/review/command.js +246 -0
  376. package/src/runtime/review/dominance.js +68 -0
  377. package/src/runtime/review/findings.js +434 -0
  378. package/src/runtime/review/identity.js +255 -0
  379. package/src/runtime/review/protocol-command.js +109 -0
  380. package/src/runtime/review/protocols.js +405 -0
  381. package/src/runtime/review/resolve-protocol.js +302 -0
  382. package/src/runtime/review/start.js +530 -0
  383. package/src/runtime/roadmap/facts.js +479 -0
  384. package/src/runtime/roadmap/gates.js +270 -0
  385. package/src/runtime/roadmap/loop.js +1636 -0
  386. package/src/runtime/roadmap/merge.js +381 -0
  387. package/src/runtime/roadmap/rows.js +1113 -0
  388. package/src/runtime/roadmap/seed.js +951 -0
  389. package/src/runtime/rules-report.js +336 -0
  390. package/src/runtime/scaffold/artifacts.js +63 -9
  391. package/src/runtime/scaffold/generate.js +21 -9
  392. package/src/runtime/settings/autopilot.js +106 -0
  393. package/src/runtime/settings/cli.js +280 -0
  394. package/src/runtime/settings/config-write.js +314 -0
  395. package/src/runtime/settings/files.js +271 -0
  396. package/src/runtime/settings/legacy.js +154 -0
  397. package/src/runtime/settings/lexical.js +56 -0
  398. package/src/runtime/settings/prepared-run.js +269 -0
  399. package/src/runtime/settings/project-layers.js +118 -0
  400. package/src/runtime/settings/refs.js +288 -0
  401. package/src/runtime/settings/registry.js +553 -0
  402. package/src/runtime/settings/resolve.js +713 -0
  403. package/src/runtime/settings/shared.js +37 -0
  404. package/src/runtime/settings/worker-billing.js +107 -0
  405. package/src/runtime/settings/write.js +269 -0
  406. package/src/runtime/slot-command.js +78 -0
  407. package/src/runtime/slots.js +671 -0
  408. package/src/runtime/source-version.js +290 -0
  409. package/src/runtime/state-root.js +217 -0
  410. package/src/runtime/storage/cli.js +12 -5
  411. package/src/runtime/storage/engine.js +9 -2
  412. package/src/runtime/storage/registry.js +14 -1
  413. package/src/runtime/storage/roots.js +66 -16
  414. package/src/runtime/storage/rules.js +82 -10
  415. package/src/runtime/storage/upkeep.js +10 -2
  416. package/src/runtime/storage/work-status.js +245 -26
  417. package/src/runtime/task-attempts.js +589 -69
  418. package/src/runtime/task-classifier.js +47 -11
  419. package/src/runtime/task-graph.js +373 -0
  420. package/src/runtime/task-run.js +778 -0
  421. package/src/runtime/task-scope.js +202 -56
  422. package/src/runtime/task-sources.js +147 -0
  423. package/src/runtime/task-state.js +440 -65
  424. package/src/runtime/team/admission.js +301 -0
  425. package/src/runtime/team/envelope.js +324 -0
  426. package/src/runtime/team/review-guard.js +156 -0
  427. package/src/runtime/trace/analyze.js +427 -0
  428. package/src/runtime/trace/baseline.js +770 -0
  429. package/src/runtime/trace/ledger.js +63 -140
  430. package/src/runtime/trace/otlp-export.js +568 -0
  431. package/src/runtime/trace/otlp-map.js +481 -0
  432. package/src/runtime/trace/spans.js +745 -0
  433. package/src/runtime/trace/telemetry.js +225 -0
  434. package/src/runtime/trace/views.js +1 -0
  435. package/src/runtime/transcripts/command.js +98 -0
  436. package/src/runtime/transcripts/cursor.js +326 -0
  437. package/src/runtime/transcripts/read.js +126 -0
  438. package/src/runtime/transcripts/readers/claude-jsonl.js +74 -0
  439. package/src/runtime/transcripts/readers/codex-rollout.js +70 -0
  440. package/src/runtime/transcripts/readers/copilot-events.js +90 -0
  441. package/src/runtime/transcripts/readers/kiro-jsonl.js +77 -0
  442. package/src/runtime/transcripts/readers/pi-jsonl.js +59 -0
  443. package/src/runtime/transcripts/registry.js +161 -0
  444. package/src/runtime/transcripts/stats.js +161 -0
  445. package/src/runtime/verification/check-key.js +628 -0
  446. package/src/runtime/verification/contract-runner.js +546 -11
  447. package/src/runtime/verification/engine.js +499 -9
  448. package/src/runtime/verification/impact.js +617 -0
  449. package/src/runtime/verification/inflight.js +205 -0
  450. package/src/runtime/verification/registry.js +176 -0
  451. package/src/runtime/verification/reuse.js +184 -0
  452. package/src/runtime/verification/telemetry-emit.js +254 -0
  453. package/src/runtime/verification/test-selection.js +67 -7
  454. package/src/runtime/worker/active.js +90 -0
  455. package/src/runtime/worker/cancel.js +222 -0
  456. package/src/runtime/worker/catalogue.js +122 -0
  457. package/src/runtime/worker/command.js +677 -0
  458. package/src/runtime/worker/evidence.js +288 -0
  459. package/src/runtime/worker/launch.js +288 -0
  460. package/src/runtime/worker/models.js +137 -0
  461. package/src/runtime/worker/parsers.js +218 -0
  462. package/src/runtime/worker/probes/index.js +706 -0
  463. package/src/runtime/worker/process-tree.js +415 -0
  464. package/src/runtime/worker/quota.js +43 -0
  465. package/src/runtime/worker/readiness.js +713 -0
  466. package/src/runtime/worker/run-store.js +166 -0
  467. package/src/runtime/worker/supervisor.js +541 -0
  468. package/src/runtime/worker/worktree-check.js +252 -0
  469. package/src/runtime/workflow-engine.js +5 -1
  470. package/src/runtime/workflow-orchestrator.js +578 -63
  471. package/src/runtime/worktree.js +149 -22
  472. package/src/state/index.js +1 -1
  473. package/src/state/lockfile.js +64 -10
  474. package/src/state/run-lock.js +1 -1
  475. package/types/node-commonjs.d.ts +106 -7
  476. package/core/.antigravity-plugin/hooks.json +0 -39
  477. package/core/harnesses/gemini/hooks/hooks.json +0 -77
  478. package/core/harnesses/gemini/hooks/lib.sh +0 -302
  479. package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +0 -21
  480. package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
  481. package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -21
  482. package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
  483. package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -22
  484. package/core/harnesses/gemini/hooks/session-end.impl.sh +0 -89
  485. package/core/harnesses/gemini/hooks/session-end.sh +0 -5
  486. package/core/harnesses/gemini/hooks/session-start.sh +0 -5
  487. package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
  488. package/core/harnesses/shared/hooks/adapters/arg-fields.js +0 -19
  489. package/core/harnesses/shared/hooks/adapters/gemini.js +0 -114
  490. package/core/harnesses/shared/hooks/hooks.json +0 -47
  491. package/core/shared/guidance/FLAGS.md +0 -25
  492. package/core/shared/guidance/rules/RULE_01_SAFETY.md +0 -25
  493. package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +0 -23
  494. package/core/shared/guidance/rules/RULE_03_QUALITY.md +0 -14
  495. package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +0 -46
  496. package/src/adapters/gemini/hooks.js +0 -210
  497. package/src/adapters/gemini/index.js +0 -390
@@ -0,0 +1,225 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ const nodeFs = require('node:fs');
6
+ const path = require('node:path');
7
+ const { resolveRunsLocation } = require('./ledger.js');
8
+ const { identityFields } = require('../context/identity.js');
9
+ const { REPO_ROOT } = require('../../helper/repo-root.js');
10
+ const { loadProtocols, presetIds } = require('../review/protocols.js');
11
+
12
+ /**
13
+ * Telemetry contract v1 (088 A.1, IC-001, IC-002): the switch and the writer for the lines that
14
+ * live in `telemetry/<UTC date>.jsonl`, a subdirectory of the run ledger's own directory.
15
+ *
16
+ * Why not `RunLedger`: it drops every line that has no `verb` (ledger.ts `sanitizeRunEvent`) and its
17
+ * readers list only the `YYYY-MM-DD.jsonl` files directly under `runs/`. These lines are keyed on
18
+ * `kind`, carry a field set per kind taken from `core/registry/telemetry.json`, and sit in a
19
+ * subdirectory those readers never see.
20
+ *
21
+ * Rules that constrain everything below.
22
+ * NFR-004 — records are metadata. A field is written only when the registry names it for the kind
23
+ * and its value passes that field's type; anything else is dropped by name or becomes null, and
24
+ * the dropped value is never echoed.
25
+ * NFR-005 / FR-002 — telemetry never changes an outcome. `write` never throws, writes one
26
+ * open-and-write per call, never blocks on a non-regular target, and writes nothing at all when
27
+ * `DOFLOW_TELEMETRY=off`.
28
+ */
29
+
30
+ /** Same pattern as ledger.ts `SAFE_TOKEN` (not exported there); a test holds the two equal. */
31
+ const SAFE_TOKEN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$/;
32
+ /** The run and lineage rule (context/identity.ts `ID_PATTERN`). */
33
+ const ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,55}$/;
34
+ const HEX16 = /^[0-9a-f]{16}$/;
35
+ const HEX32 = /^[0-9a-f]{32}$/;
36
+ const ISO_MS = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
37
+ const DATE = /^\d{4}-\d{2}-\d{2}$/;
38
+
39
+
40
+
41
+
42
+ /**
43
+ * Vocabularies another registry owns, which telemetry.json names in `enum:<name>` types but does not
44
+ * copy (119 C-9): a preset added to protocols.json is a valid `protocol` with no edit here. Each is
45
+ * read from its owner, through the owner's validator, the first time a writer needs it. An owner that
46
+ * cannot be read or fails its validator gives no list, so the field is written null (as any value
47
+ * outside its vocabulary is) and the rest of the line is still written.
48
+ */
49
+ const DERIVED_VOCABULARIES = Object.freeze({
50
+ depth: 'core/registry/protocols.json review preset depth labels, loosest first',
51
+ protocol: 'core/registry/protocols.json order.review, then the literal package',
52
+ });
53
+
54
+ /** The derived vocabularies read from the registries under `repoRoot`; `{}` when protocols.json is unusable. */
55
+ function derivedVocabularies(repoRoot = REPO_ROOT) {
56
+ const loaded = loadProtocols({ repoRoot });
57
+ if (!loaded.ok) return {};
58
+ const review = presetIds(loaded.registry, 'review');
59
+ return {
60
+ depth: [...review].reverse().map((id) => loaded.registry.presets[id].depth),
61
+ // 122 IC-024: `package` stands for any preset an active package contributes, so no third-party name is written.
62
+ protocol: [...review, 'package'],
63
+ };
64
+ }
65
+
66
+ /**
67
+ * The one reader of the switch in Node. The bridge and the observer test the same string in shell.
68
+ * @param {Object} [env=process.env]
69
+ * @returns {boolean}
70
+ */
71
+ function telemetryOff(env = process.env) {
72
+ return env.DOFLOW_TELEMETRY === 'off';
73
+ }
74
+
75
+ /** One type alternative of a field (`token`, `uint`, `enum:<vocabulary>`, ...) against one value. */
76
+ function matches(alternative , value , vocabulary ) {
77
+ if (alternative === 'null') return value === null;
78
+ if (alternative === 'uint') return typeof value === 'number' && Number.isFinite(value) && Math.trunc(value) >= 0;
79
+ if (alternative === 'int') return typeof value === 'number' && Number.isFinite(value);
80
+ if (typeof value !== 'string') return false;
81
+ if (alternative === 'token') return SAFE_TOKEN.test(value);
82
+ if (alternative === 'id') return ID.test(value);
83
+ if (alternative === 'hex16') return HEX16.test(value);
84
+ if (alternative === 'hex32') return HEX32.test(value);
85
+ if (alternative === 'iso8601ms') return ISO_MS.test(value);
86
+ if (alternative === 'date') return DATE.test(value);
87
+ if (alternative.startsWith('enum:')) {
88
+ const list = vocabulary(alternative.slice(5));
89
+ return Array.isArray(list) && list.includes(value);
90
+ }
91
+ return false; // an unknown type never passes, so a typo in the registry writes null, not content
92
+ }
93
+
94
+ /** The value as it is written: integers truncated, anything failing the field's type null. */
95
+ function coerce(type , value , vocabulary ) {
96
+ if (typeof type !== 'string' || !type.split('|').some((alternative) => matches(alternative, value, vocabulary))) return null;
97
+ return typeof value === 'number' ? Math.trunc(value) : value;
98
+ }
99
+
100
+ let shipped = null;
101
+ function shippedRegistry() {
102
+ if (!shipped) shipped = JSON.parse(nodeFs.readFileSync(path.join(REPO_ROOT, 'core', 'registry', 'telemetry.json'), 'utf8'));
103
+ return shipped;
104
+ }
105
+
106
+ class TelemetryWriter {
107
+ registry ;
108
+ env ;
109
+ fsImpl ;
110
+ now ;
111
+ start ;
112
+ repoRoot ;
113
+ derived = null;
114
+
115
+ /**
116
+ * @param {Object} [options]
117
+ * @param {Object} [options.registry] a parsed `telemetry.json` (default: the shipped one)
118
+ * @param {Object} [options.env=process.env] the switch, the identity trio and `DOFLOW_CONFIG_DIR`
119
+ * @param {Object} [options.fsImpl=fs] test seam
120
+ * @param {Function} [options.now] returns the current Date; test seam
121
+ * @param {string} [options.start] directory to walk up from for `.doflow` (default: cwd)
122
+ * @param {string} [options.repoRoot=REPO_ROOT] the install root the derived vocabularies are read from
123
+ */
124
+ constructor({ registry, env = process.env, fsImpl = nodeFs, now = () => new Date(), start, repoRoot = REPO_ROOT } = {}) {
125
+ this.registry = registry;
126
+ this.repoRoot = repoRoot;
127
+ this.env = env;
128
+ this.fsImpl = fsImpl;
129
+ this.now = now;
130
+ this.start = start;
131
+ }
132
+
133
+ /**
134
+ * Appends the records, one line each, in one open-and-write to the partition named for the UTC
135
+ * date of the first line. Never throws; a fault writes nothing.
136
+ * @param {Array<{kind: string}>} records
137
+ * @returns {void}
138
+ */
139
+ write(records ) {
140
+ try {
141
+ if (telemetryOff(this.env) || !Array.isArray(records)) return;
142
+ const registry = this.registry || shippedRegistry();
143
+ const lines = [];
144
+ let first = '';
145
+ for (const record of records) {
146
+ let line = null;
147
+ try { line = this.line(registry, record); } catch { line = null; } // a hostile record costs only itself
148
+ if (!line) continue;
149
+ if (!first) first = line.timestamp;
150
+ lines.push(JSON.stringify(line));
151
+ }
152
+ if (!lines.length) return;
153
+ const { dir: ledgerDir } = resolveRunsLocation({ start: this.start, env: this.env, fsImpl: this.fsImpl });
154
+ const dir = path.join(ledgerDir, 'telemetry');
155
+ // Non-recursive: when the ledger directory is absent this throws ENOENT and nothing is written or invented.
156
+ try {
157
+ this.fsImpl.mkdirSync(dir, { recursive: false });
158
+ } catch (error ) {
159
+ if (error?.code !== 'EEXIST') throw error;
160
+ }
161
+ this.append(path.join(dir, `${first.slice(0, 10)}.jsonl`), Buffer.from(`${lines.join('\n')}\n`, 'utf8'));
162
+ } catch {
163
+ // A telemetry fault changes no outcome (FR-002, NFR-005): there is nothing to report to.
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Appends to a regular file and nothing else. A FIFO with no reader would block a plain open
169
+ * forever, so: lstat first and skip anything that exists and is not a regular file (FIFO,
170
+ * directory, socket, device, symlink); then open O_NONBLOCK|O_NOFOLLOW so a swap between the lstat
171
+ * and the open fails (ENXIO, ELOOP) instead of blocking or following; then fstat the descriptor.
172
+ */
173
+ append(file , data ) {
174
+ let known = null;
175
+ try {
176
+ known = this.fsImpl.lstatSync(file);
177
+ } catch (error ) {
178
+ if (error?.code !== 'ENOENT') return;
179
+ }
180
+ if (known && !known.isFile()) return;
181
+ const { O_WRONLY, O_APPEND, O_CREAT, O_NOFOLLOW = 0, O_NONBLOCK = 0 } = nodeFs.constants;
182
+ const fd = this.fsImpl.openSync(file, O_WRONLY | O_APPEND | O_CREAT | O_NOFOLLOW | O_NONBLOCK, 0o666);
183
+ try {
184
+ if (!this.fsImpl.fstatSync(fd).isFile()) return;
185
+ for (let done = 0; done < data.length;) done += this.fsImpl.writeSync(fd, data, done);
186
+ } finally {
187
+ this.fsImpl.closeSync(fd);
188
+ }
189
+ }
190
+
191
+ /** The sanitised line for one record, or null when it has no registered `kind`. */
192
+ line(registry , record ) {
193
+ if (!record || typeof record !== 'object' || Array.isArray(record)) return null;
194
+ const kind = record.kind;
195
+ if (typeof kind !== 'string' || !Object.hasOwn(registry.kinds, kind)) return null;
196
+ const vocabularies = registry.vocabularies;
197
+ const vocabulary = (name) => {
198
+ if (Object.hasOwn(DERIVED_VOCABULARIES, name)) {
199
+ this.derived ??= derivedVocabularies(this.repoRoot);
200
+ return this.derived[name] ?? null;
201
+ }
202
+ return vocabularies && Object.hasOwn(vocabularies, name) ? vocabularies[name] : null;
203
+ };
204
+ const timestamp = typeof record.timestamp === 'string' && ISO_MS.test(record.timestamp)
205
+ ? record.timestamp
206
+ : this.now().toISOString();
207
+ // Identity comes from the environment only (a caller-supplied value is ignored), as for the ledger.
208
+ const identity = identityFields(this.env);
209
+ const out = {
210
+ timestamp,
211
+ kind,
212
+ run: identity.run ?? null,
213
+ lineage: identity.lineage ?? null,
214
+ attempt: identity.attempt === undefined ? null : Number(identity.attempt),
215
+ };
216
+ const fields = registry.kinds[kind].fields || {};
217
+ for (const key of Object.keys(fields)) {
218
+ if (!Object.hasOwn(record, key)) continue;
219
+ out[key] = coerce(fields[key].type, record[key], vocabulary);
220
+ }
221
+ return out;
222
+ }
223
+ }
224
+
225
+ module.exports = { SAFE_TOKEN, DERIVED_VOCABULARIES, derivedVocabularies, telemetryOff, TelemetryWriter };
@@ -451,6 +451,7 @@ function buildDiscover(read , { providerHealth = null, gapMs = SESSION_GAP_M
451
451
 
452
452
  module.exports = {
453
453
  SESSION_GAP_MS,
454
+ percentile,
454
455
  groupSessions,
455
456
  buildTrace,
456
457
  buildStats,
@@ -0,0 +1,98 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * `doflow transcript` (093 V8): the read-only per-agent transcript verb. `--action read --agent <key>` prints
5
+ * one agent's recent events with DoFlow's own record events (IC-022, `readAgent`); `--action stats` prints the
6
+ * project's traffic and who works with whom (IC-023, `computeStats`). Run with `cwd` = a project. It writes
7
+ * nothing: no state, no config home, no harness home, no upkeep.
8
+ */
9
+
10
+ const { finishRuntime, captureCaught } = require('../cli-result.js');
11
+
12
+ const ACTION_FLAGS = {
13
+ read: new Set(['--action', '--agent', '--cursor', '--limit', '--json']),
14
+ stats: new Set(['--action', '--window', '--json']),
15
+ };
16
+ const AGENT_KEY = /^[0-9a-f]{16}$/;
17
+ const CURSOR = /^[0-9]+:[0-9]+:[0-9]+:[0-9]+$/;
18
+ const DIGITS = /^[0-9]+$/;
19
+
20
+ const dash = (value ) => (value === null || value === undefined ? '-' : String(value));
21
+
22
+ /** The text form of `read`: a header, one line per event and a footer. Text and arguments are JSON-quoted (one line each). */
23
+ function readLines(body ) {
24
+ const lines = [
25
+ `agent ${body.agentKey} harness ${dash(body.harness)} reader ${dash(body.reader.id)} ${body.reader.status} transcript ${body.transcript.linked ? 'linked' : body.transcript.reason}`,
26
+ ];
27
+ for (const e of body.events) {
28
+ const parts = [`event ${e.seq} ${dash(e.at)} ${e.kind}`];
29
+ if (e.model !== null) parts.push(`model ${e.model}`);
30
+ if (e.tool) parts.push(`tool ${dash(e.tool.name)} status ${dash(e.tool.status)} bytes ${dash(e.tool.bytes)} ms ${dash(e.tool.durationMs)}`);
31
+ if (e.text !== null) parts.push(`text ${JSON.stringify(e.text)}`);
32
+ if (e.args !== null) parts.push(`args ${JSON.stringify(e.args)}`);
33
+ if (e.cut) parts.push('cut');
34
+ if (e.tokens) parts.push(`tokens in ${dash(e.tokens.input)} out ${dash(e.tokens.output)} cache-read ${dash(e.tokens.cacheRead)} cache-write ${dash(e.tokens.cacheWrite)} reasoning ${dash(e.tokens.reasoning)}`);
35
+ if (e.record) parts.push(`record ${e.record.type} task ${dash(e.record.task)} result ${dash(e.record.result)}`);
36
+ lines.push(parts.join(' '));
37
+ }
38
+ lines.push(`cursor ${body.cursor} reset ${body.reset} eof ${body.eof} other ${body.otherRecords} unparsable ${body.unparsable} masked ${body.masked} warnings ${body.warnings.join(',') || '-'}`);
39
+ return lines;
40
+ }
41
+
42
+ /** The text form of `stats`: keys, models and counts only. */
43
+ function statsLines(body ) {
44
+ return [
45
+ `project ${body.projectKey} window ${body.windowMin} min`,
46
+ ...body.agents.map((a ) => `agent ${a.agentKey} harness ${dash(a.harness)} model ${dash(a.model)} events/min ${dash(a.eventsPerMin)} tool-calls ${dash(a.toolCalls)} tool-errors ${dash(a.toolErrors)} tool-ms ${a.toolDurationMs ? `p50 ${a.toolDurationMs.p50} max ${a.toolDurationMs.max} n ${a.toolDurationMs.n}` : '-'} tokens ${a.tokens ? `in ${dash(a.tokens.input)} out ${dash(a.tokens.output)}` : '-'} last-record ${dash(a.lastRecordAt)}`),
47
+ ...body.models.map((m ) => `model ${m.model} agents ${m.agents} events/min ${dash(m.eventsPerMin)} tool-calls ${dash(m.toolCalls)}`),
48
+ ...body.pairs.map((p ) => `pair ${p.a} ${p.b} ${p.basis}${p.task ? ` task ${p.task}` : ''}${p.tasks ? ` tasks ${p.tasks.join(',')}` : ''}`),
49
+ `not-checked ${body.notChecked}`,
50
+ ];
51
+ }
52
+
53
+ function handleTranscriptCommand({
54
+ action, agent, cursor, limit, window, json = false, flags = [], env = process.env, cwd = process.cwd(), exec, alive, now, out = (line ) => console.log(line),
55
+ } = {}) {
56
+ const usage = (message ) => {
57
+ out(json ? JSON.stringify({ ok: false, error: 'usage', message }) : `doflow transcript: ${message}`);
58
+ return finishRuntime(2);
59
+ };
60
+ if (action !== 'read' && action !== 'stats') return usage('action must be read or stats');
61
+ const extra = (flags ).map((flag) => flag.split('=')[0]).find((flag) => !ACTION_FLAGS[action].has(flag));
62
+ if (extra !== undefined) return usage(`unsupported flag ${extra} for ${action}; only ${[...ACTION_FLAGS[action]].join(', ')} apply`);
63
+
64
+ let count ;
65
+ let minutes ;
66
+ if (action === 'read') {
67
+ if (typeof agent !== 'string' || !AGENT_KEY.test(agent)) return usage('--agent must be an agent key of 16 hex characters');
68
+ if (cursor !== undefined && (typeof cursor !== 'string' || !CURSOR.test(cursor))) return usage('--cursor must be the cursor of an earlier read');
69
+ if (limit !== undefined) {
70
+ if (typeof limit !== 'string' || !DIGITS.test(limit) || Number(limit) < 1) return usage('--limit must be a whole number of 1 or more (at most 500 are returned)');
71
+ count = Math.min(500, Number(limit));
72
+ }
73
+ } else if (window !== undefined) {
74
+ if (typeof window !== 'string' || !DIGITS.test(window) || Number(window) < 1 || Number(window) > 60) return usage('--window must be a whole number of minutes, 1 to 60');
75
+ minutes = Number(window);
76
+ }
77
+
78
+ try {
79
+ const common = { env, cwd, ...(exec ? { exec } : {}), ...(alive ? { alive } : {}), ...(now ? { now } : {}) };
80
+ const result = action === 'read'
81
+ ? require('./read.js').readAgent({ ...common, agent, cursor, limit: count })
82
+ : require('./stats.js').computeStats({ ...common, ...(minutes !== undefined ? { windowMin: minutes } : {}) });
83
+ if (!result.ok) {
84
+ if (json) out(JSON.stringify({ v: 1, error: result.error }));
85
+ else console.error(`[ERROR] transcript: ${result.error}`);
86
+ return finishRuntime(1);
87
+ }
88
+ if (json) out(JSON.stringify(result.body));
89
+ else for (const line of action === 'read' ? readLines(result.body) : statsLines(result.body)) out(line);
90
+ return finishRuntime(0);
91
+ } catch (error ) {
92
+ captureCaught(error, 'transcript', 1);
93
+ console.error(`[ERROR] transcript: ${error?.message || String(error)}`);
94
+ return finishRuntime(1);
95
+ }
96
+ }
97
+
98
+ module.exports = { handleTranscriptCommand };
@@ -0,0 +1,326 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * 093 V.4 (IC-022): the byte cursor, the bounded window read and the driver every transcript reader shares.
5
+ *
6
+ * A reader (`readers/<id>.ts`) is a `SPEC` of three small parts: its required keys, an optional `init`, and
7
+ * `parse(record, ctx)`, which returns the normalised events of one record. The driver owns the rest:
8
+ * - the cursor `<inode>:<byteOffset>:<recordSeq>:<recordAtMs>`; a changed inode or a shorter file resets it
9
+ * to the last 64 records (`reset: true`); a read with no cursor starts the same way with `reset: false`;
10
+ * - at most 256 KiB per call, whole lines only: an unfinished last line is left for the next read;
11
+ * - `otherRecords` (records and blocks never read into an event: thinking, prompts, attachments, unknown types),
12
+ * `unparsable` (lines that are not a JSON object), and the `format-unrecognized` warning when more than 20 %
13
+ * of the processed records are unparsable or lack the reader's required keys;
14
+ * - text policy: every text-bearing string is masked with `maskBody` AFTER JSON decoding, then cut after
15
+ * masking (reply 4 KiB, tool arguments 512 B) with `cut: true`. Never on the raw line (R§4.8): escaped JSON
16
+ * such as `\"token\":\"...\"` inside a string slips past the key rules until it is decoded.
17
+ * Read-only: files are opened `'r'` and nothing is created or written. Ceilings: `recordSeq` counts events
18
+ * from the reset point, not from the start of the file; Claude usage dedup and the model of an event only see
19
+ * the current window, so a message split across two reads can count its usage twice.
20
+ */
21
+
22
+ const fs = require('node:fs');
23
+ const { createHash } = require('node:crypto');
24
+ const { maskBody } = require('../mask.js');
25
+
26
+ const MAX_READ_BYTES = 256 * 1024;
27
+ const TAIL_RECORDS = 64;
28
+ const DEFAULT_LIMIT = 200;
29
+ const MAX_LIMIT = 500;
30
+ const TEXT_CAP = 4096;
31
+ const ARGS_CAP = 512;
32
+ const MASK_MARGIN = 4096; // bytes kept past the cap before masking, so a secret cut by the cap is still whole when masked
33
+ const LEAF_MAX = 8192;
34
+ const UNRECOGNIZED_RATIO = 0.2;
35
+ const LABEL = /^[A-Za-z0-9 ._:/@-]{1,64}$/;
36
+ const CURSOR = /^(\d+):(\d+):(\d+):(\d+)$/;
37
+
38
+ /** IC-004's label rule: a value that is not a short plain label is withheld (null for an absent value). */
39
+ function label(value ) {
40
+ if (value === null || value === undefined) return null;
41
+ return typeof value === 'string' && LABEL.test(value) ? value : '(withheld)';
42
+ }
43
+
44
+ function parseCursor(text ) {
45
+ const m = typeof text === 'string' ? CURSOR.exec(text) : null;
46
+ if (!m) return null;
47
+ const [offset, seq, atMs] = [Number(m[2]), Number(m[3]), Number(m[4])];
48
+ return [offset, seq, atMs].every(Number.isSafeInteger) ? { ino: m[1], offset, seq, atMs } : null;
49
+ }
50
+
51
+ /** Milliseconds from an ISO string or an epoch-ms number; NaN when neither. */
52
+ function toMs(at ) {
53
+ if (typeof at === 'number') return Number.isFinite(at) ? at : NaN;
54
+ return typeof at === 'string' ? Date.parse(at) : NaN;
55
+ }
56
+
57
+ function toIso(at ) {
58
+ const ms = toMs(at);
59
+ return Number.isFinite(ms) ? new Date(ms).toISOString() : null;
60
+ }
61
+
62
+ function callKey(id ) {
63
+ return typeof id === 'string' || typeof id === 'number' ? createHash('sha256').update(String(id)).digest('hex').slice(0, 16) : null;
64
+ }
65
+
66
+ /** Bytes of a tool result's content, never the content itself. */
67
+ function sizeOf(value ) {
68
+ if (value === null || value === undefined) return null;
69
+ return Buffer.byteLength(typeof value === 'string' ? value : JSON.stringify(value) ?? '', 'utf8');
70
+ }
71
+
72
+ /** A token record with a number or null per field; null when nothing is known (unknown is never zero). */
73
+ function tokens(fields ) {
74
+ const out = {};
75
+ for (const key of ['input', 'output', 'cacheRead', 'cacheWrite', 'reasoning']) {
76
+ out[key] = Number.isFinite(fields[key]) ? fields[key] : null;
77
+ }
78
+ return Object.values(out).some((v) => v !== null) ? out : null;
79
+ }
80
+
81
+ function cutBytes(text , cap ) {
82
+ const buf = Buffer.from(text, 'utf8');
83
+ if (buf.length <= cap) return { text, cut: false };
84
+ let end = cap;
85
+ while (end > 0 && (buf[end] & 0xc0) === 0x80) end -= 1; // never split a character
86
+ return { text: buf.toString('utf8', 0, end), cut: true };
87
+ }
88
+
89
+ /** Reply text: mask (after decoding), then cut. */
90
+ function capText(value , cap ) {
91
+ const s = String(value);
92
+ const long = s.length > cap + MASK_MARGIN;
93
+ const m = maskBody(long ? s.slice(0, cap + MASK_MARGIN) : s);
94
+ const r = cutBytes(m.text, cap);
95
+ return { text: r.text, cut: r.cut || long, masked: m.masked };
96
+ }
97
+
98
+ /**
99
+ * Tool arguments: a JSON string is decoded, every string leaf is masked on its own decoded text (so a
100
+ * quoted key inside an embedded JSON string is plain again), the structure is rendered, the rendering is
101
+ * masked once more (a `{"password":"x"}` pair only shows its key next to its value there), then cut.
102
+ */
103
+ function capArgs(value , cap ) {
104
+ let masked = 0;
105
+ let leaves = 0;
106
+ let v = value;
107
+ if (typeof v === 'string') {
108
+ try { const parsed = JSON.parse(v); if (parsed !== null && typeof parsed === 'object') v = parsed; } catch { /* plain text */ }
109
+ }
110
+ const walk = (x , depth ) => {
111
+ if (typeof x === 'string') {
112
+ if (++leaves > 64) return '...';
113
+ const r = maskBody(x.length > LEAF_MAX ? x.slice(0, LEAF_MAX) : x);
114
+ masked += r.masked;
115
+ return r.text;
116
+ }
117
+ if (x === null || typeof x !== 'object') return x;
118
+ if (depth > 6) return '...';
119
+ if (Array.isArray(x)) return x.slice(0, 64).map((item) => walk(item, depth + 1));
120
+ const out = {};
121
+ for (const key of Object.keys(x).slice(0, 64)) out[key] = walk(x[key], depth + 1);
122
+ return out;
123
+ };
124
+ const walked = walk(v, 0);
125
+ const r = capText(typeof walked === 'string' ? walked : JSON.stringify(walked) ?? '', cap);
126
+ return { text: r.text, cut: r.cut, masked: masked + r.masked };
127
+ }
128
+
129
+ function readRange(fd , from , to ) {
130
+ const buf = Buffer.alloc(Math.max(0, to - from));
131
+ let got = 0;
132
+ while (got < buf.length) {
133
+ const n = fs.readSync(fd, buf, got, buf.length - got, from + got);
134
+ if (n === 0) break;
135
+ got += n;
136
+ }
137
+ return got < buf.length ? buf.subarray(0, got) : buf;
138
+ }
139
+
140
+
141
+
142
+ /**
143
+ * Whole lines of `buf` (absolute offset `base` for its first byte). `skipFirst` drops the leading partial
144
+ * line (the buffer then starts one byte before the first wanted byte). Blank lines are skipped but consumed.
145
+ */
146
+ function splitLines(buf , base , skipFirst ) {
147
+ const lines = [];
148
+ let p = 0;
149
+ if (skipFirst) {
150
+ const i = buf.indexOf(10);
151
+ p = i === -1 ? buf.length : i + 1;
152
+ }
153
+ for (let nl = buf.indexOf(10, p); nl !== -1 && p < buf.length; nl = buf.indexOf(10, p)) {
154
+ const text = buf.toString('utf8', p, nl);
155
+ if (text.trim() !== '') lines.push({ text, start: base + p, end: base + nl + 1 });
156
+ p = nl + 1;
157
+ }
158
+ return { lines, end: base + p, rest: buf.length - p };
159
+ }
160
+
161
+ /**
162
+ * The window one call reads. `lines` are the records to process; `prior()` returns the raw lines before the
163
+ * window (Copilot differences its cumulative usage against them).
164
+ */
165
+ function openWindow(file , cursorText , maxBytes , tailRecords = TAIL_RECORDS) {
166
+ const fd = fs.openSync(file, 'r');
167
+ try {
168
+ const st = fs.fstatSync(fd, { bigint: true });
169
+ const ino = String(st.ino);
170
+ const size = Number(st.size);
171
+ const cursor = parseCursor(cursorText);
172
+ const reset = cursor !== null && (cursor.ino !== ino || size < cursor.offset);
173
+ const tail = cursor === null || reset;
174
+ const from = tail ? Math.max(0, size - maxBytes) : (cursor ).offset;
175
+ const skipFirst = tail && from > 0;
176
+ const base = skipFirst ? from - 1 : from;
177
+ const buf = readRange(fd, base, Math.min(size, from + maxBytes));
178
+ const split = splitLines(buf, base, skipFirst);
179
+ let { lines } = split;
180
+ if (tail && lines.length > tailRecords) lines = lines.slice(-tailRecords);
181
+ const windowEnd = base + buf.length;
182
+ // One line longer than the whole window: consume it as a single unparsable record.
183
+ if (!skipFirst && lines.length === 0 && split.rest > 0 && windowEnd < size) {
184
+ lines = [{ text: '', start: from, end: windowEnd, oversize: true }];
185
+ }
186
+ const startOffset = lines.length ? lines[0].start : split.end;
187
+ return {
188
+ ino, size, cursor, reset, tail, lines, startOffset, completeEnd: lines.length ? lines[lines.length - 1].end : split.end,
189
+ reachedEnd: windowEnd >= size,
190
+ prior() {
191
+ if (startOffset === 0) return [];
192
+ const pFrom = Math.max(0, startOffset - maxBytes);
193
+ const pBase = pFrom > 0 ? pFrom - 1 : 0;
194
+ const pfd = fs.openSync(file, 'r');
195
+ try { return splitLines(readRange(pfd, pBase, startOffset), pBase, pFrom > 0).lines.map((l) => l.text); } finally { fs.closeSync(pfd); }
196
+ },
197
+ };
198
+ } finally {
199
+ fs.closeSync(fd);
200
+ }
201
+ }
202
+
203
+ /** The first `maxLines` JSON-object records of a file (at most 64 KiB), for a reader's `header`. */
204
+ function headRecords(file , maxLines = 8, maxBytes = 64 * 1024) {
205
+ const fd = fs.openSync(file, 'r');
206
+ try {
207
+ const out = [];
208
+ const { lines } = splitLines(readRange(fd, 0, maxBytes), 0, false);
209
+ for (const line of lines) {
210
+ if (out.length >= maxLines) break;
211
+ try { const rec = JSON.parse(line.text); if (rec && typeof rec === 'object') out.push(rec); } catch { /* torn */ }
212
+ }
213
+ return out;
214
+ } finally {
215
+ fs.closeSync(fd);
216
+ }
217
+ }
218
+
219
+ /** A small JSON companion file (at most 256 KiB), or null when absent or unreadable. */
220
+ function readJsonFile(file ) {
221
+ try {
222
+ const fd = fs.openSync(file, 'r');
223
+ try {
224
+ const size = fs.fstatSync(fd).size;
225
+ if (size > MAX_READ_BYTES) return null;
226
+ const parsed = JSON.parse(readRange(fd, 0, size).toString('utf8'));
227
+ return parsed !== null && typeof parsed === 'object' ? parsed : null;
228
+ } finally { fs.closeSync(fd); }
229
+ } catch { return null; }
230
+ }
231
+
232
+ function makeContext(mode , agentKey ) {
233
+ const ctx = { mode, agentKey, pending: 0, state: {}, calls: new Map () };
234
+ ctx.tool = (name , id , extra = {}) => ({
235
+ name: label(name), callKey: callKey(id), status: extra.status ?? null, bytes: extra.bytes ?? null, durationMs: extra.durationMs ?? null,
236
+ });
237
+ ctx.noteCall = (id , at , name ) => { if (id !== undefined && id !== null) ctx.calls.set(String(id), { ms: toMs(at), name: typeof name === 'string' ? name : null }); };
238
+ ctx.callOf = (id ) => (id === undefined || id === null ? undefined : ctx.calls.get(String(id)));
239
+ /** Milliseconds from the call's record to `at`, when both are in the window. */
240
+ ctx.durationOf = (id , at ) => {
241
+ const call = ctx.callOf(id);
242
+ const d = call ? toMs(at) - call.ms : NaN;
243
+ return Number.isFinite(d) && d >= 0 ? d : null;
244
+ };
245
+ ctx.event = (kind , at , f = {}) => {
246
+ let text = null;
247
+ let args = null;
248
+ let cut = false;
249
+ if (mode === 'text') {
250
+ if (f.text !== undefined && f.text !== null) { const r = capText(f.text, TEXT_CAP); text = r.text; cut = r.cut; ctx.pending += r.masked; }
251
+ if (f.args !== undefined && f.args !== null) { const r = capArgs(f.args, ARGS_CAP); args = r.text; cut = cut || r.cut; ctx.pending += r.masked; }
252
+ }
253
+ return {
254
+ seq: 0, at: toIso(at), kind, agentKey: ctx.agentKey, model: label(f.model ?? ctx.state.model ?? null),
255
+ tool: f.tool ?? null, text, args, cut, tokens: f.tokens ?? null, record: null,
256
+ };
257
+ };
258
+ return ctx;
259
+ }
260
+
261
+ /**
262
+ * Runs one reader over one transcript file. `opts`: `file`, `mode` ('text' | 'metadata'), `cursor`, `limit`
263
+ * (default 200, at most 500), `agentKey`, `companion` (Kiro's `.json`), `maxBytes` (default 256 KiB), `tailRecords`
264
+ * (records kept by a read with no cursor, default 64; the traffic stats read the whole 256 KiB tail).
265
+ * Throws the fs error when the file cannot be opened; the caller maps it to `not-found`.
266
+ */
267
+ function readTranscript(spec , opts ) {
268
+ const mode = opts.mode === 'metadata' ? 'metadata' : 'text';
269
+ const limit = Math.min(MAX_LIMIT, Math.max(1, Number.isInteger(opts.limit) ? opts.limit : DEFAULT_LIMIT));
270
+ const win = openWindow(opts.file, opts.cursor, opts.maxBytes ?? MAX_READ_BYTES, opts.tailRecords ?? TAIL_RECORDS);
271
+ const ctx = makeContext(mode, opts.agentKey ?? null);
272
+ if (spec.init) ctx.state = spec.init(win, opts, ctx) ?? {};
273
+
274
+ const events = [];
275
+ let seq = win.tail ? 0 : win.cursor.seq;
276
+ let atMs = win.tail ? 0 : win.cursor.atMs;
277
+ let committedEnd = null;
278
+ let [other, unparsable, bad, processed, masked] = [0, 0, 0, 0, 0];
279
+ let stopped = false;
280
+
281
+ for (const line of win.lines) {
282
+ let rec = null;
283
+ if (!line.oversize) { try { rec = JSON.parse(line.text); } catch { /* counted below */ } }
284
+ ctx.pending = 0;
285
+ let out ;
286
+ if (rec === null || typeof rec !== 'object' || Array.isArray(rec)) out = { events: [], other: 0, unparsable: 1 };
287
+ else if (!spec.requiredKeys.every((key ) => rec[key] !== undefined)) out = { events: [], other: 1, bad: 1 };
288
+ else {
289
+ out = spec.parse(rec, ctx);
290
+ if (out.other === undefined) out.other = out.events.length ? 0 : 1;
291
+ }
292
+ if (events.length > 0 && events.length + out.events.length > limit) { stopped = true; break; }
293
+ for (const event of out.events) { event.seq = seq; seq += 1; events.push(event); }
294
+ other += out.other;
295
+ unparsable += out.unparsable ?? 0;
296
+ bad += out.bad ?? 0;
297
+ masked += ctx.pending;
298
+ processed += 1;
299
+ committedEnd = line.end;
300
+ const ms = rec ? toMs(spec.at ? spec.at(rec) : rec.timestamp) : NaN;
301
+ if (Number.isFinite(ms)) atMs = ms;
302
+ }
303
+
304
+ const offset = committedEnd ?? (win.tail ? win.startOffset : win.cursor.offset);
305
+ let eof = !stopped && win.reachedEnd;
306
+ if (spec.extra) {
307
+ ctx.pending = 0;
308
+ const more = spec.extra(ctx, win, opts, limit - events.length);
309
+ for (const event of more.events) { event.seq = seq; seq += 1; events.push(event); }
310
+ masked += ctx.pending;
311
+ if (Number.isFinite(more.atMs)) atMs = more.atMs;
312
+ if (more.more) eof = false;
313
+ }
314
+
315
+ const warnings = [];
316
+ if (processed > 0 && (unparsable + bad) / processed > UNRECOGNIZED_RATIO) warnings.push('format-unrecognized');
317
+ return {
318
+ events, cursor: `${win.ino}:${offset}:${seq}:${Math.trunc(atMs)}`, reset: win.reset, eof,
319
+ otherRecords: other, unparsable, masked, warnings,
320
+ };
321
+ }
322
+
323
+ module.exports = {
324
+ MAX_READ_BYTES, TAIL_RECORDS, DEFAULT_LIMIT, MAX_LIMIT, TEXT_CAP, ARGS_CAP, UNRECOGNIZED_RATIO,
325
+ parseCursor, readTranscript, headRecords, readJsonFile, label, toMs, toIso, sizeOf, tokens,
326
+ };