@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
@@ -5,12 +5,14 @@
5
5
  // Shared copy-tree engine — the generic "own a tree of files" implementation every asset
6
6
  // declaring `renderer: "copy-tree"` in core/registry/assets.json needs. Codex's adapter already
7
7
  // proved this per-file fingerprint/conflict model for agents and hooks (ownedRemovalPlan,
8
- // nativeManagedResources); this module generalizes it so Claude, Gemini, and Codex's own
8
+ // nativeManagedResources); this module generalizes it so every adapter's
9
9
  // tree-shaped assets (skills, rules, agent-specs, templates, scripts, modes, references) share
10
- // one implementation instead of three adapter-specific copies.
10
+ // one implementation instead of adapter-specific copies.
11
11
  const fs = require('node:fs');
12
12
  const path = require('node:path');
13
13
  const crypto = require('node:crypto');
14
+ const { guidanceManifest } = require('./guidance-manifest.js');
15
+ const { REPO_ROOT } = require('../helper/repo-root.js');
14
16
 
15
17
  function sha256(buf ) {
16
18
  return crypto.createHash('sha256').update(buf).digest('hex');
@@ -39,7 +41,7 @@ const LAYOUTS = {
39
41
  const ext = path.extname(sourceRel);
40
42
  return path.join(sourceRel.slice(0, sourceRel.length - ext.length), `agent${ext || '.md'}`);
41
43
  },
42
- /** `RULE_01_SAFETY.md` -> `RULE_01_SAFETY.instructions.md` — GitHub's path-specific
44
+ /** `RULES.md` -> `RULES.instructions.md` — GitHub's path-specific
43
45
  * instructions are discovered by their `.instructions.md` suffix. */
44
46
  'instructions-md': (sourceRel ) => {
45
47
  const ext = path.extname(sourceRel);
@@ -59,6 +61,8 @@ const LAYOUTS = {
59
61
  const ext = path.extname(sourceRel);
60
62
  return `${sourceRel.slice(0, sourceRel.length - ext.length)}.json`;
61
63
  },
64
+ /** `rules/RULES.md` -> `RULES.md` for flat rule directories. */
65
+ flat: (sourceRel ) => path.basename(sourceRel),
62
66
  };
63
67
 
64
68
  /** Resolve a declared layout name to its mapper. Unknown names fail loudly rather than silently
@@ -70,6 +74,25 @@ function resolveLayout(name ) {
70
74
  return layout;
71
75
  }
72
76
 
77
+ /** Source-file selectors for projections that intentionally materialize only part of a tree. */
78
+ const SELECTORS = {
79
+ // A dangling import that is not generated would install a core set missing a file the entry file
80
+ // names, silently; the selector fails instead (MCP_INDEX.md is generated and exempt) (107-B C-4).
81
+ 'guidance-manifest': (sourceDir , fsImpl ) => {
82
+ const { files, dangling } = guidanceManifest(sourceDir, fsImpl);
83
+ if (dangling.length) throw new Error(`guidance-manifest: DOFLOW_CORE.md imports files that do not exist under ${sourceDir}: ${dangling.join(', ')}`);
84
+ return new Set(files);
85
+ },
86
+ };
87
+
88
+ /** Resolve a declared selector name, or return null when a projection mirrors the whole tree. */
89
+ function resolveSelect(name ) {
90
+ if (name == null) return null;
91
+ const selector = SELECTORS[name];
92
+ if (!selector) throw new Error(`Unknown copy-tree select '${name}' (known: ${Object.keys(SELECTORS).join(', ')})`);
93
+ return selector;
94
+ }
95
+
73
96
  /** Destination content transforms. Where a harness reads a supported but different file FORMAT
74
97
  * than the shared source authors (Copilot's path-specific instructions require an `applyTo`
75
98
  * frontmatter header; OpenCode's markdown agents want their own frontmatter vocabulary), the
@@ -87,10 +110,32 @@ const TRANSFORMS = {
87
110
  void sourceRel;
88
111
  return Buffer.from(`---\napplyTo: '**'\n---\n\n${stripFrontmatter(content.toString('utf8'))}`);
89
112
  },
90
- /** Shared agent spec frontmatter -> only the keys Gemini CLI's subagent schema accepts */
91
- 'gemini-agents': (sourceRel , content ) => {
113
+ /** `<rule>.md` -> the same body under Antigravity's `trigger: always_on` front-matter. Antigravity
114
+ * silently discards a `.agents/rules/*.md` file without valid front-matter, so the shared core
115
+ * files (which carry none) are loaded only through this header (107-B review C-2). */
116
+ 'antigravity-always-on-rules': (sourceRel , content ) => {
117
+ void sourceRel;
118
+ return Buffer.from(`---\ntrigger: always_on\n---\n\n${stripFrontmatter(content.toString('utf8'))}`);
119
+ },
120
+ /** Shared agent spec -> the same file without `access`, plus Claude's read-only limit keys (118 IC-005) */
121
+ 'claude-agents': (sourceRel , content ) => {
92
122
  void sourceRel;
93
- return Buffer.from(filterGeminiAgentFrontmatter(content.toString('utf8')));
123
+ return Buffer.from(applyAgentLimits(content.toString('utf8'), 'claude'));
124
+ },
125
+ /** Shared agent spec -> the same file without `access`; Codex's limit is `unenforced` (the TOML carries it), so no keys are added */
126
+ 'codex-agents': (sourceRel , content ) => {
127
+ void sourceRel;
128
+ return Buffer.from(applyAgentLimits(content.toString('utf8'), 'codex'));
129
+ },
130
+ /** Shared agent spec -> the same file without `access`, plus Copilot's read-only limit keys (118 IC-005) */
131
+ 'copilot-agents': (sourceRel , content ) => {
132
+ void sourceRel;
133
+ return Buffer.from(applyAgentLimits(content.toString('utf8'), 'copilot'));
134
+ },
135
+ /** Shared agent spec -> the same file without `access`; Antigravity's limit is `unenforced`, so no keys are added */
136
+ 'antigravity-agents': (sourceRel , content ) => {
137
+ void sourceRel;
138
+ return Buffer.from(applyAgentLimits(content.toString('utf8'), 'antigravity'));
94
139
  },
95
140
  /** Shared agent spec frontmatter -> OpenCode's markdown-agent vocabulary */
96
141
  'opencode-agents': (sourceRel , content ) => {
@@ -102,8 +147,80 @@ const TRANSFORMS = {
102
147
  void sourceRel;
103
148
  return Buffer.from(renderKiroAgent(content.toString('utf8')));
104
149
  },
150
+ /** Hook tiers (maint-hooks): the same Kiro agent with no `hooks`, for Kiro outside `hooks.harnesses`. */
151
+ 'kiro-agents-content': (sourceRel , content ) => {
152
+ void sourceRel;
153
+ return Buffer.from(renderKiroAgent(content.toString('utf8'), { hooks: false }));
154
+ },
155
+ /** Hook tiers: an authored Kiro agent JSON (core/harnesses/kiro/agents) without its `hooks` key. */
156
+ 'kiro-agent-content': (sourceRel , content ) => {
157
+ if (!sourceRel.endsWith('.json')) return content;
158
+ const agent = JSON.parse(content.toString('utf8'));
159
+ delete agent.hooks;
160
+ return Buffer.from(`${JSON.stringify(agent, null, 2)}\n`);
161
+ },
105
162
  };
106
163
 
164
+ /** Row 131: core/registry/contracts.json, read once. Its `agentSkillsSpec` holds the Agent Skills
165
+ * spec keys; each contract's `skillFields` the keys that harness documents. */
166
+ const SKILL_CONTRACTS = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, 'core', 'registry', 'contracts.json'), 'utf8'));
167
+
168
+ /** One `skill-frontmatter-<harness>` transform per contract that lists skillFields: a SKILL.md keeps
169
+ * the spec keys and that harness's skillFields at the top level, and every other top-level key moves
170
+ * under `metadata` as a quoted string. Files other than SKILL.md pass through unchanged. */
171
+ for (const contract of SKILL_CONTRACTS.contracts.filter((entry ) => Array.isArray(entry.skillFields))) {
172
+ const allowed = new Set ([...SKILL_CONTRACTS.agentSkillsSpec.skillFields, ...contract.skillFields]);
173
+ TRANSFORMS[`skill-frontmatter-${contract.harness}`] = (sourceRel , content ) => (
174
+ path.basename(sourceRel) === 'SKILL.md' ? Buffer.from(portableSkillFrontmatter(content.toString('utf8'), allowed, sourceRel)) : content);
175
+ }
176
+
177
+ /** A one-line YAML scalar's value. Single-quoted: `''` is one quote. Double-quoted: JSON's escapes
178
+ * (`\"`, `\\`, `\n`, `\t`, `\uXXXX`, ...), which YAML reads the same way; any other escape throws
179
+ * rather than be guessed. Anything else is plain and taken as written. */
180
+ function yamlScalar(value , key , file ) {
181
+ const single = value.match(/^'((?:[^']|'')*)'$/);
182
+ if (single) return single[1].replace(/''/g, "'");
183
+ if (!/^"(?:[^"\\]|\\.)*"$/.test(value)) return value;
184
+ try { return JSON.parse(value); } catch {
185
+ throw new Error(`${file}: '${key}' has a double-quoted value with an escape this projector does not decode`);
186
+ }
187
+ }
188
+
189
+ /** Move every top-level frontmatter key outside `allowed` under `metadata:`. A moved value is
190
+ * written as a JSON string (valid YAML), a plain scalar's continuation lines joined with a space; a
191
+ * nested map or a block scalar (`|`, `>`) is not handled, so it throws rather than guess, and so does a
192
+ * flow-style `metadata` (`{...}` on the key's line), which block entries cannot be appended to without a
193
+ * YAML parser. `file` names the source in each error. */
194
+ function portableSkillFrontmatter(text , allowed , file = 'SKILL.md') {
195
+ const match = text.match(/^---\n([\s\S]*?\n)---(?=\n|$)/);
196
+ if (!match) return text;
197
+ const entries = [];
198
+ for (const line of match[1].replace(/\n$/, '').split('\n')) {
199
+ // A top-level key, bare or quoted (`"key":`, `'key':`); `prefix` is the length up to its colon.
200
+ const found = line.match(/^(?:"((?:[^"\\]|\\.)+)"|'((?:[^']|'')+)'|([A-Za-z][A-Za-z0-9_-]*))[ \t]*:/);
201
+ const key = found ? (found[1]?.replace(/\\(.)/g, '$1') ?? found[2]?.replace(/''/g, "'") ?? found[3]) : null;
202
+ if (key || !entries.length) entries.push({ key, prefix: found ? found[0].length : 0, lines: [line] });
203
+ else entries[entries.length - 1].lines.push(line);
204
+ }
205
+ const metadataEntry = entries.find((entry) => entry.key === 'metadata');
206
+ if (metadataEntry && /^[ \t]*[^\s#]/.test(metadataEntry.lines[0].slice(metadataEntry.prefix))) {
207
+ throw new Error(`${file}: flow-style metadata cannot take moved keys; write metadata as a block map`);
208
+ }
209
+ const moved = entries.filter((entry) => entry.key && entry.key !== 'metadata' && !allowed.has(entry.key));
210
+ if (!moved.length) return text;
211
+ const metadata = moved.map(({ key, prefix, lines }) => {
212
+ const head = lines[0].slice(prefix).trim();
213
+ if (!head || /^[|>][-+0-9]*$/.test(head)) throw new Error(`${file}: '${key}' has no scalar value to move under metadata`);
214
+ const value = [head, ...lines.slice(1).map((part) => part.trim())].join(' ').trim();
215
+ return ` ${/^[A-Za-z][A-Za-z0-9_-]*$/.test(key ) ? key : JSON.stringify(key)}: ${JSON.stringify(yamlScalar(value, key , file))}`;
216
+ });
217
+ const kept = entries.filter((entry) => !moved.includes(entry));
218
+ const existing = kept.find((entry) => entry.key === 'metadata');
219
+ if (existing) existing.lines.push(...metadata);
220
+ else kept.push({ key: 'metadata', prefix: 9, lines: ['metadata:', ...metadata] });
221
+ return `---\n${kept.flatMap((entry) => entry.lines).join('\n')}\n${text.slice(match[0].length - 3)}`;
222
+ }
223
+
107
224
  function resolveTransform(name ) {
108
225
  if (!name) return null;
109
226
  const transform = TRANSFORMS[name];
@@ -130,23 +247,113 @@ function renderClaudeOutputStyle(sourceRel , content ) {
130
247
  return ['---', `name: DoFlow: ${base}`, `description: ${JSON.stringify(description)}`, 'keep-coding-instructions: true', '---', '', text.replace(/\n*$/, ''), ''].join('\n');
131
248
  }
132
249
 
133
- /** The frontmatter keys Gemini CLI documents for a custom subagent (https://geminicli.com/docs/core/subagents, accessed 2026-10-06).
134
- * Its agent schema is strict, so a spec key outside this list (DoFlow's `effort`) would be rejected. */
135
- const GEMINI_AGENT_KEYS = new Set(['name', 'description', 'kind', 'tools', 'mcpServers', 'model', 'temperature', 'max_turns', 'timeout_mins']);
250
+ /** One harness's `agentLimits` block from core/registry/harnesses.json, read once. */
251
+ let harnessAgentLimits = null;
252
+ function agentLimitsOf(harnessId ) {
253
+ if (!harnessAgentLimits) {
254
+ harnessAgentLimits = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, 'core', 'registry', 'harnesses.json'), 'utf8')).harnesses;
255
+ }
256
+ const harness = (harnessAgentLimits ).find((item ) => item.id === harnessId);
257
+ if (!harness) throw new Error(`agentLimits: unknown harness '${harnessId}'`);
258
+ return harness.agentLimits || {};
259
+ }
260
+
261
+ /** The `apply` keys a harness writes into the file of a `read-only` agent spec (118 IC-003), read from
262
+ * `agentLimits.readOnly`. `null` when the harness's limit is `unenforced`: nothing is written, and the
263
+ * registry says why. */
264
+ function readOnlyApply(harnessId ) {
265
+ const entry = agentLimitsOf(harnessId).readOnly;
266
+ return entry?.status === 'native' ? entry.apply : null;
267
+ }
268
+
269
+ /** The MCP servers DoFlow's registry names, read once and sorted: every `core/registry/mcp.json`
270
+ * server (the ones an install writes) and every capability provider's `mcp.server` in
271
+ * capabilities.json (semble, graphify). All are retrieval servers, so a read-only agent keeps them. */
272
+ let registryMcpServers = null;
273
+ function approvedMcpServers() {
274
+ if (!registryMcpServers) {
275
+ const read = (file ) => JSON.parse(fs.readFileSync(path.join(REPO_ROOT, 'core', 'registry', file), 'utf8'));
276
+ const providers = Object.values(read('capabilities.json').capabilities || {}).flatMap((capability ) => capability.providers || []);
277
+ registryMcpServers = [...new Set ([
278
+ ...read('mcp.json').servers.map((server ) => server.id),
279
+ ...providers.map((provider ) => provider.mcp?.server).filter(Boolean),
280
+ ])].sort();
281
+ }
282
+ return registryMcpServers ;
283
+ }
284
+
285
+ /** One category's tools on a harness: the `map` entry, or for `mcp` the selector of each approved server. */
286
+ function categoryTools(entry , category ) {
287
+ return category === 'mcp' ? approvedMcpServers().map((server) => entry.mcpSelector.replace('{server}', server)) : entry.map[category];
288
+ }
289
+
290
+ /** The categories of a spec's `tool-scope` value (`read, shell, web`), or null when it declares none. */
291
+ function scopeCategories(value ) {
292
+ if (value === undefined) return null;
293
+ return value.split(',').map((item ) => item.trim()).filter(Boolean);
294
+ }
295
+
296
+ /** The keys a harness writes for a declared tool scope (row 135), from `agentLimits.toolScope`: the
297
+ * category tools as a comma string or a list, or a closed permission object (`"*": deny`, then an
298
+ * allow per declared tool). `null` when the spec declares no scope or the harness cannot express one
299
+ * (`unenforced`): then nothing is written, never a broader scope. */
300
+ function toolScopeApply(harnessId , scope ) {
301
+ if (!scope) return null;
302
+ const entry = agentLimitsOf(harnessId).toolScope;
303
+ if (entry?.status !== 'native') return null;
304
+ for (const category of scope) {
305
+ if (category !== 'mcp' && !Object.hasOwn(entry.map, category)) throw new Error(`tool-scope: unknown category '${category}' for harness '${harnessId}'`);
306
+ }
307
+ const tools = scope.flatMap((category) => categoryTools(entry, category));
308
+ if (entry.form === 'closed') return { [entry.field]: { '*': 'deny', ...Object.fromEntries(tools.map((tool ) => [tool, 'allow'])) } };
309
+ return { [entry.field]: entry.form === 'comma' ? tools.join(', ') : tools };
310
+ }
311
+
312
+ /** The keys an agent file gains: the scope keys, then the read-only keys. Where both write one key the
313
+ * narrower wins: two lists keep only the tools both name, plus the approved MCP selectors (retrieval
314
+ * servers a read-only agent keeps); two permission objects keep every `deny`. */
315
+ function agentLimitKeys(harnessId , { readOnly, scope } ) {
316
+ const scoped = toolScopeApply(harnessId, scope);
317
+ const limits = readOnly ? readOnlyApply(harnessId) : null;
318
+ if (!scoped || !limits) return scoped || limits;
319
+ const merged = { ...scoped };
320
+ for (const [key, value] of Object.entries(limits)) {
321
+ const have = merged[key];
322
+ if (have === undefined) merged[key] = value;
323
+ else if (Array.isArray(have) && Array.isArray(value)) {
324
+ const mcp = scope?.includes('mcp') ? categoryTools(agentLimitsOf(harnessId).toolScope, 'mcp') : [];
325
+ merged[key] = have.filter((tool ) => value.includes(tool) || mcp.includes(tool));
326
+ }
327
+ else if (have && value && typeof have === 'object' && typeof value === 'object') merged[key] = { ...have, ...value };
328
+ else throw new Error(`agentLimits: harness '${harnessId}' writes '${key}' from both the tool scope and the read-only limit`);
329
+ }
330
+ return merged;
331
+ }
136
332
 
137
- /** Keep only Gemini's documented top-level frontmatter keys, with their indented continuation
138
- * lines, in source order. The body after the closing `---` passes through byte for byte, and a
139
- * file without frontmatter is returned unchanged. */
140
- function filterGeminiAgentFrontmatter(text ) {
333
+ /** One frontmatter key as YAML lines: a scalar inline, an array as a flow sequence, an object as an
334
+ * indented block. A key that is not a plain name (`*`, `context7_*`) is double-quoted. */
335
+ function yamlLines(key , value , indent = '') {
336
+ const name = /^[A-Za-z0-9_-]+$/.test(key) ? key : JSON.stringify(key);
337
+ if (Array.isArray(value)) return [`${indent}${name}: ${JSON.stringify(value)}`];
338
+ if (value && typeof value === 'object') {
339
+ return [`${indent}${name}:`, ...Object.entries(value).flatMap(([k, v]) => yamlLines(k, v, `${indent} `))];
340
+ }
341
+ return [`${indent}${name}: ${value}`];
342
+ }
343
+
344
+ /** Rewrite a shared agent spec's frontmatter for one harness: `access` and `tool-scope` are never projected under
345
+ * those names; the spec gains the harness's keys for its tool scope and, when `read-only`, its `apply` keys. Body and every other
346
+ * key pass through byte for byte; a file without frontmatter is returned unchanged. */
347
+ function applyAgentLimits(text , harnessId ) {
141
348
  const match = text.match(/^---\r?\n([\s\S]*?\r?\n)---(?=\r?\n|$)/);
142
349
  if (!match) return text;
143
- let keep = false;
144
- const kept = match[1].split(/(?<=\n)/).filter((line ) => {
145
- const key = line.match(/^([A-Za-z][A-Za-z0-9_-]*):/);
146
- if (key) keep = GEMINI_AGENT_KEYS.has(key[1]);
147
- return keep;
148
- });
149
- return `${text.slice(0, text.indexOf('\n') + 1)}${kept.join('')}${text.slice(match[0].length - 3)}`;
350
+ const lines = match[1].split(/(?<=\n)/);
351
+ const readOnly = lines.some((line ) => /^access:\s*read-only\s*$/.test(line));
352
+ const scopeLine = lines.find((line ) => /^tool-scope:/.test(line));
353
+ const apply = agentLimitKeys(harnessId, { readOnly, scope: scopeCategories(scopeLine?.replace(/^tool-scope:/, '')) });
354
+ const added = apply ? Object.entries(apply).flatMap(([key, value]) => yamlLines(key, value)).map((line ) => `${line}\n`) : [];
355
+ const kept = lines.filter((line ) => !/^(access|tool-scope):/.test(line));
356
+ return `${text.slice(0, text.indexOf('\n') + 1)}${kept.join('')}${added.join('')}${text.slice(match[0].length - 3)}`;
150
357
  }
151
358
 
152
359
  /** The command a Kiro hook runs for one front-door script: the project copy when the git root
@@ -164,21 +371,21 @@ function kiroHook({ name, trigger, matcher, script, timeout }
164
371
  };
165
372
  }
166
373
 
167
- /** The three preToolUse hooks every DoFlow-rendered Kiro agent carries. The MCP guard uses the
374
+ /** The two preToolUse hooks every DoFlow-rendered Kiro agent carries. The MCP guard uses the
168
375
  * "*" matcher: Kiro documents "@<server>" (every tool of one server) and "*" but no all-MCP
169
376
  * matcher, and mcp-tool-guard.sh exits 0 immediately for any non-MCP tool name, so "*" costs one
170
377
  * fast no-op for built-in tools and still covers every MCP server. */
171
378
  function kiroPreToolUseHooks() {
172
379
  return [
173
380
  kiroHook({ name: 'doflow-pre-implementation-gate', trigger: 'preToolUse', matcher: 'write', script: 'pre-implement-gate.sh', timeout: 10 }),
174
- kiroHook({ name: 'doflow-pre-bash-guard', trigger: 'preToolUse', matcher: 'shell', script: 'pre-bash-guard.sh', timeout: 10 }),
175
381
  kiroHook({ name: 'doflow-mcp-tool-guard', trigger: 'preToolUse', matcher: '*', script: 'mcp-tool-guard.sh', timeout: 10 }),
176
382
  ];
177
383
  }
178
384
 
179
385
  /** Map one core/shared agent-spec file onto a kiro-cli custom agent. Spec keys other than
180
- * name/description (model: inherit, effort) are dropped: Kiro inherits the session model. */
181
- function renderKiroAgent(text ) {
386
+ * name/description (model: inherit, effort) are dropped: Kiro inherits the session model. `access` is
387
+ * never projected; the spec's tool scope (narrowed by `apply.tools` when `read-only`) replaces `tools: ["*"]`. */
388
+ function renderKiroAgent(text , { hooks = true } = {}) {
182
389
  const fm = {};
183
390
  let body = text;
184
391
  if (text.startsWith('---')) {
@@ -191,17 +398,16 @@ function renderKiroAgent(text ) {
191
398
  body = text.slice(end + 4).replace(/^\n+/, '');
192
399
  }
193
400
  }
401
+ const limits = agentLimitKeys('kiro', { readOnly: fm.access === 'read-only', scope: scopeCategories(fm['tool-scope']) });
194
402
  const agent = {
195
403
  name: fm.name || '', description: fm.description || '',
196
404
  prompt: `${body.replace(/\s+$/, '')}\n`,
197
- tools: ['*'],
198
- hooks: kiroPreToolUseHooks(),
405
+ tools: limits?.tools ?? ['*'],
406
+ ...(hooks ? { hooks: kiroPreToolUseHooks() } : {}),
199
407
  };
200
408
  return `${JSON.stringify(agent, null, 2)}\n`;
201
409
  }
202
410
 
203
- const OPENCODE_READONLY_AGENTS = new Set(['spec-analyst', 'system-architect', 'quality-guardian', 'research-writer']);
204
-
205
411
  /** Map one core/shared agent-spec file onto OpenCode's documented markdown-agent frontmatter
206
412
  * (description / mode / permission; model omitted because OpenCode has no 'inherit'). Unknown
207
413
  * spec keys (tools, effort) are dropped rather than passed through, so OpenCode never sees
@@ -222,9 +428,8 @@ function renderOpencodeAgent(text ) {
222
428
  const lines = ['---'];
223
429
  if (fm.description) lines.push(`description: ${JSON.stringify(fm.description)}`);
224
430
  lines.push('mode: subagent');
225
- if (OPENCODE_READONLY_AGENTS.has(fm.name)) {
226
- lines.push('permission:', ' edit: deny', ' bash: deny');
227
- }
431
+ const limits = agentLimitKeys('opencode', { readOnly: fm.access === 'read-only', scope: scopeCategories(fm['tool-scope']) });
432
+ if (limits) lines.push(...Object.entries(limits).flatMap(([key, value]) => yamlLines(key, value)));
228
433
  lines.push('---', '', body.replace(/\n*$/, ''), '');
229
434
  return lines.join('\n');
230
435
  }
@@ -233,11 +438,15 @@ function renderOpencodeAgent(text ) {
233
438
  * the destination tree beyond checking existence. `relPath` is destination-relative and doubles as
234
439
  * the ledger identity, so a layout change relocates the recorded resource too — planTree's
235
440
  * relocation handling then removes the old path rather than orphaning it. */
236
- function discoverTree({ sourceDir, destDir, fsImpl = fs, layout, transform } ) {
441
+ function discoverTree({ sourceDir, destDir, fsImpl = fs, layout, transform, select } ) {
237
442
  if (!fsImpl.existsSync(sourceDir)) throw new Error(`copy-tree source is missing: ${sourceDir}`);
238
443
  const mapRel = typeof layout === 'function' ? layout : resolveLayout(layout);
239
444
  const mapContent = typeof transform === 'function' ? transform : resolveTransform(transform);
240
- const files = walkRelFiles(sourceDir, fsImpl).map((sourceRel) => {
445
+ const selected = resolveSelect(select)?.(sourceDir, fsImpl);
446
+ const files = walkRelFiles(sourceDir, fsImpl).filter((sourceRel) => {
447
+ if (!selected) return true;
448
+ return selected.has(sourceRel.split(path.sep).join('/'));
449
+ }).map((sourceRel) => {
241
450
  const relPath = mapRel(sourceRel);
242
451
  const sourceAbs = path.join(sourceDir, sourceRel);
243
452
  const destAbs = path.join(destDir, relPath);
@@ -247,6 +456,27 @@ function discoverTree({ sourceDir, destDir, fsImpl = fs, layout, transform }
247
456
  return { files };
248
457
  }
249
458
 
459
+ /** Selected files that map to one destination cannot both be installed safely. */
460
+ function selectedDestinationCollisions(files , sourceDir , destDir ) {
461
+ const sourcesByDestination = new Map ();
462
+ for (const file of files) {
463
+ const key = path.resolve(file.destAbs);
464
+ const sources = sourcesByDestination.get(key) || [];
465
+ sources.push(file);
466
+ sourcesByDestination.set(key, sources);
467
+ }
468
+
469
+ const destinations = new Set ();
470
+ const conflicts = [];
471
+ for (const [destination, sources] of sourcesByDestination) {
472
+ if (sources.length < 2) continue;
473
+ destinations.add(destination);
474
+ const names = sources.map((file) => path.relative(sourceDir, file.sourceAbs).split(path.sep).join('/'));
475
+ conflicts.push(`selected files ${names.map((name) => `'${name}'`).join(' and ')} map to the same destination '${path.relative(destDir, destination).split(path.sep).join('/')}'`);
476
+ }
477
+ return { destinations, conflicts };
478
+ }
479
+
250
480
  /** A file's identity on disk (device + inode), or null when the path does not resolve to one. */
251
481
  function fileId(file , fsImpl ) {
252
482
  try {
@@ -292,7 +522,7 @@ function sameFileIndex(paths , fsImpl = fs)
292
522
  * `siblingReplaced` lists the harnesses whose recorded bytes this plan replaces: the file matches
293
523
  * neither the source nor this harness's own record, only a sibling's (forced or not).
294
524
  */
295
- function planTree({ sourceDir, destDir, previousResources = [], operation = 'apply', fsImpl = fs, layout, transform, force = false, keepModified = false, siblingFingerprints = new Map() } ) {
525
+ function planTree({ sourceDir, destDir, previousResources = [], operation = 'apply', fsImpl = fs, layout, transform, select, force = false, keepModified = false, siblingFingerprints = new Map() } ) {
296
526
  const prevByPath = new Map (previousResources.map((resource ) => [resource.relPath, resource]));
297
527
  const changes = [];
298
528
  const conflicts = [];
@@ -307,7 +537,7 @@ function planTree({ sourceDir, destDir, previousResources = [], operation = 'app
307
537
  const sourceFingerprint = (destAbs ) => {
308
538
  if (sourceByDest === undefined) {
309
539
  sourceByDest = sourceDir && fsImpl.existsSync(sourceDir)
310
- ? new Map (discoverTree({ sourceDir, destDir, fsImpl, layout, transform }).files.map((file) => [file.destAbs, file.fingerprint]))
540
+ ? new Map (discoverTree({ sourceDir, destDir, fsImpl, layout, transform, select }).files.map((file) => [file.destAbs, file.fingerprint]))
311
541
  : new Map();
312
542
  }
313
543
  return sourceByDest.get(destAbs);
@@ -335,7 +565,7 @@ function planTree({ sourceDir, destDir, previousResources = [], operation = 'app
335
565
  // Untampered on removal means the same two signals the apply path below already accepts: the
336
566
  // bytes this harness last recorded, or the bytes the current source would write. The second
337
567
  // one matters because a destination tree can be claimed by several harnesses (scripts.doflow
338
- // is one `<project>/.doflow/scripts` for claude, codex and gemini), so a sibling's update
568
+ // is one `<project>/.doflow/scripts` for claude, codex and others), so a sibling's update
339
569
  // legitimately rewrites files this harness's rows still describe — the same "a sibling
340
570
  // changed bytes my row still describes" case the apply path had to be taught, arriving here
341
571
  // as an un-releasable claim instead of a refused install. A hand edit matches neither and is
@@ -366,10 +596,18 @@ function planTree({ sourceDir, destDir, previousResources = [], operation = 'app
366
596
  return { changes, conflicts, kept, siblingReplaced: [] };
367
597
  }
368
598
 
369
- const { files } = discoverTree({ sourceDir, destDir, fsImpl, layout, transform });
599
+ const { files } = discoverTree({ sourceDir, destDir, fsImpl, layout, transform, select });
600
+ const collisions = select == null
601
+ ? { destinations: new Set (), conflicts: [] }
602
+ : selectedDestinationCollisions(files, sourceDir, destDir);
603
+ conflicts.push(...collisions.conflicts);
370
604
  producedSameFile = sameFileIndex(files.map((file) => file.destAbs), fsImpl);
371
605
  const satisfiedAtSameLocation = new Set();
372
606
  for (const file of files) {
607
+ if (collisions.destinations.has(path.resolve(file.destAbs))) {
608
+ satisfiedAtSameLocation.add(file.relPath);
609
+ continue;
610
+ }
373
611
  const prev = prevByPath.get(file.relPath);
374
612
  // prev.target === undefined means "location unknown" (pre-this-fix ledger data, or a
375
613
  // caller-constructed previousResources entry) — treat as same-location, matching pre-fix
@@ -442,16 +680,18 @@ function applyTree({ changes = [], fsImpl = fs, transform } )
442
680
  /** Why a removal the runtime prune planned may no longer run, or null when it still may: the
443
681
  * prune's plan-time proof (src/lifecycle/index.js pruneRetiredRuntimeFiles) re-run right before the
444
682
  * unlink, so a directory swapped for a symlink, or a file linked to a shipped one, after the plan was
445
- * made is not deleted through. `guard` is `{ root, source }`: the tree and the source it projects.
683
+ * made is not deleted through. `guard` is `{ root, source, scope }`: the tree, the source it projects
684
+ * and the scope root the tree must resolve inside (a tree linked out of its scope is refused).
446
685
  * One walk of the source and one stat per shipped path per tree per call, cached in `cache`. */
447
686
  function pruneGuardRefusal(change , cache , fsImpl ) {
448
- const { root, source } = change.pruneGuard;
449
- const key = `${root}\0${source}`;
687
+ const { root, source, scope } = change.pruneGuard;
688
+ const key = `${root}\0${source}\0${scope ?? ''}`;
450
689
  if (!cache.has(key)) {
451
690
  const produced = fsImpl.existsSync(source) ? discoverTree({ sourceDir: source, destDir: root, fsImpl }).files.map((file ) => file.destAbs) : [];
452
- cache.set(key, { realRoot: fsImpl.realpathSync(root), sameFile: sameFileIndex(produced, fsImpl) });
691
+ cache.set(key, { realRoot: fsImpl.realpathSync(root), realScope: scope ? fsImpl.realpathSync(scope) : null, sameFile: sameFileIndex(produced, fsImpl) });
453
692
  }
454
- const { realRoot, sameFile } = cache.get(key);
693
+ const { realRoot, realScope, sameFile } = cache.get(key);
694
+ if (realScope && !realRoot.startsWith(`${realScope}${path.sep}`)) return `${root} resolves outside the scope root ${scope}`;
455
695
  if (!fsImpl.realpathSync(change.target).startsWith(`${realRoot}${path.sep}`)) return `it resolves outside ${root}`;
456
696
  const shipped = sameFile(change.target);
457
697
  return shipped ? `it is the same file as ${shipped}, which this version ships` : null;
@@ -500,12 +740,17 @@ function pruneEmptyAncestors(startDir , { fsImpl = fs }
500
740
  * `plannedWrites` lists the destinations the run being verified wrote. An absent source file is
501
741
  * not reported in general, because a user may delete one, but an absent file this run wrote means
502
742
  * the run lost it (a case-only rename once deleted the file it had just written) and is a conflict. */
503
- function verifyTree({ sourceDir, destDir, fsImpl = fs, layout, transform, plannedWrites = [] } ) {
504
- const { files } = discoverTree({ sourceDir, destDir, fsImpl, layout, transform });
743
+ function verifyTree({ sourceDir, destDir, fsImpl = fs, layout, transform, select, plannedWrites = [] } ) {
744
+ const { files } = discoverTree({ sourceDir, destDir, fsImpl, layout, transform, select });
505
745
  const written = new Set(plannedWrites);
506
746
  const resources = [];
507
747
  const conflicts = [];
748
+ const collisions = select == null
749
+ ? { destinations: new Set (), conflicts: [] }
750
+ : selectedDestinationCollisions(files, sourceDir, destDir);
751
+ conflicts.push(...collisions.conflicts);
508
752
  for (const file of files) {
753
+ if (collisions.destinations.has(path.resolve(file.destAbs))) continue;
509
754
  if (!file.exists) {
510
755
  if (written.has(file.destAbs)) conflicts.push(`${file.relPath} was written by this run but is missing`);
511
756
  continue;
@@ -517,7 +762,7 @@ function verifyTree({ sourceDir, destDir, fsImpl = fs, layout, transform, planne
517
762
  return { ok: conflicts.length === 0, resources, conflicts };
518
763
  }
519
764
 
520
- // ---- per-adapter copy-tree glue, shared so codex/claude/gemini don't each carry their own copy ----
765
+ // ---- per-adapter copy-tree glue, shared so each adapter doesn't carry its own copy ----
521
766
 
522
767
  /** Filter a harness's asset list down to the ones this engine handles. */
523
768
  function copyTreeAssets(assets ) {
@@ -617,4 +862,4 @@ function ledgerFileResources(resources , harness , assetId )
617
862
  .map((resource ) => ({ relPath: resource.identity, fingerprint: resource.fingerprint, target: resource.target }));
618
863
  }
619
864
 
620
- module.exports = { kiroHook, kiroHookCommand, kiroPreToolUseHooks, walkRelFiles, discoverTree, sameFileIndex, planTree, applyTree, removeTree, verifyTree, copyTreeAssets, copyTreeDestDir, sharedTreeDestDir, ledgerFileResources, ledgerSiblingFingerprints, siblingReplacedNotices, resolveLayout, LAYOUTS, resolveTransform, TRANSFORMS, fingerprint, pruneEmptyAncestors, readJson, sourceDirFor };
865
+ module.exports = { readOnlyApply, toolScopeApply, approvedMcpServers, agentLimitKeys, kiroHook, kiroHookCommand, kiroPreToolUseHooks, walkRelFiles, discoverTree, sameFileIndex, planTree, applyTree, removeTree, verifyTree, copyTreeAssets, copyTreeDestDir, sharedTreeDestDir, ledgerFileResources, ledgerSiblingFingerprints, siblingReplacedNotices, resolveLayout, LAYOUTS, resolveSelect, SELECTORS, resolveTransform, TRANSFORMS, portableSkillFrontmatter, fingerprint, pruneEmptyAncestors, readJson, sourceDirFor };
@@ -0,0 +1,71 @@
1
+ 'use strict';
2
+
3
+ // The shared, harness-neutral core set: the breadth-first @-import walk rooted at DOFLOW_CORE.md.
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+
7
+ // Generated per install, excluded by name rather than forgotten.
8
+ //
9
+ // MCP_INDEX.md and everything it imports under guidance/mcp/ are written at install time from the
10
+ // MCP servers the user actually selected. Their size is a property of the machine, not of
11
+ // DoFlow-authored content, and it differs between two correct installs — so the import walk stops
12
+ // at MCP_INDEX.md and does not descend into its @mcp/* imports.
13
+ const GENERATED_EXCLUSIONS = new Map ([
14
+ ['MCP_INDEX.md', 'generated per install from the servers actually selected; size varies per machine'],
15
+ ]);
16
+
17
+ function importsIn(text ) {
18
+ return [...text.matchAll(/^@(\S+)$/gm)].map(([, target]) => target);
19
+ }
20
+
21
+ /** The first path segment of `rel` (relative to `root`) that is a symlink, or null. */
22
+ function symlinkedSegment(root , rel , fsImpl ) {
23
+ if (typeof fsImpl.lstatSync !== 'function') return null;
24
+ let current = root;
25
+ for (const segment of rel.split('/')) {
26
+ current = path.join(current, segment);
27
+ if (fsImpl.lstatSync(current).isSymbolicLink()) return path.relative(root, current).split(path.sep).join('/');
28
+ }
29
+ return null;
30
+ }
31
+
32
+ function guidanceManifest(root , fsImpl = fs) {
33
+ const rootFile = 'DOFLOW_CORE.md';
34
+ if (!fsImpl.existsSync(path.join(root, rootFile))) {
35
+ throw new Error(`guidanceManifest root is missing ${rootFile}: ${root}`);
36
+ }
37
+
38
+ const files = [];
39
+ const generated = [];
40
+ const dangling = [];
41
+ const seen = new Set ();
42
+ const queue = [rootFile];
43
+
44
+ while (queue.length) {
45
+ const rel = path.posix.normalize(queue.shift() .replace(/\\/g, '/'));
46
+ if (seen.has(rel)) continue;
47
+ seen.add(rel);
48
+ if (GENERATED_EXCLUSIONS.has(rel)) { generated.push(rel); continue; }
49
+
50
+ // The core set is a closed set of files under the guidance root. An import that leaves it,
51
+ // or that is (or sits under) a symlink, is not something the copy-tree engine installs, so it
52
+ // is refused here rather than reported as part of a set that is never written (107-B review C-4).
53
+ if (rel === '..' || rel.startsWith('../') || path.posix.isAbsolute(rel)) {
54
+ throw new Error(`guidanceManifest: ${rel} escapes the guidance root: ${root}`);
55
+ }
56
+ const full = path.join(root, ...rel.split('/'));
57
+ if (!fsImpl.existsSync(full)) { dangling.push(rel); continue; }
58
+ const linked = symlinkedSegment(root, rel, fsImpl);
59
+ if (linked) throw new Error(`guidanceManifest: ${rel} is or sits under a symlink (${linked}); the copy-tree engine does not install symlinks: ${root}`);
60
+ const text = fsImpl.readFileSync(full, 'utf8');
61
+ files.push(rel);
62
+ for (const target of importsIn(text)) {
63
+ if (path.posix.isAbsolute(target)) throw new Error(`guidanceManifest: ${rel} imports the absolute path ${target}: ${root}`);
64
+ queue.push(path.posix.normalize(path.posix.join(path.posix.dirname(rel), target)));
65
+ }
66
+ }
67
+
68
+ return { files, generated, dangling };
69
+ }
70
+
71
+ module.exports = { GENERATED_EXCLUSIONS, guidanceManifest };
@@ -0,0 +1,19 @@
1
+ 'use strict';
2
+
3
+ const path = require('node:path');
4
+
5
+ const GUIDANCE_TOKEN = '{{GUIDANCE}}';
6
+
7
+ function renderGuidancePointer(content , { scope, scopeRoot } ) {
8
+ if (scope !== 'global' && scope !== 'project') throw new Error(`Unknown guidance pointer scope '${scope}'`);
9
+ if (typeof content !== 'string') throw new TypeError('Guidance pointer content must be a string');
10
+
11
+ const guidancePath = scope === 'global'
12
+ ? path.resolve(scopeRoot, '.doflow', 'guidance').replace(/\\/g, '/')
13
+ : '.doflow/guidance';
14
+ const rendered = content.split(GUIDANCE_TOKEN).join(guidancePath);
15
+ if (rendered.includes(GUIDANCE_TOKEN)) throw new Error('Guidance pointer contains an unresolved token');
16
+ return rendered;
17
+ }
18
+
19
+ module.exports = { renderGuidancePointer };
@@ -2,10 +2,9 @@
2
2
 
3
3
  // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
4
 
5
- // Shared hook-command verification — the one piece of codex/hooks.ts and gemini/hooks.ts that
6
- // proved byte-identical on inspection (unlike SUPPORTED_EVENTS, validateHooksConfig, and
7
- // classifyClaudeGuardrails, which encode genuine per-harness differences and stay in each
8
- // adapter). verifyHookCommands and the two private helpers it calls, commandScriptNames and
5
+ // Hook-command verification, kept apart from codex/hooks.ts because it does not depend on the
6
+ // harness (unlike SUPPORTED_EVENTS, validateHooksConfig, and classifyClaudeGuardrails, which
7
+ // encode genuine per-harness differences and stay in each adapter). verifyHookCommands and the two private helpers it calls, commandScriptNames and
9
8
  // hookHandlers, check that a hook config's command handlers resolve to a real, executable script
10
9
  // in the harness's own hooks directory — that check does not vary by harness, so it lives once.
11
10
  const fs = require('node:fs');
@@ -23,12 +22,9 @@ function hookHandlers(config ) {
23
22
  (Array.isArray(group?.hooks) ? group.hooks : []).map((handler ) => ({ event, handler }))));
24
23
  }
25
24
 
26
- // On `trusted`: both harnesses gate hook execution behind their own trust prompt — Codex through
27
- // its config, Gemini by fingerprinting a hook's name and command and warning before running one
28
- // that changed (geminicli.com/docs/hooks/). DoFlow verifies neither gate live; doing so would need
29
- // a running CLI session. It reports trust as a prerequisite whose shape it observed, not one it
30
- // bypassed or assumed satisfied. That reading is identical for both harnesses, which is part of
31
- // why this function is shared.
25
+ // On `trusted`: Codex gates hook execution behind its own trust prompt, through its config.
26
+ // DoFlow does not verify that gate live; doing so would need a running CLI session. It reports
27
+ // trust as a prerequisite whose shape it observed, not one it bypassed or assumed satisfied.
32
28
  // Windows carries no executable permission bits — statSync().mode always reports 0666/0444 and
33
29
  // chmodSync can only toggle the read-only flag — so the POSIX "not executable" failure mode
34
30
  // cannot exist there. Requiring the bit would refuse every hooks install on win32 (the scripts