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
@@ -1,69 +1,364 @@
1
1
  'use strict';
2
2
 
3
- const { execFileSync } = require('node:child_process');
3
+ const { runIssueOperation: defaultRunIssueOperation } = require('../forge-issues');
4
+ const { resolveIssueBackend, hasExplicitBackendSignal, shouldUseKernelBroker } = require('../issue-backend');
5
+ const {
6
+ ISSUE_COMMAND_SCHEMA_VERSION,
7
+ ISSUE_COMMAND_ERROR_SCHEMA_VERSION,
8
+ ISSUE_COMMAND_EXIT_CODES,
9
+ resolveNextCommands,
10
+ normalizePriority,
11
+ } = require('../kernel/issue-command-contract');
12
+ const { getResolvedRuntimeGraph } = require('../core/runtime-graph');
13
+ const { renderIssueEnvelope } = require('../issue-render');
14
+ const { recordStageTransition } = require('../workflow/stage-transition');
4
15
 
16
+ // The Forge issue command surface. Each subcommand routes through the shared
17
+ // runIssueOperation, which selects the active backend (Kernel by --kernel /
18
+ // --issue-backend kernel / FORGE_ISSUE_BACKEND=kernel; Beads otherwise) and performs
19
+ // any backend-specific argument translation. This module therefore carries NO direct
20
+ // issue-tracker invocation or argv translation — those live in the backend
21
+ // abstraction (lib/forge-issues.js + the issue adapters).
5
22
  const SUBCOMMANDS = {
6
23
  create: {
7
- description: 'Create a Beads issue via Forge',
8
- usage: 'forge create [title] [bd-create-flags]',
9
- helpCommand: 'create',
10
- buildBdArgs: (args) => ['create', ...args],
24
+ description: 'Create an issue via Forge',
25
+ usage: 'forge create [title] [flags]',
11
26
  },
12
27
  update: {
13
- description: 'Update a Beads issue via Forge',
14
- usage: 'forge update <id...> [bd-update-flags]',
15
- helpCommand: 'update',
16
- buildBdArgs: (args) => ['update', ...args],
28
+ description: 'Update an issue via Forge',
29
+ usage: 'forge update <id...> [flags]',
17
30
  },
18
31
  claim: {
19
- description: 'Claim a Beads issue via Forge',
20
- usage: 'forge claim <id> [bd-update-flags]',
21
- helpCommand: 'update',
22
- buildBdArgs: (args) => {
23
- const [issueId, ...rest] = args;
24
- if (!issueId) {
25
- return { error: 'Missing issue id. Usage: forge claim <id> [bd-update-flags]' };
26
- }
27
- return ['update', issueId, '--claim', ...rest];
28
- },
32
+ description: 'Claim an issue via Forge',
33
+ usage: 'forge claim <id> [flags]',
34
+ },
35
+ release: {
36
+ description: 'Release a Kernel issue claim via Forge',
37
+ usage: 'forge release <id>',
38
+ },
39
+ comment: {
40
+ description: 'Add an issue comment via Forge',
41
+ usage: 'forge comment <id> <body...>',
29
42
  },
30
43
  close: {
31
- description: 'Close a Beads issue via Forge',
32
- usage: 'forge close <id...> [bd-close-flags]',
33
- helpCommand: 'close',
34
- buildBdArgs: (args) => ['close', ...args],
44
+ description: 'Close an issue via Forge',
45
+ usage: 'forge close <id...> [flags]',
35
46
  },
36
47
  show: {
37
- description: 'Show a Beads issue via Forge',
38
- usage: 'forge show <id> [bd-show-flags]',
39
- helpCommand: 'show',
40
- buildBdArgs: (args) => ['show', ...args],
48
+ description: 'Show an issue via Forge',
49
+ usage: 'forge show <id> [flags]',
41
50
  },
42
51
  list: {
43
- description: 'List Beads issues via Forge',
44
- usage: 'forge list [bd-list-flags]',
45
- helpCommand: 'list',
46
- buildBdArgs: (args) => ['list', ...args],
52
+ description: 'List issues via Forge',
53
+ usage: 'forge list [flags]',
47
54
  },
48
55
  ready: {
49
- description: 'Show ready Beads issues via Forge',
50
- usage: 'forge ready [bd-ready-flags]',
51
- helpCommand: 'ready',
52
- buildBdArgs: (args) => ['ready', ...args],
56
+ description: 'Show ready issues via Forge',
57
+ usage: 'forge ready [flags]',
58
+ },
59
+ search: {
60
+ description: 'Search issues via Forge',
61
+ usage: 'forge issue search <query> [flags]',
62
+ },
63
+ stats: {
64
+ description: 'Show issue statistics via Forge',
65
+ usage: 'forge issue stats [flags]',
66
+ },
67
+ // KAP-7: derived read queries. The backend abstraction maps each to its tracker
68
+ // equivalent (the Kernel passes the operation name through unchanged; the Beads
69
+ // backend maps each to its passthrough subcommand). They are READS, so they are
70
+ // intentionally NOT in WRITE_SUBCOMMANDS.
71
+ blocked: {
72
+ description: 'Show blocked issues via Forge',
73
+ usage: 'forge issue blocked [flags]',
74
+ },
75
+ stale: {
76
+ description: 'Show stale issues via Forge',
77
+ usage: 'forge issue stale [--days <n>]',
78
+ },
79
+ orphans: {
80
+ description: 'Show issues with dangling dependency edges via Forge',
81
+ usage: 'forge issue orphans',
82
+ },
83
+ // KAP-12: read-only content lint — issues missing required content
84
+ // (task/bug with no acceptance_criteria). A READ, so NOT in WRITE_SUBCOMMANDS.
85
+ lint: {
86
+ description: 'Show issues missing required content via Forge',
87
+ usage: 'forge issue lint',
88
+ },
89
+ // Epic support: an epic's DIRECT children + kernel-computed rollup
90
+ // (done-only percentage, per-status counts, blocked list). A READ, so
91
+ // intentionally NOT in WRITE_SUBCOMMANDS.
92
+ children: {
93
+ description: 'Show an epic\'s child issues + rollup via Forge',
94
+ usage: 'forge issue children <epic-id> [--json]',
95
+ },
96
+ // Lease-ownership verification (kernel d71a824b). A READ whose EXIT CODE encodes the
97
+ // verdict: exit 0 iff the resolving actor holds the live lease, non-zero otherwise.
98
+ // A claim returning ok:true does not prove ownership (a duplicate replay also returns
99
+ // ok:true), so a worker calls `owns` to CONFIRM before mutating a claimed issue. A
100
+ // READ, so intentionally NOT in WRITE_SUBCOMMANDS.
101
+ owns: {
102
+ description: 'Verify the current actor holds the live lease on an issue via Forge',
103
+ usage: 'forge issue owns <id> [--json]',
104
+ },
105
+ // Active-lease listing (kernel issue 7dc229d4). A bare passthrough to the Kernel
106
+ // lease table (kernel_claims); Beads has no lease table to enumerate, so
107
+ // forge-issues.js rejects the Beads path explicitly. A READ, so intentionally NOT
108
+ // in WRITE_SUBCOMMANDS.
109
+ claims: {
110
+ description: 'Show active issue leases (claims) via Forge',
111
+ usage: 'forge issue claims [--json]',
112
+ },
113
+ dep: {
114
+ description: 'Manage issue dependencies via Forge',
115
+ usage: 'forge issue dep <add|remove> <issue-id> <blocks-issue-id>',
116
+ // The dep subcommand fans out to two actions. Declaring them here keeps the
117
+ // supported action set discoverable from the command spec (and lets the
118
+ // release-readiness gate confirm dep add/remove are present).
119
+ actions: {
120
+ add: { description: 'Add a dependency edge (issue-id blocked by blocks-issue-id)' },
121
+ remove: { description: 'Remove a dependency edge' },
122
+ },
53
123
  },
54
124
  };
55
125
 
56
- function normalizeArgs(args = []) {
57
- return args.filter(arg => arg !== '--');
126
+ const WRITE_SUBCOMMANDS = new Set(['create', 'update', 'claim', 'release', 'comment', 'close', 'dep']);
127
+
128
+ // Human-first reads (kernel issue a9bbd065, 0.1.0 critical path): these
129
+ // subcommands default to a compact text rendering (lib/issue-render.js) instead
130
+ // of the raw forge.issue.v1 envelope. --json (or FORGE_JSON=1) restores the
131
+ // byte-identical contract output for scripts. DELIBERATE breaking change to the
132
+ // DEFAULT output only, approved before the 0.1.0 API freeze — the kernel
133
+ // contract itself is untouched.
134
+ const HUMAN_RENDERED_SUBCOMMANDS = new Set(['ready', 'list', 'show']);
135
+ // Writes render a human confirmation ONLY at an interactive TTY; piped/CI callers keep
136
+ // the machine-parseable JSON envelope by default so scripts work without --json (842a8be7).
137
+ const HUMAN_RENDERED_WRITE_SUBCOMMANDS = new Set(['create', 'update', 'claim', 'release', 'comment', 'close']);
138
+ const DEP_ACTIONS = Object.keys(SUBCOMMANDS.dep.actions);
139
+
140
+ // ---------------------------------------------------------------------------
141
+ // Check-after-write verification (gate.issue_verify, kernel issue 5f928cd0).
142
+ //
143
+ // The kernel has two PROVEN cases where a mutation's ok:true lied about the
144
+ // stored outcome: 145d9ad1 (close --reason/closed_at dropped by the
145
+ // kernel_issues projection) and d71a824b (idempotent claim replay telling a
146
+ // losing agent it won). So after a successful kernel mutation, this boundary
147
+ // re-reads the issue THROUGH THE SAME runner/broker and asserts the intended
148
+ // delta actually landed, emitting `verified: true|false` + `mismatches: []` in
149
+ // the envelope. WARN MODE ONLY: a mismatch prints a warning and the write's
150
+ // success/exit code are never touched; a verification READ error yields
151
+ // `verified: null` (unknown) rather than failing the write. Governed by the
152
+ // default-ON, unlocked `gate.issue_verify` (runtime-graph) — disable via
153
+ // `forge gate disable gate.issue_verify`, which skips the read-back entirely.
154
+ const ISSUE_VERIFY_GATE_ID = 'gate.issue_verify';
155
+ const VERIFIED_SUBCOMMANDS = new Set(['create', 'update', 'close', 'claim', 'comment']);
156
+
157
+ // Resolve whether gate.issue_verify is enabled for this project. An injected
158
+ // opts.resolveRuntimeGraph wins (tests); an unresolvable config (lint errors)
159
+ // SKIPS verification rather than breaking the write path.
160
+ function isIssueVerifyEnabled(projectRoot, opts = {}) {
161
+ const resolveGraph = opts.resolveRuntimeGraph || getResolvedRuntimeGraph;
162
+ try {
163
+ const graph = resolveGraph({ projectRoot });
164
+ const gate = (graph.gates || []).find(candidate => candidate.id === ISSUE_VERIFY_GATE_ID);
165
+ return gate ? gate.enabled !== false : true;
166
+ } catch {
167
+ return false;
168
+ }
169
+ }
170
+
171
+ // Minimal --flag parser mirroring the broker's parseFlagPairs semantics
172
+ // (--key value and --key=value; last value wins) so the expected delta is
173
+ // derived from the SAME tokens the broker consumed.
174
+ function parseVerifyFlags(args = []) {
175
+ const flags = {};
176
+ for (let index = 0; index < args.length; index += 1) {
177
+ const token = args[index];
178
+ if (typeof token !== 'string' || !token.startsWith('--')) continue;
179
+ const body = token.slice(2);
180
+ const eq = body.indexOf('=');
181
+ if (eq !== -1) {
182
+ flags[body.slice(0, eq)] = body.slice(eq + 1);
183
+ continue;
184
+ }
185
+ const next = args[index + 1];
186
+ if (typeof next === 'string' && !next.startsWith('--')) {
187
+ flags[body] = next;
188
+ index += 1;
189
+ } else {
190
+ flags[body] = true;
191
+ }
192
+ }
193
+ return flags;
194
+ }
195
+
196
+ // The exact-match fields a mutation promised, derived from its flags. Only the
197
+ // REQUESTED fields are asserted (no coupling to backend defaults). Close always
198
+ // asserts the terminal status the broker resolves (explicit --status wins, else
199
+ // 'done' — mirrors the driver's resolveMutationStatus).
200
+ function expectedIssueFields(subcommand, flags) {
201
+ const expected = {};
202
+ if (subcommand === 'create' || subcommand === 'update') {
203
+ if (typeof flags.title === 'string') expected.title = flags.title;
204
+ }
205
+ if (subcommand === 'create' && typeof flags.type === 'string') expected.type = flags.type;
206
+ if (subcommand === 'update') {
207
+ if (typeof flags.status === 'string') expected.status = flags.status;
208
+ if (typeof flags.assignee === 'string') expected.assignee = flags.assignee;
209
+ if (typeof flags.priority === 'string') expected.priority = normalizePriority(flags.priority);
210
+ }
211
+ if (subcommand === 'close') {
212
+ expected.status = typeof flags.status === 'string' ? flags.status : 'done';
213
+ }
214
+ return expected;
215
+ }
216
+
217
+ function collectFieldMismatches(expected, data) {
218
+ const mismatches = [];
219
+ for (const [field, value] of Object.entries(expected)) {
220
+ const stored = data ? data[field] : undefined;
221
+ if (stored !== value) {
222
+ mismatches.push(`${field}: expected ${JSON.stringify(value)}, read back ${JSON.stringify(stored ?? null)}`);
223
+ }
224
+ }
225
+ return mismatches;
226
+ }
227
+
228
+ // Close-specific presence checks — the regression trap for 145d9ad1, where the
229
+ // projection silently dropped closed_at and the --reason text.
230
+ function collectCloseMismatches(flags, data, mismatches) {
231
+ if (data.closed_at === null || data.closed_at === undefined) {
232
+ mismatches.push('closed_at: expected a close timestamp, read back null (145d9ad1-class projection drop)');
233
+ }
234
+ const reasonRequested = typeof flags.reason === 'string' || typeof flags['close-reason'] === 'string';
235
+ if (reasonRequested && (data.close_reason === null || data.close_reason === undefined)) {
236
+ mismatches.push('close_reason: expected the requested reason to persist, read back null (145d9ad1-class projection drop)');
237
+ }
238
+ }
239
+
240
+ // Claim verification uses the claim-safety primitive directly: `owns` encodes
241
+ // "does the RESOLVING ACTOR hold the live lease" — exactly the check that
242
+ // catches a d71a824b phantom-claim replay (ok:true for a lease someone else holds).
243
+ async function verifyClaimDelta(runner, issueId, projectRoot, opts) {
244
+ const readBack = await runner('owns', [issueId, '--json'], projectRoot, opts);
245
+ if (!readBack || readBack.ok !== true || !readBack.data || typeof readBack.data !== 'object') {
246
+ return { verified: null };
247
+ }
248
+ if (readBack.data.owned === true) {
249
+ return { verified: true, mismatches: [] };
250
+ }
251
+ const heldBy = JSON.stringify(readBack.data.claimed_by ?? null);
252
+ return {
253
+ verified: false,
254
+ mismatches: [
255
+ `claimed_by: expected the resolved actor to hold the live lease, but owns reports held by ${heldBy} `
256
+ + '(d71a824b-class phantom-claim replay)',
257
+ ],
258
+ };
58
259
  }
59
260
 
60
- function getExecOptions(projectRoot) {
261
+ // Comment verification matches the MINTED comment_id in the re-read comments
262
+ // array (show returns comments with ids), sidestepping the count-increment
263
+ // approach that would need a pre-write read.
264
+ function verifyCommentDelta(result, data) {
265
+ const commentId = result.data && typeof result.data.comment_id === 'string' ? result.data.comment_id : null;
266
+ if (!commentId) return { verified: null };
267
+ const comments = Array.isArray(data.comments) ? data.comments : [];
268
+ if (comments.some(comment => comment && comment.id === commentId)) {
269
+ return { verified: true, mismatches: [] };
270
+ }
61
271
  return {
62
- cwd: projectRoot,
63
- stdio: 'inherit',
272
+ verified: false,
273
+ mismatches: [`comment: expected comment ${commentId} on the issue, not found in the read-back`],
64
274
  };
65
275
  }
66
276
 
277
+ // Re-read the mutated issue through the SAME runner and assert the intended
278
+ // delta. Returns { verified: true|false|null, mismatches? }. NEVER throws — a
279
+ // verification failure must never make a successful write report failure.
280
+ async function verifyIssueMutation(subcommand, operationArgs, result, runner, projectRoot, opts) {
281
+ try {
282
+ const flags = parseVerifyFlags(operationArgs);
283
+ const issueId = (result.data && typeof result.data.id === 'string' && result.data.id)
284
+ ? result.data.id
285
+ : operationArgs.find(arg => typeof arg === 'string' && !arg.startsWith('-'));
286
+ if (!issueId) return { verified: null };
287
+ if (subcommand === 'claim') {
288
+ return await verifyClaimDelta(runner, issueId, projectRoot, opts);
289
+ }
290
+ const readBack = await runner('show', [issueId, '--json'], projectRoot, opts);
291
+ if (!readBack || readBack.ok !== true || !readBack.data || typeof readBack.data !== 'object') {
292
+ return { verified: null };
293
+ }
294
+ if (subcommand === 'comment') {
295
+ return verifyCommentDelta(result, readBack.data);
296
+ }
297
+ const mismatches = collectFieldMismatches(expectedIssueFields(subcommand, flags), readBack.data);
298
+ if (subcommand === 'close') {
299
+ collectCloseMismatches(flags, readBack.data, mismatches);
300
+ }
301
+ return { verified: mismatches.length === 0, mismatches };
302
+ } catch {
303
+ return { verified: null };
304
+ }
305
+ }
306
+
307
+ // Warn-mode reporting: verified:false lists the mismatches; verified:null says
308
+ // the read-back could not confirm. Either way the write stays successful.
309
+ function warnVerifyOutcome(subcommand, issueRef, verification) {
310
+ if (verification.verified === true) return;
311
+ const lines = verification.verified === false
312
+ ? [
313
+ `[forge verify] WARNING: '${subcommand}' reported ok but the read-back of ${issueRef} does not match the requested change:`,
314
+ ...verification.mismatches.map(entry => ` - ${entry}`),
315
+ ]
316
+ : [`[forge verify] WARNING: '${subcommand}' succeeded but the read-back of ${issueRef} could not confirm the change (verification read failed).`];
317
+ lines.push('The write itself succeeded (exit 0). Check-after-write is governed by gate.issue_verify (warn mode) — disable via: forge gate disable gate.issue_verify');
318
+ console.warn(lines.join('\n'));
319
+ }
320
+
321
+ // Run verification for one successful mutation result and attach the outcome
322
+ // (verified / mismatches) to it, warning on anything but verified:true.
323
+ async function applyIssueVerification(subcommand, operationArgs, result, runner, projectRoot, opts) {
324
+ const verification = await verifyIssueMutation(subcommand, operationArgs, result, runner, projectRoot, opts);
325
+ result.verified = verification.verified;
326
+ if (Array.isArray(verification.mismatches)) {
327
+ result.mismatches = verification.mismatches;
328
+ }
329
+ const issueRef = (result.data && result.data.id) || operationArgs.find(arg => typeof arg === 'string' && !arg.startsWith('-')) || '<issue>';
330
+ warnVerifyOutcome(subcommand, issueRef, verification);
331
+ }
332
+
333
+ function normalizeArgs(args = []) {
334
+ return args.filter(arg => arg !== '--');
335
+ }
336
+
337
+ // 5a5ba3a6: auto-wire stage_runs. When a kernel `comment` carries the Descriptive
338
+ // Context Convention's `stage: <from> -> <to>` line, mirror it into stage_runs
339
+ // (complete the from-stage, start the to-stage) so `current_stage` becomes real
340
+ // without a manual `forge stage` verb call. Best-effort and non-blocking:
341
+ // recordStageTransition never throws, so a stage_run write can never break the
342
+ // comment that already succeeded. Kernel path only — reuses the driver already
343
+ // assembled on opts (no second DB handle).
344
+ function recordStageTransitionFromComment(subcommand, operationArgs, result, opts) {
345
+ if (subcommand !== 'comment' || !shouldUseKernelBroker(opts)) return;
346
+ if (!result || result.ok !== true || !result.data) return;
347
+ const issueId = typeof result.data.id === 'string' ? result.data.id : null;
348
+ if (!issueId) return;
349
+ // Drop the leading issue-ref positional; the rest is the comment body.
350
+ const body = normalizeArgs(operationArgs)
351
+ .filter(arg => typeof arg === 'string' && !arg.startsWith('-'))
352
+ .slice(1)
353
+ .join(' ');
354
+ recordStageTransition({
355
+ driver: opts.kernelDriver,
356
+ issueId,
357
+ body,
358
+ config: opts.kernelDatabasePath ? { databasePath: opts.kernelDatabasePath } : {},
359
+ });
360
+ }
361
+
67
362
  function formatIssueHelp() {
68
363
  const lines = [
69
364
  'Usage: forge issue <subcommand> [...]',
@@ -81,55 +376,428 @@ function formatIssueHelp() {
81
376
  lines.push(' forge claim forge-abc');
82
377
  lines.push(' forge update forge-abc --priority 1');
83
378
  lines.push(' forge close forge-abc --reason "Done"');
379
+ lines.push(' forge comment forge-abc "Handoff note"');
84
380
  lines.push(' forge issue show forge-abc --json');
381
+ lines.push(' forge issue search "kernel contract" --json');
382
+ lines.push(' forge issue stats --json');
383
+ lines.push(' forge issue dep add forge-work forge-blocker');
85
384
 
86
385
  return lines.join('\n');
87
386
  }
88
387
 
89
- function extractErrorMessage(error) {
90
- if (error?.code === 'ENOENT') {
91
- return 'Beads (bd) command not found. Install or initialize Beads before using Forge issue commands.';
388
+ // Map a CLI subcommand to the backend operation name. `dep` fans out to
389
+ // `dep.<action>`; every other subcommand uses its own name (the backend performs
390
+ // any tracker-specific translation, e.g. Beads claim -> `update <id> --claim`).
391
+ function resolveIssueOperation(subcommand, args) {
392
+ if (subcommand === 'dep') {
393
+ return `dep.${normalizeArgs(args)[0]}`;
92
394
  }
395
+ return subcommand;
396
+ }
93
397
 
94
- // With stdio: 'inherit', error.stderr and error.stdout are always null.
95
- // Only error.message is available for diagnostics.
96
- return error?.message?.trim() || 'Beads command failed';
398
+ // The Kernel create payload (buildCreatePayload) reads only the --title flag, so a
399
+ // bare leading positional (`forge create "title"`) would be ignored and the title
400
+ // would default to the minted UUID. For parity on the KERNEL PATH ONLY, translate a
401
+ // single leading bare positional into `--title <value>` when no explicit
402
+ // --title/--title= is present. The Beads backend keeps its native positional
403
+ // handling (this never runs for the Beads path).
404
+ function withKernelCreateTitle(args) {
405
+ const hasTitle = args.some(
406
+ arg => arg === '--title' || (typeof arg === 'string' && arg.startsWith('--title=')),
407
+ );
408
+ if (hasTitle || args.length === 0) {
409
+ return args;
410
+ }
411
+ const leading = args[0];
412
+ if (typeof leading === 'string' && !leading.startsWith('-')) {
413
+ return ['--title', leading, ...args.slice(1)];
414
+ }
415
+ return args;
97
416
  }
98
417
 
99
- function buildBdArgs(subcommand, rawArgs) {
100
- const spec = SUBCOMMANDS[subcommand];
101
- if (!spec) {
102
- return { error: `Unknown issue subcommand '${subcommand}'.\n\n${formatIssueHelp()}` };
418
+ // Resolve the operation args passed to the backend. `dep` drops its leading action
419
+ // token (it is carried in the operation name); `create` on the Kernel path gains the
420
+ // positional-title parity translation. Everything else passes its normalized args
421
+ // through verbatim.
422
+ function resolveOperationArgs(subcommand, args, opts = {}) {
423
+ const normalizedArgs = normalizeArgs(args);
424
+
425
+ if (subcommand === 'dep') {
426
+ return normalizedArgs.slice(1);
103
427
  }
104
428
 
105
- const args = normalizeArgs(rawArgs);
106
- if (args.includes('--help') || args.includes('-h')) {
107
- return [spec.helpCommand, '--help'];
429
+ if (subcommand === 'create' && shouldUseKernelBroker(opts)) {
430
+ return withKernelCreateTitle(normalizedArgs);
108
431
  }
109
432
 
110
- return spec.buildBdArgs(args);
433
+ return normalizedArgs;
434
+ }
435
+
436
+ // Validate the dep action up front so a bad `forge issue dep <action>` fails with a
437
+ // usage message instead of routing to a `dep.undefined` operation.
438
+ function validateDepArgs(args) {
439
+ const normalized = normalizeArgs(args);
440
+ const [action, ...rest] = normalized;
441
+ if (!DEP_ACTIONS.includes(action)) {
442
+ return {
443
+ error: `Unsupported dependency action: ${action || '(missing)'}. Usage: forge issue dep <add|remove> <issue-id> <blocks-issue-id>`,
444
+ };
445
+ }
446
+ if (rest.length < 2) {
447
+ return {
448
+ error: `Missing dependency ids. Usage: forge issue dep ${action} <issue-id> <blocks-issue-id>`,
449
+ };
450
+ }
451
+ return null;
111
452
  }
112
453
 
113
- async function runIssueSubcommand(subcommand, args, projectRoot, opts = {}) {
114
- const exec = opts._exec || execFileSync;
115
- const bdArgs = buildBdArgs(subcommand, args);
454
+ // Resolve the active issue backend (kernel|beads) and thread it into opts so the
455
+ // shared runIssueOperation deps see it. OPT-IN ONLY: opts is left byte-identical
456
+ // when no explicit signal is present (env/config/explicit), preserving the Beads
457
+ // default path. A copy is returned — the caller's opts object is never mutated.
458
+ function withResolvedIssueBackend(projectRoot, opts = {}) {
459
+ const env = opts.env || process.env;
460
+ const signalContext = { deps: opts, env, projectRoot };
461
+ if (!hasExplicitBackendSignal(signalContext)) {
462
+ return opts;
463
+ }
116
464
 
117
- if (!Array.isArray(bdArgs)) {
118
- return { success: false, error: bdArgs.error };
465
+ // Run EVERY explicit value through the resolver — including an explicit
466
+ // opts.issueBackend so case is normalized and unknown values warn + fall back.
467
+ // opts.issueBackend still wins precedence over env/config inside the resolver.
468
+ // Identity is preserved when the resolved value already matches, keeping the
469
+ // no-op path byte-identical.
470
+ const issueBackend = resolveIssueBackend(signalContext);
471
+ if (opts.issueBackend === issueBackend) {
472
+ return opts;
119
473
  }
474
+ return { ...opts, issueBackend };
475
+ }
120
476
 
121
- try {
122
- exec('bd', bdArgs, getExecOptions(projectRoot));
123
- return { success: true, subcommand };
124
- } catch (error) {
477
+ // The Kernel broker returns the issue-command contract shape
478
+ // ({ ok, schema_version, command, data, next_commands } or { ok:false, error })
479
+ // rather than the Beads-style { success, output }. The bin/forge.js result printer
480
+ // keys on `success`/`output`, so a raw kernel contract would render as
481
+ // "Command failed". Normalize ONLY the contract shape (ok defined, success
482
+ // undefined) into { success, output } here, at the command boundary — the kernel
483
+ // contract itself stays untouched. Every other result passes through byte-identical.
484
+ //
485
+ // Response-contract parity (the Beads behavior the Kernel replaced):
486
+ // * SUCCESS → the printed envelope carries `ok:true` (consumers gate on it).
487
+ // * FAILURE → the contract `exit_code` is surfaced as `result.exitCode` so the bin
488
+ // printer exits with the error class's code (not always 1); and on
489
+ // `--json` the full forge.issue.error.v1 envelope is emitted on stdout
490
+ // (without --json the human message alone goes to stderr, as before).
491
+ function normalizeIssueResult(result, operation, { json = false, humanRender = null } = {}) {
492
+ if (!result || typeof result !== 'object') {
493
+ return result;
494
+ }
495
+ if (result.ok === undefined || result.success !== undefined) {
496
+ return result;
497
+ }
498
+
499
+ if (result.ok === true) {
500
+ const envelope = {
501
+ ok: true,
502
+ schema_version: result.schema_version,
503
+ command: result.command,
504
+ data: result.data ?? null,
505
+ next_commands: result.next_commands ?? [],
506
+ };
507
+ // Check-after-write outcome (gate.issue_verify): additive keys, present only
508
+ // when verification ran (verified may be null when the read-back failed).
509
+ if (result.verified !== undefined) envelope.verified = result.verified;
510
+ if (result.mismatches !== undefined) envelope.mismatches = result.mismatches;
125
511
  return {
126
- success: false,
127
- error: extractErrorMessage(error),
512
+ success: true,
513
+ operation,
514
+ // Human-first reads (a9bbd065): render the envelope as text unless the
515
+ // caller asked for the contract (--json / FORGE_JSON=1). The envelope —
516
+ // including verified/mismatches — is rendered, never dropped.
517
+ output: humanRender
518
+ ? renderIssueEnvelope(humanRender, envelope)
519
+ : JSON.stringify(envelope, null, 2),
128
520
  };
129
521
  }
522
+
523
+ const message = result.error?.message
524
+ || result.message
525
+ || `Issue ${operation} failed`;
526
+ const normalized = { success: false, error: message };
527
+ if (Number.isInteger(result.error?.exit_code)) {
528
+ normalized.exitCode = result.error.exit_code;
529
+ }
530
+ if (json) {
531
+ normalized.output = JSON.stringify({
532
+ ok: false,
533
+ schema_version: result.schema_version || ISSUE_COMMAND_ERROR_SCHEMA_VERSION,
534
+ command: result.command,
535
+ error: result.error,
536
+ next_commands: result.next_commands ?? [],
537
+ }, null, 2);
538
+ }
539
+ return normalized;
540
+ }
541
+
542
+ // `owns` is a read whose EXIT CODE encodes the ownership verdict: exit 0 when the
543
+ // resolving actor holds the live lease, non-zero otherwise. The kernel read itself
544
+ // succeeds (ok:true) whether or not the actor owns the lease — the verdict rides in
545
+ // `data.owned` — so the plain read normalizer (which maps every ok:true to success)
546
+ // would exit 0 on a NON-owned issue and let a worker mutate someone else's claim.
547
+ // This normalizer converts a `owned:false` verdict into a non-zero conflict result
548
+ // with a clear "you do not own the lease" message, while a genuine read failure (issue
549
+ // not found) still flows through normalizeIssueResult with its contract exit code. The
550
+ // `output` (the full envelope) is preserved even on the non-owned failure so `--json`
551
+ // consumers still receive `{ ok:true, data:{ owned:false, ... } }` on stdout.
552
+ function normalizeOwnsResult(result, operation, { json = false } = {}) {
553
+ // Raw read failure (e.g. not found) or a non-contract shape → defer to the standard
554
+ // normalizer so the contract exit code (notFound=3, ...) and error envelope survive.
555
+ if (!result || typeof result !== 'object' || result.ok !== true) {
556
+ return normalizeIssueResult(result, operation, { json });
557
+ }
558
+
559
+ const data = result.data && typeof result.data === 'object' ? result.data : {};
560
+ const envelope = JSON.stringify({
561
+ ok: true,
562
+ schema_version: result.schema_version,
563
+ command: result.command,
564
+ data,
565
+ next_commands: result.next_commands ?? [],
566
+ }, null, 2);
567
+
568
+ if (data.owned === true) {
569
+ return { success: true, operation, output: envelope };
570
+ }
571
+
572
+ const heldBy = typeof data.claimed_by === 'string' && data.claimed_by ? data.claimed_by : 'nobody';
573
+ const expiredNote = data.expired ? ' — the lease has expired and can be reclaimed' : '';
574
+ const issueId = typeof data.id === 'string' && data.id ? data.id : '<issue>';
575
+ const normalized = {
576
+ success: false,
577
+ operation,
578
+ error: `You do not own the lease for ${issueId} (held by ${heldBy}${expiredNote}). `
579
+ + 'You lost the race or the claim collapsed to a duplicate — reselect via '
580
+ + '`forge issue ready --json` and re-claim before working.',
581
+ exitCode: ISSUE_COMMAND_EXIT_CODES.conflict,
582
+ };
583
+ // On --json, surface the full ownership envelope on stdout (the bin printer emits
584
+ // result.output before the stderr error) so machine consumers still get the verdict.
585
+ if (json) {
586
+ normalized.output = envelope;
587
+ }
588
+ return normalized;
589
+ }
590
+
591
+ // Split close args into the leading run of positional ids and the trailing flag
592
+ // tokens (everything from the first `-`-prefixed token onward). A flag value such
593
+ // as `done` in `--reason done` sits after a dash token, so it is correctly kept
594
+ // with the flags and never mistaken for an id.
595
+ function splitLeadingIds(args = []) {
596
+ const flagIndex = args.findIndex(arg => typeof arg === 'string' && arg.startsWith('-'));
597
+ if (flagIndex === -1) {
598
+ return { ids: [...args], flags: [] };
599
+ }
600
+ return { ids: args.slice(0, flagIndex), flags: args.slice(flagIndex) };
601
+ }
602
+
603
+ // Kernel close fans out one runner call per id (the kernel close op closes a single
604
+ // id — the first positional), then aggregates the per-id outcomes into ONE
605
+ // forge.issue.v1 envelope (`mutationBatch` in the contract) — NEVER a bare array,
606
+ // which broke envelope parity for multi-id close. `ok` is true only when every id
607
+ // closed; per-id outcomes live in `data.results` and the contract `exit_code` of the
608
+ // first failure is surfaced as `exitCode` so the bin printer exits with the error
609
+ // class's code. KERNEL PATH ONLY: the Beads passthrough keeps its single
610
+ // `close id1 id2 ...` invocation.
611
+ async function runKernelBatchClose(runner, operation, ids, flags, projectRoot, opts, verifyEnabled = false) {
612
+ const results = [];
613
+ let allSucceeded = true;
614
+ let firstFailureExit;
615
+ for (const id of ids) {
616
+ const raw = await runner(operation, [id, ...flags], projectRoot, opts);
617
+ const succeeded = Boolean(raw && (raw.ok === true || raw.success === true));
618
+ if (succeeded) {
619
+ // Echo the CANONICAL id the kernel closed (raw.data.id) rather than the
620
+ // caller's token — with git-style short-id support the broker may have
621
+ // resolved a prefix to the full id, and the envelope should report the
622
+ // resolved id the read-back verified.
623
+ const closedId = (raw.data && typeof raw.data.id === 'string' && raw.data.id) ? raw.data.id : id;
624
+ const entry = { id: closedId, ok: true };
625
+ if (raw.data && Number.isInteger(raw.data.revision)) entry.revision = raw.data.revision;
626
+ if (raw.data && Array.isArray(raw.data.newly_unblocked)) entry.newly_unblocked = raw.data.newly_unblocked;
627
+ // Per-id check-after-write (gate.issue_verify): the batch path fans out one
628
+ // close per id, so each id gets its own read-back and verified/mismatches.
629
+ if (verifyEnabled && raw.ok === true) {
630
+ const verification = await verifyIssueMutation('close', [id, ...flags], raw, runner, projectRoot, opts);
631
+ entry.verified = verification.verified;
632
+ if (Array.isArray(verification.mismatches) && verification.mismatches.length > 0) {
633
+ entry.mismatches = verification.mismatches;
634
+ }
635
+ warnVerifyOutcome('close', id, verification);
636
+ }
637
+ results.push(entry);
638
+ continue;
639
+ }
640
+ allSucceeded = false;
641
+ const error = (raw && raw.error && typeof raw.error === 'object')
642
+ ? raw.error
643
+ : { message: (raw && (raw.error || raw.message)) || `Issue ${operation} failed for ${id}` };
644
+ if (firstFailureExit === undefined && Number.isInteger(error.exit_code)) {
645
+ firstFailureExit = error.exit_code;
646
+ }
647
+ results.push({ id, ok: false, error });
648
+ }
649
+
650
+ const envelope = {
651
+ ok: allSucceeded,
652
+ schema_version: ISSUE_COMMAND_SCHEMA_VERSION,
653
+ command: 'issue.close',
654
+ data: {
655
+ results,
656
+ count: results.length,
657
+ closed: results.filter(entry => entry.ok).map(entry => entry.id),
658
+ },
659
+ next_commands: resolveNextCommands('issue.close'),
660
+ };
661
+ // Aggregate the per-id verification into envelope-level verified/mismatches:
662
+ // false if any closed id mismatched, null if any read-back was inconclusive,
663
+ // true only when every closed id verified clean.
664
+ const verifiedEntries = results.filter(entry => entry.ok && entry.verified !== undefined);
665
+ if (verifiedEntries.length > 0) {
666
+ if (verifiedEntries.some(entry => entry.verified === false)) {
667
+ envelope.verified = false;
668
+ envelope.mismatches = verifiedEntries
669
+ .filter(entry => Array.isArray(entry.mismatches))
670
+ .flatMap(entry => entry.mismatches.map(mismatch => `${entry.id}: ${mismatch}`));
671
+ } else if (verifiedEntries.some(entry => entry.verified === null)) {
672
+ envelope.verified = null;
673
+ } else {
674
+ envelope.verified = true;
675
+ envelope.mismatches = [];
676
+ }
677
+ }
678
+
679
+ const normalized = {
680
+ success: allSucceeded,
681
+ operation,
682
+ output: JSON.stringify(envelope, null, 2),
683
+ };
684
+ if (!allSucceeded) {
685
+ const failed = results.filter(entry => !entry.ok).map(entry => entry.id);
686
+ normalized.error = `Failed to close ${failed.length} of ${ids.length} issue(s): ${failed.join(', ')}`;
687
+ normalized.exitCode = Number.isInteger(firstFailureExit)
688
+ ? firstFailureExit
689
+ : ISSUE_COMMAND_EXIT_CODES.internal;
690
+ }
691
+ return normalized;
692
+ }
693
+
694
+ async function runIssueSubcommand(subcommand, args, projectRoot, rawOpts = {}) {
695
+ const spec = SUBCOMMANDS[subcommand];
696
+ if (!spec) {
697
+ return { success: false, error: `Unknown issue subcommand '${subcommand}'.\n\n${formatIssueHelp()}` };
698
+ }
699
+
700
+ // Backend-agnostic --help short-circuit: print the subcommand's usage and return
701
+ // BEFORE resolving the backend or dispatching any operation. Without this, a help
702
+ // request was forwarded to the active backend as an operation arg — absorbed
703
+ // harmlessly by Beads (which swallowed --help), but broken under the Kernel default:
704
+ // the plural path failed with a bare "Command failed" and the singular path
705
+ // SILENTLY minted a junk issue (and could queue a GitHub projection). Help must
706
+ // never touch a backend.
707
+ if (normalizeArgs(args).some(arg => arg === '--help' || arg === '-h')) {
708
+ return { success: true, output: `${spec.usage}\n\n${spec.description}` };
709
+ }
710
+
711
+ if (subcommand === 'dep') {
712
+ const depError = validateDepArgs(args);
713
+ if (depError) {
714
+ return { success: false, error: depError.error };
715
+ }
716
+ }
717
+
718
+ // `children` needs a leading <epic-id> positional; without it the kernel read
719
+ // binds `undefined` and surfaces a raw SQLite error. Fail with usage instead.
720
+ if (subcommand === 'children' && !normalizeArgs(args).some((arg) => !arg.startsWith('--'))) {
721
+ return { success: false, error: `Usage: ${spec.usage}` };
722
+ }
723
+
724
+ // These subcommands bind their target issue from the first positional. Without it the
725
+ // kernel path fabricates a random UUID and quarantines it ("invalid_claim_scope",
726
+ // exit 0) — surface a clean usage error with a non-zero exit instead (kernel 842a8be7).
727
+ const REQUIRES_LEADING_ID = new Set(['claim', 'release', 'comment', 'show', 'update', 'close', 'owns']);
728
+ if (REQUIRES_LEADING_ID.has(subcommand) && !normalizeArgs(args).some((arg) => !arg.startsWith('--'))) {
729
+ return { success: false, error: `Missing required argument <id>.\nUsage: ${spec.usage}`, exitCode: 6 };
730
+ }
731
+
732
+ const opts = withResolvedIssueBackend(projectRoot, rawOpts);
733
+
734
+ // Both backends are reached through the same runIssueOperation seam. Naming the
735
+ // injected local `runIssueOperation` keeps the dispatch a literal call to a binding
736
+ // named `runIssueOperation` (the kernel-evidence gate is syntactic) while still
737
+ // honoring an injected runner; the `kernelBroker: opts.kernelBroker` passthrough is
738
+ // a runtime no-op (undefined under Beads) that documents the Kernel-capable surface.
739
+ const runIssueOperation = opts.runIssueOperation || defaultRunIssueOperation;
740
+ const operation = resolveIssueOperation(subcommand, args);
741
+ const operationArgs = resolveOperationArgs(subcommand, args, opts);
742
+
743
+ // Check-after-write (gate.issue_verify): resolved ONCE per invocation, kernel
744
+ // path only. Reads and the Beads path never trigger a read-back.
745
+ const verifyEnabled = VERIFIED_SUBCOMMANDS.has(subcommand)
746
+ && shouldUseKernelBroker(opts)
747
+ && isIssueVerifyEnabled(projectRoot, opts);
748
+
749
+ // Kernel batch close: >1 leading positional id → one runner call per id,
750
+ // aggregated. A single id falls through to the byte-identical single path.
751
+ if (subcommand === 'close' && shouldUseKernelBroker(opts)) {
752
+ const { ids, flags } = splitLeadingIds(operationArgs);
753
+ if (ids.length > 1) {
754
+ return runKernelBatchClose(runIssueOperation, operation, ids, flags, projectRoot, opts, verifyEnabled);
755
+ }
756
+ }
757
+
758
+ const result = await runIssueOperation(
759
+ operation,
760
+ operationArgs,
761
+ projectRoot,
762
+ { ...opts, kernelBroker: opts.kernelBroker },
763
+ );
764
+ // Verify only a SUCCESSFUL kernel-contract mutation (ok:true, not a Beads
765
+ // {success,output} shape). Warn-only: attaches verified/mismatches, never
766
+ // changes the result's success or exit code.
767
+ if (verifyEnabled && result && typeof result === 'object' && result.ok === true && result.success === undefined) {
768
+ await applyIssueVerification(subcommand, operationArgs, result, runIssueOperation, projectRoot, opts);
769
+ }
770
+ // Best-effort, non-blocking: mirror a stage-transition comment into stage_runs.
771
+ recordStageTransitionFromComment(subcommand, operationArgs, result, opts);
772
+ // Contract output is opt-in for the human-first reads: an explicit --json flag
773
+ // or FORGE_JSON=1 in the environment (for scripts that cannot alter argv).
774
+ const jsonRequested = normalizeArgs(args).includes('--json')
775
+ || (opts.env || process.env).FORGE_JSON === '1';
776
+ // `owns` maps the ownership verdict (data.owned) to the process exit code; every
777
+ // other subcommand uses the plain read/mutation normalization.
778
+ if (subcommand === 'owns') {
779
+ return normalizeOwnsResult(result, operation, { json: jsonRequested });
780
+ }
781
+ // Reads render human-first always; writes only when the caller is an interactive
782
+ // terminal. The CLI entry (bin/forge.js) sets rawOpts.isInteractive from
783
+ // process.stdout.isTTY; every other caller (scripts, tests, pipes) defaults to
784
+ // non-interactive and keeps the machine-parseable JSON envelope (842a8be7).
785
+ const interactive = rawOpts.isInteractive === true;
786
+ const humanSubcommand = !jsonRequested
787
+ && (HUMAN_RENDERED_SUBCOMMANDS.has(subcommand)
788
+ || (interactive && HUMAN_RENDERED_WRITE_SUBCOMMANDS.has(subcommand)))
789
+ ? subcommand
790
+ : null;
791
+ return normalizeIssueResult(result, operation, {
792
+ json: jsonRequested,
793
+ humanRender: humanSubcommand,
794
+ });
130
795
  }
131
796
 
132
- function makeAliasCommand(subcommand) {
797
+ // Build a registry command for a single issue subcommand (e.g. `forge claim`).
798
+ // Each routes through the shared runIssueSubcommand dispatch, so the backend
799
+ // abstraction owns all tracker selection and argument translation.
800
+ function createIssueSubcommand(subcommand) {
133
801
  const spec = SUBCOMMANDS[subcommand];
134
802
  if (!spec) {
135
803
  throw new Error(`Unknown issue subcommand '${subcommand}'`);
@@ -148,8 +816,8 @@ function makeAliasCommand(subcommand) {
148
816
  function createIssueCommand() {
149
817
  return {
150
818
  name: 'issue',
151
- description: 'Manage Beads issues through the Forge command surface',
152
- usage: 'forge issue <create|update|claim|close|show|list|ready> [...]',
819
+ description: 'Manage issues through the Forge command surface',
820
+ usage: 'forge issue <create|update|claim|release|close|show|list|ready|search|stats|dep> [...]',
153
821
  flags: {},
154
822
  handler: async (args, _flags, projectRoot, opts = {}) => {
155
823
  const [subcommand, ...rest] = normalizeArgs(args);
@@ -165,8 +833,14 @@ function createIssueCommand() {
165
833
 
166
834
  module.exports = {
167
835
  SUBCOMMANDS,
168
- buildBdArgs,
836
+ WRITE_SUBCOMMANDS,
169
837
  createIssueCommand,
170
- makeAliasCommand,
838
+ createIssueSubcommand,
839
+ normalizeArgs,
840
+ normalizeIssueResult,
841
+ normalizeOwnsResult,
842
+ resolveIssueOperation,
843
+ resolveOperationArgs,
171
844
  runIssueSubcommand,
845
+ withResolvedIssueBackend,
172
846
  };