@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
@@ -0,0 +1,293 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+ const crypto = require('node:crypto');
8
+ const { taskStoreDir, taskRecord, resolveTaskScope } = require('./task-scope.js');
9
+ const { assertSafeTaskId } = require('./evidence-ledger.js');
10
+ const { readTaskState, updateTaskState } = require('./task-state.js');
11
+ const { storeFile, activeAttempt } = require('./task-attempts.js');
12
+ const { projectRoot: lifecycleProjectRoot } = require('./lifecycle/root.js');
13
+
14
+ /**
15
+ * Path of a task's retrieval-plan store.
16
+ *
17
+ * The task id is validated by the evidence ledger's own rule, unchanged: it becomes a filename, so
18
+ * it must never be able to name a path.
19
+ * @param {string} projectRoot
20
+ * @param {string} taskId
21
+ * @param {string|null} [slug]
22
+ * @returns {string}
23
+ */
24
+ function planPath(projectRoot , taskId , slug = null) {
25
+ const dir = taskStoreDir({ projectRoot, store: 'retrieval', taskId: assertSafeTaskId(taskId), slug });
26
+ return path.join(dir, `${taskId}.json`);
27
+ }
28
+
29
+ /**
30
+ * Reads and validates one task's retrieval plan via readTaskState.
31
+ * Returns null when the plan file does not exist.
32
+ * @param {string} projectRoot
33
+ * @param {string} taskId
34
+ * @param {string|null} [slug]
35
+ * @param {any} [fsImpl]
36
+ * @returns {any|null}
37
+ */
38
+ function readPlan(projectRoot , taskId , slug = null, fsImpl = fs) {
39
+ return readTaskState(fsImpl, taskRecord({ projectRoot, store: 'retrieval', taskId: assertSafeTaskId(taskId), slug, fsImpl }).read);
40
+ }
41
+
42
+ /**
43
+ * Updates a task's retrieval plan under the task-state lock.
44
+ * @param {string} projectRoot
45
+ * @param {string} taskId
46
+ * @param {string|null} slug
47
+ * @param {(disk: any|null) => any} build
48
+ * @param {any} [fsImpl]
49
+ * @returns {string} the written file path
50
+ */
51
+ function updatePlan(projectRoot , taskId , slug , build , fsImpl = fs, prepare ) {
52
+ const { file, seed } = taskRecord({ projectRoot, store: 'retrieval', taskId: assertSafeTaskId(taskId), slug, fsImpl });
53
+ return updateTaskState({ fsImpl, file, build, prepare: () => { seed(); if (prepare) prepare(); } });
54
+ }
55
+
56
+ /** Move a syntactically unreadable plan aside before its replacement is written under the lock. */
57
+ function moveCorruptPlanAside(projectRoot , taskId , slug = null, fsImpl = fs) {
58
+ const file = planPath(projectRoot, taskId, slug);
59
+ if (!fsImpl.existsSync(file)) return null;
60
+ try {
61
+ JSON.parse(fsImpl.readFileSync(file, 'utf8'));
62
+ return null;
63
+ } catch (error ) {
64
+ if (!(error instanceof SyntaxError)) throw error;
65
+ const corruptFile = `${file}.corrupt-${new Date().toISOString().replace(/[:.]/g, '-')}`;
66
+ fsImpl.renameSync(file, corruptFile);
67
+ return corruptFile;
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Mints a new plan id: rp_<base36 ms>_<4 hex>.
73
+ * @returns {string}
74
+ */
75
+ function mintPlanId() {
76
+ return `rp_${Date.now().toString(36)}_${crypto.randomBytes(2).toString('hex')}`;
77
+ }
78
+
79
+ /**
80
+ * Formats declared intents from a plan: sorted, comma-joined, or 'none declared'.
81
+ *
82
+ * @param {any} plan
83
+ * @returns {string}
84
+ */
85
+ function declaredIntentList(plan , sep = ', ') {
86
+ const declaredIntents = Array.isArray(plan?.needs)
87
+ ? plan.needs.map((n ) => n.intent).filter(Boolean).sort()
88
+ : [];
89
+ return declaredIntents.length > 0 ? declaredIntents.join(sep) : 'none declared';
90
+ }
91
+
92
+ /**
93
+ * Resolves a declared need from a plan for a given intent.
94
+ *
95
+ * @param {any} plan
96
+ * @param {string} intent
97
+ * @param {string} taskId
98
+ * @returns {{ ok: true, queryRef: string, need: any } | { ok: false, message: string }}
99
+ */
100
+ function resolveNeed(plan , intent , taskId ) {
101
+ if (!plan) {
102
+ const message = `no retrieval plan is declared for task '${taskId}', so --need '${intent}' names nothing. Declare one first: doflow retrieval-plan --task-id ${taskId} --action declare --need ${intent}, then re-run.`;
103
+ return { ok: false, message };
104
+ }
105
+ const list = declaredIntentList(plan);
106
+
107
+ if (!plan.planId) {
108
+ const message = `the retrieval plan for task '${taskId}' was declared before query binding and has no plan id. Re-declare it: doflow retrieval-plan --task-id ${taskId} --action declare --need ${declaredIntentList(plan, ',')}, then re-run.`;
109
+ return { ok: false, message };
110
+ }
111
+
112
+ const need = Array.isArray(plan.needs) ? plan.needs.find((n ) => n.intent === intent) : null;
113
+ if (!need) {
114
+ const message = `'${intent}' is not a declared need of task '${taskId}' (plan ${plan.planId}). Declared: ${list}. Re-run with one of them, or re-declare the plan with this need.`;
115
+ return { ok: false, message };
116
+ }
117
+
118
+ if (!need.queryRef) {
119
+ const message = `the retrieval plan for task '${taskId}' was declared before query binding and has no plan id. Re-declare it: doflow retrieval-plan --task-id ${taskId} --action declare --need ${declaredIntentList(plan, ',')}, then re-run.`;
120
+ return { ok: false, message };
121
+ }
122
+
123
+ return {
124
+ ok: true,
125
+ queryRef: need.queryRef,
126
+ need,
127
+ };
128
+ }
129
+
130
+ /** Resolve the plan's query binding and the task's active attempt in one reusable operation. */
131
+ function resolveRetrievalBinding({ projectRoot, taskId, intent, slug = null, fsImpl = fs, attemptRoot = null } ) {
132
+ const plan = readPlan(projectRoot, taskId, slug, fsImpl);
133
+ const resolved = resolveNeed(plan, intent, taskId);
134
+ if (!resolved.ok) return resolved;
135
+ return {
136
+ ok: true,
137
+ plan,
138
+ queryRef: resolved.queryRef,
139
+ need: resolved.need,
140
+ attemptRef: activeAttemptRef(projectRoot, taskId, slug, fsImpl, attemptRoot),
141
+ };
142
+ }
143
+
144
+ /** Append one validated provider attempt under the retrieval-plan state lock. */
145
+ function appendRetrievalAttempt({
146
+ projectRoot, taskId, intent, provider, status, reason, note = null, indexState,
147
+ slug = null, attemptRef = undefined, fsImpl = fs, expectedCapability = undefined,
148
+ expectedPrimaryProvider = undefined, registeredProviders = undefined,
149
+ } ) {
150
+ const allowedIndexStates = ['FRESH', 'STALE', 'UNKNOWN', 'NOT_APPLICABLE'];
151
+ if (!allowedIndexStates.includes(indexState)) {
152
+ throw new Error(`attempt indexState '${indexState}' is invalid; expected one of ${allowedIndexStates.join(', ')}`);
153
+ }
154
+ const allowedReasons = {
155
+ EMPTY: ['empty-answer', 'index-stale', 'index-unverifiable'],
156
+ UNREACHED: ['provider-unavailable', 'policy-bypass', 'provider-error', 'timeout'],
157
+ UNVERIFIED: ['index-unverifiable'],
158
+ };
159
+ if (status === 'RETRIEVED') throw new Error('RETRIEVED is derived from evidence and cannot be appended as a provider attempt');
160
+ if (!Object.prototype.hasOwnProperty.call(allowedReasons, status) || !allowedReasons[status].includes(reason)) {
161
+ throw new Error(`attempt reason '${reason}' is not valid for status '${status}'`);
162
+ }
163
+ if (!Array.isArray(registeredProviders) || !registeredProviders.includes(provider)) {
164
+ throw new Error(`provider '${provider}' is not registered for this capability`);
165
+ }
166
+ if (note !== null && String(note).length > 160) throw new Error('attempt note is longer than 160 characters');
167
+
168
+ let savedAttempt = null;
169
+ const file = updatePlan(projectRoot, taskId, slug, (disk ) => {
170
+ if (disk === null) throw new Error(`the retrieval plan for task ${taskId} was removed during the update; re-declare it`);
171
+ const current = disk;
172
+ const targetNeed = Array.isArray(current.needs) ? current.needs.find((need ) => need.intent === intent) : null;
173
+ if (!targetNeed) throw new Error(`need '${intent}' missing from disk plan during update`);
174
+ if (expectedCapability !== undefined && targetNeed.capability !== expectedCapability) {
175
+ throw new Error(`need '${intent}' capability changed during update`);
176
+ }
177
+ if (expectedPrimaryProvider !== undefined && targetNeed.provider !== expectedPrimaryProvider) {
178
+ throw new Error(`need '${intent}' provider changed during update`);
179
+ }
180
+ if (provider === undefined || provider === null || String(provider).trim() === '') throw new Error('attempt provider is required');
181
+ if (!Array.isArray(targetNeed.attempts)) targetNeed.attempts = [];
182
+ const n = targetNeed.attempts.reduce((max , prior ) => Math.max(max, Number.isInteger(prior?.n) ? prior.n : 0), 0) + 1;
183
+ const role = provider === targetNeed.provider ? 'primary' : 'fallback';
184
+ savedAttempt = {
185
+ n, provider, capability: targetNeed.capability, role, status, reason,
186
+ note: note !== undefined && note !== null ? String(note) : null,
187
+ indexState,
188
+ attemptRef: attemptRef === undefined ? activeAttemptRef(projectRoot, taskId, slug, fsImpl) : attemptRef,
189
+ evidenceIds: [],
190
+ recordedAt: new Date().toISOString(),
191
+ recordedBy: 'caller',
192
+ };
193
+ targetNeed.attempts.push(savedAttempt);
194
+ return current;
195
+ }, fsImpl);
196
+ return { file, attempt: savedAttempt };
197
+ }
198
+
199
+ /**
200
+ * Reads the 070 active attempt ref for a task, returning `${taskId}#${token}` or null.
201
+ *
202
+ * Resolves the feature slug via resolveTaskScope, reads storeFile(lifecycleProjectRoot(projectRoot), featureSlug),
203
+ * and finds the active attempt for the task. If no slug, no store, no entry or no active attempt exists,
204
+ * or if any failure occurs finding or reading the store, returns null (IC-005: "no attempt" never fails the write).
205
+ *
206
+ * @param {string} projectRoot
207
+ * @param {string} taskId
208
+ * @param {string|null} [slug]
209
+ * @param {any} [fsImpl]
210
+ * @returns {string|null}
211
+ */
212
+ function activeAttemptRef(projectRoot , taskId , slug = null, fsImpl = fs, attemptRoot = null) {
213
+ try {
214
+ const featureSlug = resolveTaskScope({ projectRoot, taskId, slug }).slug;
215
+ if (!featureSlug) return null;
216
+
217
+ const file = storeFile(attemptRoot || lifecycleProjectRoot(projectRoot), featureSlug);
218
+ if (!fsImpl.existsSync(file)) return null;
219
+
220
+ const data = readTaskState(fsImpl, file);
221
+ if (!data || !data.tasks || !data.tasks[taskId]) return null;
222
+
223
+ const active = activeAttempt(data.tasks[taskId]);
224
+ if (!active || active.token === undefined || active.token === null) return null;
225
+
226
+ return `${taskId}#${active.token}`;
227
+ } catch {
228
+ return null;
229
+ }
230
+ }
231
+
232
+ /** Binds active evidence to declared query refs and explains evidence that could have matched. */
233
+ function bindEvidence(plan , items ) {
234
+ const byNeed = new Map ();
235
+ const unbound = [];
236
+ const needs = Array.isArray(plan?.needs) ? plan.needs : [];
237
+ for (const need of needs) byNeed.set(need.intent, []);
238
+ for (const item of items) {
239
+ if (item.status !== 'active') continue;
240
+ const source = item.source || {};
241
+ const exact = needs.find((need ) => need.queryRef && need.queryRef === source.queryRef);
242
+ if (exact) {
243
+ if (plan.attemptRef && source.attemptRef && plan.attemptRef !== source.attemptRef) {
244
+ unbound.push({ id: item.id, cause: 'other-attempt' });
245
+ } else {
246
+ const matches = byNeed.get(exact.intent);
247
+ if (matches) matches.push(item);
248
+ }
249
+ continue;
250
+ }
251
+ if (source.queryRef) {
252
+ unbound.push({ id: item.id, cause: 'replaced-plan' });
253
+ } else if (needs.some((need ) => need.capability && need.capability === source.capability)) {
254
+ unbound.push({ id: item.id, cause: 'no-query-ref' });
255
+ }
256
+ }
257
+ return { byNeed, unbound };
258
+ }
259
+
260
+ /** Selects the highest-precedence recorded attempt, with the earliest attempt winning ties. */
261
+ function deriveOutcome(need ) {
262
+ const precedence = { RETRIEVED: 0, EMPTY: 1, UNVERIFIED: 3, UNREACHED: 4 };
263
+ const attempts = (Array.isArray(need?.attempts) ? need.attempts : []).filter((attempt ) =>
264
+ !(attempt.recordedBy === 'derived' && attempt.basis !== 'report-need'
265
+ && (!Array.isArray(attempt.evidenceIds) || attempt.evidenceIds.length === 0)));
266
+ const ranked = [...attempts].sort((a, b) => {
267
+ const rank = (attempt ) => attempt.status === 'EMPTY' && ['STALE', 'UNKNOWN'].includes(attempt.indexState)
268
+ ? (attempt.indexState === 'STALE' ? 2 : 3)
269
+ : (precedence[attempt.status] ?? 4);
270
+ return rank(a) - rank(b) || a.n - b.n;
271
+ });
272
+ const winner = ranked[0];
273
+ return {
274
+ result: winner ? winner.status : 'UNREACHED',
275
+ resolvedBy: winner ? winner.n : null,
276
+ viaFallback: Boolean(winner && winner.role === 'fallback'),
277
+ };
278
+ }
279
+
280
+ module.exports = {
281
+ planPath,
282
+ readPlan,
283
+ updatePlan,
284
+ moveCorruptPlanAside,
285
+ mintPlanId,
286
+ resolveNeed,
287
+ resolveRetrievalBinding,
288
+ appendRetrievalAttempt,
289
+ activeAttemptRef,
290
+ declaredIntentList,
291
+ bindEvidence,
292
+ deriveOutcome,
293
+ };
@@ -0,0 +1,246 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+ const { spawnSync } = require('node:child_process');
8
+ const { validateFindings, countFindings, reviewAdvisories, invalidProjectCandidate, invalidPresetCandidate, telemetryProtocol } = require('./findings.js');
9
+ const { resolveActiveFeature } = require('../feature-resolve.js');
10
+ const { TelemetryWriter } = require('../trace/telemetry.js');
11
+ const { finishRuntime } = require('../cli-result.js');
12
+ const { handleReviewStart, validSlug } = require('./start.js');
13
+ const { crossCheckAuthor, authoringIdentities } = require('./identity.js');
14
+ const { isQualifiedId } = require('../../helper/package-ids.js');
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+ const TELEMETRY_COUNT_KEYS = Object.freeze([
23
+ 'rounds', 'seats', 'seat_findings', 'converged', 'blocker', 'should_fix', 'note',
24
+ 'open', 'fixed', 'refuted', 'user_accepted', 'pre_existing', 'escaped', 'confirmed',
25
+ 'plausible', 'unverified_blockers', 'bug_classes',
26
+ ]);
27
+
28
+ // Inherited location variables would point the check at another repository (C-8).
29
+ const GIT_LOCATION_KEYS = Object.freeze([
30
+ 'GIT_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_OBJECT_DIRECTORY', 'GIT_COMMON_DIR',
31
+ 'GIT_ALTERNATE_OBJECT_DIRECTORIES',
32
+ ]);
33
+
34
+ function defaultGit(args , cwd , env ) {
35
+ const gitEnv = { ...env };
36
+ for (const key of GIT_LOCATION_KEYS) delete gitEnv[key];
37
+ return spawnSync('git', ['-C', cwd, ...args], { cwd, env: gitEnv, stdio: 'ignore' });
38
+ }
39
+
40
+ /** The project's `review.sameFamily` (119 L-C3), read for the checkout the verb runs in. A settings view that cannot
41
+ * load, or an invalid project or local value for the key (C-6: the resolver would fall back to the default without a
42
+ * word), is `warn` plus `unread`; the caller turns `unread` into an error. Never a crash, never a silent `refuse`. */
43
+ function readSameFamily(load , cwd , env ) {
44
+ try {
45
+ const resolution = load({ projectRoot: path.resolve(cwd), env }).get('review.sameFamily');
46
+ if (invalidProjectCandidate(resolution)) return { value: 'warn', unread: true };
47
+ return { value: resolution.value === 'refuse' ? 'refuse' : 'warn', unread: false };
48
+ } catch {
49
+ return { value: 'warn', unread: true };
50
+ }
51
+ }
52
+
53
+ /** The effective `review.preset` (C-2 closure), read for the checkout the verb runs in. A settings view that cannot
54
+ * load, or an invalid user, project or local value (the resolver would fall back to `auto` without a word), is `unread`;
55
+ * the caller turns `unread` into an error, so a broken setting never relaxes the check. */
56
+ function readPreset(load , cwd , env ) {
57
+ try {
58
+ const resolution = load({ projectRoot: path.resolve(cwd), env }).get('review.preset');
59
+ if (invalidPresetCandidate(resolution)) return { value: 'auto', unread: true };
60
+ return { value: typeof resolution.value === 'string' ? resolution.value : 'auto', unread: false };
61
+ } catch {
62
+ return { value: 'auto', unread: true };
63
+ }
64
+ }
65
+
66
+ // A preset id: a core one as protocols.ts validates it (`^[a-z][a-z0-9-]*$`), or a package preset's qualified id
67
+ // (`<package>.<preset>`, 122 IC-003). Whether it is available and strict enough is the resolver's answer (IC-014).
68
+ const CORE_PRESET_ID = /^[a-z][a-z0-9-]*$/;
69
+ const presetIdShape = (value ) => typeof value === 'string' && (CORE_PRESET_ID.test(value) || isQualifiedId(value));
70
+
71
+ function usage(message , json , out ) {
72
+ out(json
73
+ ? JSON.stringify({ ok: false, error: 'usage', message })
74
+ : `doflow review: ${message}`);
75
+ return finishRuntime(2);
76
+ }
77
+
78
+ function hasValue(value ) {
79
+ return value !== undefined && value !== null;
80
+ }
81
+
82
+ function emptyValue(value ) {
83
+ return typeof value === 'string' && value.trim().length === 0;
84
+ }
85
+
86
+ function handleReviewCommand({
87
+ action,
88
+ slug,
89
+ path: recordPath,
90
+ base,
91
+ preset,
92
+ adviceClient,
93
+ json = false,
94
+ cwd = process.cwd(),
95
+ env = process.env,
96
+ git = defaultGit,
97
+ loadSettings = (options ) => require('../settings/resolve.js').loadSettings(options),
98
+ writer,
99
+ resolve = resolveActiveFeature,
100
+ out = (line ) => console.log(line),
101
+ } = {}) {
102
+ if (action !== 'check' && action !== 'start') return usage('action must be check or start', json, out);
103
+ if (emptyValue(slug) || emptyValue(recordPath) || emptyValue(base) || emptyValue(preset)) return usage('flag values must not be empty', json, out);
104
+ if (action === 'start') {
105
+ // 095b IC-001: the start verb reads no record path, and the slug becomes a file name.
106
+ if (hasValue(recordPath)) return usage('--path is not valid with --action start', json, out);
107
+ if ((hasValue(slug) && !validSlug(slug)) || (hasValue(base) && typeof base !== 'string')) {
108
+ return usage('--slug must be a plain feature name and --base a string', json, out);
109
+ }
110
+ if (hasValue(preset) && !presetIdShape(preset)) {
111
+ return usage('--preset must be a preset id (lower-case letters, digits and hyphens, or <package>.<preset> for a package preset)', json, out);
112
+ }
113
+ return handleReviewStart({ slug: hasValue(slug) ? slug : null, base: hasValue(base) ? base : null, preset: hasValue(preset) ? preset : null, json, cwd, env, resolve, adviceClient, out });
114
+ }
115
+ if (hasValue(base)) return usage('--base is valid only with --action start', json, out);
116
+ if (hasValue(preset)) return usage('--preset is valid only with --action start', json, out);
117
+ if (hasValue(slug) && hasValue(recordPath)) return usage('use either --slug or --path, not both', json, out);
118
+ if ((hasValue(slug) && typeof slug !== 'string') || (hasValue(recordPath) && typeof recordPath !== 'string')) {
119
+ return usage('--slug and --path values must be strings', json, out);
120
+ }
121
+
122
+ const telemetryWriter = writer || new TelemetryWriter({ env, start: cwd });
123
+ let file = null;
124
+ let resolverMessage = null;
125
+ if (hasValue(recordPath)) {
126
+ file = path.resolve(cwd, recordPath);
127
+ } else {
128
+ let feature ;
129
+ try {
130
+ feature = (resolve )({ projectRoot: cwd, slug: hasValue(slug) ? slug : null });
131
+ } catch (error ) {
132
+ feature = { error: 'resolver-failed', message: error?.message || String(error) };
133
+ }
134
+ if (feature?.error || typeof feature?.featureFolder !== 'string' || !feature.featureFolder) {
135
+ resolverMessage = typeof feature?.message === 'string' && feature.message
136
+ ? feature.message
137
+ : (typeof feature?.error === 'string' && feature.error ? feature.error : 'feature could not be resolved');
138
+ } else {
139
+ file = path.resolve(feature.featureFolder, 'review', 'findings.json');
140
+ }
141
+ }
142
+
143
+ const errors = [];
144
+ const errorDetails = new Map ();
145
+ const ancestryAdvisories = [];
146
+ const identityAdvisories = [];
147
+ let sameFamily = 'warn';
148
+ let reviewPreset = 'auto';
149
+ let authors = [];
150
+ let record = null;
151
+ if (file === null) {
152
+ errors.push('missing');
153
+ errorDetails.set('missing', resolverMessage || 'feature record could not be located');
154
+ } else {
155
+ let raw ;
156
+ try {
157
+ raw = fs.readFileSync(file, 'utf8');
158
+ } catch (error ) {
159
+ if (error?.code === 'ENOENT') errors.push('missing');
160
+ else errors.push('unreadable');
161
+ if (error?.message) errorDetails.set(errors[0], error.message);
162
+ raw = '';
163
+ }
164
+
165
+ if (errors.length === 0) {
166
+ // Settings are read only for a record that declares an author: nothing else depends on them (L-C3).
167
+ let peek = null;
168
+ try { peek = JSON.parse(raw); } catch { /* validateFindings reports `json` */ }
169
+ if (peek !== null && typeof peek === 'object' && Object.hasOwn(peek, 'author')) {
170
+ const read = readSameFamily(loadSettings, cwd, env);
171
+ sameFamily = read.value;
172
+ if (read.unread) errors.push('same-family-setting-unread');
173
+ }
174
+ // A record that omits `protocol` is held to the effective `review.preset` (C-2 closure), so it is read for every record.
175
+ if (peek !== null && typeof peek === 'object') {
176
+ const read = readPreset(loadSettings, cwd, env);
177
+ reviewPreset = read.value;
178
+ if (read.unread) errors.push('review-preset-setting-unread');
179
+ }
180
+ authors = authoringIdentities({ slug: peek?.slug, cwd, env, record: peek, fsImpl: fs });
181
+ const validation = validateFindings(raw, { sameFamily, preset: reviewPreset, authors });
182
+ for (const error of validation.errors) errors.push(error); // a spread overflows the stack at ~200k errors (C-6)
183
+ if (!validation.errors.includes('json')) {
184
+ try { record = JSON.parse(raw); } catch { record = null; }
185
+ }
186
+ if (validation.ok && record) {
187
+ if (Object.hasOwn(record, 'author')) identityAdvisories.push(...crossCheckAuthor({ record, cwd, env }));
188
+ for (const finding of record.converged) {
189
+ if (typeof finding.preExistingAt !== 'string') continue;
190
+ let status ;
191
+ try {
192
+ status = (git )(
193
+ ['merge-base', '--is-ancestor', finding.preExistingAt, record.reviewedHead],
194
+ path.dirname(file),
195
+ env,
196
+ )?.status;
197
+ } catch {
198
+ status = null;
199
+ }
200
+ if (status === 1) errors.push(`preExistingAt-not-ancestor:${finding.id}`);
201
+ else if (status !== 0) ancestryAdvisories.push(`ancestry-unchecked:${finding.id}`);
202
+ }
203
+ }
204
+ }
205
+ }
206
+
207
+ const advisories = (record && validateFindings(record, { sameFamily, preset: reviewPreset, authors }).ok
208
+ ? reviewAdvisories(record, { sameFamily, authors }).concat(identityAdvisories)
209
+ : []).concat(ancestryAdvisories).sort();
210
+ const valid = errors.length === 0;
211
+ const counts = valid && record ? countFindings(record) : null;
212
+ const result = { schema: 1, path: file, valid, errors, advisories, counts };
213
+
214
+ if (json) {
215
+ out(JSON.stringify(result));
216
+ } else {
217
+ out(`review check: ${valid ? 'valid' : 'invalid'} (${errors.length} errors, ${advisories.length} advisories)`);
218
+ for (const error of errors) {
219
+ const detail = errorDetails.get(error);
220
+ out(`error: ${error}${detail ? `: ${detail}` : ''}`);
221
+ }
222
+ for (const advisory of advisories) out(`advisory: ${advisory}`);
223
+ if (counts) out(Object.entries(counts).map(([key, value]) => `${key}=${value}`).join(' '));
224
+ }
225
+
226
+ // Only a validated slug is a feature slug; an invalid record's slug can hold anything (C-1, IC-004).
227
+ const task = valid && record && typeof record.slug === 'string' ? record.slug : null;
228
+ const telemetryRecord = {
229
+ kind: 'review.run',
230
+ task,
231
+ depth: null,
232
+ risk: null,
233
+ protocol: null,
234
+ head: null,
235
+ ...Object.fromEntries(TELEMETRY_COUNT_KEYS.map((key) => [key, null])),
236
+ advisories: advisories.length,
237
+ errors: errors.length,
238
+ };
239
+ if (counts && record) Object.assign(telemetryRecord, counts, { depth: record.depth ?? null, risk: record.risk ?? null, protocol: telemetryProtocol(record.protocol?.id), head: record.reviewedHead });
240
+ try { telemetryWriter.write([telemetryRecord]); } catch { /* telemetry never changes the check result */ }
241
+
242
+ return finishRuntime(valid ? 0 : 1);
243
+ }
244
+
245
+ module.exports = { handleReviewCommand };
246
+
@@ -0,0 +1,68 @@
1
+ 'use strict';
2
+
3
+ // 122 IC-014: `dominates(P, B, context)`, the strictness comparison between two protocol presets. Pure:
4
+ // no filesystem, no throw. P dominates B when every field that makes a review or debate stricter is at
5
+ // least as strict in P. `context` carries the ordered core tiers (models.json `dispatchTiers.tiers`,
6
+ // loosest first) and the synthesizer lattice (protocols.json `synthesizerStrength`). An unknown tier or
7
+ // synthesizer has no rank and never dominates: no `indexOf` of -1 is read as a rank.
8
+
9
+ const FAMILY_STRICTNESS = { any: 0, 'different-from-author': 1 };
10
+
11
+ const isObject = (value ) => value !== null && typeof value === 'object' && !Array.isArray(value);
12
+
13
+ /** A rank from `ranks`, or null when `key` has none. */
14
+ function rankOf(ranks , key ) {
15
+ return typeof key === 'string' && Object.hasOwn(ranks, key) && Number.isInteger(ranks[key]) ? ranks[key] : null;
16
+ }
17
+
18
+ /** True when seat `p` covers seat `b`: a lens superset, a family at least as strict, a tier at least as high. */
19
+ function seatCovers(p , b , tierRank ) {
20
+ if (!isObject(p) || !isObject(b) || !isObject(p.model) || !isObject(b.model)) return false;
21
+ const pLenses = new Set(Array.isArray(p.lenses) ? p.lenses : []);
22
+ if (!(Array.isArray(b.lenses) ? b.lenses : []).every((lens ) => pLenses.has(lens))) return false;
23
+ const pFamily = rankOf(FAMILY_STRICTNESS, p.model.family ?? 'any');
24
+ const bFamily = rankOf(FAMILY_STRICTNESS, b.model.family ?? 'any');
25
+ if (pFamily === null || bFamily === null || pFamily < bFamily) return false;
26
+ const pTier = rankOf(tierRank, p.model.tier);
27
+ const bTier = rankOf(tierRank, b.model.tier);
28
+ return pTier !== null && bTier !== null && pTier >= bTier;
29
+ }
30
+
31
+ /**
32
+ * True when every B seat maps to a distinct P seat that covers it: a maximum bipartite matching by
33
+ * augmenting paths (Kuhn), so a first-fit choice never hides a matching that exists. Seats are few.
34
+ */
35
+ function seatsMatch(pSeats , bSeats , tierRank ) {
36
+ if (bSeats.length > pSeats.length) return false;
37
+ const covers = bSeats.map((b) => pSeats.map((p) => seatCovers(p, b, tierRank)));
38
+ const owner = new Array(pSeats.length).fill(-1); // P seat index -> B seat index
39
+ const augment = (bi , seen ) => {
40
+ for (let pi = 0; pi < pSeats.length; pi++) {
41
+ if (!covers[bi][pi] || seen[pi]) continue;
42
+ seen[pi] = true;
43
+ if (owner[pi] === -1 || augment(owner[pi], seen)) { owner[pi] = bi; return true; }
44
+ }
45
+ return false;
46
+ };
47
+ return bSeats.every((_, bi) => augment(bi, new Array(pSeats.length).fill(false)));
48
+ }
49
+
50
+ function dominates(P , B , context = {}) {
51
+ if (!isObject(P) || !isObject(B) || !isObject(context)) return false;
52
+ const { tiers, synthesizerStrength } = context;
53
+ if (!Array.isArray(tiers) || !isObject(synthesizerStrength)) return false;
54
+ const tierRank = Object.fromEntries(tiers.map((tier , i ) => [tier, i]));
55
+
56
+ if (P.kind !== B.kind || P.decider !== B.decider) return false;
57
+ if (!Array.isArray(P.seats) || !Array.isArray(B.seats) || !seatsMatch(P.seats, B.seats, tierRank)) return false;
58
+ if (!isObject(P.rounds) || !isObject(B.rounds)) return false;
59
+ for (const field of ['default', 'min']) {
60
+ if (!Number.isInteger(P.rounds[field]) || !Number.isInteger(B.rounds[field]) || P.rounds[field] < B.rounds[field]) return false;
61
+ }
62
+ if (B.crossAnswer === true && P.crossAnswer !== true) return false;
63
+ const pSynth = rankOf(synthesizerStrength, P.synthesizer);
64
+ const bSynth = rankOf(synthesizerStrength, B.synthesizer);
65
+ return pSynth !== null && bSynth !== null && pSynth >= bSynth;
66
+ }
67
+
68
+ module.exports = { dominates };