@khoavu882/doflow 2.0.0-beta.4 → 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 (497) hide show
  1. package/README.md +12 -11
  2. package/bin/doflow.js +21 -11
  3. package/core/.antigravity-plugin/plugin.json +1 -3
  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 +7 -17
  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-bash-guard.sh +7 -17
  15. package/core/harnesses/claude/hooks/pre-compact.sh +23 -27
  16. package/core/harnesses/claude/hooks/pre-implement-gate.sh +7 -18
  17. package/core/harnesses/claude/hooks/session-end.sh +23 -10
  18. package/core/harnesses/claude/hooks/session-start.sh +5 -3
  19. package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
  20. package/core/harnesses/claude/hooks/stop-check.sh +5 -3
  21. package/core/harnesses/claude/hooks/subagent-audit.sh +5 -5
  22. package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -3
  23. package/core/harnesses/claude/settings/settings.json +18 -0
  24. package/core/harnesses/claude/statusline/doflow-statusline.sh +1 -0
  25. package/core/harnesses/codex/agents/core-implementer.toml +1 -0
  26. package/core/harnesses/codex/agents/delivery-lead.toml +10 -0
  27. package/core/harnesses/codex/agents/quality-guardian.toml +1 -1
  28. package/core/harnesses/codex/agents/research-writer.toml +1 -1
  29. package/core/harnesses/codex/agents/reviewer.toml +10 -0
  30. package/core/harnesses/codex/agents/spec-analyst.toml +1 -1
  31. package/core/harnesses/codex/agents/system-architect.toml +1 -1
  32. package/core/harnesses/codex/hooks/lib.sh +44 -25
  33. package/core/harnesses/codex/hooks/mcp-tool-guard.sh +7 -15
  34. package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
  35. package/core/harnesses/codex/hooks/pre-bash-guard.sh +7 -15
  36. package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
  37. package/core/harnesses/codex/hooks/pre-implement-gate.sh +7 -16
  38. package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
  39. package/core/harnesses/codex/hooks/session-start.sh +5 -3
  40. package/core/harnesses/codex/hooks/stop-check.sh +6 -5
  41. package/core/harnesses/codex/hooks/subagent-audit.sh +18 -6
  42. package/core/harnesses/codex/hooks/user-prompt-submit.sh +5 -4
  43. package/core/harnesses/copilot/hooks/doflow-lib.sh +93 -0
  44. package/core/harnesses/copilot/hooks/doflow-mcp-tool-guard.sh +9 -0
  45. package/core/harnesses/copilot/hooks/doflow-pre-bash-guard.sh +9 -0
  46. package/core/harnesses/copilot/hooks/doflow-pre-implement-gate.sh +9 -0
  47. package/core/harnesses/copilot/hooks/doflow-session-start.sh +9 -0
  48. package/core/harnesses/copilot/hooks/doflow-stop-check.sh +9 -0
  49. package/core/harnesses/copilot/hooks/doflow-subagent-start.sh +9 -0
  50. package/core/harnesses/copilot/hooks/doflow-subagent-stop.sh +9 -0
  51. package/core/harnesses/copilot/hooks/doflow.json +57 -0
  52. package/core/harnesses/kiro/agents/doflow.json +1 -1
  53. package/core/harnesses/kiro/hooks/lib.sh +26 -6
  54. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +21 -5
  55. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +21 -5
  56. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +21 -5
  57. package/core/harnesses/kiro/hooks/session-start.sh +12 -5
  58. package/core/harnesses/kiro/hooks/stop-check.sh +22 -30
  59. package/core/harnesses/pi/extensions/doflow-guard.js +167 -0
  60. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +27 -12
  61. package/core/harnesses/shared/hooks/adapters/antigravity.js +14 -57
  62. package/core/harnesses/shared/hooks/bridge/bridge.sh +277 -0
  63. package/core/harnesses/shared/hooks/bridge/envelope.jq +129 -0
  64. package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
  65. package/core/harnesses/shared/hooks/bridge/respond.sh +70 -0
  66. package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +28 -14
  67. package/core/harnesses/shared/hooks/policies/capture-failure.sh +51 -16
  68. package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
  69. package/core/harnesses/shared/hooks/policies/contract.sh +78 -0
  70. package/core/harnesses/shared/hooks/policies/deny-json.sh +35 -9
  71. package/core/harnesses/shared/hooks/policies/emission-rules.sh +37 -0
  72. package/core/harnesses/shared/hooks/policies/emission.jq +209 -0
  73. package/core/harnesses/shared/hooks/policies/emission.sh +383 -0
  74. package/core/harnesses/shared/hooks/policies/identity.sh +177 -0
  75. package/core/harnesses/shared/hooks/policies/lib.sh +65 -40
  76. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +48 -35
  77. package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +284 -33
  78. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +180 -110
  79. package/core/harnesses/shared/hooks/policies/session-context.sh +66 -7
  80. package/core/harnesses/shared/hooks/policies/stop-check.sh +61 -23
  81. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +39 -6
  82. package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
  83. package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +97 -159
  84. package/core/harnesses/shared/hooks/stream-hook-runner.js +114 -187
  85. package/core/harnesses/shared/locator/doflow-run +2 -3
  86. package/core/registry/advice.json +3 -3
  87. package/core/registry/assets.json +72 -80
  88. package/core/registry/capabilities.json +12 -0
  89. package/core/registry/contracts.json +60 -33
  90. package/core/registry/contribution-points.json +32 -0
  91. package/core/registry/gates.json +32 -0
  92. package/core/registry/guidance-caps.json +40 -0
  93. package/core/registry/harnesses.json +1542 -277
  94. package/core/registry/lifecycle.json +2837 -37
  95. package/core/registry/mcp.json +5 -1
  96. package/core/registry/models.json +7 -1
  97. package/core/registry/package-templates/README.md +19 -0
  98. package/core/registry/package-templates/agent/files/agent.md +9 -0
  99. package/core/registry/package-templates/agent/template.json +4 -0
  100. package/core/registry/package-templates/capability-provider/files/provider.json +9 -0
  101. package/core/registry/package-templates/capability-provider/template.json +11 -0
  102. package/core/registry/package-templates/debate-preset/files/preset.json +26 -0
  103. package/core/registry/package-templates/debate-preset/template.json +4 -0
  104. package/core/registry/package-templates/review-preset/files/preset.json +30 -0
  105. package/core/registry/package-templates/review-preset/template.json +4 -0
  106. package/core/registry/package-templates/skill/files/SKILL.md +8 -0
  107. package/core/registry/package-templates/skill/template.json +4 -0
  108. package/core/registry/package-templates/worker/files/worker.json +92 -0
  109. package/core/registry/package-templates/worker/template.json +11 -0
  110. package/core/registry/packages/doflow/doflow-package.json +15 -0
  111. package/core/registry/packages/doflow/presets/debate-3.json +26 -0
  112. package/core/registry/protocols.json +61 -0
  113. package/core/registry/routes.json +17 -0
  114. package/core/registry/rules.json +431 -0
  115. package/core/registry/settings.json +276 -0
  116. package/core/registry/storage.json +701 -30
  117. package/core/registry/telemetry.json +236 -0
  118. package/core/registry/transcripts.json +142 -0
  119. package/core/registry/verification.json +76 -1
  120. package/core/registry/workflows.json +55 -0
  121. package/core/shared/agent-specs/core-implementer.md +3 -0
  122. package/core/shared/agent-specs/delivery-lead.md +68 -0
  123. package/core/shared/agent-specs/quality-guardian.md +5 -1
  124. package/core/shared/agent-specs/research-writer.md +10 -3
  125. package/core/shared/agent-specs/reviewer.md +53 -0
  126. package/core/shared/agent-specs/spec-analyst.md +5 -6
  127. package/core/shared/agent-specs/system-architect.md +5 -1
  128. package/core/shared/guidance/DOFLOW_CORE.md +1 -5
  129. package/core/shared/guidance/modes/MODE_DeepResearch.md +1 -1
  130. package/core/shared/guidance/modes/MODE_Orchestration.md +4 -3
  131. package/core/shared/guidance/modes/MODE_Task_Management.md +14 -14
  132. package/core/shared/guidance/pointers/codex.md +1 -4
  133. package/core/shared/guidance/references/ARTIFACT_FORMAT.md +30 -16
  134. package/core/shared/guidance/references/ARTIFACT_VOICE.md +3 -2
  135. package/core/shared/guidance/references/CONSTITUTION_BASE.md +17 -15
  136. package/core/shared/guidance/references/DIAGRAM_GUIDE.md +1 -1
  137. package/core/shared/guidance/references/DOFLOW_CHAIN.md +17 -19
  138. package/core/shared/guidance/references/EVIDENCE_LEDGER.md +7 -8
  139. package/core/shared/guidance/references/MODEL_SELECTION.md +5 -3
  140. package/core/shared/guidance/references/ORCHESTRATION.md +1 -2
  141. package/core/shared/guidance/rules/RULES.md +45 -0
  142. package/core/shared/scripts/doflow/bash/do-args.sh +37 -0
  143. package/core/shared/scripts/doflow/bash/do-exec-paths.sh +4 -0
  144. package/core/shared/scripts/doflow/bash/do-git-state.sh +15 -4
  145. package/core/shared/scripts/doflow/bash/do-parallel-check.sh +4 -0
  146. package/core/shared/scripts/doflow/bash/do-paths.sh +40 -2
  147. package/core/shared/scripts/doflow/bash/do-prereqs.sh +2 -1
  148. package/core/shared/scripts/doflow/bash/do-review-package.sh +4 -0
  149. package/core/shared/scripts/doflow/bash/do-task-brief.sh +58 -16
  150. package/core/shared/scripts/doflow/bash/render-audit.sh +26 -2
  151. package/core/shared/scripts/doflow/bash/render-diagrams.sh +1 -0
  152. package/core/shared/scripts/doflow/bash/render-puml.sh +2 -1
  153. package/core/shared/scripts/doflow/bash/validate-artifacts.sh +48 -4
  154. package/core/shared/scripts/doflow/bin/doflow-run +154 -35
  155. package/core/shared/skills/do/SKILL.md +12 -10
  156. package/core/shared/skills/do/references/advice_calls.md +26 -0
  157. package/core/shared/skills/do/references/pm_routing.md +1 -2
  158. package/core/shared/skills/do/references/program_mode.md +31 -0
  159. package/core/shared/skills/do-brainstorm/SKILL.md +20 -74
  160. package/core/shared/skills/do-brainstorm/references/handoff_branches.md +35 -0
  161. package/core/shared/skills/do-brainstorm/references/intent_seed.md +21 -0
  162. package/core/shared/skills/do-brainstorm/references/round_log.md +17 -0
  163. package/core/shared/skills/do-code-review/SKILL.md +41 -144
  164. package/core/shared/skills/do-code-review/content-types/markdown.md +24 -2
  165. package/core/shared/skills/do-code-review/references/tools.md +90 -0
  166. package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +549 -7
  167. package/core/shared/skills/do-design/SKILL.md +24 -23
  168. package/core/shared/skills/do-diagnose/SKILL.md +14 -4
  169. package/core/shared/skills/do-document/SKILL.md +11 -1
  170. package/core/shared/skills/do-document/references/deep_research.md +1 -2
  171. package/core/shared/skills/do-document/references/embedded-research.md +18 -6
  172. package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
  173. package/core/shared/skills/do-execute-plan/SKILL.md +17 -59
  174. package/core/shared/skills/do-execute-plan/references/handoff_record.md +7 -2
  175. package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +13 -0
  176. package/core/shared/skills/do-execute-plan/references/readiness_gate.md +28 -1
  177. package/core/shared/skills/do-execute-plan/references/run_position.md +13 -1
  178. package/core/shared/skills/do-execute-plan/references/scaffold.md +17 -9
  179. package/core/shared/skills/do-execute-plan/references/state_checkpoint.md +11 -0
  180. package/core/shared/skills/do-execute-plan/references/task_attempts.md +78 -24
  181. package/core/shared/skills/do-flow/SKILL.md +8 -9
  182. package/core/shared/skills/do-flow/references/task_classes.md +2 -2
  183. package/core/shared/skills/do-git/SKILL.md +4 -0
  184. package/core/shared/skills/do-implement/SKILL.md +7 -9
  185. package/core/shared/skills/do-plan/SKILL.md +29 -19
  186. package/core/shared/skills/do-test/SKILL.md +7 -4
  187. package/core/shared/templates/doflow/constitution-template.md +9 -3
  188. package/core/shared/templates/doflow/data-model-template.md +1 -1
  189. package/core/shared/templates/doflow/design-template.md +113 -16
  190. package/core/shared/templates/doflow/plan-template.md +11 -10
  191. package/core/shared/templates/doflow/question-log-template.md +14 -2
  192. package/core/shared/templates/doflow/requirement-template.md +1 -1
  193. package/core/worker-evidence/antigravity/checks.json +14 -0
  194. package/core/worker-evidence/codex/2026-10-18-cli-check.json +107 -0
  195. package/core/worker-evidence/codex/2026-10-18-live-run-edit.json +73 -0
  196. package/core/worker-evidence/codex/2026-10-18-live-run-read-only.json +62 -0
  197. package/core/worker-evidence/codex/2026-10-24-cli-check.json +126 -0
  198. package/core/worker-evidence/codex/checks.json +38 -0
  199. package/package.json +10 -6
  200. package/src/adapters/antigravity/index.js +226 -113
  201. package/src/adapters/claude/index.js +39 -5
  202. package/src/adapters/codex/agents.js +25 -15
  203. package/src/adapters/codex/hooks.js +40 -5
  204. package/src/adapters/codex/index.js +100 -22
  205. package/src/adapters/codex/native-projection.js +6 -2
  206. package/src/adapters/copilot/index.js +58 -23
  207. package/src/adapters/copy-tree.js +294 -48
  208. package/src/adapters/guidance-manifest.js +71 -0
  209. package/src/adapters/guidance-pointer.js +19 -0
  210. package/src/adapters/hook-commands.js +6 -10
  211. package/src/adapters/index.js +1 -0
  212. package/src/adapters/kiro/index.js +38 -13
  213. package/src/adapters/opencode/index.js +8 -8
  214. package/src/adapters/pi/hooks.js +120 -0
  215. package/src/adapters/pi/index.js +48 -16
  216. package/src/adapters/resolve-source.js +26 -2
  217. package/src/cli/commands/install.js +3 -2
  218. package/src/cli/commands/reconcile.js +53 -9
  219. package/src/cli/commands/remove.js +2 -2
  220. package/src/cli/commands/rollback.js +6 -3
  221. package/src/cli/commands/self-update.js +18 -9
  222. package/src/cli/commands/status.js +21 -2
  223. package/src/cli/commands/ui.js +99 -0
  224. package/src/cli/commands/update.js +3 -2
  225. package/src/cli/index.js +338 -33
  226. package/src/cli/runtime-commands.js +243 -52
  227. package/src/cli/shared.js +0 -2
  228. package/src/helper/git.js +1 -1
  229. package/src/helper/guardrails.js +1 -1
  230. package/src/helper/marker-merge.js +31 -13
  231. package/src/helper/package-ids.js +37 -0
  232. package/src/helper/semver.js +94 -0
  233. package/src/install/backup.js +105 -15
  234. package/src/install/context.js +1 -1
  235. package/src/install/targets.js +31 -16
  236. package/src/lifecycle/coverage.js +74 -0
  237. package/src/lifecycle/hook-step.js +61 -0
  238. package/src/lifecycle/hook-tier.js +160 -0
  239. package/src/lifecycle/index.js +434 -86
  240. package/src/lifecycle/retired-harnesses.js +318 -0
  241. package/src/lifecycle/view.js +35 -15
  242. package/src/registry/index.js +686 -32
  243. package/src/registry/worker-ids.js +45 -0
  244. package/src/release/build-package.js +0 -3
  245. package/src/release/docs-blocks.js +492 -0
  246. package/src/release/preflight.js +425 -0
  247. package/src/release/rehearse.js +401 -0
  248. package/src/release/release-workflow.js +733 -0
  249. package/src/release/sync-plugin-versions.js +43 -7
  250. package/src/runtime/advice/bench-gate.js +25 -0
  251. package/src/runtime/advice/cli.js +59 -16
  252. package/src/runtime/advice/client.js +32 -9
  253. package/src/runtime/advice/config.js +98 -133
  254. package/src/runtime/advice/doctor.js +1 -1
  255. package/src/runtime/advice/elevation.js +97 -0
  256. package/src/runtime/advice/endpoint-guard.js +9 -3
  257. package/src/runtime/advice/mode.js +37 -0
  258. package/src/runtime/advice/record.js +18 -5
  259. package/src/runtime/advice/registry.js +4 -1
  260. package/src/runtime/advice/shadow-config.js +229 -0
  261. package/src/runtime/advice/shadow-dispatcher.js +276 -0
  262. package/src/runtime/brief-rules.js +81 -0
  263. package/src/runtime/brief-tools.js +111 -34
  264. package/src/runtime/candidate.js +514 -0
  265. package/src/runtime/capabilities/executor.js +450 -0
  266. package/src/runtime/capabilities/loopback.js +41 -0
  267. package/src/runtime/capabilities/sanitizer.js +88 -0
  268. package/src/runtime/capability-router.js +135 -3
  269. package/src/runtime/checkouts.js +73 -11
  270. package/src/runtime/claims.js +27 -12
  271. package/src/runtime/cli-result.js +11 -4
  272. package/src/runtime/cli.js +221 -277
  273. package/src/runtime/config-home.js +42 -0
  274. package/src/runtime/context/arbitrate.js +767 -0
  275. package/src/runtime/context/budget.js +203 -0
  276. package/src/runtime/context/compose.js +1367 -0
  277. package/src/runtime/context/delivery-store.js +100 -0
  278. package/src/runtime/context/emission-rules.js +118 -0
  279. package/src/runtime/context/identity.js +227 -0
  280. package/src/runtime/context/mandatory.js +594 -0
  281. package/src/runtime/context/pointer.js +142 -0
  282. package/src/runtime/context/primary-command.js +117 -0
  283. package/src/runtime/context/primary.js +344 -0
  284. package/src/runtime/context/summary-store.js +655 -0
  285. package/src/runtime/context-pack.js +1106 -245
  286. package/src/runtime/decision-register.js +13 -4
  287. package/src/runtime/dispatch/admission.js +392 -0
  288. package/src/runtime/dispatch/attempt-end.js +274 -0
  289. package/src/runtime/dispatch/command.js +628 -0
  290. package/src/runtime/dispatch/controls.js +904 -0
  291. package/src/runtime/dispatch/envelope.js +276 -0
  292. package/src/runtime/dispatch/grants.js +310 -0
  293. package/src/runtime/dispatch/integrate.js +617 -0
  294. package/src/runtime/dispatch/loop.js +1835 -0
  295. package/src/runtime/dispatch/store.js +352 -0
  296. package/src/runtime/dispatch/verify-run.js +201 -0
  297. package/src/runtime/evidence-fields.js +39 -0
  298. package/src/runtime/evidence-ledger.js +41 -7
  299. package/src/runtime/failure/capture.js +3 -1
  300. package/src/runtime/failure/cli.js +25 -9
  301. package/src/runtime/failure/home.js +57 -30
  302. package/src/runtime/failure/store.js +7 -2
  303. package/src/runtime/feature-pointer.js +174 -0
  304. package/src/runtime/feature-resolve.js +13 -2
  305. package/src/runtime/fleet/command.js +77 -0
  306. package/src/runtime/fleet/projects.js +178 -0
  307. package/src/runtime/fleet/tree.js +383 -0
  308. package/src/runtime/freshness.js +77 -10
  309. package/src/runtime/git-memo.js +46 -0
  310. package/src/runtime/guidance/ablation.js +427 -0
  311. package/src/runtime/health.js +565 -17
  312. package/src/runtime/history-compactor.js +13 -3
  313. package/src/runtime/implementation-gate.js +277 -22
  314. package/src/runtime/inventory/coverage.js +1 -1
  315. package/src/runtime/inventory/index.js +1 -1
  316. package/src/runtime/inventory/precedence.js +2 -2
  317. package/src/runtime/inventory/siblings.js +6 -4
  318. package/src/runtime/knowledge/code-corpus.js +277 -0
  319. package/src/runtime/knowledge/code-lexical.js +130 -0
  320. package/src/runtime/knowledge/code-retrieval.js +307 -0
  321. package/src/runtime/knowledge/code-validation.js +106 -0
  322. package/src/runtime/knowledge/gp3-gate.js +74 -0
  323. package/src/runtime/knowledge/graph-adapter.js +288 -0
  324. package/src/runtime/knowledge/graph-fallback.js +394 -0
  325. package/src/runtime/knowledge/hybrid.js +136 -8
  326. package/src/runtime/knowledge/retrieval.js +34 -1
  327. package/src/runtime/knowledge/rtk-compress.js +77 -0
  328. package/src/runtime/knowledge/semble-bridge.py +106 -0
  329. package/src/runtime/knowledge/semble-lifecycle.js +114 -0
  330. package/src/runtime/knowledge/semble-provider.js +365 -0
  331. package/src/runtime/lifecycle/cli.js +16 -6
  332. package/src/runtime/lifecycle/event-store.js +8 -6
  333. package/src/runtime/lifecycle/followup.js +6 -1
  334. package/src/runtime/lifecycle/overview.js +24 -1
  335. package/src/runtime/lifecycle/report-store.js +8 -10
  336. package/src/runtime/lifecycle/retention.js +95 -31
  337. package/src/runtime/lifecycle/root.js +9 -1
  338. package/src/runtime/lifecycle/status.js +34 -17
  339. package/src/runtime/lifecycle/store-upkeep.js +56 -7
  340. package/src/runtime/mask.js +134 -7
  341. package/src/runtime/mcp-consultations.js +40 -6
  342. package/src/runtime/model-router.js +79 -7
  343. package/src/runtime/models/catalogue.js +191 -0
  344. package/src/runtime/models/command.js +153 -0
  345. package/src/runtime/off-words.js +12 -0
  346. package/src/runtime/outcome.js +3 -3
  347. package/src/runtime/packages/command.js +1009 -0
  348. package/src/runtime/packages/contributions.js +116 -0
  349. package/src/runtime/packages/digest.js +132 -0
  350. package/src/runtime/packages/fetch.js +182 -0
  351. package/src/runtime/packages/grants.js +192 -0
  352. package/src/runtime/packages/index-file.js +134 -0
  353. package/src/runtime/packages/instruction.js +90 -0
  354. package/src/runtime/packages/manifest.js +331 -0
  355. package/src/runtime/packages/points.js +477 -0
  356. package/src/runtime/packages/programs.js +216 -0
  357. package/src/runtime/packages/scaffold.js +277 -0
  358. package/src/runtime/packages/source.js +90 -0
  359. package/src/runtime/packages/state.js +164 -0
  360. package/src/runtime/packages/store.js +449 -0
  361. package/src/runtime/packages/tar.js +106 -0
  362. package/src/runtime/packages/view.js +406 -0
  363. package/src/runtime/plan-check.js +59 -0
  364. package/src/runtime/plan-content.js +324 -0
  365. package/src/runtime/policy-init.js +118 -0
  366. package/src/runtime/profiles/doctor.js +96 -0
  367. package/src/runtime/profiles/resolve.js +244 -0
  368. package/src/runtime/profiles/rollback.js +254 -0
  369. package/src/runtime/profiles/sampler.js +41 -0
  370. package/src/runtime/readiness-record.js +16 -11
  371. package/src/runtime/readiness.js +127 -16
  372. package/src/runtime/research-request.js +50 -25
  373. package/src/runtime/retrieval-plan.js +402 -73
  374. package/src/runtime/retrieval-record.js +293 -0
  375. package/src/runtime/review/command.js +246 -0
  376. package/src/runtime/review/dominance.js +68 -0
  377. package/src/runtime/review/findings.js +434 -0
  378. package/src/runtime/review/identity.js +255 -0
  379. package/src/runtime/review/protocol-command.js +109 -0
  380. package/src/runtime/review/protocols.js +405 -0
  381. package/src/runtime/review/resolve-protocol.js +302 -0
  382. package/src/runtime/review/start.js +530 -0
  383. package/src/runtime/roadmap/facts.js +479 -0
  384. package/src/runtime/roadmap/gates.js +270 -0
  385. package/src/runtime/roadmap/loop.js +1636 -0
  386. package/src/runtime/roadmap/merge.js +381 -0
  387. package/src/runtime/roadmap/rows.js +1113 -0
  388. package/src/runtime/roadmap/seed.js +951 -0
  389. package/src/runtime/rules-report.js +336 -0
  390. package/src/runtime/scaffold/artifacts.js +63 -9
  391. package/src/runtime/scaffold/generate.js +21 -9
  392. package/src/runtime/settings/autopilot.js +106 -0
  393. package/src/runtime/settings/cli.js +280 -0
  394. package/src/runtime/settings/config-write.js +314 -0
  395. package/src/runtime/settings/files.js +271 -0
  396. package/src/runtime/settings/legacy.js +154 -0
  397. package/src/runtime/settings/lexical.js +56 -0
  398. package/src/runtime/settings/prepared-run.js +269 -0
  399. package/src/runtime/settings/project-layers.js +118 -0
  400. package/src/runtime/settings/refs.js +288 -0
  401. package/src/runtime/settings/registry.js +553 -0
  402. package/src/runtime/settings/resolve.js +713 -0
  403. package/src/runtime/settings/shared.js +37 -0
  404. package/src/runtime/settings/worker-billing.js +107 -0
  405. package/src/runtime/settings/write.js +269 -0
  406. package/src/runtime/slot-command.js +78 -0
  407. package/src/runtime/slots.js +671 -0
  408. package/src/runtime/source-version.js +290 -0
  409. package/src/runtime/state-root.js +217 -0
  410. package/src/runtime/storage/cli.js +12 -5
  411. package/src/runtime/storage/engine.js +9 -2
  412. package/src/runtime/storage/registry.js +14 -1
  413. package/src/runtime/storage/roots.js +66 -16
  414. package/src/runtime/storage/rules.js +82 -10
  415. package/src/runtime/storage/upkeep.js +10 -2
  416. package/src/runtime/storage/work-status.js +245 -26
  417. package/src/runtime/task-attempts.js +589 -69
  418. package/src/runtime/task-classifier.js +47 -11
  419. package/src/runtime/task-graph.js +373 -0
  420. package/src/runtime/task-run.js +778 -0
  421. package/src/runtime/task-scope.js +202 -56
  422. package/src/runtime/task-sources.js +147 -0
  423. package/src/runtime/task-state.js +440 -65
  424. package/src/runtime/team/admission.js +301 -0
  425. package/src/runtime/team/envelope.js +324 -0
  426. package/src/runtime/team/review-guard.js +156 -0
  427. package/src/runtime/trace/analyze.js +427 -0
  428. package/src/runtime/trace/baseline.js +770 -0
  429. package/src/runtime/trace/ledger.js +63 -140
  430. package/src/runtime/trace/otlp-export.js +568 -0
  431. package/src/runtime/trace/otlp-map.js +481 -0
  432. package/src/runtime/trace/spans.js +745 -0
  433. package/src/runtime/trace/telemetry.js +225 -0
  434. package/src/runtime/trace/views.js +1 -0
  435. package/src/runtime/transcripts/command.js +98 -0
  436. package/src/runtime/transcripts/cursor.js +326 -0
  437. package/src/runtime/transcripts/read.js +126 -0
  438. package/src/runtime/transcripts/readers/claude-jsonl.js +74 -0
  439. package/src/runtime/transcripts/readers/codex-rollout.js +70 -0
  440. package/src/runtime/transcripts/readers/copilot-events.js +90 -0
  441. package/src/runtime/transcripts/readers/kiro-jsonl.js +77 -0
  442. package/src/runtime/transcripts/readers/pi-jsonl.js +59 -0
  443. package/src/runtime/transcripts/registry.js +161 -0
  444. package/src/runtime/transcripts/stats.js +161 -0
  445. package/src/runtime/verification/check-key.js +628 -0
  446. package/src/runtime/verification/contract-runner.js +546 -11
  447. package/src/runtime/verification/engine.js +499 -9
  448. package/src/runtime/verification/impact.js +617 -0
  449. package/src/runtime/verification/inflight.js +205 -0
  450. package/src/runtime/verification/registry.js +176 -0
  451. package/src/runtime/verification/reuse.js +184 -0
  452. package/src/runtime/verification/telemetry-emit.js +254 -0
  453. package/src/runtime/verification/test-selection.js +67 -7
  454. package/src/runtime/worker/active.js +90 -0
  455. package/src/runtime/worker/cancel.js +222 -0
  456. package/src/runtime/worker/catalogue.js +122 -0
  457. package/src/runtime/worker/command.js +677 -0
  458. package/src/runtime/worker/evidence.js +288 -0
  459. package/src/runtime/worker/launch.js +288 -0
  460. package/src/runtime/worker/models.js +137 -0
  461. package/src/runtime/worker/parsers.js +218 -0
  462. package/src/runtime/worker/probes/index.js +706 -0
  463. package/src/runtime/worker/process-tree.js +415 -0
  464. package/src/runtime/worker/quota.js +43 -0
  465. package/src/runtime/worker/readiness.js +713 -0
  466. package/src/runtime/worker/run-store.js +166 -0
  467. package/src/runtime/worker/supervisor.js +541 -0
  468. package/src/runtime/worker/worktree-check.js +252 -0
  469. package/src/runtime/workflow-engine.js +5 -1
  470. package/src/runtime/workflow-orchestrator.js +578 -63
  471. package/src/runtime/worktree.js +149 -22
  472. package/src/state/index.js +1 -1
  473. package/src/state/lockfile.js +64 -10
  474. package/src/state/run-lock.js +1 -1
  475. package/types/node-commonjs.d.ts +106 -7
  476. package/core/.antigravity-plugin/hooks.json +0 -39
  477. package/core/harnesses/gemini/hooks/hooks.json +0 -77
  478. package/core/harnesses/gemini/hooks/lib.sh +0 -302
  479. package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +0 -21
  480. package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
  481. package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -21
  482. package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
  483. package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -22
  484. package/core/harnesses/gemini/hooks/session-end.impl.sh +0 -89
  485. package/core/harnesses/gemini/hooks/session-end.sh +0 -5
  486. package/core/harnesses/gemini/hooks/session-start.sh +0 -5
  487. package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
  488. package/core/harnesses/shared/hooks/adapters/arg-fields.js +0 -19
  489. package/core/harnesses/shared/hooks/adapters/gemini.js +0 -114
  490. package/core/harnesses/shared/hooks/hooks.json +0 -47
  491. package/core/shared/guidance/FLAGS.md +0 -25
  492. package/core/shared/guidance/rules/RULE_01_SAFETY.md +0 -25
  493. package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +0 -23
  494. package/core/shared/guidance/rules/RULE_03_QUALITY.md +0 -14
  495. package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +0 -46
  496. package/src/adapters/gemini/hooks.js +0 -210
  497. package/src/adapters/gemini/index.js +0 -390
@@ -6,6 +6,8 @@ const fs = require('node:fs');
6
6
  const path = require('node:path');
7
7
  const { parseYamlFile } = require('./capability-router.js');
8
8
  const { resolveLocator, describeResolution } = require('./locator-resolve.js');
9
+ const { readPlan, bindEvidence, declaredIntentList } = require('./retrieval-record.js');
10
+ const { fileRootFor } = require('./freshness.js');
9
11
  const { REPO_ROOT } = require('../helper/repo-root.js');
10
12
 
11
13
  const READINESS_STATES = new Set([
@@ -63,6 +65,7 @@ class ReadinessEngine {
63
65
 
64
66
 
65
67
 
68
+
66
69
 
67
70
 
68
71
  /**
@@ -71,6 +74,8 @@ class ReadinessEngine {
71
74
  * @param {string} [options.projectRoot] the repository under work, which evidence locators name.
72
75
  * Distinct from repoRoot on purpose: when the CLI runs inside another project, the templates
73
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.
74
79
  * @param {string} [options.templatePath]
75
80
  * @param {Object} [options.templates]
76
81
  * @param {Object} [options.fsImpl]
@@ -79,6 +84,7 @@ class ReadinessEngine {
79
84
  this.fsImpl = options.fsImpl || fs;
80
85
  this.repoRoot = options.repoRoot || REPO_ROOT;
81
86
  this.projectRoot = options.projectRoot || process.cwd();
87
+ this.sourceRoot = options.sourceRoot || this.projectRoot;
82
88
  this.templatePath = options.templatePath || path.join(this.repoRoot, 'core', 'registry', 'readiness-templates.json');
83
89
 
84
90
  if (options.templates) {
@@ -133,6 +139,22 @@ class ReadinessEngine {
133
139
  action: 'Execute automated tests or record runtime observation reproducing the behavior.',
134
140
  };
135
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
+ }
136
158
  return {
137
159
  intent: 'locate-concept',
138
160
  capability: 'code.semantic-search',
@@ -211,7 +233,23 @@ class ReadinessEngine {
211
233
  hasBlocked = true;
212
234
  }
213
235
 
214
- const taskEvidence = evidenceLedger ? evidenceLedger.queryEvidence({ taskId, status: 'FRESH' }) : [];
236
+ const allTaskEvidence = evidenceLedger ? evidenceLedger.queryEvidence({ taskId }) : [];
237
+ let plan = null;
238
+ let unreadablePlan = null;
239
+ try {
240
+ plan = readPlan(this.projectRoot, taskId);
241
+ } catch (error ) {
242
+ if (error.code !== 'store-unreadable') throw error;
243
+ unreadablePlan = { unreadable: true, reason: error.message };
244
+ }
245
+ const queryBound = Boolean(plan?.planId);
246
+ const binding = queryBound ? bindEvidence(plan, allTaskEvidence) : null;
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.
250
+ const taskEvidence = allTaskEvidence.filter((item ) => item.freshness?.status === 'FRESH'
251
+ && item.status !== 'superseded'
252
+ && (!queryBound || !unboundIds.has(item.id)));
215
253
 
216
254
  // An item accepted before FR-004 existed, or one whose target file has since shrunk, can be
217
255
  // FRESH and still point at nothing. *Stale* (the file changed at all) and *unresolvable* (the
@@ -225,13 +263,14 @@ class ReadinessEngine {
225
263
  // in evaluateClaim — it is not a claim that the file never changed. An item that genuinely went
226
264
  // stale and was never superseded still forces NEEDS_EVIDENCE exactly as before.
227
265
  const staleEvidence = (evidenceLedger ? evidenceLedger.getAllEvidence() : [])
228
- .filter((item ) => item.taskId === taskId && item.freshness?.status === 'STALE' && item.status !== 'superseded')
266
+ .filter((item ) => item.taskId === taskId && item.result !== 'EMPTY'
267
+ && item.freshness?.status === 'STALE' && item.status !== 'superseded')
229
268
  .map((item ) => ({ evidenceId: item.id, locator: item.locator, reason: 'file-modified-since-recorded' }));
230
269
 
231
270
  const unresolvableEvidence = [];
232
271
  for (const item of taskEvidence) {
233
272
  if (item.provenance !== 'extracted' || !item.locator || !item.locator.file) continue;
234
- const resolution = resolveLocator({ locator: item.locator, repoRoot: this.projectRoot });
273
+ const resolution = resolveLocator({ locator: item.locator, repoRoot: fileRootFor(this.sourceRoot, this.projectRoot, item.locator) });
235
274
  if (!resolution.resolved) {
236
275
  unresolvableEvidence.push({
237
276
  evidenceId: item.id,
@@ -242,9 +281,30 @@ class ReadinessEngine {
242
281
  }
243
282
  }
244
283
 
284
+ const staleAbsence = [];
285
+ const staleAbsenceByRequirement = new Map ();
286
+ for (const item of allTaskEvidence) {
287
+ if (item.result !== 'EMPTY' || item.status !== 'active'
288
+ || !['STALE', 'UNKNOWN'].includes(item.freshness?.status)
289
+ || (queryBound && unboundIds.has(item.id))) continue;
290
+ for (const requirement of Array.isArray(item.establishes) ? item.establishes : []) {
291
+ const entry = { evidenceId: item.id, requirement, provider: item.source?.provider || null,
292
+ freshness: item.freshness.status, refresh: plan?.providers?.[item.source?.provider]?.refresh || null };
293
+ staleAbsence.push(entry);
294
+ const entries = staleAbsenceByRequirement.get(requirement) || [];
295
+ entries.push({ item, entry });
296
+ staleAbsenceByRequirement.set(requirement, entries);
297
+ }
298
+ }
299
+
300
+ const unboundEvidence = queryBound
301
+ ? { count: binding.unbound.length, ids: binding.unbound.slice(0, 5).map((item ) => item.id) }
302
+ : undefined;
303
+
245
304
  for (const [reqId, reqDef] of Object.entries (requirements)) {
246
305
  let isSatisfied = false;
247
306
  let reason = null;
307
+ let reasonCode = null;
248
308
  let matchedEvidenceIds = [];
249
309
 
250
310
  // Check verification plan requirement
@@ -260,7 +320,18 @@ class ReadinessEngine {
260
320
  // not by any supported claim the task happens to hold (review R1 — a supported claim about an
261
321
  // unrelated invariant satisfied the bug template's root-cause requirement).
262
322
  else if (reqDef.requiresClaimStatus) {
263
- 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);
264
335
  if (reqDef.requiresClaimRole) {
265
336
  const withRole = matchingClaims.filter((c ) => c.role === reqDef.requiresClaimRole);
266
337
  if (withRole.length === 0 && matchingClaims.length > 0) {
@@ -273,6 +344,12 @@ class ReadinessEngine {
273
344
  if (matchingClaims.length > 0) {
274
345
  isSatisfied = true;
275
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.';
276
353
  } else if (!reason) {
277
354
  reason = `Requires at least one claim with status '${reqDef.requiresClaimStatus}'`
278
355
  + `${reqDef.requiresClaimRole ? ` and role '${reqDef.requiresClaimRole}'` : ''}.`;
@@ -286,18 +363,18 @@ class ReadinessEngine {
286
363
  // (review R1, P1 — reproduced through the public CLI).
287
364
  else if (Array.isArray(reqDef.evidenceKinds) && reqDef.evidenceKinds.length > 0) {
288
365
  const allowedKinds = new Set(reqDef.evidenceKinds);
366
+ const rawKindMatched = allTaskEvidence.filter((ev ) => allowedKinds.has(ev.kind));
289
367
  const kindMatched = taskEvidence.filter((ev ) => allowedKinds.has(ev.kind));
290
368
  const extracted = kindMatched.filter((ev ) => ev.provenance === 'extracted');
291
369
  const bound = extracted.filter((ev ) => Array.isArray(ev.establishes) && ev.establishes.includes(reqId));
292
- let qualifying = bound;
293
- if (reqDef.requiresObservation) {
294
- qualifying = qualifying.filter((ev ) => ev.observation && Number.isInteger(ev.observation.exitCode));
295
- if (reqDef.expectedExit === 'zero') {
296
- qualifying = qualifying.filter((ev ) => ev.observation.exitCode === 0);
297
- } else if (reqDef.expectedExit === 'nonzero') {
298
- qualifying = qualifying.filter((ev ) => ev.observation.exitCode !== 0);
299
- }
300
- }
370
+ const observationOk = (ev ) => !reqDef.requiresObservation || (ev.observation
371
+ && Number.isInteger(ev.observation.exitCode)
372
+ && (reqDef.expectedExit !== 'zero' || ev.observation.exitCode === 0)
373
+ && (reqDef.expectedExit !== 'nonzero' || ev.observation.exitCode !== 0));
374
+ const qualifies = (ev ) => ev.provenance === 'extracted'
375
+ && Array.isArray(ev.establishes) && ev.establishes.includes(reqId)
376
+ && observationOk(ev);
377
+ const qualifying = taskEvidence.filter((ev ) => allowedKinds.has(ev.kind) && qualifies(ev));
301
378
  if (qualifying.length > 0) {
302
379
  isSatisfied = true;
303
380
  matchedEvidenceIds = qualifying.map((ev ) => ev.id);
@@ -319,6 +396,32 @@ class ReadinessEngine {
319
396
  + `${reqDef.expectedExit ? ` with a${reqDef.expectedExit === 'zero' ? ' passing (0)' : ' non-zero'} exit status` : ''} — `
320
397
  + 'record the execution itself, not a description of one.';
321
398
  }
399
+ if (!isSatisfied && reqDef.required) {
400
+ const staleAbsences = staleAbsenceByRequirement.get(reqId) || [];
401
+ if (staleAbsences.length > 0) {
402
+ const { item, entry } = staleAbsences[0];
403
+ const refresh = entry.refresh || 'none declared';
404
+ reasonCode = 'stale-absence-unverified';
405
+ const prefix = `'${reqId}' rests only on an empty answer from ${entry.provider || 'unknown provider'}, whose index was ${entry.freshness} when it was recorded (${item.id}). An empty answer from that index cannot establish absence. `;
406
+ if (item.freshness?.staleReason === 'tree-changed-since-recorded'
407
+ || item.freshness?.staleReason === 'tree-unverifiable') {
408
+ reason = `${prefix}the working tree changed since it was recorded; re-run the lookup and record the absence again.`;
409
+ } else if (item.freshness?.gitCommit === null) {
410
+ reason = `${prefix}absence cannot be established outside a git work tree; record a positive item.`;
411
+ } else {
412
+ reason = `${prefix}Run the declared refresh command (${refresh}) and record the absence again, or re-run the lookup with a live provider such as native.rg: doflow evidence --task-id ${taskId} --action add --absence --need <intent> --provider native.rg ...`;
413
+ }
414
+ } else if (queryBound) {
415
+ const unboundKind = rawKindMatched.filter((ev ) => unboundIds.has(ev.id));
416
+ const unboundQualifying = unboundKind.filter((ev ) =>
417
+ ev.freshness?.status === 'FRESH' && qualifies(ev));
418
+ if (unboundQualifying.length > 0) {
419
+ const ids = unboundQualifying.slice(0, 5).map((item ) => item.id).join(', ');
420
+ reasonCode = 'unbound-retrieval-evidence';
421
+ reason = `${unboundQualifying.length} item(s) of the right kind are not bound to a declared need of plan ${plan.planId} (${ids}), so they do not count. Re-record them with --need <intent>; declared: ${declaredIntentList(plan)}.`;
422
+ }
423
+ }
424
+ }
322
425
  }
323
426
  // Scope verified / scope clear
324
427
  else if (reqId === 'scope_clear' || reqId === 'scope_verified' || reqId === 'invariants_captured') {
@@ -347,6 +450,7 @@ class ReadinessEngine {
347
450
  required: Boolean(reqDef.required),
348
451
  satisfied: isSatisfied,
349
452
  reason,
453
+ reasonCode,
350
454
  evidenceIds: matchedEvidenceIds,
351
455
  recommendedAction: isSatisfied ? null : this.suggestAction(reqId, reqDef, taskProfile),
352
456
  });
@@ -383,6 +487,10 @@ class ReadinessEngine {
383
487
  + 'Re-record them against the tree as it stands.';
384
488
  }
385
489
 
490
+ if (unreadablePlan) {
491
+ summary += ` Warning: retrieval plan is unreadable and was treated as absent: ${unreadablePlan.reason}`;
492
+ }
493
+
386
494
  return {
387
495
  taskId,
388
496
  taskClass,
@@ -399,6 +507,9 @@ class ReadinessEngine {
399
507
  evidenceCount: taskEvidence.length,
400
508
  unresolvableEvidence,
401
509
  staleEvidence,
510
+ staleAbsence,
511
+ ...(unreadablePlan ? { retrievalPlan: unreadablePlan } : {}),
512
+ ...(unboundEvidence ? { unboundEvidence } : {}),
402
513
  };
403
514
  }
404
515
  }
@@ -406,7 +517,7 @@ class ReadinessEngine {
406
517
  /** One live snapshot for readiness inspection and orchestration transitions. Reads never persist
407
518
  * derived freshness or claim status; every invocation evaluates the current project again.
408
519
  * `mode` defaults to 'workflow' — fail closed: the standalone exemption must be declared. */
409
- function evaluateTaskReadiness({ taskProfile, repoRoot = REPO_ROOT, projectRoot = process.cwd(), mode = 'workflow' } ) {
520
+ function evaluateTaskReadiness({ taskProfile, repoRoot = REPO_ROOT, projectRoot = process.cwd(), treeRoot = projectRoot, sourceRoot = projectRoot, mode = 'workflow' } ) {
410
521
  // Validated before any ledger work, not just inside stageEntryFor at the end — an invalid mode
411
522
  // used to pay a full freshness/claims evaluation before erroring (review suggestion, 026).
412
523
  if (!EXECUTION_MODES.has(mode)) {
@@ -417,11 +528,11 @@ function evaluateTaskReadiness({ taskProfile, repoRoot = REPO_ROOT, projectRoot
417
528
  const { FreshnessValidator } = require('./freshness.js');
418
529
  const ledger = new EvidenceLedger({ repoRoot: projectRoot });
419
530
  ledger.load(taskProfile.taskId);
420
- new FreshnessValidator({ repoRoot: projectRoot }).validateLedgerFreshness(ledger);
531
+ new FreshnessValidator({ repoRoot: projectRoot, treeRoot, sourceRoot }).validateLedgerFreshness(ledger);
421
532
  const claims = new ClaimsManager({ evidenceLedger: ledger, repoRoot: projectRoot });
422
533
  claims.load(taskProfile.taskId);
423
534
  claims.evaluateAll();
424
- const report = new ReadinessEngine({ repoRoot, projectRoot }).evaluateReadiness(taskProfile, ledger, claims);
535
+ const report = new ReadinessEngine({ repoRoot, projectRoot, sourceRoot }).evaluateReadiness(taskProfile, ledger, claims);
425
536
  return { ...report, executionMode: mode, stageEntry: stageEntryFor(report.state, mode) };
426
537
  }
427
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 };