forge-workflow 0.0.9 → 0.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (479) hide show
  1. package/.claude/rules/{greptile-review-process.md → review-process.md} +56 -41
  2. package/.claude/scripts/{greptile-resolve.sh → review-resolve.sh} +13 -3
  3. package/.cursor/rules/permissions-guidance.mdc +2 -2
  4. package/.forge/hooks/check-tdd.js +3 -0
  5. package/.forge/hooks/forge-native-hook.js +245 -0
  6. package/.forge/protected-paths.yaml +157 -0
  7. package/AGENTS.md +151 -61
  8. package/CHANGELOG.md +681 -0
  9. package/CLAUDE.md +9 -106
  10. package/QUICKSTART.md +171 -0
  11. package/README.md +271 -363
  12. package/bin/forge-cmd.js +120 -9
  13. package/bin/forge-preflight.js +26 -5
  14. package/bin/forge.js +466 -489
  15. package/docs/INDEX.md +93 -0
  16. package/docs/PROJECT_DESIGN.md +685 -0
  17. package/docs/architecture/index.md +66 -0
  18. package/docs/architecture/notes/README.md +35 -0
  19. package/docs/architecture/subsystems/README.md +46 -0
  20. package/docs/{TOOLCHAIN.md → forge/TOOLCHAIN.md} +56 -47
  21. package/docs/forge/VALIDATION.md +82 -0
  22. package/docs/{AGENT_INSTALL_PROMPT.md → guides/AGENT_INSTALL_PROMPT.md} +3 -3
  23. package/docs/guides/BEADS_GITHUB_SYNC.md +32 -0
  24. package/docs/{ENHANCED_ONBOARDING.md → guides/ENHANCED_ONBOARDING.md} +16 -12
  25. package/docs/guides/GREPTILE_SETUP.md +46 -0
  26. package/docs/guides/MANUAL_REVIEW_GUIDE.md +58 -0
  27. package/docs/guides/MIGRATION.md +56 -0
  28. package/docs/guides/SETUP.md +118 -0
  29. package/docs/guides/SUPPORT.md +185 -0
  30. package/docs/guides/WORKFLOW_TEMPLATES.md +74 -0
  31. package/docs/guides/memory-backends.md +183 -0
  32. package/docs/reference/ADAPTERS.md +128 -0
  33. package/docs/reference/AGENT_SKILL_PARITY.md +175 -0
  34. package/docs/reference/COMMANDS.md +205 -0
  35. package/docs/reference/DECISION_DRIFT_GUARDS.md +97 -0
  36. package/docs/{EXAMPLES.md → reference/EXAMPLES.md} +7 -5
  37. package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +135 -0
  38. package/docs/reference/HERMES_INTEGRATION.md +118 -0
  39. package/docs/reference/INSIGHTS_RECAP.md +63 -0
  40. package/docs/reference/INSTALL.md +164 -0
  41. package/docs/reference/KERNEL_TAXONOMY_VALIDATION.md +161 -0
  42. package/docs/reference/PROTECTED_PATH_MANIFEST.md +25 -0
  43. package/docs/reference/RELEASE.md +68 -0
  44. package/docs/reference/RESEARCH_TEMPLATE.md +292 -0
  45. package/docs/{ROADMAP.md → reference/ROADMAP.md} +12 -9
  46. package/docs/reference/SKILLS.md +35 -0
  47. package/docs/reference/STATUS_BOARD.md +80 -0
  48. package/docs/reference/TEMPLATES.md +106 -0
  49. package/docs/reference/TOOLCHAIN.md +658 -0
  50. package/docs/reference/VALIDATION.md +82 -0
  51. package/docs/reference/agent-permissions.md +169 -0
  52. package/docs/reference/beads-to-kernel-migration-ux.md +61 -0
  53. package/docs/reference/control-plane-guarantees.md +125 -0
  54. package/docs/reference/dependency-chain.md +331 -0
  55. package/docs/reference/forge-kernel-issue-command-contract.md +161 -0
  56. package/docs/reference/forge-kernel-schema.md +72 -0
  57. package/docs/reference/kernel-conflict-evaluators.md +27 -0
  58. package/docs/reference/patch-md-format.md +77 -0
  59. package/docs/reference/protected-state-surfaces.md +59 -0
  60. package/docs/reference/shepherd.md +115 -0
  61. package/docs/reference/superpowers-analysis.md +320 -0
  62. package/docs/reference/superpowers-integration-options.md +404 -0
  63. package/docs/reference/test-environment.md +519 -0
  64. package/docs/reference/upgrade-safety.md +59 -0
  65. package/lefthook.yml +18 -0
  66. package/lib/adapter-cli.js +307 -0
  67. package/lib/adapters/beads-issue-adapter.js +127 -0
  68. package/lib/adapters/beads-kernel-compat.js +1042 -0
  69. package/lib/adapters/greptile-review-adapter.js +141 -0
  70. package/lib/adapters/kernel-issue-adapter.js +101 -0
  71. package/lib/adapters/pr-state-adapter.js +484 -0
  72. package/lib/adoption-profiles.js +126 -0
  73. package/lib/agents/README.md +2 -6
  74. package/lib/agents/claude.plugin.json +3 -8
  75. package/lib/agents/codex.plugin.json +9 -1
  76. package/lib/agents/cursor.plugin.json +2 -6
  77. package/lib/agents/hermes.plugin.json +22 -0
  78. package/lib/agents-config.js +39 -1236
  79. package/lib/audit-evidence.js +282 -0
  80. package/lib/beads-setup.js +225 -28
  81. package/lib/beads-sync-scaffold.js +36 -107
  82. package/lib/codex-skills.js +51 -1
  83. package/lib/commands/_issue.js +744 -70
  84. package/lib/commands/_manifest.js +91 -0
  85. package/lib/commands/_registry.js +85 -34
  86. package/lib/commands/_resolve-command-opts.js +261 -0
  87. package/lib/commands/_serve-security.js +270 -0
  88. package/lib/commands/adapter.js +12 -0
  89. package/lib/commands/add.js +118 -0
  90. package/lib/commands/audit.js +70 -0
  91. package/lib/commands/blocked.js +5 -0
  92. package/lib/commands/board.js +64 -0
  93. package/lib/commands/claim.js +21 -2
  94. package/lib/commands/claims.js +7 -0
  95. package/lib/commands/clean.js +485 -75
  96. package/lib/commands/close.js +2 -2
  97. package/lib/commands/comment.js +5 -0
  98. package/lib/commands/control.js +148 -0
  99. package/lib/commands/create.js +2 -2
  100. package/lib/commands/dev.js +185 -7
  101. package/lib/commands/doc-gate.js +336 -0
  102. package/lib/commands/doctor.js +156 -0
  103. package/lib/commands/explain.js +15 -0
  104. package/lib/commands/export.js +237 -0
  105. package/lib/commands/gate.js +192 -0
  106. package/lib/commands/hooks.js +242 -0
  107. package/lib/commands/inbox.js +118 -0
  108. package/lib/commands/init.js +598 -0
  109. package/lib/commands/insights.js +79 -0
  110. package/lib/commands/issue.js +12 -1
  111. package/lib/commands/issues.js +66 -0
  112. package/lib/commands/lint.js +5 -0
  113. package/lib/commands/list.js +2 -2
  114. package/lib/commands/merge.js +312 -0
  115. package/lib/commands/migrate.js +523 -0
  116. package/lib/commands/new.js +12 -0
  117. package/lib/commands/options.js +241 -0
  118. package/lib/commands/orient.js +13 -0
  119. package/lib/commands/orphans.js +5 -0
  120. package/lib/commands/patch.js +67 -0
  121. package/lib/commands/plan.js +436 -24
  122. package/lib/commands/preflight.js +211 -0
  123. package/lib/commands/prime.js +13 -0
  124. package/lib/commands/push.js +69 -2
  125. package/lib/commands/ready.js +2 -2
  126. package/lib/commands/recall.js +116 -0
  127. package/lib/commands/recap.js +61 -0
  128. package/lib/commands/recommend.js +22 -2
  129. package/lib/commands/release.js +91 -0
  130. package/lib/commands/remember.js +74 -0
  131. package/lib/commands/role.js +99 -0
  132. package/lib/commands/serve.js +581 -0
  133. package/lib/commands/setup.js +851 -979
  134. package/lib/commands/shepherd.js +436 -0
  135. package/lib/commands/ship.js +23 -1
  136. package/lib/commands/show.js +2 -2
  137. package/lib/commands/stage.js +192 -0
  138. package/lib/commands/stale.js +5 -0
  139. package/lib/commands/status.js +329 -11
  140. package/lib/commands/sync.js +34 -46
  141. package/lib/commands/team.js +15 -2
  142. package/lib/commands/test.js +58 -7
  143. package/lib/commands/update.js +2 -2
  144. package/lib/commands/upgrade.js +47 -0
  145. package/lib/commands/validate.js +56 -25
  146. package/lib/commands/worktree.js +308 -128
  147. package/lib/config-writer.js +202 -0
  148. package/lib/control-plane.js +236 -0
  149. package/lib/core/runtime-graph.js +946 -0
  150. package/lib/dep-guard/keyword-ripple.js +184 -0
  151. package/lib/deprecated-sync-cleanup.js +362 -0
  152. package/lib/detect-agent.js +2 -28
  153. package/lib/detect-worktree.js +42 -17
  154. package/lib/doc-gate/declaration.js +177 -0
  155. package/lib/doc-gate/detect.js +289 -0
  156. package/lib/doc-gate/gate.js +375 -0
  157. package/lib/doc-gate/okf-config.js +128 -0
  158. package/lib/doc-gate/okf.js +429 -0
  159. package/lib/docs-command.js +1161 -6
  160. package/lib/forge-issues.js +697 -0
  161. package/lib/forge-lock.js +262 -0
  162. package/lib/gate-events.js +193 -0
  163. package/lib/global-flags.js +74 -0
  164. package/lib/greptile-match.js +7 -63
  165. package/lib/harness-capability-matrix.js +380 -0
  166. package/lib/hook-global-installer.js +347 -0
  167. package/lib/hook-renderer.js +451 -0
  168. package/lib/inbox.js +391 -0
  169. package/lib/insights.js +397 -0
  170. package/lib/issue-adapter.js +156 -0
  171. package/lib/issue-backend.js +145 -0
  172. package/lib/issue-render.js +220 -0
  173. package/lib/issue-sync/authority.js +100 -0
  174. package/lib/issue-sync/github-pull.js +184 -0
  175. package/lib/issue-sync/import-primitives.js +98 -0
  176. package/lib/issue-sync/legacy-link-bridge.js +436 -0
  177. package/lib/issue-sync/link-store.js +292 -0
  178. package/lib/issue-sync/project-github.js +123 -0
  179. package/lib/issue-sync/reconcile.js +195 -0
  180. package/lib/issue-sync/schema.js +126 -0
  181. package/lib/kernel/backing-issue.js +305 -0
  182. package/lib/kernel/broker.js +1218 -0
  183. package/lib/kernel/cli-broker-factory.js +130 -0
  184. package/lib/kernel/conflict-signal.js +82 -0
  185. package/lib/kernel/evaluators.js +195 -0
  186. package/lib/kernel/fs-class.js +495 -0
  187. package/lib/kernel/issue-command-contract.js +559 -0
  188. package/lib/kernel/issue-id-resolver.js +186 -0
  189. package/lib/kernel/lease-enforcer.js +158 -0
  190. package/lib/kernel/migrations.js +333 -0
  191. package/lib/kernel/planning-buckets-schema.js +109 -0
  192. package/lib/kernel/projection-jsonl-writer.js +450 -0
  193. package/lib/kernel/readiness-model.js +329 -0
  194. package/lib/kernel/schema.js +356 -0
  195. package/lib/kernel/sqlite-driver.js +2504 -0
  196. package/lib/kernel/taxonomy-validator.js +394 -0
  197. package/lib/lefthook-check.js +8 -4
  198. package/lib/lefthook-wiring.js +413 -0
  199. package/lib/mcp-config-renderer.js +288 -0
  200. package/lib/memory/graphiti-mcp.js +106 -0
  201. package/lib/memory/router.js +387 -0
  202. package/lib/memory/typed-api.js +102 -0
  203. package/lib/memory-digest.js +195 -0
  204. package/lib/merge-rules.js +395 -0
  205. package/lib/migrate-dry-run.js +466 -0
  206. package/lib/orientation.js +863 -0
  207. package/lib/package-manager-remediation.js +103 -0
  208. package/lib/package-root.js +381 -0
  209. package/lib/patch-intent.js +890 -0
  210. package/lib/plugin-catalog.js +3 -4
  211. package/lib/plugin-manager.js +0 -5
  212. package/lib/pr-bundle.js +186 -0
  213. package/lib/pr-monitor/differ.js +195 -0
  214. package/lib/pr-monitor/events.js +0 -0
  215. package/lib/pr-monitor/gather.js +124 -0
  216. package/lib/pr-monitor/journal.js +299 -0
  217. package/lib/pr-monitor/monitor.js +146 -0
  218. package/lib/pr-monitor/render-sticky.js +157 -0
  219. package/lib/pr-monitor/watch-lifecycle.js +95 -0
  220. package/lib/pr-monitor/watch.js +247 -0
  221. package/lib/pr-pull.js +1273 -0
  222. package/lib/pr-shepherd.js +494 -0
  223. package/lib/pr-state-validator.js +59 -0
  224. package/lib/preflight/gates.js +237 -0
  225. package/lib/preflight/runner.js +116 -0
  226. package/lib/project-discovery.js +0 -53
  227. package/lib/project-memory.js +166 -0
  228. package/lib/protected-path-manifest.js +281 -0
  229. package/lib/protected-state-surfaces.js +387 -0
  230. package/lib/release-readiness.js +2089 -0
  231. package/lib/reset.js +59 -45
  232. package/lib/review-adapter.js +68 -0
  233. package/lib/rules-sync.js +260 -0
  234. package/lib/runtime-health.js +332 -23
  235. package/lib/safety-config-renderer.js +268 -0
  236. package/lib/setup-action-log.js +1 -7
  237. package/lib/setup.js +27 -65
  238. package/lib/shell-utils.js +76 -6
  239. package/lib/skills-sync.js +330 -0
  240. package/lib/smart-status/conflicts.js +205 -0
  241. package/lib/smart-status/scoring.js +191 -0
  242. package/lib/status/beads-snapshot.js +145 -0
  243. package/lib/status/presenter.js +216 -0
  244. package/lib/status/snapshot.js +186 -0
  245. package/lib/sync-backend.js +202 -0
  246. package/lib/untrusted-content.js +52 -0
  247. package/lib/upgrade-safety.js +199 -0
  248. package/lib/workflow/enforce-stage.js +298 -47
  249. package/lib/workflow/stage-transition.js +115 -0
  250. package/lib/workflow/stages.js +30 -6
  251. package/lib/workflow/state-manager.js +159 -14
  252. package/lib/workflow/state.js +23 -1
  253. package/lib/workflow-profiles.js +17 -5
  254. package/package.json +46 -36
  255. package/rules/documentation.md +19 -0
  256. package/rules/kernel-tracking.md +26 -0
  257. package/rules/security.md +22 -0
  258. package/rules/tdd.md +20 -0
  259. package/rules/workflow.md +27 -0
  260. package/scripts/auto-backing-issue.js +47 -0
  261. package/scripts/beads-context.sh +165 -22
  262. package/scripts/beads-migrate-to-dolt.sh +7 -0
  263. package/scripts/beads-upgrade-smoke.sh +284 -0
  264. package/scripts/behavioral-judge.sh +115 -11
  265. package/scripts/benchmark.js +349 -63
  266. package/scripts/bootstrap-windows-tools.sh +78 -0
  267. package/scripts/branch-protection.js +2 -3
  268. package/scripts/check-agents.js +34 -137
  269. package/scripts/commitlint.js +3 -1
  270. package/scripts/conflict-detect.sh +3 -0
  271. package/scripts/dep-guard-analyze.js +52 -17
  272. package/scripts/dep-guard-keyword-ripple.js +29 -0
  273. package/scripts/dep-guard-render-review.js +86 -0
  274. package/scripts/dep-guard.sh +64 -232
  275. package/scripts/file-index.sh +3 -0
  276. package/scripts/forge-team/lib/claim.sh +34 -18
  277. package/scripts/forge-team/lib/dashboard.sh +61 -86
  278. package/scripts/forge-team/lib/epic.sh +99 -263
  279. package/scripts/forge-team/lib/hooks.sh +26 -28
  280. package/scripts/forge-team/lib/identity.sh +4 -4
  281. package/scripts/forge-team/lib/sync-github.sh +144 -47
  282. package/scripts/forge-team/lib/verify.sh +93 -83
  283. package/scripts/forge-team/lib/workload.sh +41 -65
  284. package/scripts/forge-team/tests/claim.test.sh +25 -19
  285. package/scripts/forge-team/tests/dashboard.test.sh +31 -46
  286. package/scripts/forge-team/tests/epic.test.sh +52 -71
  287. package/scripts/forge-team/tests/hooks.test.sh +38 -50
  288. package/scripts/forge-team/tests/identity.test.sh +3 -3
  289. package/scripts/forge-team/tests/integration.test.sh +44 -66
  290. package/scripts/forge-team/tests/sync-github.test.sh +183 -79
  291. package/scripts/forge-team/tests/verify.test.sh +37 -46
  292. package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
  293. package/scripts/forge-team/tests/workload.test.sh +32 -66
  294. package/scripts/gen-command-manifest.js +153 -0
  295. package/scripts/gen-embedded-assets.mjs +129 -0
  296. package/scripts/install.ps1 +139 -0
  297. package/scripts/install.sh +268 -0
  298. package/scripts/lib/beads-migrate-to-dolt.mjs +503 -0
  299. package/scripts/lib/release-asset.mjs +84 -0
  300. package/scripts/parity-check.mjs +145 -0
  301. package/scripts/parity-check.test.mjs +58 -0
  302. package/scripts/pin-agentic-workflow-images.js +112 -0
  303. package/scripts/pr-coordinator.sh +3 -0
  304. package/scripts/preflight-sonar.eslint.config.mjs +44 -0
  305. package/scripts/preflight.sh +108 -0
  306. package/scripts/protected-state-check.js +104 -0
  307. package/scripts/smart-status-score.js +31 -0
  308. package/scripts/smart-status-sessions.js +51 -0
  309. package/scripts/smart-status.sh +117 -369
  310. package/scripts/spikes/config-race-bench.js +111 -0
  311. package/scripts/spikes/harness-capability-matrix.js +13 -0
  312. package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
  313. package/scripts/spikes/protected-path-manifest.js +20 -0
  314. package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
  315. package/scripts/sync-agent-skills.js +62 -0
  316. package/scripts/sync-agentic-workflow.js +48 -0
  317. package/scripts/sync-utils.sh +3 -0
  318. package/scripts/test-ci-shard.js +251 -0
  319. package/scripts/test-dashboard.js +188 -52
  320. package/scripts/test-full-suite.js +186 -0
  321. package/scripts/test-profile.js +278 -0
  322. package/scripts/test.js +302 -28
  323. package/scripts/validate.js +143 -0
  324. package/scripts/validate.sh +18 -1
  325. package/skills/claim-safety/SKILL.md +102 -0
  326. package/skills/claim-safety/evals/evals.json +46 -0
  327. package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +46 -52
  328. package/skills/dev/evals/evals.json +50 -0
  329. package/skills/hermes-forge/SKILL.md +185 -0
  330. package/skills/hermes-forge/evals/evals.json +46 -0
  331. package/skills/issue-basics/SKILL.md +111 -0
  332. package/skills/issue-basics/evals/evals.json +46 -0
  333. package/skills/kernel/SKILL.md +166 -0
  334. package/skills/kernel/evals/evals.json +50 -0
  335. package/skills/memory/SKILL.md +102 -0
  336. package/skills/parallel-deep-research/SKILL.md +14 -11
  337. package/skills/parallel-deep-research/evals/evals.json +11 -27
  338. package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +134 -159
  339. package/skills/plan/evals/evals.json +42 -0
  340. package/skills/research/SKILL.md +195 -0
  341. package/skills/research/evals/evals.json +42 -0
  342. package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
  343. package/skills/review/evals/evals.json +42 -0
  344. package/skills/rollback/SKILL.md +110 -0
  345. package/skills/rollback/evals/evals.json +46 -0
  346. package/skills/rollback/references/methods.md +204 -0
  347. package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
  348. package/skills/shepherd/SKILL.md +66 -0
  349. package/skills/shepherd/evals/evals.json +42 -0
  350. package/skills/ship/SKILL.md +251 -0
  351. package/skills/ship/evals/evals.json +42 -0
  352. package/skills/smith/SKILL.md +142 -0
  353. package/skills/smith/evals/evals.json +46 -0
  354. package/skills/smith/references/autonomy-and-gates.md +94 -0
  355. package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
  356. package/skills/sonarcloud/evals/evals.json +46 -0
  357. package/skills/sonarcloud-analysis/SKILL.md +18 -13
  358. package/skills/sonarcloud-analysis/evals/evals.json +11 -15
  359. package/skills/status/SKILL.md +102 -0
  360. package/skills/status/evals/evals.json +50 -0
  361. package/skills/triage-ready/SKILL.md +121 -0
  362. package/skills/triage-ready/evals/evals.json +42 -0
  363. package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
  364. package/skills/validate/evals/evals.json +42 -0
  365. package/skills/verify/SKILL.md +299 -0
  366. package/skills/verify/evals/evals.json +50 -0
  367. package/.claude/commands/dev.md +0 -345
  368. package/.claude/commands/plan.md +0 -566
  369. package/.claude/commands/premerge.md +0 -186
  370. package/.claude/commands/research.md +0 -42
  371. package/.claude/commands/review.md +0 -451
  372. package/.claude/commands/rollback.md +0 -721
  373. package/.claude/commands/ship.md +0 -213
  374. package/.claude/commands/sonarcloud.md +0 -152
  375. package/.claude/commands/status.md +0 -90
  376. package/.claude/commands/validate.md +0 -288
  377. package/.claude/commands/verify.md +0 -269
  378. package/.claude/rules/workflow.md +0 -121
  379. package/.cline/workflows/dev.md +0 -342
  380. package/.cline/workflows/plan.md +0 -563
  381. package/.cline/workflows/premerge.md +0 -183
  382. package/.cline/workflows/research.md +0 -39
  383. package/.cline/workflows/review.md +0 -448
  384. package/.cline/workflows/rollback.md +0 -718
  385. package/.cline/workflows/ship.md +0 -210
  386. package/.cline/workflows/sonarcloud.md +0 -146
  387. package/.cline/workflows/status.md +0 -87
  388. package/.cline/workflows/validate.md +0 -285
  389. package/.cline/workflows/verify.md +0 -266
  390. package/.codex/config.toml +0 -11
  391. package/.codex/skills/dev/SKILL.md +0 -345
  392. package/.codex/skills/plan/SKILL.md +0 -566
  393. package/.codex/skills/premerge/SKILL.md +0 -186
  394. package/.codex/skills/research/SKILL.md +0 -42
  395. package/.codex/skills/review/SKILL.md +0 -451
  396. package/.codex/skills/rollback/SKILL.md +0 -721
  397. package/.codex/skills/ship/SKILL.md +0 -213
  398. package/.codex/skills/sonarcloud/SKILL.md +0 -149
  399. package/.codex/skills/status/SKILL.md +0 -90
  400. package/.codex/skills/validate/SKILL.md +0 -288
  401. package/.codex/skills/verify/SKILL.md +0 -269
  402. package/.cursor/commands/dev.md +0 -342
  403. package/.cursor/commands/plan.md +0 -563
  404. package/.cursor/commands/premerge.md +0 -183
  405. package/.cursor/commands/research.md +0 -39
  406. package/.cursor/commands/review.md +0 -448
  407. package/.cursor/commands/ship.md +0 -210
  408. package/.cursor/commands/sonarcloud.md +0 -146
  409. package/.cursor/commands/status.md +0 -87
  410. package/.cursor/commands/validate.md +0 -285
  411. package/.cursor/commands/verify.md +0 -266
  412. package/.cursorrules +0 -149
  413. package/.github/prompts/premerge.prompt.md +0 -188
  414. package/.github/prompts/research.prompt.md +0 -44
  415. package/.github/prompts/rollback.prompt.md +0 -723
  416. package/.github/prompts/ship.prompt.md +0 -215
  417. package/.github/prompts/status.prompt.md +0 -92
  418. package/.github/prompts/verify.prompt.md +0 -271
  419. package/.github/workflows/beads-to-github.yml +0 -56
  420. package/.github/workflows/github-to-beads.yml +0 -97
  421. package/.kilocode/workflows/dev.md +0 -346
  422. package/.kilocode/workflows/plan.md +0 -567
  423. package/.kilocode/workflows/premerge.md +0 -187
  424. package/.kilocode/workflows/research.md +0 -43
  425. package/.kilocode/workflows/review.md +0 -452
  426. package/.kilocode/workflows/rollback.md +0 -722
  427. package/.kilocode/workflows/ship.md +0 -214
  428. package/.kilocode/workflows/sonarcloud.md +0 -150
  429. package/.kilocode/workflows/status.md +0 -91
  430. package/.kilocode/workflows/validate.md +0 -289
  431. package/.kilocode/workflows/verify.md +0 -270
  432. package/.opencode/commands/dev.md +0 -345
  433. package/.opencode/commands/plan.md +0 -566
  434. package/.opencode/commands/premerge.md +0 -186
  435. package/.opencode/commands/research.md +0 -42
  436. package/.opencode/commands/review.md +0 -451
  437. package/.opencode/commands/rollback.md +0 -721
  438. package/.opencode/commands/ship.md +0 -213
  439. package/.opencode/commands/sonarcloud.md +0 -149
  440. package/.opencode/commands/status.md +0 -90
  441. package/.opencode/commands/validate.md +0 -288
  442. package/.opencode/commands/verify.md +0 -269
  443. package/.roo/commands/dev.md +0 -346
  444. package/.roo/commands/plan.md +0 -567
  445. package/.roo/commands/premerge.md +0 -187
  446. package/.roo/commands/research.md +0 -43
  447. package/.roo/commands/review.md +0 -452
  448. package/.roo/commands/rollback.md +0 -722
  449. package/.roo/commands/ship.md +0 -214
  450. package/.roo/commands/sonarcloud.md +0 -150
  451. package/.roo/commands/status.md +0 -91
  452. package/.roo/commands/validate.md +0 -289
  453. package/.roo/commands/verify.md +0 -270
  454. package/docs/BEADS_GITHUB_SYNC.md +0 -255
  455. package/docs/GREPTILE_SETUP.md +0 -400
  456. package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
  457. package/docs/SETUP.md +0 -663
  458. package/docs/VALIDATION.md +0 -363
  459. package/lib/agents/cline.plugin.json +0 -29
  460. package/lib/agents/copilot.plugin.json +0 -24
  461. package/lib/agents/kilocode.plugin.json +0 -22
  462. package/lib/agents/opencode.plugin.json +0 -23
  463. package/lib/agents/roo.plugin.json +0 -30
  464. package/lib/beads-health-check.js +0 -143
  465. package/lib/commands/commands-reset.js +0 -147
  466. package/opencode.json +0 -67
  467. package/scripts/beads-context.test.js +0 -567
  468. package/scripts/github-beads-sync/comment.mjs +0 -64
  469. package/scripts/github-beads-sync/config.mjs +0 -148
  470. package/scripts/github-beads-sync/github-api.mjs +0 -131
  471. package/scripts/github-beads-sync/index.mjs +0 -332
  472. package/scripts/github-beads-sync/label-mapper.mjs +0 -54
  473. package/scripts/github-beads-sync/mapping.mjs +0 -78
  474. package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
  475. package/scripts/github-beads-sync/reverse-sync.mjs +0 -138
  476. package/scripts/github-beads-sync/run-bd.mjs +0 -161
  477. package/scripts/github-beads-sync/sanitize.mjs +0 -121
  478. package/scripts/github-beads-sync.config.json +0 -26
  479. package/scripts/sync-commands.js +0 -600
@@ -8,8 +8,8 @@
8
8
  #
9
9
  # Env overrides (for testing):
10
10
  # GH_CMD — Path to gh binary (default: gh)
11
- # BD_CMD — Path to bd binary (default: bd)
12
- # BEADS_ROOT — Path to .beads/ parent directory (default: .)
11
+ # FORGE_CMD — Path to forge binary (default: forge)
12
+ # FORGE_ROOT — Path to .forge/ parent directory (default: .)
13
13
  #
14
14
  # This file does NOT set errexit/pipefail — callers manage their own shell options.
15
15
 
@@ -56,11 +56,11 @@ _hooks_warn() {
56
56
  }
57
57
 
58
58
  # ── _check_auto_sync_enabled ─────────────────────────────────────────────
59
- # Reads .beads/config.yaml for team.auto-sync setting.
59
+ # Reads .forge/config.yaml for the team.auto-sync setting.
60
60
  # Returns 0 if enabled (default), 1 if disabled.
61
61
  _check_auto_sync_enabled() {
62
- local beads_root="${BEADS_ROOT:-.}"
63
- local config_file="$beads_root/.beads/config.yaml"
62
+ local forge_root="${FORGE_ROOT:-.}"
63
+ local config_file="$forge_root/.forge/config.yaml"
64
64
 
65
65
  if [[ ! -f "$config_file" ]]; then
66
66
  # Default: enabled
@@ -108,7 +108,7 @@ forge_team_sync() {
108
108
  done
109
109
 
110
110
  local gh_cmd="${GH_CMD:-gh}"
111
- local bd_cmd="${BD_CMD:-bd}"
111
+ local forge_cmd="${FORGE_CMD:-forge}"
112
112
 
113
113
  # 1. Check gh auth status
114
114
  if ! "$gh_cmd" auth status >/dev/null 2>&1; then
@@ -122,44 +122,42 @@ forge_team_sync() {
122
122
  return 0
123
123
  fi
124
124
 
125
- # 3. Get all in_progress issues
126
- local list_output
127
- list_output="$("$bd_cmd" list --status=in_progress 2>/dev/null)" || {
125
+ # 3. Get all in_progress issues as JSON
126
+ local list_json
127
+ list_json="$("$forge_cmd" issue list --status=in_progress --json 2>/dev/null)" || {
128
128
  if [[ "$quiet" == "false" ]]; then
129
129
  _hooks_warn "Failed to list issues, sync skipped"
130
130
  fi
131
131
  return 0
132
132
  }
133
133
 
134
- # Filter empty lines
135
- if [[ -z "$list_output" ]] || [[ -z "$(echo "$list_output" | tr -d '[:space:]')" ]]; then
134
+ # Build "id<TAB>has_github" lines (has_github is true when a github_issue:<n>
135
+ # label is present on the issue).
136
+ local issue_lines
137
+ issue_lines="$(printf '%s' "$list_json" | jq -r '
138
+ (.data.issues // [])[]
139
+ | [ .id, (((.labels // []) | any(test("^github_issue:"))) | tostring) ]
140
+ | join("\t")' 2>/dev/null | tr -d '\r')"
141
+
142
+ if [[ -z "$issue_lines" ]] || [[ -z "$(printf '%s' "$issue_lines" | tr -d '[:space:]')" ]]; then
136
143
  if [[ "$quiet" == "false" ]]; then
137
144
  _hooks_info "Nothing to sync"
138
145
  fi
139
146
  return 0
140
147
  fi
141
148
 
142
- # 4. For each issue, check for github_issue state and sync
149
+ # 4. For each issue with a github_issue label, sync its status to GitHub
143
150
  local synced=0
144
151
  local warnings=0
145
152
 
146
- while IFS= read -r line; do
147
- [[ -z "$line" ]] && continue
148
-
149
- # Extract issue id
150
- local issue_id
151
- issue_id="$(echo "$line" | grep -oE 'forge-[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)*' | head -1)"
153
+ while IFS=$'\t' read -r issue_id has_github; do
152
154
  [[ -z "$issue_id" ]] && continue
153
155
 
154
- # Get details to check for github_issue state
155
- local show_output
156
- show_output="$("$bd_cmd" show "$issue_id" 2>/dev/null)" || continue
157
-
158
- # Check if it has github_issue:N state
159
- local gh_issue_num
160
- gh_issue_num="$(printf '%s' "$show_output" | grep -oP 'github_issue:\K[0-9]+' | head -1 || true)"
161
-
162
- if [[ -z "$gh_issue_num" ]]; then
156
+ # Skip only when there is neither a github_issue:<n> label nor a legacy
157
+ # mapping-file association. _get_github_issue_number (from sync-github.sh)
158
+ # checks the label first, then the mapping-file fallback, so mapping-only
159
+ # linked issues are still synced instead of being skipped here.
160
+ if [[ "$has_github" != "true" ]] && ! _get_github_issue_number "$issue_id" >/dev/null 2>&1; then
163
161
  # No GitHub issue linked, skip but warn
164
162
  warnings=$((warnings + 1))
165
163
  if [[ "$quiet" == "false" ]]; then
@@ -177,7 +175,7 @@ forge_team_sync() {
177
175
  _hooks_warn "Failed to sync $issue_id to GitHub"
178
176
  fi
179
177
  fi
180
- done <<< "$list_output"
178
+ done <<< "$issue_lines"
181
179
 
182
180
  # 5. Report
183
181
  if [[ "$quiet" == "false" ]]; then
@@ -2,7 +2,7 @@
2
2
  # scripts/forge-team/lib/identity.sh — GitHub-username-only identity mapping
3
3
  #
4
4
  # Auto-detects developer via `gh api user`. Stores identity in
5
- # .beads/team-map.jsonl (append-only, LWW per GitHub username).
5
+ # .forge/team-map.jsonl (append-only, LWW per GitHub username).
6
6
  #
7
7
  # Functions:
8
8
  # get_github_user — Current dev's GitHub username (cached)
@@ -14,7 +14,7 @@
14
14
  #
15
15
  # Env overrides:
16
16
  # GH_CMD — Path to gh binary (for testing)
17
- # TEAM_MAP_ROOT — Root dir for .beads/team-map.jsonl (default: .)
17
+ # TEAM_MAP_ROOT — Root dir for .forge/team-map.jsonl (default: .)
18
18
  #
19
19
  # This file does NOT set errexit/pipefail — callers manage their own shell options.
20
20
 
@@ -69,7 +69,7 @@ _identity_prompt() {
69
69
  # _team_map_file — Returns the path to team-map.jsonl
70
70
  _team_map_file() {
71
71
  local root="${TEAM_MAP_ROOT:-.}"
72
- printf '%s' "$root/.beads/team-map.jsonl"
72
+ printf '%s' "$root/.forge/team-map.jsonl"
73
73
  }
74
74
 
75
75
  # _validate_github_username <username>
@@ -124,7 +124,7 @@ get_github_user() {
124
124
  }
125
125
 
126
126
  # team_map_add <github-user> [display-name]
127
- # Adds/updates entry in .beads/team-map.jsonl.
127
+ # Adds/updates entry in .forge/team-map.jsonl.
128
128
  # Schema: {"github":"<user>","display_name":"<name>","updated_at":"<ts>","is_bot":false}
129
129
  team_map_add() {
130
130
  local github_user="${1:-}"
@@ -1,17 +1,17 @@
1
1
  #!/usr/bin/env bash
2
- # scripts/forge-team/lib/sync-github.sh — Bidirectional sync between Beads and GitHub
2
+ # scripts/forge-team/lib/sync-github.sh — Bidirectional sync between Forge issues and GitHub
3
3
  #
4
4
  # Functions:
5
- # sync_issue_create <beads-id> — Create GitHub issue from Beads
6
- # sync_issue_claim <beads-id> — Assign current dev on GitHub
7
- # sync_issue_status <beads-id> <status> — Update status label on GitHub
8
- # sync_issue_close <beads-id> — Close GitHub issue
9
- # sync_issue_deps <beads-id-a> <beads-id-b> — Add "Blocked by" comment
10
- # _get_github_issue_number <beads-id> — Extract github_issue from bd show
5
+ # sync_issue_create <issue-id> — Create GitHub issue from a Forge issue
6
+ # sync_issue_claim <issue-id> — Assign current dev on GitHub
7
+ # sync_issue_status <issue-id> <status> — Update status label on GitHub
8
+ # sync_issue_close <issue-id> — Close GitHub issue
9
+ # sync_issue_deps <issue-id-a> <issue-id-b> — Add "Blocked by" comment
10
+ # _get_github_issue_number <issue-id> — Extract canonical GitHub issue from issue show
11
11
  #
12
12
  # Env overrides (for testing):
13
- # GH_CMD — Path to gh binary (default: gh)
14
- # BD_CMD — Path to bd binary (default: bd)
13
+ # GH_CMD — Path to gh binary (default: gh)
14
+ # FORGE_CMD — Path to forge binary (default: forge)
15
15
  #
16
16
  # All GitHub-sourced strings are sanitized via sanitize_for_agent() before
17
17
  # use in shell commands (OWASP A03).
@@ -71,29 +71,123 @@ _safe_sanitize() {
71
71
  printf '%s' "$input"
72
72
  }
73
73
 
74
+ _extract_issue_number_from_url() {
75
+ local input="${1:-}"
76
+ local suffix="${input##*/issues/}"
77
+
78
+ if [[ "$suffix" == "$input" ]]; then
79
+ return 1
80
+ fi
81
+
82
+ suffix="${suffix%%[^0-9]*}"
83
+ if [[ -z "$suffix" ]]; then
84
+ return 1
85
+ fi
86
+
87
+ printf '%s' "$suffix"
88
+ }
89
+
90
+ _sync_mapping_file() {
91
+ local root="${TEAM_MAP_ROOT:-.}"
92
+ printf '%s' "${SYNC_MAPPING_FILE:-$root/.github/beads-mapping.json}"
93
+ }
94
+
95
+ _persist_issue_mapping() {
96
+ local beads_id="${1:-}"
97
+ local issue_num="${2:-}"
98
+ local mapping_file
99
+ mapping_file="$(_sync_mapping_file)"
100
+
101
+ node - "$mapping_file" "$issue_num" "$beads_id" <<'NODE'
102
+ const fs = require('node:fs');
103
+ const path = require('node:path');
104
+
105
+ const [mappingPath, issueNum, beadsId] = process.argv.slice(2);
106
+ let data = {};
107
+
108
+ if (fs.existsSync(mappingPath)) {
109
+ const raw = fs.readFileSync(mappingPath, 'utf8').trim();
110
+ if (raw) {
111
+ data = JSON.parse(raw);
112
+ }
113
+ }
114
+
115
+ fs.mkdirSync(path.dirname(mappingPath), { recursive: true });
116
+ data[String(issueNum)] = beadsId;
117
+ fs.writeFileSync(mappingPath, `${JSON.stringify(data, null, 2)}\n`, 'utf8');
118
+ NODE
119
+ }
120
+
121
+ _get_issue_number_from_mapping() {
122
+ local beads_id="${1:-}"
123
+ local mapping_file
124
+ mapping_file="$(_sync_mapping_file)"
125
+
126
+ node - "$mapping_file" "$beads_id" <<'NODE'
127
+ const fs = require('node:fs');
128
+
129
+ const [mappingPath, beadsId] = process.argv.slice(2);
130
+ if (!fs.existsSync(mappingPath)) {
131
+ process.exit(0);
132
+ }
133
+
134
+ const raw = fs.readFileSync(mappingPath, 'utf8').trim();
135
+ if (!raw) {
136
+ process.exit(0);
137
+ }
138
+
139
+ const data = JSON.parse(raw);
140
+ let newestIssueNum = null;
141
+ for (const [issueNum, mappedBeadsId] of Object.entries(data)) {
142
+ if (mappedBeadsId !== beadsId) {
143
+ continue;
144
+ }
145
+ const parsedIssueNum = Number.parseInt(issueNum, 10);
146
+ if (!Number.isFinite(parsedIssueNum)) {
147
+ continue;
148
+ }
149
+ if (newestIssueNum == null || parsedIssueNum > newestIssueNum) {
150
+ newestIssueNum = parsedIssueNum;
151
+ }
152
+ }
153
+ if (newestIssueNum != null) {
154
+ process.stdout.write(String(newestIssueNum));
155
+ }
156
+ NODE
157
+ }
158
+
74
159
  # ── _get_github_issue_number ─────────────────────────────────────────────
75
- # Extracts github_issue number from `bd show <id>` output.
76
- # Looks for `github_issue:N` pattern.
160
+ # Extracts the canonical GitHub issue number from the issue's github_issue:<n>
161
+ # label (via `issue show <id> --json`), falling back to the local mapping file.
77
162
  # Returns the number or empty string (exit 1 if not found).
78
163
  _get_github_issue_number() {
79
164
  local beads_id="${1:-}"
80
165
  if [[ -z "$beads_id" ]]; then
81
- _sync_error "beads-id required"
166
+ _sync_error "issue id required"
82
167
  return 1
83
168
  fi
84
169
 
85
- local bd_cmd="${BD_CMD:-bd}"
170
+ local forge_cmd="${FORGE_CMD:-forge}"
86
171
  local show_output
87
- show_output="$("$bd_cmd" show "$beads_id" 2>/dev/null)" || {
88
- _sync_error "Failed to get beads info for $beads_id"
172
+ show_output="$("$forge_cmd" issue show "$beads_id" --json 2>/dev/null)" || {
173
+ _sync_error "Failed to get issue info for $beads_id"
89
174
  return 1
90
175
  }
91
176
 
92
177
  local issue_num
93
- issue_num="$(printf '%s' "$show_output" | grep -oP 'github_issue:\K[0-9]+' | head -1)"
178
+ issue_num="$(printf '%s' "$show_output" | jq -r '
179
+ (.data.issue // .data // {})
180
+ | (.labels // [])
181
+ | map(select(startswith("github_issue:")))
182
+ | (.[0] // "")
183
+ | ltrimstr("github_issue:")' 2>/dev/null)"
94
184
 
95
185
  if [[ -z "$issue_num" ]]; then
96
- _sync_error "No github_issue found for $beads_id"
186
+ issue_num="$(_get_issue_number_from_mapping "$beads_id" 2>/dev/null || true)"
187
+ fi
188
+
189
+ if [[ -z "$issue_num" ]]; then
190
+ _sync_error "No canonical GitHub number found for $beads_id"
97
191
  return 1
98
192
  fi
99
193
 
@@ -102,22 +196,16 @@ _get_github_issue_number() {
102
196
  }
103
197
 
104
198
  # ── _get_issue_title ─────────────────────────────────────────────────────
105
- # Extracts issue title from `bd show <id>` output.
106
- # Looks for "Title: <text>" line or falls back to the summary line.
199
+ # Extracts the issue title from `issue show <id> --json`. Falls back to the
200
+ # issue id when the title is empty.
107
201
  _get_issue_title() {
108
202
  local beads_id="${1:-}"
109
- local bd_cmd="${BD_CMD:-bd}"
203
+ local forge_cmd="${FORGE_CMD:-forge}"
110
204
  local show_output
111
- show_output="$("$bd_cmd" show "$beads_id" 2>/dev/null)" || return 1
205
+ show_output="$("$forge_cmd" issue show "$beads_id" --json 2>/dev/null)" || return 1
112
206
 
113
207
  local title
114
- # Try "Title: ..." line first
115
- title="$(printf '%s' "$show_output" | grep -oP '^Title:\s*\K.*' | head -1)"
116
-
117
- if [[ -z "$title" ]]; then
118
- # Fallback: extract from summary line "○ <id> · <title> [...]"
119
- title="$(printf '%s' "$show_output" | grep -oP '·\s*\K[^\[]+' | head -1 | sed 's/[[:space:]]*$//')"
120
- fi
208
+ title="$(printf '%s' "$show_output" | jq -r '(.data.issue // .data // {}) | (.title // "")' 2>/dev/null)"
121
209
 
122
210
  if [[ -z "$title" ]]; then
123
211
  title="$beads_id"
@@ -128,9 +216,10 @@ _get_issue_title() {
128
216
 
129
217
  # ── Public API ───────────────────────────────────────────────────────────
130
218
 
131
- # sync_issue_create <beads-id>
132
- # Creates GitHub issue from Beads issue data.
133
- # Stores GitHub issue number back via `bd set-state`.
219
+ # sync_issue_create <issue-id>
220
+ # Creates a GitHub issue from the Forge issue data.
221
+ # Persists both the canonical mapping file and the github_issue:<n> label so
222
+ # that downstream hook and verify flows can resolve the GitHub association.
134
223
  # Returns 0 on success, 1 on failure.
135
224
  sync_issue_create() {
136
225
  local beads_id="${1:-}"
@@ -140,9 +229,24 @@ sync_issue_create() {
140
229
  fi
141
230
 
142
231
  local gh_cmd="${GH_CMD:-gh}"
143
- local bd_cmd="${BD_CMD:-bd}"
232
+ local forge_cmd="${FORGE_CMD:-forge}"
233
+
234
+ # Retry-safety / idempotency: the mapping file is persisted *before* the
235
+ # github_issue:<n> label is stored, so a previous run that created the GitHub
236
+ # issue and mapping but failed on the label leaves a recoverable mapping entry.
237
+ # On retry, repair the missing label from the mapping instead of calling
238
+ # `gh issue create` again and creating a duplicate GitHub issue.
239
+ local existing_num
240
+ existing_num="$(_get_issue_number_from_mapping "$beads_id" 2>/dev/null || true)"
241
+ if [[ -n "$existing_num" ]]; then
242
+ if ! "$forge_cmd" issue update "$beads_id" --label "github_issue:$existing_num" >/dev/null 2>&1; then
243
+ _sync_error "Failed to store github_issue:$existing_num for $beads_id"
244
+ return 1
245
+ fi
246
+ return 0
247
+ fi
144
248
 
145
- # Get title from bd show
249
+ # Get title from the issue backend
146
250
  local raw_title
147
251
  raw_title="$(_get_issue_title "$beads_id")" || {
148
252
  _sync_error "Failed to get title for $beads_id"
@@ -162,28 +266,21 @@ sync_issue_create() {
162
266
 
163
267
  # Extract issue number from URL (e.g., https://github.com/.../issues/42)
164
268
  local issue_num
165
- issue_num="$(printf '%s' "$gh_output" | grep -oP '/issues/\K[0-9]+' | head -1)"
269
+ issue_num="$(_extract_issue_number_from_url "$gh_output")"
166
270
 
167
271
  if [[ -z "$issue_num" ]]; then
168
272
  _sync_error "Could not extract issue number from: $gh_output"
169
273
  return 1
170
274
  fi
171
275
 
172
- # Store issue number in Beads
173
- "$bd_cmd" set-state "$beads_id" "github_issue=$issue_num" >/dev/null 2>&1 || {
174
- _sync_error "Failed to store github_issue=$issue_num for $beads_id"
276
+ if ! _persist_issue_mapping "$beads_id" "$issue_num"; then
277
+ _sync_error "Failed to persist GitHub link for $beads_id"
175
278
  return 1
176
- }
279
+ fi
177
280
 
178
- # Update beads-mapping.json for verify Check 4
179
- local mapping_file=".github/beads-mapping.json"
180
- if [[ -f "$mapping_file" ]]; then
181
- local updated
182
- updated="$(jq --arg num "$issue_num" --arg id "$beads_id" '.[$num] = $id' "$mapping_file")"
183
- printf '%s\n' "$updated" > "$mapping_file"
184
- else
185
- mkdir -p .github
186
- printf '%s\n' "{\"$issue_num\":\"$beads_id\"}" > "$mapping_file"
281
+ if ! "$forge_cmd" issue update "$beads_id" --label "github_issue:$issue_num" >/dev/null 2>&1; then
282
+ _sync_error "Failed to store github_issue:$issue_num for $beads_id"
283
+ return 1
187
284
  fi
188
285
 
189
286
  return 0
@@ -7,15 +7,15 @@
7
7
  # Checks:
8
8
  # 1. gh auth status (GitHub CLI authenticated)
9
9
  # 2. Identity mapped in team-map.jsonl
10
- # 3. Orphan Beads issues (Beads without GitHub counterpart)
11
- # 4. Orphan GitHub issues (GitHub without Beads counterpart)
12
- # 5. Assignee consistency between Beads and GitHub
10
+ # 3. Orphan tracked issues (no GitHub counterpart)
11
+ # 4. Orphan GitHub issues (no tracked counterpart)
12
+ # 5. Assignee consistency between tracked issues and GitHub
13
13
  #
14
14
  # Env overrides (for testing):
15
15
  # GH_CMD — Path to gh binary (default: gh)
16
- # BD_CMD — Path to bd binary (default: bd)
17
- # TEAM_MAP_ROOT — Root dir for .beads/ and .github/ (default: .)
18
- # VERIFY_MAPPING_FILE — Path to beads-mapping.json (default: $TEAM_MAP_ROOT/.github/beads-mapping.json)
16
+ # FORGE_CMD — Path to forge binary (default: forge)
17
+ # TEAM_MAP_ROOT — Root dir for .forge/ and .github/ (default: .)
18
+ # VERIFY_MAPPING_FILE — Path to the GitHub issue mapping (default: $TEAM_MAP_ROOT/.github/beads-mapping.json)
19
19
  #
20
20
  # This file does NOT set errexit/pipefail — callers manage their own shell options.
21
21
 
@@ -66,16 +66,19 @@ _mapping_file() {
66
66
  printf '%s' "${VERIFY_MAPPING_FILE:-$root/.github/beads-mapping.json}"
67
67
  }
68
68
 
69
- # ── _extract_beads_ids ───────────────────────────────────────────────────
70
- # Parse bd list output to extract beads IDs.
71
- # Input format: "◐ forge-aaa · Feature A" or "○ forge-bbb · Feature B"
72
- # Output: one ID per line
73
- _extract_beads_ids() {
74
- local input="${1:-}"
75
- if [[ -z "$input" ]]; then
76
- return 0
77
- fi
78
- printf '%s\n' "$input" | grep -oP '(forge-[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)*)' || true
69
+ # ── _verify_active_issues_json ─────────────────────────────────────────────
70
+ # Merged JSON array of open + in_progress issues. The Kernel list filter does
71
+ # not accept comma-joined statuses, so query each status separately and
72
+ # concatenate the issue arrays.
73
+ _verify_active_issues_json() {
74
+ local forge_cmd="${FORGE_CMD:-forge}"
75
+ local open_json inprog_json
76
+ open_json="$("$forge_cmd" issue list --status=open --json 2>/dev/null)" || open_json=""
77
+ inprog_json="$("$forge_cmd" issue list --status=in_progress --json 2>/dev/null)" || inprog_json=""
78
+ jq -n \
79
+ --argjson a "${open_json:-null}" \
80
+ --argjson b "${inprog_json:-null}" \
81
+ '((($a.data.issues) // []) + (($b.data.issues) // []))' 2>/dev/null || echo "[]"
79
82
  }
80
83
 
81
84
  # ── Public API ───────────────────────────────────────────────────────────
@@ -84,7 +87,6 @@ _extract_beads_ids() {
84
87
  # Exit 0 if all clean, exit 1 if any issues found.
85
88
  cmd_verify() {
86
89
  local gh_cmd="${GH_CMD:-gh}"
87
- local bd_cmd="${BD_CMD:-bd}"
88
90
  local issues_found=0
89
91
 
90
92
  echo "forge team verify"
@@ -130,47 +132,75 @@ cmd_verify() {
130
132
  issues_found=$((issues_found + 1))
131
133
  fi
132
134
 
133
- # ── Check 3: Orphan Beads issues (Beads without GitHub) ─────────────
134
- local bd_output
135
- bd_output="$("$bd_cmd" list --status=open,in_progress 2>/dev/null)" || bd_output=""
136
-
137
- local beads_ids
138
- beads_ids="$(_extract_beads_ids "$bd_output")"
139
-
140
- local beads_total=0
141
- local beads_synced=0
142
- local beads_orphans=()
143
-
144
- if [[ -n "$beads_ids" ]]; then
145
- while IFS= read -r bid; do
146
- [[ -z "$bid" ]] && continue
147
- beads_total=$((beads_total + 1))
148
-
149
- # Check if this beads issue has github_issue state
150
- local show_output
151
- show_output="$("$bd_cmd" show "$bid" 2>/dev/null)" || show_output=""
152
- if printf '%s' "$show_output" | grep -qP 'github_issue:\d+'; then
153
- beads_synced=$((beads_synced + 1))
154
- else
155
- beads_orphans+=("$bid")
156
- fi
157
- done <<< "$beads_ids"
158
- fi
135
+ # ── Association set: labels mapping (shared by Check 3 and Check 4)
136
+ # A GitHub↔Forge association can live in either the issue's `github_issue:<n>`
137
+ # label OR the local mapping file. Build a single set from BOTH so Check 3
138
+ # (Forge→GitHub) and Check 4 (GitHub→Forge) agree: a label-only or
139
+ # mapping-only association must never be misreported as an orphan.
140
+ local mapping_file
141
+ mapping_file="$(_mapping_file)"
142
+ local mapping_content="{}"
143
+ [[ -f "$mapping_file" ]] && mapping_content="$(cat "$mapping_file" 2>/dev/null || echo '{}')"
144
+
145
+ local issues_json
146
+ issues_json="$(_verify_active_issues_json)"
147
+
148
+ local -A assoc_gh_num=() # associated GitHub numbers (mapping keys ∪ label numbers)
149
+ local -A assoc_beads=() # associated Forge ids (mapping values ∪ labelled ids)
150
+
151
+ # From the mapping file: keys are GitHub numbers, values are Forge ids. The
152
+ # value is a string today; tolerate a legacy/forward object form ({beads_id}).
153
+ local _m_num _m_bid
154
+ while IFS=$'\t' read -r _m_num _m_bid; do
155
+ [[ -n "$_m_num" ]] && assoc_gh_num["$_m_num"]=1
156
+ [[ -n "$_m_bid" && "$_m_bid" != "null" ]] && assoc_beads["$_m_bid"]=1
157
+ done <<< "$(printf '%s' "$mapping_content" | jq -r '
158
+ to_entries[]
159
+ | [ (.key | tostring),
160
+ (if (.value | type) == "object" then (.value.beads_id // "") else (.value | tostring) end) ]
161
+ | @tsv' 2>/dev/null | tr -d '\r')"
162
+
163
+ # From active issues: a github_issue:<n> label links the issue id to number n.
164
+ local _l_id _l_num
165
+ while IFS=$'\t' read -r _l_id _l_num; do
166
+ [[ -z "$_l_id" ]] && continue
167
+ assoc_beads["$_l_id"]=1
168
+ [[ -n "$_l_num" ]] && assoc_gh_num["$_l_num"]=1
169
+ done <<< "$(printf '%s' "$issues_json" | jq -r '
170
+ .[]
171
+ | . as $x
172
+ | ((($x.labels // []) | map(select(startswith("github_issue:"))) | .[0] // "") | ltrimstr("github_issue:")) as $num
173
+ | select($num != "")
174
+ | [ $x.id, $num ] | @tsv' 2>/dev/null | tr -d '\r')"
175
+
176
+ # ── Check 3: Orphan tracked issues (no GitHub counterpart) ──────────
177
+ local issue_total issue_synced=0
178
+ issue_total="$(printf '%s' "$issues_json" | jq 'length' 2>/dev/null || echo 0)"
179
+
180
+ local issue_orphans=()
181
+ local _aid
182
+ while IFS= read -r _aid; do
183
+ [[ -z "$_aid" ]] && continue
184
+ if [[ -n "${assoc_beads[$_aid]:-}" ]]; then
185
+ issue_synced=$((issue_synced + 1))
186
+ else
187
+ issue_orphans+=("$_aid")
188
+ fi
189
+ done <<< "$(printf '%s' "$issues_json" | jq -r '.[].id' 2>/dev/null | tr -d '\r')"
159
190
 
160
- if [[ ${#beads_orphans[@]} -eq 0 ]]; then
161
- echo "✓ Beads→GitHub: ${beads_synced}/${beads_total} issues synced"
191
+ if [[ ${#issue_orphans[@]} -eq 0 ]]; then
192
+ echo "✓ Forge→GitHub: ${issue_synced}/${issue_total} issues synced"
162
193
  else
163
- echo "✗ Beads→GitHub: ${#beads_orphans[@]} orphan issues found"
164
- for orphan in "${beads_orphans[@]}"; do
194
+ echo "✗ Forge→GitHub: ${#issue_orphans[@]} orphan issues found"
195
+ for orphan in "${issue_orphans[@]}"; do
165
196
  _verify_prompt "${orphan} has no GitHub issue. Run: forge team sync-issue ${orphan}"
166
197
  done
167
- issues_found=$((issues_found + ${#beads_orphans[@]}))
198
+ issues_found=$((issues_found + ${#issue_orphans[@]}))
168
199
  fi
169
200
 
170
- # ── Check 4: Orphan GitHub issues (GitHub without Beads) ────────────
171
- local mapping_file
172
- mapping_file="$(_mapping_file)"
173
-
201
+ # ── Check 4: Orphan GitHub issues (no tracked counterpart) ──────────
202
+ # Uses the same association set: a GitHub number is mapped when it appears in
203
+ # the mapping file OR as a github_issue:<n> label on an active issue.
174
204
  local gh_issues_json
175
205
  gh_issues_json="$("$gh_cmd" issue list --state open --json number,title --limit 100 2>/dev/null)" || gh_issues_json="[]"
176
206
 
@@ -178,43 +208,23 @@ cmd_verify() {
178
208
  gh_issue_count="$(printf '%s' "$gh_issues_json" | jq 'length')"
179
209
 
180
210
  local gh_orphans=()
181
-
182
- if [[ "$gh_issue_count" -gt 0 ]] && [[ -f "$mapping_file" ]]; then
183
- local mapping_content
184
- mapping_content="$(cat "$mapping_file")"
185
-
186
- local i=0
187
- while [[ $i -lt $gh_issue_count ]]; do
188
- local issue_num
189
- issue_num="$(printf '%s' "$gh_issues_json" | jq -r ".[$i].number")"
190
-
191
- # Check if this GitHub issue number is a key in the mapping
192
- local mapped
193
- mapped="$(printf '%s' "$mapping_content" | jq -r --arg n "$issue_num" '.[$n] // empty')"
194
-
195
- if [[ -z "$mapped" ]]; then
196
- gh_orphans+=("$issue_num")
197
- fi
198
- i=$((i + 1))
199
- done
200
- elif [[ "$gh_issue_count" -gt 0 ]] && [[ ! -f "$mapping_file" ]]; then
201
- # No mapping file — all GitHub issues are orphans
202
- local i=0
203
- while [[ $i -lt $gh_issue_count ]]; do
204
- local issue_num
205
- issue_num="$(printf '%s' "$gh_issues_json" | jq -r ".[$i].number")"
211
+ local i=0
212
+ while [[ $i -lt $gh_issue_count ]]; do
213
+ local issue_num
214
+ issue_num="$(printf '%s' "$gh_issues_json" | jq -r ".[$i].number")"
215
+ if [[ -z "${assoc_gh_num[$issue_num]:-}" ]]; then
206
216
  gh_orphans+=("$issue_num")
207
- i=$((i + 1))
208
- done
209
- fi
217
+ fi
218
+ i=$((i + 1))
219
+ done
210
220
 
211
221
  if [[ ${#gh_orphans[@]} -eq 0 ]]; then
212
222
  local mapped_count=$((gh_issue_count))
213
- echo "✓ GitHub→Beads: ${mapped_count}/${mapped_count} issues mapped"
223
+ echo "✓ GitHub→Forge: ${mapped_count}/${mapped_count} issues mapped"
214
224
  else
215
- echo "✗ GitHub→Beads: ${#gh_orphans[@]} orphan issues found"
225
+ echo "✗ GitHub→Forge: ${#gh_orphans[@]} orphan issues found"
216
226
  for orphan_num in "${gh_orphans[@]}"; do
217
- _verify_prompt "GitHub #${orphan_num} has no Beads issue. Run: forge team import #${orphan_num}"
227
+ _verify_prompt "GitHub #${orphan_num} has no tracked issue. Run: forge team import #${orphan_num}"
218
228
  done
219
229
  issues_found=$((issues_found + ${#gh_orphans[@]}))
220
230
  fi