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
@@ -0,0 +1,697 @@
1
+ 'use strict';
2
+
3
+ const { spawn } = require('node:child_process');
4
+ const { existsSync } = require('node:fs');
5
+ const path = require('node:path');
6
+ const { isBeadsInitialized } = require('./beads-setup');
7
+ const { BeadsIssueAdapter } = require('./adapters/beads-issue-adapter');
8
+ const { KernelIssueAdapter } = require('./adapters/kernel-issue-adapter');
9
+ const { createGitHubProjectionPlan } = require('./issue-sync/project-github');
10
+ const { createLocalBroker, buildLocalBrokerConfig } = require('./kernel/broker');
11
+ const { createBuiltinSQLiteDriver } = require('./kernel/sqlite-driver');
12
+ const { shouldUseKernelBroker } = require('./issue-backend');
13
+ const { detectWorktree } = require('./detect-worktree');
14
+ const { DEFAULT_LEASE_TTL_MS } = require('./kernel/lease-enforcer');
15
+
16
+ const OPERATION_TO_BD = {
17
+ create: 'create',
18
+ list: 'list',
19
+ ready: 'ready',
20
+ show: 'show',
21
+ search: 'search',
22
+ stats: 'status',
23
+ close: 'close',
24
+ update: 'update',
25
+ comment: 'comments',
26
+ // KAP-7/KAP-12 derived reads: identity passthroughs to the matching bd subcommand.
27
+ // Routed here (not in _issue.js) so the issue command surface carries no bd args.
28
+ blocked: 'blocked',
29
+ stale: 'stale',
30
+ orphans: 'orphans',
31
+ lint: 'lint',
32
+ // Epic support: identity passthrough to `bd children` for the beads opt-out. The
33
+ // kernel default emits the richer `issue.children` rollup; this passthrough only
34
+ // keeps the beads path from a null-deref when children is invoked under --issue-backend
35
+ // beads (the bd output shape differs and carries no rollup).
36
+ children: 'children',
37
+ };
38
+
39
+ // Beads has no verified release operation; the claim lease lives only in the Kernel
40
+ // backend. Surfaced as an explicit error so `forge release <id>` on the beads opt-out
41
+ // fails loudly instead of silently mapping to an unrelated bd subcommand.
42
+ const BEADS_RELEASE_KERNEL_ONLY_ERROR =
43
+ 'forge release <id> is defined for the Kernel issue backend; Beads passthrough has no verified release operation.';
44
+
45
+ // `bd children` emits a different payload (no rollup) than the kernel
46
+ // `issue.children` envelope this command promises. Reject the beads backend
47
+ // explicitly instead of silently proxying a contract-violating shape.
48
+ const BEADS_CHILDREN_KERNEL_ONLY_ERROR =
49
+ 'forge issue children is defined for the Kernel issue backend; Beads passthrough has no equivalent issue.children rollup.';
50
+
51
+ // Lease ownership lives only in the Kernel backend (kernel_claims); Beads has no
52
+ // verified lease to check. Reject the beads path explicitly so `forge issue owns <id>`
53
+ // fails loudly instead of mapping to an unrelated/absent Beads subcommand.
54
+ const BEADS_OWNS_KERNEL_ONLY_ERROR =
55
+ 'forge issue owns <id> is defined for the Kernel issue backend; Beads passthrough has no lease-ownership verification.';
56
+
57
+ // Active leases live only in the Kernel backend (kernel_claims); Beads has no lease
58
+ // table to enumerate. Reject the beads path explicitly so `forge claims` fails loudly
59
+ // instead of mapping to an absent Beads subcommand.
60
+ const BEADS_CLAIMS_KERNEL_ONLY_ERROR =
61
+ 'forge claims is defined for the Kernel issue backend; Beads passthrough has no active-lease listing.';
62
+
63
+ const OPERATION_METHOD_ALIASES = {
64
+ 'dep.add': 'depAdd',
65
+ 'dep.remove': 'depRemove',
66
+ };
67
+
68
+ function getSpawnOptions(projectRoot) {
69
+ return {
70
+ cwd: projectRoot,
71
+ stdio: ['ignore', 'pipe', 'pipe'],
72
+ };
73
+ }
74
+
75
+ function getPathEntries(env = process.env, delimiter = path.delimiter) {
76
+ const rawPath = env.PATH || env.Path || '';
77
+ return rawPath
78
+ .split(delimiter)
79
+ .map(entry => entry.trim().replace(/^"(.*)"$/, '$1'))
80
+ .filter(Boolean);
81
+ }
82
+
83
+ function resolveWindowsCommandCandidates(commandNames, deps = {}) {
84
+ const env = deps.env || process.env;
85
+ const fileExists = deps.existsSync || existsSync;
86
+ const resolved = [];
87
+ const seen = new Set();
88
+ const pathEntries = getPathEntries(env, ';');
89
+
90
+ for (const dir of pathEntries) {
91
+ for (const commandName of commandNames) {
92
+ const fullPath = isWindowsPathEntry(dir)
93
+ ? path.win32.join(dir, commandName)
94
+ : path.posix.join(dir, commandName);
95
+ const dedupeKey = fullPath.toLowerCase();
96
+ if (!seen.has(dedupeKey) && fileExists(fullPath)) {
97
+ seen.add(dedupeKey);
98
+ resolved.push(fullPath);
99
+ }
100
+ }
101
+ }
102
+
103
+ return resolved;
104
+ }
105
+
106
+ function isWindowsPathEntry(entry = '') {
107
+ return /^(?:[a-zA-Z]:[\\/]|\\\\)/.test(entry) || entry.includes('\\');
108
+ }
109
+
110
+ function getBdCommandCandidates(deps = {}) {
111
+ const platform = deps.platform || process.platform;
112
+ if (platform === 'win32') {
113
+ const resolved = resolveWindowsCommandCandidates(['bd.exe', 'bd.cmd'], deps);
114
+ return [...new Set([...resolved, 'bd.exe', 'bd'])];
115
+ }
116
+
117
+ return ['bd'];
118
+ }
119
+
120
+ function normalizeExecOutput(output) {
121
+ if (typeof output === 'string') {
122
+ return output;
123
+ }
124
+
125
+ if (Buffer.isBuffer(output)) {
126
+ return output.toString('utf8');
127
+ }
128
+
129
+ return '';
130
+ }
131
+
132
+ function hasBdSoftFailure(output) {
133
+ return /(^|\n)Error(?: resolving| updating| fetching| adding)?\b/i.test(output);
134
+ }
135
+
136
+ function hasEmptyShowPayload(operation, output) {
137
+ if (operation !== 'show') {
138
+ return false;
139
+ }
140
+
141
+ const trimmedOutput = output.trim();
142
+ return trimmedOutput === '' || trimmedOutput === '[]' || trimmedOutput === 'null';
143
+ }
144
+
145
+ function isHelpInvocation(args = []) {
146
+ return args.includes('--help') || args.includes('-h');
147
+ }
148
+
149
+ function extractErrorMessage(error) {
150
+ if (error?.code === 'ENOENT') {
151
+ return 'Beads (bd) command not found. Install or initialize Beads before using forge issues.';
152
+ }
153
+
154
+ return error?.message?.trim() || 'Beads command failed';
155
+ }
156
+
157
+ function getCommandErrorMessage(result) {
158
+ const output = [result?.stdout, result?.stderr].filter(Boolean).join('\n').trim();
159
+ if (output) {
160
+ return output;
161
+ }
162
+
163
+ if (typeof result?.code === 'number') {
164
+ return `Beads command failed with exit code ${result.code}`;
165
+ }
166
+
167
+ return 'Beads command failed';
168
+ }
169
+
170
+ function buildBdArgs(operation, args) {
171
+ if (operation === 'comment') {
172
+ return ['comments', 'add', ...args];
173
+ }
174
+
175
+ // De-bead parity: `forge claim <id>` maps to bd `update <id> --claim`. The
176
+ // translation lived in _issue.js; it now lives in the beads layer so the issue
177
+ // command surface stays free of bd argv. The issue id is the first positional.
178
+ if (operation === 'claim') {
179
+ const [issueId, ...rest] = args;
180
+ if (!issueId) {
181
+ return { error: 'Missing issue id. Usage: forge claim <id> [bd-update-flags]' };
182
+ }
183
+ return ['update', issueId, '--claim', ...rest];
184
+ }
185
+
186
+ if (operation === 'dep.add') {
187
+ return ['dep', 'add', ...args];
188
+ }
189
+
190
+ if (operation === 'dep.remove') {
191
+ return ['dep', 'remove', ...args];
192
+ }
193
+
194
+ const bdCommand = OPERATION_TO_BD[operation];
195
+ if (!bdCommand) {
196
+ return null;
197
+ }
198
+
199
+ return [bdCommand, ...args];
200
+ }
201
+
202
+ function shouldCaptureOutput(operation, args, deps = {}) {
203
+ if (deps.captureOutput === true) {
204
+ return true;
205
+ }
206
+
207
+ if (isHelpInvocation(args)) {
208
+ return true;
209
+ }
210
+
211
+ if (args.includes('--json')) {
212
+ return true;
213
+ }
214
+
215
+ return operation === 'update' || operation === 'show';
216
+ }
217
+
218
+ async function runBdCommand(operation, args, projectRoot, deps = {}) {
219
+ const spawnBd = deps.spawn || spawn;
220
+ const stdoutTarget = deps.stdout || process.stdout;
221
+ const stderrTarget = deps.stderr || process.stderr;
222
+ const captureOutput = shouldCaptureOutput(operation, args.slice(1), deps);
223
+ const commandCandidates = deps.commandCandidates || getBdCommandCandidates(deps);
224
+ let lastError;
225
+
226
+ for (const command of commandCandidates) {
227
+ try {
228
+ return await new Promise((resolve, reject) => {
229
+ const child = spawnBd(command, args, getSpawnOptions(projectRoot));
230
+ let stdout = '';
231
+ let stderr = '';
232
+
233
+ child.stdout?.setEncoding?.('utf8');
234
+ child.stderr?.setEncoding?.('utf8');
235
+ child.stdout?.on?.('data', chunk => {
236
+ const normalizedChunk = normalizeExecOutput(chunk);
237
+ if (captureOutput) {
238
+ stdout += normalizedChunk;
239
+ } else {
240
+ stdoutTarget?.write?.(normalizedChunk);
241
+ }
242
+ });
243
+ child.stderr?.on?.('data', chunk => {
244
+ const normalizedChunk = normalizeExecOutput(chunk);
245
+ if (captureOutput) {
246
+ stderr += normalizedChunk;
247
+ }
248
+ stderrTarget?.write?.(normalizedChunk);
249
+ });
250
+
251
+ child.on('error', reject);
252
+ child.on('close', code => {
253
+ resolve({
254
+ code,
255
+ stdout,
256
+ stderr,
257
+ });
258
+ });
259
+ });
260
+ } catch (error) {
261
+ lastError = error;
262
+ // ENOENT/EINVAL means this candidate is not directly spawnable — try next.
263
+ if (error?.code !== 'ENOENT' && error?.code !== 'EINVAL') {
264
+ throw error;
265
+ }
266
+ }
267
+ }
268
+
269
+ throw lastError;
270
+ }
271
+
272
+ async function runBeadsOperation(operation, args, context, deps) {
273
+ // Beads has no verified release; fail before the init check so the kernel-only
274
+ // contract is reported even in an uninitialized repo (parity with the old
275
+ // _issue.js release guard, which short-circuited on the beads path).
276
+ if (operation === 'release' && !isHelpInvocation(args)) {
277
+ return { success: false, error: BEADS_RELEASE_KERNEL_ONLY_ERROR };
278
+ }
279
+
280
+ // `children` only has a verified shape on the kernel backend (the
281
+ // issue.children rollup); fail loudly rather than proxy `bd children`.
282
+ if (operation === 'children' && !isHelpInvocation(args)) {
283
+ return { success: false, error: BEADS_CHILDREN_KERNEL_ONLY_ERROR };
284
+ }
285
+
286
+ // `owns` verifies a Kernel lease; Beads has no such lease. Fail loudly rather
287
+ // than silently proxy to a non-existent Beads subcommand.
288
+ if (operation === 'owns' && !isHelpInvocation(args)) {
289
+ return { success: false, error: BEADS_OWNS_KERNEL_ONLY_ERROR };
290
+ }
291
+
292
+ // `claims` enumerates the Kernel lease table; Beads has none. Fail loudly rather
293
+ // than silently proxy to a non-existent Beads subcommand.
294
+ if (operation === 'claims' && !isHelpInvocation(args)) {
295
+ return { success: false, error: BEADS_CLAIMS_KERNEL_ONLY_ERROR };
296
+ }
297
+
298
+ const checkInit = deps.isBeadsInitialized || isBeadsInitialized;
299
+ if (!isHelpInvocation(args) && !checkInit(context.projectRoot)) {
300
+ return {
301
+ success: false,
302
+ error: 'Beads is not initialized in this project. Run forge setup before using forge issues.',
303
+ };
304
+ }
305
+
306
+ const bdArgs = buildBdArgs(operation, args);
307
+ // A non-array result is a translation error (e.g. claim with no issue id).
308
+ if (!Array.isArray(bdArgs)) {
309
+ return { success: false, error: bdArgs.error };
310
+ }
311
+ // The reported operation is the logical operation name (comment -> 'comment',
312
+ // show -> 'show'), NOT the bd subcommand argv[0] (which is 'comments'/'status').
313
+ // The one exception is claim: it maps to `update <id> --claim`, and the historical
314
+ // _issue.js beads path reported operation 'update', so preserve that here.
315
+ const reportedOperation = operation === 'claim' ? 'update' : operation;
316
+ const runCommand = deps.runBdCommand || ((cmdArgs, projectRoot) => runBdCommand(operation, cmdArgs, projectRoot, deps));
317
+
318
+ try {
319
+ const result = await runCommand(bdArgs, context.projectRoot);
320
+ const stdout = normalizeExecOutput(result?.stdout);
321
+ const stderr = normalizeExecOutput(result?.stderr);
322
+ const combinedOutput = [stdout, stderr].filter(Boolean).join('\n');
323
+
324
+ if (result?.code !== 0) {
325
+ return {
326
+ success: false,
327
+ error: getCommandErrorMessage(result),
328
+ };
329
+ }
330
+
331
+ if (hasBdSoftFailure(combinedOutput)) {
332
+ return {
333
+ success: false,
334
+ error: combinedOutput.trim() || 'Beads command failed',
335
+ };
336
+ }
337
+
338
+ if (hasEmptyShowPayload(operation, combinedOutput)) {
339
+ return {
340
+ success: false,
341
+ error: `Issue not found: ${args[0] || 'unknown issue'}`,
342
+ };
343
+ }
344
+
345
+ return {
346
+ success: true,
347
+ operation: reportedOperation,
348
+ output: stdout,
349
+ stderr,
350
+ };
351
+ } catch (error) {
352
+ return {
353
+ success: false,
354
+ error: extractErrorMessage(error),
355
+ };
356
+ }
357
+ }
358
+
359
+ function createBeadsIssueBackend(deps = {}) {
360
+ return new BeadsIssueAdapter({
361
+ runBeadsOperation: (operation, args, context, contextDeps = {}) =>
362
+ runBeadsOperation(operation, args, context, {
363
+ ...deps,
364
+ ...contextDeps,
365
+ }),
366
+ });
367
+ }
368
+
369
+ // Build the default local broker for the CLI path. createLocalBroker does NOT
370
+ // construct a driver on its own (`const driver = options.driver`), so when no
371
+ // driver is injected we build a builtin SQLite driver pointed at the resolved
372
+ // kernel DB path. Without this, broker.initialize()/runIssueOperation would throw
373
+ // on a missing `exec` method before ever reaching the "no tables" case.
374
+ function createDefaultKernelBroker(brokerContext, deps) {
375
+ let driver = deps.kernelDriver;
376
+ if (!driver) {
377
+ const config = buildLocalBrokerConfig({
378
+ projectRoot: brokerContext.projectRoot,
379
+ gitCommonDir: deps.gitCommonDir,
380
+ databasePath: deps.kernelDatabasePath,
381
+ execFileSync: deps.execFileSync,
382
+ });
383
+ driver = createBuiltinSQLiteDriver({ databasePath: config.databasePath });
384
+ }
385
+
386
+ return createLocalBroker({
387
+ projectRoot: brokerContext.projectRoot,
388
+ gitCommonDir: deps.gitCommonDir,
389
+ databasePath: deps.kernelDatabasePath,
390
+ driver,
391
+ execFileSync: deps.execFileSync,
392
+ // Pass-through for the D19 default-on filesystem gate (fires in the broker's
393
+ // getConfig chokepoint). Forwarding it lets callers/tests inject a deterministic
394
+ // classifier instead of paying the real Windows drive probe per per-op broker.
395
+ classifyFilesystem: deps.classifyFilesystem,
396
+ });
397
+ }
398
+
399
+ // Symbol marker for the memoized lazy-init promise. Stored ON the broker object
400
+ // so that re-wrapping the SAME broker instance (e.g. a long-lived injected broker
401
+ // hit by several runIssueOperation calls) still initializes exactly once.
402
+ const LAZY_INIT_PROMISE = Symbol('forgeKernelLazyInitPromise');
403
+
404
+ // Wrap a broker so the kernel DB is migrated lazily and exactly once before the
405
+ // first issue operation. initialize() applies migrations idempotently
406
+ // (CREATE TABLE IF NOT EXISTS), so a fresh DB gains its tables automatically.
407
+ // Brokers without an initialize() method (e.g. test fakes) pass through untouched.
408
+ function withLazyKernelInit(broker) {
409
+ if (!broker || typeof broker.initialize !== 'function') {
410
+ return broker;
411
+ }
412
+
413
+ const ensureInitialized = () => {
414
+ if (!broker[LAZY_INIT_PROMISE]) {
415
+ broker[LAZY_INIT_PROMISE] = Promise.resolve(broker.initialize()).catch((error) => {
416
+ // Reset so a transient init failure can be retried on the next op rather
417
+ // than permanently poisoning the broker with a rejected cached promise.
418
+ broker[LAZY_INIT_PROMISE] = null;
419
+ throw error;
420
+ });
421
+ }
422
+ return broker[LAZY_INIT_PROMISE];
423
+ };
424
+
425
+ // Delegate explicitly rather than spreading `...broker`: a spread copies only the
426
+ // broker's OWN enumerable props, so a future class-based broker would silently
427
+ // lose its prototype methods. The adapter only ever calls runIssueOperation (plus
428
+ // optional initialize), so forwarding exactly those two is the complete surface.
429
+ return {
430
+ async initialize() {
431
+ return ensureInitialized();
432
+ },
433
+ async runIssueOperation(operation, args = [], context = {}) {
434
+ await ensureInitialized();
435
+ return broker.runIssueOperation(operation, args, context);
436
+ },
437
+ };
438
+ }
439
+
440
+ function createKernelIssueBackend(context = {}) {
441
+ const deps = context.deps || {};
442
+ const createBroker = deps.createKernelBroker
443
+ || ((brokerContext) => createDefaultKernelBroker(brokerContext, deps));
444
+
445
+ const broker = deps.kernelBroker || createBroker(context);
446
+
447
+ return new KernelIssueAdapter({
448
+ broker: withLazyKernelInit(broker),
449
+ });
450
+ }
451
+
452
+ function createIssueService({ backend } = {}) {
453
+ // Default issue backend is now the Kernel. Beads is reachable opt-OUT via the
454
+ // selector (--issue-backend beads / FORGE_ISSUE_BACKEND=beads / config), which
455
+ // resolves to an explicit `backend` upstream in runIssueOperation. This no-arg
456
+ // fallback fires only for a no-backend direct call to createIssueService — the
457
+ // CLI path always threads an explicit backend through resolveCommandOpts.
458
+ const resolvedBackend = backend || createKernelIssueBackend();
459
+
460
+ return {
461
+ async run(operation, args = [], context = {}) {
462
+ const methodName = operation === 'show' && typeof resolvedBackend?.show !== 'function'
463
+ ? 'read'
464
+ : OPERATION_METHOD_ALIASES[operation] || operation;
465
+ const method = resolvedBackend?.[methodName];
466
+ if (typeof method !== 'function') {
467
+ return {
468
+ success: false,
469
+ error: `Unsupported issue operation: ${operation}`,
470
+ };
471
+ }
472
+
473
+ return method.call(resolvedBackend, args, context);
474
+ },
475
+ };
476
+ }
477
+
478
+ // Resolve a distinct per-agent actor for Kernel issue mutations. The Kernel keys a
479
+ // claim's idempotency on `claim.create:<issue_id>:<actor>` (lib/kernel/broker.js
480
+ // buildClaimMutationEvent), so two concurrent agents that both fall back to the 'forge'
481
+ // default collide on ONE key: the 2nd claim replays as an idempotent duplicate (ok:true)
482
+ // instead of reaching the lease-conflict path, so a genuine loser is told it won
483
+ // (kernel d71a824b). An explicit per-agent identity produces a distinct key so the
484
+ // second claimant reaches the claim_conflict guard.
485
+ //
486
+ // Precedence: FORGE_ACTOR (explicit per-agent id) -> FORGE_SESSION_ID (stable session id)
487
+ // -> undefined. Returning undefined is deliberate: buildIssueMutationEvent applies
488
+ // `context.actor || 'forge'`, so a no-env caller (the main repo, existing tests) is
489
+ // byte-identical to before — only an explicitly-distinct identity changes behavior.
490
+ // A git-worktree-derived identity is a deferred follow-up: it would change the default
491
+ // actor in every worktree checkout, so it must be validated against the suite first.
492
+ function resolveIssueActor(env = {}) {
493
+ const source = env || {};
494
+ const explicit = typeof source.FORGE_ACTOR === 'string' ? source.FORGE_ACTOR.trim() : '';
495
+ if (explicit) return explicit;
496
+ const session = typeof source.FORGE_SESSION_ID === 'string' ? source.FORGE_SESSION_ID.trim() : '';
497
+ if (session) return session;
498
+ return undefined;
499
+ }
500
+
501
+ // Resolve the worktree_id stamped onto a claim lease (kernel d71a824b) so the dashboard
502
+ // can show WHICH worktree holds a lease (the kernel DB is shared across all worktrees of
503
+ // a repo). Precedence: FORGE_WORKTREE_ID (explicit override) -> the current worktree's
504
+ // directory name (git-common-dir/registry-derived via detectWorktree) -> undefined.
505
+ // Best-effort: any failure (not a git repo, git unavailable) yields undefined, so the
506
+ // lease's worktree_id is simply null — never a thrown error on the claim path.
507
+ function resolveWorktreeId(projectRoot, env = {}, deps = {}) {
508
+ const override = typeof env.FORGE_WORKTREE_ID === 'string' ? env.FORGE_WORKTREE_ID.trim() : '';
509
+ if (override) return override;
510
+ try {
511
+ const detect = deps.detectWorktree || detectWorktree;
512
+ const info = detect(projectRoot);
513
+ if (info && typeof info.currentWorktree === 'string' && info.currentWorktree) {
514
+ return path.basename(info.currentWorktree);
515
+ }
516
+ } catch {
517
+ // best-effort: worktree_id is an optional presence signal, never a hard failure
518
+ }
519
+ return undefined;
520
+ }
521
+
522
+ // Resolve the lease TTL (ms) applied to a claim when the caller pins no explicit
523
+ // --expires. FORGE_LEASE_TTL_MS overrides the DEFAULT_LEASE_TTL_MS constant; an explicit
524
+ // 0 / negative / non-numeric value is an OPT-OUT (returns null → never-expiring lease,
525
+ // the historical default), preserving the null-expiry capability from the CLI.
526
+ function resolveLeaseTtlMs(env = {}) {
527
+ const raw = env.FORGE_LEASE_TTL_MS;
528
+ if (raw === undefined || raw === null || String(raw).trim() === '') return DEFAULT_LEASE_TTL_MS;
529
+ const ttl = Number(raw);
530
+ if (!Number.isFinite(ttl) || ttl <= 0) return null;
531
+ return ttl;
532
+ }
533
+
534
+ // Graceful Beads→Kernel fallback (issue 7f09ae93). When the resolved backend is
535
+ // Beads but Beads is NOT initialized in this project AND a Forge Kernel store
536
+ // already exists at `<gitCommonDir>/forge/kernel.sqlite`, route the operation to
537
+ // the kernel instead of dead-ending on "Beads is not initialized". This unblocks
538
+ // upgraders whose repo predates the kernel default (or whose global `forge` binary
539
+ // is stale and still resolves Beads): the kernel is the correct default and already
540
+ // holds their issues. Returns the resolved kernel paths when the fallback applies,
541
+ // or null to keep the Beads path (so a genuine no-store repo still gets the Beads
542
+ // not-initialized error). Path resolution reuses buildLocalBrokerConfig so the probed
543
+ // location matches exactly what the broker would open. Never throws.
544
+ function resolveBeadsFallbackToKernel(projectRoot, deps = {}) {
545
+ const checkInit = deps.isBeadsInitialized || isBeadsInitialized;
546
+ if (checkInit(projectRoot)) {
547
+ // Beads is initialized — honor the explicit Beads selection, no fallback.
548
+ return null;
549
+ }
550
+
551
+ let config;
552
+ try {
553
+ config = buildLocalBrokerConfig({
554
+ projectRoot,
555
+ gitCommonDir: deps.gitCommonDir,
556
+ databasePath: deps.kernelDatabasePath,
557
+ execFileSync: deps.execFileSync,
558
+ });
559
+ } catch {
560
+ // No resolvable kernel location (e.g. projectRoot is not a git repo) — there is
561
+ // no safe fallback target, so let the Beads path report its own error.
562
+ return null;
563
+ }
564
+
565
+ const fileExists = deps.existsSync || existsSync;
566
+ if (!fileExists(config.databasePath)) {
567
+ // No kernel store either — preserve the Beads not-initialized error.
568
+ return null;
569
+ }
570
+
571
+ return { kernelDatabasePath: config.databasePath, gitCommonDir: config.gitCommonDir };
572
+ }
573
+
574
+ async function runIssueOperation(operation, rawArgs, projectRoot, deps = {}) {
575
+ const injectedService = deps.createService;
576
+ let routeToKernel = shouldUseKernelBroker(deps);
577
+ let effectiveDeps = deps;
578
+
579
+ // Beads→Kernel graceful fallback (7f09ae93). Only considered when a service is not
580
+ // injected (an injected service owns backend selection), the selection is not
581
+ // already Kernel, and this is not a help invocation (help must never touch a
582
+ // backend). When it applies, treat the op as Kernel-routed end-to-end (backend
583
+ // construction AND the projection-target seam below).
584
+ if (!injectedService && !routeToKernel && !isHelpInvocation(rawArgs)) {
585
+ const fallback = resolveBeadsFallbackToKernel(projectRoot, deps);
586
+ if (fallback) {
587
+ routeToKernel = true;
588
+ effectiveDeps = {
589
+ ...deps,
590
+ useKernelBroker: true,
591
+ kernelDatabasePath: fallback.kernelDatabasePath,
592
+ gitCommonDir: fallback.gitCommonDir,
593
+ };
594
+ const notify = deps.warn || ((msg) => process.stderr.write(`${msg}\n`));
595
+ notify(
596
+ 'Notice: Beads is not initialized, but a Forge Kernel store exists — using the '
597
+ + 'kernel backend (the default). Run `forge setup` to finish migrating.',
598
+ );
599
+ }
600
+ }
601
+
602
+ const createService = injectedService || (() => {
603
+ const context = {
604
+ projectRoot,
605
+ deps: effectiveDeps,
606
+ };
607
+
608
+ if (routeToKernel) {
609
+ return createIssueService({
610
+ backend: createKernelIssueBackend(context),
611
+ });
612
+ }
613
+
614
+ const backendDeps = {
615
+ isBeadsInitialized: deps.isBeadsInitialized,
616
+ runBdCommand: deps.runBdCommand,
617
+ spawn: deps.spawn,
618
+ platform: deps.platform,
619
+ env: deps.env,
620
+ };
621
+
622
+ return createIssueService({
623
+ backend: createBeadsIssueBackend(backendDeps),
624
+ });
625
+ });
626
+
627
+ const service = createService();
628
+ // Thread a distinct per-agent actor (and, when present, a stable session id) into the
629
+ // mutation context so the Kernel's claim idempotency key is scoped per agent rather than
630
+ // to the shared 'forge' default (kernel d71a824b). Undefined values are omitted so the
631
+ // historical default is preserved for no-env callers, and the Beads backend — which
632
+ // ignores context.actor/sessionId — is unaffected.
633
+ const actorEnv = deps.env || process.env;
634
+ const actor = resolveIssueActor(actorEnv);
635
+ const sessionId = typeof actorEnv.FORGE_SESSION_ID === 'string' && actorEnv.FORGE_SESSION_ID.trim()
636
+ ? actorEnv.FORGE_SESSION_ID.trim()
637
+ : undefined;
638
+ // The worktree id and lease TTL only shape a claim's lease row, so resolve them ONLY
639
+ // for the claim op — worktree detection shells out to git, and running it on every read
640
+ // (list/show/ready) would be pure cost. leaseTtlMs=null (explicit opt-out) is omitted so
641
+ // the broker keeps a null (never-expiring) lease.
642
+ const isClaim = operation === 'claim';
643
+ const worktreeId = isClaim ? resolveWorktreeId(projectRoot, actorEnv, deps) : undefined;
644
+ const leaseTtlMs = isClaim ? resolveLeaseTtlMs(actorEnv) : undefined;
645
+ const result = await service.run(operation, rawArgs, {
646
+ projectRoot,
647
+ deps,
648
+ ...(actor ? { actor } : {}),
649
+ ...(sessionId ? { sessionId } : {}),
650
+ ...(worktreeId ? { worktreeId } : {}),
651
+ ...(leaseTtlMs ? { leaseTtlMs } : {}),
652
+ // Kernel mutations enqueue a projection-outbox "dirty marker" that `forge export`
653
+ // (the D16 git-JSONL portability projection) drains under target 'jsonl'. The
654
+ // broker's legacy primitive default is 'beads', so without steering the target
655
+ // here every Kernel-created issue is enqueued as 'beads' and `forge export` — which
656
+ // drains 'jsonl' — finds nothing, silently never emitting git-tracked JSONL. Only
657
+ // the Kernel path uses this seam (`context.projectionTarget`); Beads ignores it.
658
+ ...(routeToKernel ? { projectionTarget: 'jsonl' } : {}),
659
+ });
660
+
661
+ const queueGitHubProjection = deps.enqueueGitHubProjection || deps.queueGitHubProjection;
662
+ if (result?.success && typeof queueGitHubProjection === 'function') {
663
+ const projectionPlan = createGitHubProjectionPlan(operation, rawArgs);
664
+ if (projectionPlan) {
665
+ await queueGitHubProjection(projectionPlan, {
666
+ operation,
667
+ args: rawArgs,
668
+ projectRoot,
669
+ result,
670
+ });
671
+ }
672
+ }
673
+
674
+ return result;
675
+ }
676
+
677
+ module.exports = {
678
+ createIssueService,
679
+ createBeadsIssueBackend,
680
+ createKernelIssueBackend,
681
+ runBeadsOperation,
682
+ runIssueOperation,
683
+ buildBdArgs,
684
+ extractErrorMessage,
685
+ getCommandErrorMessage,
686
+ getSpawnOptions,
687
+ hasEmptyShowPayload,
688
+ hasBdSoftFailure,
689
+ isHelpInvocation,
690
+ normalizeExecOutput,
691
+ runBdCommand,
692
+ getBdCommandCandidates,
693
+ getPathEntries,
694
+ resolveIssueActor,
695
+ resolveBeadsFallbackToKernel,
696
+ resolveWindowsCommandCandidates,
697
+ };