@khoavu882/doflow 2.0.0-beta.5 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (494) hide show
  1. package/README.md +16 -16
  2. package/bin/doflow.js +22 -12
  3. package/core/.antigravity-plugin/plugin.json +1 -2
  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 +4 -1
  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-compact.sh +23 -27
  15. package/core/harnesses/claude/hooks/pre-implement-gate.sh +4 -1
  16. package/core/harnesses/claude/hooks/session-end.sh +23 -10
  17. package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
  18. package/core/harnesses/claude/settings/settings.json +26 -9
  19. package/core/harnesses/claude/statusline/doflow-statusline.sh +1 -0
  20. package/core/harnesses/codex/agents/core-implementer.toml +1 -0
  21. package/core/harnesses/codex/agents/delivery-lead.toml +10 -0
  22. package/core/harnesses/codex/agents/quality-guardian.toml +1 -1
  23. package/core/harnesses/codex/agents/research-writer.toml +1 -1
  24. package/core/harnesses/codex/agents/reviewer.toml +10 -0
  25. package/core/harnesses/codex/agents/spec-analyst.toml +1 -1
  26. package/core/harnesses/codex/agents/system-architect.toml +1 -1
  27. package/core/harnesses/codex/hooks/hooks.json +0 -10
  28. package/core/harnesses/codex/hooks/lib.sh +44 -25
  29. package/core/harnesses/codex/hooks/mcp-tool-guard.sh +4 -1
  30. package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
  31. package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
  32. package/core/harnesses/codex/hooks/pre-implement-gate.sh +4 -1
  33. package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
  34. package/core/harnesses/codex/hooks/subagent-audit.sh +1 -0
  35. package/core/harnesses/copilot/hooks/doflow-lib.sh +94 -0
  36. package/core/harnesses/copilot/hooks/doflow-mcp-tool-guard.sh +9 -0
  37. package/core/harnesses/copilot/hooks/doflow-pre-implement-gate.sh +9 -0
  38. package/core/harnesses/copilot/hooks/doflow-session-start.sh +9 -0
  39. package/core/harnesses/copilot/hooks/doflow-stop-check.sh +9 -0
  40. package/core/harnesses/copilot/hooks/doflow-subagent-start.sh +9 -0
  41. package/core/harnesses/copilot/hooks/doflow-subagent-stop.sh +9 -0
  42. package/core/harnesses/copilot/hooks/doflow.json +51 -0
  43. package/core/harnesses/kiro/agents/doflow.json +1 -11
  44. package/core/harnesses/kiro/hooks/doflow.json +0 -10
  45. package/core/harnesses/kiro/hooks/lib.sh +26 -6
  46. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +8 -3
  47. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +8 -3
  48. package/core/harnesses/kiro/hooks/session-start.sh +1 -0
  49. package/core/harnesses/kiro/hooks/stop-check.sh +1 -0
  50. package/core/harnesses/pi/extensions/doflow-guard.js +11 -2
  51. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +1 -2
  52. package/core/harnesses/shared/hooks/adapters/antigravity.js +1 -1
  53. package/core/harnesses/shared/hooks/bridge/bridge.sh +126 -23
  54. package/core/harnesses/shared/hooks/bridge/envelope.jq +87 -18
  55. package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
  56. package/core/harnesses/shared/hooks/bridge/respond.sh +16 -2
  57. package/core/harnesses/shared/hooks/policies/capture-failure.sh +49 -16
  58. package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
  59. package/core/harnesses/shared/hooks/policies/contract.sh +3 -0
  60. package/core/harnesses/shared/hooks/policies/deny-json.sh +20 -7
  61. package/core/harnesses/shared/hooks/policies/emission-rules.sh +37 -0
  62. package/core/harnesses/shared/hooks/policies/emission.jq +209 -0
  63. package/core/harnesses/shared/hooks/policies/emission.sh +383 -0
  64. package/core/harnesses/shared/hooks/policies/identity.sh +177 -0
  65. package/core/harnesses/shared/hooks/policies/lib.sh +63 -40
  66. package/core/harnesses/shared/hooks/policies/mcp-policy.conf +1 -1
  67. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +7 -8
  68. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +132 -57
  69. package/core/harnesses/shared/hooks/policies/session-context.sh +30 -1
  70. package/core/harnesses/shared/hooks/policies/stop-check.sh +27 -15
  71. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +6 -3
  72. package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
  73. package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +60 -155
  74. package/core/harnesses/shared/hooks/stream-hook-runner.js +58 -20
  75. package/core/harnesses/shared/locator/doflow-run +2 -3
  76. package/core/registry/advice.json +3 -3
  77. package/core/registry/assets.json +55 -82
  78. package/core/registry/capabilities.json +12 -0
  79. package/core/registry/contracts.json +58 -31
  80. package/core/registry/contribution-points.json +35 -0
  81. package/core/registry/gates.json +32 -0
  82. package/core/registry/guidance-caps.json +40 -0
  83. package/core/registry/harnesses.json +1445 -366
  84. package/core/registry/intake.json +53 -0
  85. package/core/registry/lifecycle.json +599 -996
  86. package/core/registry/mcp.json +5 -1
  87. package/core/registry/models.json +7 -1
  88. package/core/registry/package-templates/README.md +19 -0
  89. package/core/registry/package-templates/agent/files/agent.md +9 -0
  90. package/core/registry/package-templates/agent/template.json +4 -0
  91. package/core/registry/package-templates/capability-provider/files/provider.json +9 -0
  92. package/core/registry/package-templates/capability-provider/template.json +11 -0
  93. package/core/registry/package-templates/debate-preset/files/preset.json +26 -0
  94. package/core/registry/package-templates/debate-preset/template.json +4 -0
  95. package/core/registry/package-templates/intake-category/files/category.json +8 -0
  96. package/core/registry/package-templates/intake-category/template.json +4 -0
  97. package/core/registry/package-templates/review-preset/files/preset.json +30 -0
  98. package/core/registry/package-templates/review-preset/template.json +4 -0
  99. package/core/registry/package-templates/skill/files/SKILL.md +8 -0
  100. package/core/registry/package-templates/skill/template.json +4 -0
  101. package/core/registry/package-templates/worker/files/worker.json +92 -0
  102. package/core/registry/package-templates/worker/template.json +11 -0
  103. package/core/registry/packages/doflow/doflow-package.json +15 -0
  104. package/core/registry/packages/doflow/presets/debate-3.json +26 -0
  105. package/core/registry/protocols.json +61 -0
  106. package/core/registry/routes.json +17 -0
  107. package/core/registry/rules.json +431 -0
  108. package/core/registry/settings.json +272 -0
  109. package/core/registry/storage.json +687 -31
  110. package/core/registry/telemetry.json +236 -0
  111. package/core/registry/transcripts.json +142 -0
  112. package/core/registry/verification.json +76 -6
  113. package/core/registry/workflows.json +55 -0
  114. package/core/shared/agent-specs/core-implementer.md +3 -0
  115. package/core/shared/agent-specs/delivery-lead.md +68 -0
  116. package/core/shared/agent-specs/quality-guardian.md +5 -1
  117. package/core/shared/agent-specs/research-writer.md +10 -3
  118. package/core/shared/agent-specs/reviewer.md +53 -0
  119. package/core/shared/agent-specs/spec-analyst.md +5 -6
  120. package/core/shared/agent-specs/system-architect.md +5 -1
  121. package/core/shared/guidance/DOFLOW_CORE.md +1 -5
  122. package/core/shared/guidance/modes/MODE_DeepResearch.md +1 -1
  123. package/core/shared/guidance/modes/MODE_Orchestration.md +4 -3
  124. package/core/shared/guidance/modes/MODE_Task_Management.md +14 -14
  125. package/core/shared/guidance/pointers/codex.md +1 -4
  126. package/core/shared/guidance/references/ARTIFACT_FORMAT.md +30 -16
  127. package/core/shared/guidance/references/ARTIFACT_VOICE.md +3 -2
  128. package/core/shared/guidance/references/CONSTITUTION_BASE.md +17 -15
  129. package/core/shared/guidance/references/DIAGRAM_GUIDE.md +1 -1
  130. package/core/shared/guidance/references/DOFLOW_CHAIN.md +17 -19
  131. package/core/shared/guidance/references/EVIDENCE_LEDGER.md +7 -8
  132. package/core/shared/guidance/references/MODEL_SELECTION.md +5 -3
  133. package/core/shared/guidance/references/ORCHESTRATION.md +3 -2
  134. package/core/shared/guidance/references/WORKFLOW_HANDOFF.md +19 -1
  135. package/core/shared/guidance/rules/RULES.md +45 -0
  136. package/core/shared/scripts/doflow/bash/do-args.sh +37 -0
  137. package/core/shared/scripts/doflow/bash/do-exec-paths.sh +4 -0
  138. package/core/shared/scripts/doflow/bash/do-git-state.sh +15 -4
  139. package/core/shared/scripts/doflow/bash/do-parallel-check.sh +4 -0
  140. package/core/shared/scripts/doflow/bash/do-paths.sh +40 -2
  141. package/core/shared/scripts/doflow/bash/do-prereqs.sh +2 -1
  142. package/core/shared/scripts/doflow/bash/do-review-package.sh +4 -0
  143. package/core/shared/scripts/doflow/bash/do-task-brief.sh +58 -16
  144. package/core/shared/scripts/doflow/bash/render-audit.sh +26 -2
  145. package/core/shared/scripts/doflow/bash/render-diagrams.sh +1 -0
  146. package/core/shared/scripts/doflow/bash/render-puml.sh +2 -1
  147. package/core/shared/scripts/doflow/bash/validate-artifacts.sh +49 -5
  148. package/core/shared/scripts/doflow/bin/doflow-run +152 -34
  149. package/core/shared/skills/do/SKILL.md +14 -12
  150. package/core/shared/skills/do/references/advice_calls.md +26 -0
  151. package/core/shared/skills/do/references/pm_routing.md +1 -2
  152. package/core/shared/skills/do/references/program_mode.md +31 -0
  153. package/core/shared/skills/do-brainstorm/SKILL.md +45 -97
  154. package/core/shared/skills/do-brainstorm/references/handoff_branches.md +36 -0
  155. package/core/shared/skills/do-brainstorm/references/intent_seed.md +21 -0
  156. package/core/shared/skills/do-brainstorm/references/round_log.md +17 -0
  157. package/core/shared/skills/do-code-review/SKILL.md +48 -148
  158. package/core/shared/skills/do-code-review/content-types/markdown.md +24 -2
  159. package/core/shared/skills/do-code-review/references/tools.md +90 -0
  160. package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +549 -7
  161. package/core/shared/skills/do-design/SKILL.md +23 -20
  162. package/core/shared/skills/do-diagnose/SKILL.md +18 -6
  163. package/core/shared/skills/do-document/SKILL.md +12 -1
  164. package/core/shared/skills/do-document/references/deep_research.md +1 -2
  165. package/core/shared/skills/do-document/references/embedded-research.md +19 -7
  166. package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
  167. package/core/shared/skills/do-execute-plan/SKILL.md +19 -60
  168. package/core/shared/skills/do-execute-plan/references/handoff_record.md +7 -2
  169. package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +13 -0
  170. package/core/shared/skills/do-execute-plan/references/readiness_gate.md +26 -1
  171. package/core/shared/skills/do-execute-plan/references/run_position.md +16 -2
  172. package/core/shared/skills/do-execute-plan/references/scaffold.md +17 -9
  173. package/core/shared/skills/do-execute-plan/references/state_checkpoint.md +11 -0
  174. package/core/shared/skills/do-execute-plan/references/task_attempts.md +78 -24
  175. package/core/shared/skills/do-flow/SKILL.md +10 -11
  176. package/core/shared/skills/do-flow/references/task_classes.md +5 -5
  177. package/core/shared/skills/do-git/SKILL.md +4 -0
  178. package/core/shared/skills/do-implement/SKILL.md +13 -14
  179. package/core/shared/skills/do-plan/SKILL.md +34 -20
  180. package/core/shared/skills/do-test/SKILL.md +7 -4
  181. package/core/shared/templates/doflow/constitution-template.md +9 -3
  182. package/core/shared/templates/doflow/data-model-template.md +1 -1
  183. package/core/shared/templates/doflow/design-template.md +113 -16
  184. package/core/shared/templates/doflow/plan-template.md +11 -10
  185. package/core/shared/templates/doflow/question-log-template.md +14 -2
  186. package/core/shared/templates/doflow/requirement-template.md +1 -1
  187. package/core/shared/workflows/do-flow-chain.md +2 -0
  188. package/core/worker-evidence/antigravity/checks.json +14 -0
  189. package/core/worker-evidence/codex/2026-10-18-cli-check.json +107 -0
  190. package/core/worker-evidence/codex/2026-10-18-live-run-edit.json +73 -0
  191. package/core/worker-evidence/codex/2026-10-18-live-run-read-only.json +62 -0
  192. package/core/worker-evidence/codex/2026-10-24-cli-check.json +126 -0
  193. package/core/worker-evidence/codex/checks.json +38 -0
  194. package/package.json +10 -6
  195. package/src/adapters/antigravity/index.js +143 -66
  196. package/src/adapters/claude/index.js +46 -5
  197. package/src/adapters/codex/agents.js +25 -15
  198. package/src/adapters/codex/hooks.js +39 -3
  199. package/src/adapters/codex/index.js +61 -13
  200. package/src/adapters/codex/native-projection.js +6 -2
  201. package/src/adapters/copilot/index.js +58 -23
  202. package/src/adapters/copy-tree.js +291 -46
  203. package/src/adapters/guidance-manifest.js +71 -0
  204. package/src/adapters/guidance-pointer.js +19 -0
  205. package/src/adapters/hook-commands.js +6 -10
  206. package/src/adapters/index.js +1 -0
  207. package/src/adapters/kiro/index.js +23 -12
  208. package/src/adapters/opencode/index.js +8 -8
  209. package/src/adapters/pi/hooks.js +2 -1
  210. package/src/adapters/pi/index.js +14 -7
  211. package/src/adapters/resolve-source.js +26 -2
  212. package/src/cli/commands/install.js +2 -1
  213. package/src/cli/commands/reconcile.js +53 -9
  214. package/src/cli/commands/remove.js +2 -2
  215. package/src/cli/commands/rollback.js +6 -3
  216. package/src/cli/commands/self-update.js +18 -9
  217. package/src/cli/commands/status.js +22 -3
  218. package/src/cli/commands/ui.js +99 -0
  219. package/src/cli/commands/update.js +2 -1
  220. package/src/cli/index.js +337 -32
  221. package/src/cli/runtime-commands.js +220 -60
  222. package/src/cli/shared.js +0 -2
  223. package/src/helper/git.js +1 -1
  224. package/src/helper/guardrails.js +1 -1
  225. package/src/helper/marker-merge.js +31 -13
  226. package/src/helper/package-ids.js +37 -0
  227. package/src/helper/semver.js +94 -0
  228. package/src/install/backup.js +105 -15
  229. package/src/install/context.js +1 -1
  230. package/src/install/targets.js +31 -16
  231. package/src/lifecycle/coverage.js +4 -16
  232. package/src/lifecycle/hook-tier.js +160 -0
  233. package/src/lifecycle/index.js +412 -78
  234. package/src/lifecycle/retired-harnesses.js +318 -0
  235. package/src/lifecycle/view.js +35 -15
  236. package/src/registry/index.js +551 -26
  237. package/src/registry/worker-ids.js +45 -0
  238. package/src/release/build-package.js +0 -3
  239. package/src/release/docs-blocks.js +472 -0
  240. package/src/release/preflight.js +425 -0
  241. package/src/release/rehearse.js +401 -0
  242. package/src/release/release-workflow.js +733 -0
  243. package/src/release/sync-plugin-versions.js +43 -7
  244. package/src/runtime/advice/bench-gate.js +25 -0
  245. package/src/runtime/advice/cli.js +59 -16
  246. package/src/runtime/advice/client.js +32 -9
  247. package/src/runtime/advice/config.js +98 -133
  248. package/src/runtime/advice/doctor.js +1 -1
  249. package/src/runtime/advice/elevation.js +97 -0
  250. package/src/runtime/advice/endpoint-guard.js +9 -3
  251. package/src/runtime/advice/mode.js +37 -0
  252. package/src/runtime/advice/record.js +18 -5
  253. package/src/runtime/advice/registry.js +4 -1
  254. package/src/runtime/advice/shadow-config.js +229 -0
  255. package/src/runtime/advice/shadow-dispatcher.js +276 -0
  256. package/src/runtime/brief-rules.js +81 -0
  257. package/src/runtime/brief-tools.js +111 -34
  258. package/src/runtime/candidate.js +20 -11
  259. package/src/runtime/capabilities/executor.js +450 -0
  260. package/src/runtime/capabilities/loopback.js +41 -0
  261. package/src/runtime/capabilities/sanitizer.js +88 -0
  262. package/src/runtime/capability-router.js +135 -3
  263. package/src/runtime/checkouts.js +73 -11
  264. package/src/runtime/claims.js +13 -7
  265. package/src/runtime/cli.js +196 -19
  266. package/src/runtime/config-home.js +42 -0
  267. package/src/runtime/context/arbitrate.js +767 -0
  268. package/src/runtime/context/delivery-store.js +4 -6
  269. package/src/runtime/context/emission-rules.js +118 -0
  270. package/src/runtime/context/identity.js +227 -0
  271. package/src/runtime/context/pointer.js +67 -12
  272. package/src/runtime/context/primary-command.js +117 -0
  273. package/src/runtime/context/primary.js +344 -0
  274. package/src/runtime/context/summary-store.js +655 -0
  275. package/src/runtime/context-pack.js +158 -79
  276. package/src/runtime/decision-register.js +13 -4
  277. package/src/runtime/dispatch/admission.js +392 -0
  278. package/src/runtime/dispatch/attempt-end.js +274 -0
  279. package/src/runtime/dispatch/command.js +628 -0
  280. package/src/runtime/dispatch/controls.js +904 -0
  281. package/src/runtime/dispatch/envelope.js +276 -0
  282. package/src/runtime/dispatch/grants.js +310 -0
  283. package/src/runtime/dispatch/integrate.js +617 -0
  284. package/src/runtime/dispatch/loop.js +1835 -0
  285. package/src/runtime/dispatch/store.js +352 -0
  286. package/src/runtime/dispatch/verify-run.js +201 -0
  287. package/src/runtime/evidence-ledger.js +52 -7
  288. package/src/runtime/failure/capture.js +3 -1
  289. package/src/runtime/failure/cli.js +25 -9
  290. package/src/runtime/failure/home.js +57 -30
  291. package/src/runtime/failure/store.js +7 -2
  292. package/src/runtime/feature-pointer.js +174 -0
  293. package/src/runtime/feature-resolve.js +13 -2
  294. package/src/runtime/fleet/command.js +77 -0
  295. package/src/runtime/fleet/projects.js +178 -0
  296. package/src/runtime/fleet/tree.js +383 -0
  297. package/src/runtime/freshness.js +33 -3
  298. package/src/runtime/git-memo.js +46 -0
  299. package/src/runtime/guidance/ablation.js +427 -0
  300. package/src/runtime/health.js +624 -18
  301. package/src/runtime/history-compactor.js +13 -3
  302. package/src/runtime/implementation-gate.js +281 -26
  303. package/src/runtime/intake/engine.js +677 -0
  304. package/src/runtime/intake/index.js +136 -0
  305. package/src/runtime/intake/manifest.js +140 -0
  306. package/src/runtime/intake/taxonomy.js +206 -0
  307. package/src/runtime/inventory/coverage.js +1 -1
  308. package/src/runtime/inventory/index.js +1 -1
  309. package/src/runtime/inventory/precedence.js +2 -2
  310. package/src/runtime/inventory/siblings.js +6 -4
  311. package/src/runtime/knowledge/code-corpus.js +277 -0
  312. package/src/runtime/knowledge/code-lexical.js +130 -0
  313. package/src/runtime/knowledge/code-retrieval.js +307 -0
  314. package/src/runtime/knowledge/code-validation.js +106 -0
  315. package/src/runtime/knowledge/gp3-gate.js +74 -0
  316. package/src/runtime/knowledge/graph-adapter.js +288 -0
  317. package/src/runtime/knowledge/graph-fallback.js +394 -0
  318. package/src/runtime/knowledge/hybrid.js +136 -8
  319. package/src/runtime/knowledge/retrieval.js +34 -1
  320. package/src/runtime/knowledge/rtk-compress.js +77 -0
  321. package/src/runtime/knowledge/semble-bridge.py +106 -0
  322. package/src/runtime/knowledge/semble-lifecycle.js +114 -0
  323. package/src/runtime/knowledge/semble-provider.js +365 -0
  324. package/src/runtime/lifecycle/cli.js +88 -10
  325. package/src/runtime/lifecycle/event-store.js +8 -6
  326. package/src/runtime/lifecycle/followup.js +38 -7
  327. package/src/runtime/lifecycle/overview.js +26 -2
  328. package/src/runtime/lifecycle/report-store.js +8 -10
  329. package/src/runtime/lifecycle/retention.js +95 -31
  330. package/src/runtime/lifecycle/root.js +9 -1
  331. package/src/runtime/lifecycle/status.js +34 -17
  332. package/src/runtime/lifecycle/store-upkeep.js +56 -7
  333. package/src/runtime/mask.js +134 -7
  334. package/src/runtime/mcp-consultations.js +40 -6
  335. package/src/runtime/model-router.js +79 -7
  336. package/src/runtime/models/catalogue.js +191 -0
  337. package/src/runtime/models/command.js +153 -0
  338. package/src/runtime/off-words.js +12 -0
  339. package/src/runtime/outcome.js +2 -2
  340. package/src/runtime/packages/command.js +1009 -0
  341. package/src/runtime/packages/contributions.js +116 -0
  342. package/src/runtime/packages/digest.js +132 -0
  343. package/src/runtime/packages/fetch.js +182 -0
  344. package/src/runtime/packages/grants.js +192 -0
  345. package/src/runtime/packages/index-file.js +134 -0
  346. package/src/runtime/packages/instruction.js +90 -0
  347. package/src/runtime/packages/manifest.js +331 -0
  348. package/src/runtime/packages/points.js +487 -0
  349. package/src/runtime/packages/programs.js +216 -0
  350. package/src/runtime/packages/scaffold.js +277 -0
  351. package/src/runtime/packages/source.js +90 -0
  352. package/src/runtime/packages/state.js +164 -0
  353. package/src/runtime/packages/store.js +449 -0
  354. package/src/runtime/packages/tar.js +106 -0
  355. package/src/runtime/packages/view.js +406 -0
  356. package/src/runtime/plan-check.js +59 -0
  357. package/src/runtime/plan-content.js +324 -0
  358. package/src/runtime/policy-init.js +118 -0
  359. package/src/runtime/profiles/doctor.js +96 -0
  360. package/src/runtime/profiles/resolve.js +244 -0
  361. package/src/runtime/profiles/rollback.js +254 -0
  362. package/src/runtime/profiles/sampler.js +41 -0
  363. package/src/runtime/readiness-record.js +16 -11
  364. package/src/runtime/readiness.js +46 -5
  365. package/src/runtime/research-request.js +50 -25
  366. package/src/runtime/retrieval-plan.js +10 -30
  367. package/src/runtime/retrieval-record.js +75 -5
  368. package/src/runtime/review/command.js +246 -0
  369. package/src/runtime/review/dominance.js +68 -0
  370. package/src/runtime/review/findings.js +434 -0
  371. package/src/runtime/review/identity.js +255 -0
  372. package/src/runtime/review/protocol-command.js +109 -0
  373. package/src/runtime/review/protocols.js +405 -0
  374. package/src/runtime/review/resolve-protocol.js +302 -0
  375. package/src/runtime/review/start.js +530 -0
  376. package/src/runtime/roadmap/attendance.js +137 -0
  377. package/src/runtime/roadmap/facts.js +479 -0
  378. package/src/runtime/roadmap/gates.js +273 -0
  379. package/src/runtime/roadmap/loop.js +1719 -0
  380. package/src/runtime/roadmap/merge.js +390 -0
  381. package/src/runtime/roadmap/rows.js +1122 -0
  382. package/src/runtime/roadmap/seed.js +1063 -0
  383. package/src/runtime/roadmap/worktree-cleanup.js +147 -0
  384. package/src/runtime/rules-report.js +336 -0
  385. package/src/runtime/scaffold/artifacts.js +60 -9
  386. package/src/runtime/scaffold/generate.js +21 -9
  387. package/src/runtime/settings/autopilot.js +106 -0
  388. package/src/runtime/settings/cli.js +280 -0
  389. package/src/runtime/settings/config-write.js +314 -0
  390. package/src/runtime/settings/files.js +271 -0
  391. package/src/runtime/settings/legacy.js +154 -0
  392. package/src/runtime/settings/lexical.js +56 -0
  393. package/src/runtime/settings/prepared-run.js +269 -0
  394. package/src/runtime/settings/project-layers.js +118 -0
  395. package/src/runtime/settings/refs.js +288 -0
  396. package/src/runtime/settings/registry.js +553 -0
  397. package/src/runtime/settings/resolve.js +713 -0
  398. package/src/runtime/settings/shared.js +37 -0
  399. package/src/runtime/settings/worker-billing.js +107 -0
  400. package/src/runtime/settings/write.js +269 -0
  401. package/src/runtime/slot-command.js +78 -0
  402. package/src/runtime/slots.js +671 -0
  403. package/src/runtime/source-version.js +1 -0
  404. package/src/runtime/state-root.js +217 -0
  405. package/src/runtime/storage/cli.js +12 -5
  406. package/src/runtime/storage/engine.js +9 -2
  407. package/src/runtime/storage/registry.js +14 -1
  408. package/src/runtime/storage/roots.js +66 -16
  409. package/src/runtime/storage/rules.js +82 -10
  410. package/src/runtime/storage/upkeep.js +10 -2
  411. package/src/runtime/storage/work-status.js +245 -26
  412. package/src/runtime/task-attempts.js +589 -69
  413. package/src/runtime/task-classifier.js +85 -14
  414. package/src/runtime/task-graph.js +373 -0
  415. package/src/runtime/task-run.js +778 -0
  416. package/src/runtime/task-scope.js +176 -55
  417. package/src/runtime/task-sources.js +147 -0
  418. package/src/runtime/task-state.js +373 -64
  419. package/src/runtime/team/admission.js +301 -0
  420. package/src/runtime/team/envelope.js +324 -0
  421. package/src/runtime/team/review-guard.js +156 -0
  422. package/src/runtime/trace/analyze.js +427 -0
  423. package/src/runtime/trace/baseline.js +770 -0
  424. package/src/runtime/trace/ledger.js +63 -140
  425. package/src/runtime/trace/otlp-export.js +568 -0
  426. package/src/runtime/trace/otlp-map.js +481 -0
  427. package/src/runtime/trace/spans.js +745 -0
  428. package/src/runtime/trace/telemetry.js +225 -0
  429. package/src/runtime/trace/views.js +1 -0
  430. package/src/runtime/transcripts/command.js +98 -0
  431. package/src/runtime/transcripts/cursor.js +326 -0
  432. package/src/runtime/transcripts/read.js +126 -0
  433. package/src/runtime/transcripts/readers/claude-jsonl.js +74 -0
  434. package/src/runtime/transcripts/readers/codex-rollout.js +70 -0
  435. package/src/runtime/transcripts/readers/copilot-events.js +90 -0
  436. package/src/runtime/transcripts/readers/kiro-jsonl.js +77 -0
  437. package/src/runtime/transcripts/readers/pi-jsonl.js +59 -0
  438. package/src/runtime/transcripts/registry.js +161 -0
  439. package/src/runtime/transcripts/stats.js +161 -0
  440. package/src/runtime/verb-view.js +267 -0
  441. package/src/runtime/verification/check-key.js +628 -0
  442. package/src/runtime/verification/contract-runner.js +546 -11
  443. package/src/runtime/verification/engine.js +499 -54
  444. package/src/runtime/verification/impact.js +617 -0
  445. package/src/runtime/verification/inflight.js +205 -0
  446. package/src/runtime/verification/registry.js +176 -0
  447. package/src/runtime/verification/reuse.js +184 -0
  448. package/src/runtime/verification/telemetry-emit.js +254 -0
  449. package/src/runtime/verification/test-selection.js +67 -7
  450. package/src/runtime/worker/active.js +90 -0
  451. package/src/runtime/worker/cancel.js +222 -0
  452. package/src/runtime/worker/catalogue.js +122 -0
  453. package/src/runtime/worker/command.js +677 -0
  454. package/src/runtime/worker/evidence.js +288 -0
  455. package/src/runtime/worker/launch.js +256 -0
  456. package/src/runtime/worker/models.js +137 -0
  457. package/src/runtime/worker/parsers.js +218 -0
  458. package/src/runtime/worker/probes/index.js +706 -0
  459. package/src/runtime/worker/process-tree.js +415 -0
  460. package/src/runtime/worker/quota.js +43 -0
  461. package/src/runtime/worker/readiness.js +713 -0
  462. package/src/runtime/worker/run-store.js +166 -0
  463. package/src/runtime/worker/supervisor.js +541 -0
  464. package/src/runtime/worker/worktree-check.js +252 -0
  465. package/src/runtime/workflow-engine.js +5 -1
  466. package/src/runtime/workflow-orchestrator.js +610 -67
  467. package/src/runtime/worktree.js +149 -22
  468. package/src/state/index.js +1 -1
  469. package/src/state/lockfile.js +64 -10
  470. package/src/state/run-lock.js +1 -1
  471. package/types/node-commonjs.d.ts +106 -7
  472. package/core/harnesses/claude/hooks/pre-bash-guard.sh +0 -10
  473. package/core/harnesses/codex/hooks/pre-bash-guard.sh +0 -10
  474. package/core/harnesses/gemini/hooks/hooks.json +0 -77
  475. package/core/harnesses/gemini/hooks/lib.sh +0 -302
  476. package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +0 -10
  477. package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
  478. package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -10
  479. package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
  480. package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -10
  481. package/core/harnesses/gemini/hooks/session-end.impl.sh +0 -89
  482. package/core/harnesses/gemini/hooks/session-end.sh +0 -5
  483. package/core/harnesses/gemini/hooks/session-start.sh +0 -7
  484. package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
  485. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +0 -24
  486. package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +0 -91
  487. package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +0 -378
  488. package/core/shared/guidance/FLAGS.md +0 -25
  489. package/core/shared/guidance/rules/RULE_01_SAFETY.md +0 -25
  490. package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +0 -23
  491. package/core/shared/guidance/rules/RULE_03_QUALITY.md +0 -14
  492. package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +0 -46
  493. package/src/adapters/gemini/hooks.js +0 -251
  494. package/src/adapters/gemini/index.js +0 -429
@@ -95,7 +95,9 @@ function doflowFrame(error ) {
95
95
  for (const line of stack.split('\n')) {
96
96
  const match = /^\s*at (?:async )?(?:(.+?) \()?(.+?):\d+:\d+\)?$/.exec(line);
97
97
  if (!match) continue;
98
- const file = match[2].replace(/^file:\/\//, '');
98
+ const file = process.platform === 'win32' && /^file:\/\/\/[A-Za-z]:/.test(match[2])
99
+ ? path.normalize(match[2].slice('file:///'.length))
100
+ : match[2].replace(/^file:\/\//, '');
99
101
  if (!file.startsWith(root) || file.includes(`${path.sep}node_modules${path.sep}`)) continue;
100
102
  const fn = (match[1] || '').replace(/^new /, '').split('.').pop() .replace(/\s.*$/, '') || '<anonymous>';
101
103
  return `${path.relative(REPO_ROOT, file).split(path.sep).join('/')}:${fn}`;
@@ -12,9 +12,11 @@
12
12
  */
13
13
 
14
14
  const fs = require('node:fs');
15
+ const path = require('node:path');
15
16
  const { finishRuntime, usageError } = require('../cli-result.js');
16
17
  const { maskLine, printSafe } = require('../mask.js');
17
- const { failureHome, captureSwitch, sentinelPath } = require('./home.js');
18
+ const { failureHome, captureSwitchDetailed } = require('./home.js');
19
+ const { writeLayer, backupLayerFile } = require('../settings/write.js');
18
20
  const { rotateIfDue } = require('./capture.js');
19
21
  const store = require('./store.js');
20
22
  const { projectRoot } = require('../lifecycle/root.js');
@@ -119,19 +121,33 @@ function settleFailure({ fp, as, reason, cwd, env = process.env, now = new Date(
119
121
 
120
122
  // ── capture ────────────────────────────────────────────────────────────────────────────────────
121
123
 
122
- function captureState({ set, env = process.env, now = new Date() } = {}) {
124
+ function captureState({ set, env = process.env } = {}) {
123
125
  if (set !== undefined && set !== 'on' && set !== 'off') throw new FailureUsageError(`--set must be on or off (got '${set}')`);
124
126
  const home = failureHome(env);
125
- if (home !== null && set === 'off') {
126
- fs.mkdirSync(home, { recursive: true, mode: 0o700 });
127
- fs.writeFileSync(sentinelPath(home), `${now.toISOString()}\n`, { mode: 0o600 });
128
- } else if (home !== null && set === 'on') {
129
- fs.rmSync(sentinelPath(home), { force: true });
127
+ let settingsFile = null;
128
+ if (home !== null && set !== undefined) {
129
+ const file = path.join(path.dirname(home), 'settings.json');
130
+ settingsFile = file;
131
+ const written = writeLayer({ file, set: { 'failure.capture': set }, scrubKeys: set === 'on' ? ['failure.capture'] : [] });
132
+ if (!written.ok && set === 'off' && written.code === 'unparseable') {
133
+ const backup = backupLayerFile(file);
134
+ if (!backup.ok) return refusal('capture', 'write-failed', backup.detail);
135
+ console.error(printSafe(`note: malformed settings preserved at ${backup.file}`));
136
+ const recovered = writeLayer({ file, set: { 'failure.capture': set }, force: true });
137
+ if (!recovered.ok) return refusal('capture', 'write-failed', recovered.detail);
138
+ } else if (!written.ok) {
139
+ return refusal('capture', 'write-failed', written.detail);
140
+ }
130
141
  }
131
- const state = captureSwitch(home, env);
142
+ const state = captureSwitchDetailed(home, env);
143
+ if (settingsFile && state.decidedBy === 'core/registry/settings.json') state.decidedBy = settingsFile;
144
+ for (const note of state.deprecatedNotes) console.error(printSafe(`note: ${note}`));
132
145
  // With no failure home nothing is captured, whatever the switch says.
133
146
  const result = { ok: true, action: 'capture', effective: home === null ? 'off' : state.effective, sentinel: state.sentinel, env: state.env };
134
147
  if (home === null) result.home = null;
148
+ else if (set !== undefined) {
149
+ result.decidedBy = state.decidedBy;
150
+ }
135
151
  return result;
136
152
  }
137
153
 
@@ -147,7 +163,7 @@ function lines(result ) {
147
163
  return [...out, ...result.next.map((n ) => `next: ${n}`)];
148
164
  }
149
165
  if (result.action === 'settle') return [`${result.fp} settled as ${result.as}${result.followup ? `, follow-up ${result.followup.id}` : ''}`];
150
- return [`capture ${result.effective}${result.sentinel ? ' (switched off on this machine)' : ''}${result.env ? ` (DOFLOW_FAILURE_CAPTURE=${result.env})` : ''}`];
166
+ return [`capture ${result.effective}${result.sentinel ? ' (switched off on this machine)' : ''}${result.env ? ` (DOFLOW_FAILURE_CAPTURE=${result.env})` : ''}${result.decidedBy ? ` (decided by ${result.decidedBy})` : ''}`];
151
167
  }
152
168
 
153
169
  /**
@@ -4,32 +4,50 @@
4
4
 
5
5
  /**
6
6
  * Where DoFlow's own failures are kept on this machine, and whether capture is on (IC-011, IC-014).
7
- * One folder per machine, shared by every project (DEC-007). Node and the bash writers resolve the
8
- * home the same way, so a failure captured by either lands in the one list: `XDG_CONFIG_HOME` when it
9
- * is set, non-empty and absolute, else `$HOME/.config` when `HOME` is set and absolute. Node reads the
10
- * `HOME` variable and not `os.homedir()`, so both sides agree. A relative or missing base means
11
- * there is no home, and capture is skipped.
7
+ * One folder per machine, shared by every project (DEC-007), `failures` beside `settings.json` in the
8
+ * config home (`../config-home.js`, shared with the bash writers so a failure captured by either lands
9
+ * in the one list). No config home means no failure home, and capture is skipped.
10
+ *
11
+ * The switch (IC-005), in order: no home skips; the environment can only turn capture off; the sentinel
12
+ * `<home>/off` file turns it off; a `failure.capture` value in `<config home>/settings.json` other than
13
+ * "on" turns it off (the lexical rule, `../settings/lexical.js`). Otherwise it is on.
12
14
  */
13
15
 
14
- const fs = require('node:fs');
15
16
  const path = require('node:path');
16
-
17
- const OFF_VALUES = new Set(['off', '0', 'false', 'no']);
17
+ const { configHome } = require('../config-home.js');
18
+ const { isOffWord } = require('../off-words.js');
18
19
 
19
20
  /** @param {Object} [env] defaults to process.env
20
21
  * @returns {string|null} the failure home, an absolute path, or null when none can be resolved */
21
22
  function failureHome(env = process.env) {
22
- const xdg = env.XDG_CONFIG_HOME;
23
- if (typeof xdg === 'string' && xdg !== '') {
24
- return path.isAbsolute(xdg) ? path.join(xdg, 'doflow', 'failures') : null;
25
- }
26
- const home = env.HOME;
27
- if (typeof home === 'string' && path.isAbsolute(home)) return path.join(home, '.config', 'doflow', 'failures');
28
- return null;
23
+ const { dir } = configHome(env);
24
+ return dir === null ? null : path.join(dir, 'failures');
29
25
  }
30
26
 
31
27
  const sentinelPath = (home ) => path.join(home, 'off');
32
28
  const eventsPath = (home ) => path.join(home, 'events.jsonl');
29
+ /** `settings.json` sits in the config home, one level above the failure home. */
30
+ const settingsPath = (home ) => path.join(path.dirname(home), 'settings.json');
31
+
32
+ /** Resolves the machine-wide setting and returns the file and sentinel parts of the old result shape. */
33
+ function resolvedCapture(home , env ) {
34
+ const { loadSettings } = require('../settings/resolve.js');
35
+ const resolverEnv = { ...env, XDG_CONFIG_HOME: path.dirname(path.dirname(home)) };
36
+ const view = loadSettings({ global: true, env: resolverEnv });
37
+ const result = view.get('failure.capture', 'global');
38
+ const sentinel = result.candidates.some((candidate ) =>
39
+ candidate.legacy && candidate.source === sentinelPath(home) && candidate.value === 'off');
40
+ const file = result.candidates.find((candidate ) =>
41
+ !candidate.legacy && candidate.layer === 'user' && candidate.source === settingsPath(home));
42
+ const settings = file?.value === 'on' || file?.value === 'off' ? file.value : null;
43
+ return {
44
+ effective: result.value === 'off' ? 'off' : 'on', sentinel, settings,
45
+ decidedBy: result.origin.source,
46
+ deprecatedNotes: view.notes.filter((note ) => note.code === 'deprecated'
47
+ && note.key === 'failure.capture' && result.origin.legacy && note.source === result.origin.source)
48
+ .map((note ) => note.text),
49
+ };
50
+ }
33
51
 
34
52
  /** The value of `DOFLOW_FAILURE_CAPTURE` when it is set to anything, else null. */
35
53
  function envSetting(env = process.env) {
@@ -38,27 +56,36 @@ function envSetting(env = process.env) {
38
56
  }
39
57
 
40
58
  /**
41
- * The capture switch (IC-014). The sentinel file wins over everything and the environment can only
42
- * turn capture off, never on over a sentinel. One `stat`, no folder created.
59
+ * The capture switch (IC-014, IC-005). The resolver applies the global sentinel, lexical settings
60
+ * file value and environment layers; any off value wins. The resolver reads without creating a folder.
43
61
  * @param {string|null} home
44
62
  * @param {Object} [env]
45
- * @returns {{effective: 'on'|'off', sentinel: boolean, env: string|null}}
63
+ * @returns {{effective: 'on'|'off', sentinel: boolean, env: string|null, settings: 'on'|'off'|null}}
64
+ * `settings` is the lexical read of settings.json: null when the file holds no `failure.capture`
46
65
  */
47
- function captureSwitch(home , env = process.env) {
48
- let sentinel = false;
49
- if (home) {
50
- try { sentinel = fs.statSync(sentinelPath(home)).isFile(); } catch { sentinel = false; }
51
- }
66
+ function captureSwitchDetailed(home , env = process.env) {
52
67
  const setting = envSetting(env);
53
- const envOff = setting !== null && OFF_VALUES.has(setting.trim().toLowerCase());
54
- return { effective: sentinel || envOff ? 'off' : 'on', sentinel, env: setting };
68
+ if (!home) {
69
+ const envOff = isOffWord(setting);
70
+ return { effective: envOff ? 'off' : 'on', sentinel: false, env: setting, settings: null, decidedBy: null, deprecatedNotes: [] };
71
+ }
72
+ try {
73
+ const resolved = resolvedCapture(home, env);
74
+ return { ...resolved, env: setting };
75
+ } catch {
76
+ return { effective: 'off', sentinel: false, env: setting, settings: null, decidedBy: null, deprecatedNotes: [] };
77
+ }
55
78
  }
56
79
 
57
- /** The hot-path check a writer makes: the environment first, then one `stat` of the sentinel. */
80
+ /** The public switch shape, with the resolver's deciding source added by IC-005 P3. */
81
+ function captureSwitch(home , env = process.env) {
82
+ const state = captureSwitchDetailed(home, env);
83
+ return { effective: state.effective, sentinel: state.sentinel, env: state.env, settings: state.settings, decidedBy: state.decidedBy };
84
+ }
85
+
86
+ /** The hot-path check a writer makes; the shared resolver owns sentinel and settings-file reads. */
58
87
  function captureIsOff(home , env = process.env) {
59
- const setting = envSetting(env);
60
- if (setting !== null && OFF_VALUES.has(setting.trim().toLowerCase())) return true;
61
- try { return fs.statSync(sentinelPath(home)).isFile(); } catch { return false; }
88
+ try { return resolvedCapture(home, env).effective === 'off'; } catch { return true; }
62
89
  }
63
90
 
64
- module.exports = { failureHome, captureSwitch, captureIsOff, sentinelPath, eventsPath, envSetting };
91
+ module.exports = { failureHome, captureSwitch, captureSwitchDetailed, captureIsOff, sentinelPath, eventsPath, envSetting };
@@ -20,6 +20,7 @@ const path = require('node:path');
20
20
  const crypto = require('node:crypto');
21
21
  const { failureHome, captureSwitch, eventsPath } = require('./home.js');
22
22
  const { printSafe } = require('../mask.js');
23
+ const { appendRegular } = require('./capture.js');
23
24
 
24
25
  const ROTATED = /^events-\d{8}T\d{6}Z-\d+\.jsonl$/;
25
26
  const SETTLEMENTS = 'settlements.jsonl';
@@ -203,14 +204,18 @@ function loadEntries({ env = process.env } = {}) {
203
204
  }
204
205
 
205
206
  /**
206
- * Appends one settlement line (IC-013). Only `failure --action settle` calls this.
207
+ * Appends one settlement line (IC-013). Only `failure --action settle` calls this. The line goes to a
208
+ * regular file only (`appendRegular`, shared with the capture writer): a FIFO at `settlements.jsonl`
209
+ * would block the open for ever, and a symlink or device is not a place to write.
207
210
  * @param {string} home
208
211
  * @param {{fp: string, as: string, reason?: string|null, followup?: string|null, now?: Date}} settlement
212
+ * @throws when the file cannot be appended to; it is left as it was
209
213
  */
210
214
  function appendSettlement(home , { fp, as, reason = null, followup = null, now = new Date() } ) {
211
215
  fs.mkdirSync(home, { recursive: true, mode: 0o700 });
212
216
  const line = JSON.stringify({ v: 1, at: now.toISOString(), fp, as, reason, followup });
213
- fs.appendFileSync(settlementsPath(home), `${line}\n`, { flag: 'a', mode: 0o600 });
217
+ const written = appendRegular(settlementsPath(home), `${line}\n`);
218
+ if (!written) throw new Error(`${settlementsPath(home)} is not a regular file that can be appended to (a FIFO, a link, a device or a file without write access), so the settlement was not recorded. Nothing was written.`);
214
219
  }
215
220
 
216
221
  const DOFLOW_PACKAGE = '@khoavu882/doflow';
@@ -0,0 +1,174 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ /**
6
+ * The feature pointer (row 135): `<storeRoot>/.doflow/state/feature/<id>.json` names the feature one
7
+ * checkout of the repository is working on, so a fresh session reads it instead of inferring the
8
+ * feature from the branch alone. Only the runtime writes it, when a feature is started or switched
9
+ * (`lifecycle --action init`, an `orchestrate` start or catch-up that begins a `feature` run) and
10
+ * clears it when the feature finishes. The branch stays the other witness: `describe` reports a
11
+ * disagreement instead of trusting either side.
12
+ *
13
+ * One file per checkout, named by the first 16 hex of the sha256 of the checkout's realpath git
14
+ * toplevel, so two worktrees on two features never share a file: there is no read-modify-write
15
+ * across checkouts to lose an update in, and each write is a temp file renamed into place.
16
+ */
17
+
18
+ const fs = require('node:fs');
19
+ const path = require('node:path');
20
+ const crypto = require('node:crypto');
21
+ const { execFileSync } = require('node:child_process');
22
+ const { resolveActiveFeature } = require('./feature-resolve.js');
23
+
24
+ const SCHEMA = 1;
25
+ const POINTER_DIR = path.join('.doflow', 'state', 'feature');
26
+ const POINTER_NAME = /^[0-9a-f]{16}\.json$/;
27
+
28
+
29
+
30
+ const checkoutId = (checkout ) => crypto.createHash('sha256').update(checkout).digest('hex').slice(0, 16);
31
+ /** The pointer file of the checkout whose realpath git toplevel is `checkout`. */
32
+ const pointerFile = (storeRoot , checkout ) => path.join(storeRoot, POINTER_DIR, `${checkoutId(checkout)}.json`);
33
+
34
+ function git(cwd , args ) {
35
+ try { return execFileSync('git', args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim(); } catch { return null; }
36
+ }
37
+
38
+ /** The checkout `cwd` is in (its realpath git toplevel) and its current branch, or null outside a repository. */
39
+ function checkoutOf(cwd ) {
40
+ const top = git(cwd, ['rev-parse', '--show-toplevel']);
41
+ if (!top) return null;
42
+ let key = top;
43
+ try { key = fs.realpathSync(top); } catch { /* keep git's spelling */ }
44
+ return { key, branch: git(cwd, ['rev-parse', '--abbrev-ref', 'HEAD']) || null };
45
+ }
46
+
47
+ /** One pointer file: `{status:'none'}`, `{status:'ok', data}` or `{status:'unreadable', reason}`. */
48
+ function readEntry(file ) {
49
+ let text ;
50
+ try { text = fs.readFileSync(file, 'utf8'); } catch (error ) {
51
+ return error.code === 'ENOENT' ? { status: 'none' } : { status: 'unreadable', reason: error.message };
52
+ }
53
+ try {
54
+ const data = JSON.parse(text);
55
+ if (!data || typeof data !== 'object' || data.schema !== SCHEMA || typeof data.slug !== 'string') {
56
+ return { status: 'unreadable', reason: `${path.basename(file)} is not a schema ${SCHEMA} pointer file` };
57
+ }
58
+ return { status: 'ok', data };
59
+ } catch (error ) {
60
+ return { status: 'unreadable', reason: `${path.basename(file)} is not valid JSON: ${error.message}` };
61
+ }
62
+ }
63
+
64
+ function writeEntry(file , entry ) {
65
+ fs.mkdirSync(path.dirname(file), { recursive: true });
66
+ const temp = `${file}.${process.pid}.tmp`;
67
+ try {
68
+ fs.writeFileSync(temp, `${JSON.stringify(entry, null, 2)}\n`);
69
+ fs.renameSync(temp, file);
70
+ } finally {
71
+ try { fs.unlinkSync(temp); } catch { /* renamed away, or never written */ }
72
+ }
73
+ }
74
+
75
+ function removeEntry(file ) {
76
+ try { fs.unlinkSync(file); } catch (error ) { if (error.code !== 'ENOENT') throw error; }
77
+ }
78
+
79
+ /** Runs a pointer write; a failure is one stderr note and never reaches the verb's exit status. */
80
+ function bestEffort(what , fn ) {
81
+ try { fn(); } catch (error ) { console.error(`doflow: note: feature pointer not ${what}: ${error.message}`); }
82
+ }
83
+
84
+ /** The artifact folder of `slug`, repo-relative: the resolver's answer, else the default folder when it exists. */
85
+ function artifactDirOf(checkout , storeRoot , slug ) {
86
+ const found = resolveActiveFeature({ projectRoot: checkout, slug });
87
+ if (!found.error && found.paths && typeof found.paths.feature_dir === 'string') return found.paths.feature_dir;
88
+ const rel = path.join('agent-docs', 'doflow', slug);
89
+ for (const root of [checkout, storeRoot]) {
90
+ try { if (fs.statSync(path.join(root, rel)).isDirectory()) return rel; } catch { /* absent */ }
91
+ }
92
+ return null;
93
+ }
94
+
95
+ /** Sets the calling checkout's entry to `slug` (a later start of another slug overwrites it, and so does an unreadable file). */
96
+ function recordFeature({ storeRoot, cwd, slug, taskId = null } ) {
97
+ if (!slug) return;
98
+ bestEffort('written', () => {
99
+ const here = checkoutOf(cwd);
100
+ if (!here) return;
101
+ writeEntry(pointerFile(storeRoot, here.key), {
102
+ schema: SCHEMA, checkout: here.key, slug, artifactDir: artifactDirOf(here.key, storeRoot, slug), branch: here.branch, taskId, updatedAt: new Date().toISOString(),
103
+ });
104
+ });
105
+ }
106
+
107
+ /** Removes the calling checkout's entry when it names `slug`; an unreadable file is left alone. */
108
+ function clearCheckout({ storeRoot, cwd, slug } ) {
109
+ if (!slug) return;
110
+ bestEffort('cleared', () => {
111
+ const here = checkoutOf(cwd);
112
+ if (!here) return;
113
+ const file = pointerFile(storeRoot, here.key);
114
+ const read = readEntry(file);
115
+ if (read.status === 'ok' && read.data .slug === slug) removeEntry(file);
116
+ });
117
+ }
118
+
119
+ /** Removes every entry naming `slug`, whichever checkout holds it (the feature is finished). */
120
+ function clearSlug({ storeRoot, slug } ) {
121
+ if (!slug) return;
122
+ bestEffort('cleared', () => {
123
+ const dir = path.join(storeRoot, POINTER_DIR);
124
+ let names ;
125
+ try { names = fs.readdirSync(dir); } catch (error ) { if (error.code === 'ENOENT') return; throw error; }
126
+ for (const name of names.filter((n) => POINTER_NAME.test(n))) {
127
+ const read = readEntry(path.join(dir, name));
128
+ if (read.status === 'ok' && read.data .slug === slug) removeEntry(path.join(dir, name));
129
+ }
130
+ });
131
+ }
132
+
133
+ /**
134
+ * What the calling checkout's pointer says against what its branch says.
135
+ * `state`: none (no entry), agree, mismatch (branch or branch-derived slug differs) or unreadable.
136
+ * A mismatch names neither side as the answer: `slug` is null and both are reported.
137
+ */
138
+ function describeActive({ storeRoot, cwd } ) {
139
+ const here = checkoutOf(cwd);
140
+ const found = resolveActiveFeature({ projectRoot: here ? here.key : cwd });
141
+ const derived = !found.error && found.paths && typeof found.paths.feature_slug === 'string' ? found.paths.feature_slug : null;
142
+ const current = { branch: here ? here.branch : null, slug: derived };
143
+ const base = { slug: null, artifactDir: null, branch: null, pointer: null, current };
144
+ if (!here) return { ...base, state: 'none' };
145
+ const file = pointerFile(storeRoot, here.key);
146
+ const read = readEntry(file);
147
+ if (read.status === 'unreadable') return { ...base, state: 'unreadable', reason: read.reason, file: path.relative(storeRoot, file) };
148
+ if (read.status === 'none') return { ...base, state: 'none' };
149
+ const entry = read.data ;
150
+ const mismatch = entry.branch !== current.branch || (derived !== null && derived !== entry.slug);
151
+ if (mismatch) return { ...base, state: 'mismatch', pointer: entry };
152
+ return { state: 'agree', slug: entry.slug, artifactDir: entry.artifactDir ?? null, branch: entry.branch ?? null, pointer: entry, current };
153
+ }
154
+
155
+ /**
156
+ * The calling checkout's pointer entry when its recorded branch is the checkout's branch, else null
157
+ * (no repository, no entry, unreadable, or a branch switch since). Unlike `describeActive` it does
158
+ * not run the feature resolver: a caller on a hot path (the context-pack verb, issue #9) needs the
159
+ * runtime's own record, not a second derivation from the branch.
160
+ */
161
+ function activeEntry({ storeRoot, cwd, checkout } ) {
162
+ // `checkout` is the caller's already-resolved checkout (its feature resolution): no git child is spawned for it.
163
+ let here ;
164
+ if (checkout?.repoRoot) {
165
+ let key = checkout.repoRoot;
166
+ try { key = fs.realpathSync(key); } catch { /* keep the resolver's spelling */ }
167
+ here = { key, branch: checkout.branch || null };
168
+ } else here = checkoutOf(cwd);
169
+ if (!here) return null;
170
+ const read = readEntry(pointerFile(storeRoot, here.key));
171
+ return read.status === 'ok' && read.data .branch === here.branch ? read.data : null;
172
+ }
173
+
174
+ module.exports = { recordFeature, clearCheckout, clearSlug, describeActive, activeEntry, pointerFile, POINTER_DIR };
@@ -27,7 +27,7 @@ const PATHS_HELPER = resolveBashHelper('do-paths.sh');
27
27
  * @param {Object} options
28
28
  * @param {string} options.projectRoot working directory the resolution is relative to
29
29
  * @param {string|null} [options.slug] explicit feature override, passed straight through
30
- * @returns {{repoRoot:string, featureDir:string, paths:Object}|{error:string, message:string}}
30
+ * @returns {{repoRoot:string, featureRoot:string, featureDir:string, featureFolder:string, paths:Object}|{error:string, message:string}}
31
31
  * `paths` is the resolver's own parsed JSON (repo-root-relative artifact paths and has_* flags),
32
32
  * so a caller needs no second resolver call.
33
33
  */
@@ -73,7 +73,18 @@ function resolveActiveFeature({ projectRoot, slug = null }
73
73
  if (!paths.repo_root || !paths.feature_dir) {
74
74
  return { error: 'no-active-feature', message: 'the resolver named no active feature directory to scaffold' };
75
75
  }
76
- return { repoRoot: paths.repo_root, featureDir: path.resolve(paths.repo_root, paths.feature_dir), paths };
76
+ // `featureDir` is the folder in the checkout asked about (what every caller used before 115 and
77
+ // still tests for "this checkout has the folder"). `featureRoot` is the checkout that holds the
78
+ // folder (the main checkout when a linked worktree has none of its own) and `featureFolder` the
79
+ // folder there: the `decision` verb reads and writes its register through those.
80
+ const featureRoot = paths.feature_root || paths.repo_root;
81
+ return {
82
+ repoRoot: paths.repo_root,
83
+ featureRoot,
84
+ featureDir: path.resolve(paths.repo_root, paths.feature_dir),
85
+ featureFolder: path.resolve(featureRoot, paths.feature_dir),
86
+ paths,
87
+ };
77
88
  }
78
89
 
79
90
  module.exports = { resolveActiveFeature, PATHS_HELPER };
@@ -0,0 +1,77 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * `doflow fleet` (093 V8): the cross-project, read-only Fleet verb. `--action projects` prints
5
+ * `listProjects` (IC-019); `--action tree`, run with `cwd` = one project, prints that project's agent
6
+ * hierarchy (IC-020, `buildTree`). It writes nothing: no state, no config home, no upkeep.
7
+ */
8
+
9
+ const { finishRuntime, captureCaught } = require('../cli-result.js');
10
+
11
+ const ALLOWED_FLAGS = new Set(['--action', '--json']);
12
+
13
+ function dash(value ) {
14
+ return value === null || value === undefined ? '-' : String(value);
15
+ }
16
+
17
+ /** The text form: one line per project, no path (`root` is for the Console server only). */
18
+ function projectLines(body ) {
19
+ const { excluded } = body;
20
+ return [
21
+ `store: ${body.store}`,
22
+ ...body.projects.map((p ) => `project ${p.projectKey} ${p.label}${p.current ? ' current' : ''} checkouts ${p.checkouts} sources ${p.sources.join(',')} last-record ${dash(p.lastRecordAt)}`),
23
+ `excluded: missing ${excluded.missing} temp ${excluded.temp} not-doflow ${excluded.notDoflow} unreadable ${excluded.unreadable}`,
24
+ ...(body.truncated ? ['truncated: true'] : []),
25
+ ];
26
+ }
27
+
28
+ /** The text form of `tree`: one line per node and per edge; keys, labels and bases only. */
29
+ function treeLines(body ) {
30
+ return [
31
+ `project ${body.projectKey}`,
32
+ ...body.nodes.map((n ) => `node ${n.agentKey} ${n.kind} harness ${dash(n.harness)} model ${dash(n.model)} label ${dash(n.label)} depth ${n.depth} liveness ${n.liveness} transcript ${n.transcript.linked ? 'linked' : n.transcript.reason}`),
33
+ ...body.edges.map((e ) => `edge ${e.from} -> ${e.to} ${e.basis}`),
34
+ `limits: nodes ${body.limits.nodes} not-checked ${body.limits.notChecked} unreadable ${body.unreadable}`,
35
+ ];
36
+ }
37
+
38
+ function handleFleetCommand({ action, json = false, flags = [], env = process.env, cwd = process.cwd(), exec, tempRoots, alive, out = (line ) => console.log(line) } = {}) {
39
+ const usage = (message ) => {
40
+ out(json ? JSON.stringify({ ok: false, error: 'usage', message }) : `doflow fleet: ${message}`);
41
+ return finishRuntime(2);
42
+ };
43
+ if (action !== 'projects' && action !== 'tree') return usage('action must be projects or tree');
44
+ const extra = (flags ).map((flag) => flag.split('=')[0]).find((flag) => !ALLOWED_FLAGS.has(flag));
45
+ if (extra !== undefined) return usage(`unsupported flag ${extra}; only --action and --json apply`);
46
+
47
+ try {
48
+ if (action === 'tree') {
49
+ const { buildTree } = require('./tree.js');
50
+ const built = buildTree({ env, cwd, ...(exec ? { exec } : {}), ...(alive ? { alive } : {}) });
51
+ if (!built.ok) {
52
+ if (json) out(JSON.stringify({ v: 1, error: built.error }));
53
+ else console.error('[ERROR] fleet: cannot list checkouts');
54
+ return finishRuntime(1);
55
+ }
56
+ if (json) out(JSON.stringify(built.body));
57
+ else for (const line of treeLines(built.body)) out(line);
58
+ return finishRuntime(0);
59
+ }
60
+ const { listProjects } = require('./projects.js');
61
+ const listed = listProjects({ env, cwd, ...(exec ? { exec } : {}), ...(tempRoots ? { tempRoots } : {}) });
62
+ if (!listed.ok) {
63
+ if (json) out(JSON.stringify({ v: 1, error: listed.error }));
64
+ else console.error('[ERROR] fleet: cannot list projects');
65
+ return finishRuntime(1);
66
+ }
67
+ if (json) out(JSON.stringify(listed.body));
68
+ else for (const line of projectLines(listed.body)) out(line);
69
+ return finishRuntime(0);
70
+ } catch (error ) {
71
+ captureCaught(error, 'fleet', 1);
72
+ console.error(`[ERROR] fleet: ${error?.message || String(error)}`);
73
+ return finishRuntime(1);
74
+ }
75
+ }
76
+
77
+ module.exports = { handleFleetCommand };