forge-workflow 0.0.10 → 0.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (454) hide show
  1. package/.claude/rules/{greptile-review-process.md → review-process.md} +56 -41
  2. package/.claude/scripts/{greptile-resolve.sh → review-resolve.sh} +13 -3
  3. package/.cursor/rules/permissions-guidance.mdc +2 -2
  4. package/.forge/hooks/check-tdd.js +3 -0
  5. package/.forge/hooks/forge-native-hook.js +245 -0
  6. package/.forge/protected-paths.yaml +157 -0
  7. package/AGENTS.md +150 -61
  8. package/CHANGELOG.md +681 -0
  9. package/CLAUDE.md +9 -118
  10. package/QUICKSTART.md +171 -0
  11. package/README.md +271 -363
  12. package/bin/forge-cmd.js +120 -9
  13. package/bin/forge-preflight.js +26 -5
  14. package/bin/forge.js +461 -489
  15. package/docs/INDEX.md +93 -0
  16. package/docs/PROJECT_DESIGN.md +685 -0
  17. package/docs/architecture/index.md +66 -0
  18. package/docs/architecture/notes/README.md +35 -0
  19. package/docs/architecture/subsystems/README.md +46 -0
  20. package/docs/forge/TOOLCHAIN.md +670 -0
  21. package/docs/forge/VALIDATION.md +82 -0
  22. package/docs/{AGENT_INSTALL_PROMPT.md → guides/AGENT_INSTALL_PROMPT.md} +3 -3
  23. package/docs/guides/BEADS_GITHUB_SYNC.md +32 -0
  24. package/docs/{ENHANCED_ONBOARDING.md → guides/ENHANCED_ONBOARDING.md} +16 -12
  25. package/docs/guides/GREPTILE_SETUP.md +46 -0
  26. package/docs/guides/MANUAL_REVIEW_GUIDE.md +58 -0
  27. package/docs/guides/MIGRATION.md +56 -0
  28. package/docs/guides/SETUP.md +118 -0
  29. package/docs/guides/SUPPORT.md +185 -0
  30. package/docs/guides/WORKFLOW_TEMPLATES.md +74 -0
  31. package/docs/guides/memory-backends.md +183 -0
  32. package/docs/reference/ADAPTERS.md +128 -0
  33. package/docs/reference/AGENT_SKILL_PARITY.md +175 -0
  34. package/docs/reference/COMMANDS.md +205 -0
  35. package/docs/reference/DECISION_DRIFT_GUARDS.md +97 -0
  36. package/docs/{EXAMPLES.md → reference/EXAMPLES.md} +7 -5
  37. package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +135 -0
  38. package/docs/reference/HERMES_INTEGRATION.md +118 -0
  39. package/docs/reference/INSIGHTS_RECAP.md +63 -0
  40. package/docs/reference/INSTALL.md +164 -0
  41. package/docs/reference/KERNEL_TAXONOMY_VALIDATION.md +161 -0
  42. package/docs/reference/PROTECTED_PATH_MANIFEST.md +25 -0
  43. package/docs/reference/RELEASE.md +68 -0
  44. package/docs/reference/RESEARCH_TEMPLATE.md +292 -0
  45. package/docs/{ROADMAP.md → reference/ROADMAP.md} +12 -9
  46. package/docs/reference/SKILLS.md +35 -0
  47. package/docs/reference/STATUS_BOARD.md +80 -0
  48. package/docs/reference/TEMPLATES.md +106 -0
  49. package/docs/{TOOLCHAIN.md → reference/TOOLCHAIN.md} +62 -47
  50. package/docs/reference/VALIDATION.md +82 -0
  51. package/docs/reference/agent-permissions.md +169 -0
  52. package/docs/reference/beads-to-kernel-migration-ux.md +61 -0
  53. package/docs/reference/control-plane-guarantees.md +125 -0
  54. package/docs/reference/dependency-chain.md +331 -0
  55. package/docs/reference/forge-kernel-issue-command-contract.md +161 -0
  56. package/docs/reference/forge-kernel-schema.md +72 -0
  57. package/docs/reference/kernel-conflict-evaluators.md +27 -0
  58. package/docs/reference/patch-md-format.md +77 -0
  59. package/docs/reference/protected-state-surfaces.md +59 -0
  60. package/docs/reference/shepherd.md +115 -0
  61. package/docs/reference/superpowers-analysis.md +320 -0
  62. package/docs/reference/superpowers-integration-options.md +404 -0
  63. package/docs/reference/test-environment.md +519 -0
  64. package/docs/reference/upgrade-safety.md +59 -0
  65. package/lefthook.yml +18 -0
  66. package/lib/adapter-cli.js +307 -0
  67. package/lib/adapters/beads-issue-adapter.js +127 -0
  68. package/lib/adapters/beads-kernel-compat.js +1042 -0
  69. package/lib/adapters/greptile-review-adapter.js +141 -0
  70. package/lib/adapters/kernel-issue-adapter.js +101 -0
  71. package/lib/adapters/pr-state-adapter.js +484 -0
  72. package/lib/adoption-profiles.js +126 -0
  73. package/lib/agents/README.md +2 -6
  74. package/lib/agents/claude.plugin.json +3 -8
  75. package/lib/agents/codex.plugin.json +9 -1
  76. package/lib/agents/cursor.plugin.json +2 -6
  77. package/lib/agents/hermes.plugin.json +22 -0
  78. package/lib/agents-config.js +39 -1236
  79. package/lib/audit-evidence.js +282 -0
  80. package/lib/beads-setup.js +121 -0
  81. package/lib/beads-sync-scaffold.js +25 -101
  82. package/lib/codex-skills.js +51 -1
  83. package/lib/commands/_issue.js +741 -77
  84. package/lib/commands/_manifest.js +91 -0
  85. package/lib/commands/_registry.js +85 -34
  86. package/lib/commands/_resolve-command-opts.js +261 -0
  87. package/lib/commands/_serve-security.js +270 -0
  88. package/lib/commands/adapter.js +12 -0
  89. package/lib/commands/add.js +118 -0
  90. package/lib/commands/audit.js +70 -0
  91. package/lib/commands/blocked.js +5 -0
  92. package/lib/commands/board.js +64 -0
  93. package/lib/commands/claim.js +21 -2
  94. package/lib/commands/claims.js +7 -0
  95. package/lib/commands/clean.js +485 -75
  96. package/lib/commands/close.js +2 -2
  97. package/lib/commands/comment.js +5 -0
  98. package/lib/commands/control.js +148 -0
  99. package/lib/commands/create.js +2 -2
  100. package/lib/commands/dev.js +185 -7
  101. package/lib/commands/doc-gate.js +336 -0
  102. package/lib/commands/doctor.js +156 -0
  103. package/lib/commands/explain.js +15 -0
  104. package/lib/commands/export.js +237 -0
  105. package/lib/commands/gate.js +192 -0
  106. package/lib/commands/hooks.js +242 -0
  107. package/lib/commands/inbox.js +118 -0
  108. package/lib/commands/init.js +598 -0
  109. package/lib/commands/insights.js +79 -0
  110. package/lib/commands/issue.js +12 -1
  111. package/lib/commands/issues.js +17 -0
  112. package/lib/commands/lint.js +5 -0
  113. package/lib/commands/list.js +2 -2
  114. package/lib/commands/merge.js +312 -0
  115. package/lib/commands/migrate.js +523 -0
  116. package/lib/commands/new.js +12 -0
  117. package/lib/commands/options.js +241 -0
  118. package/lib/commands/orient.js +13 -0
  119. package/lib/commands/orphans.js +5 -0
  120. package/lib/commands/patch.js +67 -0
  121. package/lib/commands/plan.js +436 -24
  122. package/lib/commands/preflight.js +211 -0
  123. package/lib/commands/prime.js +13 -0
  124. package/lib/commands/push.js +69 -2
  125. package/lib/commands/ready.js +2 -2
  126. package/lib/commands/recall.js +116 -0
  127. package/lib/commands/recap.js +61 -0
  128. package/lib/commands/recommend.js +0 -1
  129. package/lib/commands/release.js +91 -0
  130. package/lib/commands/remember.js +74 -0
  131. package/lib/commands/role.js +99 -0
  132. package/lib/commands/serve.js +581 -0
  133. package/lib/commands/setup.js +838 -972
  134. package/lib/commands/shepherd.js +436 -0
  135. package/lib/commands/ship.js +23 -1
  136. package/lib/commands/show.js +2 -2
  137. package/lib/commands/stage.js +192 -0
  138. package/lib/commands/stale.js +5 -0
  139. package/lib/commands/status.js +158 -21
  140. package/lib/commands/sync.js +34 -46
  141. package/lib/commands/team.js +4 -1
  142. package/lib/commands/test.js +43 -27
  143. package/lib/commands/update.js +2 -2
  144. package/lib/commands/upgrade.js +47 -0
  145. package/lib/commands/validate.js +43 -18
  146. package/lib/commands/worktree.js +307 -100
  147. package/lib/config-writer.js +202 -0
  148. package/lib/control-plane.js +236 -0
  149. package/lib/core/runtime-graph.js +946 -0
  150. package/lib/dep-guard/keyword-ripple.js +2 -2
  151. package/lib/deprecated-sync-cleanup.js +362 -0
  152. package/lib/detect-agent.js +2 -28
  153. package/lib/detect-worktree.js +35 -9
  154. package/lib/doc-gate/declaration.js +177 -0
  155. package/lib/doc-gate/detect.js +289 -0
  156. package/lib/doc-gate/gate.js +375 -0
  157. package/lib/doc-gate/okf-config.js +128 -0
  158. package/lib/doc-gate/okf.js +429 -0
  159. package/lib/docs-command.js +1161 -6
  160. package/lib/forge-issues.js +382 -11
  161. package/lib/forge-lock.js +262 -0
  162. package/lib/gate-events.js +193 -0
  163. package/lib/global-flags.js +74 -0
  164. package/lib/greptile-match.js +7 -63
  165. package/lib/harness-capability-matrix.js +380 -0
  166. package/lib/hook-global-installer.js +347 -0
  167. package/lib/hook-renderer.js +451 -0
  168. package/lib/inbox.js +391 -0
  169. package/lib/insights.js +397 -0
  170. package/lib/issue-adapter.js +156 -0
  171. package/lib/issue-backend.js +145 -0
  172. package/lib/issue-render.js +220 -0
  173. package/lib/kernel/backing-issue.js +305 -0
  174. package/lib/kernel/broker.js +1218 -0
  175. package/lib/kernel/cli-broker-factory.js +130 -0
  176. package/lib/kernel/conflict-signal.js +82 -0
  177. package/lib/kernel/evaluators.js +195 -0
  178. package/lib/kernel/fs-class.js +495 -0
  179. package/lib/kernel/issue-command-contract.js +559 -0
  180. package/lib/kernel/issue-id-resolver.js +186 -0
  181. package/lib/kernel/lease-enforcer.js +158 -0
  182. package/lib/kernel/migrations.js +333 -0
  183. package/lib/kernel/planning-buckets-schema.js +109 -0
  184. package/lib/kernel/projection-jsonl-writer.js +450 -0
  185. package/lib/kernel/readiness-model.js +329 -0
  186. package/lib/kernel/schema.js +356 -0
  187. package/lib/kernel/sqlite-driver.js +2504 -0
  188. package/lib/kernel/taxonomy-validator.js +394 -0
  189. package/lib/lefthook-check.js +3 -2
  190. package/lib/lefthook-wiring.js +413 -0
  191. package/lib/mcp-config-renderer.js +288 -0
  192. package/lib/memory/graphiti-mcp.js +106 -0
  193. package/lib/memory/router.js +387 -0
  194. package/lib/memory/typed-api.js +102 -0
  195. package/lib/memory-digest.js +195 -0
  196. package/lib/merge-rules.js +395 -0
  197. package/lib/migrate-dry-run.js +466 -0
  198. package/lib/orientation.js +863 -0
  199. package/lib/package-manager-remediation.js +103 -0
  200. package/lib/package-root.js +381 -0
  201. package/lib/patch-intent.js +890 -0
  202. package/lib/plugin-catalog.js +3 -4
  203. package/lib/plugin-manager.js +0 -5
  204. package/lib/pr-bundle.js +186 -0
  205. package/lib/pr-monitor/differ.js +195 -0
  206. package/lib/pr-monitor/events.js +0 -0
  207. package/lib/pr-monitor/gather.js +124 -0
  208. package/lib/pr-monitor/journal.js +299 -0
  209. package/lib/pr-monitor/monitor.js +146 -0
  210. package/lib/pr-monitor/render-sticky.js +157 -0
  211. package/lib/pr-monitor/watch-lifecycle.js +95 -0
  212. package/lib/pr-monitor/watch.js +247 -0
  213. package/lib/pr-pull.js +1273 -0
  214. package/lib/pr-shepherd.js +494 -0
  215. package/lib/pr-state-validator.js +59 -0
  216. package/lib/preflight/gates.js +237 -0
  217. package/lib/preflight/runner.js +116 -0
  218. package/lib/project-discovery.js +0 -53
  219. package/lib/project-memory.js +99 -497
  220. package/lib/protected-path-manifest.js +281 -0
  221. package/lib/protected-state-surfaces.js +387 -0
  222. package/lib/release-readiness.js +2089 -0
  223. package/lib/reset.js +59 -45
  224. package/lib/review-adapter.js +68 -0
  225. package/lib/rules-sync.js +260 -0
  226. package/lib/runtime-health.js +241 -20
  227. package/lib/safety-config-renderer.js +268 -0
  228. package/lib/setup-action-log.js +1 -7
  229. package/lib/setup.js +27 -65
  230. package/lib/shell-utils.js +76 -6
  231. package/lib/skills-sync.js +330 -0
  232. package/lib/smart-status/scoring.js +17 -3
  233. package/lib/status/beads-snapshot.js +45 -2
  234. package/lib/status/presenter.js +169 -18
  235. package/lib/status/snapshot.js +186 -0
  236. package/lib/sync-backend.js +202 -0
  237. package/lib/untrusted-content.js +52 -0
  238. package/lib/upgrade-safety.js +199 -0
  239. package/lib/workflow/enforce-stage.js +296 -47
  240. package/lib/workflow/stage-transition.js +115 -0
  241. package/lib/workflow/stages.js +30 -6
  242. package/lib/workflow/state-manager.js +11 -22
  243. package/lib/workflow/state.js +23 -1
  244. package/lib/workflow-profiles.js +17 -5
  245. package/package.json +37 -35
  246. package/rules/documentation.md +19 -0
  247. package/rules/kernel-tracking.md +26 -0
  248. package/rules/security.md +22 -0
  249. package/rules/tdd.md +20 -0
  250. package/rules/workflow.md +27 -0
  251. package/scripts/auto-backing-issue.js +47 -0
  252. package/scripts/beads-context.sh +81 -57
  253. package/scripts/beads-upgrade-smoke.sh +24 -3
  254. package/scripts/bootstrap-windows-tools.sh +78 -0
  255. package/scripts/branch-protection.js +2 -3
  256. package/scripts/check-agents.js +34 -137
  257. package/scripts/commitlint.js +3 -1
  258. package/scripts/conflict-detect.sh +3 -0
  259. package/scripts/dep-guard.sh +22 -3
  260. package/scripts/file-index.sh +3 -0
  261. package/scripts/forge-team/lib/claim.sh +34 -18
  262. package/scripts/forge-team/lib/dashboard.sh +61 -86
  263. package/scripts/forge-team/lib/epic.sh +99 -263
  264. package/scripts/forge-team/lib/hooks.sh +26 -28
  265. package/scripts/forge-team/lib/identity.sh +4 -4
  266. package/scripts/forge-team/lib/sync-github.sh +49 -84
  267. package/scripts/forge-team/lib/verify.sh +93 -83
  268. package/scripts/forge-team/lib/workload.sh +41 -65
  269. package/scripts/forge-team/tests/claim.test.sh +25 -19
  270. package/scripts/forge-team/tests/dashboard.test.sh +31 -46
  271. package/scripts/forge-team/tests/epic.test.sh +52 -71
  272. package/scripts/forge-team/tests/hooks.test.sh +38 -50
  273. package/scripts/forge-team/tests/identity.test.sh +3 -3
  274. package/scripts/forge-team/tests/integration.test.sh +44 -66
  275. package/scripts/forge-team/tests/sync-github.test.sh +50 -83
  276. package/scripts/forge-team/tests/verify.test.sh +37 -46
  277. package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
  278. package/scripts/forge-team/tests/workload.test.sh +32 -66
  279. package/scripts/gen-command-manifest.js +153 -0
  280. package/scripts/gen-embedded-assets.mjs +129 -0
  281. package/scripts/install.ps1 +139 -0
  282. package/scripts/install.sh +268 -0
  283. package/scripts/lib/release-asset.mjs +84 -0
  284. package/scripts/parity-check.mjs +145 -0
  285. package/scripts/parity-check.test.mjs +58 -0
  286. package/scripts/pin-agentic-workflow-images.js +112 -0
  287. package/scripts/pr-coordinator.sh +3 -0
  288. package/scripts/preflight-sonar.eslint.config.mjs +44 -0
  289. package/scripts/preflight.sh +21 -94
  290. package/scripts/protected-state-check.js +104 -0
  291. package/scripts/smart-status.sh +60 -57
  292. package/scripts/spikes/config-race-bench.js +111 -0
  293. package/scripts/spikes/harness-capability-matrix.js +13 -0
  294. package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
  295. package/scripts/spikes/protected-path-manifest.js +20 -0
  296. package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
  297. package/scripts/sync-agent-skills.js +62 -0
  298. package/scripts/sync-utils.sh +3 -0
  299. package/scripts/test-ci-shard.js +13 -6
  300. package/scripts/test.js +95 -12
  301. package/skills/claim-safety/SKILL.md +102 -0
  302. package/skills/claim-safety/evals/evals.json +46 -0
  303. package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +44 -50
  304. package/skills/dev/evals/evals.json +50 -0
  305. package/skills/hermes-forge/SKILL.md +185 -0
  306. package/skills/hermes-forge/evals/evals.json +46 -0
  307. package/skills/issue-basics/SKILL.md +111 -0
  308. package/skills/issue-basics/evals/evals.json +46 -0
  309. package/skills/kernel/SKILL.md +166 -0
  310. package/skills/kernel/evals/evals.json +50 -0
  311. package/skills/memory/SKILL.md +102 -0
  312. package/skills/parallel-deep-research/SKILL.md +14 -11
  313. package/skills/parallel-deep-research/evals/evals.json +11 -27
  314. package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +132 -157
  315. package/skills/plan/evals/evals.json +42 -0
  316. package/skills/research/SKILL.md +195 -0
  317. package/skills/research/evals/evals.json +42 -0
  318. package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
  319. package/skills/review/evals/evals.json +42 -0
  320. package/skills/rollback/SKILL.md +110 -0
  321. package/skills/rollback/evals/evals.json +46 -0
  322. package/skills/rollback/references/methods.md +204 -0
  323. package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
  324. package/skills/shepherd/SKILL.md +66 -0
  325. package/skills/shepherd/evals/evals.json +42 -0
  326. package/{.github/prompts/ship.prompt.md → skills/ship/SKILL.md} +81 -45
  327. package/skills/ship/evals/evals.json +42 -0
  328. package/skills/smith/SKILL.md +142 -0
  329. package/skills/smith/evals/evals.json +46 -0
  330. package/skills/smith/references/autonomy-and-gates.md +94 -0
  331. package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
  332. package/skills/sonarcloud/evals/evals.json +46 -0
  333. package/skills/sonarcloud-analysis/SKILL.md +18 -13
  334. package/skills/sonarcloud-analysis/evals/evals.json +11 -15
  335. package/{.github/prompts/status.prompt.md → skills/status/SKILL.md} +20 -10
  336. package/skills/status/evals/evals.json +50 -0
  337. package/skills/triage-ready/SKILL.md +121 -0
  338. package/skills/triage-ready/evals/evals.json +42 -0
  339. package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
  340. package/skills/validate/evals/evals.json +42 -0
  341. package/skills/verify/SKILL.md +299 -0
  342. package/skills/verify/evals/evals.json +50 -0
  343. package/.claude/commands/dev.md +0 -345
  344. package/.claude/commands/plan.md +0 -566
  345. package/.claude/commands/premerge.md +0 -186
  346. package/.claude/commands/research.md +0 -42
  347. package/.claude/commands/review.md +0 -451
  348. package/.claude/commands/rollback.md +0 -721
  349. package/.claude/commands/ship.md +0 -213
  350. package/.claude/commands/sonarcloud.md +0 -152
  351. package/.claude/commands/status.md +0 -90
  352. package/.claude/commands/validate.md +0 -288
  353. package/.claude/commands/verify.md +0 -269
  354. package/.claude/rules/workflow.md +0 -121
  355. package/.cline/workflows/dev.md +0 -342
  356. package/.cline/workflows/plan.md +0 -563
  357. package/.cline/workflows/premerge.md +0 -183
  358. package/.cline/workflows/research.md +0 -39
  359. package/.cline/workflows/review.md +0 -448
  360. package/.cline/workflows/rollback.md +0 -718
  361. package/.cline/workflows/ship.md +0 -210
  362. package/.cline/workflows/sonarcloud.md +0 -146
  363. package/.cline/workflows/status.md +0 -87
  364. package/.cline/workflows/validate.md +0 -285
  365. package/.cline/workflows/verify.md +0 -266
  366. package/.codex/config.toml +0 -11
  367. package/.codex/skills/dev/SKILL.md +0 -345
  368. package/.codex/skills/plan/SKILL.md +0 -566
  369. package/.codex/skills/premerge/SKILL.md +0 -186
  370. package/.codex/skills/research/SKILL.md +0 -42
  371. package/.codex/skills/review/SKILL.md +0 -451
  372. package/.codex/skills/rollback/SKILL.md +0 -721
  373. package/.codex/skills/ship/SKILL.md +0 -213
  374. package/.codex/skills/sonarcloud/SKILL.md +0 -149
  375. package/.codex/skills/status/SKILL.md +0 -90
  376. package/.codex/skills/validate/SKILL.md +0 -288
  377. package/.codex/skills/verify/SKILL.md +0 -269
  378. package/.cursor/commands/dev.md +0 -342
  379. package/.cursor/commands/plan.md +0 -563
  380. package/.cursor/commands/premerge.md +0 -183
  381. package/.cursor/commands/research.md +0 -39
  382. package/.cursor/commands/review.md +0 -448
  383. package/.cursor/commands/ship.md +0 -210
  384. package/.cursor/commands/sonarcloud.md +0 -146
  385. package/.cursor/commands/status.md +0 -87
  386. package/.cursor/commands/validate.md +0 -285
  387. package/.cursor/commands/verify.md +0 -266
  388. package/.cursorrules +0 -149
  389. package/.github/prompts/premerge.prompt.md +0 -188
  390. package/.github/prompts/research.prompt.md +0 -44
  391. package/.github/prompts/rollback.prompt.md +0 -723
  392. package/.github/prompts/verify.prompt.md +0 -271
  393. package/.github/workflows/beads-to-github.yml +0 -89
  394. package/.github/workflows/github-to-beads.yml +0 -100
  395. package/.kilocode/workflows/dev.md +0 -346
  396. package/.kilocode/workflows/plan.md +0 -567
  397. package/.kilocode/workflows/premerge.md +0 -187
  398. package/.kilocode/workflows/research.md +0 -43
  399. package/.kilocode/workflows/review.md +0 -452
  400. package/.kilocode/workflows/rollback.md +0 -722
  401. package/.kilocode/workflows/ship.md +0 -214
  402. package/.kilocode/workflows/sonarcloud.md +0 -150
  403. package/.kilocode/workflows/status.md +0 -91
  404. package/.kilocode/workflows/validate.md +0 -289
  405. package/.kilocode/workflows/verify.md +0 -270
  406. package/.opencode/commands/dev.md +0 -345
  407. package/.opencode/commands/plan.md +0 -566
  408. package/.opencode/commands/premerge.md +0 -186
  409. package/.opencode/commands/research.md +0 -42
  410. package/.opencode/commands/review.md +0 -451
  411. package/.opencode/commands/rollback.md +0 -721
  412. package/.opencode/commands/ship.md +0 -213
  413. package/.opencode/commands/sonarcloud.md +0 -149
  414. package/.opencode/commands/status.md +0 -90
  415. package/.opencode/commands/validate.md +0 -288
  416. package/.opencode/commands/verify.md +0 -269
  417. package/.roo/commands/dev.md +0 -346
  418. package/.roo/commands/plan.md +0 -567
  419. package/.roo/commands/premerge.md +0 -187
  420. package/.roo/commands/research.md +0 -43
  421. package/.roo/commands/review.md +0 -452
  422. package/.roo/commands/rollback.md +0 -722
  423. package/.roo/commands/ship.md +0 -214
  424. package/.roo/commands/sonarcloud.md +0 -150
  425. package/.roo/commands/status.md +0 -91
  426. package/.roo/commands/validate.md +0 -289
  427. package/.roo/commands/verify.md +0 -270
  428. package/docs/BEADS_GITHUB_SYNC.md +0 -281
  429. package/docs/GREPTILE_SETUP.md +0 -400
  430. package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
  431. package/docs/SETUP.md +0 -663
  432. package/docs/VALIDATION.md +0 -363
  433. package/lib/agents/cline.plugin.json +0 -29
  434. package/lib/agents/copilot.plugin.json +0 -24
  435. package/lib/agents/kilocode.plugin.json +0 -22
  436. package/lib/agents/opencode.plugin.json +0 -23
  437. package/lib/agents/roo.plugin.json +0 -30
  438. package/lib/beads-bootstrap.js +0 -225
  439. package/lib/beads-health-check.js +0 -188
  440. package/lib/commands/commands-reset.js +0 -147
  441. package/opencode.json +0 -67
  442. package/scripts/beads-context.test.js +0 -584
  443. package/scripts/github-beads-sync/comment.mjs +0 -64
  444. package/scripts/github-beads-sync/config.mjs +0 -148
  445. package/scripts/github-beads-sync/github-api.mjs +0 -131
  446. package/scripts/github-beads-sync/index.mjs +0 -356
  447. package/scripts/github-beads-sync/label-mapper.mjs +0 -54
  448. package/scripts/github-beads-sync/mapping.mjs +0 -132
  449. package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
  450. package/scripts/github-beads-sync/reverse-sync.mjs +0 -162
  451. package/scripts/github-beads-sync/run-bd.mjs +0 -161
  452. package/scripts/github-beads-sync/sanitize.mjs +0 -121
  453. package/scripts/github-beads-sync.config.json +0 -26
  454. package/scripts/sync-commands.js +0 -600
@@ -0,0 +1,220 @@
1
+ 'use strict';
2
+
3
+ // Human-first rendering for the forge issue read surface (kernel issue a9bbd065,
4
+ // 0.1.0 critical path). `forge ready`, `forge list`, and `forge show` default to
5
+ // the compact text views below; the forge.issue.v1 JSON contract is unchanged and
6
+ // stays available behind --json (or FORGE_JSON=1). Dependency-free by design: no
7
+ // colors, no emoji, plain aligned text that survives any terminal or log file.
8
+
9
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
10
+
11
+ // Kernel UUIDs are unusable at a glance — display the 8-char prefix. Non-UUID ids
12
+ // (legacy `forge-2agy.2` style) are already short and MUST stay full: they are the
13
+ // only handle the CLI accepts. The full UUID remains accessible via `show` (which
14
+ // prints it verbatim) and via --json everywhere.
15
+ function shortId(id) {
16
+ if (typeof id !== 'string') return id == null ? '' : String(id);
17
+ return UUID_RE.test(id) ? id.slice(0, 8) : id;
18
+ }
19
+
20
+ // Human-readable display handle: `<title-slug>-<short-id>` (kernel 1db53c60), e.g.
21
+ // "Add OAuth login" → "add-oauth-login-56a3a16d". The slug (first few title words) makes
22
+ // a queue scannable at a glance; the short-id keeps it unique and resolvable — `forge show
23
+ // <handle>` works because the resolver reads the trailing hex. The canonical id stays the
24
+ // full UUID; the handle is display + lookup sugar, recomputed from the current title.
25
+ function issueHandle(issue) {
26
+ const id = issue && issue.id != null ? String(issue.id) : '';
27
+ const short = shortId(id);
28
+ const title = issue && typeof issue.title === 'string' ? issue.title : '';
29
+ const slug = title
30
+ .toLowerCase()
31
+ .replace(/[^a-z0-9]+/g, '-')
32
+ .replace(/^-+|-+$/g, '')
33
+ .split('-')
34
+ .filter(Boolean)
35
+ .slice(0, 4)
36
+ .join('-');
37
+ return slug ? `${slug}-${short}` : short;
38
+ }
39
+
40
+ // gate.issue_verify (kernel 5f928cd0) attaches verified/mismatches to mutation
41
+ // envelopes. Human mode must surface — never hide — a failed or unconfirmed
42
+ // read-back, mirroring the JSON envelope's verified/mismatches keys.
43
+ function verificationLines(envelope) {
44
+ if (!envelope || envelope.verified === undefined || envelope.verified === true) {
45
+ return [];
46
+ }
47
+ if (envelope.verified === false) {
48
+ const lines = ['WARNING: read-back verification failed (gate.issue_verify):'];
49
+ for (const mismatch of Array.isArray(envelope.mismatches) ? envelope.mismatches : []) {
50
+ lines.push(` - ${mismatch}`);
51
+ }
52
+ return lines;
53
+ }
54
+ // verified === null — the verification read itself failed.
55
+ return ['WARNING: read-back verification could not confirm the result (gate.issue_verify).'];
56
+ }
57
+
58
+ // Render rows as space-aligned columns. Every column except the last is padded to
59
+ // its widest cell; the last (title) runs free so long titles never distort the grid.
60
+ function renderTable(headers, rows) {
61
+ const all = [headers, ...rows];
62
+ const widths = headers.map((_, column) => (
63
+ column === headers.length - 1
64
+ ? 0
65
+ : Math.max(...all.map(row => String(row[column] ?? '').length))
66
+ ));
67
+ return all.map(row => row
68
+ .map((cell, column) => {
69
+ const text = String(cell ?? '');
70
+ return column === row.length - 1 ? text : text.padEnd(widths[column]);
71
+ })
72
+ .join(' ')
73
+ .replace(/\s+$/, ''));
74
+ }
75
+
76
+ // forge ready / forge list: one aligned row per issue. Columns mirror the triage
77
+ // decision: which issue (short id), what it is (type), where it stands (status),
78
+ // how urgent (priority), and what it says (title).
79
+ function renderIssueList(envelope, { emptyMessage = 'No issues found.' } = {}) {
80
+ const data = envelope && envelope.data && typeof envelope.data === 'object' ? envelope.data : {};
81
+ const issues = Array.isArray(data.issues) ? data.issues : [];
82
+ const lines = [];
83
+
84
+ if (issues.length === 0) {
85
+ lines.push(emptyMessage);
86
+ } else {
87
+ lines.push(...renderTable(
88
+ ['ID', 'TYPE', 'STATUS', 'PRIORITY', 'TITLE'],
89
+ issues.map(issue => [
90
+ issueHandle(issue),
91
+ issue.type ?? '-',
92
+ issue.status ?? '-',
93
+ issue.priority ?? '-',
94
+ issue.title ?? '',
95
+ ]),
96
+ ));
97
+ lines.push('');
98
+ lines.push(`${issues.length} issue${issues.length === 1 ? '' : 's'}`);
99
+ }
100
+
101
+ lines.push(...verificationLines(envelope));
102
+ return lines.join('\n');
103
+ }
104
+
105
+ function pushField(lines, label, value) {
106
+ if (value === null || value === undefined || value === '' || (Array.isArray(value) && value.length === 0)) {
107
+ return;
108
+ }
109
+ lines.push(`${label}: ${Array.isArray(value) ? value.join(', ') : value}`);
110
+ }
111
+
112
+ function pushSection(lines, label, text) {
113
+ if (typeof text !== 'string' || text.trim() === '') return;
114
+ lines.push('', `${label}:`, text);
115
+ }
116
+
117
+ // forge show: the full detail view. This is the surface that prints the FULL id
118
+ // (list/ready show only the 8-char prefix), so every field a script or human
119
+ // needs to act on the issue is reachable without --json.
120
+ function renderIssueShow(envelope) {
121
+ const issue = envelope && envelope.data && typeof envelope.data === 'object' ? envelope.data : {};
122
+ const lines = [];
123
+
124
+ lines.push(issue.title ?? '(untitled)');
125
+ lines.push(String(issue.id ?? ''));
126
+ lines.push('');
127
+
128
+ const status = issue.blocked === true ? `${issue.status ?? '-'} (blocked)` : (issue.status ?? '-');
129
+ const core = [`Type: ${issue.type ?? '-'}`, `Status: ${status}`, `Priority: ${issue.priority ?? '-'}`];
130
+ if (issue.rank !== null && issue.rank !== undefined) core.push(`Rank: ${issue.rank}`);
131
+ lines.push(core.join(' '));
132
+
133
+ pushField(lines, 'Labels', issue.labels);
134
+ pushField(lines, 'Assignee', issue.assignee);
135
+ pushField(lines, 'Claimed by', issue.claimed_by);
136
+ pushField(lines, 'Parent', shortId(issue.parent_id ?? null) || null);
137
+ pushField(lines, 'Blocked by', Array.isArray(issue.blocked_by) ? issue.blocked_by.map(shortId) : null);
138
+ pushField(lines, 'Dependencies', Array.isArray(issue.dependencies) ? issue.dependencies.map(shortId) : null);
139
+ pushField(lines, 'Dependents', Array.isArray(issue.dependents) ? issue.dependents.map(shortId) : null);
140
+ pushField(lines, 'Created', issue.created_at);
141
+ pushField(lines, 'Updated', issue.updated_at);
142
+ if (issue.closed_at) {
143
+ pushField(lines, 'Closed', issue.close_reason ? `${issue.closed_at} (${issue.close_reason})` : issue.closed_at);
144
+ }
145
+
146
+ if (typeof issue.body === 'string' && issue.body.trim() !== '') {
147
+ lines.push('', issue.body);
148
+ }
149
+ pushSection(lines, 'Acceptance criteria', issue.acceptance_criteria);
150
+ pushSection(lines, 'Design', issue.design);
151
+ pushSection(lines, 'Notes', issue.notes);
152
+
153
+ const comments = Array.isArray(issue.comments) ? issue.comments : [];
154
+ if (comments.length > 0) {
155
+ lines.push('', `Comments (${comments.length}):`);
156
+ for (const comment of comments) {
157
+ const meta = [comment.created_at, comment.actor].filter(Boolean).join(' ');
158
+ lines.push(` [${meta}] ${comment.body ?? ''}`);
159
+ }
160
+ }
161
+
162
+ const verification = verificationLines(envelope);
163
+ if (verification.length > 0) {
164
+ lines.push('', ...verification);
165
+ }
166
+ return lines.join('\n');
167
+ }
168
+
169
+ // Write subcommands → the past-tense verb used in their confirmation line.
170
+ const MUTATION_VERBS = {
171
+ create: 'Created',
172
+ update: 'Updated',
173
+ claim: 'Claimed',
174
+ release: 'Released',
175
+ comment: 'Commented on',
176
+ close: 'Closed',
177
+ };
178
+
179
+ // Concise, human-first confirmation for a write, used only at an interactive terminal
180
+ // (piped/non-interactive callers keep the machine-parseable JSON envelope — see the
181
+ // TTY gate in lib/commands/_issue.js).
182
+ function renderIssueMutation(subcommand, envelope) {
183
+ const issue = envelope && envelope.data && typeof envelope.data === 'object' ? envelope.data : {};
184
+ const verb = MUTATION_VERBS[subcommand] || 'Updated';
185
+ // Use the readable handle (slug-shortid) so a stream of confirmations stays scannable,
186
+ // consistent with `forge list`/`ready` (kernel 1db53c60).
187
+ const handle = issueHandle(issue) || '(issue)';
188
+ let headline = `✓ ${verb} ${handle}`;
189
+ if (issue.status) {
190
+ headline += ` [${issue.status}]`;
191
+ }
192
+ const lines = [headline];
193
+ const verification = verificationLines(envelope);
194
+ if (verification.length > 0) {
195
+ lines.push(...verification);
196
+ }
197
+ return lines.join('\n');
198
+ }
199
+
200
+ // Dispatch a human rendering for one subcommand's success envelope.
201
+ function renderIssueEnvelope(subcommand, envelope) {
202
+ if (MUTATION_VERBS[subcommand]) {
203
+ return renderIssueMutation(subcommand, envelope);
204
+ }
205
+ if (subcommand === 'show') {
206
+ return renderIssueShow(envelope);
207
+ }
208
+ return renderIssueList(envelope, {
209
+ emptyMessage: subcommand === 'ready' ? 'No ready issues.' : 'No issues found.',
210
+ });
211
+ }
212
+
213
+ module.exports = {
214
+ shortId,
215
+ issueHandle,
216
+ renderIssueList,
217
+ renderIssueShow,
218
+ renderIssueMutation,
219
+ renderIssueEnvelope,
220
+ };
@@ -0,0 +1,305 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Auto Kernel Tracking — foundational primitive.
5
+ *
6
+ * `ensureBackingIssue()` guarantees that a unit of work (a branch/worktree) has a
7
+ * backing Kernel issue, WITHOUT anyone remembering to run `forge issue create`.
8
+ * It is the reusable core of the auto-tracking design
9
+ * (docs/work/2026-07-06-auto-kernel-tracking/design.md, kernel issue 67ab465b /
10
+ * PR1 131ab014); PR2/PR3 wire it into `forge worktree`, `forge push`, and the
11
+ * lefthook pre-push, but this module is deliberately side-effect-free beyond the
12
+ * injected kernel and never wires itself in.
13
+ *
14
+ * Contract (all guarantees are covered by test/kernel/backing-issue.test.js):
15
+ * - Idempotent + deduped BY BRANCH: if the branch already resolves to an issue
16
+ * (via the kernel_worktrees linkage row, or an issue id encoded in the branch
17
+ * name), that issue is returned/linked — never a duplicate.
18
+ * - Auto-creates a good-enough stub otherwise: title derived from the branch,
19
+ * body noting it was auto-created, labelled `auto-stub`. Then links the branch.
20
+ * - Skips work it should not track: main/master, detached HEAD (no branch), and
21
+ * ignore-glob branches (tmp/spike/wip/throwaway). Returns null, creates nothing.
22
+ * - Degrades gracefully: a missing/limited kernel, a non-kernel backend, or any
23
+ * thrown error yields null (never throws) — so it is safe to call from a hook.
24
+ * - Fully injectable: pass the kernel `driver` + `broker` (or a fake with the
25
+ * same method surface), plus an optional clock (`now`) and id source
26
+ * (`generateId`), so it is unit-testable without a real repo or sqlite DB.
27
+ *
28
+ * Storage choice: the branch->issue link lives in the `kernel_worktrees` row
29
+ * (`issue_id`, keyed by worktree `path`, with `branch` recorded) — the existing
30
+ * "the issue this branch serves" linkage cited by the design. No new table.
31
+ *
32
+ * @module kernel/backing-issue
33
+ */
34
+
35
+ const { randomUUID } = require('node:crypto');
36
+
37
+ const DEFAULT_PROTECTED_BRANCHES = Object.freeze(['main', 'master']);
38
+ const DEFAULT_IGNORE_GLOBS = Object.freeze(['tmp/*', 'spike/*', 'wip/*', 'throwaway/*']);
39
+ const AUTO_STUB_LABEL = 'auto-stub';
40
+ // Branch prefixes stripped before deriving a title / probing for an encoded issue id.
41
+ const BRANCH_PREFIX = /^(feat|feature|fix|bugfix|hotfix|chore|refactor|docs|test|spike|wip)\//i;
42
+ // An issue key encoded in a branch slug, e.g. `feat/kap-7-foo` -> `kap-7`.
43
+ const ENCODED_ISSUE_ID = /^([a-z][a-z0-9]*-\d+)\b/i;
44
+ // Regex metacharacters escaped when compiling an ignore glob.
45
+ const GLOB_METACHARS = '.+^${}()|[]\\';
46
+
47
+ /**
48
+ * Resolve the injected kernel driver/broker from a variety of accepted shapes, so
49
+ * a caller can pass `{ driver, broker }`, the real `{ kernelDriver, kernelBroker }`
50
+ * from buildMigratedKernelIssueDeps(), or `{ kernel: <either> }`.
51
+ *
52
+ * @param {object} options
53
+ * @returns {{ driver: object|null, broker: object|null }}
54
+ */
55
+ function resolveKernel(options) {
56
+ const kernel = options.kernel || {};
57
+ const driver = options.driver || options.kernelDriver || kernel.driver || kernel.kernelDriver || null;
58
+ const broker = options.broker || options.kernelBroker || kernel.broker || kernel.kernelBroker || null;
59
+ return { driver, broker };
60
+ }
61
+
62
+ /**
63
+ * @param {object} driver
64
+ * @returns {boolean} whether the driver exposes the worktree-linkage surface used here.
65
+ */
66
+ function driverIsUsable(driver) {
67
+ return Boolean(
68
+ driver
69
+ && typeof driver.registerWorktree === 'function'
70
+ && (typeof driver.getWorktreeLinkage === 'function' || typeof driver.listWorktrees === 'function'),
71
+ );
72
+ }
73
+
74
+ /** @param {object} broker @returns {boolean} whether the broker can create issues. */
75
+ function brokerIsUsable(broker) {
76
+ return Boolean(broker && typeof broker.runIssueOperation === 'function');
77
+ }
78
+
79
+ /** @param {string} glob @returns {RegExp} anchored regex; `*` = one segment, `**` = any. */
80
+ function globToRegExp(glob) {
81
+ const source = String(glob);
82
+ let pattern = '';
83
+ for (let i = 0; i < source.length; i += 1) {
84
+ const char = source[i];
85
+ if (char === '*') {
86
+ if (source[i + 1] === '*') {
87
+ pattern += '.*';
88
+ i += 1;
89
+ } else {
90
+ pattern += '[^/]*';
91
+ }
92
+ } else if (GLOB_METACHARS.includes(char)) {
93
+ pattern += `\\${char}`;
94
+ } else {
95
+ pattern += char;
96
+ }
97
+ }
98
+ return new RegExp(`^${pattern}$`);
99
+ }
100
+
101
+ /**
102
+ * @param {string} branch
103
+ * @param {string[]} globs
104
+ * @returns {boolean} whether the branch matches any ignore glob.
105
+ */
106
+ function matchesIgnoreGlob(branch, globs) {
107
+ return globs.some(glob => globToRegExp(glob).test(branch));
108
+ }
109
+
110
+ /**
111
+ * Decide whether the branch is a trackable unit of work.
112
+ *
113
+ * @param {string} branch
114
+ * @param {{ protectedBranches: string[], ignoreGlobs: string[] }} config
115
+ * @returns {{ skip: boolean, reason?: string }}
116
+ */
117
+ function classifyBranch(branch, { protectedBranches, ignoreGlobs }) {
118
+ if (!branch || typeof branch !== 'string' || branch === 'HEAD') {
119
+ return { skip: true, reason: 'detached HEAD / no branch' };
120
+ }
121
+ if (protectedBranches.includes(branch)) {
122
+ return { skip: true, reason: `protected branch (${branch})` };
123
+ }
124
+ if (matchesIgnoreGlob(branch, ignoreGlobs)) {
125
+ return { skip: true, reason: `ignore-glob branch (${branch})` };
126
+ }
127
+ return { skip: false };
128
+ }
129
+
130
+ /** @param {string} branch @returns {string} branch slug with any known prefix removed. */
131
+ function stripPrefix(branch) {
132
+ return branch.replace(BRANCH_PREFIX, '');
133
+ }
134
+
135
+ /**
136
+ * Derive a human-ish issue title from a branch name.
137
+ *
138
+ * @param {string} branch
139
+ * @returns {string}
140
+ */
141
+ function deriveTitle(branch) {
142
+ const slug = stripPrefix(branch).replace(/[-_/]+/g, ' ').trim();
143
+ if (!slug) return branch;
144
+ return slug.charAt(0).toUpperCase() + slug.slice(1);
145
+ }
146
+
147
+ /**
148
+ * Extract an issue id encoded in the branch name (e.g. `feat/kap-7-foo` -> `kap-7`).
149
+ *
150
+ * @param {string} branch
151
+ * @returns {string|null}
152
+ */
153
+ function extractEncodedIssueId(branch) {
154
+ const match = ENCODED_ISSUE_ID.exec(stripPrefix(branch));
155
+ return match ? match[1] : null;
156
+ }
157
+
158
+ /**
159
+ * Find an existing branch->issue linkage row (with a non-null issue_id). Prefers the
160
+ * path-keyed row; falls back to scanning by branch. Best-effort — returns null on any error.
161
+ *
162
+ * @param {object} driver
163
+ * @param {{ worktreePath?: string, branch: string }} query
164
+ * @returns {object|null} the linkage row, or null.
165
+ */
166
+ function findExistingLink(driver, { worktreePath, branch }) {
167
+ try {
168
+ if (worktreePath && typeof driver.getWorktreeLinkage === 'function') {
169
+ const row = driver.getWorktreeLinkage({ path: worktreePath });
170
+ if (row && row.issue_id && row.branch === branch) return row;
171
+ }
172
+ if (typeof driver.listWorktrees === 'function') {
173
+ const rows = driver.listWorktrees() || [];
174
+ const match = rows.find(row => row && row.branch === branch && row.issue_id);
175
+ if (match) return match;
176
+ }
177
+ } catch {
178
+ return null;
179
+ }
180
+ return null;
181
+ }
182
+
183
+ /**
184
+ * Persist the branch->issue link into the kernel_worktrees registry.
185
+ *
186
+ * @param {object} driver
187
+ * @param {object} params
188
+ * @returns {object|null} the upserted row, or null on error.
189
+ */
190
+ function linkBranchToIssue(driver, { projectRoot, worktreePath, gitCommonDir, branch, issueId, actor, now }) {
191
+ try {
192
+ return driver.registerWorktree({
193
+ git_common_dir: gitCommonDir || projectRoot || 'unknown',
194
+ path: worktreePath || projectRoot,
195
+ branch,
196
+ actor: actor || null,
197
+ issue_id: issueId,
198
+ work_folder: null,
199
+ registered_at: now().toISOString(),
200
+ state: 'active',
201
+ });
202
+ } catch {
203
+ return null;
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Ensure the given branch has a backing Kernel issue. Idempotent, deduped by branch,
209
+ * best-effort (never throws). See the module doc for the full contract.
210
+ *
211
+ * @param {object} options
212
+ * @param {string} options.branch - Current branch (falsy or 'HEAD' => detached => skip).
213
+ * @param {string} [options.projectRoot] - Repo root; default worktree path / git-common-dir fallback.
214
+ * @param {string} [options.worktreePath] - Absolute worktree path (linkage row key). Defaults to projectRoot.
215
+ * @param {string} [options.gitCommonDir] - Git common dir for the linkage row.
216
+ * @param {object} [options.driver] - Kernel driver (real or fake). Also accepts kernelDriver / kernel.*.
217
+ * @param {object} [options.broker] - Kernel broker (real or fake). Also accepts kernelBroker / kernel.*.
218
+ * @param {string} [options.actor] - Actor recorded on the issue/linkage. Default FORGE_ACTOR or 'forge'.
219
+ * @param {string[]} [options.protectedBranches] - Never-track branches. Default ['main','master'].
220
+ * @param {string[]} [options.ignoreGlobs] - Never-track globs. Default tmp/spike/wip/throwaway.
221
+ * @param {() => Date} [options.now] - Clock injection. Default () => new Date().
222
+ * @param {() => string} [options.generateId] - Issue id source. Default randomUUID.
223
+ * @returns {Promise<{issueId: string, branch: string, created: boolean, existed: boolean, linked?: boolean, label?: string}|null>}
224
+ * The backing issue descriptor, or null when the branch is skipped or the kernel is unavailable.
225
+ */
226
+ async function ensureBackingIssue(options = {}) {
227
+ try {
228
+ const {
229
+ branch,
230
+ projectRoot,
231
+ worktreePath,
232
+ gitCommonDir,
233
+ actor = process.env.FORGE_ACTOR || 'forge',
234
+ protectedBranches = DEFAULT_PROTECTED_BRANCHES,
235
+ ignoreGlobs = DEFAULT_IGNORE_GLOBS,
236
+ now = () => new Date(),
237
+ generateId = randomUUID,
238
+ } = options;
239
+
240
+ // 1. Only track real units of work.
241
+ const classification = classifyBranch(branch, { protectedBranches, ignoreGlobs });
242
+ if (classification.skip) return null;
243
+
244
+ // 2. Kernel must be present + usable, else degrade (non-kernel backend / offline).
245
+ const { driver, broker } = resolveKernel(options);
246
+ if (!driverIsUsable(driver) || !brokerIsUsable(broker)) return null;
247
+
248
+ const linkParams = { projectRoot, worktreePath, gitCommonDir, branch, actor, now };
249
+
250
+ // 3. Idempotency: an existing branch->issue link wins (the common, cheap case).
251
+ const existing = findExistingLink(driver, { worktreePath, branch });
252
+ if (existing) {
253
+ return { issueId: existing.issue_id, branch, created: false, existed: true };
254
+ }
255
+
256
+ // 4. Dedupe: if the branch encodes an existing issue id, link that instead of
257
+ // minting a stub.
258
+ const encodedId = extractEncodedIssueId(branch);
259
+ if (encodedId && typeof driver.loadKernelEntity === 'function') {
260
+ let entity = null;
261
+ try {
262
+ entity = await driver.loadKernelEntity('issue', encodedId);
263
+ } catch {
264
+ entity = null;
265
+ }
266
+ if (entity) {
267
+ // Report link persistence honestly: if registerWorktree failed (returns null),
268
+ // linked:false surfaces that the branch->issue link did NOT persist, so the
269
+ // idempotency guarantee is degraded (a later call may re-create) rather than
270
+ // silently claiming success. Callers/hooks can warn on linked:false.
271
+ const linked = linkBranchToIssue(driver, { ...linkParams, issueId: encodedId });
272
+ return { issueId: encodedId, branch, created: false, existed: true, linked: linked !== null };
273
+ }
274
+ }
275
+
276
+ // 5. Create a good-enough stub, then link the branch to it.
277
+ const issueId = generateId();
278
+ const title = deriveTitle(branch);
279
+ const body = `Auto-created stub for branch ${branch} — enrich with design/acceptance.`;
280
+ const createResult = await broker.runIssueOperation(
281
+ 'create',
282
+ ['--id', issueId, '--title', title, '--body', body, '--label', AUTO_STUB_LABEL, '--type', 'task', '--status', 'open'],
283
+ { actor, origin: 'forge-autotrack', idempotencyKey: `issue.create:${issueId}` },
284
+ );
285
+ if (createResult && createResult.ok === false) return null;
286
+
287
+ const linked = linkBranchToIssue(driver, { ...linkParams, issueId });
288
+ return { issueId, branch, created: true, existed: false, linked: linked !== null, label: AUTO_STUB_LABEL };
289
+ } catch {
290
+ // Best-effort: a hook must never be broken by tracking. Degrade to null.
291
+ return null;
292
+ }
293
+ }
294
+
295
+ module.exports = {
296
+ ensureBackingIssue,
297
+ // Exported for focused unit tests / reuse by PR2-PR4 surfaces.
298
+ classifyBranch,
299
+ deriveTitle,
300
+ extractEncodedIssueId,
301
+ matchesIgnoreGlob,
302
+ DEFAULT_PROTECTED_BRANCHES,
303
+ DEFAULT_IGNORE_GLOBS,
304
+ AUTO_STUB_LABEL,
305
+ };