@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,1113 @@
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(/\u2028/g, '\\u2028')
17
+ .replace(/\u2029/g, '\\u2029');
18
+ } catch {
19
+ text = '';
20
+ }
21
+ } else {
22
+ try {
23
+ const encoded = JSON.stringify(value);
24
+ text = encoded === undefined ? 'undefined' : encoded;
25
+ } catch {
26
+ text = '[unserializable]';
27
+ }
28
+ }
29
+ return text.length > 60 ? `${text.slice(0, 57)}...` : text;
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
+
127
+ const ID_PATTERN = /^[0-9]{3}([a-z]|-[A-Z])?$/;
128
+ const FORBIDDEN_KEYS = new Set(['status', 'phase', 'evidence']);
129
+
130
+ const SEED_RULES = [
131
+ 'seed-invalid-json',
132
+ 'seed-invalid',
133
+ 'seed-unknown-key',
134
+ 'seed-typed-status',
135
+ 'slug-id-mismatch',
136
+ 'duplicate-id',
137
+ 'class-unknown',
138
+ 'needs-unresolved',
139
+ 'needs-cycle',
140
+ 'id-exists',
141
+ 'row-not-found',
142
+ 'row-started',
143
+ 'row-not-proposed',
144
+ ] ;
145
+
146
+ const TOP_KEYS = ['schema', 'layout', 'rows'];
147
+ const ROW_KEYS = [
148
+ 'id',
149
+ 'slug',
150
+ 'title',
151
+ 'class',
152
+ 'needs',
153
+ 'writeSet',
154
+ 'requires',
155
+ 'hold',
156
+ 'proposed',
157
+ 'verifyArgv',
158
+ 'mergeCommit',
159
+ 'intent',
160
+ 'acceptance',
161
+ ];
162
+
163
+ let _cachedRegistryClasses = null;
164
+
165
+ function registryClasses() {
166
+ if (_cachedRegistryClasses === null) {
167
+ const registryPath = path.join(__dirname, '../../../core/registry/workflows.json');
168
+ const doc = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
169
+ _cachedRegistryClasses = Object.keys(doc.classes || {});
170
+ }
171
+ return _cachedRegistryClasses;
172
+ }
173
+
174
+ class SeedParseError extends Error {
175
+ code ;
176
+ rowId ;
177
+ constructor(code , message , rowId ) {
178
+ super(message);
179
+ this.name = 'SeedParseError';
180
+ this.code = code;
181
+ this.rowId = rowId;
182
+ }
183
+ }
184
+
185
+
186
+
187
+
188
+
189
+
190
+
191
+
192
+ /**
193
+ * Validates a roadmap seed document (store or draft) without throwing.
194
+ * Returns every finding in stable IC-006 order.
195
+ */
196
+ function seedFindings(
197
+ rawDoc ,
198
+ opts
199
+ ) {
200
+ let doc ;
201
+ if (typeof rawDoc === 'string') {
202
+ try {
203
+ doc = JSON.parse(rawDoc);
204
+ } catch (err ) {
205
+ return [
206
+ {
207
+ rule: 'seed-invalid-json',
208
+ message: `Seed is not valid JSON: ${safeText(err.message)}`,
209
+ },
210
+ ];
211
+ }
212
+ } else {
213
+ doc = rawDoc;
214
+ }
215
+
216
+ if (!doc || typeof doc !== 'object' || Array.isArray(doc)) {
217
+ return [
218
+ {
219
+ rule: 'seed-invalid',
220
+ message: 'Seed must be an object',
221
+ },
222
+ ];
223
+ }
224
+
225
+ const isDraft = Boolean(opts?.draft);
226
+ const items = [];
227
+ let seq = 0;
228
+
229
+ const add = (
230
+ rowPos ,
231
+ rule ,
232
+ message ,
233
+ rowId ,
234
+ rowIds
235
+ ) => {
236
+ const finding = { rule, message };
237
+ if (rowId !== undefined) finding.rowId = rowId;
238
+ if (rowIds !== undefined) finding.rowIds = rowIds;
239
+ items.push({
240
+ rowPos,
241
+ ruleIdx: SEED_RULES.indexOf(rule),
242
+ seq: ++seq,
243
+ finding,
244
+ });
245
+ };
246
+
247
+ // Document level checks (rowPos = -1)
248
+ // Top-level key status/phase/evidence -> seed-typed-status
249
+ for (const k of Object.keys(doc)) {
250
+ if (FORBIDDEN_KEYS.has(k)) {
251
+ add(-1, 'seed-typed-status', `Top-level seed declares forbidden key '${safeText(k)}'`);
252
+ }
253
+ }
254
+
255
+ // Any other key not in TOP_KEYS -> seed-unknown-key
256
+ // In draft mode TOP_KEYS is only schema and rows, so layout is seed-unknown-key
257
+ const allowedTop = isDraft ? new Set(['schema', 'rows']) : new Set(TOP_KEYS);
258
+ for (const k of Object.keys(doc)) {
259
+ if (!FORBIDDEN_KEYS.has(k) && !allowedTop.has(k)) {
260
+ add(-1, 'seed-unknown-key', `Top-level seed declares unknown key '${safeText(k)}'`);
261
+ }
262
+ }
263
+
264
+ // In draft mode additionally schema must be exactly 1
265
+ if (isDraft && doc.schema !== 1) {
266
+ add(-1, 'seed-invalid', `Draft 'schema' must be 1, got ${safeText(doc.schema)}`);
267
+ } else if (!isDraft && doc.schema !== 1) {
268
+ add(-1, 'seed-invalid', "Seed 'schema' must be 1");
269
+ }
270
+
271
+ // rows not an array -> seed-invalid ("rows must be an array")
272
+ if (!Array.isArray(doc.rows)) {
273
+ add(-1, 'seed-invalid', "Seed 'rows' must be an array");
274
+ items.sort((a, b) => a.rowPos - b.rowPos || a.ruleIdx - b.ruleIdx || a.seq - b.seq);
275
+ return items.map((x) => x.finding);
276
+ }
277
+
278
+ // In draft mode additionally rows non-empty
279
+ if (isDraft && doc.rows.length === 0) {
280
+ add(-1, 'seed-invalid', "Draft 'rows' must not be empty");
281
+ }
282
+
283
+ // Row level checks
284
+ const rowKeySet = new Set(ROW_KEYS);
285
+ const validClasses = opts?.classes ?? registryClasses();
286
+ const validClassSet = new Set(validClasses);
287
+
288
+ const seenIds = new Set ();
289
+ const allRowIds = new Set ();
290
+ for (const r of doc.rows) {
291
+ if (r && typeof r === 'object' && typeof r.id === 'string') {
292
+ allRowIds.add(r.id);
293
+ }
294
+ }
295
+
296
+ for (let idx = 0; idx < doc.rows.length; idx++) {
297
+ const row = doc.rows[idx];
298
+ if (!row || typeof row !== 'object' || Array.isArray(row)) {
299
+ add(idx, 'seed-invalid', 'Each row must be an object');
300
+ continue;
301
+ }
302
+
303
+ const rowId = typeof row.id === 'string' ? row.id : undefined;
304
+
305
+ // 1. seed-invalid:
306
+ // 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)
307
+ if (
308
+ typeof row.title !== 'string' ||
309
+ row.title.trim().length === 0 ||
310
+ Array.from(row.title).length > 120 ||
311
+ /[\r\n\u2028\u2029]/.test(row.title)
312
+ ) {
313
+ add(
314
+ idx,
315
+ 'seed-invalid',
316
+ `Row '${safeText(rowId ?? 'unknown')}' title must be a non-empty single-line string of at most 120 characters`,
317
+ rowId
318
+ );
319
+ }
320
+
321
+ // needs: if present must be an array of strings (the key may be absent)
322
+ if (
323
+ row.needs !== undefined &&
324
+ (!Array.isArray(row.needs) || !row.needs.every((n ) => typeof n === 'string'))
325
+ ) {
326
+ add(
327
+ idx,
328
+ 'seed-invalid',
329
+ `Row '${safeText(rowId ?? 'unknown')}' needs must be an array of strings`,
330
+ rowId
331
+ );
332
+ }
333
+
334
+ // writeSet: if present, an array of 1 to 32 canonical repo-relative paths.
335
+ if (row.writeSet !== undefined && validWriteSet(row.writeSet) === null) {
336
+ add(
337
+ idx,
338
+ 'seed-invalid',
339
+ `Row '${safeText(rowId ?? 'unknown')}' writeSet must be 1 to 32 repo-relative paths with no leading slash, drive letter, backslash, empty, '.' or '..' segment`,
340
+ rowId
341
+ );
342
+ }
343
+
344
+ // requires: if present an array of non-empty strings
345
+ if (
346
+ row.requires !== undefined &&
347
+ (!Array.isArray(row.requires) ||
348
+ !row.requires.every((s ) => typeof s === 'string' && s.trim().length > 0))
349
+ ) {
350
+ add(
351
+ idx,
352
+ 'seed-invalid',
353
+ `Row '${safeText(rowId ?? 'unknown')}' requires must be an array of non-empty strings`,
354
+ rowId
355
+ );
356
+ }
357
+
358
+ // hold: as today (non-empty single-line string at most 120 chars)
359
+ if (
360
+ row.hold !== undefined &&
361
+ (typeof row.hold !== 'string' ||
362
+ row.hold.trim().length === 0 ||
363
+ Array.from(row.hold).length > 120 ||
364
+ /[\r\n\u2028\u2029]/.test(row.hold))
365
+ ) {
366
+ add(
367
+ idx,
368
+ 'seed-invalid',
369
+ `Row '${safeText(rowId ?? 'unknown')}' hold must be a non-empty single-line string of at most 120 characters`,
370
+ rowId
371
+ );
372
+ }
373
+
374
+ // proposed (maint-thin): only `true`; absent means ready. Only seed apply/amend writes it, so a draft may not
375
+ // declare it at all (a draft cannot pre-mark a row, and no model can launder a promotion).
376
+ if (isDraft && 'proposed' in row) {
377
+ add(
378
+ idx,
379
+ 'seed-invalid',
380
+ `Draft row '${safeText(rowId ?? 'unknown')}' must not declare proposed; seed apply marks it`,
381
+ rowId
382
+ );
383
+ } else if (row.proposed !== undefined && row.proposed !== true) {
384
+ add(
385
+ idx,
386
+ 'seed-invalid',
387
+ `Row '${safeText(rowId ?? 'unknown')}' proposed must be true when present`,
388
+ rowId
389
+ );
390
+ }
391
+
392
+ // verifyArgv: as today (1 to 8 non-empty strings)
393
+ if (
394
+ row.verifyArgv !== undefined &&
395
+ (!Array.isArray(row.verifyArgv) ||
396
+ row.verifyArgv.length === 0 ||
397
+ row.verifyArgv.length > 8 ||
398
+ !row.verifyArgv.every((arg ) => typeof arg === 'string' && arg.trim().length > 0))
399
+ ) {
400
+ add(
401
+ idx,
402
+ 'seed-invalid',
403
+ `Row '${safeText(rowId ?? 'unknown')}' verifyArgv must be 1 to 8 non-empty strings`,
404
+ rowId
405
+ );
406
+ }
407
+
408
+ // mergeCommit: if present a 40-hex lowercase string (store mode only)
409
+ if (!isDraft && row.mergeCommit !== undefined) {
410
+ if (typeof row.mergeCommit !== 'string' || !/^[0-9a-f]{40}$/.test(row.mergeCommit)) {
411
+ add(
412
+ idx,
413
+ 'seed-invalid',
414
+ `Row '${safeText(rowId ?? 'unknown')}' mergeCommit must be a 40-hex lowercase string`,
415
+ rowId
416
+ );
417
+ }
418
+ }
419
+
420
+ // intent: if present a non-empty single-line string at most 200 characters
421
+ if (
422
+ row.intent !== undefined &&
423
+ (typeof row.intent !== 'string' ||
424
+ row.intent.trim().length === 0 ||
425
+ Array.from(row.intent).length > 200 ||
426
+ /[\r\n\u2028\u2029]/.test(row.intent))
427
+ ) {
428
+ add(
429
+ idx,
430
+ 'seed-invalid',
431
+ `Row '${safeText(rowId ?? 'unknown')}' intent must be a non-empty single-line string of at most 200 characters`,
432
+ rowId
433
+ );
434
+ }
435
+
436
+ // acceptance: if present an array of 1 to 8 non-empty single-line strings, each at most 200 characters
437
+ if (
438
+ row.acceptance !== undefined &&
439
+ (!Array.isArray(row.acceptance) ||
440
+ row.acceptance.length < 1 ||
441
+ row.acceptance.length > 8 ||
442
+ !row.acceptance.every(
443
+ (s ) =>
444
+ typeof s === 'string' &&
445
+ s.trim().length > 0 &&
446
+ Array.from(s).length <= 200 &&
447
+ !/[\r\n\u2028\u2029]/.test(s)
448
+ ))
449
+ ) {
450
+ add(
451
+ idx,
452
+ 'seed-invalid',
453
+ `Row '${safeText(rowId ?? 'unknown')}' acceptance must be an array of 1 to 8 non-empty single-line strings of at most 200 characters`,
454
+ rowId
455
+ );
456
+ }
457
+
458
+ // 2. seed-unknown-key: a key not in ROW_KEYS (excluding forbidden keys and draft mergeCommit)
459
+ for (const k of Object.keys(row)) {
460
+ if (!FORBIDDEN_KEYS.has(k) && !(isDraft && k === 'mergeCommit') && !rowKeySet.has(k)) {
461
+ add(idx, 'seed-unknown-key', `Row '${safeText(rowId ?? 'unknown')}' declares unknown key '${safeText(k)}'`, rowId);
462
+ }
463
+ }
464
+
465
+ // 3. seed-typed-status: key status/phase/evidence; in draft mode also a mergeCommit key
466
+ for (const k of Object.keys(row)) {
467
+ if (FORBIDDEN_KEYS.has(k)) {
468
+ add(idx, 'seed-typed-status', `Row '${safeText(rowId ?? 'unknown')}' declares forbidden key '${safeText(k)}'`, rowId);
469
+ }
470
+ }
471
+ if (isDraft && 'mergeCommit' in row) {
472
+ add(idx, 'seed-typed-status', `Draft row '${safeText(rowId ?? 'unknown')}' declares forbidden key 'mergeCommit'`, rowId);
473
+ }
474
+
475
+ // 4. slug-id-mismatch: id not matching pattern or slug not starting with id-
476
+ if (typeof row.id !== 'string' || !ID_PATTERN.test(row.id)) {
477
+ add(
478
+ idx,
479
+ 'slug-id-mismatch',
480
+ `Row id '${safeText(row.id)}' does not match pattern ^[0-9]{3}([a-z]|-[A-Z])?$`,
481
+ rowId
482
+ );
483
+ } else if (typeof row.slug !== 'string' || !row.slug.startsWith(`${row.id}-`)) {
484
+ add(
485
+ idx,
486
+ 'slug-id-mismatch',
487
+ `Row slug '${safeText(row.slug)}' must start with '${safeText(row.id)}-'`,
488
+ rowId
489
+ );
490
+ }
491
+
492
+ // 5. duplicate-id: reported on each repeated occurrence of an already-seen id (the later row)
493
+ if (typeof row.id === 'string' && ID_PATTERN.test(row.id)) {
494
+ if (seenIds.has(row.id)) {
495
+ add(idx, 'duplicate-id', `Duplicate row id: ${safeText(row.id)}`, rowId);
496
+ } else {
497
+ seenIds.add(row.id);
498
+ }
499
+ }
500
+
501
+ // 6. class-unknown: class not a string in opts.classes ?? registryClasses(); message lists the valid set
502
+ if (typeof row.class !== 'string' || !validClassSet.has(row.class)) {
503
+ add(
504
+ idx,
505
+ 'class-unknown',
506
+ `Row '${safeText(rowId ?? 'unknown')}' class '${safeText(row.class)}' is unknown; valid classes: ${validClasses.join(', ')}`,
507
+ rowId
508
+ );
509
+ }
510
+
511
+ // 7. needs-unresolved (skipped in draft mode)
512
+ if (!isDraft && Array.isArray(row.needs)) {
513
+ for (const need of row.needs) {
514
+ if (typeof need === 'string' && !allRowIds.has(need)) {
515
+ add(
516
+ idx,
517
+ 'needs-unresolved',
518
+ `Row '${safeText(rowId ?? 'unknown')}' declares unresolved need '${safeText(need)}'`,
519
+ rowId
520
+ );
521
+ }
522
+ }
523
+ }
524
+ }
525
+
526
+ // 8. needs-cycle (skipped in draft mode)
527
+ if (!isDraft) {
528
+ const firstRowById = new Map ();
529
+ const firstRowList = [];
530
+
531
+ for (let idx = 0; idx < doc.rows.length; idx++) {
532
+ const r = doc.rows[idx];
533
+ if (r && typeof r === 'object' && typeof r.id === 'string') {
534
+ if (!firstRowById.has(r.id)) {
535
+ const entry = { row: r, docPos: idx };
536
+ firstRowById.set(r.id, entry);
537
+ firstRowList.push(entry);
538
+ }
539
+ }
540
+ }
541
+
542
+ const WHITE = 0;
543
+ const GRAY = 1;
544
+ const BLACK = 2;
545
+ const color = new Map ();
546
+ const callStack = [];
547
+ const seenCycleKeys = new Set ();
548
+
549
+ const dfs = (u ) => {
550
+ color.set(u, GRAY);
551
+ callStack.push(u);
552
+
553
+ const entry = firstRowById.get(u);
554
+ const uNeeds = entry && Array.isArray(entry.row.needs) ? entry.row.needs : [];
555
+
556
+ for (const v of uNeeds) {
557
+ if (typeof v !== 'string' || !firstRowById.has(v)) continue;
558
+ const vColor = color.get(v) ?? WHITE;
559
+ if (vColor === GRAY) {
560
+ // Cycle found
561
+ const cycleStartIdx = callStack.indexOf(v);
562
+ const cyclePath = callStack.slice(cycleStartIdx);
563
+
564
+ let minPos = Infinity;
565
+ let minIdx = 0;
566
+ for (let i = 0; i < cyclePath.length; i++) {
567
+ const pos = firstRowById.get(cyclePath[i]) .docPos;
568
+ if (pos < minPos) {
569
+ minPos = pos;
570
+ minIdx = i;
571
+ }
572
+ }
573
+ const canonicalPath = cyclePath.slice(minIdx).concat(cyclePath.slice(0, minIdx));
574
+ const cycleKey = canonicalPath.join('->');
575
+ if (!seenCycleKeys.has(cycleKey)) {
576
+ seenCycleKeys.add(cycleKey);
577
+ const msg = `needs cycle ${canonicalPath.map(safeText).join(' -> ')} -> ${safeText(canonicalPath[0])}`;
578
+ add(minPos, 'needs-cycle', msg, undefined, canonicalPath);
579
+ }
580
+ } else if (vColor === WHITE) {
581
+ dfs(v);
582
+ }
583
+ }
584
+
585
+ callStack.pop();
586
+ color.set(u, BLACK);
587
+ };
588
+
589
+ for (const entry of firstRowList) {
590
+ if ((color.get(entry.row.id) ?? WHITE) === WHITE) {
591
+ dfs(entry.row.id);
592
+ }
593
+ }
594
+ }
595
+
596
+ // Sort findings: document-level (-1) first, then by rowPos, then by ruleIdx, then seq
597
+ items.sort((a, b) => a.rowPos - b.rowPos || a.ruleIdx - b.ruleIdx || a.seq - b.seq);
598
+ return items.map((x) => x.finding);
599
+ }
600
+
601
+ /**
602
+ * Validates and parses rows.json seed data.
603
+ * Throws SeedParseError for the first finding if any finding exists.
604
+ */
605
+ function parseSeed(raw ) {
606
+ const findings = seedFindings(raw, {});
607
+ if (findings.length > 0) {
608
+ const first = findings[0];
609
+ throw new SeedParseError(first.rule, first.message, first.rowId ?? first.rowIds?.[0]);
610
+ }
611
+ const doc = typeof raw === 'string' ? JSON.parse(raw) : raw;
612
+ return doc ;
613
+ }
614
+
615
+ function normalizePath(p ) {
616
+ return p.replace(/^\.\//, '').replace(/^\//, '');
617
+ }
618
+
619
+ function pathsOverlap(p1 , p2 ) {
620
+ const n1 = normalizePath(p1);
621
+ const n2 = normalizePath(p2);
622
+ if (n1 === n2) return true;
623
+ if (n1.endsWith('/')) {
624
+ if (n2.startsWith(n1)) return true;
625
+ } else {
626
+ if (n2.startsWith(`${n1}/`)) return true;
627
+ }
628
+ if (n2.endsWith('/')) {
629
+ if (n1.startsWith(n2)) return true;
630
+ } else {
631
+ if (n1.startsWith(`${n2}/`)) return true;
632
+ }
633
+ return false;
634
+ }
635
+
636
+ function validWriteSet(value ) {
637
+ if (!Array.isArray(value) || value.length < 1 || value.length > 32) return null;
638
+ const canonical = (entry ) => {
639
+ if (typeof entry !== 'string' || entry.trim().length === 0 || entry.startsWith('/') || /^[A-Za-z]:/.test(entry) || entry.includes('\\')) {
640
+ return false;
641
+ }
642
+ const withoutDirectoryMarker = entry.endsWith('/') ? entry.slice(0, -1) : entry;
643
+ return withoutDirectoryMarker.split('/').every((segment) => segment !== '' && segment !== '.' && segment !== '..');
644
+ };
645
+ return value.every(canonical) ? value : null;
646
+ }
647
+
648
+ function writeSetsOverlap(ws1 , ws2 ) {
649
+ if (!ws1 || !ws2 || ws1.length === 0 || ws2.length === 0) return false;
650
+ for (const p1 of ws1) {
651
+ for (const p2 of ws2) {
652
+ if (pathsOverlap(p1, p2)) return true;
653
+ }
654
+ }
655
+ return false;
656
+ }
657
+
658
+ /**
659
+ * Derives the status of each row from git facts only (merges and `feat/<slug>` branches); the run
660
+ * record's holds and pauses are machine state, which `status` shows separately (C11, C19).
661
+ * Trailed merges in git develop history take precedence; subjects are NEVER evidence.
662
+ */
663
+ function deriveStatus(
664
+ rows ,
665
+ gitFacts = {}
666
+ ) {
667
+ const statuses = new Map ();
668
+ const rowStatus = {};
669
+
670
+ // Group 2-parent first-parent merges on develop by DoFlow-Row trailer value
671
+ const trailerMergesByRow = new Map ();
672
+ for (const m of gitFacts.merges || []) {
673
+ if (m.parents && m.parents.length === 2 && m.rowTrailer) {
674
+ const rowId = m.rowTrailer.trim();
675
+ if (!trailerMergesByRow.has(rowId)) trailerMergesByRow.set(rowId, []);
676
+ trailerMergesByRow.get(rowId) .push(m);
677
+ }
678
+ }
679
+
680
+ for (const row of rows) {
681
+ let status = 'not-started';
682
+ let evidence = null;
683
+ let holdReason = null;
684
+
685
+ const trailerMerges = trailerMergesByRow.get(row.id) || [];
686
+ if (trailerMerges.length > 0) {
687
+ status = 'merged';
688
+ const m = trailerMerges[0];
689
+ evidence = {
690
+ kind: 'trailer',
691
+ sha: m.sha,
692
+ shortSha: m.sha.slice(0, 7),
693
+ };
694
+ } else if (row.mergeCommit) {
695
+ // Must be a 2-parent first-parent merge in git develop history
696
+ const matchingMerge = (gitFacts.merges || []).find(
697
+ (m) => m.sha === row.mergeCommit && m.parents && m.parents.length === 2
698
+ );
699
+ if (matchingMerge) {
700
+ status = 'merged';
701
+ evidence = {
702
+ kind: 'seeded',
703
+ sha: row.mergeCommit,
704
+ shortSha: row.mergeCommit.slice(0, 7),
705
+ };
706
+ }
707
+ }
708
+
709
+ if (status !== 'merged') {
710
+ const branchName = `feat/${row.slug}`;
711
+ const branchExists =
712
+ (gitFacts.branches || []).includes(branchName) ||
713
+ (gitFacts.branches || []).includes(`refs/heads/${branchName}`);
714
+
715
+ if (branchExists) status = 'in-flight';
716
+ // maint-thin: an unstarted row a seed write proposed waits for a person's promote.
717
+ else if (row.proposed === true) status = 'proposed';
718
+ }
719
+
720
+ // A seed-level hold is reported for unmerged rows.
721
+ if (status !== 'merged' && row.hold) holdReason = row.hold;
722
+
723
+ const info = {
724
+ rowId: row.id,
725
+ status,
726
+ evidence,
727
+ holdReason,
728
+ };
729
+ statuses.set(row.id, info);
730
+ rowStatus[row.id] = status;
731
+ }
732
+
733
+ return { statuses, rowStatus };
734
+ }
735
+
736
+ /**
737
+ * S1 Pick table: chooses the next ready row in seed order.
738
+ * - Ready only when all needs are merged
739
+ * - Write set disjoint from all in-flight rows
740
+ * - WIP bound (default 2)
741
+ * - Row without a write set runs alone
742
+ * - Non-feature class is class-unsupported
743
+ * - Yields one reason per skipped row
744
+ */
745
+ function pick(
746
+ rows ,
747
+ statuses ,
748
+ options = {}
749
+ ) {
750
+ const wip = options.wip ?? 2;
751
+ const getStatus = (id ) => {
752
+ if (statuses instanceof Map) {
753
+ return statuses.get(id)?.status || 'not-started';
754
+ }
755
+ const val = statuses[id];
756
+ if (typeof val === 'string') return val;
757
+ return val?.status || 'not-started';
758
+ };
759
+
760
+ const inFlightRows = rows.filter((r) => getStatus(r.id) === 'in-flight');
761
+ const inFlightCount = inFlightRows.length;
762
+ const exclusiveInFlight = inFlightRows.some((r) => !r.writeSet || r.writeSet.length === 0);
763
+
764
+ const skips = {};
765
+ let picked = null;
766
+
767
+ for (const row of rows) {
768
+ if (row.hold) {
769
+ skips[row.id] = 'row-held';
770
+ continue;
771
+ }
772
+
773
+ const status = getStatus(row.id);
774
+ if (status === 'merged' || status === 'in-flight') {
775
+ continue;
776
+ }
777
+
778
+ // maint-thin: autopilot never picks a proposed row; a person promotes it first.
779
+ if (row.proposed === true) {
780
+ skips[row.id] = 'row-proposed';
781
+ continue;
782
+ }
783
+
784
+ // Class check
785
+ if (row.class !== 'feature') {
786
+ skips[row.id] = 'class-unsupported';
787
+ continue;
788
+ }
789
+
790
+ // Dependencies check
791
+ const hasUnmergedNeed = (row.needs || []).some((needId) => getStatus(needId) !== 'merged');
792
+ if (hasUnmergedNeed) {
793
+ skips[row.id] = 'need-unmerged';
794
+ continue;
795
+ }
796
+
797
+ // Exclusive in-flight check (another row running alone)
798
+ if (exclusiveInFlight) {
799
+ skips[row.id] = 'exclusive-row-in-flight';
800
+ continue;
801
+ }
802
+
803
+ // Candidate has no write set and requires running alone
804
+ const hasNoWriteSet = !row.writeSet || row.writeSet.length === 0;
805
+ if (hasNoWriteSet && inFlightCount > 0) {
806
+ skips[row.id] = 'needs-exclusive';
807
+ continue;
808
+ }
809
+
810
+ // Write set collision check
811
+ const overlapping = inFlightRows.find((ifr) => writeSetsOverlap(row.writeSet, ifr.writeSet));
812
+ if (overlapping) {
813
+ skips[row.id] = `write-set-overlap:${overlapping.id}`;
814
+ continue;
815
+ }
816
+
817
+ // WIP cap check
818
+ if (inFlightCount >= wip) {
819
+ skips[row.id] = 'wip-full';
820
+ continue;
821
+ }
822
+
823
+ // Row is ready!
824
+ if (!picked) {
825
+ picked = row;
826
+ }
827
+ }
828
+
829
+ return { picked, skips };
830
+ }
831
+
832
+ /**
833
+ * Renders byte-stable ROADMAP.md markdown table.
834
+ * Columns: Row, Title, Class, Needs, Status, Evidence, Next.
835
+ */
836
+ function render(
837
+ rows ,
838
+ statuses
839
+ ) {
840
+ const getInfo = (id ) => {
841
+ if (statuses instanceof Map) {
842
+ return statuses.get(id) || { rowId: id, status: 'not-started', evidence: null };
843
+ }
844
+ return statuses[id] || { rowId: id, status: 'not-started', evidence: null };
845
+ };
846
+
847
+ const pickRes = pick(rows, statuses);
848
+
849
+ const lines = [
850
+ '# Roadmap',
851
+ '',
852
+ '| Row | Title | Class | Needs | Status | Evidence | Next |',
853
+ '|---|---|---|---|---|---|---|',
854
+ ];
855
+
856
+ for (const row of rows) {
857
+ const info = getInfo(row.id);
858
+ const needsStr = row.needs && row.needs.length > 0 ? row.needs.join(', ') : '-';
859
+ const statusStr = info.status;
860
+
861
+ let evidenceStr = '-';
862
+ if (info.evidence) {
863
+ evidenceStr = `${info.evidence.shortSha} (${info.evidence.kind})`;
864
+ }
865
+
866
+ let nextStr = '-';
867
+ if (info.status === 'merged') {
868
+ nextStr = '-';
869
+ } else if (row.hold) {
870
+ nextStr = `held: ${row.hold}`;
871
+ } else if (info.status === 'in-flight') {
872
+ nextStr = info.holdReason || '-';
873
+ } else {
874
+ // not-started
875
+ if (pickRes.picked && pickRes.picked.id === row.id) {
876
+ nextStr = 'pick';
877
+ } else if (pickRes.skips[row.id]) {
878
+ nextStr = pickRes.skips[row.id];
879
+ }
880
+ }
881
+
882
+ lines.push(`| ${row.id} | ${row.title} | ${row.class} | ${needsStr} | ${statusStr} | ${evidenceStr} | ${nextStr} |`);
883
+ }
884
+
885
+ lines.push('');
886
+ return lines.join('\n');
887
+ }
888
+
889
+ /**
890
+ * Writes rendered table to disk using atomic temp+rename only when bytes differ.
891
+ */
892
+ function renderRoadmapFile({
893
+ roadmapPath,
894
+ content,
895
+ fsImpl = fs,
896
+ }
897
+
898
+
899
+
900
+ ) {
901
+ let existing = null;
902
+ try {
903
+ existing = fsImpl.readFileSync(roadmapPath, 'utf8');
904
+ } catch {
905
+ existing = null;
906
+ }
907
+
908
+ const bytes = Buffer.byteLength(content, 'utf8');
909
+ if (existing === content) {
910
+ return { written: false, bytes };
911
+ }
912
+
913
+ const dir = path.dirname(roadmapPath);
914
+ fsImpl.mkdirSync(dir, { recursive: true });
915
+ const tmpPath = path.join(dir, `.tmp-roadmap-${process.pid}-${Date.now()}`);
916
+ try {
917
+ fsImpl.writeFileSync(tmpPath, content, 'utf8');
918
+ fsImpl.renameSync(tmpPath, roadmapPath);
919
+ } catch (error) {
920
+ try { fsImpl.unlinkSync(tmpPath); } catch { /* ignore temp cleanup failure */ }
921
+ throw error;
922
+ }
923
+ return { written: true, bytes };
924
+ }
925
+
926
+ /**
927
+ * Performs IC-003 integrity check on seed, git facts, and rendered table:
928
+ * 1. render-drift
929
+ * 2. duplicate-merge
930
+ * 3. trailer-unknown-row
931
+ * 4. commit-claimed-twice
932
+ * 5. trailered-and-seeded
933
+ * 6. pointer-not-merge
934
+ * 7. needs-unresolved
935
+ * 8. duplicate-id
936
+ * 9. slug-id-mismatch
937
+ * Plus: seed-typed-status
938
+ */
939
+ function check({
940
+ rows = [],
941
+ gitFacts = {},
942
+ rendered = null,
943
+ seedRaw = null,
944
+ }
945
+
946
+
947
+
948
+
949
+ ) {
950
+ const findings = [];
951
+ const offendingRowIdsSet = new Set ();
952
+
953
+ const addFinding = (finding ) => {
954
+ findings.push(finding);
955
+ if (finding.rowId) offendingRowIdsSet.add(finding.rowId);
956
+ if (finding.rowIds) {
957
+ for (const id of finding.rowIds) offendingRowIdsSet.add(id);
958
+ }
959
+ };
960
+
961
+ // 1. Validate seed with seedFindings
962
+ const seedInput =
963
+ seedRaw !== undefined && seedRaw !== null
964
+ ? seedRaw
965
+ : {
966
+ schema: 1,
967
+ rows: rows.map((r) =>
968
+ r && typeof r === 'object'
969
+ ? { ...r, title: r.title ?? r.slug ?? r.id ?? 'row' }
970
+ : r
971
+ ),
972
+ };
973
+ const seedFindingList = seedFindings(seedInput, {});
974
+ for (const f of seedFindingList) {
975
+ addFinding(f);
976
+ }
977
+
978
+ // 2. Compute effectiveRows and rowIdSet for git checks
979
+ let rawObj = null;
980
+ if (seedRaw !== undefined && seedRaw !== null) {
981
+ if (typeof seedRaw === 'string') {
982
+ try {
983
+ rawObj = JSON.parse(seedRaw);
984
+ } catch {
985
+ rawObj = null;
986
+ }
987
+ } else {
988
+ rawObj = seedRaw;
989
+ }
990
+ }
991
+
992
+ const fallbackRawRows = (rawObj && Array.isArray(rawObj.rows)) ? rawObj.rows : [];
993
+ const effectiveRows = rows.length > 0 ? rows : fallbackRawRows.filter((r ) => r && typeof r === 'object' && r.id);
994
+ const rowIdSet = new Set (effectiveRows.map((r ) => r.id));
995
+
996
+ // 3. Git trailer checks
997
+ const trailerMerges = (gitFacts.merges || []).filter(
998
+ (m) => m.parents && m.parents.length === 2 && Boolean(m.rowTrailer)
999
+ );
1000
+
1001
+ const trailerCountByRow = new Map ();
1002
+ for (const m of trailerMerges) {
1003
+ const rowId = m.rowTrailer .trim();
1004
+ if (!trailerCountByRow.has(rowId)) trailerCountByRow.set(rowId, []);
1005
+ trailerCountByRow.get(rowId) .push(m);
1006
+ }
1007
+
1008
+ for (const [rowId, list] of trailerCountByRow.entries()) {
1009
+ if (!rowIdSet.has(rowId)) {
1010
+ addFinding({
1011
+ rule: 'trailer-unknown-row',
1012
+ message: `Merge trailer references unknown row '${rowId}'`,
1013
+ rowId,
1014
+ });
1015
+ }
1016
+ if (list.length > 1) {
1017
+ addFinding({
1018
+ rule: 'duplicate-merge',
1019
+ message: `Row '${rowId}' has multiple merge commits (${list.map((x) => x.sha).join(', ')})`,
1020
+ rowId,
1021
+ });
1022
+ }
1023
+ }
1024
+
1025
+ // 4. Seeded pointer checks & trailered-and-seeded
1026
+ for (const r of effectiveRows) {
1027
+ if (r.mergeCommit) {
1028
+ // Check if also trailered
1029
+ if (trailerCountByRow.has(r.id)) {
1030
+ addFinding({
1031
+ rule: 'trailered-and-seeded',
1032
+ message: `Row '${r.id}' has both mergeCommit and git trailer`,
1033
+ rowId: r.id,
1034
+ });
1035
+ }
1036
+
1037
+ // Check if mergeCommit is a 2-parent first-parent merge
1038
+ const isTwoParentMerge = (gitFacts.merges || []).some(
1039
+ (m) => m.sha === r.mergeCommit && m.parents && m.parents.length === 2
1040
+ );
1041
+ if (!isTwoParentMerge) {
1042
+ addFinding({
1043
+ rule: 'pointer-not-merge',
1044
+ message: `Row '${r.id}' seeded mergeCommit '${r.mergeCommit}' is not a first-parent two-parent merge`,
1045
+ rowId: r.id,
1046
+ });
1047
+ }
1048
+ }
1049
+ }
1050
+
1051
+ // 5. Commit claimed twice
1052
+ const commitClaimants = new Map ();
1053
+ for (const r of effectiveRows) {
1054
+ if (r.mergeCommit) {
1055
+ if (!commitClaimants.has(r.mergeCommit)) commitClaimants.set(r.mergeCommit, new Set());
1056
+ commitClaimants.get(r.mergeCommit) .add(r.id);
1057
+ }
1058
+ }
1059
+ for (const m of trailerMerges) {
1060
+ const rowId = m.rowTrailer .trim();
1061
+ if (!commitClaimants.has(m.sha)) commitClaimants.set(m.sha, new Set());
1062
+ commitClaimants.get(m.sha) .add(rowId);
1063
+ }
1064
+
1065
+ for (const [sha, claimants] of commitClaimants.entries()) {
1066
+ if (claimants.size > 1) {
1067
+ addFinding({
1068
+ rule: 'commit-claimed-twice',
1069
+ message: `Commit '${sha}' is claimed by multiple rows: ${Array.from(claimants).join(', ')}`,
1070
+ rowIds: Array.from(claimants),
1071
+ });
1072
+ }
1073
+ }
1074
+
1075
+ // 6. Render drift check (only when parsed rows are valid without seed syntax errors)
1076
+ const storeSeedRules = new Set(SEED_RULES.slice(0, 9));
1077
+ const hasSeedErrors = findings.some((f) => storeSeedRules.has(f.rule ));
1078
+ if (rendered !== undefined && rendered !== null && rows.length > 0 && !hasSeedErrors) {
1079
+ const { statuses } = deriveStatus(rows, gitFacts);
1080
+ const expected = render(rows, statuses);
1081
+ if (rendered !== expected) {
1082
+ addFinding({
1083
+ rule: 'render-drift',
1084
+ message: 'Rendered ROADMAP.md differs from derived status table',
1085
+ });
1086
+ }
1087
+ }
1088
+
1089
+ const offendingRowIds = Array.from(offendingRowIdsSet);
1090
+ return {
1091
+ ok: findings.length === 0,
1092
+ findings,
1093
+ offendingRowIds,
1094
+ };
1095
+ }
1096
+
1097
+ module.exports = {
1098
+ parseSeed,
1099
+ safeText,
1100
+ validWriteSet,
1101
+ pathsOverlap,
1102
+ writeSetsOverlap,
1103
+ deriveStatus,
1104
+ pick,
1105
+ render,
1106
+ renderRoadmapFile,
1107
+ check,
1108
+ seedFindings,
1109
+ registryClasses,
1110
+ SEED_RULES,
1111
+ ROW_KEYS,
1112
+ TOP_KEYS,
1113
+ };