forge-workflow 0.0.10 → 0.1.0-beta.3

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 (468) 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 +82 -5
  5. package/.forge/hooks/forge-native-hook.js +431 -0
  6. package/.forge/protected-paths.yaml +157 -0
  7. package/AGENTS.md +151 -61
  8. package/CHANGELOG.md +709 -0
  9. package/CLAUDE.md +9 -118
  10. package/QUICKSTART.md +175 -0
  11. package/README.md +275 -365
  12. package/bin/forge-cmd.js +120 -9
  13. package/bin/forge-preflight.js +26 -5
  14. package/bin/forge.js +532 -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/forge/TOOLCHAIN.md +670 -0
  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 +121 -0
  29. package/docs/guides/SUPPORT.md +190 -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 +214 -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/{TOOLCHAIN.md → reference/TOOLCHAIN.md} +62 -47
  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 +155 -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/activation/ensure-forge-home.js +135 -0
  67. package/lib/adapter-cli.js +307 -0
  68. package/lib/adapters/beads-issue-adapter.js +127 -0
  69. package/lib/adapters/beads-kernel-compat.js +1109 -0
  70. package/lib/adapters/greptile-review-adapter.js +141 -0
  71. package/lib/adapters/kernel-issue-adapter.js +101 -0
  72. package/lib/adapters/pr-state-adapter.js +484 -0
  73. package/lib/adoption-profiles.js +139 -0
  74. package/lib/agents/README.md +2 -6
  75. package/lib/agents/claude.plugin.json +3 -8
  76. package/lib/agents/codex.plugin.json +9 -1
  77. package/lib/agents/cursor.plugin.json +2 -6
  78. package/lib/agents/hermes.plugin.json +22 -0
  79. package/lib/agents-config.js +39 -1236
  80. package/lib/audit-evidence.js +282 -0
  81. package/lib/beads-detect.js +60 -0
  82. package/lib/beads-nudge.js +91 -0
  83. package/lib/beads-setup.js +121 -0
  84. package/lib/beads-sync-scaffold.js +25 -101
  85. package/lib/codex-skills.js +51 -1
  86. package/lib/commands/_aliases.js +248 -0
  87. package/lib/commands/_issue.js +780 -77
  88. package/lib/commands/_manifest.js +93 -0
  89. package/lib/commands/_registry.js +99 -34
  90. package/lib/commands/_resolve-command-opts.js +230 -0
  91. package/lib/commands/_serve-security.js +270 -0
  92. package/lib/commands/adapter.js +12 -0
  93. package/lib/commands/add.js +118 -0
  94. package/lib/commands/audit.js +70 -0
  95. package/lib/commands/blocked.js +5 -0
  96. package/lib/commands/board.js +64 -0
  97. package/lib/commands/claim.js +21 -2
  98. package/lib/commands/claims.js +7 -0
  99. package/lib/commands/clean.js +485 -75
  100. package/lib/commands/close.js +2 -2
  101. package/lib/commands/comment.js +5 -0
  102. package/lib/commands/control.js +148 -0
  103. package/lib/commands/create.js +2 -2
  104. package/lib/commands/dev.js +185 -7
  105. package/lib/commands/doc-gate.js +336 -0
  106. package/lib/commands/doctor.js +156 -0
  107. package/lib/commands/explain.js +15 -0
  108. package/lib/commands/export.js +237 -0
  109. package/lib/commands/gate.js +209 -0
  110. package/lib/commands/hooks.js +377 -0
  111. package/lib/commands/inbox.js +118 -0
  112. package/lib/commands/init.js +604 -0
  113. package/lib/commands/insights.js +79 -0
  114. package/lib/commands/issue.js +12 -1
  115. package/lib/commands/issues.js +17 -0
  116. package/lib/commands/lint.js +5 -0
  117. package/lib/commands/list.js +2 -2
  118. package/lib/commands/memory.js +81 -0
  119. package/lib/commands/merge.js +312 -0
  120. package/lib/commands/migrate.js +362 -0
  121. package/lib/commands/new.js +12 -0
  122. package/lib/commands/options.js +241 -0
  123. package/lib/commands/orient.js +13 -0
  124. package/lib/commands/orphans.js +5 -0
  125. package/lib/commands/patch.js +67 -0
  126. package/lib/commands/plan.js +481 -29
  127. package/lib/commands/pr.js +88 -0
  128. package/lib/commands/preflight.js +211 -0
  129. package/lib/commands/prime.js +13 -0
  130. package/lib/commands/push.js +135 -2
  131. package/lib/commands/ready.js +2 -2
  132. package/lib/commands/recall.js +171 -0
  133. package/lib/commands/recap.js +75 -0
  134. package/lib/commands/recommend.js +0 -1
  135. package/lib/commands/release.js +104 -0
  136. package/lib/commands/remember.js +140 -0
  137. package/lib/commands/role.js +99 -0
  138. package/lib/commands/serve.js +581 -0
  139. package/lib/commands/setup.js +900 -971
  140. package/lib/commands/shepherd.js +501 -0
  141. package/lib/commands/ship.js +59 -1
  142. package/lib/commands/show.js +2 -2
  143. package/lib/commands/stage.js +192 -0
  144. package/lib/commands/stale.js +5 -0
  145. package/lib/commands/status.js +158 -21
  146. package/lib/commands/sync.js +34 -46
  147. package/lib/commands/team.js +4 -1
  148. package/lib/commands/test.js +43 -27
  149. package/lib/commands/update.js +2 -2
  150. package/lib/commands/upgrade.js +47 -0
  151. package/lib/commands/validate.js +43 -18
  152. package/lib/commands/worktree.js +362 -99
  153. package/lib/config-writer.js +202 -0
  154. package/lib/control-plane.js +236 -0
  155. package/lib/core/runtime-graph.js +977 -0
  156. package/lib/dep-guard/keyword-ripple.js +2 -2
  157. package/lib/deprecated-sync-cleanup.js +362 -0
  158. package/lib/detect-agent.js +2 -28
  159. package/lib/detect-worktree.js +35 -9
  160. package/lib/doc-gate/declaration.js +177 -0
  161. package/lib/doc-gate/detect.js +289 -0
  162. package/lib/doc-gate/gate.js +375 -0
  163. package/lib/doc-gate/okf-config.js +128 -0
  164. package/lib/doc-gate/okf.js +429 -0
  165. package/lib/docs-command.js +1161 -6
  166. package/lib/forge-issues.js +382 -11
  167. package/lib/forge-lock.js +262 -0
  168. package/lib/gate-events.js +192 -0
  169. package/lib/global-flags.js +104 -0
  170. package/lib/greptile-match.js +7 -63
  171. package/lib/grounding/context-events.js +230 -0
  172. package/lib/grounding/read-first.js +112 -0
  173. package/lib/harness-capability-matrix.js +380 -0
  174. package/lib/hook-global-installer.js +347 -0
  175. package/lib/hook-renderer.js +541 -0
  176. package/lib/inbox.js +391 -0
  177. package/lib/insights.js +397 -0
  178. package/lib/issue-adapter.js +156 -0
  179. package/lib/issue-backend.js +145 -0
  180. package/lib/issue-render.js +220 -0
  181. package/lib/kernel/backing-issue.js +311 -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/owned-kernel.js +43 -0
  192. package/lib/kernel/planning-buckets-schema.js +109 -0
  193. package/lib/kernel/projection-jsonl-writer.js +450 -0
  194. package/lib/kernel/readiness-model.js +329 -0
  195. package/lib/kernel/schema.js +356 -0
  196. package/lib/kernel/sqlite-driver.js +2540 -0
  197. package/lib/kernel/taxonomy-validator.js +394 -0
  198. package/lib/lefthook-check.js +3 -2
  199. package/lib/lefthook-wiring.js +413 -0
  200. package/lib/mcp-config-renderer.js +288 -0
  201. package/lib/memory/graphiti-mcp.js +106 -0
  202. package/lib/memory/router.js +387 -0
  203. package/lib/memory/typed-api.js +102 -0
  204. package/lib/memory-digest.js +195 -0
  205. package/lib/merge-rules.js +395 -0
  206. package/lib/migrate-dry-run.js +466 -0
  207. package/lib/orientation.js +863 -0
  208. package/lib/package-manager-remediation.js +103 -0
  209. package/lib/package-root.js +381 -0
  210. package/lib/patch-intent.js +890 -0
  211. package/lib/plugin-catalog.js +3 -4
  212. package/lib/plugin-manager.js +0 -5
  213. package/lib/pr-bundle.js +186 -0
  214. package/lib/pr-monitor/auto-actions.js +175 -0
  215. package/lib/pr-monitor/differ.js +195 -0
  216. package/lib/pr-monitor/digest.js +206 -0
  217. package/lib/pr-monitor/events.js +0 -0
  218. package/lib/pr-monitor/gather.js +124 -0
  219. package/lib/pr-monitor/journal.js +299 -0
  220. package/lib/pr-monitor/monitor.js +146 -0
  221. package/lib/pr-monitor/render-sticky.js +192 -0
  222. package/lib/pr-monitor/upsert-sticky.js +169 -0
  223. package/lib/pr-monitor/watch-lifecycle.js +95 -0
  224. package/lib/pr-monitor/watch.js +247 -0
  225. package/lib/pr-pull.js +1314 -0
  226. package/lib/pr-shepherd.js +494 -0
  227. package/lib/pr-state-validator.js +59 -0
  228. package/lib/preflight/gates.js +237 -0
  229. package/lib/preflight/runner.js +116 -0
  230. package/lib/project-discovery.js +0 -53
  231. package/lib/project-memory.js +99 -497
  232. package/lib/protected-path-manifest.js +281 -0
  233. package/lib/protected-state-surfaces.js +387 -0
  234. package/lib/release-readiness.js +2105 -0
  235. package/lib/reset.js +59 -45
  236. package/lib/review-adapter.js +68 -0
  237. package/lib/rules-sync.js +260 -0
  238. package/lib/runtime-health.js +241 -20
  239. package/lib/safety-config-renderer.js +268 -0
  240. package/lib/setup-action-log.js +1 -7
  241. package/lib/setup.js +27 -65
  242. package/lib/shell-utils.js +76 -6
  243. package/lib/skills-sync.js +330 -0
  244. package/lib/smart-status/scoring.js +17 -3
  245. package/lib/status/beads-snapshot.js +45 -2
  246. package/lib/status/presenter.js +169 -18
  247. package/lib/status/snapshot.js +186 -0
  248. package/lib/sync-backend.js +202 -0
  249. package/lib/untrusted-content.js +52 -0
  250. package/lib/upgrade-safety.js +251 -0
  251. package/lib/workflow/enforce-stage.js +351 -45
  252. package/lib/workflow/stage-transition.js +115 -0
  253. package/lib/workflow/stages.js +30 -6
  254. package/lib/workflow/state-manager.js +11 -22
  255. package/lib/workflow/state.js +23 -1
  256. package/lib/workflow-profiles.js +17 -5
  257. package/package.json +37 -35
  258. package/rules/documentation.md +19 -0
  259. package/rules/kernel-tracking.md +26 -0
  260. package/rules/security.md +22 -0
  261. package/rules/tdd.md +20 -0
  262. package/rules/workflow.md +27 -0
  263. package/scripts/auto-backing-issue.js +47 -0
  264. package/scripts/beads-context.sh +81 -57
  265. package/scripts/beads-upgrade-smoke.sh +24 -3
  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.sh +22 -3
  272. package/scripts/file-index.sh +3 -0
  273. package/scripts/forge-team/lib/claim.sh +34 -18
  274. package/scripts/forge-team/lib/dashboard.sh +61 -86
  275. package/scripts/forge-team/lib/epic.sh +99 -263
  276. package/scripts/forge-team/lib/hooks.sh +26 -28
  277. package/scripts/forge-team/lib/identity.sh +4 -4
  278. package/scripts/forge-team/lib/sync-github.sh +49 -84
  279. package/scripts/forge-team/lib/verify.sh +93 -83
  280. package/scripts/forge-team/lib/workload.sh +41 -65
  281. package/scripts/forge-team/tests/claim.test.sh +25 -19
  282. package/scripts/forge-team/tests/dashboard.test.sh +31 -46
  283. package/scripts/forge-team/tests/epic.test.sh +52 -71
  284. package/scripts/forge-team/tests/hooks.test.sh +38 -50
  285. package/scripts/forge-team/tests/identity.test.sh +3 -3
  286. package/scripts/forge-team/tests/integration.test.sh +44 -66
  287. package/scripts/forge-team/tests/sync-github.test.sh +50 -83
  288. package/scripts/forge-team/tests/verify.test.sh +37 -46
  289. package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
  290. package/scripts/forge-team/tests/workload.test.sh +32 -66
  291. package/scripts/gen-command-manifest.js +153 -0
  292. package/scripts/gen-embedded-assets.mjs +129 -0
  293. package/scripts/install.ps1 +139 -0
  294. package/scripts/install.sh +268 -0
  295. package/scripts/lib/release-asset.mjs +84 -0
  296. package/scripts/parity-check.mjs +145 -0
  297. package/scripts/parity-check.test.mjs +58 -0
  298. package/scripts/pin-agentic-workflow-images.js +112 -0
  299. package/scripts/pr-auto-actions.js +93 -0
  300. package/scripts/pr-coordinator.sh +3 -0
  301. package/scripts/pr-verdict-label.js +50 -0
  302. package/scripts/preflight-sonar.eslint.config.mjs +44 -0
  303. package/scripts/preflight.sh +21 -94
  304. package/scripts/protected-state-check.js +104 -0
  305. package/scripts/smart-status.sh +60 -57
  306. package/scripts/spikes/config-race-bench.js +111 -0
  307. package/scripts/spikes/harness-capability-matrix.js +13 -0
  308. package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
  309. package/scripts/spikes/protected-path-manifest.js +20 -0
  310. package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
  311. package/scripts/sync-agent-skills.js +62 -0
  312. package/scripts/sync-utils.sh +3 -0
  313. package/scripts/test-ci-shard.js +13 -6
  314. package/scripts/test.js +95 -12
  315. package/skills/claim-safety/SKILL.md +102 -0
  316. package/skills/claim-safety/evals/evals.json +46 -0
  317. package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +44 -50
  318. package/skills/dev/evals/evals.json +50 -0
  319. package/skills/hermes-forge/SKILL.md +185 -0
  320. package/skills/hermes-forge/evals/evals.json +46 -0
  321. package/skills/issue-basics/SKILL.md +111 -0
  322. package/skills/issue-basics/evals/evals.json +46 -0
  323. package/skills/kernel/SKILL.md +166 -0
  324. package/skills/kernel/evals/evals.json +50 -0
  325. package/skills/memory/SKILL.md +102 -0
  326. package/skills/parallel-deep-research/SKILL.md +14 -11
  327. package/skills/parallel-deep-research/evals/evals.json +11 -27
  328. package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +132 -157
  329. package/skills/plan/evals/evals.json +42 -0
  330. package/skills/research/SKILL.md +195 -0
  331. package/skills/research/evals/evals.json +42 -0
  332. package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
  333. package/skills/review/evals/evals.json +42 -0
  334. package/skills/rollback/SKILL.md +110 -0
  335. package/skills/rollback/evals/evals.json +46 -0
  336. package/skills/rollback/references/methods.md +204 -0
  337. package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
  338. package/skills/shepherd/SKILL.md +66 -0
  339. package/skills/shepherd/evals/evals.json +42 -0
  340. package/{.github/prompts/ship.prompt.md → skills/ship/SKILL.md} +81 -45
  341. package/skills/ship/evals/evals.json +42 -0
  342. package/skills/smith/SKILL.md +142 -0
  343. package/skills/smith/evals/evals.json +46 -0
  344. package/skills/smith/references/autonomy-and-gates.md +94 -0
  345. package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
  346. package/skills/sonarcloud/evals/evals.json +46 -0
  347. package/skills/sonarcloud-analysis/SKILL.md +18 -13
  348. package/skills/sonarcloud-analysis/evals/evals.json +11 -15
  349. package/{.github/prompts/status.prompt.md → skills/status/SKILL.md} +20 -10
  350. package/skills/status/evals/evals.json +50 -0
  351. package/skills/triage-ready/SKILL.md +121 -0
  352. package/skills/triage-ready/evals/evals.json +42 -0
  353. package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
  354. package/skills/validate/evals/evals.json +42 -0
  355. package/skills/verify/SKILL.md +299 -0
  356. package/skills/verify/evals/evals.json +50 -0
  357. package/.claude/commands/dev.md +0 -345
  358. package/.claude/commands/plan.md +0 -566
  359. package/.claude/commands/premerge.md +0 -186
  360. package/.claude/commands/research.md +0 -42
  361. package/.claude/commands/review.md +0 -451
  362. package/.claude/commands/rollback.md +0 -721
  363. package/.claude/commands/ship.md +0 -213
  364. package/.claude/commands/sonarcloud.md +0 -152
  365. package/.claude/commands/status.md +0 -90
  366. package/.claude/commands/validate.md +0 -288
  367. package/.claude/commands/verify.md +0 -269
  368. package/.claude/rules/workflow.md +0 -121
  369. package/.cline/workflows/dev.md +0 -342
  370. package/.cline/workflows/plan.md +0 -563
  371. package/.cline/workflows/premerge.md +0 -183
  372. package/.cline/workflows/research.md +0 -39
  373. package/.cline/workflows/review.md +0 -448
  374. package/.cline/workflows/rollback.md +0 -718
  375. package/.cline/workflows/ship.md +0 -210
  376. package/.cline/workflows/sonarcloud.md +0 -146
  377. package/.cline/workflows/status.md +0 -87
  378. package/.cline/workflows/validate.md +0 -285
  379. package/.cline/workflows/verify.md +0 -266
  380. package/.codex/config.toml +0 -11
  381. package/.codex/skills/dev/SKILL.md +0 -345
  382. package/.codex/skills/plan/SKILL.md +0 -566
  383. package/.codex/skills/premerge/SKILL.md +0 -186
  384. package/.codex/skills/research/SKILL.md +0 -42
  385. package/.codex/skills/review/SKILL.md +0 -451
  386. package/.codex/skills/rollback/SKILL.md +0 -721
  387. package/.codex/skills/ship/SKILL.md +0 -213
  388. package/.codex/skills/sonarcloud/SKILL.md +0 -149
  389. package/.codex/skills/status/SKILL.md +0 -90
  390. package/.codex/skills/validate/SKILL.md +0 -288
  391. package/.codex/skills/verify/SKILL.md +0 -269
  392. package/.cursor/commands/dev.md +0 -342
  393. package/.cursor/commands/plan.md +0 -563
  394. package/.cursor/commands/premerge.md +0 -183
  395. package/.cursor/commands/research.md +0 -39
  396. package/.cursor/commands/review.md +0 -448
  397. package/.cursor/commands/ship.md +0 -210
  398. package/.cursor/commands/sonarcloud.md +0 -146
  399. package/.cursor/commands/status.md +0 -87
  400. package/.cursor/commands/validate.md +0 -285
  401. package/.cursor/commands/verify.md +0 -266
  402. package/.cursorrules +0 -149
  403. package/.github/prompts/premerge.prompt.md +0 -188
  404. package/.github/prompts/research.prompt.md +0 -44
  405. package/.github/prompts/rollback.prompt.md +0 -723
  406. package/.github/prompts/verify.prompt.md +0 -271
  407. package/.github/workflows/beads-to-github.yml +0 -89
  408. package/.github/workflows/github-to-beads.yml +0 -100
  409. package/.kilocode/workflows/dev.md +0 -346
  410. package/.kilocode/workflows/plan.md +0 -567
  411. package/.kilocode/workflows/premerge.md +0 -187
  412. package/.kilocode/workflows/research.md +0 -43
  413. package/.kilocode/workflows/review.md +0 -452
  414. package/.kilocode/workflows/rollback.md +0 -722
  415. package/.kilocode/workflows/ship.md +0 -214
  416. package/.kilocode/workflows/sonarcloud.md +0 -150
  417. package/.kilocode/workflows/status.md +0 -91
  418. package/.kilocode/workflows/validate.md +0 -289
  419. package/.kilocode/workflows/verify.md +0 -270
  420. package/.opencode/commands/dev.md +0 -345
  421. package/.opencode/commands/plan.md +0 -566
  422. package/.opencode/commands/premerge.md +0 -186
  423. package/.opencode/commands/research.md +0 -42
  424. package/.opencode/commands/review.md +0 -451
  425. package/.opencode/commands/rollback.md +0 -721
  426. package/.opencode/commands/ship.md +0 -213
  427. package/.opencode/commands/sonarcloud.md +0 -149
  428. package/.opencode/commands/status.md +0 -90
  429. package/.opencode/commands/validate.md +0 -288
  430. package/.opencode/commands/verify.md +0 -269
  431. package/.roo/commands/dev.md +0 -346
  432. package/.roo/commands/plan.md +0 -567
  433. package/.roo/commands/premerge.md +0 -187
  434. package/.roo/commands/research.md +0 -43
  435. package/.roo/commands/review.md +0 -452
  436. package/.roo/commands/rollback.md +0 -722
  437. package/.roo/commands/ship.md +0 -214
  438. package/.roo/commands/sonarcloud.md +0 -150
  439. package/.roo/commands/status.md +0 -91
  440. package/.roo/commands/validate.md +0 -289
  441. package/.roo/commands/verify.md +0 -270
  442. package/docs/BEADS_GITHUB_SYNC.md +0 -281
  443. package/docs/GREPTILE_SETUP.md +0 -400
  444. package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
  445. package/docs/SETUP.md +0 -663
  446. package/docs/VALIDATION.md +0 -363
  447. package/lib/agents/cline.plugin.json +0 -29
  448. package/lib/agents/copilot.plugin.json +0 -24
  449. package/lib/agents/kilocode.plugin.json +0 -22
  450. package/lib/agents/opencode.plugin.json +0 -23
  451. package/lib/agents/roo.plugin.json +0 -30
  452. package/lib/beads-bootstrap.js +0 -225
  453. package/lib/beads-health-check.js +0 -188
  454. package/lib/commands/commands-reset.js +0 -147
  455. package/opencode.json +0 -67
  456. package/scripts/beads-context.test.js +0 -584
  457. package/scripts/github-beads-sync/comment.mjs +0 -64
  458. package/scripts/github-beads-sync/config.mjs +0 -148
  459. package/scripts/github-beads-sync/github-api.mjs +0 -131
  460. package/scripts/github-beads-sync/index.mjs +0 -356
  461. package/scripts/github-beads-sync/label-mapper.mjs +0 -54
  462. package/scripts/github-beads-sync/mapping.mjs +0 -132
  463. package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
  464. package/scripts/github-beads-sync/reverse-sync.mjs +0 -162
  465. package/scripts/github-beads-sync/run-bd.mjs +0 -161
  466. package/scripts/github-beads-sync/sanitize.mjs +0 -121
  467. package/scripts/github-beads-sync.config.json +0 -26
  468. package/scripts/sync-commands.js +0 -600
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { runIssueOperation } = require('../forge-issues');
4
+ const { SUBCOMMANDS } = require('./_issue');
4
5
 
5
6
  const SUPPORTED_SUBCOMMANDS = ['create', 'list', 'show', 'close', 'update'];
6
7
 
@@ -25,6 +26,10 @@ module.exports = {
25
26
  name: 'issues',
26
27
  description: 'Manage issues through the Forge authority layer',
27
28
  usage: 'forge issues <subcommand> [...]',
29
+ // Undocumented back-compat alias for the canonical `forge issue` surface. It stays
30
+ // registered and executable, but `hidden: true` omits it from `forge --help` so the
31
+ // singular `issue` reads as the one canonical issue surface (kernel issue 450c6e34).
32
+ hidden: true,
28
33
  flags: {},
29
34
  handler: async (args, _flags, projectRoot, opts = {}) => {
30
35
  const [subcommand, ...rest] = normalizeArgs(args);
@@ -43,6 +48,18 @@ module.exports = {
43
48
  };
44
49
  }
45
50
 
51
+ // --help for a specific subcommand: print its usage and return BEFORE dispatching
52
+ // to any backend. Otherwise --help is forwarded as an operation arg, which is
53
+ // absorbed by Beads but fails with a bare "Command failed" on the Kernel default
54
+ // (mirrors the short-circuit in lib/commands/_issue.js runIssueSubcommand).
55
+ if (rest.some(arg => arg === '--help' || arg === '-h')) {
56
+ const spec = SUBCOMMANDS[subcommand];
57
+ return {
58
+ success: true,
59
+ output: spec ? `${spec.usage}\n\n${spec.description}` : formatHelp(),
60
+ };
61
+ }
62
+
46
63
  const runner = opts.runIssueOperation || runIssueOperation;
47
64
  return runner(subcommand, rest, projectRoot, opts);
48
65
  },
@@ -0,0 +1,5 @@
1
+ 'use strict';
2
+
3
+ const { createIssueSubcommand } = require('./_issue');
4
+
5
+ module.exports = createIssueSubcommand('lint');
@@ -1,5 +1,5 @@
1
1
  'use strict';
2
2
 
3
- const { makeAliasCommand } = require('./_issue');
3
+ const { createIssueSubcommand } = require('./_issue');
4
4
 
5
- module.exports = makeAliasCommand('list');
5
+ module.exports = createIssueSubcommand('list');
@@ -0,0 +1,81 @@
1
+ 'use strict';
2
+
3
+ const remember = require('./remember');
4
+ const recall = require('./recall');
5
+ const insights = require('./insights');
6
+ const { stripGlobalFlags } = require('../global-flags');
7
+
8
+ // One memorable surface over the EXISTING memory commands (kernel issue 25362344): every
9
+ // subcommand delegates to the standalone remember/recall/insights handlers — the same
10
+ // kernel-backed store, not a reimplementation. The standalone `forge remember`/`forge
11
+ // recall`/`forge insights` commands remain registered as back-compat aliases, so nothing
12
+ // that already calls them breaks.
13
+ const SUBCOMMANDS = {
14
+ add: {
15
+ handler: remember.handler,
16
+ summary: 'Persist a memory note (= forge remember; supports --type + What/Why/Where/Learned)',
17
+ },
18
+ recall: {
19
+ handler: recall.handler,
20
+ summary: 'Retrieve memory notes, newest first (= forge recall; filter with --type)',
21
+ },
22
+ search: {
23
+ // Search IS recall with a query — recall runs a BM25 token-AND search when a query is
24
+ // present, so the same handler serves both without a second code path.
25
+ handler: recall.handler,
26
+ summary: 'Search memory notes by query (recall with a query; filter with --type)',
27
+ },
28
+ insights: {
29
+ handler: insights.handler,
30
+ summary: 'Detect recurring evidence patterns and suggest follow-ups (= forge insights)',
31
+ },
32
+ };
33
+
34
+ const usage = 'Usage: forge memory <add|recall|search|insights> [args]';
35
+
36
+ function renderHelp() {
37
+ const width = Math.max(...Object.keys(SUBCOMMANDS).map(name => name.length));
38
+ const lines = [
39
+ usage,
40
+ '',
41
+ 'Subcommands:',
42
+ ...Object.entries(SUBCOMMANDS).map(
43
+ ([name, { summary }]) => ` ${name.padEnd(width)} ${summary}`
44
+ ),
45
+ '',
46
+ 'Back-compat: forge remember / forge recall / forge insights remain available as aliases.',
47
+ ];
48
+ return lines.join('\n');
49
+ }
50
+
51
+ async function handler(args, flags, projectRoot, opts) {
52
+ // The subcommand is the first positional token; global flags (e.g. `-p <dir>`) are stripped
53
+ // first so they never masquerade as the subcommand.
54
+ const positional = stripGlobalFlags(args).find(arg => !arg.startsWith('-'));
55
+
56
+ if (!positional || positional === 'help' || args.includes('--help') || args.includes('-h')) {
57
+ return { success: true, output: renderHelp() };
58
+ }
59
+
60
+ const sub = SUBCOMMANDS[positional];
61
+ if (!sub) {
62
+ return {
63
+ success: false,
64
+ error: `Unknown memory subcommand: ${positional}\n\n${renderHelp()}`,
65
+ };
66
+ }
67
+
68
+ // Forward everything EXCEPT the consumed subcommand token to the delegate, preserving any
69
+ // global flags the delegate re-parses (e.g. `-p <dir>`, `--all`).
70
+ const idx = args.indexOf(positional);
71
+ const childArgs = idx >= 0 ? [...args.slice(0, idx), ...args.slice(idx + 1)] : args;
72
+ return sub.handler(childArgs, flags, projectRoot, opts);
73
+ }
74
+
75
+ module.exports = {
76
+ name: 'memory',
77
+ description:
78
+ 'Unified memory surface: forge memory add|recall|search|insights (wraps remember/recall/insights)',
79
+ usage,
80
+ handler,
81
+ };
@@ -0,0 +1,312 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * merge command — opt-in conditional auto-merge.
5
+ *
6
+ * `forge merge --auto <pr>` is the ONLY path by which Forge will merge a PR on
7
+ * its own, and it stays OFF unless the user has explicitly opted in. It reads
8
+ * the `merge.auto` section of `.forge/config.yaml`:
9
+ *
10
+ * merge:
11
+ * auto:
12
+ * enabled: true # default false / absent → strict NO-OP
13
+ * rules: # ALL must pass (see lib/merge-rules.js)
14
+ * - checks_green # or scope it, e.g. for a docs-only task that
15
+ * # may take a coverage dip:
16
+ * # - checks_green: { ignore: ["Coverage"] }
17
+ * - threads_resolved
18
+ * - no_conflicts # recommended: never merge a conflicting branch
19
+ * - not_behind
20
+ * - settle_min: 10
21
+ *
22
+ * Flow: with no config (or `enabled` not true) the command is a strict NO-OP —
23
+ * it prints why and merges nothing, preserving the test-enforced
24
+ * never-auto-merge-by-default invariant. When enabled, it fetches the PR
25
+ * context via `gh`, evaluates the rules with the pure `evaluateMergeRules`, and
26
+ * merges ONLY when every rule passes. Two extra safety layers wrap that decision:
27
+ * a pre-flight guard that no-ops on an already merged/closed PR (idempotent
28
+ * re-runs), and a TOCTOU live re-check that re-fetches and re-evaluates right
29
+ * before merging so a stale snapshot can never merge a since-changed PR. The
30
+ * gh-fetch and the merge action are isolated behind injectable `fetchPrContext`
31
+ * / `mergePr` seams so the decision path is unit-testable without the network.
32
+ *
33
+ * A bring-your-own custom-predicate seam (registered via `forge add`) is a
34
+ * documented follow-up and intentionally NOT wired here. Further follow-ups
35
+ * documented in lib/merge-rules.js: opt-in `auto_update`, required-checks
36
+ * scoping for `checks_green`, a configurable merge `method`, and post-merge
37
+ * branch deletion.
38
+ *
39
+ * @module commands/merge
40
+ */
41
+
42
+ const { execFileSync } = require('node:child_process');
43
+
44
+ const { loadRawConfig } = require('../config-writer');
45
+ const { evaluateMergeRules } = require('../merge-rules');
46
+
47
+ /** Default `gh` runner. Only reached by the default fetch/merge seams (never in unit tests). */
48
+ function defaultGh(args, options = {}) {
49
+ return execFileSync('gh', args, { encoding: 'utf8', ...options }); // NOSONAR S4036 - hardcoded CLI (gh), args array (no shell), developer-tool context
50
+ }
51
+
52
+ /** Parse `gh ... --json` output, returning `null` on any failure (callers fail closed). */
53
+ function ghJson(gh, args) {
54
+ try {
55
+ return JSON.parse(gh(args) || '{}');
56
+ } catch (_err) {
57
+ return null;
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Read the unresolved review-thread count via the GraphQL API. `reviewThreads`
63
+ * is not a valid `gh pr view --json` field, so this needs a dedicated query.
64
+ * Returns `undefined` on any failure so `threads_resolved` fails closed.
65
+ */
66
+ function fetchUnresolvedThreadCount(gh, pr) {
67
+ try {
68
+ const repo = ghJson(gh, ['repo', 'view', '--json', 'owner,name']);
69
+ const owner = repo && repo.owner && repo.owner.login;
70
+ const name = repo && repo.name;
71
+ if (!owner || !name) return undefined;
72
+ // Paginate through ALL review threads — a PR can have >100, and the ones on
73
+ // later pages could be the unresolved/newest ones. Stopping at page 1 would
74
+ // both miss them and make a large PR un-mergeable. Loop on the GraphQL cursor
75
+ // until hasNextPage is false; any unreadable page → undefined (fail closed).
76
+ const query = 'query($o:String!,$n:String!,$pr:Int!,$after:String){repository(owner:$o,name:$n)'
77
+ + '{pullRequest(number:$pr){reviewThreads(first:100,after:$after){pageInfo{hasNextPage endCursor}nodes{isResolved isOutdated}}}}}';
78
+ let after = '';
79
+ let count = 0;
80
+ for (let page = 0; page < 100; page += 1) { // 100-page cap = 10k threads backstop
81
+ const args = ['api', 'graphql', '-f', `query=${query}`,
82
+ '-F', `o=${owner}`, '-F', `n=${name}`, '-F', `pr=${Number(pr)}`];
83
+ if (after) args.push('-F', `after=${after}`);
84
+ const data = JSON.parse(gh(args) || '{}');
85
+ const threads = (((data.data || {}).repository || {}).pullRequest || {}).reviewThreads;
86
+ if (!threads) return undefined; // unreadable page → fail closed
87
+ count += (threads.nodes || []).filter((t) => t && t.isResolved === false && t.isOutdated === false).length;
88
+ if (!threads.pageInfo || !threads.pageInfo.hasNextPage) return count;
89
+ after = threads.pageInfo.endCursor;
90
+ if (!after) return undefined; // hasNextPage but no cursor → fail closed
91
+ }
92
+ return undefined; // exceeded the page cap → fail closed rather than undercount
93
+ } catch (_err) {
94
+ return undefined;
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Default PR-context fetcher (the network seam). Assembles the shape consumed
100
+ * by `evaluateMergeRules` from `gh`. Anything it cannot read is left absent so
101
+ * the dependent rule fails closed rather than guessing. Fully replaced by
102
+ * `deps.fetchPrContext` in tests.
103
+ *
104
+ * @returns {object} prContext
105
+ */
106
+ function defaultFetchPrContext({ pr, gh = defaultGh, now = Date.now() }) {
107
+ const view = ghJson(gh, ['pr', 'view', String(pr), '--json',
108
+ 'number,state,isDraft,mergeable,mergeStateStatus,statusCheckRollup,reviews,comments,updatedAt']) || {};
109
+
110
+ const rollup = Array.isArray(view.statusCheckRollup) ? view.statusCheckRollup : null;
111
+ const checks = (rollup || []).map((c) => ({
112
+ name: c.name || c.context || '?',
113
+ conclusion: String(c.conclusion || c.state || c.status || ''),
114
+ }));
115
+
116
+ const comments = Array.isArray(view.comments)
117
+ ? view.comments.map((c) => ({
118
+ author: (c.author && c.author.login) || '',
119
+ at: c.createdAt || c.submittedAt || '',
120
+ }))
121
+ : [];
122
+
123
+ const reviews = Array.isArray(view.reviews) ? view.reviews : [];
124
+ const approvals = reviews
125
+ .filter((r) => String(r.state).toUpperCase() === 'APPROVED')
126
+ .map((r) => ({ author: (r.author && r.author.login) || '' }));
127
+
128
+ // Derive from GitHub's mergeStateStatus / mergeable. Only a known set maps to
129
+ // a definite answer; anything else stays undefined so the dependent rule fails
130
+ // closed rather than guessing.
131
+ const mergeStateStatus = String(view.mergeStateStatus || '').toUpperCase();
132
+ const mergeable = String(view.mergeable || '').toUpperCase();
133
+
134
+ let behindBase;
135
+ if (mergeStateStatus === 'BEHIND') behindBase = true;
136
+ else if (['CLEAN', 'UNSTABLE', 'HAS_HOOKS', 'BLOCKED', 'DIRTY'].includes(mergeStateStatus)) behindBase = false;
137
+
138
+ // Conflict status: DIRTY (or mergeable=CONFLICTING) → conflicting; a clean set
139
+ // of states → not conflicting; UNKNOWN / missing / still-computing → undefined.
140
+ let conflicting;
141
+ if (mergeStateStatus === 'DIRTY' || mergeable === 'CONFLICTING') conflicting = true;
142
+ else if (['CLEAN', 'UNSTABLE', 'HAS_HOOKS', 'BLOCKED'].includes(mergeStateStatus)) conflicting = false;
143
+
144
+ const isDraft = typeof view.isDraft === 'boolean' ? view.isDraft : undefined;
145
+ const state = view.state ? String(view.state).toUpperCase() : undefined;
146
+
147
+ const stamps = [
148
+ ...comments.map((c) => c.at),
149
+ ...reviews.map((r) => r.submittedAt || r.createdAt || ''),
150
+ view.updatedAt || '',
151
+ ].map((s) => Date.parse(s)).filter((n) => !Number.isNaN(n));
152
+ const lastActivityAt = stamps.length ? Math.max(...stamps) : undefined;
153
+
154
+ return {
155
+ checks,
156
+ requiredChecksKnown: rollup !== null,
157
+ unresolvedThreads: fetchUnresolvedThreadCount(gh, pr),
158
+ behindBase,
159
+ conflicting,
160
+ isDraft,
161
+ state,
162
+ approvals,
163
+ comments,
164
+ lastActivityAt,
165
+ now,
166
+ };
167
+ }
168
+
169
+ /** Default merge action (squash). Fully replaced by `deps.mergePr` in tests. */
170
+ function defaultMergePr({ pr, gh = defaultGh }) {
171
+ gh(['pr', 'merge', String(pr), '--squash']);
172
+ return { merged: true, method: 'squash' };
173
+ }
174
+
175
+ /**
176
+ * Command handler.
177
+ *
178
+ * @param {string[]} args - Positional + flag args (first positional is the PR).
179
+ * @param {object} _flags - Parsed flags (unused; flags are read from args).
180
+ * @param {string} projectRoot - Project root.
181
+ * @param {object} [deps] - Injected seams for testing: loadConfig, fetchPrContext, mergePr, gh, now.
182
+ * @returns {Promise<object>} result envelope.
183
+ */
184
+ async function handler(args, _flags, projectRoot, deps = {}) {
185
+ const argv = Array.isArray(args) ? args : [];
186
+ const positional = argv.filter((a) => !String(a).startsWith('--'));
187
+ const flags = new Set(argv.filter((a) => String(a).startsWith('--')));
188
+ const pr = positional[0];
189
+ const root = projectRoot || process.cwd();
190
+
191
+ if (!flags.has('--auto')) {
192
+ return { success: false, error: 'Usage: forge merge --auto <pr> (opt-in conditional auto-merge; OFF by default)' };
193
+ }
194
+ if (!pr) {
195
+ return { success: false, error: 'Usage: forge merge --auto <pr>' };
196
+ }
197
+
198
+ const loadConfig = deps.loadConfig || loadRawConfig;
199
+ let config;
200
+ try {
201
+ config = loadConfig(root) || {};
202
+ } catch (err) {
203
+ // A malformed .forge/config.yaml must NOT crash the command — fail closed:
204
+ // refuse to auto-merge and report, rather than throwing past the contract.
205
+ const reason = `Could not read merge config (${err.message}) — refusing to auto-merge (fail-closed).`;
206
+ process.stdout.write(`${reason}\n`);
207
+ return { success: false, merged: false, error: reason };
208
+ }
209
+ const auto = (config.merge && config.merge.auto) || {};
210
+ const enabled = auto.enabled === true;
211
+ const rules = Array.isArray(auto.rules) ? auto.rules : [];
212
+
213
+ // Invariant: absent config or `enabled` not true → strict NO-OP. Forge never
214
+ // auto-merges unless the user has explicitly opted in via .forge/config.yaml.
215
+ if (!enabled) {
216
+ const reason = 'Auto-merge is OPT-IN and OFF by default (merge.auto.enabled is not true in .forge/config.yaml). No action taken.';
217
+ process.stdout.write(`${reason}\n`);
218
+ return { success: true, merged: false, enabled: false, reason };
219
+ }
220
+
221
+ // Opted in but no rules → refuse (fail-closed): an empty ruleset is vacuously
222
+ // "allowed", which would merge unconditionally. Treat that as misconfiguration.
223
+ if (rules.length === 0) {
224
+ const reason = 'merge.auto.enabled is true but no rules are configured — refusing to auto-merge (fail-closed). Add rules under merge.auto.rules.';
225
+ process.stdout.write(`${reason}\n`);
226
+ return { success: false, merged: false, enabled: true, reason };
227
+ }
228
+
229
+ const fetchPrContext = deps.fetchPrContext || defaultFetchPrContext;
230
+ const mergePr = deps.mergePr || defaultMergePr;
231
+ const gh = deps.gh || defaultGh;
232
+
233
+ let prContext;
234
+ try {
235
+ prContext = await fetchPrContext({ pr, projectRoot: root, gh, now: deps.now || Date.now() });
236
+ } catch (err) {
237
+ return { success: false, merged: false, error: `Failed to fetch PR context: ${err.message}` };
238
+ }
239
+
240
+ // Pre-flight: a PR that is already merged or closed is terminal. Re-running the
241
+ // command must be an idempotent NO-OP — never an error and never a second merge
242
+ // attempt. (An absent/unknown state falls through to the fail-closed rules.)
243
+ const prState = prContext && prContext.state ? String(prContext.state).toUpperCase() : '';
244
+ if (prState && prState !== 'OPEN') {
245
+ const reason = `PR #${pr} is ${prState} (not OPEN) — nothing to merge. No action taken.`;
246
+ process.stdout.write(`${reason}\n`);
247
+ return { success: true, merged: false, enabled: true, state: prState, reason };
248
+ }
249
+
250
+ const { allowed, unmet } = evaluateMergeRules(prContext, rules);
251
+
252
+ if (!allowed) {
253
+ process.stdout.write(`Auto-merge conditions NOT met for PR #${pr} — ${unmet.length} unmet rule(s):\n`);
254
+ for (const item of unmet) {
255
+ process.stdout.write(` x ${item.rule} — ${item.reason}\n`);
256
+ }
257
+ return { success: true, merged: false, enabled: true, allowed: false, unmet, reason: 'auto-merge conditions not met' };
258
+ }
259
+
260
+ // TOCTOU guard: PR state can change between the first fetch and the merge — a
261
+ // new comment resets settle_min, a required check regresses, a thread opens.
262
+ // Re-pull LIVE data and re-evaluate immediately before merging so our custom
263
+ // rules (which GitHub's server-side branch protection does NOT enforce) are
264
+ // honored against the freshest possible state, never a stale snapshot.
265
+ let freshContext;
266
+ try {
267
+ freshContext = await fetchPrContext({ pr, projectRoot: root, gh, now: deps.now || Date.now() });
268
+ } catch (err) {
269
+ return { success: false, merged: false, error: `Failed to re-fetch PR context before merge: ${err.message}` };
270
+ }
271
+ // Re-apply the terminal-state guard on the FRESH context: the PR may have been
272
+ // merged or closed between the first fetch and now. Never merge a terminal PR.
273
+ const freshState = freshContext && freshContext.state ? String(freshContext.state).toUpperCase() : '';
274
+ if (freshState && freshState !== 'OPEN') {
275
+ const reason = `PR #${pr} became ${freshState} (not OPEN) before merge — nothing to merge. No action taken.`;
276
+ process.stdout.write(`${reason}\n`);
277
+ return { success: true, merged: false, enabled: true, state: freshState, reason };
278
+ }
279
+ const recheck = evaluateMergeRules(freshContext, rules);
280
+ if (!recheck.allowed) {
281
+ process.stdout.write(`Auto-merge ABORTED for PR #${pr} — state changed since first check; ${recheck.unmet.length} rule(s) now unmet:\n`);
282
+ for (const item of recheck.unmet) {
283
+ process.stdout.write(` x ${item.rule} — ${item.reason}\n`);
284
+ }
285
+ return { success: true, merged: false, enabled: true, allowed: false, unmet: recheck.unmet, reason: 'PR state changed before merge (live re-check failed)' };
286
+ }
287
+
288
+ try {
289
+ const mergeResult = await mergePr({ pr, projectRoot: root, gh });
290
+ process.stdout.write(`All ${rules.length} merge rule(s) passed — merged PR #${pr}.\n`);
291
+ return {
292
+ success: true,
293
+ merged: true,
294
+ enabled: true,
295
+ allowed: true,
296
+ reason: 'all merge rules passed',
297
+ ...(mergeResult && typeof mergeResult === 'object' ? mergeResult : {}),
298
+ };
299
+ } catch (err) {
300
+ return { success: false, merged: false, error: `Merge failed: ${err.message}` };
301
+ }
302
+ }
303
+
304
+ module.exports = {
305
+ name: 'merge',
306
+ description: 'Opt-in conditional auto-merge: merge a PR only when all user-configured rules pass (OFF by default)',
307
+ usage: 'Usage: forge merge --auto <pr>',
308
+ handler,
309
+ // Exported seams for testing / reuse.
310
+ defaultFetchPrContext,
311
+ defaultMergePr,
312
+ };