@khoavu882/doflow 2.0.0-beta.5 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (494) hide show
  1. package/README.md +16 -16
  2. package/bin/doflow.js +22 -12
  3. package/core/.antigravity-plugin/plugin.json +1 -2
  4. package/core/.claude-plugin/plugin.json +1 -1
  5. package/core/.codex-plugin/plugin.json +1 -1
  6. package/core/.plugin/plugin.json +1 -1
  7. package/core/harnesses/claude/hooks/lib.sh +43 -24
  8. package/core/harnesses/claude/hooks/mcp-tool-guard.sh +4 -1
  9. package/core/harnesses/claude/hooks/permission-audit.sh +3 -0
  10. package/core/harnesses/claude/hooks/post-compact.sh +35 -32
  11. package/core/harnesses/claude/hooks/post-edit-lint.sh +2 -0
  12. package/core/harnesses/claude/hooks/post-shell-observe.sh +48 -0
  13. package/core/harnesses/claude/hooks/post-tool-failure.sh +3 -0
  14. package/core/harnesses/claude/hooks/pre-compact.sh +23 -27
  15. package/core/harnesses/claude/hooks/pre-implement-gate.sh +4 -1
  16. package/core/harnesses/claude/hooks/session-end.sh +23 -10
  17. package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
  18. package/core/harnesses/claude/settings/settings.json +26 -9
  19. package/core/harnesses/claude/statusline/doflow-statusline.sh +1 -0
  20. package/core/harnesses/codex/agents/core-implementer.toml +1 -0
  21. package/core/harnesses/codex/agents/delivery-lead.toml +10 -0
  22. package/core/harnesses/codex/agents/quality-guardian.toml +1 -1
  23. package/core/harnesses/codex/agents/research-writer.toml +1 -1
  24. package/core/harnesses/codex/agents/reviewer.toml +10 -0
  25. package/core/harnesses/codex/agents/spec-analyst.toml +1 -1
  26. package/core/harnesses/codex/agents/system-architect.toml +1 -1
  27. package/core/harnesses/codex/hooks/hooks.json +0 -10
  28. package/core/harnesses/codex/hooks/lib.sh +44 -25
  29. package/core/harnesses/codex/hooks/mcp-tool-guard.sh +4 -1
  30. package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
  31. package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
  32. package/core/harnesses/codex/hooks/pre-implement-gate.sh +4 -1
  33. package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
  34. package/core/harnesses/codex/hooks/subagent-audit.sh +1 -0
  35. package/core/harnesses/copilot/hooks/doflow-lib.sh +94 -0
  36. package/core/harnesses/copilot/hooks/doflow-mcp-tool-guard.sh +9 -0
  37. package/core/harnesses/copilot/hooks/doflow-pre-implement-gate.sh +9 -0
  38. package/core/harnesses/copilot/hooks/doflow-session-start.sh +9 -0
  39. package/core/harnesses/copilot/hooks/doflow-stop-check.sh +9 -0
  40. package/core/harnesses/copilot/hooks/doflow-subagent-start.sh +9 -0
  41. package/core/harnesses/copilot/hooks/doflow-subagent-stop.sh +9 -0
  42. package/core/harnesses/copilot/hooks/doflow.json +51 -0
  43. package/core/harnesses/kiro/agents/doflow.json +1 -11
  44. package/core/harnesses/kiro/hooks/doflow.json +0 -10
  45. package/core/harnesses/kiro/hooks/lib.sh +26 -6
  46. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +8 -3
  47. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +8 -3
  48. package/core/harnesses/kiro/hooks/session-start.sh +1 -0
  49. package/core/harnesses/kiro/hooks/stop-check.sh +1 -0
  50. package/core/harnesses/pi/extensions/doflow-guard.js +11 -2
  51. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +1 -2
  52. package/core/harnesses/shared/hooks/adapters/antigravity.js +1 -1
  53. package/core/harnesses/shared/hooks/bridge/bridge.sh +126 -23
  54. package/core/harnesses/shared/hooks/bridge/envelope.jq +87 -18
  55. package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
  56. package/core/harnesses/shared/hooks/bridge/respond.sh +16 -2
  57. package/core/harnesses/shared/hooks/policies/capture-failure.sh +49 -16
  58. package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
  59. package/core/harnesses/shared/hooks/policies/contract.sh +3 -0
  60. package/core/harnesses/shared/hooks/policies/deny-json.sh +20 -7
  61. package/core/harnesses/shared/hooks/policies/emission-rules.sh +37 -0
  62. package/core/harnesses/shared/hooks/policies/emission.jq +209 -0
  63. package/core/harnesses/shared/hooks/policies/emission.sh +383 -0
  64. package/core/harnesses/shared/hooks/policies/identity.sh +177 -0
  65. package/core/harnesses/shared/hooks/policies/lib.sh +63 -40
  66. package/core/harnesses/shared/hooks/policies/mcp-policy.conf +1 -1
  67. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +7 -8
  68. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +132 -57
  69. package/core/harnesses/shared/hooks/policies/session-context.sh +30 -1
  70. package/core/harnesses/shared/hooks/policies/stop-check.sh +27 -15
  71. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +6 -3
  72. package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
  73. package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +60 -155
  74. package/core/harnesses/shared/hooks/stream-hook-runner.js +58 -20
  75. package/core/harnesses/shared/locator/doflow-run +2 -3
  76. package/core/registry/advice.json +3 -3
  77. package/core/registry/assets.json +55 -82
  78. package/core/registry/capabilities.json +12 -0
  79. package/core/registry/contracts.json +58 -31
  80. package/core/registry/contribution-points.json +35 -0
  81. package/core/registry/gates.json +32 -0
  82. package/core/registry/guidance-caps.json +40 -0
  83. package/core/registry/harnesses.json +1445 -366
  84. package/core/registry/intake.json +53 -0
  85. package/core/registry/lifecycle.json +599 -996
  86. package/core/registry/mcp.json +5 -1
  87. package/core/registry/models.json +7 -1
  88. package/core/registry/package-templates/README.md +19 -0
  89. package/core/registry/package-templates/agent/files/agent.md +9 -0
  90. package/core/registry/package-templates/agent/template.json +4 -0
  91. package/core/registry/package-templates/capability-provider/files/provider.json +9 -0
  92. package/core/registry/package-templates/capability-provider/template.json +11 -0
  93. package/core/registry/package-templates/debate-preset/files/preset.json +26 -0
  94. package/core/registry/package-templates/debate-preset/template.json +4 -0
  95. package/core/registry/package-templates/intake-category/files/category.json +8 -0
  96. package/core/registry/package-templates/intake-category/template.json +4 -0
  97. package/core/registry/package-templates/review-preset/files/preset.json +30 -0
  98. package/core/registry/package-templates/review-preset/template.json +4 -0
  99. package/core/registry/package-templates/skill/files/SKILL.md +8 -0
  100. package/core/registry/package-templates/skill/template.json +4 -0
  101. package/core/registry/package-templates/worker/files/worker.json +92 -0
  102. package/core/registry/package-templates/worker/template.json +11 -0
  103. package/core/registry/packages/doflow/doflow-package.json +15 -0
  104. package/core/registry/packages/doflow/presets/debate-3.json +26 -0
  105. package/core/registry/protocols.json +61 -0
  106. package/core/registry/routes.json +17 -0
  107. package/core/registry/rules.json +431 -0
  108. package/core/registry/settings.json +272 -0
  109. package/core/registry/storage.json +687 -31
  110. package/core/registry/telemetry.json +236 -0
  111. package/core/registry/transcripts.json +142 -0
  112. package/core/registry/verification.json +76 -6
  113. package/core/registry/workflows.json +55 -0
  114. package/core/shared/agent-specs/core-implementer.md +3 -0
  115. package/core/shared/agent-specs/delivery-lead.md +68 -0
  116. package/core/shared/agent-specs/quality-guardian.md +5 -1
  117. package/core/shared/agent-specs/research-writer.md +10 -3
  118. package/core/shared/agent-specs/reviewer.md +53 -0
  119. package/core/shared/agent-specs/spec-analyst.md +5 -6
  120. package/core/shared/agent-specs/system-architect.md +5 -1
  121. package/core/shared/guidance/DOFLOW_CORE.md +1 -5
  122. package/core/shared/guidance/modes/MODE_DeepResearch.md +1 -1
  123. package/core/shared/guidance/modes/MODE_Orchestration.md +4 -3
  124. package/core/shared/guidance/modes/MODE_Task_Management.md +14 -14
  125. package/core/shared/guidance/pointers/codex.md +1 -4
  126. package/core/shared/guidance/references/ARTIFACT_FORMAT.md +30 -16
  127. package/core/shared/guidance/references/ARTIFACT_VOICE.md +3 -2
  128. package/core/shared/guidance/references/CONSTITUTION_BASE.md +17 -15
  129. package/core/shared/guidance/references/DIAGRAM_GUIDE.md +1 -1
  130. package/core/shared/guidance/references/DOFLOW_CHAIN.md +17 -19
  131. package/core/shared/guidance/references/EVIDENCE_LEDGER.md +7 -8
  132. package/core/shared/guidance/references/MODEL_SELECTION.md +5 -3
  133. package/core/shared/guidance/references/ORCHESTRATION.md +3 -2
  134. package/core/shared/guidance/references/WORKFLOW_HANDOFF.md +19 -1
  135. package/core/shared/guidance/rules/RULES.md +45 -0
  136. package/core/shared/scripts/doflow/bash/do-args.sh +37 -0
  137. package/core/shared/scripts/doflow/bash/do-exec-paths.sh +4 -0
  138. package/core/shared/scripts/doflow/bash/do-git-state.sh +15 -4
  139. package/core/shared/scripts/doflow/bash/do-parallel-check.sh +4 -0
  140. package/core/shared/scripts/doflow/bash/do-paths.sh +40 -2
  141. package/core/shared/scripts/doflow/bash/do-prereqs.sh +2 -1
  142. package/core/shared/scripts/doflow/bash/do-review-package.sh +4 -0
  143. package/core/shared/scripts/doflow/bash/do-task-brief.sh +58 -16
  144. package/core/shared/scripts/doflow/bash/render-audit.sh +26 -2
  145. package/core/shared/scripts/doflow/bash/render-diagrams.sh +1 -0
  146. package/core/shared/scripts/doflow/bash/render-puml.sh +2 -1
  147. package/core/shared/scripts/doflow/bash/validate-artifacts.sh +49 -5
  148. package/core/shared/scripts/doflow/bin/doflow-run +152 -34
  149. package/core/shared/skills/do/SKILL.md +14 -12
  150. package/core/shared/skills/do/references/advice_calls.md +26 -0
  151. package/core/shared/skills/do/references/pm_routing.md +1 -2
  152. package/core/shared/skills/do/references/program_mode.md +31 -0
  153. package/core/shared/skills/do-brainstorm/SKILL.md +45 -97
  154. package/core/shared/skills/do-brainstorm/references/handoff_branches.md +36 -0
  155. package/core/shared/skills/do-brainstorm/references/intent_seed.md +21 -0
  156. package/core/shared/skills/do-brainstorm/references/round_log.md +17 -0
  157. package/core/shared/skills/do-code-review/SKILL.md +48 -148
  158. package/core/shared/skills/do-code-review/content-types/markdown.md +24 -2
  159. package/core/shared/skills/do-code-review/references/tools.md +90 -0
  160. package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +549 -7
  161. package/core/shared/skills/do-design/SKILL.md +23 -20
  162. package/core/shared/skills/do-diagnose/SKILL.md +18 -6
  163. package/core/shared/skills/do-document/SKILL.md +12 -1
  164. package/core/shared/skills/do-document/references/deep_research.md +1 -2
  165. package/core/shared/skills/do-document/references/embedded-research.md +19 -7
  166. package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
  167. package/core/shared/skills/do-execute-plan/SKILL.md +19 -60
  168. package/core/shared/skills/do-execute-plan/references/handoff_record.md +7 -2
  169. package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +13 -0
  170. package/core/shared/skills/do-execute-plan/references/readiness_gate.md +26 -1
  171. package/core/shared/skills/do-execute-plan/references/run_position.md +16 -2
  172. package/core/shared/skills/do-execute-plan/references/scaffold.md +17 -9
  173. package/core/shared/skills/do-execute-plan/references/state_checkpoint.md +11 -0
  174. package/core/shared/skills/do-execute-plan/references/task_attempts.md +78 -24
  175. package/core/shared/skills/do-flow/SKILL.md +10 -11
  176. package/core/shared/skills/do-flow/references/task_classes.md +5 -5
  177. package/core/shared/skills/do-git/SKILL.md +4 -0
  178. package/core/shared/skills/do-implement/SKILL.md +13 -14
  179. package/core/shared/skills/do-plan/SKILL.md +34 -20
  180. package/core/shared/skills/do-test/SKILL.md +7 -4
  181. package/core/shared/templates/doflow/constitution-template.md +9 -3
  182. package/core/shared/templates/doflow/data-model-template.md +1 -1
  183. package/core/shared/templates/doflow/design-template.md +113 -16
  184. package/core/shared/templates/doflow/plan-template.md +11 -10
  185. package/core/shared/templates/doflow/question-log-template.md +14 -2
  186. package/core/shared/templates/doflow/requirement-template.md +1 -1
  187. package/core/shared/workflows/do-flow-chain.md +2 -0
  188. package/core/worker-evidence/antigravity/checks.json +14 -0
  189. package/core/worker-evidence/codex/2026-10-18-cli-check.json +107 -0
  190. package/core/worker-evidence/codex/2026-10-18-live-run-edit.json +73 -0
  191. package/core/worker-evidence/codex/2026-10-18-live-run-read-only.json +62 -0
  192. package/core/worker-evidence/codex/2026-10-24-cli-check.json +126 -0
  193. package/core/worker-evidence/codex/checks.json +38 -0
  194. package/package.json +10 -6
  195. package/src/adapters/antigravity/index.js +143 -66
  196. package/src/adapters/claude/index.js +46 -5
  197. package/src/adapters/codex/agents.js +25 -15
  198. package/src/adapters/codex/hooks.js +39 -3
  199. package/src/adapters/codex/index.js +61 -13
  200. package/src/adapters/codex/native-projection.js +6 -2
  201. package/src/adapters/copilot/index.js +58 -23
  202. package/src/adapters/copy-tree.js +291 -46
  203. package/src/adapters/guidance-manifest.js +71 -0
  204. package/src/adapters/guidance-pointer.js +19 -0
  205. package/src/adapters/hook-commands.js +6 -10
  206. package/src/adapters/index.js +1 -0
  207. package/src/adapters/kiro/index.js +23 -12
  208. package/src/adapters/opencode/index.js +8 -8
  209. package/src/adapters/pi/hooks.js +2 -1
  210. package/src/adapters/pi/index.js +14 -7
  211. package/src/adapters/resolve-source.js +26 -2
  212. package/src/cli/commands/install.js +2 -1
  213. package/src/cli/commands/reconcile.js +53 -9
  214. package/src/cli/commands/remove.js +2 -2
  215. package/src/cli/commands/rollback.js +6 -3
  216. package/src/cli/commands/self-update.js +18 -9
  217. package/src/cli/commands/status.js +22 -3
  218. package/src/cli/commands/ui.js +99 -0
  219. package/src/cli/commands/update.js +2 -1
  220. package/src/cli/index.js +337 -32
  221. package/src/cli/runtime-commands.js +220 -60
  222. package/src/cli/shared.js +0 -2
  223. package/src/helper/git.js +1 -1
  224. package/src/helper/guardrails.js +1 -1
  225. package/src/helper/marker-merge.js +31 -13
  226. package/src/helper/package-ids.js +37 -0
  227. package/src/helper/semver.js +94 -0
  228. package/src/install/backup.js +105 -15
  229. package/src/install/context.js +1 -1
  230. package/src/install/targets.js +31 -16
  231. package/src/lifecycle/coverage.js +4 -16
  232. package/src/lifecycle/hook-tier.js +160 -0
  233. package/src/lifecycle/index.js +412 -78
  234. package/src/lifecycle/retired-harnesses.js +318 -0
  235. package/src/lifecycle/view.js +35 -15
  236. package/src/registry/index.js +551 -26
  237. package/src/registry/worker-ids.js +45 -0
  238. package/src/release/build-package.js +0 -3
  239. package/src/release/docs-blocks.js +472 -0
  240. package/src/release/preflight.js +425 -0
  241. package/src/release/rehearse.js +401 -0
  242. package/src/release/release-workflow.js +733 -0
  243. package/src/release/sync-plugin-versions.js +43 -7
  244. package/src/runtime/advice/bench-gate.js +25 -0
  245. package/src/runtime/advice/cli.js +59 -16
  246. package/src/runtime/advice/client.js +32 -9
  247. package/src/runtime/advice/config.js +98 -133
  248. package/src/runtime/advice/doctor.js +1 -1
  249. package/src/runtime/advice/elevation.js +97 -0
  250. package/src/runtime/advice/endpoint-guard.js +9 -3
  251. package/src/runtime/advice/mode.js +37 -0
  252. package/src/runtime/advice/record.js +18 -5
  253. package/src/runtime/advice/registry.js +4 -1
  254. package/src/runtime/advice/shadow-config.js +229 -0
  255. package/src/runtime/advice/shadow-dispatcher.js +276 -0
  256. package/src/runtime/brief-rules.js +81 -0
  257. package/src/runtime/brief-tools.js +111 -34
  258. package/src/runtime/candidate.js +20 -11
  259. package/src/runtime/capabilities/executor.js +450 -0
  260. package/src/runtime/capabilities/loopback.js +41 -0
  261. package/src/runtime/capabilities/sanitizer.js +88 -0
  262. package/src/runtime/capability-router.js +135 -3
  263. package/src/runtime/checkouts.js +73 -11
  264. package/src/runtime/claims.js +13 -7
  265. package/src/runtime/cli.js +196 -19
  266. package/src/runtime/config-home.js +42 -0
  267. package/src/runtime/context/arbitrate.js +767 -0
  268. package/src/runtime/context/delivery-store.js +4 -6
  269. package/src/runtime/context/emission-rules.js +118 -0
  270. package/src/runtime/context/identity.js +227 -0
  271. package/src/runtime/context/pointer.js +67 -12
  272. package/src/runtime/context/primary-command.js +117 -0
  273. package/src/runtime/context/primary.js +344 -0
  274. package/src/runtime/context/summary-store.js +655 -0
  275. package/src/runtime/context-pack.js +158 -79
  276. package/src/runtime/decision-register.js +13 -4
  277. package/src/runtime/dispatch/admission.js +392 -0
  278. package/src/runtime/dispatch/attempt-end.js +274 -0
  279. package/src/runtime/dispatch/command.js +628 -0
  280. package/src/runtime/dispatch/controls.js +904 -0
  281. package/src/runtime/dispatch/envelope.js +276 -0
  282. package/src/runtime/dispatch/grants.js +310 -0
  283. package/src/runtime/dispatch/integrate.js +617 -0
  284. package/src/runtime/dispatch/loop.js +1835 -0
  285. package/src/runtime/dispatch/store.js +352 -0
  286. package/src/runtime/dispatch/verify-run.js +201 -0
  287. package/src/runtime/evidence-ledger.js +52 -7
  288. package/src/runtime/failure/capture.js +3 -1
  289. package/src/runtime/failure/cli.js +25 -9
  290. package/src/runtime/failure/home.js +57 -30
  291. package/src/runtime/failure/store.js +7 -2
  292. package/src/runtime/feature-pointer.js +174 -0
  293. package/src/runtime/feature-resolve.js +13 -2
  294. package/src/runtime/fleet/command.js +77 -0
  295. package/src/runtime/fleet/projects.js +178 -0
  296. package/src/runtime/fleet/tree.js +383 -0
  297. package/src/runtime/freshness.js +33 -3
  298. package/src/runtime/git-memo.js +46 -0
  299. package/src/runtime/guidance/ablation.js +427 -0
  300. package/src/runtime/health.js +624 -18
  301. package/src/runtime/history-compactor.js +13 -3
  302. package/src/runtime/implementation-gate.js +281 -26
  303. package/src/runtime/intake/engine.js +677 -0
  304. package/src/runtime/intake/index.js +136 -0
  305. package/src/runtime/intake/manifest.js +140 -0
  306. package/src/runtime/intake/taxonomy.js +206 -0
  307. package/src/runtime/inventory/coverage.js +1 -1
  308. package/src/runtime/inventory/index.js +1 -1
  309. package/src/runtime/inventory/precedence.js +2 -2
  310. package/src/runtime/inventory/siblings.js +6 -4
  311. package/src/runtime/knowledge/code-corpus.js +277 -0
  312. package/src/runtime/knowledge/code-lexical.js +130 -0
  313. package/src/runtime/knowledge/code-retrieval.js +307 -0
  314. package/src/runtime/knowledge/code-validation.js +106 -0
  315. package/src/runtime/knowledge/gp3-gate.js +74 -0
  316. package/src/runtime/knowledge/graph-adapter.js +288 -0
  317. package/src/runtime/knowledge/graph-fallback.js +394 -0
  318. package/src/runtime/knowledge/hybrid.js +136 -8
  319. package/src/runtime/knowledge/retrieval.js +34 -1
  320. package/src/runtime/knowledge/rtk-compress.js +77 -0
  321. package/src/runtime/knowledge/semble-bridge.py +106 -0
  322. package/src/runtime/knowledge/semble-lifecycle.js +114 -0
  323. package/src/runtime/knowledge/semble-provider.js +365 -0
  324. package/src/runtime/lifecycle/cli.js +88 -10
  325. package/src/runtime/lifecycle/event-store.js +8 -6
  326. package/src/runtime/lifecycle/followup.js +38 -7
  327. package/src/runtime/lifecycle/overview.js +26 -2
  328. package/src/runtime/lifecycle/report-store.js +8 -10
  329. package/src/runtime/lifecycle/retention.js +95 -31
  330. package/src/runtime/lifecycle/root.js +9 -1
  331. package/src/runtime/lifecycle/status.js +34 -17
  332. package/src/runtime/lifecycle/store-upkeep.js +56 -7
  333. package/src/runtime/mask.js +134 -7
  334. package/src/runtime/mcp-consultations.js +40 -6
  335. package/src/runtime/model-router.js +79 -7
  336. package/src/runtime/models/catalogue.js +191 -0
  337. package/src/runtime/models/command.js +153 -0
  338. package/src/runtime/off-words.js +12 -0
  339. package/src/runtime/outcome.js +2 -2
  340. package/src/runtime/packages/command.js +1009 -0
  341. package/src/runtime/packages/contributions.js +116 -0
  342. package/src/runtime/packages/digest.js +132 -0
  343. package/src/runtime/packages/fetch.js +182 -0
  344. package/src/runtime/packages/grants.js +192 -0
  345. package/src/runtime/packages/index-file.js +134 -0
  346. package/src/runtime/packages/instruction.js +90 -0
  347. package/src/runtime/packages/manifest.js +331 -0
  348. package/src/runtime/packages/points.js +487 -0
  349. package/src/runtime/packages/programs.js +216 -0
  350. package/src/runtime/packages/scaffold.js +277 -0
  351. package/src/runtime/packages/source.js +90 -0
  352. package/src/runtime/packages/state.js +164 -0
  353. package/src/runtime/packages/store.js +449 -0
  354. package/src/runtime/packages/tar.js +106 -0
  355. package/src/runtime/packages/view.js +406 -0
  356. package/src/runtime/plan-check.js +59 -0
  357. package/src/runtime/plan-content.js +324 -0
  358. package/src/runtime/policy-init.js +118 -0
  359. package/src/runtime/profiles/doctor.js +96 -0
  360. package/src/runtime/profiles/resolve.js +244 -0
  361. package/src/runtime/profiles/rollback.js +254 -0
  362. package/src/runtime/profiles/sampler.js +41 -0
  363. package/src/runtime/readiness-record.js +16 -11
  364. package/src/runtime/readiness.js +46 -5
  365. package/src/runtime/research-request.js +50 -25
  366. package/src/runtime/retrieval-plan.js +10 -30
  367. package/src/runtime/retrieval-record.js +75 -5
  368. package/src/runtime/review/command.js +246 -0
  369. package/src/runtime/review/dominance.js +68 -0
  370. package/src/runtime/review/findings.js +434 -0
  371. package/src/runtime/review/identity.js +255 -0
  372. package/src/runtime/review/protocol-command.js +109 -0
  373. package/src/runtime/review/protocols.js +405 -0
  374. package/src/runtime/review/resolve-protocol.js +302 -0
  375. package/src/runtime/review/start.js +530 -0
  376. package/src/runtime/roadmap/attendance.js +137 -0
  377. package/src/runtime/roadmap/facts.js +479 -0
  378. package/src/runtime/roadmap/gates.js +273 -0
  379. package/src/runtime/roadmap/loop.js +1719 -0
  380. package/src/runtime/roadmap/merge.js +390 -0
  381. package/src/runtime/roadmap/rows.js +1122 -0
  382. package/src/runtime/roadmap/seed.js +1063 -0
  383. package/src/runtime/roadmap/worktree-cleanup.js +147 -0
  384. package/src/runtime/rules-report.js +336 -0
  385. package/src/runtime/scaffold/artifacts.js +60 -9
  386. package/src/runtime/scaffold/generate.js +21 -9
  387. package/src/runtime/settings/autopilot.js +106 -0
  388. package/src/runtime/settings/cli.js +280 -0
  389. package/src/runtime/settings/config-write.js +314 -0
  390. package/src/runtime/settings/files.js +271 -0
  391. package/src/runtime/settings/legacy.js +154 -0
  392. package/src/runtime/settings/lexical.js +56 -0
  393. package/src/runtime/settings/prepared-run.js +269 -0
  394. package/src/runtime/settings/project-layers.js +118 -0
  395. package/src/runtime/settings/refs.js +288 -0
  396. package/src/runtime/settings/registry.js +553 -0
  397. package/src/runtime/settings/resolve.js +713 -0
  398. package/src/runtime/settings/shared.js +37 -0
  399. package/src/runtime/settings/worker-billing.js +107 -0
  400. package/src/runtime/settings/write.js +269 -0
  401. package/src/runtime/slot-command.js +78 -0
  402. package/src/runtime/slots.js +671 -0
  403. package/src/runtime/source-version.js +1 -0
  404. package/src/runtime/state-root.js +217 -0
  405. package/src/runtime/storage/cli.js +12 -5
  406. package/src/runtime/storage/engine.js +9 -2
  407. package/src/runtime/storage/registry.js +14 -1
  408. package/src/runtime/storage/roots.js +66 -16
  409. package/src/runtime/storage/rules.js +82 -10
  410. package/src/runtime/storage/upkeep.js +10 -2
  411. package/src/runtime/storage/work-status.js +245 -26
  412. package/src/runtime/task-attempts.js +589 -69
  413. package/src/runtime/task-classifier.js +85 -14
  414. package/src/runtime/task-graph.js +373 -0
  415. package/src/runtime/task-run.js +778 -0
  416. package/src/runtime/task-scope.js +176 -55
  417. package/src/runtime/task-sources.js +147 -0
  418. package/src/runtime/task-state.js +373 -64
  419. package/src/runtime/team/admission.js +301 -0
  420. package/src/runtime/team/envelope.js +324 -0
  421. package/src/runtime/team/review-guard.js +156 -0
  422. package/src/runtime/trace/analyze.js +427 -0
  423. package/src/runtime/trace/baseline.js +770 -0
  424. package/src/runtime/trace/ledger.js +63 -140
  425. package/src/runtime/trace/otlp-export.js +568 -0
  426. package/src/runtime/trace/otlp-map.js +481 -0
  427. package/src/runtime/trace/spans.js +745 -0
  428. package/src/runtime/trace/telemetry.js +225 -0
  429. package/src/runtime/trace/views.js +1 -0
  430. package/src/runtime/transcripts/command.js +98 -0
  431. package/src/runtime/transcripts/cursor.js +326 -0
  432. package/src/runtime/transcripts/read.js +126 -0
  433. package/src/runtime/transcripts/readers/claude-jsonl.js +74 -0
  434. package/src/runtime/transcripts/readers/codex-rollout.js +70 -0
  435. package/src/runtime/transcripts/readers/copilot-events.js +90 -0
  436. package/src/runtime/transcripts/readers/kiro-jsonl.js +77 -0
  437. package/src/runtime/transcripts/readers/pi-jsonl.js +59 -0
  438. package/src/runtime/transcripts/registry.js +161 -0
  439. package/src/runtime/transcripts/stats.js +161 -0
  440. package/src/runtime/verb-view.js +267 -0
  441. package/src/runtime/verification/check-key.js +628 -0
  442. package/src/runtime/verification/contract-runner.js +546 -11
  443. package/src/runtime/verification/engine.js +499 -54
  444. package/src/runtime/verification/impact.js +617 -0
  445. package/src/runtime/verification/inflight.js +205 -0
  446. package/src/runtime/verification/registry.js +176 -0
  447. package/src/runtime/verification/reuse.js +184 -0
  448. package/src/runtime/verification/telemetry-emit.js +254 -0
  449. package/src/runtime/verification/test-selection.js +67 -7
  450. package/src/runtime/worker/active.js +90 -0
  451. package/src/runtime/worker/cancel.js +222 -0
  452. package/src/runtime/worker/catalogue.js +122 -0
  453. package/src/runtime/worker/command.js +677 -0
  454. package/src/runtime/worker/evidence.js +288 -0
  455. package/src/runtime/worker/launch.js +256 -0
  456. package/src/runtime/worker/models.js +137 -0
  457. package/src/runtime/worker/parsers.js +218 -0
  458. package/src/runtime/worker/probes/index.js +706 -0
  459. package/src/runtime/worker/process-tree.js +415 -0
  460. package/src/runtime/worker/quota.js +43 -0
  461. package/src/runtime/worker/readiness.js +713 -0
  462. package/src/runtime/worker/run-store.js +166 -0
  463. package/src/runtime/worker/supervisor.js +541 -0
  464. package/src/runtime/worker/worktree-check.js +252 -0
  465. package/src/runtime/workflow-engine.js +5 -1
  466. package/src/runtime/workflow-orchestrator.js +610 -67
  467. package/src/runtime/worktree.js +149 -22
  468. package/src/state/index.js +1 -1
  469. package/src/state/lockfile.js +64 -10
  470. package/src/state/run-lock.js +1 -1
  471. package/types/node-commonjs.d.ts +106 -7
  472. package/core/harnesses/claude/hooks/pre-bash-guard.sh +0 -10
  473. package/core/harnesses/codex/hooks/pre-bash-guard.sh +0 -10
  474. package/core/harnesses/gemini/hooks/hooks.json +0 -77
  475. package/core/harnesses/gemini/hooks/lib.sh +0 -302
  476. package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +0 -10
  477. package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
  478. package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -10
  479. package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
  480. package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -10
  481. package/core/harnesses/gemini/hooks/session-end.impl.sh +0 -89
  482. package/core/harnesses/gemini/hooks/session-end.sh +0 -5
  483. package/core/harnesses/gemini/hooks/session-start.sh +0 -7
  484. package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
  485. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +0 -24
  486. package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +0 -91
  487. package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +0 -378
  488. package/core/shared/guidance/FLAGS.md +0 -25
  489. package/core/shared/guidance/rules/RULE_01_SAFETY.md +0 -25
  490. package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +0 -23
  491. package/core/shared/guidance/rules/RULE_03_QUALITY.md +0 -14
  492. package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +0 -46
  493. package/src/adapters/gemini/hooks.js +0 -251
  494. package/src/adapters/gemini/index.js +0 -429
@@ -0,0 +1,1122 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Roadmap rows: pure data model, seed parser, status derivation, pick table,
5
+ * markdown table render, and integrity check (feature 108, IC-001, IC-002, IC-003; FR-001, FR-002, FR-003).
6
+ */
7
+
8
+ const fs = require('node:fs');
9
+ const path = require('node:path');
10
+
11
+ function safeText(value ) {
12
+ let text ;
13
+ if (typeof value === 'string') {
14
+ try {
15
+ text = JSON.stringify(value).slice(1, -1)
16
+ .replace(/[\p{Cc}\p{Cf}\u2028\u2029]/gu, (c ) => { const cp = c.codePointAt(0) ; return cp > 0xffff ? `\\u{${cp.toString(16)}}` : `\\u${cp.toString(16).padStart(4, '0')}`; });
17
+ } catch {
18
+ text = '';
19
+ }
20
+ } else {
21
+ try {
22
+ const encoded = JSON.stringify(value);
23
+ text = encoded === undefined ? 'undefined' : encoded;
24
+ } catch {
25
+ text = '[unserializable]';
26
+ }
27
+ }
28
+ return text.length > 60 ? `${text.slice(0, 57)}...` : text;
29
+ }
30
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+
47
+
48
+
49
+
50
+
51
+
52
+
53
+
54
+
55
+
56
+
57
+
58
+
59
+
60
+
61
+
62
+
63
+
64
+
65
+
66
+
67
+
68
+
69
+
70
+
71
+
72
+
73
+
74
+
75
+
76
+
77
+
78
+
79
+
80
+
81
+
82
+
83
+
84
+
85
+
86
+
87
+
88
+
89
+
90
+
91
+
92
+
93
+
94
+
95
+
96
+
97
+
98
+
99
+
100
+
101
+
102
+
103
+
104
+
105
+
106
+
107
+
108
+
109
+
110
+
111
+
112
+
113
+
114
+
115
+
116
+
117
+
118
+
119
+
120
+
121
+
122
+
123
+
124
+
125
+
126
+ const ID_PATTERN = /^[0-9]{3}([a-z]|-[A-Z])?$/;
127
+ const FORBIDDEN_KEYS = new Set(['status', 'phase', 'evidence']);
128
+
129
+ const SEED_RULES = [
130
+ 'seed-invalid-json',
131
+ 'seed-invalid',
132
+ 'seed-unknown-key',
133
+ 'seed-typed-status',
134
+ 'slug-id-mismatch',
135
+ 'duplicate-id',
136
+ 'class-unknown',
137
+ 'needs-unresolved',
138
+ 'needs-cycle',
139
+ 'id-exists',
140
+ 'row-not-found',
141
+ 'row-started',
142
+ 'row-not-proposed',
143
+ ] ;
144
+
145
+ const TOP_KEYS = ['schema', 'layout', 'rows'];
146
+ const ROW_KEYS = [
147
+ 'id',
148
+ 'slug',
149
+ 'title',
150
+ 'class',
151
+ 'needs',
152
+ 'writeSet',
153
+ 'requires',
154
+ 'hold',
155
+ 'proposed',
156
+ 'verifyArgv',
157
+ 'mergeCommit',
158
+ 'intent',
159
+ 'acceptance',
160
+ ];
161
+
162
+ let _cachedRegistryClasses = null;
163
+
164
+ function registryClasses() {
165
+ if (_cachedRegistryClasses === null) {
166
+ const registryPath = path.join(__dirname, '../../../core/registry/workflows.json');
167
+ const doc = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
168
+ _cachedRegistryClasses = Object.keys(doc.classes || {});
169
+ }
170
+ return _cachedRegistryClasses;
171
+ }
172
+
173
+ class SeedParseError extends Error {
174
+ code ;
175
+ rowId ;
176
+ constructor(code , message , rowId ) {
177
+ super(message);
178
+ this.name = 'SeedParseError';
179
+ this.code = code;
180
+ this.rowId = rowId;
181
+ }
182
+ }
183
+
184
+
185
+
186
+
187
+
188
+
189
+
190
+
191
+ /**
192
+ * Validates a roadmap seed document (store or draft) without throwing.
193
+ * Returns every finding in stable IC-006 order.
194
+ */
195
+ function seedFindings(
196
+ rawDoc ,
197
+ opts
198
+ ) {
199
+ let doc ;
200
+ if (typeof rawDoc === 'string') {
201
+ try {
202
+ doc = JSON.parse(rawDoc);
203
+ } catch (err ) {
204
+ return [
205
+ {
206
+ rule: 'seed-invalid-json',
207
+ message: `Seed is not valid JSON: ${safeText(err.message)}`,
208
+ },
209
+ ];
210
+ }
211
+ } else {
212
+ doc = rawDoc;
213
+ }
214
+
215
+ if (!doc || typeof doc !== 'object' || Array.isArray(doc)) {
216
+ return [
217
+ {
218
+ rule: 'seed-invalid',
219
+ message: 'Seed must be an object',
220
+ },
221
+ ];
222
+ }
223
+
224
+ const isDraft = Boolean(opts?.draft);
225
+ const items = [];
226
+ let seq = 0;
227
+
228
+ const add = (
229
+ rowPos ,
230
+ rule ,
231
+ message ,
232
+ rowId ,
233
+ rowIds
234
+ ) => {
235
+ const finding = { rule, message };
236
+ if (rowId !== undefined) finding.rowId = rowId;
237
+ if (rowIds !== undefined) finding.rowIds = rowIds;
238
+ items.push({
239
+ rowPos,
240
+ ruleIdx: SEED_RULES.indexOf(rule),
241
+ seq: ++seq,
242
+ finding,
243
+ });
244
+ };
245
+
246
+ // Document level checks (rowPos = -1)
247
+ // Top-level key status/phase/evidence -> seed-typed-status
248
+ for (const k of Object.keys(doc)) {
249
+ if (FORBIDDEN_KEYS.has(k)) {
250
+ add(-1, 'seed-typed-status', `Top-level seed declares forbidden key '${safeText(k)}'`);
251
+ }
252
+ }
253
+
254
+ // Any other key not in TOP_KEYS -> seed-unknown-key
255
+ // In draft mode TOP_KEYS is only schema and rows, so layout is seed-unknown-key
256
+ const allowedTop = isDraft ? new Set(['schema', 'rows']) : new Set(TOP_KEYS);
257
+ for (const k of Object.keys(doc)) {
258
+ if (!FORBIDDEN_KEYS.has(k) && !allowedTop.has(k)) {
259
+ add(-1, 'seed-unknown-key', `Top-level seed declares unknown key '${safeText(k)}'`);
260
+ }
261
+ }
262
+
263
+ // In draft mode additionally schema must be exactly 1
264
+ if (isDraft && doc.schema !== 1) {
265
+ add(-1, 'seed-invalid', `Draft 'schema' must be 1, got ${safeText(doc.schema)}`);
266
+ } else if (!isDraft && doc.schema !== 1) {
267
+ add(-1, 'seed-invalid', "Seed 'schema' must be 1");
268
+ }
269
+
270
+ // rows not an array -> seed-invalid ("rows must be an array")
271
+ if (!Array.isArray(doc.rows)) {
272
+ add(-1, 'seed-invalid', "Seed 'rows' must be an array");
273
+ items.sort((a, b) => a.rowPos - b.rowPos || a.ruleIdx - b.ruleIdx || a.seq - b.seq);
274
+ return items.map((x) => x.finding);
275
+ }
276
+
277
+ // In draft mode additionally rows non-empty
278
+ if (isDraft && doc.rows.length === 0) {
279
+ add(-1, 'seed-invalid', "Draft 'rows' must not be empty");
280
+ }
281
+
282
+ // Row level checks
283
+ const rowKeySet = new Set(ROW_KEYS);
284
+ const validClasses = opts?.classes ?? registryClasses();
285
+ const validClassSet = new Set(validClasses);
286
+
287
+ const seenIds = new Set ();
288
+ const allRowIds = new Set ();
289
+ for (const r of doc.rows) {
290
+ if (r && typeof r === 'object' && typeof r.id === 'string') {
291
+ allRowIds.add(r.id);
292
+ }
293
+ }
294
+
295
+ for (let idx = 0; idx < doc.rows.length; idx++) {
296
+ const row = doc.rows[idx];
297
+ if (!row || typeof row !== 'object' || Array.isArray(row)) {
298
+ add(idx, 'seed-invalid', 'Each row must be an object');
299
+ continue;
300
+ }
301
+
302
+ const rowId = typeof row.id === 'string' ? row.id : undefined;
303
+
304
+ // 1. seed-invalid:
305
+ // title: must be a non-empty single-line string (no \r \n U+2028 U+2029) of at most 120 characters (count with Array.from)
306
+ if (
307
+ typeof row.title !== 'string' ||
308
+ row.title.trim().length === 0 ||
309
+ Array.from(row.title).length > 120 ||
310
+ /[\r\n\u2028\u2029]/.test(row.title)
311
+ ) {
312
+ add(
313
+ idx,
314
+ 'seed-invalid',
315
+ `Row '${safeText(rowId ?? 'unknown')}' title must be a non-empty single-line string of at most 120 characters`,
316
+ rowId
317
+ );
318
+ }
319
+
320
+ // needs: if present must be an array of strings (the key may be absent)
321
+ if (
322
+ row.needs !== undefined &&
323
+ (!Array.isArray(row.needs) || !row.needs.every((n ) => typeof n === 'string'))
324
+ ) {
325
+ add(
326
+ idx,
327
+ 'seed-invalid',
328
+ `Row '${safeText(rowId ?? 'unknown')}' needs must be an array of strings`,
329
+ rowId
330
+ );
331
+ }
332
+
333
+ // writeSet: if present, an array of 1 to 32 canonical repo-relative paths.
334
+ if (row.writeSet !== undefined && validWriteSet(row.writeSet) === null) {
335
+ add(
336
+ idx,
337
+ 'seed-invalid',
338
+ `Row '${safeText(rowId ?? 'unknown')}' writeSet must be 1 to 32 repo-relative paths with no leading slash, drive letter, backslash, empty, '.' or '..' segment`,
339
+ rowId
340
+ );
341
+ }
342
+
343
+ // requires: if present an array of non-empty strings
344
+ if (
345
+ row.requires !== undefined &&
346
+ (!Array.isArray(row.requires) ||
347
+ !row.requires.every((s ) => typeof s === 'string' && s.trim().length > 0))
348
+ ) {
349
+ add(
350
+ idx,
351
+ 'seed-invalid',
352
+ `Row '${safeText(rowId ?? 'unknown')}' requires must be an array of non-empty strings`,
353
+ rowId
354
+ );
355
+ }
356
+
357
+ // hold: as today (non-empty single-line string at most 120 chars)
358
+ if (
359
+ row.hold !== undefined &&
360
+ (typeof row.hold !== 'string' ||
361
+ row.hold.trim().length === 0 ||
362
+ Array.from(row.hold).length > 120 ||
363
+ /[\r\n\u2028\u2029]/.test(row.hold))
364
+ ) {
365
+ add(
366
+ idx,
367
+ 'seed-invalid',
368
+ `Row '${safeText(rowId ?? 'unknown')}' hold must be a non-empty single-line string of at most 120 characters`,
369
+ rowId
370
+ );
371
+ }
372
+
373
+ // proposed (maint-thin): only `true`; absent means ready. Only seed apply/amend writes it, so a draft may not
374
+ // declare it at all (a draft cannot pre-mark a row, and no model can launder a promotion).
375
+ if (isDraft && 'proposed' in row) {
376
+ add(
377
+ idx,
378
+ 'seed-invalid',
379
+ `Draft row '${safeText(rowId ?? 'unknown')}' must not declare proposed; seed apply marks it`,
380
+ rowId
381
+ );
382
+ } else if (row.proposed !== undefined && row.proposed !== true) {
383
+ add(
384
+ idx,
385
+ 'seed-invalid',
386
+ `Row '${safeText(rowId ?? 'unknown')}' proposed must be true when present`,
387
+ rowId
388
+ );
389
+ }
390
+
391
+ // verifyArgv: as today (1 to 8 non-empty strings)
392
+ if (
393
+ row.verifyArgv !== undefined &&
394
+ (!Array.isArray(row.verifyArgv) ||
395
+ row.verifyArgv.length === 0 ||
396
+ row.verifyArgv.length > 8 ||
397
+ !row.verifyArgv.every((arg ) => typeof arg === 'string' && arg.trim().length > 0))
398
+ ) {
399
+ add(
400
+ idx,
401
+ 'seed-invalid',
402
+ `Row '${safeText(rowId ?? 'unknown')}' verifyArgv must be 1 to 8 non-empty strings`,
403
+ rowId
404
+ );
405
+ }
406
+
407
+ // mergeCommit: if present a 40-hex lowercase string (store mode only)
408
+ if (!isDraft && row.mergeCommit !== undefined) {
409
+ if (typeof row.mergeCommit !== 'string' || !/^[0-9a-f]{40}$/.test(row.mergeCommit)) {
410
+ add(
411
+ idx,
412
+ 'seed-invalid',
413
+ `Row '${safeText(rowId ?? 'unknown')}' mergeCommit must be a 40-hex lowercase string`,
414
+ rowId
415
+ );
416
+ }
417
+ }
418
+
419
+ // intent: if present a non-empty single-line string at most 200 characters
420
+ if (
421
+ row.intent !== undefined &&
422
+ (typeof row.intent !== 'string' ||
423
+ row.intent.trim().length === 0 ||
424
+ Array.from(row.intent).length > 200 ||
425
+ /[\r\n\u2028\u2029]/.test(row.intent))
426
+ ) {
427
+ add(
428
+ idx,
429
+ 'seed-invalid',
430
+ `Row '${safeText(rowId ?? 'unknown')}' intent must be a non-empty single-line string of at most 200 characters`,
431
+ rowId
432
+ );
433
+ }
434
+
435
+ // acceptance: if present an array of 1 to 8 non-empty single-line strings, each at most 200 characters
436
+ if (
437
+ row.acceptance !== undefined &&
438
+ (!Array.isArray(row.acceptance) ||
439
+ row.acceptance.length < 1 ||
440
+ row.acceptance.length > 8 ||
441
+ !row.acceptance.every(
442
+ (s ) =>
443
+ typeof s === 'string' &&
444
+ s.trim().length > 0 &&
445
+ Array.from(s).length <= 200 &&
446
+ !/[\r\n\u2028\u2029]/.test(s)
447
+ ))
448
+ ) {
449
+ add(
450
+ idx,
451
+ 'seed-invalid',
452
+ `Row '${safeText(rowId ?? 'unknown')}' acceptance must be an array of 1 to 8 non-empty single-line strings of at most 200 characters`,
453
+ rowId
454
+ );
455
+ }
456
+
457
+ // 2. seed-unknown-key: a key not in ROW_KEYS (excluding forbidden keys and draft mergeCommit)
458
+ for (const k of Object.keys(row)) {
459
+ if (!FORBIDDEN_KEYS.has(k) && !(isDraft && k === 'mergeCommit') && !rowKeySet.has(k)) {
460
+ add(idx, 'seed-unknown-key', `Row '${safeText(rowId ?? 'unknown')}' declares unknown key '${safeText(k)}'`, rowId);
461
+ }
462
+ }
463
+
464
+ // 3. seed-typed-status: key status/phase/evidence; in draft mode also a mergeCommit key
465
+ for (const k of Object.keys(row)) {
466
+ if (FORBIDDEN_KEYS.has(k)) {
467
+ add(idx, 'seed-typed-status', `Row '${safeText(rowId ?? 'unknown')}' declares forbidden key '${safeText(k)}'`, rowId);
468
+ }
469
+ }
470
+ if (isDraft && 'mergeCommit' in row) {
471
+ add(idx, 'seed-typed-status', `Draft row '${safeText(rowId ?? 'unknown')}' declares forbidden key 'mergeCommit'`, rowId);
472
+ }
473
+
474
+ // 4. slug-id-mismatch: id not matching pattern or slug not starting with id-
475
+ if (typeof row.id !== 'string' || !ID_PATTERN.test(row.id)) {
476
+ add(
477
+ idx,
478
+ 'slug-id-mismatch',
479
+ `Row id '${safeText(row.id)}' does not match pattern ^[0-9]{3}([a-z]|-[A-Z])?$`,
480
+ rowId
481
+ );
482
+ } else if (typeof row.slug !== 'string' || !row.slug.startsWith(`${row.id}-`)) {
483
+ add(
484
+ idx,
485
+ 'slug-id-mismatch',
486
+ `Row slug '${safeText(row.slug)}' must start with '${safeText(row.id)}-'`,
487
+ rowId
488
+ );
489
+ }
490
+
491
+ // 5. duplicate-id: reported on each repeated occurrence of an already-seen id (the later row)
492
+ if (typeof row.id === 'string' && ID_PATTERN.test(row.id)) {
493
+ if (seenIds.has(row.id)) {
494
+ add(idx, 'duplicate-id', `Duplicate row id: ${safeText(row.id)}`, rowId);
495
+ } else {
496
+ seenIds.add(row.id);
497
+ }
498
+ }
499
+
500
+ // 6. class-unknown: class not a string in opts.classes ?? registryClasses(); message lists the valid set
501
+ if (typeof row.class !== 'string' || !validClassSet.has(row.class)) {
502
+ add(
503
+ idx,
504
+ 'class-unknown',
505
+ `Row '${safeText(rowId ?? 'unknown')}' class '${safeText(row.class)}' is unknown; valid classes: ${validClasses.join(', ')}`,
506
+ rowId
507
+ );
508
+ }
509
+
510
+ // 7. needs-unresolved (skipped in draft mode)
511
+ if (!isDraft && Array.isArray(row.needs)) {
512
+ for (const need of row.needs) {
513
+ if (typeof need === 'string' && !allRowIds.has(need)) {
514
+ add(
515
+ idx,
516
+ 'needs-unresolved',
517
+ `Row '${safeText(rowId ?? 'unknown')}' declares unresolved need '${safeText(need)}'`,
518
+ rowId
519
+ );
520
+ }
521
+ }
522
+ }
523
+ }
524
+
525
+ // 8. needs-cycle (skipped in draft mode)
526
+ if (!isDraft) {
527
+ const firstRowById = new Map ();
528
+ const firstRowList = [];
529
+
530
+ for (let idx = 0; idx < doc.rows.length; idx++) {
531
+ const r = doc.rows[idx];
532
+ if (r && typeof r === 'object' && typeof r.id === 'string') {
533
+ if (!firstRowById.has(r.id)) {
534
+ const entry = { row: r, docPos: idx };
535
+ firstRowById.set(r.id, entry);
536
+ firstRowList.push(entry);
537
+ }
538
+ }
539
+ }
540
+
541
+ const WHITE = 0;
542
+ const GRAY = 1;
543
+ const BLACK = 2;
544
+ const color = new Map ();
545
+ const callStack = [];
546
+ const seenCycleKeys = new Set ();
547
+
548
+ const dfs = (u ) => {
549
+ color.set(u, GRAY);
550
+ callStack.push(u);
551
+
552
+ const entry = firstRowById.get(u);
553
+ const uNeeds = entry && Array.isArray(entry.row.needs) ? entry.row.needs : [];
554
+
555
+ for (const v of uNeeds) {
556
+ if (typeof v !== 'string' || !firstRowById.has(v)) continue;
557
+ const vColor = color.get(v) ?? WHITE;
558
+ if (vColor === GRAY) {
559
+ // Cycle found
560
+ const cycleStartIdx = callStack.indexOf(v);
561
+ const cyclePath = callStack.slice(cycleStartIdx);
562
+
563
+ let minPos = Infinity;
564
+ let minIdx = 0;
565
+ for (let i = 0; i < cyclePath.length; i++) {
566
+ const pos = firstRowById.get(cyclePath[i]) .docPos;
567
+ if (pos < minPos) {
568
+ minPos = pos;
569
+ minIdx = i;
570
+ }
571
+ }
572
+ const canonicalPath = cyclePath.slice(minIdx).concat(cyclePath.slice(0, minIdx));
573
+ const cycleKey = canonicalPath.join('->');
574
+ if (!seenCycleKeys.has(cycleKey)) {
575
+ seenCycleKeys.add(cycleKey);
576
+ const msg = `needs cycle ${canonicalPath.map(safeText).join(' -> ')} -> ${safeText(canonicalPath[0])}`;
577
+ add(minPos, 'needs-cycle', msg, undefined, canonicalPath);
578
+ }
579
+ } else if (vColor === WHITE) {
580
+ dfs(v);
581
+ }
582
+ }
583
+
584
+ callStack.pop();
585
+ color.set(u, BLACK);
586
+ };
587
+
588
+ for (const entry of firstRowList) {
589
+ if ((color.get(entry.row.id) ?? WHITE) === WHITE) {
590
+ dfs(entry.row.id);
591
+ }
592
+ }
593
+ }
594
+
595
+ // Sort findings: document-level (-1) first, then by rowPos, then by ruleIdx, then seq
596
+ items.sort((a, b) => a.rowPos - b.rowPos || a.ruleIdx - b.ruleIdx || a.seq - b.seq);
597
+ return items.map((x) => x.finding);
598
+ }
599
+
600
+ /**
601
+ * Validates and parses rows.json seed data.
602
+ * Throws SeedParseError for the first finding if any finding exists.
603
+ */
604
+ function parseSeed(raw ) {
605
+ const findings = seedFindings(raw, {});
606
+ if (findings.length > 0) {
607
+ const first = findings[0];
608
+ throw new SeedParseError(first.rule, first.message, first.rowId ?? first.rowIds?.[0]);
609
+ }
610
+ const doc = typeof raw === 'string' ? JSON.parse(raw) : raw;
611
+ return doc ;
612
+ }
613
+
614
+ function normalizePath(p ) {
615
+ return p.replace(/^\.\//, '').replace(/^\//, '');
616
+ }
617
+
618
+ function pathsOverlap(p1 , p2 ) {
619
+ const n1 = normalizePath(p1);
620
+ const n2 = normalizePath(p2);
621
+ if (n1 === n2) return true;
622
+ if (n1.endsWith('/')) {
623
+ if (n2.startsWith(n1)) return true;
624
+ } else {
625
+ if (n2.startsWith(`${n1}/`)) return true;
626
+ }
627
+ if (n2.endsWith('/')) {
628
+ if (n1.startsWith(n2)) return true;
629
+ } else {
630
+ if (n1.startsWith(`${n2}/`)) return true;
631
+ }
632
+ return false;
633
+ }
634
+
635
+ function validWriteSet(value ) {
636
+ if (!Array.isArray(value) || value.length < 1 || value.length > 32) return null;
637
+ const canonical = (entry ) => {
638
+ if (typeof entry !== 'string' || entry.trim().length === 0 || entry.startsWith('/') || /^[A-Za-z]:/.test(entry) || entry.includes('\\')) {
639
+ return false;
640
+ }
641
+ const withoutDirectoryMarker = entry.endsWith('/') ? entry.slice(0, -1) : entry;
642
+ return withoutDirectoryMarker.split('/').every((segment) => segment !== '' && segment !== '.' && segment !== '..');
643
+ };
644
+ return value.every(canonical) ? value : null;
645
+ }
646
+
647
+ function writeSetsOverlap(ws1 , ws2 ) {
648
+ if (!ws1 || !ws2 || ws1.length === 0 || ws2.length === 0) return false;
649
+ for (const p1 of ws1) {
650
+ for (const p2 of ws2) {
651
+ if (pathsOverlap(p1, p2)) return true;
652
+ }
653
+ }
654
+ return false;
655
+ }
656
+
657
+ /**
658
+ * Derives the status of each row from git facts only (merges and `feat/<slug>` branches); the run
659
+ * record's holds and pauses are machine state, which `status` shows separately (C11, C19).
660
+ * Trailed merges in git develop history take precedence; subjects are NEVER evidence.
661
+ */
662
+ function deriveStatus(
663
+ rows ,
664
+ gitFacts = {}
665
+ ) {
666
+ const statuses = new Map ();
667
+ const rowStatus = {};
668
+
669
+ // Group 2-parent first-parent merges on develop by DoFlow-Row trailer value
670
+ const trailerMergesByRow = new Map ();
671
+ for (const m of gitFacts.merges || []) {
672
+ if (m.parents && m.parents.length === 2 && m.rowTrailer) {
673
+ const rowId = m.rowTrailer.trim();
674
+ if (!trailerMergesByRow.has(rowId)) trailerMergesByRow.set(rowId, []);
675
+ trailerMergesByRow.get(rowId) .push(m);
676
+ }
677
+ }
678
+
679
+ for (const row of rows) {
680
+ let status = 'not-started';
681
+ let evidence = null;
682
+ let holdReason = null;
683
+
684
+ const trailerMerges = trailerMergesByRow.get(row.id) || [];
685
+ if (trailerMerges.length > 0) {
686
+ status = 'merged';
687
+ const m = trailerMerges[0];
688
+ evidence = {
689
+ kind: 'trailer',
690
+ sha: m.sha,
691
+ shortSha: m.sha.slice(0, 7),
692
+ };
693
+ } else if (row.mergeCommit) {
694
+ // Must be a 2-parent first-parent merge in git develop history
695
+ const matchingMerge = (gitFacts.merges || []).find(
696
+ (m) => m.sha === row.mergeCommit && m.parents && m.parents.length === 2
697
+ );
698
+ if (matchingMerge) {
699
+ status = 'merged';
700
+ evidence = {
701
+ kind: 'seeded',
702
+ sha: row.mergeCommit,
703
+ shortSha: row.mergeCommit.slice(0, 7),
704
+ };
705
+ }
706
+ }
707
+
708
+ if (status !== 'merged') {
709
+ const branchName = `feat/${row.slug}`;
710
+ const branchExists =
711
+ (gitFacts.branches || []).includes(branchName) ||
712
+ (gitFacts.branches || []).includes(`refs/heads/${branchName}`);
713
+
714
+ if (branchExists) status = 'in-flight';
715
+ // maint-thin: an unstarted row a seed write proposed waits for a person's promote.
716
+ else if (row.proposed === true) status = 'proposed';
717
+ }
718
+
719
+ // A seed-level hold is reported for unmerged rows.
720
+ if (status !== 'merged' && row.hold) holdReason = row.hold;
721
+
722
+ const info = {
723
+ rowId: row.id,
724
+ status,
725
+ evidence,
726
+ holdReason,
727
+ };
728
+ statuses.set(row.id, info);
729
+ rowStatus[row.id] = status;
730
+ }
731
+
732
+ return { statuses, rowStatus };
733
+ }
734
+
735
+ /**
736
+ * The task classes the roadmap loop runs: the feature chain, and the four classes that take a
737
+ * gate-b-only policy (core/registry/gates.json). research, review and operations stay out: they
738
+ * produce no change the loop could merge. dependency-change stays out too: a change to any
739
+ * package.json dependency key is always denied `protected-path:package.json` (gates.ts), so such a
740
+ * row could never merge without a person; a user ruling reopens it.
741
+ */
742
+ const LOOP_CLASSES = Object.freeze(['feature', 'bug', 'refactor', 'documentation', 'trivial-edit']);
743
+
744
+ /**
745
+ * S1 Pick table: chooses the next ready row in seed order.
746
+ * - Ready only when all needs are merged
747
+ * - Write set disjoint from all in-flight rows
748
+ * - WIP bound (default 2)
749
+ * - Row without a write set runs alone
750
+ * - A class outside LOOP_CLASSES (research, review, operations, dependency-change, anything else) is class-unsupported
751
+ * - Yields one reason per skipped row
752
+ */
753
+ function pick(
754
+ rows ,
755
+ statuses ,
756
+ options = {}
757
+ ) {
758
+ const wip = options.wip ?? 2;
759
+ const getStatus = (id ) => {
760
+ if (statuses instanceof Map) {
761
+ return statuses.get(id)?.status || 'not-started';
762
+ }
763
+ const val = statuses[id];
764
+ if (typeof val === 'string') return val;
765
+ return val?.status || 'not-started';
766
+ };
767
+
768
+ const inFlightRows = rows.filter((r) => getStatus(r.id) === 'in-flight');
769
+ const inFlightCount = inFlightRows.length;
770
+ const exclusiveInFlight = inFlightRows.some((r) => !r.writeSet || r.writeSet.length === 0);
771
+
772
+ const skips = {};
773
+ let picked = null;
774
+
775
+ for (const row of rows) {
776
+ if (row.hold) {
777
+ skips[row.id] = 'row-held';
778
+ continue;
779
+ }
780
+
781
+ const status = getStatus(row.id);
782
+ if (status === 'merged' || status === 'in-flight') {
783
+ continue;
784
+ }
785
+
786
+ // maint-thin: autopilot never picks a proposed row; a person promotes it first.
787
+ if (row.proposed === true) {
788
+ skips[row.id] = 'row-proposed';
789
+ continue;
790
+ }
791
+
792
+ // Class check
793
+ if (!LOOP_CLASSES.includes(row.class)) {
794
+ skips[row.id] = 'class-unsupported';
795
+ continue;
796
+ }
797
+
798
+ // Dependencies check
799
+ const hasUnmergedNeed = (row.needs || []).some((needId) => getStatus(needId) !== 'merged');
800
+ if (hasUnmergedNeed) {
801
+ skips[row.id] = 'need-unmerged';
802
+ continue;
803
+ }
804
+
805
+ // Exclusive in-flight check (another row running alone)
806
+ if (exclusiveInFlight) {
807
+ skips[row.id] = 'exclusive-row-in-flight';
808
+ continue;
809
+ }
810
+
811
+ // Candidate has no write set and requires running alone
812
+ const hasNoWriteSet = !row.writeSet || row.writeSet.length === 0;
813
+ if (hasNoWriteSet && inFlightCount > 0) {
814
+ skips[row.id] = 'needs-exclusive';
815
+ continue;
816
+ }
817
+
818
+ // Write set collision check
819
+ const overlapping = inFlightRows.find((ifr) => writeSetsOverlap(row.writeSet, ifr.writeSet));
820
+ if (overlapping) {
821
+ skips[row.id] = `write-set-overlap:${overlapping.id}`;
822
+ continue;
823
+ }
824
+
825
+ // WIP cap check
826
+ if (inFlightCount >= wip) {
827
+ skips[row.id] = 'wip-full';
828
+ continue;
829
+ }
830
+
831
+ // Row is ready!
832
+ if (!picked) {
833
+ picked = row;
834
+ }
835
+ }
836
+
837
+ return { picked, skips };
838
+ }
839
+
840
+ /**
841
+ * Renders byte-stable ROADMAP.md markdown table.
842
+ * Columns: Row, Title, Class, Needs, Status, Evidence, Next.
843
+ */
844
+ function render(
845
+ rows ,
846
+ statuses
847
+ ) {
848
+ const getInfo = (id ) => {
849
+ if (statuses instanceof Map) {
850
+ return statuses.get(id) || { rowId: id, status: 'not-started', evidence: null };
851
+ }
852
+ return statuses[id] || { rowId: id, status: 'not-started', evidence: null };
853
+ };
854
+
855
+ const pickRes = pick(rows, statuses);
856
+
857
+ const lines = [
858
+ '# Roadmap',
859
+ '',
860
+ '| Row | Title | Class | Needs | Status | Evidence | Next |',
861
+ '|---|---|---|---|---|---|---|',
862
+ ];
863
+
864
+ for (const row of rows) {
865
+ const info = getInfo(row.id);
866
+ const needsStr = row.needs && row.needs.length > 0 ? row.needs.join(', ') : '-';
867
+ const statusStr = info.status;
868
+
869
+ let evidenceStr = '-';
870
+ if (info.evidence) {
871
+ evidenceStr = `${info.evidence.shortSha} (${info.evidence.kind})`;
872
+ }
873
+
874
+ let nextStr = '-';
875
+ if (info.status === 'merged') {
876
+ nextStr = '-';
877
+ } else if (row.hold) {
878
+ nextStr = `held: ${row.hold}`;
879
+ } else if (info.status === 'in-flight') {
880
+ nextStr = info.holdReason || '-';
881
+ } else {
882
+ // not-started
883
+ if (pickRes.picked && pickRes.picked.id === row.id) {
884
+ nextStr = 'pick';
885
+ } else if (pickRes.skips[row.id]) {
886
+ nextStr = pickRes.skips[row.id];
887
+ }
888
+ }
889
+
890
+ lines.push(`| ${row.id} | ${row.title} | ${row.class} | ${needsStr} | ${statusStr} | ${evidenceStr} | ${nextStr} |`);
891
+ }
892
+
893
+ lines.push('');
894
+ return lines.join('\n');
895
+ }
896
+
897
+ /**
898
+ * Writes rendered table to disk using atomic temp+rename only when bytes differ.
899
+ */
900
+ function renderRoadmapFile({
901
+ roadmapPath,
902
+ content,
903
+ fsImpl = fs,
904
+ }
905
+
906
+
907
+
908
+ ) {
909
+ let existing = null;
910
+ try {
911
+ existing = fsImpl.readFileSync(roadmapPath, 'utf8');
912
+ } catch {
913
+ existing = null;
914
+ }
915
+
916
+ const bytes = Buffer.byteLength(content, 'utf8');
917
+ if (existing === content) {
918
+ return { written: false, bytes };
919
+ }
920
+
921
+ const dir = path.dirname(roadmapPath);
922
+ fsImpl.mkdirSync(dir, { recursive: true });
923
+ const tmpPath = path.join(dir, `.tmp-roadmap-${process.pid}-${Date.now()}`);
924
+ try {
925
+ fsImpl.writeFileSync(tmpPath, content, 'utf8');
926
+ fsImpl.renameSync(tmpPath, roadmapPath);
927
+ } catch (error) {
928
+ try { fsImpl.unlinkSync(tmpPath); } catch { /* ignore temp cleanup failure */ }
929
+ throw error;
930
+ }
931
+ return { written: true, bytes };
932
+ }
933
+
934
+ /**
935
+ * Performs IC-003 integrity check on seed, git facts, and rendered table:
936
+ * 1. render-drift
937
+ * 2. duplicate-merge
938
+ * 3. trailer-unknown-row
939
+ * 4. commit-claimed-twice
940
+ * 5. trailered-and-seeded
941
+ * 6. pointer-not-merge
942
+ * 7. needs-unresolved
943
+ * 8. duplicate-id
944
+ * 9. slug-id-mismatch
945
+ * Plus: seed-typed-status
946
+ */
947
+ function check({
948
+ rows = [],
949
+ gitFacts = {},
950
+ rendered = null,
951
+ seedRaw = null,
952
+ }
953
+
954
+
955
+
956
+
957
+ ) {
958
+ const findings = [];
959
+ const offendingRowIdsSet = new Set ();
960
+
961
+ const addFinding = (finding ) => {
962
+ findings.push(finding);
963
+ if (finding.rowId) offendingRowIdsSet.add(finding.rowId);
964
+ if (finding.rowIds) {
965
+ for (const id of finding.rowIds) offendingRowIdsSet.add(id);
966
+ }
967
+ };
968
+
969
+ // 1. Validate seed with seedFindings
970
+ const seedInput =
971
+ seedRaw !== undefined && seedRaw !== null
972
+ ? seedRaw
973
+ : {
974
+ schema: 1,
975
+ rows: rows.map((r) =>
976
+ r && typeof r === 'object'
977
+ ? { ...r, title: r.title ?? r.slug ?? r.id ?? 'row' }
978
+ : r
979
+ ),
980
+ };
981
+ const seedFindingList = seedFindings(seedInput, {});
982
+ for (const f of seedFindingList) {
983
+ addFinding(f);
984
+ }
985
+
986
+ // 2. Compute effectiveRows and rowIdSet for git checks
987
+ let rawObj = null;
988
+ if (seedRaw !== undefined && seedRaw !== null) {
989
+ if (typeof seedRaw === 'string') {
990
+ try {
991
+ rawObj = JSON.parse(seedRaw);
992
+ } catch {
993
+ rawObj = null;
994
+ }
995
+ } else {
996
+ rawObj = seedRaw;
997
+ }
998
+ }
999
+
1000
+ const fallbackRawRows = (rawObj && Array.isArray(rawObj.rows)) ? rawObj.rows : [];
1001
+ const effectiveRows = rows.length > 0 ? rows : fallbackRawRows.filter((r ) => r && typeof r === 'object' && r.id);
1002
+ const rowIdSet = new Set (effectiveRows.map((r ) => r.id));
1003
+
1004
+ // 3. Git trailer checks
1005
+ const trailerMerges = (gitFacts.merges || []).filter(
1006
+ (m) => m.parents && m.parents.length === 2 && Boolean(m.rowTrailer)
1007
+ );
1008
+
1009
+ const trailerCountByRow = new Map ();
1010
+ for (const m of trailerMerges) {
1011
+ const rowId = m.rowTrailer .trim();
1012
+ if (!trailerCountByRow.has(rowId)) trailerCountByRow.set(rowId, []);
1013
+ trailerCountByRow.get(rowId) .push(m);
1014
+ }
1015
+
1016
+ for (const [rowId, list] of trailerCountByRow.entries()) {
1017
+ if (!rowIdSet.has(rowId)) {
1018
+ addFinding({
1019
+ rule: 'trailer-unknown-row',
1020
+ message: `Merge trailer references unknown row '${rowId}'`,
1021
+ rowId,
1022
+ });
1023
+ }
1024
+ if (list.length > 1) {
1025
+ addFinding({
1026
+ rule: 'duplicate-merge',
1027
+ message: `Row '${rowId}' has multiple merge commits (${list.map((x) => x.sha).join(', ')})`,
1028
+ rowId,
1029
+ });
1030
+ }
1031
+ }
1032
+
1033
+ // 4. Seeded pointer checks & trailered-and-seeded
1034
+ for (const r of effectiveRows) {
1035
+ if (r.mergeCommit) {
1036
+ // Check if also trailered
1037
+ if (trailerCountByRow.has(r.id)) {
1038
+ addFinding({
1039
+ rule: 'trailered-and-seeded',
1040
+ message: `Row '${r.id}' has both mergeCommit and git trailer`,
1041
+ rowId: r.id,
1042
+ });
1043
+ }
1044
+
1045
+ // Check if mergeCommit is a 2-parent first-parent merge
1046
+ const isTwoParentMerge = (gitFacts.merges || []).some(
1047
+ (m) => m.sha === r.mergeCommit && m.parents && m.parents.length === 2
1048
+ );
1049
+ if (!isTwoParentMerge) {
1050
+ addFinding({
1051
+ rule: 'pointer-not-merge',
1052
+ message: `Row '${r.id}' seeded mergeCommit '${r.mergeCommit}' is not a first-parent two-parent merge`,
1053
+ rowId: r.id,
1054
+ });
1055
+ }
1056
+ }
1057
+ }
1058
+
1059
+ // 5. Commit claimed twice
1060
+ const commitClaimants = new Map ();
1061
+ for (const r of effectiveRows) {
1062
+ if (r.mergeCommit) {
1063
+ if (!commitClaimants.has(r.mergeCommit)) commitClaimants.set(r.mergeCommit, new Set());
1064
+ commitClaimants.get(r.mergeCommit) .add(r.id);
1065
+ }
1066
+ }
1067
+ for (const m of trailerMerges) {
1068
+ const rowId = m.rowTrailer .trim();
1069
+ if (!commitClaimants.has(m.sha)) commitClaimants.set(m.sha, new Set());
1070
+ commitClaimants.get(m.sha) .add(rowId);
1071
+ }
1072
+
1073
+ for (const [sha, claimants] of commitClaimants.entries()) {
1074
+ if (claimants.size > 1) {
1075
+ addFinding({
1076
+ rule: 'commit-claimed-twice',
1077
+ message: `Commit '${sha}' is claimed by multiple rows: ${Array.from(claimants).join(', ')}`,
1078
+ rowIds: Array.from(claimants),
1079
+ });
1080
+ }
1081
+ }
1082
+
1083
+ // 6. Render drift check (only when parsed rows are valid without seed syntax errors)
1084
+ const storeSeedRules = new Set(SEED_RULES.slice(0, 9));
1085
+ const hasSeedErrors = findings.some((f) => storeSeedRules.has(f.rule ));
1086
+ if (rendered !== undefined && rendered !== null && rows.length > 0 && !hasSeedErrors) {
1087
+ const { statuses } = deriveStatus(rows, gitFacts);
1088
+ const expected = render(rows, statuses);
1089
+ if (rendered !== expected) {
1090
+ addFinding({
1091
+ rule: 'render-drift',
1092
+ message: 'Rendered ROADMAP.md differs from derived status table',
1093
+ });
1094
+ }
1095
+ }
1096
+
1097
+ const offendingRowIds = Array.from(offendingRowIdsSet);
1098
+ return {
1099
+ ok: findings.length === 0,
1100
+ findings,
1101
+ offendingRowIds,
1102
+ };
1103
+ }
1104
+
1105
+ module.exports = {
1106
+ parseSeed,
1107
+ safeText,
1108
+ validWriteSet,
1109
+ pathsOverlap,
1110
+ writeSetsOverlap,
1111
+ deriveStatus,
1112
+ pick,
1113
+ render,
1114
+ renderRoadmapFile,
1115
+ check,
1116
+ seedFindings,
1117
+ registryClasses,
1118
+ LOOP_CLASSES,
1119
+ SEED_RULES,
1120
+ ROW_KEYS,
1121
+ TOP_KEYS,
1122
+ };