@khoavu882/doflow 2.0.0-beta.4 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (497) hide show
  1. package/README.md +12 -11
  2. package/bin/doflow.js +21 -11
  3. package/core/.antigravity-plugin/plugin.json +1 -3
  4. package/core/.claude-plugin/plugin.json +1 -1
  5. package/core/.codex-plugin/plugin.json +1 -1
  6. package/core/.plugin/plugin.json +1 -1
  7. package/core/harnesses/claude/hooks/lib.sh +43 -24
  8. package/core/harnesses/claude/hooks/mcp-tool-guard.sh +7 -17
  9. package/core/harnesses/claude/hooks/permission-audit.sh +3 -0
  10. package/core/harnesses/claude/hooks/post-compact.sh +35 -32
  11. package/core/harnesses/claude/hooks/post-edit-lint.sh +2 -0
  12. package/core/harnesses/claude/hooks/post-shell-observe.sh +48 -0
  13. package/core/harnesses/claude/hooks/post-tool-failure.sh +3 -0
  14. package/core/harnesses/claude/hooks/pre-bash-guard.sh +7 -17
  15. package/core/harnesses/claude/hooks/pre-compact.sh +23 -27
  16. package/core/harnesses/claude/hooks/pre-implement-gate.sh +7 -18
  17. package/core/harnesses/claude/hooks/session-end.sh +23 -10
  18. package/core/harnesses/claude/hooks/session-start.sh +5 -3
  19. package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
  20. package/core/harnesses/claude/hooks/stop-check.sh +5 -3
  21. package/core/harnesses/claude/hooks/subagent-audit.sh +5 -5
  22. package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -3
  23. package/core/harnesses/claude/settings/settings.json +18 -0
  24. package/core/harnesses/claude/statusline/doflow-statusline.sh +1 -0
  25. package/core/harnesses/codex/agents/core-implementer.toml +1 -0
  26. package/core/harnesses/codex/agents/delivery-lead.toml +10 -0
  27. package/core/harnesses/codex/agents/quality-guardian.toml +1 -1
  28. package/core/harnesses/codex/agents/research-writer.toml +1 -1
  29. package/core/harnesses/codex/agents/reviewer.toml +10 -0
  30. package/core/harnesses/codex/agents/spec-analyst.toml +1 -1
  31. package/core/harnesses/codex/agents/system-architect.toml +1 -1
  32. package/core/harnesses/codex/hooks/lib.sh +44 -25
  33. package/core/harnesses/codex/hooks/mcp-tool-guard.sh +7 -15
  34. package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
  35. package/core/harnesses/codex/hooks/pre-bash-guard.sh +7 -15
  36. package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
  37. package/core/harnesses/codex/hooks/pre-implement-gate.sh +7 -16
  38. package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
  39. package/core/harnesses/codex/hooks/session-start.sh +5 -3
  40. package/core/harnesses/codex/hooks/stop-check.sh +6 -5
  41. package/core/harnesses/codex/hooks/subagent-audit.sh +18 -6
  42. package/core/harnesses/codex/hooks/user-prompt-submit.sh +5 -4
  43. package/core/harnesses/copilot/hooks/doflow-lib.sh +93 -0
  44. package/core/harnesses/copilot/hooks/doflow-mcp-tool-guard.sh +9 -0
  45. package/core/harnesses/copilot/hooks/doflow-pre-bash-guard.sh +9 -0
  46. package/core/harnesses/copilot/hooks/doflow-pre-implement-gate.sh +9 -0
  47. package/core/harnesses/copilot/hooks/doflow-session-start.sh +9 -0
  48. package/core/harnesses/copilot/hooks/doflow-stop-check.sh +9 -0
  49. package/core/harnesses/copilot/hooks/doflow-subagent-start.sh +9 -0
  50. package/core/harnesses/copilot/hooks/doflow-subagent-stop.sh +9 -0
  51. package/core/harnesses/copilot/hooks/doflow.json +57 -0
  52. package/core/harnesses/kiro/agents/doflow.json +1 -1
  53. package/core/harnesses/kiro/hooks/lib.sh +26 -6
  54. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +21 -5
  55. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +21 -5
  56. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +21 -5
  57. package/core/harnesses/kiro/hooks/session-start.sh +12 -5
  58. package/core/harnesses/kiro/hooks/stop-check.sh +22 -30
  59. package/core/harnesses/pi/extensions/doflow-guard.js +167 -0
  60. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +27 -12
  61. package/core/harnesses/shared/hooks/adapters/antigravity.js +14 -57
  62. package/core/harnesses/shared/hooks/bridge/bridge.sh +277 -0
  63. package/core/harnesses/shared/hooks/bridge/envelope.jq +129 -0
  64. package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
  65. package/core/harnesses/shared/hooks/bridge/respond.sh +70 -0
  66. package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +28 -14
  67. package/core/harnesses/shared/hooks/policies/capture-failure.sh +51 -16
  68. package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
  69. package/core/harnesses/shared/hooks/policies/contract.sh +78 -0
  70. package/core/harnesses/shared/hooks/policies/deny-json.sh +35 -9
  71. package/core/harnesses/shared/hooks/policies/emission-rules.sh +37 -0
  72. package/core/harnesses/shared/hooks/policies/emission.jq +209 -0
  73. package/core/harnesses/shared/hooks/policies/emission.sh +383 -0
  74. package/core/harnesses/shared/hooks/policies/identity.sh +177 -0
  75. package/core/harnesses/shared/hooks/policies/lib.sh +65 -40
  76. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +48 -35
  77. package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +284 -33
  78. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +180 -110
  79. package/core/harnesses/shared/hooks/policies/session-context.sh +66 -7
  80. package/core/harnesses/shared/hooks/policies/stop-check.sh +61 -23
  81. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +39 -6
  82. package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
  83. package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +97 -159
  84. package/core/harnesses/shared/hooks/stream-hook-runner.js +114 -187
  85. package/core/harnesses/shared/locator/doflow-run +2 -3
  86. package/core/registry/advice.json +3 -3
  87. package/core/registry/assets.json +72 -80
  88. package/core/registry/capabilities.json +12 -0
  89. package/core/registry/contracts.json +60 -33
  90. package/core/registry/contribution-points.json +32 -0
  91. package/core/registry/gates.json +32 -0
  92. package/core/registry/guidance-caps.json +40 -0
  93. package/core/registry/harnesses.json +1542 -277
  94. package/core/registry/lifecycle.json +2837 -37
  95. package/core/registry/mcp.json +5 -1
  96. package/core/registry/models.json +7 -1
  97. package/core/registry/package-templates/README.md +19 -0
  98. package/core/registry/package-templates/agent/files/agent.md +9 -0
  99. package/core/registry/package-templates/agent/template.json +4 -0
  100. package/core/registry/package-templates/capability-provider/files/provider.json +9 -0
  101. package/core/registry/package-templates/capability-provider/template.json +11 -0
  102. package/core/registry/package-templates/debate-preset/files/preset.json +26 -0
  103. package/core/registry/package-templates/debate-preset/template.json +4 -0
  104. package/core/registry/package-templates/review-preset/files/preset.json +30 -0
  105. package/core/registry/package-templates/review-preset/template.json +4 -0
  106. package/core/registry/package-templates/skill/files/SKILL.md +8 -0
  107. package/core/registry/package-templates/skill/template.json +4 -0
  108. package/core/registry/package-templates/worker/files/worker.json +92 -0
  109. package/core/registry/package-templates/worker/template.json +11 -0
  110. package/core/registry/packages/doflow/doflow-package.json +15 -0
  111. package/core/registry/packages/doflow/presets/debate-3.json +26 -0
  112. package/core/registry/protocols.json +61 -0
  113. package/core/registry/routes.json +17 -0
  114. package/core/registry/rules.json +431 -0
  115. package/core/registry/settings.json +276 -0
  116. package/core/registry/storage.json +701 -30
  117. package/core/registry/telemetry.json +236 -0
  118. package/core/registry/transcripts.json +142 -0
  119. package/core/registry/verification.json +76 -1
  120. package/core/registry/workflows.json +55 -0
  121. package/core/shared/agent-specs/core-implementer.md +3 -0
  122. package/core/shared/agent-specs/delivery-lead.md +68 -0
  123. package/core/shared/agent-specs/quality-guardian.md +5 -1
  124. package/core/shared/agent-specs/research-writer.md +10 -3
  125. package/core/shared/agent-specs/reviewer.md +53 -0
  126. package/core/shared/agent-specs/spec-analyst.md +5 -6
  127. package/core/shared/agent-specs/system-architect.md +5 -1
  128. package/core/shared/guidance/DOFLOW_CORE.md +1 -5
  129. package/core/shared/guidance/modes/MODE_DeepResearch.md +1 -1
  130. package/core/shared/guidance/modes/MODE_Orchestration.md +4 -3
  131. package/core/shared/guidance/modes/MODE_Task_Management.md +14 -14
  132. package/core/shared/guidance/pointers/codex.md +1 -4
  133. package/core/shared/guidance/references/ARTIFACT_FORMAT.md +30 -16
  134. package/core/shared/guidance/references/ARTIFACT_VOICE.md +3 -2
  135. package/core/shared/guidance/references/CONSTITUTION_BASE.md +17 -15
  136. package/core/shared/guidance/references/DIAGRAM_GUIDE.md +1 -1
  137. package/core/shared/guidance/references/DOFLOW_CHAIN.md +17 -19
  138. package/core/shared/guidance/references/EVIDENCE_LEDGER.md +7 -8
  139. package/core/shared/guidance/references/MODEL_SELECTION.md +5 -3
  140. package/core/shared/guidance/references/ORCHESTRATION.md +1 -2
  141. package/core/shared/guidance/rules/RULES.md +45 -0
  142. package/core/shared/scripts/doflow/bash/do-args.sh +37 -0
  143. package/core/shared/scripts/doflow/bash/do-exec-paths.sh +4 -0
  144. package/core/shared/scripts/doflow/bash/do-git-state.sh +15 -4
  145. package/core/shared/scripts/doflow/bash/do-parallel-check.sh +4 -0
  146. package/core/shared/scripts/doflow/bash/do-paths.sh +40 -2
  147. package/core/shared/scripts/doflow/bash/do-prereqs.sh +2 -1
  148. package/core/shared/scripts/doflow/bash/do-review-package.sh +4 -0
  149. package/core/shared/scripts/doflow/bash/do-task-brief.sh +58 -16
  150. package/core/shared/scripts/doflow/bash/render-audit.sh +26 -2
  151. package/core/shared/scripts/doflow/bash/render-diagrams.sh +1 -0
  152. package/core/shared/scripts/doflow/bash/render-puml.sh +2 -1
  153. package/core/shared/scripts/doflow/bash/validate-artifacts.sh +48 -4
  154. package/core/shared/scripts/doflow/bin/doflow-run +154 -35
  155. package/core/shared/skills/do/SKILL.md +12 -10
  156. package/core/shared/skills/do/references/advice_calls.md +26 -0
  157. package/core/shared/skills/do/references/pm_routing.md +1 -2
  158. package/core/shared/skills/do/references/program_mode.md +31 -0
  159. package/core/shared/skills/do-brainstorm/SKILL.md +20 -74
  160. package/core/shared/skills/do-brainstorm/references/handoff_branches.md +35 -0
  161. package/core/shared/skills/do-brainstorm/references/intent_seed.md +21 -0
  162. package/core/shared/skills/do-brainstorm/references/round_log.md +17 -0
  163. package/core/shared/skills/do-code-review/SKILL.md +41 -144
  164. package/core/shared/skills/do-code-review/content-types/markdown.md +24 -2
  165. package/core/shared/skills/do-code-review/references/tools.md +90 -0
  166. package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +549 -7
  167. package/core/shared/skills/do-design/SKILL.md +24 -23
  168. package/core/shared/skills/do-diagnose/SKILL.md +14 -4
  169. package/core/shared/skills/do-document/SKILL.md +11 -1
  170. package/core/shared/skills/do-document/references/deep_research.md +1 -2
  171. package/core/shared/skills/do-document/references/embedded-research.md +18 -6
  172. package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
  173. package/core/shared/skills/do-execute-plan/SKILL.md +17 -59
  174. package/core/shared/skills/do-execute-plan/references/handoff_record.md +7 -2
  175. package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +13 -0
  176. package/core/shared/skills/do-execute-plan/references/readiness_gate.md +28 -1
  177. package/core/shared/skills/do-execute-plan/references/run_position.md +13 -1
  178. package/core/shared/skills/do-execute-plan/references/scaffold.md +17 -9
  179. package/core/shared/skills/do-execute-plan/references/state_checkpoint.md +11 -0
  180. package/core/shared/skills/do-execute-plan/references/task_attempts.md +78 -24
  181. package/core/shared/skills/do-flow/SKILL.md +8 -9
  182. package/core/shared/skills/do-flow/references/task_classes.md +2 -2
  183. package/core/shared/skills/do-git/SKILL.md +4 -0
  184. package/core/shared/skills/do-implement/SKILL.md +7 -9
  185. package/core/shared/skills/do-plan/SKILL.md +29 -19
  186. package/core/shared/skills/do-test/SKILL.md +7 -4
  187. package/core/shared/templates/doflow/constitution-template.md +9 -3
  188. package/core/shared/templates/doflow/data-model-template.md +1 -1
  189. package/core/shared/templates/doflow/design-template.md +113 -16
  190. package/core/shared/templates/doflow/plan-template.md +11 -10
  191. package/core/shared/templates/doflow/question-log-template.md +14 -2
  192. package/core/shared/templates/doflow/requirement-template.md +1 -1
  193. package/core/worker-evidence/antigravity/checks.json +14 -0
  194. package/core/worker-evidence/codex/2026-10-18-cli-check.json +107 -0
  195. package/core/worker-evidence/codex/2026-10-18-live-run-edit.json +73 -0
  196. package/core/worker-evidence/codex/2026-10-18-live-run-read-only.json +62 -0
  197. package/core/worker-evidence/codex/2026-10-24-cli-check.json +126 -0
  198. package/core/worker-evidence/codex/checks.json +38 -0
  199. package/package.json +10 -6
  200. package/src/adapters/antigravity/index.js +226 -113
  201. package/src/adapters/claude/index.js +39 -5
  202. package/src/adapters/codex/agents.js +25 -15
  203. package/src/adapters/codex/hooks.js +40 -5
  204. package/src/adapters/codex/index.js +100 -22
  205. package/src/adapters/codex/native-projection.js +6 -2
  206. package/src/adapters/copilot/index.js +58 -23
  207. package/src/adapters/copy-tree.js +294 -48
  208. package/src/adapters/guidance-manifest.js +71 -0
  209. package/src/adapters/guidance-pointer.js +19 -0
  210. package/src/adapters/hook-commands.js +6 -10
  211. package/src/adapters/index.js +1 -0
  212. package/src/adapters/kiro/index.js +38 -13
  213. package/src/adapters/opencode/index.js +8 -8
  214. package/src/adapters/pi/hooks.js +120 -0
  215. package/src/adapters/pi/index.js +48 -16
  216. package/src/adapters/resolve-source.js +26 -2
  217. package/src/cli/commands/install.js +3 -2
  218. package/src/cli/commands/reconcile.js +53 -9
  219. package/src/cli/commands/remove.js +2 -2
  220. package/src/cli/commands/rollback.js +6 -3
  221. package/src/cli/commands/self-update.js +18 -9
  222. package/src/cli/commands/status.js +21 -2
  223. package/src/cli/commands/ui.js +99 -0
  224. package/src/cli/commands/update.js +3 -2
  225. package/src/cli/index.js +338 -33
  226. package/src/cli/runtime-commands.js +243 -52
  227. package/src/cli/shared.js +0 -2
  228. package/src/helper/git.js +1 -1
  229. package/src/helper/guardrails.js +1 -1
  230. package/src/helper/marker-merge.js +31 -13
  231. package/src/helper/package-ids.js +37 -0
  232. package/src/helper/semver.js +94 -0
  233. package/src/install/backup.js +105 -15
  234. package/src/install/context.js +1 -1
  235. package/src/install/targets.js +31 -16
  236. package/src/lifecycle/coverage.js +74 -0
  237. package/src/lifecycle/hook-step.js +61 -0
  238. package/src/lifecycle/hook-tier.js +160 -0
  239. package/src/lifecycle/index.js +434 -86
  240. package/src/lifecycle/retired-harnesses.js +318 -0
  241. package/src/lifecycle/view.js +35 -15
  242. package/src/registry/index.js +686 -32
  243. package/src/registry/worker-ids.js +45 -0
  244. package/src/release/build-package.js +0 -3
  245. package/src/release/docs-blocks.js +492 -0
  246. package/src/release/preflight.js +425 -0
  247. package/src/release/rehearse.js +401 -0
  248. package/src/release/release-workflow.js +733 -0
  249. package/src/release/sync-plugin-versions.js +43 -7
  250. package/src/runtime/advice/bench-gate.js +25 -0
  251. package/src/runtime/advice/cli.js +59 -16
  252. package/src/runtime/advice/client.js +32 -9
  253. package/src/runtime/advice/config.js +98 -133
  254. package/src/runtime/advice/doctor.js +1 -1
  255. package/src/runtime/advice/elevation.js +97 -0
  256. package/src/runtime/advice/endpoint-guard.js +9 -3
  257. package/src/runtime/advice/mode.js +37 -0
  258. package/src/runtime/advice/record.js +18 -5
  259. package/src/runtime/advice/registry.js +4 -1
  260. package/src/runtime/advice/shadow-config.js +229 -0
  261. package/src/runtime/advice/shadow-dispatcher.js +276 -0
  262. package/src/runtime/brief-rules.js +81 -0
  263. package/src/runtime/brief-tools.js +111 -34
  264. package/src/runtime/candidate.js +514 -0
  265. package/src/runtime/capabilities/executor.js +450 -0
  266. package/src/runtime/capabilities/loopback.js +41 -0
  267. package/src/runtime/capabilities/sanitizer.js +88 -0
  268. package/src/runtime/capability-router.js +135 -3
  269. package/src/runtime/checkouts.js +73 -11
  270. package/src/runtime/claims.js +27 -12
  271. package/src/runtime/cli-result.js +11 -4
  272. package/src/runtime/cli.js +221 -277
  273. package/src/runtime/config-home.js +42 -0
  274. package/src/runtime/context/arbitrate.js +767 -0
  275. package/src/runtime/context/budget.js +203 -0
  276. package/src/runtime/context/compose.js +1367 -0
  277. package/src/runtime/context/delivery-store.js +100 -0
  278. package/src/runtime/context/emission-rules.js +118 -0
  279. package/src/runtime/context/identity.js +227 -0
  280. package/src/runtime/context/mandatory.js +594 -0
  281. package/src/runtime/context/pointer.js +142 -0
  282. package/src/runtime/context/primary-command.js +117 -0
  283. package/src/runtime/context/primary.js +344 -0
  284. package/src/runtime/context/summary-store.js +655 -0
  285. package/src/runtime/context-pack.js +1106 -245
  286. package/src/runtime/decision-register.js +13 -4
  287. package/src/runtime/dispatch/admission.js +392 -0
  288. package/src/runtime/dispatch/attempt-end.js +274 -0
  289. package/src/runtime/dispatch/command.js +628 -0
  290. package/src/runtime/dispatch/controls.js +904 -0
  291. package/src/runtime/dispatch/envelope.js +276 -0
  292. package/src/runtime/dispatch/grants.js +310 -0
  293. package/src/runtime/dispatch/integrate.js +617 -0
  294. package/src/runtime/dispatch/loop.js +1835 -0
  295. package/src/runtime/dispatch/store.js +352 -0
  296. package/src/runtime/dispatch/verify-run.js +201 -0
  297. package/src/runtime/evidence-fields.js +39 -0
  298. package/src/runtime/evidence-ledger.js +41 -7
  299. package/src/runtime/failure/capture.js +3 -1
  300. package/src/runtime/failure/cli.js +25 -9
  301. package/src/runtime/failure/home.js +57 -30
  302. package/src/runtime/failure/store.js +7 -2
  303. package/src/runtime/feature-pointer.js +174 -0
  304. package/src/runtime/feature-resolve.js +13 -2
  305. package/src/runtime/fleet/command.js +77 -0
  306. package/src/runtime/fleet/projects.js +178 -0
  307. package/src/runtime/fleet/tree.js +383 -0
  308. package/src/runtime/freshness.js +77 -10
  309. package/src/runtime/git-memo.js +46 -0
  310. package/src/runtime/guidance/ablation.js +427 -0
  311. package/src/runtime/health.js +565 -17
  312. package/src/runtime/history-compactor.js +13 -3
  313. package/src/runtime/implementation-gate.js +277 -22
  314. package/src/runtime/inventory/coverage.js +1 -1
  315. package/src/runtime/inventory/index.js +1 -1
  316. package/src/runtime/inventory/precedence.js +2 -2
  317. package/src/runtime/inventory/siblings.js +6 -4
  318. package/src/runtime/knowledge/code-corpus.js +277 -0
  319. package/src/runtime/knowledge/code-lexical.js +130 -0
  320. package/src/runtime/knowledge/code-retrieval.js +307 -0
  321. package/src/runtime/knowledge/code-validation.js +106 -0
  322. package/src/runtime/knowledge/gp3-gate.js +74 -0
  323. package/src/runtime/knowledge/graph-adapter.js +288 -0
  324. package/src/runtime/knowledge/graph-fallback.js +394 -0
  325. package/src/runtime/knowledge/hybrid.js +136 -8
  326. package/src/runtime/knowledge/retrieval.js +34 -1
  327. package/src/runtime/knowledge/rtk-compress.js +77 -0
  328. package/src/runtime/knowledge/semble-bridge.py +106 -0
  329. package/src/runtime/knowledge/semble-lifecycle.js +114 -0
  330. package/src/runtime/knowledge/semble-provider.js +365 -0
  331. package/src/runtime/lifecycle/cli.js +16 -6
  332. package/src/runtime/lifecycle/event-store.js +8 -6
  333. package/src/runtime/lifecycle/followup.js +6 -1
  334. package/src/runtime/lifecycle/overview.js +24 -1
  335. package/src/runtime/lifecycle/report-store.js +8 -10
  336. package/src/runtime/lifecycle/retention.js +95 -31
  337. package/src/runtime/lifecycle/root.js +9 -1
  338. package/src/runtime/lifecycle/status.js +34 -17
  339. package/src/runtime/lifecycle/store-upkeep.js +56 -7
  340. package/src/runtime/mask.js +134 -7
  341. package/src/runtime/mcp-consultations.js +40 -6
  342. package/src/runtime/model-router.js +79 -7
  343. package/src/runtime/models/catalogue.js +191 -0
  344. package/src/runtime/models/command.js +153 -0
  345. package/src/runtime/off-words.js +12 -0
  346. package/src/runtime/outcome.js +3 -3
  347. package/src/runtime/packages/command.js +1009 -0
  348. package/src/runtime/packages/contributions.js +116 -0
  349. package/src/runtime/packages/digest.js +132 -0
  350. package/src/runtime/packages/fetch.js +182 -0
  351. package/src/runtime/packages/grants.js +192 -0
  352. package/src/runtime/packages/index-file.js +134 -0
  353. package/src/runtime/packages/instruction.js +90 -0
  354. package/src/runtime/packages/manifest.js +331 -0
  355. package/src/runtime/packages/points.js +477 -0
  356. package/src/runtime/packages/programs.js +216 -0
  357. package/src/runtime/packages/scaffold.js +277 -0
  358. package/src/runtime/packages/source.js +90 -0
  359. package/src/runtime/packages/state.js +164 -0
  360. package/src/runtime/packages/store.js +449 -0
  361. package/src/runtime/packages/tar.js +106 -0
  362. package/src/runtime/packages/view.js +406 -0
  363. package/src/runtime/plan-check.js +59 -0
  364. package/src/runtime/plan-content.js +324 -0
  365. package/src/runtime/policy-init.js +118 -0
  366. package/src/runtime/profiles/doctor.js +96 -0
  367. package/src/runtime/profiles/resolve.js +244 -0
  368. package/src/runtime/profiles/rollback.js +254 -0
  369. package/src/runtime/profiles/sampler.js +41 -0
  370. package/src/runtime/readiness-record.js +16 -11
  371. package/src/runtime/readiness.js +127 -16
  372. package/src/runtime/research-request.js +50 -25
  373. package/src/runtime/retrieval-plan.js +402 -73
  374. package/src/runtime/retrieval-record.js +293 -0
  375. package/src/runtime/review/command.js +246 -0
  376. package/src/runtime/review/dominance.js +68 -0
  377. package/src/runtime/review/findings.js +434 -0
  378. package/src/runtime/review/identity.js +255 -0
  379. package/src/runtime/review/protocol-command.js +109 -0
  380. package/src/runtime/review/protocols.js +405 -0
  381. package/src/runtime/review/resolve-protocol.js +302 -0
  382. package/src/runtime/review/start.js +530 -0
  383. package/src/runtime/roadmap/facts.js +479 -0
  384. package/src/runtime/roadmap/gates.js +270 -0
  385. package/src/runtime/roadmap/loop.js +1636 -0
  386. package/src/runtime/roadmap/merge.js +381 -0
  387. package/src/runtime/roadmap/rows.js +1113 -0
  388. package/src/runtime/roadmap/seed.js +951 -0
  389. package/src/runtime/rules-report.js +336 -0
  390. package/src/runtime/scaffold/artifacts.js +63 -9
  391. package/src/runtime/scaffold/generate.js +21 -9
  392. package/src/runtime/settings/autopilot.js +106 -0
  393. package/src/runtime/settings/cli.js +280 -0
  394. package/src/runtime/settings/config-write.js +314 -0
  395. package/src/runtime/settings/files.js +271 -0
  396. package/src/runtime/settings/legacy.js +154 -0
  397. package/src/runtime/settings/lexical.js +56 -0
  398. package/src/runtime/settings/prepared-run.js +269 -0
  399. package/src/runtime/settings/project-layers.js +118 -0
  400. package/src/runtime/settings/refs.js +288 -0
  401. package/src/runtime/settings/registry.js +553 -0
  402. package/src/runtime/settings/resolve.js +713 -0
  403. package/src/runtime/settings/shared.js +37 -0
  404. package/src/runtime/settings/worker-billing.js +107 -0
  405. package/src/runtime/settings/write.js +269 -0
  406. package/src/runtime/slot-command.js +78 -0
  407. package/src/runtime/slots.js +671 -0
  408. package/src/runtime/source-version.js +290 -0
  409. package/src/runtime/state-root.js +217 -0
  410. package/src/runtime/storage/cli.js +12 -5
  411. package/src/runtime/storage/engine.js +9 -2
  412. package/src/runtime/storage/registry.js +14 -1
  413. package/src/runtime/storage/roots.js +66 -16
  414. package/src/runtime/storage/rules.js +82 -10
  415. package/src/runtime/storage/upkeep.js +10 -2
  416. package/src/runtime/storage/work-status.js +245 -26
  417. package/src/runtime/task-attempts.js +589 -69
  418. package/src/runtime/task-classifier.js +47 -11
  419. package/src/runtime/task-graph.js +373 -0
  420. package/src/runtime/task-run.js +778 -0
  421. package/src/runtime/task-scope.js +202 -56
  422. package/src/runtime/task-sources.js +147 -0
  423. package/src/runtime/task-state.js +440 -65
  424. package/src/runtime/team/admission.js +301 -0
  425. package/src/runtime/team/envelope.js +324 -0
  426. package/src/runtime/team/review-guard.js +156 -0
  427. package/src/runtime/trace/analyze.js +427 -0
  428. package/src/runtime/trace/baseline.js +770 -0
  429. package/src/runtime/trace/ledger.js +63 -140
  430. package/src/runtime/trace/otlp-export.js +568 -0
  431. package/src/runtime/trace/otlp-map.js +481 -0
  432. package/src/runtime/trace/spans.js +745 -0
  433. package/src/runtime/trace/telemetry.js +225 -0
  434. package/src/runtime/trace/views.js +1 -0
  435. package/src/runtime/transcripts/command.js +98 -0
  436. package/src/runtime/transcripts/cursor.js +326 -0
  437. package/src/runtime/transcripts/read.js +126 -0
  438. package/src/runtime/transcripts/readers/claude-jsonl.js +74 -0
  439. package/src/runtime/transcripts/readers/codex-rollout.js +70 -0
  440. package/src/runtime/transcripts/readers/copilot-events.js +90 -0
  441. package/src/runtime/transcripts/readers/kiro-jsonl.js +77 -0
  442. package/src/runtime/transcripts/readers/pi-jsonl.js +59 -0
  443. package/src/runtime/transcripts/registry.js +161 -0
  444. package/src/runtime/transcripts/stats.js +161 -0
  445. package/src/runtime/verification/check-key.js +628 -0
  446. package/src/runtime/verification/contract-runner.js +546 -11
  447. package/src/runtime/verification/engine.js +499 -9
  448. package/src/runtime/verification/impact.js +617 -0
  449. package/src/runtime/verification/inflight.js +205 -0
  450. package/src/runtime/verification/registry.js +176 -0
  451. package/src/runtime/verification/reuse.js +184 -0
  452. package/src/runtime/verification/telemetry-emit.js +254 -0
  453. package/src/runtime/verification/test-selection.js +67 -7
  454. package/src/runtime/worker/active.js +90 -0
  455. package/src/runtime/worker/cancel.js +222 -0
  456. package/src/runtime/worker/catalogue.js +122 -0
  457. package/src/runtime/worker/command.js +677 -0
  458. package/src/runtime/worker/evidence.js +288 -0
  459. package/src/runtime/worker/launch.js +288 -0
  460. package/src/runtime/worker/models.js +137 -0
  461. package/src/runtime/worker/parsers.js +218 -0
  462. package/src/runtime/worker/probes/index.js +706 -0
  463. package/src/runtime/worker/process-tree.js +415 -0
  464. package/src/runtime/worker/quota.js +43 -0
  465. package/src/runtime/worker/readiness.js +713 -0
  466. package/src/runtime/worker/run-store.js +166 -0
  467. package/src/runtime/worker/supervisor.js +541 -0
  468. package/src/runtime/worker/worktree-check.js +252 -0
  469. package/src/runtime/workflow-engine.js +5 -1
  470. package/src/runtime/workflow-orchestrator.js +578 -63
  471. package/src/runtime/worktree.js +149 -22
  472. package/src/state/index.js +1 -1
  473. package/src/state/lockfile.js +64 -10
  474. package/src/state/run-lock.js +1 -1
  475. package/types/node-commonjs.d.ts +106 -7
  476. package/core/.antigravity-plugin/hooks.json +0 -39
  477. package/core/harnesses/gemini/hooks/hooks.json +0 -77
  478. package/core/harnesses/gemini/hooks/lib.sh +0 -302
  479. package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +0 -21
  480. package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
  481. package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -21
  482. package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
  483. package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -22
  484. package/core/harnesses/gemini/hooks/session-end.impl.sh +0 -89
  485. package/core/harnesses/gemini/hooks/session-end.sh +0 -5
  486. package/core/harnesses/gemini/hooks/session-start.sh +0 -5
  487. package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
  488. package/core/harnesses/shared/hooks/adapters/arg-fields.js +0 -19
  489. package/core/harnesses/shared/hooks/adapters/gemini.js +0 -114
  490. package/core/harnesses/shared/hooks/hooks.json +0 -47
  491. package/core/shared/guidance/FLAGS.md +0 -25
  492. package/core/shared/guidance/rules/RULE_01_SAFETY.md +0 -25
  493. package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +0 -23
  494. package/core/shared/guidance/rules/RULE_03_QUALITY.md +0 -14
  495. package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +0 -46
  496. package/src/adapters/gemini/hooks.js +0 -210
  497. package/src/adapters/gemini/index.js +0 -390
@@ -0,0 +1,314 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ /**
6
+ * `doflow config schema|validate|set|unset` (119 IC-007, IC-008, IC-009; D-2). One read, validate and
7
+ * write path over the settings registry, the resolver and the atomic `writeLayer`. A value is never
8
+ * judged by a copy of the fold's rules: the layer document as it would be after the write is served to
9
+ * `loadSettings` in place of the file, and the key's candidate for that layer is read back, so scope
10
+ * (rule 1), the value rules and narrowing (rule 3) are the resolver's own verdicts.
11
+ *
12
+ * Checks run in this order and the first failing one is reported per key: locked (`invariant.*`),
13
+ * unknown-key, security (a `securityRelevant` key is written by a person in the file, never by this
14
+ * verb), then at a layer too-large (the file as `writeLayer` would write it is over the reader's
15
+ * limit), scope, the value rules (type, enum, range, items, length), not-narrower. Nothing is written,
16
+ * and no directory is created, until every check passes.
17
+ */
18
+
19
+ const path = require('node:path');
20
+ const nodeFs = require('node:fs');
21
+ const { configHome } = require('../config-home.js');
22
+ const { finishRuntime, usageError } = require('../cli-result.js');
23
+ const { validateValue, readLayerFile } = require('./files.js');
24
+ const { loadSettings, clearSettingsCache } = require('./resolve.js');
25
+ const { projectLayerSource } = require('./project-layers.js');
26
+ const { writeLayer, layerDocument, serializeLayer, oversize } = require('./write.js');
27
+ const { REF_META } = require('./refs.js');
28
+ const { maskUrl } = require('./shared.js');
29
+ const { loadProtocols, presetIds } = require('../review/protocols.js');
30
+ const { LADDER_MAX, classifyWorkerId, retiredAdvisory } = require('../../registry/worker-ids.js');
31
+
32
+ const LAYERS = ['user', 'project', 'local'];
33
+ const LAYER_FILE = { user: 'settings.json', project: 'settings.json', local: 'settings.local.json' };
34
+ /** A write that finds the layer changed since it was checked is checked again, this many times in all. */
35
+ const WRITE_ATTEMPTS = 3;
36
+ /** `writeLayer` refusals reported under their own code; any other write fault is `write-failed`. */
37
+ const WRITER_CODES = new Set(['locked', 'changed', 'too-large']);
38
+ /** The codes whose entry carries the key's `allowed` values (IC-009). */
39
+ const VALUE_CODES = new Set(['type', 'enum', 'range', 'items', 'length', 'url', 'reserved']);
40
+
41
+
42
+
43
+
44
+ function isObject(value ) {
45
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
46
+ }
47
+
48
+ function allowedOf(registry , key ) {
49
+ const spec = registry.keys.get(key);
50
+ if (!spec) return null;
51
+ if (spec.type === 'enum' || spec.type === 'worker-list' || spec.type === 'worker-models' || (spec.type === 'string-list' && spec.valuesRef)) return registry.enumValues(key);
52
+ if (spec.type === 'integer') return { min: spec.min ?? null, max: spec.max ?? null };
53
+ return null;
54
+ }
55
+
56
+ function entryOf(registry , key , layer , code , message ) {
57
+ return { key, layer, ok: false, code, message, allowed: VALUE_CODES.has(code) ? allowedOf(registry, key) : null };
58
+ }
59
+
60
+ function parseValue(text ) {
61
+ try { return { ok: true, value: JSON.parse(text) }; } catch { return { ok: false, value: null }; }
62
+ }
63
+
64
+ const NOT_JSON = 'the value is not JSON; quote a string, for example \'"on"\'';
65
+
66
+ /** The checks that need only the key: locked, unknown-key and, for a write, security. */
67
+ function keyEntry(registry , key , layer , writes ) {
68
+ if (key.startsWith('invariant.')) {
69
+ return entryOf(registry, key, layer, 'locked', `invariant ${key.slice('invariant.'.length)} is locked in core/registry/protocols.json and is never a setting`);
70
+ }
71
+ const spec = registry.keys.get(key);
72
+ if (!spec) return entryOf(registry, key, layer, 'unknown-key', `${key} is not a registered setting`);
73
+ if (writes && spec.securityRelevant) {
74
+ return entryOf(registry, key, layer, 'security', `${key} is security-relevant: a person writes it in the settings file; config set and unset never do`);
75
+ }
76
+ return null;
77
+ }
78
+
79
+ /** `fsImpl` with one file's bytes replaced; everything else is read from `base`. */
80
+ function servedFs(file , bytes , base ) {
81
+ const asRead = (p , options ) => {
82
+ const encoding = typeof options === 'string' ? options : options?.encoding;
83
+ return encoding ? bytes.toString(encoding) : bytes;
84
+ };
85
+ return {
86
+ statSync: (p ) => (p === file ? { isFile: () => true, size: bytes.length } : base.statSync(p)),
87
+ lstatSync: (p ) => (p === file ? { isFile: () => true, isDirectory: () => false, isSymbolicLink: () => false, size: bytes.length } : base.lstatSync(p)),
88
+ readFileSync: (p , options ) => (p === file ? asRead(p, options) : base.readFileSync(p, options)),
89
+ accessSync: (p , mode ) => (p === file ? undefined : base.accessSync(p, mode)),
90
+ realpathSync: (p ) => base.realpathSync(p),
91
+ };
92
+ }
93
+
94
+
95
+ /** The layer file for a layer, or the usage message when this machine or folder has none. */
96
+ function layerFile(layer , env , projectRoot , fsImpl ) {
97
+ if (layer === 'user') {
98
+ const home = configHome(env);
99
+ if (!home.dir) return { error: 'no config home resolves; set an absolute HOME or XDG_CONFIG_HOME' };
100
+ return { file: path.join(home.dir, LAYER_FILE.user) };
101
+ }
102
+ const source = projectLayerSource(projectRoot, fsImpl);
103
+ if (!source.layersFrom) return { error: `${projectRoot} has no usable project settings folder (.doflow)` };
104
+ return { file: path.join(source.layersFrom, LAYER_FILE[layer]) };
105
+ }
106
+
107
+ /**
108
+ * The layer-dependent verdict for `value` at `layer` in `file`: the existing document plus the value,
109
+ * served to the resolver as that file, and the key's candidate read back. Null when the write is fine.
110
+ * `read` is the caller's own `readLayerFile` result, so a write can require the file it then replaces
111
+ * to be the one judged here. For `review.rounds` the effective `review.preset` of the same view is read too:
112
+ * a value below that preset's `rounds.min` is refused; under `auto` it is accepted and, when below the
113
+ * strictest preset's `rounds.min`, an advisory is pushed to `notes` (the caller's array). `siblings` are other
114
+ * keys of the same change (a prepared run's settings), served in the document so keys are judged together.
115
+ */
116
+ function layerEntry(registry , key , layer , file , parsed ,
117
+ { env, projectRoot, fsImpl, repoRoot, notes, siblings, read = readLayerFile(file, { registry, fsImpl, includeDocument: true }) } ) {
118
+ if (read.status === 'invalid') {
119
+ return entryOf(registry, key, layer, 'layer-invalid', `${file} ${read.reason}; fix or remove it first`);
120
+ }
121
+ const values = read.status === 'ok' ? { ...read.document, ...siblings } : { ...siblings };
122
+ values[key] = parsed.ok ? parsed.value : null;
123
+ const bytes = serializeLayer(layerDocument(registry, values));
124
+ const tooLarge = oversize(file, bytes);
125
+ if (tooLarge) return entryOf(registry, key, layer, 'too-large', tooLarge.detail);
126
+ const view = loadSettings({ projectRoot, env, fsImpl: servedFs(file, bytes, fsImpl), registry });
127
+ const candidate = view.get(key).candidates.find((c ) => c.layer === layer && c.source === file && !c.legacy);
128
+ if (!candidate) throw new Error(`the resolver did not read ${file}`);
129
+ if (candidate.status === 'ignored-scope') return entryOf(registry, key, layer, 'scope', `${key} cannot be set at the ${layer} layer (${candidate.reason})`);
130
+ if (!parsed.ok) return entryOf(registry, key, layer, 'type', NOT_JSON);
131
+ if (candidate.status === 'invalid') return entryOf(registry, key, layer, candidate.code, candidate.reason);
132
+ if (candidate.code === 'retired' && notes && registry.keys.get(key).type === 'enum') notes.push(`note: ${candidate.reason}`);
133
+ // IC-002, IC-004: valid worker lists and model maps keep dangling ids advisory.
134
+ const type = registry.keys.get(key).type;
135
+ if ((type === 'worker-list' || type === 'worker-models') && notes) {
136
+ const known = registry.enumValues(key) ?? [];
137
+ const retired = registry.retired?.() ?? [];
138
+ const ids = type === 'worker-models' ? Object.keys(parsed.value ) : parsed.value ;
139
+ for (const id of ids) {
140
+ if (id === 'primary') continue;
141
+ const kind = classifyWorkerId(id, known, retired);
142
+ if (kind === 'retired') notes.push(`note: ${retiredAdvisory(key, retired.find((entry ) => entry.id === id))}`);
143
+ else if (kind === 'dangling') notes.push(`note: ${key}: worker '${id}' is not installed (ignored)`);
144
+ }
145
+ if (type === 'worker-list' && candidate.code === 'order-ignored') notes.push(`note: ${key}: the order is ignored at the ${layer} layer; the order below applies`);
146
+ }
147
+ if (candidate.status === 'not-narrower') return entryOf(registry, key, layer, 'not-narrower', `${candidate.reason} (the value is wider than the layers below)`);
148
+ if (key === 'review.rounds' && Number.isInteger(parsed.value)) return roundsFloor(registry, view, layer, parsed.value , repoRoot, notes);
149
+ return null;
150
+ }
151
+
152
+ /** The `rounds.min` check of `review.rounds` against the preset the view resolves (see layerEntry). */
153
+ function roundsFloor(registry , view , layer , rounds , repoRoot , notes ) {
154
+ const protocols = loadProtocols({ repoRoot });
155
+ if (!protocols.ok) throw new Error(protocols.errors.join('; '));
156
+ const preset = view.get('review.preset').value;
157
+ const presets = protocols.registry.presets;
158
+ if (typeof preset === 'string' && preset !== 'auto' && Object.hasOwn(presets, preset)) {
159
+ const floor = presets[preset].rounds.min;
160
+ return rounds < floor ? entryOf(registry, 'review.rounds', layer, 'not-narrower', `review.rounds ${rounds} is below ${preset} rounds.min ${floor}`) : null;
161
+ }
162
+ const strictest = presetIds(protocols.registry, 'review')[0];
163
+ const floor = presets[strictest]?.rounds.min;
164
+ if (preset === 'auto' && notes && floor !== undefined && rounds < floor) {
165
+ notes.push(`note: review.rounds ${rounds} is below ${strictest} rounds.min ${floor}; the resolver ignores it when the risk rule picks ${strictest}`);
166
+ }
167
+ return null;
168
+ }
169
+
170
+ function refuse(action , errors , json ) {
171
+ if (json) console.log(JSON.stringify({ ok: false, errors }, null, 2));
172
+ else for (const error of errors) console.error(`config ${action}: ${error.key}: ${error.code}: ${error.message}`);
173
+ return finishRuntime(1);
174
+ }
175
+
176
+ function done(json , payload , text ) {
177
+ if (json) console.log(JSON.stringify(payload, null, 2));
178
+ else for (const line of text) console.log(line);
179
+ return finishRuntime(0);
180
+ }
181
+
182
+ // ── schema ────────────────────────────────────────────────────────────────────────────────────────
183
+
184
+ function defaultOf(registry , key ) {
185
+ const spec = registry.keys.get(key);
186
+ if (Object.hasOwn(spec, 'default')) return spec.default;
187
+ const deps = {};
188
+ for (const dep of REF_META[spec.defaultRef]?.dependsOn ?? []) deps[dep] = defaultOf(registry, dep);
189
+ return registry.resolveRef(spec.defaultRef, deps);
190
+ }
191
+
192
+ function presetSummary(preset ) {
193
+ const seats = preset.seats.length;
194
+ const rounds = preset.rounds.default;
195
+ return `${seats} seat${seats === 1 ? '' : 's'}, ${rounds} round${rounds === 1 ? '' : 's'}, synthesizer ${preset.synthesizer}, decider ${preset.decider}`;
196
+ }
197
+
198
+ function buildSchema(registry , protocols ) {
199
+ const keys = [...registry.keys].map(([key, spec] ) => {
200
+ const fallback = defaultOf(registry, key);
201
+ return {
202
+ key, type: spec.type, values: registry.enumValues(key), order: spec.order ?? null, tiers: spec.tiers ?? null,
203
+ min: spec.min ?? null, max: spec.max ?? null, maxItems: spec.type === 'worker-list' ? LADDER_MAX + 1 : spec.maxItems ?? null, maxLength: spec.maxLength ?? null,
204
+ default: spec.type === 'url' && typeof fallback === 'string' ? maskUrl(fallback) : fallback,
205
+ scope: spec.scope, combine: spec.combine, description: spec.description,
206
+ securityRelevant: spec.securityRelevant, writable: !spec.securityRelevant, locked: false,
207
+ };
208
+ });
209
+ const invariants = protocols.invariants.map((inv ) => ({
210
+ id: inv.id, key: `invariant.${inv.id}`, locked: true, writable: false, statement: inv.statement, enforcedBy: inv.enforcedBy,
211
+ ...(inv.authorKinds ? { authorKinds: inv.authorKinds } : {}),
212
+ }));
213
+ const presets = ['review', 'debate'].flatMap((kind) => presetIds(protocols, kind)).map((id ) => {
214
+ const preset = protocols.presets[id];
215
+ return {
216
+ id, kind: preset.kind, ...(preset.kind === 'review' ? { depth: preset.depth } : {}),
217
+ seats: preset.seats.length, rounds: preset.rounds.default, summary: presetSummary(preset),
218
+ };
219
+ });
220
+ return { schema: 1, keys, invariants, presets };
221
+ }
222
+
223
+ function handleSchema({ json, registry, repoRoot, fsImpl } ) {
224
+ let schema ;
225
+ try {
226
+ const protocols = loadProtocols({ repoRoot, fsImpl });
227
+ if (!protocols.ok) throw new Error(protocols.errors.join('; '));
228
+ schema = buildSchema(registry, protocols.registry);
229
+ } catch (error ) {
230
+ const detail = error?.message || String(error);
231
+ if (json) console.log(JSON.stringify({ ok: false, status: 'ERROR', exitCode: 2, error: 'settings-registry-invalid', detail }, null, 2));
232
+ else console.error(`doflow config: settings schema unavailable: ${detail}`);
233
+ return finishRuntime(2);
234
+ }
235
+ const text = [
236
+ ...schema.keys.map((k ) => `${k.key} ${k.type} default=${JSON.stringify(k.default)} scope=${k.scope} writable=${k.writable}`),
237
+ ...schema.invariants.map((i ) => `${i.key} locked: ${i.statement}`),
238
+ ...schema.presets.map((p ) => `preset ${p.id} ${p.kind} ${p.summary}`),
239
+ ];
240
+ return done(json, schema, text);
241
+ }
242
+
243
+ // ── validate, set, unset ──────────────────────────────────────────────────────────────────────────
244
+
245
+ function handleChange({ action, key, valueText, layer, json, registry, repoRoot, projectRoot, env, fsImpl } ) {
246
+ const writes = action !== 'validate';
247
+ const root = path.resolve(projectRoot);
248
+ const keyFault = keyEntry(registry, key, layer ?? null,
249
+ writes || (layer !== undefined && !['worker-list', 'worker-models'].includes(registry.keys.get(key)?.type)));
250
+ if (keyFault) return refuse(action, [keyFault], json);
251
+
252
+ const parsed = action === 'unset' ? { ok: true, value: null } : parseValue(valueText);
253
+ const spec = registry.keys.get(key);
254
+ if (layer === undefined) {
255
+ if (!parsed.ok) return refuse(action, [entryOf(registry, key, null, 'type', NOT_JSON)], json);
256
+ const fault = validateValue(spec, parsed.value, registry.enumValues(key));
257
+ const retired = fault?.code === 'enum' && spec.valuesRef === 'harnesses.primaryIds'
258
+ ? (registry.retired?.() ?? []).find((entry ) => entry.id === parsed.value) : null;
259
+ if (retired) {
260
+ const note = `note: ${retiredAdvisory(key, retired)}`;
261
+ return done(json, { ok: true, errors: [], notes: [note] }, [`${key}: valid`, note]);
262
+ }
263
+ if (fault) return refuse(action, [entryOf(registry, key, null, fault.code, fault.reason)], json);
264
+ return done(json, { ok: true, errors: [] }, [`${key}: valid`]);
265
+ }
266
+
267
+ const where = layerFile(layer, env, root, fsImpl);
268
+ if ('error' in where) return usageError('config', where.error, json);
269
+ const { file } = where;
270
+
271
+ // The layer is read, judged and written as one unit: `expect` makes writeLayer refuse (code `changed`)
272
+ // when another writer replaced the file in between, and the unit then runs again on the new file.
273
+ // `backup` keeps the replaced bytes in `<file>.config.bak` (IC-008 "atomic, backup"; one file, replaced).
274
+ for (let attempt = 1; ; attempt++) {
275
+ const read = readLayerFile(file, { registry, fsImpl, includeDocument: true });
276
+ const expect = read.status === 'ok' ? read.document : null;
277
+ const notes = [];
278
+ let written ;
279
+ if (action === 'unset') {
280
+ if (read.status === 'invalid') return refuse(action, [entryOf(registry, key, layer, 'layer-invalid', `${file} ${read.reason}; fix or remove it first`)], json);
281
+ if (read.status === 'absent' || !isObject(read.document) || !Object.hasOwn(read.document, key)) {
282
+ return done(json, { ok: true, key, layer, file, changed: false, errors: [] }, [`${key} is not set (${layer}: ${file})`]);
283
+ }
284
+ written = writeLayer({ file, unset: [key], expect, backup: true, fsImpl });
285
+ } else {
286
+ const fault = layerEntry(registry, key, layer, file, parsed, { env, projectRoot: root, fsImpl, repoRoot, notes, read });
287
+ if (fault) return refuse(action, [fault], json);
288
+ if (action === 'validate') return done(json, { ok: true, errors: [], ...(notes.length ? { notes } : {}) }, [`${key}: valid (${layer}: ${file})`, ...notes]);
289
+ written = writeLayer({ file, set: { [key]: parsed.value }, expect, backup: true, fsImpl });
290
+ }
291
+ if (!written.ok && written.code === 'changed' && attempt < WRITE_ATTEMPTS) continue;
292
+ if (!written.ok) {
293
+ const code = WRITER_CODES.has(written.code) ? written.code : 'write-failed';
294
+ return refuse(action, [entryOf(registry, key, layer, code, written.detail)], json);
295
+ }
296
+ clearSettingsCache();
297
+ if (action === 'unset') return done(json, { ok: true, key, layer, file, changed: true, errors: [] }, [`${key} unset (${layer}: ${file})`]);
298
+ const resolution = loadSettings({ projectRoot: root, env, fsImpl, registry }).get(key);
299
+ const origin = { ...resolution.origin };
300
+ const shown = JSON.stringify(resolution.value);
301
+ const text = [`${key} = ${shown} (${origin.layer}: ${origin.source})`];
302
+ if (origin.source !== file) text.push(`written: ${layer}: ${file}`);
303
+ text.push(...notes);
304
+ return done(json, { ok: true, key, layer, file, value: resolution.value, origin, errors: [], ...(notes.length ? { notes } : {}) }, text);
305
+ }
306
+ }
307
+
308
+ function handleConfigAction(options ) {
309
+ const { action, fsImpl = nodeFs } = options;
310
+ if (action === 'schema') return handleSchema({ ...options, fsImpl });
311
+ return handleChange({ ...options, fsImpl });
312
+ }
313
+
314
+ module.exports = { handleConfigAction, buildSchema, keyEntry, layerFile, layerEntry, LAYERS };
@@ -0,0 +1,271 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ /**
6
+ * Reading one settings layer file (`settings.json` / `settings.local.json`; 086 IC-002, data-model §2)
7
+ * and the value encodings that file uses. A leaf module: it takes the registry as an argument and
8
+ * imports nothing from it.
9
+ *
10
+ * Nothing here ever returns file text. `JSON.parse` quotes the first bytes of the input in its
11
+ * "Unexpected token" messages (Node 24 and 26 both do), so a parse failure is reported as a code and a
12
+ * byte offset only, and `error.message` is never forwarded. `jsonFailure` is the shared helper for any
13
+ * other reader that parses a user's JSON (the legacy decision-model reads move onto it in C.4).
14
+ */
15
+
16
+ const nodeFs = require('node:fs');
17
+ const { workerIdFault, LADDER_MAX } = require('../../registry/worker-ids.js');
18
+
19
+ /** A file larger than this is a layer fault, not a read (data-model §2). */
20
+ const MAX_BYTES = 65536;
21
+ const BOM = '\uFEFF';
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+ function isObject(value ) {
41
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
42
+ }
43
+
44
+ /**
45
+ * The error text of a failed `JSON.parse` reduced to a byte offset into `text`. V8 states a position as
46
+ * `... in JSON at position N` at the END of the message; the "Unexpected token" form quotes input, so
47
+ * only the anchored tail is trusted. `Unexpected end of JSON input` means the end of the text.
48
+ * @param text the string that was parsed (after any BOM was removed)
49
+ * @param bomBytes bytes removed before `text`, added to the result so the offset is a file offset
50
+ */
51
+ function jsonFailure(error , text , bomBytes = 0) {
52
+ const message = String(error?.message ?? '');
53
+ let index = null;
54
+ if (message === 'Unexpected end of JSON input') index = text.length;
55
+ else {
56
+ const m = / in JSON at position (\d+)(?: \(line \d+ column \d+\))?$/.exec(message);
57
+ if (m) index = Math.min(Number(m[1]), text.length);
58
+ }
59
+ if (index === null) return { offset: null, reason: 'not valid JSON' };
60
+ const offset = Buffer.byteLength(text.slice(0, index), 'utf8') + bomBytes;
61
+ return { offset, reason: `not valid JSON at offset ${offset}` };
62
+ }
63
+
64
+ /** IC-002 `model-id`: 1 to 120 characters, a leading letter or digit (so never `-`), no trailing space. */
65
+ const MODEL_ID_RE = /^[A-Za-z0-9][A-Za-z0-9 ._:/@()[\]#+-]*$/;
66
+ const MODEL_ID_MAX = 120;
67
+ /** The two billing values a person may record (the registry's `unknown` is not a claim). */
68
+ const RECORDED_BILLING = ['free', 'metered'];
69
+ const BILLING_ENTRY_FIELDS = ['billing', 'asserted', 'basis'];
70
+ const BILLING_MAX_ENTRIES = 16;
71
+ const BILLING_BASIS_MAX = 120;
72
+
73
+ /** `YYYY-MM-DD` naming a real calendar date. */
74
+ function isCalendarDate(value ) {
75
+ if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return false;
76
+ const [year, month, day] = value.split('-').map(Number);
77
+ const at = new Date(Date.UTC(year, month - 1, day));
78
+ return at.getUTCFullYear() === year && at.getUTCMonth() === month - 1 && at.getUTCDate() === day;
79
+ }
80
+
81
+ /**
82
+ * The `workers.billing` record: an object of 1..16 entries keyed by worker id, each exactly
83
+ * `{billing, asserted[, basis]}`. The first fault wins and invalidates the whole key; a reason
84
+ * names the entry (when its id is well formed) and the field, never the value.
85
+ */
86
+ function validateWorkerBilling(value ) {
87
+ if (!isObject(value)) return { code: 'type', reason: 'must be an object keyed by worker id' };
88
+ const ids = Object.keys(value);
89
+ if (ids.length < 1 || ids.length > BILLING_MAX_ENTRIES) return { code: 'range', reason: `must hold 1 to ${BILLING_MAX_ENTRIES} worker entries` };
90
+ for (const id of ids) {
91
+ const fault = workerIdFault(id);
92
+ if (fault) return fault.code === 'reserved' ? fault : { code: 'type', reason: 'has an entry key that is not a worker id (lowercase built-in or qualified id)' };
93
+ const entry = value[id];
94
+ const at = `entry '${id}'`;
95
+ if (!isObject(entry)) return { code: 'type', reason: `${at} must be an object` };
96
+ if (Object.keys(entry).some((field) => !BILLING_ENTRY_FIELDS.includes(field))) return { code: 'type', reason: `${at} has a field other than billing, asserted and basis` };
97
+ if (typeof entry.billing !== 'string') return { code: 'type', reason: `${at} field 'billing' must be a string` };
98
+ if (!RECORDED_BILLING.includes(entry.billing)) return { code: 'enum', reason: `${at} field 'billing' must be one of: ${RECORDED_BILLING.join(', ')}` };
99
+ if (!isCalendarDate(entry.asserted)) return { code: 'range', reason: `${at} field 'asserted' must be a real YYYY-MM-DD date` };
100
+ if (Object.hasOwn(entry, 'basis')) {
101
+ const basis = entry.basis;
102
+ if (typeof basis !== 'string') return { code: 'type', reason: `${at} field 'basis' must be a string` };
103
+ if (/[\r\n\v\f\u0085\u2028\u2029]/.test(basis) || basis.length < 1 || basis.length > BILLING_BASIS_MAX) return { code: 'range', reason: `${at} field 'basis' must be one line of 1 to ${BILLING_BASIS_MAX} characters` };
104
+ }
105
+ }
106
+ return null;
107
+ }
108
+
109
+ /**
110
+ * Whether a JSON value is acceptable for a key (data-model §2 encodings). The reason never echoes the
111
+ * value. `enumValues` is the allowed list for an enum key, or for a string-list key with a `valuesRef`.
112
+ */
113
+ function validateValue(spec , value , enumValues = null) {
114
+ switch (spec.type) {
115
+ case 'boolean':
116
+ return typeof value === 'boolean' ? null : { code: 'type', reason: 'must be true or false' };
117
+ case 'enum':
118
+ if (typeof value !== 'string') return { code: 'type', reason: 'must be a string' };
119
+ if (!enumValues) return { code: 'enum', reason: 'has no allowed values available' };
120
+ return enumValues.includes(value) ? null : { code: 'enum', reason: `must be one of: ${enumValues.join(', ')}` };
121
+ case 'integer':
122
+ if (typeof value !== 'number' || !Number.isSafeInteger(value)) return { code: 'type', reason: 'must be a whole number' };
123
+ if (value < (spec.min ?? Number.MIN_SAFE_INTEGER)) return { code: 'range', reason: `must be at least ${spec.min}` };
124
+ return value > (spec.max ?? Number.MAX_SAFE_INTEGER) ? { code: 'range', reason: `must be at most ${spec.max}` } : null;
125
+ case 'string-list': {
126
+ if (!Array.isArray(value) || value.some((item) => typeof item !== 'string' || !item.trim())) return { code: 'type', reason: 'must be a list of non-empty strings' };
127
+ if (value.length > spec.maxItems) return { code: 'items', reason: `must hold at most ${spec.maxItems} items` };
128
+ if (new Set(value).size !== value.length) return { code: 'items', reason: 'must not repeat an item' };
129
+ if (value.some((item) => item.length > spec.maxLength)) return { code: 'length', reason: `each item must be at most ${spec.maxLength} characters` };
130
+ if (!spec.valuesRef || value.length === 0) return null;
131
+ if (!enumValues) return { code: 'enum', reason: 'has no allowed values available' };
132
+ return value.every((item) => enumValues.includes(item)) ? null : { code: 'enum', reason: `each item must be one of: ${enumValues.join(', ')}` };
133
+ }
134
+ case 'string':
135
+ return typeof value === 'string' && value.trim() ? null : { code: 'type', reason: 'must be a non-empty string' };
136
+ case 'model-id':
137
+ if (typeof value !== 'string' || value === '') return { code: 'type', reason: 'must be a model name string' };
138
+ if (value.length > MODEL_ID_MAX) return { code: 'length', reason: `must be at most ${MODEL_ID_MAX} characters` };
139
+ return MODEL_ID_RE.test(value) && !value.endsWith(' ') ? null : { code: 'type', reason: 'must start with a letter or digit, use only letters, digits, spaces and . _ : / @ ( ) [ ] # + -, and not end with a space' };
140
+ case 'model-list': {
141
+ if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {
142
+ return { code: 'type', reason: 'must be a list of model name strings' };
143
+ }
144
+ if (value.length > 64) {
145
+ return { code: 'items', reason: 'must hold at most 64 items' };
146
+ }
147
+ if (new Set(value).size !== value.length) {
148
+ return { code: 'items', reason: 'must not repeat an item' };
149
+ }
150
+ for (const item of value) {
151
+ if (item === '' || item.length > MODEL_ID_MAX || !MODEL_ID_RE.test(item) || item.endsWith(' ')) {
152
+ return { code: 'type', reason: 'each item must start with a letter or digit, use only letters, digits, spaces and . _ : / @ ( ) [ ] # + -, and not end with a space' };
153
+ }
154
+ }
155
+ return null;
156
+ }
157
+ case 'url':
158
+ if (typeof value !== 'string' || !value.trim()) return { code: 'type', reason: 'must be a URL string' };
159
+ try { new URL(value); return null; } catch { return { code: 'url', reason: 'must be a URL string' }; }
160
+ // IC-002: membership is advisory; only shape, count and the final primary token constrain validity.
161
+ case 'worker-list': {
162
+ if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) return { code: 'type', reason: 'must be a list of strings' };
163
+ if (value.length < 1 || value.length > LADDER_MAX + 1 || value.filter((item) => item !== 'primary').length > LADDER_MAX) {
164
+ return { code: 'items', reason: `must hold 1 to ${LADDER_MAX} worker ids plus an optional final primary` };
165
+ }
166
+ if (new Set(value).size !== value.length) return { code: 'items', reason: 'must not repeat an item' };
167
+ if (value.includes('primary') && value.at(-1) !== 'primary') return { code: 'items', reason: 'primary must be the last item' };
168
+ for (const item of value) {
169
+ if (item === 'primary') continue;
170
+ const fault = workerIdFault(item);
171
+ if (fault) return fault;
172
+ }
173
+ return null;
174
+ }
175
+ // IC-002: the first shape fault invalidates the entire worker model map.
176
+ case 'worker-models': {
177
+ if (!isObject(value) || (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)) {
178
+ return { code: 'type', reason: 'must be an object keyed by worker id' };
179
+ }
180
+ const ids = Object.keys(value);
181
+ if (ids.length < 1 || ids.length > 16) return { code: 'items', reason: 'must hold 1 to 16 worker entries' };
182
+ for (const id of ids) {
183
+ const fault = workerIdFault(id);
184
+ if (fault) return fault.code === 'reserved' ? fault : { code: 'type', reason: 'has an entry key that is not a worker id (lowercase built-in or qualified id)' };
185
+ const entry = value[id];
186
+ if (!isObject(entry) || (Object.getPrototypeOf(entry) !== Object.prototype && Object.getPrototypeOf(entry) !== null)) {
187
+ return { code: 'type', reason: 'each worker entry must be an object keyed by tier' };
188
+ }
189
+ const tiers = Object.keys(entry);
190
+ if (tiers.length < 1 || tiers.length > 3) return { code: 'items', reason: 'each worker entry must hold 1 to 3 tiers' };
191
+ for (const tier of tiers) {
192
+ if (!Array.isArray(spec.tiers) || spec.tiers.length === 0) return { code: 'enum', reason: 'has no allowed tiers available' };
193
+ if (!spec.tiers.includes(tier)) return { code: 'enum', reason: `each tier must be one of: ${spec.tiers.join(', ')}` };
194
+ const modelFault = validateValue({ type: 'model-id' }, entry[tier]);
195
+ if (modelFault) return { code: modelFault.code, reason: `a tier's model ${modelFault.reason}` };
196
+ }
197
+ }
198
+ return null;
199
+ }
200
+ case 'worker-billing':
201
+ return validateWorkerBilling(value);
202
+ default:
203
+ return { code: 'type', reason: 'has an unknown type' };
204
+ }
205
+ }
206
+
207
+ function invalid(code , reason , offset = null, notes = []) {
208
+ return { status: 'invalid', code, offset, reason, values: new Map(), notes };
209
+ }
210
+
211
+ /**
212
+ * Read one layer file. Never throws, never creates a file or folder, never blocks on a FIFO.
213
+ * A missing file (including a dangling link) is `absent`; anything else that cannot be used is an
214
+ * invalid layer with a code (data-model §2): not-file, unreadable, too-large, unparseable, schema.
215
+ * @param registry `{ keys: Map<string, KeySpec>, enumValues(key): string[] | null }`
216
+ */
217
+ function readLayerFile(file , { registry, fsImpl = nodeFs, includeDocument = false } ) {
218
+ let stat ;
219
+ try { stat = fsImpl.statSync(file); }
220
+ catch (error ) {
221
+ if (error?.code === 'ENOENT') return { status: 'absent', code: null, offset: null, reason: null, values: new Map(), notes: [] };
222
+ return invalid('unreadable', 'cannot be read');
223
+ }
224
+ if (!stat.isFile()) return invalid('not-file', 'is not a regular file');
225
+ if (stat.size > MAX_BYTES) return invalid('too-large', `is larger than ${MAX_BYTES} bytes`);
226
+
227
+ let buffer ;
228
+ try { buffer = fsImpl.readFileSync(file); }
229
+ catch { return invalid('unreadable', 'cannot be read'); }
230
+ if (buffer.length > MAX_BYTES) return invalid('too-large', `is larger than ${MAX_BYTES} bytes`);
231
+
232
+ let text = buffer.toString('utf8');
233
+ let bomBytes = 0;
234
+ if (text.startsWith(BOM)) { text = text.slice(1); bomBytes = 3; }
235
+
236
+ let parsed ;
237
+ try { parsed = JSON.parse(text); }
238
+ catch (error ) {
239
+ const failure = jsonFailure(error, text, bomBytes);
240
+ return invalid('unparseable', failure.reason, failure.offset);
241
+ }
242
+ if (!isObject(parsed)) return invalid('unparseable', 'must be a JSON object');
243
+
244
+ const notes = [];
245
+ if (!Object.hasOwn(parsed, 'schema')) {
246
+ notes.push({ code: 'schema-missing', key: null, source: file, text: 'has no "schema"; read as schema 1' });
247
+ } else if (parsed.schema !== 1) {
248
+ return invalid('schema', 'schema must be 1');
249
+ }
250
+
251
+ const values = new Map ();
252
+ for (const [key, raw] of Object.entries(parsed)) {
253
+ if (key === 'schema') continue;
254
+ const spec = registry.keys.get(key);
255
+ if (!spec) {
256
+ const shown = JSON.stringify(key.length > 80 ? `${key.slice(0, 80)}...` : key);
257
+ if (isObject(raw) || Array.isArray(raw)) {
258
+ notes.push({ code: 'nested', key, source: file, text: `${shown} is a nested object; use dotted keys (not applied)` });
259
+ } else {
260
+ notes.push({ code: 'unknown-key', key, source: file, text: `${shown} is not a DoFlow setting (ignored)` });
261
+ }
262
+ continue;
263
+ }
264
+ const error = validateValue(spec, raw, registry.enumValues(key));
265
+ values.set(key, { raw, value: error ? null : raw, error });
266
+ }
267
+ return { status: 'ok', code: null, offset: null, reason: null, values, notes,
268
+ ...(includeDocument ? { document: parsed } : {}) };
269
+ }
270
+
271
+ module.exports = { MAX_BYTES, jsonFailure, validateValue, readLayerFile };