@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
@@ -0,0 +1,154 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ /**
6
+ * The 2.0 settings files, read as deprecated sources (086 IC-008, FR-010). The rules are the 2.0
7
+ * ones, copied from `advice/config.ts` (parseUserSetting, parseProjectSetting, readJson),
8
+ * `storage/roots.ts` (readSetting + existsSync) and `failure/home.ts` (the sentinel `isFile`), so an
9
+ * old file gives the result it gave in 2.0. Two departures, both in the safe direction: a JSON parse
10
+ * error names its offset only, never the file text (IC-002, `jsonFailure`); a path that is not a
11
+ * regular file is never opened, so a FIFO cannot block a read (2.0 would have waited on it).
12
+ *
13
+ * `../settings/resolve.js` requires this module only after a `stat` hit on a legacy path, so a
14
+ * machine with no old files never compiles it. Nothing here writes, and nothing returns file text.
15
+ */
16
+
17
+ const nodeFs = require('node:fs');
18
+ const { jsonFailure } = require('./files.js');
19
+
20
+ const PROJECT_NOTE = 'a project cannot enable the decision model or choose its endpoint';
21
+ const USER_KEYS = new Set(['schema', 'enabled', 'provider', 'endpoint', 'model']);
22
+ const PROJECT_KEYS = ['schema', 'enabled', 'model', 'endpoint', 'provider'];
23
+ /** The decision-model.json fields that carry a setting, in file order. */
24
+ const FIELDS = ['enabled', 'provider', 'endpoint', 'model'];
25
+
26
+
27
+
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+ /** What the user file is checked against: the provider default and list, and each provider's default endpoint. */
40
+
41
+
42
+ function isObject(value ) {
43
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
44
+ }
45
+
46
+ /** 2.0 `parseUserSetting` (advice/config.ts): the whole file is valid or none of it is. */
47
+ function parseUserSetting(value , providers ) {
48
+ if (!isObject(value)) return { error: 'user setting must be a JSON object', code: 'unparseable' };
49
+ if (value.schema !== 1) return { error: 'user setting schema must be 1', code: 'schema' };
50
+ if (typeof value.enabled !== 'boolean') return { error: 'user setting enabled must be a boolean', code: 'type' };
51
+
52
+ const providerId = value.provider === undefined ? providers.defaultProvider : value.provider;
53
+ if (typeof providerId !== 'string' || !providers.ids.includes(providerId)) {
54
+ return { error: `user setting provider '${providerId}' is not in advice.json providers`, code: 'enum' };
55
+ }
56
+ let endpoint ;
57
+ if (value.endpoint !== undefined) {
58
+ if (typeof value.endpoint !== 'string' || !value.endpoint.trim()) return { error: 'user setting endpoint must be a URL string', code: 'url' };
59
+ try { new URL(value.endpoint); } catch { return { error: 'user setting endpoint must be a URL string', code: 'url' }; }
60
+ endpoint = value.endpoint;
61
+ } else {
62
+ endpoint = providers.defaultEndpoint(providerId);
63
+ }
64
+ if (typeof endpoint !== 'string' || !endpoint) return { error: `providers.${providerId}.defaultEndpoint is missing`, code: 'enum' };
65
+
66
+ if (value.model !== undefined && (typeof value.model !== 'string' || !value.model.trim())) {
67
+ return { error: 'user setting model must be a non-empty string', code: 'type' };
68
+ }
69
+ if (value.enabled && (typeof value.model !== 'string' || !value.model.trim())) {
70
+ return { error: 'user setting model is required when enabled is true', code: 'cross-key' };
71
+ }
72
+ const notes = Object.keys(value)
73
+ .filter((key) => !USER_KEYS.has(key))
74
+ .map((key) => ({ code: 'unknown-key', field: null, text: `unknown user key '${key}' ignored` }));
75
+ return { notes };
76
+ }
77
+
78
+ /** 2.0 `parseProjectSetting`: a project may only disable and choose a model; the rest is noted, never applied. */
79
+ function parseProjectSetting(value ) {
80
+ if (!isObject(value)) return { error: 'project setting must be a JSON object', code: 'unparseable' };
81
+ if (value.schema !== 1) return { error: 'project setting schema must be 1', code: 'schema' };
82
+ if (value.enabled !== undefined && typeof value.enabled !== 'boolean') return { error: 'project setting enabled must be a boolean', code: 'type' };
83
+ if (value.model !== undefined && (typeof value.model !== 'string' || !value.model.trim())) return { error: 'project setting model must be a non-empty string', code: 'type' };
84
+
85
+ const notes = [];
86
+ if (value.enabled === true) notes.push({ code: 'scope', field: 'enabled', text: `project key 'enabled' ignored: ${PROJECT_NOTE}` });
87
+ for (const key of ['endpoint', 'provider']) {
88
+ if (Object.hasOwn(value, key)) notes.push({ code: 'scope', field: key, text: `project key '${key}' ignored: ${PROJECT_NOTE}` });
89
+ }
90
+ for (const key of Object.keys(value)) {
91
+ if (!PROJECT_KEYS.includes(key)) {
92
+ const shown = JSON.stringify(key.length > 80 ? `${key.slice(0, 80)}...` : key);
93
+ notes.push({ code: 'unknown-key', field: null, text: `unknown project key ${shown} ignored` });
94
+ }
95
+ }
96
+ return { notes };
97
+ }
98
+
99
+ function failed(code , reason , offset = null) {
100
+ return { status: 'invalid', code, offset, reason, fields: new Map(), notes: [] };
101
+ }
102
+
103
+ /**
104
+ * `<config home>/doflow/decision-model.json` (flavour `user`) or `.doflow/decision-model.json`
105
+ * (`project`). A missing file is absent; any other fault is the whole file invalid with today's text.
106
+ * A valid file yields the fields it holds: a project's `enabled: true`, `endpoint` and `provider` are
107
+ * yielded too (the resolver's scope rules ignore them) with today's notes.
108
+ */
109
+ function readDecisionModel(file , flavour , providers , fsImpl = nodeFs) {
110
+ try {
111
+ if (!fsImpl.statSync(file).isFile()) return failed('not-file', 'is not a regular file');
112
+ } catch (error ) {
113
+ if (error?.code === 'ENOENT') return { status: 'absent', code: null, offset: null, reason: null, fields: new Map(), notes: [] };
114
+ return failed('unreadable', String(error?.message ?? 'cannot be read'));
115
+ }
116
+ let text ;
117
+ try { text = fsImpl.readFileSync(file, 'utf8'); }
118
+ catch (error ) { return failed('unreadable', String(error?.message ?? 'cannot be read')); }
119
+ let value ;
120
+ try { value = JSON.parse(text); }
121
+ catch (error ) {
122
+ const failure = jsonFailure(error, text);
123
+ return failed('unparseable', failure.reason, failure.offset);
124
+ }
125
+ const parsed = flavour === 'user'
126
+ ? parseUserSetting(value, providers ?? { defaultProvider: undefined, ids: [], defaultEndpoint: () => null })
127
+ : parseProjectSetting(value);
128
+ if ('error' in parsed) return failed(parsed.code, parsed.error);
129
+ const fields = new Map ();
130
+ for (const field of FIELDS) if (Object.hasOwn(value, field)) fields.set(field, value[field]);
131
+ return { status: 'ok', code: null, offset: null, reason: null, fields, notes: parsed.notes };
132
+ }
133
+
134
+ /**
135
+ * The `prune` file (2.0 `readSetting` behind `existsSync`): absent → null; otherwise its first line,
136
+ * trimmed, any case, `on` or `off`; anything else, an empty file or a path that cannot be read as a
137
+ * file is `off`.
138
+ */
139
+ function readLineOnOff(file , fsImpl = nodeFs) {
140
+ let stat ;
141
+ try { stat = fsImpl.statSync(file); } catch { return null; }
142
+ if (!stat.isFile()) return 'off';
143
+ try {
144
+ const first = String(fsImpl.readFileSync(file, 'utf8')).split(/\r?\n/, 1)[0].trim().toLowerCase();
145
+ return first === 'on' || first === 'off' ? first : 'off';
146
+ } catch { return 'off'; }
147
+ }
148
+
149
+ /** A sentinel file (`failures/off`): present when the path is a regular file; its content is never read. */
150
+ function readSentinel(file , fsImpl = nodeFs) {
151
+ try { return fsImpl.statSync(file).isFile(); } catch { return false; }
152
+ }
153
+
154
+ module.exports = { PROJECT_NOTE, readDecisionModel, readLineOnOff, readSentinel };
@@ -0,0 +1,56 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ /**
6
+ * The lexical `failure.capture` rule v1 (086 IC-004, DD-3, OI-1): the one line rule over the raw text
7
+ * of `settings.json` that the bash dispatcher, the bash policy, the JS hook runner and Node all apply,
8
+ * so a failing hook never needs `jq`, `node` or a JSON parser to know whether capture is off. The
9
+ * text is the file's bytes up to the first NUL. Every match of `"failure.capture"` + `:` + a value
10
+ * must capture exactly `"on"`; any other capture is `off`; no match is "no setting" (null). JSON
11
+ * validity is irrelevant and nothing here reports file text.
12
+ *
13
+ * The bare alternative excludes `"` (OI-1) so `{"failure.capture":"on"abc,}` reads the same in
14
+ * bash (leftmost-longest) and JS (leftmost-first). The whitespace class is the C-locale
15
+ * `[[:space:]]`, spelled out: JS `\s` would add Unicode spaces (U+00A0) that bash does not match.
16
+ * The file is read as `latin1`, one character per byte, as bash does under `LC_ALL=C`.
17
+ */
18
+
19
+ const nodeFs = require('node:fs');
20
+
21
+
22
+
23
+ const WS = '[ \\t\\n\\v\\f\\r]';
24
+ const CAPTURE_RE = new RegExp(`"failure\\.capture"${WS}*:${WS}*("[^"]*"|[^,}" \\t\\n\\v\\f\\r]*)`, 'g');
25
+
26
+ /** @param text the file's bytes as a latin1 string */
27
+ function lexicalCapture(text ) {
28
+ let out = null;
29
+ for (const m of text.split('\0', 1)[0].matchAll(CAPTURE_RE)) out = m[1] === '"on"' ? (out ?? 'on') : 'off';
30
+ return out;
31
+ }
32
+
33
+ /**
34
+ * A missing file is no setting; a path that is not a regular file, or cannot be read, is `off`.
35
+ * Never throws, never returns file text.
36
+ */
37
+ function readLexicalCapture(file , fsImpl = nodeFs) {
38
+ try {
39
+ if (!fsImpl.statSync(file).isFile()) return 'off';
40
+ } catch (error ) {
41
+ const code = error?.code;
42
+ if (code === 'ENOENT' || code === 'ELOOP' || code === 'ENOTDIR') return null;
43
+ if (code === 'EACCES') {
44
+ try {
45
+ const parent = require('node:path').dirname(file);
46
+ if (!fsImpl.statSync(parent).isDirectory()) return null;
47
+ try { fsImpl.accessSync(parent, fsImpl.constants?.X_OK ?? nodeFs.constants.X_OK); return null; }
48
+ catch (accessError ) { return accessError?.code === 'EACCES' ? 'off' : null; }
49
+ } catch { return null; }
50
+ }
51
+ return 'off';
52
+ }
53
+ try { return lexicalCapture(fsImpl.readFileSync(file, 'latin1')); } catch { return 'off'; }
54
+ }
55
+
56
+ module.exports = { lexicalCapture, readLexicalCapture };
@@ -0,0 +1,269 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ /**
6
+ * `doflow config validate --prepared-run <file>` (119 IC-010, FR-013, NFR-005). Checks a prepared-run file
7
+ * `{ schema: 1, taskClass, primary: { harness, model }, settings: { <key>: <value> }, presets: { review, debate } }`
8
+ * and returns one IC-009 entry per fault, keyed by JSON path. It starts no harness, calls no model, runs no git,
9
+ * opens no network connection and writes nothing. Each `settings` and `primary` entry is judged exactly as
10
+ * `config validate <key> <value> --layer project` (config-write.ts `keyEntry` + `layerEntry`), so no value
11
+ * rule is repeated here. The presets
12
+ * are checked against what a run can know before there is a diff (L-E1): the risk-policy step of the protocol
13
+ * resolution needs the diff and is not applied.
14
+ */
15
+
16
+ const path = require('node:path');
17
+ const nodeFs = require('node:fs');
18
+ const { finishRuntime, usageError } = require('../cli-result.js');
19
+ const { keyEntry, layerFile, layerEntry } = require('./config-write.js');
20
+ const { loadProtocols, presetIds, contributedPresetIds } = require('../review/protocols.js');
21
+ const { gateMinSeats, settingsPick, atLeastAsStrict } = require('../review/resolve-protocol.js');
22
+
23
+ /** The worker fields are judged here, without applying them or launching a harness. */
24
+ const KNOWN = new Set(['schema', 'taskClass', 'primary', 'workers', 'settings', 'presets']);
25
+
26
+
27
+
28
+ function entry(key , layer , code , message , allowed = null) {
29
+ return { key, layer, ok: false, code, message, allowed };
30
+ }
31
+
32
+ function isObject(value ) {
33
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
34
+ }
35
+
36
+ /** IC-018: qualified worker ids need JSON-path brackets; built-in ids stay readable. */
37
+ function bindingKey(id , tier ) {
38
+ const worker = /^[a-z][a-z0-9-]*$/.test(id) ? `.${id}` : `[${JSON.stringify(id)}]`;
39
+ return `workers.models${worker}.${tier}`;
40
+ }
41
+
42
+ /** The task classes of the workflow registry, the one `doflow classify` validates against. */
43
+ function taskClasses(repoRoot , fsImpl ) {
44
+ const { WorkflowEngine } = require('../workflow-engine.js');
45
+ return new WorkflowEngine({ repoRoot, fsImpl }).listClasses();
46
+ }
47
+
48
+ function validatePrepared(doc , ctx ) {
49
+ const { registry, protocols, classes } = ctx;
50
+ const errors = [];
51
+ const notes = ctx.notes;
52
+
53
+ if (!Object.hasOwn(doc, 'schema')) errors.push(entry('schema', null, 'type', 'schema is required and must be 1'));
54
+ else if (doc.schema !== 1) errors.push(entry('schema', null, 'enum', 'schema must be 1', [1]));
55
+
56
+ let taskClass = null;
57
+ if (typeof doc.taskClass !== 'string') {
58
+ errors.push(entry('taskClass', null, 'type', Object.hasOwn(doc, 'taskClass') ? 'taskClass must be a string' : 'taskClass is required'));
59
+ } else if (!classes.includes(doc.taskClass)) {
60
+ errors.push(entry('taskClass', null, 'enum', `${doc.taskClass} is not a task class in core/registry/workflows.yaml`, classes));
61
+ } else taskClass = doc.taskClass;
62
+
63
+ const settings = isObject(doc.settings) ? doc.settings : {};
64
+ if (Object.hasOwn(doc, 'settings') && !isObject(doc.settings)) errors.push(entry('settings', null, 'type', 'settings must be an object of setting keys to values'));
65
+ let where = null;
66
+ for (const key of Object.keys(settings)) {
67
+ const named = (e ) => ({ ...e, key: `settings.${e.key}` });
68
+ const keyFault = keyEntry(registry, key, 'project', true);
69
+ if (keyFault) { errors.push(named(keyFault)); continue; }
70
+ if (settings[key] === null) { errors.push(entry(`settings.${key}`, 'project', 'type', `${key} cannot be null; leave the key out`)); continue; }
71
+ where ??= layerFile('project', ctx.env, ctx.projectRoot, ctx.fsImpl);
72
+ if ('error' in where) throw Object.assign(new Error(where.error), { usage: true });
73
+ const fault = layerEntry(registry, key, 'project', where.file, { ok: true, value: settings[key] },
74
+ { env: ctx.env, projectRoot: ctx.projectRoot, fsImpl: ctx.fsImpl, siblings: settings });
75
+ if (fault) errors.push(named(fault));
76
+ }
77
+
78
+ const primary = isObject(doc.primary) ? doc.primary : {};
79
+ if (Object.hasOwn(doc, 'primary') && !isObject(doc.primary)) {
80
+ errors.push(entry('primary', null, 'type', 'primary must be an object with harness and model'));
81
+ } else if (Object.hasOwn(doc, 'primary')) {
82
+ for (const field of Object.keys(primary)) {
83
+ const key = `primary.${field}`;
84
+ if (field !== 'harness' && field !== 'model') {
85
+ errors.push(entry(key, null, 'unknown-key', `${field} is not a primary field; expected harness, model`));
86
+ continue;
87
+ }
88
+ const keyFault = keyEntry(registry, key, 'project', true);
89
+ if (keyFault) { errors.push(keyFault); continue; }
90
+ if (primary[field] === null) {
91
+ errors.push(entry(key, 'project', 'type', `${key} cannot be null; leave the key out`));
92
+ continue;
93
+ }
94
+ where ??= layerFile('project', ctx.env, ctx.projectRoot, ctx.fsImpl);
95
+ if ('error' in where) throw Object.assign(new Error(where.error), { usage: true });
96
+ const fault = layerEntry(registry, key, 'project', where.file, { ok: true, value: primary[field] },
97
+ { env: ctx.env, projectRoot: ctx.projectRoot, fsImpl: ctx.fsImpl, siblings: primary });
98
+ if (fault) errors.push(fault);
99
+ }
100
+ }
101
+
102
+ if (Object.hasOwn(doc, 'workers')) {
103
+ const { validateValue } = require('./files.js');
104
+ const workers = isObject(doc.workers) ? doc.workers : {};
105
+ if (!isObject(doc.workers)) {
106
+ errors.push(entry('workers', null, 'type', 'workers must be an object with enabled and models'));
107
+ } else {
108
+ for (const field of Object.keys(workers)) {
109
+ if (field !== 'enabled' && field !== 'models') {
110
+ errors.push(entry(`workers.${field}`, null, 'unknown-key', `${field} is not a workers field; expected enabled, models`));
111
+ }
112
+ }
113
+
114
+ if (Object.hasOwn(workers, 'enabled')) {
115
+ const key = 'workers.enabled';
116
+ const value = workers.enabled;
117
+ const spec = registry.keys.get(key);
118
+ const allowed = registry.enumValues(key);
119
+ if (value === null) {
120
+ errors.push(entry(key, null, 'type', `${key} cannot be null; leave the key out`));
121
+ } else {
122
+ const fault = validateValue(spec, value, allowed);
123
+ if (fault) {
124
+ let index = -1;
125
+ if (Array.isArray(value)) {
126
+ for (let i = 0; i < value.length; i += 1) {
127
+ if (validateValue(spec, value.slice(0, i + 1), allowed)) { index = i; break; }
128
+ }
129
+ }
130
+ if (index >= 0) {
131
+ const prefixFault = validateValue(spec, value.slice(0, index + 1), allowed);
132
+ errors.push(entry(`${key}[${index}]`, null, prefixFault.code, prefixFault.reason, allowed));
133
+ } else errors.push(entry(key, null, fault.code, fault.reason, allowed));
134
+ } else {
135
+ where ??= layerFile('project', ctx.env, ctx.projectRoot, ctx.fsImpl);
136
+ if ('error' in where) throw Object.assign(new Error(where.error), { usage: true });
137
+ const fault = layerEntry(registry, key, 'project', where.file, { ok: true, value },
138
+ { env: ctx.env, projectRoot: ctx.projectRoot, fsImpl: ctx.fsImpl, notes, siblings: {} });
139
+ if (fault) {
140
+ if (fault.code === 'not-narrower') {
141
+ const match = fault.message.match(/\('([^']*)' is not in the value below\)/);
142
+ const index = match ? value.indexOf(match[1]) : -1;
143
+ if (index >= 0) fault.key = `${key}[${index}]`;
144
+ }
145
+ errors.push(fault);
146
+ }
147
+ }
148
+ }
149
+ }
150
+
151
+ if (Object.hasOwn(workers, 'models')) {
152
+ const key = 'workers.models';
153
+ const value = workers.models;
154
+ const spec = registry.keys.get(key);
155
+ const allowed = registry.enumValues(key);
156
+ if (value === null) {
157
+ errors.push(entry(key, null, 'type', `${key} cannot be null; leave the key out`));
158
+ } else {
159
+ const fault = validateValue(spec, value, allowed);
160
+ if (fault) {
161
+ errors.push(entry(key, null, fault.code, fault.reason, allowed));
162
+ } else {
163
+ const { loadRegistry } = require('../../registry/index.js');
164
+ const { workerCatalogue } = require('../worker/catalogue.js');
165
+ const workerRegistry = loadRegistry({ repoRoot: ctx.repoRoot, fsImpl: ctx.fsImpl });
166
+ const catalogue = workerCatalogue({ registry: workerRegistry });
167
+ const byId = new Map (catalogue.map((worker ) => [worker.id, worker]));
168
+ for (const id of Object.keys(value)) {
169
+ const worker = byId.get(id);
170
+ if (!worker) {
171
+ notes.push(`note: ${key}: worker '${id}' is not installed (ignored)`);
172
+ continue;
173
+ }
174
+ const status = worker.worker.model?.status || 'absent';
175
+ if (status === 'supported') continue;
176
+ for (const tier of Object.keys(value[id])) {
177
+ errors.push(entry(bindingKey(id, tier), null, 'model-flag-unevidenced',
178
+ `${id} has no evidenced model flag (descriptor model is ${status}); dispatch would skip the worker for this binding`));
179
+ }
180
+ }
181
+ }
182
+ }
183
+ }
184
+ }
185
+ }
186
+
187
+ const presets = isObject(doc.presets) ? doc.presets : {};
188
+ if (Object.hasOwn(doc, 'presets') && !isObject(doc.presets)) errors.push(entry('presets', null, 'type', 'presets must be an object with review and debate preset ids'));
189
+ let loaded = null; // the start verb's inputs, loaded once and only when a preset is checked
190
+ const inputs = () => (loaded ??= ctx.inputs(taskClass));
191
+ for (const kind of ['review', 'debate']) {
192
+ if (!Object.hasOwn(presets, kind)) continue;
193
+ const order = presetIds(protocols, kind); // the effective core order: the only ids that have a rank
194
+ const named = [...order, ...contributedPresetIds(protocols, kind)]; // selectable by name (122 IC-014)
195
+ const key = `presets.${kind}`;
196
+ const id = presets[kind];
197
+ if (typeof id !== 'string' || !named.includes(id)) {
198
+ errors.push(entry(key, null, 'enum', `${typeof id === 'string' ? id : JSON.stringify(id)} is not a ${kind} preset`, named));
199
+ continue;
200
+ }
201
+ // The effective preset setting (C-3): the user/project/local layers `review --action start` reads, with this
202
+ // file's own entry as the project layer, applied by the resolver's rule (settingsPick) to the loosest preset.
203
+ const settingKey = `${kind}.preset`;
204
+ const view = { ...inputs().settingsView };
205
+ const current = view[settingKey];
206
+ if (typeof settings[settingKey] === 'string' && (!current || ['default', 'user', 'project'].includes(current.layer))) {
207
+ view[settingKey] = { value: settings[settingKey], layer: 'project' };
208
+ }
209
+ const pick = settingsPick({ kind, pick: { id: order[order.length - 1], origin: 'registry' }, settingsView: view, registry: protocols });
210
+ // Two core ids compare by their place in the order; a pair with a contributed id by dominance (never an indexOf of -1).
211
+ if (!atLeastAsStrict(protocols, kind, id, pick.id)) {
212
+ errors.push(entry(key, null, 'not-narrower', `${id} is looser than settings["${settingKey}"] ${pick.id} (${pick.origin} layer)`));
213
+ continue;
214
+ }
215
+ if (kind !== 'review') continue;
216
+ if (taskClass === null) continue;
217
+ // maint-thin: the seat floor binds unless autopilot is verifiably off (reviewUnattended), as in review start.
218
+ const floor = inputs().unattended === false ? 0 : gateMinSeats(inputs().gatePolicy, taskClass);
219
+ const seats = protocols.presets[id].seats.length;
220
+ if (seats < floor) { // the seat floor (L-E1); the E.1-control-floor mutation targets this line
221
+ errors.push(entry(key, null, 'not-narrower', `${id} has ${seats} seat${seats === 1 ? '' : 's'}; the gate-b floor for ${taskClass} is ${floor} seats`));
222
+ }
223
+ }
224
+
225
+ for (const key of Object.keys(doc)) {
226
+ if (KNOWN.has(key)) continue;
227
+ errors.push(entry(key, null, 'unknown-key', `${key} is not a prepared-run field; expected schema, taskClass, primary, workers, settings, presets`));
228
+ }
229
+ return errors;
230
+ }
231
+
232
+ function report(file , errors , json , notes = []) {
233
+ if (json) console.log(JSON.stringify({ ok: errors.length === 0, errors, ...(notes.length ? { notes } : {}) }, null, 2));
234
+ else {
235
+ if (errors.length === 0) console.log(`prepared run: valid (${file})`);
236
+ else for (const error of errors) console.error(`${error.key}: ${error.message}`);
237
+ for (const note of notes) console.log(note);
238
+ }
239
+ return finishRuntime(errors.length === 0 ? 0 : 1);
240
+ }
241
+
242
+ function handlePreparedRun({ file, json, registry, repoRoot, projectRoot, env, fsImpl = nodeFs } ) {
243
+ let text ;
244
+ try { text = fsImpl.readFileSync(path.resolve(file), 'utf8'); } catch { return usageError('config', `cannot read the prepared run ${file}`, json); }
245
+ let doc ;
246
+ try { doc = JSON.parse(text); } catch { return report(file, [entry('$', null, 'type', 'the prepared run is not valid JSON')], json); }
247
+ if (!isObject(doc)) return report(file, [entry('$', null, 'type', 'the prepared run must be a JSON object')], json);
248
+
249
+ try {
250
+ const protocols = loadProtocols({ repoRoot, fsImpl });
251
+ if (!protocols.ok) throw new Error(protocols.errors.join('; '));
252
+ const root = path.resolve(projectRoot);
253
+ const notes = [];
254
+ const ctx = {
255
+ registry, protocols: protocols.registry, classes: taskClasses(repoRoot, fsImpl), env, projectRoot: root, repoRoot, fsImpl, notes,
256
+ // what `review --action start` loads: the protocol settings view and gates.json narrowed by the project's override
257
+ inputs: (taskClass ) => {
258
+ const inputs = require('../review/start.js').loadProtocolInputs(taskClass, root, env);
259
+ if ('error' in inputs) throw Object.assign(new Error(inputs.message), { usage: true });
260
+ return inputs;
261
+ },
262
+ };
263
+ return report(file, validatePrepared(doc, ctx), json, notes);
264
+ } catch (error ) {
265
+ return usageError('config', error?.message || String(error), json, error.usage ? undefined : error);
266
+ }
267
+ }
268
+
269
+ module.exports = { handlePreparedRun, bindingKey };
@@ -0,0 +1,118 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ /**
6
+ * 086 IC-007, DD-9: where the project layer of the settings lives. Locates files only; both project
7
+ * files are untrusted (D-16) and the resolver reads them. A linked worktree with no settings of its
8
+ * own falls back to the main checkout's `.doflow/` (OI-4, a declared change in this release), found by reading
9
+ * `.git` (`gitdir: <g>`), `<g>/gitdir` (the back-pointer to this worktree, so a forged or stale `.git`
10
+ * inherits nothing) and `<g>/commondir`: three small reads, no process (no `git worktree list` per
11
+ * verb). A sandbox (`.doflow-worktree-base`, which every dispatch worktree under
12
+ * `<root>/.doflow/state/dispatch-worktrees/<id>` carries) stays isolated and uses only its own.
13
+ *
14
+ * The root is the caller's, authoritative: nothing walks up to a nearer `.git`, so a folder with its own
15
+ * `.doflow` inside a repository gets its own layers, as 2.0 read them. Every fault yields the checkout's
16
+ * own `.doflow`, never a throw. A `.doflow` that is a symlink out of its checkout is not followed: the own
17
+ * one yields no project layer, a main checkout's falls back to the own. Pure data: the same tree gives the same answer, so the caller may memoize per process.
18
+ */
19
+
20
+ const nodeFs = require('node:fs');
21
+ const path = require('node:path');
22
+
23
+ const MARKER = '.doflow-worktree-base';
24
+ const SETTINGS_FILES = Object.freeze(['settings.json', 'settings.local.json', 'decision-model.json', 'prune']);
25
+ const MAX_POINTER_BYTES = 4096;
26
+ const GITDIR_LINE = /^gitdir:[ \t]*([^\r\n]+?)[ \t]*\r?\n?$/;
27
+
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+ function lstatOrNull(p , fsImpl ) {
38
+ try { return fsImpl.lstatSync(p); } catch { return null; }
39
+ }
40
+
41
+ /** True when `<root>/.doflow` may serve as a layer folder: absent is fine (the files are absent
42
+ * too), a real folder is fine, a symlink must resolve inside `root`. */
43
+ function containedDoflow(root , fsImpl ) {
44
+ const dir = path.join(root, '.doflow');
45
+ const stat = lstatOrNull(dir, fsImpl);
46
+ if (!stat || !stat.isSymbolicLink()) return true;
47
+ try {
48
+ const rel = path.relative(fsImpl.realpathSync(root), fsImpl.realpathSync(dir));
49
+ return !!rel && rel !== '..' && !rel.startsWith(`..${path.sep}`) && !path.isAbsolute(rel);
50
+ } catch { return false; }
51
+ }
52
+
53
+ function holdsSettings(root , fsImpl ) {
54
+ return SETTINGS_FILES.some((name) => lstatOrNull(path.join(root, '.doflow', name), fsImpl) !== null);
55
+ }
56
+
57
+ /** A small regular file's text, or null (missing, not a file, over the bound, unreadable). */
58
+ function readPointer(file , fsImpl ) {
59
+ try {
60
+ const stat = fsImpl.statSync(file);
61
+ if (!stat.isFile() || stat.size > MAX_POINTER_BYTES) return null;
62
+ const text = String(fsImpl.readFileSync(file, 'utf8'));
63
+ return text.includes('\0') ? null : text;
64
+ } catch { return null; }
65
+ }
66
+
67
+ /** The main checkout a linked worktree at `root` belongs to, from `.git` and `commondir`; null on any fault. */
68
+ function mainCheckoutOf(root , fsImpl ) {
69
+ const pointer = readPointer(path.join(root, '.git'), fsImpl);
70
+ const match = pointer === null ? null : GITDIR_LINE.exec(pointer);
71
+ if (!match) return null;
72
+ const gitdir = path.resolve(root, match[1]);
73
+ // git writes `<gitdir>/gitdir` pointing back at this worktree's `.git` file; a `.git` file that names
74
+ // some other worktree's admin dir (hostile or stale after a move) fails this and inherits nothing
75
+ const back = readPointer(path.join(gitdir, 'gitdir'), fsImpl);
76
+ const backLine = back === null ? '' : back.replace(/\r?\n$/, '');
77
+ if (!backLine || /[\r\n]/.test(backLine)) return null;
78
+ try {
79
+ if (fsImpl.realpathSync(path.resolve(gitdir, backLine)) !== fsImpl.realpathSync(path.join(root, '.git'))) return null;
80
+ } catch { return null; }
81
+ const common = readPointer(path.join(gitdir, 'commondir'), fsImpl);
82
+ const line = common === null ? '' : common.replace(/\r?\n$/, '');
83
+ if (!line || /[\r\n]/.test(line)) return null;
84
+ const commonDir = path.resolve(gitdir, line);
85
+ return path.basename(commonDir) === '.git' ? path.dirname(commonDir) : null;
86
+ }
87
+
88
+ function located(root , checkout , fsImpl ) {
89
+ if (!containedDoflow(root, fsImpl)) return { layersFrom: null, checkout: 'own', files: [] };
90
+ const layersFrom = path.join(root, '.doflow');
91
+ return { layersFrom, checkout, files: [path.join(layersFrom, 'settings.local.json'), path.join(layersFrom, 'settings.json')] };
92
+ }
93
+
94
+ /**
95
+ * @param projectRoot the checkout root, authoritative: the caller's own root, with no walk-up
96
+ * @param fsImpl `lstatSync`, `statSync`, `readFileSync`, `realpathSync`
97
+ * @returns the `.doflow` folder and the ordered layer files, with which checkout they belong to
98
+ */
99
+ function projectLayerSource(projectRoot , fsImpl = nodeFs) {
100
+ if (!projectRoot) return { layersFrom: null, checkout: null, files: [] };
101
+ try {
102
+ const root = path.resolve(projectRoot);
103
+ const git = lstatOrNull(path.join(root, '.git'), fsImpl);
104
+ if (git && git.isDirectory()) return located(root, 'own', fsImpl);
105
+ if (lstatOrNull(path.join(root, MARKER), fsImpl)) return located(root, 'sandbox', fsImpl);
106
+ if (!git) {
107
+ return lstatOrNull(path.join(root, '.doflow'), fsImpl) ? located(root, 'own', fsImpl) : { layersFrom: null, checkout: 'own', files: [] };
108
+ }
109
+ if (holdsSettings(root, fsImpl)) return located(root, 'own', fsImpl);
110
+ const main = mainCheckoutOf(root, fsImpl);
111
+ if (main && lstatOrNull(path.join(main, '.doflow'), fsImpl) && containedDoflow(main, fsImpl)) return located(main, 'main', fsImpl);
112
+ return located(root, 'own', fsImpl);
113
+ } catch {
114
+ return { layersFrom: null, checkout: null, files: [] };
115
+ }
116
+ }
117
+
118
+ module.exports = { projectLayerSource, containedDoflow, MARKER };