forge-workflow 0.0.10 → 0.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (454) hide show
  1. package/.claude/rules/{greptile-review-process.md → review-process.md} +56 -41
  2. package/.claude/scripts/{greptile-resolve.sh → review-resolve.sh} +13 -3
  3. package/.cursor/rules/permissions-guidance.mdc +2 -2
  4. package/.forge/hooks/check-tdd.js +3 -0
  5. package/.forge/hooks/forge-native-hook.js +245 -0
  6. package/.forge/protected-paths.yaml +157 -0
  7. package/AGENTS.md +150 -61
  8. package/CHANGELOG.md +681 -0
  9. package/CLAUDE.md +9 -118
  10. package/QUICKSTART.md +171 -0
  11. package/README.md +271 -363
  12. package/bin/forge-cmd.js +120 -9
  13. package/bin/forge-preflight.js +26 -5
  14. package/bin/forge.js +461 -489
  15. package/docs/INDEX.md +93 -0
  16. package/docs/PROJECT_DESIGN.md +685 -0
  17. package/docs/architecture/index.md +66 -0
  18. package/docs/architecture/notes/README.md +35 -0
  19. package/docs/architecture/subsystems/README.md +46 -0
  20. package/docs/forge/TOOLCHAIN.md +670 -0
  21. package/docs/forge/VALIDATION.md +82 -0
  22. package/docs/{AGENT_INSTALL_PROMPT.md → guides/AGENT_INSTALL_PROMPT.md} +3 -3
  23. package/docs/guides/BEADS_GITHUB_SYNC.md +32 -0
  24. package/docs/{ENHANCED_ONBOARDING.md → guides/ENHANCED_ONBOARDING.md} +16 -12
  25. package/docs/guides/GREPTILE_SETUP.md +46 -0
  26. package/docs/guides/MANUAL_REVIEW_GUIDE.md +58 -0
  27. package/docs/guides/MIGRATION.md +56 -0
  28. package/docs/guides/SETUP.md +118 -0
  29. package/docs/guides/SUPPORT.md +185 -0
  30. package/docs/guides/WORKFLOW_TEMPLATES.md +74 -0
  31. package/docs/guides/memory-backends.md +183 -0
  32. package/docs/reference/ADAPTERS.md +128 -0
  33. package/docs/reference/AGENT_SKILL_PARITY.md +175 -0
  34. package/docs/reference/COMMANDS.md +205 -0
  35. package/docs/reference/DECISION_DRIFT_GUARDS.md +97 -0
  36. package/docs/{EXAMPLES.md → reference/EXAMPLES.md} +7 -5
  37. package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +135 -0
  38. package/docs/reference/HERMES_INTEGRATION.md +118 -0
  39. package/docs/reference/INSIGHTS_RECAP.md +63 -0
  40. package/docs/reference/INSTALL.md +164 -0
  41. package/docs/reference/KERNEL_TAXONOMY_VALIDATION.md +161 -0
  42. package/docs/reference/PROTECTED_PATH_MANIFEST.md +25 -0
  43. package/docs/reference/RELEASE.md +68 -0
  44. package/docs/reference/RESEARCH_TEMPLATE.md +292 -0
  45. package/docs/{ROADMAP.md → reference/ROADMAP.md} +12 -9
  46. package/docs/reference/SKILLS.md +35 -0
  47. package/docs/reference/STATUS_BOARD.md +80 -0
  48. package/docs/reference/TEMPLATES.md +106 -0
  49. package/docs/{TOOLCHAIN.md → reference/TOOLCHAIN.md} +62 -47
  50. package/docs/reference/VALIDATION.md +82 -0
  51. package/docs/reference/agent-permissions.md +169 -0
  52. package/docs/reference/beads-to-kernel-migration-ux.md +61 -0
  53. package/docs/reference/control-plane-guarantees.md +125 -0
  54. package/docs/reference/dependency-chain.md +331 -0
  55. package/docs/reference/forge-kernel-issue-command-contract.md +161 -0
  56. package/docs/reference/forge-kernel-schema.md +72 -0
  57. package/docs/reference/kernel-conflict-evaluators.md +27 -0
  58. package/docs/reference/patch-md-format.md +77 -0
  59. package/docs/reference/protected-state-surfaces.md +59 -0
  60. package/docs/reference/shepherd.md +115 -0
  61. package/docs/reference/superpowers-analysis.md +320 -0
  62. package/docs/reference/superpowers-integration-options.md +404 -0
  63. package/docs/reference/test-environment.md +519 -0
  64. package/docs/reference/upgrade-safety.md +59 -0
  65. package/lefthook.yml +18 -0
  66. package/lib/adapter-cli.js +307 -0
  67. package/lib/adapters/beads-issue-adapter.js +127 -0
  68. package/lib/adapters/beads-kernel-compat.js +1042 -0
  69. package/lib/adapters/greptile-review-adapter.js +141 -0
  70. package/lib/adapters/kernel-issue-adapter.js +101 -0
  71. package/lib/adapters/pr-state-adapter.js +484 -0
  72. package/lib/adoption-profiles.js +126 -0
  73. package/lib/agents/README.md +2 -6
  74. package/lib/agents/claude.plugin.json +3 -8
  75. package/lib/agents/codex.plugin.json +9 -1
  76. package/lib/agents/cursor.plugin.json +2 -6
  77. package/lib/agents/hermes.plugin.json +22 -0
  78. package/lib/agents-config.js +39 -1236
  79. package/lib/audit-evidence.js +282 -0
  80. package/lib/beads-setup.js +121 -0
  81. package/lib/beads-sync-scaffold.js +25 -101
  82. package/lib/codex-skills.js +51 -1
  83. package/lib/commands/_issue.js +741 -77
  84. package/lib/commands/_manifest.js +91 -0
  85. package/lib/commands/_registry.js +85 -34
  86. package/lib/commands/_resolve-command-opts.js +261 -0
  87. package/lib/commands/_serve-security.js +270 -0
  88. package/lib/commands/adapter.js +12 -0
  89. package/lib/commands/add.js +118 -0
  90. package/lib/commands/audit.js +70 -0
  91. package/lib/commands/blocked.js +5 -0
  92. package/lib/commands/board.js +64 -0
  93. package/lib/commands/claim.js +21 -2
  94. package/lib/commands/claims.js +7 -0
  95. package/lib/commands/clean.js +485 -75
  96. package/lib/commands/close.js +2 -2
  97. package/lib/commands/comment.js +5 -0
  98. package/lib/commands/control.js +148 -0
  99. package/lib/commands/create.js +2 -2
  100. package/lib/commands/dev.js +185 -7
  101. package/lib/commands/doc-gate.js +336 -0
  102. package/lib/commands/doctor.js +156 -0
  103. package/lib/commands/explain.js +15 -0
  104. package/lib/commands/export.js +237 -0
  105. package/lib/commands/gate.js +192 -0
  106. package/lib/commands/hooks.js +242 -0
  107. package/lib/commands/inbox.js +118 -0
  108. package/lib/commands/init.js +598 -0
  109. package/lib/commands/insights.js +79 -0
  110. package/lib/commands/issue.js +12 -1
  111. package/lib/commands/issues.js +17 -0
  112. package/lib/commands/lint.js +5 -0
  113. package/lib/commands/list.js +2 -2
  114. package/lib/commands/merge.js +312 -0
  115. package/lib/commands/migrate.js +523 -0
  116. package/lib/commands/new.js +12 -0
  117. package/lib/commands/options.js +241 -0
  118. package/lib/commands/orient.js +13 -0
  119. package/lib/commands/orphans.js +5 -0
  120. package/lib/commands/patch.js +67 -0
  121. package/lib/commands/plan.js +436 -24
  122. package/lib/commands/preflight.js +211 -0
  123. package/lib/commands/prime.js +13 -0
  124. package/lib/commands/push.js +69 -2
  125. package/lib/commands/ready.js +2 -2
  126. package/lib/commands/recall.js +116 -0
  127. package/lib/commands/recap.js +61 -0
  128. package/lib/commands/recommend.js +0 -1
  129. package/lib/commands/release.js +91 -0
  130. package/lib/commands/remember.js +74 -0
  131. package/lib/commands/role.js +99 -0
  132. package/lib/commands/serve.js +581 -0
  133. package/lib/commands/setup.js +838 -972
  134. package/lib/commands/shepherd.js +436 -0
  135. package/lib/commands/ship.js +23 -1
  136. package/lib/commands/show.js +2 -2
  137. package/lib/commands/stage.js +192 -0
  138. package/lib/commands/stale.js +5 -0
  139. package/lib/commands/status.js +158 -21
  140. package/lib/commands/sync.js +34 -46
  141. package/lib/commands/team.js +4 -1
  142. package/lib/commands/test.js +43 -27
  143. package/lib/commands/update.js +2 -2
  144. package/lib/commands/upgrade.js +47 -0
  145. package/lib/commands/validate.js +43 -18
  146. package/lib/commands/worktree.js +307 -100
  147. package/lib/config-writer.js +202 -0
  148. package/lib/control-plane.js +236 -0
  149. package/lib/core/runtime-graph.js +946 -0
  150. package/lib/dep-guard/keyword-ripple.js +2 -2
  151. package/lib/deprecated-sync-cleanup.js +362 -0
  152. package/lib/detect-agent.js +2 -28
  153. package/lib/detect-worktree.js +35 -9
  154. package/lib/doc-gate/declaration.js +177 -0
  155. package/lib/doc-gate/detect.js +289 -0
  156. package/lib/doc-gate/gate.js +375 -0
  157. package/lib/doc-gate/okf-config.js +128 -0
  158. package/lib/doc-gate/okf.js +429 -0
  159. package/lib/docs-command.js +1161 -6
  160. package/lib/forge-issues.js +382 -11
  161. package/lib/forge-lock.js +262 -0
  162. package/lib/gate-events.js +193 -0
  163. package/lib/global-flags.js +74 -0
  164. package/lib/greptile-match.js +7 -63
  165. package/lib/harness-capability-matrix.js +380 -0
  166. package/lib/hook-global-installer.js +347 -0
  167. package/lib/hook-renderer.js +451 -0
  168. package/lib/inbox.js +391 -0
  169. package/lib/insights.js +397 -0
  170. package/lib/issue-adapter.js +156 -0
  171. package/lib/issue-backend.js +145 -0
  172. package/lib/issue-render.js +220 -0
  173. package/lib/kernel/backing-issue.js +305 -0
  174. package/lib/kernel/broker.js +1218 -0
  175. package/lib/kernel/cli-broker-factory.js +130 -0
  176. package/lib/kernel/conflict-signal.js +82 -0
  177. package/lib/kernel/evaluators.js +195 -0
  178. package/lib/kernel/fs-class.js +495 -0
  179. package/lib/kernel/issue-command-contract.js +559 -0
  180. package/lib/kernel/issue-id-resolver.js +186 -0
  181. package/lib/kernel/lease-enforcer.js +158 -0
  182. package/lib/kernel/migrations.js +333 -0
  183. package/lib/kernel/planning-buckets-schema.js +109 -0
  184. package/lib/kernel/projection-jsonl-writer.js +450 -0
  185. package/lib/kernel/readiness-model.js +329 -0
  186. package/lib/kernel/schema.js +356 -0
  187. package/lib/kernel/sqlite-driver.js +2504 -0
  188. package/lib/kernel/taxonomy-validator.js +394 -0
  189. package/lib/lefthook-check.js +3 -2
  190. package/lib/lefthook-wiring.js +413 -0
  191. package/lib/mcp-config-renderer.js +288 -0
  192. package/lib/memory/graphiti-mcp.js +106 -0
  193. package/lib/memory/router.js +387 -0
  194. package/lib/memory/typed-api.js +102 -0
  195. package/lib/memory-digest.js +195 -0
  196. package/lib/merge-rules.js +395 -0
  197. package/lib/migrate-dry-run.js +466 -0
  198. package/lib/orientation.js +863 -0
  199. package/lib/package-manager-remediation.js +103 -0
  200. package/lib/package-root.js +381 -0
  201. package/lib/patch-intent.js +890 -0
  202. package/lib/plugin-catalog.js +3 -4
  203. package/lib/plugin-manager.js +0 -5
  204. package/lib/pr-bundle.js +186 -0
  205. package/lib/pr-monitor/differ.js +195 -0
  206. package/lib/pr-monitor/events.js +0 -0
  207. package/lib/pr-monitor/gather.js +124 -0
  208. package/lib/pr-monitor/journal.js +299 -0
  209. package/lib/pr-monitor/monitor.js +146 -0
  210. package/lib/pr-monitor/render-sticky.js +157 -0
  211. package/lib/pr-monitor/watch-lifecycle.js +95 -0
  212. package/lib/pr-monitor/watch.js +247 -0
  213. package/lib/pr-pull.js +1273 -0
  214. package/lib/pr-shepherd.js +494 -0
  215. package/lib/pr-state-validator.js +59 -0
  216. package/lib/preflight/gates.js +237 -0
  217. package/lib/preflight/runner.js +116 -0
  218. package/lib/project-discovery.js +0 -53
  219. package/lib/project-memory.js +99 -497
  220. package/lib/protected-path-manifest.js +281 -0
  221. package/lib/protected-state-surfaces.js +387 -0
  222. package/lib/release-readiness.js +2089 -0
  223. package/lib/reset.js +59 -45
  224. package/lib/review-adapter.js +68 -0
  225. package/lib/rules-sync.js +260 -0
  226. package/lib/runtime-health.js +241 -20
  227. package/lib/safety-config-renderer.js +268 -0
  228. package/lib/setup-action-log.js +1 -7
  229. package/lib/setup.js +27 -65
  230. package/lib/shell-utils.js +76 -6
  231. package/lib/skills-sync.js +330 -0
  232. package/lib/smart-status/scoring.js +17 -3
  233. package/lib/status/beads-snapshot.js +45 -2
  234. package/lib/status/presenter.js +169 -18
  235. package/lib/status/snapshot.js +186 -0
  236. package/lib/sync-backend.js +202 -0
  237. package/lib/untrusted-content.js +52 -0
  238. package/lib/upgrade-safety.js +199 -0
  239. package/lib/workflow/enforce-stage.js +296 -47
  240. package/lib/workflow/stage-transition.js +115 -0
  241. package/lib/workflow/stages.js +30 -6
  242. package/lib/workflow/state-manager.js +11 -22
  243. package/lib/workflow/state.js +23 -1
  244. package/lib/workflow-profiles.js +17 -5
  245. package/package.json +37 -35
  246. package/rules/documentation.md +19 -0
  247. package/rules/kernel-tracking.md +26 -0
  248. package/rules/security.md +22 -0
  249. package/rules/tdd.md +20 -0
  250. package/rules/workflow.md +27 -0
  251. package/scripts/auto-backing-issue.js +47 -0
  252. package/scripts/beads-context.sh +81 -57
  253. package/scripts/beads-upgrade-smoke.sh +24 -3
  254. package/scripts/bootstrap-windows-tools.sh +78 -0
  255. package/scripts/branch-protection.js +2 -3
  256. package/scripts/check-agents.js +34 -137
  257. package/scripts/commitlint.js +3 -1
  258. package/scripts/conflict-detect.sh +3 -0
  259. package/scripts/dep-guard.sh +22 -3
  260. package/scripts/file-index.sh +3 -0
  261. package/scripts/forge-team/lib/claim.sh +34 -18
  262. package/scripts/forge-team/lib/dashboard.sh +61 -86
  263. package/scripts/forge-team/lib/epic.sh +99 -263
  264. package/scripts/forge-team/lib/hooks.sh +26 -28
  265. package/scripts/forge-team/lib/identity.sh +4 -4
  266. package/scripts/forge-team/lib/sync-github.sh +49 -84
  267. package/scripts/forge-team/lib/verify.sh +93 -83
  268. package/scripts/forge-team/lib/workload.sh +41 -65
  269. package/scripts/forge-team/tests/claim.test.sh +25 -19
  270. package/scripts/forge-team/tests/dashboard.test.sh +31 -46
  271. package/scripts/forge-team/tests/epic.test.sh +52 -71
  272. package/scripts/forge-team/tests/hooks.test.sh +38 -50
  273. package/scripts/forge-team/tests/identity.test.sh +3 -3
  274. package/scripts/forge-team/tests/integration.test.sh +44 -66
  275. package/scripts/forge-team/tests/sync-github.test.sh +50 -83
  276. package/scripts/forge-team/tests/verify.test.sh +37 -46
  277. package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
  278. package/scripts/forge-team/tests/workload.test.sh +32 -66
  279. package/scripts/gen-command-manifest.js +153 -0
  280. package/scripts/gen-embedded-assets.mjs +129 -0
  281. package/scripts/install.ps1 +139 -0
  282. package/scripts/install.sh +268 -0
  283. package/scripts/lib/release-asset.mjs +84 -0
  284. package/scripts/parity-check.mjs +145 -0
  285. package/scripts/parity-check.test.mjs +58 -0
  286. package/scripts/pin-agentic-workflow-images.js +112 -0
  287. package/scripts/pr-coordinator.sh +3 -0
  288. package/scripts/preflight-sonar.eslint.config.mjs +44 -0
  289. package/scripts/preflight.sh +21 -94
  290. package/scripts/protected-state-check.js +104 -0
  291. package/scripts/smart-status.sh +60 -57
  292. package/scripts/spikes/config-race-bench.js +111 -0
  293. package/scripts/spikes/harness-capability-matrix.js +13 -0
  294. package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
  295. package/scripts/spikes/protected-path-manifest.js +20 -0
  296. package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
  297. package/scripts/sync-agent-skills.js +62 -0
  298. package/scripts/sync-utils.sh +3 -0
  299. package/scripts/test-ci-shard.js +13 -6
  300. package/scripts/test.js +95 -12
  301. package/skills/claim-safety/SKILL.md +102 -0
  302. package/skills/claim-safety/evals/evals.json +46 -0
  303. package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +44 -50
  304. package/skills/dev/evals/evals.json +50 -0
  305. package/skills/hermes-forge/SKILL.md +185 -0
  306. package/skills/hermes-forge/evals/evals.json +46 -0
  307. package/skills/issue-basics/SKILL.md +111 -0
  308. package/skills/issue-basics/evals/evals.json +46 -0
  309. package/skills/kernel/SKILL.md +166 -0
  310. package/skills/kernel/evals/evals.json +50 -0
  311. package/skills/memory/SKILL.md +102 -0
  312. package/skills/parallel-deep-research/SKILL.md +14 -11
  313. package/skills/parallel-deep-research/evals/evals.json +11 -27
  314. package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +132 -157
  315. package/skills/plan/evals/evals.json +42 -0
  316. package/skills/research/SKILL.md +195 -0
  317. package/skills/research/evals/evals.json +42 -0
  318. package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
  319. package/skills/review/evals/evals.json +42 -0
  320. package/skills/rollback/SKILL.md +110 -0
  321. package/skills/rollback/evals/evals.json +46 -0
  322. package/skills/rollback/references/methods.md +204 -0
  323. package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
  324. package/skills/shepherd/SKILL.md +66 -0
  325. package/skills/shepherd/evals/evals.json +42 -0
  326. package/{.github/prompts/ship.prompt.md → skills/ship/SKILL.md} +81 -45
  327. package/skills/ship/evals/evals.json +42 -0
  328. package/skills/smith/SKILL.md +142 -0
  329. package/skills/smith/evals/evals.json +46 -0
  330. package/skills/smith/references/autonomy-and-gates.md +94 -0
  331. package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
  332. package/skills/sonarcloud/evals/evals.json +46 -0
  333. package/skills/sonarcloud-analysis/SKILL.md +18 -13
  334. package/skills/sonarcloud-analysis/evals/evals.json +11 -15
  335. package/{.github/prompts/status.prompt.md → skills/status/SKILL.md} +20 -10
  336. package/skills/status/evals/evals.json +50 -0
  337. package/skills/triage-ready/SKILL.md +121 -0
  338. package/skills/triage-ready/evals/evals.json +42 -0
  339. package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
  340. package/skills/validate/evals/evals.json +42 -0
  341. package/skills/verify/SKILL.md +299 -0
  342. package/skills/verify/evals/evals.json +50 -0
  343. package/.claude/commands/dev.md +0 -345
  344. package/.claude/commands/plan.md +0 -566
  345. package/.claude/commands/premerge.md +0 -186
  346. package/.claude/commands/research.md +0 -42
  347. package/.claude/commands/review.md +0 -451
  348. package/.claude/commands/rollback.md +0 -721
  349. package/.claude/commands/ship.md +0 -213
  350. package/.claude/commands/sonarcloud.md +0 -152
  351. package/.claude/commands/status.md +0 -90
  352. package/.claude/commands/validate.md +0 -288
  353. package/.claude/commands/verify.md +0 -269
  354. package/.claude/rules/workflow.md +0 -121
  355. package/.cline/workflows/dev.md +0 -342
  356. package/.cline/workflows/plan.md +0 -563
  357. package/.cline/workflows/premerge.md +0 -183
  358. package/.cline/workflows/research.md +0 -39
  359. package/.cline/workflows/review.md +0 -448
  360. package/.cline/workflows/rollback.md +0 -718
  361. package/.cline/workflows/ship.md +0 -210
  362. package/.cline/workflows/sonarcloud.md +0 -146
  363. package/.cline/workflows/status.md +0 -87
  364. package/.cline/workflows/validate.md +0 -285
  365. package/.cline/workflows/verify.md +0 -266
  366. package/.codex/config.toml +0 -11
  367. package/.codex/skills/dev/SKILL.md +0 -345
  368. package/.codex/skills/plan/SKILL.md +0 -566
  369. package/.codex/skills/premerge/SKILL.md +0 -186
  370. package/.codex/skills/research/SKILL.md +0 -42
  371. package/.codex/skills/review/SKILL.md +0 -451
  372. package/.codex/skills/rollback/SKILL.md +0 -721
  373. package/.codex/skills/ship/SKILL.md +0 -213
  374. package/.codex/skills/sonarcloud/SKILL.md +0 -149
  375. package/.codex/skills/status/SKILL.md +0 -90
  376. package/.codex/skills/validate/SKILL.md +0 -288
  377. package/.codex/skills/verify/SKILL.md +0 -269
  378. package/.cursor/commands/dev.md +0 -342
  379. package/.cursor/commands/plan.md +0 -563
  380. package/.cursor/commands/premerge.md +0 -183
  381. package/.cursor/commands/research.md +0 -39
  382. package/.cursor/commands/review.md +0 -448
  383. package/.cursor/commands/ship.md +0 -210
  384. package/.cursor/commands/sonarcloud.md +0 -146
  385. package/.cursor/commands/status.md +0 -87
  386. package/.cursor/commands/validate.md +0 -285
  387. package/.cursor/commands/verify.md +0 -266
  388. package/.cursorrules +0 -149
  389. package/.github/prompts/premerge.prompt.md +0 -188
  390. package/.github/prompts/research.prompt.md +0 -44
  391. package/.github/prompts/rollback.prompt.md +0 -723
  392. package/.github/prompts/verify.prompt.md +0 -271
  393. package/.github/workflows/beads-to-github.yml +0 -89
  394. package/.github/workflows/github-to-beads.yml +0 -100
  395. package/.kilocode/workflows/dev.md +0 -346
  396. package/.kilocode/workflows/plan.md +0 -567
  397. package/.kilocode/workflows/premerge.md +0 -187
  398. package/.kilocode/workflows/research.md +0 -43
  399. package/.kilocode/workflows/review.md +0 -452
  400. package/.kilocode/workflows/rollback.md +0 -722
  401. package/.kilocode/workflows/ship.md +0 -214
  402. package/.kilocode/workflows/sonarcloud.md +0 -150
  403. package/.kilocode/workflows/status.md +0 -91
  404. package/.kilocode/workflows/validate.md +0 -289
  405. package/.kilocode/workflows/verify.md +0 -270
  406. package/.opencode/commands/dev.md +0 -345
  407. package/.opencode/commands/plan.md +0 -566
  408. package/.opencode/commands/premerge.md +0 -186
  409. package/.opencode/commands/research.md +0 -42
  410. package/.opencode/commands/review.md +0 -451
  411. package/.opencode/commands/rollback.md +0 -721
  412. package/.opencode/commands/ship.md +0 -213
  413. package/.opencode/commands/sonarcloud.md +0 -149
  414. package/.opencode/commands/status.md +0 -90
  415. package/.opencode/commands/validate.md +0 -288
  416. package/.opencode/commands/verify.md +0 -269
  417. package/.roo/commands/dev.md +0 -346
  418. package/.roo/commands/plan.md +0 -567
  419. package/.roo/commands/premerge.md +0 -187
  420. package/.roo/commands/research.md +0 -43
  421. package/.roo/commands/review.md +0 -452
  422. package/.roo/commands/rollback.md +0 -722
  423. package/.roo/commands/ship.md +0 -214
  424. package/.roo/commands/sonarcloud.md +0 -150
  425. package/.roo/commands/status.md +0 -91
  426. package/.roo/commands/validate.md +0 -289
  427. package/.roo/commands/verify.md +0 -270
  428. package/docs/BEADS_GITHUB_SYNC.md +0 -281
  429. package/docs/GREPTILE_SETUP.md +0 -400
  430. package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
  431. package/docs/SETUP.md +0 -663
  432. package/docs/VALIDATION.md +0 -363
  433. package/lib/agents/cline.plugin.json +0 -29
  434. package/lib/agents/copilot.plugin.json +0 -24
  435. package/lib/agents/kilocode.plugin.json +0 -22
  436. package/lib/agents/opencode.plugin.json +0 -23
  437. package/lib/agents/roo.plugin.json +0 -30
  438. package/lib/beads-bootstrap.js +0 -225
  439. package/lib/beads-health-check.js +0 -188
  440. package/lib/commands/commands-reset.js +0 -147
  441. package/opencode.json +0 -67
  442. package/scripts/beads-context.test.js +0 -584
  443. package/scripts/github-beads-sync/comment.mjs +0 -64
  444. package/scripts/github-beads-sync/config.mjs +0 -148
  445. package/scripts/github-beads-sync/github-api.mjs +0 -131
  446. package/scripts/github-beads-sync/index.mjs +0 -356
  447. package/scripts/github-beads-sync/label-mapper.mjs +0 -54
  448. package/scripts/github-beads-sync/mapping.mjs +0 -132
  449. package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
  450. package/scripts/github-beads-sync/reverse-sync.mjs +0 -162
  451. package/scripts/github-beads-sync/run-bd.mjs +0 -161
  452. package/scripts/github-beads-sync/sanitize.mjs +0 -121
  453. package/scripts/github-beads-sync.config.json +0 -26
  454. package/scripts/sync-commands.js +0 -600
@@ -5,11 +5,12 @@
5
5
  *
6
6
  * CLI command that validates all agent configs are complete and in sync.
7
7
  *
8
- * 1. Runs syncCommands({ check: true }) to verify agent command files match canonical source
9
- * 2. Reads lib/agents/*.plugin.json to verify each agent with capabilities.commands: true
10
- * has its command directory populated with expected files
11
- * 3. Reports: missing files, out-of-sync files, stale files
12
- * 4. Exits 0 if all clean, exits non-zero if issues found
8
+ * 1. Runs the skills drift check (lib/skills-sync) to verify generated agent
9
+ * skill mirrors (e.g. .codex/skills) match the canonical root skills/ source.
10
+ * 2. Reads lib/agents/*.plugin.json to validate each plugin's schema/parity
11
+ * (support metadata, rules/skills scaffold paths, deprecated-tier consistency).
12
+ * 3. Reports: skill drift, missing/stale skills, plugin parity errors.
13
+ * 4. Exits 0 if all clean, exits non-zero if issues found.
13
14
  *
14
15
  * Usage:
15
16
  * node scripts/check-agents.js
@@ -18,51 +19,22 @@
18
19
  * checkAgents(repoRoot) -> { errors: string[], warnings: string[] }
19
20
  */
20
21
 
21
- const { syncCommands } = require('./sync-commands');
22
- const { AGENT_ADAPTERS } = require('./sync-commands');
23
22
  const path = require('path');
24
23
  const fs = require('fs');
24
+ const { checkSkillsSync } = require('../lib/skills-sync');
25
25
  const { validatePluginSchema } = require('../lib/plugin-manager');
26
26
 
27
- const SYNC_ADAPTER_BY_PLUGIN_ID = Object.freeze({
28
- claude: 'claude-code',
29
- cline: 'cline',
30
- codex: 'codex',
31
- copilot: 'github-copilot',
32
- cursor: 'cursor',
33
- kilocode: 'kilo-code',
34
- opencode: 'opencode',
35
- roo: 'roo-code',
36
- });
37
-
38
- function normalizeRelativeDir(dir) {
39
- if (!dir) return null;
40
- return String(dir).replace(/\\/g, '/').replace(/\/+$/, '');
41
- }
42
-
43
- function getDeclaredCommandDir(plugin) {
44
- const dirs = plugin.directories || {};
45
- return dirs.commands || dirs.workflows || dirs.prompts || dirs.skills || null;
46
- }
47
-
48
- function getExpectedCommandDir(plugin) {
49
- const adapterId = SYNC_ADAPTER_BY_PLUGIN_ID[plugin.id];
50
- if (!adapterId) {
51
- return null;
52
- }
53
-
54
- const adapter = AGENT_ADAPTERS[adapterId];
55
- if (!adapter) {
56
- return null;
57
- }
58
-
59
- if (plugin.id === 'codex') {
60
- return normalizeRelativeDir(plugin.directories?.skills || adapter.baseDir);
61
- }
62
-
63
- return normalizeRelativeDir(adapter.baseDir);
64
- }
65
-
27
+ /**
28
+ * Validate a single plugin's schema + capability/scaffold parity.
29
+ *
30
+ * Command-surface parity is intentionally NOT validated: Forge is skills-only
31
+ * (the .claude/commands/.cursor/commands/.codex/skills command surface was
32
+ * removed in PR-A0). The surviving checks cover support metadata, the rules and
33
+ * skills scaffold paths, and deprecated-tier consistency.
34
+ *
35
+ * @param {object} plugin
36
+ * @param {string[]} errors - mutated with any parity errors
37
+ */
66
38
  function validatePluginParity(plugin, errors) {
67
39
  const validation = validatePluginSchema(plugin);
68
40
  if (!validation.valid) {
@@ -78,25 +50,6 @@ function validatePluginParity(plugin, errors) {
78
50
  const dirs = plugin.directories || {};
79
51
  const supportStatus = plugin.support?.status;
80
52
 
81
- if (capabilities.commands) {
82
- const expectedDir = getExpectedCommandDir(plugin);
83
- const declaredDir = normalizeRelativeDir(getDeclaredCommandDir(plugin));
84
-
85
- if (!expectedDir) {
86
- errors.push(
87
- `Plugin "${plugin.name}" (${plugin.id}) declares commands support but has no Forge sync adapter.`
88
- );
89
- } else if (!declaredDir) {
90
- errors.push(
91
- `Plugin "${plugin.name}" (${plugin.id}) declares commands support but has no command directory configured.`
92
- );
93
- } else if (declaredDir !== expectedDir) {
94
- errors.push(
95
- `Plugin "${plugin.name}" (${plugin.id}) command directory "${declaredDir}" does not match sync output "${expectedDir}".`
96
- );
97
- }
98
- }
99
-
100
53
  if (capabilities.rules && !dirs.rules) {
101
54
  errors.push(
102
55
  `Plugin "${plugin.name}" (${plugin.id}) declares rules capability but has no scaffold path configured.`
@@ -136,47 +89,34 @@ function checkAgents(repoRoot) {
136
89
  /** @type {string[]} */
137
90
  const warnings = [];
138
91
 
139
- // ---- 1. Run sync check ----
140
- let syncResult;
92
+ // ---- 1. Skills drift check ----
93
+ let driftResult;
141
94
  try {
142
- syncResult = syncCommands({ check: true, dryRun: false, repoRoot });
95
+ driftResult = checkSkillsSync({ repoRoot });
143
96
  } catch (err) {
144
- errors.push(`Sync check failed: ${err.message}`);
97
+ errors.push(`Skills sync check failed: ${err.message}`);
145
98
  return { errors, warnings };
146
99
  }
147
100
 
148
- if (syncResult.empty) {
149
- errors.push('No command files found in .claude/commands/ — cannot verify sync.');
101
+ const canonicalSkillsDir = path.join(repoRoot, 'skills');
102
+ if (!fs.existsSync(canonicalSkillsDir)) {
103
+ errors.push('No canonical skills found in skills/ — cannot verify skill sync.');
150
104
  return { errors, warnings };
151
- } else {
152
- if (syncResult.manifestMissing) {
153
- warnings.push(
154
- 'Sync manifest (.forge/sync-manifest.json) not found — stale file detection skipped. ' +
155
- 'Run "node scripts/sync-commands.js" to generate it.'
156
- );
157
- }
105
+ }
158
106
 
159
- if (syncResult.outOfSync && syncResult.outOfSync.length > 0) {
160
- for (const entry of syncResult.outOfSync) {
161
- const relPath = path.join(entry.dir, entry.filename);
162
- const exists = fs.existsSync(entry.filePath);
163
- const status = exists ? 'modified' : 'missing';
164
- errors.push(`Out of sync [${entry.agent}]: ${relPath} (${status})`);
165
- }
166
- }
107
+ if (driftResult.checkedAgents.length === 0) {
108
+ warnings.push(
109
+ 'No generated agent skill directories found (e.g. .codex/skills) — skill drift check skipped. ' +
110
+ 'Run "forge setup" or "skills sync" to populate them.'
111
+ );
112
+ }
167
113
 
168
- if (syncResult.staleFiles && syncResult.staleFiles.length > 0) {
169
- for (const filePath of syncResult.staleFiles) {
170
- const relPath = path.relative(repoRoot, filePath);
171
- errors.push(`Stale file (no longer generated, should be removed): ${relPath}`);
172
- }
173
- }
114
+ for (const d of driftResult.drift) {
115
+ errors.push(`Out of sync [${d.agent}]: ${d.skill}/${d.file} (${d.status})`);
174
116
  }
175
117
 
176
118
  // ---- 2. Validate plugin catalog ----
177
119
  const pluginDir = path.join(repoRoot, 'lib', 'agents');
178
- /** @type {object[]} */
179
- const plugins = [];
180
120
 
181
121
  if (!fs.existsSync(pluginDir)) {
182
122
  warnings.push('No plugin directory found at lib/agents/ — plugin catalog validation skipped.');
@@ -190,54 +130,11 @@ function checkAgents(repoRoot) {
190
130
  for (const file of pluginFiles) {
191
131
  try {
192
132
  const plugin = JSON.parse(fs.readFileSync(path.join(pluginDir, file), 'utf8'));
193
- plugins.push(plugin);
194
133
  validatePluginParity(plugin, errors);
195
134
  } catch (_err) {
196
135
  errors.push(`Failed to parse plugin file: lib/agents/${file}`);
197
136
  }
198
137
  }
199
-
200
- // For each plugin with commands: true, verify its command directory has files
201
- for (const plugin of plugins) {
202
- if (!plugin.capabilities || !plugin.capabilities.commands) {
203
- continue;
204
- }
205
-
206
- // Find the command directory from the plugin's directories config
207
- const dirs = plugin.directories || {};
208
- // Plugins use different keys: commands, workflows, prompts, skills
209
- const commandDir = dirs.commands || dirs.workflows || dirs.prompts;
210
-
211
- if (!commandDir) {
212
- // Plugin declares commands: true but has no command directory configured
213
- // Codex is the only agent that uses skills as its command mechanism
214
- if (plugin.id === 'codex' && dirs.skills) {
215
- // Codex CLI: commands live in .codex/skills/<name>/SKILL.md — skip standard check
216
- continue;
217
- }
218
- warnings.push(
219
- `Plugin "${plugin.name}" (${plugin.id}) has commands: true but no command directory configured.`
220
- );
221
- continue;
222
- }
223
-
224
- const absCmdDir = path.join(repoRoot, commandDir);
225
-
226
- if (!fs.existsSync(absCmdDir)) {
227
- errors.push(
228
- `Plugin "${plugin.name}" (${plugin.id}) has commands: true but directory "${commandDir}" does not exist.`
229
- );
230
- continue;
231
- }
232
-
233
- // Check the directory has at least one file
234
- const files = fs.readdirSync(absCmdDir).filter((f) => !f.startsWith('.'));
235
- if (files.length === 0) {
236
- errors.push(
237
- `Plugin "${plugin.name}" (${plugin.id}) has commands: true but directory "${commandDir}" is empty.`
238
- );
239
- }
240
- }
241
138
  }
242
139
 
243
140
  return { errors, warnings };
@@ -12,7 +12,9 @@ const isWindows = process.platform === 'win32';
12
12
 
13
13
  const commitMsgFile = process.argv[2];
14
14
  if (!commitMsgFile) {
15
- console.error('❌ No commit message file provided');
15
+ // Write synchronously so the message is flushed before exit: on Windows the
16
+ // stderr pipe is async and process.exit() can otherwise truncate it.
17
+ require('node:fs').writeSync(2, '❌ No commit message file provided\n');
16
18
  process.exit(1);
17
19
  }
18
20
 
@@ -17,6 +17,9 @@
17
17
  set -euo pipefail
18
18
 
19
19
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
20
+ if [[ -f "$SCRIPT_DIR/bootstrap-windows-tools.sh" ]]; then
21
+ source "$SCRIPT_DIR/bootstrap-windows-tools.sh"
22
+ fi
20
23
 
21
24
  # Source file-index helpers (file_index_read, file_index_get)
22
25
  source "$SCRIPT_DIR/file-index.sh" || { echo "FATAL: failed to source file-index.sh" >&2; exit 2; }
@@ -14,9 +14,28 @@ set -euo pipefail
14
14
 
15
15
  # Source shared sanitize library
16
16
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
17
+ if [[ -f "$SCRIPT_DIR/bootstrap-windows-tools.sh" ]]; then
18
+ source "$SCRIPT_DIR/bootstrap-windows-tools.sh"
19
+ fi
17
20
  NODE_CMD="${NODE_CMD:-node}"
18
21
  source "$SCRIPT_DIR/lib/sanitize.sh"
19
22
 
23
+ node_script_path() {
24
+ case "$1" in
25
+ /[A-Za-z]/*)
26
+ _drive="$(printf '%s' "${1:1:1}" | tr '[:lower:]' '[:upper:]')"
27
+ printf '%s:/%s\n' "$_drive" "${1:3}"
28
+ ;;
29
+ *)
30
+ if command -v cygpath >/dev/null 2>&1; then
31
+ cygpath -m "$1"
32
+ else
33
+ printf '%s\n' "$1"
34
+ fi
35
+ ;;
36
+ esac
37
+ }
38
+
20
39
  # ---- Helpers ----------------------------------------------------------------
21
40
 
22
41
  usage() {
@@ -226,12 +245,12 @@ run_phase3_analyzer() {
226
245
  "${in_progress_json:-[]}" \
227
246
  "$(printf '%s' "$task_file" | jq -R '.')" \
228
247
  "$(printf '%s' "$repository_root" | jq -R '.')" \
229
- | "$NODE_CMD" "$analyzer_script" --stdin
248
+ | "$NODE_CMD" "$(node_script_path "$analyzer_script")" --stdin
230
249
  }
231
250
 
232
251
  render_phase3_review() {
233
252
  local renderer_script="${DEP_GUARD_RENDER_SCRIPT:-scripts/dep-guard-render-review.js}"
234
- "$NODE_CMD" "$renderer_script"
253
+ "$NODE_CMD" "$(node_script_path "$renderer_script")"
235
254
  }
236
255
 
237
256
  # ---- Subcommands ------------------------------------------------------------
@@ -344,7 +363,7 @@ cmd_check_ripple_keyword_v1() {
344
363
  ISSUE_ID="$issue_id" \
345
364
  SOURCE_TITLE="$src_title" \
346
365
  LIST_OUTPUT="$list_output" \
347
- "$NODE_CMD" "$SCRIPT_DIR/dep-guard-keyword-ripple.js"
366
+ "$NODE_CMD" "$(node_script_path "$SCRIPT_DIR/dep-guard-keyword-ripple.js")"
348
367
  return 0
349
368
 
350
369
  }
@@ -21,6 +21,9 @@ fi
21
21
 
22
22
  # Source shared libraries
23
23
  _FI_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
24
+ if [[ -f "$_FI_SCRIPT_DIR/bootstrap-windows-tools.sh" ]]; then
25
+ source "$_FI_SCRIPT_DIR/bootstrap-windows-tools.sh"
26
+ fi
24
27
  source "$_FI_SCRIPT_DIR/lib/sanitize.sh"
25
28
  if [[ -f "$_FI_SCRIPT_DIR/lib/jsonl-lock.sh" ]]; then
26
29
  source "$_FI_SCRIPT_DIR/lib/jsonl-lock.sh"
@@ -10,8 +10,8 @@
10
10
  # forge_team_claim — Top-level dispatcher entry point
11
11
  #
12
12
  # Env overrides (for testing):
13
- # GH_CMD — Path to gh binary
14
- # BD_CMD — Path to bd binary
13
+ # GH_CMD — Path to gh binary
14
+ # FORGE_CMD — Path to forge binary
15
15
  #
16
16
  # This file does NOT set errexit/pipefail — callers manage their own shell options.
17
17
 
@@ -61,16 +61,22 @@ _claim_prompt() {
61
61
  fi
62
62
  }
63
63
 
64
- # _get_github_issue_number <beads-id>
65
- # Extracts github_issue:N from bd show output. Returns number or empty.
64
+ # _get_github_issue_number <issue-id>
65
+ # Extracts the GitHub issue number from the issue's github_issue:<n> label.
66
+ # Returns the number or empty.
66
67
  _get_github_issue_number() {
67
- local beads_id="$1"
68
- local bd_cmd="${BD_CMD:-bd}"
69
- local bd_output
70
- bd_output="$("$bd_cmd" show "$beads_id" 2>/dev/null)" || return 1
68
+ local issue_id="$1"
69
+ local forge_cmd="${FORGE_CMD:-forge}"
70
+ local show_output
71
+ show_output="$("$forge_cmd" issue show "$issue_id" --json 2>/dev/null)" || return 1
71
72
 
72
73
  local issue_num
73
- issue_num="$(printf '%s' "$bd_output" | grep -oP 'github_issue:\K[0-9]+' || true)"
74
+ issue_num="$(printf '%s' "$show_output" | jq -r '
75
+ (.data.issue // .data // {})
76
+ | (.labels // [])
77
+ | map(select(startswith("github_issue:")))
78
+ | (.[0] // "")
79
+ | ltrimstr("github_issue:")' 2>/dev/null)"
74
80
  if [[ -z "$issue_num" ]]; then
75
81
  return 1
76
82
  fi
@@ -90,7 +96,7 @@ _get_github_assignee() {
90
96
  # _claim_lock_dir — Returns the path to the claim lock directory
91
97
  _claim_lock_dir() {
92
98
  local root="${TEAM_MAP_ROOT:-.}"
93
- printf '%s' "$root/.beads"
99
+ printf '%s' "$root/.forge"
94
100
  }
95
101
 
96
102
  # ── Public API ───────────────────────────────────────────────────────────
@@ -149,7 +155,7 @@ claim_with_lock() {
149
155
  fi
150
156
 
151
157
  local gh_cmd="${GH_CMD:-gh}"
152
- local bd_cmd="${BD_CMD:-bd}"
158
+ local forge_cmd="${FORGE_CMD:-forge}"
153
159
  local lock_dir
154
160
  lock_dir="$(_claim_lock_dir)"
155
161
  mkdir -p "$lock_dir"
@@ -186,8 +192,13 @@ claim_with_lock() {
186
192
  exit 1
187
193
  fi
188
194
  fi
189
- # Claim on both Beads and GitHub
190
- "$bd_cmd" update "$beads_id" --claim >/dev/null 2>&1 || true
195
+ # Claim on both the Forge issue backend and GitHub. The Forge claim is the
196
+ # authoritative backend update, so a failure must abort (exit the flock
197
+ # subshell non-zero); the GitHub assignee edit stays best-effort.
198
+ "$forge_cmd" issue claim "$beads_id" >/dev/null 2>&1 || {
199
+ _claim_error "Failed to claim $beads_id in Forge"
200
+ exit 1
201
+ }
191
202
  "$gh_cmd" issue edit "$issue_num" --add-assignee "$current_user" >/dev/null 2>&1 || true
192
203
  ) 200>"$lock_file"
193
204
  local subshell_rc=$?
@@ -215,8 +226,13 @@ claim_with_lock() {
215
226
  return 1
216
227
  fi
217
228
  fi
218
- # Claim on both Beads and GitHub
219
- "$bd_cmd" update "$beads_id" --claim >/dev/null 2>&1 || true
229
+ # Claim on both the Forge issue backend and GitHub. The Forge claim is the
230
+ # authoritative backend update, so a failure must abort (return non-zero so
231
+ # the RETURN trap releases the lock); the GitHub assignee edit stays best-effort.
232
+ "$forge_cmd" issue claim "$beads_id" >/dev/null 2>&1 || {
233
+ _claim_error "Failed to claim $beads_id in Forge"
234
+ return 1
235
+ }
220
236
  "$gh_cmd" issue edit "$issue_num" --add-assignee "$current_user" >/dev/null 2>&1 || true
221
237
  fi
222
238
 
@@ -234,13 +250,13 @@ forge_team_claim() {
234
250
  return 1
235
251
  fi
236
252
 
237
- local bd_cmd="${BD_CMD:-bd}"
253
+ local forge_cmd="${FORGE_CMD:-forge}"
238
254
 
239
255
  if [[ "$force_flag" == "--force" ]]; then
240
256
  # Skip pre-claim check, claim directly (pass --force to skip lock re-check too)
241
257
  claim_with_lock "$beads_id" "--force" || return 1
242
- # Log override via bd comments
243
- "$bd_cmd" comments add "$beads_id" "Force-claimed by $(get_github_user 2>/dev/null || echo 'unknown')" >/dev/null 2>&1 || true
258
+ # Log the override as an issue comment
259
+ "$forge_cmd" issue comment "$beads_id" "Force-claimed by $(get_github_user 2>/dev/null || echo 'unknown')" >/dev/null 2>&1 || true
244
260
  _claim_info "$beads_id force-claimed successfully"
245
261
  return 0
246
262
  fi
@@ -2,12 +2,12 @@
2
2
  # dashboard.sh — Team health dashboard for forge-team.
3
3
  #
4
4
  # Provides cmd_dashboard [--format=json] which aggregates team issue data
5
- # from Beads and displays per-developer stats, stale assignments, and
6
- # blocked issues.
5
+ # from the Forge issue backend and displays per-developer stats, stale
6
+ # assignments, and blocked issues.
7
7
 
8
8
  # ── Dependencies ─────────────────────────────────────────────────────────
9
- # BD_CMD — path to bd binary (default: bd)
10
- BD_CMD="${BD_CMD:-bd}"
9
+ # FORGE_CMD — path to forge binary (default: forge)
10
+ FORGE_CMD="${FORGE_CMD:-forge}"
11
11
 
12
12
  # FORGE_NOW — override current epoch for testing (default: real time)
13
13
  _now() {
@@ -45,73 +45,46 @@ _iso_to_epoch() {
45
45
 
46
46
  # ── Data Collection ──────────────────────────────────────────────────────
47
47
 
48
- # Parse bd list output into issue IDs and titles.
49
- # Each line: "◐ forge-aaa · Feature A" or "○ forge-bbb · Feature B"
50
- _parse_issue_list() {
51
- local line
52
- while IFS= read -r line; do
53
- [[ -z "$line" ]] && continue
54
- # Extract issue ID: between status icon and " · "
55
- local id title
56
- id="$(echo "$line" | sed -E 's/^[^ ]+ +([^ ]+) +·.*/\1/')"
57
- title="$(echo "$line" | sed -E 's/^[^ ]+ +[^ ]+ +· +(.*)/\1/')"
58
- echo "$id|$title"
59
- done
48
+ # _forge_active_issues_json Merged JSON array of open + in_progress issues.
49
+ # The Kernel list filter does not accept comma-joined statuses, so query each
50
+ # status separately and concatenate the issue arrays.
51
+ _forge_active_issues_json() {
52
+ local open_json inprog_json
53
+ open_json="$("$FORGE_CMD" issue list --status=open --json 2>/dev/null)" || open_json=""
54
+ inprog_json="$("$FORGE_CMD" issue list --status=in_progress --json 2>/dev/null)" || inprog_json=""
55
+ jq -n \
56
+ --argjson a "${open_json:-null}" \
57
+ --argjson b "${inprog_json:-null}" \
58
+ '(((($a.data.issues) // []) + (($b.data.issues) // [])))' 2>/dev/null || echo "[]"
60
59
  }
61
60
 
62
- # Get issue details: status, owner, updated timestamp, blocked-by list
63
- # Output: id|title|status|owner|updated_epoch|blocked_by
64
- _get_issue_details() {
65
- local issue_id="$1"
66
- local show_output
67
- show_output="$($BD_CMD show "$issue_id" 2>/dev/null)" || return 1
68
-
69
- local status="OPEN" owner="" updated="" blocked_by="" title=""
70
-
71
- local line
72
- local in_depends=0
73
- while IFS= read -r line; do
74
- # First line has status: "◐ forge-aaa · Feature A [● P2 · IN_PROGRESS]"
75
- if [[ "$line" =~ \[.*·[[:space:]]*(OPEN|IN_PROGRESS|BLOCKED)\] ]]; then
76
- status="${BASH_REMATCH[1]}"
77
- # Extract title from first line
78
- title="$(echo "$line" | sed -E 's/^[^ ]+ +[^ ]+ +· +(.+) +\[.*/\1/')"
79
- fi
80
-
81
- # Owner line
82
- if [[ "$line" =~ ^Owner:[[:space:]]*(.+) ]]; then
83
- owner="${BASH_REMATCH[1]}"
84
- fi
85
-
86
- # Updated line
87
- if [[ "$line" =~ ^Updated:[[:space:]]*(.+) ]]; then
88
- updated="${BASH_REMATCH[1]}"
89
- fi
90
-
91
- # DEPENDS ON section
92
- if [[ "$line" == "DEPENDS ON" ]]; then
93
- in_depends=1
94
- continue
95
- fi
96
-
97
- if [[ $in_depends -eq 1 && "$line" =~ →.*([a-z]+-[a-z]+):.* ]]; then
98
- # Extract dependency issue ID
99
- local dep_id
100
- dep_id="$(echo "$line" | sed -E 's/.*→ +[^ ]+ +([a-z]+-[a-z0-9]+):.*/\1/')"
101
- if [[ -n "$blocked_by" ]]; then
102
- blocked_by="$blocked_by,$dep_id"
103
- else
104
- blocked_by="$dep_id"
105
- fi
106
- fi
107
- done <<< "$show_output"
108
-
109
- local updated_epoch=0
110
- if [[ -n "$updated" ]]; then
111
- updated_epoch="$(_iso_to_epoch "$updated")"
112
- fi
113
-
114
- echo "$issue_id|$title|$status|$owner|$updated_epoch|$blocked_by"
61
+ # _issue_detail_lines <issues-json>
62
+ # Emit one tab-delimited (jq @tsv) line per issue:
63
+ # id<TAB>title<TAB>STATUS<TAB>owner<TAB>updated_iso<TAB>blocked_by
64
+ # @tsv escapes any tab/newline/CR inside fields (issue titles are user-controlled
65
+ # and may contain '|', tabs, or newlines), so parsing stays field-aligned.
66
+ # STATUS is the upper-cased Kernel status; owner is assignee/claimed_by.
67
+ # A Kernel issue's `.dependencies` lists the issues it BLOCKS (outgoing edges),
68
+ # so blocked_by is computed by reverse scan: the issues (within this active set)
69
+ # whose `.dependencies` include this issue.
70
+ _issue_detail_lines() {
71
+ printf '%s' "$1" | jq -r '
72
+ . as $all
73
+ | $all[]
74
+ | . as $x
75
+ | [ $x.id,
76
+ ($x.title // ""),
77
+ (($x.status // "open") | ascii_upcase),
78
+ ($x.assignee // $x.claimed_by // ""),
79
+ (($x.updated_at // "") | sub("\\.[0-9]+Z$"; "Z")),
80
+ ( [ $all[]
81
+ | select( (.dependencies // [])
82
+ | map(if type == "object" then (.id // .to // "") else . end)
83
+ | any(. == $x.id) )
84
+ | .id ]
85
+ | join(",") )
86
+ ]
87
+ | @tsv' | tr -d '\r'
115
88
  }
116
89
 
117
90
  # ── Dashboard Command ────────────────────────────────────────────────────
@@ -128,12 +101,14 @@ cmd_dashboard() {
128
101
  esac
129
102
  done
130
103
 
131
- # 1. Get all open/in_progress issues
132
- local issue_list
133
- issue_list="$($BD_CMD list --status=open,in_progress 2>/dev/null)" || issue_list=""
104
+ # 1. Get all open/in_progress issues as a merged JSON array
105
+ local issues_json
106
+ issues_json="$(_forge_active_issues_json)"
134
107
 
135
108
  # Check for empty
136
- if [[ -z "$issue_list" ]]; then
109
+ local active_count
110
+ active_count="$(printf '%s' "$issues_json" | jq 'length' 2>/dev/null || echo 0)"
111
+ if [[ "$active_count" -eq 0 ]]; then
137
112
  if [[ "$format" == "json" ]]; then
138
113
  echo '{"developers":{},"total":0,"stale":[],"blocked":[],"message":"No active issues — team is clear"}'
139
114
  else
@@ -142,12 +117,12 @@ cmd_dashboard() {
142
117
  return 0
143
118
  fi
144
119
 
145
- # 2. Parse issue IDs
120
+ # 2. Build per-issue detail lines: id|title|STATUS|owner|updated_iso|blocked_by
146
121
  local issues=()
147
122
  while IFS= read -r entry; do
148
123
  [[ -z "$entry" ]] && continue
149
124
  issues+=("$entry")
150
- done <<< "$(echo "$issue_list" | _parse_issue_list)"
125
+ done <<< "$(_issue_detail_lines "$issues_json")"
151
126
 
152
127
  # 3. Collect details for each issue
153
128
  local now_epoch
@@ -163,12 +138,12 @@ cmd_dashboard() {
163
138
  local total_open=0 total_inprogress=0 total_blocked=0 total_stale=0
164
139
 
165
140
  for entry in "${issues[@]}"; do
166
- local issue_id="${entry%%|*}"
141
+ IFS=$'\t' read -r _id _title _status _owner _updated_iso _blocked_by <<< "$entry"
167
142
 
168
- local details
169
- details="$(_get_issue_details "$issue_id")" || continue
170
-
171
- IFS='|' read -r _id _title _status _owner _updated_epoch _blocked_by <<< "$details"
143
+ local _updated_epoch=0
144
+ if [[ -n "$_updated_iso" ]]; then
145
+ _updated_epoch="$(_iso_to_epoch "$_updated_iso")"
146
+ fi
172
147
 
173
148
  # Default owner if empty
174
149
  [[ -z "$_owner" ]] && _owner="unassigned"
@@ -199,7 +174,7 @@ cmd_dashboard() {
199
174
  if [[ -n "$_blocked_by" ]]; then
200
175
  dev_blocked[$_owner]=$(( ${dev_blocked[$_owner]} + 1 ))
201
176
  total_blocked=$((total_blocked + 1))
202
- blocked_entries+=("$_id|$_owner|$_title|$_blocked_by")
177
+ blocked_entries+=("$_id"$'\t'"$_owner"$'\t'"$_title"$'\t'"$_blocked_by")
203
178
  fi
204
179
 
205
180
  # Check for stale (>48h since last update)
@@ -209,7 +184,7 @@ cmd_dashboard() {
209
184
  local hours=$(( age / 3600 ))
210
185
  dev_stale[$_owner]=$(( ${dev_stale[$_owner]} + 1 ))
211
186
  total_stale=$((total_stale + 1))
212
- stale_entries+=("$_id|$_owner|$_title|${hours}h since last update")
187
+ stale_entries+=("$_id"$'\t'"$_owner"$'\t'"$_title"$'\t'"${hours}h since last update")
213
188
  fi
214
189
  fi
215
190
  done
@@ -275,7 +250,7 @@ _dashboard_text() {
275
250
  echo "Stale assignments (>48h):"
276
251
  local entry
277
252
  for entry in "${stale_entries[@]}"; do
278
- IFS='|' read -r _id _owner _title _age <<< "$entry"
253
+ IFS=$'\t' read -r _id _owner _title _age <<< "$entry"
279
254
  echo " ⚠ $_id [$_owner] — $_title ($_age)"
280
255
  done
281
256
  fi
@@ -286,7 +261,7 @@ _dashboard_text() {
286
261
  echo "Blocked issues:"
287
262
  local entry
288
263
  for entry in "${blocked_entries[@]}"; do
289
- IFS='|' read -r _id _owner _title _deps <<< "$entry"
264
+ IFS=$'\t' read -r _id _owner _title _deps <<< "$entry"
290
265
  echo " ⚠ $_id [$_owner] — $_title (blocked by $_deps)"
291
266
  done
292
267
  fi
@@ -321,7 +296,7 @@ _dashboard_json() {
321
296
  for entry in "${stale_entries[@]}"; do
322
297
  [[ $first -eq 0 ]] && stale_json+=","
323
298
  first=0
324
- IFS='|' read -r _id _owner _title _age <<< "$entry"
299
+ IFS=$'\t' read -r _id _owner _title _age <<< "$entry"
325
300
  stale_json+="$(jq -n -c --arg id "$_id" --arg owner "$_owner" --arg title "$_title" --arg age "$_age" '{id:$id,owner:$owner,title:$title,age:$age}')"
326
301
  done
327
302
  stale_json+="]"
@@ -332,7 +307,7 @@ _dashboard_json() {
332
307
  for entry in "${blocked_entries[@]}"; do
333
308
  [[ $first -eq 0 ]] && blocked_json+=","
334
309
  first=0
335
- IFS='|' read -r _id _owner _title _deps <<< "$entry"
310
+ IFS=$'\t' read -r _id _owner _title _deps <<< "$entry"
336
311
  blocked_json+="$(jq -n -c --arg id "$_id" --arg owner "$_owner" --arg title "$_title" --arg deps "$_deps" '{id:$id,owner:$owner,title:$title,blocked_by:$deps}')"
337
312
  done
338
313
  blocked_json+="]"