@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
@@ -0,0 +1,136 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * The `intake` verb (136 IC-001): `doflow-run intake --action <sort|preview|undo|list|file-prompt>`. This module
5
+ * resolves the feature folder, runs the engine's action and maps its result to an exit status; it never
6
+ * prompts. Exit 0 = done (a sort with zero moves too), 1 = a finding to act on (a sort or undo that skipped or
7
+ * failed an entry, or an undo with nothing left to undo), 2 = the call cannot proceed (no active feature, an
8
+ * unknown action, no input for `file-prompt`, a manifest from a newer schema, a lock that could not be taken).
9
+ * `--json` prints the engine's object unmodified.
10
+ */
11
+
12
+ const nodeFs = require('node:fs');
13
+ const path = require('node:path');
14
+ const { resolveActiveFeature } = require('../feature-resolve.js');
15
+ const { finishRuntime, usageError } = require('../cli-result.js');
16
+ const { loadTaxonomy, isPlainText } = require('./taxonomy.js');
17
+ const { IntakeUsageError } = require('./manifest.js');
18
+ const { previewSort, sortFolder, undoRun, listFolder, filePrompt, CONTEXT_DIR } = require('./engine.js');
19
+
20
+ const VALID_ACTIONS = ['sort', 'preview', 'undo', 'list', 'file-prompt'];
21
+
22
+ /**
23
+ * The bytes of `--file`, opened without blocking and checked on the open descriptor: a regular file (a link to one
24
+ * is followed; a FIFO, a device or a folder is refused, and a FIFO is never waited on) of at most `maxBytes`.
25
+ */
26
+ function readRegularFile(file , maxBytes , fsImpl ) {
27
+ const fd = fsImpl.openSync(path.resolve(file), nodeFs.constants.O_RDONLY | (nodeFs.constants.O_NONBLOCK || 0));
28
+ try {
29
+ const stat = fsImpl.fstatSync(fd);
30
+ if (!stat.isFile()) throw new IntakeUsageError(`--file ${file} is not a regular file; file-prompt reads only a regular text file`);
31
+ if (stat.size > maxBytes) throw new IntakeUsageError(`--file ${file} is ${stat.size} bytes; file-prompt takes at most ${maxBytes}`);
32
+ return fsImpl.readFileSync(fd);
33
+ } finally {
34
+ fsImpl.closeSync(fd);
35
+ }
36
+ }
37
+
38
+ /** At most `maxBytes + 1` bytes of stdin: one over the cap is enough to refuse, and a huge stream is never held whole. */
39
+ function readStdinCapped(maxBytes , fsImpl ) {
40
+ const buffer = Buffer.alloc(maxBytes + 1);
41
+ let length = 0;
42
+ while (length < buffer.length) {
43
+ let read ;
44
+ try {
45
+ read = fsImpl.readSync(0, buffer, length, buffer.length - length, null);
46
+ } catch (error ) {
47
+ if (error.code === 'EOF') break; // Windows reports the end of a closed pipe as an error
48
+ throw error;
49
+ }
50
+ if (read === 0) break;
51
+ length += read;
52
+ }
53
+ return buffer.subarray(0, length);
54
+ }
55
+
56
+ /**
57
+ * The prompt text as bytes, unchanged: from `--stdin` or from `--file <path>`. Either way it must be plain text
58
+ * (no NUL byte, valid UTF-8) of at most `maxBytes`, so a binary or a huge file is never filed as a prompt.
59
+ */
60
+ function readInput({ stdin, file } , fsImpl , maxBytes ) {
61
+ if (!stdin && !file) throw new IntakeUsageError('--action file-prompt needs the text from --stdin or --file <path>');
62
+ if (stdin && file) throw new IntakeUsageError('--stdin and --file cannot be combined');
63
+ const from = stdin ? 'stdin' : `--file ${file}`;
64
+ let bytes ;
65
+ try {
66
+ bytes = stdin ? readStdinCapped(maxBytes, fsImpl) : readRegularFile(file, maxBytes, fsImpl);
67
+ } catch (error ) {
68
+ if (error instanceof IntakeUsageError) throw error;
69
+ throw new IntakeUsageError(`cannot read ${from}: ${error.message}`);
70
+ }
71
+ if (bytes.length > maxBytes) throw new IntakeUsageError(`${from} is ${stdin ? `over ${maxBytes}` : bytes.length} bytes; file-prompt takes at most ${maxBytes}`);
72
+ if (!isPlainText(bytes)) throw new IntakeUsageError(`${from} is not plain text (it holds a NUL byte or is not valid UTF-8); file-prompt files text only`);
73
+ return bytes;
74
+ }
75
+
76
+ /**
77
+ * Runs one action and returns its result without printing.
78
+ * @returns {{exitCode:number, result?:Object, usage?:string}}
79
+ */
80
+ function runIntake({ action, projectRoot, slug = null, flags = {}, now = new Date(), fsImpl = nodeFs, taxonomyOptions = {} } ) {
81
+ try {
82
+ if (!VALID_ACTIONS.includes(action)) throw new IntakeUsageError(`unknown --action '${action}'. Valid: ${VALID_ACTIONS.join(', ')}`);
83
+ const feature = resolveActiveFeature({ projectRoot, slug });
84
+ if (feature.error) throw new IntakeUsageError(feature.message);
85
+ const loaded = loadTaxonomy({ projectRoot: feature.repoRoot, ...taxonomyOptions, fsImpl });
86
+ if (!loaded.ok) throw new IntakeUsageError(`the intake categories cannot be used (${loaded.reason}): ${loaded.detail}`);
87
+ const common = { contextDir: path.join(feature.featureFolder, CONTEXT_DIR), taxonomy: loaded.taxonomy, now, fsImpl };
88
+ let result ;
89
+ if (action === 'sort') result = sortFolder(common);
90
+ else if (action === 'preview') result = previewSort(common);
91
+ else if (action === 'list') result = listFolder(common);
92
+ else if (action === 'undo') result = undoRun({ ...common, runId: flags.runId || null });
93
+ else result = filePrompt({ ...common, bytes: readInput(flags, fsImpl, loaded.taxonomy.pointerMaxBytes), stage: flags.stage || null });
94
+ const finding = (Array.isArray(result.skipped) && result.skipped.length > 0) || (Array.isArray(result.kept) && result.kept.length > 0) || result.state === 'nothing-to-undo';
95
+ return { exitCode: finding ? 1 : 0, result };
96
+ } catch (error ) {
97
+ if (error instanceof IntakeUsageError) return { exitCode: 2, usage: error.message };
98
+ // Another writer holds the manifest lock: the call cannot proceed now (IC-001), so it is usage, not a finding.
99
+ if (/^Could not lock/.test(error?.message || '')) return { exitCode: 2, usage: error.message };
100
+ throw error;
101
+ }
102
+ }
103
+
104
+ function humanLines(result ) {
105
+ const lines = [];
106
+ if (result.state === 'absent') return [`no ${CONTEXT_DIR} folder in this feature; nothing to ${result.action}`];
107
+ if (result.action === 'sort' || result.action === 'preview') {
108
+ for (const move of result.moves) lines.push(`${result.action === 'preview' ? 'would move' : 'moved'} ${move.from} -> ${move.to} [${move.category}]`);
109
+ for (const skip of result.skipped) lines.push(`skipped ${skip.path} (${skip.reason}${skip.detail ? `: ${skip.detail}` : ''})`);
110
+ lines.push(`${result.moves.length} ${result.action === 'preview' ? 'to move' : 'moved'}, ${result.skipped.length} skipped, ${result.unmatched.length} unmatched${result.runId ? `; undo with --action undo --run-id ${result.runId}` : ''}`);
111
+ } else if (result.action === 'undo') {
112
+ if (result.state === 'nothing-to-undo') return ['no run left to undo'];
113
+ for (const move of result.restored) lines.push(`restored ${move.to} -> ${move.from}`);
114
+ for (const skip of result.skipped) lines.push(`skipped ${skip.to} (${skip.reason}${skip.detail ? `: ${skip.detail}` : ''})`);
115
+ for (const move of result.kept) lines.push(`kept ${move.to} (failed move from ${move.from}${move.reason ? `: ${move.reason}` : ''})`);
116
+ for (const dir of result.removedDirs) lines.push(`removed empty folder ${dir}`);
117
+ lines.push(`${result.restored.length} restored, ${result.skipped.length} skipped${result.kept.length ? `, ${result.kept.length} kept` : ''} (${result.runId})`);
118
+ } else if (result.action === 'list') {
119
+ for (const file of result.files) lines.push(`${file.path} ${file.category ?? '-'} ${file.kind} ${file.bytes} bytes ${file.unreadable ? 'unreadable' : file.sha256}${file.pointer ? ' pointer' : ''}`);
120
+ lines.push(`${result.files.length} file${result.files.length === 1 ? '' : 's'}`);
121
+ } else {
122
+ lines.push(`filed ${result.path} (${result.bytes} bytes, ${result.sha256}; ${result.runId})`);
123
+ }
124
+ return lines;
125
+ }
126
+
127
+ /** `intake` verb. Exit 0 = done, 1 = a finding, 2 = usage. `--json` prints the result object unmodified. */
128
+ function handleIntakeCommand({ action = 'sort', projectRoot, slug = null, json = false, flags = {} } = {}) {
129
+ const run = runIntake({ action, projectRoot: projectRoot || process.cwd(), slug, flags });
130
+ if (run.usage !== undefined) return usageError('intake', run.usage, json);
131
+ if (json) console.log(JSON.stringify(run.result, null, 2));
132
+ else for (const line of humanLines(run.result)) console.log(line);
133
+ return finishRuntime(run.exitCode);
134
+ }
135
+
136
+ module.exports = { handleIntakeCommand, runIntake, readInput, humanLines, VALID_ACTIONS };
@@ -0,0 +1,140 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * The intake manifest (136 IC-007): `additional-context/.intake/manifest.json`, the one record of every sort
5
+ * and every filed prompt. It is rewritten whole through a temporary file and a rename, under the
6
+ * `acquireLock` lock `task-state.ts` provides for its own files, the way `decision-register.ts` writes its
7
+ * register. The reader refuses what it cannot trust (invalid JSON, a newer schema, a path that leaves the
8
+ * folder) with an `IntakeUsageError` and never rewrites such a file: the manifest is evidence of what moved.
9
+ */
10
+
11
+ const nodeFs = require('node:fs');
12
+ const path = require('node:path');
13
+ const nodeCrypto = require('node:crypto');
14
+ const { acquireLock } = require('../task-state.js');
15
+
16
+ const MANIFEST_SCHEMA = 1;
17
+ const INTAKE_DIR = '.intake';
18
+ const MANIFEST_FILE = 'manifest.json';
19
+ const STATES = ['planned', 'done', 'failed', 'undone', 'undo-skipped'];
20
+ const HASH = /^sha256:[0-9a-f]{64}$/;
21
+
22
+ /** A caller mistake or an input the engine refuses to guess at (exit 2). */
23
+ class IntakeUsageError extends Error {}
24
+
25
+ function manifestFile(contextDir ) { return path.join(contextDir, INTAKE_DIR, MANIFEST_FILE); }
26
+
27
+ /** True for a relative path of plain segments: nothing absolute, no `..`, no hidden segment, no backslash. */
28
+ function isConfinedPath(rel ) {
29
+ if (typeof rel !== 'string' || rel === '' || rel.includes('\\') || rel.includes('\0')) return false;
30
+ return rel.split('/').every((segment ) => segment !== '' && !segment.startsWith('.'));
31
+ }
32
+
33
+ /**
34
+ * Refuses a `.intake` that is a link or a plain file: the lock and the manifest would be written wherever it
35
+ * points. An absent `.intake` is fine (it is created as a real folder).
36
+ */
37
+ function assertIntakeDir(contextDir , fsImpl = nodeFs) {
38
+ const dir = path.join(contextDir, INTAKE_DIR);
39
+ let stat ;
40
+ try { stat = fsImpl.lstatSync(dir); } catch (error ) {
41
+ if (error.code === 'ENOENT' || error.code === 'ENOTDIR') return;
42
+ throw error;
43
+ }
44
+ if (stat.isSymbolicLink() || !stat.isDirectory()) throw new IntakeUsageError(`${dir} is not a real folder; intake writes its lock and manifest only inside a real ${INTAKE_DIR} folder. Nothing was written.`);
45
+ }
46
+
47
+ function badManifest(file , why ) {
48
+ return new IntakeUsageError(`${file} ${why}; the intake manifest is never rewritten when it cannot be trusted. Restore it or move it aside.`);
49
+ }
50
+
51
+ function validateMove(move , file , where ) {
52
+ const ok = move && typeof move === 'object'
53
+ && (move.from === null || isConfinedPath(move.from))
54
+ && isConfinedPath(move.to)
55
+ && typeof move.sha256 === 'string' && HASH.test(move.sha256)
56
+ && Number.isInteger(move.bytes) && move.bytes >= 0
57
+ && typeof move.at === 'string'
58
+ && STATES.includes(move.state)
59
+ && (move.reason === null || typeof move.reason === 'string');
60
+ if (!ok) throw badManifest(file, `has a malformed or unconfined move at ${where}`);
61
+ }
62
+
63
+ /** The manifest, or an empty one when none exists. Throws IntakeUsageError for a file it cannot trust. */
64
+ function readManifest(contextDir , fsImpl = nodeFs) {
65
+ assertIntakeDir(contextDir, fsImpl);
66
+ const file = manifestFile(contextDir);
67
+ if (!fsImpl.existsSync(file)) return { schema: MANIFEST_SCHEMA, runs: [] };
68
+ let data ;
69
+ try {
70
+ data = JSON.parse(fsImpl.readFileSync(file, 'utf8'));
71
+ } catch (error ) {
72
+ throw badManifest(file, `is not valid JSON (${error.message})`);
73
+ }
74
+ if (!data || typeof data !== 'object' || !Number.isInteger(data.schema) || !Array.isArray(data.runs)) {
75
+ throw badManifest(file, 'is not an intake manifest');
76
+ }
77
+ if (data.schema > MANIFEST_SCHEMA) {
78
+ throw new IntakeUsageError(`${file} is schema ${data.schema} and this DoFlow reads schema ${MANIFEST_SCHEMA}; upgrade DoFlow. The manifest was not touched.`);
79
+ }
80
+ if (data.schema !== MANIFEST_SCHEMA) throw badManifest(file, `has schema ${data.schema}`);
81
+ data.runs.forEach((run , i ) => {
82
+ const ok = run && typeof run === 'object' && typeof run.id === 'string' && ['sort', 'file-prompt'].includes(run.action)
83
+ && Array.isArray(run.createdDirs) && run.createdDirs.every(isConfinedPath) && Array.isArray(run.moves);
84
+ if (!ok) throw badManifest(file, `has a malformed run at runs[${i}]`);
85
+ run.moves.forEach((move , j ) => validateMove(move, file, `runs[${i}].moves[${j}]`));
86
+ });
87
+ return data;
88
+ }
89
+
90
+ /**
91
+ * Writes the manifest whole: temp file, then rename, so a reader sees the old file or the new one. `assertHeld`
92
+ * (from `withManifestLock`) is checked between the two: a writer that stalled past the lock's staleness and was
93
+ * reclaimed writes nothing (the way `updateTaskState` does).
94
+ */
95
+ function writeManifest(contextDir , manifest , fsImpl = nodeFs, assertHeld = () => {}) {
96
+ const file = manifestFile(contextDir);
97
+ fsImpl.mkdirSync(path.dirname(file), { recursive: true });
98
+ const tmp = `${file}.${process.pid}.tmp`;
99
+ try {
100
+ fsImpl.writeFileSync(tmp, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
101
+ assertHeld();
102
+ fsImpl.renameSync(tmp, file);
103
+ } catch (error ) {
104
+ try { fsImpl.rmSync(tmp, { force: true }); } catch { /* nothing to clean */ }
105
+ throw error;
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Runs `fn` holding the manifest's lock. A lock that cannot be taken throws the lock error unchanged. `fn` gets
111
+ * `assertHeld`, which throws an `IntakeUsageError` (exit 2) once the lock was reclaimed by another writer; the
112
+ * engine calls it before every manifest rename and every move of a user's file, so nothing more is written or moved.
113
+ */
114
+ function withManifestLock (contextDir , fn , fsImpl = nodeFs) {
115
+ assertIntakeDir(contextDir, fsImpl);
116
+ fsImpl.mkdirSync(path.join(contextDir, INTAKE_DIR), { recursive: true });
117
+ // The first check and the mkdir are two calls: a link put at `.intake` in between would make mkdir succeed
118
+ // silently and the lock land wherever it points. Looked at again here. Ceiling accepted: a swap after this
119
+ // look and before the lock is taken is one syscall wide; Node has no openat to close it.
120
+ assertIntakeDir(contextDir, fsImpl);
121
+ const release = acquireLock(fsImpl, manifestFile(contextDir));
122
+ const assertHeld = () => {
123
+ if (typeof release.held === 'function' && !release.held()) {
124
+ throw new IntakeUsageError(`the intake lock on ${manifestFile(contextDir)} was taken over by another writer (this run stalled too long); nothing further was written or moved. Run the command again.`);
125
+ }
126
+ };
127
+ try {
128
+ return fn(assertHeld);
129
+ } finally {
130
+ release();
131
+ }
132
+ }
133
+
134
+ /** `run-<UTC yyyymmddThhmmssZ>-<4 hex>`. */
135
+ function newRunId(now ) {
136
+ const stamp = now.toISOString().replace(/[-:]/g, '').replace(/\.\d+Z$/, 'Z');
137
+ return `run-${stamp}-${nodeCrypto.randomBytes(2).toString('hex')}`;
138
+ }
139
+
140
+ module.exports = { readManifest, writeManifest, withManifestLock, newRunId, manifestFile, isConfinedPath, assertIntakeDir, IntakeUsageError, MANIFEST_SCHEMA, INTAKE_DIR };
@@ -0,0 +1,206 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * The intake taxonomy (136 IC-005): `core/registry/intake.json` read on its own, not through `loadRegistry`,
5
+ * so a bad file turns intake off with a reason and never fails an installer command (the transcripts pattern,
6
+ * `src/runtime/transcripts/registry.js`). `validateTaxonomy` checks the closed shape and the id and name
7
+ * uniqueness; `loadTaxonomy` returns the core categories in file order, then the valid `intake-category`
8
+ * contributions of active packages (IC-006). A rule matches on a file's name and extension only, never on its content.
9
+ */
10
+
11
+ const fs = require('node:fs');
12
+ const path = require('node:path');
13
+ const { REPO_ROOT } = require('../../helper/repo-root.js');
14
+
15
+ const INTAKE_FILE = 'core/registry/intake.json';
16
+
17
+ const TOP_KEYS = ['version', 'description', 'recentWindowSeconds', 'pointerMaxBytes', 'textExtensions', 'promptCategory', 'categories'];
18
+ const CATEGORY_KEYS = ['id', 'names', 'summary', 'rules'];
19
+ const RULE_KEYS = ['extensions', 'namePattern', 'into', 'companionOf'];
20
+ const KEBAB = /^[a-z0-9]+(-[a-z0-9]+)*$/;
21
+ const EXTENSION = /^[a-z0-9]+$/;
22
+ const NAME_MAX = 40;
23
+ const INTO_SEGMENT = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
24
+
25
+ function isObject(value ) {
26
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
27
+ }
28
+
29
+ function unknownKeys(value , allowed , where , errors ) {
30
+ for (const key of Object.keys(value)) {
31
+ if (!allowed.includes(key)) errors.push(`${where}.${key} is not an allowed key (allowed: ${allowed.join(', ')})`);
32
+ }
33
+ }
34
+
35
+ function extensionList(value , where , errors , { required } ) {
36
+ if (!Array.isArray(value) || (required && value.length === 0)) {
37
+ errors.push(`${where} must be ${required ? 'a non-empty' : 'an'} array of lower-case extensions without a dot`);
38
+ return;
39
+ }
40
+ for (const ext of value) {
41
+ if (typeof ext !== 'string' || !EXTENSION.test(ext)) errors.push(`${where} entry ${JSON.stringify(ext)} must be a lower-case extension without a dot`);
42
+ }
43
+ if (new Set(value).size !== value.length) errors.push(`${where} names an extension twice`);
44
+ }
45
+
46
+ function validateRule(rule , where , errors ) {
47
+ if (!isObject(rule)) { errors.push(`${where} must be an object`); return; }
48
+ unknownKeys(rule, RULE_KEYS, where, errors);
49
+ extensionList(rule.extensions, `${where}.extensions`, errors, { required: true });
50
+ if (Object.hasOwn(rule, 'namePattern')) {
51
+ if (typeof rule.namePattern !== 'string' || rule.namePattern === '') {
52
+ errors.push(`${where}.namePattern must be a non-empty regular expression string`);
53
+ } else {
54
+ try { new RegExp(rule.namePattern, 'i'); } catch (error ) { errors.push(`${where}.namePattern does not compile (${error.message})`); }
55
+ }
56
+ }
57
+ if (Object.hasOwn(rule, 'into')) {
58
+ const into = rule.into;
59
+ const segments = typeof into === 'string' ? into.split('/') : [];
60
+ if (typeof into !== 'string' || segments.some((segment ) => !INTO_SEGMENT.test(segment) || segment === '..')) {
61
+ errors.push(`${where}.into must be a relative sub-path of plain names, with no '..', no leading dot and no empty segment`);
62
+ }
63
+ }
64
+ if (Object.hasOwn(rule, 'companionOf')) {
65
+ extensionList(rule.companionOf, `${where}.companionOf`, errors, { required: true });
66
+ if (Object.hasOwn(rule, 'into')) errors.push(`${where} has companionOf, so it follows its companion and takes no into`);
67
+ }
68
+ }
69
+
70
+ /**
71
+ * The shape errors of one category (IC-005, and IC-006's contributed entry): closed keys, a lower-kebab `id`
72
+ * and `names`, a summary, and valid rules. Uniqueness across categories is the caller's, who sees them all.
73
+ */
74
+ function validateCategory(category , where ) {
75
+ const errors = [];
76
+ if (!isObject(category)) return [`${where} must be an object`];
77
+ unknownKeys(category, CATEGORY_KEYS, where, errors);
78
+ for (const key of CATEGORY_KEYS) {
79
+ if (!Object.hasOwn(category, key)) errors.push(`${where}.${key} is missing`);
80
+ }
81
+ const kebab = (value ) => typeof value === 'string' && value.length <= NAME_MAX && KEBAB.test(value);
82
+ if (Object.hasOwn(category, 'id') && !kebab(category.id)) errors.push(`${where}.id must be lower-kebab, at most ${NAME_MAX} characters`);
83
+ if (Object.hasOwn(category, 'names')) {
84
+ if (!Array.isArray(category.names) || !category.names.every(kebab)) errors.push(`${where}.names must be an array of lower-kebab names, at most ${NAME_MAX} characters each`);
85
+ }
86
+ if (Object.hasOwn(category, 'summary') && (typeof category.summary !== 'string' || category.summary.trim() === '')) {
87
+ errors.push(`${where}.summary must be a non-empty string`);
88
+ }
89
+ if (Object.hasOwn(category, 'rules')) {
90
+ if (!Array.isArray(category.rules)) errors.push(`${where}.rules must be an array`);
91
+ else category.rules.forEach((rule , i ) => validateRule(rule, `${where}.rules[${i}]`, errors));
92
+ }
93
+ return errors;
94
+ }
95
+
96
+ /** Checks the registry's shape, then that every id and name is used once across all categories. */
97
+ function validateTaxonomy(registry ) {
98
+ const errors = [];
99
+ if (!isObject(registry)) return { ok: false, errors: ['registry must be an object'] };
100
+ unknownKeys(registry, TOP_KEYS, 'registry', errors);
101
+ for (const key of TOP_KEYS) {
102
+ if (!Object.hasOwn(registry, key)) errors.push(`registry.${key} is missing`);
103
+ }
104
+ if (Object.hasOwn(registry, 'version') && registry.version !== 1) errors.push('version must be 1');
105
+ if (Object.hasOwn(registry, 'description') && (typeof registry.description !== 'string' || registry.description.trim() === '')) {
106
+ errors.push('description must be a non-empty string');
107
+ }
108
+ if (Object.hasOwn(registry, 'recentWindowSeconds') && !(Number.isInteger(registry.recentWindowSeconds) && registry.recentWindowSeconds >= 0)) {
109
+ errors.push('recentWindowSeconds must be an integer of 0 or more');
110
+ }
111
+ if (Object.hasOwn(registry, 'pointerMaxBytes') && !(Number.isInteger(registry.pointerMaxBytes) && registry.pointerMaxBytes > 0)) {
112
+ errors.push('pointerMaxBytes must be a positive integer');
113
+ }
114
+ if (Object.hasOwn(registry, 'textExtensions')) extensionList(registry.textExtensions, 'textExtensions', errors, { required: true });
115
+ if (!Array.isArray(registry.categories) || registry.categories.length === 0) {
116
+ errors.push('categories must be a non-empty array');
117
+ return { ok: false, errors };
118
+ }
119
+ registry.categories.forEach((category , i ) => errors.push(...validateCategory(category, `categories[${i}]`)));
120
+ const owner = new Map ();
121
+ const claim = (name , id ) => {
122
+ if (typeof name !== 'string') return;
123
+ if (owner.has(name)) errors.push(`'${name}' is used by both '${owner.get(name)}' and '${id}'; ids and names are unique across categories`);
124
+ else owner.set(name, String(id));
125
+ };
126
+ for (const category of registry.categories) {
127
+ if (!isObject(category)) continue;
128
+ claim(category.id, category.id);
129
+ if (Array.isArray(category.names)) for (const name of category.names) claim(name, category.id);
130
+ }
131
+ const prompt = registry.categories.find((category ) => isObject(category) && category.id === registry.promptCategory);
132
+ if (!prompt) errors.push(`promptCategory '${registry.promptCategory}' is not a category id`);
133
+ else if (Array.isArray(prompt.rules) && prompt.rules.length > 0) errors.push(`the prompt category '${prompt.id}' must have no rules`);
134
+ return { ok: errors.length === 0, errors };
135
+ }
136
+
137
+ /**
138
+ * Returns `{ ok: true, taxonomy }` with the core categories in file order, each marked `origin: 'core'`, followed by
139
+ * the categories active packages contribute (IC-006), or `{ ok: false, reason: 'config-invalid', detail }` when the
140
+ * core file cannot be read or fails its shape. A contribution is tried after every core category, in ascending
141
+ * qualified-id order; its `id` becomes the qualified id, which is also its folder name. One whose id or names
142
+ * collide with a core category or an earlier contribution is left out and named in `problems`. The core list never
143
+ * depends on the package view: `contributionsFor` yields nothing when the point registry fails to load.
144
+ * `contributions: false` skips the package view. `view`, `homeDir`, `env`, `projectRoot` and `tmpDir` reach it.
145
+ */
146
+ function loadTaxonomy({ repoRoot = REPO_ROOT, file, fsImpl = fs, contributions = true, problems, view, homeDir, env, projectRoot, tmpDir } = {}) {
147
+ const target = file || path.join(path.resolve(repoRoot), INTAKE_FILE);
148
+ let registry ;
149
+ try {
150
+ registry = JSON.parse(fsImpl.readFileSync(target, 'utf8'));
151
+ } catch (error ) {
152
+ return { ok: false, reason: 'config-invalid', detail: `${target}: ${error.message}` };
153
+ }
154
+ const validation = validateTaxonomy(registry);
155
+ if (!validation.ok) return { ok: false, reason: 'config-invalid', detail: validation.errors.join('; ') };
156
+ const categories = registry.categories.map((category ) => ({ ...category, origin: 'core' }));
157
+ if (contributions) {
158
+ const used = new Set (categories.flatMap((category ) => [category.id, ...category.names]));
159
+ const { contributionsFor } = require('../packages/contributions.js');
160
+ for (const contribution of contributionsFor('intake-category', 1, { repoRoot, fsImpl, view, homeDir, env, projectRoot, tmpDir, problems })) {
161
+ const id = contribution.qualifiedId;
162
+ const clash = [id, ...contribution.data.names].find((name ) => used.has(name));
163
+ if (clash !== undefined) {
164
+ problems?.push(`intake-category contribution '${id}' is left out: '${clash}' is already the id or a name of another category`);
165
+ continue;
166
+ }
167
+ for (const name of [id, ...contribution.data.names]) used.add(name);
168
+ categories.push({ ...contribution.data, id, origin: id });
169
+ }
170
+ }
171
+ return { ok: true, taxonomy: { ...registry, categories } };
172
+ }
173
+
174
+ /**
175
+ * The folder a category files into: the first of its id and known names that is already a folder in the
176
+ * context folder, else its id (FR-003, IC-005). `existing` is the set of folder names found there.
177
+ */
178
+ function categoryFolder(category , existing ) {
179
+ return [category.id, ...category.names].find((name ) => existing.has(name)) ?? category.id;
180
+ }
181
+
182
+ /** True for bytes a text file can hold: no NUL byte and valid UTF-8. A binary saved as `.md` fails it. */
183
+ function isPlainText(buffer ) {
184
+ if (buffer.includes(0)) return false;
185
+ try {
186
+ new TextDecoder('utf-8', { fatal: true }).decode(buffer);
187
+ return true;
188
+ } catch {
189
+ return false;
190
+ }
191
+ }
192
+
193
+ /**
194
+ * True for a `/`-separated path inside a feature's user-context folder: `agent-docs/doflow/<slug>/additional-context/`
195
+ * anywhere in it (a main checkout, a linked worktree or a relative path between them). A folder of that name
196
+ * elsewhere in a project (`src/additional-context/`) is not the user's. The path is normalised first (a backslash
197
+ * becomes `/`, then `.`, `//` and `a/..` are resolved), so `x/./additional-context/` and `x/y/../additional-context/`
198
+ * are read as what they name and `additional-context/../../a.ts` as `a.ts`. A `..` left after that is only a leading
199
+ * one, the relative path between two checkouts, which the rule above keeps.
200
+ */
201
+ function isUserContextPath(file ) {
202
+ const parts = path.posix.normalize(file.replace(/\\/g, '/')).split('/');
203
+ return parts.some((part, i) => part === 'agent-docs' && parts[i + 1] === 'doflow' && Boolean(parts[i + 2]) && parts[i + 3] === 'additional-context');
204
+ }
205
+
206
+ module.exports = { isUserContextPath, isPlainText, loadTaxonomy, validateTaxonomy, validateCategory, categoryFolder, INTAKE_FILE };
@@ -8,7 +8,7 @@
8
8
  * States which of the report's findings the existing repair path can actually act on. That set is
9
9
  * narrower than the set of findings, and the difference is not self-evident:
10
10
  *
11
- * $ doflow status -g → 321 pending changes across 8 harnesses
11
+ * $ doflow status -g → 321 pending changes across several harnesses
12
12
  * $ doflow reconcile -g --dry-run → 10 drifts for 1 harness
13
13
  *
14
14
  * Both numbers are internally consistent. `status` reads the ledger, which records what DoFlow
@@ -162,7 +162,7 @@ function planBasis(snapshot ) {
162
162
  * source fingerprint, **and** the recorded ledger row equal to it too — which is right for its own
163
163
  * purpose, planning a write that also refreshes the row. It means a change is emitted whenever the
164
164
  * row alone is stale, and that happens routinely: the shared hooks asset projects one destination for
165
- * claude, codex, gemini, kiro and antigravity while ownership is recorded per harness, so a sibling's
165
+ * claude, codex, pi, kiro and antigravity while ownership is recorded per harness, so a sibling's
166
166
  * install legitimately refreshes bytes the other harnesses' rows still describe by their old hash.
167
167
  * Reading such a change as divergence reported one file, identical bytes, under contradictory
168
168
  * verdicts per harness, with a remedy offering to "restore" a file that was already correct.
@@ -21,7 +21,7 @@
21
21
  * first-match reading of that same order would get it backwards).
22
22
  *
23
23
  * A `merged` mode means the copies are combined rather than chosen between, so no single copy
24
- * "wins". An `unestablished` mode means the order may be documented (`gemini` documents one) or
24
+ * "wins". An `unestablished` mode means the order may be documented (no shipped harness does today) or
25
25
  * may not (`pi`, `antigravity` document neither), but either way the rule for choosing between
26
26
  * copies is not — so no winner is named. Those two cases are separate states under IC-003 and the
27
27
  * withheld reason says which: a documented order with an undocumented winner names the order it
@@ -96,7 +96,7 @@ function resolveShadowVerdict(presentScopes , precedence ) {
96
96
 
97
97
  if (mode === 'unestablished') {
98
98
  // IC-003 keeps these two states distinct: an order established with the mode explicitly
99
- // unestablished (`gemini`) is not the same finding as an order sought and found undocumented
99
+ // unestablished is not the same finding as an order sought and found undocumented
100
100
  // (`pi`, `antigravity`). Both withhold the verdict; only one of them knows the reading order.
101
101
  const orderIsEstablished = Array.isArray(order) && order.length > 0;
102
102
  return {
@@ -23,7 +23,7 @@ const path = require('node:path');
23
23
  *
24
24
  * **The bound on cost (R6).** Only the parent directories of assets already enumerated from the
25
25
  * ledger are read — one level, never recursed into. A managed tree that nests (Kiro's steering tree
26
- * mirrors `core/shared/guidance/`, `rules/` and `mcp/` included) is still covered completely
26
+ * mirrors the core set of `core/shared/guidance/`, `rules/` included) is still covered completely
27
27
  * without a walk, because every subdirectory holding a managed file is itself the parent of a
28
28
  * managed resource and so is inspected on its own account. `mcp-builder/` and `skill-creator/`
29
29
  * sitting beside a managed skills directory are never enumerated: neither is the parent of a
@@ -51,10 +51,12 @@ const path = require('node:path');
51
51
  * *every* file it copies under an `applyTo: '**'` front-matter header, so every file landing in
52
52
  * `.github/instructions/` is always applied. Asset `instructions.copilot`, native dir
53
53
  * `instructions`.
54
- * - `src/adapters/kiro/index.js` (header comment, lines 8-12) — "Kiro receives DoFlow's *full*
55
- * guidance tree as steering files", with no single-file instruction surface at all. Asset
54
+ * - `src/adapters/kiro/index.js` (header comment, lines 8-12) — Kiro has no single-file
55
+ * instruction surface and loads every steering file without front-matter. DoFlow-produced
56
+ * steering holds only the core set (selected via `guidance-manifest`), but the directory is
57
+ * still loaded wholesale, so an unmanaged file there is operative. Asset
56
58
  * `guidance.context-layer` for the `kiro` harness only, native dir `steering`; the same asset's
57
- * `../.doflow/guidance` projection for claude/codex/gemini/antigravity is name-resolved by
59
+ * `../.doflow/guidance` projection for the other harnesses is name-resolved by
58
60
  * explicit import and is not in this set.
59
61
  *
60
62
  * This is the single place to change. If the registry later declares load semantics per projection,