@khoavu882/doflow 2.0.0-beta.5 → 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 (471) hide show
  1. package/README.md +12 -11
  2. package/bin/doflow.js +21 -11
  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/permission-audit.sh +3 -0
  9. package/core/harnesses/claude/hooks/post-compact.sh +35 -32
  10. package/core/harnesses/claude/hooks/post-edit-lint.sh +2 -0
  11. package/core/harnesses/claude/hooks/post-shell-observe.sh +48 -0
  12. package/core/harnesses/claude/hooks/post-tool-failure.sh +3 -0
  13. package/core/harnesses/claude/hooks/pre-compact.sh +23 -27
  14. package/core/harnesses/claude/hooks/session-end.sh +23 -10
  15. package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
  16. package/core/harnesses/claude/settings/settings.json +18 -0
  17. package/core/harnesses/claude/statusline/doflow-statusline.sh +1 -0
  18. package/core/harnesses/codex/agents/core-implementer.toml +1 -0
  19. package/core/harnesses/codex/agents/delivery-lead.toml +10 -0
  20. package/core/harnesses/codex/agents/quality-guardian.toml +1 -1
  21. package/core/harnesses/codex/agents/research-writer.toml +1 -1
  22. package/core/harnesses/codex/agents/reviewer.toml +10 -0
  23. package/core/harnesses/codex/agents/spec-analyst.toml +1 -1
  24. package/core/harnesses/codex/agents/system-architect.toml +1 -1
  25. package/core/harnesses/codex/hooks/lib.sh +44 -25
  26. package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
  27. package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
  28. package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
  29. package/core/harnesses/codex/hooks/subagent-audit.sh +1 -0
  30. package/core/harnesses/copilot/hooks/doflow-lib.sh +93 -0
  31. package/core/harnesses/copilot/hooks/doflow-mcp-tool-guard.sh +9 -0
  32. package/core/harnesses/copilot/hooks/doflow-pre-bash-guard.sh +9 -0
  33. package/core/harnesses/copilot/hooks/doflow-pre-implement-gate.sh +9 -0
  34. package/core/harnesses/copilot/hooks/doflow-session-start.sh +9 -0
  35. package/core/harnesses/copilot/hooks/doflow-stop-check.sh +9 -0
  36. package/core/harnesses/copilot/hooks/doflow-subagent-start.sh +9 -0
  37. package/core/harnesses/copilot/hooks/doflow-subagent-stop.sh +9 -0
  38. package/core/harnesses/copilot/hooks/doflow.json +57 -0
  39. package/core/harnesses/kiro/agents/doflow.json +1 -1
  40. package/core/harnesses/kiro/hooks/lib.sh +26 -6
  41. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +1 -0
  42. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +1 -0
  43. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +1 -0
  44. package/core/harnesses/kiro/hooks/session-start.sh +1 -0
  45. package/core/harnesses/kiro/hooks/stop-check.sh +1 -0
  46. package/core/harnesses/pi/extensions/doflow-guard.js +8 -0
  47. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +0 -1
  48. package/core/harnesses/shared/hooks/bridge/bridge.sh +102 -9
  49. package/core/harnesses/shared/hooks/bridge/envelope.jq +87 -18
  50. package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
  51. package/core/harnesses/shared/hooks/bridge/respond.sh +10 -2
  52. package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +28 -14
  53. package/core/harnesses/shared/hooks/policies/capture-failure.sh +48 -15
  54. package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
  55. package/core/harnesses/shared/hooks/policies/contract.sh +3 -0
  56. package/core/harnesses/shared/hooks/policies/deny-json.sh +2 -11
  57. package/core/harnesses/shared/hooks/policies/emission-rules.sh +37 -0
  58. package/core/harnesses/shared/hooks/policies/emission.jq +209 -0
  59. package/core/harnesses/shared/hooks/policies/emission.sh +383 -0
  60. package/core/harnesses/shared/hooks/policies/identity.sh +177 -0
  61. package/core/harnesses/shared/hooks/policies/lib.sh +63 -40
  62. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +3 -2
  63. package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +213 -12
  64. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +114 -38
  65. package/core/harnesses/shared/hooks/policies/session-context.sh +30 -1
  66. package/core/harnesses/shared/hooks/policies/stop-check.sh +20 -9
  67. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +5 -2
  68. package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
  69. package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +60 -155
  70. package/core/harnesses/shared/hooks/stream-hook-runner.js +47 -8
  71. package/core/harnesses/shared/locator/doflow-run +2 -3
  72. package/core/registry/advice.json +3 -3
  73. package/core/registry/assets.json +55 -82
  74. package/core/registry/capabilities.json +12 -0
  75. package/core/registry/contracts.json +58 -31
  76. package/core/registry/contribution-points.json +32 -0
  77. package/core/registry/gates.json +32 -0
  78. package/core/registry/guidance-caps.json +40 -0
  79. package/core/registry/harnesses.json +1442 -363
  80. package/core/registry/lifecycle.json +485 -304
  81. package/core/registry/mcp.json +5 -1
  82. package/core/registry/models.json +7 -1
  83. package/core/registry/package-templates/README.md +19 -0
  84. package/core/registry/package-templates/agent/files/agent.md +9 -0
  85. package/core/registry/package-templates/agent/template.json +4 -0
  86. package/core/registry/package-templates/capability-provider/files/provider.json +9 -0
  87. package/core/registry/package-templates/capability-provider/template.json +11 -0
  88. package/core/registry/package-templates/debate-preset/files/preset.json +26 -0
  89. package/core/registry/package-templates/debate-preset/template.json +4 -0
  90. package/core/registry/package-templates/review-preset/files/preset.json +30 -0
  91. package/core/registry/package-templates/review-preset/template.json +4 -0
  92. package/core/registry/package-templates/skill/files/SKILL.md +8 -0
  93. package/core/registry/package-templates/skill/template.json +4 -0
  94. package/core/registry/package-templates/worker/files/worker.json +92 -0
  95. package/core/registry/package-templates/worker/template.json +11 -0
  96. package/core/registry/packages/doflow/doflow-package.json +15 -0
  97. package/core/registry/packages/doflow/presets/debate-3.json +26 -0
  98. package/core/registry/protocols.json +61 -0
  99. package/core/registry/routes.json +17 -0
  100. package/core/registry/rules.json +431 -0
  101. package/core/registry/settings.json +276 -0
  102. package/core/registry/storage.json +679 -31
  103. package/core/registry/telemetry.json +236 -0
  104. package/core/registry/transcripts.json +142 -0
  105. package/core/registry/verification.json +76 -1
  106. package/core/registry/workflows.json +55 -0
  107. package/core/shared/agent-specs/core-implementer.md +3 -0
  108. package/core/shared/agent-specs/delivery-lead.md +68 -0
  109. package/core/shared/agent-specs/quality-guardian.md +5 -1
  110. package/core/shared/agent-specs/research-writer.md +10 -3
  111. package/core/shared/agent-specs/reviewer.md +53 -0
  112. package/core/shared/agent-specs/spec-analyst.md +5 -6
  113. package/core/shared/agent-specs/system-architect.md +5 -1
  114. package/core/shared/guidance/DOFLOW_CORE.md +1 -5
  115. package/core/shared/guidance/modes/MODE_DeepResearch.md +1 -1
  116. package/core/shared/guidance/modes/MODE_Orchestration.md +4 -3
  117. package/core/shared/guidance/modes/MODE_Task_Management.md +14 -14
  118. package/core/shared/guidance/pointers/codex.md +1 -4
  119. package/core/shared/guidance/references/ARTIFACT_FORMAT.md +30 -16
  120. package/core/shared/guidance/references/ARTIFACT_VOICE.md +3 -2
  121. package/core/shared/guidance/references/CONSTITUTION_BASE.md +17 -15
  122. package/core/shared/guidance/references/DIAGRAM_GUIDE.md +1 -1
  123. package/core/shared/guidance/references/DOFLOW_CHAIN.md +17 -19
  124. package/core/shared/guidance/references/EVIDENCE_LEDGER.md +7 -8
  125. package/core/shared/guidance/references/MODEL_SELECTION.md +5 -3
  126. package/core/shared/guidance/references/ORCHESTRATION.md +1 -2
  127. package/core/shared/guidance/rules/RULES.md +45 -0
  128. package/core/shared/scripts/doflow/bash/do-args.sh +37 -0
  129. package/core/shared/scripts/doflow/bash/do-exec-paths.sh +4 -0
  130. package/core/shared/scripts/doflow/bash/do-git-state.sh +15 -4
  131. package/core/shared/scripts/doflow/bash/do-parallel-check.sh +4 -0
  132. package/core/shared/scripts/doflow/bash/do-paths.sh +40 -2
  133. package/core/shared/scripts/doflow/bash/do-prereqs.sh +2 -1
  134. package/core/shared/scripts/doflow/bash/do-review-package.sh +4 -0
  135. package/core/shared/scripts/doflow/bash/do-task-brief.sh +58 -16
  136. package/core/shared/scripts/doflow/bash/render-audit.sh +26 -2
  137. package/core/shared/scripts/doflow/bash/render-diagrams.sh +1 -0
  138. package/core/shared/scripts/doflow/bash/render-puml.sh +2 -1
  139. package/core/shared/scripts/doflow/bash/validate-artifacts.sh +48 -4
  140. package/core/shared/scripts/doflow/bin/doflow-run +149 -34
  141. package/core/shared/skills/do/SKILL.md +12 -10
  142. package/core/shared/skills/do/references/advice_calls.md +26 -0
  143. package/core/shared/skills/do/references/pm_routing.md +1 -2
  144. package/core/shared/skills/do/references/program_mode.md +31 -0
  145. package/core/shared/skills/do-brainstorm/SKILL.md +20 -74
  146. package/core/shared/skills/do-brainstorm/references/handoff_branches.md +35 -0
  147. package/core/shared/skills/do-brainstorm/references/intent_seed.md +21 -0
  148. package/core/shared/skills/do-brainstorm/references/round_log.md +17 -0
  149. package/core/shared/skills/do-code-review/SKILL.md +41 -144
  150. package/core/shared/skills/do-code-review/content-types/markdown.md +24 -2
  151. package/core/shared/skills/do-code-review/references/tools.md +90 -0
  152. package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +549 -7
  153. package/core/shared/skills/do-design/SKILL.md +18 -17
  154. package/core/shared/skills/do-diagnose/SKILL.md +14 -4
  155. package/core/shared/skills/do-document/SKILL.md +11 -1
  156. package/core/shared/skills/do-document/references/deep_research.md +1 -2
  157. package/core/shared/skills/do-document/references/embedded-research.md +18 -6
  158. package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
  159. package/core/shared/skills/do-execute-plan/SKILL.md +15 -58
  160. package/core/shared/skills/do-execute-plan/references/handoff_record.md +7 -2
  161. package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +13 -0
  162. package/core/shared/skills/do-execute-plan/references/readiness_gate.md +26 -1
  163. package/core/shared/skills/do-execute-plan/references/run_position.md +13 -1
  164. package/core/shared/skills/do-execute-plan/references/scaffold.md +17 -9
  165. package/core/shared/skills/do-execute-plan/references/state_checkpoint.md +11 -0
  166. package/core/shared/skills/do-execute-plan/references/task_attempts.md +78 -24
  167. package/core/shared/skills/do-flow/SKILL.md +8 -9
  168. package/core/shared/skills/do-flow/references/task_classes.md +2 -2
  169. package/core/shared/skills/do-git/SKILL.md +4 -0
  170. package/core/shared/skills/do-implement/SKILL.md +4 -7
  171. package/core/shared/skills/do-plan/SKILL.md +26 -15
  172. package/core/shared/skills/do-test/SKILL.md +7 -4
  173. package/core/shared/templates/doflow/constitution-template.md +9 -3
  174. package/core/shared/templates/doflow/data-model-template.md +1 -1
  175. package/core/shared/templates/doflow/design-template.md +113 -16
  176. package/core/shared/templates/doflow/plan-template.md +11 -10
  177. package/core/shared/templates/doflow/question-log-template.md +14 -2
  178. package/core/shared/templates/doflow/requirement-template.md +1 -1
  179. package/core/worker-evidence/antigravity/checks.json +14 -0
  180. package/core/worker-evidence/codex/2026-10-18-cli-check.json +107 -0
  181. package/core/worker-evidence/codex/2026-10-18-live-run-edit.json +73 -0
  182. package/core/worker-evidence/codex/2026-10-18-live-run-read-only.json +62 -0
  183. package/core/worker-evidence/codex/2026-10-24-cli-check.json +126 -0
  184. package/core/worker-evidence/codex/checks.json +38 -0
  185. package/package.json +10 -6
  186. package/src/adapters/antigravity/index.js +143 -66
  187. package/src/adapters/claude/index.js +24 -4
  188. package/src/adapters/codex/agents.js +25 -15
  189. package/src/adapters/codex/index.js +48 -13
  190. package/src/adapters/codex/native-projection.js +6 -2
  191. package/src/adapters/copilot/index.js +58 -23
  192. package/src/adapters/copy-tree.js +290 -44
  193. package/src/adapters/guidance-manifest.js +71 -0
  194. package/src/adapters/guidance-pointer.js +19 -0
  195. package/src/adapters/hook-commands.js +6 -10
  196. package/src/adapters/index.js +1 -0
  197. package/src/adapters/kiro/index.js +23 -12
  198. package/src/adapters/opencode/index.js +8 -8
  199. package/src/adapters/pi/hooks.js +2 -1
  200. package/src/adapters/pi/index.js +14 -7
  201. package/src/adapters/resolve-source.js +26 -2
  202. package/src/cli/commands/install.js +2 -1
  203. package/src/cli/commands/reconcile.js +53 -9
  204. package/src/cli/commands/remove.js +2 -2
  205. package/src/cli/commands/rollback.js +6 -3
  206. package/src/cli/commands/self-update.js +18 -9
  207. package/src/cli/commands/status.js +21 -2
  208. package/src/cli/commands/ui.js +99 -0
  209. package/src/cli/commands/update.js +2 -1
  210. package/src/cli/index.js +322 -29
  211. package/src/cli/runtime-commands.js +209 -57
  212. package/src/cli/shared.js +0 -2
  213. package/src/helper/git.js +1 -1
  214. package/src/helper/guardrails.js +1 -1
  215. package/src/helper/marker-merge.js +31 -13
  216. package/src/helper/package-ids.js +37 -0
  217. package/src/helper/semver.js +94 -0
  218. package/src/install/backup.js +105 -15
  219. package/src/install/context.js +1 -1
  220. package/src/install/targets.js +31 -16
  221. package/src/lifecycle/coverage.js +20 -3
  222. package/src/lifecycle/hook-tier.js +160 -0
  223. package/src/lifecycle/index.js +412 -78
  224. package/src/lifecycle/retired-harnesses.js +318 -0
  225. package/src/lifecycle/view.js +35 -15
  226. package/src/registry/index.js +551 -26
  227. package/src/registry/worker-ids.js +45 -0
  228. package/src/release/build-package.js +0 -3
  229. package/src/release/docs-blocks.js +492 -0
  230. package/src/release/preflight.js +425 -0
  231. package/src/release/rehearse.js +401 -0
  232. package/src/release/release-workflow.js +733 -0
  233. package/src/release/sync-plugin-versions.js +43 -7
  234. package/src/runtime/advice/bench-gate.js +25 -0
  235. package/src/runtime/advice/cli.js +59 -16
  236. package/src/runtime/advice/client.js +32 -9
  237. package/src/runtime/advice/config.js +98 -133
  238. package/src/runtime/advice/doctor.js +1 -1
  239. package/src/runtime/advice/elevation.js +97 -0
  240. package/src/runtime/advice/endpoint-guard.js +9 -3
  241. package/src/runtime/advice/mode.js +37 -0
  242. package/src/runtime/advice/record.js +18 -5
  243. package/src/runtime/advice/registry.js +4 -1
  244. package/src/runtime/advice/shadow-config.js +229 -0
  245. package/src/runtime/advice/shadow-dispatcher.js +276 -0
  246. package/src/runtime/brief-rules.js +81 -0
  247. package/src/runtime/brief-tools.js +111 -34
  248. package/src/runtime/candidate.js +20 -11
  249. package/src/runtime/capabilities/executor.js +450 -0
  250. package/src/runtime/capabilities/loopback.js +41 -0
  251. package/src/runtime/capabilities/sanitizer.js +88 -0
  252. package/src/runtime/capability-router.js +135 -3
  253. package/src/runtime/checkouts.js +73 -11
  254. package/src/runtime/claims.js +13 -7
  255. package/src/runtime/cli.js +196 -19
  256. package/src/runtime/config-home.js +42 -0
  257. package/src/runtime/context/arbitrate.js +767 -0
  258. package/src/runtime/context/delivery-store.js +4 -6
  259. package/src/runtime/context/emission-rules.js +118 -0
  260. package/src/runtime/context/identity.js +227 -0
  261. package/src/runtime/context/primary-command.js +117 -0
  262. package/src/runtime/context/primary.js +344 -0
  263. package/src/runtime/context/summary-store.js +655 -0
  264. package/src/runtime/context-pack.js +154 -78
  265. package/src/runtime/decision-register.js +13 -4
  266. package/src/runtime/dispatch/admission.js +392 -0
  267. package/src/runtime/dispatch/attempt-end.js +274 -0
  268. package/src/runtime/dispatch/command.js +628 -0
  269. package/src/runtime/dispatch/controls.js +904 -0
  270. package/src/runtime/dispatch/envelope.js +276 -0
  271. package/src/runtime/dispatch/grants.js +310 -0
  272. package/src/runtime/dispatch/integrate.js +617 -0
  273. package/src/runtime/dispatch/loop.js +1835 -0
  274. package/src/runtime/dispatch/store.js +352 -0
  275. package/src/runtime/dispatch/verify-run.js +201 -0
  276. package/src/runtime/evidence-ledger.js +34 -7
  277. package/src/runtime/failure/capture.js +3 -1
  278. package/src/runtime/failure/cli.js +25 -9
  279. package/src/runtime/failure/home.js +57 -30
  280. package/src/runtime/failure/store.js +7 -2
  281. package/src/runtime/feature-pointer.js +174 -0
  282. package/src/runtime/feature-resolve.js +13 -2
  283. package/src/runtime/fleet/command.js +77 -0
  284. package/src/runtime/fleet/projects.js +178 -0
  285. package/src/runtime/fleet/tree.js +383 -0
  286. package/src/runtime/freshness.js +33 -3
  287. package/src/runtime/git-memo.js +46 -0
  288. package/src/runtime/guidance/ablation.js +427 -0
  289. package/src/runtime/health.js +565 -17
  290. package/src/runtime/history-compactor.js +13 -3
  291. package/src/runtime/implementation-gate.js +277 -22
  292. package/src/runtime/inventory/coverage.js +1 -1
  293. package/src/runtime/inventory/index.js +1 -1
  294. package/src/runtime/inventory/precedence.js +2 -2
  295. package/src/runtime/inventory/siblings.js +6 -4
  296. package/src/runtime/knowledge/code-corpus.js +277 -0
  297. package/src/runtime/knowledge/code-lexical.js +130 -0
  298. package/src/runtime/knowledge/code-retrieval.js +307 -0
  299. package/src/runtime/knowledge/code-validation.js +106 -0
  300. package/src/runtime/knowledge/gp3-gate.js +74 -0
  301. package/src/runtime/knowledge/graph-adapter.js +288 -0
  302. package/src/runtime/knowledge/graph-fallback.js +394 -0
  303. package/src/runtime/knowledge/hybrid.js +136 -8
  304. package/src/runtime/knowledge/retrieval.js +34 -1
  305. package/src/runtime/knowledge/rtk-compress.js +77 -0
  306. package/src/runtime/knowledge/semble-bridge.py +106 -0
  307. package/src/runtime/knowledge/semble-lifecycle.js +114 -0
  308. package/src/runtime/knowledge/semble-provider.js +365 -0
  309. package/src/runtime/lifecycle/cli.js +16 -6
  310. package/src/runtime/lifecycle/event-store.js +8 -6
  311. package/src/runtime/lifecycle/followup.js +6 -1
  312. package/src/runtime/lifecycle/overview.js +24 -1
  313. package/src/runtime/lifecycle/report-store.js +8 -10
  314. package/src/runtime/lifecycle/retention.js +95 -31
  315. package/src/runtime/lifecycle/root.js +9 -1
  316. package/src/runtime/lifecycle/status.js +34 -17
  317. package/src/runtime/lifecycle/store-upkeep.js +56 -7
  318. package/src/runtime/mask.js +134 -7
  319. package/src/runtime/mcp-consultations.js +40 -6
  320. package/src/runtime/model-router.js +79 -7
  321. package/src/runtime/models/catalogue.js +191 -0
  322. package/src/runtime/models/command.js +153 -0
  323. package/src/runtime/off-words.js +12 -0
  324. package/src/runtime/outcome.js +2 -2
  325. package/src/runtime/packages/command.js +1009 -0
  326. package/src/runtime/packages/contributions.js +116 -0
  327. package/src/runtime/packages/digest.js +132 -0
  328. package/src/runtime/packages/fetch.js +182 -0
  329. package/src/runtime/packages/grants.js +192 -0
  330. package/src/runtime/packages/index-file.js +134 -0
  331. package/src/runtime/packages/instruction.js +90 -0
  332. package/src/runtime/packages/manifest.js +331 -0
  333. package/src/runtime/packages/points.js +477 -0
  334. package/src/runtime/packages/programs.js +216 -0
  335. package/src/runtime/packages/scaffold.js +277 -0
  336. package/src/runtime/packages/source.js +90 -0
  337. package/src/runtime/packages/state.js +164 -0
  338. package/src/runtime/packages/store.js +449 -0
  339. package/src/runtime/packages/tar.js +106 -0
  340. package/src/runtime/packages/view.js +406 -0
  341. package/src/runtime/plan-check.js +59 -0
  342. package/src/runtime/plan-content.js +324 -0
  343. package/src/runtime/policy-init.js +118 -0
  344. package/src/runtime/profiles/doctor.js +96 -0
  345. package/src/runtime/profiles/resolve.js +244 -0
  346. package/src/runtime/profiles/rollback.js +254 -0
  347. package/src/runtime/profiles/sampler.js +41 -0
  348. package/src/runtime/readiness-record.js +16 -11
  349. package/src/runtime/readiness.js +46 -5
  350. package/src/runtime/research-request.js +50 -25
  351. package/src/runtime/retrieval-plan.js +10 -30
  352. package/src/runtime/retrieval-record.js +75 -5
  353. package/src/runtime/review/command.js +246 -0
  354. package/src/runtime/review/dominance.js +68 -0
  355. package/src/runtime/review/findings.js +434 -0
  356. package/src/runtime/review/identity.js +255 -0
  357. package/src/runtime/review/protocol-command.js +109 -0
  358. package/src/runtime/review/protocols.js +405 -0
  359. package/src/runtime/review/resolve-protocol.js +302 -0
  360. package/src/runtime/review/start.js +530 -0
  361. package/src/runtime/roadmap/facts.js +479 -0
  362. package/src/runtime/roadmap/gates.js +270 -0
  363. package/src/runtime/roadmap/loop.js +1636 -0
  364. package/src/runtime/roadmap/merge.js +381 -0
  365. package/src/runtime/roadmap/rows.js +1113 -0
  366. package/src/runtime/roadmap/seed.js +951 -0
  367. package/src/runtime/rules-report.js +336 -0
  368. package/src/runtime/scaffold/artifacts.js +60 -9
  369. package/src/runtime/scaffold/generate.js +21 -9
  370. package/src/runtime/settings/autopilot.js +106 -0
  371. package/src/runtime/settings/cli.js +280 -0
  372. package/src/runtime/settings/config-write.js +314 -0
  373. package/src/runtime/settings/files.js +271 -0
  374. package/src/runtime/settings/legacy.js +154 -0
  375. package/src/runtime/settings/lexical.js +56 -0
  376. package/src/runtime/settings/prepared-run.js +269 -0
  377. package/src/runtime/settings/project-layers.js +118 -0
  378. package/src/runtime/settings/refs.js +288 -0
  379. package/src/runtime/settings/registry.js +553 -0
  380. package/src/runtime/settings/resolve.js +713 -0
  381. package/src/runtime/settings/shared.js +37 -0
  382. package/src/runtime/settings/worker-billing.js +107 -0
  383. package/src/runtime/settings/write.js +269 -0
  384. package/src/runtime/slot-command.js +78 -0
  385. package/src/runtime/slots.js +671 -0
  386. package/src/runtime/source-version.js +1 -0
  387. package/src/runtime/state-root.js +217 -0
  388. package/src/runtime/storage/cli.js +12 -5
  389. package/src/runtime/storage/engine.js +9 -2
  390. package/src/runtime/storage/registry.js +14 -1
  391. package/src/runtime/storage/roots.js +66 -16
  392. package/src/runtime/storage/rules.js +82 -10
  393. package/src/runtime/storage/upkeep.js +10 -2
  394. package/src/runtime/storage/work-status.js +245 -26
  395. package/src/runtime/task-attempts.js +589 -69
  396. package/src/runtime/task-classifier.js +47 -11
  397. package/src/runtime/task-graph.js +373 -0
  398. package/src/runtime/task-run.js +778 -0
  399. package/src/runtime/task-scope.js +176 -55
  400. package/src/runtime/task-sources.js +147 -0
  401. package/src/runtime/task-state.js +374 -64
  402. package/src/runtime/team/admission.js +301 -0
  403. package/src/runtime/team/envelope.js +324 -0
  404. package/src/runtime/team/review-guard.js +156 -0
  405. package/src/runtime/trace/analyze.js +427 -0
  406. package/src/runtime/trace/baseline.js +770 -0
  407. package/src/runtime/trace/ledger.js +63 -140
  408. package/src/runtime/trace/otlp-export.js +568 -0
  409. package/src/runtime/trace/otlp-map.js +481 -0
  410. package/src/runtime/trace/spans.js +745 -0
  411. package/src/runtime/trace/telemetry.js +225 -0
  412. package/src/runtime/trace/views.js +1 -0
  413. package/src/runtime/transcripts/command.js +98 -0
  414. package/src/runtime/transcripts/cursor.js +326 -0
  415. package/src/runtime/transcripts/read.js +126 -0
  416. package/src/runtime/transcripts/readers/claude-jsonl.js +74 -0
  417. package/src/runtime/transcripts/readers/codex-rollout.js +70 -0
  418. package/src/runtime/transcripts/readers/copilot-events.js +90 -0
  419. package/src/runtime/transcripts/readers/kiro-jsonl.js +77 -0
  420. package/src/runtime/transcripts/readers/pi-jsonl.js +59 -0
  421. package/src/runtime/transcripts/registry.js +161 -0
  422. package/src/runtime/transcripts/stats.js +161 -0
  423. package/src/runtime/verification/check-key.js +628 -0
  424. package/src/runtime/verification/contract-runner.js +546 -11
  425. package/src/runtime/verification/engine.js +499 -9
  426. package/src/runtime/verification/impact.js +617 -0
  427. package/src/runtime/verification/inflight.js +205 -0
  428. package/src/runtime/verification/registry.js +176 -0
  429. package/src/runtime/verification/reuse.js +184 -0
  430. package/src/runtime/verification/telemetry-emit.js +254 -0
  431. package/src/runtime/verification/test-selection.js +67 -7
  432. package/src/runtime/worker/active.js +90 -0
  433. package/src/runtime/worker/cancel.js +222 -0
  434. package/src/runtime/worker/catalogue.js +122 -0
  435. package/src/runtime/worker/command.js +677 -0
  436. package/src/runtime/worker/evidence.js +288 -0
  437. package/src/runtime/worker/launch.js +288 -0
  438. package/src/runtime/worker/models.js +137 -0
  439. package/src/runtime/worker/parsers.js +218 -0
  440. package/src/runtime/worker/probes/index.js +706 -0
  441. package/src/runtime/worker/process-tree.js +415 -0
  442. package/src/runtime/worker/quota.js +43 -0
  443. package/src/runtime/worker/readiness.js +713 -0
  444. package/src/runtime/worker/run-store.js +166 -0
  445. package/src/runtime/worker/supervisor.js +541 -0
  446. package/src/runtime/worker/worktree-check.js +252 -0
  447. package/src/runtime/workflow-engine.js +5 -1
  448. package/src/runtime/workflow-orchestrator.js +578 -63
  449. package/src/runtime/worktree.js +149 -22
  450. package/src/state/index.js +1 -1
  451. package/src/state/lockfile.js +64 -10
  452. package/src/state/run-lock.js +1 -1
  453. package/types/node-commonjs.d.ts +106 -7
  454. package/core/harnesses/gemini/hooks/hooks.json +0 -77
  455. package/core/harnesses/gemini/hooks/lib.sh +0 -302
  456. package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +0 -10
  457. package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
  458. package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -10
  459. package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
  460. package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -10
  461. package/core/harnesses/gemini/hooks/session-end.impl.sh +0 -89
  462. package/core/harnesses/gemini/hooks/session-end.sh +0 -5
  463. package/core/harnesses/gemini/hooks/session-start.sh +0 -7
  464. package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
  465. package/core/shared/guidance/FLAGS.md +0 -25
  466. package/core/shared/guidance/rules/RULE_01_SAFETY.md +0 -25
  467. package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +0 -23
  468. package/core/shared/guidance/rules/RULE_03_QUALITY.md +0 -14
  469. package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +0 -46
  470. package/src/adapters/gemini/hooks.js +0 -251
  471. package/src/adapters/gemini/index.js +0 -429
@@ -22,11 +22,14 @@ const os = require('node:os');
22
22
  const path = require('node:path');
23
23
  const { execFileSync } = require('node:child_process');
24
24
  const { CapabilityRouter } = require('./capability-router.js');
25
- const { loadRegistry } = require('../registry/index.js');
25
+ const { loadRegistry, agentToolScopeView } = require('../registry/index.js');
26
26
  const { evaluateReach } = require('./reach.js');
27
27
  const { mcpReach } = require('./mcp-reach.js');
28
28
  const { OUTCOME_LABELS } = require('./mcp-consultations.js');
29
29
  const { REPO_ROOT } = require('../helper/repo-root.js');
30
+ const { readGrants, detectedProjectRoot } = require('./packages/grants.js');
31
+ const { readState, grantRevoked } = require('./packages/state.js');
32
+ const { transcriptsDoctorView } = require('./transcripts/registry.js');
30
33
 
31
34
  /** Answered a probe. */
32
35
  const HEALTHY = 'HEALTHY';
@@ -82,9 +85,10 @@ const SCAN_FILE_LIMIT = 4000;
82
85
  * @param {Object} [options]
83
86
  * @param {Function} [options.execFileImpl=execFileSync]
84
87
  * @param {number} [options.timeoutMs=PROBE_TIMEOUT_MS]
88
+ * @param {boolean} [options.retryOnTimeout] run the probe once more after a timeout; defaults to true for git only
85
89
  * @returns {{ok: boolean, output: string, durationMs: number, timedOut: boolean, error: string|null}}
86
90
  */
87
- function runProbe(command , { execFileImpl = execFileSync, timeoutMs = PROBE_TIMEOUT_MS } = {}) {
91
+ function runProbe(command , { execFileImpl = execFileSync, timeoutMs = PROBE_TIMEOUT_MS, retryOnTimeout = path.basename(String(command[0])) === 'git' } = {}) {
88
92
  const [bin, ...args] = command;
89
93
  const started = Number(process.hrtime.bigint() / 1000000n);
90
94
  try {
@@ -105,6 +109,9 @@ function runProbe(command , { execFileImpl = execFileSync, timeoutMs = PROBE
105
109
  // repairs — a timeout usually means a first-run index build, a non-zero exit usually means a
106
110
  // broken install — so they are reported apart rather than as one failure string.
107
111
  const timedOut = error.killed === true || error.code === 'ETIMEDOUT' || error.signal === 'SIGTERM';
112
+ // A cold macOS git shim can outlast the first probe and answer at once the second time, which made the
113
+ // first `doflow doctor` exit 1 falsely (FU-93wtsf). Only the git probe is retried, once.
114
+ if (timedOut && retryOnTimeout) return runProbe(command, { execFileImpl, timeoutMs, retryOnTimeout: false });
108
115
  return {
109
116
  ok: false,
110
117
  output: '',
@@ -243,6 +250,95 @@ function probeFreshness(providerId , { root = process.cwd(), homeDir = os.ho
243
250
  };
244
251
  }
245
252
 
253
+ async function probeLocalProvider(
254
+ provider ,
255
+ options = {}
256
+ ) {
257
+ const timeoutMs = options.timeoutMs ?? 1500;
258
+
259
+ if (provider.kind === 'http') {
260
+ const targetUrl = provider.healthEndpoint || provider.endpoint;
261
+ const { probeHttpEndpoint } = require('./capabilities/executor.js');
262
+ const probeRes = await probeHttpEndpoint(targetUrl, {
263
+ timeoutMs,
264
+ fetchImpl: options.fetchImpl,
265
+ });
266
+ if (probeRes.ok) {
267
+ return {
268
+ id: provider.id,
269
+ name: provider.name || provider.id,
270
+ kind: 'http',
271
+ status: 'HEALTHY',
272
+ endpoint: targetUrl,
273
+ answered: true,
274
+ durationMs: probeRes.durationMs,
275
+ detail: `HTTP ${probeRes.status}`,
276
+ };
277
+ }
278
+ if (probeRes.isUnavailable) {
279
+ return {
280
+ id: provider.id,
281
+ name: provider.name || provider.id,
282
+ kind: 'http',
283
+ status: 'UNAVAILABLE',
284
+ endpoint: targetUrl,
285
+ answered: false,
286
+ durationMs: probeRes.durationMs,
287
+ detail: `Local server at ${targetUrl} is not running`,
288
+ };
289
+ }
290
+ return {
291
+ id: provider.id,
292
+ name: provider.name || provider.id,
293
+ kind: 'http',
294
+ status: 'UNHEALTHY',
295
+ endpoint: targetUrl,
296
+ answered: probeRes.status !== undefined,
297
+ durationMs: probeRes.durationMs,
298
+ detail: probeRes.status ? `HTTP status ${probeRes.status}` : (probeRes.error || 'HTTP request failed'),
299
+ };
300
+ }
301
+
302
+ if (provider.kind === 'cli') {
303
+ const binary = provider.binary || provider.program;
304
+ const isAvail = options.router ? options.router.isBinaryAvailable(binary) : true;
305
+ if (!isAvail) {
306
+ return {
307
+ id: provider.id,
308
+ name: provider.name || provider.id,
309
+ kind: 'cli',
310
+ status: 'UNAVAILABLE',
311
+ binary,
312
+ answered: false,
313
+ durationMs: null,
314
+ detail: `Binary '${binary}' not found on PATH`,
315
+ };
316
+ }
317
+ const checkCommand = provider.checkCommand || [binary, '--version'];
318
+ const probe = runProbe(checkCommand, { execFileImpl: options.execFileImpl, timeoutMs });
319
+ return {
320
+ id: provider.id,
321
+ name: provider.name || provider.id,
322
+ kind: 'cli',
323
+ status: probe.ok ? 'HEALTHY' : 'UNHEALTHY',
324
+ binary,
325
+ answered: probe.ok,
326
+ durationMs: probe.durationMs,
327
+ detail: probe.ok ? banner(probe.output) : probe.error,
328
+ };
329
+ }
330
+
331
+ return {
332
+ id: provider.id,
333
+ name: provider.name || provider.id,
334
+ kind: provider.kind || 'native',
335
+ status: 'UNVERIFIED',
336
+ answered: false,
337
+ durationMs: null,
338
+ detail: 'unsupported probe kind',
339
+ };
340
+ }
341
+
246
342
  /**
247
343
  * Probes one provider declared in `capabilities.json`.
248
344
  * @param {Object} provider
@@ -257,10 +353,11 @@ function probeProvider(provider , { router, projectRoot = process.cwd(), exe
257
353
  if (!provider || !provider.id) {
258
354
  return { id: null, status: UNVERIFIED, detail: 'no provider declared' };
259
355
  }
356
+ const binary = provider.binary || provider.program || null;
260
357
  const result = {
261
358
  id: provider.id,
262
359
  name: provider.name || provider.id,
263
- binary: provider.binary || null,
360
+ binary,
264
361
  present: null,
265
362
  answered: false,
266
363
  status: UNVERIFIED,
@@ -270,22 +367,32 @@ function probeProvider(provider , { router, projectRoot = process.cwd(), exe
270
367
  freshness: null,
271
368
  };
272
369
 
273
- if (provider.binary) {
274
- result.present = router.isBinaryAvailable(provider.binary);
370
+ if (binary) {
371
+ result.present = router ? router.isBinaryAvailable(binary) : false;
275
372
  if (!result.present) {
276
373
  result.status = ABSENT;
277
- result.detail = `binary '${provider.binary}' is not on PATH`;
374
+ result.detail = `binary '${binary}' is not on PATH`;
375
+ return result;
376
+ }
377
+ }
378
+
379
+ if (provider.kind === 'http' || provider.endpoint) {
380
+ const { validateLoopbackUrl } = require('./capabilities/loopback.js');
381
+ const valid = validateLoopbackUrl(provider.endpoint);
382
+ if (!valid.ok) {
383
+ result.status = UNHEALTHY;
384
+ result.detail = `Disallowed endpoint: ${valid.reason}`;
278
385
  return result;
279
386
  }
280
387
  }
281
388
 
282
389
  const command = Array.isArray(provider.checkCommand) && provider.checkCommand.length
283
390
  ? provider.checkCommand
284
- : null;
391
+ : (provider.kind === 'cli' && binary ? [binary, '--version'] : null);
285
392
  if (!command) {
286
393
  result.status = UNVERIFIED;
287
- result.detail = provider.binary
288
- ? `binary '${provider.binary}' is present but the registry declares no probe command, so DoFlow cannot establish that it answers`
394
+ result.detail = binary
395
+ ? `binary '${binary}' is present but the registry declares no probe command, so DoFlow cannot establish that it answers`
289
396
  : 'declares neither a binary nor a probe command, so its function depends entirely on the project (see the project commands section)';
290
397
  return result;
291
398
  }
@@ -500,13 +607,29 @@ const byText = (a , b ) => (a < b ? -1 : a > b ? 1 : 0);
500
607
  */
501
608
  function mcpConsultationsView({ projectRoot, fsImpl } ) {
502
609
  const dir = path.join(projectRoot, '.doflow', 'state', 'orchestration');
503
- let files = [];
504
- try { files = fsImpl.readdirSync(dir).filter((name ) => name.endsWith('.json')).sort(); } catch { files = []; }
610
+ // Runs live flat or in `<slug>/` (115: a task id other than its feature's slug is namespaced).
611
+ const listed = [];
612
+ const readRunFile = (rel , name , namespace ) => {
613
+ let run;
614
+ try { run = JSON.parse(fsImpl.readFileSync(path.join(dir, rel), 'utf8')); } catch { run = null; }
615
+ listed.push({ rel, name, namespace, run });
616
+ };
617
+ let entries = [];
618
+ try { entries = fsImpl.readdirSync(dir, { withFileTypes: true }); } catch { entries = []; }
619
+ for (const entry of entries) if (entry.name.endsWith('.json') && !entry.isDirectory()) readRunFile(entry.name, entry.name, '');
620
+ for (const entry of entries) {
621
+ if (!entry.isDirectory() || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(entry.name) || entry.name.endsWith('.lock')) continue;
622
+ let inner = [];
623
+ try { inner = fsImpl.readdirSync(path.join(dir, entry.name), { withFileTypes: true }); } catch { inner = []; }
624
+ for (const file of inner) if (file.name.endsWith('.json') && !file.isDirectory()) readRunFile(`${entry.name}/${file.name}`, file.name, entry.name);
625
+ }
626
+ listed.sort((a, b) => byText(a.rel, b.rel));
627
+ // A flat run whose namespaced copy exists is that copy's seed, not a second run.
628
+ const files = listed.filter((one) => one.namespace !== '' || !listed.some((other) => other.namespace !== '' && other.name === one.name
629
+ && (typeof one.run?.featureSlug !== 'string' || one.run.featureSlug === other.namespace)));
505
630
  let unreadableRuns = 0;
506
631
  const bySkill = new Map();
507
- for (const name of files) {
508
- let run;
509
- try { run = JSON.parse(fsImpl.readFileSync(path.join(dir, name), 'utf8')); } catch { run = null; }
632
+ for (const { name, run } of files) {
510
633
  if (!run || typeof run !== 'object' || Array.isArray(run)) { unreadableRuns += 1; continue; }
511
634
  for (const item of Array.isArray(run.history) ? run.history : []) {
512
635
  const record = item && item.consultations;
@@ -555,6 +678,68 @@ function mcpView({ registry, projectRoot, fsImpl, adapters } ) {
555
678
  return { servers, ...(serversNote ? { serversNote } : {}), consultations: mcpConsultationsView({ projectRoot, fsImpl }) };
556
679
  }
557
680
 
681
+ /**
682
+ * The doctor's `packages` block (122 IC-008, IC-009): the grants file path and mtime, every grant with its state,
683
+ * and the counts held in `state.json`. A trust or exempt grant a revocation covers (or any, when `state.json` is
684
+ * unreadable) is listed `revoked`. Read-only and not blocking; a failure degrades to a note.
685
+ */
686
+ function packagesView({ projectRoot, fsImpl, env, homeDir, tmpDir } ) {
687
+ try {
688
+ // The trust root is the project detected from the directory reported on, never that directory itself (IC-008).
689
+ const home = homeDir ?? (typeof env?.HOME === 'string' && path.isAbsolute(env.HOME) ? env.HOME : os.homedir());
690
+ const grants = readGrants({ env, fsImpl, projectRoot: detectedProjectRoot({ cwd: projectRoot, homeDir: home, fsImpl }), tmpDir });
691
+ const state = readState({ homeDir, fsImpl });
692
+ const rows = grants.entries.map((entry ) => {
693
+ const revoked = entry.state === 'valid' && entry.grant !== 'fetch' && grantRevoked(state, entry);
694
+ const target = entry.grant === 'fetch' ? (entry.source.url || entry.source.name) : `${entry.package}@${entry.version}`;
695
+ return { line: entry.line, grant: entry.grant, target, state: revoked ? 'revoked' : entry.state, ...(revoked ? { reason: state.unreadable ? 'state-unreadable' : 'revoked' } : entry.reason ? { reason: entry.reason } : {}) };
696
+ });
697
+ return {
698
+ grants: { path: grants.path, noConfigHome: grants.noConfigHome, exists: grants.exists, mtime: grants.mtime, rows, findings: grants.findings },
699
+ suspendedProjected: state.state.suspended.filter((item ) => item.projected).map((item ) => item.package).sort(),
700
+ state: {
701
+ path: state.path, exists: state.exists, unreadable: state.unreadable, findings: state.findings,
702
+ revoked: state.state.revoked.length, suspended: state.state.suspended.length, removed: state.state.removed.length,
703
+ },
704
+ };
705
+ } catch (error ) {
706
+ return { note: `package grants unavailable: ${error.message}` };
707
+ }
708
+ }
709
+
710
+ function printPackagesSection(packages ) {
711
+ console.log('\n[Packages] — person grants and DoFlow state; not blocking');
712
+ if (packages.note) { console.log(` ${packages.note}`); return; }
713
+ const { grants, state } = packages;
714
+ if (grants.noConfigHome) console.log(' grants no config home (HOME and XDG_CONFIG_HOME unset): no grant can exist');
715
+ else if (!grants.exists) console.log(` grants none (no file at ${grants.path})`);
716
+ else {
717
+ console.log(` grants ${grants.path} (modified ${grants.mtime})`);
718
+ for (const row of grants.rows) console.log(` line ${String(row.line).padEnd(5)}${row.grant.padEnd(8)}${row.target.padEnd(28)}${row.state}${row.reason ? ` (${row.reason})` : ''}`);
719
+ for (const item of grants.findings) console.log(` ${item.code}: ${item.message}`);
720
+ }
721
+ if (state.unreadable) console.log(` state ${state.path} is unreadable: every package counts as revoked (run: doflow package --action verify --repair)`);
722
+ else if (state.revoked || state.suspended || state.removed) console.log(` state ${state.revoked} revoked, ${state.suspended} suspended, ${state.removed} removed (${state.path})`);
723
+ for (const id of packages.suspendedProjected ?? []) {
724
+ console.log(` suspended ${id} still has projected files (run: doflow package --action verify)`);
725
+ }
726
+ }
727
+
728
+ /** Whether the text report shows `[Packages]`: only when there is something to say. */
729
+ /** The `[Transcripts]` row (093 V.1): shown only when the registry is stale or drifted; never a finding. */
730
+ function printTranscriptsSection(transcripts ) {
731
+ if (transcripts.state === 'ok') return;
732
+ console.log('\n[Transcripts] — transcript locator registry; not blocking');
733
+ if (transcripts.state === 'drift') {
734
+ console.log(' transcripts.json drift: the transcript feature is off');
735
+ for (const error of transcripts.errors) console.log(` ${error}`);
736
+ return;
737
+ }
738
+ for (const item of transcripts.stale) console.log(` ${item.harness.padEnd(18)}verifiedOn ${item.verifiedOn} (${item.ageDays} days ago): re-verify its locations`);
739
+ }
740
+
741
+ const packagesWorthShowing = (packages ) => Boolean(packages.note || packages.grants.exists || packages.state.exists);
742
+
558
743
  const MCP_STATE_MARK = Object.freeze({
559
744
  selected: '✓ selected',
560
745
  configured: '✓ configured',
@@ -602,7 +787,33 @@ function printMcpSections(mcp ) {
602
787
  if (unreadableRuns) console.log(` ${unreadableRuns} run file(s) could not be read`);
603
788
  }
604
789
 
605
- function buildHealthReport({ repoRoot, projectRoot = process.cwd(), router, execFileImpl, fsImpl = fs, detector, adapters } = {}) {
790
+ /** The first release of each Node line that carries the 2026-10-21 security fix (CVE-2026-111497). */
791
+ const NODE_PATCHED_RELEASE = Object.freeze({ 24: [24, 22, 1], 26: [26, 12, 1] });
792
+
793
+ /**
794
+ * A warning when `version` (a `process.version` string) is a release of a Node line below that
795
+ * line's patched release; null for a patched release and for a line this table does not know.
796
+ * The floor in `bin/doflow.js` already refuses anything under 24.22.1, so in practice this fires for
797
+ * a Node 26 below 26.12.1; the 24 entry keeps the table complete for a caller that bypasses the bin. A line with no entry (25, 27) gets no warning. A prerelease of the
798
+ * patched release (26.12.1-rc.1) counts as below it.
799
+ */
800
+ function nodeSecurityWarning(version ) {
801
+ const parts = /^v?(\d+)\.(\d+)\.(\d+)(-.*)?$/.exec(String(version));
802
+ if (!parts) return null;
803
+ const running = [Number(parts[1]), Number(parts[2]), Number(parts[3])];
804
+ const patched = NODE_PATCHED_RELEASE[running[0]];
805
+ if (!patched) return null;
806
+ const order = patched.reduce((result, part, i) => result || Math.sign(running[i] - part), 0);
807
+ const below = order < 0 || (order === 0 && Boolean(parts[4])); // a prerelease of the patched release sorts below it
808
+ if (!below) return null;
809
+ return {
810
+ kind: 'node-security-release',
811
+ subject: 'node',
812
+ detail: `Node ${String(version).replace(/^v/, '')} is below ${patched.join('.')}, the first ${running[0]}.x release with the fix for CVE-2026-111497 (2026-10-21 security release); upgrade Node`,
813
+ };
814
+ }
815
+
816
+ function buildHealthReport({ repoRoot, projectRoot = process.cwd(), router, execFileImpl, fsImpl = fs, detector, adapters, env = process.env, nodeVersion = process.version, homeDir, tmpDir, now } = {}) {
606
817
  const root = repoRoot || REPO_ROOT;
607
818
  const registry = loadRegistry({ repoRoot: root });
608
819
  const activeRouter = router || new CapabilityRouter({ repoRoot: root, projectRoot });
@@ -665,7 +876,9 @@ function buildHealthReport({ repoRoot, projectRoot = process.cwd(), router, exec
665
876
  .filter((row ) => row.state === 'NO-REACH')
666
877
  .map((row ) => ({ kind: 'runtime-no-reach', subject: `${row.harness}:${row.scope}`, detail: `${row.reason}; fix: ${row.fix}` })),
667
878
  ];
879
+ const nodeWarning = nodeSecurityWarning(nodeVersion);
668
880
  const warnings = [
881
+ ...(nodeWarning ? [nodeWarning] : []),
669
882
  ...freshness
670
883
  .filter((item) => item.state === 'STALE')
671
884
  .map((item) => ({ kind: 'index-stale', subject: item.providerId, detail: `${item.label} is behind the working tree; refresh with: ${item.refresh}` })),
@@ -704,6 +917,8 @@ function buildHealthReport({ repoRoot, projectRoot = process.cwd(), router, exec
704
917
  freshness,
705
918
  project,
706
919
  mcp: mcpView({ registry, projectRoot, fsImpl, adapters }),
920
+ packages: packagesView({ projectRoot, fsImpl, env, homeDir, tmpDir }),
921
+ transcripts: transcriptsDoctorView({ repoRoot: root, fsImpl, now }),
707
922
  findings,
708
923
  warnings,
709
924
  summary: {
@@ -713,6 +928,8 @@ function buildHealthReport({ repoRoot, projectRoot = process.cwd(), router, exec
713
928
  absent: Object.values(probes).filter((probe) => probe.status === ABSENT).length,
714
929
  unverified: Object.values(probes).filter((probe) => probe.status === UNVERIFIED).length,
715
930
  },
931
+ // IC-006: the read switch of doctor's own environment, only when set (additive key).
932
+ ...(env.DOFLOW_UPKEEP === 'off' ? { upkeepSwitch: 'off' } : {}),
716
933
  };
717
934
  }
718
935
 
@@ -755,6 +972,121 @@ const FRESHNESS_MARK = Object.freeze({
755
972
  UNKNOWN: '? UNKNOWN',
756
973
  });
757
974
 
975
+ /** IC-014: read enabled state, bound tiers and unfiltered eligibility without writing or running worker probes. */
976
+ function workersDoctorView({
977
+ env = process.env, projectRoot, repoRoot = REPO_ROOT, descriptors, now,
978
+ } = {}) {
979
+ const reg = loadRegistry({ repoRoot });
980
+ const root = require('./lifecycle/root.js').projectRoot(projectRoot);
981
+ const { activeWorkers } = require('./worker/active.js');
982
+ // Readiness already requires health.ts; load it only after this module has initialized.
983
+ const { select, enabledOf, workerDescriptors } = require('./worker/readiness.js');
984
+ const { readBindings, resolveBinding } = require('./worker/models.js');
985
+ const active = activeWorkers({ registry: reg, env, projectRoot: root });
986
+ const bindings = readBindings({ env, projectRoot: root });
987
+ const entries = workerDescriptors({ descriptors, registry: reg });
988
+ const tiers = reg.dispatchTiers?.tiers ?? [];
989
+ const selection = select({
990
+ descriptors, registry: reg, env, cwd: projectRoot, root, now, needs: [], includeDisabled: true,
991
+ });
992
+ const rows = selection.candidates.map(({ harness, eligible, gaps } ) => {
993
+ const gap = gaps[0];
994
+ const descriptor = entries.find((entry ) => entry.harness === harness)?.worker;
995
+ const bound = tiers.flatMap((tier ) => {
996
+ const resolved = resolveBinding({ worker: harness, tier, bindings, descriptor });
997
+ return resolved.source === 'binding' ? [{ tier, flag: resolved.flag }] : [];
998
+ });
999
+ return {
1000
+ harness,
1001
+ enabled: enabledOf(active, harness),
1002
+ eligible,
1003
+ firstGap: gap ? { requirement: gap.requirement, result: gap.result, reason: gap.reason } : null,
1004
+ bound,
1005
+ };
1006
+ });
1007
+ return {
1008
+ state: active.state,
1009
+ origin: active.origin,
1010
+ inSession: active.inSession,
1011
+ reason: active.reason,
1012
+ dangling: active.dangling,
1013
+ bindings: { state: bindings.state, reason: bindings.reason },
1014
+ rows,
1015
+ };
1016
+ }
1017
+
1018
+ /** IC-005: read observed catalogue, login states, derived billing and enabled models without running probes. */
1019
+ function workerPoolDoctorView({
1020
+ env = process.env, projectRoot, repoRoot = REPO_ROOT,
1021
+ } = {}) {
1022
+ const { readCatalogue } = require('./models/catalogue.js');
1023
+ const { readAllowedModels } = require('./worker/models.js');
1024
+ const { readWorkerBilling, effectiveBilling } = require('./settings/worker-billing.js');
1025
+ const { HARNESS_IDS } = require('./worker/probes/index.js');
1026
+ const reg = loadRegistry({ repoRoot });
1027
+ const root = require('./lifecycle/root.js').projectRoot(projectRoot);
1028
+
1029
+ const catalogue = readCatalogue(root);
1030
+ const allowed = readAllowedModels({ projectRoot: root }) || [];
1031
+ const workerIds = reg.harnesses.map((h ) => h.id);
1032
+ const billingRecord = readWorkerBilling(env, { workerIds });
1033
+
1034
+ const observedModelIds = new Set ();
1035
+ const harnesses = [];
1036
+ const allHarnessIds = Array.from(new Set([...HARNESS_IDS, ...Object.keys(catalogue?.harnesses || {})]));
1037
+
1038
+ for (const id of allHarnessIds) {
1039
+ const catEntry = catalogue?.harnesses?.[id];
1040
+ const regEntry = reg.harnesses.find((h ) => h.id === id);
1041
+ const descBilling = regEntry?.worker?.billing ?? 'unknown';
1042
+
1043
+ if (catEntry) {
1044
+ for (const m of catEntry.offeredModels || []) {
1045
+ if (m && m.id) observedModelIds.add(m.id);
1046
+ }
1047
+ const effBilling = effectiveBilling(descBilling, id, billingRecord, catalogue);
1048
+ harnesses.push({
1049
+ harness: id,
1050
+ binary: catEntry.binary || id,
1051
+ installed: Boolean(catEntry.installed),
1052
+ loginState: catEntry.loginState,
1053
+ reloginHint: catEntry.reloginHint || null,
1054
+ detectedMode: catEntry.detectedMode,
1055
+ effectiveBilling: effBilling,
1056
+ offeredCount: (catEntry.offeredModels || []).length,
1057
+ });
1058
+ } else {
1059
+ const effBilling = effectiveBilling(descBilling, id, billingRecord, null);
1060
+ harnesses.push({
1061
+ harness: id,
1062
+ binary: id,
1063
+ installed: false,
1064
+ loginState: 'uncalibrated',
1065
+ reloginHint: null,
1066
+ detectedMode: 'unverified',
1067
+ effectiveBilling: effBilling,
1068
+ offeredCount: 0,
1069
+ });
1070
+ }
1071
+ }
1072
+
1073
+ let enabledCount = 0;
1074
+ for (const mId of observedModelIds) {
1075
+ if (allowed.includes(mId)) enabledCount++;
1076
+ }
1077
+
1078
+ return {
1079
+ state: catalogue ? 'synced' : 'unsynced',
1080
+ syncedAt: catalogue?.syncedAt || null,
1081
+ harnesses,
1082
+ models: {
1083
+ enabledCount,
1084
+ listedCount: observedModelIds.size,
1085
+ enabled: allowed,
1086
+ },
1087
+ };
1088
+ }
1089
+
758
1090
  /**
759
1091
  * Handles `doflow doctor` — installation and capability health.
760
1092
  * @param {Object} options
@@ -765,11 +1097,159 @@ const FRESHNESS_MARK = Object.freeze({
765
1097
  * installed harness's skills cannot reach a runtime
766
1098
  */
767
1099
  async function handleDoctorCommand({ json = false, repoRoot, projectRoot = process.cwd(), ...rest } = {}) {
768
- const report = buildHealthReport({ repoRoot, projectRoot, ...rest });
1100
+ const root = repoRoot || REPO_ROOT;
1101
+ const activeRouter = rest.router || new CapabilityRouter({ repoRoot: root, projectRoot });
1102
+ const report = buildHealthReport({ repoRoot: root, projectRoot, router: activeRouter, ...rest });
1103
+
1104
+ // Probe local capability providers asynchronously (REV-1)
1105
+ for (const capability of Object.values (activeRouter.capabilities || {})) {
1106
+ for (const provider of capability.providers || []) {
1107
+ if (!provider || !provider.id) continue;
1108
+ if (provider.kind === 'http' || provider.kind === 'cli') {
1109
+ const localRes = await probeLocalProvider(provider, {
1110
+ router: activeRouter,
1111
+ execFileImpl: rest.execFileImpl,
1112
+ fetchImpl: rest.fetchImpl,
1113
+ });
1114
+ const provIdx = report.providers.findIndex((p ) => p.id === provider.id);
1115
+ if (provIdx !== -1) {
1116
+ report.providers[provIdx] = { ...report.providers[provIdx], ...localRes };
1117
+ } else {
1118
+ report.providers.push(localRes);
1119
+ }
1120
+ }
1121
+ }
1122
+ }
1123
+
1124
+ // Refresh report.capabilities with updated probe states
1125
+ const probeMap = {};
1126
+ for (const p of report.providers) {
1127
+ probeMap[p.id] = p;
1128
+ }
1129
+ for (const cap of report.capabilities) {
1130
+ const capDef = activeRouter.capabilities[cap.capability];
1131
+ if (capDef && Array.isArray(capDef.providers)) {
1132
+ const declared = capDef.providers.map((p ) => probeMap[p.id]).filter(Boolean);
1133
+ const rolled = capabilityStatus(declared);
1134
+ cap.status = rolled.status;
1135
+ cap.activeProvider = rolled.activeProvider;
1136
+ cap.providers = declared.map((p ) => ({ id: p.id, status: p.status, detail: p.detail }));
1137
+ }
1138
+ }
1139
+
1140
+ // Refresh summary, findings, warnings, and effective identity
1141
+ report.summary.healthy = report.providers.filter((p ) => p.status === HEALTHY).length;
1142
+ report.summary.unhealthy = report.providers.filter((p ) => p.status === UNHEALTHY).length;
1143
+ report.summary.absent = report.providers.filter((p ) => p.status === ABSENT).length;
1144
+ report.summary.unverified = report.providers.filter((p ) => p.status === UNVERIFIED).length;
1145
+ report.summary.probed = report.providers.length;
1146
+
1147
+ const nonProviderFindings = report.findings.filter(
1148
+ (f ) => f.kind !== 'provider-unhealthy' && f.kind !== 'capability-degraded'
1149
+ );
1150
+ report.findings = [
1151
+ ...nonProviderFindings,
1152
+ ...report.providers
1153
+ .filter((p ) => p.status === UNHEALTHY)
1154
+ .map((p ) => ({ kind: 'provider-unhealthy', subject: p.id, detail: p.detail })),
1155
+ ...report.capabilities
1156
+ .filter((c ) => c.status === 'DEGRADED')
1157
+ .map((c ) => ({ kind: 'capability-degraded', subject: c.capability, detail: 'every installed provider for this capability failed its probe' })),
1158
+ ];
1159
+
1160
+ const nonProviderWarnings = report.warnings.filter(
1161
+ (w ) => w.kind !== 'provider-unverified'
1162
+ );
1163
+ report.warnings = [
1164
+ ...nonProviderWarnings,
1165
+ ...report.providers
1166
+ .filter((p ) => p.status === UNVERIFIED)
1167
+ .map((p ) => ({ kind: 'provider-unverified', subject: p.id, detail: p.detail })),
1168
+ ];
1169
+
1170
+ report.identity.connectedCapabilities = report.capabilities
1171
+ .filter((c ) => c.activeProvider)
1172
+ .map((c ) => c.capability);
1173
+
769
1174
  const { buildAdviceDoctor, printAdviceDoctor } = require('./advice/doctor.js');
1175
+ const { buildSettingsReport, printSettingsReport } = require('./settings/cli.js');
770
1176
  const adviceSections = await buildAdviceDoctor({ repoRoot, projectRoot, fsImpl: rest.fsImpl });
1177
+ const settings = buildSettingsReport({ repoRoot, projectRoot, env: rest.env, fsImpl: rest.fsImpl });
771
1178
  report.decisionModel = adviceSections.decisionModel;
772
1179
  report.advice = adviceSections.advice;
1180
+ report.settings = settings;
1181
+ const { buildProfilesDoctor, printProfilesDoctor } = require('./profiles/doctor.js');
1182
+ const profilesSection = buildProfilesDoctor({ repoRoot, projectRoot, env: rest.env, fsImpl: rest.fsImpl });
1183
+ report.rolloutProfile = profilesSection;
1184
+ try {
1185
+ const { loadRuleLedger, getAblationCandidates } = require('./guidance/ablation.js');
1186
+ const ledger = loadRuleLedger({ repoRoot, fsImpl: rest.fsImpl });
1187
+ const candidates = getAblationCandidates(ledger);
1188
+ const neverAblateCount = ledger.rules.filter((r ) => r.neverAblate).length;
1189
+ report.guidance = {
1190
+ rulesCount: ledger.rules.length,
1191
+ neverAblateCount,
1192
+ candidateCount: candidates.length,
1193
+ };
1194
+ } catch (error ) {
1195
+ report.guidance = { state: 'unavailable', message: error?.message || String(error) };
1196
+ }
1197
+ try {
1198
+ const { AdmissionController, AdmissionError, resolveTeamSettings } = require('./team/admission.js');
1199
+ const { validateTaskBrief, validateTaskHandBack, quarantineInput, formatTaskBriefPrompt, EnvelopeError } = require('./team/envelope.js');
1200
+ const { assertReviewerSeparation, assertMechanicalPreChecks, ReviewGuardError, NeverReviewOwnCodeError, MechanicalPreChecksIncompleteError } = require('./team/review-guard.js');
1201
+ const teamController = new AdmissionController({ projectRoot, fsImpl: rest.fsImpl });
1202
+ const activeClaims = teamController.getActiveClaims();
1203
+ report.team = {
1204
+ state: 'ready',
1205
+ activeClaimsCount: activeClaims.length,
1206
+ writerCap: teamController.writerCap,
1207
+ maxAttempts: teamController.maxAttempts,
1208
+ _symbols: [
1209
+ AdmissionController,
1210
+ AdmissionError,
1211
+ resolveTeamSettings,
1212
+ validateTaskBrief,
1213
+ validateTaskHandBack,
1214
+ quarantineInput,
1215
+ formatTaskBriefPrompt,
1216
+ EnvelopeError,
1217
+ assertReviewerSeparation,
1218
+ assertMechanicalPreChecks,
1219
+ ReviewGuardError,
1220
+ NeverReviewOwnCodeError,
1221
+ MechanicalPreChecksIncompleteError,
1222
+ ],
1223
+ };
1224
+ } catch (error ) {
1225
+ report.team = { state: 'unavailable', message: error?.message || String(error) };
1226
+ }
1227
+ try {
1228
+ report.agentToolScope = agentToolScopeView(loadRegistry({ repoRoot, sources: 'skip' }));
1229
+ } catch (error ) {
1230
+ report.agentToolScope = { state: 'unavailable', message: error?.message || String(error) };
1231
+ }
1232
+ // Read on use (row 120 A.4): no probe runs, nothing is written, and a fault never breaks the report.
1233
+ let primaryText ;
1234
+ try {
1235
+ const { primaryDoctorView, resolveForReport } = require('./context/primary-command.js');
1236
+ const resolved = resolveForReport({ env: rest.env ?? process.env, projectRoot, repoRoot });
1237
+ report.primary = primaryDoctorView(resolved);
1238
+ primaryText = `${report.primary.state} ${report.primary.harness ?? '-'}/${report.primary.model ?? '-'} (${report.primary.origin}), probe ${report.primary.probe}`;
1239
+ } catch (error ) {
1240
+ report.primary = { state: 'unavailable', message: error?.message || String(error) };
1241
+ primaryText = `unavailable (${report.primary.message})`;
1242
+ }
1243
+ try {
1244
+ report.workers = workersDoctorView({ env: rest.env ?? process.env, projectRoot, repoRoot });
1245
+ } catch (error ) {
1246
+ report.workers = { state: 'unavailable', message: error?.message || String(error) };
1247
+ }
1248
+ try {
1249
+ report.workerPool = workerPoolDoctorView({ env: rest.env ?? process.env, projectRoot, repoRoot });
1250
+ } catch (error ) {
1251
+ report.workerPool = { state: 'unavailable', message: error?.message || String(error) };
1252
+ }
773
1253
 
774
1254
  if (json) {
775
1255
  console.log(JSON.stringify(report, null, 2));
@@ -779,6 +1259,7 @@ async function handleDoctorCommand({ json = false, repoRoot, projectRoot = proce
779
1259
  console.log('\nDoFlow System Diagnostics (doflow doctor)');
780
1260
  console.log('═'.repeat(72));
781
1261
  console.log('Health means a provider answered a probe, not that a binary exists on PATH.');
1262
+ if (report.upkeepSwitch === 'off') console.log('note: DOFLOW_UPKEEP=off in this environment: upkeep and lifecycle retention are skipped');
782
1263
 
783
1264
  console.log('\n[Effective Identity]');
784
1265
  const identity = report.identity;
@@ -789,6 +1270,11 @@ async function handleDoctorCommand({ json = false, repoRoot, projectRoot = proce
789
1270
  console.log('\n[Harness Adapters]');
790
1271
  for (const harness of report.harnesses) console.log(` ${harness.displayName.padEnd(28)} adapter PASS`);
791
1272
 
1273
+ console.log('\n[Agent Tool Scope]');
1274
+ if (Array.isArray(report.agentToolScope)) {
1275
+ for (const row of report.agentToolScope) console.log(` ${row.harness.padEnd(14)}${row.status === 'native' ? `native ${row.field}` : `unenforced projects no tool field: ${row.reason}`}`);
1276
+ } else console.log(` unavailable (${report.agentToolScope.message})`);
1277
+
792
1278
  console.log('\n[Runtime Reach]');
793
1279
  const reachLines = report.harnesses.flatMap((harness ) => harness.reach.map((row ) => {
794
1280
  const detail = ({ REACHED: row.root, 'NO-REACH': `fix: ${row.fix}`, 'N/A': row.reason } )[row.state];
@@ -826,11 +1312,69 @@ async function handleDoctorCommand({ json = false, repoRoot, projectRoot = proce
826
1312
  console.log(` test ${commandLine(commands.test)}`);
827
1313
  if (commands.reason) console.log(` note ${commands.reason}`);
828
1314
 
1315
+ console.log('\n[Primary and Workers]');
1316
+ console.log(` primary: ${primaryText}`);
1317
+ if (report.workers.state === 'unavailable') {
1318
+ console.log(` worker: unavailable (${report.workers.message})`);
1319
+ } else {
1320
+ if (report.workers.bindings.state === 'invalid') {
1321
+ console.log(` workers.models: invalid (${report.workers.bindings.reason})`);
1322
+ }
1323
+ if (report.workers.state === 'invalid') {
1324
+ console.log(` workers.enabled: invalid (${report.workers.reason})`);
1325
+ }
1326
+ for (const row of report.workers.rows) {
1327
+ const position = row.enabled.position === null ? '' : ` #${row.enabled.position}`;
1328
+ const eligibility = row.eligible ? 'eligible' : `ineligible (${row.firstGap.requirement})`;
1329
+ const bound = row.bound.map((item ) => `${item.tier}(${item.flag})`).join(', ') || 'none';
1330
+ console.log(` worker ${row.harness}: ${row.enabled.state}${position}, ${eligibility}; bound tiers: ${bound}`);
1331
+ }
1332
+ for (const id of report.workers.dangling) {
1333
+ console.log(` worker ${id}: dangling (no registry or enabled package knows it)`);
1334
+ }
1335
+ }
1336
+
1337
+ console.log('\n[Worker Pool & Models]');
1338
+ if (report.workerPool.state === 'unavailable') {
1339
+ console.log(` worker pool: unavailable (${report.workerPool.message})`);
1340
+ } else if (report.workerPool.state === 'unsynced') {
1341
+ console.log(' No observed catalogue. Run `doflow models --action sync` to probe worker pool.');
1342
+ console.log(` Models: ${report.workerPool.models.enabledCount} enabled, ${report.workerPool.models.listedCount} listed`);
1343
+ } else {
1344
+ for (const h of report.workerPool.harnesses) {
1345
+ const binStatus = h.installed ? 'installed' : (h.loginState === 'uncalibrated' ? 'unknown' : 'missing');
1346
+ const hint = h.loginState === 'logged-out' && h.reloginHint ? ` (re-login: ${h.reloginHint})` : '';
1347
+ const billSource = h.effectiveBilling.source ? ` (${h.effectiveBilling.source})` : '';
1348
+ console.log(` ${h.harness.padEnd(14)} binary: ${binStatus.padEnd(10)} ${h.loginState.padEnd(12)} mode: ${h.detectedMode.padEnd(14)} billing: ${h.effectiveBilling.value}${billSource}${hint}`);
1349
+ }
1350
+ console.log(` Models: ${report.workerPool.models.enabledCount} enabled, ${report.workerPool.models.listedCount} listed`);
1351
+ }
1352
+
829
1353
  printMcpSections(report.mcp);
1354
+ if (packagesWorthShowing(report.packages)) printPackagesSection(report.packages);
1355
+ printTranscriptsSection(report.transcripts);
830
1356
  printAdviceDoctor(adviceSections);
1357
+ printProfilesDoctor(profilesSection);
1358
+ printSettingsReport(settings);
1359
+ if (report.guidance) {
1360
+ console.log('\n[Guidance Rules]');
1361
+ if (report.guidance.state === 'unavailable') {
1362
+ console.log(` guidance: unavailable (${report.guidance.message})`);
1363
+ } else {
1364
+ console.log(` ${report.guidance.rulesCount} registered rules (${report.guidance.neverAblateCount} protected invariants, ${report.guidance.candidateCount} ablation candidates)`);
1365
+ }
1366
+ }
1367
+ if (report.team) {
1368
+ console.log('\n[Agent Teams & Coordination]');
1369
+ if (report.team.state === 'unavailable') {
1370
+ console.log(` team: unavailable (${report.team.message})`);
1371
+ } else {
1372
+ console.log(` writer cap: ${report.team.writerCap}, max attempts: ${report.team.maxAttempts}, active claims: ${report.team.activeClaimsCount}`);
1373
+ }
1374
+ }
831
1375
 
832
1376
  if (report.warnings.length) {
833
- console.log('\n[Warnings] — not blocking; a stale or unprobeable provider still degrades cleanly');
1377
+ console.log('\n[Warnings] — not blocking; a stale or unprobeable provider still degrades cleanly, and a Node below the security release of its line is named here');
834
1378
  for (const warning of report.warnings) console.log(` ${warning.subject.padEnd(18)}${warning.detail}`);
835
1379
  }
836
1380
 
@@ -853,11 +1397,15 @@ module.exports = {
853
1397
  runProbe,
854
1398
  probeProvider,
855
1399
  probeProviders,
1400
+ probeLocalProvider,
856
1401
  probeFreshness,
857
1402
  PROVIDER_ARTIFACTS,
858
1403
  newestSourceMtime,
859
1404
  detectProjectCommands,
860
1405
  buildHealthReport,
1406
+ nodeSecurityWarning,
1407
+ workersDoctorView,
1408
+ workerPoolDoctorView,
861
1409
  handleDoctorCommand,
862
1410
  PROBE_TIMEOUT_MS,
863
1411
  };