@khoavu882/doflow 2.0.0-beta.4 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (497) hide show
  1. package/README.md +12 -11
  2. package/bin/doflow.js +21 -11
  3. package/core/.antigravity-plugin/plugin.json +1 -3
  4. package/core/.claude-plugin/plugin.json +1 -1
  5. package/core/.codex-plugin/plugin.json +1 -1
  6. package/core/.plugin/plugin.json +1 -1
  7. package/core/harnesses/claude/hooks/lib.sh +43 -24
  8. package/core/harnesses/claude/hooks/mcp-tool-guard.sh +7 -17
  9. package/core/harnesses/claude/hooks/permission-audit.sh +3 -0
  10. package/core/harnesses/claude/hooks/post-compact.sh +35 -32
  11. package/core/harnesses/claude/hooks/post-edit-lint.sh +2 -0
  12. package/core/harnesses/claude/hooks/post-shell-observe.sh +48 -0
  13. package/core/harnesses/claude/hooks/post-tool-failure.sh +3 -0
  14. package/core/harnesses/claude/hooks/pre-bash-guard.sh +7 -17
  15. package/core/harnesses/claude/hooks/pre-compact.sh +23 -27
  16. package/core/harnesses/claude/hooks/pre-implement-gate.sh +7 -18
  17. package/core/harnesses/claude/hooks/session-end.sh +23 -10
  18. package/core/harnesses/claude/hooks/session-start.sh +5 -3
  19. package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
  20. package/core/harnesses/claude/hooks/stop-check.sh +5 -3
  21. package/core/harnesses/claude/hooks/subagent-audit.sh +5 -5
  22. package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -3
  23. package/core/harnesses/claude/settings/settings.json +18 -0
  24. package/core/harnesses/claude/statusline/doflow-statusline.sh +1 -0
  25. package/core/harnesses/codex/agents/core-implementer.toml +1 -0
  26. package/core/harnesses/codex/agents/delivery-lead.toml +10 -0
  27. package/core/harnesses/codex/agents/quality-guardian.toml +1 -1
  28. package/core/harnesses/codex/agents/research-writer.toml +1 -1
  29. package/core/harnesses/codex/agents/reviewer.toml +10 -0
  30. package/core/harnesses/codex/agents/spec-analyst.toml +1 -1
  31. package/core/harnesses/codex/agents/system-architect.toml +1 -1
  32. package/core/harnesses/codex/hooks/lib.sh +44 -25
  33. package/core/harnesses/codex/hooks/mcp-tool-guard.sh +7 -15
  34. package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
  35. package/core/harnesses/codex/hooks/pre-bash-guard.sh +7 -15
  36. package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
  37. package/core/harnesses/codex/hooks/pre-implement-gate.sh +7 -16
  38. package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
  39. package/core/harnesses/codex/hooks/session-start.sh +5 -3
  40. package/core/harnesses/codex/hooks/stop-check.sh +6 -5
  41. package/core/harnesses/codex/hooks/subagent-audit.sh +18 -6
  42. package/core/harnesses/codex/hooks/user-prompt-submit.sh +5 -4
  43. package/core/harnesses/copilot/hooks/doflow-lib.sh +93 -0
  44. package/core/harnesses/copilot/hooks/doflow-mcp-tool-guard.sh +9 -0
  45. package/core/harnesses/copilot/hooks/doflow-pre-bash-guard.sh +9 -0
  46. package/core/harnesses/copilot/hooks/doflow-pre-implement-gate.sh +9 -0
  47. package/core/harnesses/copilot/hooks/doflow-session-start.sh +9 -0
  48. package/core/harnesses/copilot/hooks/doflow-stop-check.sh +9 -0
  49. package/core/harnesses/copilot/hooks/doflow-subagent-start.sh +9 -0
  50. package/core/harnesses/copilot/hooks/doflow-subagent-stop.sh +9 -0
  51. package/core/harnesses/copilot/hooks/doflow.json +57 -0
  52. package/core/harnesses/kiro/agents/doflow.json +1 -1
  53. package/core/harnesses/kiro/hooks/lib.sh +26 -6
  54. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +21 -5
  55. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +21 -5
  56. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +21 -5
  57. package/core/harnesses/kiro/hooks/session-start.sh +12 -5
  58. package/core/harnesses/kiro/hooks/stop-check.sh +22 -30
  59. package/core/harnesses/pi/extensions/doflow-guard.js +167 -0
  60. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +27 -12
  61. package/core/harnesses/shared/hooks/adapters/antigravity.js +14 -57
  62. package/core/harnesses/shared/hooks/bridge/bridge.sh +277 -0
  63. package/core/harnesses/shared/hooks/bridge/envelope.jq +129 -0
  64. package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
  65. package/core/harnesses/shared/hooks/bridge/respond.sh +70 -0
  66. package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +28 -14
  67. package/core/harnesses/shared/hooks/policies/capture-failure.sh +51 -16
  68. package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
  69. package/core/harnesses/shared/hooks/policies/contract.sh +78 -0
  70. package/core/harnesses/shared/hooks/policies/deny-json.sh +35 -9
  71. package/core/harnesses/shared/hooks/policies/emission-rules.sh +37 -0
  72. package/core/harnesses/shared/hooks/policies/emission.jq +209 -0
  73. package/core/harnesses/shared/hooks/policies/emission.sh +383 -0
  74. package/core/harnesses/shared/hooks/policies/identity.sh +177 -0
  75. package/core/harnesses/shared/hooks/policies/lib.sh +65 -40
  76. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +48 -35
  77. package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +284 -33
  78. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +180 -110
  79. package/core/harnesses/shared/hooks/policies/session-context.sh +66 -7
  80. package/core/harnesses/shared/hooks/policies/stop-check.sh +61 -23
  81. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +39 -6
  82. package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
  83. package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +97 -159
  84. package/core/harnesses/shared/hooks/stream-hook-runner.js +114 -187
  85. package/core/harnesses/shared/locator/doflow-run +2 -3
  86. package/core/registry/advice.json +3 -3
  87. package/core/registry/assets.json +72 -80
  88. package/core/registry/capabilities.json +12 -0
  89. package/core/registry/contracts.json +60 -33
  90. package/core/registry/contribution-points.json +32 -0
  91. package/core/registry/gates.json +32 -0
  92. package/core/registry/guidance-caps.json +40 -0
  93. package/core/registry/harnesses.json +1542 -277
  94. package/core/registry/lifecycle.json +2837 -37
  95. package/core/registry/mcp.json +5 -1
  96. package/core/registry/models.json +7 -1
  97. package/core/registry/package-templates/README.md +19 -0
  98. package/core/registry/package-templates/agent/files/agent.md +9 -0
  99. package/core/registry/package-templates/agent/template.json +4 -0
  100. package/core/registry/package-templates/capability-provider/files/provider.json +9 -0
  101. package/core/registry/package-templates/capability-provider/template.json +11 -0
  102. package/core/registry/package-templates/debate-preset/files/preset.json +26 -0
  103. package/core/registry/package-templates/debate-preset/template.json +4 -0
  104. package/core/registry/package-templates/review-preset/files/preset.json +30 -0
  105. package/core/registry/package-templates/review-preset/template.json +4 -0
  106. package/core/registry/package-templates/skill/files/SKILL.md +8 -0
  107. package/core/registry/package-templates/skill/template.json +4 -0
  108. package/core/registry/package-templates/worker/files/worker.json +92 -0
  109. package/core/registry/package-templates/worker/template.json +11 -0
  110. package/core/registry/packages/doflow/doflow-package.json +15 -0
  111. package/core/registry/packages/doflow/presets/debate-3.json +26 -0
  112. package/core/registry/protocols.json +61 -0
  113. package/core/registry/routes.json +17 -0
  114. package/core/registry/rules.json +431 -0
  115. package/core/registry/settings.json +276 -0
  116. package/core/registry/storage.json +701 -30
  117. package/core/registry/telemetry.json +236 -0
  118. package/core/registry/transcripts.json +142 -0
  119. package/core/registry/verification.json +76 -1
  120. package/core/registry/workflows.json +55 -0
  121. package/core/shared/agent-specs/core-implementer.md +3 -0
  122. package/core/shared/agent-specs/delivery-lead.md +68 -0
  123. package/core/shared/agent-specs/quality-guardian.md +5 -1
  124. package/core/shared/agent-specs/research-writer.md +10 -3
  125. package/core/shared/agent-specs/reviewer.md +53 -0
  126. package/core/shared/agent-specs/spec-analyst.md +5 -6
  127. package/core/shared/agent-specs/system-architect.md +5 -1
  128. package/core/shared/guidance/DOFLOW_CORE.md +1 -5
  129. package/core/shared/guidance/modes/MODE_DeepResearch.md +1 -1
  130. package/core/shared/guidance/modes/MODE_Orchestration.md +4 -3
  131. package/core/shared/guidance/modes/MODE_Task_Management.md +14 -14
  132. package/core/shared/guidance/pointers/codex.md +1 -4
  133. package/core/shared/guidance/references/ARTIFACT_FORMAT.md +30 -16
  134. package/core/shared/guidance/references/ARTIFACT_VOICE.md +3 -2
  135. package/core/shared/guidance/references/CONSTITUTION_BASE.md +17 -15
  136. package/core/shared/guidance/references/DIAGRAM_GUIDE.md +1 -1
  137. package/core/shared/guidance/references/DOFLOW_CHAIN.md +17 -19
  138. package/core/shared/guidance/references/EVIDENCE_LEDGER.md +7 -8
  139. package/core/shared/guidance/references/MODEL_SELECTION.md +5 -3
  140. package/core/shared/guidance/references/ORCHESTRATION.md +1 -2
  141. package/core/shared/guidance/rules/RULES.md +45 -0
  142. package/core/shared/scripts/doflow/bash/do-args.sh +37 -0
  143. package/core/shared/scripts/doflow/bash/do-exec-paths.sh +4 -0
  144. package/core/shared/scripts/doflow/bash/do-git-state.sh +15 -4
  145. package/core/shared/scripts/doflow/bash/do-parallel-check.sh +4 -0
  146. package/core/shared/scripts/doflow/bash/do-paths.sh +40 -2
  147. package/core/shared/scripts/doflow/bash/do-prereqs.sh +2 -1
  148. package/core/shared/scripts/doflow/bash/do-review-package.sh +4 -0
  149. package/core/shared/scripts/doflow/bash/do-task-brief.sh +58 -16
  150. package/core/shared/scripts/doflow/bash/render-audit.sh +26 -2
  151. package/core/shared/scripts/doflow/bash/render-diagrams.sh +1 -0
  152. package/core/shared/scripts/doflow/bash/render-puml.sh +2 -1
  153. package/core/shared/scripts/doflow/bash/validate-artifacts.sh +48 -4
  154. package/core/shared/scripts/doflow/bin/doflow-run +154 -35
  155. package/core/shared/skills/do/SKILL.md +12 -10
  156. package/core/shared/skills/do/references/advice_calls.md +26 -0
  157. package/core/shared/skills/do/references/pm_routing.md +1 -2
  158. package/core/shared/skills/do/references/program_mode.md +31 -0
  159. package/core/shared/skills/do-brainstorm/SKILL.md +20 -74
  160. package/core/shared/skills/do-brainstorm/references/handoff_branches.md +35 -0
  161. package/core/shared/skills/do-brainstorm/references/intent_seed.md +21 -0
  162. package/core/shared/skills/do-brainstorm/references/round_log.md +17 -0
  163. package/core/shared/skills/do-code-review/SKILL.md +41 -144
  164. package/core/shared/skills/do-code-review/content-types/markdown.md +24 -2
  165. package/core/shared/skills/do-code-review/references/tools.md +90 -0
  166. package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +549 -7
  167. package/core/shared/skills/do-design/SKILL.md +24 -23
  168. package/core/shared/skills/do-diagnose/SKILL.md +14 -4
  169. package/core/shared/skills/do-document/SKILL.md +11 -1
  170. package/core/shared/skills/do-document/references/deep_research.md +1 -2
  171. package/core/shared/skills/do-document/references/embedded-research.md +18 -6
  172. package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
  173. package/core/shared/skills/do-execute-plan/SKILL.md +17 -59
  174. package/core/shared/skills/do-execute-plan/references/handoff_record.md +7 -2
  175. package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +13 -0
  176. package/core/shared/skills/do-execute-plan/references/readiness_gate.md +28 -1
  177. package/core/shared/skills/do-execute-plan/references/run_position.md +13 -1
  178. package/core/shared/skills/do-execute-plan/references/scaffold.md +17 -9
  179. package/core/shared/skills/do-execute-plan/references/state_checkpoint.md +11 -0
  180. package/core/shared/skills/do-execute-plan/references/task_attempts.md +78 -24
  181. package/core/shared/skills/do-flow/SKILL.md +8 -9
  182. package/core/shared/skills/do-flow/references/task_classes.md +2 -2
  183. package/core/shared/skills/do-git/SKILL.md +4 -0
  184. package/core/shared/skills/do-implement/SKILL.md +7 -9
  185. package/core/shared/skills/do-plan/SKILL.md +29 -19
  186. package/core/shared/skills/do-test/SKILL.md +7 -4
  187. package/core/shared/templates/doflow/constitution-template.md +9 -3
  188. package/core/shared/templates/doflow/data-model-template.md +1 -1
  189. package/core/shared/templates/doflow/design-template.md +113 -16
  190. package/core/shared/templates/doflow/plan-template.md +11 -10
  191. package/core/shared/templates/doflow/question-log-template.md +14 -2
  192. package/core/shared/templates/doflow/requirement-template.md +1 -1
  193. package/core/worker-evidence/antigravity/checks.json +14 -0
  194. package/core/worker-evidence/codex/2026-10-18-cli-check.json +107 -0
  195. package/core/worker-evidence/codex/2026-10-18-live-run-edit.json +73 -0
  196. package/core/worker-evidence/codex/2026-10-18-live-run-read-only.json +62 -0
  197. package/core/worker-evidence/codex/2026-10-24-cli-check.json +126 -0
  198. package/core/worker-evidence/codex/checks.json +38 -0
  199. package/package.json +10 -6
  200. package/src/adapters/antigravity/index.js +226 -113
  201. package/src/adapters/claude/index.js +39 -5
  202. package/src/adapters/codex/agents.js +25 -15
  203. package/src/adapters/codex/hooks.js +40 -5
  204. package/src/adapters/codex/index.js +100 -22
  205. package/src/adapters/codex/native-projection.js +6 -2
  206. package/src/adapters/copilot/index.js +58 -23
  207. package/src/adapters/copy-tree.js +294 -48
  208. package/src/adapters/guidance-manifest.js +71 -0
  209. package/src/adapters/guidance-pointer.js +19 -0
  210. package/src/adapters/hook-commands.js +6 -10
  211. package/src/adapters/index.js +1 -0
  212. package/src/adapters/kiro/index.js +38 -13
  213. package/src/adapters/opencode/index.js +8 -8
  214. package/src/adapters/pi/hooks.js +120 -0
  215. package/src/adapters/pi/index.js +48 -16
  216. package/src/adapters/resolve-source.js +26 -2
  217. package/src/cli/commands/install.js +3 -2
  218. package/src/cli/commands/reconcile.js +53 -9
  219. package/src/cli/commands/remove.js +2 -2
  220. package/src/cli/commands/rollback.js +6 -3
  221. package/src/cli/commands/self-update.js +18 -9
  222. package/src/cli/commands/status.js +21 -2
  223. package/src/cli/commands/ui.js +99 -0
  224. package/src/cli/commands/update.js +3 -2
  225. package/src/cli/index.js +338 -33
  226. package/src/cli/runtime-commands.js +243 -52
  227. package/src/cli/shared.js +0 -2
  228. package/src/helper/git.js +1 -1
  229. package/src/helper/guardrails.js +1 -1
  230. package/src/helper/marker-merge.js +31 -13
  231. package/src/helper/package-ids.js +37 -0
  232. package/src/helper/semver.js +94 -0
  233. package/src/install/backup.js +105 -15
  234. package/src/install/context.js +1 -1
  235. package/src/install/targets.js +31 -16
  236. package/src/lifecycle/coverage.js +74 -0
  237. package/src/lifecycle/hook-step.js +61 -0
  238. package/src/lifecycle/hook-tier.js +160 -0
  239. package/src/lifecycle/index.js +434 -86
  240. package/src/lifecycle/retired-harnesses.js +318 -0
  241. package/src/lifecycle/view.js +35 -15
  242. package/src/registry/index.js +686 -32
  243. package/src/registry/worker-ids.js +45 -0
  244. package/src/release/build-package.js +0 -3
  245. package/src/release/docs-blocks.js +492 -0
  246. package/src/release/preflight.js +425 -0
  247. package/src/release/rehearse.js +401 -0
  248. package/src/release/release-workflow.js +733 -0
  249. package/src/release/sync-plugin-versions.js +43 -7
  250. package/src/runtime/advice/bench-gate.js +25 -0
  251. package/src/runtime/advice/cli.js +59 -16
  252. package/src/runtime/advice/client.js +32 -9
  253. package/src/runtime/advice/config.js +98 -133
  254. package/src/runtime/advice/doctor.js +1 -1
  255. package/src/runtime/advice/elevation.js +97 -0
  256. package/src/runtime/advice/endpoint-guard.js +9 -3
  257. package/src/runtime/advice/mode.js +37 -0
  258. package/src/runtime/advice/record.js +18 -5
  259. package/src/runtime/advice/registry.js +4 -1
  260. package/src/runtime/advice/shadow-config.js +229 -0
  261. package/src/runtime/advice/shadow-dispatcher.js +276 -0
  262. package/src/runtime/brief-rules.js +81 -0
  263. package/src/runtime/brief-tools.js +111 -34
  264. package/src/runtime/candidate.js +514 -0
  265. package/src/runtime/capabilities/executor.js +450 -0
  266. package/src/runtime/capabilities/loopback.js +41 -0
  267. package/src/runtime/capabilities/sanitizer.js +88 -0
  268. package/src/runtime/capability-router.js +135 -3
  269. package/src/runtime/checkouts.js +73 -11
  270. package/src/runtime/claims.js +27 -12
  271. package/src/runtime/cli-result.js +11 -4
  272. package/src/runtime/cli.js +221 -277
  273. package/src/runtime/config-home.js +42 -0
  274. package/src/runtime/context/arbitrate.js +767 -0
  275. package/src/runtime/context/budget.js +203 -0
  276. package/src/runtime/context/compose.js +1367 -0
  277. package/src/runtime/context/delivery-store.js +100 -0
  278. package/src/runtime/context/emission-rules.js +118 -0
  279. package/src/runtime/context/identity.js +227 -0
  280. package/src/runtime/context/mandatory.js +594 -0
  281. package/src/runtime/context/pointer.js +142 -0
  282. package/src/runtime/context/primary-command.js +117 -0
  283. package/src/runtime/context/primary.js +344 -0
  284. package/src/runtime/context/summary-store.js +655 -0
  285. package/src/runtime/context-pack.js +1106 -245
  286. package/src/runtime/decision-register.js +13 -4
  287. package/src/runtime/dispatch/admission.js +392 -0
  288. package/src/runtime/dispatch/attempt-end.js +274 -0
  289. package/src/runtime/dispatch/command.js +628 -0
  290. package/src/runtime/dispatch/controls.js +904 -0
  291. package/src/runtime/dispatch/envelope.js +276 -0
  292. package/src/runtime/dispatch/grants.js +310 -0
  293. package/src/runtime/dispatch/integrate.js +617 -0
  294. package/src/runtime/dispatch/loop.js +1835 -0
  295. package/src/runtime/dispatch/store.js +352 -0
  296. package/src/runtime/dispatch/verify-run.js +201 -0
  297. package/src/runtime/evidence-fields.js +39 -0
  298. package/src/runtime/evidence-ledger.js +41 -7
  299. package/src/runtime/failure/capture.js +3 -1
  300. package/src/runtime/failure/cli.js +25 -9
  301. package/src/runtime/failure/home.js +57 -30
  302. package/src/runtime/failure/store.js +7 -2
  303. package/src/runtime/feature-pointer.js +174 -0
  304. package/src/runtime/feature-resolve.js +13 -2
  305. package/src/runtime/fleet/command.js +77 -0
  306. package/src/runtime/fleet/projects.js +178 -0
  307. package/src/runtime/fleet/tree.js +383 -0
  308. package/src/runtime/freshness.js +77 -10
  309. package/src/runtime/git-memo.js +46 -0
  310. package/src/runtime/guidance/ablation.js +427 -0
  311. package/src/runtime/health.js +565 -17
  312. package/src/runtime/history-compactor.js +13 -3
  313. package/src/runtime/implementation-gate.js +277 -22
  314. package/src/runtime/inventory/coverage.js +1 -1
  315. package/src/runtime/inventory/index.js +1 -1
  316. package/src/runtime/inventory/precedence.js +2 -2
  317. package/src/runtime/inventory/siblings.js +6 -4
  318. package/src/runtime/knowledge/code-corpus.js +277 -0
  319. package/src/runtime/knowledge/code-lexical.js +130 -0
  320. package/src/runtime/knowledge/code-retrieval.js +307 -0
  321. package/src/runtime/knowledge/code-validation.js +106 -0
  322. package/src/runtime/knowledge/gp3-gate.js +74 -0
  323. package/src/runtime/knowledge/graph-adapter.js +288 -0
  324. package/src/runtime/knowledge/graph-fallback.js +394 -0
  325. package/src/runtime/knowledge/hybrid.js +136 -8
  326. package/src/runtime/knowledge/retrieval.js +34 -1
  327. package/src/runtime/knowledge/rtk-compress.js +77 -0
  328. package/src/runtime/knowledge/semble-bridge.py +106 -0
  329. package/src/runtime/knowledge/semble-lifecycle.js +114 -0
  330. package/src/runtime/knowledge/semble-provider.js +365 -0
  331. package/src/runtime/lifecycle/cli.js +16 -6
  332. package/src/runtime/lifecycle/event-store.js +8 -6
  333. package/src/runtime/lifecycle/followup.js +6 -1
  334. package/src/runtime/lifecycle/overview.js +24 -1
  335. package/src/runtime/lifecycle/report-store.js +8 -10
  336. package/src/runtime/lifecycle/retention.js +95 -31
  337. package/src/runtime/lifecycle/root.js +9 -1
  338. package/src/runtime/lifecycle/status.js +34 -17
  339. package/src/runtime/lifecycle/store-upkeep.js +56 -7
  340. package/src/runtime/mask.js +134 -7
  341. package/src/runtime/mcp-consultations.js +40 -6
  342. package/src/runtime/model-router.js +79 -7
  343. package/src/runtime/models/catalogue.js +191 -0
  344. package/src/runtime/models/command.js +153 -0
  345. package/src/runtime/off-words.js +12 -0
  346. package/src/runtime/outcome.js +3 -3
  347. package/src/runtime/packages/command.js +1009 -0
  348. package/src/runtime/packages/contributions.js +116 -0
  349. package/src/runtime/packages/digest.js +132 -0
  350. package/src/runtime/packages/fetch.js +182 -0
  351. package/src/runtime/packages/grants.js +192 -0
  352. package/src/runtime/packages/index-file.js +134 -0
  353. package/src/runtime/packages/instruction.js +90 -0
  354. package/src/runtime/packages/manifest.js +331 -0
  355. package/src/runtime/packages/points.js +477 -0
  356. package/src/runtime/packages/programs.js +216 -0
  357. package/src/runtime/packages/scaffold.js +277 -0
  358. package/src/runtime/packages/source.js +90 -0
  359. package/src/runtime/packages/state.js +164 -0
  360. package/src/runtime/packages/store.js +449 -0
  361. package/src/runtime/packages/tar.js +106 -0
  362. package/src/runtime/packages/view.js +406 -0
  363. package/src/runtime/plan-check.js +59 -0
  364. package/src/runtime/plan-content.js +324 -0
  365. package/src/runtime/policy-init.js +118 -0
  366. package/src/runtime/profiles/doctor.js +96 -0
  367. package/src/runtime/profiles/resolve.js +244 -0
  368. package/src/runtime/profiles/rollback.js +254 -0
  369. package/src/runtime/profiles/sampler.js +41 -0
  370. package/src/runtime/readiness-record.js +16 -11
  371. package/src/runtime/readiness.js +127 -16
  372. package/src/runtime/research-request.js +50 -25
  373. package/src/runtime/retrieval-plan.js +402 -73
  374. package/src/runtime/retrieval-record.js +293 -0
  375. package/src/runtime/review/command.js +246 -0
  376. package/src/runtime/review/dominance.js +68 -0
  377. package/src/runtime/review/findings.js +434 -0
  378. package/src/runtime/review/identity.js +255 -0
  379. package/src/runtime/review/protocol-command.js +109 -0
  380. package/src/runtime/review/protocols.js +405 -0
  381. package/src/runtime/review/resolve-protocol.js +302 -0
  382. package/src/runtime/review/start.js +530 -0
  383. package/src/runtime/roadmap/facts.js +479 -0
  384. package/src/runtime/roadmap/gates.js +270 -0
  385. package/src/runtime/roadmap/loop.js +1636 -0
  386. package/src/runtime/roadmap/merge.js +381 -0
  387. package/src/runtime/roadmap/rows.js +1113 -0
  388. package/src/runtime/roadmap/seed.js +951 -0
  389. package/src/runtime/rules-report.js +336 -0
  390. package/src/runtime/scaffold/artifacts.js +63 -9
  391. package/src/runtime/scaffold/generate.js +21 -9
  392. package/src/runtime/settings/autopilot.js +106 -0
  393. package/src/runtime/settings/cli.js +280 -0
  394. package/src/runtime/settings/config-write.js +314 -0
  395. package/src/runtime/settings/files.js +271 -0
  396. package/src/runtime/settings/legacy.js +154 -0
  397. package/src/runtime/settings/lexical.js +56 -0
  398. package/src/runtime/settings/prepared-run.js +269 -0
  399. package/src/runtime/settings/project-layers.js +118 -0
  400. package/src/runtime/settings/refs.js +288 -0
  401. package/src/runtime/settings/registry.js +553 -0
  402. package/src/runtime/settings/resolve.js +713 -0
  403. package/src/runtime/settings/shared.js +37 -0
  404. package/src/runtime/settings/worker-billing.js +107 -0
  405. package/src/runtime/settings/write.js +269 -0
  406. package/src/runtime/slot-command.js +78 -0
  407. package/src/runtime/slots.js +671 -0
  408. package/src/runtime/source-version.js +290 -0
  409. package/src/runtime/state-root.js +217 -0
  410. package/src/runtime/storage/cli.js +12 -5
  411. package/src/runtime/storage/engine.js +9 -2
  412. package/src/runtime/storage/registry.js +14 -1
  413. package/src/runtime/storage/roots.js +66 -16
  414. package/src/runtime/storage/rules.js +82 -10
  415. package/src/runtime/storage/upkeep.js +10 -2
  416. package/src/runtime/storage/work-status.js +245 -26
  417. package/src/runtime/task-attempts.js +589 -69
  418. package/src/runtime/task-classifier.js +47 -11
  419. package/src/runtime/task-graph.js +373 -0
  420. package/src/runtime/task-run.js +778 -0
  421. package/src/runtime/task-scope.js +202 -56
  422. package/src/runtime/task-sources.js +147 -0
  423. package/src/runtime/task-state.js +440 -65
  424. package/src/runtime/team/admission.js +301 -0
  425. package/src/runtime/team/envelope.js +324 -0
  426. package/src/runtime/team/review-guard.js +156 -0
  427. package/src/runtime/trace/analyze.js +427 -0
  428. package/src/runtime/trace/baseline.js +770 -0
  429. package/src/runtime/trace/ledger.js +63 -140
  430. package/src/runtime/trace/otlp-export.js +568 -0
  431. package/src/runtime/trace/otlp-map.js +481 -0
  432. package/src/runtime/trace/spans.js +745 -0
  433. package/src/runtime/trace/telemetry.js +225 -0
  434. package/src/runtime/trace/views.js +1 -0
  435. package/src/runtime/transcripts/command.js +98 -0
  436. package/src/runtime/transcripts/cursor.js +326 -0
  437. package/src/runtime/transcripts/read.js +126 -0
  438. package/src/runtime/transcripts/readers/claude-jsonl.js +74 -0
  439. package/src/runtime/transcripts/readers/codex-rollout.js +70 -0
  440. package/src/runtime/transcripts/readers/copilot-events.js +90 -0
  441. package/src/runtime/transcripts/readers/kiro-jsonl.js +77 -0
  442. package/src/runtime/transcripts/readers/pi-jsonl.js +59 -0
  443. package/src/runtime/transcripts/registry.js +161 -0
  444. package/src/runtime/transcripts/stats.js +161 -0
  445. package/src/runtime/verification/check-key.js +628 -0
  446. package/src/runtime/verification/contract-runner.js +546 -11
  447. package/src/runtime/verification/engine.js +499 -9
  448. package/src/runtime/verification/impact.js +617 -0
  449. package/src/runtime/verification/inflight.js +205 -0
  450. package/src/runtime/verification/registry.js +176 -0
  451. package/src/runtime/verification/reuse.js +184 -0
  452. package/src/runtime/verification/telemetry-emit.js +254 -0
  453. package/src/runtime/verification/test-selection.js +67 -7
  454. package/src/runtime/worker/active.js +90 -0
  455. package/src/runtime/worker/cancel.js +222 -0
  456. package/src/runtime/worker/catalogue.js +122 -0
  457. package/src/runtime/worker/command.js +677 -0
  458. package/src/runtime/worker/evidence.js +288 -0
  459. package/src/runtime/worker/launch.js +288 -0
  460. package/src/runtime/worker/models.js +137 -0
  461. package/src/runtime/worker/parsers.js +218 -0
  462. package/src/runtime/worker/probes/index.js +706 -0
  463. package/src/runtime/worker/process-tree.js +415 -0
  464. package/src/runtime/worker/quota.js +43 -0
  465. package/src/runtime/worker/readiness.js +713 -0
  466. package/src/runtime/worker/run-store.js +166 -0
  467. package/src/runtime/worker/supervisor.js +541 -0
  468. package/src/runtime/worker/worktree-check.js +252 -0
  469. package/src/runtime/workflow-engine.js +5 -1
  470. package/src/runtime/workflow-orchestrator.js +578 -63
  471. package/src/runtime/worktree.js +149 -22
  472. package/src/state/index.js +1 -1
  473. package/src/state/lockfile.js +64 -10
  474. package/src/state/run-lock.js +1 -1
  475. package/types/node-commonjs.d.ts +106 -7
  476. package/core/.antigravity-plugin/hooks.json +0 -39
  477. package/core/harnesses/gemini/hooks/hooks.json +0 -77
  478. package/core/harnesses/gemini/hooks/lib.sh +0 -302
  479. package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +0 -21
  480. package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
  481. package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -21
  482. package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
  483. package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -22
  484. package/core/harnesses/gemini/hooks/session-end.impl.sh +0 -89
  485. package/core/harnesses/gemini/hooks/session-end.sh +0 -5
  486. package/core/harnesses/gemini/hooks/session-start.sh +0 -5
  487. package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
  488. package/core/harnesses/shared/hooks/adapters/arg-fields.js +0 -19
  489. package/core/harnesses/shared/hooks/adapters/gemini.js +0 -114
  490. package/core/harnesses/shared/hooks/hooks.json +0 -47
  491. package/core/shared/guidance/FLAGS.md +0 -25
  492. package/core/shared/guidance/rules/RULE_01_SAFETY.md +0 -25
  493. package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +0 -23
  494. package/core/shared/guidance/rules/RULE_03_QUALITY.md +0 -14
  495. package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +0 -46
  496. package/src/adapters/gemini/hooks.js +0 -210
  497. package/src/adapters/gemini/index.js +0 -390
@@ -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
@@ -177,8 +384,9 @@ function kiroPreToolUseHooks() {
177
384
  }
178
385
 
179
386
  /** 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 ) {
387
+ * name/description (model: inherit, effort) are dropped: Kiro inherits the session model. `access` is
388
+ * never projected; the spec's tool scope (narrowed by `apply.tools` when `read-only`) replaces `tools: ["*"]`. */
389
+ function renderKiroAgent(text , { hooks = true } = {}) {
182
390
  const fm = {};
183
391
  let body = text;
184
392
  if (text.startsWith('---')) {
@@ -191,17 +399,16 @@ function renderKiroAgent(text ) {
191
399
  body = text.slice(end + 4).replace(/^\n+/, '');
192
400
  }
193
401
  }
402
+ const limits = agentLimitKeys('kiro', { readOnly: fm.access === 'read-only', scope: scopeCategories(fm['tool-scope']) });
194
403
  const agent = {
195
404
  name: fm.name || '', description: fm.description || '',
196
405
  prompt: `${body.replace(/\s+$/, '')}\n`,
197
- tools: ['*'],
198
- hooks: kiroPreToolUseHooks(),
406
+ tools: limits?.tools ?? ['*'],
407
+ ...(hooks ? { hooks: kiroPreToolUseHooks() } : {}),
199
408
  };
200
409
  return `${JSON.stringify(agent, null, 2)}\n`;
201
410
  }
202
411
 
203
- const OPENCODE_READONLY_AGENTS = new Set(['spec-analyst', 'system-architect', 'quality-guardian', 'research-writer']);
204
-
205
412
  /** Map one core/shared agent-spec file onto OpenCode's documented markdown-agent frontmatter
206
413
  * (description / mode / permission; model omitted because OpenCode has no 'inherit'). Unknown
207
414
  * spec keys (tools, effort) are dropped rather than passed through, so OpenCode never sees
@@ -222,9 +429,8 @@ function renderOpencodeAgent(text ) {
222
429
  const lines = ['---'];
223
430
  if (fm.description) lines.push(`description: ${JSON.stringify(fm.description)}`);
224
431
  lines.push('mode: subagent');
225
- if (OPENCODE_READONLY_AGENTS.has(fm.name)) {
226
- lines.push('permission:', ' edit: deny', ' bash: deny');
227
- }
432
+ const limits = agentLimitKeys('opencode', { readOnly: fm.access === 'read-only', scope: scopeCategories(fm['tool-scope']) });
433
+ if (limits) lines.push(...Object.entries(limits).flatMap(([key, value]) => yamlLines(key, value)));
228
434
  lines.push('---', '', body.replace(/\n*$/, ''), '');
229
435
  return lines.join('\n');
230
436
  }
@@ -233,11 +439,15 @@ function renderOpencodeAgent(text ) {
233
439
  * the destination tree beyond checking existence. `relPath` is destination-relative and doubles as
234
440
  * the ledger identity, so a layout change relocates the recorded resource too — planTree's
235
441
  * relocation handling then removes the old path rather than orphaning it. */
236
- function discoverTree({ sourceDir, destDir, fsImpl = fs, layout, transform } ) {
442
+ function discoverTree({ sourceDir, destDir, fsImpl = fs, layout, transform, select } ) {
237
443
  if (!fsImpl.existsSync(sourceDir)) throw new Error(`copy-tree source is missing: ${sourceDir}`);
238
444
  const mapRel = typeof layout === 'function' ? layout : resolveLayout(layout);
239
445
  const mapContent = typeof transform === 'function' ? transform : resolveTransform(transform);
240
- const files = walkRelFiles(sourceDir, fsImpl).map((sourceRel) => {
446
+ const selected = resolveSelect(select)?.(sourceDir, fsImpl);
447
+ const files = walkRelFiles(sourceDir, fsImpl).filter((sourceRel) => {
448
+ if (!selected) return true;
449
+ return selected.has(sourceRel.split(path.sep).join('/'));
450
+ }).map((sourceRel) => {
241
451
  const relPath = mapRel(sourceRel);
242
452
  const sourceAbs = path.join(sourceDir, sourceRel);
243
453
  const destAbs = path.join(destDir, relPath);
@@ -247,6 +457,27 @@ function discoverTree({ sourceDir, destDir, fsImpl = fs, layout, transform }
247
457
  return { files };
248
458
  }
249
459
 
460
+ /** Selected files that map to one destination cannot both be installed safely. */
461
+ function selectedDestinationCollisions(files , sourceDir , destDir ) {
462
+ const sourcesByDestination = new Map ();
463
+ for (const file of files) {
464
+ const key = path.resolve(file.destAbs);
465
+ const sources = sourcesByDestination.get(key) || [];
466
+ sources.push(file);
467
+ sourcesByDestination.set(key, sources);
468
+ }
469
+
470
+ const destinations = new Set ();
471
+ const conflicts = [];
472
+ for (const [destination, sources] of sourcesByDestination) {
473
+ if (sources.length < 2) continue;
474
+ destinations.add(destination);
475
+ const names = sources.map((file) => path.relative(sourceDir, file.sourceAbs).split(path.sep).join('/'));
476
+ conflicts.push(`selected files ${names.map((name) => `'${name}'`).join(' and ')} map to the same destination '${path.relative(destDir, destination).split(path.sep).join('/')}'`);
477
+ }
478
+ return { destinations, conflicts };
479
+ }
480
+
250
481
  /** A file's identity on disk (device + inode), or null when the path does not resolve to one. */
251
482
  function fileId(file , fsImpl ) {
252
483
  try {
@@ -292,7 +523,7 @@ function sameFileIndex(paths , fsImpl = fs)
292
523
  * `siblingReplaced` lists the harnesses whose recorded bytes this plan replaces: the file matches
293
524
  * neither the source nor this harness's own record, only a sibling's (forced or not).
294
525
  */
295
- function planTree({ sourceDir, destDir, previousResources = [], operation = 'apply', fsImpl = fs, layout, transform, force = false, keepModified = false, siblingFingerprints = new Map() } ) {
526
+ function planTree({ sourceDir, destDir, previousResources = [], operation = 'apply', fsImpl = fs, layout, transform, select, force = false, keepModified = false, siblingFingerprints = new Map() } ) {
296
527
  const prevByPath = new Map (previousResources.map((resource ) => [resource.relPath, resource]));
297
528
  const changes = [];
298
529
  const conflicts = [];
@@ -307,7 +538,7 @@ function planTree({ sourceDir, destDir, previousResources = [], operation = 'app
307
538
  const sourceFingerprint = (destAbs ) => {
308
539
  if (sourceByDest === undefined) {
309
540
  sourceByDest = sourceDir && fsImpl.existsSync(sourceDir)
310
- ? new Map (discoverTree({ sourceDir, destDir, fsImpl, layout, transform }).files.map((file) => [file.destAbs, file.fingerprint]))
541
+ ? new Map (discoverTree({ sourceDir, destDir, fsImpl, layout, transform, select }).files.map((file) => [file.destAbs, file.fingerprint]))
311
542
  : new Map();
312
543
  }
313
544
  return sourceByDest.get(destAbs);
@@ -335,7 +566,7 @@ function planTree({ sourceDir, destDir, previousResources = [], operation = 'app
335
566
  // Untampered on removal means the same two signals the apply path below already accepts: the
336
567
  // bytes this harness last recorded, or the bytes the current source would write. The second
337
568
  // 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
569
+ // is one `<project>/.doflow/scripts` for claude, codex and others), so a sibling's update
339
570
  // legitimately rewrites files this harness's rows still describe — the same "a sibling
340
571
  // changed bytes my row still describes" case the apply path had to be taught, arriving here
341
572
  // as an un-releasable claim instead of a refused install. A hand edit matches neither and is
@@ -366,10 +597,18 @@ function planTree({ sourceDir, destDir, previousResources = [], operation = 'app
366
597
  return { changes, conflicts, kept, siblingReplaced: [] };
367
598
  }
368
599
 
369
- const { files } = discoverTree({ sourceDir, destDir, fsImpl, layout, transform });
600
+ const { files } = discoverTree({ sourceDir, destDir, fsImpl, layout, transform, select });
601
+ const collisions = select == null
602
+ ? { destinations: new Set (), conflicts: [] }
603
+ : selectedDestinationCollisions(files, sourceDir, destDir);
604
+ conflicts.push(...collisions.conflicts);
370
605
  producedSameFile = sameFileIndex(files.map((file) => file.destAbs), fsImpl);
371
606
  const satisfiedAtSameLocation = new Set();
372
607
  for (const file of files) {
608
+ if (collisions.destinations.has(path.resolve(file.destAbs))) {
609
+ satisfiedAtSameLocation.add(file.relPath);
610
+ continue;
611
+ }
373
612
  const prev = prevByPath.get(file.relPath);
374
613
  // prev.target === undefined means "location unknown" (pre-this-fix ledger data, or a
375
614
  // caller-constructed previousResources entry) — treat as same-location, matching pre-fix
@@ -442,16 +681,18 @@ function applyTree({ changes = [], fsImpl = fs, transform } )
442
681
  /** Why a removal the runtime prune planned may no longer run, or null when it still may: the
443
682
  * prune's plan-time proof (src/lifecycle/index.js pruneRetiredRuntimeFiles) re-run right before the
444
683
  * 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.
684
+ * made is not deleted through. `guard` is `{ root, source, scope }`: the tree, the source it projects
685
+ * and the scope root the tree must resolve inside (a tree linked out of its scope is refused).
446
686
  * One walk of the source and one stat per shipped path per tree per call, cached in `cache`. */
447
687
  function pruneGuardRefusal(change , cache , fsImpl ) {
448
- const { root, source } = change.pruneGuard;
449
- const key = `${root}\0${source}`;
688
+ const { root, source, scope } = change.pruneGuard;
689
+ const key = `${root}\0${source}\0${scope ?? ''}`;
450
690
  if (!cache.has(key)) {
451
691
  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) });
692
+ cache.set(key, { realRoot: fsImpl.realpathSync(root), realScope: scope ? fsImpl.realpathSync(scope) : null, sameFile: sameFileIndex(produced, fsImpl) });
453
693
  }
454
- const { realRoot, sameFile } = cache.get(key);
694
+ const { realRoot, realScope, sameFile } = cache.get(key);
695
+ if (realScope && !realRoot.startsWith(`${realScope}${path.sep}`)) return `${root} resolves outside the scope root ${scope}`;
455
696
  if (!fsImpl.realpathSync(change.target).startsWith(`${realRoot}${path.sep}`)) return `it resolves outside ${root}`;
456
697
  const shipped = sameFile(change.target);
457
698
  return shipped ? `it is the same file as ${shipped}, which this version ships` : null;
@@ -500,12 +741,17 @@ function pruneEmptyAncestors(startDir , { fsImpl = fs }
500
741
  * `plannedWrites` lists the destinations the run being verified wrote. An absent source file is
501
742
  * not reported in general, because a user may delete one, but an absent file this run wrote means
502
743
  * 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 });
744
+ function verifyTree({ sourceDir, destDir, fsImpl = fs, layout, transform, select, plannedWrites = [] } ) {
745
+ const { files } = discoverTree({ sourceDir, destDir, fsImpl, layout, transform, select });
505
746
  const written = new Set(plannedWrites);
506
747
  const resources = [];
507
748
  const conflicts = [];
749
+ const collisions = select == null
750
+ ? { destinations: new Set (), conflicts: [] }
751
+ : selectedDestinationCollisions(files, sourceDir, destDir);
752
+ conflicts.push(...collisions.conflicts);
508
753
  for (const file of files) {
754
+ if (collisions.destinations.has(path.resolve(file.destAbs))) continue;
509
755
  if (!file.exists) {
510
756
  if (written.has(file.destAbs)) conflicts.push(`${file.relPath} was written by this run but is missing`);
511
757
  continue;
@@ -517,7 +763,7 @@ function verifyTree({ sourceDir, destDir, fsImpl = fs, layout, transform, planne
517
763
  return { ok: conflicts.length === 0, resources, conflicts };
518
764
  }
519
765
 
520
- // ---- per-adapter copy-tree glue, shared so codex/claude/gemini don't each carry their own copy ----
766
+ // ---- per-adapter copy-tree glue, shared so each adapter doesn't carry its own copy ----
521
767
 
522
768
  /** Filter a harness's asset list down to the ones this engine handles. */
523
769
  function copyTreeAssets(assets ) {
@@ -578,17 +824,17 @@ function sharedTreeDestDir(rootDir , nativeDir ) {
578
824
  return dest;
579
825
  }
580
826
 
581
- /** The assets that project a tree several harnesses claim at one scope root: the runtime tree, and
582
- * the guidance tree. A harness whose
827
+ /** The assets that project a tree several harnesses claim at one scope root: the runtime, guidance,
828
+ * and shared hooks trees. A harness whose
583
829
  * install predates its own rows for a shared tree (Pi gained guidance rows in v1.15.0) can only
584
830
  * recognise the bytes a sibling recorded. */
585
831
  const SHARED_TREE_ASSETS = new Set([
586
832
  'scripts.doflow', 'runtime.cli', 'runtime.lib', 'runtime.registry',
587
- 'guidance.context-layer', 'kiro.guidance-tree',
833
+ 'guidance.context-layer', 'kiro.guidance-tree', 'hooks.shared',
588
834
  ]);
589
835
 
590
836
  /** For each shared-tree target, the fingerprints other harnesses recorded and which harness
591
- * recorded each, so an update accepts a runtime or guidance tree a sibling wrote (several harnesses
837
+ * recorded each, so an update accepts a runtime, guidance, or shared hooks tree a sibling wrote (several harnesses
592
838
  * claim one `.doflow` file) while bytes no such row recorded are still refused as a hand edit. */
593
839
  function ledgerSiblingFingerprints(resources , harness ) {
594
840
  const byTarget = new Map ();
@@ -617,4 +863,4 @@ function ledgerFileResources(resources , harness , assetId )
617
863
  .map((resource ) => ({ relPath: resource.identity, fingerprint: resource.fingerprint, target: resource.target }));
618
864
  }
619
865
 
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 };
866
+ 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 };