@khoavu882/doflow 2.0.0-beta.5 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (494) hide show
  1. package/README.md +16 -16
  2. package/bin/doflow.js +22 -12
  3. package/core/.antigravity-plugin/plugin.json +1 -2
  4. package/core/.claude-plugin/plugin.json +1 -1
  5. package/core/.codex-plugin/plugin.json +1 -1
  6. package/core/.plugin/plugin.json +1 -1
  7. package/core/harnesses/claude/hooks/lib.sh +43 -24
  8. package/core/harnesses/claude/hooks/mcp-tool-guard.sh +4 -1
  9. package/core/harnesses/claude/hooks/permission-audit.sh +3 -0
  10. package/core/harnesses/claude/hooks/post-compact.sh +35 -32
  11. package/core/harnesses/claude/hooks/post-edit-lint.sh +2 -0
  12. package/core/harnesses/claude/hooks/post-shell-observe.sh +48 -0
  13. package/core/harnesses/claude/hooks/post-tool-failure.sh +3 -0
  14. package/core/harnesses/claude/hooks/pre-compact.sh +23 -27
  15. package/core/harnesses/claude/hooks/pre-implement-gate.sh +4 -1
  16. package/core/harnesses/claude/hooks/session-end.sh +23 -10
  17. package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
  18. package/core/harnesses/claude/settings/settings.json +26 -9
  19. package/core/harnesses/claude/statusline/doflow-statusline.sh +1 -0
  20. package/core/harnesses/codex/agents/core-implementer.toml +1 -0
  21. package/core/harnesses/codex/agents/delivery-lead.toml +10 -0
  22. package/core/harnesses/codex/agents/quality-guardian.toml +1 -1
  23. package/core/harnesses/codex/agents/research-writer.toml +1 -1
  24. package/core/harnesses/codex/agents/reviewer.toml +10 -0
  25. package/core/harnesses/codex/agents/spec-analyst.toml +1 -1
  26. package/core/harnesses/codex/agents/system-architect.toml +1 -1
  27. package/core/harnesses/codex/hooks/hooks.json +0 -10
  28. package/core/harnesses/codex/hooks/lib.sh +44 -25
  29. package/core/harnesses/codex/hooks/mcp-tool-guard.sh +4 -1
  30. package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
  31. package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
  32. package/core/harnesses/codex/hooks/pre-implement-gate.sh +4 -1
  33. package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
  34. package/core/harnesses/codex/hooks/subagent-audit.sh +1 -0
  35. package/core/harnesses/copilot/hooks/doflow-lib.sh +94 -0
  36. package/core/harnesses/copilot/hooks/doflow-mcp-tool-guard.sh +9 -0
  37. package/core/harnesses/copilot/hooks/doflow-pre-implement-gate.sh +9 -0
  38. package/core/harnesses/copilot/hooks/doflow-session-start.sh +9 -0
  39. package/core/harnesses/copilot/hooks/doflow-stop-check.sh +9 -0
  40. package/core/harnesses/copilot/hooks/doflow-subagent-start.sh +9 -0
  41. package/core/harnesses/copilot/hooks/doflow-subagent-stop.sh +9 -0
  42. package/core/harnesses/copilot/hooks/doflow.json +51 -0
  43. package/core/harnesses/kiro/agents/doflow.json +1 -11
  44. package/core/harnesses/kiro/hooks/doflow.json +0 -10
  45. package/core/harnesses/kiro/hooks/lib.sh +26 -6
  46. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +8 -3
  47. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +8 -3
  48. package/core/harnesses/kiro/hooks/session-start.sh +1 -0
  49. package/core/harnesses/kiro/hooks/stop-check.sh +1 -0
  50. package/core/harnesses/pi/extensions/doflow-guard.js +11 -2
  51. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +1 -2
  52. package/core/harnesses/shared/hooks/adapters/antigravity.js +1 -1
  53. package/core/harnesses/shared/hooks/bridge/bridge.sh +126 -23
  54. package/core/harnesses/shared/hooks/bridge/envelope.jq +87 -18
  55. package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
  56. package/core/harnesses/shared/hooks/bridge/respond.sh +16 -2
  57. package/core/harnesses/shared/hooks/policies/capture-failure.sh +49 -16
  58. package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
  59. package/core/harnesses/shared/hooks/policies/contract.sh +3 -0
  60. package/core/harnesses/shared/hooks/policies/deny-json.sh +20 -7
  61. package/core/harnesses/shared/hooks/policies/emission-rules.sh +37 -0
  62. package/core/harnesses/shared/hooks/policies/emission.jq +209 -0
  63. package/core/harnesses/shared/hooks/policies/emission.sh +383 -0
  64. package/core/harnesses/shared/hooks/policies/identity.sh +177 -0
  65. package/core/harnesses/shared/hooks/policies/lib.sh +63 -40
  66. package/core/harnesses/shared/hooks/policies/mcp-policy.conf +1 -1
  67. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +7 -8
  68. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +132 -57
  69. package/core/harnesses/shared/hooks/policies/session-context.sh +30 -1
  70. package/core/harnesses/shared/hooks/policies/stop-check.sh +27 -15
  71. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +6 -3
  72. package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
  73. package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +60 -155
  74. package/core/harnesses/shared/hooks/stream-hook-runner.js +58 -20
  75. package/core/harnesses/shared/locator/doflow-run +2 -3
  76. package/core/registry/advice.json +3 -3
  77. package/core/registry/assets.json +55 -82
  78. package/core/registry/capabilities.json +12 -0
  79. package/core/registry/contracts.json +58 -31
  80. package/core/registry/contribution-points.json +35 -0
  81. package/core/registry/gates.json +32 -0
  82. package/core/registry/guidance-caps.json +40 -0
  83. package/core/registry/harnesses.json +1445 -366
  84. package/core/registry/intake.json +53 -0
  85. package/core/registry/lifecycle.json +599 -996
  86. package/core/registry/mcp.json +5 -1
  87. package/core/registry/models.json +7 -1
  88. package/core/registry/package-templates/README.md +19 -0
  89. package/core/registry/package-templates/agent/files/agent.md +9 -0
  90. package/core/registry/package-templates/agent/template.json +4 -0
  91. package/core/registry/package-templates/capability-provider/files/provider.json +9 -0
  92. package/core/registry/package-templates/capability-provider/template.json +11 -0
  93. package/core/registry/package-templates/debate-preset/files/preset.json +26 -0
  94. package/core/registry/package-templates/debate-preset/template.json +4 -0
  95. package/core/registry/package-templates/intake-category/files/category.json +8 -0
  96. package/core/registry/package-templates/intake-category/template.json +4 -0
  97. package/core/registry/package-templates/review-preset/files/preset.json +30 -0
  98. package/core/registry/package-templates/review-preset/template.json +4 -0
  99. package/core/registry/package-templates/skill/files/SKILL.md +8 -0
  100. package/core/registry/package-templates/skill/template.json +4 -0
  101. package/core/registry/package-templates/worker/files/worker.json +92 -0
  102. package/core/registry/package-templates/worker/template.json +11 -0
  103. package/core/registry/packages/doflow/doflow-package.json +15 -0
  104. package/core/registry/packages/doflow/presets/debate-3.json +26 -0
  105. package/core/registry/protocols.json +61 -0
  106. package/core/registry/routes.json +17 -0
  107. package/core/registry/rules.json +431 -0
  108. package/core/registry/settings.json +272 -0
  109. package/core/registry/storage.json +687 -31
  110. package/core/registry/telemetry.json +236 -0
  111. package/core/registry/transcripts.json +142 -0
  112. package/core/registry/verification.json +76 -6
  113. package/core/registry/workflows.json +55 -0
  114. package/core/shared/agent-specs/core-implementer.md +3 -0
  115. package/core/shared/agent-specs/delivery-lead.md +68 -0
  116. package/core/shared/agent-specs/quality-guardian.md +5 -1
  117. package/core/shared/agent-specs/research-writer.md +10 -3
  118. package/core/shared/agent-specs/reviewer.md +53 -0
  119. package/core/shared/agent-specs/spec-analyst.md +5 -6
  120. package/core/shared/agent-specs/system-architect.md +5 -1
  121. package/core/shared/guidance/DOFLOW_CORE.md +1 -5
  122. package/core/shared/guidance/modes/MODE_DeepResearch.md +1 -1
  123. package/core/shared/guidance/modes/MODE_Orchestration.md +4 -3
  124. package/core/shared/guidance/modes/MODE_Task_Management.md +14 -14
  125. package/core/shared/guidance/pointers/codex.md +1 -4
  126. package/core/shared/guidance/references/ARTIFACT_FORMAT.md +30 -16
  127. package/core/shared/guidance/references/ARTIFACT_VOICE.md +3 -2
  128. package/core/shared/guidance/references/CONSTITUTION_BASE.md +17 -15
  129. package/core/shared/guidance/references/DIAGRAM_GUIDE.md +1 -1
  130. package/core/shared/guidance/references/DOFLOW_CHAIN.md +17 -19
  131. package/core/shared/guidance/references/EVIDENCE_LEDGER.md +7 -8
  132. package/core/shared/guidance/references/MODEL_SELECTION.md +5 -3
  133. package/core/shared/guidance/references/ORCHESTRATION.md +3 -2
  134. package/core/shared/guidance/references/WORKFLOW_HANDOFF.md +19 -1
  135. package/core/shared/guidance/rules/RULES.md +45 -0
  136. package/core/shared/scripts/doflow/bash/do-args.sh +37 -0
  137. package/core/shared/scripts/doflow/bash/do-exec-paths.sh +4 -0
  138. package/core/shared/scripts/doflow/bash/do-git-state.sh +15 -4
  139. package/core/shared/scripts/doflow/bash/do-parallel-check.sh +4 -0
  140. package/core/shared/scripts/doflow/bash/do-paths.sh +40 -2
  141. package/core/shared/scripts/doflow/bash/do-prereqs.sh +2 -1
  142. package/core/shared/scripts/doflow/bash/do-review-package.sh +4 -0
  143. package/core/shared/scripts/doflow/bash/do-task-brief.sh +58 -16
  144. package/core/shared/scripts/doflow/bash/render-audit.sh +26 -2
  145. package/core/shared/scripts/doflow/bash/render-diagrams.sh +1 -0
  146. package/core/shared/scripts/doflow/bash/render-puml.sh +2 -1
  147. package/core/shared/scripts/doflow/bash/validate-artifacts.sh +49 -5
  148. package/core/shared/scripts/doflow/bin/doflow-run +152 -34
  149. package/core/shared/skills/do/SKILL.md +14 -12
  150. package/core/shared/skills/do/references/advice_calls.md +26 -0
  151. package/core/shared/skills/do/references/pm_routing.md +1 -2
  152. package/core/shared/skills/do/references/program_mode.md +31 -0
  153. package/core/shared/skills/do-brainstorm/SKILL.md +45 -97
  154. package/core/shared/skills/do-brainstorm/references/handoff_branches.md +36 -0
  155. package/core/shared/skills/do-brainstorm/references/intent_seed.md +21 -0
  156. package/core/shared/skills/do-brainstorm/references/round_log.md +17 -0
  157. package/core/shared/skills/do-code-review/SKILL.md +48 -148
  158. package/core/shared/skills/do-code-review/content-types/markdown.md +24 -2
  159. package/core/shared/skills/do-code-review/references/tools.md +90 -0
  160. package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +549 -7
  161. package/core/shared/skills/do-design/SKILL.md +23 -20
  162. package/core/shared/skills/do-diagnose/SKILL.md +18 -6
  163. package/core/shared/skills/do-document/SKILL.md +12 -1
  164. package/core/shared/skills/do-document/references/deep_research.md +1 -2
  165. package/core/shared/skills/do-document/references/embedded-research.md +19 -7
  166. package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
  167. package/core/shared/skills/do-execute-plan/SKILL.md +19 -60
  168. package/core/shared/skills/do-execute-plan/references/handoff_record.md +7 -2
  169. package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +13 -0
  170. package/core/shared/skills/do-execute-plan/references/readiness_gate.md +26 -1
  171. package/core/shared/skills/do-execute-plan/references/run_position.md +16 -2
  172. package/core/shared/skills/do-execute-plan/references/scaffold.md +17 -9
  173. package/core/shared/skills/do-execute-plan/references/state_checkpoint.md +11 -0
  174. package/core/shared/skills/do-execute-plan/references/task_attempts.md +78 -24
  175. package/core/shared/skills/do-flow/SKILL.md +10 -11
  176. package/core/shared/skills/do-flow/references/task_classes.md +5 -5
  177. package/core/shared/skills/do-git/SKILL.md +4 -0
  178. package/core/shared/skills/do-implement/SKILL.md +13 -14
  179. package/core/shared/skills/do-plan/SKILL.md +34 -20
  180. package/core/shared/skills/do-test/SKILL.md +7 -4
  181. package/core/shared/templates/doflow/constitution-template.md +9 -3
  182. package/core/shared/templates/doflow/data-model-template.md +1 -1
  183. package/core/shared/templates/doflow/design-template.md +113 -16
  184. package/core/shared/templates/doflow/plan-template.md +11 -10
  185. package/core/shared/templates/doflow/question-log-template.md +14 -2
  186. package/core/shared/templates/doflow/requirement-template.md +1 -1
  187. package/core/shared/workflows/do-flow-chain.md +2 -0
  188. package/core/worker-evidence/antigravity/checks.json +14 -0
  189. package/core/worker-evidence/codex/2026-10-18-cli-check.json +107 -0
  190. package/core/worker-evidence/codex/2026-10-18-live-run-edit.json +73 -0
  191. package/core/worker-evidence/codex/2026-10-18-live-run-read-only.json +62 -0
  192. package/core/worker-evidence/codex/2026-10-24-cli-check.json +126 -0
  193. package/core/worker-evidence/codex/checks.json +38 -0
  194. package/package.json +10 -6
  195. package/src/adapters/antigravity/index.js +143 -66
  196. package/src/adapters/claude/index.js +46 -5
  197. package/src/adapters/codex/agents.js +25 -15
  198. package/src/adapters/codex/hooks.js +39 -3
  199. package/src/adapters/codex/index.js +61 -13
  200. package/src/adapters/codex/native-projection.js +6 -2
  201. package/src/adapters/copilot/index.js +58 -23
  202. package/src/adapters/copy-tree.js +291 -46
  203. package/src/adapters/guidance-manifest.js +71 -0
  204. package/src/adapters/guidance-pointer.js +19 -0
  205. package/src/adapters/hook-commands.js +6 -10
  206. package/src/adapters/index.js +1 -0
  207. package/src/adapters/kiro/index.js +23 -12
  208. package/src/adapters/opencode/index.js +8 -8
  209. package/src/adapters/pi/hooks.js +2 -1
  210. package/src/adapters/pi/index.js +14 -7
  211. package/src/adapters/resolve-source.js +26 -2
  212. package/src/cli/commands/install.js +2 -1
  213. package/src/cli/commands/reconcile.js +53 -9
  214. package/src/cli/commands/remove.js +2 -2
  215. package/src/cli/commands/rollback.js +6 -3
  216. package/src/cli/commands/self-update.js +18 -9
  217. package/src/cli/commands/status.js +22 -3
  218. package/src/cli/commands/ui.js +99 -0
  219. package/src/cli/commands/update.js +2 -1
  220. package/src/cli/index.js +337 -32
  221. package/src/cli/runtime-commands.js +220 -60
  222. package/src/cli/shared.js +0 -2
  223. package/src/helper/git.js +1 -1
  224. package/src/helper/guardrails.js +1 -1
  225. package/src/helper/marker-merge.js +31 -13
  226. package/src/helper/package-ids.js +37 -0
  227. package/src/helper/semver.js +94 -0
  228. package/src/install/backup.js +105 -15
  229. package/src/install/context.js +1 -1
  230. package/src/install/targets.js +31 -16
  231. package/src/lifecycle/coverage.js +4 -16
  232. package/src/lifecycle/hook-tier.js +160 -0
  233. package/src/lifecycle/index.js +412 -78
  234. package/src/lifecycle/retired-harnesses.js +318 -0
  235. package/src/lifecycle/view.js +35 -15
  236. package/src/registry/index.js +551 -26
  237. package/src/registry/worker-ids.js +45 -0
  238. package/src/release/build-package.js +0 -3
  239. package/src/release/docs-blocks.js +472 -0
  240. package/src/release/preflight.js +425 -0
  241. package/src/release/rehearse.js +401 -0
  242. package/src/release/release-workflow.js +733 -0
  243. package/src/release/sync-plugin-versions.js +43 -7
  244. package/src/runtime/advice/bench-gate.js +25 -0
  245. package/src/runtime/advice/cli.js +59 -16
  246. package/src/runtime/advice/client.js +32 -9
  247. package/src/runtime/advice/config.js +98 -133
  248. package/src/runtime/advice/doctor.js +1 -1
  249. package/src/runtime/advice/elevation.js +97 -0
  250. package/src/runtime/advice/endpoint-guard.js +9 -3
  251. package/src/runtime/advice/mode.js +37 -0
  252. package/src/runtime/advice/record.js +18 -5
  253. package/src/runtime/advice/registry.js +4 -1
  254. package/src/runtime/advice/shadow-config.js +229 -0
  255. package/src/runtime/advice/shadow-dispatcher.js +276 -0
  256. package/src/runtime/brief-rules.js +81 -0
  257. package/src/runtime/brief-tools.js +111 -34
  258. package/src/runtime/candidate.js +20 -11
  259. package/src/runtime/capabilities/executor.js +450 -0
  260. package/src/runtime/capabilities/loopback.js +41 -0
  261. package/src/runtime/capabilities/sanitizer.js +88 -0
  262. package/src/runtime/capability-router.js +135 -3
  263. package/src/runtime/checkouts.js +73 -11
  264. package/src/runtime/claims.js +13 -7
  265. package/src/runtime/cli.js +196 -19
  266. package/src/runtime/config-home.js +42 -0
  267. package/src/runtime/context/arbitrate.js +767 -0
  268. package/src/runtime/context/delivery-store.js +4 -6
  269. package/src/runtime/context/emission-rules.js +118 -0
  270. package/src/runtime/context/identity.js +227 -0
  271. package/src/runtime/context/pointer.js +67 -12
  272. package/src/runtime/context/primary-command.js +117 -0
  273. package/src/runtime/context/primary.js +344 -0
  274. package/src/runtime/context/summary-store.js +655 -0
  275. package/src/runtime/context-pack.js +158 -79
  276. package/src/runtime/decision-register.js +13 -4
  277. package/src/runtime/dispatch/admission.js +392 -0
  278. package/src/runtime/dispatch/attempt-end.js +274 -0
  279. package/src/runtime/dispatch/command.js +628 -0
  280. package/src/runtime/dispatch/controls.js +904 -0
  281. package/src/runtime/dispatch/envelope.js +276 -0
  282. package/src/runtime/dispatch/grants.js +310 -0
  283. package/src/runtime/dispatch/integrate.js +617 -0
  284. package/src/runtime/dispatch/loop.js +1835 -0
  285. package/src/runtime/dispatch/store.js +352 -0
  286. package/src/runtime/dispatch/verify-run.js +201 -0
  287. package/src/runtime/evidence-ledger.js +52 -7
  288. package/src/runtime/failure/capture.js +3 -1
  289. package/src/runtime/failure/cli.js +25 -9
  290. package/src/runtime/failure/home.js +57 -30
  291. package/src/runtime/failure/store.js +7 -2
  292. package/src/runtime/feature-pointer.js +174 -0
  293. package/src/runtime/feature-resolve.js +13 -2
  294. package/src/runtime/fleet/command.js +77 -0
  295. package/src/runtime/fleet/projects.js +178 -0
  296. package/src/runtime/fleet/tree.js +383 -0
  297. package/src/runtime/freshness.js +33 -3
  298. package/src/runtime/git-memo.js +46 -0
  299. package/src/runtime/guidance/ablation.js +427 -0
  300. package/src/runtime/health.js +624 -18
  301. package/src/runtime/history-compactor.js +13 -3
  302. package/src/runtime/implementation-gate.js +281 -26
  303. package/src/runtime/intake/engine.js +677 -0
  304. package/src/runtime/intake/index.js +136 -0
  305. package/src/runtime/intake/manifest.js +140 -0
  306. package/src/runtime/intake/taxonomy.js +206 -0
  307. package/src/runtime/inventory/coverage.js +1 -1
  308. package/src/runtime/inventory/index.js +1 -1
  309. package/src/runtime/inventory/precedence.js +2 -2
  310. package/src/runtime/inventory/siblings.js +6 -4
  311. package/src/runtime/knowledge/code-corpus.js +277 -0
  312. package/src/runtime/knowledge/code-lexical.js +130 -0
  313. package/src/runtime/knowledge/code-retrieval.js +307 -0
  314. package/src/runtime/knowledge/code-validation.js +106 -0
  315. package/src/runtime/knowledge/gp3-gate.js +74 -0
  316. package/src/runtime/knowledge/graph-adapter.js +288 -0
  317. package/src/runtime/knowledge/graph-fallback.js +394 -0
  318. package/src/runtime/knowledge/hybrid.js +136 -8
  319. package/src/runtime/knowledge/retrieval.js +34 -1
  320. package/src/runtime/knowledge/rtk-compress.js +77 -0
  321. package/src/runtime/knowledge/semble-bridge.py +106 -0
  322. package/src/runtime/knowledge/semble-lifecycle.js +114 -0
  323. package/src/runtime/knowledge/semble-provider.js +365 -0
  324. package/src/runtime/lifecycle/cli.js +88 -10
  325. package/src/runtime/lifecycle/event-store.js +8 -6
  326. package/src/runtime/lifecycle/followup.js +38 -7
  327. package/src/runtime/lifecycle/overview.js +26 -2
  328. package/src/runtime/lifecycle/report-store.js +8 -10
  329. package/src/runtime/lifecycle/retention.js +95 -31
  330. package/src/runtime/lifecycle/root.js +9 -1
  331. package/src/runtime/lifecycle/status.js +34 -17
  332. package/src/runtime/lifecycle/store-upkeep.js +56 -7
  333. package/src/runtime/mask.js +134 -7
  334. package/src/runtime/mcp-consultations.js +40 -6
  335. package/src/runtime/model-router.js +79 -7
  336. package/src/runtime/models/catalogue.js +191 -0
  337. package/src/runtime/models/command.js +153 -0
  338. package/src/runtime/off-words.js +12 -0
  339. package/src/runtime/outcome.js +2 -2
  340. package/src/runtime/packages/command.js +1009 -0
  341. package/src/runtime/packages/contributions.js +116 -0
  342. package/src/runtime/packages/digest.js +132 -0
  343. package/src/runtime/packages/fetch.js +182 -0
  344. package/src/runtime/packages/grants.js +192 -0
  345. package/src/runtime/packages/index-file.js +134 -0
  346. package/src/runtime/packages/instruction.js +90 -0
  347. package/src/runtime/packages/manifest.js +331 -0
  348. package/src/runtime/packages/points.js +487 -0
  349. package/src/runtime/packages/programs.js +216 -0
  350. package/src/runtime/packages/scaffold.js +277 -0
  351. package/src/runtime/packages/source.js +90 -0
  352. package/src/runtime/packages/state.js +164 -0
  353. package/src/runtime/packages/store.js +449 -0
  354. package/src/runtime/packages/tar.js +106 -0
  355. package/src/runtime/packages/view.js +406 -0
  356. package/src/runtime/plan-check.js +59 -0
  357. package/src/runtime/plan-content.js +324 -0
  358. package/src/runtime/policy-init.js +118 -0
  359. package/src/runtime/profiles/doctor.js +96 -0
  360. package/src/runtime/profiles/resolve.js +244 -0
  361. package/src/runtime/profiles/rollback.js +254 -0
  362. package/src/runtime/profiles/sampler.js +41 -0
  363. package/src/runtime/readiness-record.js +16 -11
  364. package/src/runtime/readiness.js +46 -5
  365. package/src/runtime/research-request.js +50 -25
  366. package/src/runtime/retrieval-plan.js +10 -30
  367. package/src/runtime/retrieval-record.js +75 -5
  368. package/src/runtime/review/command.js +246 -0
  369. package/src/runtime/review/dominance.js +68 -0
  370. package/src/runtime/review/findings.js +434 -0
  371. package/src/runtime/review/identity.js +255 -0
  372. package/src/runtime/review/protocol-command.js +109 -0
  373. package/src/runtime/review/protocols.js +405 -0
  374. package/src/runtime/review/resolve-protocol.js +302 -0
  375. package/src/runtime/review/start.js +530 -0
  376. package/src/runtime/roadmap/attendance.js +137 -0
  377. package/src/runtime/roadmap/facts.js +479 -0
  378. package/src/runtime/roadmap/gates.js +273 -0
  379. package/src/runtime/roadmap/loop.js +1719 -0
  380. package/src/runtime/roadmap/merge.js +390 -0
  381. package/src/runtime/roadmap/rows.js +1122 -0
  382. package/src/runtime/roadmap/seed.js +1063 -0
  383. package/src/runtime/roadmap/worktree-cleanup.js +147 -0
  384. package/src/runtime/rules-report.js +336 -0
  385. package/src/runtime/scaffold/artifacts.js +60 -9
  386. package/src/runtime/scaffold/generate.js +21 -9
  387. package/src/runtime/settings/autopilot.js +106 -0
  388. package/src/runtime/settings/cli.js +280 -0
  389. package/src/runtime/settings/config-write.js +314 -0
  390. package/src/runtime/settings/files.js +271 -0
  391. package/src/runtime/settings/legacy.js +154 -0
  392. package/src/runtime/settings/lexical.js +56 -0
  393. package/src/runtime/settings/prepared-run.js +269 -0
  394. package/src/runtime/settings/project-layers.js +118 -0
  395. package/src/runtime/settings/refs.js +288 -0
  396. package/src/runtime/settings/registry.js +553 -0
  397. package/src/runtime/settings/resolve.js +713 -0
  398. package/src/runtime/settings/shared.js +37 -0
  399. package/src/runtime/settings/worker-billing.js +107 -0
  400. package/src/runtime/settings/write.js +269 -0
  401. package/src/runtime/slot-command.js +78 -0
  402. package/src/runtime/slots.js +671 -0
  403. package/src/runtime/source-version.js +1 -0
  404. package/src/runtime/state-root.js +217 -0
  405. package/src/runtime/storage/cli.js +12 -5
  406. package/src/runtime/storage/engine.js +9 -2
  407. package/src/runtime/storage/registry.js +14 -1
  408. package/src/runtime/storage/roots.js +66 -16
  409. package/src/runtime/storage/rules.js +82 -10
  410. package/src/runtime/storage/upkeep.js +10 -2
  411. package/src/runtime/storage/work-status.js +245 -26
  412. package/src/runtime/task-attempts.js +589 -69
  413. package/src/runtime/task-classifier.js +85 -14
  414. package/src/runtime/task-graph.js +373 -0
  415. package/src/runtime/task-run.js +778 -0
  416. package/src/runtime/task-scope.js +176 -55
  417. package/src/runtime/task-sources.js +147 -0
  418. package/src/runtime/task-state.js +373 -64
  419. package/src/runtime/team/admission.js +301 -0
  420. package/src/runtime/team/envelope.js +324 -0
  421. package/src/runtime/team/review-guard.js +156 -0
  422. package/src/runtime/trace/analyze.js +427 -0
  423. package/src/runtime/trace/baseline.js +770 -0
  424. package/src/runtime/trace/ledger.js +63 -140
  425. package/src/runtime/trace/otlp-export.js +568 -0
  426. package/src/runtime/trace/otlp-map.js +481 -0
  427. package/src/runtime/trace/spans.js +745 -0
  428. package/src/runtime/trace/telemetry.js +225 -0
  429. package/src/runtime/trace/views.js +1 -0
  430. package/src/runtime/transcripts/command.js +98 -0
  431. package/src/runtime/transcripts/cursor.js +326 -0
  432. package/src/runtime/transcripts/read.js +126 -0
  433. package/src/runtime/transcripts/readers/claude-jsonl.js +74 -0
  434. package/src/runtime/transcripts/readers/codex-rollout.js +70 -0
  435. package/src/runtime/transcripts/readers/copilot-events.js +90 -0
  436. package/src/runtime/transcripts/readers/kiro-jsonl.js +77 -0
  437. package/src/runtime/transcripts/readers/pi-jsonl.js +59 -0
  438. package/src/runtime/transcripts/registry.js +161 -0
  439. package/src/runtime/transcripts/stats.js +161 -0
  440. package/src/runtime/verb-view.js +267 -0
  441. package/src/runtime/verification/check-key.js +628 -0
  442. package/src/runtime/verification/contract-runner.js +546 -11
  443. package/src/runtime/verification/engine.js +499 -54
  444. package/src/runtime/verification/impact.js +617 -0
  445. package/src/runtime/verification/inflight.js +205 -0
  446. package/src/runtime/verification/registry.js +176 -0
  447. package/src/runtime/verification/reuse.js +184 -0
  448. package/src/runtime/verification/telemetry-emit.js +254 -0
  449. package/src/runtime/verification/test-selection.js +67 -7
  450. package/src/runtime/worker/active.js +90 -0
  451. package/src/runtime/worker/cancel.js +222 -0
  452. package/src/runtime/worker/catalogue.js +122 -0
  453. package/src/runtime/worker/command.js +677 -0
  454. package/src/runtime/worker/evidence.js +288 -0
  455. package/src/runtime/worker/launch.js +256 -0
  456. package/src/runtime/worker/models.js +137 -0
  457. package/src/runtime/worker/parsers.js +218 -0
  458. package/src/runtime/worker/probes/index.js +706 -0
  459. package/src/runtime/worker/process-tree.js +415 -0
  460. package/src/runtime/worker/quota.js +43 -0
  461. package/src/runtime/worker/readiness.js +713 -0
  462. package/src/runtime/worker/run-store.js +166 -0
  463. package/src/runtime/worker/supervisor.js +541 -0
  464. package/src/runtime/worker/worktree-check.js +252 -0
  465. package/src/runtime/workflow-engine.js +5 -1
  466. package/src/runtime/workflow-orchestrator.js +610 -67
  467. package/src/runtime/worktree.js +149 -22
  468. package/src/state/index.js +1 -1
  469. package/src/state/lockfile.js +64 -10
  470. package/src/state/run-lock.js +1 -1
  471. package/types/node-commonjs.d.ts +106 -7
  472. package/core/harnesses/claude/hooks/pre-bash-guard.sh +0 -10
  473. package/core/harnesses/codex/hooks/pre-bash-guard.sh +0 -10
  474. package/core/harnesses/gemini/hooks/hooks.json +0 -77
  475. package/core/harnesses/gemini/hooks/lib.sh +0 -302
  476. package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +0 -10
  477. package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
  478. package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -10
  479. package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
  480. package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -10
  481. package/core/harnesses/gemini/hooks/session-end.impl.sh +0 -89
  482. package/core/harnesses/gemini/hooks/session-end.sh +0 -5
  483. package/core/harnesses/gemini/hooks/session-start.sh +0 -7
  484. package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
  485. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +0 -24
  486. package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +0 -91
  487. package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +0 -378
  488. package/core/shared/guidance/FLAGS.md +0 -25
  489. package/core/shared/guidance/rules/RULE_01_SAFETY.md +0 -25
  490. package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +0 -23
  491. package/core/shared/guidance/rules/RULE_03_QUALITY.md +0 -14
  492. package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +0 -46
  493. package/src/adapters/gemini/hooks.js +0 -251
  494. package/src/adapters/gemini/index.js +0 -429
@@ -7,6 +7,7 @@ const path = require('node:path');
7
7
  const { parseYamlFile } = require('./capability-router.js');
8
8
  const { resolveLocator, describeResolution } = require('./locator-resolve.js');
9
9
  const { readPlan, bindEvidence, declaredIntentList } = require('./retrieval-record.js');
10
+ const { fileRootFor } = require('./freshness.js');
10
11
  const { REPO_ROOT } = require('../helper/repo-root.js');
11
12
 
12
13
  const READINESS_STATES = new Set([
@@ -64,6 +65,7 @@ class ReadinessEngine {
64
65
 
65
66
 
66
67
 
68
+
67
69
 
68
70
 
69
71
  /**
@@ -72,6 +74,8 @@ class ReadinessEngine {
72
74
  * @param {string} [options.projectRoot] the repository under work, which evidence locators name.
73
75
  * Distinct from repoRoot on purpose: when the CLI runs inside another project, the templates
74
76
  * come from the install and the locators must be resolved against that project, not the install.
77
+ * @param {string} [options.sourceRoot] the checkout the verb runs in, where a relative locator's file
78
+ * is read (IC-007, `fileRootFor`); differs from projectRoot in a linked worktree. Defaults to projectRoot.
75
79
  * @param {string} [options.templatePath]
76
80
  * @param {Object} [options.templates]
77
81
  * @param {Object} [options.fsImpl]
@@ -80,6 +84,7 @@ class ReadinessEngine {
80
84
  this.fsImpl = options.fsImpl || fs;
81
85
  this.repoRoot = options.repoRoot || REPO_ROOT;
82
86
  this.projectRoot = options.projectRoot || process.cwd();
87
+ this.sourceRoot = options.sourceRoot || this.projectRoot;
83
88
  this.templatePath = options.templatePath || path.join(this.repoRoot, 'core', 'registry', 'readiness-templates.json');
84
89
 
85
90
  if (options.templates) {
@@ -134,6 +139,22 @@ class ReadinessEngine {
134
139
  action: 'Execute automated tests or record runtime observation reproducing the behavior.',
135
140
  };
136
141
  }
142
+ // These are satisfied by what the caller states, not by retrieved evidence (evaluateReadiness),
143
+ // so the action names the flag that states it (--scope / --invariants, cli/index.ts).
144
+ if (reqId === 'scope_clear' || reqId === 'scope_verified') {
145
+ return {
146
+ intent: 'locate-concept',
147
+ capability: 'code.semantic-search',
148
+ action: `State the scope boundary for requirement '${reqId}' (${reqDef.description}): pass --scope "<boundary>" when evaluating readiness.`,
149
+ };
150
+ }
151
+ if (reqId === 'invariants_captured') {
152
+ return {
153
+ intent: 'locate-concept',
154
+ capability: 'code.semantic-search',
155
+ action: `State the invariants the change must preserve for requirement '${reqId}' (${reqDef.description}): pass --invariants "<invariants>" when evaluating readiness.`,
156
+ };
157
+ }
137
158
  return {
138
159
  intent: 'locate-concept',
139
160
  capability: 'code.semantic-search',
@@ -224,7 +245,10 @@ class ReadinessEngine {
224
245
  const queryBound = Boolean(plan?.planId);
225
246
  const binding = queryBound ? bindEvidence(plan, allTaskEvidence) : null;
226
247
  const unboundIds = new Set(binding ? binding.unbound.map((item ) => item.id) : []);
248
+ // A superseded item is retired: it neither satisfies a requirement nor can it be unresolvable
249
+ // (the stale check below already skips it), so nothing else can be left that clears it.
227
250
  const taskEvidence = allTaskEvidence.filter((item ) => item.freshness?.status === 'FRESH'
251
+ && item.status !== 'superseded'
228
252
  && (!queryBound || !unboundIds.has(item.id)));
229
253
 
230
254
  // An item accepted before FR-004 existed, or one whose target file has since shrunk, can be
@@ -246,7 +270,7 @@ class ReadinessEngine {
246
270
  const unresolvableEvidence = [];
247
271
  for (const item of taskEvidence) {
248
272
  if (item.provenance !== 'extracted' || !item.locator || !item.locator.file) continue;
249
- const resolution = resolveLocator({ locator: item.locator, repoRoot: this.projectRoot });
273
+ const resolution = resolveLocator({ locator: item.locator, repoRoot: fileRootFor(this.sourceRoot, this.projectRoot, item.locator) });
250
274
  if (!resolution.resolved) {
251
275
  unresolvableEvidence.push({
252
276
  evidenceId: item.id,
@@ -296,7 +320,18 @@ class ReadinessEngine {
296
320
  // not by any supported claim the task happens to hold (review R1 — a supported claim about an
297
321
  // unrelated invariant satisfied the bug template's root-cause requirement).
298
322
  else if (reqDef.requiresClaimStatus) {
299
- let matchingClaims = taskClaims.filter((c ) => c.status === reqDef.requiresClaimStatus);
323
+ // A `supported` claim counts only while at least one of its supporting items is live
324
+ // (active, FRESH, bound) in `taskEvidence`: support that was superseded, went stale or is
325
+ // unbound no longer backs it (107-B review C-1). Other statuses are not evidence-backed.
326
+ const liveEvidenceIds = new Set(taskEvidence.map((item ) => item.id));
327
+ const statusMatched = taskClaims.filter((c ) => c.status === reqDef.requiresClaimStatus);
328
+ const hasLiveSupport = (c ) => reqDef.requiresClaimStatus !== 'supported'
329
+ || (c.supportingEvidence || []).some((id ) => liveEvidenceIds.has(id));
330
+ // A claim whose stored status still reads `supported` but whose support was retired is
331
+ // named as such, so the user is not told to add a supported claim they can already see.
332
+ const retired = statusMatched.filter((c ) => !hasLiveSupport(c)
333
+ && (!reqDef.requiresClaimRole || c.role === reqDef.requiresClaimRole));
334
+ let matchingClaims = statusMatched.filter(hasLiveSupport);
300
335
  if (reqDef.requiresClaimRole) {
301
336
  const withRole = matchingClaims.filter((c ) => c.role === reqDef.requiresClaimRole);
302
337
  if (withRole.length === 0 && matchingClaims.length > 0) {
@@ -309,6 +344,12 @@ class ReadinessEngine {
309
344
  if (matchingClaims.length > 0) {
310
345
  isSatisfied = true;
311
346
  matchedEvidenceIds = matchingClaims.flatMap((c ) => c.supportingEvidence);
347
+ } else if (!reason && retired.length > 0) {
348
+ reasonCode = 'claim-support-retired';
349
+ reason = `${retired.length} claim(s) with status '${reqDef.requiresClaimStatus}'`
350
+ + `${reqDef.requiresClaimRole ? ` and role '${reqDef.requiresClaimRole}'` : ''} have no live supporting `
351
+ + `evidence (${retired.slice(0, 5).map((c ) => c.id).join(', ')}): each supporting item was superseded, `
352
+ + 'went stale or is not bound to the task plan. Link a fresh item with `claim --action link` and re-evaluate.';
312
353
  } else if (!reason) {
313
354
  reason = `Requires at least one claim with status '${reqDef.requiresClaimStatus}'`
314
355
  + `${reqDef.requiresClaimRole ? ` and role '${reqDef.requiresClaimRole}'` : ''}.`;
@@ -476,7 +517,7 @@ class ReadinessEngine {
476
517
  /** One live snapshot for readiness inspection and orchestration transitions. Reads never persist
477
518
  * derived freshness or claim status; every invocation evaluates the current project again.
478
519
  * `mode` defaults to 'workflow' — fail closed: the standalone exemption must be declared. */
479
- function evaluateTaskReadiness({ taskProfile, repoRoot = REPO_ROOT, projectRoot = process.cwd(), treeRoot = projectRoot, mode = 'workflow' } ) {
520
+ function evaluateTaskReadiness({ taskProfile, repoRoot = REPO_ROOT, projectRoot = process.cwd(), treeRoot = projectRoot, sourceRoot = projectRoot, mode = 'workflow' } ) {
480
521
  // Validated before any ledger work, not just inside stageEntryFor at the end — an invalid mode
481
522
  // used to pay a full freshness/claims evaluation before erroring (review suggestion, 026).
482
523
  if (!EXECUTION_MODES.has(mode)) {
@@ -487,11 +528,11 @@ function evaluateTaskReadiness({ taskProfile, repoRoot = REPO_ROOT, projectRoot
487
528
  const { FreshnessValidator } = require('./freshness.js');
488
529
  const ledger = new EvidenceLedger({ repoRoot: projectRoot });
489
530
  ledger.load(taskProfile.taskId);
490
- new FreshnessValidator({ repoRoot: projectRoot, treeRoot }).validateLedgerFreshness(ledger);
531
+ new FreshnessValidator({ repoRoot: projectRoot, treeRoot, sourceRoot }).validateLedgerFreshness(ledger);
491
532
  const claims = new ClaimsManager({ evidenceLedger: ledger, repoRoot: projectRoot });
492
533
  claims.load(taskProfile.taskId);
493
534
  claims.evaluateAll();
494
- const report = new ReadinessEngine({ repoRoot, projectRoot }).evaluateReadiness(taskProfile, ledger, claims);
535
+ const report = new ReadinessEngine({ repoRoot, projectRoot, sourceRoot }).evaluateReadiness(taskProfile, ledger, claims);
495
536
  return { ...report, executionMode: mode, stageEntry: stageEntryFor(report.state, mode) };
496
537
  }
497
538
 
@@ -8,13 +8,15 @@ const crypto = require('node:crypto');
8
8
  const { acquireLock, updateTaskState, readTaskState } = require('./task-state.js');
9
9
  const { EvidenceLedger, assertSafeTaskId } = require('./evidence-ledger.js');
10
10
  const { ClaimsManager } = require('./claims.js');
11
- const { taskStoreDir } = require('./task-scope.js');
12
- const { FreshnessValidator } = require('./freshness.js');
11
+ const { taskRecord } = require('./task-scope.js');
12
+ const { FreshnessValidator, fileRootFor } = require('./freshness.js');
13
13
  const { resolveLocator } = require('./locator-resolve.js');
14
14
  const { finishRuntime, usageError } = require('./cli-result.js');
15
15
 
16
16
  const STATES = new Set(['OPEN', 'ANSWERED', 'UNRESOLVED']);
17
- const REASONS = new Set(['detected-gap', 'user-request']);
17
+ const REASONS = new Set(['detected-gap', 'user-request', 'ideation']);
18
+ // Stage kinds an ideation request may attach to (IC-001): the feature's discovery and design stages.
19
+ const IDEATION_KINDS = new Set(['discovery', 'design']);
18
20
  const safeId = (value , label ) => {
19
21
  if (typeof value !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(value)) {
20
22
  throw new Error(`${label} must be a safe non-empty identifier`);
@@ -22,7 +24,7 @@ const safeId = (value , label ) => {
22
24
  return value;
23
25
  };
24
26
 
25
- function hasLocatedSource(item , projectRoot ) {
27
+ function hasLocatedSource(item , projectRoot , stateRoot = projectRoot) {
26
28
  const locator = item.locator || {};
27
29
  if (locator.uri !== undefined) {
28
30
  if (typeof locator.uri !== 'string') return false;
@@ -32,33 +34,43 @@ function hasLocatedSource(item , projectRoot ) {
32
34
  } catch { return false; }
33
35
  }
34
36
  return Boolean((locator.uri && typeof locator.uri === 'string')
35
- || (locator.file && resolveLocator({ locator, repoRoot: projectRoot }).resolved));
37
+ || (locator.file && resolveLocator({ locator, repoRoot: fileRootFor(projectRoot, stateRoot, locator) }).resolved));
36
38
  }
37
39
 
40
+ // The run, the requests, the evidence and the claims are all read and written under `projectRoot`,
41
+ // the task's state root (115 IC-004); `sourceRoot` is the checkout the verb runs in, where a relative
42
+ // evidence locator resolves and is hashed (IC-007). Both default to the same directory.
38
43
  class ResearchRequestStore {
39
44
 
45
+
40
46
 
41
47
 
42
48
 
43
- constructor({ projectRoot = process.cwd(), fsImpl = fs, slug = null } = {}) {
49
+ constructor({ projectRoot = process.cwd(), sourceRoot, fsImpl = fs, slug = null } = {}) {
44
50
  this.projectRoot = projectRoot;
51
+ this.sourceRoot = sourceRoot || projectRoot;
45
52
  this.fsImpl = fsImpl;
46
53
  this.slug = slug;
47
54
  this.stateDir = path.join(projectRoot, '.doflow', 'state', 'research');
48
55
  }
49
56
 
50
- /** The feature's namespace directory when the task has one (task-scope.js), else the flat store. */
57
+ /** The task's request file in the feature's namespace when it has one (task-scope.ts), else the flat store. */
58
+ record(taskId ) {
59
+ return taskRecord({ projectRoot: this.projectRoot, store: 'research', taskId: assertSafeTaskId(taskId), slug: this.slug, fsImpl: this.fsImpl });
60
+ }
61
+
62
+ /** Where the task's requests are written. */
51
63
  file(taskId ) {
52
- const dir = taskStoreDir({ projectRoot: this.projectRoot, store: 'research', taskId: assertSafeTaskId(taskId), slug: this.slug });
53
- return path.join(dir, `${taskId}.json`);
64
+ return this.record(taskId).file;
54
65
  }
55
66
 
67
+ /** The task's run, read where the orchestrator keeps it (115 IC-004). */
56
68
  runFile(taskId ) {
57
- return path.join(this.projectRoot, '.doflow', 'state', 'orchestration', `${assertSafeTaskId(taskId)}.json`);
69
+ return taskRecord({ projectRoot: this.projectRoot, store: 'orchestration', taskId: assertSafeTaskId(taskId), slug: this.slug, fsImpl: this.fsImpl }).read;
58
70
  }
59
71
 
60
72
  read(taskId ) {
61
- const data = readTaskState(this.fsImpl, this.file(taskId));
73
+ const data = readTaskState(this.fsImpl, this.record(taskId).read);
62
74
  if (!data) return { taskId, revision: 0, requests: [] };
63
75
  if (data.taskId !== taskId || !Array.isArray(data.requests)
64
76
  || data.requests.some((r ) => !STATES.has(r.status))) {
@@ -70,7 +82,8 @@ class ResearchRequestStore {
70
82
  // Re-read inside updateTaskState's lock: overlapping writers cannot lose a request.
71
83
  change(taskId , update ) {
72
84
  let result;
73
- updateTaskState({ fsImpl: this.fsImpl, file: this.file(taskId), build: (disk ) => {
85
+ const { file, seed } = this.record(taskId);
86
+ updateTaskState({ fsImpl: this.fsImpl, file, prepare: seed, build: (disk ) => {
74
87
  if (disk && (disk.taskId !== taskId || !Array.isArray(disk.requests))) {
75
88
  throw new Error(`Invalid research state for task '${taskId}'`);
76
89
  }
@@ -90,6 +103,7 @@ class ResearchRequestStore {
90
103
  if (current?.type !== 'stage' || current.id !== stageId) {
91
104
  throw new Error(`Current feature stage is '${current?.type === 'stage' ? current.id : 'none'}', not '${stageId}'`);
92
105
  }
106
+ return current;
93
107
  }
94
108
 
95
109
  open({ taskId, stageId, question, reason, blocking } = {}) {
@@ -103,13 +117,20 @@ class ResearchRequestStore {
103
117
  if (!this.fsImpl.existsSync(runFile)) throw new Error(`Task '${taskId}' has no active feature stage`);
104
118
  const release = acquireLock(this.fsImpl, runFile);
105
119
  try {
106
- this.stageFor(taskId, stageId);
120
+ const stage = this.stageFor(taskId, stageId);
121
+ if (reason === 'ideation' && !IDEATION_KINDS.has(stage.kind)) {
122
+ throw new Error(`reason ideation is only valid on a feature discovery or design stage, not '${stage.kind ?? 'none'}'`);
123
+ }
107
124
  return this.change(taskId, (requests ) => {
108
125
  const existing = requests.find((r ) => r.stageId === stageId && r.question === question.trim() && r.status === 'OPEN');
109
126
  if (existing) {
110
- if ((blocking ?? reason === 'detected-gap') && !existing.blocking) {
111
- existing.blocking = true;
112
- existing.reason = reason;
127
+ const upgradeBlocking = (blocking ?? reason === 'detected-gap') && !existing.blocking;
128
+ // A re-open as ideation makes the request ideation (so an OPEN one holds the stage); no
129
+ // re-open weakens it: an ideation request keeps its reason.
130
+ const nextReason = existing.reason === 'ideation' ? 'ideation' : reason === 'ideation' || upgradeBlocking ? reason : existing.reason;
131
+ if (upgradeBlocking || nextReason !== existing.reason) {
132
+ if (upgradeBlocking) existing.blocking = true;
133
+ existing.reason = nextReason;
113
134
  existing.updatedAt = new Date().toISOString();
114
135
  }
115
136
  return existing;
@@ -142,7 +163,7 @@ class ResearchRequestStore {
142
163
  if (!claimId || evidenceIds.length === 0) throw new Error('answered requires a claim and evidence IDs');
143
164
  const ledger = new EvidenceLedger({ repoRoot: this.projectRoot, slug: this.slug });
144
165
  ledger.load(taskId);
145
- new FreshnessValidator({ repoRoot: this.projectRoot }).validateLedgerFreshness(ledger);
166
+ new FreshnessValidator({ repoRoot: this.projectRoot, treeRoot: this.sourceRoot, sourceRoot: this.sourceRoot }).validateLedgerFreshness(ledger);
146
167
  const claims = new ClaimsManager({ repoRoot: this.projectRoot, evidenceLedger: ledger, slug: this.slug });
147
168
  claims.load(taskId); claims.evaluateAll();
148
169
  const claim = claims.getClaim(claimId);
@@ -153,7 +174,7 @@ class ResearchRequestStore {
153
174
  const item = ledger.getEvidence(id);
154
175
  if (!item || item.taskId !== taskId || item.provenance !== 'extracted'
155
176
  || item.status === 'superseded' || item.freshness?.status !== 'FRESH'
156
- || !hasLocatedSource(item, this.projectRoot)
177
+ || !hasLocatedSource(item, this.sourceRoot, this.projectRoot)
157
178
  || !claim.supportingEvidence.includes(id)) {
158
179
  throw new Error(`Evidence '${id}' is not fresh, located support for this claim and task`);
159
180
  }
@@ -171,8 +192,12 @@ class ResearchRequestStore {
171
192
  }
172
193
 
173
194
  blockingGap(taskId , stageId ) {
174
- const requests = this.list(taskId).filter((r ) => r.stageId === stageId && r.blocking);
175
- for (const request of requests) {
195
+ const onStage = this.list(taskId).filter((r ) => r.stageId === stageId);
196
+ // An OPEN ideation request holds the stage whatever its blocking flag (IC-002): the research
197
+ // is either answered or recorded UNRESOLVED before the stage completes.
198
+ const openIdeation = onStage.find((r ) => r.reason === 'ideation' && r.status === 'OPEN');
199
+ if (openIdeation) return openIdeation;
200
+ for (const request of onStage.filter((r ) => r.blocking)) {
176
201
  if (request.status !== 'ANSWERED') return request;
177
202
  try {
178
203
  this.validateAnswer(taskId, request);
@@ -186,7 +211,7 @@ class ResearchRequestStore {
186
211
  validateAnswer(taskId , request ) {
187
212
  const ledger = new EvidenceLedger({ repoRoot: this.projectRoot, slug: this.slug });
188
213
  ledger.load(taskId);
189
- new FreshnessValidator({ repoRoot: this.projectRoot }).validateLedgerFreshness(ledger);
214
+ new FreshnessValidator({ repoRoot: this.projectRoot, treeRoot: this.sourceRoot, sourceRoot: this.sourceRoot }).validateLedgerFreshness(ledger);
190
215
  const claims = new ClaimsManager({ repoRoot: this.projectRoot, evidenceLedger: ledger, slug: this.slug });
191
216
  claims.load(taskId); claims.evaluateAll();
192
217
  const claim = claims.getClaim(request.claimId);
@@ -201,7 +226,7 @@ class ResearchRequestStore {
201
226
  const item = ledger.getEvidence(id);
202
227
  if (!item || item.taskId !== taskId || item.provenance !== 'extracted'
203
228
  || item.status === 'superseded' || item.freshness?.status !== 'FRESH'
204
- || !hasLocatedSource(item, this.projectRoot)
229
+ || !hasLocatedSource(item, this.sourceRoot, this.projectRoot)
205
230
  || !claim.supportingEvidence.includes(id)) {
206
231
  throw new Error(`Research evidence '${id}' is no longer fresh, located support`);
207
232
  }
@@ -210,9 +235,9 @@ class ResearchRequestStore {
210
235
  }
211
236
 
212
237
  function handleResearchRequestCommand({ action = 'list', taskId, stageId, question, reason,
213
- blocking, requestId, outcome, claimId, evidenceIds, gap, json = false, projectRoot, slug } = {}) {
238
+ blocking, requestId, outcome, claimId, evidenceIds, gap, json = false, projectRoot, sourceRoot, slug } = {}) {
214
239
  try {
215
- const store = new ResearchRequestStore({ projectRoot, slug });
240
+ const store = new ResearchRequestStore({ projectRoot, sourceRoot, slug });
216
241
  let result;
217
242
  switch (action) {
218
243
  case 'open': result = store.open({ taskId, stageId, question, reason, blocking }); break;
@@ -228,4 +253,4 @@ function handleResearchRequestCommand({ action = 'list', taskId, stageId, questi
228
253
  }
229
254
  }
230
255
 
231
- module.exports = { ResearchRequestStore, handleResearchRequestCommand };
256
+ module.exports = { ResearchRequestStore, handleResearchRequestCommand, REASONS };
@@ -42,7 +42,7 @@ const { CapabilityRouter } = require('./capability-router.js');
42
42
  const { EvidenceLedger, assertSafeTaskId } = require('./evidence-ledger.js');
43
43
  const { FreshnessValidator } = require('./freshness.js');
44
44
  const { probeFreshness } = require('./health.js');
45
- const { planPath, readPlan, updatePlan, moveCorruptPlanAside, mintPlanId, activeAttemptRef, resolveNeed, bindEvidence, deriveOutcome } = require('./retrieval-record.js');
45
+ const { planPath, readPlan, updatePlan, moveCorruptPlanAside, mintPlanId, activeAttemptRef, resolveNeed, appendRetrievalAttempt, bindEvidence, deriveOutcome } = require('./retrieval-record.js');
46
46
  // One definition of the score refusal, applied at a second boundary. See the note on
47
47
  // EVIDENCE_SCORE_FIELDS in src/runtime/evidence-fields.js: relevance is a property of a search, not of a fact, and a plan
48
48
  // that recorded one would hand the next worker a confidence dressed as a measurement.
@@ -387,7 +387,7 @@ function reportPlan({ projectRoot, sourceRoot, taskId, reached, json, slug, repo
387
387
  } catch (error ) {
388
388
  return usageError('retrieval-plan', error.message, json, error);
389
389
  }
390
- new FreshnessValidator({ repoRoot: projectRoot, treeRoot: sourceRoot }).validateLedgerFreshness(ledger);
390
+ new FreshnessValidator({ repoRoot: projectRoot, treeRoot: sourceRoot, sourceRoot }).validateLedgerFreshness(ledger);
391
391
  const items = ledger.queryEvidence({ taskId });
392
392
 
393
393
  const isQueryBound = Boolean(record.planId);
@@ -730,35 +730,15 @@ function attemptPlan({
730
730
  let savedAttempt = null;
731
731
  let file ;
732
732
  try {
733
- file = updatePlan(projectRoot, taskId, slug, (disk ) => {
734
- const current = requirePlanForUpdate(disk, taskId);
735
- const targetNeed = current.needs ? current.needs.find((n ) => n.intent === intent) : null;
736
- if (!targetNeed) {
737
- throw new Error(`need '${intent}' missing from disk plan during update`);
738
- }
739
- if (!Array.isArray(targetNeed.attempts)) {
740
- targetNeed.attempts = [];
741
- }
742
- const n = targetNeed.attempts.length + 1;
743
- const role = provider === targetNeed.provider ? 'primary' : 'fallback';
744
- const attempt = {
745
- n,
746
- provider,
747
- capability: targetNeed.capability,
748
- role,
749
- status: finalStatus,
750
- reason: finalReason,
751
- note: note !== undefined && note !== null ? String(note) : null,
752
- indexState,
753
- attemptRef: activeRef,
754
- evidenceIds: [],
755
- recordedAt: new Date().toISOString(),
756
- recordedBy: 'caller',
757
- };
758
- targetNeed.attempts.push(attempt);
759
- savedAttempt = attempt;
760
- return current;
733
+ const appended = appendRetrievalAttempt({
734
+ projectRoot, taskId, intent, provider, status: finalStatus, reason: finalReason,
735
+ note, indexState, slug, attemptRef: activeRef,
736
+ expectedCapability: declaredNeed.capability,
737
+ expectedPrimaryProvider: declaredNeed.provider,
738
+ registeredProviders,
761
739
  });
740
+ file = appended.file;
741
+ savedAttempt = appended.attempt;
762
742
  } catch (error ) {
763
743
  return usageError('retrieval-plan', error.message, json, error);
764
744
  }
@@ -5,7 +5,7 @@
5
5
  const fs = require('node:fs');
6
6
  const path = require('node:path');
7
7
  const crypto = require('node:crypto');
8
- const { taskStoreDir, resolveTaskScope } = require('./task-scope.js');
8
+ const { taskStoreDir, taskRecord, resolveTaskScope } = require('./task-scope.js');
9
9
  const { assertSafeTaskId } = require('./evidence-ledger.js');
10
10
  const { readTaskState, updateTaskState } = require('./task-state.js');
11
11
  const { storeFile, activeAttempt } = require('./task-attempts.js');
@@ -36,8 +36,7 @@ function planPath(projectRoot , taskId , slug = null) {
36
36
  * @returns {any|null}
37
37
  */
38
38
  function readPlan(projectRoot , taskId , slug = null, fsImpl = fs) {
39
- const file = planPath(projectRoot, taskId, slug);
40
- return readTaskState(fsImpl, file);
39
+ return readTaskState(fsImpl, taskRecord({ projectRoot, store: 'retrieval', taskId: assertSafeTaskId(taskId), slug, fsImpl }).read);
41
40
  }
42
41
 
43
42
  /**
@@ -50,8 +49,8 @@ function readPlan(projectRoot , taskId , slug = null, fsImpl =
50
49
  * @returns {string} the written file path
51
50
  */
52
51
  function updatePlan(projectRoot , taskId , slug , build , fsImpl = fs, prepare ) {
53
- const file = planPath(projectRoot, taskId, slug);
54
- return updateTaskState({ fsImpl, file, build, prepare });
52
+ const { file, seed } = taskRecord({ projectRoot, store: 'retrieval', taskId: assertSafeTaskId(taskId), slug, fsImpl });
53
+ return updateTaskState({ fsImpl, file, build, prepare: () => { seed(); if (prepare) prepare(); } });
55
54
  }
56
55
 
57
56
  /** Move a syntactically unreadable plan aside before its replacement is written under the lock. */
@@ -128,6 +127,75 @@ function resolveNeed(plan , intent , taskId ) {
128
127
  };
129
128
  }
130
129
 
130
+ /** Resolve the plan's query binding and the task's active attempt in one reusable operation. */
131
+ function resolveRetrievalBinding({ projectRoot, taskId, intent, slug = null, fsImpl = fs, attemptRoot = null } ) {
132
+ const plan = readPlan(projectRoot, taskId, slug, fsImpl);
133
+ const resolved = resolveNeed(plan, intent, taskId);
134
+ if (!resolved.ok) return resolved;
135
+ return {
136
+ ok: true,
137
+ plan,
138
+ queryRef: resolved.queryRef,
139
+ need: resolved.need,
140
+ attemptRef: activeAttemptRef(projectRoot, taskId, slug, fsImpl, attemptRoot),
141
+ };
142
+ }
143
+
144
+ /** Append one validated provider attempt under the retrieval-plan state lock. */
145
+ function appendRetrievalAttempt({
146
+ projectRoot, taskId, intent, provider, status, reason, note = null, indexState,
147
+ slug = null, attemptRef = undefined, fsImpl = fs, expectedCapability = undefined,
148
+ expectedPrimaryProvider = undefined, registeredProviders = undefined,
149
+ } ) {
150
+ const allowedIndexStates = ['FRESH', 'STALE', 'UNKNOWN', 'NOT_APPLICABLE'];
151
+ if (!allowedIndexStates.includes(indexState)) {
152
+ throw new Error(`attempt indexState '${indexState}' is invalid; expected one of ${allowedIndexStates.join(', ')}`);
153
+ }
154
+ const allowedReasons = {
155
+ EMPTY: ['empty-answer', 'index-stale', 'index-unverifiable'],
156
+ UNREACHED: ['provider-unavailable', 'policy-bypass', 'provider-error', 'timeout'],
157
+ UNVERIFIED: ['index-unverifiable'],
158
+ };
159
+ if (status === 'RETRIEVED') throw new Error('RETRIEVED is derived from evidence and cannot be appended as a provider attempt');
160
+ if (!Object.prototype.hasOwnProperty.call(allowedReasons, status) || !allowedReasons[status].includes(reason)) {
161
+ throw new Error(`attempt reason '${reason}' is not valid for status '${status}'`);
162
+ }
163
+ if (!Array.isArray(registeredProviders) || !registeredProviders.includes(provider)) {
164
+ throw new Error(`provider '${provider}' is not registered for this capability`);
165
+ }
166
+ if (note !== null && String(note).length > 160) throw new Error('attempt note is longer than 160 characters');
167
+
168
+ let savedAttempt = null;
169
+ const file = updatePlan(projectRoot, taskId, slug, (disk ) => {
170
+ if (disk === null) throw new Error(`the retrieval plan for task ${taskId} was removed during the update; re-declare it`);
171
+ const current = disk;
172
+ const targetNeed = Array.isArray(current.needs) ? current.needs.find((need ) => need.intent === intent) : null;
173
+ if (!targetNeed) throw new Error(`need '${intent}' missing from disk plan during update`);
174
+ if (expectedCapability !== undefined && targetNeed.capability !== expectedCapability) {
175
+ throw new Error(`need '${intent}' capability changed during update`);
176
+ }
177
+ if (expectedPrimaryProvider !== undefined && targetNeed.provider !== expectedPrimaryProvider) {
178
+ throw new Error(`need '${intent}' provider changed during update`);
179
+ }
180
+ if (provider === undefined || provider === null || String(provider).trim() === '') throw new Error('attempt provider is required');
181
+ if (!Array.isArray(targetNeed.attempts)) targetNeed.attempts = [];
182
+ const n = targetNeed.attempts.reduce((max , prior ) => Math.max(max, Number.isInteger(prior?.n) ? prior.n : 0), 0) + 1;
183
+ const role = provider === targetNeed.provider ? 'primary' : 'fallback';
184
+ savedAttempt = {
185
+ n, provider, capability: targetNeed.capability, role, status, reason,
186
+ note: note !== undefined && note !== null ? String(note) : null,
187
+ indexState,
188
+ attemptRef: attemptRef === undefined ? activeAttemptRef(projectRoot, taskId, slug, fsImpl) : attemptRef,
189
+ evidenceIds: [],
190
+ recordedAt: new Date().toISOString(),
191
+ recordedBy: 'caller',
192
+ };
193
+ targetNeed.attempts.push(savedAttempt);
194
+ return current;
195
+ }, fsImpl);
196
+ return { file, attempt: savedAttempt };
197
+ }
198
+
131
199
  /**
132
200
  * Reads the 070 active attempt ref for a task, returning `${taskId}#${token}` or null.
133
201
  *
@@ -216,6 +284,8 @@ module.exports = {
216
284
  moveCorruptPlanAside,
217
285
  mintPlanId,
218
286
  resolveNeed,
287
+ resolveRetrievalBinding,
288
+ appendRetrievalAttempt,
219
289
  activeAttemptRef,
220
290
  declaredIntentList,
221
291
  bindEvidence,