@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
@@ -38,16 +38,15 @@
38
38
  * emits are counts of records.
39
39
  */
40
40
 
41
- const fs = require('node:fs');
42
- const path = require('node:path');
43
41
  const { CapabilityRouter } = require('./capability-router.js');
44
42
  const { EvidenceLedger, assertSafeTaskId } = require('./evidence-ledger.js');
45
- const { taskStoreDir } = require('./task-scope.js');
43
+ const { FreshnessValidator } = require('./freshness.js');
46
44
  const { probeFreshness } = require('./health.js');
45
+ const { planPath, readPlan, updatePlan, moveCorruptPlanAside, mintPlanId, activeAttemptRef, resolveNeed, appendRetrievalAttempt, bindEvidence, deriveOutcome } = require('./retrieval-record.js');
47
46
  // One definition of the score refusal, applied at a second boundary. See the note on
48
- // EVIDENCE_SCORE_FIELDS in cli.js: relevance is a property of a search, not of a fact, and a plan
47
+ // EVIDENCE_SCORE_FIELDS in src/runtime/evidence-fields.js: relevance is a property of a search, not of a fact, and a plan
49
48
  // that recorded one would hand the next worker a confidence dressed as a measurement.
50
- const { assertNoScoreFields } = require('./cli.js');
49
+ const { assertNoScoreFields } = require('./evidence-fields.js');
51
50
  const { finishRuntime, usageError } = require('./cli-result.js');
52
51
  const { REPO_ROOT } = require('../helper/repo-root.js');
53
52
 
@@ -73,6 +72,18 @@ const RESULTS = Object.freeze([RETRIEVED, EMPTY, UNREACHED, UNVERIFIED]);
73
72
  const NOT_APPLICABLE = 'NOT_APPLICABLE';
74
73
  const FRESHNESS_STATES = Object.freeze(['FRESH', 'STALE', 'UNKNOWN', NOT_APPLICABLE]);
75
74
 
75
+ // ── attempt reason codes (IC-013) ─────────────────────────────────────────────────────────────
76
+
77
+ const ATTEMPT_REASON_CODES = Object.freeze([
78
+ 'provider-unavailable',
79
+ 'policy-bypass',
80
+ 'provider-error',
81
+ 'timeout',
82
+ 'empty-answer',
83
+ 'index-stale',
84
+ 'index-unverifiable',
85
+ ]);
86
+
76
87
  /**
77
88
  * Design §4's mapping table, as data. It maps only the *empty* answer: a provider that returned
78
89
  * something is RETRIEVED whatever the age of its index, because the thing it returned is a
@@ -91,6 +102,13 @@ const EMPTY_ANSWER_BY_FRESHNESS = Object.freeze({
91
102
  [NOT_APPLICABLE]: EMPTY,
92
103
  });
93
104
 
105
+ function requirePlanForUpdate(disk , taskId ) {
106
+ if (disk === null) {
107
+ throw new Error(`the retrieval plan for task ${taskId} was removed during the update; re-declare it`);
108
+ }
109
+ return disk;
110
+ }
111
+
94
112
  /**
95
113
  * What an empty answer becomes, given the freshness of the index behind it.
96
114
  * @param {string} freshnessState one of FRESHNESS_STATES
@@ -107,50 +125,6 @@ function resultForEmptyAnswer(freshnessState ) {
107
125
  return mapped;
108
126
  }
109
127
 
110
- // ── the record ────────────────────────────────────────────────────────────────────────────────
111
-
112
- /**
113
- * Where a task's plan lives, under the invoking project's state root.
114
- *
115
- * The task id is validated by the evidence ledger's own rule, unchanged: it becomes a filename, so
116
- * it must never be able to name a path.
117
- * @param {string} projectRoot
118
- * @param {string} taskId
119
- * @param {string|null} [slug] the feature the task belongs to, when not the branch's (task-scope.js)
120
- * @returns {string}
121
- */
122
- function planPath(projectRoot , taskId , slug = null) {
123
- const dir = taskStoreDir({ projectRoot, store: 'retrieval', taskId: assertSafeTaskId(taskId), slug });
124
- return path.join(dir, `${taskId}.json`);
125
- }
126
-
127
- /**
128
- * @param {string} projectRoot
129
- * @param {string} taskId
130
- * @returns {Object|null} the record, or null when no plan was declared
131
- */
132
- function readPlan(projectRoot , taskId , slug = null) {
133
- const file = planPath(projectRoot, taskId, slug);
134
- if (!fs.existsSync(file)) return null;
135
- try {
136
- return JSON.parse(fs.readFileSync(file, 'utf8'));
137
- } catch (error ) {
138
- throw new Error(`the retrieval plan at '${file}' is not readable: ${error.message}`);
139
- }
140
- }
141
-
142
- /**
143
- * @param {string} projectRoot
144
- * @param {string} taskId
145
- * @param {Object} record
146
- * @returns {string} the file written
147
- */
148
- function writePlan(projectRoot , taskId , record , slug = null) {
149
- const file = planPath(projectRoot, taskId, slug);
150
- fs.mkdirSync(path.dirname(file), { recursive: true });
151
- fs.writeFileSync(file, `${JSON.stringify(record, null, 2)}\n`, 'utf8');
152
- return file;
153
- }
154
128
 
155
129
  /**
156
130
  * The `sourceMode` each provider id declares, as a lookup the plan builds once per declare.
@@ -186,8 +160,8 @@ function sourceModeLookup(capabilities ) {
186
160
  * absent, every provider without an artifact layout is treated as unknown (fail closed)
187
161
  * @returns {{state: string, artifact: string|null, refresh: string|null, label: string|null, reason: string|null}}
188
162
  */
189
- function freshnessEntry(providerId , projectRoot , sourceModeOf = () => null) {
190
- const probe = probeFreshness(providerId, { root: projectRoot });
163
+ function freshnessEntry(providerId , sourceRoot , sourceModeOf = () => null) {
164
+ const probe = probeFreshness(providerId, { root: sourceRoot });
191
165
  if (!probe) {
192
166
  // `probeFreshness` returns null for a provider it has no artifact layout for. What that means
193
167
  // depends on how the provider answers: a `live` provider reads current files or commands, so
@@ -274,8 +248,8 @@ function evidenceIdsFor(need , items ) {
274
248
  *
275
249
  * @returns {number} exit code
276
250
  */
277
- function declarePlan({ router, projectRoot, taskId, stage, intents, json, slug } ) {
278
- const needs = [];
251
+ function declarePlan({ router, projectRoot, sourceRoot, taskId, stage, intents, json, slug } ) {
252
+ const needs = [];
279
253
  for (const intent of intents) {
280
254
  let resolution;
281
255
  try {
@@ -303,18 +277,35 @@ function declarePlan({ router, projectRoot, taskId, stage, intents, json, slug }
303
277
  const sourceModeOf = sourceModeLookup(router.capabilities);
304
278
  for (const need of needs) {
305
279
  if (!need.provider || providers[need.provider]) continue;
306
- providers[need.provider] = freshnessEntry(need.provider, projectRoot, sourceModeOf);
280
+ providers[need.provider] = freshnessEntry(need.provider, sourceRoot, sourceModeOf);
307
281
  }
308
282
 
309
- const record = {
283
+ const planId = mintPlanId();
284
+ const attemptRef = activeAttemptRef(projectRoot, taskId, slug);
285
+ for (const need of needs) {
286
+ need.queryRef = `${planId}#${need.intent}`;
287
+ }
288
+
289
+ const record = {
310
290
  version: 1,
291
+ planId,
292
+ binding: 'query',
311
293
  taskId,
312
294
  stage: stage || null,
313
295
  declaredAt: new Date().toISOString(),
296
+ attemptRef,
314
297
  providers,
315
298
  needs,
316
299
  };
317
- const file = writePlan(projectRoot, taskId, record, slug);
300
+ let file ;
301
+ try {
302
+ file = updatePlan(projectRoot, taskId, slug, () => record, undefined, () => {
303
+ const corruptFile = moveCorruptPlanAside(projectRoot, taskId, slug);
304
+ if (corruptFile) console.log(`Moved unreadable retrieval plan to ${corruptFile}; declaring a new plan.`);
305
+ });
306
+ } catch (error ) {
307
+ return usageError('retrieval-plan', error.message, json, error);
308
+ }
318
309
 
319
310
  const unresolvable = needs.filter((need) => !need.provider).map((need) => need.intent);
320
311
  if (json) {
@@ -326,12 +317,13 @@ function declarePlan({ router, projectRoot, taskId, stage, intents, json, slug }
326
317
  console.log('═'.repeat(78));
327
318
  console.log(`Stage: ${record.stage || 'not stated'}`);
328
319
  console.log('─'.repeat(78));
329
- console.log('Intent'.padEnd(26) + 'Provider'.padEnd(22) + 'Index');
320
+ console.log('Intent'.padEnd(26) + 'Provider'.padEnd(22) + 'Index'.padEnd(16) + 'QueryRef');
330
321
  for (const need of needs) {
331
322
  console.log(
332
323
  need.intent.padEnd(26)
333
324
  + (need.provider || 'none — unresolvable').padEnd(22)
334
- + freshnessOf(record, need),
325
+ + freshnessOf(record, need).padEnd(16)
326
+ + (need.queryRef || ''),
335
327
  );
336
328
  }
337
329
  printProviderIndexes(record);
@@ -367,8 +359,8 @@ function declarePlan({ router, projectRoot, taskId, stage, intents, json, slug }
367
359
  *
368
360
  * @returns {number} exit code
369
361
  */
370
- function reportPlan({ projectRoot, taskId, reached, json, slug } ) {
371
- let record;
362
+ function reportPlan({ projectRoot, sourceRoot, taskId, reached, json, slug, repoRoot } ) {
363
+ let record ;
372
364
  try {
373
365
  record = readPlan(projectRoot, taskId, slug);
374
366
  } catch (error ) {
@@ -395,17 +387,121 @@ function reportPlan({ projectRoot, taskId, reached, json, slug } ) {
395
387
  } catch (error ) {
396
388
  return usageError('retrieval-plan', error.message, json, error);
397
389
  }
390
+ new FreshnessValidator({ repoRoot: projectRoot, treeRoot: sourceRoot, sourceRoot }).validateLedgerFreshness(ledger);
398
391
  const items = ledger.queryEvidence({ taskId });
399
392
 
400
- for (const need of record.needs) {
401
- need.evidenceIds = evidenceIdsFor(need, items);
402
- if (need.evidenceIds.length > 0) need.result = RETRIEVED;
403
- else if (reached.has(need.intent)) need.result = resultForEmptyAnswer(freshnessOf(record, need));
404
- else need.result = UNREACHED;
393
+ const isQueryBound = Boolean(record.planId);
394
+ let binding = isQueryBound ? bindEvidence(record, items) : null;
395
+ let reportSourceModeOf = null;
396
+ const probeByProvider = new Map ();
397
+ if (record.planId) {
398
+ const initialBinding = bindEvidence(record, items);
399
+ const missingProviders = new Set ();
400
+ for (const need of record.needs) {
401
+ for (const item of initialBinding.byNeed.get(need.intent) || []) {
402
+ if (item.result !== 'EMPTY' && !record.providers?.[item.source?.provider]) {
403
+ if (item.source?.provider) missingProviders.add(item.source.provider);
404
+ }
405
+ }
406
+ }
407
+ if (missingProviders.size > 0) {
408
+ const reportRouter = new CapabilityRouter({ repoRoot: repoRoot || REPO_ROOT });
409
+ reportSourceModeOf = sourceModeLookup(reportRouter.capabilities);
410
+ for (const providerId of missingProviders) {
411
+ probeByProvider.set(providerId, module.exports._probe(providerId, sourceRoot, reportSourceModeOf));
412
+ }
413
+ }
405
414
  }
406
415
 
407
- record.reportedAt = new Date().toISOString();
408
- const file = writePlan(projectRoot, taskId, record, slug);
416
+ let file ;
417
+ try {
418
+ file = updatePlan(projectRoot, taskId, slug, (disk ) => {
419
+ const current = requirePlanForUpdate(disk, taskId);
420
+ if (current.planId) {
421
+ const currentBinding = bindEvidence(current, items);
422
+ binding = currentBinding;
423
+ for (const need of current.needs) {
424
+ const bound = currentBinding.byNeed.get(need.intent) || [];
425
+ if (!Array.isArray(need.attempts)) need.attempts = [];
426
+ for (const attempt of need.attempts) {
427
+ if (attempt.recordedBy === 'derived') attempt.evidenceIds = [];
428
+ }
429
+ const groups = new Map ();
430
+ for (const item of bound) {
431
+ const absent = item.result === 'EMPTY';
432
+ const status = absent
433
+ ? (item.freshness?.status === 'UNKNOWN' ? UNVERIFIED : EMPTY)
434
+ : RETRIEVED;
435
+ const providerKey = item.source?.provider ?? '__no_provider__';
436
+ const freshness = item.freshness?.status || 'UNKNOWN';
437
+ const key = `${providerKey}\u0000${status}\u0000${freshness}`;
438
+ if (!groups.has(key)) groups.set(key, []);
439
+ const group = groups.get(key);
440
+ if (group) group.push(item);
441
+ }
442
+ const countedAttempts = need.attempts.filter((attempt ) =>
443
+ !(attempt.recordedBy === 'derived' && attempt.basis !== 'report-need'
444
+ && (!Array.isArray(attempt.evidenceIds) || attempt.evidenceIds.length === 0)));
445
+ if (reached.has(need.intent) && countedAttempts.length === 0 && bound.length === 0) {
446
+ const provider = need.provider;
447
+ const indexState = provider ? (current.providers?.[provider]?.state || 'UNKNOWN') : NOT_APPLICABLE;
448
+ groups.set(`${provider ?? '__no_provider__'}\u0000${resultForEmptyAnswer(indexState)}\u0000${indexState}`, []);
449
+ }
450
+ for (const [key, group] of groups) {
451
+ const [providerKey, status, freshness] = key.split('\u0000');
452
+ const provider = providerKey === '__no_provider__' ? null : providerKey;
453
+ let attempt = need.attempts.find((entry ) => entry.recordedBy === 'derived'
454
+ && entry.provider === provider && entry.status === status && entry.indexState === freshness);
455
+ if (attempt) {
456
+ attempt.evidenceIds = group.map((item ) => item.id);
457
+ continue;
458
+ }
459
+ if (status === RETRIEVED && provider && !current.providers?.[provider] && probeByProvider.has(provider)) {
460
+ current.providers ||= {};
461
+ current.providers[provider] = probeByProvider.get(provider);
462
+ }
463
+ const providerState = provider ? current.providers?.[provider]?.state : null;
464
+ const indexState = status === RETRIEVED
465
+ ? (providerState || 'UNKNOWN')
466
+ : (freshness || providerState || 'UNKNOWN');
467
+ const reason = status === RETRIEVED ? null
468
+ : indexState === 'STALE' ? 'index-stale'
469
+ : indexState === 'UNKNOWN' ? 'index-unverifiable' : 'empty-answer';
470
+ const n = need.attempts.reduce((max , entry ) => Math.max(max, entry.n || 0), 0) + 1;
471
+ need.attempts.push({
472
+ n, provider, capability: need.capability,
473
+ role: provider === null || provider === need.provider ? 'primary' : 'fallback',
474
+ status, reason, note: null, indexState,
475
+ attemptRef: current.attemptRef || null,
476
+ evidenceIds: group.map((item ) => item.id),
477
+ recordedAt: new Date().toISOString(), recordedBy: 'derived',
478
+ ...(reached.has(need.intent) && group.length === 0 ? { basis: 'report-need' } : {}),
479
+ });
480
+ }
481
+ const outcome = deriveOutcome(need);
482
+ need.result = outcome.result;
483
+ need.resolvedBy = outcome.resolvedBy;
484
+ need.viaFallback = outcome.viaFallback;
485
+ need.evidenceIds = need.attempts.filter((entry ) => entry.status === RETRIEVED
486
+ && !(entry.recordedBy === 'derived' && (!Array.isArray(entry.evidenceIds) || entry.evidenceIds.length === 0)))
487
+ .flatMap((entry ) => entry.evidenceIds || []);
488
+ }
489
+ record = current;
490
+ } else {
491
+ for (const need of current.needs) {
492
+ need.evidenceIds = evidenceIdsFor(need, items);
493
+ if (need.evidenceIds.length > 0) need.result = RETRIEVED;
494
+ else if (reached.has(need.intent)) need.result = resultForEmptyAnswer(freshnessOf(current, need));
495
+ else need.result = UNREACHED;
496
+ }
497
+ }
498
+ current.reportedAt = new Date().toISOString();
499
+ record = current;
500
+ return current;
501
+ });
502
+ } catch (error ) {
503
+ return usageError('retrieval-plan', error.message, json, error);
504
+ }
409
505
 
410
506
  // Derived views, computed at emit and never stored on a need — a need holds a provider id and
411
507
  // nothing about that provider's index (plan D7).
@@ -430,6 +526,18 @@ function reportPlan({ projectRoot, taskId, reached, json, slug } ) {
430
526
  unreached,
431
527
  unverified,
432
528
  stale,
529
+ ...(record.planId ? {
530
+ planId: record.planId,
531
+ binding: record.binding,
532
+ unbound: {
533
+ count: binding.unbound.length,
534
+ ids: binding.unbound.slice(0, 5).map((item ) => item.id),
535
+ causes: binding.unbound.reduce((counts , item ) => {
536
+ counts[item.cause] = (counts[item.cause] || 0) + 1;
537
+ return counts;
538
+ }, {}),
539
+ },
540
+ } : {}),
433
541
  }, null, 2));
434
542
  return finishRuntime(incomplete ? 1 : 0);
435
543
  }
@@ -447,8 +555,21 @@ function reportPlan({ projectRoot, taskId, reached, json, slug } ) {
447
555
  + freshness.padEnd(16)
448
556
  + need.result + (freshness === 'STALE' ? ' (stale index)' : ''),
449
557
  );
558
+ if (record.planId) {
559
+ const attempts = need.attempts || [];
560
+ if (attempts.length > 1 || (attempts.length === 1 && attempts[0].status !== RETRIEVED)) {
561
+ for (const attempt of attempts) {
562
+ console.log(` Attempt #${attempt.n} ${attempt.role} ${attempt.provider} — ${attempt.status}`
563
+ + (attempt.reason ? ` (${attempt.reason})` : '')
564
+ + (attempt.evidenceIds?.length ? ` [${attempt.evidenceIds.join(', ')}]` : ''));
565
+ }
566
+ }
567
+ }
450
568
  }
451
569
  printProviderIndexes(record);
570
+ if (record.planId && binding.unbound.length > 0) {
571
+ console.log(`${binding.unbound.length} item(s) not bound to a declared need are not counted; re-record them with --need <intent>.`);
572
+ }
452
573
  console.log('─'.repeat(78));
453
574
  // NFR-004: the skip is stated as prominently as the failure, on its own line and named.
454
575
  if (unreached.length > 0) {
@@ -464,6 +585,186 @@ function reportPlan({ projectRoot, taskId, reached, json, slug } ) {
464
585
  return finishRuntime(incomplete ? 1 : 0);
465
586
  }
466
587
 
588
+ /**
589
+ * `--action attempt` — record an attempt against a declared need (IC-012, IC-013).
590
+ *
591
+ * Rules:
592
+ * - The action appends one attempt to the need, under the plan lock (IC-020).
593
+ * - `indexState` is re-probed for `--provider` with `freshnessEntry` at record time.
594
+ * - `--status EMPTY` is mapped through `resultForEmptyAnswer(indexState)`, so an EMPTY
595
+ * from an UNKNOWN index is stored as UNVERIFIED with reason `index-unverifiable`.
596
+ * - The action writes no evidence and never changes declared fields.
597
+ * - Exits 0 when written, 2 on any refusal.
598
+ *
599
+ * @returns {number} exit code
600
+ */
601
+ function attemptPlan({
602
+ router,
603
+ projectRoot,
604
+ taskId,
605
+ intents,
606
+ provider,
607
+ attemptStatus,
608
+ reason,
609
+ note,
610
+ json,
611
+ slug,
612
+ sourceRoot,
613
+ } ) {
614
+ if (intents.length !== 1) {
615
+ return usageError(
616
+ 'retrieval-plan',
617
+ `--need takes one intent on retrieval-plan --action attempt, got ${intents.length}. Record one write per need.`,
618
+ json,
619
+ );
620
+ }
621
+ const intent = intents[0];
622
+
623
+ let record ;
624
+ try {
625
+ record = readPlan(projectRoot, taskId, slug);
626
+ } catch (error ) {
627
+ return usageError('retrieval-plan', error.message, json, error);
628
+ }
629
+
630
+ const resolved = resolveNeed(record, intent, taskId);
631
+ if (!resolved.ok) {
632
+ return usageError('retrieval-plan', resolved.message, json);
633
+ }
634
+ const declaredNeed = resolved.need;
635
+
636
+ if (attemptStatus === undefined || attemptStatus === null || attemptStatus === '') {
637
+ return usageError('retrieval-plan', '--status is required: EMPTY, UNREACHED or UNVERIFIED', json);
638
+ }
639
+
640
+ if (attemptStatus === 'RETRIEVED') {
641
+ return usageError(
642
+ 'retrieval-plan',
643
+ `RETRIEVED is never recorded by hand: it is derived from evidence bound to the need. Record what was found with: doflow evidence --task-id ${taskId} --action add --need ${intent} ...`,
644
+ json,
645
+ );
646
+ }
647
+
648
+ if (attemptStatus !== 'EMPTY' && attemptStatus !== 'UNREACHED' && attemptStatus !== 'UNVERIFIED') {
649
+ return usageError(
650
+ 'retrieval-plan',
651
+ `unknown --status '${attemptStatus}'. Valid here: EMPTY, UNREACHED, UNVERIFIED. A provider that could not be asked is UNREACHED with --reason provider-unavailable or policy-bypass.`,
652
+ json,
653
+ );
654
+ }
655
+
656
+ const REASON_CODES_LIST = ATTEMPT_REASON_CODES.join(', ');
657
+
658
+ if (reason === undefined || reason === null || reason === '') {
659
+ return usageError(
660
+ 'retrieval-plan',
661
+ `--status ${attemptStatus} needs --reason <code>: a lookup that returned no locator must say why. Codes: ${REASON_CODES_LIST}.`,
662
+ json,
663
+ );
664
+ }
665
+
666
+ if (!ATTEMPT_REASON_CODES.includes(reason)) {
667
+ return usageError(
668
+ 'retrieval-plan',
669
+ `unknown --reason '${reason}'. Codes: ${REASON_CODES_LIST}.`,
670
+ json,
671
+ );
672
+ }
673
+
674
+ const allowedReasons = {
675
+ EMPTY: ['empty-answer', 'index-stale', 'index-unverifiable'],
676
+ UNREACHED: ['provider-unavailable', 'policy-bypass', 'provider-error', 'timeout'],
677
+ UNVERIFIED: ['index-unverifiable'],
678
+ };
679
+ if (!allowedReasons[attemptStatus].includes(reason)) {
680
+ return usageError('retrieval-plan', `--reason '${reason}' is not allowed for --status ${attemptStatus}. Allowed: ${allowedReasons[attemptStatus].join(', ')}.`, json);
681
+ }
682
+
683
+ const capEntry = router.capabilities ? router.capabilities[declaredNeed.capability] : null;
684
+ const registeredProviders = capEntry && Array.isArray(capEntry.providers)
685
+ ? capEntry.providers.map((p ) => p.id).filter(Boolean)
686
+ : [];
687
+ const regList = registeredProviders.join(', ');
688
+
689
+ if (provider === undefined || provider === null || String(provider).trim() === '') {
690
+ return usageError(
691
+ 'retrieval-plan',
692
+ `--provider is required: an attempt is one provider's try at one need. Providers registered under ${declaredNeed.capability}: ${regList}.`,
693
+ json,
694
+ );
695
+ }
696
+
697
+ if (!registeredProviders.includes(provider)) {
698
+ return usageError(
699
+ 'retrieval-plan',
700
+ `provider '${provider}' is not registered under capability '${declaredNeed.capability}' (need '${intent}'). Registered: ${regList}. A fallback is another provider of the same capability, for example native.rg.`,
701
+ json,
702
+ );
703
+ }
704
+
705
+ if (note !== undefined && note !== null && String(note).length > 160) {
706
+ return usageError(
707
+ 'retrieval-plan',
708
+ `--note is at most 160 characters (got ${String(note).length}): it is a hint for a reader, and the runtime never reads it.`,
709
+ json,
710
+ );
711
+ }
712
+
713
+ const sourceModeOf = sourceModeLookup(router.capabilities);
714
+ const probe = freshnessEntry(provider, sourceRoot, sourceModeOf);
715
+ const indexState = probe.state;
716
+
717
+ let finalStatus = attemptStatus;
718
+ let finalReason = reason;
719
+
720
+ if (attemptStatus === 'EMPTY') {
721
+ finalStatus = resultForEmptyAnswer(indexState);
722
+ if (finalStatus === 'UNVERIFIED') {
723
+ finalReason = 'index-unverifiable';
724
+ } else if (indexState === 'STALE') {
725
+ finalReason = 'index-stale';
726
+ }
727
+ }
728
+
729
+ const activeRef = activeAttemptRef(projectRoot, taskId, slug);
730
+ let savedAttempt = null;
731
+ let file ;
732
+ try {
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,
739
+ });
740
+ file = appended.file;
741
+ savedAttempt = appended.attempt;
742
+ } catch (error ) {
743
+ return usageError('retrieval-plan', error.message, json, error);
744
+ }
745
+
746
+ if (json) {
747
+ console.log(JSON.stringify({
748
+ action: 'attempt',
749
+ taskId,
750
+ intent,
751
+ stateFile: file,
752
+ attempt: savedAttempt,
753
+ }, null, 2));
754
+ return finishRuntime(0);
755
+ }
756
+
757
+ console.log(`\nDoFlow Retrieval Attempt recorded [Task: ${taskId}]:`);
758
+ console.log('═'.repeat(78));
759
+ console.log(`Need: ${intent} Attempt: #${savedAttempt.n} Role: ${savedAttempt.role}`);
760
+ console.log(`Provider: ${savedAttempt.provider} Status: ${savedAttempt.status} Reason: ${savedAttempt.reason}`);
761
+ if (savedAttempt.note) console.log(`Note: ${savedAttempt.note}`);
762
+ console.log('─'.repeat(78));
763
+ console.log(`Recorded attempt #${savedAttempt.n} to ${file}`);
764
+ console.log('═'.repeat(78) + '\n');
765
+ return finishRuntime(0);
766
+ }
767
+
467
768
  /** The provider index block both actions print: state, what was located, and the remedy. */
468
769
  function printProviderIndexes(record ) {
469
770
  const entries = Object.entries (record.providers || {});
@@ -504,11 +805,12 @@ function parseIntents(raw ) {
504
805
  * @param {string} [options.stage] the stage id declaring the plan
505
806
  * @param {boolean} [options.json=false]
506
807
  * @param {string} [options.repoRoot] the DoFlow package root, holding the capability registries
507
- * @param {string} [options.stateRoot] the project the task belongs to; its state and its index
808
+ * @param {string} [options.stateRoot] the project the task belongs to; its state
809
+ * @param {string} [options.sourceRoot] the checkout whose source and index freshness are measured
508
810
  * @returns {number} exit code
509
811
  */
510
812
  function handleRetrievalPlanCommand(options = {}) {
511
- const { taskId, action = 'report', need, stage, json = false, repoRoot, stateRoot, slug } = options;
813
+ const { taskId, action = 'report', need, stage, provider, attemptStatus, reason, note, json = false, repoRoot, stateRoot, sourceRoot: requestedSourceRoot, slug } = options;
512
814
 
513
815
  // NFR-001, at the boundary where a caller's input first becomes a record. `bin/doflow.js` already
514
816
  // refuses every score-shaped flag name on the command line; this is the same rule applied to the
@@ -523,13 +825,14 @@ function handleRetrievalPlanCommand(options = {}) {
523
825
  // accepts it as an alias for its own default rather than reporting a usage error on an argument
524
826
  // the caller never typed — `verify` and `claim` already do exactly this.
525
827
  const resolvedAction = action === 'status' ? 'report' : action;
526
- if (resolvedAction !== 'declare' && resolvedAction !== 'report') {
527
- return usageError('retrieval-plan', `unknown --action '${action}'. Valid: declare, report (default)`, json);
828
+ if (resolvedAction !== 'declare' && resolvedAction !== 'report' && resolvedAction !== 'attempt') {
829
+ return usageError('retrieval-plan', `unknown --action '${action}'. Valid: declare, report (default), attempt`, json);
528
830
  }
529
831
 
530
832
  // The project the task belongs to owns both the state and the index whose age is being measured.
531
833
  // The package root owns the capability registries, and is a different directory in every install.
532
834
  const projectRoot = stateRoot || process.cwd();
835
+ const sourceRoot = requestedSourceRoot || projectRoot;
533
836
  const packageRoot = repoRoot || REPO_ROOT;
534
837
 
535
838
  try {
@@ -555,10 +858,32 @@ function handleRetrievalPlanCommand(options = {}) {
555
858
  } catch (error ) {
556
859
  return usageError('retrieval-plan', error.message, json, error);
557
860
  }
558
- return declarePlan({ router, projectRoot, taskId, stage, intents, json, slug });
861
+ return declarePlan({ router, projectRoot, sourceRoot, taskId, stage, intents, json, slug });
862
+ }
863
+
864
+ if (resolvedAction === 'attempt') {
865
+ let router;
866
+ try {
867
+ router = new CapabilityRouter({ repoRoot: packageRoot });
868
+ } catch (error ) {
869
+ return usageError('retrieval-plan', error.message, json, error);
870
+ }
871
+ return attemptPlan({
872
+ router,
873
+ projectRoot,
874
+ sourceRoot,
875
+ taskId,
876
+ intents,
877
+ provider,
878
+ attemptStatus,
879
+ reason,
880
+ note,
881
+ json,
882
+ slug,
883
+ });
559
884
  }
560
885
 
561
- return reportPlan({ projectRoot, taskId, reached: new Set(intents), json, slug });
886
+ return reportPlan({ projectRoot, sourceRoot, taskId, reached: new Set(intents), json, slug, repoRoot: packageRoot });
562
887
  }
563
888
 
564
889
  module.exports = {
@@ -575,4 +900,8 @@ module.exports = {
575
900
  NOT_APPLICABLE,
576
901
  FRESHNESS_STATES,
577
902
  EMPTY_ANSWER_BY_FRESHNESS,
903
+ ATTEMPT_REASON_CODES,
904
+ applyReport: (disk , taskId ) => requirePlanForUpdate(disk, taskId),
905
+ applyAttempt: (disk , taskId ) => requirePlanForUpdate(disk, taskId),
906
+ _probe: (providerId , root , sourceModeOf ) => freshnessEntry(providerId, root, sourceModeOf),
578
907
  };