forge-workflow 0.0.10 → 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 (454) 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 +150 -61
  8. package/CHANGELOG.md +681 -0
  9. package/CLAUDE.md +9 -118
  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 +461 -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 +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/{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 +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 +121 -0
  81. package/lib/beads-sync-scaffold.js +25 -101
  82. package/lib/codex-skills.js +51 -1
  83. package/lib/commands/_issue.js +741 -77
  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 +17 -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 +0 -1
  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 +838 -972
  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 +158 -21
  140. package/lib/commands/sync.js +34 -46
  141. package/lib/commands/team.js +4 -1
  142. package/lib/commands/test.js +43 -27
  143. package/lib/commands/update.js +2 -2
  144. package/lib/commands/upgrade.js +47 -0
  145. package/lib/commands/validate.js +43 -18
  146. package/lib/commands/worktree.js +307 -100
  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 +2 -2
  151. package/lib/deprecated-sync-cleanup.js +362 -0
  152. package/lib/detect-agent.js +2 -28
  153. package/lib/detect-worktree.js +35 -9
  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 +382 -11
  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/kernel/backing-issue.js +305 -0
  174. package/lib/kernel/broker.js +1218 -0
  175. package/lib/kernel/cli-broker-factory.js +130 -0
  176. package/lib/kernel/conflict-signal.js +82 -0
  177. package/lib/kernel/evaluators.js +195 -0
  178. package/lib/kernel/fs-class.js +495 -0
  179. package/lib/kernel/issue-command-contract.js +559 -0
  180. package/lib/kernel/issue-id-resolver.js +186 -0
  181. package/lib/kernel/lease-enforcer.js +158 -0
  182. package/lib/kernel/migrations.js +333 -0
  183. package/lib/kernel/planning-buckets-schema.js +109 -0
  184. package/lib/kernel/projection-jsonl-writer.js +450 -0
  185. package/lib/kernel/readiness-model.js +329 -0
  186. package/lib/kernel/schema.js +356 -0
  187. package/lib/kernel/sqlite-driver.js +2504 -0
  188. package/lib/kernel/taxonomy-validator.js +394 -0
  189. package/lib/lefthook-check.js +3 -2
  190. package/lib/lefthook-wiring.js +413 -0
  191. package/lib/mcp-config-renderer.js +288 -0
  192. package/lib/memory/graphiti-mcp.js +106 -0
  193. package/lib/memory/router.js +387 -0
  194. package/lib/memory/typed-api.js +102 -0
  195. package/lib/memory-digest.js +195 -0
  196. package/lib/merge-rules.js +395 -0
  197. package/lib/migrate-dry-run.js +466 -0
  198. package/lib/orientation.js +863 -0
  199. package/lib/package-manager-remediation.js +103 -0
  200. package/lib/package-root.js +381 -0
  201. package/lib/patch-intent.js +890 -0
  202. package/lib/plugin-catalog.js +3 -4
  203. package/lib/plugin-manager.js +0 -5
  204. package/lib/pr-bundle.js +186 -0
  205. package/lib/pr-monitor/differ.js +195 -0
  206. package/lib/pr-monitor/events.js +0 -0
  207. package/lib/pr-monitor/gather.js +124 -0
  208. package/lib/pr-monitor/journal.js +299 -0
  209. package/lib/pr-monitor/monitor.js +146 -0
  210. package/lib/pr-monitor/render-sticky.js +157 -0
  211. package/lib/pr-monitor/watch-lifecycle.js +95 -0
  212. package/lib/pr-monitor/watch.js +247 -0
  213. package/lib/pr-pull.js +1273 -0
  214. package/lib/pr-shepherd.js +494 -0
  215. package/lib/pr-state-validator.js +59 -0
  216. package/lib/preflight/gates.js +237 -0
  217. package/lib/preflight/runner.js +116 -0
  218. package/lib/project-discovery.js +0 -53
  219. package/lib/project-memory.js +99 -497
  220. package/lib/protected-path-manifest.js +281 -0
  221. package/lib/protected-state-surfaces.js +387 -0
  222. package/lib/release-readiness.js +2089 -0
  223. package/lib/reset.js +59 -45
  224. package/lib/review-adapter.js +68 -0
  225. package/lib/rules-sync.js +260 -0
  226. package/lib/runtime-health.js +241 -20
  227. package/lib/safety-config-renderer.js +268 -0
  228. package/lib/setup-action-log.js +1 -7
  229. package/lib/setup.js +27 -65
  230. package/lib/shell-utils.js +76 -6
  231. package/lib/skills-sync.js +330 -0
  232. package/lib/smart-status/scoring.js +17 -3
  233. package/lib/status/beads-snapshot.js +45 -2
  234. package/lib/status/presenter.js +169 -18
  235. package/lib/status/snapshot.js +186 -0
  236. package/lib/sync-backend.js +202 -0
  237. package/lib/untrusted-content.js +52 -0
  238. package/lib/upgrade-safety.js +199 -0
  239. package/lib/workflow/enforce-stage.js +296 -47
  240. package/lib/workflow/stage-transition.js +115 -0
  241. package/lib/workflow/stages.js +30 -6
  242. package/lib/workflow/state-manager.js +11 -22
  243. package/lib/workflow/state.js +23 -1
  244. package/lib/workflow-profiles.js +17 -5
  245. package/package.json +37 -35
  246. package/rules/documentation.md +19 -0
  247. package/rules/kernel-tracking.md +26 -0
  248. package/rules/security.md +22 -0
  249. package/rules/tdd.md +20 -0
  250. package/rules/workflow.md +27 -0
  251. package/scripts/auto-backing-issue.js +47 -0
  252. package/scripts/beads-context.sh +81 -57
  253. package/scripts/beads-upgrade-smoke.sh +24 -3
  254. package/scripts/bootstrap-windows-tools.sh +78 -0
  255. package/scripts/branch-protection.js +2 -3
  256. package/scripts/check-agents.js +34 -137
  257. package/scripts/commitlint.js +3 -1
  258. package/scripts/conflict-detect.sh +3 -0
  259. package/scripts/dep-guard.sh +22 -3
  260. package/scripts/file-index.sh +3 -0
  261. package/scripts/forge-team/lib/claim.sh +34 -18
  262. package/scripts/forge-team/lib/dashboard.sh +61 -86
  263. package/scripts/forge-team/lib/epic.sh +99 -263
  264. package/scripts/forge-team/lib/hooks.sh +26 -28
  265. package/scripts/forge-team/lib/identity.sh +4 -4
  266. package/scripts/forge-team/lib/sync-github.sh +49 -84
  267. package/scripts/forge-team/lib/verify.sh +93 -83
  268. package/scripts/forge-team/lib/workload.sh +41 -65
  269. package/scripts/forge-team/tests/claim.test.sh +25 -19
  270. package/scripts/forge-team/tests/dashboard.test.sh +31 -46
  271. package/scripts/forge-team/tests/epic.test.sh +52 -71
  272. package/scripts/forge-team/tests/hooks.test.sh +38 -50
  273. package/scripts/forge-team/tests/identity.test.sh +3 -3
  274. package/scripts/forge-team/tests/integration.test.sh +44 -66
  275. package/scripts/forge-team/tests/sync-github.test.sh +50 -83
  276. package/scripts/forge-team/tests/verify.test.sh +37 -46
  277. package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
  278. package/scripts/forge-team/tests/workload.test.sh +32 -66
  279. package/scripts/gen-command-manifest.js +153 -0
  280. package/scripts/gen-embedded-assets.mjs +129 -0
  281. package/scripts/install.ps1 +139 -0
  282. package/scripts/install.sh +268 -0
  283. package/scripts/lib/release-asset.mjs +84 -0
  284. package/scripts/parity-check.mjs +145 -0
  285. package/scripts/parity-check.test.mjs +58 -0
  286. package/scripts/pin-agentic-workflow-images.js +112 -0
  287. package/scripts/pr-coordinator.sh +3 -0
  288. package/scripts/preflight-sonar.eslint.config.mjs +44 -0
  289. package/scripts/preflight.sh +21 -94
  290. package/scripts/protected-state-check.js +104 -0
  291. package/scripts/smart-status.sh +60 -57
  292. package/scripts/spikes/config-race-bench.js +111 -0
  293. package/scripts/spikes/harness-capability-matrix.js +13 -0
  294. package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
  295. package/scripts/spikes/protected-path-manifest.js +20 -0
  296. package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
  297. package/scripts/sync-agent-skills.js +62 -0
  298. package/scripts/sync-utils.sh +3 -0
  299. package/scripts/test-ci-shard.js +13 -6
  300. package/scripts/test.js +95 -12
  301. package/skills/claim-safety/SKILL.md +102 -0
  302. package/skills/claim-safety/evals/evals.json +46 -0
  303. package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +44 -50
  304. package/skills/dev/evals/evals.json +50 -0
  305. package/skills/hermes-forge/SKILL.md +185 -0
  306. package/skills/hermes-forge/evals/evals.json +46 -0
  307. package/skills/issue-basics/SKILL.md +111 -0
  308. package/skills/issue-basics/evals/evals.json +46 -0
  309. package/skills/kernel/SKILL.md +166 -0
  310. package/skills/kernel/evals/evals.json +50 -0
  311. package/skills/memory/SKILL.md +102 -0
  312. package/skills/parallel-deep-research/SKILL.md +14 -11
  313. package/skills/parallel-deep-research/evals/evals.json +11 -27
  314. package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +132 -157
  315. package/skills/plan/evals/evals.json +42 -0
  316. package/skills/research/SKILL.md +195 -0
  317. package/skills/research/evals/evals.json +42 -0
  318. package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
  319. package/skills/review/evals/evals.json +42 -0
  320. package/skills/rollback/SKILL.md +110 -0
  321. package/skills/rollback/evals/evals.json +46 -0
  322. package/skills/rollback/references/methods.md +204 -0
  323. package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
  324. package/skills/shepherd/SKILL.md +66 -0
  325. package/skills/shepherd/evals/evals.json +42 -0
  326. package/{.github/prompts/ship.prompt.md → skills/ship/SKILL.md} +81 -45
  327. package/skills/ship/evals/evals.json +42 -0
  328. package/skills/smith/SKILL.md +142 -0
  329. package/skills/smith/evals/evals.json +46 -0
  330. package/skills/smith/references/autonomy-and-gates.md +94 -0
  331. package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
  332. package/skills/sonarcloud/evals/evals.json +46 -0
  333. package/skills/sonarcloud-analysis/SKILL.md +18 -13
  334. package/skills/sonarcloud-analysis/evals/evals.json +11 -15
  335. package/{.github/prompts/status.prompt.md → skills/status/SKILL.md} +20 -10
  336. package/skills/status/evals/evals.json +50 -0
  337. package/skills/triage-ready/SKILL.md +121 -0
  338. package/skills/triage-ready/evals/evals.json +42 -0
  339. package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
  340. package/skills/validate/evals/evals.json +42 -0
  341. package/skills/verify/SKILL.md +299 -0
  342. package/skills/verify/evals/evals.json +50 -0
  343. package/.claude/commands/dev.md +0 -345
  344. package/.claude/commands/plan.md +0 -566
  345. package/.claude/commands/premerge.md +0 -186
  346. package/.claude/commands/research.md +0 -42
  347. package/.claude/commands/review.md +0 -451
  348. package/.claude/commands/rollback.md +0 -721
  349. package/.claude/commands/ship.md +0 -213
  350. package/.claude/commands/sonarcloud.md +0 -152
  351. package/.claude/commands/status.md +0 -90
  352. package/.claude/commands/validate.md +0 -288
  353. package/.claude/commands/verify.md +0 -269
  354. package/.claude/rules/workflow.md +0 -121
  355. package/.cline/workflows/dev.md +0 -342
  356. package/.cline/workflows/plan.md +0 -563
  357. package/.cline/workflows/premerge.md +0 -183
  358. package/.cline/workflows/research.md +0 -39
  359. package/.cline/workflows/review.md +0 -448
  360. package/.cline/workflows/rollback.md +0 -718
  361. package/.cline/workflows/ship.md +0 -210
  362. package/.cline/workflows/sonarcloud.md +0 -146
  363. package/.cline/workflows/status.md +0 -87
  364. package/.cline/workflows/validate.md +0 -285
  365. package/.cline/workflows/verify.md +0 -266
  366. package/.codex/config.toml +0 -11
  367. package/.codex/skills/dev/SKILL.md +0 -345
  368. package/.codex/skills/plan/SKILL.md +0 -566
  369. package/.codex/skills/premerge/SKILL.md +0 -186
  370. package/.codex/skills/research/SKILL.md +0 -42
  371. package/.codex/skills/review/SKILL.md +0 -451
  372. package/.codex/skills/rollback/SKILL.md +0 -721
  373. package/.codex/skills/ship/SKILL.md +0 -213
  374. package/.codex/skills/sonarcloud/SKILL.md +0 -149
  375. package/.codex/skills/status/SKILL.md +0 -90
  376. package/.codex/skills/validate/SKILL.md +0 -288
  377. package/.codex/skills/verify/SKILL.md +0 -269
  378. package/.cursor/commands/dev.md +0 -342
  379. package/.cursor/commands/plan.md +0 -563
  380. package/.cursor/commands/premerge.md +0 -183
  381. package/.cursor/commands/research.md +0 -39
  382. package/.cursor/commands/review.md +0 -448
  383. package/.cursor/commands/ship.md +0 -210
  384. package/.cursor/commands/sonarcloud.md +0 -146
  385. package/.cursor/commands/status.md +0 -87
  386. package/.cursor/commands/validate.md +0 -285
  387. package/.cursor/commands/verify.md +0 -266
  388. package/.cursorrules +0 -149
  389. package/.github/prompts/premerge.prompt.md +0 -188
  390. package/.github/prompts/research.prompt.md +0 -44
  391. package/.github/prompts/rollback.prompt.md +0 -723
  392. package/.github/prompts/verify.prompt.md +0 -271
  393. package/.github/workflows/beads-to-github.yml +0 -89
  394. package/.github/workflows/github-to-beads.yml +0 -100
  395. package/.kilocode/workflows/dev.md +0 -346
  396. package/.kilocode/workflows/plan.md +0 -567
  397. package/.kilocode/workflows/premerge.md +0 -187
  398. package/.kilocode/workflows/research.md +0 -43
  399. package/.kilocode/workflows/review.md +0 -452
  400. package/.kilocode/workflows/rollback.md +0 -722
  401. package/.kilocode/workflows/ship.md +0 -214
  402. package/.kilocode/workflows/sonarcloud.md +0 -150
  403. package/.kilocode/workflows/status.md +0 -91
  404. package/.kilocode/workflows/validate.md +0 -289
  405. package/.kilocode/workflows/verify.md +0 -270
  406. package/.opencode/commands/dev.md +0 -345
  407. package/.opencode/commands/plan.md +0 -566
  408. package/.opencode/commands/premerge.md +0 -186
  409. package/.opencode/commands/research.md +0 -42
  410. package/.opencode/commands/review.md +0 -451
  411. package/.opencode/commands/rollback.md +0 -721
  412. package/.opencode/commands/ship.md +0 -213
  413. package/.opencode/commands/sonarcloud.md +0 -149
  414. package/.opencode/commands/status.md +0 -90
  415. package/.opencode/commands/validate.md +0 -288
  416. package/.opencode/commands/verify.md +0 -269
  417. package/.roo/commands/dev.md +0 -346
  418. package/.roo/commands/plan.md +0 -567
  419. package/.roo/commands/premerge.md +0 -187
  420. package/.roo/commands/research.md +0 -43
  421. package/.roo/commands/review.md +0 -452
  422. package/.roo/commands/rollback.md +0 -722
  423. package/.roo/commands/ship.md +0 -214
  424. package/.roo/commands/sonarcloud.md +0 -150
  425. package/.roo/commands/status.md +0 -91
  426. package/.roo/commands/validate.md +0 -289
  427. package/.roo/commands/verify.md +0 -270
  428. package/docs/BEADS_GITHUB_SYNC.md +0 -281
  429. package/docs/GREPTILE_SETUP.md +0 -400
  430. package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
  431. package/docs/SETUP.md +0 -663
  432. package/docs/VALIDATION.md +0 -363
  433. package/lib/agents/cline.plugin.json +0 -29
  434. package/lib/agents/copilot.plugin.json +0 -24
  435. package/lib/agents/kilocode.plugin.json +0 -22
  436. package/lib/agents/opencode.plugin.json +0 -23
  437. package/lib/agents/roo.plugin.json +0 -30
  438. package/lib/beads-bootstrap.js +0 -225
  439. package/lib/beads-health-check.js +0 -188
  440. package/lib/commands/commands-reset.js +0 -147
  441. package/opencode.json +0 -67
  442. package/scripts/beads-context.test.js +0 -584
  443. package/scripts/github-beads-sync/comment.mjs +0 -64
  444. package/scripts/github-beads-sync/config.mjs +0 -148
  445. package/scripts/github-beads-sync/github-api.mjs +0 -131
  446. package/scripts/github-beads-sync/index.mjs +0 -356
  447. package/scripts/github-beads-sync/label-mapper.mjs +0 -54
  448. package/scripts/github-beads-sync/mapping.mjs +0 -132
  449. package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
  450. package/scripts/github-beads-sync/reverse-sync.mjs +0 -162
  451. package/scripts/github-beads-sync/run-bd.mjs +0 -161
  452. package/scripts/github-beads-sync/sanitize.mjs +0 -121
  453. package/scripts/github-beads-sync.config.json +0 -26
  454. package/scripts/sync-commands.js +0 -600
@@ -11,6 +11,7 @@
11
11
  const { execFileSync } = require('node:child_process');
12
12
  const fs = require('node:fs');
13
13
  const path = require('node:path');
14
+ const { secureExecFileSync } = require('../shell-utils');
14
15
 
15
16
  // Constants
16
17
  const CHECK_TYPES = {
@@ -31,15 +32,11 @@ const CONFLICT_MARKER_IGNORE_DIRS = new Set([
31
32
  ]);
32
33
  const CONFLICT_MARKER_ALLOWED_DOT_DIRS = new Set([
33
34
  '.claude',
34
- '.cline',
35
35
  '.codex',
36
36
  '.cursor',
37
37
  '.forge',
38
38
  '.github',
39
39
  '.husky',
40
- '.kilocode',
41
- '.opencode',
42
- '.roo',
43
40
  '.sonarlint',
44
41
  ]);
45
42
  const CONFLICT_START_PATTERN = /^<<<<<<<(?: .*)?$/;
@@ -466,7 +463,7 @@ async function runSecurityScan() { // NOSONAR S3776
466
463
  try {
467
464
  let npmRawOutput = null;
468
465
  try {
469
- npmRawOutput = execFileSync('npm', ['audit', '--json', '--production'], getExecOptions()); // NOSONAR S4036 - hardcoded CLI command, no user input, developer tool context
466
+ npmRawOutput = secureExecFileSync('npm', ['audit', '--json', '--production'], getExecOptions()); // NOSONAR S4036 - hardcoded CLI command, no user input, developer tool context
470
467
  } catch (npmExitError) {
471
468
  if (npmExitError.killed && npmExitError.signal === 'SIGTERM') {
472
469
  return {
@@ -566,23 +563,46 @@ async function runSecurityScan() { // NOSONAR S3776
566
563
  * const result = await runAllTests();
567
564
  * console.log(`${result.passed}/${result.total} tests passed`);
568
565
  */
569
- async function runAllTests() {
566
+ function parseTestCounts(output) {
567
+ const text = String(output || '');
568
+ const passed = parseNumber(/(\d+) pass/.exec(text)); // NOSONAR S5852 - bounded \d+ pattern
569
+ const failed = parseNumber(/(\d+) fail/.exec(text)); // NOSONAR S5852 - bounded \d+ pattern
570
+ const skipped = parseNumber(/(\d+) skip/.exec(text)); // NOSONAR S5852 - bounded \d+ pattern
571
+ const ranMatch = /Ran (\d+) tests/.exec(text); // NOSONAR S5852 - bounded \d+ pattern
572
+ const total = ranMatch ? parseNumber(ranMatch, 1) : passed + failed + skipped;
573
+ return { passed, failed, skipped, total, testsFound: total > 0 };
574
+ }
575
+
576
+ async function runAllTests(exec = execFileSync) {
570
577
  const startTime = Date.now();
571
578
 
572
579
  try {
573
- const result = execFileSync('bun', ['test', '--timeout', '30000'], getExecOptions()); // NOSONAR S4036 - hardcoded CLI command, no user input, developer tool context
580
+ const result = exec('bun', ['test', '--timeout', '30000'], getExecOptions()); // NOSONAR S4036 - hardcoded CLI command, no user input, developer tool context
581
+
582
+ const { passed, failed, skipped, total, testsFound } = parseTestCounts(result);
574
583
 
575
- // Parse bun test output
576
- const passed = parseNumber(/(\d+) pass/.exec(result)); // NOSONAR S5852 - bounded \d+ pattern
577
- const failed = parseNumber(/(\d+) fail/.exec(result)); // NOSONAR S5852 - bounded \d+ pattern
578
- const skipped = parseNumber(/(\d+) skip/.exec(result)); // NOSONAR S5852 - bounded \d+ pattern
579
- const total = parseNumber(/Ran (\d+) tests/.exec(result), 1, passed + failed + skipped);
584
+ // B2 (N1): a bun run that executes 0 tests must NEVER report PASS — that
585
+ // manufactures false confidence. Surface it as an explicit SKIP instead.
586
+ if (!testsFound) {
587
+ return {
588
+ success: true,
589
+ skipped: true,
590
+ testsFound: false,
591
+ duration: Date.now() - startTime,
592
+ passed,
593
+ failed,
594
+ total,
595
+ message: 'No tests found — 0 tests executed (SKIP, not a pass). Add tests or check the runner config.',
596
+ };
597
+ }
580
598
 
581
599
  return {
582
600
  success: failed === 0,
601
+ testsFound: true,
583
602
  duration: Date.now() - startTime,
584
603
  passed,
585
604
  failed,
605
+ skipped,
586
606
  total,
587
607
  message: failed === 0
588
608
  ? `All ${total} tests passed`
@@ -601,28 +621,32 @@ async function runAllTests() {
601
621
  };
602
622
  }
603
623
 
604
- // Test execution failed
624
+ // Test runner absent — say so explicitly (do NOT silently pass).
605
625
  if (isCommandNotFound(error)) {
606
626
  return {
607
627
  success: true,
608
628
  skipped: true,
629
+ testsFound: false,
609
630
  duration: Date.now() - startTime,
610
631
  passed: 0,
611
632
  failed: 0,
612
633
  total: 0,
613
- message: 'Tests skipped: bun not found.',
634
+ message: 'Tests skipped: bun test runner not found. Install bun to run tests.',
614
635
  };
615
636
  }
616
637
 
617
638
  // Parse test failures from output
618
639
  const output = error.stdout || error.message;
619
- const passed = parseNumber(/(\d+) pass/.exec(output)); // NOSONAR S5852 - bounded \d+ pattern
620
- const failed = parseNumber(/(\d+) fail/.exec(output), 1, 1); // NOSONAR S5852 - bounded \d+ pattern
621
- const skipped = parseNumber(/(\d+) skip/.exec(output)); // NOSONAR S5852 - bounded \d+ pattern
622
- const total = passed + failed + skipped;
640
+ const counts = parseTestCounts(output);
641
+ const passed = counts.passed;
642
+ // bun exited non-zero: if the output shows no explicit failure count, treat
643
+ // it as at least one failure rather than swallowing the non-zero exit.
644
+ const failed = counts.failed > 0 ? counts.failed : Math.max(1, counts.failed);
645
+ const total = counts.total > 0 ? counts.total : passed + failed + counts.skipped;
623
646
 
624
647
  return {
625
648
  success: false,
649
+ testsFound: counts.testsFound,
626
650
  duration: Date.now() - startTime,
627
651
  passed,
628
652
  failed,
@@ -832,6 +856,7 @@ module.exports = {
832
856
  runLint,
833
857
  runSecurityScan,
834
858
  runAllTests,
859
+ parseTestCounts,
835
860
  scanForConflictMarkers,
836
861
  executeValidate,
837
862
  executeDebugMode,
@@ -3,15 +3,18 @@
3
3
  const { execFileSync, spawnSync } = require('node:child_process');
4
4
  const fs = require('node:fs');
5
5
  const path = require('node:path');
6
- const { bootstrapBeads } = require('../beads-bootstrap');
7
6
 
8
7
  /**
9
8
  * Forge Worktree Command
10
- * Manage isolated worktrees with Beads integration.
9
+ * Manage isolated worktrees.
11
10
  * Uses execFileSync (not execSync) to prevent command injection (OWASP A03).
12
11
  *
12
+ * The Kernel issue store lives in the git common dir, which every worktree of a
13
+ * repo shares, so no per-worktree issue-store bootstrap is needed — a new
14
+ * worktree already sees the same kernel.
15
+ *
13
16
  * Subcommands:
14
- * create <slug> - Create worktree at .worktrees/<slug> with branch + beads + install
17
+ * create <slug> - Create worktree at .worktrees/<slug> with branch + install
15
18
  * remove <slug> - Remove worktree via git worktree remove
16
19
  *
17
20
  * @module commands/worktree
@@ -24,7 +27,10 @@ const { bootstrapBeads } = require('../beads-bootstrap');
24
27
  * @returns {string|null} Package manager command or null
25
28
  */
26
29
  function detectPackageManager(projectRoot, fsApi) {
30
+ // Bun ships two lockfile formats: the legacy binary `bun.lockb` and the current
31
+ // text `bun.lock`. Recognize both so a Bun repo isn't mis-detected as npm.
27
32
  if (fsApi.existsSync(path.join(projectRoot, 'bun.lockb'))) return 'bun';
33
+ if (fsApi.existsSync(path.join(projectRoot, 'bun.lock'))) return 'bun';
28
34
  if (fsApi.existsSync(path.join(projectRoot, 'pnpm-lock.yaml'))) return 'pnpm';
29
35
  if (fsApi.existsSync(path.join(projectRoot, 'yarn.lock'))) return 'yarn';
30
36
  if (fsApi.existsSync(path.join(projectRoot, 'package-lock.json'))) return 'npm';
@@ -32,51 +38,6 @@ function detectPackageManager(projectRoot, fsApi) {
32
38
  return null;
33
39
  }
34
40
 
35
- /**
36
- * Stop a Dolt server running in a worktree to release file locks.
37
- * Tries graceful `bd dolt stop` first, then falls back to PID-based kill
38
- * from lock files. Never kills ALL Dolt processes - only the specific PID.
39
- *
40
- * @param {string} worktreePath - Absolute path to the worktree
41
- * @param {object} [opts] - Options for dependency injection
42
- * @param {Function} [opts._exec] - Override for execFileSync (testing)
43
- * @param {object} [opts._fs] - Override for fs module (testing)
44
- * @param {string} [opts._platform] - Override for process.platform (testing)
45
- * @returns {{ stopped: boolean, method: string, pid?: number }}
46
- */
47
- function stopDolt(worktreePath, opts = {}) {
48
- const exec = opts._exec || execFileSync;
49
- const _fs = opts._fs || fs;
50
- const platform = opts._platform || process.platform;
51
-
52
- // 1. Try graceful: bd dolt stop
53
- try {
54
- exec('bd', ['dolt', 'stop'], { cwd: worktreePath, timeout: 5000, stdio: 'pipe' });
55
- return { stopped: true, method: 'bd-dolt-stop' };
56
- } catch (_e) { /* intentional: bd not available, try PID-based kill */ } // NOSONAR S2486
57
-
58
- // 2. Try PID-based kill from lock file
59
- const lockPaths = [
60
- path.join(worktreePath, '.beads', 'dolt-server.lock'),
61
- path.join(worktreePath, '.beads', 'daemon.lock'),
62
- ];
63
- for (const lockPath of lockPaths) {
64
- try {
65
- const pid = Number.parseInt(_fs.readFileSync(lockPath, 'utf-8').trim(), 10);
66
- if (!Number.isNaN(pid)) {
67
- if (platform === 'win32') {
68
- exec('taskkill', ['/F', '/PID', String(pid)], { stdio: 'pipe' });
69
- } else {
70
- process.kill(pid, 'SIGTERM');
71
- }
72
- return { stopped: true, method: 'pid-kill', pid };
73
- }
74
- } catch (_e) { /* intentional: try next lock file */ } // NOSONAR S2486
75
- }
76
-
77
- return { stopped: false, method: 'none' };
78
- }
79
-
80
41
  /**
81
42
  * Check if a git branch already exists.
82
43
  * @param {string} branchName - Branch name to check
@@ -92,42 +53,190 @@ function branchExists(branchName, runFile) {
92
53
  }
93
54
  }
94
55
 
95
- function setupBeadsWithBootstrap(worktreePath, projectRoot, platform, fsApi, exec, options = {}) {
96
- if (!fsApi.existsSync(path.resolve(projectRoot, '.beads'))) {
97
- return 'Beads not installed - skipping .beads setup';
98
- }
99
-
100
- const bootstrapResult = bootstrapBeads(worktreePath, {
101
- _exec: exec,
102
- _fs: fsApi,
103
- _platform: platform,
104
- mainProjectRoot: projectRoot,
105
- _safeBeadsInit: options._safeBeadsInit,
106
- });
107
-
108
- try {
109
- exec('bd', ['--version'], { cwd: worktreePath, timeout: 5000, stdio: 'pipe' });
110
- } catch (_bdErr) { /* intentional: bd may not be on PATH */ // NOSONAR S2486
111
- if (bootstrapResult.success === false && bootstrapResult.warning) {
112
- return bootstrapResult.warning;
113
- }
114
- return 'Beads verification failed - .beads linked but bd may not work';
115
- }
116
-
117
- return bootstrapResult.warning;
56
+ /**
57
+ * True when a link failure is a privilege/support problem (Windows without the
58
+ * symlink privilege, restricted FS) rather than a real error. In that case we
59
+ * degrade to a package install instead of hard-failing.
60
+ * @param {Error} error
61
+ * @returns {boolean}
62
+ */
63
+ function isLinkPermissionError(error) {
64
+ return ['EPERM', 'EACCES', 'ENOSYS', 'UV_EPERM'].includes(error && error.code);
118
65
  }
119
66
 
120
67
  /**
121
- * Run package install in the new worktree if a package manager is detected.
68
+ * Run package install in the new worktree if a package manager is detected, and
69
+ * SURFACE failures (spawn error or non-zero exit) instead of swallowing them.
122
70
  * @param {string} worktreePath - Absolute path to the new worktree
123
71
  * @param {string} projectRoot - Absolute path to the project root
124
72
  * @param {Function} spawnFn - spawnSync-compatible function
125
73
  * @param {object} fsApi - fs module (for DI)
74
+ * @returns {{ linked: boolean, installed: boolean }}
75
+ * @throws {Error} when the install cannot be spawned or exits non-zero.
126
76
  */
127
77
  function runInstall(worktreePath, projectRoot, spawnFn, fsApi) {
128
78
  const pkgManager = detectPackageManager(projectRoot, fsApi);
129
- if (pkgManager) {
130
- spawnFn(pkgManager, ['install'], { cwd: worktreePath, stdio: 'pipe' });
79
+ if (!pkgManager) return { linked: false, installed: false };
80
+
81
+ // shell:true on Windows: npm/pnpm/yarn are .cmd shims that cannot be spawned
82
+ // directly (ENOENT / EINVAL). pkgManager comes from lockfile detection (fixed
83
+ // set) and args are hardcoded, so no user input reaches the shell.
84
+ const result = spawnFn(pkgManager, ['install'], {
85
+ cwd: worktreePath,
86
+ stdio: 'pipe',
87
+ shell: process.platform === 'win32',
88
+ });
89
+ if (result && result.error) {
90
+ throw new Error(`Dependency install failed: could not run '${pkgManager} install' in ${worktreePath}: ${result.error.message}`);
91
+ }
92
+ if (result && typeof result.status === 'number' && result.status !== 0) {
93
+ throw new Error(`Dependency install failed: '${pkgManager} install' exited with code ${result.status} in ${worktreePath}`);
94
+ }
95
+ return { linked: false, installed: true };
96
+ }
97
+
98
+ /**
99
+ * Populate the new worktree's node_modules. Fast path: link to the main repo's
100
+ * shared install (a junction on Windows, a directory symlink on POSIX) so a fresh
101
+ * worktree is immediately usable without a full reinstall — this repo's own
102
+ * worktrees follow that pattern. Fallback: run the detected package manager's
103
+ * install when there is no shared install to link. Both paths surface failures.
104
+ * @param {string} worktreePath - Absolute path to the new worktree
105
+ * @param {string} projectRoot - Absolute path to the project root
106
+ * @param {object} deps
107
+ * @param {Function} deps.spawnFn - spawnSync-compatible function
108
+ * @param {object} deps.fsApi - fs module (for DI)
109
+ * @param {string} deps.platform - process.platform value
110
+ * @returns {{ linked: boolean, installed: boolean }}
111
+ * @throws {Error} when linking fails for a non-privilege reason or install fails.
112
+ */
113
+ function setupWorktreeDeps(worktreePath, projectRoot, { spawnFn, fsApi, platform }) {
114
+ const srcModules = path.join(projectRoot, 'node_modules');
115
+ const destModules = path.join(worktreePath, 'node_modules');
116
+
117
+ // Already populated (e.g. git checkout carried it) — nothing to do.
118
+ if (fsApi.existsSync(destModules)) return { linked: false, installed: false };
119
+
120
+ // Fast path: link to the shared install when the main repo has one.
121
+ if (fsApi.existsSync(srcModules)) {
122
+ const linkType = platform === 'win32' ? 'junction' : 'dir';
123
+ try {
124
+ fsApi.symlinkSync(srcModules, destModules, linkType);
125
+ return { linked: true, installed: false };
126
+ } catch (error) {
127
+ // Degrade to a real install only when the link was refused for lack of
128
+ // privilege/support; any other failure is surfaced to the caller.
129
+ if (!isLinkPermissionError(error)) throw error;
130
+ }
131
+ }
132
+
133
+ return runInstall(worktreePath, projectRoot, spawnFn, fsApi);
134
+ }
135
+
136
+ /**
137
+ * Best-effort: record the issue → worktree → work-folder linkage in the Kernel so
138
+ * `forge worktree list` and orientation READ it instead of guessing the work-folder by
139
+ * a filesystem heuristic. Writes a `kernel_worktrees` row keyed by the absolute worktree
140
+ * path (idempotent upsert) and, when an issue + work-folder are given, drops a
141
+ * machine-readable `.forge-issue` marker so a folder resolves to its issue deterministically.
142
+ *
143
+ * Never throws: a repo without a reachable Kernel (or without the 007 linkage columns)
144
+ * must still get a usable worktree — orientation falls back to the folder heuristic.
145
+ *
146
+ * @param {object} params
147
+ * @param {string} params.projectRoot
148
+ * @param {string} params.worktreePath - Absolute worktree path (the row key).
149
+ * @param {string} params.branch
150
+ * @param {string|null} params.issueId
151
+ * @param {string|null} params.workFolder - Repo-relative work-folder path.
152
+ * @param {object} params.opts - DI options (may inject `_kernelDriver`, `_exec`, `_fs`).
153
+ * @returns {Promise<{registered: boolean, issueId: string|null, workFolder: string|null, reason?: string}>}
154
+ */
155
+ async function registerWorktreeLinkage({ projectRoot, worktreePath, branch, issueId, workFolder, opts }) {
156
+ const runFile = opts._exec || execFileSync;
157
+ const fsApi = opts._fs || fs;
158
+ try {
159
+ // Drop the folder → issue marker first — deterministic even if the DB write fails.
160
+ if (workFolder && issueId) {
161
+ const folderAbs = path.resolve(projectRoot, workFolder);
162
+ fsApi.mkdirSync(folderAbs, { recursive: true });
163
+ fsApi.writeFileSync(path.join(folderAbs, '.forge-issue'), `${issueId}\n`, 'utf8');
164
+ }
165
+
166
+ let driver = opts._kernelDriver;
167
+ if (!driver) {
168
+ // No reachable git repo → no shared Kernel; skip quietly (orientation still works
169
+ // off the folder heuristic). Avoids spawning `git` in a non-repo directory.
170
+ if (!fsApi.existsSync(path.join(projectRoot, '.git'))) {
171
+ return { registered: false, issueId: issueId || null, workFolder: workFolder || null, reason: 'no git repository' };
172
+ }
173
+ // Lazy require: keep the worktree command light and avoid paying kernel setup
174
+ // when linkage is not needed. Migrated deps guarantee the 007 columns exist.
175
+ const { buildMigratedKernelIssueDeps } = require('../kernel/cli-broker-factory');
176
+ driver = (await buildMigratedKernelIssueDeps({ projectRoot })).kernelDriver;
177
+ }
178
+
179
+ const { resolveGitCommonDir } = require('../kernel/broker');
180
+ const gitCommonDir = resolveGitCommonDir(projectRoot, { execFileSync: runFile });
181
+
182
+ driver.registerWorktree({
183
+ git_common_dir: gitCommonDir,
184
+ path: worktreePath,
185
+ branch,
186
+ actor: process.env.FORGE_ACTOR || null,
187
+ issue_id: issueId || null,
188
+ work_folder: workFolder || null,
189
+ registered_at: new Date().toISOString(),
190
+ state: 'active',
191
+ });
192
+
193
+ return { registered: true, issueId: issueId || null, workFolder: workFolder || null };
194
+ } catch (error) {
195
+ // Non-fatal: keep the worktree usable. Only warn when the caller explicitly asked
196
+ // for linkage (--issue/--work-folder); a plain `worktree create` stays quiet.
197
+ if (issueId || workFolder) {
198
+ process.stderr.write(`forge worktree: kernel linkage not recorded (${error.message})\n`);
199
+ }
200
+ return { registered: false, issueId: issueId || null, workFolder: workFolder || null, reason: error.message };
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Auto-file rail: guarantee the new worktree's branch has a backing Kernel issue,
206
+ * WITHOUT anyone remembering `forge issue create` (kernel issue a4b8f56f). Best-effort
207
+ * and NON-BLOCKING — the worktree must stay usable even if tracking fails, so any error
208
+ * degrades to a stderr warning and null. Skipped when `--issue` already links a real
209
+ * issue. Idempotent via ensureBackingIssue (deduped by branch), so re-creating a
210
+ * worktree or later `forge push`/pre-push firing never mints a duplicate.
211
+ *
212
+ * @param {object} params
213
+ * @param {string} params.projectRoot
214
+ * @param {string} params.worktreePath
215
+ * @param {string} params.branch
216
+ * @param {string|null} params.issueId - explicit --issue (skip auto-file when set).
217
+ * @param {object} params.opts - DI (may inject `_ensureBackingIssue`, `_kernelDriver`, `_kernelBroker`, `_fs`).
218
+ * @returns {Promise<object|null>} the backing-issue descriptor, or null when skipped/unavailable.
219
+ */
220
+ async function autoFileBackingIssue({ projectRoot, worktreePath, branch, issueId, opts }) {
221
+ if (issueId) return null; // explicit --issue already links a real issue.
222
+ const fsApi = opts._fs || fs;
223
+ try {
224
+ // Lazy require INSIDE the try: a module-load failure (missing file / broken
225
+ // sub-dependency) must degrade to null, never crash `forge worktree create`.
226
+ const ensureFn = opts._ensureBackingIssue || require('../kernel/backing-issue').ensureBackingIssue;
227
+ let driver = opts._kernelDriver;
228
+ let broker = opts._kernelBroker;
229
+ if (!driver || !broker) {
230
+ if (!fsApi.existsSync(path.join(projectRoot, '.git'))) return null; // no repo → no kernel
231
+ const { buildMigratedKernelIssueDeps } = require('../kernel/cli-broker-factory');
232
+ const deps = await buildMigratedKernelIssueDeps({ projectRoot });
233
+ driver = driver || deps.kernelDriver;
234
+ broker = broker || deps.kernelBroker;
235
+ }
236
+ return await ensureFn({ branch, projectRoot, worktreePath, driver, broker });
237
+ } catch (error) {
238
+ process.stderr.write(`forge worktree: backing issue not auto-filed (${error.message})\n`);
239
+ return null;
131
240
  }
132
241
  }
133
242
 
@@ -143,9 +252,10 @@ async function handleCreate(slug, flags, projectRoot, opts) {
143
252
  const runFile = opts._exec || execFileSync;
144
253
  const runSpawn = opts._spawn || spawnSync;
145
254
  const fsApi = opts._fs || fs;
146
- const platform = opts._platform || process.platform;
147
255
 
148
256
  const branchName = flags['--branch'] || flags.branch || `feat/${slug}`;
257
+ const issueId = flags['--issue'] || null;
258
+ const workFolder = flags['--work-folder'] || null;
149
259
  const worktreesDir = path.resolve(projectRoot, '.worktrees');
150
260
 
151
261
  // Guard: Detect bare repo state - worktrees created from bare repos produce broken branches
@@ -170,11 +280,23 @@ async function handleCreate(slug, flags, projectRoot, opts) {
170
280
 
171
281
  // Step 0: Check if worktree already exists
172
282
  if (fsApi.existsSync(worktreePath)) {
283
+ // A pre-existing worktree may be checked out on a different branch than the
284
+ // requested branchName — read the actual HEAD so the kernel row is accurate.
285
+ let existingBranch = branchName;
286
+ try {
287
+ const head = runFile('git', ['-C', worktreePath, 'rev-parse', '--abbrev-ref', 'HEAD'], { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
288
+ // A detached worktree reports the literal "HEAD" — don't persist that; keep branchName.
289
+ if (head && head !== 'HEAD') existingBranch = head;
290
+ } catch (_error) { /* not a resolvable worktree HEAD — fall back to branchName */ }
291
+ const linkage = await registerWorktreeLinkage({ projectRoot, worktreePath, branch: existingBranch, issueId, workFolder, opts });
292
+ const backing = await autoFileBackingIssue({ projectRoot, worktreePath, branch: existingBranch, issueId, opts });
173
293
  return {
174
294
  success: true,
175
295
  reused: true,
176
296
  message: `Worktree already exists at ${worktreePath}`,
177
297
  worktreePath,
298
+ linkage,
299
+ backing,
178
300
  };
179
301
  }
180
302
 
@@ -189,21 +311,52 @@ async function handleCreate(slug, flags, projectRoot, opts) {
189
311
  runFile('git', ['worktree', 'add', worktreePath, '-b', branchName], { stdio: 'pipe' });
190
312
  }
191
313
 
192
- // Step 3: Beads integration
193
- const beadsWarning = setupBeadsWithBootstrap(worktreePath, projectRoot, platform, fsApi, runFile, opts);
314
+ // Step 3: Populate node_modules (link to the shared install, else install).
315
+ // No per-worktree issue-store bootstrap is needed the Kernel DB lives in the
316
+ // shared git common dir, so the new worktree already sees the same kernel.
317
+ let deps;
318
+ try {
319
+ deps = setupWorktreeDeps(worktreePath, projectRoot, {
320
+ spawnFn: runSpawn,
321
+ fsApi,
322
+ platform: opts._platform || process.platform,
323
+ });
324
+ } catch (error) {
325
+ // Surface the failure instead of leaving a worktree with no usable deps.
326
+ return { success: false, error: error.message, worktreePath, branch: branchName };
327
+ }
194
328
 
195
- // Step 4: Run package install
196
- runInstall(worktreePath, projectRoot, runSpawn, fsApi);
329
+ const linkage = await registerWorktreeLinkage({ projectRoot, worktreePath, branch: branchName, issueId, workFolder, opts });
330
+ const backing = await autoFileBackingIssue({ projectRoot, worktreePath, branch: branchName, issueId, opts });
197
331
 
198
- const result = {
332
+ return {
199
333
  success: true,
200
334
  worktreePath,
201
335
  branch: branchName,
336
+ depsLinked: deps.linked,
337
+ depsInstalled: deps.installed,
338
+ linkage,
339
+ backing,
202
340
  };
203
- if (beadsWarning) {
204
- result.beadsWarning = beadsWarning;
341
+ }
342
+
343
+ /**
344
+ * Handle the "list" subcommand — read the worktree registry from the Kernel.
345
+ * @param {string} projectRoot - Project root path
346
+ * @param {object} opts - DI options (may inject `_kernelDriver`)
347
+ * @returns {Promise<object>} Result object with a `worktrees` array
348
+ */
349
+ async function handleList(projectRoot, opts) {
350
+ try {
351
+ let driver = opts._kernelDriver;
352
+ if (!driver) {
353
+ const { buildMigratedKernelIssueDeps } = require('../kernel/cli-broker-factory');
354
+ driver = (await buildMigratedKernelIssueDeps({ projectRoot })).kernelDriver;
355
+ }
356
+ return { success: true, worktrees: driver.listWorktrees({}) };
357
+ } catch (error) {
358
+ return { success: false, error: error.message, worktrees: [] };
205
359
  }
206
- return result;
207
360
  }
208
361
 
209
362
  /**
@@ -220,29 +373,65 @@ async function handleRemove(slug, projectRoot, opts) {
220
373
  }
221
374
 
222
375
  const runFile = opts._exec || execFileSync;
223
- const fsModule = opts._fs || fs;
224
376
  const worktreePath = path.resolve(projectRoot, '.worktrees', slug);
225
377
 
226
- // Stop Dolt server to release file locks
227
- const stopResult = stopDolt(worktreePath, { _exec: runFile, _fs: fsModule, _platform: opts._platform });
228
- if (stopResult.stopped) {
229
- // Brief wait for file locks to release (Windows needs this)
230
- await new Promise(r => setTimeout(r, 500));
231
- }
232
-
378
+ // No issue-store server to stop: the Kernel SQLite DB lives in the shared git
379
+ // common dir, so removing a worktree directory never touches it.
233
380
  runFile('git', ['worktree', 'remove', worktreePath], { stdio: 'pipe' });
234
381
 
235
382
  return { success: true, removed: worktreePath };
236
383
  }
237
384
 
385
+ // Long flags that take a value, in both `--flag value` and `--flag=value` forms.
386
+ const WORKTREE_VALUE_FLAGS = ['--branch', '--issue', '--work-folder'];
387
+ const WORKTREE_USAGE_HINT = 'Usage: forge worktree create <slug> [--branch <name>] [--issue <id>] [--work-folder <path>]';
388
+
389
+ function parseWorktreeArgs(args, flags) {
390
+ const positional = [];
391
+ const parsedFlags = { ...flags };
392
+
393
+ for (let i = 0; i < args.length; i++) {
394
+ const arg = args[i];
395
+
396
+ const spaceFlag = WORKTREE_VALUE_FLAGS.find(flag => arg === flag);
397
+ if (spaceFlag) {
398
+ const value = args[i + 1];
399
+ if (!value || value.startsWith('--')) {
400
+ return { error: `Missing value for ${spaceFlag}. ${WORKTREE_USAGE_HINT}` };
401
+ }
402
+ parsedFlags[spaceFlag] = value;
403
+ i++;
404
+ continue;
405
+ }
406
+
407
+ const eqFlag = WORKTREE_VALUE_FLAGS.find(flag => arg.startsWith(`${flag}=`));
408
+ if (eqFlag) {
409
+ const value = arg.slice(`${eqFlag}=`.length);
410
+ if (!value) {
411
+ return { error: `Missing value for ${eqFlag}. ${WORKTREE_USAGE_HINT}` };
412
+ }
413
+ parsedFlags[eqFlag] = value;
414
+ continue;
415
+ }
416
+
417
+ positional.push(arg);
418
+ }
419
+
420
+ // Back-compat alias consumed by handleCreate (flags['--branch'] || flags.branch).
421
+ if (parsedFlags['--branch']) parsedFlags.branch = parsedFlags['--branch'];
422
+
423
+ return { args: positional, flags: parsedFlags };
424
+ }
425
+
238
426
  module.exports = {
239
427
  name: 'worktree',
240
- description: 'Manage isolated worktrees with Beads integration',
241
- usage: 'forge worktree <create|remove> <slug>',
428
+ description: 'Manage isolated worktrees',
429
+ usage: 'forge worktree <create|remove|list> <slug>',
242
430
  flags: {
243
431
  '--branch': 'Custom branch name (default: feat/<slug>)',
432
+ '--issue': 'Kernel issue id to link this worktree to (records issue → worktree)',
433
+ '--work-folder': 'Repo-relative work-folder this issue owns (records worktree → work-folder + drops a .forge-issue marker)',
244
434
  },
245
- stopDolt,
246
435
 
247
436
  /**
248
437
  * Main handler for the worktree command.
@@ -257,27 +446,45 @@ module.exports = {
257
446
  * @returns {Promise<object>}
258
447
  */
259
448
  handler: async (args, flags, projectRoot, opts = {}) => {
260
- const subcommand = args[0];
261
- const slug = args[1];
449
+ const parsed = parseWorktreeArgs(args, flags);
450
+ if (parsed.error) {
451
+ return { success: false, error: parsed.error };
452
+ }
453
+
454
+ const subcommand = parsed.args[0];
262
455
 
263
- if (!subcommand || !['create', 'remove'].includes(subcommand)) {
456
+ if (!subcommand || !['create', 'remove', 'list'].includes(subcommand)) {
264
457
  return {
265
458
  success: false,
266
- error: 'Missing or invalid subcommand. Usage: forge worktree <create|remove> <slug>',
459
+ error: 'Missing or invalid subcommand. Usage: forge worktree <create|remove|list> <slug>',
267
460
  };
268
461
  }
269
462
 
463
+ // `list` reads the Kernel registry and takes no slug.
464
+ if (subcommand === 'list') {
465
+ return handleList(projectRoot, opts);
466
+ }
467
+
468
+ const slug = parsed.args[1];
270
469
  if (!slug) {
271
470
  return {
272
471
  success: false,
273
- error: 'Missing slug. Usage: forge worktree <create|remove> <slug>',
472
+ error: 'Missing slug. Usage: forge worktree <create|remove|list> <slug>',
274
473
  };
275
474
  }
276
475
 
277
476
  if (subcommand === 'create') {
278
- return handleCreate(slug, flags, projectRoot, opts);
477
+ return handleCreate(slug, parsed.flags, projectRoot, opts);
279
478
  }
280
479
 
281
480
  return handleRemove(slug, projectRoot, opts);
282
481
  },
482
+
483
+ // Exposed for unit tests; not part of the CLI surface.
484
+ _internal: {
485
+ detectPackageManager,
486
+ setupWorktreeDeps,
487
+ runInstall,
488
+ autoFileBackingIssue,
489
+ },
283
490
  };