@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,553 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ /**
6
+ * The settings key registry and its load-time checks (086 IC-001, DD-1, DD-5, DD-6;
7
+ * data-model §1). `core/registry/settings.json` declares every key DoFlow reads from a settings file:
8
+ * type, values, default, which layer may set it, how layers combine, fail-safe and bindings. This
9
+ * module loads it once per process and refuses a registry that breaks any rule below, naming the key.
10
+ * A bad registry is an install fault: the loader fails, never falls back to a default.
11
+ *
12
+ * The checks that need no I/O live in `validateSettingsRegistry` (pure, takes the registry text
13
+ * already parsed). `loadSettingsRegistry` adds the file read and, on request, the checks that reach
14
+ * other files: `verifyRefs` resolves every `valuesRef`/`orderRef`/`tiersRef`/`defaultRef` against the
15
+ * registry that owns it (refs.ts), `checkConsumers` confirms each `consumers` path exists. A key with
16
+ * an `orderRef` carries its `order` on the loaded spec; `tiersRef` similarly supplies non-enumerable
17
+ * `tiers` (IC-002). Both resolve on first read and return null when unavailable, as `enumValues` does.
18
+ * Both verification options default to off so the hot path pays for one small file read; the
19
+ * registry test and the settings guard turn them on.
20
+ */
21
+
22
+ const nodeFs = require('node:fs');
23
+ const path = require('node:path');
24
+ const { REPO_ROOT } = require('../../helper/repo-root.js');
25
+ const { REF_META, createRefs } = require('./refs.js');
26
+ const { loadCoreProtocols } = require('../review/protocols.js');
27
+ const { jsonFailure, validateValue } = require('./files.js');
28
+
29
+ const REGISTRY_FILE = 'core/registry/settings.json';
30
+
31
+ // A segment after the namespace is camelCase, or kebab-case when it names a registry id (`advice.kinds.task-class`).
32
+ const KEY_RE = /^[a-z][a-z0-9]*(\.[a-z][a-zA-Z0-9]*(-[a-z0-9]+)*)+$/;
33
+ const TYPES = ['boolean', 'enum', 'integer', 'model-id', 'model-list', 'string', 'string-list', 'url', 'worker-billing', 'worker-list', 'worker-models'];
34
+ const SCOPES = ['global-only', 'project-only', 'narrow-only', 'project-overridable'];
35
+ const COMBINES = ['override', 'narrowest', 'union', 'subset'];
36
+ const PARSES = ['positive-int', 'off-words', 'verbatim'];
37
+ const DIRECTIONS = ['any', 'narrow'];
38
+ const LEGACY_LAYERS = ['user', 'project'];
39
+ const LEGACY_KINDS = ['decision-model', 'line-onoff', 'sentinel'];
40
+ const VIEWS = ['project', 'global'];
41
+ const SHELL_READS = [null, 'lexical-v1'];
42
+ const TOP_FIELDS = ['version', 'description', 'keys'];
43
+ const SPEC_FIELDS = [
44
+ 'type', 'values', 'valuesRef', 'tiersRef', 'min', 'max', 'maxItems', 'maxLength', 'order', 'orderRef', 'default', 'defaultRef', 'scope', 'combine', 'direction', 'views',
45
+ 'securityRelevant', 'failSafe', 'requires', 'env', 'flag', 'shellRead', 'needsConfigHome', 'legacy',
46
+ 'unknownMembers',
47
+ 'consumers', 'description',
48
+ ];
49
+ const REQUIRED_FIELDS = [
50
+ 'type', 'scope', 'combine', 'securityRelevant', 'env', 'flag', 'shellRead', 'needsConfigHome',
51
+ 'legacy', 'consumers', 'description',
52
+ ];
53
+ /** The two bindings that existed before the registry keep their names (DD-4). */
54
+ const EXISTING_ENV = Object.freeze({
55
+ 'storage.retentionHours': 'DOFLOW_RETENTION_HOURS',
56
+ 'failure.capture': 'DOFLOW_FAILURE_CAPTURE',
57
+ });
58
+
59
+
60
+
61
+
62
+
63
+
64
+
65
+
66
+
67
+ function isObject(value ) {
68
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
69
+ }
70
+
71
+ function has(object , field ) {
72
+ return Object.hasOwn(object, field);
73
+ }
74
+
75
+ /** `DOFLOW_<KEY_UPPER_SNAKE>`: dots and hyphens become underscores and a camelCase hump gets one. */
76
+ function derivedEnvName(key ) {
77
+ return `DOFLOW_${key.replace(/[.-]/g, '_').replace(/([a-z0-9])([A-Z])/g, '$1_$2').toUpperCase()}`;
78
+ }
79
+
80
+ function sameList(a , b ) {
81
+ return a.length === b.length && a.every((item) => b.includes(item));
82
+ }
83
+
84
+ function relativeInside(p ) {
85
+ return typeof p === 'string' && p.length > 0 && !path.isAbsolute(p) && !p.includes('\\') && !p.split('/').some((part) => part === '..' || part === '');
86
+ }
87
+
88
+ /**
89
+ * Check a parsed registry. Every message starts with the key it is about (or `registry` for the
90
+ * top level), so a failure names its cause. Never throws.
91
+ */
92
+ function validateSettingsRegistry(raw , refs = { meta: REF_META }, options = {}) {
93
+ const errors = [];
94
+ if (!isObject(raw)) return { ok: false, errors: ['registry must be a JSON object'] };
95
+
96
+ for (const field of Object.keys(raw)) {
97
+ if (!TOP_FIELDS.includes(field)) errors.push(`registry has unknown field '${field}'`);
98
+ }
99
+ if (raw.version !== 1) errors.push('registry version must be 1');
100
+ if (typeof raw.description !== 'string' || !raw.description.trim()) errors.push('registry description must be a non-empty string');
101
+ if (!isObject(raw.keys) || Object.keys(raw.keys).length === 0) {
102
+ errors.push('registry keys must be a non-empty object');
103
+ return { ok: false, errors };
104
+ }
105
+ const specs = raw.keys;
106
+ const names = Object.keys(specs);
107
+ const refMeta = (name ) =>
108
+ typeof name === 'string' && has(refs.meta, name) ? refs.meta[name] : null;
109
+
110
+ // Enum values and list membership: literal enum values, or the resolved ref when refs are resolved.
111
+ const enumLists = new Map ();
112
+ for (const name of names) {
113
+ const spec = specs[name];
114
+ if (!isObject(spec) || !['enum', 'string-list', 'worker-list', 'worker-models'].includes(spec.type)) continue;
115
+ let list = null;
116
+ if (spec.type === 'enum' && Array.isArray(spec.values) && spec.values.every((v ) => typeof v === 'string')) list = spec.values;
117
+ else if (typeof spec.valuesRef === 'string' && refs.resolve && refMeta(spec.valuesRef)?.kind === 'values') {
118
+ try {
119
+ const resolved = refs.resolve(spec.valuesRef);
120
+ if (Array.isArray(resolved) && resolved.length > 0 && resolved.every((v) => typeof v === 'string')) list = resolved;
121
+ else errors.push(`${name} valuesRef '${spec.valuesRef}' did not resolve to a non-empty list of strings`);
122
+ } catch (error ) {
123
+ errors.push(`${name} valuesRef '${spec.valuesRef}' does not resolve: ${error.message}`);
124
+ }
125
+ }
126
+ enumLists.set(name, list);
127
+ }
128
+
129
+ for (const name of names) validateKey(name, specs[name]);
130
+ validateAcrossKeys();
131
+ return { ok: errors.length === 0, errors };
132
+
133
+ function validateKey(name , spec ) {
134
+ const err = (message ) => { errors.push(`${name} ${message}`); };
135
+ if (!KEY_RE.test(name)) err('is not a valid key name (lowercase namespace, a dot, then a camelCase or kebab-case name)');
136
+ if (!isObject(spec)) { err('must be an object'); return; }
137
+ for (const field of Object.keys(spec)) if (!SPEC_FIELDS.includes(field)) err(`has unknown field '${field}'`);
138
+ for (const field of REQUIRED_FIELDS) if (!has(spec, field)) err(`is missing '${field}'`);
139
+ if (!TYPES.includes(spec.type)) { err(`type must be one of ${TYPES.join(', ')}`); return; }
140
+
141
+ const type = spec.type;
142
+ const list = enumLists.get(name) ?? null;
143
+ /** A fault text for a value of this key, or null. A list-bound key whose list is unresolved checks only the rest. */
144
+ const faultOf = (value ) => {
145
+ if (type === 'enum' && list === null) return typeof value === 'string' ? null : 'must be a string';
146
+ if (type === 'string-list' && list === null) return validateValue({ ...spec, valuesRef: undefined }, value)?.reason ?? null;
147
+ return validateValue(spec, value, list)?.reason ?? null;
148
+ };
149
+
150
+ // values / valuesRef / min
151
+ if (type === 'enum') {
152
+ const hasValues = has(spec, 'values');
153
+ const hasRef = has(spec, 'valuesRef');
154
+ if (hasValues === hasRef) err("enum needs exactly one of 'values' or 'valuesRef'");
155
+ if (hasValues && !(Array.isArray(spec.values) && spec.values.length > 0
156
+ && spec.values.every((v ) => typeof v === 'string' && v) && new Set(spec.values).size === spec.values.length)) {
157
+ err("values must be a non-empty list of distinct non-empty strings");
158
+ }
159
+ if (hasRef && refMeta(spec.valuesRef)?.kind !== 'values') err(`valuesRef '${String(spec.valuesRef)}' is not a known values ref`);
160
+ } else if (type === 'string-list' || type === 'worker-list' || type === 'worker-models') {
161
+ if (has(spec, 'values')) err(`'values' is not allowed for a ${type} key (use 'valuesRef')`);
162
+ if (has(spec, 'valuesRef') && refMeta(spec.valuesRef)?.kind !== 'values') err(`valuesRef '${String(spec.valuesRef)}' is not a known values ref`);
163
+ } else {
164
+ if (has(spec, 'values')) err("'values' is only for enum keys");
165
+ if (has(spec, 'valuesRef')) err("'valuesRef' is only for enum, string-list, worker-list and worker-models keys");
166
+ }
167
+ // IC-002: worker model tiers are declared by a values ref, never copied into the setting.
168
+ if (type === 'worker-models') {
169
+ if (!has(spec, 'tiersRef')) err("a worker-models key needs 'tiersRef'");
170
+ else if (refMeta(spec.tiersRef)?.kind !== 'values') err(`tiersRef '${String(spec.tiersRef)}' is not a known values ref`);
171
+ else if (refs.resolve) {
172
+ try {
173
+ const tiers = refs.resolve(spec.tiersRef);
174
+ if (!Array.isArray(tiers) || tiers.length === 0 || tiers.some((tier ) => typeof tier !== 'string')
175
+ || new Set(tiers).size !== tiers.length) {
176
+ err(`tiersRef '${spec.tiersRef}' did not resolve to a non-empty list of strings`);
177
+ }
178
+ } catch (error ) {
179
+ err(`tiersRef '${spec.tiersRef}' does not resolve: ${error.message}`);
180
+ }
181
+ }
182
+ } else if (has(spec, 'tiersRef')) err("'tiersRef' is only for worker-models keys");
183
+ // 122 IC-011: a layer may name a member the list does not hold yet (a project file travels): kept, and reported as a note.
184
+ if (has(spec, 'unknownMembers')) {
185
+ if (type !== 'string-list' || !has(spec, 'valuesRef')) err("'unknownMembers' is only for string-list keys with a 'valuesRef'");
186
+ else if (spec.unknownMembers !== 'warn') err("unknownMembers must be 'warn'");
187
+ }
188
+ if (has(spec, 'min')) {
189
+ if (type !== 'integer') err("'min' is only for integer keys");
190
+ else if (!Number.isSafeInteger(spec.min)) err('min must be an integer');
191
+ }
192
+ if (has(spec, 'max')) {
193
+ if (type !== 'integer') err("'max' is only for integer keys");
194
+ else if (!Number.isSafeInteger(spec.max)) err('max must be an integer');
195
+ else if (Number.isSafeInteger(spec.min) && spec.max < spec.min) err('max must not be below min');
196
+ }
197
+ for (const bound of ['maxItems', 'maxLength']) {
198
+ if (type !== 'string-list') { if (has(spec, bound)) err(`'${bound}' is only for string-list keys`); }
199
+ else if (!has(spec, bound) || !Number.isSafeInteger(spec[bound]) || spec[bound] < 1) err(`${bound} must be a positive integer`);
200
+ }
201
+
202
+ // scope / combine / order
203
+ if (!SCOPES.includes(spec.scope)) err(`scope must be one of ${SCOPES.join(', ')}`);
204
+ if (!COMBINES.includes(spec.combine)) err(`combine must be one of ${COMBINES.join(', ')}`);
205
+ const free = type === 'string' || type === 'url' || type === 'model-id';
206
+ // IC-002: keyed worker records have no order; only an override is defined.
207
+ const record = type === 'worker-billing';
208
+ const models = type === 'worker-models';
209
+ const keyed = record || models;
210
+ // string-list is a set of items: no order ranks two lists, only a union is defined.
211
+ const multi = type === 'string-list';
212
+ // IC-002: worker lists and model lists narrow by membership, preserving the order below.
213
+ const workerList = type === 'worker-list';
214
+ const modelList = type === 'model-list';
215
+ const subsetList = workerList || modelList;
216
+ if (spec.scope === 'narrow-only' && (free || keyed || multi)) err(`a ${type} key cannot be narrow-only: no order says which value is narrower`);
217
+ if (keyed && spec.combine !== 'override') err(`a ${type} key must combine by 'override'`);
218
+ // A global-only list has one layer that can set it, so an override is defined there: the person's list
219
+ // replaces the default whole, which is how a default list is ever narrowed (hooks.harnesses).
220
+ const overrideList = multi && spec.scope === 'global-only' && spec.combine === 'override';
221
+ if (multi !== (spec.combine === 'union') && !overrideList) {
222
+ err(multi ? "a string-list key must combine by 'union' (or 'override' when global-only)" : "combine 'union' is only for string-list keys");
223
+ }
224
+ if (subsetList !== (spec.combine === 'subset')) err(subsetList ? `a ${type} key must combine by 'subset'` : "combine 'subset' is only for worker-list or model-list keys");
225
+ const needsOrder = (spec.scope === 'narrow-only' && spec.combine !== 'subset') || spec.combine === 'narrowest';
226
+ const ordered = has(spec, 'order') || has(spec, 'orderRef');
227
+ if (has(spec, 'orderRef')) {
228
+ if (has(spec, 'order')) err("needs at most one of 'order' or 'orderRef'");
229
+ if (type !== 'enum' || !has(spec, 'valuesRef')) err("'orderRef' is only for enum keys with a 'valuesRef'");
230
+ else if (refMeta(spec.orderRef)?.kind !== 'order') err(`orderRef '${String(spec.orderRef)}' is not a known order ref`);
231
+ else if (refs.resolve) {
232
+ try {
233
+ const order = refs.resolve(spec.orderRef);
234
+ if (!Array.isArray(order) || new Set(order).size !== order.length || (list && !sameList(order, list))) {
235
+ err(`orderRef '${spec.orderRef}' must resolve to every allowed value exactly once`);
236
+ }
237
+ } catch (error ) {
238
+ err(`orderRef '${spec.orderRef}' does not resolve: ${error.message}`);
239
+ }
240
+ }
241
+ } else if (has(spec, 'order')) {
242
+ const order = spec.order;
243
+ if (free || keyed || multi || subsetList) err(`order is refused for a ${type} key`);
244
+ else if (order === 'ascending' || order === 'descending') {
245
+ if (type !== 'integer') err("'ascending'/'descending' order is only for integer keys");
246
+ } else if (!Array.isArray(order) || order.length === 0 || new Set(order).size !== order.length) {
247
+ err("order must be 'ascending', 'descending' or a non-empty list of distinct values, narrowest first");
248
+ } else {
249
+ const bad = order.find((value ) => faultOf(value) !== null);
250
+ if (bad !== undefined) err('order holds a value that is not valid for this key');
251
+ else if (type === 'boolean' && !sameList(order, [false, true])) err('order must list both false and true');
252
+ else if (type === 'enum' && list && !sameList(order, list)) err('order must list every allowed value exactly once');
253
+ }
254
+ }
255
+ if (needsOrder && !ordered) {
256
+ err(spec.scope === 'narrow-only' ? "scope 'narrow-only' requires an 'order'" : "combine 'narrowest' requires an 'order'");
257
+ }
258
+
259
+ // default / defaultRef
260
+ const hasDefault = has(spec, 'default');
261
+ const hasDefaultRef = has(spec, 'defaultRef');
262
+ if (hasDefault === hasDefaultRef) err("needs exactly one of 'default' or 'defaultRef'");
263
+ if (keyed && (!hasDefault || spec.default !== null)) err(`a ${type} key must default to null`);
264
+ // A null default is unset: the consumer keeps its own unset path (an enum key such as advice.kinds.<kind>).
265
+ if (hasDefault && !(spec.default === null && (free || keyed || type === 'integer' || type === 'enum'))) {
266
+ const fault = faultOf(spec.default);
267
+ if (fault) err(`default is not valid: ${fault}`);
268
+ }
269
+ if (hasDefaultRef && refMeta(spec.defaultRef)?.kind !== 'default') err(`defaultRef '${String(spec.defaultRef)}' is not a known default ref`);
270
+
271
+ // views
272
+ if (has(spec, 'views') && !(Array.isArray(spec.views) && (sameList(spec.views, ['project']) || (spec.views.length === 2 && spec.views[0] === 'project' && spec.views[1] === 'global')))) {
273
+ err("views must be ['project'] or ['project', 'global']");
274
+ }
275
+
276
+ // securityRelevant / failSafe
277
+ if (has(spec, 'securityRelevant') && typeof spec.securityRelevant !== 'boolean') err('securityRelevant must be true or false');
278
+ if (has(spec, 'failSafe')) {
279
+ const safe = spec.failSafe;
280
+ if (isObject(safe)) {
281
+ const fields = Object.keys(safe);
282
+ if (!(fields.length === 1 && ((fields[0] === 'gate' && typeof safe.gate === 'string') || (fields[0] === 'state' && safe.state === 'invalid')))) {
283
+ err("failSafe object must be {gate: <key>} or {state: 'invalid'}");
284
+ }
285
+ } else {
286
+ const fault = Array.isArray(safe) || safe === null ? 'must be a value' : faultOf(safe);
287
+ if (fault) err(`failSafe is not valid: ${fault}`);
288
+ }
289
+ }
290
+ if (keyed && !(isObject(spec.failSafe) && sameList(Object.keys(spec.failSafe), ['state']) && spec.failSafe.state === 'invalid')) {
291
+ err(`a ${type} key needs failSafe {state: 'invalid'}`);
292
+ }
293
+ if (spec.securityRelevant === true) {
294
+ const safe = spec.failSafe;
295
+ // Records and worker lists use the consumer's invalid path as their fail-safe (IC-003).
296
+ // A project-only key's candidates never widen (resolve.ts canWiden), so no fail-safe value is ever
297
+ // taken; the consumer's invalid path voids it (worker/launch.ts dropDatabases), so the state form is allowed.
298
+ const stateSafe = (keyed || workerList || spec.scope === 'project-only') && isObject(safe) && safe.state === 'invalid';
299
+ if (!stateSafe && (!has(spec, 'failSafe') || (isObject(safe) && !has(safe, 'gate')))) err('is securityRelevant and needs a failSafe value or {gate}');
300
+ }
301
+
302
+ // A cross-key rule (resolve.ts requires) or a missing config home takes `value = spec.failSafe`; the state form
303
+ // is not a value, so it would resolve to the object {state: 'invalid'} with state ok (119 closure N-3).
304
+ // IC-003: billing retains its exemption; worker-models exempts only needsConfigHome, not requires.
305
+ if (!record && isObject(spec.failSafe) && spec.failSafe.state === 'invalid'
306
+ && ((Array.isArray(spec.requires) && spec.requires.length > 0) || (!models && spec.needsConfigHome === true))) {
307
+ err("failSafe {state: 'invalid'} cannot be combined with 'requires' or 'needsConfigHome' (they resolve to the failSafe as a value)");
308
+ }
309
+
310
+ // requires
311
+ if (has(spec, 'requires')) {
312
+ if (!Array.isArray(spec.requires)) err('requires must be a list');
313
+ else spec.requires.forEach((rule , index ) => {
314
+ const where = `requires[${index}]`;
315
+ if (!isObject(rule) || !sameList(Object.keys(rule), ['when', 'key', 'view'])) { err(`${where} must be {when, key, view}`); return; }
316
+ const fault = faultOf(rule.when);
317
+ if (fault) err(`${where}.when is not valid: ${fault}`);
318
+ if (typeof rule.key !== 'string') err(`${where}.key must be a key name`);
319
+ if (!VIEWS.includes(rule.view)) err(`${where}.view must be one of ${VIEWS.join(', ')}`);
320
+ });
321
+ }
322
+
323
+ // env / flag / shellRead / needsConfigHome
324
+ if (spec.scope === 'project-only' && spec.env !== null) err('project-only: env must be null');
325
+ if (has(spec, 'env') && spec.env !== null) {
326
+ const env = spec.env;
327
+ if (!isObject(env) || !sameList(Object.keys(env), ['name', 'parse', 'direction'])) err('env must be null or {name, parse, direction}');
328
+ else {
329
+ if (typeof env.name !== 'string' || !/^DOFLOW_[A-Z0-9_]+$/.test(env.name)) err('env.name must match DOFLOW_[A-Z0-9_]+');
330
+ else if (env.name !== derivedEnvName(name) && EXISTING_ENV[name] !== env.name) err(`env.name must be ${derivedEnvName(name)}`);
331
+ if (!PARSES.includes(env.parse)) err(`env.parse must be one of ${PARSES.join(', ')}`);
332
+ else if (env.parse === 'positive-int' && type !== 'integer') err('env.parse positive-int needs an integer key');
333
+ else if (env.parse === 'off-words' && !(type === 'enum' && list?.includes('off'))) err("env.parse off-words needs an enum key with the value 'off'");
334
+ else if (env.parse === 'verbatim' && !(['enum', 'string', 'model-id'].includes(type) && env.direction === 'any')) err("env.parse verbatim needs an enum, string or model-id key with direction 'any'");
335
+ if (!DIRECTIONS.includes(env.direction)) err(`env.direction must be one of ${DIRECTIONS.join(', ')}`);
336
+ else if (env.direction === 'any' && (spec.securityRelevant === true || spec.scope === 'global-only')) {
337
+ err("env.direction must be 'narrow': an environment variable may only narrow a security-relevant or global-only key");
338
+ } else if (env.direction === 'narrow' && !ordered) err("env.direction 'narrow' needs an 'order'");
339
+ }
340
+ }
341
+ if (has(spec, 'flag') && spec.flag !== null) err("flag must be null in v1 (a declared flag must be absent from the CLI flag tables; '--set' and '--setting' are reserved)");
342
+ if (has(spec, 'shellRead') && !SHELL_READS.includes(spec.shellRead)) err("shellRead must be null or 'lexical-v1'");
343
+ if (spec.shellRead === 'lexical-v1' && name !== 'failure.capture') err("shellRead 'lexical-v1' is only allowed for 'failure.capture'");
344
+ if (has(spec, 'needsConfigHome') && typeof spec.needsConfigHome !== 'boolean') err('needsConfigHome must be true or false');
345
+
346
+ // legacy
347
+ if (!Array.isArray(spec.legacy)) err('legacy must be a list');
348
+ else spec.legacy.forEach((entry , index ) => {
349
+ const where = `legacy[${index}]`;
350
+ if (!isObject(entry)) { err(`${where} must be an object`); return; }
351
+ for (const field of Object.keys(entry)) if (!['layer', 'kind', 'path', 'field', 'value'].includes(field)) err(`${where} has unknown field '${field}'`);
352
+ if (!LEGACY_LAYERS.includes(entry.layer)) err(`${where}.layer must be one of ${LEGACY_LAYERS.join(', ')}`);
353
+ if (!LEGACY_KINDS.includes(entry.kind)) err(`${where}.kind must be one of ${LEGACY_KINDS.join(', ')}`);
354
+ if (!relativeInside(entry.path)) err(`${where}.path must be a relative path inside its root (no '..')`);
355
+ if ((entry.kind === 'decision-model') !== (typeof entry.field === 'string' && entry.field !== '')) err(`${where}.field is required for, and only for, decision-model`);
356
+ if ((entry.kind === 'sentinel') !== has(entry, 'value')) err(`${where}.value is required for, and only for, sentinel`);
357
+ });
358
+
359
+ // consumers / description
360
+ if (!Array.isArray(spec.consumers) || spec.consumers.length === 0) err('consumers must be a non-empty list of paths');
361
+ else for (const consumer of spec.consumers) {
362
+ if (!relativeInside(consumer)) err(`consumers entry is not a relative path inside the tree`);
363
+ else if (options.consumerExists && !options.consumerExists(consumer)) err(`consumer '${consumer}' does not exist`);
364
+ }
365
+ if (typeof spec.description !== 'string' || !spec.description.trim() || /[\r\n]/.test(spec.description)) err('description must be one non-empty line');
366
+ }
367
+
368
+ function validateAcrossKeys() {
369
+ const envOwner = new Map ();
370
+ for (const name of names) {
371
+ const spec = specs[name];
372
+ if (!isObject(spec)) continue;
373
+ const err = (message ) => { errors.push(`${name} ${message}`); };
374
+
375
+ const failSafe = spec.failSafe;
376
+ if (isObject(failSafe) && typeof failSafe.gate === 'string') {
377
+ const target = isObject(specs[failSafe.gate]) ? specs[failSafe.gate] : null;
378
+ if (failSafe.gate === name) err('failSafe gate names the key itself');
379
+ else if (!target) err(`failSafe gate '${failSafe.gate}' is not a registered key`);
380
+ else if (!has(target, 'failSafe') || isObject(target.failSafe) || Array.isArray(target.failSafe)) {
381
+ err(`failSafe gate '${failSafe.gate}' needs a plain failSafe value of its own`);
382
+ }
383
+ }
384
+ if (Array.isArray(spec.requires)) {
385
+ for (const rule of spec.requires) {
386
+ if (isObject(rule) && typeof rule.key === 'string' && !has(specs, rule.key)) err(`requires names '${rule.key}', which is not a registered key`);
387
+ }
388
+ }
389
+ if (isObject(spec.env) && typeof spec.env.name === 'string') {
390
+ const owner = envOwner.get(spec.env.name);
391
+ if (owner) err(`env.name ${spec.env.name} is already bound by ${owner}`);
392
+ else envOwner.set(spec.env.name, name);
393
+ }
394
+ }
395
+
396
+ // A default that points at a ref depending on another key's default must not loop back.
397
+ const stateOf = new Map ();
398
+ let cyclic = false;
399
+ const visit = (name , trail ) => {
400
+ const state = stateOf.get(name);
401
+ if (state === 'done') return;
402
+ if (state === 'visiting') {
403
+ cyclic = true;
404
+ errors.push(`${name} defaultRef forms a cycle: ${[...trail.slice(trail.indexOf(name)), name].join(' -> ')}`);
405
+ return;
406
+ }
407
+ const spec = specs[name];
408
+ stateOf.set(name, 'visiting');
409
+ const meta = isObject(spec) ? refMeta(spec.defaultRef) : null;
410
+ for (const dep of meta?.dependsOn ?? []) {
411
+ if (!has(specs, dep)) errors.push(`${name} defaultRef '${spec.defaultRef}' depends on '${dep}', which is not a registered key`);
412
+ else visit(dep, [...trail, name]);
413
+ }
414
+ stateOf.set(name, 'done');
415
+ };
416
+ for (const name of names) visit(name, []);
417
+
418
+ // Resolve every defaultRef and hold the value to the key's own rules.
419
+ if (!refs.resolve || cyclic) return;
420
+ const memo = new Map ();
421
+ const defaultOf = (name ) => {
422
+ if (memo.has(name)) return memo.get(name);
423
+ const spec = specs[name];
424
+ let value ;
425
+ if (!isObject(spec)) value = undefined;
426
+ else if (has(spec, 'default')) value = spec.default;
427
+ else if (refMeta(spec.defaultRef)?.kind === 'default') {
428
+ const deps = {};
429
+ for (const dep of refMeta(spec.defaultRef) .dependsOn) deps[dep] = has(specs, dep) ? defaultOf(dep) : undefined;
430
+ value = refs.resolve (spec.defaultRef, deps);
431
+ }
432
+ memo.set(name, value);
433
+ return value;
434
+ };
435
+ for (const name of names) {
436
+ const spec = specs[name];
437
+ if (!isObject(spec) || !has(spec, 'defaultRef') || refMeta(spec.defaultRef)?.kind !== 'default' || !TYPES.includes(spec.type)) continue;
438
+ try {
439
+ const fault = validateValue(spec, defaultOf(name), enumLists.get(name) ?? null);
440
+ if (fault) errors.push(`${name} defaultRef '${spec.defaultRef}' resolves to an invalid value: ${fault.reason}`);
441
+ } catch (error ) {
442
+ errors.push(`${name} defaultRef '${spec.defaultRef}' does not resolve: ${error.message}`);
443
+ }
444
+ }
445
+ }
446
+ }
447
+
448
+
449
+
450
+
451
+
452
+
453
+
454
+
455
+
456
+
457
+
458
+
459
+
460
+
461
+
462
+ const cache = new Map ();
463
+
464
+ function failure(errors ) {
465
+ return { ok: false, reason: 'settings-registry-invalid', detail: errors.join('; '), errors };
466
+ }
467
+
468
+ /**
469
+ * Load `core/registry/settings.json`. Cached per process for the default file system; a caller that
470
+ * passes `fsImpl` always reads fresh. The result is plain data and never carries file text.
471
+ */
472
+ function loadSettingsRegistry({ repoRoot = REPO_ROOT, fsImpl, verifyRefs = false, checkConsumers = false } = {}) {
473
+ const cacheKey = fsImpl ? null : `${repoRoot}\0${verifyRefs}\0${checkConsumers}`;
474
+ if (cacheKey && cache.has(cacheKey)) return cache.get(cacheKey) ;
475
+ const fsUse = fsImpl || nodeFs;
476
+
477
+ const done = (result ) => { if (cacheKey) cache.set(cacheKey, result); return result; };
478
+
479
+ let text ;
480
+ try { text = fsUse.readFileSync(path.join(repoRoot, REGISTRY_FILE), 'utf8'); }
481
+ catch { return done(failure([`${REGISTRY_FILE} could not be read`])); }
482
+ let raw ;
483
+ try { raw = JSON.parse(text); }
484
+ catch (error ) { return done(failure([`${REGISTRY_FILE} is ${jsonFailure(error, text).reason}`])); }
485
+
486
+ const refs = createRefs({ repoRoot, fsImpl: fsUse });
487
+ // Registry validation is about core consistency, so its ref pass reads the core protocols file only: the presets active
488
+ // packages contribute (122 IC-014) are values without a rank and would never equal the order. A core file that does
489
+ // not load gives it no contributed ids and fails on validateProtocols as before. Run-time reads (`refs`) keep them.
490
+ const verify = verifyRefs ? createRefs({ repoRoot, fsImpl: fsUse, protocols: loadCoreProtocols({ repoRoot, fsImpl: fsUse }).registry ?? null }) : null;
491
+ const checked = validateSettingsRegistry(
492
+ raw,
493
+ { meta: REF_META, resolve: verify?.resolve },
494
+ { consumerExists: checkConsumers ? (p ) => fsUse.existsSync(path.join(repoRoot, p)) : undefined },
495
+ );
496
+ if (!checked.ok) return done(failure(checked.errors));
497
+
498
+ const keys = new Map (Object.entries(raw.keys).map(([name, spec] ) => {
499
+ if (typeof spec.orderRef === 'string') {
500
+ let order ;
501
+ Object.defineProperty(spec, 'order', {
502
+ enumerable: true,
503
+ get() {
504
+ if (order === undefined) {
505
+ try { order = refs.resolve(spec.orderRef) ; } catch { order = null; }
506
+ }
507
+ return order;
508
+ },
509
+ });
510
+ }
511
+ // IC-002: resolve tiers lazily without adding a serialized field to the declared spec.
512
+ if (typeof spec.tiersRef === 'string') {
513
+ let tiers ;
514
+ Object.defineProperty(spec, 'tiers', {
515
+ enumerable: false,
516
+ get() {
517
+ if (tiers === undefined) {
518
+ try { tiers = refs.resolve(spec.tiersRef) ; } catch { tiers = null; }
519
+ }
520
+ return tiers;
521
+ },
522
+ });
523
+ }
524
+ return [name, Object.freeze(spec)];
525
+ }));
526
+ const lists = new Map ();
527
+ const registry = {
528
+ version: 1,
529
+ keys,
530
+ enumValues(key) {
531
+ if (lists.has(key)) return lists.get(key) ;
532
+ const spec = keys.get(key);
533
+ let list = null;
534
+ if (spec?.type === 'enum' || (['string-list', 'worker-list', 'worker-models'].includes(spec?.type) && spec.valuesRef)) {
535
+ if (Array.isArray(spec.values)) list = spec.values;
536
+ else {
537
+ try { list = refs.resolve(spec.valuesRef) ; } catch { list = null; }
538
+ }
539
+ // A value with no rank is never valid: a narrow-only fold could not compare it.
540
+ if (typeof spec.orderRef === 'string' && spec.order === null) list = null;
541
+ }
542
+ lists.set(key, list);
543
+ return list;
544
+ },
545
+ resolveRef: (name, deps) => refs.resolve(name, deps),
546
+ retired() {
547
+ try { return refs.retired(); } catch { return []; }
548
+ },
549
+ };
550
+ return done({ ok: true, registry });
551
+ }
552
+
553
+ module.exports = { REGISTRY_FILE, derivedEnvName, validateSettingsRegistry, loadSettingsRegistry };