@khoavu882/doflow 2.0.0-beta.5 → 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 (471) hide show
  1. package/README.md +12 -11
  2. package/bin/doflow.js +21 -11
  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/permission-audit.sh +3 -0
  9. package/core/harnesses/claude/hooks/post-compact.sh +35 -32
  10. package/core/harnesses/claude/hooks/post-edit-lint.sh +2 -0
  11. package/core/harnesses/claude/hooks/post-shell-observe.sh +48 -0
  12. package/core/harnesses/claude/hooks/post-tool-failure.sh +3 -0
  13. package/core/harnesses/claude/hooks/pre-compact.sh +23 -27
  14. package/core/harnesses/claude/hooks/session-end.sh +23 -10
  15. package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
  16. package/core/harnesses/claude/settings/settings.json +18 -0
  17. package/core/harnesses/claude/statusline/doflow-statusline.sh +1 -0
  18. package/core/harnesses/codex/agents/core-implementer.toml +1 -0
  19. package/core/harnesses/codex/agents/delivery-lead.toml +10 -0
  20. package/core/harnesses/codex/agents/quality-guardian.toml +1 -1
  21. package/core/harnesses/codex/agents/research-writer.toml +1 -1
  22. package/core/harnesses/codex/agents/reviewer.toml +10 -0
  23. package/core/harnesses/codex/agents/spec-analyst.toml +1 -1
  24. package/core/harnesses/codex/agents/system-architect.toml +1 -1
  25. package/core/harnesses/codex/hooks/lib.sh +44 -25
  26. package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
  27. package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
  28. package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
  29. package/core/harnesses/codex/hooks/subagent-audit.sh +1 -0
  30. package/core/harnesses/copilot/hooks/doflow-lib.sh +93 -0
  31. package/core/harnesses/copilot/hooks/doflow-mcp-tool-guard.sh +9 -0
  32. package/core/harnesses/copilot/hooks/doflow-pre-bash-guard.sh +9 -0
  33. package/core/harnesses/copilot/hooks/doflow-pre-implement-gate.sh +9 -0
  34. package/core/harnesses/copilot/hooks/doflow-session-start.sh +9 -0
  35. package/core/harnesses/copilot/hooks/doflow-stop-check.sh +9 -0
  36. package/core/harnesses/copilot/hooks/doflow-subagent-start.sh +9 -0
  37. package/core/harnesses/copilot/hooks/doflow-subagent-stop.sh +9 -0
  38. package/core/harnesses/copilot/hooks/doflow.json +57 -0
  39. package/core/harnesses/kiro/agents/doflow.json +1 -1
  40. package/core/harnesses/kiro/hooks/lib.sh +26 -6
  41. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +1 -0
  42. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +1 -0
  43. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +1 -0
  44. package/core/harnesses/kiro/hooks/session-start.sh +1 -0
  45. package/core/harnesses/kiro/hooks/stop-check.sh +1 -0
  46. package/core/harnesses/pi/extensions/doflow-guard.js +8 -0
  47. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +0 -1
  48. package/core/harnesses/shared/hooks/bridge/bridge.sh +102 -9
  49. package/core/harnesses/shared/hooks/bridge/envelope.jq +87 -18
  50. package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
  51. package/core/harnesses/shared/hooks/bridge/respond.sh +10 -2
  52. package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +28 -14
  53. package/core/harnesses/shared/hooks/policies/capture-failure.sh +48 -15
  54. package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
  55. package/core/harnesses/shared/hooks/policies/contract.sh +3 -0
  56. package/core/harnesses/shared/hooks/policies/deny-json.sh +2 -11
  57. package/core/harnesses/shared/hooks/policies/emission-rules.sh +37 -0
  58. package/core/harnesses/shared/hooks/policies/emission.jq +209 -0
  59. package/core/harnesses/shared/hooks/policies/emission.sh +383 -0
  60. package/core/harnesses/shared/hooks/policies/identity.sh +177 -0
  61. package/core/harnesses/shared/hooks/policies/lib.sh +63 -40
  62. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +3 -2
  63. package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +213 -12
  64. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +114 -38
  65. package/core/harnesses/shared/hooks/policies/session-context.sh +30 -1
  66. package/core/harnesses/shared/hooks/policies/stop-check.sh +20 -9
  67. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +5 -2
  68. package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
  69. package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +60 -155
  70. package/core/harnesses/shared/hooks/stream-hook-runner.js +47 -8
  71. package/core/harnesses/shared/locator/doflow-run +2 -3
  72. package/core/registry/advice.json +3 -3
  73. package/core/registry/assets.json +55 -82
  74. package/core/registry/capabilities.json +12 -0
  75. package/core/registry/contracts.json +58 -31
  76. package/core/registry/contribution-points.json +32 -0
  77. package/core/registry/gates.json +32 -0
  78. package/core/registry/guidance-caps.json +40 -0
  79. package/core/registry/harnesses.json +1442 -363
  80. package/core/registry/lifecycle.json +485 -304
  81. package/core/registry/mcp.json +5 -1
  82. package/core/registry/models.json +7 -1
  83. package/core/registry/package-templates/README.md +19 -0
  84. package/core/registry/package-templates/agent/files/agent.md +9 -0
  85. package/core/registry/package-templates/agent/template.json +4 -0
  86. package/core/registry/package-templates/capability-provider/files/provider.json +9 -0
  87. package/core/registry/package-templates/capability-provider/template.json +11 -0
  88. package/core/registry/package-templates/debate-preset/files/preset.json +26 -0
  89. package/core/registry/package-templates/debate-preset/template.json +4 -0
  90. package/core/registry/package-templates/review-preset/files/preset.json +30 -0
  91. package/core/registry/package-templates/review-preset/template.json +4 -0
  92. package/core/registry/package-templates/skill/files/SKILL.md +8 -0
  93. package/core/registry/package-templates/skill/template.json +4 -0
  94. package/core/registry/package-templates/worker/files/worker.json +92 -0
  95. package/core/registry/package-templates/worker/template.json +11 -0
  96. package/core/registry/packages/doflow/doflow-package.json +15 -0
  97. package/core/registry/packages/doflow/presets/debate-3.json +26 -0
  98. package/core/registry/protocols.json +61 -0
  99. package/core/registry/routes.json +17 -0
  100. package/core/registry/rules.json +431 -0
  101. package/core/registry/settings.json +276 -0
  102. package/core/registry/storage.json +679 -31
  103. package/core/registry/telemetry.json +236 -0
  104. package/core/registry/transcripts.json +142 -0
  105. package/core/registry/verification.json +76 -1
  106. package/core/registry/workflows.json +55 -0
  107. package/core/shared/agent-specs/core-implementer.md +3 -0
  108. package/core/shared/agent-specs/delivery-lead.md +68 -0
  109. package/core/shared/agent-specs/quality-guardian.md +5 -1
  110. package/core/shared/agent-specs/research-writer.md +10 -3
  111. package/core/shared/agent-specs/reviewer.md +53 -0
  112. package/core/shared/agent-specs/spec-analyst.md +5 -6
  113. package/core/shared/agent-specs/system-architect.md +5 -1
  114. package/core/shared/guidance/DOFLOW_CORE.md +1 -5
  115. package/core/shared/guidance/modes/MODE_DeepResearch.md +1 -1
  116. package/core/shared/guidance/modes/MODE_Orchestration.md +4 -3
  117. package/core/shared/guidance/modes/MODE_Task_Management.md +14 -14
  118. package/core/shared/guidance/pointers/codex.md +1 -4
  119. package/core/shared/guidance/references/ARTIFACT_FORMAT.md +30 -16
  120. package/core/shared/guidance/references/ARTIFACT_VOICE.md +3 -2
  121. package/core/shared/guidance/references/CONSTITUTION_BASE.md +17 -15
  122. package/core/shared/guidance/references/DIAGRAM_GUIDE.md +1 -1
  123. package/core/shared/guidance/references/DOFLOW_CHAIN.md +17 -19
  124. package/core/shared/guidance/references/EVIDENCE_LEDGER.md +7 -8
  125. package/core/shared/guidance/references/MODEL_SELECTION.md +5 -3
  126. package/core/shared/guidance/references/ORCHESTRATION.md +1 -2
  127. package/core/shared/guidance/rules/RULES.md +45 -0
  128. package/core/shared/scripts/doflow/bash/do-args.sh +37 -0
  129. package/core/shared/scripts/doflow/bash/do-exec-paths.sh +4 -0
  130. package/core/shared/scripts/doflow/bash/do-git-state.sh +15 -4
  131. package/core/shared/scripts/doflow/bash/do-parallel-check.sh +4 -0
  132. package/core/shared/scripts/doflow/bash/do-paths.sh +40 -2
  133. package/core/shared/scripts/doflow/bash/do-prereqs.sh +2 -1
  134. package/core/shared/scripts/doflow/bash/do-review-package.sh +4 -0
  135. package/core/shared/scripts/doflow/bash/do-task-brief.sh +58 -16
  136. package/core/shared/scripts/doflow/bash/render-audit.sh +26 -2
  137. package/core/shared/scripts/doflow/bash/render-diagrams.sh +1 -0
  138. package/core/shared/scripts/doflow/bash/render-puml.sh +2 -1
  139. package/core/shared/scripts/doflow/bash/validate-artifacts.sh +48 -4
  140. package/core/shared/scripts/doflow/bin/doflow-run +149 -34
  141. package/core/shared/skills/do/SKILL.md +12 -10
  142. package/core/shared/skills/do/references/advice_calls.md +26 -0
  143. package/core/shared/skills/do/references/pm_routing.md +1 -2
  144. package/core/shared/skills/do/references/program_mode.md +31 -0
  145. package/core/shared/skills/do-brainstorm/SKILL.md +20 -74
  146. package/core/shared/skills/do-brainstorm/references/handoff_branches.md +35 -0
  147. package/core/shared/skills/do-brainstorm/references/intent_seed.md +21 -0
  148. package/core/shared/skills/do-brainstorm/references/round_log.md +17 -0
  149. package/core/shared/skills/do-code-review/SKILL.md +41 -144
  150. package/core/shared/skills/do-code-review/content-types/markdown.md +24 -2
  151. package/core/shared/skills/do-code-review/references/tools.md +90 -0
  152. package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +549 -7
  153. package/core/shared/skills/do-design/SKILL.md +18 -17
  154. package/core/shared/skills/do-diagnose/SKILL.md +14 -4
  155. package/core/shared/skills/do-document/SKILL.md +11 -1
  156. package/core/shared/skills/do-document/references/deep_research.md +1 -2
  157. package/core/shared/skills/do-document/references/embedded-research.md +18 -6
  158. package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
  159. package/core/shared/skills/do-execute-plan/SKILL.md +15 -58
  160. package/core/shared/skills/do-execute-plan/references/handoff_record.md +7 -2
  161. package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +13 -0
  162. package/core/shared/skills/do-execute-plan/references/readiness_gate.md +26 -1
  163. package/core/shared/skills/do-execute-plan/references/run_position.md +13 -1
  164. package/core/shared/skills/do-execute-plan/references/scaffold.md +17 -9
  165. package/core/shared/skills/do-execute-plan/references/state_checkpoint.md +11 -0
  166. package/core/shared/skills/do-execute-plan/references/task_attempts.md +78 -24
  167. package/core/shared/skills/do-flow/SKILL.md +8 -9
  168. package/core/shared/skills/do-flow/references/task_classes.md +2 -2
  169. package/core/shared/skills/do-git/SKILL.md +4 -0
  170. package/core/shared/skills/do-implement/SKILL.md +4 -7
  171. package/core/shared/skills/do-plan/SKILL.md +26 -15
  172. package/core/shared/skills/do-test/SKILL.md +7 -4
  173. package/core/shared/templates/doflow/constitution-template.md +9 -3
  174. package/core/shared/templates/doflow/data-model-template.md +1 -1
  175. package/core/shared/templates/doflow/design-template.md +113 -16
  176. package/core/shared/templates/doflow/plan-template.md +11 -10
  177. package/core/shared/templates/doflow/question-log-template.md +14 -2
  178. package/core/shared/templates/doflow/requirement-template.md +1 -1
  179. package/core/worker-evidence/antigravity/checks.json +14 -0
  180. package/core/worker-evidence/codex/2026-10-18-cli-check.json +107 -0
  181. package/core/worker-evidence/codex/2026-10-18-live-run-edit.json +73 -0
  182. package/core/worker-evidence/codex/2026-10-18-live-run-read-only.json +62 -0
  183. package/core/worker-evidence/codex/2026-10-24-cli-check.json +126 -0
  184. package/core/worker-evidence/codex/checks.json +38 -0
  185. package/package.json +10 -6
  186. package/src/adapters/antigravity/index.js +143 -66
  187. package/src/adapters/claude/index.js +24 -4
  188. package/src/adapters/codex/agents.js +25 -15
  189. package/src/adapters/codex/index.js +48 -13
  190. package/src/adapters/codex/native-projection.js +6 -2
  191. package/src/adapters/copilot/index.js +58 -23
  192. package/src/adapters/copy-tree.js +290 -44
  193. package/src/adapters/guidance-manifest.js +71 -0
  194. package/src/adapters/guidance-pointer.js +19 -0
  195. package/src/adapters/hook-commands.js +6 -10
  196. package/src/adapters/index.js +1 -0
  197. package/src/adapters/kiro/index.js +23 -12
  198. package/src/adapters/opencode/index.js +8 -8
  199. package/src/adapters/pi/hooks.js +2 -1
  200. package/src/adapters/pi/index.js +14 -7
  201. package/src/adapters/resolve-source.js +26 -2
  202. package/src/cli/commands/install.js +2 -1
  203. package/src/cli/commands/reconcile.js +53 -9
  204. package/src/cli/commands/remove.js +2 -2
  205. package/src/cli/commands/rollback.js +6 -3
  206. package/src/cli/commands/self-update.js +18 -9
  207. package/src/cli/commands/status.js +21 -2
  208. package/src/cli/commands/ui.js +99 -0
  209. package/src/cli/commands/update.js +2 -1
  210. package/src/cli/index.js +322 -29
  211. package/src/cli/runtime-commands.js +209 -57
  212. package/src/cli/shared.js +0 -2
  213. package/src/helper/git.js +1 -1
  214. package/src/helper/guardrails.js +1 -1
  215. package/src/helper/marker-merge.js +31 -13
  216. package/src/helper/package-ids.js +37 -0
  217. package/src/helper/semver.js +94 -0
  218. package/src/install/backup.js +105 -15
  219. package/src/install/context.js +1 -1
  220. package/src/install/targets.js +31 -16
  221. package/src/lifecycle/coverage.js +20 -3
  222. package/src/lifecycle/hook-tier.js +160 -0
  223. package/src/lifecycle/index.js +412 -78
  224. package/src/lifecycle/retired-harnesses.js +318 -0
  225. package/src/lifecycle/view.js +35 -15
  226. package/src/registry/index.js +551 -26
  227. package/src/registry/worker-ids.js +45 -0
  228. package/src/release/build-package.js +0 -3
  229. package/src/release/docs-blocks.js +492 -0
  230. package/src/release/preflight.js +425 -0
  231. package/src/release/rehearse.js +401 -0
  232. package/src/release/release-workflow.js +733 -0
  233. package/src/release/sync-plugin-versions.js +43 -7
  234. package/src/runtime/advice/bench-gate.js +25 -0
  235. package/src/runtime/advice/cli.js +59 -16
  236. package/src/runtime/advice/client.js +32 -9
  237. package/src/runtime/advice/config.js +98 -133
  238. package/src/runtime/advice/doctor.js +1 -1
  239. package/src/runtime/advice/elevation.js +97 -0
  240. package/src/runtime/advice/endpoint-guard.js +9 -3
  241. package/src/runtime/advice/mode.js +37 -0
  242. package/src/runtime/advice/record.js +18 -5
  243. package/src/runtime/advice/registry.js +4 -1
  244. package/src/runtime/advice/shadow-config.js +229 -0
  245. package/src/runtime/advice/shadow-dispatcher.js +276 -0
  246. package/src/runtime/brief-rules.js +81 -0
  247. package/src/runtime/brief-tools.js +111 -34
  248. package/src/runtime/candidate.js +20 -11
  249. package/src/runtime/capabilities/executor.js +450 -0
  250. package/src/runtime/capabilities/loopback.js +41 -0
  251. package/src/runtime/capabilities/sanitizer.js +88 -0
  252. package/src/runtime/capability-router.js +135 -3
  253. package/src/runtime/checkouts.js +73 -11
  254. package/src/runtime/claims.js +13 -7
  255. package/src/runtime/cli.js +196 -19
  256. package/src/runtime/config-home.js +42 -0
  257. package/src/runtime/context/arbitrate.js +767 -0
  258. package/src/runtime/context/delivery-store.js +4 -6
  259. package/src/runtime/context/emission-rules.js +118 -0
  260. package/src/runtime/context/identity.js +227 -0
  261. package/src/runtime/context/primary-command.js +117 -0
  262. package/src/runtime/context/primary.js +344 -0
  263. package/src/runtime/context/summary-store.js +655 -0
  264. package/src/runtime/context-pack.js +154 -78
  265. package/src/runtime/decision-register.js +13 -4
  266. package/src/runtime/dispatch/admission.js +392 -0
  267. package/src/runtime/dispatch/attempt-end.js +274 -0
  268. package/src/runtime/dispatch/command.js +628 -0
  269. package/src/runtime/dispatch/controls.js +904 -0
  270. package/src/runtime/dispatch/envelope.js +276 -0
  271. package/src/runtime/dispatch/grants.js +310 -0
  272. package/src/runtime/dispatch/integrate.js +617 -0
  273. package/src/runtime/dispatch/loop.js +1835 -0
  274. package/src/runtime/dispatch/store.js +352 -0
  275. package/src/runtime/dispatch/verify-run.js +201 -0
  276. package/src/runtime/evidence-ledger.js +34 -7
  277. package/src/runtime/failure/capture.js +3 -1
  278. package/src/runtime/failure/cli.js +25 -9
  279. package/src/runtime/failure/home.js +57 -30
  280. package/src/runtime/failure/store.js +7 -2
  281. package/src/runtime/feature-pointer.js +174 -0
  282. package/src/runtime/feature-resolve.js +13 -2
  283. package/src/runtime/fleet/command.js +77 -0
  284. package/src/runtime/fleet/projects.js +178 -0
  285. package/src/runtime/fleet/tree.js +383 -0
  286. package/src/runtime/freshness.js +33 -3
  287. package/src/runtime/git-memo.js +46 -0
  288. package/src/runtime/guidance/ablation.js +427 -0
  289. package/src/runtime/health.js +565 -17
  290. package/src/runtime/history-compactor.js +13 -3
  291. package/src/runtime/implementation-gate.js +277 -22
  292. package/src/runtime/inventory/coverage.js +1 -1
  293. package/src/runtime/inventory/index.js +1 -1
  294. package/src/runtime/inventory/precedence.js +2 -2
  295. package/src/runtime/inventory/siblings.js +6 -4
  296. package/src/runtime/knowledge/code-corpus.js +277 -0
  297. package/src/runtime/knowledge/code-lexical.js +130 -0
  298. package/src/runtime/knowledge/code-retrieval.js +307 -0
  299. package/src/runtime/knowledge/code-validation.js +106 -0
  300. package/src/runtime/knowledge/gp3-gate.js +74 -0
  301. package/src/runtime/knowledge/graph-adapter.js +288 -0
  302. package/src/runtime/knowledge/graph-fallback.js +394 -0
  303. package/src/runtime/knowledge/hybrid.js +136 -8
  304. package/src/runtime/knowledge/retrieval.js +34 -1
  305. package/src/runtime/knowledge/rtk-compress.js +77 -0
  306. package/src/runtime/knowledge/semble-bridge.py +106 -0
  307. package/src/runtime/knowledge/semble-lifecycle.js +114 -0
  308. package/src/runtime/knowledge/semble-provider.js +365 -0
  309. package/src/runtime/lifecycle/cli.js +16 -6
  310. package/src/runtime/lifecycle/event-store.js +8 -6
  311. package/src/runtime/lifecycle/followup.js +6 -1
  312. package/src/runtime/lifecycle/overview.js +24 -1
  313. package/src/runtime/lifecycle/report-store.js +8 -10
  314. package/src/runtime/lifecycle/retention.js +95 -31
  315. package/src/runtime/lifecycle/root.js +9 -1
  316. package/src/runtime/lifecycle/status.js +34 -17
  317. package/src/runtime/lifecycle/store-upkeep.js +56 -7
  318. package/src/runtime/mask.js +134 -7
  319. package/src/runtime/mcp-consultations.js +40 -6
  320. package/src/runtime/model-router.js +79 -7
  321. package/src/runtime/models/catalogue.js +191 -0
  322. package/src/runtime/models/command.js +153 -0
  323. package/src/runtime/off-words.js +12 -0
  324. package/src/runtime/outcome.js +2 -2
  325. package/src/runtime/packages/command.js +1009 -0
  326. package/src/runtime/packages/contributions.js +116 -0
  327. package/src/runtime/packages/digest.js +132 -0
  328. package/src/runtime/packages/fetch.js +182 -0
  329. package/src/runtime/packages/grants.js +192 -0
  330. package/src/runtime/packages/index-file.js +134 -0
  331. package/src/runtime/packages/instruction.js +90 -0
  332. package/src/runtime/packages/manifest.js +331 -0
  333. package/src/runtime/packages/points.js +477 -0
  334. package/src/runtime/packages/programs.js +216 -0
  335. package/src/runtime/packages/scaffold.js +277 -0
  336. package/src/runtime/packages/source.js +90 -0
  337. package/src/runtime/packages/state.js +164 -0
  338. package/src/runtime/packages/store.js +449 -0
  339. package/src/runtime/packages/tar.js +106 -0
  340. package/src/runtime/packages/view.js +406 -0
  341. package/src/runtime/plan-check.js +59 -0
  342. package/src/runtime/plan-content.js +324 -0
  343. package/src/runtime/policy-init.js +118 -0
  344. package/src/runtime/profiles/doctor.js +96 -0
  345. package/src/runtime/profiles/resolve.js +244 -0
  346. package/src/runtime/profiles/rollback.js +254 -0
  347. package/src/runtime/profiles/sampler.js +41 -0
  348. package/src/runtime/readiness-record.js +16 -11
  349. package/src/runtime/readiness.js +46 -5
  350. package/src/runtime/research-request.js +50 -25
  351. package/src/runtime/retrieval-plan.js +10 -30
  352. package/src/runtime/retrieval-record.js +75 -5
  353. package/src/runtime/review/command.js +246 -0
  354. package/src/runtime/review/dominance.js +68 -0
  355. package/src/runtime/review/findings.js +434 -0
  356. package/src/runtime/review/identity.js +255 -0
  357. package/src/runtime/review/protocol-command.js +109 -0
  358. package/src/runtime/review/protocols.js +405 -0
  359. package/src/runtime/review/resolve-protocol.js +302 -0
  360. package/src/runtime/review/start.js +530 -0
  361. package/src/runtime/roadmap/facts.js +479 -0
  362. package/src/runtime/roadmap/gates.js +270 -0
  363. package/src/runtime/roadmap/loop.js +1636 -0
  364. package/src/runtime/roadmap/merge.js +381 -0
  365. package/src/runtime/roadmap/rows.js +1113 -0
  366. package/src/runtime/roadmap/seed.js +951 -0
  367. package/src/runtime/rules-report.js +336 -0
  368. package/src/runtime/scaffold/artifacts.js +60 -9
  369. package/src/runtime/scaffold/generate.js +21 -9
  370. package/src/runtime/settings/autopilot.js +106 -0
  371. package/src/runtime/settings/cli.js +280 -0
  372. package/src/runtime/settings/config-write.js +314 -0
  373. package/src/runtime/settings/files.js +271 -0
  374. package/src/runtime/settings/legacy.js +154 -0
  375. package/src/runtime/settings/lexical.js +56 -0
  376. package/src/runtime/settings/prepared-run.js +269 -0
  377. package/src/runtime/settings/project-layers.js +118 -0
  378. package/src/runtime/settings/refs.js +288 -0
  379. package/src/runtime/settings/registry.js +553 -0
  380. package/src/runtime/settings/resolve.js +713 -0
  381. package/src/runtime/settings/shared.js +37 -0
  382. package/src/runtime/settings/worker-billing.js +107 -0
  383. package/src/runtime/settings/write.js +269 -0
  384. package/src/runtime/slot-command.js +78 -0
  385. package/src/runtime/slots.js +671 -0
  386. package/src/runtime/source-version.js +1 -0
  387. package/src/runtime/state-root.js +217 -0
  388. package/src/runtime/storage/cli.js +12 -5
  389. package/src/runtime/storage/engine.js +9 -2
  390. package/src/runtime/storage/registry.js +14 -1
  391. package/src/runtime/storage/roots.js +66 -16
  392. package/src/runtime/storage/rules.js +82 -10
  393. package/src/runtime/storage/upkeep.js +10 -2
  394. package/src/runtime/storage/work-status.js +245 -26
  395. package/src/runtime/task-attempts.js +589 -69
  396. package/src/runtime/task-classifier.js +47 -11
  397. package/src/runtime/task-graph.js +373 -0
  398. package/src/runtime/task-run.js +778 -0
  399. package/src/runtime/task-scope.js +176 -55
  400. package/src/runtime/task-sources.js +147 -0
  401. package/src/runtime/task-state.js +374 -64
  402. package/src/runtime/team/admission.js +301 -0
  403. package/src/runtime/team/envelope.js +324 -0
  404. package/src/runtime/team/review-guard.js +156 -0
  405. package/src/runtime/trace/analyze.js +427 -0
  406. package/src/runtime/trace/baseline.js +770 -0
  407. package/src/runtime/trace/ledger.js +63 -140
  408. package/src/runtime/trace/otlp-export.js +568 -0
  409. package/src/runtime/trace/otlp-map.js +481 -0
  410. package/src/runtime/trace/spans.js +745 -0
  411. package/src/runtime/trace/telemetry.js +225 -0
  412. package/src/runtime/trace/views.js +1 -0
  413. package/src/runtime/transcripts/command.js +98 -0
  414. package/src/runtime/transcripts/cursor.js +326 -0
  415. package/src/runtime/transcripts/read.js +126 -0
  416. package/src/runtime/transcripts/readers/claude-jsonl.js +74 -0
  417. package/src/runtime/transcripts/readers/codex-rollout.js +70 -0
  418. package/src/runtime/transcripts/readers/copilot-events.js +90 -0
  419. package/src/runtime/transcripts/readers/kiro-jsonl.js +77 -0
  420. package/src/runtime/transcripts/readers/pi-jsonl.js +59 -0
  421. package/src/runtime/transcripts/registry.js +161 -0
  422. package/src/runtime/transcripts/stats.js +161 -0
  423. package/src/runtime/verification/check-key.js +628 -0
  424. package/src/runtime/verification/contract-runner.js +546 -11
  425. package/src/runtime/verification/engine.js +499 -9
  426. package/src/runtime/verification/impact.js +617 -0
  427. package/src/runtime/verification/inflight.js +205 -0
  428. package/src/runtime/verification/registry.js +176 -0
  429. package/src/runtime/verification/reuse.js +184 -0
  430. package/src/runtime/verification/telemetry-emit.js +254 -0
  431. package/src/runtime/verification/test-selection.js +67 -7
  432. package/src/runtime/worker/active.js +90 -0
  433. package/src/runtime/worker/cancel.js +222 -0
  434. package/src/runtime/worker/catalogue.js +122 -0
  435. package/src/runtime/worker/command.js +677 -0
  436. package/src/runtime/worker/evidence.js +288 -0
  437. package/src/runtime/worker/launch.js +288 -0
  438. package/src/runtime/worker/models.js +137 -0
  439. package/src/runtime/worker/parsers.js +218 -0
  440. package/src/runtime/worker/probes/index.js +706 -0
  441. package/src/runtime/worker/process-tree.js +415 -0
  442. package/src/runtime/worker/quota.js +43 -0
  443. package/src/runtime/worker/readiness.js +713 -0
  444. package/src/runtime/worker/run-store.js +166 -0
  445. package/src/runtime/worker/supervisor.js +541 -0
  446. package/src/runtime/worker/worktree-check.js +252 -0
  447. package/src/runtime/workflow-engine.js +5 -1
  448. package/src/runtime/workflow-orchestrator.js +578 -63
  449. package/src/runtime/worktree.js +149 -22
  450. package/src/state/index.js +1 -1
  451. package/src/state/lockfile.js +64 -10
  452. package/src/state/run-lock.js +1 -1
  453. package/types/node-commonjs.d.ts +106 -7
  454. package/core/harnesses/gemini/hooks/hooks.json +0 -77
  455. package/core/harnesses/gemini/hooks/lib.sh +0 -302
  456. package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +0 -10
  457. package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
  458. package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -10
  459. package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
  460. package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -10
  461. package/core/harnesses/gemini/hooks/session-end.impl.sh +0 -89
  462. package/core/harnesses/gemini/hooks/session-end.sh +0 -5
  463. package/core/harnesses/gemini/hooks/session-start.sh +0 -7
  464. package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
  465. package/core/shared/guidance/FLAGS.md +0 -25
  466. package/core/shared/guidance/rules/RULE_01_SAFETY.md +0 -25
  467. package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +0 -23
  468. package/core/shared/guidance/rules/RULE_03_QUALITY.md +0 -14
  469. package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +0 -46
  470. package/src/adapters/gemini/hooks.js +0 -251
  471. package/src/adapters/gemini/index.js +0 -429
@@ -0,0 +1,116 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * The contribution seam (122 IC-013). A point's owner calls `contributionsFor(point, version)` inside its own
5
+ * loader (`loadProtocols` for the preset points); there is no shared loader for all registries. It returns the
6
+ * entries of `active` packages only, each one through the point's validator, in ascending `qualifiedId` order.
7
+ * A reserved point, an undeclared point or an unsupported version is a programming error and throws; a package
8
+ * fault never does (FR-009): the entry is left out and, when the caller passes `problems`, named there.
9
+ *
10
+ * First-party entries are trusted at runtime and verified at release (IC-015), so they go through the validator
11
+ * in core mode, which reads only the registries under `core/registry/`; the package-mode extras (read-only
12
+ * agents, comparability) are checked by guard G68. A third-party entry goes through `judgePackagePreset`, the
13
+ * package-mode validator `check` runs too (IC-014), so the two cannot disagree; its qualified id is
14
+ * `<package>.<local id>` (IC-003) and it is `firstParty: false`. Each package is judged alone: a fault in one
15
+ * leaves the others' entries in (FR-009).
16
+ */
17
+
18
+ const fs = require('node:fs');
19
+ const path = require('node:path');
20
+ const { REPO_ROOT } = require('../../helper/repo-root.js');
21
+ const { packageView } = require('./view.js');
22
+ const { loadPoints, POINT_VALIDATORS } = require('./points.js');
23
+ const { MANIFEST_FILE, judgePackagePreset, coreAgents } = require('./manifest.js');
24
+ const { loadCoreProtocols, lensIds } = require('../review/protocols.js');
25
+
26
+ const PRESET_KIND = { 'review-preset': 'review', 'debate-preset': 'debate' };
27
+
28
+ /**
29
+ * `Array<{ qualifiedId, localId, packageId, firstParty, data, dir, provenance: { contentDigest, source } }>`.
30
+ * Options: `view` (a package view; default: the view of `repoRoot`, built with `homeDir`, `env`, `projectRoot` and
31
+ * `tmpDir` when given), `repoRoot`, `fsImpl`, `protocols` (the core registry, to skip a re-read), `problems` (an
32
+ * array that collects one message per left-out entry).
33
+ */
34
+ function contributionsFor(pointId , pointVersion , { view, repoRoot = REPO_ROOT, fsImpl = fs, protocols, problems, homeDir, env, projectRoot, tmpDir } = {}) {
35
+ const loaded = loadPoints({ repoRoot, fsImpl });
36
+ if (!loaded.ok) return []; // no point registry, no package: the view reports it
37
+ const point = loaded.registry.points.find((p ) => p.id === pointId);
38
+ if (!point) throw new Error(`contributionsFor: point '${pointId}' is not declared`);
39
+ if (point.status !== 'open') throw new Error(`contributionsFor: point '${pointId}' is ${point.status} and opens with ${point.opensWith}`);
40
+ if (!point.versions.includes(pointVersion)) throw new Error(`contributionsFor: point '${pointId}' has no version ${pointVersion} (supported: ${point.versions.join(', ')})`);
41
+
42
+ const skip = (qualified , why ) => { problems?.push(`${pointId} contribution '${qualified}' is left out: ${why}`); };
43
+ const validator = POINT_VALIDATORS[point.validator];
44
+ let context = null;
45
+ let core = null;
46
+ const coreRegistry = () => {
47
+ core ??= protocols ? { ok: true, registry: protocols } : loadCoreProtocols({ repoRoot, fsImpl });
48
+ return core;
49
+ };
50
+ const out = [];
51
+ for (const pkg of (view ?? packageView({ repoRoot, fsImpl, homeDir, env, projectRoot, tmpDir })).packages) {
52
+ if (pkg.state !== 'active' || !pkg.storeDir) continue;
53
+ let entries ;
54
+ try {
55
+ entries = JSON.parse(fsImpl.readFileSync(path.join(pkg.storeDir, MANIFEST_FILE), 'utf8')).contributions;
56
+ } catch (error ) {
57
+ problems?.push(`package ${pkg.id} is left out: ${MANIFEST_FILE} cannot be read (${error.message})`);
58
+ continue;
59
+ }
60
+ for (const entry of entries) {
61
+ if (entry.point !== pointId || entry.version !== pointVersion) continue;
62
+ const qualified = pkg.firstParty ? entry.id /* the first-party package keeps bare built-in ids (IC-003) */ : `${pkg.id}.${entry.id}`;
63
+ if (point.entry !== 'file' || typeof entry.file !== 'string') { skip(qualified, 'not a file entry'); continue; }
64
+ const file = path.resolve(pkg.storeDir, entry.file);
65
+ if (!file.startsWith(path.resolve(pkg.storeDir) + path.sep)) { skip(qualified, `${entry.file} leaves the package`); continue; }
66
+ let data ;
67
+ try {
68
+ data = JSON.parse(fsImpl.readFileSync(file, 'utf8'));
69
+ } catch (error ) {
70
+ skip(qualified, `${entry.file} cannot be read as JSON (${error.message})`);
71
+ continue;
72
+ }
73
+ if (PRESET_KIND[pointId] && !coreRegistry().ok) { skip(qualified, 'the core protocol registry is unusable'); continue; }
74
+ if (pkg.firstParty) {
75
+ context ??= { lensIds: lensIds(core.registry), hardRoundCap: core.registry.hardRoundCap };
76
+ const errors = validator(entry.id, data, context);
77
+ if (errors.length > 0) { skip(qualified, errors.join('; ')); continue; }
78
+ } else if (PRESET_KIND[pointId]) {
79
+ const judged = judgePackagePreset(point.validator, entry.id, data, { protocols: core.registry, agents: coreAgents(repoRoot, fsImpl) });
80
+ const errors = [...(judged.depth ? [`presets.${entry.id}.depth is not allowed in a package preset`] : []), ...judged.errors];
81
+ if (errors.length === 0 && !judged.comparable) errors.push(`${qualified} dominates no core ${judged.kind} preset`);
82
+ if (errors.length > 0) { skip(qualified, errors.join('; ')); continue; }
83
+ } else {
84
+ const errors = validator(entry.id, data, { packageId: pkg.id });
85
+ if (errors.length > 0) { skip(qualified, errors.join('; ')); continue; }
86
+ }
87
+ out.push({
88
+ qualifiedId: qualified, localId: entry.id, packageId: pkg.id, firstParty: pkg.firstParty, data, dir: null,
89
+ provenance: { contentDigest: pkg.contentDigest, source: pkg.source },
90
+ });
91
+ }
92
+ }
93
+ return out.sort((a, b) => (a.qualifiedId < b.qualifiedId ? -1 : a.qualifiedId > b.qualifiedId ? 1 : 0));
94
+ }
95
+
96
+ /**
97
+ * The preset ids the bundled packages contribute, by kind, for `validateProtocols` (an `order` entry may name
98
+ * one without `presets` holding it). Read from the manifests whatever their state, so an incompatible package
99
+ * still names its ids; `opaque` is true when some bundled package names none (point registry or manifest
100
+ * unreadable), because then an unresolved `order` id may be one of its own.
101
+ */
102
+ function firstPartyPresets({ repoRoot = REPO_ROOT, fsImpl = fs, view } = {}) {
103
+ const result = { review: [] , debate: [] , opaque: false };
104
+ const packages = (view ?? packageView({ repoRoot, fsImpl })).packages;
105
+ if (packages.length === 0 && !loadPoints({ repoRoot, fsImpl }).ok) result.opaque = true;
106
+ for (const pkg of packages) {
107
+ for (const c of pkg.contributions) {
108
+ const kind = PRESET_KIND[c.point];
109
+ if (kind) (result )[kind].push(c.qualifiedId);
110
+ }
111
+ if (pkg.contributions.length === 0) result.opaque = true;
112
+ }
113
+ return result;
114
+ }
115
+
116
+ module.exports = { contributionsFor, firstPartyPresets };
@@ -0,0 +1,132 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Package tree rules and digests (122 IC-005). `walkTree` applies the tree rules and returns IC-004 findings
5
+ * beside the regular files it saw; `contentDigest` hashes a clean tree; `capabilityDigest` hashes what the
6
+ * manifest lets the package do. Nothing here writes. Per-file hashing is sha256 hex of the bytes, as in
7
+ * `src/adapters/copy-tree.js`; that module is not required here because it pulls in the adapter layer.
8
+ */
9
+
10
+ const fs = require('node:fs');
11
+ const path = require('node:path');
12
+ const crypto = require('node:crypto');
13
+
14
+ const SEGMENT = /^[A-Za-z0-9_][A-Za-z0-9._-]*$/;
15
+ const SEGMENT_GRAMMAR = '^[A-Za-z0-9_][A-Za-z0-9._-]*$ (no leading dot)';
16
+ const CONTENT_HEADER = 'doflow-package-content-v1\n';
17
+ const CAPABILITY_HEADER = 'doflow-package-capability-v1\n';
18
+
19
+ const sha256 = (data ) => crypto.createHash('sha256').update(data).digest('hex');
20
+
21
+
22
+
23
+ /** Key-sorted JSON, the canonical form of `fingerprint` in `src/state/index.js`. */
24
+ function canonicalJson(value ) {
25
+ const stable = (v ) => {
26
+ if (Array.isArray(v)) return v.map(stable);
27
+ if (!v || typeof v !== 'object') return v;
28
+ return Object.fromEntries(Object.keys(v).sort().map((key) => [key, stable(v[key])]));
29
+ };
30
+ return JSON.stringify(stable(value));
31
+ }
32
+
33
+ function compareBytes(a , b ) {
34
+ const x = Buffer.from(a);
35
+ const y = Buffer.from(b);
36
+ for (let i = 0; i < Math.min(x.length, y.length); i++) if (x[i] !== y[i]) return x[i] < y[i] ? -1 : 1;
37
+ return x.length === y.length ? 0 : x.length < y.length ? -1 : 1;
38
+ }
39
+
40
+ /**
41
+ * Walk `root` under the IC-005 tree rules. Never throws on content: a rule break is a finding, and a symlink
42
+ * or special file is reported and not entered. `files` lists the regular files in ascending bytewise order of
43
+ * their POSIX relative path. `limits` is `{ files, bytes, fileBytes, pathLength }`.
44
+ */
45
+ function walkTree(root , limits , { packageId = null, fsImpl = fs } = {}) {
46
+ const findings = [];
47
+ const files = [];
48
+ const add = (field , code , message , allowed ) =>
49
+ findings.push({ package: packageId, field, code, severity: 'error', message, allowed });
50
+ let total = 0;
51
+ let stop = false;
52
+
53
+ const visit = (dir , relDir ) => {
54
+ let entries ;
55
+ try {
56
+ entries = fsImpl.readdirSync(dir, { withFileTypes: true });
57
+ } catch (error ) {
58
+ add(`tree.${relDir || '.'}`, 'file-missing', `cannot read directory: ${error.message}`, 'a readable directory');
59
+ return;
60
+ }
61
+ entries.sort((a, b) => compareBytes(a.name, b.name));
62
+ for (const entry of entries) {
63
+ if (stop) return;
64
+ const rel = relDir ? `${relDir}/${entry.name}` : entry.name;
65
+ const field = `tree.${rel}`;
66
+ if (entry.isSymbolicLink()) { add(field, 'symlink', `${rel} is a symbolic link`, 'regular files and directories only'); continue; }
67
+ if (!entry.isFile() && !entry.isDirectory()) {
68
+ add(field, 'special-file', `${rel} is a device, socket or FIFO`, 'regular files and directories only');
69
+ continue;
70
+ }
71
+ if (!SEGMENT.test(entry.name)) {
72
+ add(field, 'bad-type', `${rel}: segment '${entry.name}' breaks the path grammar`, SEGMENT_GRAMMAR);
73
+ continue;
74
+ }
75
+ if (rel.length > limits.pathLength) {
76
+ add(field, 'too-large', `${rel} is ${rel.length} characters long`, `a relative path of at most ${limits.pathLength} characters`);
77
+ continue;
78
+ }
79
+ const abs = path.join(dir, entry.name);
80
+ if (entry.isDirectory()) { visit(abs, rel); continue; }
81
+ if (files.length >= limits.files) {
82
+ add('tree', 'too-many-files', `the package holds more than ${limits.files} files`, `at most ${limits.files} files`);
83
+ stop = true;
84
+ return;
85
+ }
86
+ const stat = fsImpl.lstatSync(abs);
87
+ if (stat.size > limits.fileBytes) {
88
+ add(field, 'too-large', `${rel} is ${stat.size} bytes`, `at most ${limits.fileBytes} bytes per file`);
89
+ }
90
+ total += stat.size;
91
+ files.push({ rel, abs, size: stat.size, exec: (stat.mode & 0o111) !== 0 });
92
+ }
93
+ };
94
+ visit(root, '');
95
+ if (total > limits.bytes) add('tree', 'too-large', `the package holds ${total} bytes`, `at most ${limits.bytes} bytes in total`);
96
+ files.sort((a, b) => compareBytes(a.rel, b.rel));
97
+ return { files, findings };
98
+ }
99
+
100
+ /** Content digest of a walked file list (IC-005): header, then `<path>\0<x>\0<sha256>\n` per file in bytewise path order. */
101
+ function digestFiles(files , fsImpl = fs) {
102
+ const hash = crypto.createHash('sha256').update(CONTENT_HEADER);
103
+ for (const file of [...files].sort((a, b) => compareBytes(a.rel, b.rel))) {
104
+ hash.update(`${file.rel}\0${file.exec ? '1' : '0'}\0${sha256(fsImpl.readFileSync(file.abs))}\n`);
105
+ }
106
+ return hash.digest('hex');
107
+ }
108
+
109
+ /** `contentDigest(dir)`: the IC-005 digest of a package directory. Throws when the tree breaks a tree rule. */
110
+ function contentDigest(dir , { limits = { files: 512, bytes: 4194304, fileBytes: 262144, pathLength: 200 }, fsImpl = fs } = {}) {
111
+ const { files, findings } = walkTree(dir, limits, { fsImpl });
112
+ if (findings.length > 0) throw new Error(`cannot digest ${dir}: ${findings.map((f) => `${f.code} ${f.field}`).join(', ')}`);
113
+ return digestFiles(files, fsImpl);
114
+ }
115
+
116
+ /**
117
+ * Capability digest (IC-005): what the manifest lets the package do. `tiers` maps a point id to its tier. A
118
+ * contribution's `qualifiedId` is always `<package id>.<local id>`; `programs` are passed through sorted by id
119
+ * (the IC-018 normalization arrives with the first executable point).
120
+ */
121
+ function capabilityDigest(manifest , { trustPolicyVersion, tiers } ) {
122
+ const contributions = (manifest.contributions ?? []).map((c ) => ({
123
+ point: c.point, version: c.version, qualifiedId: `${manifest.id}.${c.id}`, tier: tiers[c.point] ?? null,
124
+ })).sort((a , b ) => compareBytes(a.point, b.point) || compareBytes(a.qualifiedId, b.qualifiedId));
125
+ const programs = [...(manifest.programs ?? [])].sort((a , b ) => compareBytes(String(a.id), String(b.id)));
126
+ const body = canonicalJson({
127
+ trustPolicyVersion, id: manifest.id, version: manifest.version, doflow: manifest.doflow, contributions, programs,
128
+ });
129
+ return sha256(CAPABILITY_HEADER + body);
130
+ }
131
+
132
+ module.exports = { walkTree, digestFiles, contentDigest, capabilityDigest, canonicalJson, SEGMENT, SEGMENT_GRAMMAR };
@@ -0,0 +1,182 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Pin and fetch IC-022 git sources by commit and npm sources by exact version plus SHA-512
5
+ * integrity using argv arrays with `shell: false`. Git hooks are off, `protocol.file.allow=never`,
6
+ * and `GIT_TERMINAL_PROMPT=0`. npm pack always receives `--ignore-scripts`; UNVERIFIED: whether
7
+ * `npm pack` of a registry spec runs lifecycle scripts. Fetching uses no checkout or submodules;
8
+ * DoFlow writes no file and tools write only inside the caller's empty staging directory.
9
+ */
10
+
11
+ const fs = require('node:fs');
12
+ const path = require('node:path');
13
+ const crypto = require('node:crypto');
14
+ const { spawnSync } = require('node:child_process');
15
+ const { loadPoints } = require('./points.js');
16
+ const { readTar } = require('./tar.js');
17
+ const { parseSemVer, compareSemVer } = require('../../helper/semver.js');
18
+ const INTEGRITY = /^sha512-[A-Za-z0-9+/]+={0,2}$/u;
19
+ // The location and repository variables of `git rev-parse --local-env-vars` (git 2.50), plus GIT_ALLOW_PROTOCOL,
20
+ // which overrides `-c protocol.file.allow=never`. Hardcoded so no git process is spawned to compute it.
21
+ // GIT_CONFIG, GIT_CONFIG_PARAMETERS and GIT_CONFIG_COUNT (with its KEY_n/VALUE_n pairs) are left out on purpose:
22
+ // CI injects auth headers and `insteadOf` rewrites for private package hosts through them, and the `-c` guard
23
+ // below still wins over them.
24
+ const GIT_LOCATION_KEYS = Object.freeze([
25
+ 'GIT_ALTERNATE_OBJECT_DIRECTORIES', 'GIT_OBJECT_DIRECTORY', 'GIT_DIR', 'GIT_WORK_TREE', 'GIT_IMPLICIT_WORK_TREE',
26
+ 'GIT_GRAFT_FILE', 'GIT_INDEX_FILE', 'GIT_NO_REPLACE_OBJECTS', 'GIT_REPLACE_REF_BASE', 'GIT_PREFIX',
27
+ 'GIT_SHALLOW_FILE', 'GIT_COMMON_DIR', 'GIT_ALLOW_PROTOCOL',
28
+ ]);
29
+
30
+ function limits() {
31
+ const loaded = loadPoints();
32
+ if (!loaded.ok) throw new Error(loaded.errors.join('; '));
33
+ return loaded.registry.limits;
34
+ }
35
+
36
+ function finding(code , message ) {
37
+ return { package: null, field: 'source', code, severity: 'error', message, allowed: 'a fetchable pinned git or npm source' };
38
+ }
39
+
40
+ function runGit(args , { env, timeoutMs = 120000, cwd, maxBytes } ) {
41
+ const gitEnv = { ...env };
42
+ for (const key of GIT_LOCATION_KEYS) delete gitEnv[key];
43
+ return spawnSync('git', ['-c', 'core.hooksPath=/dev/null', '-c', 'protocol.file.allow=never', ...args], {
44
+ shell: false, cwd, env: { ...gitEnv, GIT_TERMINAL_PROMPT: '0' }, encoding: 'buffer',
45
+ timeout: timeoutMs, maxBuffer: 2 * maxBytes + 1048576,
46
+ });
47
+ }
48
+
49
+ function reason(result , command = 'git') {
50
+ const stderr = result?.stderr ? result.stderr.toString('utf8').split(/\r?\n/u)[0].slice(0, 200) : '';
51
+ return stderr || result?.error?.message?.slice(0, 200) || `${command} command failed`;
52
+ }
53
+
54
+ function runNpm(args , { env, timeoutMs = 120000, maxBytes } ) {
55
+ return spawnSync('npm', args, {
56
+ shell: false, env, encoding: 'buffer', timeout: timeoutMs, maxBuffer: 2 * maxBytes + 1048576,
57
+ });
58
+ }
59
+
60
+ function pinGit(source , { env, timeoutMs } ) {
61
+ const maxBytes = limits().bytes;
62
+ if (/^[0-9a-fA-F]{40}$/u.test(source.ref)) {
63
+ const pinned = { type: 'git', url: source.url, commit: source.ref, ...(source.path === undefined ? {} : { path: source.path }) };
64
+ return { ok: true, pinned };
65
+ }
66
+ const result = runGit(['ls-remote', source.url, source.ref], { env, timeoutMs, maxBytes });
67
+ if (result.status !== 0) return { ok: false, finding: finding('source-unpinned', reason(result)) };
68
+ const rows = result.stdout.toString('utf8').split(/\r?\n/u).map((line ) => line.split('\t'));
69
+ const refs = [
70
+ `refs/tags/${source.ref}^{}`, `refs/tags/${source.ref}`, `refs/heads/${source.ref}`, source.ref,
71
+ ];
72
+ for (const ref of refs) {
73
+ const row = rows.find((parts ) => parts[1] === ref && /^[0-9a-fA-F]{40}$/u.test(parts[0]));
74
+ if (row) return { ok: true, pinned: { type: 'git', url: source.url, commit: row[0], ...(source.path === undefined ? {} : { path: source.path }) } };
75
+ }
76
+ return { ok: false, finding: finding('source-unpinned', `ref ${source.ref} not found`) };
77
+ }
78
+
79
+ function fetchGit(pinned , { stagingDir, env, limits: treeLimits, timeoutMs } ) {
80
+ const fail = (message ) => ({ ok: false, findings: [finding('source-unpinned', message)] });
81
+ if (!/^[0-9a-fA-F]{40}$/u.test(pinned?.commit ?? '')) {
82
+ return { ok: false, findings: [finding('bad-type', 'pinned git commit must be 40 hexadecimal characters')] };
83
+ }
84
+ try {
85
+ if (!fs.statSync(stagingDir).isDirectory() || fs.readdirSync(stagingDir).length !== 0) {
86
+ return { ok: false, findings: [finding('bad-type', 'staging directory must exist and be empty')] };
87
+ }
88
+ } catch { return { ok: false, findings: [finding('bad-type', 'staging directory must exist and be empty')] }; }
89
+ const maxBytes = treeLimits.bytes;
90
+ for (const args of [
91
+ ['init', '-q'], ['remote', 'add', 'origin', pinned.url], ['fetch', '--depth', '1', '--no-tags', 'origin', pinned.commit],
92
+ ]) {
93
+ const result = runGit(args, { env, timeoutMs, cwd: stagingDir, maxBytes });
94
+ if (result.status !== 0) return fail(reason(result));
95
+ }
96
+ const args = ['archive', '--format=tar', pinned.commit, ...(pinned.path ? [pinned.path] : [])];
97
+ const result = runGit(args, { env, timeoutMs, cwd: stagingDir, maxBytes });
98
+ if (result.status !== 0) return fail(reason(result));
99
+ const archive = readTar(result.stdout, { limits: treeLimits, stripPrefix: pinned.path ? `${pinned.path}/` : undefined });
100
+ if (!archive.ok) return { ok: false, findings: archive.findings };
101
+ return { ok: true, pinned, files: archive.files };
102
+ }
103
+
104
+ function pinNpm(source , { env, timeoutMs } ) {
105
+ const maxBytes = limits().bytes;
106
+ const result = runNpm(['view', `${source.name}@${source.version}`, 'version', 'dist.integrity', '--json'], {
107
+ env, timeoutMs, maxBytes,
108
+ });
109
+ if (result.status !== 0) return { ok: false, finding: finding('source-unpinned', reason(result, 'npm')) };
110
+
111
+ let response ;
112
+ try { response = JSON.parse(result.stdout.toString('utf8')); }
113
+ catch { return { ok: false, finding: finding('source-unpinned', 'npm view answered no valid version and integrity') }; }
114
+ const entries = Array.isArray(response) ? response : [response];
115
+ const versions = entries.filter((entry ) => typeof entry?.version === 'string' && parseSemVer(entry.version) !== null);
116
+ if (versions.length === 0) {
117
+ return { ok: false, finding: finding('source-unpinned', `no version of ${source.name} matches ${source.version}`) };
118
+ }
119
+ const valid = versions.filter((entry ) => typeof entry['dist.integrity'] === 'string' && INTEGRITY.test(entry['dist.integrity']));
120
+ if (valid.length === 0) {
121
+ return { ok: false, finding: finding('source-unpinned', 'npm view answered no valid version and integrity') };
122
+ }
123
+ let selected = valid[0];
124
+ for (const entry of valid.slice(1)) {
125
+ if (compareSemVer(entry.version, selected.version) > 0) selected = entry;
126
+ }
127
+ return {
128
+ ok: true,
129
+ pinned: { type: 'npm', name: source.name, version: selected.version, integrity: selected['dist.integrity'] },
130
+ };
131
+ }
132
+
133
+ function fetchNpm(pinned , { stagingDir, env, limits: treeLimits, timeoutMs } ) {
134
+ const fail = (code , message ) => ({ ok: false, findings: [finding(code, message)] });
135
+ if (pinned?.type !== 'npm' || typeof pinned?.name !== 'string'
136
+ || !/^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/u.test(pinned.name)) {
137
+ return fail('bad-type', 'pinned npm package name is invalid');
138
+ }
139
+ if (typeof pinned.version !== 'string' || parseSemVer(pinned.version) === null) {
140
+ return fail('bad-type', 'pinned npm version must be an exact semver');
141
+ }
142
+ if (typeof pinned.integrity !== 'string' || !INTEGRITY.test(pinned.integrity)) {
143
+ return fail('bad-type', 'pinned npm integrity must be a sha512 value');
144
+ }
145
+ try {
146
+ if (!fs.statSync(stagingDir).isDirectory() || fs.readdirSync(stagingDir).length !== 0) {
147
+ return fail('bad-type', 'staging directory must exist and be empty');
148
+ }
149
+ } catch { return fail('bad-type', 'staging directory must exist and be empty'); }
150
+
151
+ const result = runNpm([
152
+ 'pack', `${pinned.name}@${pinned.version}`, '--ignore-scripts', '--json', '--pack-destination', stagingDir,
153
+ ], { env, timeoutMs, maxBytes: treeLimits.bytes });
154
+ if (result.status !== 0) return fail('source-unpinned', reason(result, 'npm'));
155
+
156
+ let packed ;
157
+ try { packed = JSON.parse(result.stdout.toString('utf8')); }
158
+ catch { return fail('source-unpinned', 'npm pack did not return valid JSON'); }
159
+ const filename = Array.isArray(packed) ? packed[0]?.filename : undefined;
160
+ if (typeof filename !== 'string' || filename.length === 0) {
161
+ return fail('source-unpinned', 'npm pack did not return a tarball filename');
162
+ }
163
+ const tarball = path.join(stagingDir, path.basename(filename));
164
+ let stat ;
165
+ try { stat = fs.statSync(tarball); }
166
+ catch { return fail('source-unpinned', 'npm pack tarball is missing'); }
167
+ if (!stat.isFile()) return fail('source-unpinned', 'npm pack tarball is not a file');
168
+ if (stat.size > treeLimits.bytes + (treeLimits.files + 64) * 1024) {
169
+ return fail('too-large', 'npm pack tarball exceeds the bounded archive size');
170
+ }
171
+
172
+ const bytes = fs.readFileSync(tarball);
173
+ const integrity = `sha512-${crypto.createHash('sha512').update(bytes).digest('base64')}`;
174
+ if (integrity !== pinned.integrity) {
175
+ return fail('integrity-mismatch', `npm tarball integrity ${integrity} does not match pinned integrity ${pinned.integrity}`);
176
+ }
177
+ const archive = readTar(bytes, { limits: treeLimits, stripPrefix: 'package/' });
178
+ if (!archive.ok) return { ok: false, findings: archive.findings };
179
+ return { ok: true, pinned, files: archive.files };
180
+ }
181
+
182
+ module.exports = { pinGit, fetchGit, pinNpm, fetchNpm };
@@ -0,0 +1,192 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * The person grant file reader (122 IC-008). `<configHome>/packages/grants.jsonl` is written only by a
5
+ * person; this module reads it and never writes, renames or deletes it (a grep test in
6
+ * test/runtime/package-grants.test.js holds every `src/` file to that).
7
+ *
8
+ * `readGrants` returns the file path and mtime, one `entries` row per well-formed line with its state, the
9
+ * `grants` that count right now, and IC-004 findings. Void rules: an unreadable file, a line over 2 KiB, more
10
+ * than 1000 lines, a malformed line, an unknown key or kind, or a duplicate key (`duplicateKey`: a trust line's grant,
11
+ * package, version, contentDigest, capabilityDigest and policy; an exempt line's grant, package, version and
12
+ * contentDigest; a fetch grant's source) voids every grant (`state-unreadable`, naming the line
13
+ * number and never its text). A file whose realpath lies in the detected project root (`detectedProjectRoot`,
14
+ * never the working directory itself), a `.doflow/state/dispatch-worktrees/` directory or the system temp directory is ignored (`grant-invalid`, reason
15
+ * `grants-file-location`). No file means no grants and no finding.
16
+ */
17
+
18
+ const fs = require('node:fs');
19
+ const os = require('node:os');
20
+ const path = require('node:path');
21
+ const { configHome } = require('../config-home.js');
22
+ const { parseSemVer } = require('../../helper/semver.js');
23
+ const { isPackageId, isQualifiedId } = require('../../helper/package-ids.js');
24
+ const { duplicateKeys } = require('../review/protocols.js');
25
+
26
+ const GRANTS_FILE = path.join('packages', 'grants.jsonl');
27
+ const LINE_MAX_BYTES = 2048;
28
+ const LINES_MAX = 1000;
29
+ const REBIND_MAX = 64;
30
+
31
+ const DIGEST = /^[0-9a-f]{64}$/;
32
+ const ISO_UTC = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/;
33
+ const KEYS = new Map ([
34
+ ['trust', { required: ['grant', 'package', 'version', 'contentDigest', 'capabilityDigest', 'policy', 'at'], optional: ['rebind'] }],
35
+ ['exempt', { required: ['grant', 'package', 'version', 'contentDigest', 'host', 'acceptRisk', 'at'], optional: [] }],
36
+ ['fetch', { required: ['grant', 'source', 'at'], optional: [] }],
37
+ ]);
38
+
39
+ const isObject = (value ) => value !== null && typeof value === 'object' && !Array.isArray(value);
40
+ const isText = (value ) => typeof value === 'string' && value !== '' && value.length <= 512;
41
+ const isTime = (value ) => typeof value === 'string' && ISO_UTC.test(value) && !Number.isNaN(Date.parse(value));
42
+ const isVersion = (value ) => typeof value === 'string' && parseSemVer(value) !== null;
43
+
44
+ function finding(code , field , message , allowed , reason ) {
45
+ return { package: null, field, code, severity: 'error', message, allowed, ...(reason ? { reason } : {}) };
46
+ }
47
+
48
+ /** The source of a `fetch` grant, as written: git `{type,url,ref,path?}` or npm `{type,name,version}`. */
49
+ function sourceProblem(source ) {
50
+ if (!isObject(source)) return 'source must be an object';
51
+ const keys = Object.keys(source).sort().join(',');
52
+ if (source.type === 'git') {
53
+ if (keys !== 'ref,type,url' && keys !== 'path,ref,type,url') return 'git source keys';
54
+ return isText(source.url) && isText(source.ref) && (source.path === undefined || isText(source.path)) ? null : 'git source values';
55
+ }
56
+ if (source.type === 'npm') {
57
+ if (keys !== 'name,type,version') return 'npm source keys';
58
+ return isText(source.name) && isText(source.version) ? null : 'npm source values';
59
+ }
60
+ return 'source type';
61
+ }
62
+
63
+ /** Why a parsed line is not a grant of its kind, or null. The reason never quotes the line. */
64
+ function lineProblem(grant ) {
65
+ if (!isObject(grant)) return 'not a JSON object';
66
+ const spec = KEYS.get(grant.grant);
67
+ if (!spec) return 'unknown kind';
68
+ const allowed = new Set([...spec.required, ...spec.optional]);
69
+ if (Object.keys(grant).some((key) => !allowed.has(key))) return 'unknown key';
70
+ if (spec.required.some((key) => !Object.hasOwn(grant, key))) return 'missing key';
71
+ if (!isTime(grant.at)) return 'bad at';
72
+ if (grant.grant === 'fetch') return sourceProblem(grant.source) ? 'bad source' : null;
73
+ if (!isPackageId(grant.package) || !isVersion(grant.version) || !DIGEST.test(grant.contentDigest)) return 'bad package, version or digest';
74
+ if (grant.grant === 'exempt') return isVersion(grant.host) && grant.acceptRisk === true ? null : 'bad host or acceptRisk';
75
+ if (!DIGEST.test(grant.capabilityDigest) || !Number.isInteger(grant.policy) || grant.policy < 1) return 'bad capabilityDigest or policy';
76
+ if (Object.hasOwn(grant, 'rebind')
77
+ && !(Array.isArray(grant.rebind) && grant.rebind.length <= REBIND_MAX && grant.rebind.every(isQualifiedId))) return 'bad rebind';
78
+ return null;
79
+ }
80
+
81
+ /**
82
+ * The duplicate key of IC-008 (FU-gjgp06, FU-92eftv): (grant, package, version, contentDigest, capabilityDigest, policy)
83
+ * for trust, (grant, package, version, contentDigest) for exempt, and a fetch grant's source as written. Lines for one
84
+ * package version with other digests or another policy coexist, so a re-trust after an edit or a policy bump is an
85
+ * append; a line with an existing key is a replacement of that line (the package verbs say which).
86
+ */
87
+ const duplicateKey = (grant ) => {
88
+ if (grant.grant === 'fetch') return `fetch ${JSON.stringify(Object.keys(grant.source).sort().map((key) => [key, grant.source[key]]))}`;
89
+ const key = `${grant.grant} ${grant.package} ${grant.version} ${grant.contentDigest}`;
90
+ return grant.grant === 'trust' ? `${key} ${grant.capabilityDigest} ${grant.policy}` : key;
91
+ };
92
+
93
+ const realOf = (fsImpl , file ) => {
94
+ try { return (fsImpl.realpathSync.native ?? fsImpl.realpathSync)(file); } catch { return path.resolve(file); }
95
+ };
96
+ const within = (child , parent ) => {
97
+ const rel = path.relative(parent, child);
98
+ return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
99
+ };
100
+
101
+ /** True when `path.join(dir, name)` exists and, with `directory`, is a directory. Never throws. */
102
+ const holds = (fsImpl , dir , name , directory ) => {
103
+ try { const stat = fsImpl.statSync(path.join(dir, name)); return !directory || stat.isDirectory(); } catch { return false; }
104
+ };
105
+
106
+ /**
107
+ * The detected project root (IC-008 as amended by FU-t622ad; IC-025 reuses it): from `cwd` upward, the nearest
108
+ * directory, `cwd` included, that holds `.git` (a directory, or the file of a linked worktree) or a `.doflow/`
109
+ * directory. The account home `homeDir` and every ancestor of it end the walk, because the home is the global scope,
110
+ * not a project. Returns that directory's real path, or null when there is no project. Spawns no process.
111
+ */
112
+ function detectedProjectRoot({ cwd = process.cwd(), homeDir = os.homedir(), fsImpl = fs } = {}) {
113
+ const home = homeDir ? realOf(fsImpl, homeDir) : null;
114
+ for (let dir = realOf(fsImpl, cwd); ; dir = path.dirname(dir)) {
115
+ if (home !== null && within(home, dir)) return null; // the home or an ancestor of it: so is every parent
116
+ if (holds(fsImpl, dir, '.git', false) || holds(fsImpl, dir, '.doflow', true)) return dir;
117
+ if (path.dirname(dir) === dir) return null;
118
+ }
119
+ }
120
+
121
+ /** `grants-file-location` (IC-008): the realpath is in the project root (none when null), a dispatch worktree or the temp dir. */
122
+ function badLocation(real , projectRoot , tmpDir , fsImpl ) {
123
+ const marker = `${path.sep}.doflow${path.sep}state${path.sep}dispatch-worktrees${path.sep}`;
124
+ return (projectRoot !== null && within(real, realOf(fsImpl, projectRoot))) || within(real, realOf(fsImpl, tmpDir)) || `${real}${path.sep}`.includes(marker);
125
+ }
126
+
127
+ /**
128
+ * Read the grant file. Options: `env` (for the config home, and its `HOME` for the account home), `fsImpl`,
129
+ * `projectRoot` (default: `detectedProjectRoot` from the working directory; `null` means no project, so only the
130
+ * dispatch-worktree and temp-dir rules apply), `tmpDir` (default `os.tmpdir()`; an injected seam for tests). Never throws.
131
+ * Returns `{ path, noConfigHome, exists, mtime, entries, grants, findings }`: `path` is null with no config home
132
+ * (no grant can exist); `mtime` is an ISO string or null; `entries` holds each well-formed line as
133
+ * `{ line, raw, state: 'valid'|'void'|'ignored', reason?, ...fields }`; `grants` are the `valid` entries.
134
+ */
135
+ function readGrants({ env = process.env, fsImpl = fs, projectRoot, tmpDir = os.tmpdir() } = {}) {
136
+ const dir = configHome(env).dir;
137
+ const result = { path: null, noConfigHome: dir === null, exists: false, mtime: null, entries: [], grants: [], findings: [] };
138
+ if (dir === null) return result;
139
+ const file = path.join(dir, GRANTS_FILE);
140
+ result.path = file;
141
+ const unreadable = (message ) => {
142
+ result.findings.push(finding('state-unreadable', 'grants.jsonl', message, 'a grants file of at most 1000 lines of at most 2 KiB, one closed-key JSON object each'));
143
+ for (const entry of result.entries) { entry.state = 'void'; entry.reason = 'state-unreadable'; }
144
+ result.grants = [];
145
+ return result;
146
+ };
147
+ let text ;
148
+ try {
149
+ const stat = fsImpl.statSync(file);
150
+ result.exists = true;
151
+ result.mtime = new Date(stat.mtimeMs ?? stat.mtime).toISOString();
152
+ if (stat.size > LINES_MAX * (LINE_MAX_BYTES + 2)) return unreadable(`the grants file is larger than ${LINES_MAX} lines of ${LINE_MAX_BYTES} bytes`);
153
+ text = String(fsImpl.readFileSync(file, 'utf8'));
154
+ } catch (error ) {
155
+ if (error?.code === 'ENOENT' && !result.exists) return result; // no file: no grants, no error
156
+ return unreadable('the grants file could not be read');
157
+ }
158
+
159
+ const lines = text.split('\n');
160
+ if (lines[lines.length - 1] === '') lines.pop();
161
+ let problem = lines.length > LINES_MAX ? `line ${LINES_MAX + 1} is past the ${LINES_MAX}-line limit` : null;
162
+ const seen = new Set ();
163
+ for (let i = 0; i < Math.min(lines.length, LINES_MAX) && problem === null; i++) {
164
+ const raw = lines[i].trim();
165
+ const number = i + 1;
166
+ if (Buffer.byteLength(lines[i]) > LINE_MAX_BYTES) { problem = `line ${number} is longer than ${LINE_MAX_BYTES} bytes`; break; }
167
+ if (raw === '' || raw.startsWith('#')) continue;
168
+ let grant ;
169
+ try { grant = duplicateKeys(raw).length ? undefined : JSON.parse(raw); } catch { grant = undefined; }
170
+ const why = grant === undefined ? 'not valid JSON' : lineProblem(grant);
171
+ if (why) { problem = `line ${number} is not a valid grant (${why})`; break; }
172
+ const key = duplicateKey(grant);
173
+ const what = grant.grant === 'fetch' ? 'source' : grant.grant === 'trust' ? '(grant, package, version, contentDigest, capabilityDigest, policy)' : '(grant, package, version, contentDigest)';
174
+ if (seen.has(key)) { problem = `line ${number} repeats the ${what} of an earlier line`; break; }
175
+ seen.add(key);
176
+ result.entries.push({ line: number, raw, state: 'valid', ...grant });
177
+ }
178
+ const root = projectRoot !== undefined ? projectRoot
179
+ : detectedProjectRoot({ homeDir: typeof env.HOME === 'string' && path.isAbsolute(env.HOME) ? env.HOME : os.homedir(), fsImpl });
180
+ const misplaced = badLocation(realOf(fsImpl, file), root, tmpDir, fsImpl);
181
+ if (problem !== null && !misplaced) return unreadable(problem);
182
+ if (misplaced) {
183
+ for (const entry of result.entries) { entry.state = 'ignored'; entry.reason = 'grants-file-location'; }
184
+ result.findings.push(finding('grant-invalid', 'grants.jsonl', 'the grants file lies inside the project root, a dispatch worktree or the system temp directory, so every grant in it is ignored',
185
+ 'a grants file outside the project and the temp directory (set XDG_CONFIG_HOME to a path in your own home)', 'grants-file-location'));
186
+ return result;
187
+ }
188
+ result.grants = result.entries;
189
+ return result;
190
+ }
191
+
192
+ module.exports = { readGrants, detectedProjectRoot, duplicateKey, GRANTS_FILE, LINE_MAX_BYTES, LINES_MAX };