@khoavu882/doflow 2.0.0-beta.5 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (494) hide show
  1. package/README.md +16 -16
  2. package/bin/doflow.js +22 -12
  3. package/core/.antigravity-plugin/plugin.json +1 -2
  4. package/core/.claude-plugin/plugin.json +1 -1
  5. package/core/.codex-plugin/plugin.json +1 -1
  6. package/core/.plugin/plugin.json +1 -1
  7. package/core/harnesses/claude/hooks/lib.sh +43 -24
  8. package/core/harnesses/claude/hooks/mcp-tool-guard.sh +4 -1
  9. package/core/harnesses/claude/hooks/permission-audit.sh +3 -0
  10. package/core/harnesses/claude/hooks/post-compact.sh +35 -32
  11. package/core/harnesses/claude/hooks/post-edit-lint.sh +2 -0
  12. package/core/harnesses/claude/hooks/post-shell-observe.sh +48 -0
  13. package/core/harnesses/claude/hooks/post-tool-failure.sh +3 -0
  14. package/core/harnesses/claude/hooks/pre-compact.sh +23 -27
  15. package/core/harnesses/claude/hooks/pre-implement-gate.sh +4 -1
  16. package/core/harnesses/claude/hooks/session-end.sh +23 -10
  17. package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
  18. package/core/harnesses/claude/settings/settings.json +26 -9
  19. package/core/harnesses/claude/statusline/doflow-statusline.sh +1 -0
  20. package/core/harnesses/codex/agents/core-implementer.toml +1 -0
  21. package/core/harnesses/codex/agents/delivery-lead.toml +10 -0
  22. package/core/harnesses/codex/agents/quality-guardian.toml +1 -1
  23. package/core/harnesses/codex/agents/research-writer.toml +1 -1
  24. package/core/harnesses/codex/agents/reviewer.toml +10 -0
  25. package/core/harnesses/codex/agents/spec-analyst.toml +1 -1
  26. package/core/harnesses/codex/agents/system-architect.toml +1 -1
  27. package/core/harnesses/codex/hooks/hooks.json +0 -10
  28. package/core/harnesses/codex/hooks/lib.sh +44 -25
  29. package/core/harnesses/codex/hooks/mcp-tool-guard.sh +4 -1
  30. package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
  31. package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
  32. package/core/harnesses/codex/hooks/pre-implement-gate.sh +4 -1
  33. package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
  34. package/core/harnesses/codex/hooks/subagent-audit.sh +1 -0
  35. package/core/harnesses/copilot/hooks/doflow-lib.sh +94 -0
  36. package/core/harnesses/copilot/hooks/doflow-mcp-tool-guard.sh +9 -0
  37. package/core/harnesses/copilot/hooks/doflow-pre-implement-gate.sh +9 -0
  38. package/core/harnesses/copilot/hooks/doflow-session-start.sh +9 -0
  39. package/core/harnesses/copilot/hooks/doflow-stop-check.sh +9 -0
  40. package/core/harnesses/copilot/hooks/doflow-subagent-start.sh +9 -0
  41. package/core/harnesses/copilot/hooks/doflow-subagent-stop.sh +9 -0
  42. package/core/harnesses/copilot/hooks/doflow.json +51 -0
  43. package/core/harnesses/kiro/agents/doflow.json +1 -11
  44. package/core/harnesses/kiro/hooks/doflow.json +0 -10
  45. package/core/harnesses/kiro/hooks/lib.sh +26 -6
  46. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +8 -3
  47. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +8 -3
  48. package/core/harnesses/kiro/hooks/session-start.sh +1 -0
  49. package/core/harnesses/kiro/hooks/stop-check.sh +1 -0
  50. package/core/harnesses/pi/extensions/doflow-guard.js +11 -2
  51. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +1 -2
  52. package/core/harnesses/shared/hooks/adapters/antigravity.js +1 -1
  53. package/core/harnesses/shared/hooks/bridge/bridge.sh +126 -23
  54. package/core/harnesses/shared/hooks/bridge/envelope.jq +87 -18
  55. package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
  56. package/core/harnesses/shared/hooks/bridge/respond.sh +16 -2
  57. package/core/harnesses/shared/hooks/policies/capture-failure.sh +49 -16
  58. package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
  59. package/core/harnesses/shared/hooks/policies/contract.sh +3 -0
  60. package/core/harnesses/shared/hooks/policies/deny-json.sh +20 -7
  61. package/core/harnesses/shared/hooks/policies/emission-rules.sh +37 -0
  62. package/core/harnesses/shared/hooks/policies/emission.jq +209 -0
  63. package/core/harnesses/shared/hooks/policies/emission.sh +383 -0
  64. package/core/harnesses/shared/hooks/policies/identity.sh +177 -0
  65. package/core/harnesses/shared/hooks/policies/lib.sh +63 -40
  66. package/core/harnesses/shared/hooks/policies/mcp-policy.conf +1 -1
  67. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +7 -8
  68. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +132 -57
  69. package/core/harnesses/shared/hooks/policies/session-context.sh +30 -1
  70. package/core/harnesses/shared/hooks/policies/stop-check.sh +27 -15
  71. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +6 -3
  72. package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
  73. package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +60 -155
  74. package/core/harnesses/shared/hooks/stream-hook-runner.js +58 -20
  75. package/core/harnesses/shared/locator/doflow-run +2 -3
  76. package/core/registry/advice.json +3 -3
  77. package/core/registry/assets.json +55 -82
  78. package/core/registry/capabilities.json +12 -0
  79. package/core/registry/contracts.json +58 -31
  80. package/core/registry/contribution-points.json +35 -0
  81. package/core/registry/gates.json +32 -0
  82. package/core/registry/guidance-caps.json +40 -0
  83. package/core/registry/harnesses.json +1445 -366
  84. package/core/registry/intake.json +53 -0
  85. package/core/registry/lifecycle.json +599 -996
  86. package/core/registry/mcp.json +5 -1
  87. package/core/registry/models.json +7 -1
  88. package/core/registry/package-templates/README.md +19 -0
  89. package/core/registry/package-templates/agent/files/agent.md +9 -0
  90. package/core/registry/package-templates/agent/template.json +4 -0
  91. package/core/registry/package-templates/capability-provider/files/provider.json +9 -0
  92. package/core/registry/package-templates/capability-provider/template.json +11 -0
  93. package/core/registry/package-templates/debate-preset/files/preset.json +26 -0
  94. package/core/registry/package-templates/debate-preset/template.json +4 -0
  95. package/core/registry/package-templates/intake-category/files/category.json +8 -0
  96. package/core/registry/package-templates/intake-category/template.json +4 -0
  97. package/core/registry/package-templates/review-preset/files/preset.json +30 -0
  98. package/core/registry/package-templates/review-preset/template.json +4 -0
  99. package/core/registry/package-templates/skill/files/SKILL.md +8 -0
  100. package/core/registry/package-templates/skill/template.json +4 -0
  101. package/core/registry/package-templates/worker/files/worker.json +92 -0
  102. package/core/registry/package-templates/worker/template.json +11 -0
  103. package/core/registry/packages/doflow/doflow-package.json +15 -0
  104. package/core/registry/packages/doflow/presets/debate-3.json +26 -0
  105. package/core/registry/protocols.json +61 -0
  106. package/core/registry/routes.json +17 -0
  107. package/core/registry/rules.json +431 -0
  108. package/core/registry/settings.json +272 -0
  109. package/core/registry/storage.json +687 -31
  110. package/core/registry/telemetry.json +236 -0
  111. package/core/registry/transcripts.json +142 -0
  112. package/core/registry/verification.json +76 -6
  113. package/core/registry/workflows.json +55 -0
  114. package/core/shared/agent-specs/core-implementer.md +3 -0
  115. package/core/shared/agent-specs/delivery-lead.md +68 -0
  116. package/core/shared/agent-specs/quality-guardian.md +5 -1
  117. package/core/shared/agent-specs/research-writer.md +10 -3
  118. package/core/shared/agent-specs/reviewer.md +53 -0
  119. package/core/shared/agent-specs/spec-analyst.md +5 -6
  120. package/core/shared/agent-specs/system-architect.md +5 -1
  121. package/core/shared/guidance/DOFLOW_CORE.md +1 -5
  122. package/core/shared/guidance/modes/MODE_DeepResearch.md +1 -1
  123. package/core/shared/guidance/modes/MODE_Orchestration.md +4 -3
  124. package/core/shared/guidance/modes/MODE_Task_Management.md +14 -14
  125. package/core/shared/guidance/pointers/codex.md +1 -4
  126. package/core/shared/guidance/references/ARTIFACT_FORMAT.md +30 -16
  127. package/core/shared/guidance/references/ARTIFACT_VOICE.md +3 -2
  128. package/core/shared/guidance/references/CONSTITUTION_BASE.md +17 -15
  129. package/core/shared/guidance/references/DIAGRAM_GUIDE.md +1 -1
  130. package/core/shared/guidance/references/DOFLOW_CHAIN.md +17 -19
  131. package/core/shared/guidance/references/EVIDENCE_LEDGER.md +7 -8
  132. package/core/shared/guidance/references/MODEL_SELECTION.md +5 -3
  133. package/core/shared/guidance/references/ORCHESTRATION.md +3 -2
  134. package/core/shared/guidance/references/WORKFLOW_HANDOFF.md +19 -1
  135. package/core/shared/guidance/rules/RULES.md +45 -0
  136. package/core/shared/scripts/doflow/bash/do-args.sh +37 -0
  137. package/core/shared/scripts/doflow/bash/do-exec-paths.sh +4 -0
  138. package/core/shared/scripts/doflow/bash/do-git-state.sh +15 -4
  139. package/core/shared/scripts/doflow/bash/do-parallel-check.sh +4 -0
  140. package/core/shared/scripts/doflow/bash/do-paths.sh +40 -2
  141. package/core/shared/scripts/doflow/bash/do-prereqs.sh +2 -1
  142. package/core/shared/scripts/doflow/bash/do-review-package.sh +4 -0
  143. package/core/shared/scripts/doflow/bash/do-task-brief.sh +58 -16
  144. package/core/shared/scripts/doflow/bash/render-audit.sh +26 -2
  145. package/core/shared/scripts/doflow/bash/render-diagrams.sh +1 -0
  146. package/core/shared/scripts/doflow/bash/render-puml.sh +2 -1
  147. package/core/shared/scripts/doflow/bash/validate-artifacts.sh +49 -5
  148. package/core/shared/scripts/doflow/bin/doflow-run +152 -34
  149. package/core/shared/skills/do/SKILL.md +14 -12
  150. package/core/shared/skills/do/references/advice_calls.md +26 -0
  151. package/core/shared/skills/do/references/pm_routing.md +1 -2
  152. package/core/shared/skills/do/references/program_mode.md +31 -0
  153. package/core/shared/skills/do-brainstorm/SKILL.md +45 -97
  154. package/core/shared/skills/do-brainstorm/references/handoff_branches.md +36 -0
  155. package/core/shared/skills/do-brainstorm/references/intent_seed.md +21 -0
  156. package/core/shared/skills/do-brainstorm/references/round_log.md +17 -0
  157. package/core/shared/skills/do-code-review/SKILL.md +48 -148
  158. package/core/shared/skills/do-code-review/content-types/markdown.md +24 -2
  159. package/core/shared/skills/do-code-review/references/tools.md +90 -0
  160. package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +549 -7
  161. package/core/shared/skills/do-design/SKILL.md +23 -20
  162. package/core/shared/skills/do-diagnose/SKILL.md +18 -6
  163. package/core/shared/skills/do-document/SKILL.md +12 -1
  164. package/core/shared/skills/do-document/references/deep_research.md +1 -2
  165. package/core/shared/skills/do-document/references/embedded-research.md +19 -7
  166. package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
  167. package/core/shared/skills/do-execute-plan/SKILL.md +19 -60
  168. package/core/shared/skills/do-execute-plan/references/handoff_record.md +7 -2
  169. package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +13 -0
  170. package/core/shared/skills/do-execute-plan/references/readiness_gate.md +26 -1
  171. package/core/shared/skills/do-execute-plan/references/run_position.md +16 -2
  172. package/core/shared/skills/do-execute-plan/references/scaffold.md +17 -9
  173. package/core/shared/skills/do-execute-plan/references/state_checkpoint.md +11 -0
  174. package/core/shared/skills/do-execute-plan/references/task_attempts.md +78 -24
  175. package/core/shared/skills/do-flow/SKILL.md +10 -11
  176. package/core/shared/skills/do-flow/references/task_classes.md +5 -5
  177. package/core/shared/skills/do-git/SKILL.md +4 -0
  178. package/core/shared/skills/do-implement/SKILL.md +13 -14
  179. package/core/shared/skills/do-plan/SKILL.md +34 -20
  180. package/core/shared/skills/do-test/SKILL.md +7 -4
  181. package/core/shared/templates/doflow/constitution-template.md +9 -3
  182. package/core/shared/templates/doflow/data-model-template.md +1 -1
  183. package/core/shared/templates/doflow/design-template.md +113 -16
  184. package/core/shared/templates/doflow/plan-template.md +11 -10
  185. package/core/shared/templates/doflow/question-log-template.md +14 -2
  186. package/core/shared/templates/doflow/requirement-template.md +1 -1
  187. package/core/shared/workflows/do-flow-chain.md +2 -0
  188. package/core/worker-evidence/antigravity/checks.json +14 -0
  189. package/core/worker-evidence/codex/2026-10-18-cli-check.json +107 -0
  190. package/core/worker-evidence/codex/2026-10-18-live-run-edit.json +73 -0
  191. package/core/worker-evidence/codex/2026-10-18-live-run-read-only.json +62 -0
  192. package/core/worker-evidence/codex/2026-10-24-cli-check.json +126 -0
  193. package/core/worker-evidence/codex/checks.json +38 -0
  194. package/package.json +10 -6
  195. package/src/adapters/antigravity/index.js +143 -66
  196. package/src/adapters/claude/index.js +46 -5
  197. package/src/adapters/codex/agents.js +25 -15
  198. package/src/adapters/codex/hooks.js +39 -3
  199. package/src/adapters/codex/index.js +61 -13
  200. package/src/adapters/codex/native-projection.js +6 -2
  201. package/src/adapters/copilot/index.js +58 -23
  202. package/src/adapters/copy-tree.js +291 -46
  203. package/src/adapters/guidance-manifest.js +71 -0
  204. package/src/adapters/guidance-pointer.js +19 -0
  205. package/src/adapters/hook-commands.js +6 -10
  206. package/src/adapters/index.js +1 -0
  207. package/src/adapters/kiro/index.js +23 -12
  208. package/src/adapters/opencode/index.js +8 -8
  209. package/src/adapters/pi/hooks.js +2 -1
  210. package/src/adapters/pi/index.js +14 -7
  211. package/src/adapters/resolve-source.js +26 -2
  212. package/src/cli/commands/install.js +2 -1
  213. package/src/cli/commands/reconcile.js +53 -9
  214. package/src/cli/commands/remove.js +2 -2
  215. package/src/cli/commands/rollback.js +6 -3
  216. package/src/cli/commands/self-update.js +18 -9
  217. package/src/cli/commands/status.js +22 -3
  218. package/src/cli/commands/ui.js +99 -0
  219. package/src/cli/commands/update.js +2 -1
  220. package/src/cli/index.js +337 -32
  221. package/src/cli/runtime-commands.js +220 -60
  222. package/src/cli/shared.js +0 -2
  223. package/src/helper/git.js +1 -1
  224. package/src/helper/guardrails.js +1 -1
  225. package/src/helper/marker-merge.js +31 -13
  226. package/src/helper/package-ids.js +37 -0
  227. package/src/helper/semver.js +94 -0
  228. package/src/install/backup.js +105 -15
  229. package/src/install/context.js +1 -1
  230. package/src/install/targets.js +31 -16
  231. package/src/lifecycle/coverage.js +4 -16
  232. package/src/lifecycle/hook-tier.js +160 -0
  233. package/src/lifecycle/index.js +412 -78
  234. package/src/lifecycle/retired-harnesses.js +318 -0
  235. package/src/lifecycle/view.js +35 -15
  236. package/src/registry/index.js +551 -26
  237. package/src/registry/worker-ids.js +45 -0
  238. package/src/release/build-package.js +0 -3
  239. package/src/release/docs-blocks.js +472 -0
  240. package/src/release/preflight.js +425 -0
  241. package/src/release/rehearse.js +401 -0
  242. package/src/release/release-workflow.js +733 -0
  243. package/src/release/sync-plugin-versions.js +43 -7
  244. package/src/runtime/advice/bench-gate.js +25 -0
  245. package/src/runtime/advice/cli.js +59 -16
  246. package/src/runtime/advice/client.js +32 -9
  247. package/src/runtime/advice/config.js +98 -133
  248. package/src/runtime/advice/doctor.js +1 -1
  249. package/src/runtime/advice/elevation.js +97 -0
  250. package/src/runtime/advice/endpoint-guard.js +9 -3
  251. package/src/runtime/advice/mode.js +37 -0
  252. package/src/runtime/advice/record.js +18 -5
  253. package/src/runtime/advice/registry.js +4 -1
  254. package/src/runtime/advice/shadow-config.js +229 -0
  255. package/src/runtime/advice/shadow-dispatcher.js +276 -0
  256. package/src/runtime/brief-rules.js +81 -0
  257. package/src/runtime/brief-tools.js +111 -34
  258. package/src/runtime/candidate.js +20 -11
  259. package/src/runtime/capabilities/executor.js +450 -0
  260. package/src/runtime/capabilities/loopback.js +41 -0
  261. package/src/runtime/capabilities/sanitizer.js +88 -0
  262. package/src/runtime/capability-router.js +135 -3
  263. package/src/runtime/checkouts.js +73 -11
  264. package/src/runtime/claims.js +13 -7
  265. package/src/runtime/cli.js +196 -19
  266. package/src/runtime/config-home.js +42 -0
  267. package/src/runtime/context/arbitrate.js +767 -0
  268. package/src/runtime/context/delivery-store.js +4 -6
  269. package/src/runtime/context/emission-rules.js +118 -0
  270. package/src/runtime/context/identity.js +227 -0
  271. package/src/runtime/context/pointer.js +67 -12
  272. package/src/runtime/context/primary-command.js +117 -0
  273. package/src/runtime/context/primary.js +344 -0
  274. package/src/runtime/context/summary-store.js +655 -0
  275. package/src/runtime/context-pack.js +158 -79
  276. package/src/runtime/decision-register.js +13 -4
  277. package/src/runtime/dispatch/admission.js +392 -0
  278. package/src/runtime/dispatch/attempt-end.js +274 -0
  279. package/src/runtime/dispatch/command.js +628 -0
  280. package/src/runtime/dispatch/controls.js +904 -0
  281. package/src/runtime/dispatch/envelope.js +276 -0
  282. package/src/runtime/dispatch/grants.js +310 -0
  283. package/src/runtime/dispatch/integrate.js +617 -0
  284. package/src/runtime/dispatch/loop.js +1835 -0
  285. package/src/runtime/dispatch/store.js +352 -0
  286. package/src/runtime/dispatch/verify-run.js +201 -0
  287. package/src/runtime/evidence-ledger.js +52 -7
  288. package/src/runtime/failure/capture.js +3 -1
  289. package/src/runtime/failure/cli.js +25 -9
  290. package/src/runtime/failure/home.js +57 -30
  291. package/src/runtime/failure/store.js +7 -2
  292. package/src/runtime/feature-pointer.js +174 -0
  293. package/src/runtime/feature-resolve.js +13 -2
  294. package/src/runtime/fleet/command.js +77 -0
  295. package/src/runtime/fleet/projects.js +178 -0
  296. package/src/runtime/fleet/tree.js +383 -0
  297. package/src/runtime/freshness.js +33 -3
  298. package/src/runtime/git-memo.js +46 -0
  299. package/src/runtime/guidance/ablation.js +427 -0
  300. package/src/runtime/health.js +624 -18
  301. package/src/runtime/history-compactor.js +13 -3
  302. package/src/runtime/implementation-gate.js +281 -26
  303. package/src/runtime/intake/engine.js +677 -0
  304. package/src/runtime/intake/index.js +136 -0
  305. package/src/runtime/intake/manifest.js +140 -0
  306. package/src/runtime/intake/taxonomy.js +206 -0
  307. package/src/runtime/inventory/coverage.js +1 -1
  308. package/src/runtime/inventory/index.js +1 -1
  309. package/src/runtime/inventory/precedence.js +2 -2
  310. package/src/runtime/inventory/siblings.js +6 -4
  311. package/src/runtime/knowledge/code-corpus.js +277 -0
  312. package/src/runtime/knowledge/code-lexical.js +130 -0
  313. package/src/runtime/knowledge/code-retrieval.js +307 -0
  314. package/src/runtime/knowledge/code-validation.js +106 -0
  315. package/src/runtime/knowledge/gp3-gate.js +74 -0
  316. package/src/runtime/knowledge/graph-adapter.js +288 -0
  317. package/src/runtime/knowledge/graph-fallback.js +394 -0
  318. package/src/runtime/knowledge/hybrid.js +136 -8
  319. package/src/runtime/knowledge/retrieval.js +34 -1
  320. package/src/runtime/knowledge/rtk-compress.js +77 -0
  321. package/src/runtime/knowledge/semble-bridge.py +106 -0
  322. package/src/runtime/knowledge/semble-lifecycle.js +114 -0
  323. package/src/runtime/knowledge/semble-provider.js +365 -0
  324. package/src/runtime/lifecycle/cli.js +88 -10
  325. package/src/runtime/lifecycle/event-store.js +8 -6
  326. package/src/runtime/lifecycle/followup.js +38 -7
  327. package/src/runtime/lifecycle/overview.js +26 -2
  328. package/src/runtime/lifecycle/report-store.js +8 -10
  329. package/src/runtime/lifecycle/retention.js +95 -31
  330. package/src/runtime/lifecycle/root.js +9 -1
  331. package/src/runtime/lifecycle/status.js +34 -17
  332. package/src/runtime/lifecycle/store-upkeep.js +56 -7
  333. package/src/runtime/mask.js +134 -7
  334. package/src/runtime/mcp-consultations.js +40 -6
  335. package/src/runtime/model-router.js +79 -7
  336. package/src/runtime/models/catalogue.js +191 -0
  337. package/src/runtime/models/command.js +153 -0
  338. package/src/runtime/off-words.js +12 -0
  339. package/src/runtime/outcome.js +2 -2
  340. package/src/runtime/packages/command.js +1009 -0
  341. package/src/runtime/packages/contributions.js +116 -0
  342. package/src/runtime/packages/digest.js +132 -0
  343. package/src/runtime/packages/fetch.js +182 -0
  344. package/src/runtime/packages/grants.js +192 -0
  345. package/src/runtime/packages/index-file.js +134 -0
  346. package/src/runtime/packages/instruction.js +90 -0
  347. package/src/runtime/packages/manifest.js +331 -0
  348. package/src/runtime/packages/points.js +487 -0
  349. package/src/runtime/packages/programs.js +216 -0
  350. package/src/runtime/packages/scaffold.js +277 -0
  351. package/src/runtime/packages/source.js +90 -0
  352. package/src/runtime/packages/state.js +164 -0
  353. package/src/runtime/packages/store.js +449 -0
  354. package/src/runtime/packages/tar.js +106 -0
  355. package/src/runtime/packages/view.js +406 -0
  356. package/src/runtime/plan-check.js +59 -0
  357. package/src/runtime/plan-content.js +324 -0
  358. package/src/runtime/policy-init.js +118 -0
  359. package/src/runtime/profiles/doctor.js +96 -0
  360. package/src/runtime/profiles/resolve.js +244 -0
  361. package/src/runtime/profiles/rollback.js +254 -0
  362. package/src/runtime/profiles/sampler.js +41 -0
  363. package/src/runtime/readiness-record.js +16 -11
  364. package/src/runtime/readiness.js +46 -5
  365. package/src/runtime/research-request.js +50 -25
  366. package/src/runtime/retrieval-plan.js +10 -30
  367. package/src/runtime/retrieval-record.js +75 -5
  368. package/src/runtime/review/command.js +246 -0
  369. package/src/runtime/review/dominance.js +68 -0
  370. package/src/runtime/review/findings.js +434 -0
  371. package/src/runtime/review/identity.js +255 -0
  372. package/src/runtime/review/protocol-command.js +109 -0
  373. package/src/runtime/review/protocols.js +405 -0
  374. package/src/runtime/review/resolve-protocol.js +302 -0
  375. package/src/runtime/review/start.js +530 -0
  376. package/src/runtime/roadmap/attendance.js +137 -0
  377. package/src/runtime/roadmap/facts.js +479 -0
  378. package/src/runtime/roadmap/gates.js +273 -0
  379. package/src/runtime/roadmap/loop.js +1719 -0
  380. package/src/runtime/roadmap/merge.js +390 -0
  381. package/src/runtime/roadmap/rows.js +1122 -0
  382. package/src/runtime/roadmap/seed.js +1063 -0
  383. package/src/runtime/roadmap/worktree-cleanup.js +147 -0
  384. package/src/runtime/rules-report.js +336 -0
  385. package/src/runtime/scaffold/artifacts.js +60 -9
  386. package/src/runtime/scaffold/generate.js +21 -9
  387. package/src/runtime/settings/autopilot.js +106 -0
  388. package/src/runtime/settings/cli.js +280 -0
  389. package/src/runtime/settings/config-write.js +314 -0
  390. package/src/runtime/settings/files.js +271 -0
  391. package/src/runtime/settings/legacy.js +154 -0
  392. package/src/runtime/settings/lexical.js +56 -0
  393. package/src/runtime/settings/prepared-run.js +269 -0
  394. package/src/runtime/settings/project-layers.js +118 -0
  395. package/src/runtime/settings/refs.js +288 -0
  396. package/src/runtime/settings/registry.js +553 -0
  397. package/src/runtime/settings/resolve.js +713 -0
  398. package/src/runtime/settings/shared.js +37 -0
  399. package/src/runtime/settings/worker-billing.js +107 -0
  400. package/src/runtime/settings/write.js +269 -0
  401. package/src/runtime/slot-command.js +78 -0
  402. package/src/runtime/slots.js +671 -0
  403. package/src/runtime/source-version.js +1 -0
  404. package/src/runtime/state-root.js +217 -0
  405. package/src/runtime/storage/cli.js +12 -5
  406. package/src/runtime/storage/engine.js +9 -2
  407. package/src/runtime/storage/registry.js +14 -1
  408. package/src/runtime/storage/roots.js +66 -16
  409. package/src/runtime/storage/rules.js +82 -10
  410. package/src/runtime/storage/upkeep.js +10 -2
  411. package/src/runtime/storage/work-status.js +245 -26
  412. package/src/runtime/task-attempts.js +589 -69
  413. package/src/runtime/task-classifier.js +85 -14
  414. package/src/runtime/task-graph.js +373 -0
  415. package/src/runtime/task-run.js +778 -0
  416. package/src/runtime/task-scope.js +176 -55
  417. package/src/runtime/task-sources.js +147 -0
  418. package/src/runtime/task-state.js +373 -64
  419. package/src/runtime/team/admission.js +301 -0
  420. package/src/runtime/team/envelope.js +324 -0
  421. package/src/runtime/team/review-guard.js +156 -0
  422. package/src/runtime/trace/analyze.js +427 -0
  423. package/src/runtime/trace/baseline.js +770 -0
  424. package/src/runtime/trace/ledger.js +63 -140
  425. package/src/runtime/trace/otlp-export.js +568 -0
  426. package/src/runtime/trace/otlp-map.js +481 -0
  427. package/src/runtime/trace/spans.js +745 -0
  428. package/src/runtime/trace/telemetry.js +225 -0
  429. package/src/runtime/trace/views.js +1 -0
  430. package/src/runtime/transcripts/command.js +98 -0
  431. package/src/runtime/transcripts/cursor.js +326 -0
  432. package/src/runtime/transcripts/read.js +126 -0
  433. package/src/runtime/transcripts/readers/claude-jsonl.js +74 -0
  434. package/src/runtime/transcripts/readers/codex-rollout.js +70 -0
  435. package/src/runtime/transcripts/readers/copilot-events.js +90 -0
  436. package/src/runtime/transcripts/readers/kiro-jsonl.js +77 -0
  437. package/src/runtime/transcripts/readers/pi-jsonl.js +59 -0
  438. package/src/runtime/transcripts/registry.js +161 -0
  439. package/src/runtime/transcripts/stats.js +161 -0
  440. package/src/runtime/verb-view.js +267 -0
  441. package/src/runtime/verification/check-key.js +628 -0
  442. package/src/runtime/verification/contract-runner.js +546 -11
  443. package/src/runtime/verification/engine.js +499 -54
  444. package/src/runtime/verification/impact.js +617 -0
  445. package/src/runtime/verification/inflight.js +205 -0
  446. package/src/runtime/verification/registry.js +176 -0
  447. package/src/runtime/verification/reuse.js +184 -0
  448. package/src/runtime/verification/telemetry-emit.js +254 -0
  449. package/src/runtime/verification/test-selection.js +67 -7
  450. package/src/runtime/worker/active.js +90 -0
  451. package/src/runtime/worker/cancel.js +222 -0
  452. package/src/runtime/worker/catalogue.js +122 -0
  453. package/src/runtime/worker/command.js +677 -0
  454. package/src/runtime/worker/evidence.js +288 -0
  455. package/src/runtime/worker/launch.js +256 -0
  456. package/src/runtime/worker/models.js +137 -0
  457. package/src/runtime/worker/parsers.js +218 -0
  458. package/src/runtime/worker/probes/index.js +706 -0
  459. package/src/runtime/worker/process-tree.js +415 -0
  460. package/src/runtime/worker/quota.js +43 -0
  461. package/src/runtime/worker/readiness.js +713 -0
  462. package/src/runtime/worker/run-store.js +166 -0
  463. package/src/runtime/worker/supervisor.js +541 -0
  464. package/src/runtime/worker/worktree-check.js +252 -0
  465. package/src/runtime/workflow-engine.js +5 -1
  466. package/src/runtime/workflow-orchestrator.js +610 -67
  467. package/src/runtime/worktree.js +149 -22
  468. package/src/state/index.js +1 -1
  469. package/src/state/lockfile.js +64 -10
  470. package/src/state/run-lock.js +1 -1
  471. package/types/node-commonjs.d.ts +106 -7
  472. package/core/harnesses/claude/hooks/pre-bash-guard.sh +0 -10
  473. package/core/harnesses/codex/hooks/pre-bash-guard.sh +0 -10
  474. package/core/harnesses/gemini/hooks/hooks.json +0 -77
  475. package/core/harnesses/gemini/hooks/lib.sh +0 -302
  476. package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +0 -10
  477. package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
  478. package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -10
  479. package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
  480. package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -10
  481. package/core/harnesses/gemini/hooks/session-end.impl.sh +0 -89
  482. package/core/harnesses/gemini/hooks/session-end.sh +0 -5
  483. package/core/harnesses/gemini/hooks/session-start.sh +0 -7
  484. package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
  485. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +0 -24
  486. package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +0 -91
  487. package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +0 -378
  488. package/core/shared/guidance/FLAGS.md +0 -25
  489. package/core/shared/guidance/rules/RULE_01_SAFETY.md +0 -25
  490. package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +0 -23
  491. package/core/shared/guidance/rules/RULE_03_QUALITY.md +0 -14
  492. package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +0 -46
  493. package/src/adapters/gemini/hooks.js +0 -251
  494. package/src/adapters/gemini/index.js +0 -429
@@ -8,7 +8,7 @@
8
8
  * the project store. Masking only narrows what a statement or an excerpt can carry.
9
9
  *
10
10
  * Two profiles. The line profile is for one-line statements and failure messages; the body profile
11
- * is for report bodies and excerpts and runs every line rule plus the body-only rules 7 to 9.
11
+ * is for report bodies and excerpts and runs every line rule plus the body-only rules 7 to 9 and 14 to 17.
12
12
  * Rules run in the table's order. Normalisation (rules 10 to 13) is a separate step applied to
13
13
  * failure messages only, after masking.
14
14
  *
@@ -36,7 +36,9 @@ const TOKEN_SHAPES = [
36
36
  /xox[abprs]-[A-Za-z0-9-]{10,}/g,
37
37
  /npm_[A-Za-z0-9]{24,}/g,
38
38
  /AIza[0-9A-Za-z_-]{35}/g,
39
- /eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g,
39
+ // Left-anchored (DEC-069): without it every `eyJ` inside a long dotless run starts a scan to the
40
+ // run's end, which makes masking one line quadratic in its length.
41
+ /(?<![A-Za-z0-9_-])eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g,
40
42
  ];
41
43
 
42
44
  const PEM = /-----BEGIN [^\n-]*-----(?:[\s\S]*?-----END [^\n-]*-----|[\s\S]*$)/g;
@@ -52,13 +54,107 @@ const KEY_VALUE_BODY = new RegExp(`(${KEY_NAME}["']?[ \\t]*[=:][ \\t]*)("[^"\\r\
52
54
  const AUTH_HEADER = /((?:Proxy-)?Authorization:)[ \t]*([^\r\n]*)/gi;
53
55
  const SECRET_FLAG = /(--(?:password|passwd|token|secret|api-key|client-secret)(?:=|[ \t]+))("[^"\r\n]*"|'[^'\r\n]*'|[^\s"']+)/gi;
54
56
 
57
+ // Rules 14 to 17 (body profile only, FU-ht8b3r). Command shapes that carry a secret without a key
58
+ // word: `curl -u user:pw` (also glued, `-uuser:pw`), `mysql -psecret` (value glued to the flag; a bare
59
+ // `-p` prompts and carries none), and the phrase `password hunter2`. The flags are masked by one
60
+ // left-to-right scan (rules 14 and 15, `maskCommandFlags`) that knows which command it is in, so every
61
+ // flag of a command is masked and `sort -u`, `git push -u` and `mysql -P 3306` stay as written.
62
+ // A command word counts only at command position (see `maskCommandFlags`), and quoted text is scanned,
63
+ // not skipped. Alternatives, in order: a quote, a command break (`|`, `;`, `&`, parentheses, a
64
+ // backtick, a line end), curl's `--user` and `-u` (the short one may be glued to its value), mysql's
65
+ // `-p` glued to its value, any other word.
66
+ const COMMAND_SCAN = /(?<quote>["'])|(?<brk>[|;&()`\r\n])|(?<=[ \t])(?:(?<long>--user)(?:=|[ \t]+)|(?<short>-u)(?:=|[ \t]*)(?=[^\s|;&]))|(?<=[ \t])(?<pass>-p)(?=[^\s|;&-])|(?<word>[^\s|;&()`"']+)/g;
67
+ // The closing quote of a quoted span, on the same line.
68
+ const QUOTE_CLOSE = { '"': /[^"\r\n]*"/y, "'": /[^'\r\n]*'/y };
69
+ // A word that is the command: bare or as a path (never a URL, which holds `:`).
70
+ const COMMAND_WORD = /^(?:(?:\.{0,2}|~)?\/(?:[^\s:]*\/)?)?(?:(curl)|(mysql(?:dump|admin|pump|import)?))$/;
71
+ // Words after which the next word is the command: wrappers, remote and container runners, shell keywords.
72
+ const WRAPPER_WORDS = new Set(['sudo', 'doas', 'su', 'env', 'exec', 'nohup', 'time', 'timeout', 'nice', 'ionice', 'xargs', 'watch',
73
+ 'command', 'builtin', 'eval', 'setsid', 'stdbuf', 'flock', 'chroot', 'ssh', 'docker', 'podman', 'kubectl', 'sh', 'bash', 'zsh', 'dash', 'ksh',
74
+ 'if', 'then', 'else', 'elif', 'do', 'while', 'until', '!', '{']);
75
+ // A first word that does not use up the command position: a prompt mark, a variable assignment or a
76
+ // label such as `Running:`.
77
+ const PROMPT_OR_ASSIGNMENT = /^(?:[$#%>+]+|[A-Za-z_][A-Za-z0-9_]*=[^=]*|[A-Za-z][A-Za-z0-9_-]*:)$/;
78
+ // A flag value: quoted, or unquoted up to a space, quote or command break.
79
+ const COMMAND_FLAG_VALUE = /"[^"\r\n]*"|'[^'\r\n]*'|[^\s"'|;&]+/y;
80
+ // The value after the phrase must hold a non-letter, as in rule 4, so ordinary prose ("the password is
81
+ // required") survives; a plain-letter value after a bare `password ` is a known gap, and so is a
82
+ // password of one to three digits (`reset password 2 times` is prose).
83
+ const PASSWORD_PHRASE = /(?<![A-Za-z0-9_.-])((?:password|passwd)[ \t]+)(?!<masked>|[=:])([^\s,;&"'`)\]}]+)/gi;
84
+ // Another user's home directory (the running user's is rule 0). `Shared` is not a person and `runner`
85
+ // is the CI runner account (`/Users/runner/work/...`), so neither is masked.
86
+ const OTHER_USER_HOME = /(?<![A-Za-z0-9_.-])\/Users\/(?!(?:Shared|runner)(?![^/\s"'`<>:;,()[\]{}])|<)[^/\s"'`<>:;,()[\]{}]+/g;
87
+
88
+ /**
89
+ * Rules 14 and 15. Scans left to right and masks the value of every `-u`/`--user` of a curl command
90
+ * and every `-p<value>` of a mysql command. A command word (`curl`, `mysql*`) is the command of its
91
+ * segment only at command position: the first word of the text, of a line or of a segment after a
92
+ * break (`|`, `;`, `&`, a parenthesis, a backtick), of a quoted span, or after a wrapper word
93
+ * (`sudo`, `env`, `ssh`, `docker`, `sh`, `bash`, ...) at the start of the segment. A word that holds `:`
94
+ * (a URL) or is followed by more of its token (`mysql-8`) is not the command. A segment has one
95
+ * command. Quoted text is scanned: its start is a command position, and a break inside it ends only
96
+ * the command inside it, so `curl -H "a|b" -u x:y` keeps its command. One pass; every step consumes
97
+ * at least one character and a closing quote is searched on its own line, so the cost is linear.
98
+ */
99
+ function maskCommandFlags(text ) {
100
+ let out = '';
101
+ let copied = 0;
102
+ let command = null;
103
+ let atStart = true; // the next word is the first of its segment
104
+ let wrapped = false; // the segment began with a wrapper word
105
+ const spans = [];
106
+ COMMAND_SCAN.lastIndex = 0;
107
+ let found ;
108
+ while ((found = COMMAND_SCAN.exec(text)) !== null) {
109
+ const groups = found.groups || {};
110
+ const end = found.index + found[0].length;
111
+ if (groups.quote) {
112
+ const top = spans[spans.length - 1];
113
+ if (top && top.quote === groups.quote) {
114
+ spans.pop();
115
+ ({ command, atStart, wrapped } = top);
116
+ atStart = false;
117
+ } else {
118
+ const close = QUOTE_CLOSE[groups.quote];
119
+ close.lastIndex = end;
120
+ if (close.test(text)) {
121
+ spans.push({ quote: groups.quote, command, atStart, wrapped });
122
+ command = null; atStart = true; wrapped = false;
123
+ }
124
+ }
125
+ } else if (groups.brk) {
126
+ if (!(groups.brk === '&' && /[<>]/.test(text[found.index - 1] || ''))) { command = null; atStart = true; wrapped = false; }
127
+ } else if (groups.word) {
128
+ const word = groups.word;
129
+ if (atStart && PROMPT_OR_ASSIGNMENT.test(word)) continue;
130
+ const named = COMMAND_WORD.exec(word);
131
+ if (command === null && (atStart || wrapped) && named) command = named[1] ? 'curl' : 'mysql';
132
+ else if (atStart) wrapped = WRAPPER_WORDS.has(word.slice(word.lastIndexOf('/') + 1));
133
+ atStart = false;
134
+ } else if (((groups.long || groups.short) && command === 'curl') || (groups.pass && command === 'mysql')) {
135
+ atStart = false;
136
+ COMMAND_FLAG_VALUE.lastIndex = end;
137
+ const value = COMMAND_FLAG_VALUE.exec(text);
138
+ if (value && !isMasked(value[0]) && !(command === 'mysql' && value[0].startsWith(MASKED))) {
139
+ out += text.slice(copied, end) + MASKED;
140
+ copied = end + value[0].length;
141
+ COMMAND_SCAN.lastIndex = copied;
142
+ }
143
+ } else {
144
+ atStart = false; // another command's flag: rescan what follows it
145
+ COMMAND_SCAN.lastIndex = end;
146
+ }
147
+ }
148
+ return out + text.slice(copied);
149
+ }
150
+
55
151
  function escapeRegExp(text ) { return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
56
152
 
57
153
  /** The home directory to abbreviate: an absolute path other than the file system root. */
58
154
  function homeOf(options ) {
59
155
  const raw = options && 'home' in options ? options.home : process.env.HOME;
60
- if (typeof raw !== 'string' || !raw.startsWith('/')) return null;
61
- const home = raw.replace(/\/+$/, '');
156
+ if (typeof raw !== 'string' || !(raw.startsWith('/') || /^[A-Za-z]:[\\/]/.test(raw))) return null;
157
+ const home = raw.replace(/[\\/]+$/, '');
62
158
  return home.length > 1 ? home : null;
63
159
  }
64
160
 
@@ -89,6 +185,30 @@ function mask40(run , before ) {
89
185
  looksLikeSecretRun(value, 1) && !isPathRun(value, index === 0 ? before : run[index - 1]) ? MASKED : value));
90
186
  }
91
187
 
188
+ const TS_TYPE_NAMES = new Set([
189
+ 'string', 'number', 'boolean', 'any', 'unknown', 'void',
190
+ 'never', 'null', 'undefined', 'symbol', 'bigint',
191
+ ]);
192
+
193
+ function isCodePreserved(head , value , offset , fullString ) {
194
+ if (value.startsWith('"') || value.startsWith("'")) return false;
195
+ const isColon = /:[ \t]*$/.test(head);
196
+ if (isColon) {
197
+ const baseType = value.replace(/\[\s*\]?$|\[$/, '');
198
+ if (TS_TYPE_NAMES.has(baseType)) return true;
199
+ if (/^[A-Z]|^\[/.test(value)) return true;
200
+ if (/^(?:new\s|[A-Za-z0-9_$]+\s*\(|[A-Za-z0-9_$]+\.[A-Za-z0-9_$]+)/.test(value)) return true;
201
+ }
202
+ const isEquals = /=[ \t]*$/.test(head);
203
+ if (isEquals) {
204
+ const before = fullString.slice(Math.max(0, offset - 30), offset);
205
+ if (/(?:const|let|var|readonly)\s+$/.test(before)) return true;
206
+ if (/\bnew\s|[A-Za-z0-9_$]+\.[A-Za-z0-9_$]+|\(/.test(value)) return true;
207
+ if (/^[\[{]/.test(value)) return true;
208
+ }
209
+ return false;
210
+ }
211
+
92
212
  function isMasked(value ) { return value.replace(/^["']|["']$/g, '') === MASKED; }
93
213
 
94
214
  function count(text , token ) { return text.split(token).length - 1; }
@@ -119,8 +239,8 @@ function mask(text , profile , options = {})
119
239
  // 3. Bearer tokens and URL credentials.
120
240
  out = out.replace(BEARER, `$1$2${MASKED}`).replace(URL_CREDENTIALS, `://${MASKED}@`);
121
241
  // 4. Key=value, unquoted, when the value is not plain letters.
122
- out = out.replace(KEY_VALUE_LINE, (match , key , separator , value ) => (
123
- isMasked(value) || value.startsWith(MASKED) || !/[^A-Za-z]/.test(value) ? match : `${key}${separator}${MASKED}`));
242
+ out = out.replace(KEY_VALUE_LINE, (match , key , separator , value , offset , fullString ) => (
243
+ isMasked(value) || value.startsWith(MASKED) || !/[^A-Za-z]/.test(value) || isCodePreserved(key + separator, value, offset, fullString) ? match : `${key}${separator}${MASKED}`));
124
244
  // 5. Long secret-looking runs that are not paths.
125
245
  out = out.replace(LONG_RUN, (run , offset ) => (
126
246
  looksLikeSecretRun(run, 2) && !isPathRun(run, out[offset - 1]) ? MASKED : mask40(run, out[offset - 1])));
@@ -129,8 +249,9 @@ function mask(text , profile , options = {})
129
249
 
130
250
  if (profile === 'body') {
131
251
  // 7. Key and value, quoted or not, whatever the value looks like.
132
- out = out.replace(KEY_VALUE_BODY, (match , head , value ) => {
252
+ out = out.replace(KEY_VALUE_BODY, (match , head , value , offset , fullString ) => {
133
253
  if (isMasked(value) || value.startsWith(MASKED)) return match;
254
+ if (isCodePreserved(head, value, offset, fullString)) return match;
134
255
  const quote = value[0] === '"' || value[0] === "'" ? value[0] : '';
135
256
  return `${head}${quote}${MASKED}${quote}`;
136
257
  });
@@ -138,6 +259,12 @@ function mask(text , profile , options = {})
138
259
  out = out.replace(AUTH_HEADER, (match , name , rest ) => (rest === '' || rest === MASKED ? match : `${name} ${MASKED}`));
139
260
  // 9. Secret-bearing command-line flags.
140
261
  out = out.replace(SECRET_FLAG, (match , flag , value ) => (isMasked(value) ? match : `${flag}${MASKED}`));
262
+ // 14 and 15. `curl -u user:pw`, `curl --user user:pw`, `curl -uuser:pw` and `mysql -psecret`, every flag of a command.
263
+ out = maskCommandFlags(out);
264
+ // 16. `password hunter2`: the key word, a space and a value with a non-letter in it.
265
+ out = out.replace(PASSWORD_PHRASE, (match , head , value ) => (/[^A-Za-z]/.test(value) && !/^[0-9]{1,3}$/.test(value) ? `${head}${MASKED}` : match));
266
+ // 17. Another user's home directory: `/Users/<name>` becomes `/Users/<masked>`.
267
+ out = out.replace(OTHER_USER_HOME, `/Users/${MASKED}`);
141
268
  }
142
269
 
143
270
  const masked = Math.max(0, [MASKED, EMAIL, IP].reduce((sum, token) => sum + count(out, token) - count(input, token), 0));
@@ -164,11 +164,33 @@ function knownServerIds(registry ) {
164
164
  return ids;
165
165
  }
166
166
 
167
+ const MANIFEST_PATTERNS = Object.freeze([
168
+ /(?:^|[\\/])package(?:-lock)?\.json$/,
169
+ /(?:^|[\\/])pnpm-lock\.yaml$/,
170
+ /(?:^|[\\/])yarn\.lock$/,
171
+ /(?:^|[\\/])Cargo\.(?:toml|lock)$/,
172
+ /(?:^|[\\/])go\.(?:mod|sum)$/,
173
+ /(?:^|[\\/])requirements.*\.txt$/,
174
+ /(?:^|[\\/])pyproject\.toml$/,
175
+ /(?:^|[\\/])poetry\.lock$/,
176
+ /(?:^|[\\/])Gemfile(?:\.lock)?$/,
177
+ /(?:^|[\\/])composer\.(?:json|lock)$/,
178
+ /(?:^|[\\/])build\.gradle(?:\.kts)?$/,
179
+ /(?:^|[\\/])pom\.xml$/,
180
+ /(?:^|[\\/])Package\.swift$/,
181
+ /(?:^|[\\/])Podfile(?:\.lock)?$/,
182
+ ]);
183
+
184
+ function touchesManifest(files ) {
185
+ if (!Array.isArray(files) || files.length === 0) return false;
186
+ return files.some((f) => MANIFEST_PATTERNS.some((pattern) => pattern.test(f)));
187
+ }
188
+
167
189
  /**
168
190
  * Servers a stage run names (IC-001 meaning). Sorted.
169
- * @param {{registry: object, skill: string, stageId?: string, taskId?: string, requests?: {list: Function}}} input
191
+ * @param {{registry: object, skill: string, stageId?: string, taskId?: string, requests?: {list: Function}, taskClass?: string, diffFiles?: string[]}} input
170
192
  */
171
- function namedServers({ registry, skill, stageId, taskId, requests } ) {
193
+ function namedServers({ registry, skill, stageId, taskId, requests, taskClass, diffFiles } ) {
172
194
  let researchRequested ;
173
195
  const hasRequest = () => {
174
196
  if (researchRequested === undefined) {
@@ -178,12 +200,21 @@ function namedServers({ registry, skill, stageId, taskId, requests } )
178
200
  }
179
201
  return researchRequested;
180
202
  };
203
+
204
+ const isDependencyChange = taskClass === 'dependency-change';
205
+ const hasManifestDiff = touchesManifest(diffFiles);
206
+
181
207
  const named = new Set ();
182
208
  for (const server of registry?.mcp || []) {
183
209
  for (const step of server.steps || []) {
184
- if (step.expectedWhen === 'research-request') {
185
- if (hasRequest()) named.add(server.id);
186
- } else if (step.skill === skill) {
210
+ if (step.skill !== skill) continue;
211
+ if (!step.expectedWhen) {
212
+ named.add(server.id);
213
+ } else if (step.expectedWhen === 'research-request' && hasRequest()) {
214
+ named.add(server.id);
215
+ } else if (step.expectedWhen === 'dependency-change' && isDependencyChange) {
216
+ named.add(server.id);
217
+ } else if (step.expectedWhen === 'manifest-review' && hasManifestDiff) {
187
218
  named.add(server.id);
188
219
  }
189
220
  }
@@ -213,13 +244,14 @@ const bySortKey = (a , b ) => (a < b ? -1 : a > b ? 1 : 0);
213
244
  function resolveConsultations({
214
245
  taskId = null, stageId = null, skill, flags, registry, ledger, requests,
215
246
  briefRecords = [], readReport, reach, addCommand, harnessName, since = null,
247
+ taskClass = null, diffFiles = [],
216
248
  } = {}) {
217
249
  const warnings = [];
218
250
  const notices = [];
219
251
  const evidenceToAdd = [];
220
252
  const registered = new Set((registry?.mcp || []).map((server ) => server.id));
221
253
  const known = knownServerIds(registry);
222
- const named = namedServers({ registry, skill, stageId, taskId, requests });
254
+ const named = namedServers({ registry, skill, stageId, taskId, requests, taskClass, diffFiles });
223
255
 
224
256
  // Rule 1: flags.
225
257
  const parsedFlags = parseMcpOutcomeFlags(flags);
@@ -408,5 +440,7 @@ module.exports = {
408
440
  parseMcpOutcomeFlags,
409
441
  parseToolsUsed,
410
442
  namedServers,
443
+ touchesManifest,
444
+ MANIFEST_PATTERNS,
411
445
  resolveConsultations,
412
446
  };
@@ -39,22 +39,58 @@ function availableProviderIds({ fsImpl = fs } = {})
39
39
  .map(([id]) => id);
40
40
  }
41
41
 
42
+ function normalizeFamilyList(value ) {
43
+ const values = Array.isArray(value) ? value : [value];
44
+ return [...new Set(values.flatMap((entry) => String(entry ?? '').split(',').map((family) => family.trim().toLowerCase()).filter(Boolean)))];
45
+ }
46
+
47
+ // families is a hand-kept stopgap; the model-facts registry (roadmap row 105) supersedes it and this field is removed then.
48
+ /** An unknown family is not evidence that a provider can review outside the author's family. */
49
+ function familyInfo(families , excludedFamilies ) {
50
+ const declared = Array.isArray(families) ? families : [];
51
+ const hasUnknown = declared.length === 0
52
+ || declared.some((family) => typeof family !== 'string' || !/^[a-z][a-z0-9-]*$/.test(family) || family === 'unknown');
53
+ const overlapsAuthor = declared.some((family) => typeof family === 'string'
54
+ && family !== 'unknown' && /^[a-z][a-z0-9-]*$/.test(family) && excludedFamilies.has(family));
55
+ const familyRelation = overlapsAuthor ? 'overlaps-author' : hasUnknown ? 'unverified' : 'cross-family';
56
+ const sameFamily = declared.length > 0 && declared.every((family) => typeof family === 'string' && excludedFamilies.has(family));
57
+ return {
58
+ families: [...declared],
59
+ familyVerified: familyRelation === 'cross-family',
60
+ familyRelation,
61
+ sameFamily,
62
+ };
63
+ }
64
+
42
65
  /**
43
66
  * Resolve one role to ranked candidates.
44
67
  * Ordering: available first, then preferred-tier match, then fallback-tier match, registry order
45
- * breaking ties. `exclude` drops provider ids entirely (e.g. the implementer when the role is
46
- * review with require:different-family) and is reported back rather than hidden.
68
+ * breaking ties. `exclude` drops provider ids entirely; `excludeFamilies` drops providers whose
69
+ * complete, known catalogue is within the excluded model families. Unknown catalogues are kept
70
+ * unverified rather than being presented as cross-family.
47
71
  * Availability unknown stays null — absence of evidence is not reported as absent.
48
72
  */
49
- function resolveModelRole({ registry, roleId, isAvailable = null, exclude = [] } = {}) {
73
+ function resolveModelRole({ registry, roleId, isAvailable = null, exclude = [], excludeFamilies } = {}) {
50
74
  const role = (registry.modelRoles || []).find((entry ) => entry.id === roleId);
51
75
  if (!role) {
52
76
  throw new Error(`Unknown model role '${roleId}'; valid: ${(registry.modelRoles || []).map((r ) => r.id).join(', ')}`);
53
77
  }
54
78
  const excluded = new Set(exclude);
79
+ const familyMode = excludeFamilies !== undefined && excludeFamilies !== null;
80
+ const normalizedFamilies = familyMode ? normalizeFamilyList(excludeFamilies) : [];
81
+ if (familyMode && normalizedFamilies.length === 0) {
82
+ throw new Error('--exclude-family requires at least one family name');
83
+ }
84
+ const excludedFamilies = new Set(normalizedFamilies);
55
85
  const candidates = [];
86
+ const familyExcluded = [];
56
87
  for (const [index, provider] of (registry.modelProviders || []).entries()) {
57
88
  if (excluded.has(provider.id)) continue;
89
+ const family = familyInfo(provider.families, excludedFamilies);
90
+ if (familyMode && family.sameFamily) {
91
+ familyExcluded.push(provider.id);
92
+ continue;
93
+ }
58
94
  const tiers = provider.tiers ?? [];
59
95
  candidates.push({
60
96
  id: provider.id,
@@ -64,6 +100,7 @@ function resolveModelRole({ registry, roleId, isAvailable = null, exclude = [] }
64
100
  available: isAvailable ? Boolean(isAvailable(provider.id)) : null,
65
101
  servesPreferredTier: role.prefer ? tiers.includes(role.prefer) : null,
66
102
  servesFallbackTier: role.fallback ? tiers.includes(role.fallback) : null,
103
+ ...(familyMode ? { families: family.families, familyRelation: family.familyRelation, familyVerified: family.familyVerified } : {}),
67
104
  _index: index,
68
105
  });
69
106
  }
@@ -79,12 +116,19 @@ function resolveModelRole({ registry, roleId, isAvailable = null, exclude = [] }
79
116
  return 0;
80
117
  });
81
118
  for (const candidate of candidates) delete candidate._index;
82
- return {
119
+ const result = {
83
120
  roleId,
84
121
  policy: { prefer: role.prefer ?? null, fallback: role.fallback ?? null, require: role.require ?? null },
85
122
  candidates,
86
123
  excluded: [...excluded],
87
124
  };
125
+ // Keep the established result shape byte-for-byte for callers that do not request family
126
+ // filtering. Family metadata is included whenever that comparison is requested.
127
+ if (familyMode) {
128
+ result.excludedFamilies = [...excludedFamilies];
129
+ result.familyExcluded = familyExcluded;
130
+ }
131
+ return result;
88
132
  }
89
133
 
90
134
  /**
@@ -118,7 +162,7 @@ function resolveRetrievalSlot({ registry, slotId, isAvailable = null } = {}
118
162
 
119
163
  /** CLI handler for `doflow model-role`. Read-only advisory routing; exits 1 on an unknown role
120
164
  * because a silently-empty candidate list would read as "no providers" rather than "bad input". */
121
- function handleModelRoleCommand({ role, exclude, json = false, repoRoot } = {}) {
165
+ function handleModelRoleCommand({ role, exclude, excludeFamilies, json = false, repoRoot } = {}) {
122
166
  if (!role) return usageError('model-role', '--role is required (one of the roles in core/registry/models.json)', json);
123
167
  const registry = (() => {
124
168
  try { return loadRegistry({ repoRoot: repoRoot || REPO_ROOT }); }
@@ -127,20 +171,48 @@ function handleModelRoleCommand({ role, exclude, json = false, repoRoot } =
127
171
  if (!registry) return undefined;
128
172
  const available = new Set(availableProviderIds());
129
173
  try {
174
+ const excludedProviderIds = Array.isArray(exclude) ? exclude : String(exclude ?? '').split(',').map((s ) => s.trim()).filter(Boolean);
175
+ const requestedFamilies = excludeFamilies === undefined || excludeFamilies === null ? null : normalizeFamilyList(excludeFamilies);
176
+ if (requestedFamilies !== null) {
177
+ const knownFamilies = [...new Set((registry.modelProviders || []).flatMap((provider ) => provider.families || []))]
178
+ .filter((family) => typeof family === 'string' && family !== 'unknown').sort();
179
+ const knownSet = new Set(knownFamilies);
180
+ const unknownFamilies = requestedFamilies.filter((family) => !knownSet.has(family));
181
+ if (requestedFamilies.length === 0) {
182
+ return usageError('model-role', `--exclude-family requires at least one family name; known families: ${knownFamilies.join(', ') || '(none)'}`, json);
183
+ }
184
+ if (requestedFamilies.includes('unknown')) {
185
+ return usageError('model-role', `unknown cannot be excluded because it names missing knowledge; known families: ${knownFamilies.join(', ') || '(none)'}`, json);
186
+ }
187
+ if (unknownFamilies.length) {
188
+ return usageError('model-role', `unknown family name${unknownFamilies.length === 1 ? '' : 's'}: ${unknownFamilies.join(', ')}; known families: ${knownFamilies.join(', ') || '(none)'}`, json);
189
+ }
190
+ }
130
191
  const resolution = resolveModelRole({
131
192
  registry,
132
193
  roleId: role,
133
194
  isAvailable: (providerId ) => available.has(providerId),
134
- exclude: Array.isArray(exclude) ? exclude : String(exclude ?? '').split(',').map((s ) => s.trim()).filter(Boolean),
195
+ exclude: excludedProviderIds,
196
+ ...(requestedFamilies === null ? {} : { excludeFamilies: requestedFamilies }),
135
197
  });
136
198
  if (json) console.log(JSON.stringify(resolution, null, 2));
137
199
  else {
138
200
  console.log(`Role '${resolution.roleId}' — prefer:${resolution.policy.prefer ?? '—'} fallback:${resolution.policy.fallback ?? '—'} require:${resolution.policy.require ?? '—'}`);
139
201
  for (const candidate of resolution.candidates) {
140
202
  const availability = candidate.available === null ? '?' : candidate.available ? 'yes' : 'no';
141
- console.log(` ${candidate.available ? '*' : ' '} ${candidate.id} (${candidate.kind}, via ${candidate.backendCli ?? 'hosted'}) available=${availability}${resolution.policy.prefer && candidate.servesPreferredTier ? ` serves ${resolution.policy.prefer}` : ''}`);
203
+ const familyStatus = requestedFamilies === null ? ''
204
+ : ` ${candidate.familyRelation}`;
205
+ console.log(` ${candidate.available ? '*' : ' '} ${candidate.id} (${candidate.kind}, via ${candidate.backendCli ?? 'hosted'}) available=${availability}${resolution.policy.prefer && candidate.servesPreferredTier ? ` serves ${resolution.policy.prefer}` : ''}${familyStatus}`);
142
206
  }
143
207
  if (resolution.excluded.length) console.log(`Excluded: ${resolution.excluded.join(', ')}`);
208
+ if (requestedFamilies !== null) {
209
+ const familySet = new Set(requestedFamilies);
210
+ const providerExcluded = new Set(excludedProviderIds);
211
+ const sameFamily = (registry.modelProviders || []).filter((provider ) => !providerExcluded.has(provider.id)
212
+ && familyInfo(provider.families, familySet).sameFamily);
213
+ for (const provider of sameFamily) console.log(` ${provider.id} same-family (excluded)`);
214
+ if (resolution.excludedFamilies.length) console.log(`Excluded families: ${resolution.excludedFamilies.join(', ')}`);
215
+ }
144
216
  }
145
217
  return finishRuntime(0);
146
218
  } catch (error ) {
@@ -0,0 +1,191 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const os = require('node:os');
5
+ const path = require('node:path');
6
+
7
+ const { PROBES, HARNESS_IDS } = require('../worker/probes/index.js');
8
+ const { configHome } = require('../config-home.js');
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+
47
+ function getProjectCataloguePath(projectRoot ) {
48
+ return path.join(projectRoot, '.doflow', 'state', 'models', 'catalogue.json');
49
+ }
50
+
51
+ function getUserCataloguePath(homeDir , env = process.env) {
52
+ if (homeDir) {
53
+ return path.join(homeDir, '.config', 'doflow', 'state', 'models-observed.json');
54
+ }
55
+ const { dir } = configHome(env);
56
+ const baseDir = dir || path.join(os.homedir(), '.config', 'doflow');
57
+ return path.join(baseDir, 'state', 'models-observed.json');
58
+ }
59
+
60
+ function resolveProjectRoot(projectRoot , fsImpl = fs) {
61
+ if (projectRoot) return path.resolve(projectRoot);
62
+ try {
63
+ const { projectRoot: findRoot } = require('../lifecycle/root.js');
64
+ const root = findRoot(process.cwd());
65
+ if (root && fsImpl.existsSync(path.join(root, '.doflow'))) return root;
66
+ } catch {
67
+ // fallback
68
+ }
69
+ if (fsImpl.existsSync(path.join(process.cwd(), '.doflow'))) {
70
+ return process.cwd();
71
+ }
72
+ return null;
73
+ }
74
+
75
+ function readCatalogue(
76
+ projectRoot ,
77
+ options = {}
78
+ ) {
79
+ const fsImpl = options.fsImpl || fs;
80
+ const root = projectRoot ? path.resolve(projectRoot) : resolveProjectRoot(undefined, fsImpl);
81
+
82
+ if (root) {
83
+ const projPath = getProjectCataloguePath(root);
84
+ if (fsImpl.existsSync(projPath)) {
85
+ try {
86
+ const raw = fsImpl.readFileSync(projPath, 'utf8');
87
+ return JSON.parse(raw);
88
+ } catch (err ) {
89
+ if (err instanceof SyntaxError) {
90
+ const corrupt = `${projPath}.corrupt-${Date.now()}`;
91
+ try {
92
+ fsImpl.renameSync(projPath, corrupt);
93
+ } catch {
94
+ // best-effort
95
+ }
96
+ return null;
97
+ }
98
+ return null;
99
+ }
100
+ }
101
+ }
102
+
103
+ // Fallback to user cache
104
+ const userPath = getUserCataloguePath(options.homeDir, options.env);
105
+ if (fsImpl.existsSync(userPath)) {
106
+ try {
107
+ const raw = fsImpl.readFileSync(userPath, 'utf8');
108
+ return JSON.parse(raw);
109
+ } catch (err ) {
110
+ if (err instanceof SyntaxError) {
111
+ const corrupt = `${userPath}.corrupt-${Date.now()}`;
112
+ try {
113
+ fsImpl.renameSync(userPath, corrupt);
114
+ } catch {
115
+ // best-effort
116
+ }
117
+ return null;
118
+ }
119
+ return null;
120
+ }
121
+ }
122
+
123
+ return null;
124
+ }
125
+
126
+ function writeCatalogue(
127
+ catalogue ,
128
+ projectRoot ,
129
+ options = {}
130
+ ) {
131
+ const fsImpl = options.fsImpl || fs;
132
+ const root = projectRoot ? path.resolve(projectRoot) : resolveProjectRoot(undefined, fsImpl);
133
+ const targetPath = root ? getProjectCataloguePath(root) : getUserCataloguePath(options.homeDir, options.env);
134
+
135
+ const targetDir = path.dirname(targetPath);
136
+ fsImpl.mkdirSync(targetDir, { recursive: true });
137
+
138
+ const tmpPath = `${targetPath}.${process.pid}.${Date.now()}-${Math.random().toString(36).slice(2)}.tmp`;
139
+ fsImpl.writeFileSync(tmpPath, JSON.stringify(catalogue, null, 2), 'utf8');
140
+ fsImpl.renameSync(tmpPath, targetPath);
141
+ }
142
+
143
+ async function syncHarness(
144
+ harnessId ,
145
+ options = {}
146
+ ) {
147
+ const probe = PROBES[harnessId];
148
+ if (!probe) {
149
+ return {
150
+ harnessId,
151
+ binary: harnessId,
152
+ installed: false,
153
+ version: null,
154
+ loginState: 'unsupported',
155
+ reloginHint: null,
156
+ detectedMode: 'unverified',
157
+ derivedBilling: 'unknown',
158
+ offeredModels: [],
159
+ lastSyncedAt: new Date().toISOString(),
160
+ };
161
+ }
162
+
163
+ return await probe(options);
164
+ }
165
+
166
+ async function syncAllHarnesses(
167
+ options = {}
168
+ ) {
169
+ const results = {};
170
+ for (const id of HARNESS_IDS) {
171
+ results[id] = await syncHarness(id, options);
172
+ }
173
+
174
+ const catalogue = {
175
+ version: 1,
176
+ syncedAt: new Date().toISOString(),
177
+ harnesses: results,
178
+ };
179
+
180
+ writeCatalogue(catalogue, options.projectRoot, options);
181
+ return catalogue;
182
+ }
183
+
184
+ module.exports = {
185
+ getProjectCataloguePath,
186
+ getUserCataloguePath,
187
+ readCatalogue,
188
+ writeCatalogue,
189
+ syncHarness,
190
+ syncAllHarnesses,
191
+ };