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
@@ -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 canonical 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).
@@ -87,45 +87,6 @@ _extract_issue_number_from_url() {
87
87
  printf '%s' "$suffix"
88
88
  }
89
89
 
90
- _extract_issue_title_from_show() {
91
- local input="${1:-}"
92
-
93
- printf '%s' "$input" | node -e '
94
- const fs = require("node:fs");
95
- const input = fs.readFileSync(0, "utf8");
96
- const lines = input.split(/\r?\n/);
97
- const titledLine = lines.find((line) => /^Title:\s*/.test(line));
98
-
99
- if (titledLine) {
100
- process.stdout.write(titledLine.replace(/^Title:\s*/, "").trim());
101
- process.exit(0);
102
- }
103
-
104
- const summaryLine = lines.find((line) => line.trim().length > 0);
105
- if (!summaryLine) {
106
- process.exit(0);
107
- }
108
-
109
- const separators = [" - ", " \u00B7 ", " \u2022 "];
110
- for (const separator of separators) {
111
- const separatorIndex = summaryLine.indexOf(separator);
112
- if (separatorIndex === -1) {
113
- continue;
114
- }
115
-
116
- const candidate = summaryLine
117
- .slice(separatorIndex + separator.length)
118
- .replace(/\s+\[[^\]]*\]\s*$/u, "")
119
- .trim();
120
-
121
- if (candidate.length > 0) {
122
- process.stdout.write(candidate);
123
- break;
124
- }
125
- }
126
- '
127
- }
128
-
129
90
  _sync_mapping_file() {
130
91
  local root="${TEAM_MAP_ROOT:-.}"
131
92
  printf '%s' "${SYNC_MAPPING_FILE:-$root/.github/beads-mapping.json}"
@@ -196,41 +157,30 @@ NODE
196
157
  }
197
158
 
198
159
  # ── _get_github_issue_number ─────────────────────────────────────────────
199
- # Extracts the canonical GitHub issue number from `bd show <id> --json`.
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.
200
162
  # Returns the number or empty string (exit 1 if not found).
201
163
  _get_github_issue_number() {
202
164
  local beads_id="${1:-}"
203
165
  if [[ -z "$beads_id" ]]; then
204
- _sync_error "beads-id required"
166
+ _sync_error "issue id required"
205
167
  return 1
206
168
  fi
207
169
 
208
- local bd_cmd="${BD_CMD:-bd}"
170
+ local forge_cmd="${FORGE_CMD:-forge}"
209
171
  local show_output
210
- show_output="$("$bd_cmd" show "$beads_id" --json 2>/dev/null)" || {
211
- _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"
212
174
  return 1
213
175
  }
214
176
 
215
177
  local issue_num
216
- issue_num="$(printf '%s' "$show_output" | node -e '
217
- const fs = require("node:fs");
218
- const input = fs.readFileSync(0, "utf8");
219
- try {
220
- const data = JSON.parse(input);
221
- const issue = Array.isArray(data) ? data[0] : data;
222
- const value = issue?.github?.number
223
- ?? issue?.githubNumber
224
- ?? issue?.issueNumber
225
- ?? issue?.github_issue
226
- ?? null;
227
- if (value != null && value !== "") {
228
- process.stdout.write(String(value));
229
- }
230
- } catch (_err) {
231
- process.exitCode = 0;
232
- }
233
- ')"
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)"
234
184
 
235
185
  if [[ -z "$issue_num" ]]; then
236
186
  issue_num="$(_get_issue_number_from_mapping "$beads_id" 2>/dev/null || true)"
@@ -246,16 +196,16 @@ _get_github_issue_number() {
246
196
  }
247
197
 
248
198
  # ── _get_issue_title ─────────────────────────────────────────────────────
249
- # Extracts issue title from `bd show <id>` output.
250
- # 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.
251
201
  _get_issue_title() {
252
202
  local beads_id="${1:-}"
253
- local bd_cmd="${BD_CMD:-bd}"
203
+ local forge_cmd="${FORGE_CMD:-forge}"
254
204
  local show_output
255
- 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
256
206
 
257
207
  local title
258
- title="$(_extract_issue_title_from_show "$show_output")"
208
+ title="$(printf '%s' "$show_output" | jq -r '(.data.issue // .data // {}) | (.title // "")' 2>/dev/null)"
259
209
 
260
210
  if [[ -z "$title" ]]; then
261
211
  title="$beads_id"
@@ -266,10 +216,10 @@ _get_issue_title() {
266
216
 
267
217
  # ── Public API ───────────────────────────────────────────────────────────
268
218
 
269
- # sync_issue_create <beads-id>
270
- # Creates GitHub issue from Beads issue data.
271
- # Persists both the canonical mapping file and the legacy github_issue state
272
- # until downstream hook and verify flows stop reading bd show output.
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.
273
223
  # Returns 0 on success, 1 on failure.
274
224
  sync_issue_create() {
275
225
  local beads_id="${1:-}"
@@ -279,9 +229,24 @@ sync_issue_create() {
279
229
  fi
280
230
 
281
231
  local gh_cmd="${GH_CMD:-gh}"
282
- 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
283
248
 
284
- # Get title from bd show
249
+ # Get title from the issue backend
285
250
  local raw_title
286
251
  raw_title="$(_get_issue_title "$beads_id")" || {
287
252
  _sync_error "Failed to get title for $beads_id"
@@ -313,8 +278,8 @@ sync_issue_create() {
313
278
  return 1
314
279
  fi
315
280
 
316
- if ! "$bd_cmd" set-state "$beads_id" "github_issue=$issue_num" >/dev/null 2>&1; then
317
- _sync_error "Failed to store github_issue=$issue_num for $beads_id"
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"
318
283
  return 1
319
284
  fi
320
285
 
@@ -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
@@ -7,7 +7,7 @@
7
7
  # cmd_workload — Main entry point for `forge team workload`
8
8
  #
9
9
  # Env overrides (for testing):
10
- # BD_CMD — Path to bd binary
10
+ # FORGE_CMD — Path to forge binary
11
11
  # GH_CMD — Path to gh binary
12
12
  # WORKLOAD_NOW — Override "now" timestamp for testing (ISO 8601)
13
13
  #
@@ -88,70 +88,46 @@ _json_escape() {
88
88
  }
89
89
 
90
90
  # _collect_issues — Gather all open/in_progress issues with details
91
- # Outputs lines: id|title|status|owner|updated|depends_on
91
+ # Outputs tab-delimited (jq @tsv) lines: id<TAB>title<TAB>status<TAB>owner<TAB>updated<TAB>depends_on
92
+ # @tsv escapes any tab/newline/CR inside fields, so user-controlled titles that
93
+ # contain '|', tabs, or newlines never shift the downstream columns.
94
+ #
95
+ # The Kernel list filter does not accept comma-joined statuses, so query each
96
+ # active status separately and concatenate the JSON issue arrays. Every field
97
+ # the workload view needs (status, assignee, updated_at, dependencies) is
98
+ # present in `issue list --json`, so no per-issue lookup is required.
92
99
  _collect_issues() {
93
- local bd_cmd="${BD_CMD:-bd}"
94
- local list_output
95
- local combined_exit=0
96
-
97
- # beads 0.62 rejects comma-separated status filters, so fall back to
98
- # separate queries when the combined form fails.
99
- list_output="$("$bd_cmd" list --status=open,in_progress 2>/dev/null)"
100
- combined_exit=$?
101
- if [[ "$combined_exit" -ne 0 ]]; then
102
- list_output="$(
103
- {
104
- "$bd_cmd" list --status=open 2>/dev/null || true
105
- "$bd_cmd" list --status=in_progress 2>/dev/null || true
106
- } | awk 'NF && !seen[$0]++'
107
- )"
108
- fi
109
-
110
- # Filter empty lines
111
- if [[ -z "$list_output" ]] || [[ -z "$(printf '%s' "$list_output" | tr -d '[:space:]')" ]]; then
112
- return 0
113
- fi
114
-
115
- while IFS= read -r line; do
116
- # Skip empty lines
117
- [[ -z "$line" ]] && continue
118
-
119
- # Extract issue id: first field like "forge-xxx" after optional icon
120
- local issue_id
121
- issue_id="$(echo "$line" | grep -oE 'forge-[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)*' | head -1)"
122
- [[ -z "$issue_id" ]] && continue
123
-
124
- # Get details via bd show
125
- local show_output
126
- show_output="$("$bd_cmd" show "$issue_id" 2>/dev/null)" || continue
127
-
128
- # Parse owner
129
- local owner
130
- owner="$(echo "$show_output" | grep -E '^Owner:' | sed 's/^Owner:[[:space:]]*//')"
131
- [[ -z "$owner" ]] && owner="unassigned"
132
-
133
- # Parse status from the header line [● Px · STATUS]
134
- local status
135
- status="$(echo "$show_output" | head -1 | grep -oE '(IN_PROGRESS|OPEN|BLOCKED|CLOSED)' | head -1)"
136
- [[ -z "$status" ]] && status="OPEN"
137
-
138
- # Parse title — text after "forge-xxx · " on first line
139
- local title
140
- title="$(echo "$show_output" | head -1 | sed -E 's/^[^·]*·[[:space:]]*//' | sed -E 's/[[:space:]]*\[.*//')"
141
-
142
- # Parse updated timestamp
143
- local updated
144
- updated="$(echo "$show_output" | grep -E '^Updated:' | sed 's/^Updated:[[:space:]]*//')"
145
- [[ -z "$updated" ]] && updated=""
146
-
147
- # Parse dependencies (DEPENDS ON section)
148
- local depends_on=""
149
- if echo "$show_output" | grep -q "DEPENDS ON"; then
150
- depends_on="$(echo "$show_output" | grep -E '^\s*→' | sed -E 's/^\s*→\s*//' | sed -E 's/:.*//' | tr '\n' ',' | sed 's/,$//')"
151
- fi
152
-
153
- echo "${issue_id}|${title}|${status}|${owner}|${updated}|${depends_on}"
154
- done <<< "$list_output"
100
+ local forge_cmd="${FORGE_CMD:-forge}"
101
+ local open_json inprog_json issues_json
102
+
103
+ open_json="$("$forge_cmd" issue list --status=open --json 2>/dev/null)" || open_json=""
104
+ inprog_json="$("$forge_cmd" issue list --status=in_progress --json 2>/dev/null)" || inprog_json=""
105
+
106
+ issues_json="$(jq -n \
107
+ --argjson a "${open_json:-null}" \
108
+ --argjson b "${inprog_json:-null}" \
109
+ '((($a.data.issues) // []) + (($b.data.issues) // []))' 2>/dev/null)" || issues_json="[]"
110
+
111
+ # A Kernel issue's `.dependencies` lists the issues it BLOCKS (outgoing), so
112
+ # depends_on (the issues that block THIS one) is a reverse scan: issues within
113
+ # the active set whose `.dependencies` include this issue.
114
+ printf '%s' "$issues_json" | jq -r '
115
+ . as $all
116
+ | $all[]
117
+ | . as $x
118
+ | [ $x.id,
119
+ ($x.title // ""),
120
+ (($x.status // "open") | ascii_upcase),
121
+ ($x.assignee // $x.claimed_by // "unassigned"),
122
+ (($x.updated_at // "") | sub("\\.[0-9]+Z$"; "Z")),
123
+ ( [ $all[]
124
+ | select( (.dependencies // [])
125
+ | map(if type == "object" then (.id // .to // "") else . end)
126
+ | any(. == $x.id) )
127
+ | .id ]
128
+ | join(",") )
129
+ ]
130
+ | @tsv' | tr -d '\r'
155
131
  }
156
132
 
157
133
  # ── Public API ───────────────────────────────────────────────────────────
@@ -213,7 +189,7 @@ cmd_workload() {
213
189
  local work_dir
214
190
  work_dir="$(mktemp -d)"
215
191
 
216
- while IFS='|' read -r issue_id title status owner updated depends_on; do
192
+ while IFS=$'\t' read -r issue_id title status owner updated depends_on; do
217
193
  [[ -z "$issue_id" ]] && continue
218
194
 
219
195
  # Apply developer filter
@@ -7,7 +7,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
7
7
  TEST_TMP="$(mktemp -d)"
8
8
  trap 'rm -rf "$TEST_TMP"' EXIT
9
9
 
10
- # Override TEAM_MAP_ROOT so tests don't touch real .beads/
10
+ # Override TEAM_MAP_ROOT so tests don't touch real .forge/
11
11
  export TEAM_MAP_ROOT="$TEST_TMP"
12
12
 
13
13
  # ── Create mock gh ───────────────────────────────────────────────────────
@@ -52,26 +52,32 @@ exit 1
52
52
  MOCK
53
53
  chmod +x "$mock_dir/gh-fail"
54
54
 
55
- # ── Create mock bd ───────────────────────────────────────────────────────
56
- cat > "$mock_dir/bd" << 'MOCK'
55
+ # ── Create mock forge ─────────────────────────────────────────────────────
56
+ # `issue show --json` carries the github_issue:<n> association as a label.
57
+ cat > "$mock_dir/forge" << 'MOCK'
57
58
  #!/usr/bin/env bash
58
- case "$1" in
59
- show)
60
- case "$2" in
61
- forge-assigned) echo "forge-assigned Assigned Issue [github_issue:42]" ;;
62
- forge-unassigned) echo "forge-unassigned Unassigned Issue [github_issue:43]" ;;
63
- forge-mine) echo "forge-mine My Issue [github_issue:44]" ;;
64
- forge-nogithub) echo "forge-nogithub No GitHub Issue" ;;
65
- *) echo "unknown issue" ;;
66
- esac ;;
67
- update) echo "Updated" ;;
68
- comments) echo "Comment added" ;;
59
+ sub="$1 $2"
60
+ id="$3"
61
+ case "$sub" in
62
+ "issue show")
63
+ case "$id" in
64
+ forge-assigned) labels='["github_issue:42"]' ;;
65
+ forge-unassigned) labels='["github_issue:43"]' ;;
66
+ forge-mine) labels='["github_issue:44"]' ;;
67
+ forge-nogithub) labels='[]' ;;
68
+ *) printf 'unexpected issue id: %s\n' "$id" >&2; exit 64 ;;
69
+ esac
70
+ printf '{"data":{"issue":{"id":"%s","labels":%s}}}\n' "$id" "$labels"
71
+ ;;
72
+ "issue claim") printf '{"data":{"id":"%s"}}\n' "$id" ;;
73
+ "issue comment") printf '{"data":{"id":"%s"}}\n' "$id" ;;
74
+ *) printf 'unexpected forge command: %s\n' "$*" >&2; exit 64 ;;
69
75
  esac
70
76
  MOCK
71
- chmod +x "$mock_dir/bd"
77
+ chmod +x "$mock_dir/forge"
72
78
 
73
79
  export GH_CMD="$mock_dir/gh"
74
- export BD_CMD="$mock_dir/bd"
80
+ export FORGE_CMD="$mock_dir/forge"
75
81
 
76
82
  # Source the library under test
77
83
  source "$SCRIPT_DIR/lib/claim.sh"
@@ -109,7 +115,7 @@ assert_exit() {
109
115
  # ── Test 1: Pre-claim on unassigned issue → returns 0 ───────────────────
110
116
  echo "── Test 1: Pre-claim on unassigned issue ──"
111
117
  unset _GITHUB_USER_CACHE
112
- mkdir -p "$TEST_TMP/.beads"
118
+ mkdir -p "$TEST_TMP/.forge"
113
119
  rc=0
114
120
  pre_claim_check "forge-unassigned" 2>/dev/null || rc=$?
115
121
  assert_exit "unassigned issue returns 0" 0 "$rc"
@@ -136,7 +142,7 @@ assert_exit "assigned to self returns 0" 0 "$rc"
136
142
  echo ""
137
143
  echo "── Test 4: --force overrides pre-claim check ──"
138
144
  unset _GITHUB_USER_CACHE
139
- mkdir -p "$TEST_TMP/.beads"
145
+ mkdir -p "$TEST_TMP/.forge"
140
146
  rc=0
141
147
  output="$(forge_team_claim "forge-assigned" "--force" 2>&1)" || rc=$?
142
148
  assert_exit "force claim succeeds" 0 "$rc"
@@ -146,7 +152,7 @@ assert_contains "confirmation output" "claimed" "$output"
146
152
  echo ""
147
153
  echo "── Test 5: Claim with lock succeeds ──"
148
154
  unset _GITHUB_USER_CACHE
149
- mkdir -p "$TEST_TMP/.beads"
155
+ mkdir -p "$TEST_TMP/.forge"
150
156
  # Use the unassigned issue so pre-claim passes
151
157
  rc=0
152
158
  output="$(forge_team_claim "forge-unassigned" 2>&1)" || rc=$?