@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
@@ -76,7 +76,7 @@ async function buildAdviceDoctor({ repoRoot = REPO_ROOT, projectRoot = process.c
76
76
  const registry = loaded.registry;
77
77
  const storage = suppliedStorage || loadStorageRegistry({ repoRoot: root, fsImpl });
78
78
  const roots = resolveRoots({ registry: storage, projectRoot, env });
79
- const setting = loaded.ok ? resolveSetting({ adviceRegistry: registry, roots, fsImpl }) : {
79
+ const setting = loaded.ok ? resolveSetting({ adviceRegistry: registry, roots, env, fsImpl }) : {
80
80
  state: 'off', reason: 'config-invalid', decidedBy: { user: null, project: null }, modelFrom: null, notes: [loaded.detail],
81
81
  };
82
82
  const store = selectedStore(roots);
@@ -0,0 +1,97 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const { REPO_ROOT } = require('../../helper/repo-root.js');
6
+
7
+ const TIER_ORDER = Object.freeze({
8
+ light: 0,
9
+ standard: 1,
10
+ frontier: 2,
11
+ });
12
+
13
+ // Risk order = key order of riskLevels in core/registry/verification.json, read once.
14
+ // An unreadable registry gives an empty order, which means "never elevated".
15
+ function loadVerifyRiskOrder() {
16
+ const order = {};
17
+ try {
18
+ const registry = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, 'core/registry/verification.json'), 'utf8'));
19
+ Object.keys(registry.riskLevels).forEach((level, i) => { order[level.toLowerCase()] = i; });
20
+ } catch {
21
+ return Object.freeze({});
22
+ }
23
+ return Object.freeze(order);
24
+ }
25
+
26
+ const VERIFY_RISK_ORDER = loadVerifyRiskOrder();
27
+
28
+ const REVIEW_DEPTH_ORDER = Object.freeze({
29
+ single: 0,
30
+ deep: 1,
31
+ });
32
+
33
+ const TASK_CLASS_ORDER = Object.freeze({
34
+ 'trivial-edit': 0,
35
+ 'refactor': 1,
36
+ 'bug': 1,
37
+ 'dependency-change': 1,
38
+ 'feature': 2,
39
+ });
40
+
41
+ function applyElevationInvariant(
42
+ kind ,
43
+ staticPick ,
44
+ advisorPick
45
+ ) {
46
+ if (!advisorPick || typeof advisorPick !== 'string' || !advisorPick.trim()) {
47
+ return { effectiveDecision: staticPick, elevated: false };
48
+ }
49
+
50
+ const advisorClean = advisorPick.trim();
51
+
52
+ if (kind === 'tier') {
53
+ const sRank = TIER_ORDER[staticPick.toLowerCase()];
54
+ const aRank = TIER_ORDER[advisorClean.toLowerCase()];
55
+ if (sRank !== undefined && aRank !== undefined && aRank > sRank) {
56
+ return { effectiveDecision: advisorClean, elevated: true };
57
+ }
58
+ return { effectiveDecision: staticPick, elevated: false };
59
+ }
60
+
61
+ if (kind === 'verify-risk') {
62
+ const sRank = VERIFY_RISK_ORDER[staticPick.toLowerCase()];
63
+ const aRank = VERIFY_RISK_ORDER[advisorClean.toLowerCase()];
64
+ if (sRank !== undefined && aRank !== undefined && aRank > sRank) {
65
+ return { effectiveDecision: advisorClean, elevated: true };
66
+ }
67
+ return { effectiveDecision: staticPick, elevated: false };
68
+ }
69
+
70
+ if (kind === 'review-depth') {
71
+ const sRank = REVIEW_DEPTH_ORDER[staticPick.toLowerCase()];
72
+ const aRank = REVIEW_DEPTH_ORDER[advisorClean.toLowerCase()];
73
+ if (sRank !== undefined && aRank !== undefined && aRank > sRank) {
74
+ return { effectiveDecision: advisorClean, elevated: true };
75
+ }
76
+ return { effectiveDecision: staticPick, elevated: false };
77
+ }
78
+
79
+ if (kind === 'task-class') {
80
+ const sRank = TASK_CLASS_ORDER[staticPick.toLowerCase()];
81
+ const aRank = TASK_CLASS_ORDER[advisorClean.toLowerCase()];
82
+ if (sRank !== undefined && aRank !== undefined && aRank > sRank) {
83
+ return { effectiveDecision: advisorClean, elevated: true };
84
+ }
85
+ return { effectiveDecision: staticPick, elevated: false };
86
+ }
87
+
88
+ return { effectiveDecision: staticPick, elevated: false };
89
+ }
90
+
91
+ module.exports = {
92
+ applyElevationInvariant,
93
+ TIER_ORDER,
94
+ VERIFY_RISK_ORDER,
95
+ REVIEW_DEPTH_ORDER,
96
+ TASK_CLASS_ORDER,
97
+ };
@@ -68,19 +68,25 @@ function refuse(value , file ) {
68
68
  };
69
69
  }
70
70
 
71
- function lookupAll(host , lookup , timeoutMs ) {
71
+ /** `signal`, when given, ends the lookup the way its timeout does; an already-aborted signal skips the lookup. */
72
+ function lookupAll(host , lookup , timeoutMs , signal ) {
72
73
  return new Promise((resolve, reject) => {
74
+ const aborted = () => Object.assign(new Error('decision model DNS validation aborted by the caller'), { code: 'ADVICE_TIMEOUT' });
75
+ if (signal?.aborted) { reject(aborted()); return; }
73
76
  let settled = false;
77
+ const onAbort = () => finish(aborted());
74
78
  const finish = (error , addresses ) => {
75
79
  if (settled) return;
76
80
  settled = true;
77
81
  clearTimeout(timer);
82
+ signal?.removeEventListener?.('abort', onAbort);
78
83
  if (error) reject(error);
79
84
  else resolve(addresses);
80
85
  };
81
86
  const timer = Number.isFinite(timeoutMs) ? setTimeout(() => {
82
87
  finish(Object.assign(new Error('decision model DNS validation timed out'), { code: 'ADVICE_TIMEOUT' }));
83
88
  }, Math.max(0, timeoutMs )) : undefined;
89
+ signal?.addEventListener?.('abort', onAbort, { once: true });
84
90
  try {
85
91
  const returned = lookup(host, { all: true, verbatim: true }, finish);
86
92
  if (returned && typeof returned.then === 'function') returned.then((addresses ) => finish(null, addresses), finish);
@@ -103,7 +109,7 @@ function pinnedLookup(address , family ) {
103
109
  };
104
110
  }
105
111
 
106
- async function checkEndpoint(value , { lookup = dns.lookup, file, timeoutMs } = {}) {
112
+ async function checkEndpoint(value , { lookup = dns.lookup, file, timeoutMs, signal } = {}) {
107
113
  const display = displayEndpoint(value);
108
114
  let url ;
109
115
  try { url = new URL(display); } catch { return refuse(display, file); }
@@ -117,7 +123,7 @@ async function checkEndpoint(value , { lookup = dns.lookup, file, timeoutMs
117
123
  }
118
124
 
119
125
  let addresses ;
120
- try { addresses = await lookupAll(host, lookup, timeoutMs); } catch (error ) {
126
+ try { addresses = await lookupAll(host, lookup, timeoutMs, signal); } catch (error ) {
121
127
  if (error?.code === 'ADVICE_TIMEOUT') return { ok: false, reason: 'timeout', message: error.message };
122
128
  return refuse(display, file);
123
129
  }
@@ -0,0 +1,37 @@
1
+ 'use strict';
2
+
3
+ // How a chosen rollout profile caps advice. One table for the dispatcher (set kinds) and the callers' unset-kind path.
4
+
5
+ const { resolveRolloutProfile } = require('../profiles/resolve.js');
6
+
7
+
8
+
9
+
10
+ /**
11
+ * The rollout profile's advisor capability, or null when no profile was chosen. It caps advice only when a
12
+ * profile was chosen (env or settings), a rollback is active or the value is invalid; a project that sets no
13
+ * profile (origin registry-default) keeps advice as configured.
14
+ */
15
+ function profileAdvisorCap({ projectRoot, env, fsImpl } = {}) {
16
+ const readable = typeof fsImpl?.readFileSync === 'function' ? fsImpl : undefined;
17
+ const profile = resolveRolloutProfile({ projectRoot, env, fsImpl: readable });
18
+ if (profile.origin.source === 'default' && profile.origin.detail === 'registry-default') return null;
19
+ return profile.capabilities.advisor;
20
+ }
21
+
22
+ /** The mode a set kind runs in under a cap. `staged` means shadow until a contract defines it (D2); anything unknown is off. */
23
+ function capMode(cap , configuredMode ) {
24
+ if (configuredMode !== 'off' && configuredMode !== 'shadow' && configuredMode !== 'act') return 'off';
25
+ if (cap === null || cap === 'act') return configuredMode;
26
+ if (cap === 'shadow' || cap === 'staged') return configuredMode === 'act' ? 'shadow' : configuredMode;
27
+ return 'off';
28
+ }
29
+
30
+ /** What the unset-kind path does under a cap: the legacy ask (no cap, or act), shadow (record, never show or apply) or off. */
31
+ function unsetKindPath(cap ) {
32
+ if (cap === null || cap === 'act') return 'legacy';
33
+ if (cap === 'shadow' || cap === 'staged') return 'shadow';
34
+ return 'off';
35
+ }
36
+
37
+ module.exports = { profileAdvisorCap, capMode, unsetKindPath };
@@ -40,16 +40,26 @@ function appendLine({ roots, at, line, fsImpl = fs, store } )
40
40
  }
41
41
  }
42
42
 
43
+ const PICK_ID = /^adv-[0-9]{13}-[0-9a-f]{6}$/;
44
+
45
+ function newPickId(epoch = Date.now()) {
46
+ return `adv-${String(epoch).padStart(13, '0')}-${crypto.randomBytes(3).toString('hex')}`;
47
+ }
48
+
49
+ /**
50
+ * `pickId` lets a caller that returned an id before the line is written (the shadow dispatcher) keep it; `mode`
51
+ * and `elevated` are the dispatcher's and are written only when given.
52
+ */
43
53
  function appendPick({
44
54
  roots, now = () => new Date(), fsImpl = fs, kind, answer, question,
45
55
  staticAnswer = null, provider = null, model = null, state, caller, taskId = null, durationMs = 0,
56
+ pickId: givenPickId, mode, elevated,
46
57
  } = {}) {
47
58
  try {
48
59
  if (typeof state !== 'string' || !answer || !question) return null;
49
60
  const at = timestamp(now);
50
61
  if (!at) return null;
51
- const epoch = Date.parse(at);
52
- const pickId = `adv-${String(epoch).padStart(13, '0')}-${crypto.randomBytes(3).toString('hex')}`;
62
+ const pickId = typeof givenPickId === 'string' && PICK_ID.test(givenPickId) ? givenPickId : newPickId(Date.parse(at));
53
63
  const options = question.type === 'choice' ? Object.keys(question.options || {})
54
64
  : question.type === 'score' ? [...(question.criteria || [])] : ['no', 'yes'];
55
65
  const instructions = kind === 'adhoc' && typeof question.instructions === 'string'
@@ -78,6 +88,8 @@ function appendPick({
78
88
  taskId: taskId ?? null,
79
89
  durationMs: Number.isFinite(durationMs) ? durationMs : 0,
80
90
  };
91
+ if (mode !== undefined) line.mode = mode;
92
+ if (elevated !== undefined) line.elevated = elevated === true;
81
93
  if (answer.type === 'score') {
82
94
  line.criteria = [...(question.criteria || [])];
83
95
  line.pickIndex = answer.pickIndex;
@@ -90,15 +102,16 @@ function appendPick({
90
102
  }
91
103
  }
92
104
 
93
- function appendMiss({ roots, now = () => new Date(), fsImpl = fs, kind, caller, reason, model = null, durationMs = 0 } = {}) {
105
+ function appendMiss({ roots, now = () => new Date(), fsImpl = fs, kind, caller, reason, model = null, durationMs = 0, mode } = {}) {
94
106
  try {
95
107
  const at = timestamp(now);
96
108
  if (!at) return null;
97
- const line = {
109
+ const line = {
98
110
  schema: 1, type: 'miss', at, kind, caller, reason,
99
111
  model: model ?? null,
100
112
  durationMs: Number.isFinite(durationMs) ? durationMs : 0,
101
113
  };
114
+ if (mode !== undefined) line.mode = mode;
102
115
  return appendLine({ roots, at, line, fsImpl });
103
116
  } catch { return null; }
104
117
  }
@@ -163,4 +176,4 @@ function findPick({ pickId, roots, now = () => new Date(), fsImpl = fs } =
163
176
  return found ? { pick: found.record, file: found.file, store: found.store } : null;
164
177
  }
165
178
 
166
- module.exports = { appendPick, appendMiss, appendOutcome, readWindow, findPick };
179
+ module.exports = { appendPick, appendMiss, appendOutcome, readWindow, findPick, newPickId };
@@ -9,6 +9,9 @@ const { REPO_ROOT } = require('../../helper/repo-root.js');
9
9
  const ADVICE_FILE = 'core/registry/advice.json';
10
10
  const MODELS_FILE = 'core/registry/models.json';
11
11
  const ADAPTER_DIR = 'src/runtime/advice/providers';
12
+ // The adapter is a sibling module of this file, so it has this file's own extension: `.ts` in a
13
+ // checkout, `.js` in the package `build:package` stages (every module there is stripped to `.js`).
14
+ const ADAPTER_EXT = path.extname(__filename);
12
15
  const TIER_KIND = 'tier';
13
16
  const LIMIT_FIELDS = ['maxStateBytes', 'maxBodyBytes', 'minQuestions', 'maxQuestions', 'minOptions', 'maxOptions'];
14
17
  const BAND_FIELDS = {
@@ -107,7 +110,7 @@ function validateAdviceRegistry(registry , { repoRoot = REPO_ROOT, models, f
107
110
  if (!/^[a-z][a-z0-9-]*$/.test(provider.adapter)) {
108
111
  addError(errors, `${base}.adapter`, 'must be a safe adapter id');
109
112
  } else {
110
- const adapterFile = path.resolve(adapterRoot, `${provider.adapter}.ts`);
113
+ const adapterFile = path.resolve(adapterRoot, `${provider.adapter}${ADAPTER_EXT}`);
111
114
  if (!adapterFile.startsWith(`${adapterRoot}${path.sep}`) || !fsImpl.existsSync(adapterFile)) {
112
115
  addError(errors, `${base}.adapter`, `must name a file under ${ADAPTER_DIR}`);
113
116
  }
@@ -0,0 +1,229 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const { resolveSetting } = require('./config.js');
6
+ const { resolveRoots } = require('../storage/roots.js');
7
+ const { loadStorageRegistry } = require('../storage/registry.js');
8
+ const { configHome } = require('../config-home.js');
9
+ const { REPO_ROOT } = require('../../helper/repo-root.js');
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+ function getSettingValue(obj , key ) {
41
+ if (!obj || typeof obj !== 'object') return undefined;
42
+ if (Object.hasOwn(obj, key)) return obj[key];
43
+ const parts = key.split('.');
44
+ let cur = obj;
45
+ for (let i = 0; i < parts.length; i++) {
46
+ if (!cur || typeof cur !== 'object') return undefined;
47
+ const p = parts[i];
48
+ if (Object.hasOwn(cur, p)) {
49
+ cur = cur[p];
50
+ } else {
51
+ const rest = parts.slice(i).join('.');
52
+ if (Object.hasOwn(cur, rest)) {
53
+ return cur[rest];
54
+ }
55
+ return undefined;
56
+ }
57
+ }
58
+ return cur;
59
+ }
60
+
61
+ /** An absent file is missing; an unreadable file, bad JSON or a non-object value is an error (fail closed). */
62
+ function readJsonFile(filePath , fsImpl ) {
63
+ let text ;
64
+ try {
65
+ text = fsImpl.readFileSync(filePath, 'utf8');
66
+ } catch (error ) {
67
+ if (error?.code === 'ENOENT' || error?.code === 'ENOTDIR') return { missing: true };
68
+ return { error: `could not be read (${error?.code || error?.message || 'unknown error'})` };
69
+ }
70
+ try {
71
+ const content = JSON.parse(text);
72
+ if (!content || typeof content !== 'object' || Array.isArray(content)) return { error: 'is not a JSON object' };
73
+ return { content };
74
+ } catch {
75
+ return { error: 'is not valid JSON' };
76
+ }
77
+ }
78
+
79
+ function resolveShadowConfig(
80
+ kind ,
81
+ options
82
+ ) {
83
+ const fsImpl = options?.fsImpl || fs;
84
+ const env = options?.env ?? process.env;
85
+ const projectRoot = options?.projectRoot || process.cwd();
86
+
87
+ let storageRegistry = null;
88
+ try {
89
+ storageRegistry = loadStorageRegistry({ repoRoot: REPO_ROOT, fsImpl });
90
+ } catch {
91
+ // best-effort registry loading
92
+ }
93
+
94
+ let roots = options?.roots;
95
+ if (!roots && storageRegistry) {
96
+ try {
97
+ roots = resolveRoots({ registry: storageRegistry, projectRoot, global: options?.global, env });
98
+ } catch {
99
+ roots = null;
100
+ }
101
+ }
102
+
103
+ // 1. The advice setting's model is the fallback when no layer names one for this kind.
104
+ let globalSetting = null;
105
+ try {
106
+ globalSetting = resolveSetting({
107
+ roots,
108
+ storageRegistry,
109
+ projectRoot,
110
+ global: options?.global,
111
+ env,
112
+ fsImpl,
113
+ });
114
+ } catch {
115
+ globalSetting = null;
116
+ }
117
+
118
+ // 2. Discover configuration files across layers (project local -> project -> user)
119
+ const candidateFiles = [];
120
+
121
+ if (projectRoot) {
122
+ candidateFiles.push({
123
+ layer: 'local',
124
+ path: path.join(projectRoot, '.doflow', 'settings.local.json'),
125
+ });
126
+ candidateFiles.push({
127
+ layer: 'project',
128
+ path: path.join(projectRoot, '.doflow', 'settings.json'),
129
+ });
130
+ }
131
+
132
+ const userHome = roots?.['config-home'] || configHome(env).dir;
133
+ const userConfigDir = userHome;
134
+
135
+ if (userConfigDir) {
136
+ candidateFiles.push({
137
+ layer: 'user',
138
+ path: path.join(userConfigDir, 'settings.json'),
139
+ });
140
+ }
141
+
142
+ const loadedCandidates = [];
143
+ const diagnostics = [];
144
+ for (const { layer, path: filePath } of candidateFiles) {
145
+ const read = readJsonFile(filePath, fsImpl);
146
+ if ('content' in read) loadedCandidates.push({ layer, filePath, content: read.content });
147
+ else if ('error' in read) diagnostics.push(`${layer} settings ${filePath} ${read.error}; advice.kinds.${kind} is off`);
148
+ }
149
+
150
+ const defaultModel = globalSetting?.model || null;
151
+
152
+ // 3. Check advice.enabled: if explicitly false in any candidate layer, force mode = 'off'
153
+ let explicitDisabledBy = null;
154
+ for (const { layer, filePath, content } of loadedCandidates) {
155
+ const enabledVal = getSettingValue(content, 'advice.enabled');
156
+ if (enabledVal === false) {
157
+ explicitDisabledBy = `${layer}:${filePath}`;
158
+ break;
159
+ }
160
+ }
161
+
162
+ const kindSet = diagnostics.length > 0
163
+ || loadedCandidates.some(({ content }) => getSettingValue(content, `advice.kinds.${kind}`) !== undefined);
164
+ let resolvedMode = 'off';
165
+ let modeDecidedBy = null;
166
+
167
+ if (explicitDisabledBy) {
168
+ resolvedMode = 'off';
169
+ modeDecidedBy = explicitDisabledBy;
170
+ } else if (diagnostics.length === 0) {
171
+ // The first layer that has advice.kinds.<kind> decides; an invalid value there is off, never a lower layer's value.
172
+ for (const { layer, filePath, content } of loadedCandidates) {
173
+ const modeVal = getSettingValue(content, `advice.kinds.${kind}`);
174
+ if (modeVal === undefined) continue;
175
+ modeDecidedBy = `${layer}:${filePath}`;
176
+ if (modeVal === 'act' || modeVal === 'shadow' || modeVal === 'off') resolvedMode = modeVal;
177
+ else diagnostics.push(`${layer} settings ${filePath} has invalid advice.kinds.${kind} ${JSON.stringify(modeVal)} (expected off, shadow or act); it is off`);
178
+ break;
179
+ }
180
+ }
181
+
182
+ // 4. Resolve model: advice.models.<kind> -> advice.model (string) -> defaultModel
183
+ let resolvedModel = null;
184
+ let modelDecidedBy = null;
185
+
186
+ // Check per-kind model override
187
+ for (const { layer, filePath, content } of loadedCandidates) {
188
+ const kindModel = getSettingValue(content, `advice.models.${kind}`);
189
+ if (typeof kindModel === 'string' && kindModel.trim()) {
190
+ resolvedModel = kindModel.trim();
191
+ modelDecidedBy = `${layer}:${filePath}`;
192
+ break;
193
+ }
194
+ }
195
+
196
+ // Fallback to general advice.model (string) from candidate files
197
+ if (!resolvedModel) {
198
+ for (const { layer, filePath, content } of loadedCandidates) {
199
+ const generalModel = getSettingValue(content, 'advice.model');
200
+ if (typeof generalModel === 'string' && generalModel.trim()) {
201
+ resolvedModel = generalModel.trim();
202
+ modelDecidedBy = `${layer}:${filePath}`;
203
+ break;
204
+ }
205
+ }
206
+ }
207
+
208
+ // Fallback to defaultModel from global setting
209
+ if (!resolvedModel) {
210
+ resolvedModel = defaultModel;
211
+ modelDecidedBy = globalSetting?.decidedBy?.project || globalSetting?.decidedBy?.user || null;
212
+ }
213
+
214
+ return {
215
+ kind,
216
+ mode: resolvedMode,
217
+ kindSet,
218
+ model: resolvedModel,
219
+ decidedBy: {
220
+ mode: modeDecidedBy,
221
+ model: modelDecidedBy,
222
+ },
223
+ diagnostics,
224
+ };
225
+ }
226
+
227
+ module.exports = {
228
+ resolveShadowConfig,
229
+ };