forge-workflow 0.0.9 → 0.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (479) hide show
  1. package/.claude/rules/{greptile-review-process.md → review-process.md} +56 -41
  2. package/.claude/scripts/{greptile-resolve.sh → review-resolve.sh} +13 -3
  3. package/.cursor/rules/permissions-guidance.mdc +2 -2
  4. package/.forge/hooks/check-tdd.js +3 -0
  5. package/.forge/hooks/forge-native-hook.js +245 -0
  6. package/.forge/protected-paths.yaml +157 -0
  7. package/AGENTS.md +151 -61
  8. package/CHANGELOG.md +681 -0
  9. package/CLAUDE.md +9 -106
  10. package/QUICKSTART.md +171 -0
  11. package/README.md +271 -363
  12. package/bin/forge-cmd.js +120 -9
  13. package/bin/forge-preflight.js +26 -5
  14. package/bin/forge.js +466 -489
  15. package/docs/INDEX.md +93 -0
  16. package/docs/PROJECT_DESIGN.md +685 -0
  17. package/docs/architecture/index.md +66 -0
  18. package/docs/architecture/notes/README.md +35 -0
  19. package/docs/architecture/subsystems/README.md +46 -0
  20. package/docs/{TOOLCHAIN.md → forge/TOOLCHAIN.md} +56 -47
  21. package/docs/forge/VALIDATION.md +82 -0
  22. package/docs/{AGENT_INSTALL_PROMPT.md → guides/AGENT_INSTALL_PROMPT.md} +3 -3
  23. package/docs/guides/BEADS_GITHUB_SYNC.md +32 -0
  24. package/docs/{ENHANCED_ONBOARDING.md → guides/ENHANCED_ONBOARDING.md} +16 -12
  25. package/docs/guides/GREPTILE_SETUP.md +46 -0
  26. package/docs/guides/MANUAL_REVIEW_GUIDE.md +58 -0
  27. package/docs/guides/MIGRATION.md +56 -0
  28. package/docs/guides/SETUP.md +118 -0
  29. package/docs/guides/SUPPORT.md +185 -0
  30. package/docs/guides/WORKFLOW_TEMPLATES.md +74 -0
  31. package/docs/guides/memory-backends.md +183 -0
  32. package/docs/reference/ADAPTERS.md +128 -0
  33. package/docs/reference/AGENT_SKILL_PARITY.md +175 -0
  34. package/docs/reference/COMMANDS.md +205 -0
  35. package/docs/reference/DECISION_DRIFT_GUARDS.md +97 -0
  36. package/docs/{EXAMPLES.md → reference/EXAMPLES.md} +7 -5
  37. package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +135 -0
  38. package/docs/reference/HERMES_INTEGRATION.md +118 -0
  39. package/docs/reference/INSIGHTS_RECAP.md +63 -0
  40. package/docs/reference/INSTALL.md +164 -0
  41. package/docs/reference/KERNEL_TAXONOMY_VALIDATION.md +161 -0
  42. package/docs/reference/PROTECTED_PATH_MANIFEST.md +25 -0
  43. package/docs/reference/RELEASE.md +68 -0
  44. package/docs/reference/RESEARCH_TEMPLATE.md +292 -0
  45. package/docs/{ROADMAP.md → reference/ROADMAP.md} +12 -9
  46. package/docs/reference/SKILLS.md +35 -0
  47. package/docs/reference/STATUS_BOARD.md +80 -0
  48. package/docs/reference/TEMPLATES.md +106 -0
  49. package/docs/reference/TOOLCHAIN.md +658 -0
  50. package/docs/reference/VALIDATION.md +82 -0
  51. package/docs/reference/agent-permissions.md +169 -0
  52. package/docs/reference/beads-to-kernel-migration-ux.md +61 -0
  53. package/docs/reference/control-plane-guarantees.md +125 -0
  54. package/docs/reference/dependency-chain.md +331 -0
  55. package/docs/reference/forge-kernel-issue-command-contract.md +161 -0
  56. package/docs/reference/forge-kernel-schema.md +72 -0
  57. package/docs/reference/kernel-conflict-evaluators.md +27 -0
  58. package/docs/reference/patch-md-format.md +77 -0
  59. package/docs/reference/protected-state-surfaces.md +59 -0
  60. package/docs/reference/shepherd.md +115 -0
  61. package/docs/reference/superpowers-analysis.md +320 -0
  62. package/docs/reference/superpowers-integration-options.md +404 -0
  63. package/docs/reference/test-environment.md +519 -0
  64. package/docs/reference/upgrade-safety.md +59 -0
  65. package/lefthook.yml +18 -0
  66. package/lib/adapter-cli.js +307 -0
  67. package/lib/adapters/beads-issue-adapter.js +127 -0
  68. package/lib/adapters/beads-kernel-compat.js +1042 -0
  69. package/lib/adapters/greptile-review-adapter.js +141 -0
  70. package/lib/adapters/kernel-issue-adapter.js +101 -0
  71. package/lib/adapters/pr-state-adapter.js +484 -0
  72. package/lib/adoption-profiles.js +126 -0
  73. package/lib/agents/README.md +2 -6
  74. package/lib/agents/claude.plugin.json +3 -8
  75. package/lib/agents/codex.plugin.json +9 -1
  76. package/lib/agents/cursor.plugin.json +2 -6
  77. package/lib/agents/hermes.plugin.json +22 -0
  78. package/lib/agents-config.js +39 -1236
  79. package/lib/audit-evidence.js +282 -0
  80. package/lib/beads-setup.js +225 -28
  81. package/lib/beads-sync-scaffold.js +36 -107
  82. package/lib/codex-skills.js +51 -1
  83. package/lib/commands/_issue.js +744 -70
  84. package/lib/commands/_manifest.js +91 -0
  85. package/lib/commands/_registry.js +85 -34
  86. package/lib/commands/_resolve-command-opts.js +261 -0
  87. package/lib/commands/_serve-security.js +270 -0
  88. package/lib/commands/adapter.js +12 -0
  89. package/lib/commands/add.js +118 -0
  90. package/lib/commands/audit.js +70 -0
  91. package/lib/commands/blocked.js +5 -0
  92. package/lib/commands/board.js +64 -0
  93. package/lib/commands/claim.js +21 -2
  94. package/lib/commands/claims.js +7 -0
  95. package/lib/commands/clean.js +485 -75
  96. package/lib/commands/close.js +2 -2
  97. package/lib/commands/comment.js +5 -0
  98. package/lib/commands/control.js +148 -0
  99. package/lib/commands/create.js +2 -2
  100. package/lib/commands/dev.js +185 -7
  101. package/lib/commands/doc-gate.js +336 -0
  102. package/lib/commands/doctor.js +156 -0
  103. package/lib/commands/explain.js +15 -0
  104. package/lib/commands/export.js +237 -0
  105. package/lib/commands/gate.js +192 -0
  106. package/lib/commands/hooks.js +242 -0
  107. package/lib/commands/inbox.js +118 -0
  108. package/lib/commands/init.js +598 -0
  109. package/lib/commands/insights.js +79 -0
  110. package/lib/commands/issue.js +12 -1
  111. package/lib/commands/issues.js +66 -0
  112. package/lib/commands/lint.js +5 -0
  113. package/lib/commands/list.js +2 -2
  114. package/lib/commands/merge.js +312 -0
  115. package/lib/commands/migrate.js +523 -0
  116. package/lib/commands/new.js +12 -0
  117. package/lib/commands/options.js +241 -0
  118. package/lib/commands/orient.js +13 -0
  119. package/lib/commands/orphans.js +5 -0
  120. package/lib/commands/patch.js +67 -0
  121. package/lib/commands/plan.js +436 -24
  122. package/lib/commands/preflight.js +211 -0
  123. package/lib/commands/prime.js +13 -0
  124. package/lib/commands/push.js +69 -2
  125. package/lib/commands/ready.js +2 -2
  126. package/lib/commands/recall.js +116 -0
  127. package/lib/commands/recap.js +61 -0
  128. package/lib/commands/recommend.js +22 -2
  129. package/lib/commands/release.js +91 -0
  130. package/lib/commands/remember.js +74 -0
  131. package/lib/commands/role.js +99 -0
  132. package/lib/commands/serve.js +581 -0
  133. package/lib/commands/setup.js +851 -979
  134. package/lib/commands/shepherd.js +436 -0
  135. package/lib/commands/ship.js +23 -1
  136. package/lib/commands/show.js +2 -2
  137. package/lib/commands/stage.js +192 -0
  138. package/lib/commands/stale.js +5 -0
  139. package/lib/commands/status.js +329 -11
  140. package/lib/commands/sync.js +34 -46
  141. package/lib/commands/team.js +15 -2
  142. package/lib/commands/test.js +58 -7
  143. package/lib/commands/update.js +2 -2
  144. package/lib/commands/upgrade.js +47 -0
  145. package/lib/commands/validate.js +56 -25
  146. package/lib/commands/worktree.js +308 -128
  147. package/lib/config-writer.js +202 -0
  148. package/lib/control-plane.js +236 -0
  149. package/lib/core/runtime-graph.js +946 -0
  150. package/lib/dep-guard/keyword-ripple.js +184 -0
  151. package/lib/deprecated-sync-cleanup.js +362 -0
  152. package/lib/detect-agent.js +2 -28
  153. package/lib/detect-worktree.js +42 -17
  154. package/lib/doc-gate/declaration.js +177 -0
  155. package/lib/doc-gate/detect.js +289 -0
  156. package/lib/doc-gate/gate.js +375 -0
  157. package/lib/doc-gate/okf-config.js +128 -0
  158. package/lib/doc-gate/okf.js +429 -0
  159. package/lib/docs-command.js +1161 -6
  160. package/lib/forge-issues.js +697 -0
  161. package/lib/forge-lock.js +262 -0
  162. package/lib/gate-events.js +193 -0
  163. package/lib/global-flags.js +74 -0
  164. package/lib/greptile-match.js +7 -63
  165. package/lib/harness-capability-matrix.js +380 -0
  166. package/lib/hook-global-installer.js +347 -0
  167. package/lib/hook-renderer.js +451 -0
  168. package/lib/inbox.js +391 -0
  169. package/lib/insights.js +397 -0
  170. package/lib/issue-adapter.js +156 -0
  171. package/lib/issue-backend.js +145 -0
  172. package/lib/issue-render.js +220 -0
  173. package/lib/issue-sync/authority.js +100 -0
  174. package/lib/issue-sync/github-pull.js +184 -0
  175. package/lib/issue-sync/import-primitives.js +98 -0
  176. package/lib/issue-sync/legacy-link-bridge.js +436 -0
  177. package/lib/issue-sync/link-store.js +292 -0
  178. package/lib/issue-sync/project-github.js +123 -0
  179. package/lib/issue-sync/reconcile.js +195 -0
  180. package/lib/issue-sync/schema.js +126 -0
  181. package/lib/kernel/backing-issue.js +305 -0
  182. package/lib/kernel/broker.js +1218 -0
  183. package/lib/kernel/cli-broker-factory.js +130 -0
  184. package/lib/kernel/conflict-signal.js +82 -0
  185. package/lib/kernel/evaluators.js +195 -0
  186. package/lib/kernel/fs-class.js +495 -0
  187. package/lib/kernel/issue-command-contract.js +559 -0
  188. package/lib/kernel/issue-id-resolver.js +186 -0
  189. package/lib/kernel/lease-enforcer.js +158 -0
  190. package/lib/kernel/migrations.js +333 -0
  191. package/lib/kernel/planning-buckets-schema.js +109 -0
  192. package/lib/kernel/projection-jsonl-writer.js +450 -0
  193. package/lib/kernel/readiness-model.js +329 -0
  194. package/lib/kernel/schema.js +356 -0
  195. package/lib/kernel/sqlite-driver.js +2504 -0
  196. package/lib/kernel/taxonomy-validator.js +394 -0
  197. package/lib/lefthook-check.js +8 -4
  198. package/lib/lefthook-wiring.js +413 -0
  199. package/lib/mcp-config-renderer.js +288 -0
  200. package/lib/memory/graphiti-mcp.js +106 -0
  201. package/lib/memory/router.js +387 -0
  202. package/lib/memory/typed-api.js +102 -0
  203. package/lib/memory-digest.js +195 -0
  204. package/lib/merge-rules.js +395 -0
  205. package/lib/migrate-dry-run.js +466 -0
  206. package/lib/orientation.js +863 -0
  207. package/lib/package-manager-remediation.js +103 -0
  208. package/lib/package-root.js +381 -0
  209. package/lib/patch-intent.js +890 -0
  210. package/lib/plugin-catalog.js +3 -4
  211. package/lib/plugin-manager.js +0 -5
  212. package/lib/pr-bundle.js +186 -0
  213. package/lib/pr-monitor/differ.js +195 -0
  214. package/lib/pr-monitor/events.js +0 -0
  215. package/lib/pr-monitor/gather.js +124 -0
  216. package/lib/pr-monitor/journal.js +299 -0
  217. package/lib/pr-monitor/monitor.js +146 -0
  218. package/lib/pr-monitor/render-sticky.js +157 -0
  219. package/lib/pr-monitor/watch-lifecycle.js +95 -0
  220. package/lib/pr-monitor/watch.js +247 -0
  221. package/lib/pr-pull.js +1273 -0
  222. package/lib/pr-shepherd.js +494 -0
  223. package/lib/pr-state-validator.js +59 -0
  224. package/lib/preflight/gates.js +237 -0
  225. package/lib/preflight/runner.js +116 -0
  226. package/lib/project-discovery.js +0 -53
  227. package/lib/project-memory.js +166 -0
  228. package/lib/protected-path-manifest.js +281 -0
  229. package/lib/protected-state-surfaces.js +387 -0
  230. package/lib/release-readiness.js +2089 -0
  231. package/lib/reset.js +59 -45
  232. package/lib/review-adapter.js +68 -0
  233. package/lib/rules-sync.js +260 -0
  234. package/lib/runtime-health.js +332 -23
  235. package/lib/safety-config-renderer.js +268 -0
  236. package/lib/setup-action-log.js +1 -7
  237. package/lib/setup.js +27 -65
  238. package/lib/shell-utils.js +76 -6
  239. package/lib/skills-sync.js +330 -0
  240. package/lib/smart-status/conflicts.js +205 -0
  241. package/lib/smart-status/scoring.js +191 -0
  242. package/lib/status/beads-snapshot.js +145 -0
  243. package/lib/status/presenter.js +216 -0
  244. package/lib/status/snapshot.js +186 -0
  245. package/lib/sync-backend.js +202 -0
  246. package/lib/untrusted-content.js +52 -0
  247. package/lib/upgrade-safety.js +199 -0
  248. package/lib/workflow/enforce-stage.js +298 -47
  249. package/lib/workflow/stage-transition.js +115 -0
  250. package/lib/workflow/stages.js +30 -6
  251. package/lib/workflow/state-manager.js +159 -14
  252. package/lib/workflow/state.js +23 -1
  253. package/lib/workflow-profiles.js +17 -5
  254. package/package.json +46 -36
  255. package/rules/documentation.md +19 -0
  256. package/rules/kernel-tracking.md +26 -0
  257. package/rules/security.md +22 -0
  258. package/rules/tdd.md +20 -0
  259. package/rules/workflow.md +27 -0
  260. package/scripts/auto-backing-issue.js +47 -0
  261. package/scripts/beads-context.sh +165 -22
  262. package/scripts/beads-migrate-to-dolt.sh +7 -0
  263. package/scripts/beads-upgrade-smoke.sh +284 -0
  264. package/scripts/behavioral-judge.sh +115 -11
  265. package/scripts/benchmark.js +349 -63
  266. package/scripts/bootstrap-windows-tools.sh +78 -0
  267. package/scripts/branch-protection.js +2 -3
  268. package/scripts/check-agents.js +34 -137
  269. package/scripts/commitlint.js +3 -1
  270. package/scripts/conflict-detect.sh +3 -0
  271. package/scripts/dep-guard-analyze.js +52 -17
  272. package/scripts/dep-guard-keyword-ripple.js +29 -0
  273. package/scripts/dep-guard-render-review.js +86 -0
  274. package/scripts/dep-guard.sh +64 -232
  275. package/scripts/file-index.sh +3 -0
  276. package/scripts/forge-team/lib/claim.sh +34 -18
  277. package/scripts/forge-team/lib/dashboard.sh +61 -86
  278. package/scripts/forge-team/lib/epic.sh +99 -263
  279. package/scripts/forge-team/lib/hooks.sh +26 -28
  280. package/scripts/forge-team/lib/identity.sh +4 -4
  281. package/scripts/forge-team/lib/sync-github.sh +144 -47
  282. package/scripts/forge-team/lib/verify.sh +93 -83
  283. package/scripts/forge-team/lib/workload.sh +41 -65
  284. package/scripts/forge-team/tests/claim.test.sh +25 -19
  285. package/scripts/forge-team/tests/dashboard.test.sh +31 -46
  286. package/scripts/forge-team/tests/epic.test.sh +52 -71
  287. package/scripts/forge-team/tests/hooks.test.sh +38 -50
  288. package/scripts/forge-team/tests/identity.test.sh +3 -3
  289. package/scripts/forge-team/tests/integration.test.sh +44 -66
  290. package/scripts/forge-team/tests/sync-github.test.sh +183 -79
  291. package/scripts/forge-team/tests/verify.test.sh +37 -46
  292. package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
  293. package/scripts/forge-team/tests/workload.test.sh +32 -66
  294. package/scripts/gen-command-manifest.js +153 -0
  295. package/scripts/gen-embedded-assets.mjs +129 -0
  296. package/scripts/install.ps1 +139 -0
  297. package/scripts/install.sh +268 -0
  298. package/scripts/lib/beads-migrate-to-dolt.mjs +503 -0
  299. package/scripts/lib/release-asset.mjs +84 -0
  300. package/scripts/parity-check.mjs +145 -0
  301. package/scripts/parity-check.test.mjs +58 -0
  302. package/scripts/pin-agentic-workflow-images.js +112 -0
  303. package/scripts/pr-coordinator.sh +3 -0
  304. package/scripts/preflight-sonar.eslint.config.mjs +44 -0
  305. package/scripts/preflight.sh +108 -0
  306. package/scripts/protected-state-check.js +104 -0
  307. package/scripts/smart-status-score.js +31 -0
  308. package/scripts/smart-status-sessions.js +51 -0
  309. package/scripts/smart-status.sh +117 -369
  310. package/scripts/spikes/config-race-bench.js +111 -0
  311. package/scripts/spikes/harness-capability-matrix.js +13 -0
  312. package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
  313. package/scripts/spikes/protected-path-manifest.js +20 -0
  314. package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
  315. package/scripts/sync-agent-skills.js +62 -0
  316. package/scripts/sync-agentic-workflow.js +48 -0
  317. package/scripts/sync-utils.sh +3 -0
  318. package/scripts/test-ci-shard.js +251 -0
  319. package/scripts/test-dashboard.js +188 -52
  320. package/scripts/test-full-suite.js +186 -0
  321. package/scripts/test-profile.js +278 -0
  322. package/scripts/test.js +302 -28
  323. package/scripts/validate.js +143 -0
  324. package/scripts/validate.sh +18 -1
  325. package/skills/claim-safety/SKILL.md +102 -0
  326. package/skills/claim-safety/evals/evals.json +46 -0
  327. package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +46 -52
  328. package/skills/dev/evals/evals.json +50 -0
  329. package/skills/hermes-forge/SKILL.md +185 -0
  330. package/skills/hermes-forge/evals/evals.json +46 -0
  331. package/skills/issue-basics/SKILL.md +111 -0
  332. package/skills/issue-basics/evals/evals.json +46 -0
  333. package/skills/kernel/SKILL.md +166 -0
  334. package/skills/kernel/evals/evals.json +50 -0
  335. package/skills/memory/SKILL.md +102 -0
  336. package/skills/parallel-deep-research/SKILL.md +14 -11
  337. package/skills/parallel-deep-research/evals/evals.json +11 -27
  338. package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +134 -159
  339. package/skills/plan/evals/evals.json +42 -0
  340. package/skills/research/SKILL.md +195 -0
  341. package/skills/research/evals/evals.json +42 -0
  342. package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
  343. package/skills/review/evals/evals.json +42 -0
  344. package/skills/rollback/SKILL.md +110 -0
  345. package/skills/rollback/evals/evals.json +46 -0
  346. package/skills/rollback/references/methods.md +204 -0
  347. package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
  348. package/skills/shepherd/SKILL.md +66 -0
  349. package/skills/shepherd/evals/evals.json +42 -0
  350. package/skills/ship/SKILL.md +251 -0
  351. package/skills/ship/evals/evals.json +42 -0
  352. package/skills/smith/SKILL.md +142 -0
  353. package/skills/smith/evals/evals.json +46 -0
  354. package/skills/smith/references/autonomy-and-gates.md +94 -0
  355. package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
  356. package/skills/sonarcloud/evals/evals.json +46 -0
  357. package/skills/sonarcloud-analysis/SKILL.md +18 -13
  358. package/skills/sonarcloud-analysis/evals/evals.json +11 -15
  359. package/skills/status/SKILL.md +102 -0
  360. package/skills/status/evals/evals.json +50 -0
  361. package/skills/triage-ready/SKILL.md +121 -0
  362. package/skills/triage-ready/evals/evals.json +42 -0
  363. package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
  364. package/skills/validate/evals/evals.json +42 -0
  365. package/skills/verify/SKILL.md +299 -0
  366. package/skills/verify/evals/evals.json +50 -0
  367. package/.claude/commands/dev.md +0 -345
  368. package/.claude/commands/plan.md +0 -566
  369. package/.claude/commands/premerge.md +0 -186
  370. package/.claude/commands/research.md +0 -42
  371. package/.claude/commands/review.md +0 -451
  372. package/.claude/commands/rollback.md +0 -721
  373. package/.claude/commands/ship.md +0 -213
  374. package/.claude/commands/sonarcloud.md +0 -152
  375. package/.claude/commands/status.md +0 -90
  376. package/.claude/commands/validate.md +0 -288
  377. package/.claude/commands/verify.md +0 -269
  378. package/.claude/rules/workflow.md +0 -121
  379. package/.cline/workflows/dev.md +0 -342
  380. package/.cline/workflows/plan.md +0 -563
  381. package/.cline/workflows/premerge.md +0 -183
  382. package/.cline/workflows/research.md +0 -39
  383. package/.cline/workflows/review.md +0 -448
  384. package/.cline/workflows/rollback.md +0 -718
  385. package/.cline/workflows/ship.md +0 -210
  386. package/.cline/workflows/sonarcloud.md +0 -146
  387. package/.cline/workflows/status.md +0 -87
  388. package/.cline/workflows/validate.md +0 -285
  389. package/.cline/workflows/verify.md +0 -266
  390. package/.codex/config.toml +0 -11
  391. package/.codex/skills/dev/SKILL.md +0 -345
  392. package/.codex/skills/plan/SKILL.md +0 -566
  393. package/.codex/skills/premerge/SKILL.md +0 -186
  394. package/.codex/skills/research/SKILL.md +0 -42
  395. package/.codex/skills/review/SKILL.md +0 -451
  396. package/.codex/skills/rollback/SKILL.md +0 -721
  397. package/.codex/skills/ship/SKILL.md +0 -213
  398. package/.codex/skills/sonarcloud/SKILL.md +0 -149
  399. package/.codex/skills/status/SKILL.md +0 -90
  400. package/.codex/skills/validate/SKILL.md +0 -288
  401. package/.codex/skills/verify/SKILL.md +0 -269
  402. package/.cursor/commands/dev.md +0 -342
  403. package/.cursor/commands/plan.md +0 -563
  404. package/.cursor/commands/premerge.md +0 -183
  405. package/.cursor/commands/research.md +0 -39
  406. package/.cursor/commands/review.md +0 -448
  407. package/.cursor/commands/ship.md +0 -210
  408. package/.cursor/commands/sonarcloud.md +0 -146
  409. package/.cursor/commands/status.md +0 -87
  410. package/.cursor/commands/validate.md +0 -285
  411. package/.cursor/commands/verify.md +0 -266
  412. package/.cursorrules +0 -149
  413. package/.github/prompts/premerge.prompt.md +0 -188
  414. package/.github/prompts/research.prompt.md +0 -44
  415. package/.github/prompts/rollback.prompt.md +0 -723
  416. package/.github/prompts/ship.prompt.md +0 -215
  417. package/.github/prompts/status.prompt.md +0 -92
  418. package/.github/prompts/verify.prompt.md +0 -271
  419. package/.github/workflows/beads-to-github.yml +0 -56
  420. package/.github/workflows/github-to-beads.yml +0 -97
  421. package/.kilocode/workflows/dev.md +0 -346
  422. package/.kilocode/workflows/plan.md +0 -567
  423. package/.kilocode/workflows/premerge.md +0 -187
  424. package/.kilocode/workflows/research.md +0 -43
  425. package/.kilocode/workflows/review.md +0 -452
  426. package/.kilocode/workflows/rollback.md +0 -722
  427. package/.kilocode/workflows/ship.md +0 -214
  428. package/.kilocode/workflows/sonarcloud.md +0 -150
  429. package/.kilocode/workflows/status.md +0 -91
  430. package/.kilocode/workflows/validate.md +0 -289
  431. package/.kilocode/workflows/verify.md +0 -270
  432. package/.opencode/commands/dev.md +0 -345
  433. package/.opencode/commands/plan.md +0 -566
  434. package/.opencode/commands/premerge.md +0 -186
  435. package/.opencode/commands/research.md +0 -42
  436. package/.opencode/commands/review.md +0 -451
  437. package/.opencode/commands/rollback.md +0 -721
  438. package/.opencode/commands/ship.md +0 -213
  439. package/.opencode/commands/sonarcloud.md +0 -149
  440. package/.opencode/commands/status.md +0 -90
  441. package/.opencode/commands/validate.md +0 -288
  442. package/.opencode/commands/verify.md +0 -269
  443. package/.roo/commands/dev.md +0 -346
  444. package/.roo/commands/plan.md +0 -567
  445. package/.roo/commands/premerge.md +0 -187
  446. package/.roo/commands/research.md +0 -43
  447. package/.roo/commands/review.md +0 -452
  448. package/.roo/commands/rollback.md +0 -722
  449. package/.roo/commands/ship.md +0 -214
  450. package/.roo/commands/sonarcloud.md +0 -150
  451. package/.roo/commands/status.md +0 -91
  452. package/.roo/commands/validate.md +0 -289
  453. package/.roo/commands/verify.md +0 -270
  454. package/docs/BEADS_GITHUB_SYNC.md +0 -255
  455. package/docs/GREPTILE_SETUP.md +0 -400
  456. package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
  457. package/docs/SETUP.md +0 -663
  458. package/docs/VALIDATION.md +0 -363
  459. package/lib/agents/cline.plugin.json +0 -29
  460. package/lib/agents/copilot.plugin.json +0 -24
  461. package/lib/agents/kilocode.plugin.json +0 -22
  462. package/lib/agents/opencode.plugin.json +0 -23
  463. package/lib/agents/roo.plugin.json +0 -30
  464. package/lib/beads-health-check.js +0 -143
  465. package/lib/commands/commands-reset.js +0 -147
  466. package/opencode.json +0 -67
  467. package/scripts/beads-context.test.js +0 -567
  468. package/scripts/github-beads-sync/comment.mjs +0 -64
  469. package/scripts/github-beads-sync/config.mjs +0 -148
  470. package/scripts/github-beads-sync/github-api.mjs +0 -131
  471. package/scripts/github-beads-sync/index.mjs +0 -332
  472. package/scripts/github-beads-sync/label-mapper.mjs +0 -54
  473. package/scripts/github-beads-sync/mapping.mjs +0 -78
  474. package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
  475. package/scripts/github-beads-sync/reverse-sync.mjs +0 -138
  476. package/scripts/github-beads-sync/run-bd.mjs +0 -161
  477. package/scripts/github-beads-sync/sanitize.mjs +0 -121
  478. package/scripts/github-beads-sync.config.json +0 -26
  479. package/scripts/sync-commands.js +0 -600
@@ -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,6 +332,320 @@ 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
+ const match = rows.find(row => row && row.branch === currentBranch && row.issue_id);
521
+ if (match) return match.issue_id;
522
+ } catch {
523
+ // fall through to branch-name parsing
524
+ }
525
+ }
526
+ const encoded = PLAN_ISSUE_UUID_RE.exec(currentBranch || '');
527
+ return encoded ? encoded[0] : null;
528
+ }
529
+
530
+ /**
531
+ * Refuse to link issue B onto a branch already bound to issue A (F4c): otherwise
532
+ * plan would link B while stage read/writes flow to A (split state). Consults the
533
+ * kernel worktree registry (via a built driver when the CLI supplies none) so a
534
+ * plan-created slug branch — whose name lacks a UUID — is still caught.
535
+ * @returns {Promise<string|null>} an error message when there is a conflict.
536
+ * @private
537
+ */
538
+ async function detectBranchIssueConflict(options, explicitIssueId) {
539
+ const current = getCurrentBranch();
540
+ if (isDefaultBranch(current)) return null;
541
+ const boundIssue = await withPlanDriver(options, driver => currentBranchIssueFromDriver(driver, current));
542
+ if (boundIssue && boundIssue !== explicitIssueId) {
543
+ return `Current branch ${current} is already bound to issue ${boundIssue}; refusing to link ${explicitIssueId} (split state). Switch branches or link the matching issue.`;
544
+ }
545
+ return null;
546
+ }
547
+
548
+ /**
549
+ * Persist the branch->issue linkage into the kernel worktree registry (F1) so a
550
+ * plan-created branch resolves to its issue for kernel-authoritative stage
551
+ * state. Best-effort, kernel-only. @private
552
+ */
553
+ async function registerBranchIssueLinkage(options, branch, issueId) {
554
+ if (!branch || !issueId) return;
555
+ const cwd = options.projectRoot || process.cwd();
556
+ await withPlanDriver(options, driver => {
557
+ if (!driver || typeof driver.registerWorktree !== 'function') return;
558
+ try {
559
+ driver.registerWorktree({
560
+ git_common_dir: getGitCommonDir(cwd) || cwd,
561
+ path: cwd,
562
+ branch,
563
+ actor: null,
564
+ issue_id: issueId,
565
+ work_folder: null,
566
+ registered_at: new Date().toISOString(),
567
+ state: 'active',
568
+ });
569
+ } catch {
570
+ // best-effort: linkage failure must not fail plan
571
+ }
572
+ });
573
+ }
574
+
575
+ /**
576
+ * Resolve the tracking issue for a plan: LINK an explicit issue, else CREATE via
577
+ * the active backend (the kernel default needs no Beads). Extracted to avoid a
578
+ * nested ternary in executePlan.
579
+ *
580
+ * @returns {Promise<{success: boolean, issueId?: string, error?: string}>}
581
+ * @private
582
+ */
583
+ async function resolveTrackingIssue({ explicitIssueId, issueBackend, featureName, researchPath, scope, options }) {
584
+ if (explicitIssueId) {
585
+ return linkExistingIssue(explicitIssueId, { ...options, issueBackend });
586
+ }
587
+ if (issueBackend === 'kernel') {
588
+ return createKernelIssue(featureName, researchPath, scope, options);
589
+ }
590
+ return createBeadsIssue(featureName, researchPath, scope);
591
+ }
592
+
593
+ async function createKernelIssue(featureName, researchPath, scope, options = {}) {
594
+ if (!featureName || !researchPath) {
595
+ return {
596
+ success: false,
597
+ error: 'Feature name and research path are required',
598
+ };
599
+ }
600
+
601
+ if (scope !== 'tactical' && scope !== 'strategic') {
602
+ return {
603
+ success: false,
604
+ error: `Invalid scope '${scope}'. Must be 'tactical' or 'strategic'`,
605
+ };
606
+ }
607
+
608
+ const built = buildFeatureIssueDescription(featureName, researchPath, scope);
609
+ if (built.error) {
610
+ return { success: false, error: built.error };
611
+ }
612
+ const description = built.description;
613
+
614
+ const run = options.runIssueOperation || runIssueOperation;
615
+ const projectRoot = options.projectRoot || process.cwd();
616
+
617
+ try {
618
+ const result = await run(
619
+ 'create',
620
+ [`--title=${featureName}`, `--description=${description}`, '--type=feature', '--priority=2'],
621
+ projectRoot,
622
+ { issueBackend: 'kernel', useKernelBroker: true, kernelBroker: options.kernelBroker },
623
+ );
624
+
625
+ // The kernel returns the issue-command contract ({ ok, data: { id } }); a failure
626
+ // is { ok:false, error }. Guard both the contract shape and any { success:false }.
627
+ if (!result || result.ok === false || result.success === false) {
628
+ const message = result?.error || 'Kernel issue creation failed';
629
+ return { success: false, error: `Failed to create kernel issue: ${message}` };
630
+ }
631
+
632
+ const issueId = result?.data?.id ?? result?.issueId ?? result?.id;
633
+ if (!issueId) {
634
+ return {
635
+ success: false,
636
+ error: 'Failed to extract issue ID from kernel create result',
637
+ };
638
+ }
639
+
640
+ return { success: true, issueId, description };
641
+ } catch (error) {
642
+ return {
643
+ success: false,
644
+ error: `Failed to create kernel issue: ${error.message}`,
645
+ };
646
+ }
647
+ }
648
+
297
649
  /**
298
650
  * Create feature branch
299
651
  * Creates and checks out a new git branch following feat/<slug> convention
@@ -596,13 +948,18 @@ function applyYAGNIFilter({ task, tasks, designDoc } = {}) {
596
948
  * @returns {Promise<{
597
949
  * success: boolean,
598
950
  * scope?: 'tactical'|'strategic',
951
+ * issueBackend?: 'kernel'|'beads',
952
+ * issueId?: string,
599
953
  * beadsIssueId?: string,
600
954
  * branchName?: string,
601
955
  *
602
956
  * summary?: string,
603
957
  * nextCommand?: string,
604
958
  * error?: string
605
- * }>} Execution result
959
+ * }>} Execution result. `issueId` is the created issue id under the active
960
+ * backend; `issueBackend` names that backend. `beadsIssueId` is a deprecated
961
+ * alias of `issueId` kept for backward compatibility (it does NOT imply the
962
+ * issue came from Beads — on the kernel path it holds the kernel issue id).
606
963
  * @example
607
964
  * const result = await executePlan('Payment Integration');
608
965
  * if (result.success) {
@@ -610,7 +967,7 @@ function applyYAGNIFilter({ task, tasks, designDoc } = {}) {
610
967
  * console.log('Next:', result.nextCommand);
611
968
  * }
612
969
  */
613
- async function executePlan(featureName) { // NOSONAR S3776
970
+ async function executePlan(featureName, options = {}) { // NOSONAR S3776
614
971
  if (!featureName || typeof featureName !== 'string') {
615
972
  return {
616
973
  success: false,
@@ -618,6 +975,15 @@ async function executePlan(featureName) { // NOSONAR S3776
618
975
  };
619
976
  }
620
977
 
978
+ // Resolve the active issue backend (explicit opts > env > .forge/config.yaml >
979
+ // default 'kernel'). Kernel is bd-free, so planning works with no Beads install.
980
+ const issueBackend = resolveIssueBackend({
981
+ deps: options,
982
+ env: options.env || process.env,
983
+ projectRoot: options.projectRoot,
984
+ warn: () => {},
985
+ });
986
+
621
987
  const featureSlug = featureName.toLowerCase()
622
988
  .replaceAll(/[^a-z0-9-]/g, '-')
623
989
  .split('-').filter(Boolean).join('-');
@@ -644,17 +1010,37 @@ async function executePlan(featureName) { // NOSONAR S3776
644
1010
  // Step 2: Detect scope (tactical vs strategic)
645
1011
  const scope = detectScope(research.content);
646
1012
 
647
- // Step 3: Create Beads issue
648
- const beads = createBeadsIssue(featureName, research.path, scope.type);
649
- if (!beads.success) {
1013
+ // Step 3: Resolve the tracking issue. `--issue <id>` LINKS an existing issue
1014
+ // (claim-first flow) instead of creating a duplicate (B4). Otherwise create
1015
+ // via the active backend — kernel (default) routes through the kernel broker
1016
+ // (no bd); beads preserves the bd path.
1017
+ const explicitIssueId = options.issue || options.issueId || null;
1018
+
1019
+ // F4c: never link issue B onto a branch already bound to issue A.
1020
+ if (explicitIssueId) {
1021
+ const conflict = await detectBranchIssueConflict({ ...options, issueBackend }, explicitIssueId);
1022
+ if (conflict) {
1023
+ return { success: false, error: conflict };
1024
+ }
1025
+ }
1026
+
1027
+ const issue = await resolveTrackingIssue({
1028
+ explicitIssueId, issueBackend, featureName, researchPath: research.path, scope: scope.type, options,
1029
+ });
1030
+ if (!issue.success) {
650
1031
  return {
651
1032
  success: false,
652
- error: `Failed to create Beads issue: ${beads.error}`,
1033
+ error: explicitIssueId
1034
+ ? `Failed to link issue: ${issue.error}`
1035
+ : `Failed to create issue: ${issue.error}`,
653
1036
  };
654
1037
  }
655
1038
 
656
- // Step 4: Create feature branch
657
- const branch = createFeatureBranch(featureSlug);
1039
+ // Step 4: Resolve the feature branch. When linking, reuse the current
1040
+ // feature branch instead of forking a second branch (B4).
1041
+ const branch = explicitIssueId
1042
+ ? reuseOrCreateFeatureBranch(featureSlug)
1043
+ : createFeatureBranch(featureSlug);
658
1044
  if (!branch.success) {
659
1045
  return {
660
1046
  success: false,
@@ -662,13 +1048,27 @@ async function executePlan(featureName) { // NOSONAR S3776
662
1048
  };
663
1049
  }
664
1050
 
1051
+ // F1: persist the branch->issue linkage so a plan-created branch resolves
1052
+ // to its issue for kernel-authoritative stage state (dev/validate/ship).
1053
+ // Kernel-only, best-effort.
1054
+ if (issueBackend === 'kernel') {
1055
+ await registerBranchIssueLinkage({ ...options, issueBackend }, branch.branchName, issue.issueId);
1056
+ }
1057
+
665
1058
  // Build result summary
666
1059
  const result = {
667
1060
  success: true,
668
1061
  scope: scope.type,
669
- beadsIssueId: beads.issueId,
1062
+ issueBackend,
1063
+ issueId: issue.issueId,
1064
+ // Deprecated alias of issueId, retained for backward compatibility. It does
1065
+ // NOT imply the Beads backend — on the kernel path it holds the kernel id.
1066
+ beadsIssueId: issue.issueId,
670
1067
  branchName: branch.branchName,
671
- summary: `Plan created for ${featureName} (${scope.type} scope)`,
1068
+ linked: Boolean(explicitIssueId),
1069
+ summary: explicitIssueId
1070
+ ? `Plan linked to existing issue ${issue.issueId} (${scope.type} scope)`
1071
+ : `Plan created for ${featureName} (${scope.type} scope)`,
672
1072
  // Strategic path: /propose not yet implemented — falls through to /dev until it is
673
1073
  nextCommand: '/dev',
674
1074
  };
@@ -685,14 +1085,24 @@ async function executePlan(featureName) { // NOSONAR S3776
685
1085
  module.exports = {
686
1086
  name: 'plan',
687
1087
  description: 'Create implementation plan from researched feature context',
688
- handler: async (args) => {
689
- const result = await executePlan(args[0]);
1088
+ handler: async (args, _flags, projectRoot, opts = {}) => {
1089
+ const { featureName, issueId, issueFlagError } = parsePlanArgs(args);
1090
+ // F5: `--issue` with no value must ERROR, never silently create a duplicate.
1091
+ if (issueFlagError) {
1092
+ return { success: false, error: '--issue requires a value (an existing issue id to link).' };
1093
+ }
1094
+ const result = await executePlan(featureName, {
1095
+ ...opts,
1096
+ projectRoot,
1097
+ issue: issueId ?? opts.issue ?? opts.issueId,
1098
+ });
690
1099
  if (!result.success) {
691
1100
  return result;
692
1101
  }
693
1102
 
694
- const lines = [`Plan created: ${result.summary || result.branchName || args[0]}`];
695
- if (result.beadsIssueId) lines.push(`Beads: ${result.beadsIssueId}`);
1103
+ const header = result.linked ? 'Plan linked' : 'Plan created';
1104
+ const lines = [`${header}: ${result.summary || result.branchName || featureName}`];
1105
+ if (result.issueId) lines.push(`${issueBackendLabel(result.issueBackend)}: ${result.issueId}`);
696
1106
  if (result.branchName) lines.push(`Branch: ${result.branchName}`);
697
1107
  if (result.nextCommand) lines.push(`Next: ${result.nextCommand}`);
698
1108
 
@@ -704,10 +1114,12 @@ module.exports = {
704
1114
  readResearchDoc,
705
1115
  detectScope,
706
1116
  createBeadsIssue,
1117
+ createKernelIssue,
707
1118
  createFeatureBranch,
708
1119
  extractDesignDecisions,
709
1120
  extractTasksFromResearch,
710
1121
  detectDRYViolation,
711
1122
  applyYAGNIFilter,
712
1123
  executePlan,
1124
+ issueBackendLabel,
713
1125
  };