@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
@@ -22,11 +22,15 @@ 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');
33
+ const { readLedger, stateRoot } = require('../state/index.js');
30
34
 
31
35
  /** Answered a probe. */
32
36
  const HEALTHY = 'HEALTHY';
@@ -82,9 +86,10 @@ const SCAN_FILE_LIMIT = 4000;
82
86
  * @param {Object} [options]
83
87
  * @param {Function} [options.execFileImpl=execFileSync]
84
88
  * @param {number} [options.timeoutMs=PROBE_TIMEOUT_MS]
89
+ * @param {boolean} [options.retryOnTimeout] run the probe once more after a timeout; defaults to true for git only
85
90
  * @returns {{ok: boolean, output: string, durationMs: number, timedOut: boolean, error: string|null}}
86
91
  */
87
- function runProbe(command , { execFileImpl = execFileSync, timeoutMs = PROBE_TIMEOUT_MS } = {}) {
92
+ function runProbe(command , { execFileImpl = execFileSync, timeoutMs = PROBE_TIMEOUT_MS, retryOnTimeout = path.basename(String(command[0])) === 'git' } = {}) {
88
93
  const [bin, ...args] = command;
89
94
  const started = Number(process.hrtime.bigint() / 1000000n);
90
95
  try {
@@ -105,6 +110,9 @@ function runProbe(command , { execFileImpl = execFileSync, timeoutMs = PROBE
105
110
  // repairs — a timeout usually means a first-run index build, a non-zero exit usually means a
106
111
  // broken install — so they are reported apart rather than as one failure string.
107
112
  const timedOut = error.killed === true || error.code === 'ETIMEDOUT' || error.signal === 'SIGTERM';
113
+ // A cold macOS git shim can outlast the first probe and answer at once the second time, which made the
114
+ // first `doflow doctor` exit 1 falsely (FU-93wtsf). Only the git probe is retried, once.
115
+ if (timedOut && retryOnTimeout) return runProbe(command, { execFileImpl, timeoutMs, retryOnTimeout: false });
108
116
  return {
109
117
  ok: false,
110
118
  output: '',
@@ -243,6 +251,95 @@ function probeFreshness(providerId , { root = process.cwd(), homeDir = os.ho
243
251
  };
244
252
  }
245
253
 
254
+ async function probeLocalProvider(
255
+ provider ,
256
+ options = {}
257
+ ) {
258
+ const timeoutMs = options.timeoutMs ?? 1500;
259
+
260
+ if (provider.kind === 'http') {
261
+ const targetUrl = provider.healthEndpoint || provider.endpoint;
262
+ const { probeHttpEndpoint } = require('./capabilities/executor.js');
263
+ const probeRes = await probeHttpEndpoint(targetUrl, {
264
+ timeoutMs,
265
+ fetchImpl: options.fetchImpl,
266
+ });
267
+ if (probeRes.ok) {
268
+ return {
269
+ id: provider.id,
270
+ name: provider.name || provider.id,
271
+ kind: 'http',
272
+ status: 'HEALTHY',
273
+ endpoint: targetUrl,
274
+ answered: true,
275
+ durationMs: probeRes.durationMs,
276
+ detail: `HTTP ${probeRes.status}`,
277
+ };
278
+ }
279
+ if (probeRes.isUnavailable) {
280
+ return {
281
+ id: provider.id,
282
+ name: provider.name || provider.id,
283
+ kind: 'http',
284
+ status: 'UNAVAILABLE',
285
+ endpoint: targetUrl,
286
+ answered: false,
287
+ durationMs: probeRes.durationMs,
288
+ detail: `Local server at ${targetUrl} is not running`,
289
+ };
290
+ }
291
+ return {
292
+ id: provider.id,
293
+ name: provider.name || provider.id,
294
+ kind: 'http',
295
+ status: 'UNHEALTHY',
296
+ endpoint: targetUrl,
297
+ answered: probeRes.status !== undefined,
298
+ durationMs: probeRes.durationMs,
299
+ detail: probeRes.status ? `HTTP status ${probeRes.status}` : (probeRes.error || 'HTTP request failed'),
300
+ };
301
+ }
302
+
303
+ if (provider.kind === 'cli') {
304
+ const binary = provider.binary || provider.program;
305
+ const isAvail = options.router ? options.router.isBinaryAvailable(binary) : true;
306
+ if (!isAvail) {
307
+ return {
308
+ id: provider.id,
309
+ name: provider.name || provider.id,
310
+ kind: 'cli',
311
+ status: 'UNAVAILABLE',
312
+ binary,
313
+ answered: false,
314
+ durationMs: null,
315
+ detail: `Binary '${binary}' not found on PATH`,
316
+ };
317
+ }
318
+ const checkCommand = provider.checkCommand || [binary, '--version'];
319
+ const probe = runProbe(checkCommand, { execFileImpl: options.execFileImpl, timeoutMs });
320
+ return {
321
+ id: provider.id,
322
+ name: provider.name || provider.id,
323
+ kind: 'cli',
324
+ status: probe.ok ? 'HEALTHY' : 'UNHEALTHY',
325
+ binary,
326
+ answered: probe.ok,
327
+ durationMs: probe.durationMs,
328
+ detail: probe.ok ? banner(probe.output) : probe.error,
329
+ };
330
+ }
331
+
332
+ return {
333
+ id: provider.id,
334
+ name: provider.name || provider.id,
335
+ kind: provider.kind || 'native',
336
+ status: 'UNVERIFIED',
337
+ answered: false,
338
+ durationMs: null,
339
+ detail: 'unsupported probe kind',
340
+ };
341
+ }
342
+
246
343
  /**
247
344
  * Probes one provider declared in `capabilities.json`.
248
345
  * @param {Object} provider
@@ -257,10 +354,11 @@ function probeProvider(provider , { router, projectRoot = process.cwd(), exe
257
354
  if (!provider || !provider.id) {
258
355
  return { id: null, status: UNVERIFIED, detail: 'no provider declared' };
259
356
  }
357
+ const binary = provider.binary || provider.program || null;
260
358
  const result = {
261
359
  id: provider.id,
262
360
  name: provider.name || provider.id,
263
- binary: provider.binary || null,
361
+ binary,
264
362
  present: null,
265
363
  answered: false,
266
364
  status: UNVERIFIED,
@@ -270,22 +368,32 @@ function probeProvider(provider , { router, projectRoot = process.cwd(), exe
270
368
  freshness: null,
271
369
  };
272
370
 
273
- if (provider.binary) {
274
- result.present = router.isBinaryAvailable(provider.binary);
371
+ if (binary) {
372
+ result.present = router ? router.isBinaryAvailable(binary) : false;
275
373
  if (!result.present) {
276
374
  result.status = ABSENT;
277
- result.detail = `binary '${provider.binary}' is not on PATH`;
375
+ result.detail = `binary '${binary}' is not on PATH`;
376
+ return result;
377
+ }
378
+ }
379
+
380
+ if (provider.kind === 'http' || provider.endpoint) {
381
+ const { validateLoopbackUrl } = require('./capabilities/loopback.js');
382
+ const valid = validateLoopbackUrl(provider.endpoint);
383
+ if (!valid.ok) {
384
+ result.status = UNHEALTHY;
385
+ result.detail = `Disallowed endpoint: ${valid.reason}`;
278
386
  return result;
279
387
  }
280
388
  }
281
389
 
282
390
  const command = Array.isArray(provider.checkCommand) && provider.checkCommand.length
283
391
  ? provider.checkCommand
284
- : null;
392
+ : (provider.kind === 'cli' && binary ? [binary, '--version'] : null);
285
393
  if (!command) {
286
394
  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`
395
+ result.detail = binary
396
+ ? `binary '${binary}' is present but the registry declares no probe command, so DoFlow cannot establish that it answers`
289
397
  : 'declares neither a binary nor a probe command, so its function depends entirely on the project (see the project commands section)';
290
398
  return result;
291
399
  }
@@ -500,13 +608,29 @@ const byText = (a , b ) => (a < b ? -1 : a > b ? 1 : 0);
500
608
  */
501
609
  function mcpConsultationsView({ projectRoot, fsImpl } ) {
502
610
  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 = []; }
611
+ // Runs live flat or in `<slug>/` (115: a task id other than its feature's slug is namespaced).
612
+ const listed = [];
613
+ const readRunFile = (rel , name , namespace ) => {
614
+ let run;
615
+ try { run = JSON.parse(fsImpl.readFileSync(path.join(dir, rel), 'utf8')); } catch { run = null; }
616
+ listed.push({ rel, name, namespace, run });
617
+ };
618
+ let entries = [];
619
+ try { entries = fsImpl.readdirSync(dir, { withFileTypes: true }); } catch { entries = []; }
620
+ for (const entry of entries) if (entry.name.endsWith('.json') && !entry.isDirectory()) readRunFile(entry.name, entry.name, '');
621
+ for (const entry of entries) {
622
+ if (!entry.isDirectory() || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(entry.name) || entry.name.endsWith('.lock')) continue;
623
+ let inner = [];
624
+ try { inner = fsImpl.readdirSync(path.join(dir, entry.name), { withFileTypes: true }); } catch { inner = []; }
625
+ for (const file of inner) if (file.name.endsWith('.json') && !file.isDirectory()) readRunFile(`${entry.name}/${file.name}`, file.name, entry.name);
626
+ }
627
+ listed.sort((a, b) => byText(a.rel, b.rel));
628
+ // A flat run whose namespaced copy exists is that copy's seed, not a second run.
629
+ const files = listed.filter((one) => one.namespace !== '' || !listed.some((other) => other.namespace !== '' && other.name === one.name
630
+ && (typeof one.run?.featureSlug !== 'string' || one.run.featureSlug === other.namespace)));
505
631
  let unreadableRuns = 0;
506
632
  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; }
633
+ for (const { name, run } of files) {
510
634
  if (!run || typeof run !== 'object' || Array.isArray(run)) { unreadableRuns += 1; continue; }
511
635
  for (const item of Array.isArray(run.history) ? run.history : []) {
512
636
  const record = item && item.consultations;
@@ -555,6 +679,68 @@ function mcpView({ registry, projectRoot, fsImpl, adapters } ) {
555
679
  return { servers, ...(serversNote ? { serversNote } : {}), consultations: mcpConsultationsView({ projectRoot, fsImpl }) };
556
680
  }
557
681
 
682
+ /**
683
+ * The doctor's `packages` block (122 IC-008, IC-009): the grants file path and mtime, every grant with its state,
684
+ * and the counts held in `state.json`. A trust or exempt grant a revocation covers (or any, when `state.json` is
685
+ * unreadable) is listed `revoked`. Read-only and not blocking; a failure degrades to a note.
686
+ */
687
+ function packagesView({ projectRoot, fsImpl, env, homeDir, tmpDir } ) {
688
+ try {
689
+ // The trust root is the project detected from the directory reported on, never that directory itself (IC-008).
690
+ const home = homeDir ?? (typeof env?.HOME === 'string' && path.isAbsolute(env.HOME) ? env.HOME : os.homedir());
691
+ const grants = readGrants({ env, fsImpl, projectRoot: detectedProjectRoot({ cwd: projectRoot, homeDir: home, fsImpl }), tmpDir });
692
+ const state = readState({ homeDir, fsImpl });
693
+ const rows = grants.entries.map((entry ) => {
694
+ const revoked = entry.state === 'valid' && entry.grant !== 'fetch' && grantRevoked(state, entry);
695
+ const target = entry.grant === 'fetch' ? (entry.source.url || entry.source.name) : `${entry.package}@${entry.version}`;
696
+ 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 } : {}) };
697
+ });
698
+ return {
699
+ grants: { path: grants.path, noConfigHome: grants.noConfigHome, exists: grants.exists, mtime: grants.mtime, rows, findings: grants.findings },
700
+ suspendedProjected: state.state.suspended.filter((item ) => item.projected).map((item ) => item.package).sort(),
701
+ state: {
702
+ path: state.path, exists: state.exists, unreadable: state.unreadable, findings: state.findings,
703
+ revoked: state.state.revoked.length, suspended: state.state.suspended.length, removed: state.state.removed.length,
704
+ },
705
+ };
706
+ } catch (error ) {
707
+ return { note: `package grants unavailable: ${error.message}` };
708
+ }
709
+ }
710
+
711
+ function printPackagesSection(packages ) {
712
+ console.log('\n[Packages] — person grants and DoFlow state; not blocking');
713
+ if (packages.note) { console.log(` ${packages.note}`); return; }
714
+ const { grants, state } = packages;
715
+ if (grants.noConfigHome) console.log(' grants no config home (HOME and XDG_CONFIG_HOME unset): no grant can exist');
716
+ else if (!grants.exists) console.log(` grants none (no file at ${grants.path})`);
717
+ else {
718
+ console.log(` grants ${grants.path} (modified ${grants.mtime})`);
719
+ 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})` : ''}`);
720
+ for (const item of grants.findings) console.log(` ${item.code}: ${item.message}`);
721
+ }
722
+ if (state.unreadable) console.log(` state ${state.path} is unreadable: every package counts as revoked (run: doflow package --action verify --repair)`);
723
+ else if (state.revoked || state.suspended || state.removed) console.log(` state ${state.revoked} revoked, ${state.suspended} suspended, ${state.removed} removed (${state.path})`);
724
+ for (const id of packages.suspendedProjected ?? []) {
725
+ console.log(` suspended ${id} still has projected files (run: doflow package --action verify)`);
726
+ }
727
+ }
728
+
729
+ /** Whether the text report shows `[Packages]`: only when there is something to say. */
730
+ /** The `[Transcripts]` row (093 V.1): shown only when the registry is stale or drifted; never a finding. */
731
+ function printTranscriptsSection(transcripts ) {
732
+ if (transcripts.state === 'ok') return;
733
+ console.log('\n[Transcripts] — transcript locator registry; not blocking');
734
+ if (transcripts.state === 'drift') {
735
+ console.log(' transcripts.json drift: the transcript feature is off');
736
+ for (const error of transcripts.errors) console.log(` ${error}`);
737
+ return;
738
+ }
739
+ for (const item of transcripts.stale) console.log(` ${item.harness.padEnd(18)}verifiedOn ${item.verifiedOn} (${item.ageDays} days ago): re-verify its locations`);
740
+ }
741
+
742
+ const packagesWorthShowing = (packages ) => Boolean(packages.note || packages.grants.exists || packages.state.exists);
743
+
558
744
  const MCP_STATE_MARK = Object.freeze({
559
745
  selected: '✓ selected',
560
746
  configured: '✓ configured',
@@ -602,7 +788,86 @@ function printMcpSections(mcp ) {
602
788
  if (unreadableRuns) console.log(` ${unreadableRuns} run file(s) could not be read`);
603
789
  }
604
790
 
605
- function buildHealthReport({ repoRoot, projectRoot = process.cwd(), router, execFileImpl, fsImpl = fs, detector, adapters } = {}) {
791
+ /** The first release of each Node line that carries the 2026-10-21 security fix (CVE-2026-111497). */
792
+ const NODE_PATCHED_RELEASE = Object.freeze({ 24: [24, 22, 1], 26: [26, 12, 1] });
793
+
794
+ /**
795
+ * A warning when `version` (a `process.version` string) is a release of a Node line below that
796
+ * line's patched release; null for a patched release and for a line this table does not know.
797
+ * The floor in `bin/doflow.js` already refuses anything under 24.22.1, so in practice this fires for
798
+ * 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
799
+ * patched release (26.12.1-rc.1) counts as below it.
800
+ */
801
+ function nodeSecurityWarning(version ) {
802
+ const parts = /^v?(\d+)\.(\d+)\.(\d+)(-.*)?$/.exec(String(version));
803
+ if (!parts) return null;
804
+ const running = [Number(parts[1]), Number(parts[2]), Number(parts[3])];
805
+ const patched = NODE_PATCHED_RELEASE[running[0]];
806
+ if (!patched) return null;
807
+ const order = patched.reduce((result, part, i) => result || Math.sign(running[i] - part), 0);
808
+ const below = order < 0 || (order === 0 && Boolean(parts[4])); // a prerelease of the patched release sorts below it
809
+ if (!below) return null;
810
+ return {
811
+ kind: 'node-security-release',
812
+ subject: 'node',
813
+ 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`,
814
+ };
815
+ }
816
+
817
+ /**
818
+ * The Claude permission lists a user's own array hides. `mergeSettings` keeps an existing array whole
819
+ * (a union would widen the user's own permissions), so a settings.json that already had a
820
+ * `permissions.allow`, `deny` or `ask` array never receives DoFlow's entries for that list. A file is
821
+ * judged only when the install ledger of its scope records a Claude `settings.json` projection (the
822
+ * ledger, not hook presence, so an install with Claude outside `hooks.harnesses` is judged too); the
823
+ * ledgers read are the global one and each project one on the walk from `projectRoot` up to the home
824
+ * directory, the same walk the reach check makes. The entries named are the shipped ones missing from
825
+ * the user's array, so the person can add them or leave the list as it is.
826
+ */
827
+ function claudePermissionWarnings({ repoRoot, projectRoot, homeDir, fsImpl = fs } ) {
828
+ let managed ;
829
+ try {
830
+ managed = JSON.parse(fsImpl.readFileSync(path.join(repoRoot, 'core', 'harnesses', 'claude', 'settings', 'settings.json'), 'utf8'));
831
+ } catch { return []; }
832
+ const lists = managed?.permissions && typeof managed.permissions === 'object' ? managed.permissions : {};
833
+ const projects = (() => {
834
+ const found = [];
835
+ const home = path.resolve(homeDir);
836
+ for (let dir = path.resolve(projectRoot); dir !== home; dir = path.dirname(dir)) {
837
+ found.push(dir);
838
+ if (path.dirname(dir) === dir) break;
839
+ }
840
+ return found;
841
+ })();
842
+ const scopes = [
843
+ { installRoot: path.resolve(homeDir), stateDir: stateRoot({ scope: 'global', homeDir }) },
844
+ ...projects.map((installRoot) => ({ installRoot, stateDir: path.join(installRoot, '.doflow', 'state') })),
845
+ ];
846
+ const warnings = [];
847
+ for (const { installRoot, stateDir } of scopes) {
848
+ let ledger ;
849
+ try { ledger = readLedger(stateDir, { fsImpl }); } catch { continue; }
850
+ const projected = (ledger?.resources || []).some((resource ) => resource?.harness === 'claude' && resource.kind === 'settings-file' && resource.identity === 'settings.json');
851
+ if (!projected) continue;
852
+ const file = path.join(installRoot, '.claude', 'settings.json');
853
+ let current ;
854
+ try { current = JSON.parse(fsImpl.readFileSync(file, 'utf8')); } catch { continue; }
855
+ for (const [list, shipped] of Object.entries (lists)) {
856
+ const own = current?.permissions?.[list];
857
+ if (!Array.isArray(own) || !Array.isArray(shipped)) continue;
858
+ const missing = shipped.filter((entry ) => !own.includes(entry));
859
+ if (!missing.length) continue;
860
+ warnings.push({
861
+ kind: 'claude-permissions-user-array',
862
+ subject: `${file}: permissions.${list}`,
863
+ detail: `your own permissions.${list} list wins whole, so DoFlow's entries are absent from it (${missing.join(', ')}); DoFlow never merges into a list you wrote, so add them yourself if you want that behaviour`,
864
+ });
865
+ }
866
+ }
867
+ return warnings;
868
+ }
869
+
870
+ function buildHealthReport({ repoRoot, projectRoot = process.cwd(), router, execFileImpl, fsImpl = fs, detector, adapters, env = process.env, nodeVersion = process.version, homeDir, tmpDir, now } = {}) {
606
871
  const root = repoRoot || REPO_ROOT;
607
872
  const registry = loadRegistry({ repoRoot: root });
608
873
  const activeRouter = router || new CapabilityRouter({ repoRoot: root, projectRoot });
@@ -665,7 +930,11 @@ function buildHealthReport({ repoRoot, projectRoot = process.cwd(), router, exec
665
930
  .filter((row ) => row.state === 'NO-REACH')
666
931
  .map((row ) => ({ kind: 'runtime-no-reach', subject: `${row.harness}:${row.scope}`, detail: `${row.reason}; fix: ${row.fix}` })),
667
932
  ];
933
+ const nodeWarning = nodeSecurityWarning(nodeVersion);
934
+ const home = homeDir ?? (typeof env?.HOME === 'string' && path.isAbsolute(env.HOME) ? env.HOME : os.homedir());
668
935
  const warnings = [
936
+ ...(nodeWarning ? [nodeWarning] : []),
937
+ ...claudePermissionWarnings({ repoRoot: root, projectRoot, homeDir: home, fsImpl }),
669
938
  ...freshness
670
939
  .filter((item) => item.state === 'STALE')
671
940
  .map((item) => ({ kind: 'index-stale', subject: item.providerId, detail: `${item.label} is behind the working tree; refresh with: ${item.refresh}` })),
@@ -704,6 +973,8 @@ function buildHealthReport({ repoRoot, projectRoot = process.cwd(), router, exec
704
973
  freshness,
705
974
  project,
706
975
  mcp: mcpView({ registry, projectRoot, fsImpl, adapters }),
976
+ packages: packagesView({ projectRoot, fsImpl, env, homeDir, tmpDir }),
977
+ transcripts: transcriptsDoctorView({ repoRoot: root, fsImpl, now }),
707
978
  findings,
708
979
  warnings,
709
980
  summary: {
@@ -713,6 +984,8 @@ function buildHealthReport({ repoRoot, projectRoot = process.cwd(), router, exec
713
984
  absent: Object.values(probes).filter((probe) => probe.status === ABSENT).length,
714
985
  unverified: Object.values(probes).filter((probe) => probe.status === UNVERIFIED).length,
715
986
  },
987
+ // IC-006: the read switch of doctor's own environment, only when set (additive key).
988
+ ...(env.DOFLOW_UPKEEP === 'off' ? { upkeepSwitch: 'off' } : {}),
716
989
  };
717
990
  }
718
991
 
@@ -755,6 +1028,121 @@ const FRESHNESS_MARK = Object.freeze({
755
1028
  UNKNOWN: '? UNKNOWN',
756
1029
  });
757
1030
 
1031
+ /** IC-014: read enabled state, bound tiers and unfiltered eligibility without writing or running worker probes. */
1032
+ function workersDoctorView({
1033
+ env = process.env, projectRoot, repoRoot = REPO_ROOT, descriptors, now,
1034
+ } = {}) {
1035
+ const reg = loadRegistry({ repoRoot });
1036
+ const root = require('./lifecycle/root.js').projectRoot(projectRoot);
1037
+ const { activeWorkers } = require('./worker/active.js');
1038
+ // Readiness already requires health.ts; load it only after this module has initialized.
1039
+ const { select, enabledOf, workerDescriptors } = require('./worker/readiness.js');
1040
+ const { readBindings, resolveBinding } = require('./worker/models.js');
1041
+ const active = activeWorkers({ registry: reg, env, projectRoot: root });
1042
+ const bindings = readBindings({ env, projectRoot: root });
1043
+ const entries = workerDescriptors({ descriptors, registry: reg });
1044
+ const tiers = reg.dispatchTiers?.tiers ?? [];
1045
+ const selection = select({
1046
+ descriptors, registry: reg, env, cwd: projectRoot, root, now, needs: [], includeDisabled: true,
1047
+ });
1048
+ const rows = selection.candidates.map(({ harness, eligible, gaps } ) => {
1049
+ const gap = gaps[0];
1050
+ const descriptor = entries.find((entry ) => entry.harness === harness)?.worker;
1051
+ const bound = tiers.flatMap((tier ) => {
1052
+ const resolved = resolveBinding({ worker: harness, tier, bindings, descriptor });
1053
+ return resolved.source === 'binding' ? [{ tier, flag: resolved.flag }] : [];
1054
+ });
1055
+ return {
1056
+ harness,
1057
+ enabled: enabledOf(active, harness),
1058
+ eligible,
1059
+ firstGap: gap ? { requirement: gap.requirement, result: gap.result, reason: gap.reason } : null,
1060
+ bound,
1061
+ };
1062
+ });
1063
+ return {
1064
+ state: active.state,
1065
+ origin: active.origin,
1066
+ inSession: active.inSession,
1067
+ reason: active.reason,
1068
+ dangling: active.dangling,
1069
+ bindings: { state: bindings.state, reason: bindings.reason },
1070
+ rows,
1071
+ };
1072
+ }
1073
+
1074
+ /** IC-005: read observed catalogue, login states, derived billing and enabled models without running probes. */
1075
+ function workerPoolDoctorView({
1076
+ env = process.env, projectRoot, repoRoot = REPO_ROOT,
1077
+ } = {}) {
1078
+ const { readCatalogue } = require('./models/catalogue.js');
1079
+ const { readAllowedModels } = require('./worker/models.js');
1080
+ const { readWorkerBilling, effectiveBilling } = require('./settings/worker-billing.js');
1081
+ const { HARNESS_IDS } = require('./worker/probes/index.js');
1082
+ const reg = loadRegistry({ repoRoot });
1083
+ const root = require('./lifecycle/root.js').projectRoot(projectRoot);
1084
+
1085
+ const catalogue = readCatalogue(root);
1086
+ const allowed = readAllowedModels({ projectRoot: root }) || [];
1087
+ const workerIds = reg.harnesses.map((h ) => h.id);
1088
+ const billingRecord = readWorkerBilling(env, { workerIds });
1089
+
1090
+ const observedModelIds = new Set ();
1091
+ const harnesses = [];
1092
+ const allHarnessIds = Array.from(new Set([...HARNESS_IDS, ...Object.keys(catalogue?.harnesses || {})]));
1093
+
1094
+ for (const id of allHarnessIds) {
1095
+ const catEntry = catalogue?.harnesses?.[id];
1096
+ const regEntry = reg.harnesses.find((h ) => h.id === id);
1097
+ const descBilling = regEntry?.worker?.billing ?? 'unknown';
1098
+
1099
+ if (catEntry) {
1100
+ for (const m of catEntry.offeredModels || []) {
1101
+ if (m && m.id) observedModelIds.add(m.id);
1102
+ }
1103
+ const effBilling = effectiveBilling(descBilling, id, billingRecord, catalogue);
1104
+ harnesses.push({
1105
+ harness: id,
1106
+ binary: catEntry.binary || id,
1107
+ installed: Boolean(catEntry.installed),
1108
+ loginState: catEntry.loginState,
1109
+ reloginHint: catEntry.reloginHint || null,
1110
+ detectedMode: catEntry.detectedMode,
1111
+ effectiveBilling: effBilling,
1112
+ offeredCount: (catEntry.offeredModels || []).length,
1113
+ });
1114
+ } else {
1115
+ const effBilling = effectiveBilling(descBilling, id, billingRecord, null);
1116
+ harnesses.push({
1117
+ harness: id,
1118
+ binary: id,
1119
+ installed: false,
1120
+ loginState: 'uncalibrated',
1121
+ reloginHint: null,
1122
+ detectedMode: 'unverified',
1123
+ effectiveBilling: effBilling,
1124
+ offeredCount: 0,
1125
+ });
1126
+ }
1127
+ }
1128
+
1129
+ let enabledCount = 0;
1130
+ for (const mId of observedModelIds) {
1131
+ if (allowed.includes(mId)) enabledCount++;
1132
+ }
1133
+
1134
+ return {
1135
+ state: catalogue ? 'synced' : 'unsynced',
1136
+ syncedAt: catalogue?.syncedAt || null,
1137
+ harnesses,
1138
+ models: {
1139
+ enabledCount,
1140
+ listedCount: observedModelIds.size,
1141
+ enabled: allowed,
1142
+ },
1143
+ };
1144
+ }
1145
+
758
1146
  /**
759
1147
  * Handles `doflow doctor` — installation and capability health.
760
1148
  * @param {Object} options
@@ -765,11 +1153,159 @@ const FRESHNESS_MARK = Object.freeze({
765
1153
  * installed harness's skills cannot reach a runtime
766
1154
  */
767
1155
  async function handleDoctorCommand({ json = false, repoRoot, projectRoot = process.cwd(), ...rest } = {}) {
768
- const report = buildHealthReport({ repoRoot, projectRoot, ...rest });
1156
+ const root = repoRoot || REPO_ROOT;
1157
+ const activeRouter = rest.router || new CapabilityRouter({ repoRoot: root, projectRoot });
1158
+ const report = buildHealthReport({ repoRoot: root, projectRoot, router: activeRouter, ...rest });
1159
+
1160
+ // Probe local capability providers asynchronously (REV-1)
1161
+ for (const capability of Object.values (activeRouter.capabilities || {})) {
1162
+ for (const provider of capability.providers || []) {
1163
+ if (!provider || !provider.id) continue;
1164
+ if (provider.kind === 'http' || provider.kind === 'cli') {
1165
+ const localRes = await probeLocalProvider(provider, {
1166
+ router: activeRouter,
1167
+ execFileImpl: rest.execFileImpl,
1168
+ fetchImpl: rest.fetchImpl,
1169
+ });
1170
+ const provIdx = report.providers.findIndex((p ) => p.id === provider.id);
1171
+ if (provIdx !== -1) {
1172
+ report.providers[provIdx] = { ...report.providers[provIdx], ...localRes };
1173
+ } else {
1174
+ report.providers.push(localRes);
1175
+ }
1176
+ }
1177
+ }
1178
+ }
1179
+
1180
+ // Refresh report.capabilities with updated probe states
1181
+ const probeMap = {};
1182
+ for (const p of report.providers) {
1183
+ probeMap[p.id] = p;
1184
+ }
1185
+ for (const cap of report.capabilities) {
1186
+ const capDef = activeRouter.capabilities[cap.capability];
1187
+ if (capDef && Array.isArray(capDef.providers)) {
1188
+ const declared = capDef.providers.map((p ) => probeMap[p.id]).filter(Boolean);
1189
+ const rolled = capabilityStatus(declared);
1190
+ cap.status = rolled.status;
1191
+ cap.activeProvider = rolled.activeProvider;
1192
+ cap.providers = declared.map((p ) => ({ id: p.id, status: p.status, detail: p.detail }));
1193
+ }
1194
+ }
1195
+
1196
+ // Refresh summary, findings, warnings, and effective identity
1197
+ report.summary.healthy = report.providers.filter((p ) => p.status === HEALTHY).length;
1198
+ report.summary.unhealthy = report.providers.filter((p ) => p.status === UNHEALTHY).length;
1199
+ report.summary.absent = report.providers.filter((p ) => p.status === ABSENT).length;
1200
+ report.summary.unverified = report.providers.filter((p ) => p.status === UNVERIFIED).length;
1201
+ report.summary.probed = report.providers.length;
1202
+
1203
+ const nonProviderFindings = report.findings.filter(
1204
+ (f ) => f.kind !== 'provider-unhealthy' && f.kind !== 'capability-degraded'
1205
+ );
1206
+ report.findings = [
1207
+ ...nonProviderFindings,
1208
+ ...report.providers
1209
+ .filter((p ) => p.status === UNHEALTHY)
1210
+ .map((p ) => ({ kind: 'provider-unhealthy', subject: p.id, detail: p.detail })),
1211
+ ...report.capabilities
1212
+ .filter((c ) => c.status === 'DEGRADED')
1213
+ .map((c ) => ({ kind: 'capability-degraded', subject: c.capability, detail: 'every installed provider for this capability failed its probe' })),
1214
+ ];
1215
+
1216
+ const nonProviderWarnings = report.warnings.filter(
1217
+ (w ) => w.kind !== 'provider-unverified'
1218
+ );
1219
+ report.warnings = [
1220
+ ...nonProviderWarnings,
1221
+ ...report.providers
1222
+ .filter((p ) => p.status === UNVERIFIED)
1223
+ .map((p ) => ({ kind: 'provider-unverified', subject: p.id, detail: p.detail })),
1224
+ ];
1225
+
1226
+ report.identity.connectedCapabilities = report.capabilities
1227
+ .filter((c ) => c.activeProvider)
1228
+ .map((c ) => c.capability);
1229
+
769
1230
  const { buildAdviceDoctor, printAdviceDoctor } = require('./advice/doctor.js');
1231
+ const { buildSettingsReport, printSettingsReport } = require('./settings/cli.js');
770
1232
  const adviceSections = await buildAdviceDoctor({ repoRoot, projectRoot, fsImpl: rest.fsImpl });
1233
+ const settings = buildSettingsReport({ repoRoot, projectRoot, env: rest.env, fsImpl: rest.fsImpl });
771
1234
  report.decisionModel = adviceSections.decisionModel;
772
1235
  report.advice = adviceSections.advice;
1236
+ report.settings = settings;
1237
+ const { buildProfilesDoctor, printProfilesDoctor } = require('./profiles/doctor.js');
1238
+ const profilesSection = buildProfilesDoctor({ repoRoot, projectRoot, env: rest.env, fsImpl: rest.fsImpl });
1239
+ report.rolloutProfile = profilesSection;
1240
+ try {
1241
+ const { loadRuleLedger, getAblationCandidates } = require('./guidance/ablation.js');
1242
+ const ledger = loadRuleLedger({ repoRoot, fsImpl: rest.fsImpl });
1243
+ const candidates = getAblationCandidates(ledger);
1244
+ const neverAblateCount = ledger.rules.filter((r ) => r.neverAblate).length;
1245
+ report.guidance = {
1246
+ rulesCount: ledger.rules.length,
1247
+ neverAblateCount,
1248
+ candidateCount: candidates.length,
1249
+ };
1250
+ } catch (error ) {
1251
+ report.guidance = { state: 'unavailable', message: error?.message || String(error) };
1252
+ }
1253
+ try {
1254
+ const { AdmissionController, AdmissionError, resolveTeamSettings } = require('./team/admission.js');
1255
+ const { validateTaskBrief, validateTaskHandBack, quarantineInput, formatTaskBriefPrompt, EnvelopeError } = require('./team/envelope.js');
1256
+ const { assertReviewerSeparation, assertMechanicalPreChecks, ReviewGuardError, NeverReviewOwnCodeError, MechanicalPreChecksIncompleteError } = require('./team/review-guard.js');
1257
+ const teamController = new AdmissionController({ projectRoot, fsImpl: rest.fsImpl });
1258
+ const activeClaims = teamController.getActiveClaims();
1259
+ report.team = {
1260
+ state: 'ready',
1261
+ activeClaimsCount: activeClaims.length,
1262
+ writerCap: teamController.writerCap,
1263
+ maxAttempts: teamController.maxAttempts,
1264
+ _symbols: [
1265
+ AdmissionController,
1266
+ AdmissionError,
1267
+ resolveTeamSettings,
1268
+ validateTaskBrief,
1269
+ validateTaskHandBack,
1270
+ quarantineInput,
1271
+ formatTaskBriefPrompt,
1272
+ EnvelopeError,
1273
+ assertReviewerSeparation,
1274
+ assertMechanicalPreChecks,
1275
+ ReviewGuardError,
1276
+ NeverReviewOwnCodeError,
1277
+ MechanicalPreChecksIncompleteError,
1278
+ ],
1279
+ };
1280
+ } catch (error ) {
1281
+ report.team = { state: 'unavailable', message: error?.message || String(error) };
1282
+ }
1283
+ try {
1284
+ report.agentToolScope = agentToolScopeView(loadRegistry({ repoRoot, sources: 'skip' }));
1285
+ } catch (error ) {
1286
+ report.agentToolScope = { state: 'unavailable', message: error?.message || String(error) };
1287
+ }
1288
+ // Read on use (row 120 A.4): no probe runs, nothing is written, and a fault never breaks the report.
1289
+ let primaryText ;
1290
+ try {
1291
+ const { primaryDoctorView, resolveForReport } = require('./context/primary-command.js');
1292
+ const resolved = resolveForReport({ env: rest.env ?? process.env, projectRoot, repoRoot });
1293
+ report.primary = primaryDoctorView(resolved);
1294
+ primaryText = `${report.primary.state} ${report.primary.harness ?? '-'}/${report.primary.model ?? '-'} (${report.primary.origin}), probe ${report.primary.probe}`;
1295
+ } catch (error ) {
1296
+ report.primary = { state: 'unavailable', message: error?.message || String(error) };
1297
+ primaryText = `unavailable (${report.primary.message})`;
1298
+ }
1299
+ try {
1300
+ report.workers = workersDoctorView({ env: rest.env ?? process.env, projectRoot, repoRoot });
1301
+ } catch (error ) {
1302
+ report.workers = { state: 'unavailable', message: error?.message || String(error) };
1303
+ }
1304
+ try {
1305
+ report.workerPool = workerPoolDoctorView({ env: rest.env ?? process.env, projectRoot, repoRoot });
1306
+ } catch (error ) {
1307
+ report.workerPool = { state: 'unavailable', message: error?.message || String(error) };
1308
+ }
773
1309
 
774
1310
  if (json) {
775
1311
  console.log(JSON.stringify(report, null, 2));
@@ -779,6 +1315,7 @@ async function handleDoctorCommand({ json = false, repoRoot, projectRoot = proce
779
1315
  console.log('\nDoFlow System Diagnostics (doflow doctor)');
780
1316
  console.log('═'.repeat(72));
781
1317
  console.log('Health means a provider answered a probe, not that a binary exists on PATH.');
1318
+ if (report.upkeepSwitch === 'off') console.log('note: DOFLOW_UPKEEP=off in this environment: upkeep and lifecycle retention are skipped');
782
1319
 
783
1320
  console.log('\n[Effective Identity]');
784
1321
  const identity = report.identity;
@@ -789,6 +1326,11 @@ async function handleDoctorCommand({ json = false, repoRoot, projectRoot = proce
789
1326
  console.log('\n[Harness Adapters]');
790
1327
  for (const harness of report.harnesses) console.log(` ${harness.displayName.padEnd(28)} adapter PASS`);
791
1328
 
1329
+ console.log('\n[Agent Tool Scope]');
1330
+ if (Array.isArray(report.agentToolScope)) {
1331
+ 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}`}`);
1332
+ } else console.log(` unavailable (${report.agentToolScope.message})`);
1333
+
792
1334
  console.log('\n[Runtime Reach]');
793
1335
  const reachLines = report.harnesses.flatMap((harness ) => harness.reach.map((row ) => {
794
1336
  const detail = ({ REACHED: row.root, 'NO-REACH': `fix: ${row.fix}`, 'N/A': row.reason } )[row.state];
@@ -826,12 +1368,71 @@ async function handleDoctorCommand({ json = false, repoRoot, projectRoot = proce
826
1368
  console.log(` test ${commandLine(commands.test)}`);
827
1369
  if (commands.reason) console.log(` note ${commands.reason}`);
828
1370
 
1371
+ console.log('\n[Primary and Workers]');
1372
+ console.log(` primary: ${primaryText}`);
1373
+ if (report.workers.state === 'unavailable') {
1374
+ console.log(` worker: unavailable (${report.workers.message})`);
1375
+ } else {
1376
+ if (report.workers.bindings.state === 'invalid') {
1377
+ console.log(` workers.models: invalid (${report.workers.bindings.reason})`);
1378
+ }
1379
+ if (report.workers.state === 'invalid') {
1380
+ console.log(` workers.enabled: invalid (${report.workers.reason})`);
1381
+ }
1382
+ for (const row of report.workers.rows) {
1383
+ const position = row.enabled.position === null ? '' : ` #${row.enabled.position}`;
1384
+ const eligibility = row.eligible ? 'eligible' : `ineligible (${row.firstGap.requirement})`;
1385
+ const bound = row.bound.map((item ) => `${item.tier}(${item.flag})`).join(', ') || 'none';
1386
+ console.log(` worker ${row.harness}: ${row.enabled.state}${position}, ${eligibility}; bound tiers: ${bound}`);
1387
+ }
1388
+ for (const id of report.workers.dangling) {
1389
+ console.log(` worker ${id}: dangling (no registry or enabled package knows it)`);
1390
+ }
1391
+ }
1392
+
1393
+ console.log('\n[Worker Pool & Models]');
1394
+ if (report.workerPool.state === 'unavailable') {
1395
+ console.log(` worker pool: unavailable (${report.workerPool.message})`);
1396
+ } else if (report.workerPool.state === 'unsynced') {
1397
+ console.log(' No observed catalogue. Run `doflow models --action sync` to probe worker pool.');
1398
+ console.log(` Models: ${report.workerPool.models.enabledCount} enabled, ${report.workerPool.models.listedCount} listed`);
1399
+ } else {
1400
+ for (const h of report.workerPool.harnesses) {
1401
+ const binStatus = h.installed ? 'installed' : (h.loginState === 'uncalibrated' ? 'unknown' : 'missing');
1402
+ const hint = h.loginState === 'logged-out' && h.reloginHint ? ` (re-login: ${h.reloginHint})` : '';
1403
+ const billSource = h.effectiveBilling.source ? ` (${h.effectiveBilling.source})` : '';
1404
+ 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}`);
1405
+ }
1406
+ console.log(` Models: ${report.workerPool.models.enabledCount} enabled, ${report.workerPool.models.listedCount} listed`);
1407
+ }
1408
+
829
1409
  printMcpSections(report.mcp);
1410
+ if (packagesWorthShowing(report.packages)) printPackagesSection(report.packages);
1411
+ printTranscriptsSection(report.transcripts);
830
1412
  printAdviceDoctor(adviceSections);
1413
+ printProfilesDoctor(profilesSection);
1414
+ printSettingsReport(settings);
1415
+ if (report.guidance) {
1416
+ console.log('\n[Guidance Rules]');
1417
+ if (report.guidance.state === 'unavailable') {
1418
+ console.log(` guidance: unavailable (${report.guidance.message})`);
1419
+ } else {
1420
+ console.log(` ${report.guidance.rulesCount} registered rules (${report.guidance.neverAblateCount} protected invariants, ${report.guidance.candidateCount} ablation candidates)`);
1421
+ }
1422
+ }
1423
+ if (report.team) {
1424
+ console.log('\n[Agent Teams & Coordination]');
1425
+ if (report.team.state === 'unavailable') {
1426
+ console.log(` team: unavailable (${report.team.message})`);
1427
+ } else {
1428
+ console.log(` writer cap: ${report.team.writerCap}, max attempts: ${report.team.maxAttempts}, active claims: ${report.team.activeClaimsCount}`);
1429
+ }
1430
+ }
831
1431
 
832
1432
  if (report.warnings.length) {
833
- console.log('\n[Warnings] — not blocking; a stale or unprobeable provider still degrades cleanly');
834
- for (const warning of report.warnings) console.log(` ${warning.subject.padEnd(18)}${warning.detail}`);
1433
+ 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');
1434
+ // A subject at or past the column width (a file path) would run into its detail, so it gets a gap.
1435
+ for (const warning of report.warnings) console.log(` ${warning.subject.padEnd(18)}${warning.subject.length >= 18 ? ' ' : ''}${warning.detail}`);
835
1436
  }
836
1437
 
837
1438
  console.log('\n[Findings]');
@@ -853,11 +1454,16 @@ module.exports = {
853
1454
  runProbe,
854
1455
  probeProvider,
855
1456
  probeProviders,
1457
+ probeLocalProvider,
856
1458
  probeFreshness,
1459
+ claudePermissionWarnings,
857
1460
  PROVIDER_ARTIFACTS,
858
1461
  newestSourceMtime,
859
1462
  detectProjectCommands,
860
1463
  buildHealthReport,
1464
+ nodeSecurityWarning,
1465
+ workersDoctorView,
1466
+ workerPoolDoctorView,
861
1467
  handleDoctorCommand,
862
1468
  PROBE_TIMEOUT_MS,
863
1469
  };