@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,178 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * `fleet --action projects` (093 IC-019): the projects this machine knows, from two read-only
5
+ * stores: the user-state `upkeep-roots` index and the `cwd` of each hook project directory
6
+ * (`<config home>/doflow/session-env/projects/<hash>/meta.json`). A candidate is kept when it is an
7
+ * existing directory (not a symlink) whose git work-tree top holds `.doflow/` and is not under a
8
+ * temp folder; candidates that share a git common dir are one project. `root` (the main checkout)
9
+ * is for the Console server only. Nothing is written: no lock, no mkdir, no upkeep.
10
+ * @returns {{ok: false, error: 'cannot-list-projects'} | {ok: true, body: Object}}
11
+ */
12
+
13
+ const fs = require('node:fs');
14
+ const os = require('node:os');
15
+ const path = require('node:path');
16
+ const { spawnSync } = require('node:child_process');
17
+ const { cwdHash, stateDir } = require('../context/identity.js');
18
+ const { listCheckouts } = require('../checkouts.js');
19
+ const { resolveRoots } = require('../storage/roots.js');
20
+ const { loadStorageRegistry } = require('../storage/registry.js');
21
+ const { REPO_ROOT } = require('../../helper/repo-root.js');
22
+
23
+ const HOOK_DIR_LIMIT = 2000;
24
+ const PROBE_LIMIT = 500; // git probes per call: each is one process, and the Console polls this
25
+ const META_MAX_BYTES = 64 * 1024;
26
+ const LABEL = /^[A-Za-z0-9 ._:/@-]{1,64}$/;
27
+ const FIXED_TEMP_ROOTS = ['/private/tmp', '/private/var/folders'];
28
+
29
+
30
+
31
+ function realOrNull(p , fsImpl ) {
32
+ try { return fsImpl.realpathSync(p); } catch { return null; }
33
+ }
34
+
35
+ function under(p , root ) {
36
+ return p === root || p.startsWith(root.endsWith(path.sep) ? root : root + path.sep);
37
+ }
38
+
39
+ /** The label rule of IC-004: at most 64 characters of `[A-Za-z0-9 ._:/@-]`, else `(withheld)`. */
40
+ function label(name ) {
41
+ return LABEL.test(name) ? name : '(withheld)';
42
+ }
43
+
44
+ function iso(ms ) {
45
+ return Number.isFinite(ms) ? new Date(ms).toISOString() : null;
46
+ }
47
+
48
+ function lstatOrNull(p , fsImpl ) {
49
+ try { return fsImpl.lstatSync(p); } catch { return null; }
50
+ }
51
+
52
+ function listProjects({
53
+ env = process.env, cwd = process.cwd(), exec = spawnSync, fsImpl = fs, tempRoots,
54
+ } = {}) {
55
+ const excluded = { missing: 0, temp: 0, notDoflow: 0, unreadable: 0 };
56
+ const body = { v: 1, store: 'absent', projects: [], excluded, truncated: false };
57
+
58
+ const userState = resolveRoots({ registry: loadStorageRegistry({ repoRoot: REPO_ROOT }), projectRoot: null, global: true, env })['user-state'];
59
+ const hooks = stateDir(env);
60
+ if (userState === null && hooks === null) return { ok: true, body };
61
+ body.store = 'present';
62
+
63
+ // Candidates keyed by the path as written; each keeps its sources and the newest last_active seen.
64
+ const candidates = new Map ();
65
+ const add = (dir , source , activeMs = -Infinity) => {
66
+ const c = candidates.get(dir) ?? { sources: new Set (), activeMs: -Infinity };
67
+ c.sources.add(source);
68
+ c.activeMs = Math.max(c.activeMs, activeMs);
69
+ candidates.set(dir, c);
70
+ };
71
+
72
+ if (userState !== null) {
73
+ const file = path.join(userState, 'upkeep-roots');
74
+ const info = lstatOrNull(file, fsImpl);
75
+ if (info && info.isFile()) {
76
+ let text = '';
77
+ try { text = fsImpl.readFileSync(file, 'utf8'); } catch { excluded.unreadable += 1; }
78
+ for (const line of text.split('\n')) {
79
+ if (line === '') continue;
80
+ if (path.isAbsolute(line)) add(line, 'upkeep-roots'); else excluded.unreadable += 1;
81
+ }
82
+ }
83
+ }
84
+
85
+ if (hooks !== null) {
86
+ const base = path.join(hooks, 'projects');
87
+ let dirs = [];
88
+ try {
89
+ for (const dirent of fsImpl.readdirSync(base, { withFileTypes: true })) {
90
+ if (!dirent.isDirectory()) continue;
91
+ const st = lstatOrNull(path.join(base, dirent.name), fsImpl);
92
+ if (st) dirs.push({ name: dirent.name, mtimeMs: st.mtimeMs });
93
+ }
94
+ } catch { /* no hook project directories */ }
95
+ dirs.sort((a, b) => b.mtimeMs - a.mtimeMs || (a.name < b.name ? -1 : 1));
96
+ if (dirs.length > HOOK_DIR_LIMIT) { body.truncated = true; dirs = dirs.slice(0, HOOK_DIR_LIMIT); }
97
+ for (const { name } of dirs) {
98
+ const file = path.join(base, name, 'meta.json');
99
+ const info = lstatOrNull(file, fsImpl);
100
+ if (!info) continue; // a directory with no meta.json is not a record
101
+ let meta ;
102
+ try {
103
+ if (!info.isFile() || info.size > META_MAX_BYTES) throw new Error('unreadable');
104
+ meta = JSON.parse(fsImpl.readFileSync(file, 'utf8'));
105
+ } catch { excluded.unreadable += 1; continue; }
106
+ if (meta === null || typeof meta !== 'object' || typeof meta.cwd !== 'string' || !path.isAbsolute(meta.cwd)) { excluded.unreadable += 1; continue; }
107
+ add(meta.cwd, 'hook-dirs', typeof meta.last_active === 'string' ? Date.parse(meta.last_active) : -Infinity);
108
+ }
109
+ }
110
+
111
+ const temp = [...(tempRoots ?? [realOrNull(os.tmpdir(), fsImpl) ?? os.tmpdir(), ...FIXED_TEMP_ROOTS])];
112
+ const isTemp = (p ) => temp.some((root) => under(p, root));
113
+
114
+ /** `git rev-parse` for one directory: its work-tree top and common git dir, or itself when outside git. */
115
+ const probe = (dir ) => {
116
+ let res ;
117
+ try {
118
+ res = exec('git', ['-C', dir, 'rev-parse', '--path-format=absolute', '--show-toplevel', '--git-common-dir'], { encoding: 'utf8', timeout: 10000 });
119
+ } catch (error ) { res = { error }; }
120
+ if (res && res.error && res.error.code === 'ENOENT') return 'git-missing';
121
+ const lines = res && !res.error && res.status === 0 ? String(res.stdout || '').split('\n') : [];
122
+ const top = lines[0] ? realOrNull(lines[0], fsImpl) : null;
123
+ const common = lines[1] ? realOrNull(lines[1], fsImpl) : null;
124
+ if (top !== null && common !== null) return { top, common };
125
+ const self = realOrNull(dir, fsImpl) ?? dir;
126
+ return { top: self, common: self };
127
+ };
128
+
129
+
130
+ const groups = new Map ();
131
+ let probes = 0;
132
+ for (const [dir, candidate] of candidates) {
133
+ const info = lstatOrNull(dir, fsImpl);
134
+ if (!info || info.isSymbolicLink() || !info.isDirectory()) { excluded.missing += 1; continue; }
135
+ const real = realOrNull(dir, fsImpl);
136
+ if (real === null) { excluded.missing += 1; continue; }
137
+ if (isTemp(real)) { excluded.temp += 1; continue; }
138
+ if (probes >= PROBE_LIMIT) { body.truncated = true; continue; }
139
+ probes += 1;
140
+ const found = probe(dir);
141
+ if (found === 'git-missing') return { ok: false, error: 'cannot-list-projects' };
142
+ const doflow = lstatOrNull(path.join(found.top, '.doflow'), fsImpl);
143
+ if (!doflow || !doflow.isDirectory()) { excluded.notDoflow += 1; continue; }
144
+ if (isTemp(found.top)) { excluded.temp += 1; continue; }
145
+ const due = lstatOrNull(path.join(found.top, '.doflow', 'state', 'upkeep-due'), fsImpl);
146
+ const g = groups.get(found.common) ?? { common: found.common, tops: new Set (), sources: new Set (), lastMs: -Infinity };
147
+ g.tops.add(found.top);
148
+ for (const s of candidate.sources) g.sources.add(s);
149
+ g.lastMs = Math.max(g.lastMs, candidate.activeMs, due && due.isFile() ? due.mtimeMs : -Infinity);
150
+ groups.set(found.common, g);
151
+ }
152
+
153
+ const here = groups.size > 0 ? probe(cwd) : null;
154
+ if (here === 'git-missing') return { ok: false, error: 'cannot-list-projects' };
155
+
156
+ const projects = [...groups.values()].map((g) => {
157
+ const first = [...g.tops].sort()[0];
158
+ const listed = listCheckouts({ cwd: first, exec, fsImpl });
159
+ const real = (p ) => (typeof p === 'string' ? realOrNull(p, fsImpl) : null);
160
+ const main = (listed.ok && real(listed.main)) || first;
161
+ const count = listed.ok ? listed.entries.filter((e ) => !e.sandbox).length : g.tops.size;
162
+ return {
163
+ projectKey: cwdHash(main),
164
+ root: main,
165
+ label: label(path.basename(main)),
166
+ current: here !== null && g.common === here.common,
167
+ checkouts: Math.max(count, 1),
168
+ sources: ['upkeep-roots', 'hook-dirs'].filter((s) => g.sources.has(s)),
169
+ lastRecordAt: iso(g.lastMs),
170
+ };
171
+ });
172
+ const time = (p ) => (p.lastRecordAt === null ? -Infinity : Date.parse(p.lastRecordAt));
173
+ projects.sort((a, b) => Number(b.current) - Number(a.current) || time(b) - time(a) || (a.projectKey < b.projectKey ? -1 : 1));
174
+ body.projects = projects;
175
+ return { ok: true, body };
176
+ }
177
+
178
+ module.exports = { listProjects };
@@ -0,0 +1,383 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * `fleet --action tree` (093 IC-020): one project's agent hierarchy as nodes and edges, built from
5
+ * records that already exist and written to nowhere. Sources: IC-018 a sessions (hook observations
6
+ * joined to the session directory), the harnesses' own sub-agent records read through V.4's readers
7
+ * (`header`, and the metadata mode for the last record time), task attempts by run/lineage, worker runs
8
+ * (caller, `childOf`, binding, end, a bounded process check) and the dispatch `primary` snapshot.
9
+ * Every edge names the record it comes from; two nodes merge only on a shared key (a runKey, a run id,
10
+ * an agent key), never on a harness or a model. A label is the record's own role text, never invented.
11
+ * A transcript is joined by session id alone (gate-a Q4 A); nothing is written, no path, pid or raw
12
+ * session id is printed. `internal` (not part of the body) carries what V.6 needs per node: the file to open, and
13
+ * `records` (DoFlow's own records of the agent: type, time, task, result) and `holds` (the tasks it is working on).
14
+ * @returns {{ok: false, error: string} | {ok: true, body: Object, internal: Map<string, Object>}}
15
+ */
16
+
17
+ const crypto = require('node:crypto');
18
+ const fs = require('node:fs');
19
+ const os = require('node:os');
20
+ const path = require('node:path');
21
+ const { spawnSync } = require('node:child_process');
22
+ const { cwdHash, stateDir } = require('../context/identity.js');
23
+ const { observedSessions } = require('../context/primary.js');
24
+ const { listCheckouts } = require('../checkouts.js');
25
+ const { readTaskState } = require('../task-state.js');
26
+ const { readRuns } = require('../worker/run-store.js');
27
+ const processTree = require('../worker/process-tree.js');
28
+ const { loadTranscriptRegistry } = require('../transcripts/registry.js');
29
+ const { readJsonFile, label } = require('../transcripts/cursor.js');
30
+ const { isSafeSlug } = require('../task-scope.js');
31
+
32
+ const MAX_SESSIONS = 64;
33
+ const MAX_NODES = 256;
34
+ const MAX_CHECKS = 16;
35
+ const SCAN_FILES = 200;
36
+ const LIST_CAP = 2000;
37
+ const SUBAGENT_FILES = 64;
38
+ const NEEDLE_BYTES = 2 * 1024 * 1024;
39
+ const SESSION_DIR = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
40
+ const RUN_KEY = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
41
+
42
+ const sha16 = (text ) => crypto.createHash('sha256').update(text).digest('hex').slice(0, 16);
43
+ const READERS = {
44
+ 'claude-jsonl': () => require('../transcripts/readers/claude-jsonl.js'),
45
+ 'codex-rollout': () => require('../transcripts/readers/codex-rollout.js'),
46
+ 'kiro-jsonl': () => require('../transcripts/readers/kiro-jsonl.js'),
47
+ 'copilot-events': () => require('../transcripts/readers/copilot-events.js'),
48
+ 'pi-jsonl': () => require('../transcripts/readers/pi-jsonl.js'),
49
+ };
50
+ /** How each harness encodes a cwd into its transcript directory name (research section 3). */
51
+ const ENCODE = {
52
+ claude: (cwd) => cwd.replace(/[^A-Za-z0-9]/g, '-'),
53
+ pi: (cwd) => cwd.replace(/^[\\/]/, '').replace(/[\\/:]/g, '-'),
54
+ };
55
+
56
+ function regularFile(file , fsImpl ) {
57
+ try { return fsImpl.lstatSync(file).isFile(); } catch { return false; }
58
+ }
59
+
60
+ /** Files under `root` matching a glob whose only wildcard is `*` inside one path segment. */
61
+ function expandGlob(root , pattern , fsImpl ) {
62
+ let found = [root];
63
+ for (const seg of pattern.split('/')) {
64
+ const next = [];
65
+ if (!seg.includes('*')) for (const dir of found) next.push(path.join(dir, seg));
66
+ else {
67
+ const re = new RegExp(`^${seg.split('*').map((part) => part.replace(/[.+?^${}()|[\]\\]/g, '\\$&')).join('[^/]*')}$`);
68
+ for (const dir of found) {
69
+ let names = [];
70
+ try { names = fsImpl.readdirSync(dir); } catch { /* not a directory */ }
71
+ for (const name of names.slice(0, LIST_CAP)) if (re.test(name)) next.push(path.join(dir, name));
72
+ }
73
+ }
74
+ found = next.slice(0, LIST_CAP);
75
+ }
76
+ return found.filter((file) => regularFile(file, fsImpl)).sort();
77
+ }
78
+
79
+ /** The registry locations of `entry` for this platform, as `[root, glob]` pairs with the base resolved. */
80
+ function locations(entry , env , home , platform ) {
81
+ const out = [];
82
+ for (const loc of entry.locations) {
83
+ if (loc.os !== platform || loc.status === 'unknown') continue;
84
+ let base = null;
85
+ if (loc.base === 'home') base = home;
86
+ else if (loc.base === 'xdg-data') base = env.XDG_DATA_HOME || path.join(home, '.local', 'share');
87
+ else if (loc.base.startsWith('env:')) base = env[loc.base.slice(4)] || null;
88
+ if (base) out.push([path.join(base, ...loc.segments), loc.glob]);
89
+ }
90
+ return out;
91
+ }
92
+
93
+ /** Does `file` hold `needle` in its first 2 MiB? Only the yes or no leaves this function. */
94
+ function fileHas(file , needle , fsImpl ) {
95
+ try {
96
+ const fd = fsImpl.openSync(file, 'r');
97
+ try {
98
+ const buf = Buffer.alloc(Math.min(NEEDLE_BYTES, fsImpl.fstatSync(fd).size));
99
+ fsImpl.readSync(fd, buf, 0, buf.length, 0);
100
+ return buf.toString('latin1').includes(needle);
101
+ } finally { fsImpl.closeSync(fd); }
102
+ } catch { return false; }
103
+ }
104
+
105
+ function buildTree({
106
+ env = process.env, cwd = process.cwd(), exec = spawnSync, fsImpl = fs, platform = process.platform, home, alive = processTree.alive, now = Date.now,
107
+ } = {}) {
108
+ const observed = observedSessions({ env, cwd, exec, fsImpl, now });
109
+ if (!observed.ok) return { ok: false, error: observed.error };
110
+ const listed = listCheckouts({ cwd, exec, fsImpl });
111
+ const checkouts = listed.ok ? listed.entries.filter((e ) => !e.sandbox).map((e ) => e.path) : [];
112
+ const projectKey = cwdHash(listed.ok ? listed.main : cwd);
113
+ const homeDir = home ?? env.HOME ?? os.homedir();
114
+ const loaded = loadTranscriptRegistry();
115
+ const registry = {};
116
+ for (const entry of loaded.ok ? loaded.registry.entries : []) registry[entry.harness] = entry;
117
+
118
+ const nodes = new Map ();
119
+ const edges = new Map ();
120
+ const internal = new Map ();
121
+ const counts = { unreadable: 0, notChecked: 0 };
122
+ let checks = 0;
123
+
124
+ /** The node for `agentKey`, created once: a second source with the same key adds what the first lacked. */
125
+ const node = (agentKey , fields , intern ) => {
126
+ let n = nodes.get(agentKey);
127
+ if (!n) {
128
+ if (nodes.size >= MAX_NODES) { counts.notChecked += 1; return null; }
129
+ n = {
130
+ agentKey, kind: null, harness: null, model: null, modelBasis: null, label: null, depth: 0, worktreeKey: projectKey,
131
+ startedAt: null, lastRecordAt: null, liveness: 'not-checked', ended: null, transcript: { reader: null, linked: false, reason: 'no-reader' },
132
+ ...fields,
133
+ };
134
+ nodes.set(agentKey, n);
135
+ internal.set(agentKey, { kind: fields.kind, records: [], holds: [] });
136
+ } else {
137
+ for (const [key, value] of Object.entries(fields)) if (n[key] === null) n[key] = value;
138
+ }
139
+ Object.assign(internal.get(agentKey), intern ?? {});
140
+ return n;
141
+ };
142
+ /** One of DoFlow's own records of an agent (`records`) or a task it is working on (`holds`), kept for V.6. */
143
+ const note = (n , list , item ) => { if (n) internal.get(n.agentKey)[list].push(item); };
144
+ const slugOf = (store , name ) => (isSafeSlug(store?.slug) ? store.slug : name.replace(/\.json$/, ''));
145
+ const edge = (from , to , basis ) => {
146
+ if (from !== to) edges.set(`${from}|${to}|${basis}`, { from, to, basis }); // kept only when both ends are nodes, below
147
+ };
148
+ const harnessOf = (id ) => (typeof id !== 'string' ? null : registry[id] ? id : '(other)');
149
+ const transcriptOf = (harness , file ) => {
150
+ const entry = harness ? registry[harness] : null;
151
+ if (!entry) return { reader: null, linked: false, reason: 'no-reader' };
152
+ if (entry.join === 'none') return { reader: null, linked: false, reason: 'not-linked' };
153
+ if (entry.reader === null) return { reader: null, linked: false, reason: 'no-reader' };
154
+ return file ? { reader: entry.reader, linked: true, reason: null } : { reader: entry.reader, linked: false, reason: 'not-found' };
155
+ };
156
+ const lastAt = (harness , file , companion ) => {
157
+ try {
158
+ const out = READERS[registry[harness].reader]().read({ file, companion, mode: 'metadata', maxBytes: 32768, limit: 64 });
159
+ return [...out.events].reverse().find((e ) => e.at)?.at ?? null;
160
+ } catch { return null; }
161
+ };
162
+
163
+ // ---- main sessions (IC-018 a) and the transcript each one's id names ----------------------------------
164
+ const sessions = observed.body.sessions.filter((s ) => s.kind === 'hook');
165
+ counts.notChecked += Math.max(0, sessions.length - MAX_SESSIONS);
166
+ const root = stateDir(env);
167
+ const rawIds = new Map ();
168
+ if (root !== null) {
169
+ try { for (const name of fsImpl.readdirSync(path.join(root, 'sessions')).slice(0, LIST_CAP)) if (SESSION_DIR.test(name)) rawIds.set(sha16(name), name); } catch { /* none */ }
170
+ }
171
+ const scanned = new Map (); // harness -> [{file, companion, header}] of the bounded scan
172
+ const scan = (harness ) => {
173
+ if (scanned.has(harness)) return scanned.get(harness);
174
+ const entry = registry[harness];
175
+ const files = [];
176
+ if (entry && entry.reader) {
177
+ for (const [dir, glob] of locations(entry, env, homeDir, platform)) {
178
+ if (harness === 'pi') for (const c of checkouts) files.push(...expandGlob(dir, glob.replace('{cwdEncoded}', ENCODE.pi(c)), fsImpl));
179
+ else files.push(...expandGlob(dir, glob.replace('{sessionId}', '*').replace('{cwdEncoded}', '*'), fsImpl));
180
+ }
181
+ }
182
+ const newest = harness === 'kiro' || harness === 'pi'
183
+ ? files.map((file) => { try { return { file, t: fsImpl.lstatSync(file).mtimeMs }; } catch { return { file, t: 0 }; } }).sort((a, b) => b.t - a.t).map((x) => x.file)
184
+ : [...files].sort().reverse();
185
+ const out = [];
186
+ for (const file of [...new Set(newest)].slice(0, SCAN_FILES)) {
187
+ try { out.push({ file, header: READERS[entry.reader]().header({ file }) }); } catch { counts.unreadable += 1; }
188
+ }
189
+ scanned.set(harness, out);
190
+ return out;
191
+ };
192
+ const locate = (harness , id ) => {
193
+ const entry = registry[harness];
194
+ if (!entry || !entry.reader || entry.join === 'none' || !SESSION_DIR.test(id)) return null;
195
+ if (harness === 'pi') return scan('pi').find((x) => x.header.sessionId === id)?.file ?? null;
196
+ for (const [dir, glob] of locations(entry, env, homeDir, platform)) {
197
+ const hit = expandGlob(dir, glob.replace('{sessionId}', id).replace('{cwdEncoded}', '*'), fsImpl)[0];
198
+ if (hit) return hit;
199
+ }
200
+ return null;
201
+ };
202
+
203
+ const mains = [];
204
+ for (const s of sessions.slice(0, MAX_SESSIONS)) {
205
+ const harness = harnessOf(s.harness);
206
+ const rawId = rawIds.get(s.sessionKey) ?? null;
207
+ const file = harness && rawId ? locate(harness, rawId) : null;
208
+ const agentKey = sha16(`${harness ?? ''}:${rawId ?? s.sessionKey}`);
209
+ const companion = file && harness === 'kiro' ? file.replace(/\.jsonl$/, '.json') : undefined;
210
+ const n = node(agentKey, {
211
+ kind: 'main-session', harness, model: label(s.model), modelBasis: s.model ? 'hook-observation' : null, worktreeKey: s.worktreeKey,
212
+ startedAt: s.startedAt, lastRecordAt: (file && harness && lastAt(harness, file, companion)) || s.lastHookRecordAt,
213
+ transcript: transcriptOf(harness, file),
214
+ }, { harness, sessionId: rawId, file, companion });
215
+ if (n) {
216
+ mains.push({ n, harness, rawId, file });
217
+ if (s.startedAt) note(n, 'records', { type: 'hook-record', at: s.startedAt, task: null, result: null });
218
+ if (s.lastHookRecordAt && s.lastHookRecordAt !== s.startedAt) note(n, 'records', { type: 'hook-record', at: s.lastHookRecordAt, task: null, result: null });
219
+ }
220
+ }
221
+
222
+ // ---- harness sub-agent records (IC-020 sources), through the readers' header ---------------------------
223
+ const child = (parent , harness , idPart , header , file , companion ) => {
224
+ const agentKey = sha16(`${harness}:${idPart}`);
225
+ const depth = Number.isInteger(header.depth) ? header.depth : parent.depth + 1;
226
+ const n = node(agentKey, {
227
+ kind: 'harness-subagent', harness, model: header.model, modelBasis: header.model ? 'harness-record' : null, label: header.label, depth,
228
+ worktreeKey: parent.worktreeKey, lastRecordAt: file ? lastAt(harness, file, companion) : null, transcript: transcriptOf(harness, file),
229
+ }, { harness, sessionId: header.sessionId, agentId: header.agentId, file, companion });
230
+ if (n) edge(parent.agentKey, agentKey, 'harness-subagent-record');
231
+ };
232
+ for (const { n, harness, rawId, file } of mains) {
233
+ if (!harness || !rawId || !file || !registry[harness]) continue;
234
+ const reader = READERS[registry[harness].reader]();
235
+ try {
236
+ if (harness === 'claude') {
237
+ const dir = path.join(path.dirname(file), path.basename(file, '.jsonl'), 'subagents');
238
+ let names = [];
239
+ try { names = fsImpl.readdirSync(dir).filter((x ) => /^agent-.+\.jsonl$/.test(x)).sort().slice(0, SUBAGENT_FILES); } catch { /* none */ }
240
+ const subs = names.map((name) => {
241
+ const sub = path.join(dir, name);
242
+ const meta = readJsonFile(sub.replace(/\.jsonl$/, '.meta.json')) || {};
243
+ return { sub, header: reader.header({ file: sub }), toolUseId: typeof meta.toolUseId === 'string' ? meta.toolUseId : null };
244
+ });
245
+ const made = new Map ();
246
+ for (const s of subs.sort((a, b) => (a.header.depth ?? 1) - (b.header.depth ?? 1))) {
247
+ const depth = s.header.depth ?? 1;
248
+ // A depth-1 agent was spawned by the main session; deeper ones by the file that holds their tool_use id.
249
+ const candidates = depth <= 1 ? [{ file, node: n }] : made.get(depth - 1) ?? [];
250
+ const parent = depth <= 1 ? candidates[0] : candidates.find((c) => s.toolUseId !== null && fileHas(c.file, s.toolUseId, fsImpl));
251
+ const agentId = s.header.agentId ?? path.basename(s.sub, '.jsonl').replace(/^agent-/, '');
252
+ const agentKey = sha16(`claude:${rawId}:${agentId}`);
253
+ const made1 = node(agentKey, {
254
+ kind: 'harness-subagent', harness, model: s.header.model, modelBasis: s.header.model ? 'harness-record' : null, label: s.header.label, depth,
255
+ worktreeKey: n.worktreeKey, lastRecordAt: lastAt(harness, s.sub), transcript: transcriptOf(harness, s.sub),
256
+ }, { harness, sessionId: rawId, agentId, file: s.sub });
257
+ if (!made1) continue;
258
+ if (parent) edge(parent.node.agentKey, agentKey, 'harness-subagent-record');
259
+ made.set(depth, [...(made.get(depth) ?? []), { file: s.sub, node: made1 }]);
260
+ }
261
+ } else if (harness === 'copilot') {
262
+ for (const c of reader.header({ file }).children) child(n, harness, `${rawId}:${c.agentId}`, { ...c, model: null, sessionId: rawId }, null);
263
+ } else if (harness === 'codex' || harness === 'pi' || harness === 'kiro') {
264
+ // Children name their parent session id in their own header: follow the chain through the bounded scan.
265
+ const parents = new Map ([[rawId, n]]);
266
+ const headers = scan(harness);
267
+ for (let grew = true; grew;) {
268
+ grew = false;
269
+ for (const h of headers) {
270
+ const p = h.header.parentSessionId;
271
+ if (!p || !parents.has(p) || !h.header.sessionId || parents.has(h.header.sessionId)) continue;
272
+ const companion = harness === 'kiro' ? h.file.replace(/\.jsonl$/, '.json') : undefined;
273
+ child(parents.get(p), harness, h.header.sessionId, h.header, h.file, companion);
274
+ const made = nodes.get(sha16(`${harness}:${h.header.sessionId}`));
275
+ if (made) { parents.set(h.header.sessionId, made); grew = true; }
276
+ }
277
+ }
278
+ }
279
+ } catch { counts.unreadable += 1; }
280
+ }
281
+
282
+ // ---- DoFlow records: attempts, worker runs, dispatch --------------------------------------------------
283
+ const readStores = (checkout , dirName , read ) => {
284
+ const dir = path.join(checkout, '.doflow', 'state', dirName);
285
+ let names = [];
286
+ try { names = fsImpl.readdirSync(dir).filter((x ) => x.endsWith('.json')).sort(); } catch { return []; }
287
+ const out = [];
288
+ for (const name of names) {
289
+ try { const data = read(path.join(dir, name)); if (data) out.push([name, data]); } catch { counts.unreadable += 1; }
290
+ }
291
+ return out;
292
+ };
293
+ const runNode = (runKey , extra = {}) => {
294
+ if (!RUN_KEY.test(runKey)) return null;
295
+ return node(sha16(`doflow:${runKey}`), { kind: 'doflow-run', worktreeKey: runKey.split('/')[0], ...extra }, { runKey });
296
+ };
297
+ const attemptRuns = new Map (); // `task#token` -> runKey of the attempt
298
+ for (const checkout of checkouts) {
299
+ for (const [name, store] of readStores(checkout, 'task-attempts', (file) => readTaskState(fsImpl, file))) {
300
+ for (const [taskId, entry] of Object.entries(store.tasks ?? {}) ) {
301
+ for (const a of Array.isArray(entry?.attempts) ? entry.attempts : []) {
302
+ if (typeof a.runKey !== 'string') continue;
303
+ attemptRuns.set(`${taskId}#${a.token}`, a.runKey);
304
+ const at = [a.endedAt, a.finishedAt, a.startedAt].find((v) => typeof v === 'string') ?? null;
305
+ const n = runNode(a.runKey, { startedAt: typeof a.startedAt === 'string' ? a.startedAt : null, lastRecordAt: at });
306
+ if (n) {
307
+ if (typeof a.owner === 'string' && (n.label === null || (at ?? '') >= (n.lastRecordAt ?? ''))) n.label = label(a.owner);
308
+ if (at && (n.lastRecordAt === null || at > n.lastRecordAt)) n.lastRecordAt = at;
309
+ if (typeof a.startedAt === 'string' && (n.startedAt === null || a.startedAt < n.startedAt)) n.startedAt = a.startedAt;
310
+ const task = label(taskId);
311
+ if (typeof a.startedAt === 'string') note(n, 'records', { type: 'attempt-claim', at: a.startedAt, task, result: null });
312
+ const endedAt = [a.finishedAt, a.endedAt].find((v) => typeof v === 'string');
313
+ if (endedAt) note(n, 'records', { type: 'attempt-end', at: endedAt, task, result: typeof a.result === 'string' ? label(a.result) : null });
314
+ if (a.state === 'active') note(n, 'holds', { slug: slugOf(store, name), task: taskId });
315
+ }
316
+ }
317
+ }
318
+ }
319
+ }
320
+
321
+ const runs = new Map ();
322
+ for (const checkout of checkouts) {
323
+ for (const [name, byId] of readStores(checkout, 'worker-runs', (file) => readRuns(file, fsImpl))) {
324
+ for (const [runId, run] of Object.entries(byId) ) if (!runs.has(runId)) runs.set(runId, { run, checkout, slug: slugOf(null, name) });
325
+ }
326
+ }
327
+ const ordered = [...runs.entries()].sort((a, b) => String(b[1].run.startedAt ?? b[1].run.createdAt ?? '').localeCompare(String(a[1].run.startedAt ?? a[1].run.createdAt ?? '')) || (a[0] < b[0] ? -1 : 1));
328
+ const workerKey = (runId ) => sha16(`worker:${runId}`);
329
+ for (const [runId, { run, checkout, slug }] of ordered) {
330
+ const caller = run.caller && typeof run.caller.runKey === 'string' ? runNode(run.caller.runKey) : null;
331
+ const harness = harnessOf(run.harness);
332
+ const isChild = typeof run.childOf === 'string' && run.childOf !== '';
333
+ let liveness = 'not-checked';
334
+ if (run.phase !== 'ended') {
335
+ if (checks < MAX_CHECKS) {
336
+ checks += 1;
337
+ const seen = [alive(run.supervisor, { platform }), alive(run.worker, { platform })];
338
+ liveness = seen.includes(true) ? 'process-seen' : seen.every((v) => v === false) ? 'process-gone' : 'not-checked';
339
+ } else counts.notChecked += 1;
340
+ }
341
+ const n = node(workerKey(runId), {
342
+ kind: isChild ? 'child-worker-run' : 'worker-run', harness, model: label(run.binding?.model), modelBasis: run.binding?.model ? 'worker-binding' : null,
343
+ depth: isChild ? 2 : caller ? 1 : 0, worktreeKey: (run.caller?.runKey ?? '').split('/')[0] || cwdHash(checkout),
344
+ startedAt: run.startedAt ?? run.createdAt ?? null, lastRecordAt: run.end?.at ?? run.startedAt ?? run.createdAt ?? null, liveness,
345
+ ended: run.end && typeof run.end.at === 'string' ? { at: run.end.at, result: typeof run.end.state === 'string' ? run.end.state : null } : null,
346
+ transcript: transcriptOf(harness, null),
347
+ }, { harness, runId });
348
+ if (!n) continue;
349
+ const task = label(run.task);
350
+ const startedAt = run.startedAt ?? run.createdAt;
351
+ if (typeof startedAt === 'string') note(n, 'records', { type: 'worker-run-start', at: startedAt, task, result: null });
352
+ if (run.end && typeof run.end.at === 'string') note(n, 'records', { type: 'worker-run-end', at: run.end.at, task, result: typeof run.end.state === 'string' ? label(run.end.state) : null });
353
+ if (run.phase !== 'ended' && typeof run.task === 'string') note(n, 'holds', { slug, task: run.task });
354
+ if (isChild) edge(workerKey(run.childOf), n.agentKey, 'worker-childOf');
355
+ else if (caller) edge(caller.agentKey, n.agentKey, 'worker-caller');
356
+ else {
357
+ const viaAttempt = attemptRuns.get(`${run.task}#${run.token}`);
358
+ const owner = viaAttempt ? runNode(viaAttempt) : null;
359
+ if (owner) edge(owner.agentKey, n.agentKey, 'attempt-runKey');
360
+ }
361
+ }
362
+ for (const checkout of checkouts) {
363
+ for (const [, store] of readStores(checkout, 'dispatch', (file) => readTaskState(fsImpl, file))) {
364
+ for (const d of Object.values(store.dispatches ?? {}) ) {
365
+ if (typeof d?.parentRunKey !== 'string') continue;
366
+ const snap = d.primary && typeof d.primary === 'object' ? d.primary : null;
367
+ const owner = runNode(d.parentRunKey, snap ? { harness: harnessOf(snap.harness), model: label(typeof snap.model === 'string' ? snap.model : null), modelBasis: snap.model ? 'dispatch-snapshot' : null } : {});
368
+ const launched = [...runs.entries()].find(([, r]) => r.run.task === d.task && r.run.token === d.token && !r.run.childOf);
369
+ if (owner && launched) edge(owner.agentKey, workerKey(launched[0]), 'dispatch-claim');
370
+ }
371
+ }
372
+ }
373
+
374
+ // ---- what leaves: no internal field, kept nodes' edges only ---------------------------------------------
375
+ const keep = [...nodes.values()];
376
+ const body = {
377
+ v: 1, projectKey, nodes: keep, edges: [...edges.values()].filter((e ) => nodes.has(e.from) && nodes.has(e.to)),
378
+ limits: { nodes: MAX_NODES, notChecked: counts.notChecked }, unreadable: counts.unreadable,
379
+ };
380
+ return { ok: true, body, internal };
381
+ }
382
+
383
+ module.exports = { buildTree, READERS, MAX_SESSIONS, MAX_NODES, MAX_CHECKS };