@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,336 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * `doflow rules`: the read-only view of the values the runtime owns for a project, each with its source, so
5
+ * guidance and policy text can point here instead of copying numbers. Four sections:
6
+ * review the protocol `review --action start` resolves per task class, attended and unattended (the real
7
+ * `resolveProtocol`; nothing is copied from the registries)
8
+ * gates per class and gate `autopilot` and `minSeats` from the packaged gates.json, and the loop limits
9
+ * tiers owner -> tier, and tier -> model per worker from `workers.models`
10
+ * settings the effective value and layer of the keys above
11
+ * A value that cannot be resolved prints as `unresolved:<reason>`. The verb writes nothing and spawns only `git`
12
+ * (to say whether develop holds a committed gate policy).
13
+ */
14
+
15
+ const fs = require('node:fs');
16
+ const path = require('node:path');
17
+ const { spawnSync } = require('node:child_process');
18
+ const { REPO_ROOT } = require('../helper/repo-root.js');
19
+ const { finishRuntime, usageError, captureCaught } = require('./cli-result.js');
20
+ const { resolveProtocol } = require('./review/resolve-protocol.js');
21
+ const { invalidProjectCandidate, invalidPresetCandidate } = require('./review/findings.js');
22
+
23
+ const REGISTRY = (file ) => path.join(REPO_ROOT, 'core', 'registry', file);
24
+ const PROTOCOLS_FILE = 'core/registry/protocols.json';
25
+ const GATES_FILE = 'core/registry/gates.json';
26
+ const MODELS_FILE = 'core/registry/models.json';
27
+ const SETTINGS_FILE = 'core/registry/settings.json';
28
+ const VERIFICATION_FILE = 'core/registry/verification.json';
29
+ // Keys of the settings section, beyond the ones `resolveProtocol` reads.
30
+ const REPORT_SETTINGS = ['review.preset', 'review.rounds', 'review.sameFamily', 'autopilot.enabled'];
31
+ const GIT_LOCATION_KEYS = ['GIT_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_OBJECT_DIRECTORY', 'GIT_COMMON_DIR', 'GIT_ALTERNATE_OBJECT_DIRECTORIES'];
32
+
33
+ const unresolved = (reason ) => `unresolved:${String(reason instanceof Error ? reason.message : reason).split('\n')[0].slice(0, 160)}`;
34
+ const isUnresolved = (value ) => typeof value === 'string' && value.startsWith('unresolved:');
35
+
36
+ /** Where an origin or layer name comes from: a registry file for a registry value, else the settings layer. */
37
+ function sourceOf(origin ) {
38
+ switch (origin) {
39
+ case 'registry': return PROTOCOLS_FILE;
40
+ case 'floor': return GATES_FILE;
41
+ case 'risk': return VERIFICATION_FILE;
42
+ case 'default': return SETTINGS_FILE;
43
+ case 'env': return 'environment';
44
+ default: return `settings:${origin}`;
45
+ }
46
+ }
47
+
48
+ const readJson = (file ) => JSON.parse(fs.readFileSync(REGISTRY(file), 'utf8'));
49
+
50
+ /** The effective `{ value, layer, source }` of a setting, or `{ value: 'unresolved:…' }` when the resolver cannot say. */
51
+ function settingEntry(settings , key , view ) {
52
+ try {
53
+ const resolution = view ? settings.get(key, view) : settings.get(key);
54
+ // As the protocol loader judges them: a preset key refuses any invalid layer, another review.* key an invalid project or local one.
55
+ const invalid = key.endsWith('.preset') ? invalidPresetCandidate(resolution) : key.startsWith('review.') ? invalidProjectCandidate(resolution) : undefined;
56
+ if (resolution.state === 'invalid' || invalid) {
57
+ const candidate = invalid ?? resolution.candidates.find((c ) => c.status === 'invalid');
58
+ return { value: unresolved(`invalid ${key}${candidate ? ` in ${candidate.source}: ${candidate.reason}` : ''}`), layer: null, source: null };
59
+ }
60
+ const layer = resolution.origin?.layer ?? 'default';
61
+ return { value: resolution.value ?? null, layer, source: sourceOf(layer) };
62
+ } catch (error) {
63
+ return { value: unresolved(error), layer: null, source: null };
64
+ }
65
+ }
66
+
67
+ /** The keys `resolveProtocol` reads from the settings, from the registry's own `consumers` lists. */
68
+ function protocolSettingKeys() {
69
+ return Object.entries (readJson('settings.json').keys)
70
+ .filter(([key, spec]) => key.startsWith('review.') && (spec.consumers ?? []).some((c ) => c.endsWith('review/resolve-protocol.ts')))
71
+ .map(([key]) => key);
72
+ }
73
+
74
+ function gitShow(project , args ) {
75
+ const env = { ...process.env };
76
+ for (const key of GIT_LOCATION_KEYS) delete env[key];
77
+ const res = spawnSync('git', ['-C', project, ...args], { env, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 10000 });
78
+ return { status: res.status, stdout: typeof res.stdout === 'string' ? res.stdout : '' };
79
+ }
80
+
81
+ /** Whether the project's local develop holds a committed gate policy, and whether it is the packaged one. The working tree is never read. */
82
+ function projectPolicyState(project , packagedText ) {
83
+ const develop = gitShow(project, ['rev-parse', '--verify', '-q', 'refs/heads/develop']);
84
+ if (develop.status === null) return unresolved('git cannot run');
85
+ if (develop.status !== 0) return unresolved('no local develop branch');
86
+ const blob = gitShow(project, ['show', `refs/heads/develop:${GATES_FILE}`]);
87
+ if (blob.status !== 0) return 'absent';
88
+ return blob.stdout === packagedText ? 'committed on develop, same as packaged' : 'committed on develop, differs from packaged';
89
+ }
90
+
91
+
92
+
93
+ /** The gate policy as `review --action start` reads it (shipped gates.json narrowed by the project's override), and what the override did. */
94
+ function loadPolicyFor(project ) {
95
+ try {
96
+ const loaded = require('./review/start.js').loadGatePolicy(project);
97
+ if (!loaded.ok) return { policy: null, override: `invalid:${loaded.reason}`, reason: loaded.reason };
98
+ return { policy: loaded.policy, override: loaded.override, reason: null };
99
+ } catch (error) {
100
+ const reason = unresolved(error).slice('unresolved:'.length);
101
+ return { policy: null, override: `invalid:${reason}`, reason };
102
+ }
103
+ }
104
+
105
+ function gatesSection(taskClasses , project , loaded ) {
106
+ let packagedText = '';
107
+ try { packagedText = fs.readFileSync(REGISTRY('gates.json'), 'utf8'); } catch { /* the policy load has failed the same way */ }
108
+ const head = {
109
+ source: GATES_FILE, override: loaded.override,
110
+ projectPolicy: project ? projectPolicyState(project, packagedText) : unresolved('no project'),
111
+ };
112
+ const policy = loaded.policy;
113
+ if (!policy) return { policy: null, section: { ...head, classes: unresolved(loaded.reason), loop: unresolved(loaded.reason) } };
114
+ const classes = {};
115
+ for (const name of taskClasses) {
116
+ const gates = {};
117
+ for (const [gate, rule] of Object.entries (policy.classes?.[name] ?? {})) {
118
+ gates[gate] = {
119
+ autopilot: typeof rule?.autopilot === 'string' ? rule.autopilot : unresolved('autopilot not declared'),
120
+ minSeats: Number.isInteger(rule?.minSeats) && rule.minSeats > 0 ? rule.minSeats : 0,
121
+ };
122
+ }
123
+ classes[name] = gates;
124
+ }
125
+ return {
126
+ policy,
127
+ section: {
128
+ ...head,
129
+ classes,
130
+ loop: { maxSteps: policy.loop?.maxSteps ?? unresolved('loop.maxSteps not declared'), wip: policy.loop?.wip ?? unresolved('loop.wip not declared') },
131
+ },
132
+ };
133
+ }
134
+
135
+ function protocolRow(registry , protocol ) {
136
+ const preset = registry.presets[protocol.id];
137
+ const origin = protocol.origins.seats === 'floor' ? 'floor' : protocol.origins.id;
138
+ return {
139
+ preset: protocol.id,
140
+ seats: { count: protocol.seats.length, roles: protocol.seats.map((seat ) => seat.agent) },
141
+ rounds: { min: preset.rounds.min, max: registry.hardRoundCap, default: preset.rounds.default, effective: protocol.rounds },
142
+ crossAnswer: protocol.crossAnswer,
143
+ synthesizer: protocol.synthesizer,
144
+ origin,
145
+ source: sourceOf(origin),
146
+ ...(protocol.disagreements.length ? { disagreements: protocol.disagreements } : {}),
147
+ };
148
+ }
149
+
150
+ /** The review section: `resolveProtocol` per class, attended (floor 0) and unattended (the class's gate-b floor). */
151
+ function reviewSection(taskClasses , settings , gatePolicy , gatesReason ) {
152
+ const classes = {};
153
+ let inputs ;
154
+ try {
155
+ const protocols = require('./review/protocols.js').loadProtocols();
156
+ if (!protocols.ok) throw new Error(protocols.errors.join('; '));
157
+ const { loadVerificationRegistry, reviewDepthPolicy } = require('./verification/registry.js');
158
+ const verification = loadVerificationRegistry({ repoRoot: REPO_ROOT });
159
+ const settingsView = {};
160
+ for (const key of protocolSettingKeys()) {
161
+ const entry = settingEntry(settings, key);
162
+ if (isUnresolved(entry.value)) throw new Error(String(entry.value).slice('unresolved:'.length));
163
+ settingsView[key] = { value: entry.value, layer: entry.layer };
164
+ }
165
+ inputs = { registry: protocols.registry, settingsView, risk: verification.policy.defaultRiskLevel, riskPolicy: reviewDepthPolicy(verification) };
166
+ } catch (error) {
167
+ for (const name of taskClasses) classes[name] = { attended: unresolved(error), unattended: unresolved(error) };
168
+ return { classes };
169
+ }
170
+ for (const name of taskClasses) {
171
+ const row = (unattended ) => {
172
+ if (unattended && gatesReason) return unresolved(gatesReason);
173
+ try {
174
+ const resolved = resolveProtocol({
175
+ kind: 'review', taskClass: name, risk: inputs.risk, paths: null, runPreset: null, advicePick: null,
176
+ settingsView: inputs.settingsView, gatePolicy, unattended, registry: inputs.registry, riskPolicy: inputs.riskPolicy,
177
+ });
178
+ return resolved.ok ? protocolRow(inputs.registry, resolved.protocol) : unresolved(`${resolved.reason}:${resolved.field}`);
179
+ } catch (error) {
180
+ return unresolved(error);
181
+ }
182
+ };
183
+ classes[name] = { attended: row(false), unattended: row(true) };
184
+ }
185
+ return { classes };
186
+ }
187
+
188
+ function tiersSection(env ) {
189
+ let tiers ;
190
+ try {
191
+ tiers = readJson('models.json').dispatchTiers;
192
+ if (!tiers) throw new Error(`${MODELS_FILE} has no dispatchTiers`);
193
+ } catch (error) {
194
+ return unresolved(error);
195
+ }
196
+ let models ;
197
+ try {
198
+ const { readBindings } = require('./worker/models.js');
199
+ const bindings = readBindings({ env, projectRoot: null });
200
+ const { knownWorkerIds } = require('../registry/worker-ids.js');
201
+ const workers = knownWorkerIds({ registry: { harnesses: readJson('harnesses.json').harnesses } }).ids;
202
+ models = bindings.state === 'invalid'
203
+ ? unresolved(`workers.models ${bindings.reason}`)
204
+ : Object.fromEntries(workers.map((worker) => [worker, Object.fromEntries(tiers.tiers.map((tier ) => [tier, bindings.map?.[worker]?.[tier] ?? 'unset']))]));
205
+ } catch (error) {
206
+ models = unresolved(error);
207
+ }
208
+ return {
209
+ source: MODELS_FILE, default: tiers.default, tiers: tiers.tiers, byOwner: tiers.byOwner,
210
+ modelsSource: 'settings:user workers.models (global-only)', models,
211
+ };
212
+ }
213
+
214
+ /** The report for `project` (null with `global`: the user layer only). */
215
+ function buildRulesReport({ taskClass = null, global = false, project = null, env = process.env } = {}) {
216
+ let taskClasses ;
217
+ try {
218
+ taskClasses = Object.keys(readJson('gates.json').classes);
219
+ } catch (error) {
220
+ return { schema: 1, review: unresolved(error), gates: unresolved(error), tiers: tiersSection(env), settings: {} };
221
+ }
222
+ if (taskClass) taskClasses = taskClasses.filter((name) => name === taskClass);
223
+
224
+ let settings = null;
225
+ let settingsReason = null;
226
+ try {
227
+ settings = require('./settings/resolve.js').loadSettings({ projectRoot: project, global, env: { ...env } });
228
+ } catch (error) {
229
+ settingsReason = unresolved(error);
230
+ }
231
+
232
+ let globalSettings = settings;
233
+ if (settings && !global) {
234
+ try { globalSettings = require('./settings/resolve.js').loadSettings({ global: true, env: { ...env } }); } catch (error) { globalSettings = null; }
235
+ }
236
+ const loadedPolicy = loadPolicyFor(global ? null : project);
237
+ const gates = gatesSection(taskClasses, global ? null : project, loadedPolicy);
238
+ const report = { schema: 1 };
239
+ report.review = settings
240
+ ? reviewSection(taskClasses, settings, gates.policy, loadedPolicy.reason)
241
+ : { classes: Object.fromEntries(taskClasses.map((name) => [name, { attended: settingsReason, unattended: settingsReason }])) };
242
+ report.review.override = loadedPolicy.override;
243
+ report.gates = gates.section;
244
+ report.tiers = tiersSection(env);
245
+ report.settings = {};
246
+ for (const key of REPORT_SETTINGS) {
247
+ // The autopilot switch is global-only: read it from the global view whatever the project says.
248
+ const view = key === 'autopilot.enabled' ? globalSettings : settings;
249
+ report.settings[key] = view
250
+ ? settingEntry(view, key, key === 'autopilot.enabled' ? 'global' : undefined)
251
+ : { value: settingsReason ?? unresolved('settings unreadable'), layer: null, source: null };
252
+ }
253
+ return report;
254
+ }
255
+
256
+ function seatsText(seats ) {
257
+ const counts = new Map ();
258
+ for (const role of seats.roles) counts.set(role, (counts.get(role) ?? 0) + 1);
259
+ return `${seats.count} seat${seats.count === 1 ? '' : 's'} (${[...counts].map(([role, n]) => (n > 1 ? `${role} x${n}` : role)).join(', ')})`;
260
+ }
261
+
262
+ function protocolText(row ) {
263
+ if (typeof row === 'string') return row;
264
+ return `${row.preset}: ${seatsText(row.seats)}, rounds ${row.rounds.effective} (min ${row.rounds.min}, max ${row.rounds.max}), cross-answer ${row.crossAnswer ? 'on' : 'off'}, synthesizer ${row.synthesizer}, origin=${row.origin}${row.disagreements ? ` [ignored: ${row.disagreements.map((d ) => `${d.field}=${d.value} ${d.reason}`).join('; ')}]` : ''}`;
265
+ }
266
+
267
+ /** The compact text form (about 45 lines for all nine classes). */
268
+ function rulesLines(report ) {
269
+ const lines = [];
270
+ if (typeof report.review === 'string') lines.push(`review: ${report.review}`);
271
+ else lines.push(`review: protocol per task class, as \`review --action start\` resolves it (origin: registry=${PROTOCOLS_FILE}, floor=${GATES_FILE} gate-b minSeats, user|project|local=settings layer; gate override: ${report.review.override ?? 'none'})`);
272
+ for (const [name, rows] of Object.entries (report.review.classes ?? {})) {
273
+ lines.push(` ${name} attended: ${protocolText(rows.attended)}`);
274
+ lines.push(` ${name} unattended: ${protocolText(rows.unattended)}`);
275
+ }
276
+ lines.push('');
277
+ if (typeof report.gates === 'string') lines.push(`gates: ${report.gates}`);
278
+ else {
279
+ lines.push(`gates: ${report.gates.source}; override: ${report.gates.override}; develop policy: ${report.gates.projectPolicy}`);
280
+ const none = [];
281
+ if (typeof report.gates.classes === 'string') lines.push(` ${report.gates.classes}`);
282
+ else for (const [name, gates] of Object.entries (report.gates.classes)) {
283
+ const entries = Object.entries (gates);
284
+ if (entries.length === 0) none.push(name);
285
+ for (const [gate, rule] of entries) lines.push(` ${name} ${gate}: autopilot=${rule.autopilot} minSeats=${rule.minSeats}`);
286
+ }
287
+ if (none.length) lines.push(` no gates declared: ${none.join(', ')}`);
288
+ if (typeof report.gates.loop !== 'string') lines.push(` loop: maxSteps=${report.gates.loop.maxSteps} wip=${report.gates.loop.wip}`);
289
+ }
290
+ lines.push('');
291
+ if (typeof report.tiers === 'string') lines.push(`tiers: ${report.tiers}`);
292
+ else {
293
+ lines.push(`tiers: owner -> tier from ${report.tiers.source} (default ${report.tiers.default}; tiers ${report.tiers.tiers.join(', ')})`);
294
+ lines.push(` ${Object.entries(report.tiers.byOwner).map(([owner, tier]) => `${owner}=${tier}`).join(' ')}`);
295
+ if (typeof report.tiers.models === 'string') lines.push(` models: ${report.tiers.models}`);
296
+ else {
297
+ const set = Object.entries (report.tiers.models).filter(([, byTier]) => Object.values(byTier).some((model) => model !== 'unset'));
298
+ lines.push(` model per worker and tier (${report.tiers.modelsSource}): ${set.length === 0 ? `unset for all ${Object.keys(report.tiers.models).length} workers` : ''}`.trimEnd());
299
+ for (const [worker, byTier] of set) lines.push(` ${worker}: ${Object.entries(byTier).map(([tier, model]) => `${tier}=${model}`).join(' ')}`);
300
+ }
301
+ }
302
+ lines.push('');
303
+ lines.push('settings: effective value and layer');
304
+ for (const [key, entry] of Object.entries (report.settings)) {
305
+ lines.push(` ${key} = ${isUnresolved(entry.value) ? entry.value : JSON.stringify(entry.value)}${entry.layer ? ` (${entry.layer}, ${entry.source})` : ''}`);
306
+ }
307
+ return lines;
308
+ }
309
+
310
+ function handleRulesCommand({ taskClass, json = false, global = false, positional = [], env = process.env, cwd = process.cwd(), out = (line ) => console.log(line) } = {}) {
311
+ const usage = (message ) => usageError('rules', message, false);
312
+ try {
313
+ if (positional.length > 1) return usage(`unexpected positional argument '${positional[1]}'`);
314
+ if (global && positional.length > 0) return usage('-g reads the user layer only and takes no project');
315
+ let project = null;
316
+ if (!global) {
317
+ project = path.resolve(cwd, positional[0] ?? '.');
318
+ if (!fs.existsSync(project) || !fs.statSync(project).isDirectory()) return usage(`project '${positional[0]}' is not a directory`);
319
+ }
320
+ if (taskClass != null) {
321
+ let known = [];
322
+ try { known = Object.keys(readJson('gates.json').classes); } catch { /* every class is then unchecked; the report says unresolved */ }
323
+ if (known.length > 0 && !known.includes(taskClass)) return usage(`--task-class '${taskClass}' is not a task class (${known.join(', ')})`);
324
+ }
325
+ const report = buildRulesReport({ taskClass, global, project, env });
326
+ if (json) out(JSON.stringify(report));
327
+ else for (const line of rulesLines(report)) out(line);
328
+ return finishRuntime(0);
329
+ } catch (error ) {
330
+ captureCaught(error, 'rules', 1);
331
+ console.error(`[ERROR] rules: ${error?.message || String(error)}`);
332
+ return finishRuntime(1);
333
+ }
334
+ }
335
+
336
+ module.exports = { handleRulesCommand, buildRulesReport, rulesLines };
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * Artifact parsing — turns the text of `requirement.md`, `design.md` and `plan.md` into the
7
7
  * structures `scaffold.js`'s generator reads: a requirement index and acceptance criteria, a
8
- * component index and whatever §4 declares as an interface, and the task list that is where the
8
+ * component index and whatever API / Interface Contracts declares as an interface, and the task list that is where the
9
9
  * intended file layout actually comes from.
10
10
  *
11
11
  * Pure text-in, structure-out: nothing here touches the filesystem or knows about the scaffold
@@ -93,7 +93,7 @@ function parseRequirement(text ) {
93
93
  }
94
94
 
95
95
  /**
96
- * `design.md` → the component index (traceability) and whatever §4 declares as an interface
96
+ * `design.md` → the component index (traceability) and whatever API / Interface Contracts declares as an interface
97
97
  * (carried through verbatim rather than paraphrased).
98
98
  */
99
99
  function parseDesign(text ) {
@@ -113,7 +113,7 @@ function parseDesign(text ) {
113
113
  gaps.push({ artifact: 'design.md', what: 'component index', why: 'no `| Cn | … |` table row found under a "Components & Boundaries" heading' });
114
114
  }
115
115
 
116
- // §4's subsections name their component in the heading — "### 4.5 Scaffold output contract (C16)".
116
+ // the Interface Contracts subsections name their component in the heading — "### 4.5 Scaffold output contract (C16)".
117
117
  // That attribution is what lets a declared interface reach the file that implements it, instead
118
118
  // of being restated from memory somewhere downstream.
119
119
  const contractBody = section(text, /API\s*\/?\s*Interface Contracts|Interface Contracts/i);
@@ -151,20 +151,57 @@ function parseDesign(text ) {
151
151
  * `external-contract:` names the case this generator cannot help with: a dependency with no local
152
152
  * repo to scan — a vendor API, a SaaS integration, a service in another org — whose contract exists
153
153
  * only as a document. The doc is an *input* to the scaffold, never part of it.
154
+ *
155
+ * `tier:` (106 review R-1) is the task line's dispatch tier, read by dispatch the way brief-tools
156
+ * reads it. It never opens the metadata, and the parsed task carries `tier` only when the line has
157
+ * one, so a line without it parses exactly as before; the line hash and the graph tuples list their
158
+ * fields and do not read it.
159
+ */
160
+ const TASK_KEYS = ['owner', 'files', 'depends-on', 'external-contract', 'acceptance', 'budget', 'tier'];
161
+
162
+ /** DEC-021: the keys that open the metadata wherever they follow whitespace or a dash (8ce5ad7). */
163
+ const LEGACY_KEY_PATTERN = /(?:^|[\s—-])(owner|files|depends-on|external-contract):\s/;
164
+ /** DEC-021: the 080 keys open the metadata only after the em-dash separator. */
165
+ const NEW_KEY_PATTERN = /—\s*(acceptance|budget):\s/;
166
+
167
+ /** A `depends-on:` value naming another task of the same plan (IC-001). */
168
+ const TASK_ID = /^[A-Za-z]+\.\d+$/;
169
+
170
+ /**
171
+ * The `**Task graph:**` header before the first `## ` section (IC-001, DEC-008): `'v1'` opts the
172
+ * plan into the graph rules, absence is `null`, and any other value is kept as `{ invalid }` for
173
+ * IC-003's `bad-header`.
154
174
  */
155
- const TASK_KEYS = ['owner', 'files', 'depends-on', 'external-contract'];
175
+ function parseGraphHeader(text ) {
176
+ for (const line of text.split(/\r?\n/)) {
177
+ if (/^##\s/.test(line)) break;
178
+ const match = line.match(/^\*\*Task graph:\*\*\s*(.*)$/);
179
+ if (match) return match[1].trim() === 'v1' ? 'v1' : { invalid: match[1].trim() };
180
+ }
181
+ return null;
182
+ }
183
+
184
+ /** `budget: attempts=<n>`, n in 1..999; anything else is kept verbatim as `{ invalid }`. */
185
+ function parseBudget(value ) {
186
+ if (value === undefined) return null;
187
+ const raw = value.replace(/`/g, '').trim();
188
+ const match = raw.match(/^attempts=([0-9]+)$/);
189
+ const attempts = match ? Number(match[1]) : NaN;
190
+ return attempts >= 1 && attempts <= 999 ? { attempts } : { invalid: raw };
191
+ }
156
192
 
157
193
  /** `plan.md` → the task list, which is where the intended file layout actually comes from. */
158
194
  function parsePlan(text ) {
159
195
  const gaps = [];
160
196
  const tasks = [];
197
+ const graphHeader = parseGraphHeader(text);
161
198
 
162
199
  // Anchored on the whole heading: `### Task Summary` is orientation, not the task list, and a
163
200
  // loose match would read the summary table's rows as tasks.
164
201
  const body = section(text, /^##\s+(?:\d+\.\s*)?Tasks\s*$/i);
165
202
  if (body === null) {
166
203
  gaps.push({ artifact: 'plan.md', what: 'task list', why: 'no "Tasks" section' });
167
- return { tasks, gaps };
204
+ return { tasks, gaps, graphHeader };
168
205
  }
169
206
 
170
207
  for (const line of joinWrapped(body)) {
@@ -174,8 +211,13 @@ function parsePlan(text ) {
174
211
  if (!match) continue;
175
212
  const [, mark, id, rest] = match;
176
213
 
177
- const keyPattern = new RegExp(`(?:^|[\\s—-])(${TASK_KEYS.join('|')}):\\s`);
178
- const keyAt = rest.search(keyPattern);
214
+ // DEC-021 new-keys-open-after-separator: the four pre-080 keys open the metadata as on 8ce5ad7;
215
+ // `acceptance:` and `budget:` open it only after the ` — ` separator, so prose such as
216
+ // "Record the acceptance: criteria" in a description keeps its 8ce5ad7 parse and line hash.
217
+ // Once the metadata has started, every `;` chunk parses all six keys.
218
+ const legacyAt = rest.search(LEGACY_KEY_PATTERN);
219
+ const newAt = rest.search(NEW_KEY_PATTERN);
220
+ const keyAt = legacyAt === -1 ? newAt : newAt === -1 ? legacyAt : Math.min(legacyAt, newAt);
179
221
  const head = (keyAt === -1 ? rest : rest.slice(0, keyAt)).replace(/[\s—-]+$/, '').trim();
180
222
  const meta = keyAt === -1 ? '' : rest.slice(keyAt).replace(/^[\s—-]+/, '');
181
223
 
@@ -190,6 +232,9 @@ function parsePlan(text ) {
190
232
  }
191
233
 
192
234
  const list = (value ) => (value ? value.split(',').map((v) => v.replace(/`/g, '').trim()).filter(Boolean) : []);
235
+ const dependsOn = list(fields['depends-on']);
236
+ // A near-miss starts with a task id but carries more text (`A.1 (after review)`).
237
+ const nearMiss = (value ) => !TASK_ID.test(value) && TASK_ID.test(value.split(/[\s(]/)[0]);
193
238
 
194
239
  tasks.push({
195
240
  id,
@@ -199,15 +244,21 @@ function parsePlan(text ) {
199
244
  stories,
200
245
  owner: fields.owner || null,
201
246
  files: list(fields.files),
202
- dependsOn: list(fields['depends-on']),
247
+ dependsOn,
203
248
  externalContract: fields['external-contract'] || null,
249
+ parallel: /\[P\]/.test(head),
250
+ taskDeps: dependsOn.filter((value) => TASK_ID.test(value)),
251
+ nearMissDeps: dependsOn.filter(nearMiss),
252
+ acceptance: list(fields.acceptance),
253
+ budget: parseBudget(fields.budget),
254
+ ...(fields.tier === undefined ? {} : { tier: fields.tier.replace(/`/g, '').trim() }),
204
255
  });
205
256
  }
206
257
 
207
258
  if (tasks.length === 0) {
208
259
  gaps.push({ artifact: 'plan.md', what: 'task list', why: 'the Tasks section holds no `- [ ] <phase>.<n> …` items — there is no intended file layout to mirror' });
209
260
  }
210
- return { tasks, gaps };
261
+ return { tasks, gaps, graphHeader };
211
262
  }
212
263
 
213
264
  module.exports = {
@@ -336,13 +336,13 @@ function planSourceStubs({ byPath, scaffoldDir, repoRoot, fsImpl, slug, message,
336
336
  ? reqs.map((r ) => `requirement: ${r.id} — ${r.summary}`)
337
337
  : ['requirement: none — no user story on the owning task resolves to a requirement']),
338
338
  ...(comps.length
339
- ? comps.map((c ) => `component: ${c.id} — ${c.name} (design.md §3)`)
339
+ ? comps.map((c ) => `component: ${c.id} — ${c.name} (design.md Components & Boundaries)`)
340
340
  : ['component: none — no design component serves this task\'s requirements']),
341
341
  ];
342
342
 
343
343
  const bodyParts = [''];
344
344
  if (declared.length) {
345
- header.push(`shape: declared in design.md §4 (${[...new Set(declared.map((d ) => d.component))].join(', ')}), quoted below`);
345
+ header.push(`shape: declared in design.md API / Interface Contracts (${[...new Set(declared.map((d ) => d.component))].join(', ')}), quoted below`);
346
346
  for (const decl of declared) {
347
347
  const lines = decl.block.split('\n');
348
348
  const shown = lines.slice(0, 40);
@@ -354,11 +354,11 @@ function planSourceStubs({ byPath, scaffoldDir, repoRoot, fsImpl, slug, message,
354
354
  ].join('\n'));
355
355
  }
356
356
  } else {
357
- header.push('shape: not declared in design.md §4 — placeholder signature below');
357
+ header.push('shape: not declared in design.md API / Interface Contracts — placeholder signature below');
358
358
  // Not a skip — the file was emitted. Conflating the two would let 29 "we wrote it, but the
359
359
  // design never said what it should look like" rows hide inside a skip count, which is the
360
360
  // opposite of what the manifest is for.
361
- shapeUndeclared.push({ path: path.join('src', file) + STUB_SUFFIX, why: 'design.md §4 declares no interface for this file\'s component — the signature below is a placeholder, not a contract' });
361
+ shapeUndeclared.push({ path: path.join('src', file) + STUB_SUFFIX, why: 'design.md API / Interface Contracts declares no interface for this file\'s component — the signature below is a placeholder, not a contract' });
362
362
  }
363
363
 
364
364
  const pkg = snake(words(path.basename(path.dirname(file)) || 'scaffold')).replace(/_/g, '');
@@ -422,7 +422,7 @@ function planTestStubs({ requirement, componentsForRequirement, language, slug,
422
422
  'reads as coverage. Replace the body, do not delete the failure.',
423
423
  '',
424
424
  `requirement: ${id}${req ? ` — ${req.summary}` : ' — not present in the requirement index'}`,
425
- ...(comps.length ? comps.map((c ) => `component: ${c.id} — ${c.name} (design.md §3)`) : []),
425
+ ...(comps.length ? comps.map((c ) => `component: ${c.id} — ${c.name} (design.md Components & Boundaries)`) : []),
426
426
  `criteria: ${cases.length} from requirement.md §6`,
427
427
  `language: ${language.id} (detected from ${language.signal} by command-detect.ts)`,
428
428
  ];
@@ -442,15 +442,27 @@ function planTestStubs({ requirement, componentsForRequirement, language, slug,
442
442
  /**
443
443
  * contracts/ — partitioned here, generated by the prose algorithm. See the module header for why
444
444
  * the walk-up is not reimplemented in code.
445
+ * A task-id `depends-on:` value (`taskDeps`) is an edge in the task graph, not a service, so it
446
+ * gets no frame. A task whose `external-contract:` is left with no external value after that
447
+ * filter is pushed onto `notEvaluated` as `external-contract-orphaned` rather than dropped (IC-001).
445
448
  * @param {Array<Object>} tasks from `plan.tasks`
446
449
  * @param {Set<string>} ownedPaths every path a task in this plan already owns
450
+ * @param {Array<Object>} notEvaluated mutated with one entry per orphaned `external-contract:`
447
451
  * @returns {Array<Object>} externalDependencies
448
452
  */
449
- function partitionExternalDependencies(tasks , ownedPaths ) {
453
+ function partitionExternalDependencies(tasks , ownedPaths , notEvaluated ) {
450
454
  const externalDependencies = [];
451
455
  const dependencyTasks = new Map ();
452
456
  for (const task of tasks) {
453
- for (const dep of task.dependsOn) {
457
+ const taskDeps = new Set(task.taskDeps || []);
458
+ const external = task.dependsOn.filter((dep ) => !taskDeps.has(dep));
459
+ if (task.externalContract && external.length === 0) {
460
+ notEvaluated.push({
461
+ what: `plan.md → ${task.id} external-contract: ${task.externalContract}`,
462
+ why: 'external-contract-orphaned — no external depends-on: value remains once task-id dependencies are removed, so no contract frame is generated from it',
463
+ });
464
+ }
465
+ for (const dep of external) {
454
466
  if (!dependencyTasks.has(dep)) dependencyTasks.set(dep, []);
455
467
  dependencyTasks.get(dep) .push(task);
456
468
  }
@@ -714,7 +726,7 @@ function generateScaffold(options = {}) {
714
726
  const planned = new Map([...sourcePlanned, ...testPlanned]);
715
727
 
716
728
  const ownedPaths = new Set([...byPath.keys()]);
717
- const externalDependencies = partitionExternalDependencies(plan.tasks, ownedPaths);
729
+ const externalDependencies = partitionExternalDependencies(plan.tasks, ownedPaths, notEvaluated);
718
730
 
719
731
  const { written, unchanged, preserved } = applyPlannedFiles({ planned, blocked, scaffoldDir, fsImpl });
720
732
 
@@ -777,7 +789,7 @@ function renderManifest(result , planned ) {
777
789
  '_Nothing generated._');
778
790
 
779
791
  lines.push('## Emitted without a declared shape', '');
780
- lines.push('These files exist and are traceable, but `design.md` §4 never declared an interface for',
792
+ lines.push('These files exist and are traceable, but `design.md` API / Interface Contracts never declared an interface for',
781
793
  'them, so their signature is a placeholder rather than a contract. Reviewing the placeholder as',
782
794
  'if it were the designed API is the mistake this section exists to prevent.', '');
783
795
  table(['Path', 'Why'], result.shapeUndeclared.map((s ) => [s.path, s.why]),