forge-workflow 0.0.9 → 0.1.0-beta.2

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 (479) hide show
  1. package/.claude/rules/{greptile-review-process.md → review-process.md} +56 -41
  2. package/.claude/scripts/{greptile-resolve.sh → review-resolve.sh} +13 -3
  3. package/.cursor/rules/permissions-guidance.mdc +2 -2
  4. package/.forge/hooks/check-tdd.js +3 -0
  5. package/.forge/hooks/forge-native-hook.js +245 -0
  6. package/.forge/protected-paths.yaml +157 -0
  7. package/AGENTS.md +151 -61
  8. package/CHANGELOG.md +681 -0
  9. package/CLAUDE.md +9 -106
  10. package/QUICKSTART.md +171 -0
  11. package/README.md +271 -363
  12. package/bin/forge-cmd.js +120 -9
  13. package/bin/forge-preflight.js +26 -5
  14. package/bin/forge.js +466 -489
  15. package/docs/INDEX.md +93 -0
  16. package/docs/PROJECT_DESIGN.md +685 -0
  17. package/docs/architecture/index.md +66 -0
  18. package/docs/architecture/notes/README.md +35 -0
  19. package/docs/architecture/subsystems/README.md +46 -0
  20. package/docs/{TOOLCHAIN.md → forge/TOOLCHAIN.md} +56 -47
  21. package/docs/forge/VALIDATION.md +82 -0
  22. package/docs/{AGENT_INSTALL_PROMPT.md → guides/AGENT_INSTALL_PROMPT.md} +3 -3
  23. package/docs/guides/BEADS_GITHUB_SYNC.md +32 -0
  24. package/docs/{ENHANCED_ONBOARDING.md → guides/ENHANCED_ONBOARDING.md} +16 -12
  25. package/docs/guides/GREPTILE_SETUP.md +46 -0
  26. package/docs/guides/MANUAL_REVIEW_GUIDE.md +58 -0
  27. package/docs/guides/MIGRATION.md +56 -0
  28. package/docs/guides/SETUP.md +118 -0
  29. package/docs/guides/SUPPORT.md +185 -0
  30. package/docs/guides/WORKFLOW_TEMPLATES.md +74 -0
  31. package/docs/guides/memory-backends.md +183 -0
  32. package/docs/reference/ADAPTERS.md +128 -0
  33. package/docs/reference/AGENT_SKILL_PARITY.md +175 -0
  34. package/docs/reference/COMMANDS.md +205 -0
  35. package/docs/reference/DECISION_DRIFT_GUARDS.md +97 -0
  36. package/docs/{EXAMPLES.md → reference/EXAMPLES.md} +7 -5
  37. package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +135 -0
  38. package/docs/reference/HERMES_INTEGRATION.md +118 -0
  39. package/docs/reference/INSIGHTS_RECAP.md +63 -0
  40. package/docs/reference/INSTALL.md +164 -0
  41. package/docs/reference/KERNEL_TAXONOMY_VALIDATION.md +161 -0
  42. package/docs/reference/PROTECTED_PATH_MANIFEST.md +25 -0
  43. package/docs/reference/RELEASE.md +68 -0
  44. package/docs/reference/RESEARCH_TEMPLATE.md +292 -0
  45. package/docs/{ROADMAP.md → reference/ROADMAP.md} +12 -9
  46. package/docs/reference/SKILLS.md +35 -0
  47. package/docs/reference/STATUS_BOARD.md +80 -0
  48. package/docs/reference/TEMPLATES.md +106 -0
  49. package/docs/reference/TOOLCHAIN.md +658 -0
  50. package/docs/reference/VALIDATION.md +82 -0
  51. package/docs/reference/agent-permissions.md +169 -0
  52. package/docs/reference/beads-to-kernel-migration-ux.md +61 -0
  53. package/docs/reference/control-plane-guarantees.md +125 -0
  54. package/docs/reference/dependency-chain.md +331 -0
  55. package/docs/reference/forge-kernel-issue-command-contract.md +161 -0
  56. package/docs/reference/forge-kernel-schema.md +72 -0
  57. package/docs/reference/kernel-conflict-evaluators.md +27 -0
  58. package/docs/reference/patch-md-format.md +77 -0
  59. package/docs/reference/protected-state-surfaces.md +59 -0
  60. package/docs/reference/shepherd.md +115 -0
  61. package/docs/reference/superpowers-analysis.md +320 -0
  62. package/docs/reference/superpowers-integration-options.md +404 -0
  63. package/docs/reference/test-environment.md +519 -0
  64. package/docs/reference/upgrade-safety.md +59 -0
  65. package/lefthook.yml +18 -0
  66. package/lib/adapter-cli.js +307 -0
  67. package/lib/adapters/beads-issue-adapter.js +127 -0
  68. package/lib/adapters/beads-kernel-compat.js +1042 -0
  69. package/lib/adapters/greptile-review-adapter.js +141 -0
  70. package/lib/adapters/kernel-issue-adapter.js +101 -0
  71. package/lib/adapters/pr-state-adapter.js +484 -0
  72. package/lib/adoption-profiles.js +126 -0
  73. package/lib/agents/README.md +2 -6
  74. package/lib/agents/claude.plugin.json +3 -8
  75. package/lib/agents/codex.plugin.json +9 -1
  76. package/lib/agents/cursor.plugin.json +2 -6
  77. package/lib/agents/hermes.plugin.json +22 -0
  78. package/lib/agents-config.js +39 -1236
  79. package/lib/audit-evidence.js +282 -0
  80. package/lib/beads-setup.js +225 -28
  81. package/lib/beads-sync-scaffold.js +36 -107
  82. package/lib/codex-skills.js +51 -1
  83. package/lib/commands/_issue.js +744 -70
  84. package/lib/commands/_manifest.js +91 -0
  85. package/lib/commands/_registry.js +85 -34
  86. package/lib/commands/_resolve-command-opts.js +261 -0
  87. package/lib/commands/_serve-security.js +270 -0
  88. package/lib/commands/adapter.js +12 -0
  89. package/lib/commands/add.js +118 -0
  90. package/lib/commands/audit.js +70 -0
  91. package/lib/commands/blocked.js +5 -0
  92. package/lib/commands/board.js +64 -0
  93. package/lib/commands/claim.js +21 -2
  94. package/lib/commands/claims.js +7 -0
  95. package/lib/commands/clean.js +485 -75
  96. package/lib/commands/close.js +2 -2
  97. package/lib/commands/comment.js +5 -0
  98. package/lib/commands/control.js +148 -0
  99. package/lib/commands/create.js +2 -2
  100. package/lib/commands/dev.js +185 -7
  101. package/lib/commands/doc-gate.js +336 -0
  102. package/lib/commands/doctor.js +156 -0
  103. package/lib/commands/explain.js +15 -0
  104. package/lib/commands/export.js +237 -0
  105. package/lib/commands/gate.js +192 -0
  106. package/lib/commands/hooks.js +242 -0
  107. package/lib/commands/inbox.js +118 -0
  108. package/lib/commands/init.js +598 -0
  109. package/lib/commands/insights.js +79 -0
  110. package/lib/commands/issue.js +12 -1
  111. package/lib/commands/issues.js +66 -0
  112. package/lib/commands/lint.js +5 -0
  113. package/lib/commands/list.js +2 -2
  114. package/lib/commands/merge.js +312 -0
  115. package/lib/commands/migrate.js +523 -0
  116. package/lib/commands/new.js +12 -0
  117. package/lib/commands/options.js +241 -0
  118. package/lib/commands/orient.js +13 -0
  119. package/lib/commands/orphans.js +5 -0
  120. package/lib/commands/patch.js +67 -0
  121. package/lib/commands/plan.js +436 -24
  122. package/lib/commands/preflight.js +211 -0
  123. package/lib/commands/prime.js +13 -0
  124. package/lib/commands/push.js +69 -2
  125. package/lib/commands/ready.js +2 -2
  126. package/lib/commands/recall.js +116 -0
  127. package/lib/commands/recap.js +61 -0
  128. package/lib/commands/recommend.js +22 -2
  129. package/lib/commands/release.js +91 -0
  130. package/lib/commands/remember.js +74 -0
  131. package/lib/commands/role.js +99 -0
  132. package/lib/commands/serve.js +581 -0
  133. package/lib/commands/setup.js +851 -979
  134. package/lib/commands/shepherd.js +436 -0
  135. package/lib/commands/ship.js +23 -1
  136. package/lib/commands/show.js +2 -2
  137. package/lib/commands/stage.js +192 -0
  138. package/lib/commands/stale.js +5 -0
  139. package/lib/commands/status.js +329 -11
  140. package/lib/commands/sync.js +34 -46
  141. package/lib/commands/team.js +15 -2
  142. package/lib/commands/test.js +58 -7
  143. package/lib/commands/update.js +2 -2
  144. package/lib/commands/upgrade.js +47 -0
  145. package/lib/commands/validate.js +56 -25
  146. package/lib/commands/worktree.js +308 -128
  147. package/lib/config-writer.js +202 -0
  148. package/lib/control-plane.js +236 -0
  149. package/lib/core/runtime-graph.js +946 -0
  150. package/lib/dep-guard/keyword-ripple.js +184 -0
  151. package/lib/deprecated-sync-cleanup.js +362 -0
  152. package/lib/detect-agent.js +2 -28
  153. package/lib/detect-worktree.js +42 -17
  154. package/lib/doc-gate/declaration.js +177 -0
  155. package/lib/doc-gate/detect.js +289 -0
  156. package/lib/doc-gate/gate.js +375 -0
  157. package/lib/doc-gate/okf-config.js +128 -0
  158. package/lib/doc-gate/okf.js +429 -0
  159. package/lib/docs-command.js +1161 -6
  160. package/lib/forge-issues.js +697 -0
  161. package/lib/forge-lock.js +262 -0
  162. package/lib/gate-events.js +193 -0
  163. package/lib/global-flags.js +74 -0
  164. package/lib/greptile-match.js +7 -63
  165. package/lib/harness-capability-matrix.js +380 -0
  166. package/lib/hook-global-installer.js +347 -0
  167. package/lib/hook-renderer.js +451 -0
  168. package/lib/inbox.js +391 -0
  169. package/lib/insights.js +397 -0
  170. package/lib/issue-adapter.js +156 -0
  171. package/lib/issue-backend.js +145 -0
  172. package/lib/issue-render.js +220 -0
  173. package/lib/issue-sync/authority.js +100 -0
  174. package/lib/issue-sync/github-pull.js +184 -0
  175. package/lib/issue-sync/import-primitives.js +98 -0
  176. package/lib/issue-sync/legacy-link-bridge.js +436 -0
  177. package/lib/issue-sync/link-store.js +292 -0
  178. package/lib/issue-sync/project-github.js +123 -0
  179. package/lib/issue-sync/reconcile.js +195 -0
  180. package/lib/issue-sync/schema.js +126 -0
  181. package/lib/kernel/backing-issue.js +305 -0
  182. package/lib/kernel/broker.js +1218 -0
  183. package/lib/kernel/cli-broker-factory.js +130 -0
  184. package/lib/kernel/conflict-signal.js +82 -0
  185. package/lib/kernel/evaluators.js +195 -0
  186. package/lib/kernel/fs-class.js +495 -0
  187. package/lib/kernel/issue-command-contract.js +559 -0
  188. package/lib/kernel/issue-id-resolver.js +186 -0
  189. package/lib/kernel/lease-enforcer.js +158 -0
  190. package/lib/kernel/migrations.js +333 -0
  191. package/lib/kernel/planning-buckets-schema.js +109 -0
  192. package/lib/kernel/projection-jsonl-writer.js +450 -0
  193. package/lib/kernel/readiness-model.js +329 -0
  194. package/lib/kernel/schema.js +356 -0
  195. package/lib/kernel/sqlite-driver.js +2504 -0
  196. package/lib/kernel/taxonomy-validator.js +394 -0
  197. package/lib/lefthook-check.js +8 -4
  198. package/lib/lefthook-wiring.js +413 -0
  199. package/lib/mcp-config-renderer.js +288 -0
  200. package/lib/memory/graphiti-mcp.js +106 -0
  201. package/lib/memory/router.js +387 -0
  202. package/lib/memory/typed-api.js +102 -0
  203. package/lib/memory-digest.js +195 -0
  204. package/lib/merge-rules.js +395 -0
  205. package/lib/migrate-dry-run.js +466 -0
  206. package/lib/orientation.js +863 -0
  207. package/lib/package-manager-remediation.js +103 -0
  208. package/lib/package-root.js +381 -0
  209. package/lib/patch-intent.js +890 -0
  210. package/lib/plugin-catalog.js +3 -4
  211. package/lib/plugin-manager.js +0 -5
  212. package/lib/pr-bundle.js +186 -0
  213. package/lib/pr-monitor/differ.js +195 -0
  214. package/lib/pr-monitor/events.js +0 -0
  215. package/lib/pr-monitor/gather.js +124 -0
  216. package/lib/pr-monitor/journal.js +299 -0
  217. package/lib/pr-monitor/monitor.js +146 -0
  218. package/lib/pr-monitor/render-sticky.js +157 -0
  219. package/lib/pr-monitor/watch-lifecycle.js +95 -0
  220. package/lib/pr-monitor/watch.js +247 -0
  221. package/lib/pr-pull.js +1273 -0
  222. package/lib/pr-shepherd.js +494 -0
  223. package/lib/pr-state-validator.js +59 -0
  224. package/lib/preflight/gates.js +237 -0
  225. package/lib/preflight/runner.js +116 -0
  226. package/lib/project-discovery.js +0 -53
  227. package/lib/project-memory.js +166 -0
  228. package/lib/protected-path-manifest.js +281 -0
  229. package/lib/protected-state-surfaces.js +387 -0
  230. package/lib/release-readiness.js +2089 -0
  231. package/lib/reset.js +59 -45
  232. package/lib/review-adapter.js +68 -0
  233. package/lib/rules-sync.js +260 -0
  234. package/lib/runtime-health.js +332 -23
  235. package/lib/safety-config-renderer.js +268 -0
  236. package/lib/setup-action-log.js +1 -7
  237. package/lib/setup.js +27 -65
  238. package/lib/shell-utils.js +76 -6
  239. package/lib/skills-sync.js +330 -0
  240. package/lib/smart-status/conflicts.js +205 -0
  241. package/lib/smart-status/scoring.js +191 -0
  242. package/lib/status/beads-snapshot.js +145 -0
  243. package/lib/status/presenter.js +216 -0
  244. package/lib/status/snapshot.js +186 -0
  245. package/lib/sync-backend.js +202 -0
  246. package/lib/untrusted-content.js +52 -0
  247. package/lib/upgrade-safety.js +199 -0
  248. package/lib/workflow/enforce-stage.js +298 -47
  249. package/lib/workflow/stage-transition.js +115 -0
  250. package/lib/workflow/stages.js +30 -6
  251. package/lib/workflow/state-manager.js +159 -14
  252. package/lib/workflow/state.js +23 -1
  253. package/lib/workflow-profiles.js +17 -5
  254. package/package.json +46 -36
  255. package/rules/documentation.md +19 -0
  256. package/rules/kernel-tracking.md +26 -0
  257. package/rules/security.md +22 -0
  258. package/rules/tdd.md +20 -0
  259. package/rules/workflow.md +27 -0
  260. package/scripts/auto-backing-issue.js +47 -0
  261. package/scripts/beads-context.sh +165 -22
  262. package/scripts/beads-migrate-to-dolt.sh +7 -0
  263. package/scripts/beads-upgrade-smoke.sh +284 -0
  264. package/scripts/behavioral-judge.sh +115 -11
  265. package/scripts/benchmark.js +349 -63
  266. package/scripts/bootstrap-windows-tools.sh +78 -0
  267. package/scripts/branch-protection.js +2 -3
  268. package/scripts/check-agents.js +34 -137
  269. package/scripts/commitlint.js +3 -1
  270. package/scripts/conflict-detect.sh +3 -0
  271. package/scripts/dep-guard-analyze.js +52 -17
  272. package/scripts/dep-guard-keyword-ripple.js +29 -0
  273. package/scripts/dep-guard-render-review.js +86 -0
  274. package/scripts/dep-guard.sh +64 -232
  275. package/scripts/file-index.sh +3 -0
  276. package/scripts/forge-team/lib/claim.sh +34 -18
  277. package/scripts/forge-team/lib/dashboard.sh +61 -86
  278. package/scripts/forge-team/lib/epic.sh +99 -263
  279. package/scripts/forge-team/lib/hooks.sh +26 -28
  280. package/scripts/forge-team/lib/identity.sh +4 -4
  281. package/scripts/forge-team/lib/sync-github.sh +144 -47
  282. package/scripts/forge-team/lib/verify.sh +93 -83
  283. package/scripts/forge-team/lib/workload.sh +41 -65
  284. package/scripts/forge-team/tests/claim.test.sh +25 -19
  285. package/scripts/forge-team/tests/dashboard.test.sh +31 -46
  286. package/scripts/forge-team/tests/epic.test.sh +52 -71
  287. package/scripts/forge-team/tests/hooks.test.sh +38 -50
  288. package/scripts/forge-team/tests/identity.test.sh +3 -3
  289. package/scripts/forge-team/tests/integration.test.sh +44 -66
  290. package/scripts/forge-team/tests/sync-github.test.sh +183 -79
  291. package/scripts/forge-team/tests/verify.test.sh +37 -46
  292. package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
  293. package/scripts/forge-team/tests/workload.test.sh +32 -66
  294. package/scripts/gen-command-manifest.js +153 -0
  295. package/scripts/gen-embedded-assets.mjs +129 -0
  296. package/scripts/install.ps1 +139 -0
  297. package/scripts/install.sh +268 -0
  298. package/scripts/lib/beads-migrate-to-dolt.mjs +503 -0
  299. package/scripts/lib/release-asset.mjs +84 -0
  300. package/scripts/parity-check.mjs +145 -0
  301. package/scripts/parity-check.test.mjs +58 -0
  302. package/scripts/pin-agentic-workflow-images.js +112 -0
  303. package/scripts/pr-coordinator.sh +3 -0
  304. package/scripts/preflight-sonar.eslint.config.mjs +44 -0
  305. package/scripts/preflight.sh +108 -0
  306. package/scripts/protected-state-check.js +104 -0
  307. package/scripts/smart-status-score.js +31 -0
  308. package/scripts/smart-status-sessions.js +51 -0
  309. package/scripts/smart-status.sh +117 -369
  310. package/scripts/spikes/config-race-bench.js +111 -0
  311. package/scripts/spikes/harness-capability-matrix.js +13 -0
  312. package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
  313. package/scripts/spikes/protected-path-manifest.js +20 -0
  314. package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
  315. package/scripts/sync-agent-skills.js +62 -0
  316. package/scripts/sync-agentic-workflow.js +48 -0
  317. package/scripts/sync-utils.sh +3 -0
  318. package/scripts/test-ci-shard.js +251 -0
  319. package/scripts/test-dashboard.js +188 -52
  320. package/scripts/test-full-suite.js +186 -0
  321. package/scripts/test-profile.js +278 -0
  322. package/scripts/test.js +302 -28
  323. package/scripts/validate.js +143 -0
  324. package/scripts/validate.sh +18 -1
  325. package/skills/claim-safety/SKILL.md +102 -0
  326. package/skills/claim-safety/evals/evals.json +46 -0
  327. package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +46 -52
  328. package/skills/dev/evals/evals.json +50 -0
  329. package/skills/hermes-forge/SKILL.md +185 -0
  330. package/skills/hermes-forge/evals/evals.json +46 -0
  331. package/skills/issue-basics/SKILL.md +111 -0
  332. package/skills/issue-basics/evals/evals.json +46 -0
  333. package/skills/kernel/SKILL.md +166 -0
  334. package/skills/kernel/evals/evals.json +50 -0
  335. package/skills/memory/SKILL.md +102 -0
  336. package/skills/parallel-deep-research/SKILL.md +14 -11
  337. package/skills/parallel-deep-research/evals/evals.json +11 -27
  338. package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +134 -159
  339. package/skills/plan/evals/evals.json +42 -0
  340. package/skills/research/SKILL.md +195 -0
  341. package/skills/research/evals/evals.json +42 -0
  342. package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
  343. package/skills/review/evals/evals.json +42 -0
  344. package/skills/rollback/SKILL.md +110 -0
  345. package/skills/rollback/evals/evals.json +46 -0
  346. package/skills/rollback/references/methods.md +204 -0
  347. package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
  348. package/skills/shepherd/SKILL.md +66 -0
  349. package/skills/shepherd/evals/evals.json +42 -0
  350. package/skills/ship/SKILL.md +251 -0
  351. package/skills/ship/evals/evals.json +42 -0
  352. package/skills/smith/SKILL.md +142 -0
  353. package/skills/smith/evals/evals.json +46 -0
  354. package/skills/smith/references/autonomy-and-gates.md +94 -0
  355. package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
  356. package/skills/sonarcloud/evals/evals.json +46 -0
  357. package/skills/sonarcloud-analysis/SKILL.md +18 -13
  358. package/skills/sonarcloud-analysis/evals/evals.json +11 -15
  359. package/skills/status/SKILL.md +102 -0
  360. package/skills/status/evals/evals.json +50 -0
  361. package/skills/triage-ready/SKILL.md +121 -0
  362. package/skills/triage-ready/evals/evals.json +42 -0
  363. package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
  364. package/skills/validate/evals/evals.json +42 -0
  365. package/skills/verify/SKILL.md +299 -0
  366. package/skills/verify/evals/evals.json +50 -0
  367. package/.claude/commands/dev.md +0 -345
  368. package/.claude/commands/plan.md +0 -566
  369. package/.claude/commands/premerge.md +0 -186
  370. package/.claude/commands/research.md +0 -42
  371. package/.claude/commands/review.md +0 -451
  372. package/.claude/commands/rollback.md +0 -721
  373. package/.claude/commands/ship.md +0 -213
  374. package/.claude/commands/sonarcloud.md +0 -152
  375. package/.claude/commands/status.md +0 -90
  376. package/.claude/commands/validate.md +0 -288
  377. package/.claude/commands/verify.md +0 -269
  378. package/.claude/rules/workflow.md +0 -121
  379. package/.cline/workflows/dev.md +0 -342
  380. package/.cline/workflows/plan.md +0 -563
  381. package/.cline/workflows/premerge.md +0 -183
  382. package/.cline/workflows/research.md +0 -39
  383. package/.cline/workflows/review.md +0 -448
  384. package/.cline/workflows/rollback.md +0 -718
  385. package/.cline/workflows/ship.md +0 -210
  386. package/.cline/workflows/sonarcloud.md +0 -146
  387. package/.cline/workflows/status.md +0 -87
  388. package/.cline/workflows/validate.md +0 -285
  389. package/.cline/workflows/verify.md +0 -266
  390. package/.codex/config.toml +0 -11
  391. package/.codex/skills/dev/SKILL.md +0 -345
  392. package/.codex/skills/plan/SKILL.md +0 -566
  393. package/.codex/skills/premerge/SKILL.md +0 -186
  394. package/.codex/skills/research/SKILL.md +0 -42
  395. package/.codex/skills/review/SKILL.md +0 -451
  396. package/.codex/skills/rollback/SKILL.md +0 -721
  397. package/.codex/skills/ship/SKILL.md +0 -213
  398. package/.codex/skills/sonarcloud/SKILL.md +0 -149
  399. package/.codex/skills/status/SKILL.md +0 -90
  400. package/.codex/skills/validate/SKILL.md +0 -288
  401. package/.codex/skills/verify/SKILL.md +0 -269
  402. package/.cursor/commands/dev.md +0 -342
  403. package/.cursor/commands/plan.md +0 -563
  404. package/.cursor/commands/premerge.md +0 -183
  405. package/.cursor/commands/research.md +0 -39
  406. package/.cursor/commands/review.md +0 -448
  407. package/.cursor/commands/ship.md +0 -210
  408. package/.cursor/commands/sonarcloud.md +0 -146
  409. package/.cursor/commands/status.md +0 -87
  410. package/.cursor/commands/validate.md +0 -285
  411. package/.cursor/commands/verify.md +0 -266
  412. package/.cursorrules +0 -149
  413. package/.github/prompts/premerge.prompt.md +0 -188
  414. package/.github/prompts/research.prompt.md +0 -44
  415. package/.github/prompts/rollback.prompt.md +0 -723
  416. package/.github/prompts/ship.prompt.md +0 -215
  417. package/.github/prompts/status.prompt.md +0 -92
  418. package/.github/prompts/verify.prompt.md +0 -271
  419. package/.github/workflows/beads-to-github.yml +0 -56
  420. package/.github/workflows/github-to-beads.yml +0 -97
  421. package/.kilocode/workflows/dev.md +0 -346
  422. package/.kilocode/workflows/plan.md +0 -567
  423. package/.kilocode/workflows/premerge.md +0 -187
  424. package/.kilocode/workflows/research.md +0 -43
  425. package/.kilocode/workflows/review.md +0 -452
  426. package/.kilocode/workflows/rollback.md +0 -722
  427. package/.kilocode/workflows/ship.md +0 -214
  428. package/.kilocode/workflows/sonarcloud.md +0 -150
  429. package/.kilocode/workflows/status.md +0 -91
  430. package/.kilocode/workflows/validate.md +0 -289
  431. package/.kilocode/workflows/verify.md +0 -270
  432. package/.opencode/commands/dev.md +0 -345
  433. package/.opencode/commands/plan.md +0 -566
  434. package/.opencode/commands/premerge.md +0 -186
  435. package/.opencode/commands/research.md +0 -42
  436. package/.opencode/commands/review.md +0 -451
  437. package/.opencode/commands/rollback.md +0 -721
  438. package/.opencode/commands/ship.md +0 -213
  439. package/.opencode/commands/sonarcloud.md +0 -149
  440. package/.opencode/commands/status.md +0 -90
  441. package/.opencode/commands/validate.md +0 -288
  442. package/.opencode/commands/verify.md +0 -269
  443. package/.roo/commands/dev.md +0 -346
  444. package/.roo/commands/plan.md +0 -567
  445. package/.roo/commands/premerge.md +0 -187
  446. package/.roo/commands/research.md +0 -43
  447. package/.roo/commands/review.md +0 -452
  448. package/.roo/commands/rollback.md +0 -722
  449. package/.roo/commands/ship.md +0 -214
  450. package/.roo/commands/sonarcloud.md +0 -150
  451. package/.roo/commands/status.md +0 -91
  452. package/.roo/commands/validate.md +0 -289
  453. package/.roo/commands/verify.md +0 -270
  454. package/docs/BEADS_GITHUB_SYNC.md +0 -255
  455. package/docs/GREPTILE_SETUP.md +0 -400
  456. package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
  457. package/docs/SETUP.md +0 -663
  458. package/docs/VALIDATION.md +0 -363
  459. package/lib/agents/cline.plugin.json +0 -29
  460. package/lib/agents/copilot.plugin.json +0 -24
  461. package/lib/agents/kilocode.plugin.json +0 -22
  462. package/lib/agents/opencode.plugin.json +0 -23
  463. package/lib/agents/roo.plugin.json +0 -30
  464. package/lib/beads-health-check.js +0 -143
  465. package/lib/commands/commands-reset.js +0 -147
  466. package/opencode.json +0 -67
  467. package/scripts/beads-context.test.js +0 -567
  468. package/scripts/github-beads-sync/comment.mjs +0 -64
  469. package/scripts/github-beads-sync/config.mjs +0 -148
  470. package/scripts/github-beads-sync/github-api.mjs +0 -131
  471. package/scripts/github-beads-sync/index.mjs +0 -332
  472. package/scripts/github-beads-sync/label-mapper.mjs +0 -54
  473. package/scripts/github-beads-sync/mapping.mjs +0 -78
  474. package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
  475. package/scripts/github-beads-sync/reverse-sync.mjs +0 -138
  476. package/scripts/github-beads-sync/run-bd.mjs +0 -161
  477. package/scripts/github-beads-sync/sanitize.mjs +0 -121
  478. package/scripts/github-beads-sync.config.json +0 -26
  479. package/scripts/sync-commands.js +0 -600
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { repairWorkflowRuntimeAssets } = require('../commands/setup');
4
4
  const { checkRuntimeHealth } = require('../runtime-health');
5
+ const { resolveIssueBackend } = require('../issue-backend');
5
6
  const { normalizeStageId } = require('./stages');
6
7
  const {
7
8
  getAllowedTransitionsForWorkflowState,
@@ -10,6 +11,173 @@ const {
10
11
  } = require('./state');
11
12
  const { loadState, WORKFLOW_STATE_FILENAME } = require('./state-manager');
12
13
 
14
+ const STATELESS_ENTRY_STAGES = new Set(['plan', 'dev', 'validate', 'verify']);
15
+
16
+ const UUID_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;
17
+
18
+ // Canonical full-ladder path (critical superset). Used to derive a stage's
19
+ // immediate predecessor for the kernel completion gate.
20
+ const STAGE_PATH = Object.freeze(['plan', 'dev', 'validate', 'ship', 'review', 'verify']);
21
+
22
+ function stagePredecessor(stageId) {
23
+ const index = STAGE_PATH.indexOf(stageId);
24
+ return index > 0 ? STAGE_PATH[index - 1] : null;
25
+ }
26
+
27
+ // Emit a warning to stderr. Writes even under FORGE_JSON=1 on purpose: stderr
28
+ // never pollutes machine-readable stdout, and a dropped kernel write must always
29
+ // leave a trace ("never silent") so a JSON-mode agent can see it.
30
+ function defaultStageWarn(message) {
31
+ process.stderr.write(`${message}\n`);
32
+ }
33
+
34
+ // Best-effort kernel write (action 'start' | 'complete') at the stage
35
+ // chokepoint. Idempotent per (issue_id, stage). A failure warns to stderr but
36
+ // never throws — ship's gate stays tolerant of whatever IS durably recorded.
37
+ function recordStageRunSafe(driver, issueId, stageId, action, warn = defaultStageWarn) {
38
+ if (!driver || !issueId || !stageId || typeof driver.recordStageRun !== 'function') {
39
+ return false;
40
+ }
41
+
42
+ try {
43
+ driver.recordStageRun({ issue_id: issueId, stage: stageId, action }, {});
44
+ return true;
45
+ } catch (error) {
46
+ warn(`[forge] could not record stage '${stageId}' (${action}) for ${issueId} in the kernel: ${error.message}`);
47
+ return false;
48
+ }
49
+ }
50
+
51
+ // Latest stage-run row for a specific (issue, stage), or null.
52
+ function findLatestStageRun(driver, issueId, stage) {
53
+ if (!driver || typeof driver.listStageRuns !== 'function') {
54
+ return null;
55
+ }
56
+
57
+ try {
58
+ const runs = driver.listStageRuns({ issue_id: issueId }, {}) || [];
59
+ let latest = null;
60
+ for (const run of runs) {
61
+ if (run?.stage === stage) {
62
+ latest = run;
63
+ }
64
+ }
65
+ return latest;
66
+ } catch {
67
+ return null;
68
+ }
69
+ }
70
+
71
+ // Decide whether entering stageId is allowed given ONLY the kernel's recorded
72
+ // stage history (used when no inline/file workflow state exists):
73
+ // - Stateless stages (plan/dev/validate/verify) are always re-entrant, so the
74
+ // dev<->validate rework loop never dead-ends.
75
+ // - A gated stage (ship/review) requires its immediate predecessor to be
76
+ // COMPLETED (status 'done'); merely ENTERING validate does not unlock ship.
77
+ // - When nothing is recorded for the predecessor, returns { kernelEmpty } so
78
+ // the caller falls through to the fail-closed hard block.
79
+ function evaluateKernelStageGate(driver, issueId, stageId) {
80
+ if (STATELESS_ENTRY_STAGES.has(stageId)) {
81
+ return { allowed: true };
82
+ }
83
+
84
+ const predecessor = stagePredecessor(stageId);
85
+ const predecessorRun = predecessor ? findLatestStageRun(driver, issueId, predecessor) : null;
86
+ if (predecessorRun?.status === 'done') {
87
+ return { allowed: true };
88
+ }
89
+ if (predecessorRun) {
90
+ return {
91
+ allowed: false,
92
+ reason: `Stage ${stageId} requires ${predecessor} to be completed first (currently ${predecessorRun.status}).`,
93
+ };
94
+ }
95
+ return { allowed: false, kernelEmpty: true };
96
+ }
97
+
98
+ // Verify a kernel issue exists before binding stage state to it (F4a: a UUID
99
+ // parsed from a branch name must not bind to a phantom issue).
100
+ async function kernelIssueExists(driver, issueId) {
101
+ if (!driver || typeof driver.findIssueIdsByPrefix !== 'function') {
102
+ return false;
103
+ }
104
+
105
+ try {
106
+ // findIssueIdsByPrefix returns rows ({ id, title }); tolerate plain-id shapes too.
107
+ const matches = await driver.findIssueIdsByPrefix(issueId, 6, {}, {});
108
+ return Array.isArray(matches) && matches.some(row => (row?.id ?? row) === issueId);
109
+ } catch {
110
+ return false;
111
+ }
112
+ }
113
+
114
+ // Resolve the kernel issue that THIS worktree/branch is working on, so stage
115
+ // state can be read and written without an explicit issue argument on `forge
116
+ // ship`. Prefers the branch->issue linkage registry (authoritative); falls back
117
+ // to a UUID encoded in the branch name, but ONLY after verifying it exists.
118
+ async function resolveActiveIssueId(driver, branch) {
119
+ if (!driver || !branch) {
120
+ return null;
121
+ }
122
+
123
+ try {
124
+ if (typeof driver.listWorktrees === 'function') {
125
+ const rows = driver.listWorktrees() || [];
126
+ const match = rows.find(row => row && row.branch === branch && row.issue_id);
127
+ if (match) {
128
+ return match.issue_id;
129
+ }
130
+ }
131
+ } catch {
132
+ // Fall through to branch-name parsing.
133
+ }
134
+
135
+ const encoded = UUID_RE.exec(String(branch));
136
+ if (encoded && await kernelIssueExists(driver, encoded[0])) {
137
+ return encoded[0];
138
+ }
139
+ return null;
140
+ }
141
+
142
+ // Lazily build a kernel driver from the project root (the real CLI path).
143
+ // Best-effort: returns null when the kernel is unavailable so the caller
144
+ // degrades to legacy file/beads state instead of crashing a stage command.
145
+ async function buildKernelDriver(projectRoot) {
146
+ if (!projectRoot) {
147
+ return null;
148
+ }
149
+
150
+ try {
151
+ const { buildMigratedKernelIssueDeps } = require('../kernel/cli-broker-factory');
152
+ const deps = await buildMigratedKernelIssueDeps({ projectRoot });
153
+ return deps.kernelDriver || null;
154
+ } catch {
155
+ return null;
156
+ }
157
+ }
158
+
159
+ function detectBranchName(projectRoot) {
160
+ try {
161
+ const { detectWorktree } = require('../detect-worktree');
162
+ const info = detectWorktree(projectRoot || process.cwd());
163
+ return info?.branch || null;
164
+ } catch {
165
+ return null;
166
+ }
167
+ }
168
+
169
+ // Resolve { driver, issueId } for kernel stage-state: build the driver from the
170
+ // project root and resolve the active issue from the branch when not explicitly
171
+ // injected. Best-effort — returns nulls when the kernel is absent.
172
+ async function resolveKernelContext({ kernelDriver, activeIssueId, branch, projectRoot }) {
173
+ const driver = kernelDriver || await buildKernelDriver(projectRoot);
174
+ let issueId = activeIssueId || null;
175
+ if (driver && !issueId) {
176
+ issueId = await resolveActiveIssueId(driver, branch || detectBranchName(projectRoot));
177
+ }
178
+ return { driver, issueId };
179
+ }
180
+
13
181
  function getOverrideInput(flags = {}) {
14
182
  if (Object.hasOwn(flags, 'overrideStage')) {
15
183
  return flags.overrideStage;
@@ -101,55 +269,18 @@ function formatDiagnostics(diagnostics = []) {
101
269
  .join('; ');
102
270
  }
103
271
 
104
- async function enforceStageEntry({ commandName, args = [], flags = {}, projectRoot, workflowState, health, repairRuntime } = {}) {
105
- const stageId = normalizeStageId(commandName);
106
- if (!stageId) {
107
- return { allowed: true };
108
- }
109
-
110
- if (projectRoot) {
111
- repairWorkflowRuntimeAssets(projectRoot);
112
- }
113
-
114
- let runtimeHealth = health || checkRuntimeHealth(projectRoot);
115
- if (runtimeHealth.hardStop && typeof repairRuntime === 'function') {
116
- const repairedHealth = await repairRuntime({
117
- commandName,
118
- flags,
119
- projectRoot,
120
- workflowState,
121
- health: runtimeHealth,
122
- });
123
- if (repairedHealth) {
124
- runtimeHealth = repairedHealth;
125
- }
126
- }
127
- if (runtimeHealth.hardStop) {
128
- throw new Error(`Stage ${stageId} blocked by runtime prerequisites: ${formatDiagnostics(runtimeHealth.diagnostics)}`);
129
- }
130
-
131
- const stateInput = resolveWorkflowStateInput(workflowState, flags, args, projectRoot);
132
- const currentState = readWorkflowStateInput(stateInput);
133
- if (!currentState) {
134
- if (stageId === 'plan') {
135
- return { allowed: true, stage: stageId, workflowState: null };
136
- }
137
-
138
- throw new Error(
139
- `Stage ${stageId} requires authoritative workflow state. ` +
140
- `Provide --workflow-state or restore ${WORKFLOW_STATE_FILENAME} before continuing.`
141
- );
142
- }
143
-
272
+ // Enforce a stage entry against authoritative FILE/inline workflow state
273
+ // (unchanged legacy behavior: normal path-transition + override rules).
274
+ function enforceWithFileState(currentState, stageId, flags, args, finish) {
144
275
  const currentStage = currentState.currentStage;
145
276
  const classification = currentState.workflowDecisions?.classification;
146
277
  if (!currentStage || !classification || stageId === currentStage) {
147
- return { allowed: true, stage: stageId, workflowState: currentState };
278
+ return finish({ allowed: true, stage: stageId, workflowState: currentState });
148
279
  }
149
280
 
150
281
  const allowedTransitions = getAllowedTransitionsForWorkflowState(currentState);
151
282
  if (allowedTransitions.includes(stageId)) {
152
- return { allowed: true, stage: stageId, workflowState: currentState };
283
+ return finish({ allowed: true, stage: stageId, workflowState: currentState });
153
284
  }
154
285
 
155
286
  const override = parseOverride(flags, args);
@@ -166,12 +297,128 @@ async function enforceStageEntry({ commandName, args = [], flags = {}, projectRo
166
297
  );
167
298
  }
168
299
 
169
- return {
170
- allowed: true,
171
- stage: stageId,
172
- workflowState: currentState,
173
- override,
300
+ return finish({ allowed: true, stage: stageId, workflowState: currentState, override });
301
+ }
302
+
303
+ function isInlineStateProvided(workflowState, flags, args) {
304
+ return Boolean(
305
+ workflowState || flags.workflowState || flags['--workflow-state'] || getCliFlagValue('--workflow-state', args)
306
+ );
307
+ }
308
+
309
+ // Enforce a stage entry against kernel-recorded stage state (completion gate).
310
+ // Returns a decided enforcement result, or null to fall through to the
311
+ // stateless / hard-block rules.
312
+ function enforceWithKernelState(driver, issueId, stageId, finish) {
313
+ const gate = evaluateKernelStageGate(driver, issueId, stageId);
314
+ if (gate.allowed) {
315
+ return finish({ allowed: true, stage: stageId, workflowState: null });
316
+ }
317
+ if (!gate.kernelEmpty) {
318
+ throw new Error(gate.reason);
319
+ }
320
+ return null;
321
+ }
322
+
323
+ async function resolveStageRuntimeHealth({ health, checkHealth, projectRoot, issueBackend, commandName, flags, workflowState, repairRuntime }) {
324
+ const runHealthCheck = checkHealth || checkRuntimeHealth;
325
+ let runtimeHealth = health || runHealthCheck(projectRoot, { issueBackend });
326
+ if (runtimeHealth.hardStop && typeof repairRuntime === 'function') {
327
+ const repaired = await repairRuntime({ commandName, flags, projectRoot, workflowState, health: runtimeHealth });
328
+ if (repaired) {
329
+ runtimeHealth = repaired;
330
+ }
331
+ }
332
+ return runtimeHealth;
333
+ }
334
+
335
+ async function enforceStageEntry({
336
+ commandName,
337
+ args = [],
338
+ flags = {},
339
+ projectRoot,
340
+ workflowState,
341
+ health,
342
+ repairRuntime,
343
+ checkHealth,
344
+ // B1 — kernel stage-state authority. Injectable for tests; the real CLI sets
345
+ // autoResolveKernel:true so the driver + active issue are resolved from the
346
+ // worktree. When neither a driver nor autoResolveKernel is provided, the
347
+ // kernel path is inert and behavior matches the legacy file/beads state.
348
+ kernelDriver,
349
+ activeIssueId,
350
+ branch,
351
+ autoResolveKernel = false,
352
+ warn = defaultStageWarn,
353
+ } = {}) {
354
+ const stageId = normalizeStageId(commandName);
355
+ if (!stageId) {
356
+ return { allowed: true };
357
+ }
358
+
359
+ if (projectRoot) {
360
+ repairWorkflowRuntimeAssets(projectRoot);
361
+ }
362
+
363
+ // Resolve the active issue backend (env > .forge/config.yaml > default 'kernel') so
364
+ // the runtime gate only treats bd as a hard prerequisite for the beads backend. The
365
+ // kernel default needs no bd, so stages must run without it.
366
+ const issueBackend = resolveIssueBackend({ deps: {}, env: process.env, projectRoot, warn: () => {} });
367
+ const runtimeHealth = await resolveStageRuntimeHealth({
368
+ health, checkHealth, projectRoot, issueBackend, commandName, flags, workflowState, repairRuntime,
369
+ });
370
+ if (runtimeHealth.hardStop) {
371
+ throw new Error(`Stage ${stageId} blocked by runtime prerequisites: ${formatDiagnostics(runtimeHealth.diagnostics)}`);
372
+ }
373
+
374
+ const stateInput = resolveWorkflowStateInput(workflowState, flags, args, projectRoot);
375
+
376
+ // Kernel stage-state authority is active when a driver is injected (tests) or
377
+ // the caller opts in (real CLI via autoResolveKernel). Inline/flag state
378
+ // disables it: the caller is explicitly driving state, so no kernel side
379
+ // effects should occur.
380
+ const kernelEnabled = !isInlineStateProvided(workflowState, flags, args)
381
+ && (Boolean(kernelDriver) || autoResolveKernel === true);
382
+ const { driver, issueId } = kernelEnabled
383
+ ? await resolveKernelContext({ kernelDriver, activeIssueId, branch, projectRoot })
384
+ : { driver: null, issueId: null };
385
+ const kernelActive = Boolean(kernelEnabled && driver && issueId);
386
+
387
+ // On an allowed entry, record the stage as started (active) AND return a
388
+ // recordCompletion() the command runner calls after the handler SUCCEEDS — so
389
+ // a stage only counts as 'done' when its command actually passed (the ship
390
+ // gate below requires the predecessor to be done, not merely entered).
391
+ const finish = (result) => {
392
+ if (kernelActive) {
393
+ recordStageRunSafe(driver, issueId, stageId, 'start', warn);
394
+ result.recordCompletion = () => recordStageRunSafe(driver, issueId, stageId, 'complete', warn);
395
+ }
396
+ return result;
174
397
  };
398
+
399
+ const currentState = readWorkflowStateInput(stateInput);
400
+ if (currentState) {
401
+ return enforceWithFileState(currentState, stageId, flags, args, finish);
402
+ }
403
+
404
+ // No inline/file state: the kernel is authoritative. Gate on recorded stage
405
+ // completions (tolerant read) so `ship` is reachable from a pure-CLI
406
+ // plan->dev->validate progression with no .forge-state.json.
407
+ if (kernelActive) {
408
+ const decided = enforceWithKernelState(driver, issueId, stageId, finish);
409
+ if (decided) {
410
+ return decided;
411
+ }
412
+ }
413
+
414
+ if (STATELESS_ENTRY_STAGES.has(stageId)) {
415
+ return finish({ allowed: true, stage: stageId, workflowState: null });
416
+ }
417
+
418
+ throw new Error(
419
+ `Stage ${stageId} requires authoritative workflow state. ` +
420
+ `Provide --workflow-state or restore ${WORKFLOW_STATE_FILENAME} before continuing.`
421
+ );
175
422
  }
176
423
 
177
424
  module.exports = {
@@ -180,4 +427,8 @@ module.exports = {
180
427
  parseOverride,
181
428
  resolveWorkflowStateInput,
182
429
  readWorkflowStateFile,
430
+ resolveActiveIssueId,
431
+ evaluateKernelStageGate,
432
+ recordStageRunSafe,
433
+ stagePredecessor,
183
434
  };
@@ -0,0 +1,115 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Stage-transition auto-recording (5a5ba3a6).
5
+ *
6
+ * PR #348 (f61601ab) added the stage_runs record/read capability but nothing
7
+ * populated it automatically — `current_stage` stayed unknown. The Descriptive
8
+ * Context Convention (AGENTS.md) already has agents record each stage boundary as
9
+ * a kernel issue comment shaped:
10
+ *
11
+ * stage: <from> -> <to>
12
+ * summary: ...
13
+ *
14
+ * This module turns that existing, already-followed convention into a structured
15
+ * stage_run WITHOUT any new manual verb call: parse the `stage:` line and, best
16
+ * effort, complete the from-stage and start the to-stage. Recording is strictly
17
+ * non-blocking — a stage_run write failure must NEVER break the comment that
18
+ * triggered it.
19
+ *
20
+ * @module workflow/stage-transition
21
+ */
22
+
23
+ const { normalizeStageId } = require('./stages');
24
+
25
+ // `stage: <from> -> <to>` on its own line, case-insensitive, arrow spacing optional.
26
+ const STAGE_LINE = /^\s*stage:\s*([a-z]+)\s*->\s*([a-z]+)\s*$/im;
27
+
28
+ /**
29
+ * Parse a stage-transition line out of a comment body.
30
+ *
31
+ * @param {string} body - Comment body (may be multi-line).
32
+ * @returns {{from: string, to: string}|null} Normalized stages, or null when the
33
+ * body has no valid `stage: X -> Y` line (both tokens must be canonical stages).
34
+ */
35
+ function parseStageTransition(body) {
36
+ if (typeof body !== 'string' || body.length === 0) {
37
+ return null;
38
+ }
39
+ const match = body.match(STAGE_LINE);
40
+ if (!match) {
41
+ return null;
42
+ }
43
+ // STAGE_LINE is case-insensitive, so an uppercase token (e.g. `stage: DEV ->
44
+ // VALIDATE`) matches. normalizeStageId only knows lowercase canonical ids, so
45
+ // lowercase both captures first or an uppercase-but-valid stage is wrongly rejected.
46
+ const from = normalizeStageId(match[1].toLowerCase());
47
+ const to = normalizeStageId(match[2].toLowerCase());
48
+ if (!from || !to) {
49
+ return null;
50
+ }
51
+ return { from, to };
52
+ }
53
+
54
+ /**
55
+ * Best-effort record of a stage transition into stage_runs. Parses the comment
56
+ * body; on a valid transition it completes the from-stage and starts the to-stage.
57
+ *
58
+ * The write must be ATOMIC: completing `from` and starting `to` are one logical
59
+ * transition, so a failure partway through must leave neither persisted (otherwise
60
+ * `current_stage` reflects a half-transition — from marked done, to never started).
61
+ * The preferred path is a single transactional `driver.recordStageTransition` that
62
+ * wraps both writes in one transaction; a driver that only exposes `recordStageRun`
63
+ * falls back to two sequential writes (non-atomic, tolerated only because the whole
64
+ * operation is non-blocking).
65
+ *
66
+ * ANY failure (parse miss, missing driver, DB error) is swallowed and reported as
67
+ * `{ recorded: false }` — this function never throws.
68
+ *
69
+ * @param {object} params
70
+ * @param {object} [params.driver] - Kernel driver exposing recordStageTransition
71
+ * (preferred) and/or recordStageRun(input, config).
72
+ * @param {string} params.issueId - Full kernel issue id the comment targets.
73
+ * @param {string} params.body - The comment body just written.
74
+ * @param {object} [params.config] - Driver config (e.g. { databasePath }).
75
+ * @returns {{recorded: boolean, from?: string, to?: string}}
76
+ */
77
+ function recordStageTransition({ driver, issueId, body, config = {} } = {}) {
78
+ try {
79
+ const transition = parseStageTransition(body);
80
+ if (!transition || !issueId || !driver) {
81
+ return { recorded: false };
82
+ }
83
+ // Preferred: one atomic driver op — complete(from) + start(to) inside a single
84
+ // transaction, so a mid-transition failure rolls back and never persists a
85
+ // wrong `current_stage`.
86
+ if (typeof driver.recordStageTransition === 'function') {
87
+ driver.recordStageTransition(
88
+ { issue_id: issueId, from: transition.from, to: transition.to },
89
+ config,
90
+ );
91
+ return { from: transition.from, to: transition.to, recorded: true };
92
+ }
93
+ // Fallback for a minimal driver without the atomic op: two sequential writes.
94
+ if (typeof driver.recordStageRun === 'function') {
95
+ driver.recordStageRun(
96
+ { issue_id: issueId, stage: transition.from, action: 'complete' },
97
+ config,
98
+ );
99
+ driver.recordStageRun(
100
+ { issue_id: issueId, stage: transition.to, action: 'start' },
101
+ config,
102
+ );
103
+ return { from: transition.from, to: transition.to, recorded: true };
104
+ }
105
+ return { recorded: false };
106
+ } catch {
107
+ // Non-blocking by contract: never let a stage_run write break the comment.
108
+ return { recorded: false };
109
+ }
110
+ }
111
+
112
+ module.exports = {
113
+ parseStageTransition,
114
+ recordStageTransition,
115
+ };
@@ -15,7 +15,6 @@ const STAGE_IDS = Object.freeze([
15
15
  'validate',
16
16
  'ship',
17
17
  'review',
18
- 'premerge',
19
18
  'verify',
20
19
  ]);
21
20
 
@@ -25,7 +24,6 @@ const STAGE_LABELS = Object.freeze({
25
24
  validate: 'Validate',
26
25
  ship: 'Ship',
27
26
  review: 'Review',
28
- premerge: 'Premerge',
29
27
  verify: 'Verify',
30
28
  });
31
29
 
@@ -35,14 +33,13 @@ const STAGE_COMMANDS = Object.freeze({
35
33
  validate: '/validate',
36
34
  ship: '/ship',
37
35
  review: '/review',
38
- premerge: '/premerge',
39
36
  verify: '/verify',
40
37
  });
41
38
 
42
39
  const WORKFLOW_STAGE_MATRIX = Object.freeze({
43
- critical: Object.freeze(['plan', 'dev', 'validate', 'ship', 'review', 'premerge', 'verify']),
44
- standard: Object.freeze(['plan', 'dev', 'validate', 'ship', 'review', 'premerge']),
45
- refactor: Object.freeze(['plan', 'dev', 'validate', 'ship', 'premerge']),
40
+ critical: Object.freeze(['plan', 'dev', 'validate', 'ship', 'review', 'verify']),
41
+ standard: Object.freeze(['plan', 'dev', 'validate', 'ship', 'review']),
42
+ refactor: Object.freeze(['plan', 'dev', 'validate', 'ship']),
46
43
  simple: Object.freeze(['dev', 'validate', 'ship']),
47
44
  hotfix: Object.freeze(['dev', 'validate', 'ship']),
48
45
  // Docs-only work intentionally reuses /verify as a pre-ship content check to
@@ -51,6 +48,18 @@ const WORKFLOW_STAGE_MATRIX = Object.freeze({
51
48
  docs: Object.freeze(['verify', 'ship']),
52
49
  });
53
50
 
51
+ // Pre-merge is a task-type gate/checkpoint embedded inside existing stages
52
+ // (the doc-completion + PR-handoff checks that run before merge), not a
53
+ // standalone universal workflow stage. It is keyed with a hyphen ('pre-merge')
54
+ // so it never re-enters the stage model. `enabledFor` lists the classifications
55
+ // that run the gate; `embeddedIn` names the stages where it fires.
56
+ const WORKFLOW_GATES = Object.freeze({
57
+ 'pre-merge': Object.freeze({
58
+ embeddedIn: Object.freeze(['ship', 'review']),
59
+ enabledFor: Object.freeze(['critical', 'standard', 'refactor']),
60
+ }),
61
+ });
62
+
54
63
  const WORKFLOW_TERMINAL_STAGES = Object.freeze(Object.entries(WORKFLOW_STAGE_MATRIX).reduce((accumulator, [classification, path]) => {
55
64
  accumulator[classification] = path.at(-1);
56
65
  return accumulator;
@@ -77,6 +86,19 @@ function getWorkflowPath(classification) {
77
86
  return normalized ? WORKFLOW_STAGE_MATRIX[normalized] : Object.freeze([]);
78
87
  }
79
88
 
89
+ function getGatesForClassification(classification) {
90
+ const normalized = normalizeClassification(classification);
91
+ if (!normalized) {
92
+ return Object.freeze([]);
93
+ }
94
+
95
+ return Object.freeze(
96
+ Object.entries(WORKFLOW_GATES)
97
+ .filter(([, gate]) => gate.enabledFor.includes(normalized))
98
+ .map(([gateId]) => gateId),
99
+ );
100
+ }
101
+
80
102
  function getStageWorkflow(stageId, classification) {
81
103
  const normalizedStage = normalizeStageId(stageId);
82
104
  const normalizedClassification = normalizeClassification(classification);
@@ -186,6 +208,7 @@ module.exports = {
186
208
  STAGE_LABELS,
187
209
  STAGE_COMMANDS,
188
210
  WORKFLOW_STAGE_MATRIX,
211
+ WORKFLOW_GATES,
189
212
  WORKFLOW_TERMINAL_STAGES,
190
213
  STAGE_TRANSITIONS,
191
214
  STAGE_MODEL,
@@ -193,6 +216,7 @@ module.exports = {
193
216
  normalizeStageId,
194
217
  isCanonicalStageId,
195
218
  getWorkflowPath,
219
+ getGatesForClassification,
196
220
  getStageWorkflow,
197
221
  getAllowedTransitions,
198
222
  canTransition,