forge-workflow 0.0.10 → 0.1.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (468) hide show
  1. package/.claude/rules/{greptile-review-process.md → review-process.md} +56 -41
  2. package/.claude/scripts/{greptile-resolve.sh → review-resolve.sh} +13 -3
  3. package/.cursor/rules/permissions-guidance.mdc +2 -2
  4. package/.forge/hooks/check-tdd.js +82 -5
  5. package/.forge/hooks/forge-native-hook.js +431 -0
  6. package/.forge/protected-paths.yaml +157 -0
  7. package/AGENTS.md +151 -61
  8. package/CHANGELOG.md +709 -0
  9. package/CLAUDE.md +9 -118
  10. package/QUICKSTART.md +175 -0
  11. package/README.md +275 -365
  12. package/bin/forge-cmd.js +120 -9
  13. package/bin/forge-preflight.js +26 -5
  14. package/bin/forge.js +532 -489
  15. package/docs/INDEX.md +93 -0
  16. package/docs/PROJECT_DESIGN.md +685 -0
  17. package/docs/architecture/index.md +66 -0
  18. package/docs/architecture/notes/README.md +35 -0
  19. package/docs/architecture/subsystems/README.md +46 -0
  20. package/docs/forge/TOOLCHAIN.md +670 -0
  21. package/docs/forge/VALIDATION.md +82 -0
  22. package/docs/{AGENT_INSTALL_PROMPT.md → guides/AGENT_INSTALL_PROMPT.md} +3 -3
  23. package/docs/guides/BEADS_GITHUB_SYNC.md +32 -0
  24. package/docs/{ENHANCED_ONBOARDING.md → guides/ENHANCED_ONBOARDING.md} +16 -12
  25. package/docs/guides/GREPTILE_SETUP.md +46 -0
  26. package/docs/guides/MANUAL_REVIEW_GUIDE.md +58 -0
  27. package/docs/guides/MIGRATION.md +56 -0
  28. package/docs/guides/SETUP.md +121 -0
  29. package/docs/guides/SUPPORT.md +190 -0
  30. package/docs/guides/WORKFLOW_TEMPLATES.md +74 -0
  31. package/docs/guides/memory-backends.md +183 -0
  32. package/docs/reference/ADAPTERS.md +128 -0
  33. package/docs/reference/AGENT_SKILL_PARITY.md +175 -0
  34. package/docs/reference/COMMANDS.md +214 -0
  35. package/docs/reference/DECISION_DRIFT_GUARDS.md +97 -0
  36. package/docs/{EXAMPLES.md → reference/EXAMPLES.md} +7 -5
  37. package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +135 -0
  38. package/docs/reference/HERMES_INTEGRATION.md +118 -0
  39. package/docs/reference/INSIGHTS_RECAP.md +63 -0
  40. package/docs/reference/INSTALL.md +164 -0
  41. package/docs/reference/KERNEL_TAXONOMY_VALIDATION.md +161 -0
  42. package/docs/reference/PROTECTED_PATH_MANIFEST.md +25 -0
  43. package/docs/reference/RELEASE.md +68 -0
  44. package/docs/reference/RESEARCH_TEMPLATE.md +292 -0
  45. package/docs/{ROADMAP.md → reference/ROADMAP.md} +12 -9
  46. package/docs/reference/SKILLS.md +35 -0
  47. package/docs/reference/STATUS_BOARD.md +80 -0
  48. package/docs/reference/TEMPLATES.md +106 -0
  49. package/docs/{TOOLCHAIN.md → reference/TOOLCHAIN.md} +62 -47
  50. package/docs/reference/VALIDATION.md +82 -0
  51. package/docs/reference/agent-permissions.md +169 -0
  52. package/docs/reference/beads-to-kernel-migration-ux.md +61 -0
  53. package/docs/reference/control-plane-guarantees.md +125 -0
  54. package/docs/reference/dependency-chain.md +331 -0
  55. package/docs/reference/forge-kernel-issue-command-contract.md +161 -0
  56. package/docs/reference/forge-kernel-schema.md +72 -0
  57. package/docs/reference/kernel-conflict-evaluators.md +27 -0
  58. package/docs/reference/patch-md-format.md +77 -0
  59. package/docs/reference/protected-state-surfaces.md +59 -0
  60. package/docs/reference/shepherd.md +155 -0
  61. package/docs/reference/superpowers-analysis.md +320 -0
  62. package/docs/reference/superpowers-integration-options.md +404 -0
  63. package/docs/reference/test-environment.md +519 -0
  64. package/docs/reference/upgrade-safety.md +59 -0
  65. package/lefthook.yml +18 -0
  66. package/lib/activation/ensure-forge-home.js +135 -0
  67. package/lib/adapter-cli.js +307 -0
  68. package/lib/adapters/beads-issue-adapter.js +127 -0
  69. package/lib/adapters/beads-kernel-compat.js +1109 -0
  70. package/lib/adapters/greptile-review-adapter.js +141 -0
  71. package/lib/adapters/kernel-issue-adapter.js +101 -0
  72. package/lib/adapters/pr-state-adapter.js +484 -0
  73. package/lib/adoption-profiles.js +139 -0
  74. package/lib/agents/README.md +2 -6
  75. package/lib/agents/claude.plugin.json +3 -8
  76. package/lib/agents/codex.plugin.json +9 -1
  77. package/lib/agents/cursor.plugin.json +2 -6
  78. package/lib/agents/hermes.plugin.json +22 -0
  79. package/lib/agents-config.js +39 -1236
  80. package/lib/audit-evidence.js +282 -0
  81. package/lib/beads-detect.js +60 -0
  82. package/lib/beads-nudge.js +91 -0
  83. package/lib/beads-setup.js +121 -0
  84. package/lib/beads-sync-scaffold.js +25 -101
  85. package/lib/codex-skills.js +51 -1
  86. package/lib/commands/_aliases.js +248 -0
  87. package/lib/commands/_issue.js +780 -77
  88. package/lib/commands/_manifest.js +93 -0
  89. package/lib/commands/_registry.js +99 -34
  90. package/lib/commands/_resolve-command-opts.js +230 -0
  91. package/lib/commands/_serve-security.js +270 -0
  92. package/lib/commands/adapter.js +12 -0
  93. package/lib/commands/add.js +118 -0
  94. package/lib/commands/audit.js +70 -0
  95. package/lib/commands/blocked.js +5 -0
  96. package/lib/commands/board.js +64 -0
  97. package/lib/commands/claim.js +21 -2
  98. package/lib/commands/claims.js +7 -0
  99. package/lib/commands/clean.js +485 -75
  100. package/lib/commands/close.js +2 -2
  101. package/lib/commands/comment.js +5 -0
  102. package/lib/commands/control.js +148 -0
  103. package/lib/commands/create.js +2 -2
  104. package/lib/commands/dev.js +185 -7
  105. package/lib/commands/doc-gate.js +336 -0
  106. package/lib/commands/doctor.js +156 -0
  107. package/lib/commands/explain.js +15 -0
  108. package/lib/commands/export.js +237 -0
  109. package/lib/commands/gate.js +209 -0
  110. package/lib/commands/hooks.js +377 -0
  111. package/lib/commands/inbox.js +118 -0
  112. package/lib/commands/init.js +604 -0
  113. package/lib/commands/insights.js +79 -0
  114. package/lib/commands/issue.js +12 -1
  115. package/lib/commands/issues.js +17 -0
  116. package/lib/commands/lint.js +5 -0
  117. package/lib/commands/list.js +2 -2
  118. package/lib/commands/memory.js +81 -0
  119. package/lib/commands/merge.js +312 -0
  120. package/lib/commands/migrate.js +362 -0
  121. package/lib/commands/new.js +12 -0
  122. package/lib/commands/options.js +241 -0
  123. package/lib/commands/orient.js +13 -0
  124. package/lib/commands/orphans.js +5 -0
  125. package/lib/commands/patch.js +67 -0
  126. package/lib/commands/plan.js +481 -29
  127. package/lib/commands/pr.js +88 -0
  128. package/lib/commands/preflight.js +211 -0
  129. package/lib/commands/prime.js +13 -0
  130. package/lib/commands/push.js +135 -2
  131. package/lib/commands/ready.js +2 -2
  132. package/lib/commands/recall.js +171 -0
  133. package/lib/commands/recap.js +75 -0
  134. package/lib/commands/recommend.js +0 -1
  135. package/lib/commands/release.js +104 -0
  136. package/lib/commands/remember.js +140 -0
  137. package/lib/commands/role.js +99 -0
  138. package/lib/commands/serve.js +581 -0
  139. package/lib/commands/setup.js +900 -971
  140. package/lib/commands/shepherd.js +501 -0
  141. package/lib/commands/ship.js +59 -1
  142. package/lib/commands/show.js +2 -2
  143. package/lib/commands/stage.js +192 -0
  144. package/lib/commands/stale.js +5 -0
  145. package/lib/commands/status.js +158 -21
  146. package/lib/commands/sync.js +34 -46
  147. package/lib/commands/team.js +4 -1
  148. package/lib/commands/test.js +43 -27
  149. package/lib/commands/update.js +2 -2
  150. package/lib/commands/upgrade.js +47 -0
  151. package/lib/commands/validate.js +43 -18
  152. package/lib/commands/worktree.js +362 -99
  153. package/lib/config-writer.js +202 -0
  154. package/lib/control-plane.js +236 -0
  155. package/lib/core/runtime-graph.js +977 -0
  156. package/lib/dep-guard/keyword-ripple.js +2 -2
  157. package/lib/deprecated-sync-cleanup.js +362 -0
  158. package/lib/detect-agent.js +2 -28
  159. package/lib/detect-worktree.js +35 -9
  160. package/lib/doc-gate/declaration.js +177 -0
  161. package/lib/doc-gate/detect.js +289 -0
  162. package/lib/doc-gate/gate.js +375 -0
  163. package/lib/doc-gate/okf-config.js +128 -0
  164. package/lib/doc-gate/okf.js +429 -0
  165. package/lib/docs-command.js +1161 -6
  166. package/lib/forge-issues.js +382 -11
  167. package/lib/forge-lock.js +262 -0
  168. package/lib/gate-events.js +192 -0
  169. package/lib/global-flags.js +104 -0
  170. package/lib/greptile-match.js +7 -63
  171. package/lib/grounding/context-events.js +230 -0
  172. package/lib/grounding/read-first.js +112 -0
  173. package/lib/harness-capability-matrix.js +380 -0
  174. package/lib/hook-global-installer.js +347 -0
  175. package/lib/hook-renderer.js +541 -0
  176. package/lib/inbox.js +391 -0
  177. package/lib/insights.js +397 -0
  178. package/lib/issue-adapter.js +156 -0
  179. package/lib/issue-backend.js +145 -0
  180. package/lib/issue-render.js +220 -0
  181. package/lib/kernel/backing-issue.js +311 -0
  182. package/lib/kernel/broker.js +1218 -0
  183. package/lib/kernel/cli-broker-factory.js +130 -0
  184. package/lib/kernel/conflict-signal.js +82 -0
  185. package/lib/kernel/evaluators.js +195 -0
  186. package/lib/kernel/fs-class.js +495 -0
  187. package/lib/kernel/issue-command-contract.js +559 -0
  188. package/lib/kernel/issue-id-resolver.js +186 -0
  189. package/lib/kernel/lease-enforcer.js +158 -0
  190. package/lib/kernel/migrations.js +333 -0
  191. package/lib/kernel/owned-kernel.js +43 -0
  192. package/lib/kernel/planning-buckets-schema.js +109 -0
  193. package/lib/kernel/projection-jsonl-writer.js +450 -0
  194. package/lib/kernel/readiness-model.js +329 -0
  195. package/lib/kernel/schema.js +356 -0
  196. package/lib/kernel/sqlite-driver.js +2540 -0
  197. package/lib/kernel/taxonomy-validator.js +394 -0
  198. package/lib/lefthook-check.js +3 -2
  199. package/lib/lefthook-wiring.js +413 -0
  200. package/lib/mcp-config-renderer.js +288 -0
  201. package/lib/memory/graphiti-mcp.js +106 -0
  202. package/lib/memory/router.js +387 -0
  203. package/lib/memory/typed-api.js +102 -0
  204. package/lib/memory-digest.js +195 -0
  205. package/lib/merge-rules.js +395 -0
  206. package/lib/migrate-dry-run.js +466 -0
  207. package/lib/orientation.js +863 -0
  208. package/lib/package-manager-remediation.js +103 -0
  209. package/lib/package-root.js +381 -0
  210. package/lib/patch-intent.js +890 -0
  211. package/lib/plugin-catalog.js +3 -4
  212. package/lib/plugin-manager.js +0 -5
  213. package/lib/pr-bundle.js +186 -0
  214. package/lib/pr-monitor/auto-actions.js +175 -0
  215. package/lib/pr-monitor/differ.js +195 -0
  216. package/lib/pr-monitor/digest.js +206 -0
  217. package/lib/pr-monitor/events.js +0 -0
  218. package/lib/pr-monitor/gather.js +124 -0
  219. package/lib/pr-monitor/journal.js +299 -0
  220. package/lib/pr-monitor/monitor.js +146 -0
  221. package/lib/pr-monitor/render-sticky.js +192 -0
  222. package/lib/pr-monitor/upsert-sticky.js +169 -0
  223. package/lib/pr-monitor/watch-lifecycle.js +95 -0
  224. package/lib/pr-monitor/watch.js +247 -0
  225. package/lib/pr-pull.js +1314 -0
  226. package/lib/pr-shepherd.js +494 -0
  227. package/lib/pr-state-validator.js +59 -0
  228. package/lib/preflight/gates.js +237 -0
  229. package/lib/preflight/runner.js +116 -0
  230. package/lib/project-discovery.js +0 -53
  231. package/lib/project-memory.js +99 -497
  232. package/lib/protected-path-manifest.js +281 -0
  233. package/lib/protected-state-surfaces.js +387 -0
  234. package/lib/release-readiness.js +2105 -0
  235. package/lib/reset.js +59 -45
  236. package/lib/review-adapter.js +68 -0
  237. package/lib/rules-sync.js +260 -0
  238. package/lib/runtime-health.js +241 -20
  239. package/lib/safety-config-renderer.js +268 -0
  240. package/lib/setup-action-log.js +1 -7
  241. package/lib/setup.js +27 -65
  242. package/lib/shell-utils.js +76 -6
  243. package/lib/skills-sync.js +330 -0
  244. package/lib/smart-status/scoring.js +17 -3
  245. package/lib/status/beads-snapshot.js +45 -2
  246. package/lib/status/presenter.js +169 -18
  247. package/lib/status/snapshot.js +186 -0
  248. package/lib/sync-backend.js +202 -0
  249. package/lib/untrusted-content.js +52 -0
  250. package/lib/upgrade-safety.js +251 -0
  251. package/lib/workflow/enforce-stage.js +351 -45
  252. package/lib/workflow/stage-transition.js +115 -0
  253. package/lib/workflow/stages.js +30 -6
  254. package/lib/workflow/state-manager.js +11 -22
  255. package/lib/workflow/state.js +23 -1
  256. package/lib/workflow-profiles.js +17 -5
  257. package/package.json +37 -35
  258. package/rules/documentation.md +19 -0
  259. package/rules/kernel-tracking.md +26 -0
  260. package/rules/security.md +22 -0
  261. package/rules/tdd.md +20 -0
  262. package/rules/workflow.md +27 -0
  263. package/scripts/auto-backing-issue.js +47 -0
  264. package/scripts/beads-context.sh +81 -57
  265. package/scripts/beads-upgrade-smoke.sh +24 -3
  266. package/scripts/bootstrap-windows-tools.sh +78 -0
  267. package/scripts/branch-protection.js +2 -3
  268. package/scripts/check-agents.js +34 -137
  269. package/scripts/commitlint.js +3 -1
  270. package/scripts/conflict-detect.sh +3 -0
  271. package/scripts/dep-guard.sh +22 -3
  272. package/scripts/file-index.sh +3 -0
  273. package/scripts/forge-team/lib/claim.sh +34 -18
  274. package/scripts/forge-team/lib/dashboard.sh +61 -86
  275. package/scripts/forge-team/lib/epic.sh +99 -263
  276. package/scripts/forge-team/lib/hooks.sh +26 -28
  277. package/scripts/forge-team/lib/identity.sh +4 -4
  278. package/scripts/forge-team/lib/sync-github.sh +49 -84
  279. package/scripts/forge-team/lib/verify.sh +93 -83
  280. package/scripts/forge-team/lib/workload.sh +41 -65
  281. package/scripts/forge-team/tests/claim.test.sh +25 -19
  282. package/scripts/forge-team/tests/dashboard.test.sh +31 -46
  283. package/scripts/forge-team/tests/epic.test.sh +52 -71
  284. package/scripts/forge-team/tests/hooks.test.sh +38 -50
  285. package/scripts/forge-team/tests/identity.test.sh +3 -3
  286. package/scripts/forge-team/tests/integration.test.sh +44 -66
  287. package/scripts/forge-team/tests/sync-github.test.sh +50 -83
  288. package/scripts/forge-team/tests/verify.test.sh +37 -46
  289. package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
  290. package/scripts/forge-team/tests/workload.test.sh +32 -66
  291. package/scripts/gen-command-manifest.js +153 -0
  292. package/scripts/gen-embedded-assets.mjs +129 -0
  293. package/scripts/install.ps1 +139 -0
  294. package/scripts/install.sh +268 -0
  295. package/scripts/lib/release-asset.mjs +84 -0
  296. package/scripts/parity-check.mjs +145 -0
  297. package/scripts/parity-check.test.mjs +58 -0
  298. package/scripts/pin-agentic-workflow-images.js +112 -0
  299. package/scripts/pr-auto-actions.js +93 -0
  300. package/scripts/pr-coordinator.sh +3 -0
  301. package/scripts/pr-verdict-label.js +50 -0
  302. package/scripts/preflight-sonar.eslint.config.mjs +44 -0
  303. package/scripts/preflight.sh +21 -94
  304. package/scripts/protected-state-check.js +104 -0
  305. package/scripts/smart-status.sh +60 -57
  306. package/scripts/spikes/config-race-bench.js +111 -0
  307. package/scripts/spikes/harness-capability-matrix.js +13 -0
  308. package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
  309. package/scripts/spikes/protected-path-manifest.js +20 -0
  310. package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
  311. package/scripts/sync-agent-skills.js +62 -0
  312. package/scripts/sync-utils.sh +3 -0
  313. package/scripts/test-ci-shard.js +13 -6
  314. package/scripts/test.js +95 -12
  315. package/skills/claim-safety/SKILL.md +102 -0
  316. package/skills/claim-safety/evals/evals.json +46 -0
  317. package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +44 -50
  318. package/skills/dev/evals/evals.json +50 -0
  319. package/skills/hermes-forge/SKILL.md +185 -0
  320. package/skills/hermes-forge/evals/evals.json +46 -0
  321. package/skills/issue-basics/SKILL.md +111 -0
  322. package/skills/issue-basics/evals/evals.json +46 -0
  323. package/skills/kernel/SKILL.md +166 -0
  324. package/skills/kernel/evals/evals.json +50 -0
  325. package/skills/memory/SKILL.md +102 -0
  326. package/skills/parallel-deep-research/SKILL.md +14 -11
  327. package/skills/parallel-deep-research/evals/evals.json +11 -27
  328. package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +132 -157
  329. package/skills/plan/evals/evals.json +42 -0
  330. package/skills/research/SKILL.md +195 -0
  331. package/skills/research/evals/evals.json +42 -0
  332. package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
  333. package/skills/review/evals/evals.json +42 -0
  334. package/skills/rollback/SKILL.md +110 -0
  335. package/skills/rollback/evals/evals.json +46 -0
  336. package/skills/rollback/references/methods.md +204 -0
  337. package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
  338. package/skills/shepherd/SKILL.md +66 -0
  339. package/skills/shepherd/evals/evals.json +42 -0
  340. package/{.github/prompts/ship.prompt.md → skills/ship/SKILL.md} +81 -45
  341. package/skills/ship/evals/evals.json +42 -0
  342. package/skills/smith/SKILL.md +142 -0
  343. package/skills/smith/evals/evals.json +46 -0
  344. package/skills/smith/references/autonomy-and-gates.md +94 -0
  345. package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
  346. package/skills/sonarcloud/evals/evals.json +46 -0
  347. package/skills/sonarcloud-analysis/SKILL.md +18 -13
  348. package/skills/sonarcloud-analysis/evals/evals.json +11 -15
  349. package/{.github/prompts/status.prompt.md → skills/status/SKILL.md} +20 -10
  350. package/skills/status/evals/evals.json +50 -0
  351. package/skills/triage-ready/SKILL.md +121 -0
  352. package/skills/triage-ready/evals/evals.json +42 -0
  353. package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
  354. package/skills/validate/evals/evals.json +42 -0
  355. package/skills/verify/SKILL.md +299 -0
  356. package/skills/verify/evals/evals.json +50 -0
  357. package/.claude/commands/dev.md +0 -345
  358. package/.claude/commands/plan.md +0 -566
  359. package/.claude/commands/premerge.md +0 -186
  360. package/.claude/commands/research.md +0 -42
  361. package/.claude/commands/review.md +0 -451
  362. package/.claude/commands/rollback.md +0 -721
  363. package/.claude/commands/ship.md +0 -213
  364. package/.claude/commands/sonarcloud.md +0 -152
  365. package/.claude/commands/status.md +0 -90
  366. package/.claude/commands/validate.md +0 -288
  367. package/.claude/commands/verify.md +0 -269
  368. package/.claude/rules/workflow.md +0 -121
  369. package/.cline/workflows/dev.md +0 -342
  370. package/.cline/workflows/plan.md +0 -563
  371. package/.cline/workflows/premerge.md +0 -183
  372. package/.cline/workflows/research.md +0 -39
  373. package/.cline/workflows/review.md +0 -448
  374. package/.cline/workflows/rollback.md +0 -718
  375. package/.cline/workflows/ship.md +0 -210
  376. package/.cline/workflows/sonarcloud.md +0 -146
  377. package/.cline/workflows/status.md +0 -87
  378. package/.cline/workflows/validate.md +0 -285
  379. package/.cline/workflows/verify.md +0 -266
  380. package/.codex/config.toml +0 -11
  381. package/.codex/skills/dev/SKILL.md +0 -345
  382. package/.codex/skills/plan/SKILL.md +0 -566
  383. package/.codex/skills/premerge/SKILL.md +0 -186
  384. package/.codex/skills/research/SKILL.md +0 -42
  385. package/.codex/skills/review/SKILL.md +0 -451
  386. package/.codex/skills/rollback/SKILL.md +0 -721
  387. package/.codex/skills/ship/SKILL.md +0 -213
  388. package/.codex/skills/sonarcloud/SKILL.md +0 -149
  389. package/.codex/skills/status/SKILL.md +0 -90
  390. package/.codex/skills/validate/SKILL.md +0 -288
  391. package/.codex/skills/verify/SKILL.md +0 -269
  392. package/.cursor/commands/dev.md +0 -342
  393. package/.cursor/commands/plan.md +0 -563
  394. package/.cursor/commands/premerge.md +0 -183
  395. package/.cursor/commands/research.md +0 -39
  396. package/.cursor/commands/review.md +0 -448
  397. package/.cursor/commands/ship.md +0 -210
  398. package/.cursor/commands/sonarcloud.md +0 -146
  399. package/.cursor/commands/status.md +0 -87
  400. package/.cursor/commands/validate.md +0 -285
  401. package/.cursor/commands/verify.md +0 -266
  402. package/.cursorrules +0 -149
  403. package/.github/prompts/premerge.prompt.md +0 -188
  404. package/.github/prompts/research.prompt.md +0 -44
  405. package/.github/prompts/rollback.prompt.md +0 -723
  406. package/.github/prompts/verify.prompt.md +0 -271
  407. package/.github/workflows/beads-to-github.yml +0 -89
  408. package/.github/workflows/github-to-beads.yml +0 -100
  409. package/.kilocode/workflows/dev.md +0 -346
  410. package/.kilocode/workflows/plan.md +0 -567
  411. package/.kilocode/workflows/premerge.md +0 -187
  412. package/.kilocode/workflows/research.md +0 -43
  413. package/.kilocode/workflows/review.md +0 -452
  414. package/.kilocode/workflows/rollback.md +0 -722
  415. package/.kilocode/workflows/ship.md +0 -214
  416. package/.kilocode/workflows/sonarcloud.md +0 -150
  417. package/.kilocode/workflows/status.md +0 -91
  418. package/.kilocode/workflows/validate.md +0 -289
  419. package/.kilocode/workflows/verify.md +0 -270
  420. package/.opencode/commands/dev.md +0 -345
  421. package/.opencode/commands/plan.md +0 -566
  422. package/.opencode/commands/premerge.md +0 -186
  423. package/.opencode/commands/research.md +0 -42
  424. package/.opencode/commands/review.md +0 -451
  425. package/.opencode/commands/rollback.md +0 -721
  426. package/.opencode/commands/ship.md +0 -213
  427. package/.opencode/commands/sonarcloud.md +0 -149
  428. package/.opencode/commands/status.md +0 -90
  429. package/.opencode/commands/validate.md +0 -288
  430. package/.opencode/commands/verify.md +0 -269
  431. package/.roo/commands/dev.md +0 -346
  432. package/.roo/commands/plan.md +0 -567
  433. package/.roo/commands/premerge.md +0 -187
  434. package/.roo/commands/research.md +0 -43
  435. package/.roo/commands/review.md +0 -452
  436. package/.roo/commands/rollback.md +0 -722
  437. package/.roo/commands/ship.md +0 -214
  438. package/.roo/commands/sonarcloud.md +0 -150
  439. package/.roo/commands/status.md +0 -91
  440. package/.roo/commands/validate.md +0 -289
  441. package/.roo/commands/verify.md +0 -270
  442. package/docs/BEADS_GITHUB_SYNC.md +0 -281
  443. package/docs/GREPTILE_SETUP.md +0 -400
  444. package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
  445. package/docs/SETUP.md +0 -663
  446. package/docs/VALIDATION.md +0 -363
  447. package/lib/agents/cline.plugin.json +0 -29
  448. package/lib/agents/copilot.plugin.json +0 -24
  449. package/lib/agents/kilocode.plugin.json +0 -22
  450. package/lib/agents/opencode.plugin.json +0 -23
  451. package/lib/agents/roo.plugin.json +0 -30
  452. package/lib/beads-bootstrap.js +0 -225
  453. package/lib/beads-health-check.js +0 -188
  454. package/lib/commands/commands-reset.js +0 -147
  455. package/opencode.json +0 -67
  456. package/scripts/beads-context.test.js +0 -584
  457. package/scripts/github-beads-sync/comment.mjs +0 -64
  458. package/scripts/github-beads-sync/config.mjs +0 -148
  459. package/scripts/github-beads-sync/github-api.mjs +0 -131
  460. package/scripts/github-beads-sync/index.mjs +0 -356
  461. package/scripts/github-beads-sync/label-mapper.mjs +0 -54
  462. package/scripts/github-beads-sync/mapping.mjs +0 -132
  463. package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
  464. package/scripts/github-beads-sync/reverse-sync.mjs +0 -162
  465. package/scripts/github-beads-sync/run-bd.mjs +0 -161
  466. package/scripts/github-beads-sync/sanitize.mjs +0 -121
  467. package/scripts/github-beads-sync.config.json +0 -26
  468. package/scripts/sync-commands.js +0 -600
@@ -11,6 +11,8 @@
11
11
  const fs = require('node:fs');
12
12
  const path = require('node:path');
13
13
  const { execFileSync } = require('node:child_process');
14
+ const { resolveIssueBackend } = require('../issue-backend');
15
+ const { runIssueOperation } = require('../forge-issues');
14
16
 
15
17
  // Constants for security
16
18
  // Note: cwd is resolved at call-time via getExecOptions() to avoid stale require-time snapshots
@@ -22,6 +24,21 @@ function getExecOptions() {
22
24
  const MAX_SLUG_LENGTH = 100;
23
25
  const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
24
26
 
27
+ /**
28
+ * Human-readable label for the issue backend that created an issue.
29
+ * Keeps printed output backend-accurate so kernel-created issues are not
30
+ * mislabeled as "Beads".
31
+ *
32
+ * @param {string} [backend] - Resolved issue backend ('kernel' | 'beads').
33
+ * @returns {string} Display label ('Kernel', 'Beads', or a neutral 'Issue').
34
+ * @private
35
+ */
36
+ function issueBackendLabel(backend) {
37
+ if (backend === 'beads') return 'Beads';
38
+ if (backend === 'kernel') return 'Kernel';
39
+ return 'Issue';
40
+ }
41
+
25
42
  /**
26
43
  * Validate feature slug format
27
44
  * Ensures slug matches expected pattern and doesn't contain path traversal
@@ -58,6 +75,33 @@ function validateFeatureSlug(slug) {
58
75
  return { valid: true };
59
76
  }
60
77
 
78
+ /**
79
+ * Build the issue description shared by both issue backends (kernel and beads).
80
+ * Strategic scope appends a design-doc pointer derived from a sanitized slug.
81
+ *
82
+ * @param {string} featureName
83
+ * @param {string} researchPath
84
+ * @param {'tactical'|'strategic'} scope
85
+ * @returns {{description: string}|{error: string}}
86
+ * @private
87
+ */
88
+ function buildFeatureIssueDescription(featureName, researchPath, scope) {
89
+ let description = `Research: ${researchPath}`;
90
+
91
+ if (scope === 'strategic') {
92
+ // Sanitize derived slug: keep only safe characters (OWASP A03)
93
+ const featureSlug = featureName.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-').replace(/(?:^-+|-+$)/g, '').slice(0, MAX_SLUG_LENGTH); // NOSONAR S5852 - non-overlapping anchors, no backtracking
94
+ const slugValidation = validateFeatureSlug(featureSlug);
95
+ if (!slugValidation.valid) {
96
+ return { error: `Cannot generate valid slug from feature name: ${slugValidation.error}` };
97
+ }
98
+ const dateSlug = new Date().toISOString().slice(0, 10);
99
+ description += `\n\nDesign: docs/work/${dateSlug}-${featureSlug}/plan.md`;
100
+ }
101
+
102
+ return { description };
103
+ }
104
+
61
105
  /**
62
106
  * Read research document from file
63
107
  *
@@ -235,17 +279,11 @@ function createBeadsIssue(featureName, researchPath, scope) {
235
279
  }
236
280
 
237
281
  try {
238
- let description = `Research: ${researchPath}`;
239
-
240
- if (scope === 'strategic') {
241
- // Sanitize derived slug: keep only safe characters (OWASP A03)
242
- const featureSlug = featureName.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-').replace(/(?:^-+|-+$)/g, '').slice(0, MAX_SLUG_LENGTH); // NOSONAR S5852 - non-overlapping anchors, no backtracking
243
- const slugValidation = validateFeatureSlug(featureSlug);
244
- if (!slugValidation.valid) {
245
- return { success: false, error: `Cannot generate valid slug from feature name: ${slugValidation.error}` };
246
- }
247
- description += `\n\nDesign: docs/plans/${featureSlug}-design.md`;
282
+ const built = buildFeatureIssueDescription(featureName, researchPath, scope);
283
+ if (built.error) {
284
+ return { success: false, error: built.error };
248
285
  }
286
+ const description = built.description;
249
287
 
250
288
  // Execute bd create command using execFileSync for safety (OWASP A03)
251
289
  const result = execFileSync( // NOSONAR S4036 - hardcoded CLI command, no user input, developer tool context
@@ -294,9 +332,337 @@ function createBeadsIssue(featureName, researchPath, scope) {
294
332
  }
295
333
  }
296
334
 
335
+ /**
336
+ * Create an issue via the Forge Kernel backend (bd-free).
337
+ *
338
+ * Mirrors `forge issue create` on the kernel: routes through runIssueOperation with
339
+ * the kernel broker instead of shelling out to `bd create`. Used by `forge plan` when
340
+ * the resolved issue backend is the kernel (the default), so planning needs no Beads
341
+ * install. The beads path (createBeadsIssue) is preserved for backend=beads.
342
+ *
343
+ * @param {string} featureName
344
+ * @param {string} researchPath
345
+ * @param {'tactical'|'strategic'} scope
346
+ * @param {object} [options]
347
+ * @param {string} [options.projectRoot] - Repo root for the kernel store (defaults to cwd)
348
+ * @param {object} [options.kernelBroker] - Pre-built kernel broker (optional)
349
+ * @param {Function} [options.runIssueOperation] - Injectable runner (tests)
350
+ * @returns {Promise<{success: boolean, issueId?: string, description?: string, error?: string}>}
351
+ */
352
+ const PLAN_ISSUE_UUID_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;
353
+
354
+ /**
355
+ * Parse plan's own args: extract a `--issue <id>` / `--issue=<id>` selector and
356
+ * return the remaining positional feature name. The global flag parser only
357
+ * recognizes an allowlist, so — like `stage`/`worktree` — plan extracts its own.
358
+ *
359
+ * @param {string[]} args
360
+ * @returns {{ featureName: string|undefined, issueId: string|null }}
361
+ * @private
362
+ */
363
+ function parsePlanArgs(args = []) {
364
+ let issueId = null;
365
+ let issueFlagSeen = false;
366
+ const positional = [];
367
+ for (let i = 0; i < args.length; i += 1) {
368
+ const arg = args[i];
369
+ if (arg === '--issue') {
370
+ issueFlagSeen = true;
371
+ const next = i + 1 < args.length ? args[i + 1] : null;
372
+ // A following flag (or nothing) is NOT a value — treat as a missing value
373
+ // (F5) so we never silently fall back to CREATE a duplicate issue.
374
+ if (typeof next === 'string' && !next.startsWith('-')) {
375
+ issueId = next;
376
+ i += 1;
377
+ }
378
+ continue;
379
+ }
380
+ if (typeof arg === 'string' && arg.startsWith('--issue=')) {
381
+ issueFlagSeen = true;
382
+ issueId = arg.slice('--issue='.length) || null;
383
+ continue;
384
+ }
385
+ positional.push(arg);
386
+ }
387
+ return { featureName: positional[0], issueId, issueFlagError: issueFlagSeen && !issueId };
388
+ }
389
+
390
+ /**
391
+ * Link an EXISTING issue instead of creating a new one (B4). Verifies the issue
392
+ * exists via `issue show` so `plan --issue <id>` links the claim-first flow's
393
+ * issue rather than forking a duplicate.
394
+ *
395
+ * @param {string} issueId
396
+ * @param {object} [options]
397
+ * @returns {Promise<{success: boolean, issueId?: string, error?: string}>}
398
+ * @private
399
+ */
400
+ async function linkExistingIssue(issueId, options = {}) {
401
+ if (!issueId || typeof issueId !== 'string') {
402
+ return { success: false, error: 'An issue id is required to link an existing issue' };
403
+ }
404
+
405
+ const run = options.runIssueOperation || runIssueOperation;
406
+ const projectRoot = options.projectRoot || process.cwd();
407
+ const issueBackend = options.issueBackend || 'kernel';
408
+
409
+ try {
410
+ const result = await run(
411
+ 'show',
412
+ [issueId],
413
+ projectRoot,
414
+ {
415
+ issueBackend,
416
+ useKernelBroker: issueBackend !== 'beads',
417
+ kernelBroker: options.kernelBroker,
418
+ },
419
+ );
420
+
421
+ if (!result || result.ok === false || result.success === false) {
422
+ const message = result?.error || 'issue not found';
423
+ return { success: false, error: `Issue ${issueId} not found: ${message}` };
424
+ }
425
+
426
+ const resolvedId = result?.data?.id ?? result?.issueId ?? result?.id ?? issueId;
427
+ return { success: true, issueId: resolvedId };
428
+ } catch (error) {
429
+ return { success: false, error: `Failed to look up issue ${issueId}: ${error.message}` };
430
+ }
431
+ }
432
+
433
+ /**
434
+ * Read the current git branch (empty string when detached / not a repo).
435
+ * @private
436
+ */
437
+ function getCurrentBranch() {
438
+ try {
439
+ return execFileSync('git', ['branch', '--show-current'], { ...getExecOptions(), stdio: 'pipe' }).trim(); // NOSONAR S4036 - hardcoded CLI command, developer tool context
440
+ } catch {
441
+ return '';
442
+ }
443
+ }
444
+
445
+ function isDefaultBranch(name) {
446
+ return name === '' || name === 'main' || name === 'master';
447
+ }
448
+
449
+ /**
450
+ * When linking an existing issue, reuse the current feature branch instead of
451
+ * forking a second branch (B4). Only creates a fresh `feat/<slug>` branch when
452
+ * still sitting on a default branch.
453
+ *
454
+ * @param {string} featureSlug
455
+ * @returns {{success: boolean, branchName?: string, reused?: boolean, error?: string}}
456
+ * @private
457
+ */
458
+ function reuseOrCreateFeatureBranch(featureSlug) {
459
+ const current = getCurrentBranch();
460
+ if (!isDefaultBranch(current)) {
461
+ return { success: true, branchName: current, reused: true };
462
+ }
463
+ return createFeatureBranch(featureSlug);
464
+ }
465
+
466
+ /** Read `git rev-parse --git-common-dir` (null on failure). @private */
467
+ function getGitCommonDir(cwd) {
468
+ try {
469
+ return execFileSync('git', ['rev-parse', '--git-common-dir'], { ...getExecOptions(), cwd, stdio: 'pipe' }).trim(); // NOSONAR S4036 - hardcoded CLI command, developer tool context
470
+ } catch {
471
+ return null;
472
+ }
473
+ }
474
+
475
+ /** An already-resolved kernel driver (tests / commandOpts), or null. @private */
476
+ function injectedPlanDriver(options = {}) {
477
+ return options.kernelDriver || options.driver || null;
478
+ }
479
+
480
+ /**
481
+ * Run `fn(driver)` with a kernel driver: an injected one (tests / commandOpts)
482
+ * when present, otherwise a throwaway migrated driver built from the project
483
+ * root and CLOSED afterwards, so it never leaves an open DB handle (which would
484
+ * lock the dir on Windows). `plan` is NOT an issue command, so
485
+ * `resolveCommandOpts` gives it no driver — building here is what lets the
486
+ * branch->issue registry be consulted in the real CLI (both the F4c conflict
487
+ * read and the F1 linkage write). `fn(null)` when the kernel is unavailable.
488
+ * @private
489
+ */
490
+ async function withPlanDriver(options, fn) {
491
+ const injected = injectedPlanDriver(options);
492
+ if (injected) {
493
+ return fn(injected);
494
+ }
495
+
496
+ let driver;
497
+ try {
498
+ const { buildMigratedKernelIssueDeps } = require('../kernel/cli-broker-factory');
499
+ const deps = await buildMigratedKernelIssueDeps({ projectRoot: options.projectRoot || process.cwd() });
500
+ driver = deps.kernelDriver || null;
501
+ } catch {
502
+ return fn(null);
503
+ }
504
+ try {
505
+ return fn(driver);
506
+ } finally {
507
+ if (driver && typeof driver.close === 'function') driver.close();
508
+ }
509
+ }
510
+
511
+ /**
512
+ * Which issue the CURRENT branch is already bound to: the kernel worktree
513
+ * registry (authoritative) first, then a UUID encoded in the branch name.
514
+ * @private
515
+ */
516
+ function currentBranchIssueFromDriver(driver, currentBranch) {
517
+ if (driver && typeof driver.listWorktrees === 'function') {
518
+ try {
519
+ const rows = driver.listWorktrees() || [];
520
+ // Only an ACTIVE (live) linkage row binds the branch: a superseded/stale
521
+ // registration for a reused branch name must not trigger a false split-state
522
+ // conflict against the OLD issue (R4/be18881c). Tolerate a null state for
523
+ // rows written before the state column was populated.
524
+ const match = rows.find(row => row && row.branch === currentBranch && row.issue_id
525
+ && (row.state === 'active' || row.state == null));
526
+ if (match) return match.issue_id;
527
+ } catch {
528
+ // fall through to branch-name parsing
529
+ }
530
+ }
531
+ const encoded = PLAN_ISSUE_UUID_RE.exec(currentBranch || '');
532
+ return encoded ? encoded[0] : null;
533
+ }
534
+
535
+ /**
536
+ * Refuse to link issue B onto a branch already bound to issue A (F4c): otherwise
537
+ * plan would link B while stage read/writes flow to A (split state). Consults the
538
+ * kernel worktree registry (via a built driver when the CLI supplies none) so a
539
+ * plan-created slug branch — whose name lacks a UUID — is still caught.
540
+ * @returns {Promise<string|null>} an error message when there is a conflict.
541
+ * @private
542
+ */
543
+ async function detectBranchIssueConflict(options, explicitIssueId) {
544
+ const current = getCurrentBranch();
545
+ if (isDefaultBranch(current)) return null;
546
+ const boundIssue = await withPlanDriver(options, driver => currentBranchIssueFromDriver(driver, current));
547
+ if (boundIssue && boundIssue !== explicitIssueId) {
548
+ return `Current branch ${current} is already bound to issue ${boundIssue}; refusing to link ${explicitIssueId} (split state). Switch branches or link the matching issue.`;
549
+ }
550
+ return null;
551
+ }
552
+
553
+ /**
554
+ * Persist the branch->issue linkage into the kernel worktree registry (F1) so a
555
+ * plan-created branch resolves to its issue for kernel-authoritative stage
556
+ * state. Best-effort, kernel-only. @private
557
+ */
558
+ async function registerBranchIssueLinkage(options, branch, issueId) {
559
+ if (!branch || !issueId) return;
560
+ const cwd = options.projectRoot || process.cwd();
561
+ // F6 defaultStageWarn pattern: write to stderr so a dropped linkage never
562
+ // pollutes machine-readable stdout, yet leaves a trace even under FORGE_JSON=1.
563
+ const warn = options.warn || (message => process.stderr.write(`${message}\n`));
564
+ await withPlanDriver(options, driver => {
565
+ if (!driver || typeof driver.registerWorktree !== 'function') return;
566
+ try {
567
+ driver.registerWorktree({
568
+ git_common_dir: getGitCommonDir(cwd) || cwd,
569
+ path: cwd,
570
+ branch,
571
+ actor: null,
572
+ issue_id: issueId,
573
+ work_folder: null,
574
+ registered_at: new Date().toISOString(),
575
+ state: 'active',
576
+ });
577
+ } catch (error) {
578
+ // Best-effort: linkage failure must not fail plan, but it must NOT be
579
+ // silent (R3) — otherwise ship later fail-closes with no signal at plan
580
+ // time about the dropped branch->issue linkage.
581
+ warn(`[forge] could not register branch->issue linkage for ${branch} -> ${issueId}: ${error.message}`);
582
+ }
583
+ });
584
+ }
585
+
586
+ /**
587
+ * Resolve the tracking issue for a plan: LINK an explicit issue, else CREATE via
588
+ * the active backend (the kernel default needs no Beads). Extracted to avoid a
589
+ * nested ternary in executePlan.
590
+ *
591
+ * @returns {Promise<{success: boolean, issueId?: string, error?: string}>}
592
+ * @private
593
+ */
594
+ async function resolveTrackingIssue({ explicitIssueId, issueBackend, featureName, researchPath, scope, options }) {
595
+ if (explicitIssueId) {
596
+ return linkExistingIssue(explicitIssueId, { ...options, issueBackend });
597
+ }
598
+ if (issueBackend === 'kernel') {
599
+ return createKernelIssue(featureName, researchPath, scope, options);
600
+ }
601
+ return createBeadsIssue(featureName, researchPath, scope);
602
+ }
603
+
604
+ async function createKernelIssue(featureName, researchPath, scope, options = {}) {
605
+ if (!featureName || !researchPath) {
606
+ return {
607
+ success: false,
608
+ error: 'Feature name and research path are required',
609
+ };
610
+ }
611
+
612
+ if (scope !== 'tactical' && scope !== 'strategic') {
613
+ return {
614
+ success: false,
615
+ error: `Invalid scope '${scope}'. Must be 'tactical' or 'strategic'`,
616
+ };
617
+ }
618
+
619
+ const built = buildFeatureIssueDescription(featureName, researchPath, scope);
620
+ if (built.error) {
621
+ return { success: false, error: built.error };
622
+ }
623
+ const description = built.description;
624
+
625
+ const run = options.runIssueOperation || runIssueOperation;
626
+ const projectRoot = options.projectRoot || process.cwd();
627
+
628
+ try {
629
+ const result = await run(
630
+ 'create',
631
+ [`--title=${featureName}`, `--description=${description}`, '--type=feature', '--priority=2'],
632
+ projectRoot,
633
+ { issueBackend: 'kernel', useKernelBroker: true, kernelBroker: options.kernelBroker },
634
+ );
635
+
636
+ // The kernel returns the issue-command contract ({ ok, data: { id } }); a failure
637
+ // is { ok:false, error }. Guard both the contract shape and any { success:false }.
638
+ if (!result || result.ok === false || result.success === false) {
639
+ const message = result?.error || 'Kernel issue creation failed';
640
+ return { success: false, error: `Failed to create kernel issue: ${message}` };
641
+ }
642
+
643
+ const issueId = result?.data?.id ?? result?.issueId ?? result?.id;
644
+ if (!issueId) {
645
+ return {
646
+ success: false,
647
+ error: 'Failed to extract issue ID from kernel create result',
648
+ };
649
+ }
650
+
651
+ return { success: true, issueId, description };
652
+ } catch (error) {
653
+ return {
654
+ success: false,
655
+ error: `Failed to create kernel issue: ${error.message}`,
656
+ };
657
+ }
658
+ }
659
+
297
660
  /**
298
661
  * Create feature branch
299
- * Creates and checks out a new git branch following feat/<slug> convention
662
+ * Creates a new git branch following feat/<slug> convention WITHOUT switching
663
+ * the shared checkout's HEAD (uses `git branch`, not `git checkout -b`).
664
+ * Switching HEAD in the shared working tree corrupts concurrent agents
665
+ * (kernel issue aa14966c); isolated work happens in a dedicated worktree.
300
666
  *
301
667
  * Security: Uses execFileSync with array args to prevent command injection
302
668
  *
@@ -326,14 +692,19 @@ function createFeatureBranch(featureSlug) {
326
692
  execFileSync('git', ['rev-parse', '--verify', branchName], { ...getExecOptions(), stdio: 'pipe' }); // NOSONAR S4036 - hardcoded CLI command, no user input, developer tool context
327
693
  return {
328
694
  success: false,
329
- error: `Branch ${branchName} already exists\n\nSwitch to it with: git checkout ${branchName}`,
695
+ error: `Branch ${branchName} already exists\n\nWork on it in an isolated checkout: forge worktree create ${featureSlug}\n(or, working solo: git switch ${branchName})`,
330
696
  };
331
697
  } catch {
332
698
  // Branch doesn't exist, continue (expected case)
333
699
  }
334
700
 
335
- // Create and checkout branch
336
- execFileSync('git', ['checkout', '-b', branchName], getExecOptions()); // NOSONAR S4036 - hardcoded CLI command, no user input, developer tool context
701
+ // Create the branch WITHOUT switching the shared checkout's HEAD.
702
+ // Historically this used `git checkout -b`, which flipped the shared
703
+ // working tree onto the new branch and corrupted concurrent agents
704
+ // (kernel issue aa14966c). `git branch` creates the ref at the current
705
+ // HEAD without touching the working tree; isolated work happens in a
706
+ // dedicated worktree (`forge worktree create`), never the shared tree.
707
+ execFileSync('git', ['branch', branchName], getExecOptions()); // NOSONAR S4036 - hardcoded CLI command, no user input, developer tool context
337
708
 
338
709
  return {
339
710
  success: true,
@@ -596,13 +967,18 @@ function applyYAGNIFilter({ task, tasks, designDoc } = {}) {
596
967
  * @returns {Promise<{
597
968
  * success: boolean,
598
969
  * scope?: 'tactical'|'strategic',
970
+ * issueBackend?: 'kernel'|'beads',
971
+ * issueId?: string,
599
972
  * beadsIssueId?: string,
600
973
  * branchName?: string,
601
974
  *
602
975
  * summary?: string,
603
976
  * nextCommand?: string,
604
977
  * error?: string
605
- * }>} Execution result
978
+ * }>} Execution result. `issueId` is the created issue id under the active
979
+ * backend; `issueBackend` names that backend. `beadsIssueId` is a deprecated
980
+ * alias of `issueId` kept for backward compatibility (it does NOT imply the
981
+ * issue came from Beads — on the kernel path it holds the kernel issue id).
606
982
  * @example
607
983
  * const result = await executePlan('Payment Integration');
608
984
  * if (result.success) {
@@ -610,7 +986,7 @@ function applyYAGNIFilter({ task, tasks, designDoc } = {}) {
610
986
  * console.log('Next:', result.nextCommand);
611
987
  * }
612
988
  */
613
- async function executePlan(featureName) { // NOSONAR S3776
989
+ async function executePlan(featureName, options = {}) { // NOSONAR S3776
614
990
  if (!featureName || typeof featureName !== 'string') {
615
991
  return {
616
992
  success: false,
@@ -618,6 +994,15 @@ async function executePlan(featureName) { // NOSONAR S3776
618
994
  };
619
995
  }
620
996
 
997
+ // Resolve the active issue backend (explicit opts > env > .forge/config.yaml >
998
+ // default 'kernel'). Kernel is bd-free, so planning works with no Beads install.
999
+ const issueBackend = resolveIssueBackend({
1000
+ deps: options,
1001
+ env: options.env || process.env,
1002
+ projectRoot: options.projectRoot,
1003
+ warn: () => {},
1004
+ });
1005
+
621
1006
  const featureSlug = featureName.toLowerCase()
622
1007
  .replaceAll(/[^a-z0-9-]/g, '-')
623
1008
  .split('-').filter(Boolean).join('-');
@@ -644,17 +1029,37 @@ async function executePlan(featureName) { // NOSONAR S3776
644
1029
  // Step 2: Detect scope (tactical vs strategic)
645
1030
  const scope = detectScope(research.content);
646
1031
 
647
- // Step 3: Create Beads issue
648
- const beads = createBeadsIssue(featureName, research.path, scope.type);
649
- if (!beads.success) {
1032
+ // Step 3: Resolve the tracking issue. `--issue <id>` LINKS an existing issue
1033
+ // (claim-first flow) instead of creating a duplicate (B4). Otherwise create
1034
+ // via the active backend — kernel (default) routes through the kernel broker
1035
+ // (no bd); beads preserves the bd path.
1036
+ const explicitIssueId = options.issue || options.issueId || null;
1037
+
1038
+ // F4c: never link issue B onto a branch already bound to issue A.
1039
+ if (explicitIssueId) {
1040
+ const conflict = await detectBranchIssueConflict({ ...options, issueBackend }, explicitIssueId);
1041
+ if (conflict) {
1042
+ return { success: false, error: conflict };
1043
+ }
1044
+ }
1045
+
1046
+ const issue = await resolveTrackingIssue({
1047
+ explicitIssueId, issueBackend, featureName, researchPath: research.path, scope: scope.type, options,
1048
+ });
1049
+ if (!issue.success) {
650
1050
  return {
651
1051
  success: false,
652
- error: `Failed to create Beads issue: ${beads.error}`,
1052
+ error: explicitIssueId
1053
+ ? `Failed to link issue: ${issue.error}`
1054
+ : `Failed to create issue: ${issue.error}`,
653
1055
  };
654
1056
  }
655
1057
 
656
- // Step 4: Create feature branch
657
- const branch = createFeatureBranch(featureSlug);
1058
+ // Step 4: Resolve the feature branch. When linking, reuse the current
1059
+ // feature branch instead of forking a second branch (B4).
1060
+ const branch = explicitIssueId
1061
+ ? reuseOrCreateFeatureBranch(featureSlug)
1062
+ : createFeatureBranch(featureSlug);
658
1063
  if (!branch.success) {
659
1064
  return {
660
1065
  success: false,
@@ -662,13 +1067,33 @@ async function executePlan(featureName) { // NOSONAR S3776
662
1067
  };
663
1068
  }
664
1069
 
1070
+ // F1: persist the branch->issue linkage so a plan-created branch resolves
1071
+ // to its issue for kernel-authoritative stage state (dev/validate/ship).
1072
+ // Kernel-only, best-effort.
1073
+ if (issueBackend === 'kernel') {
1074
+ await registerBranchIssueLinkage({ ...options, issueBackend }, branch.branchName, issue.issueId);
1075
+ }
1076
+
665
1077
  // Build result summary
666
1078
  const result = {
667
1079
  success: true,
668
1080
  scope: scope.type,
669
- beadsIssueId: beads.issueId,
1081
+ issueBackend,
1082
+ issueId: issue.issueId,
1083
+ // Deprecated alias of issueId, retained for backward compatibility. It does
1084
+ // NOT imply the Beads backend — on the kernel path it holds the kernel id.
1085
+ beadsIssueId: issue.issueId,
670
1086
  branchName: branch.branchName,
671
- summary: `Plan created for ${featureName} (${scope.type} scope)`,
1087
+ linked: Boolean(explicitIssueId),
1088
+ // A FRESH branch was created (HEAD did NOT move — aa14966c). Stage
1089
+ // commands resolve the CHECKED-OUT branch, so the user must enter an
1090
+ // isolated checkout on this branch before /dev, or stage state resolves
1091
+ // against the default branch (no linkage → ship dead-ends). When the
1092
+ // branch was reused, HEAD is already on it and /dev works directly.
1093
+ branchCreated: !branch.reused,
1094
+ summary: explicitIssueId
1095
+ ? `Plan linked to existing issue ${issue.issueId} (${scope.type} scope)`
1096
+ : `Plan created for ${featureName} (${scope.type} scope)`,
672
1097
  // Strategic path: /propose not yet implemented — falls through to /dev until it is
673
1098
  nextCommand: '/dev',
674
1099
  };
@@ -685,16 +1110,39 @@ async function executePlan(featureName) { // NOSONAR S3776
685
1110
  module.exports = {
686
1111
  name: 'plan',
687
1112
  description: 'Create implementation plan from researched feature context',
688
- handler: async (args) => {
689
- const result = await executePlan(args[0]);
1113
+ handler: async (args, _flags, projectRoot, opts = {}) => {
1114
+ const { featureName, issueId, issueFlagError } = parsePlanArgs(args);
1115
+ // F5: `--issue` with no value must ERROR, never silently create a duplicate.
1116
+ if (issueFlagError) {
1117
+ return { success: false, error: '--issue requires a value (an existing issue id to link).' };
1118
+ }
1119
+ const result = await executePlan(featureName, {
1120
+ ...opts,
1121
+ projectRoot,
1122
+ issue: issueId ?? opts.issue ?? opts.issueId,
1123
+ });
690
1124
  if (!result.success) {
691
1125
  return result;
692
1126
  }
693
1127
 
694
- const lines = [`Plan created: ${result.summary || result.branchName || args[0]}`];
695
- if (result.beadsIssueId) lines.push(`Beads: ${result.beadsIssueId}`);
1128
+ const header = result.linked ? 'Plan linked' : 'Plan created';
1129
+ const lines = [`${header}: ${result.summary || result.branchName || featureName}`];
1130
+ if (result.issueId) lines.push(`${issueBackendLabel(result.issueBackend)}: ${result.issueId}`);
696
1131
  if (result.branchName) lines.push(`Branch: ${result.branchName}`);
697
- if (result.nextCommand) lines.push(`Next: ${result.nextCommand}`);
1132
+ if (result.branchCreated) {
1133
+ // A fresh branch was created but HEAD was NOT switched (aa14966c). Stage
1134
+ // commands (/dev, /validate, /ship) resolve the CHECKED-OUT branch — from
1135
+ // the shared tree that is still the default branch, which has no
1136
+ // branch->issue linkage, so ship would dead-end. Direct the user into an
1137
+ // isolated checkout on the new branch first.
1138
+ const slug = String(result.branchName).replace(/^feat\//, '');
1139
+ lines.push('Next: work on this branch in an isolated checkout (HEAD stays put in the shared tree):');
1140
+ lines.push(` forge worktree create ${slug} # concurrent-safe; checks out the existing ${result.branchName}`);
1141
+ lines.push(` # or, working solo: git switch ${result.branchName}`);
1142
+ lines.push(`Then run ${result.nextCommand || '/dev'} from that checkout.`);
1143
+ } else if (result.nextCommand) {
1144
+ lines.push(`Next: ${result.nextCommand}`);
1145
+ }
698
1146
 
699
1147
  return {
700
1148
  ...result,
@@ -704,10 +1152,14 @@ module.exports = {
704
1152
  readResearchDoc,
705
1153
  detectScope,
706
1154
  createBeadsIssue,
1155
+ createKernelIssue,
707
1156
  createFeatureBranch,
708
1157
  extractDesignDecisions,
709
1158
  extractTasksFromResearch,
710
1159
  detectDRYViolation,
711
1160
  applyYAGNIFilter,
712
1161
  executePlan,
1162
+ issueBackendLabel,
1163
+ registerBranchIssueLinkage,
1164
+ currentBranchIssueFromDriver,
713
1165
  };