@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,106 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Read an in-memory ustar or pax archive under the IC-005 limits while reading it.
5
+ * Link entries are refused as `symlink`. This module writes nothing and spawns nothing.
6
+ */
7
+
8
+ const zlib = require('node:zlib');
9
+ const { SEGMENT, SEGMENT_GRAMMAR } = require('./digest.js');
10
+
11
+ function readTar(input , { limits, stripPrefix, packageId = null } ) {
12
+ const findings = [];
13
+ const files = [];
14
+ const seen = new Set ();
15
+ const add = (field , code , message , allowed ) => {
16
+ findings.push({ package: packageId, field, code, severity: 'error', message, allowed });
17
+ return { ok: false, findings };
18
+ };
19
+ let data = input;
20
+ if (data[0] === 0x1f && data[1] === 0x8b) {
21
+ try { data = zlib.gunzipSync(data, { maxOutputLength: limits.bytes + (limits.files + 64) * 1024 }); }
22
+ catch { return add('tree', 'too-large', 'the compressed archive exceeds the bounded tar size', `at most ${limits.bytes} bytes plus archive headers`); }
23
+ }
24
+ const text = (buf ) => buf.toString('utf8').replace(/\0.*$/su, '');
25
+ const octal = (buf ) => {
26
+ const value = text(buf).trim();
27
+ return value ? parseInt(value, 8) : 0;
28
+ };
29
+ const blocks = Math.floor(data.length / 512);
30
+ let index = 0;
31
+ let total = 0;
32
+ let count = 0;
33
+ let pax = null;
34
+ while (index < blocks) {
35
+ const h = data.subarray(index * 512, (index + 1) * 512);
36
+ if (h.every((v ) => v === 0)) break;
37
+ const magic = h.subarray(257, 263).toString('binary');
38
+ if (magic !== 'ustar\0' && magic !== 'ustar ') return add('tree', 'bad-type', 'tar header is not ustar or pax', 'ustar or pax archive headers');
39
+ const expected = octal(h.subarray(148, 156));
40
+ let sum = 0;
41
+ for (let j = 0; j < 512; j++) sum += j >= 148 && j < 156 ? 32 : h[j];
42
+ if (sum !== expected) return add('tree', 'bad-type', 'tar header checksum does not match', 'a valid tar header checksum');
43
+ const size = octal(h.subarray(124, 136));
44
+ const type = h[156] === 0 ? '0' : String.fromCharCode(h[156]);
45
+ index++;
46
+ if (!Number.isSafeInteger(size) || size < 0 || index * 512 + size > data.length) return add('tree', 'bad-type', 'tar entry is truncated or has an invalid size', 'a complete tar entry');
47
+ const payloadStart = index * 512;
48
+ const payloadEnd = payloadStart + size;
49
+ const payload = data.subarray(payloadStart, payloadEnd);
50
+ index += Math.ceil(size / 512);
51
+ if (type === 'g') continue;
52
+ if (type === 'x') {
53
+ pax = {};
54
+ const raw = payload.toString('utf8');
55
+ let at = 0;
56
+ while (at < raw.length) {
57
+ const space = raw.indexOf(' ', at);
58
+ const n = Number(raw.slice(at, space));
59
+ if (space < 0 || !Number.isInteger(n) || n <= 0 || at + n > raw.length) return add('tree', 'bad-type', 'pax header is malformed', 'valid pax path and size records');
60
+ const row = raw.slice(space + 1, at + n).replace(/\n$/u, '');
61
+ const eq = row.indexOf('=');
62
+ if (eq > 0) pax[row.slice(0, eq)] = row.slice(eq + 1);
63
+ at += n;
64
+ }
65
+ continue;
66
+ }
67
+ let name = pax?.path ?? text(h.subarray(0, 100));
68
+ const prefix = text(h.subarray(345, 500));
69
+ if (!pax?.path && prefix) name = `${prefix}/${name}`;
70
+ const actualSize = pax?.size === undefined ? size : Number(pax.size);
71
+ pax = null;
72
+ if (type === '1' || type === '2') return add(`tree.${name}`, 'symlink', `${name} is a symbolic link`, 'regular files and directories only');
73
+ if (type === '3' || type === '4' || type === '6') return add(`tree.${name}`, 'special-file', `${name} is a device, socket or FIFO`, 'regular files and directories only');
74
+ if (type !== '0' && type !== '5') return add(`tree.${name}`, 'bad-type', `unsupported tar entry type '${type}'`, 'regular files and directories only');
75
+ if (type === '5') continue;
76
+ name = name.replace(/^\.\//u, '');
77
+ if (name.startsWith('/') || name.split('/').includes('..')) return add(`tree.${name}`, 'path-escapes-root', `${name} leaves the package root`, 'a path inside the package root');
78
+ if (stripPrefix) {
79
+ if (!name.startsWith(stripPrefix)) return add(`tree.${name}`, 'path-escapes-root', `${name} is outside ${stripPrefix}`, `a path inside ${stripPrefix}`);
80
+ name = name.slice(stripPrefix.length);
81
+ }
82
+ const segments = name.split('/');
83
+ if (segments.some((s ) => !SEGMENT.test(s))) return add(`tree.${name}`, 'bad-type', `${name}: a segment breaks the path grammar`, SEGMENT_GRAMMAR);
84
+ if (name.length > limits.pathLength) return add(`tree.${name}`, 'too-large', `${name} is ${name.length} characters long`, `a relative path of at most ${limits.pathLength} characters`);
85
+ if (!Number.isSafeInteger(actualSize) || actualSize < 0 || actualSize !== size) return add(`tree.${name}`, 'bad-type', 'pax size does not match the tar entry', 'a complete tar entry');
86
+ if (actualSize > limits.fileBytes) return add(`tree.${name}`, 'too-large', `${name} is ${actualSize} bytes`, `at most ${limits.fileBytes} bytes per file`);
87
+ if (count >= limits.files) return add('tree', 'too-many-files', `the package holds more than ${limits.files} files`, `at most ${limits.files} files`);
88
+ if (total + actualSize > limits.bytes) return add('tree', 'too-large', `the package holds more than ${limits.bytes} bytes`, `at most ${limits.bytes} bytes in total`);
89
+ if (seen.has(name)) return add(`tree.${name}`, 'bad-type', `${name} appears twice in the archive`, 'each file path at most once');
90
+ total += actualSize;
91
+ count++;
92
+ seen.add(name);
93
+ const mode = octal(h.subarray(100, 108));
94
+ files.push({ path: name, mode: (mode & 0o111) !== 0 ? 0o755 : 0o644, data: payload });
95
+ }
96
+ const compareBytes = (a , b ) => {
97
+ const x = Buffer.from(a);
98
+ const y = Buffer.from(b);
99
+ for (let i = 0; i < Math.min(x.length, y.length); i++) if (x[i] !== y[i]) return x[i] < y[i] ? -1 : 1;
100
+ return x.length === y.length ? 0 : x.length < y.length ? -1 : 1;
101
+ };
102
+ files.sort((a , b ) => compareBytes(a.path, b.path));
103
+ return { ok: true, files };
104
+ }
105
+
106
+ module.exports = { readTar };
@@ -0,0 +1,406 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * The package view (122 IC-010): the effective state of every package, read-only, with no network, no
5
+ * harness and no model call. It holds the bundled first-party packages (those listed in
6
+ * `contribution-points.json` `bundled`) and the third-party packages the global lock records
7
+ * (`~/.doflow/doflow.lock` `packages`, stored at `~/.doflow/packages/store/<id>/<contentDigest first 16 hex>/`).
8
+ *
9
+ * A third-party package takes the first state that applies, in this order: `incompatible` (the manifest, a
10
+ * contribution or the IC-005 tree fails the checker against the running core, or the host is outside its `doflow`
11
+ * range with no matching `exempt` grant), `suspended` (the store entry or its manifest is missing, or the tree
12
+ * re-hashes to another content digest: `content-changed`), `disabled` (the lock bit is false), `needs-trust` (the tier
13
+ * is above inert and no valid `trust` grant exists), else `active`. Only an ENABLED entry is walked and re-hashed
14
+ * (the IC-005 caps apply to that walk). A DISABLED entry is read no further than its manifest, which is
15
+ * checked lexically (no contribution file, no tree walk, no digest), so a byte flip in a disabled entry shows once
16
+ * it is enabled. With no third-party row nothing under the store, the grants file or `state.json` is read. The grants
17
+ * (`readGrants`, IC-008) and revocations (`readState`, IC-009) are read once per build, only when a third-party row
18
+ * exists; they and the lock are read and never written.
19
+ *
20
+ * The memo is keyed on everything the view reads: the runtime root, the home (lock, store, state), the config home
21
+ * (grants), the project root and the temp dir (where the grants file may lie). A caller that injects `fsImpl`
22
+ * bypasses it. `resetPackageView` clears it for tests.
23
+ *
24
+ * The resolved `packages.disabled` setting (IC-011) is applied last, over the memoised states: a listed id is `disabled`
25
+ * with reason `disabled-by-<layer>`, and an invalid value disables every non-bundled package with `settings-invalid`
26
+ * (PN-7, fail closed). The bundled package can be disabled by the person like any other. A caller passes
27
+ * `settingsView['packages.disabled']` (`{ value, layer, state?, byId? }`); with none, the project settings of the working
28
+ * directory are read, unless `fsImpl` is injected, which keeps the view hermetic.
29
+ *
30
+ * A bundled package is `active` unless its directory or manifest is missing or invalid, then `incompatible`
31
+ * with IC-004 reason codes; a lock row with source `bundled` and `enabled: false` disables it. It is trusted at
32
+ * runtime and verified at release (IC-015): only the manifest's shape
33
+ * and lexical paths are checked here (no contribution file is read, nothing under `core/shared` is needed), and
34
+ * its tree is never re-hashed (guard G68 checks the registry digest); `contentDigest` is the registry's value.
35
+ * The projected runtime ships no `package.json`, so the host version of a bundled package then falls back to the
36
+ * package's own manifest `version` (PN-8 keeps the two equal); a readable `package.json` that differs still
37
+ * makes it `host-incompatible`. Each package is judged alone, so a fault in one never changes
38
+ * another and the view itself never throws (FR-009).
39
+ */
40
+
41
+ const fs = require('node:fs');
42
+ const os = require('node:os');
43
+ const path = require('node:path');
44
+ const { REPO_ROOT } = require('../../helper/repo-root.js');
45
+ const { parseSemVer } = require('../../helper/semver.js');
46
+ const { configHome } = require('../config-home.js');
47
+ const { readLock, lockPackages } = require('../../state/lockfile.js');
48
+ const { loadPoints, pointRows } = require('./points.js');
49
+ const { checkManifest, checkPackage, MANIFEST_FILE } = require('./manifest.js');
50
+ const { readGrants, detectedProjectRoot } = require('./grants.js');
51
+ const { readState, grantRevoked } = require('./state.js');
52
+ const { loadCoreProtocols } = require('../review/protocols.js');
53
+
54
+ const TIER_ORDER = ['inert', 'instruction', 'executable'];
55
+ const memo = new Map ();
56
+ const fullMemo = new Map ();
57
+ // Building a third-party view loads the core protocol registry, whose validation asks for the first-party preset ids
58
+ // (`firstPartyPresets`), which asks for this view. A call made while one is being built gets the bundled-only view,
59
+ // which is all that question needs.
60
+ let building = 0;
61
+
62
+ /**
63
+ * The running DoFlow version. A checkout or the npm package has `<repoRoot>/package.json`, read first. A projected
64
+ * runtime (`<scope root>/.doflow/runtime`) ships none, but the install recorded the version in the lock it wrote beside
65
+ * it, so `<scope root>/.doflow/doflow.lock` `sourceVersion` (set from the installing DoFlow's package.json) is read
66
+ * instead, only for that layout and only when it is semver. The lock is parsed directly: `readLock` validates the whole
67
+ * document and throws, where one field is all this needs. Anything else is null, which the checker reports as
68
+ * host-incompatible (fail closed).
69
+ */
70
+ function hostVersion(repoRoot ) {
71
+ try { return JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8')).version || null; } catch { /* no package.json: try the lock */ }
72
+ const parent = path.dirname(path.resolve(repoRoot));
73
+ if (path.basename(path.resolve(repoRoot)) !== 'runtime' || path.basename(parent) !== '.doflow') return null;
74
+ try {
75
+ const version = JSON.parse(fs.readFileSync(path.join(parent, 'doflow.lock'), 'utf8')).sourceVersion;
76
+ return typeof version === 'string' && parseSemVer(version) ? version : null;
77
+ } catch { return null; }
78
+ }
79
+
80
+ function finding(pkg , field , code , message , allowed ) {
81
+ return { package: pkg, field, code, severity: 'error', message, allowed };
82
+ }
83
+
84
+ /** True when `target` is `root` itself or inside it. */
85
+ const within = (root , target ) => {
86
+ const rel = path.relative(root, target);
87
+ return rel !== '' && !rel.startsWith('..') && !path.isAbsolute(rel);
88
+ };
89
+
90
+ /** Resolve a bundled `dir` inside the runtime root; `{ error }` is the finding when it is missing or leaves the root. */
91
+ function bundledDir(repoRoot , entry , fsImpl ) {
92
+ const abs = path.resolve(repoRoot, entry.dir);
93
+ const escapes = finding(entry.id, 'dir', 'path-escapes-root', `bundled dir '${entry.dir}' leaves the runtime root`, 'a directory inside the runtime root');
94
+ if (!within(path.resolve(repoRoot), abs)) return { error: escapes };
95
+ try {
96
+ if (!fsImpl.statSync(abs).isDirectory()) return { error: finding(entry.id, 'dir', 'file-missing', `bundled dir '${entry.dir}' is not a directory`, 'a package directory') };
97
+ if (!within(fsImpl.realpathSync(repoRoot), fsImpl.realpathSync(abs))) return { error: escapes };
98
+ } catch {
99
+ return { error: finding(entry.id, 'dir', 'file-missing', `bundled dir '${entry.dir}' does not exist`, 'a package directory') };
100
+ }
101
+ return { dir: abs };
102
+ }
103
+
104
+ /** The contributions a manifest names, as view rows (`active: false` until the state is known). `qualify` gives the qualified id. */
105
+ function contributionList(manifest , tiers , qualify ) {
106
+ const list = Array.isArray(manifest?.contributions) ? manifest.contributions : [];
107
+ return list.filter((c ) => c && typeof c === 'object' && typeof c.point === 'string' && typeof c.id === 'string').map((c ) => ({
108
+ point: c.point, version: c.version, qualifiedId: qualify(c), tier: tiers[c.point] ?? null, active: false,
109
+ }));
110
+ }
111
+
112
+ /** The highest tier among view contribution rows; null when none has a known tier. */
113
+ function tierMaxOf(contributions ) {
114
+ const ranks = contributions.map((c ) => TIER_ORDER.indexOf(c.tier)).filter((r ) => r >= 0);
115
+ return ranks.length > 0 ? TIER_ORDER[Math.max(...ranks)] : null;
116
+ }
117
+
118
+ /** The state of one bundled package. Never throws. */
119
+ function bundledState(entry , ctx ) {
120
+ const { repoRoot, fsImpl, pointsRegistry, host, bundledIds } = ctx;
121
+ const tiers = Object.fromEntries(pointsRegistry.points.map((p ) => [p.id, p.tier]));
122
+ const state = {
123
+ id: entry.id, version: null, source: { type: 'bundled' }, firstParty: true, tierMax: null, enabled: true,
124
+ state: 'active', reasons: [], contributions: [], grant: { trust: true, exempt: false },
125
+ contentDigest: entry.contentDigest, capabilityDigest: null, storeDir: null,
126
+ };
127
+ let findings = [];
128
+ try {
129
+ const located = bundledDir(repoRoot, entry, fsImpl);
130
+ if (located.error) findings = [located.error];
131
+ else {
132
+ state.storeDir = located.dir;
133
+ let text = null;
134
+ try { text = fsImpl.readFileSync(path.join(located.dir , MANIFEST_FILE), 'utf8'); } catch { /* reported below */ }
135
+ if (text === null) {
136
+ findings = [finding(entry.id, MANIFEST_FILE, 'file-missing', `${MANIFEST_FILE} is missing`, `a ${MANIFEST_FILE} at the package root`)];
137
+ } else {
138
+ let manifest = null;
139
+ try { manifest = JSON.parse(text); } catch { /* the checker reports it */ }
140
+ const hostFor = host ?? (typeof manifest?.version === 'string' ? manifest.version : null);
141
+ // `protocols` is unused for a bundled entry without `root`; passing it stops the checker from loading the
142
+ // merged registry, which would build this view again.
143
+ findings = checkManifest(text, { pointsRegistry, hostVersion: hostFor, bundled: bundledIds, repoRoot, fsImpl, protocols: { presets: {} } })
144
+ .map((f ) => ({ ...f, package: f.package ?? entry.id }));
145
+ if (manifest && typeof manifest === 'object') {
146
+ if (typeof manifest.version === 'string') state.version = manifest.version;
147
+ if (manifest.id !== entry.id) {
148
+ findings.push(finding(entry.id, 'id', 'bad-id', `manifest id ${JSON.stringify(manifest.id)} differs from the bundled id '${entry.id}'`, entry.id));
149
+ }
150
+ state.contributions = contributionList(manifest, tiers, (c ) => c.id /* IC-003: the first-party package keeps bare built-in ids */);
151
+ state.tierMax = tierMaxOf(state.contributions);
152
+ }
153
+ }
154
+ }
155
+ } catch (error ) {
156
+ findings = [finding(entry.id, 'dir', 'state-unreadable', `cannot evaluate the bundled package: ${error?.message || String(error)}`, 'a readable package directory')];
157
+ }
158
+ const errors = findings.filter((f) => f.severity === 'error');
159
+ if (errors.length > 0) {
160
+ state.state = 'incompatible';
161
+ state.reasons = [...new Set (errors.map((f) => f.code))];
162
+ }
163
+ for (const c of state.contributions) c.active = state.state === 'active';
164
+ return { state, findings };
165
+ }
166
+
167
+ /** Why a `trust` grant does not hold for `state`, or null when one does. The first that applies: unreadable, missing, content, policy, capabilities, revoked. */
168
+ function trustReason(state , ctx ) {
169
+ const { grants, stateRead, pointsRegistry } = ctx;
170
+ if (stateRead.unreadable || grants.findings.some((f ) => f.code === 'state-unreadable')) return 'state-unreadable';
171
+ // Lines for one version with other digests coexist (IC-008 duplicate key): judge the one whose digests match, else
172
+ // the one with the matching content digest, else any (each of those reads content-changed).
173
+ const lines = grants.grants.filter((g ) => g.grant === 'trust' && g.package === state.id && g.version === state.version);
174
+ const grant = lines.find((g ) => g.contentDigest === state.contentDigest && g.capabilityDigest === state.capabilityDigest)
175
+ ?? lines.find((g ) => g.contentDigest === state.contentDigest) ?? lines[0];
176
+ if (!grant) return 'grant-missing';
177
+ if (grant.contentDigest !== state.contentDigest) return 'content-changed';
178
+ if (grant.policy < pointsRegistry.trustPolicyVersion) return 'policy-changed';
179
+ if (grant.capabilityDigest !== state.capabilityDigest) return 'capabilities-changed';
180
+ if (grantRevoked(stateRead, grant)) return 'revoked';
181
+ return null;
182
+ }
183
+
184
+ /** The valid `exempt` grant for exactly this package version, content digest and host version (IC-008), or null. */
185
+ function exemptGrantFor(row , ctx ) {
186
+ if (!ctx.host) return null;
187
+ return ctx.grants.grants.find((g ) => g.grant === 'exempt' && g.acceptRisk === true && g.package === row.id && g.version === row.version
188
+ && g.contentDigest === row.contentDigest && g.host === ctx.host && !grantRevoked(ctx.stateRead, g)) ?? null;
189
+ }
190
+
191
+ /** The state of one third-party package (a lock row). Never throws. */
192
+ function thirdPartyState(row , ctx ) {
193
+ const { fsImpl, pointsRegistry } = ctx;
194
+ const storeDir = path.join(ctx.storeRoot, row.id, row.contentDigest.slice(0, 16));
195
+ const state = {
196
+ id: row.id, version: row.version, source: row.source, firstParty: false, tierMax: null, enabled: row.enabled,
197
+ state: 'active', reasons: [], contributions: [], grant: { trust: false, exempt: false },
198
+ contentDigest: row.contentDigest, capabilityDigest: row.capabilityDigest, storeDir,
199
+ };
200
+ let findings = [];
201
+ const settle = (name , reasons ) => {
202
+ state.state = name;
203
+ state.reasons = reasons;
204
+ for (const c of state.contributions) c.active = name === 'active';
205
+ return { state, findings };
206
+ };
207
+ try {
208
+ let text = null;
209
+ try { text = String(fsImpl.readFileSync(path.join(storeDir, MANIFEST_FILE), 'utf8')); } catch { /* the store entry is missing */ }
210
+ if (text === null) {
211
+ findings = [finding(row.id, `store/${row.id}/${row.contentDigest.slice(0, 16)}`, 'file-missing', `the store entry for ${row.id} has no ${MANIFEST_FILE}`, 'an intact store entry')];
212
+ return settle('suspended', ['content-changed']);
213
+ }
214
+ let manifest = null;
215
+ try { manifest = JSON.parse(text); } catch { /* the checker reports it */ }
216
+ const tiers = Object.fromEntries(pointsRegistry.points.map((p ) => [p.id, p.tier]));
217
+ if (manifest && typeof manifest === 'object') {
218
+ state.contributions = contributionList(manifest, tiers, (c ) => `${row.id}.${c.id}`);
219
+ state.tierMax = tierMaxOf(state.contributions);
220
+ }
221
+
222
+ const exempt = exemptGrantFor(row, ctx);
223
+ const options = { pointsRegistry, hostVersion: ctx.host, bundled: [], repoRoot: ctx.repoRoot, fsImpl, protocols: ctx.protocols, hostExempt: exempt !== null };
224
+ let checked = null;
225
+ if (row.enabled) {
226
+ checked = checkPackage(storeDir, options);
227
+ findings = checked.findings;
228
+ } else {
229
+ findings = checkManifest(text, options); // no `root`: the manifest alone, lexically; nothing else under the entry is read
230
+ }
231
+ findings = findings.map((f ) => ({ ...f, package: f.package ?? row.id }));
232
+ if (manifest && typeof manifest === 'object' && manifest.id !== row.id) {
233
+ findings.push(finding(row.id, 'id', 'bad-id', `manifest id ${JSON.stringify(manifest.id)} differs from the recorded id '${row.id}'`, row.id));
234
+ }
235
+ const errors = findings.filter((f ) => f.severity === 'error');
236
+ if (errors.length > 0) return settle('incompatible', [...new Set (errors.map((f ) => f.code))]);
237
+ state.grant.exempt = findings.some((f ) => f.code === 'host-incompatible' && f.severity === 'warning');
238
+
239
+ if (checked && checked.contentDigest !== row.contentDigest) {
240
+ findings.push(finding(row.id, 'contentDigest', 'digest-mismatch', `the store entry for ${row.id} hashes to ${checked.contentDigest}, not the recorded ${row.contentDigest}`, 'the recorded content digest'));
241
+ return settle('suspended', ['content-changed']);
242
+ }
243
+ if (checked) state.capabilityDigest = checked.capabilityDigest;
244
+ if (!row.enabled) return settle('disabled', ['disabled-by-lock']);
245
+ if (state.tierMax !== 'inert' && state.tierMax !== null) {
246
+ const reason = trustReason(state, ctx);
247
+ if (reason !== null) return settle('needs-trust', [reason]);
248
+ state.grant.trust = true;
249
+ }
250
+ return settle('active', []);
251
+ } catch (error ) {
252
+ findings = [finding(row.id, 'store', 'state-unreadable', `cannot evaluate the package: ${error?.message || String(error)}`, 'a readable store entry')];
253
+ return settle('incompatible', ['state-unreadable']);
254
+ }
255
+ }
256
+
257
+ /**
258
+ * The bundled view plus the third-party packages of the global lock. Returns `bundled` itself when the lock holds
259
+ * nothing that changes it. An unreadable lock adds one `state-unreadable` finding and no package.
260
+ */
261
+ function withLock(bundled , o ) {
262
+ let rows ;
263
+ try {
264
+ rows = lockPackages(readLock({ scope: 'global', homeDir: o.home }, { fsImpl: o.fsImpl }));
265
+ } catch (error ) {
266
+ return { ...bundled, findings: [...bundled.findings, finding(null, 'doflow.lock', 'state-unreadable', error?.message || String(error), 'a readable doflow.lock')] };
267
+ }
268
+ let packages = bundled.packages.map((pkg ) => {
269
+ const row = rows.find((r ) => r.id === pkg.id && r.source?.type === 'bundled');
270
+ if (!row || row.enabled || pkg.state !== 'active') return pkg;
271
+ return { ...pkg, enabled: false, state: 'disabled', reasons: ['disabled-by-lock'], contributions: pkg.contributions.map((c ) => ({ ...c, active: false })) };
272
+ });
273
+ const thirdParty = rows.filter((r ) => r.source?.type !== 'bundled');
274
+ if (thirdParty.length === 0) return packages.some((p , i ) => p !== bundled.packages[i]) ? { ...bundled, packages } : bundled;
275
+
276
+ const loaded = loadPoints({ repoRoot: o.repoRoot, fsImpl: o.fsImpl });
277
+ if (!loaded.ok) return { ...bundled, packages };
278
+ const core = loadCoreProtocols({ repoRoot: o.repoRoot, fsImpl: o.fsImpl });
279
+ const grants = readGrants({ env: o.env, fsImpl: o.fsImpl, projectRoot: o.projectRoot, ...(o.tmpDir === undefined ? {} : { tmpDir: o.tmpDir }) });
280
+ const stateRead = readState({ homeDir: o.home, fsImpl: o.fsImpl });
281
+ const ctx = {
282
+ repoRoot: o.repoRoot, fsImpl: o.fsImpl, pointsRegistry: loaded.registry, host: hostVersion(o.repoRoot), grants, stateRead,
283
+ protocols: core.ok ? core.registry : undefined, storeRoot: path.join(o.home, '.doflow', 'packages', 'store'),
284
+ };
285
+ const bundledIds = new Set (bundled.packages.map((p ) => p.id));
286
+ const findings = [...bundled.findings, ...grants.findings, ...stateRead.findings];
287
+ for (const row of thirdParty) {
288
+ if (bundledIds.has(row.id)) {
289
+ findings.push(finding(row.id, 'id', 'collision-package', `the lock records '${row.id}' from a ${row.source?.type} source, but that id belongs to a bundled package`, 'an id no bundled package uses'));
290
+ continue;
291
+ }
292
+ const one = thirdPartyState(row, ctx);
293
+ packages = [...packages, one.state];
294
+ findings.push(...one.findings);
295
+ }
296
+ return { packages, points: bundled.points, findings, stateRead, grants, homeDir: o.home, env: o.env, tmpDir: o.tmpDir };
297
+ }
298
+
299
+ const DISABLED_KEY = 'packages.disabled';
300
+
301
+ /**
302
+ * The `packages.disabled` entry of the project settings of the working directory, in the shape a caller injects.
303
+ * `byId` names the lowest layer that listed each id. A registry or settings fault is an invalid value: fail closed.
304
+ * The settings are read as a view of this one key (`only`): a full load validates every key of a layer file, and the
305
+ * `review.preset` and `debate.preset` value lists hold the presets of active packages (IC-014), which is this view
306
+ * again. The key's own list (`packages.ids`) reads the lock alone, so this read never re-enters the view.
307
+ */
308
+ function resolvedDisabledEntry(env ) {
309
+ try {
310
+ const resolution = require('../settings/resolve.js').loadSettings({ projectRoot: process.cwd(), env: env ?? process.env, only: [DISABLED_KEY] }).get(DISABLED_KEY);
311
+ const byId = {};
312
+ for (const c of resolution.candidates) {
313
+ if (c.status !== 'decided' || !Array.isArray(c.value)) continue;
314
+ for (const id of c.value) byId[id] ??= c.layer;
315
+ }
316
+ return { value: resolution.value, layer: resolution.origin.layer, state: resolution.state, byId };
317
+ } catch {
318
+ return { value: null, layer: 'default', state: 'invalid' };
319
+ }
320
+ }
321
+
322
+ /**
323
+ * The packages with the `packages.disabled` entry applied (IC-010 state 3). Only a package that would be `active` or
324
+ * `needs-trust` changes: `incompatible` and `suspended` outrank `disabled`. Returns `packages` itself when nothing changes.
325
+ */
326
+ function applySettingsDisable(packages , entry ) {
327
+ if (!entry) return packages;
328
+ const invalid = entry.state === 'invalid';
329
+ const listed = new Set (!invalid && Array.isArray(entry.value) ? entry.value : []);
330
+ const reasonFor = (pkg ) => {
331
+ if (invalid) return pkg.source?.type === 'bundled' ? null : 'settings-invalid';
332
+ return listed.has(pkg.id) ? `disabled-by-${entry.byId?.[pkg.id] ?? entry.layer}` : null;
333
+ };
334
+ let changed = false;
335
+ const out = packages.map((pkg ) => {
336
+ const reason = pkg.state === 'active' || pkg.state === 'needs-trust' ? reasonFor(pkg) : null;
337
+ if (reason === null) return pkg;
338
+ changed = true;
339
+ return { ...pkg, state: 'disabled', reasons: [reason], contributions: pkg.contributions.map((c ) => ({ ...c, active: false })) };
340
+ });
341
+ return changed ? out : packages;
342
+ }
343
+
344
+ /**
345
+ * `{ packages, points, findings }`. `repoRoot`, `fsImpl`, `homeDir` (the account home, default: `HOME` of `env`, else
346
+ * `os.homedir()`), `projectRoot` (default: the project detected from the working directory, `detectedProjectRoot`;
347
+ * `null` means no project) and `tmpDir` (default: the system temp directory; both only decide whether the grants file
348
+ * counts, IC-008) are for tests and the verbs; an injected `fsImpl` bypasses
349
+ * the memo. A point registry that cannot be read leaves no package to find: `packages` and `points` are empty and the
350
+ * one finding is `state-unreadable` on `points`.
351
+ */
352
+ function packageView({ settingsView, env, fsImpl = fs, repoRoot = REPO_ROOT, homeDir, projectRoot, tmpDir } = {}) {
353
+ const base = fullView({ repoRoot, fsImpl, env, homeDir, projectRoot, tmpDir });
354
+ const entry = settingsView ? settingsView[DISABLED_KEY] : fsImpl === fs ? resolvedDisabledEntry(env) : undefined;
355
+ const packages = applySettingsDisable(base.packages, entry);
356
+ return packages === base.packages ? base : { ...base, packages };
357
+ }
358
+
359
+ /** The states before `packages.disabled` is applied: the bundled view plus the lock's third-party packages, memoised on everything they read. */
360
+ function fullView(o ) {
361
+ const bundled = baseView(o.repoRoot, o.fsImpl);
362
+ if (building > 0 || bundled.points.length === 0) return bundled;
363
+ const environment = o.env ?? process.env;
364
+ const home = o.homeDir ?? (typeof environment.HOME === 'string' && path.isAbsolute(environment.HOME) ? environment.HOME : os.homedir());
365
+ const projectRoot = o.projectRoot === undefined ? detectedProjectRoot({ homeDir: home, fsImpl: o.fsImpl })
366
+ : o.projectRoot === null ? null : path.resolve(o.projectRoot);
367
+ const key = JSON.stringify([path.resolve(o.repoRoot), path.resolve(home), configHome(environment).dir, projectRoot, o.tmpDir ?? null]);
368
+ if (o.fsImpl === fs && fullMemo.has(key)) return fullMemo.get(key);
369
+ building += 1;
370
+ let result ;
371
+ try {
372
+ result = withLock(bundled, { ...o, env: environment, home, projectRoot });
373
+ } finally {
374
+ building -= 1;
375
+ }
376
+ if (o.fsImpl === fs) fullMemo.set(key, result);
377
+ return result;
378
+ }
379
+
380
+ /** The states before any setting is applied; memoised per runtime root for the real file system. */
381
+ function baseView(repoRoot , fsImpl ) {
382
+ const key = path.resolve(repoRoot);
383
+ if (fsImpl === fs && memo.has(key)) return memo.get(key);
384
+ const loaded = loadPoints({ repoRoot, fsImpl });
385
+ let result ;
386
+ if (!loaded.ok) {
387
+ result = { packages: [], points: [], findings: [finding(null, 'points', 'state-unreadable', loaded.errors.join('; '), 'a readable point registry')] };
388
+ } else {
389
+ const pointsRegistry = loaded.registry;
390
+ const ctx = { repoRoot, fsImpl, pointsRegistry, host: hostVersion(repoRoot), bundledIds: pointsRegistry.bundled.map((b ) => b.id) };
391
+ const packages = [];
392
+ const findings = [];
393
+ for (const entry of pointsRegistry.bundled) {
394
+ const one = bundledState(entry, ctx);
395
+ packages.push(one.state);
396
+ findings.push(...one.findings);
397
+ }
398
+ result = { packages, points: pointRows(pointsRegistry), findings };
399
+ }
400
+ if (fsImpl === fs) memo.set(key, result);
401
+ return result;
402
+ }
403
+
404
+ function resetPackageView() { memo.clear(); fullMemo.clear(); }
405
+
406
+ module.exports = { packageView, resetPackageView, hostVersion, applySettingsDisable };
@@ -0,0 +1,59 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ /**
6
+ * `plan-check <plan path> [--json]` (feature 115, IC-001): the plan judgement `planVerdict` makes,
7
+ * printed, plus row 134's content findings (`plan-content.ts`), each with a `path:line`. Read-only:
8
+ * it reads the plan and the requirement beside it and writes nothing. Exit 0 ok, 1 problems, 2
9
+ * usage or an unreadable file; the exit and `ok` are the task-graph verdict alone, so plan-check,
10
+ * task-attempt and the roadmap facts still agree about a plan's task graph.
11
+ */
12
+
13
+ const fs = require('node:fs');
14
+ const path = require('node:path');
15
+ const { planVerdict } = require('./task-graph.js');
16
+ const { planFindings } = require('./plan-content.js');
17
+ const { finishRuntime } = require('./cli-result.js');
18
+
19
+ const TEXT_MAX = 200;
20
+ const ALLOWED_FLAGS = new Set(['--json']);
21
+
22
+ function refuse(error , message , json ) {
23
+ if (json) console.log(JSON.stringify({ ok: false, error, message }));
24
+ else console.error(`doflow plan-check: ${error}: ${message}`);
25
+ return finishRuntime(2);
26
+ }
27
+
28
+ function describe(file , verdict , findings ) {
29
+ const warned = verdict.warnings.length ? `; warnings: ${verdict.warnings.join('; ')}` : '';
30
+ const found = findings.length ? `; findings: ${findings.map((f ) => `${f.rule} ${f.path}:${f.line}`).join(', ')}` : '';
31
+ const parts = verdict.problems.map((problem ) => (problem.ids.length ? `${problem.kind} ${problem.ids.join(',')}` : problem.kind));
32
+ const line = verdict.ok
33
+ ? `plan-check: ${file}: ok, ${verdict.tasks} tasks${warned}${found}`
34
+ : `plan-check: ${file}: ${verdict.problems.length} problem(s): ${parts.join('; ')}${warned}${found}`;
35
+ return line.length > TEXT_MAX ? `${line.slice(0, TEXT_MAX - 3)}...` : line;
36
+ }
37
+
38
+ function handlePlanCheckCommand({ positional = [], extraFlags = [], flagsGiven, json = false, cwd = process.cwd() } = {}) {
39
+ const flags = flagsGiven || extraFlags;
40
+ const badFlag = flags.find((f ) => !ALLOWED_FLAGS.has(f.split('=')[0]));
41
+ if (badFlag) return refuse('usage', `unknown flag '${badFlag}'; usage: plan-check <plan path> [--json]`, json);
42
+ if (positional.length === 0) return refuse('usage', 'a plan path is required; usage: plan-check <plan path> [--json]', json);
43
+ if (positional.length > 1) return refuse('usage', `exactly one plan path is accepted, got ${positional.length}; usage: plan-check <plan path> [--json]`, json);
44
+ const file = positional[0];
45
+ let text ;
46
+ const planAbs = path.resolve(cwd, file);
47
+ try {
48
+ text = fs.readFileSync(planAbs, 'utf8');
49
+ } catch (error ) {
50
+ return refuse('plan-unreadable', `cannot read '${file}': ${error.code || error.message}`, json);
51
+ }
52
+ const verdict = planVerdict(text);
53
+ const findings = planFindings({ planAbs, text, displayPath: file, cwd });
54
+ if (json) console.log(JSON.stringify({ ...verdict, findings, file }));
55
+ else console.log(describe(file, verdict, findings));
56
+ return finishRuntime(verdict.ok ? 0 : 1);
57
+ }
58
+
59
+ module.exports = { handlePlanCheckCommand };