forge-workflow 0.0.10 → 0.1.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (468) hide show
  1. package/.claude/rules/{greptile-review-process.md → review-process.md} +56 -41
  2. package/.claude/scripts/{greptile-resolve.sh → review-resolve.sh} +13 -3
  3. package/.cursor/rules/permissions-guidance.mdc +2 -2
  4. package/.forge/hooks/check-tdd.js +82 -5
  5. package/.forge/hooks/forge-native-hook.js +431 -0
  6. package/.forge/protected-paths.yaml +157 -0
  7. package/AGENTS.md +151 -61
  8. package/CHANGELOG.md +709 -0
  9. package/CLAUDE.md +9 -118
  10. package/QUICKSTART.md +175 -0
  11. package/README.md +275 -365
  12. package/bin/forge-cmd.js +120 -9
  13. package/bin/forge-preflight.js +26 -5
  14. package/bin/forge.js +532 -489
  15. package/docs/INDEX.md +93 -0
  16. package/docs/PROJECT_DESIGN.md +685 -0
  17. package/docs/architecture/index.md +66 -0
  18. package/docs/architecture/notes/README.md +35 -0
  19. package/docs/architecture/subsystems/README.md +46 -0
  20. package/docs/forge/TOOLCHAIN.md +670 -0
  21. package/docs/forge/VALIDATION.md +82 -0
  22. package/docs/{AGENT_INSTALL_PROMPT.md → guides/AGENT_INSTALL_PROMPT.md} +3 -3
  23. package/docs/guides/BEADS_GITHUB_SYNC.md +32 -0
  24. package/docs/{ENHANCED_ONBOARDING.md → guides/ENHANCED_ONBOARDING.md} +16 -12
  25. package/docs/guides/GREPTILE_SETUP.md +46 -0
  26. package/docs/guides/MANUAL_REVIEW_GUIDE.md +58 -0
  27. package/docs/guides/MIGRATION.md +56 -0
  28. package/docs/guides/SETUP.md +121 -0
  29. package/docs/guides/SUPPORT.md +190 -0
  30. package/docs/guides/WORKFLOW_TEMPLATES.md +74 -0
  31. package/docs/guides/memory-backends.md +183 -0
  32. package/docs/reference/ADAPTERS.md +128 -0
  33. package/docs/reference/AGENT_SKILL_PARITY.md +175 -0
  34. package/docs/reference/COMMANDS.md +214 -0
  35. package/docs/reference/DECISION_DRIFT_GUARDS.md +97 -0
  36. package/docs/{EXAMPLES.md → reference/EXAMPLES.md} +7 -5
  37. package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +135 -0
  38. package/docs/reference/HERMES_INTEGRATION.md +118 -0
  39. package/docs/reference/INSIGHTS_RECAP.md +63 -0
  40. package/docs/reference/INSTALL.md +164 -0
  41. package/docs/reference/KERNEL_TAXONOMY_VALIDATION.md +161 -0
  42. package/docs/reference/PROTECTED_PATH_MANIFEST.md +25 -0
  43. package/docs/reference/RELEASE.md +68 -0
  44. package/docs/reference/RESEARCH_TEMPLATE.md +292 -0
  45. package/docs/{ROADMAP.md → reference/ROADMAP.md} +12 -9
  46. package/docs/reference/SKILLS.md +35 -0
  47. package/docs/reference/STATUS_BOARD.md +80 -0
  48. package/docs/reference/TEMPLATES.md +106 -0
  49. package/docs/{TOOLCHAIN.md → reference/TOOLCHAIN.md} +62 -47
  50. package/docs/reference/VALIDATION.md +82 -0
  51. package/docs/reference/agent-permissions.md +169 -0
  52. package/docs/reference/beads-to-kernel-migration-ux.md +61 -0
  53. package/docs/reference/control-plane-guarantees.md +125 -0
  54. package/docs/reference/dependency-chain.md +331 -0
  55. package/docs/reference/forge-kernel-issue-command-contract.md +161 -0
  56. package/docs/reference/forge-kernel-schema.md +72 -0
  57. package/docs/reference/kernel-conflict-evaluators.md +27 -0
  58. package/docs/reference/patch-md-format.md +77 -0
  59. package/docs/reference/protected-state-surfaces.md +59 -0
  60. package/docs/reference/shepherd.md +155 -0
  61. package/docs/reference/superpowers-analysis.md +320 -0
  62. package/docs/reference/superpowers-integration-options.md +404 -0
  63. package/docs/reference/test-environment.md +519 -0
  64. package/docs/reference/upgrade-safety.md +59 -0
  65. package/lefthook.yml +18 -0
  66. package/lib/activation/ensure-forge-home.js +135 -0
  67. package/lib/adapter-cli.js +307 -0
  68. package/lib/adapters/beads-issue-adapter.js +127 -0
  69. package/lib/adapters/beads-kernel-compat.js +1109 -0
  70. package/lib/adapters/greptile-review-adapter.js +141 -0
  71. package/lib/adapters/kernel-issue-adapter.js +101 -0
  72. package/lib/adapters/pr-state-adapter.js +484 -0
  73. package/lib/adoption-profiles.js +139 -0
  74. package/lib/agents/README.md +2 -6
  75. package/lib/agents/claude.plugin.json +3 -8
  76. package/lib/agents/codex.plugin.json +9 -1
  77. package/lib/agents/cursor.plugin.json +2 -6
  78. package/lib/agents/hermes.plugin.json +22 -0
  79. package/lib/agents-config.js +39 -1236
  80. package/lib/audit-evidence.js +282 -0
  81. package/lib/beads-detect.js +60 -0
  82. package/lib/beads-nudge.js +91 -0
  83. package/lib/beads-setup.js +121 -0
  84. package/lib/beads-sync-scaffold.js +25 -101
  85. package/lib/codex-skills.js +51 -1
  86. package/lib/commands/_aliases.js +248 -0
  87. package/lib/commands/_issue.js +780 -77
  88. package/lib/commands/_manifest.js +93 -0
  89. package/lib/commands/_registry.js +99 -34
  90. package/lib/commands/_resolve-command-opts.js +230 -0
  91. package/lib/commands/_serve-security.js +270 -0
  92. package/lib/commands/adapter.js +12 -0
  93. package/lib/commands/add.js +118 -0
  94. package/lib/commands/audit.js +70 -0
  95. package/lib/commands/blocked.js +5 -0
  96. package/lib/commands/board.js +64 -0
  97. package/lib/commands/claim.js +21 -2
  98. package/lib/commands/claims.js +7 -0
  99. package/lib/commands/clean.js +485 -75
  100. package/lib/commands/close.js +2 -2
  101. package/lib/commands/comment.js +5 -0
  102. package/lib/commands/control.js +148 -0
  103. package/lib/commands/create.js +2 -2
  104. package/lib/commands/dev.js +185 -7
  105. package/lib/commands/doc-gate.js +336 -0
  106. package/lib/commands/doctor.js +156 -0
  107. package/lib/commands/explain.js +15 -0
  108. package/lib/commands/export.js +237 -0
  109. package/lib/commands/gate.js +209 -0
  110. package/lib/commands/hooks.js +377 -0
  111. package/lib/commands/inbox.js +118 -0
  112. package/lib/commands/init.js +604 -0
  113. package/lib/commands/insights.js +79 -0
  114. package/lib/commands/issue.js +12 -1
  115. package/lib/commands/issues.js +17 -0
  116. package/lib/commands/lint.js +5 -0
  117. package/lib/commands/list.js +2 -2
  118. package/lib/commands/memory.js +81 -0
  119. package/lib/commands/merge.js +312 -0
  120. package/lib/commands/migrate.js +362 -0
  121. package/lib/commands/new.js +12 -0
  122. package/lib/commands/options.js +241 -0
  123. package/lib/commands/orient.js +13 -0
  124. package/lib/commands/orphans.js +5 -0
  125. package/lib/commands/patch.js +67 -0
  126. package/lib/commands/plan.js +481 -29
  127. package/lib/commands/pr.js +88 -0
  128. package/lib/commands/preflight.js +211 -0
  129. package/lib/commands/prime.js +13 -0
  130. package/lib/commands/push.js +135 -2
  131. package/lib/commands/ready.js +2 -2
  132. package/lib/commands/recall.js +171 -0
  133. package/lib/commands/recap.js +75 -0
  134. package/lib/commands/recommend.js +0 -1
  135. package/lib/commands/release.js +104 -0
  136. package/lib/commands/remember.js +140 -0
  137. package/lib/commands/role.js +99 -0
  138. package/lib/commands/serve.js +581 -0
  139. package/lib/commands/setup.js +900 -971
  140. package/lib/commands/shepherd.js +501 -0
  141. package/lib/commands/ship.js +59 -1
  142. package/lib/commands/show.js +2 -2
  143. package/lib/commands/stage.js +192 -0
  144. package/lib/commands/stale.js +5 -0
  145. package/lib/commands/status.js +158 -21
  146. package/lib/commands/sync.js +34 -46
  147. package/lib/commands/team.js +4 -1
  148. package/lib/commands/test.js +43 -27
  149. package/lib/commands/update.js +2 -2
  150. package/lib/commands/upgrade.js +47 -0
  151. package/lib/commands/validate.js +43 -18
  152. package/lib/commands/worktree.js +362 -99
  153. package/lib/config-writer.js +202 -0
  154. package/lib/control-plane.js +236 -0
  155. package/lib/core/runtime-graph.js +977 -0
  156. package/lib/dep-guard/keyword-ripple.js +2 -2
  157. package/lib/deprecated-sync-cleanup.js +362 -0
  158. package/lib/detect-agent.js +2 -28
  159. package/lib/detect-worktree.js +35 -9
  160. package/lib/doc-gate/declaration.js +177 -0
  161. package/lib/doc-gate/detect.js +289 -0
  162. package/lib/doc-gate/gate.js +375 -0
  163. package/lib/doc-gate/okf-config.js +128 -0
  164. package/lib/doc-gate/okf.js +429 -0
  165. package/lib/docs-command.js +1161 -6
  166. package/lib/forge-issues.js +382 -11
  167. package/lib/forge-lock.js +262 -0
  168. package/lib/gate-events.js +192 -0
  169. package/lib/global-flags.js +104 -0
  170. package/lib/greptile-match.js +7 -63
  171. package/lib/grounding/context-events.js +230 -0
  172. package/lib/grounding/read-first.js +112 -0
  173. package/lib/harness-capability-matrix.js +380 -0
  174. package/lib/hook-global-installer.js +347 -0
  175. package/lib/hook-renderer.js +541 -0
  176. package/lib/inbox.js +391 -0
  177. package/lib/insights.js +397 -0
  178. package/lib/issue-adapter.js +156 -0
  179. package/lib/issue-backend.js +145 -0
  180. package/lib/issue-render.js +220 -0
  181. package/lib/kernel/backing-issue.js +311 -0
  182. package/lib/kernel/broker.js +1218 -0
  183. package/lib/kernel/cli-broker-factory.js +130 -0
  184. package/lib/kernel/conflict-signal.js +82 -0
  185. package/lib/kernel/evaluators.js +195 -0
  186. package/lib/kernel/fs-class.js +495 -0
  187. package/lib/kernel/issue-command-contract.js +559 -0
  188. package/lib/kernel/issue-id-resolver.js +186 -0
  189. package/lib/kernel/lease-enforcer.js +158 -0
  190. package/lib/kernel/migrations.js +333 -0
  191. package/lib/kernel/owned-kernel.js +43 -0
  192. package/lib/kernel/planning-buckets-schema.js +109 -0
  193. package/lib/kernel/projection-jsonl-writer.js +450 -0
  194. package/lib/kernel/readiness-model.js +329 -0
  195. package/lib/kernel/schema.js +356 -0
  196. package/lib/kernel/sqlite-driver.js +2540 -0
  197. package/lib/kernel/taxonomy-validator.js +394 -0
  198. package/lib/lefthook-check.js +3 -2
  199. package/lib/lefthook-wiring.js +413 -0
  200. package/lib/mcp-config-renderer.js +288 -0
  201. package/lib/memory/graphiti-mcp.js +106 -0
  202. package/lib/memory/router.js +387 -0
  203. package/lib/memory/typed-api.js +102 -0
  204. package/lib/memory-digest.js +195 -0
  205. package/lib/merge-rules.js +395 -0
  206. package/lib/migrate-dry-run.js +466 -0
  207. package/lib/orientation.js +863 -0
  208. package/lib/package-manager-remediation.js +103 -0
  209. package/lib/package-root.js +381 -0
  210. package/lib/patch-intent.js +890 -0
  211. package/lib/plugin-catalog.js +3 -4
  212. package/lib/plugin-manager.js +0 -5
  213. package/lib/pr-bundle.js +186 -0
  214. package/lib/pr-monitor/auto-actions.js +175 -0
  215. package/lib/pr-monitor/differ.js +195 -0
  216. package/lib/pr-monitor/digest.js +206 -0
  217. package/lib/pr-monitor/events.js +0 -0
  218. package/lib/pr-monitor/gather.js +124 -0
  219. package/lib/pr-monitor/journal.js +299 -0
  220. package/lib/pr-monitor/monitor.js +146 -0
  221. package/lib/pr-monitor/render-sticky.js +192 -0
  222. package/lib/pr-monitor/upsert-sticky.js +169 -0
  223. package/lib/pr-monitor/watch-lifecycle.js +95 -0
  224. package/lib/pr-monitor/watch.js +247 -0
  225. package/lib/pr-pull.js +1314 -0
  226. package/lib/pr-shepherd.js +494 -0
  227. package/lib/pr-state-validator.js +59 -0
  228. package/lib/preflight/gates.js +237 -0
  229. package/lib/preflight/runner.js +116 -0
  230. package/lib/project-discovery.js +0 -53
  231. package/lib/project-memory.js +99 -497
  232. package/lib/protected-path-manifest.js +281 -0
  233. package/lib/protected-state-surfaces.js +387 -0
  234. package/lib/release-readiness.js +2105 -0
  235. package/lib/reset.js +59 -45
  236. package/lib/review-adapter.js +68 -0
  237. package/lib/rules-sync.js +260 -0
  238. package/lib/runtime-health.js +241 -20
  239. package/lib/safety-config-renderer.js +268 -0
  240. package/lib/setup-action-log.js +1 -7
  241. package/lib/setup.js +27 -65
  242. package/lib/shell-utils.js +76 -6
  243. package/lib/skills-sync.js +330 -0
  244. package/lib/smart-status/scoring.js +17 -3
  245. package/lib/status/beads-snapshot.js +45 -2
  246. package/lib/status/presenter.js +169 -18
  247. package/lib/status/snapshot.js +186 -0
  248. package/lib/sync-backend.js +202 -0
  249. package/lib/untrusted-content.js +52 -0
  250. package/lib/upgrade-safety.js +251 -0
  251. package/lib/workflow/enforce-stage.js +351 -45
  252. package/lib/workflow/stage-transition.js +115 -0
  253. package/lib/workflow/stages.js +30 -6
  254. package/lib/workflow/state-manager.js +11 -22
  255. package/lib/workflow/state.js +23 -1
  256. package/lib/workflow-profiles.js +17 -5
  257. package/package.json +37 -35
  258. package/rules/documentation.md +19 -0
  259. package/rules/kernel-tracking.md +26 -0
  260. package/rules/security.md +22 -0
  261. package/rules/tdd.md +20 -0
  262. package/rules/workflow.md +27 -0
  263. package/scripts/auto-backing-issue.js +47 -0
  264. package/scripts/beads-context.sh +81 -57
  265. package/scripts/beads-upgrade-smoke.sh +24 -3
  266. package/scripts/bootstrap-windows-tools.sh +78 -0
  267. package/scripts/branch-protection.js +2 -3
  268. package/scripts/check-agents.js +34 -137
  269. package/scripts/commitlint.js +3 -1
  270. package/scripts/conflict-detect.sh +3 -0
  271. package/scripts/dep-guard.sh +22 -3
  272. package/scripts/file-index.sh +3 -0
  273. package/scripts/forge-team/lib/claim.sh +34 -18
  274. package/scripts/forge-team/lib/dashboard.sh +61 -86
  275. package/scripts/forge-team/lib/epic.sh +99 -263
  276. package/scripts/forge-team/lib/hooks.sh +26 -28
  277. package/scripts/forge-team/lib/identity.sh +4 -4
  278. package/scripts/forge-team/lib/sync-github.sh +49 -84
  279. package/scripts/forge-team/lib/verify.sh +93 -83
  280. package/scripts/forge-team/lib/workload.sh +41 -65
  281. package/scripts/forge-team/tests/claim.test.sh +25 -19
  282. package/scripts/forge-team/tests/dashboard.test.sh +31 -46
  283. package/scripts/forge-team/tests/epic.test.sh +52 -71
  284. package/scripts/forge-team/tests/hooks.test.sh +38 -50
  285. package/scripts/forge-team/tests/identity.test.sh +3 -3
  286. package/scripts/forge-team/tests/integration.test.sh +44 -66
  287. package/scripts/forge-team/tests/sync-github.test.sh +50 -83
  288. package/scripts/forge-team/tests/verify.test.sh +37 -46
  289. package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
  290. package/scripts/forge-team/tests/workload.test.sh +32 -66
  291. package/scripts/gen-command-manifest.js +153 -0
  292. package/scripts/gen-embedded-assets.mjs +129 -0
  293. package/scripts/install.ps1 +139 -0
  294. package/scripts/install.sh +268 -0
  295. package/scripts/lib/release-asset.mjs +84 -0
  296. package/scripts/parity-check.mjs +145 -0
  297. package/scripts/parity-check.test.mjs +58 -0
  298. package/scripts/pin-agentic-workflow-images.js +112 -0
  299. package/scripts/pr-auto-actions.js +93 -0
  300. package/scripts/pr-coordinator.sh +3 -0
  301. package/scripts/pr-verdict-label.js +50 -0
  302. package/scripts/preflight-sonar.eslint.config.mjs +44 -0
  303. package/scripts/preflight.sh +21 -94
  304. package/scripts/protected-state-check.js +104 -0
  305. package/scripts/smart-status.sh +60 -57
  306. package/scripts/spikes/config-race-bench.js +111 -0
  307. package/scripts/spikes/harness-capability-matrix.js +13 -0
  308. package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
  309. package/scripts/spikes/protected-path-manifest.js +20 -0
  310. package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
  311. package/scripts/sync-agent-skills.js +62 -0
  312. package/scripts/sync-utils.sh +3 -0
  313. package/scripts/test-ci-shard.js +13 -6
  314. package/scripts/test.js +95 -12
  315. package/skills/claim-safety/SKILL.md +102 -0
  316. package/skills/claim-safety/evals/evals.json +46 -0
  317. package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +44 -50
  318. package/skills/dev/evals/evals.json +50 -0
  319. package/skills/hermes-forge/SKILL.md +185 -0
  320. package/skills/hermes-forge/evals/evals.json +46 -0
  321. package/skills/issue-basics/SKILL.md +111 -0
  322. package/skills/issue-basics/evals/evals.json +46 -0
  323. package/skills/kernel/SKILL.md +166 -0
  324. package/skills/kernel/evals/evals.json +50 -0
  325. package/skills/memory/SKILL.md +102 -0
  326. package/skills/parallel-deep-research/SKILL.md +14 -11
  327. package/skills/parallel-deep-research/evals/evals.json +11 -27
  328. package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +132 -157
  329. package/skills/plan/evals/evals.json +42 -0
  330. package/skills/research/SKILL.md +195 -0
  331. package/skills/research/evals/evals.json +42 -0
  332. package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
  333. package/skills/review/evals/evals.json +42 -0
  334. package/skills/rollback/SKILL.md +110 -0
  335. package/skills/rollback/evals/evals.json +46 -0
  336. package/skills/rollback/references/methods.md +204 -0
  337. package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
  338. package/skills/shepherd/SKILL.md +66 -0
  339. package/skills/shepherd/evals/evals.json +42 -0
  340. package/{.github/prompts/ship.prompt.md → skills/ship/SKILL.md} +81 -45
  341. package/skills/ship/evals/evals.json +42 -0
  342. package/skills/smith/SKILL.md +142 -0
  343. package/skills/smith/evals/evals.json +46 -0
  344. package/skills/smith/references/autonomy-and-gates.md +94 -0
  345. package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
  346. package/skills/sonarcloud/evals/evals.json +46 -0
  347. package/skills/sonarcloud-analysis/SKILL.md +18 -13
  348. package/skills/sonarcloud-analysis/evals/evals.json +11 -15
  349. package/{.github/prompts/status.prompt.md → skills/status/SKILL.md} +20 -10
  350. package/skills/status/evals/evals.json +50 -0
  351. package/skills/triage-ready/SKILL.md +121 -0
  352. package/skills/triage-ready/evals/evals.json +42 -0
  353. package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
  354. package/skills/validate/evals/evals.json +42 -0
  355. package/skills/verify/SKILL.md +299 -0
  356. package/skills/verify/evals/evals.json +50 -0
  357. package/.claude/commands/dev.md +0 -345
  358. package/.claude/commands/plan.md +0 -566
  359. package/.claude/commands/premerge.md +0 -186
  360. package/.claude/commands/research.md +0 -42
  361. package/.claude/commands/review.md +0 -451
  362. package/.claude/commands/rollback.md +0 -721
  363. package/.claude/commands/ship.md +0 -213
  364. package/.claude/commands/sonarcloud.md +0 -152
  365. package/.claude/commands/status.md +0 -90
  366. package/.claude/commands/validate.md +0 -288
  367. package/.claude/commands/verify.md +0 -269
  368. package/.claude/rules/workflow.md +0 -121
  369. package/.cline/workflows/dev.md +0 -342
  370. package/.cline/workflows/plan.md +0 -563
  371. package/.cline/workflows/premerge.md +0 -183
  372. package/.cline/workflows/research.md +0 -39
  373. package/.cline/workflows/review.md +0 -448
  374. package/.cline/workflows/rollback.md +0 -718
  375. package/.cline/workflows/ship.md +0 -210
  376. package/.cline/workflows/sonarcloud.md +0 -146
  377. package/.cline/workflows/status.md +0 -87
  378. package/.cline/workflows/validate.md +0 -285
  379. package/.cline/workflows/verify.md +0 -266
  380. package/.codex/config.toml +0 -11
  381. package/.codex/skills/dev/SKILL.md +0 -345
  382. package/.codex/skills/plan/SKILL.md +0 -566
  383. package/.codex/skills/premerge/SKILL.md +0 -186
  384. package/.codex/skills/research/SKILL.md +0 -42
  385. package/.codex/skills/review/SKILL.md +0 -451
  386. package/.codex/skills/rollback/SKILL.md +0 -721
  387. package/.codex/skills/ship/SKILL.md +0 -213
  388. package/.codex/skills/sonarcloud/SKILL.md +0 -149
  389. package/.codex/skills/status/SKILL.md +0 -90
  390. package/.codex/skills/validate/SKILL.md +0 -288
  391. package/.codex/skills/verify/SKILL.md +0 -269
  392. package/.cursor/commands/dev.md +0 -342
  393. package/.cursor/commands/plan.md +0 -563
  394. package/.cursor/commands/premerge.md +0 -183
  395. package/.cursor/commands/research.md +0 -39
  396. package/.cursor/commands/review.md +0 -448
  397. package/.cursor/commands/ship.md +0 -210
  398. package/.cursor/commands/sonarcloud.md +0 -146
  399. package/.cursor/commands/status.md +0 -87
  400. package/.cursor/commands/validate.md +0 -285
  401. package/.cursor/commands/verify.md +0 -266
  402. package/.cursorrules +0 -149
  403. package/.github/prompts/premerge.prompt.md +0 -188
  404. package/.github/prompts/research.prompt.md +0 -44
  405. package/.github/prompts/rollback.prompt.md +0 -723
  406. package/.github/prompts/verify.prompt.md +0 -271
  407. package/.github/workflows/beads-to-github.yml +0 -89
  408. package/.github/workflows/github-to-beads.yml +0 -100
  409. package/.kilocode/workflows/dev.md +0 -346
  410. package/.kilocode/workflows/plan.md +0 -567
  411. package/.kilocode/workflows/premerge.md +0 -187
  412. package/.kilocode/workflows/research.md +0 -43
  413. package/.kilocode/workflows/review.md +0 -452
  414. package/.kilocode/workflows/rollback.md +0 -722
  415. package/.kilocode/workflows/ship.md +0 -214
  416. package/.kilocode/workflows/sonarcloud.md +0 -150
  417. package/.kilocode/workflows/status.md +0 -91
  418. package/.kilocode/workflows/validate.md +0 -289
  419. package/.kilocode/workflows/verify.md +0 -270
  420. package/.opencode/commands/dev.md +0 -345
  421. package/.opencode/commands/plan.md +0 -566
  422. package/.opencode/commands/premerge.md +0 -186
  423. package/.opencode/commands/research.md +0 -42
  424. package/.opencode/commands/review.md +0 -451
  425. package/.opencode/commands/rollback.md +0 -721
  426. package/.opencode/commands/ship.md +0 -213
  427. package/.opencode/commands/sonarcloud.md +0 -149
  428. package/.opencode/commands/status.md +0 -90
  429. package/.opencode/commands/validate.md +0 -288
  430. package/.opencode/commands/verify.md +0 -269
  431. package/.roo/commands/dev.md +0 -346
  432. package/.roo/commands/plan.md +0 -567
  433. package/.roo/commands/premerge.md +0 -187
  434. package/.roo/commands/research.md +0 -43
  435. package/.roo/commands/review.md +0 -452
  436. package/.roo/commands/rollback.md +0 -722
  437. package/.roo/commands/ship.md +0 -214
  438. package/.roo/commands/sonarcloud.md +0 -150
  439. package/.roo/commands/status.md +0 -91
  440. package/.roo/commands/validate.md +0 -289
  441. package/.roo/commands/verify.md +0 -270
  442. package/docs/BEADS_GITHUB_SYNC.md +0 -281
  443. package/docs/GREPTILE_SETUP.md +0 -400
  444. package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
  445. package/docs/SETUP.md +0 -663
  446. package/docs/VALIDATION.md +0 -363
  447. package/lib/agents/cline.plugin.json +0 -29
  448. package/lib/agents/copilot.plugin.json +0 -24
  449. package/lib/agents/kilocode.plugin.json +0 -22
  450. package/lib/agents/opencode.plugin.json +0 -23
  451. package/lib/agents/roo.plugin.json +0 -30
  452. package/lib/beads-bootstrap.js +0 -225
  453. package/lib/beads-health-check.js +0 -188
  454. package/lib/commands/commands-reset.js +0 -147
  455. package/opencode.json +0 -67
  456. package/scripts/beads-context.test.js +0 -584
  457. package/scripts/github-beads-sync/comment.mjs +0 -64
  458. package/scripts/github-beads-sync/config.mjs +0 -148
  459. package/scripts/github-beads-sync/github-api.mjs +0 -131
  460. package/scripts/github-beads-sync/index.mjs +0 -356
  461. package/scripts/github-beads-sync/label-mapper.mjs +0 -54
  462. package/scripts/github-beads-sync/mapping.mjs +0 -132
  463. package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
  464. package/scripts/github-beads-sync/reverse-sync.mjs +0 -162
  465. package/scripts/github-beads-sync/run-bd.mjs +0 -161
  466. package/scripts/github-beads-sync/sanitize.mjs +0 -121
  467. package/scripts/github-beads-sync.config.json +0 -26
  468. package/scripts/sync-commands.js +0 -600
@@ -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,311 @@
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
+ // Match ACTIVE (live) rows only: a superseded/stale registration for a
175
+ // reused branch name must not be treated as the existing link (be18881c —
176
+ // the third resolver, kept consistent with resolveActiveIssueId and
177
+ // currentBranchIssueFromDriver). Tolerate a null state for legacy rows.
178
+ const match = rows.find(row => row && row.branch === branch && row.issue_id
179
+ && (row.state === 'active' || row.state == null));
180
+ if (match) return match;
181
+ }
182
+ } catch {
183
+ return null;
184
+ }
185
+ return null;
186
+ }
187
+
188
+ /**
189
+ * Persist the branch->issue link into the kernel_worktrees registry.
190
+ *
191
+ * @param {object} driver
192
+ * @param {object} params
193
+ * @returns {object|null} the upserted row, or null on error.
194
+ */
195
+ function linkBranchToIssue(driver, { projectRoot, worktreePath, gitCommonDir, branch, issueId, actor, now }) {
196
+ try {
197
+ return driver.registerWorktree({
198
+ git_common_dir: gitCommonDir || projectRoot || 'unknown',
199
+ path: worktreePath || projectRoot,
200
+ branch,
201
+ actor: actor || null,
202
+ issue_id: issueId,
203
+ work_folder: null,
204
+ registered_at: now().toISOString(),
205
+ state: 'active',
206
+ });
207
+ } catch {
208
+ return null;
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Ensure the given branch has a backing Kernel issue. Idempotent, deduped by branch,
214
+ * best-effort (never throws). See the module doc for the full contract.
215
+ *
216
+ * @param {object} options
217
+ * @param {string} options.branch - Current branch (falsy or 'HEAD' => detached => skip).
218
+ * @param {string} [options.projectRoot] - Repo root; default worktree path / git-common-dir fallback.
219
+ * @param {string} [options.worktreePath] - Absolute worktree path (linkage row key). Defaults to projectRoot.
220
+ * @param {string} [options.gitCommonDir] - Git common dir for the linkage row.
221
+ * @param {object} [options.driver] - Kernel driver (real or fake). Also accepts kernelDriver / kernel.*.
222
+ * @param {object} [options.broker] - Kernel broker (real or fake). Also accepts kernelBroker / kernel.*.
223
+ * @param {string} [options.actor] - Actor recorded on the issue/linkage. Default FORGE_ACTOR or 'forge'.
224
+ * @param {string[]} [options.protectedBranches] - Never-track branches. Default ['main','master'].
225
+ * @param {string[]} [options.ignoreGlobs] - Never-track globs. Default tmp/spike/wip/throwaway.
226
+ * @param {() => Date} [options.now] - Clock injection. Default () => new Date().
227
+ * @param {() => string} [options.generateId] - Issue id source. Default randomUUID.
228
+ * @returns {Promise<{issueId: string, branch: string, created: boolean, existed: boolean, linked?: boolean, label?: string}|null>}
229
+ * The backing issue descriptor, or null when the branch is skipped or the kernel is unavailable.
230
+ */
231
+ async function ensureBackingIssue(options = {}) {
232
+ try {
233
+ const {
234
+ branch,
235
+ projectRoot,
236
+ worktreePath,
237
+ gitCommonDir,
238
+ actor = process.env.FORGE_ACTOR || 'forge',
239
+ protectedBranches = DEFAULT_PROTECTED_BRANCHES,
240
+ ignoreGlobs = DEFAULT_IGNORE_GLOBS,
241
+ now = () => new Date(),
242
+ generateId = randomUUID,
243
+ } = options;
244
+
245
+ // 1. Only track real units of work.
246
+ const classification = classifyBranch(branch, { protectedBranches, ignoreGlobs });
247
+ if (classification.skip) return null;
248
+
249
+ // 2. Kernel must be present + usable, else degrade (non-kernel backend / offline).
250
+ const { driver, broker } = resolveKernel(options);
251
+ if (!driverIsUsable(driver) || !brokerIsUsable(broker)) return null;
252
+
253
+ const linkParams = { projectRoot, worktreePath, gitCommonDir, branch, actor, now };
254
+
255
+ // 3. Idempotency: an existing branch->issue link wins (the common, cheap case).
256
+ const existing = findExistingLink(driver, { worktreePath, branch });
257
+ if (existing) {
258
+ return { issueId: existing.issue_id, branch, created: false, existed: true };
259
+ }
260
+
261
+ // 4. Dedupe: if the branch encodes an existing issue id, link that instead of
262
+ // minting a stub.
263
+ const encodedId = extractEncodedIssueId(branch);
264
+ if (encodedId && typeof driver.loadKernelEntity === 'function') {
265
+ let entity = null;
266
+ try {
267
+ entity = await driver.loadKernelEntity('issue', encodedId);
268
+ } catch {
269
+ entity = null;
270
+ }
271
+ if (entity) {
272
+ // Report link persistence honestly: if registerWorktree failed (returns null),
273
+ // linked:false surfaces that the branch->issue link did NOT persist, so the
274
+ // idempotency guarantee is degraded (a later call may re-create) rather than
275
+ // silently claiming success. Callers/hooks can warn on linked:false.
276
+ const linked = linkBranchToIssue(driver, { ...linkParams, issueId: encodedId });
277
+ return { issueId: encodedId, branch, created: false, existed: true, linked: linked !== null };
278
+ }
279
+ }
280
+
281
+ // 5. Create a good-enough stub, then link the branch to it.
282
+ const issueId = generateId();
283
+ const title = deriveTitle(branch);
284
+ const body = `Auto-created stub for branch ${branch} — enrich with design/acceptance.`;
285
+ const createResult = await broker.runIssueOperation(
286
+ 'create',
287
+ ['--id', issueId, '--title', title, '--body', body, '--label', AUTO_STUB_LABEL, '--type', 'task', '--status', 'open'],
288
+ { actor, origin: 'forge-autotrack', idempotencyKey: `issue.create:${issueId}` },
289
+ );
290
+ if (createResult && createResult.ok === false) return null;
291
+
292
+ const linked = linkBranchToIssue(driver, { ...linkParams, issueId });
293
+ return { issueId, branch, created: true, existed: false, linked: linked !== null, label: AUTO_STUB_LABEL };
294
+ } catch {
295
+ // Best-effort: a hook must never be broken by tracking. Degrade to null.
296
+ return null;
297
+ }
298
+ }
299
+
300
+ module.exports = {
301
+ ensureBackingIssue,
302
+ // Exported for focused unit tests / reuse by PR2-PR4 surfaces.
303
+ classifyBranch,
304
+ deriveTitle,
305
+ extractEncodedIssueId,
306
+ findExistingLink,
307
+ matchesIgnoreGlob,
308
+ DEFAULT_PROTECTED_BRANCHES,
309
+ DEFAULT_IGNORE_GLOBS,
310
+ AUTO_STUB_LABEL,
311
+ };