forge-workflow 0.0.9 → 0.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (479) hide show
  1. package/.claude/rules/{greptile-review-process.md → review-process.md} +56 -41
  2. package/.claude/scripts/{greptile-resolve.sh → review-resolve.sh} +13 -3
  3. package/.cursor/rules/permissions-guidance.mdc +2 -2
  4. package/.forge/hooks/check-tdd.js +3 -0
  5. package/.forge/hooks/forge-native-hook.js +245 -0
  6. package/.forge/protected-paths.yaml +157 -0
  7. package/AGENTS.md +151 -61
  8. package/CHANGELOG.md +681 -0
  9. package/CLAUDE.md +9 -106
  10. package/QUICKSTART.md +171 -0
  11. package/README.md +271 -363
  12. package/bin/forge-cmd.js +120 -9
  13. package/bin/forge-preflight.js +26 -5
  14. package/bin/forge.js +466 -489
  15. package/docs/INDEX.md +93 -0
  16. package/docs/PROJECT_DESIGN.md +685 -0
  17. package/docs/architecture/index.md +66 -0
  18. package/docs/architecture/notes/README.md +35 -0
  19. package/docs/architecture/subsystems/README.md +46 -0
  20. package/docs/{TOOLCHAIN.md → forge/TOOLCHAIN.md} +56 -47
  21. package/docs/forge/VALIDATION.md +82 -0
  22. package/docs/{AGENT_INSTALL_PROMPT.md → guides/AGENT_INSTALL_PROMPT.md} +3 -3
  23. package/docs/guides/BEADS_GITHUB_SYNC.md +32 -0
  24. package/docs/{ENHANCED_ONBOARDING.md → guides/ENHANCED_ONBOARDING.md} +16 -12
  25. package/docs/guides/GREPTILE_SETUP.md +46 -0
  26. package/docs/guides/MANUAL_REVIEW_GUIDE.md +58 -0
  27. package/docs/guides/MIGRATION.md +56 -0
  28. package/docs/guides/SETUP.md +118 -0
  29. package/docs/guides/SUPPORT.md +185 -0
  30. package/docs/guides/WORKFLOW_TEMPLATES.md +74 -0
  31. package/docs/guides/memory-backends.md +183 -0
  32. package/docs/reference/ADAPTERS.md +128 -0
  33. package/docs/reference/AGENT_SKILL_PARITY.md +175 -0
  34. package/docs/reference/COMMANDS.md +205 -0
  35. package/docs/reference/DECISION_DRIFT_GUARDS.md +97 -0
  36. package/docs/{EXAMPLES.md → reference/EXAMPLES.md} +7 -5
  37. package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +135 -0
  38. package/docs/reference/HERMES_INTEGRATION.md +118 -0
  39. package/docs/reference/INSIGHTS_RECAP.md +63 -0
  40. package/docs/reference/INSTALL.md +164 -0
  41. package/docs/reference/KERNEL_TAXONOMY_VALIDATION.md +161 -0
  42. package/docs/reference/PROTECTED_PATH_MANIFEST.md +25 -0
  43. package/docs/reference/RELEASE.md +68 -0
  44. package/docs/reference/RESEARCH_TEMPLATE.md +292 -0
  45. package/docs/{ROADMAP.md → reference/ROADMAP.md} +12 -9
  46. package/docs/reference/SKILLS.md +35 -0
  47. package/docs/reference/STATUS_BOARD.md +80 -0
  48. package/docs/reference/TEMPLATES.md +106 -0
  49. package/docs/reference/TOOLCHAIN.md +658 -0
  50. package/docs/reference/VALIDATION.md +82 -0
  51. package/docs/reference/agent-permissions.md +169 -0
  52. package/docs/reference/beads-to-kernel-migration-ux.md +61 -0
  53. package/docs/reference/control-plane-guarantees.md +125 -0
  54. package/docs/reference/dependency-chain.md +331 -0
  55. package/docs/reference/forge-kernel-issue-command-contract.md +161 -0
  56. package/docs/reference/forge-kernel-schema.md +72 -0
  57. package/docs/reference/kernel-conflict-evaluators.md +27 -0
  58. package/docs/reference/patch-md-format.md +77 -0
  59. package/docs/reference/protected-state-surfaces.md +59 -0
  60. package/docs/reference/shepherd.md +115 -0
  61. package/docs/reference/superpowers-analysis.md +320 -0
  62. package/docs/reference/superpowers-integration-options.md +404 -0
  63. package/docs/reference/test-environment.md +519 -0
  64. package/docs/reference/upgrade-safety.md +59 -0
  65. package/lefthook.yml +18 -0
  66. package/lib/adapter-cli.js +307 -0
  67. package/lib/adapters/beads-issue-adapter.js +127 -0
  68. package/lib/adapters/beads-kernel-compat.js +1042 -0
  69. package/lib/adapters/greptile-review-adapter.js +141 -0
  70. package/lib/adapters/kernel-issue-adapter.js +101 -0
  71. package/lib/adapters/pr-state-adapter.js +484 -0
  72. package/lib/adoption-profiles.js +126 -0
  73. package/lib/agents/README.md +2 -6
  74. package/lib/agents/claude.plugin.json +3 -8
  75. package/lib/agents/codex.plugin.json +9 -1
  76. package/lib/agents/cursor.plugin.json +2 -6
  77. package/lib/agents/hermes.plugin.json +22 -0
  78. package/lib/agents-config.js +39 -1236
  79. package/lib/audit-evidence.js +282 -0
  80. package/lib/beads-setup.js +225 -28
  81. package/lib/beads-sync-scaffold.js +36 -107
  82. package/lib/codex-skills.js +51 -1
  83. package/lib/commands/_issue.js +744 -70
  84. package/lib/commands/_manifest.js +91 -0
  85. package/lib/commands/_registry.js +85 -34
  86. package/lib/commands/_resolve-command-opts.js +261 -0
  87. package/lib/commands/_serve-security.js +270 -0
  88. package/lib/commands/adapter.js +12 -0
  89. package/lib/commands/add.js +118 -0
  90. package/lib/commands/audit.js +70 -0
  91. package/lib/commands/blocked.js +5 -0
  92. package/lib/commands/board.js +64 -0
  93. package/lib/commands/claim.js +21 -2
  94. package/lib/commands/claims.js +7 -0
  95. package/lib/commands/clean.js +485 -75
  96. package/lib/commands/close.js +2 -2
  97. package/lib/commands/comment.js +5 -0
  98. package/lib/commands/control.js +148 -0
  99. package/lib/commands/create.js +2 -2
  100. package/lib/commands/dev.js +185 -7
  101. package/lib/commands/doc-gate.js +336 -0
  102. package/lib/commands/doctor.js +156 -0
  103. package/lib/commands/explain.js +15 -0
  104. package/lib/commands/export.js +237 -0
  105. package/lib/commands/gate.js +192 -0
  106. package/lib/commands/hooks.js +242 -0
  107. package/lib/commands/inbox.js +118 -0
  108. package/lib/commands/init.js +598 -0
  109. package/lib/commands/insights.js +79 -0
  110. package/lib/commands/issue.js +12 -1
  111. package/lib/commands/issues.js +66 -0
  112. package/lib/commands/lint.js +5 -0
  113. package/lib/commands/list.js +2 -2
  114. package/lib/commands/merge.js +312 -0
  115. package/lib/commands/migrate.js +523 -0
  116. package/lib/commands/new.js +12 -0
  117. package/lib/commands/options.js +241 -0
  118. package/lib/commands/orient.js +13 -0
  119. package/lib/commands/orphans.js +5 -0
  120. package/lib/commands/patch.js +67 -0
  121. package/lib/commands/plan.js +436 -24
  122. package/lib/commands/preflight.js +211 -0
  123. package/lib/commands/prime.js +13 -0
  124. package/lib/commands/push.js +69 -2
  125. package/lib/commands/ready.js +2 -2
  126. package/lib/commands/recall.js +116 -0
  127. package/lib/commands/recap.js +61 -0
  128. package/lib/commands/recommend.js +22 -2
  129. package/lib/commands/release.js +91 -0
  130. package/lib/commands/remember.js +74 -0
  131. package/lib/commands/role.js +99 -0
  132. package/lib/commands/serve.js +581 -0
  133. package/lib/commands/setup.js +851 -979
  134. package/lib/commands/shepherd.js +436 -0
  135. package/lib/commands/ship.js +23 -1
  136. package/lib/commands/show.js +2 -2
  137. package/lib/commands/stage.js +192 -0
  138. package/lib/commands/stale.js +5 -0
  139. package/lib/commands/status.js +329 -11
  140. package/lib/commands/sync.js +34 -46
  141. package/lib/commands/team.js +15 -2
  142. package/lib/commands/test.js +58 -7
  143. package/lib/commands/update.js +2 -2
  144. package/lib/commands/upgrade.js +47 -0
  145. package/lib/commands/validate.js +56 -25
  146. package/lib/commands/worktree.js +308 -128
  147. package/lib/config-writer.js +202 -0
  148. package/lib/control-plane.js +236 -0
  149. package/lib/core/runtime-graph.js +946 -0
  150. package/lib/dep-guard/keyword-ripple.js +184 -0
  151. package/lib/deprecated-sync-cleanup.js +362 -0
  152. package/lib/detect-agent.js +2 -28
  153. package/lib/detect-worktree.js +42 -17
  154. package/lib/doc-gate/declaration.js +177 -0
  155. package/lib/doc-gate/detect.js +289 -0
  156. package/lib/doc-gate/gate.js +375 -0
  157. package/lib/doc-gate/okf-config.js +128 -0
  158. package/lib/doc-gate/okf.js +429 -0
  159. package/lib/docs-command.js +1161 -6
  160. package/lib/forge-issues.js +697 -0
  161. package/lib/forge-lock.js +262 -0
  162. package/lib/gate-events.js +193 -0
  163. package/lib/global-flags.js +74 -0
  164. package/lib/greptile-match.js +7 -63
  165. package/lib/harness-capability-matrix.js +380 -0
  166. package/lib/hook-global-installer.js +347 -0
  167. package/lib/hook-renderer.js +451 -0
  168. package/lib/inbox.js +391 -0
  169. package/lib/insights.js +397 -0
  170. package/lib/issue-adapter.js +156 -0
  171. package/lib/issue-backend.js +145 -0
  172. package/lib/issue-render.js +220 -0
  173. package/lib/issue-sync/authority.js +100 -0
  174. package/lib/issue-sync/github-pull.js +184 -0
  175. package/lib/issue-sync/import-primitives.js +98 -0
  176. package/lib/issue-sync/legacy-link-bridge.js +436 -0
  177. package/lib/issue-sync/link-store.js +292 -0
  178. package/lib/issue-sync/project-github.js +123 -0
  179. package/lib/issue-sync/reconcile.js +195 -0
  180. package/lib/issue-sync/schema.js +126 -0
  181. package/lib/kernel/backing-issue.js +305 -0
  182. package/lib/kernel/broker.js +1218 -0
  183. package/lib/kernel/cli-broker-factory.js +130 -0
  184. package/lib/kernel/conflict-signal.js +82 -0
  185. package/lib/kernel/evaluators.js +195 -0
  186. package/lib/kernel/fs-class.js +495 -0
  187. package/lib/kernel/issue-command-contract.js +559 -0
  188. package/lib/kernel/issue-id-resolver.js +186 -0
  189. package/lib/kernel/lease-enforcer.js +158 -0
  190. package/lib/kernel/migrations.js +333 -0
  191. package/lib/kernel/planning-buckets-schema.js +109 -0
  192. package/lib/kernel/projection-jsonl-writer.js +450 -0
  193. package/lib/kernel/readiness-model.js +329 -0
  194. package/lib/kernel/schema.js +356 -0
  195. package/lib/kernel/sqlite-driver.js +2504 -0
  196. package/lib/kernel/taxonomy-validator.js +394 -0
  197. package/lib/lefthook-check.js +8 -4
  198. package/lib/lefthook-wiring.js +413 -0
  199. package/lib/mcp-config-renderer.js +288 -0
  200. package/lib/memory/graphiti-mcp.js +106 -0
  201. package/lib/memory/router.js +387 -0
  202. package/lib/memory/typed-api.js +102 -0
  203. package/lib/memory-digest.js +195 -0
  204. package/lib/merge-rules.js +395 -0
  205. package/lib/migrate-dry-run.js +466 -0
  206. package/lib/orientation.js +863 -0
  207. package/lib/package-manager-remediation.js +103 -0
  208. package/lib/package-root.js +381 -0
  209. package/lib/patch-intent.js +890 -0
  210. package/lib/plugin-catalog.js +3 -4
  211. package/lib/plugin-manager.js +0 -5
  212. package/lib/pr-bundle.js +186 -0
  213. package/lib/pr-monitor/differ.js +195 -0
  214. package/lib/pr-monitor/events.js +0 -0
  215. package/lib/pr-monitor/gather.js +124 -0
  216. package/lib/pr-monitor/journal.js +299 -0
  217. package/lib/pr-monitor/monitor.js +146 -0
  218. package/lib/pr-monitor/render-sticky.js +157 -0
  219. package/lib/pr-monitor/watch-lifecycle.js +95 -0
  220. package/lib/pr-monitor/watch.js +247 -0
  221. package/lib/pr-pull.js +1273 -0
  222. package/lib/pr-shepherd.js +494 -0
  223. package/lib/pr-state-validator.js +59 -0
  224. package/lib/preflight/gates.js +237 -0
  225. package/lib/preflight/runner.js +116 -0
  226. package/lib/project-discovery.js +0 -53
  227. package/lib/project-memory.js +166 -0
  228. package/lib/protected-path-manifest.js +281 -0
  229. package/lib/protected-state-surfaces.js +387 -0
  230. package/lib/release-readiness.js +2089 -0
  231. package/lib/reset.js +59 -45
  232. package/lib/review-adapter.js +68 -0
  233. package/lib/rules-sync.js +260 -0
  234. package/lib/runtime-health.js +332 -23
  235. package/lib/safety-config-renderer.js +268 -0
  236. package/lib/setup-action-log.js +1 -7
  237. package/lib/setup.js +27 -65
  238. package/lib/shell-utils.js +76 -6
  239. package/lib/skills-sync.js +330 -0
  240. package/lib/smart-status/conflicts.js +205 -0
  241. package/lib/smart-status/scoring.js +191 -0
  242. package/lib/status/beads-snapshot.js +145 -0
  243. package/lib/status/presenter.js +216 -0
  244. package/lib/status/snapshot.js +186 -0
  245. package/lib/sync-backend.js +202 -0
  246. package/lib/untrusted-content.js +52 -0
  247. package/lib/upgrade-safety.js +199 -0
  248. package/lib/workflow/enforce-stage.js +298 -47
  249. package/lib/workflow/stage-transition.js +115 -0
  250. package/lib/workflow/stages.js +30 -6
  251. package/lib/workflow/state-manager.js +159 -14
  252. package/lib/workflow/state.js +23 -1
  253. package/lib/workflow-profiles.js +17 -5
  254. package/package.json +46 -36
  255. package/rules/documentation.md +19 -0
  256. package/rules/kernel-tracking.md +26 -0
  257. package/rules/security.md +22 -0
  258. package/rules/tdd.md +20 -0
  259. package/rules/workflow.md +27 -0
  260. package/scripts/auto-backing-issue.js +47 -0
  261. package/scripts/beads-context.sh +165 -22
  262. package/scripts/beads-migrate-to-dolt.sh +7 -0
  263. package/scripts/beads-upgrade-smoke.sh +284 -0
  264. package/scripts/behavioral-judge.sh +115 -11
  265. package/scripts/benchmark.js +349 -63
  266. package/scripts/bootstrap-windows-tools.sh +78 -0
  267. package/scripts/branch-protection.js +2 -3
  268. package/scripts/check-agents.js +34 -137
  269. package/scripts/commitlint.js +3 -1
  270. package/scripts/conflict-detect.sh +3 -0
  271. package/scripts/dep-guard-analyze.js +52 -17
  272. package/scripts/dep-guard-keyword-ripple.js +29 -0
  273. package/scripts/dep-guard-render-review.js +86 -0
  274. package/scripts/dep-guard.sh +64 -232
  275. package/scripts/file-index.sh +3 -0
  276. package/scripts/forge-team/lib/claim.sh +34 -18
  277. package/scripts/forge-team/lib/dashboard.sh +61 -86
  278. package/scripts/forge-team/lib/epic.sh +99 -263
  279. package/scripts/forge-team/lib/hooks.sh +26 -28
  280. package/scripts/forge-team/lib/identity.sh +4 -4
  281. package/scripts/forge-team/lib/sync-github.sh +144 -47
  282. package/scripts/forge-team/lib/verify.sh +93 -83
  283. package/scripts/forge-team/lib/workload.sh +41 -65
  284. package/scripts/forge-team/tests/claim.test.sh +25 -19
  285. package/scripts/forge-team/tests/dashboard.test.sh +31 -46
  286. package/scripts/forge-team/tests/epic.test.sh +52 -71
  287. package/scripts/forge-team/tests/hooks.test.sh +38 -50
  288. package/scripts/forge-team/tests/identity.test.sh +3 -3
  289. package/scripts/forge-team/tests/integration.test.sh +44 -66
  290. package/scripts/forge-team/tests/sync-github.test.sh +183 -79
  291. package/scripts/forge-team/tests/verify.test.sh +37 -46
  292. package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
  293. package/scripts/forge-team/tests/workload.test.sh +32 -66
  294. package/scripts/gen-command-manifest.js +153 -0
  295. package/scripts/gen-embedded-assets.mjs +129 -0
  296. package/scripts/install.ps1 +139 -0
  297. package/scripts/install.sh +268 -0
  298. package/scripts/lib/beads-migrate-to-dolt.mjs +503 -0
  299. package/scripts/lib/release-asset.mjs +84 -0
  300. package/scripts/parity-check.mjs +145 -0
  301. package/scripts/parity-check.test.mjs +58 -0
  302. package/scripts/pin-agentic-workflow-images.js +112 -0
  303. package/scripts/pr-coordinator.sh +3 -0
  304. package/scripts/preflight-sonar.eslint.config.mjs +44 -0
  305. package/scripts/preflight.sh +108 -0
  306. package/scripts/protected-state-check.js +104 -0
  307. package/scripts/smart-status-score.js +31 -0
  308. package/scripts/smart-status-sessions.js +51 -0
  309. package/scripts/smart-status.sh +117 -369
  310. package/scripts/spikes/config-race-bench.js +111 -0
  311. package/scripts/spikes/harness-capability-matrix.js +13 -0
  312. package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
  313. package/scripts/spikes/protected-path-manifest.js +20 -0
  314. package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
  315. package/scripts/sync-agent-skills.js +62 -0
  316. package/scripts/sync-agentic-workflow.js +48 -0
  317. package/scripts/sync-utils.sh +3 -0
  318. package/scripts/test-ci-shard.js +251 -0
  319. package/scripts/test-dashboard.js +188 -52
  320. package/scripts/test-full-suite.js +186 -0
  321. package/scripts/test-profile.js +278 -0
  322. package/scripts/test.js +302 -28
  323. package/scripts/validate.js +143 -0
  324. package/scripts/validate.sh +18 -1
  325. package/skills/claim-safety/SKILL.md +102 -0
  326. package/skills/claim-safety/evals/evals.json +46 -0
  327. package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +46 -52
  328. package/skills/dev/evals/evals.json +50 -0
  329. package/skills/hermes-forge/SKILL.md +185 -0
  330. package/skills/hermes-forge/evals/evals.json +46 -0
  331. package/skills/issue-basics/SKILL.md +111 -0
  332. package/skills/issue-basics/evals/evals.json +46 -0
  333. package/skills/kernel/SKILL.md +166 -0
  334. package/skills/kernel/evals/evals.json +50 -0
  335. package/skills/memory/SKILL.md +102 -0
  336. package/skills/parallel-deep-research/SKILL.md +14 -11
  337. package/skills/parallel-deep-research/evals/evals.json +11 -27
  338. package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +134 -159
  339. package/skills/plan/evals/evals.json +42 -0
  340. package/skills/research/SKILL.md +195 -0
  341. package/skills/research/evals/evals.json +42 -0
  342. package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
  343. package/skills/review/evals/evals.json +42 -0
  344. package/skills/rollback/SKILL.md +110 -0
  345. package/skills/rollback/evals/evals.json +46 -0
  346. package/skills/rollback/references/methods.md +204 -0
  347. package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
  348. package/skills/shepherd/SKILL.md +66 -0
  349. package/skills/shepherd/evals/evals.json +42 -0
  350. package/skills/ship/SKILL.md +251 -0
  351. package/skills/ship/evals/evals.json +42 -0
  352. package/skills/smith/SKILL.md +142 -0
  353. package/skills/smith/evals/evals.json +46 -0
  354. package/skills/smith/references/autonomy-and-gates.md +94 -0
  355. package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
  356. package/skills/sonarcloud/evals/evals.json +46 -0
  357. package/skills/sonarcloud-analysis/SKILL.md +18 -13
  358. package/skills/sonarcloud-analysis/evals/evals.json +11 -15
  359. package/skills/status/SKILL.md +102 -0
  360. package/skills/status/evals/evals.json +50 -0
  361. package/skills/triage-ready/SKILL.md +121 -0
  362. package/skills/triage-ready/evals/evals.json +42 -0
  363. package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
  364. package/skills/validate/evals/evals.json +42 -0
  365. package/skills/verify/SKILL.md +299 -0
  366. package/skills/verify/evals/evals.json +50 -0
  367. package/.claude/commands/dev.md +0 -345
  368. package/.claude/commands/plan.md +0 -566
  369. package/.claude/commands/premerge.md +0 -186
  370. package/.claude/commands/research.md +0 -42
  371. package/.claude/commands/review.md +0 -451
  372. package/.claude/commands/rollback.md +0 -721
  373. package/.claude/commands/ship.md +0 -213
  374. package/.claude/commands/sonarcloud.md +0 -152
  375. package/.claude/commands/status.md +0 -90
  376. package/.claude/commands/validate.md +0 -288
  377. package/.claude/commands/verify.md +0 -269
  378. package/.claude/rules/workflow.md +0 -121
  379. package/.cline/workflows/dev.md +0 -342
  380. package/.cline/workflows/plan.md +0 -563
  381. package/.cline/workflows/premerge.md +0 -183
  382. package/.cline/workflows/research.md +0 -39
  383. package/.cline/workflows/review.md +0 -448
  384. package/.cline/workflows/rollback.md +0 -718
  385. package/.cline/workflows/ship.md +0 -210
  386. package/.cline/workflows/sonarcloud.md +0 -146
  387. package/.cline/workflows/status.md +0 -87
  388. package/.cline/workflows/validate.md +0 -285
  389. package/.cline/workflows/verify.md +0 -266
  390. package/.codex/config.toml +0 -11
  391. package/.codex/skills/dev/SKILL.md +0 -345
  392. package/.codex/skills/plan/SKILL.md +0 -566
  393. package/.codex/skills/premerge/SKILL.md +0 -186
  394. package/.codex/skills/research/SKILL.md +0 -42
  395. package/.codex/skills/review/SKILL.md +0 -451
  396. package/.codex/skills/rollback/SKILL.md +0 -721
  397. package/.codex/skills/ship/SKILL.md +0 -213
  398. package/.codex/skills/sonarcloud/SKILL.md +0 -149
  399. package/.codex/skills/status/SKILL.md +0 -90
  400. package/.codex/skills/validate/SKILL.md +0 -288
  401. package/.codex/skills/verify/SKILL.md +0 -269
  402. package/.cursor/commands/dev.md +0 -342
  403. package/.cursor/commands/plan.md +0 -563
  404. package/.cursor/commands/premerge.md +0 -183
  405. package/.cursor/commands/research.md +0 -39
  406. package/.cursor/commands/review.md +0 -448
  407. package/.cursor/commands/ship.md +0 -210
  408. package/.cursor/commands/sonarcloud.md +0 -146
  409. package/.cursor/commands/status.md +0 -87
  410. package/.cursor/commands/validate.md +0 -285
  411. package/.cursor/commands/verify.md +0 -266
  412. package/.cursorrules +0 -149
  413. package/.github/prompts/premerge.prompt.md +0 -188
  414. package/.github/prompts/research.prompt.md +0 -44
  415. package/.github/prompts/rollback.prompt.md +0 -723
  416. package/.github/prompts/ship.prompt.md +0 -215
  417. package/.github/prompts/status.prompt.md +0 -92
  418. package/.github/prompts/verify.prompt.md +0 -271
  419. package/.github/workflows/beads-to-github.yml +0 -56
  420. package/.github/workflows/github-to-beads.yml +0 -97
  421. package/.kilocode/workflows/dev.md +0 -346
  422. package/.kilocode/workflows/plan.md +0 -567
  423. package/.kilocode/workflows/premerge.md +0 -187
  424. package/.kilocode/workflows/research.md +0 -43
  425. package/.kilocode/workflows/review.md +0 -452
  426. package/.kilocode/workflows/rollback.md +0 -722
  427. package/.kilocode/workflows/ship.md +0 -214
  428. package/.kilocode/workflows/sonarcloud.md +0 -150
  429. package/.kilocode/workflows/status.md +0 -91
  430. package/.kilocode/workflows/validate.md +0 -289
  431. package/.kilocode/workflows/verify.md +0 -270
  432. package/.opencode/commands/dev.md +0 -345
  433. package/.opencode/commands/plan.md +0 -566
  434. package/.opencode/commands/premerge.md +0 -186
  435. package/.opencode/commands/research.md +0 -42
  436. package/.opencode/commands/review.md +0 -451
  437. package/.opencode/commands/rollback.md +0 -721
  438. package/.opencode/commands/ship.md +0 -213
  439. package/.opencode/commands/sonarcloud.md +0 -149
  440. package/.opencode/commands/status.md +0 -90
  441. package/.opencode/commands/validate.md +0 -288
  442. package/.opencode/commands/verify.md +0 -269
  443. package/.roo/commands/dev.md +0 -346
  444. package/.roo/commands/plan.md +0 -567
  445. package/.roo/commands/premerge.md +0 -187
  446. package/.roo/commands/research.md +0 -43
  447. package/.roo/commands/review.md +0 -452
  448. package/.roo/commands/rollback.md +0 -722
  449. package/.roo/commands/ship.md +0 -214
  450. package/.roo/commands/sonarcloud.md +0 -150
  451. package/.roo/commands/status.md +0 -91
  452. package/.roo/commands/validate.md +0 -289
  453. package/.roo/commands/verify.md +0 -270
  454. package/docs/BEADS_GITHUB_SYNC.md +0 -255
  455. package/docs/GREPTILE_SETUP.md +0 -400
  456. package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
  457. package/docs/SETUP.md +0 -663
  458. package/docs/VALIDATION.md +0 -363
  459. package/lib/agents/cline.plugin.json +0 -29
  460. package/lib/agents/copilot.plugin.json +0 -24
  461. package/lib/agents/kilocode.plugin.json +0 -22
  462. package/lib/agents/opencode.plugin.json +0 -23
  463. package/lib/agents/roo.plugin.json +0 -30
  464. package/lib/beads-health-check.js +0 -143
  465. package/lib/commands/commands-reset.js +0 -147
  466. package/opencode.json +0 -67
  467. package/scripts/beads-context.test.js +0 -567
  468. package/scripts/github-beads-sync/comment.mjs +0 -64
  469. package/scripts/github-beads-sync/config.mjs +0 -148
  470. package/scripts/github-beads-sync/github-api.mjs +0 -131
  471. package/scripts/github-beads-sync/index.mjs +0 -332
  472. package/scripts/github-beads-sync/label-mapper.mjs +0 -54
  473. package/scripts/github-beads-sync/mapping.mjs +0 -78
  474. package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
  475. package/scripts/github-beads-sync/reverse-sync.mjs +0 -138
  476. package/scripts/github-beads-sync/run-bd.mjs +0 -161
  477. package/scripts/github-beads-sync/sanitize.mjs +0 -121
  478. package/scripts/github-beads-sync.config.json +0 -26
  479. package/scripts/sync-commands.js +0 -600
@@ -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+="]"
@@ -2,12 +2,15 @@
2
2
  # scripts/forge-team/lib/epic.sh — Epic rollup view
3
3
  #
4
4
  # Shows epic progress with per-developer breakdown and blocked issue tracking.
5
+ # All data comes from a SINGLE `forge issue children <id> --json` call: the kernel
6
+ # computes the rollup (done-only percentage, per-status counts) and emits each child
7
+ # with its assignee + blocked_by, so this layer only renders.
5
8
  #
6
9
  # Functions:
7
10
  # cmd_epic — Top-level dispatcher entry point
8
11
  #
9
12
  # Env overrides (for testing):
10
- # BD_CMD — Path to bd binary
13
+ # FORGE_CMD — Path to forge binary (default: forge)
11
14
  #
12
15
  # This file does NOT set errexit/pipefail — callers manage their own shell options.
13
16
 
@@ -19,85 +22,10 @@ _EPIC_LIB_LOADED=1
19
22
 
20
23
  # ── Internal helpers ─────────────────────────────────────────────────────
21
24
 
22
- # _epic_get_bd Returns the bd command path
23
- _epic_get_bd() {
24
- printf '%s' "${BD_CMD:-bd}"
25
- }
26
-
27
- # _epic_parse_blocks <bd-show-output>
28
- # Extracts child issue IDs from the BLOCKS section.
29
- # Each line in BLOCKS looks like: ← ✓ forge-child1: Child 1 (closed)
30
- # Returns one ID per line.
31
- _epic_parse_blocks() {
32
- local bd_output="$1"
33
- local in_blocks=0
34
-
35
- while IFS= read -r line; do
36
- if [[ "$line" == "BLOCKS" ]]; then
37
- in_blocks=1
38
- continue
39
- fi
40
- # Stop at next section header (non-indented, non-empty line after BLOCKS)
41
- if [[ $in_blocks -eq 1 ]]; then
42
- if [[ -z "$line" ]]; then
43
- continue
44
- fi
45
- if [[ "$line" =~ ^[A-Z] ]]; then
46
- break
47
- fi
48
- # Extract issue ID: matches forge-XXXX pattern after the arrow+status marker
49
- local child_id
50
- child_id="$(printf '%s' "$line" | sed -n 's/.*← [^ ]* \([a-z0-9-]*\):.*/\1/p')"
51
- if [[ -n "$child_id" ]]; then
52
- printf '%s\n' "$child_id"
53
- fi
54
- fi
55
- done <<< "$bd_output"
56
- }
57
-
58
- # _epic_get_child_info <child-id>
59
- # Returns: id|status|owner|title|blocked_by
60
- _epic_get_child_info() {
61
- local child_id="$1"
62
- local bd_cmd
63
- bd_cmd="$(_epic_get_bd)"
64
-
65
- local child_output
66
- child_output="$("$bd_cmd" show "$child_id" 2>/dev/null)" || return 1
67
-
68
- local first_line
69
- first_line="$(printf '%s' "$child_output" | head -1)"
70
-
71
- # Parse status from the first character/marker
72
- local status="open"
73
- if [[ "$first_line" == *"CLOSED"* ]] || [[ "$first_line" == *"✓"* ]]; then
74
- status="closed"
75
- elif [[ "$first_line" == *"IN_PROGRESS"* ]] || [[ "$first_line" == *"◐"* ]]; then
76
- status="in_progress"
77
- fi
78
-
79
- # Parse title — between · and [
80
- local title
81
- title="$(printf '%s' "$first_line" | sed -n 's/^[^ ]* [^ ]* · \(.*\) \[.*/\1/p')"
82
- if [[ -z "$title" ]]; then
83
- title="$child_id"
84
- fi
85
-
86
- # Parse owner from "Owner: xxx" line
87
- local owner=""
88
- owner="$(printf '%s' "$child_output" | sed -n 's/^Owner: *//p' | head -1)"
89
-
90
- # Check for BLOCKED_BY section
91
- local blocked_by=""
92
- blocked_by="$(printf '%s' "$child_output" | sed -n '/^BLOCKED_BY$/,/^[A-Z]/{ /^ /{ s/.*→ \([a-z0-9-]*\):.*/\1/p; }; }')"
93
-
94
- printf '%s|%s|%s|%s|%s' "$child_id" "$status" "$owner" "$title" "$blocked_by"
95
- }
96
-
97
- # _epic_status_icon <status>
25
+ # _epic_status_icon <status> (kernel status vocabulary: open|in_progress|review|done|cancelled)
98
26
  _epic_status_icon() {
99
27
  case "$1" in
100
- closed) printf '%s' "✓" ;;
28
+ done) printf '%s' "✓" ;;
101
29
  in_progress) printf '%s' "◐" ;;
102
30
  *) printf '%s' "○" ;;
103
31
  esac
@@ -115,35 +43,43 @@ cmd_epic() {
115
43
  return 1
116
44
  fi
117
45
 
118
- local bd_cmd
119
- bd_cmd="$(_epic_get_bd)"
46
+ local forge_cmd
47
+ forge_cmd="${FORGE_CMD:-forge}"
120
48
 
121
- # 1. Get epic details
122
- local epic_output
123
- epic_output="$("$bd_cmd" show "$issue_id" 2>/dev/null)" || {
49
+ # 1. Single query: epic header + direct children + kernel-computed rollup. A missing
50
+ # or invalid id makes `forge issue children` fail (empty stdout) — treat as not found.
51
+ local response
52
+ response="$("$forge_cmd" issue children "$issue_id" --json 2>/dev/null)" || {
124
53
  echo "ERROR: Could not fetch epic $issue_id" >&2
125
54
  return 1
126
55
  }
127
-
128
- # Parse epic title from first line
129
- local epic_first_line
130
- epic_first_line="$(printf '%s' "$epic_output" | head -1)"
131
- local epic_title
132
- epic_title="$(printf '%s' "$epic_first_line" | sed -n 's/^[^ ]* [^ ]* · \(.*\) \[.*/\1/p')"
133
- if [[ -z "$epic_title" ]]; then
134
- epic_title="$issue_id"
56
+ if [[ -z "$response" ]]; then
57
+ echo "ERROR: Could not fetch epic $issue_id" >&2
58
+ return 1
135
59
  fi
136
60
 
137
- # 2. Parse BLOCKS section to find child issues
138
- local children_ids
139
- children_ids="$(_epic_parse_blocks "$epic_output")"
140
-
141
- # Handle empty epic
142
- if [[ -z "$children_ids" ]]; then
61
+ # 2. Epic title (fall back to the id when the header carries no title).
62
+ local epic_title
63
+ epic_title="$(printf '%s' "$response" | jq -r '.data.epic.title // empty' | tr -d '\r')"
64
+ [[ -z "$epic_title" ]] && epic_title="$issue_id"
65
+
66
+ # 3. Rollup counts (kernel-computed). open_count keeps the historical invariant
67
+ # total = done + in_progress + open_count (the catch-all bucket also absorbs the
68
+ # kernel review/cancelled statuses), so downstream consumers see a stable shape.
69
+ local total done_count in_progress_count open_count percentage
70
+ total="$(printf '%s' "$response" | jq -r '.data.rollup.total // 0' | tr -d '\r')"
71
+ done_count="$(printf '%s' "$response" | jq -r '.data.rollup.done // 0' | tr -d '\r')"
72
+ in_progress_count="$(printf '%s' "$response" | jq -r '.data.rollup.in_progress // 0' | tr -d '\r')"
73
+ percentage="$(printf '%s' "$response" | jq -r '.data.rollup.percentage // 0' | tr -d '\r')"
74
+ open_count=$((total - done_count - in_progress_count))
75
+
76
+ # 4. Empty epic → no children.
77
+ if [[ "$total" -eq 0 ]]; then
143
78
  if [[ "$format_flag" == "--format=json" ]]; then
144
- cat <<ENDJSON
145
- {"epic_id":"$issue_id","title":"$epic_title","total":0,"done":0,"in_progress":0,"open_count":0,"percentage":0,"children":[],"by_developer":{},"blocked":[]}
146
- ENDJSON
79
+ # Build via jq --arg so a title containing quotes/backslashes stays valid
80
+ # JSON (parity with the non-empty path below).
81
+ jq -cn --arg epic_id "$issue_id" --arg title "$epic_title" \
82
+ '{epic_id:$epic_id,title:$title,total:0,done:0,in_progress:0,open_count:0,percentage:0,children:[],by_developer:{},blocked:[]}'
147
83
  return 0
148
84
  fi
149
85
  echo "Epic: $issue_id — $epic_title"
@@ -151,181 +87,81 @@ ENDJSON
151
87
  return 0
152
88
  fi
153
89
 
154
- # 3. For each child: get status, assignee
155
- local total=0
156
- local done_count=0
157
- local in_progress_count=0
158
- local open_count=0
159
- local blocked_list=""
160
-
161
- # Arrays for child data (using indexed approach for bash 3 compat)
162
- local child_lines=""
163
- # Developer tracking: accumulate "dev:status" pairs
164
- local dev_entries=""
165
-
166
- while IFS= read -r child_id; do
167
- [[ -z "$child_id" ]] && continue
168
-
169
- local info
170
- info="$(_epic_get_child_info "$child_id")" || continue
171
-
172
- local c_id c_status c_owner c_title c_blocked
173
- IFS='|' read -r c_id c_status c_owner c_title c_blocked <<< "$info"
174
-
175
- total=$((total + 1))
176
-
177
- case "$c_status" in
178
- closed) done_count=$((done_count + 1)) ;;
179
- in_progress) in_progress_count=$((in_progress_count + 1)) ;;
180
- *) open_count=$((open_count + 1)) ;;
181
- esac
182
-
183
- # Track blocked children
184
- if [[ -n "$c_blocked" ]]; then
185
- if [[ -n "$blocked_list" ]]; then
186
- blocked_list="$blocked_list"$'\n'"$c_id blocked by $c_blocked"
187
- else
188
- blocked_list="$c_id blocked by $c_blocked"
189
- fi
190
- fi
191
-
192
- # Build child line for display
193
- local icon
194
- icon="$(_epic_status_icon "$c_status")"
195
- local display_owner="${c_owner:-unassigned}"
196
- child_lines="${child_lines} $icon $c_id [$display_owner] $c_title ($c_status)"$'\n'
197
-
198
- # Track developer entries
199
- if [[ -n "$c_owner" ]]; then
200
- dev_entries="${dev_entries}${c_owner}:${c_status}"$'\n'
201
- fi
202
- done <<< "$children_ids"
203
-
204
- # 4. Calculate completion percentage
205
- local percentage=0
206
- if [[ $total -gt 0 ]]; then
207
- percentage=$(( (done_count * 100) / total ))
208
- fi
209
-
210
- # 5. Build per-developer breakdown
211
- # Collect unique developers
212
- local devs=""
213
- while IFS= read -r entry; do
214
- [[ -z "$entry" ]] && continue
215
- local dev="${entry%%:*}"
216
- if [[ -z "$devs" ]] || ! printf '%s' "$devs" | grep -qxF "$dev"; then
217
- if [[ -n "$devs" ]]; then
218
- devs="$devs"$'\n'"$dev"
219
- else
220
- devs="$dev"
221
- fi
222
- fi
223
- done <<< "$dev_entries"
224
-
225
- # JSON output
90
+ # 5. JSON output one jq transform from the kernel response. Preserves the historical
91
+ # cmd_epic shape (epic_id,title,total,done,in_progress,open_count,percentage,
92
+ # children,by_developer,blocked). Unassigned children are excluded from by_developer
93
+ # (matches the prior behavior), and per-dev open is the same total-done-in_progress
94
+ # catch-all.
226
95
  if [[ "$format_flag" == "--format=json" ]]; then
227
- local children_json="["
228
- local first_child=1
229
- while IFS= read -r child_id; do
230
- [[ -z "$child_id" ]] && continue
231
- local info
232
- info="$(_epic_get_child_info "$child_id")" || continue
233
- local c_id c_status c_owner c_title c_blocked
234
- IFS='|' read -r c_id c_status c_owner c_title c_blocked <<< "$info"
235
-
236
- if [[ $first_child -eq 0 ]]; then
237
- children_json="$children_json,"
238
- fi
239
- first_child=0
240
- children_json="$children_json{\"id\":\"$c_id\",\"status\":\"$c_status\",\"owner\":\"$c_owner\",\"title\":\"$c_title\",\"blocked_by\":\"$c_blocked\"}"
241
- done <<< "$children_ids"
242
- children_json="$children_json]"
243
-
244
- local dev_json="{"
245
- local first_dev=1
246
- while IFS= read -r dev; do
247
- [[ -z "$dev" ]] && continue
248
- local d_done=0 d_in_progress=0 d_open=0 d_total=0
249
- while IFS= read -r entry; do
250
- [[ -z "$entry" ]] && continue
251
- local e_dev="${entry%%:*}"
252
- local e_status="${entry#*:}"
253
- if [[ "$e_dev" == "$dev" ]]; then
254
- d_total=$((d_total + 1))
255
- case "$e_status" in
256
- closed) d_done=$((d_done + 1)) ;;
257
- in_progress) d_in_progress=$((d_in_progress + 1)) ;;
258
- *) d_open=$((d_open + 1)) ;;
259
- esac
260
- fi
261
- done <<< "$dev_entries"
262
-
263
- if [[ $first_dev -eq 0 ]]; then
264
- dev_json="$dev_json,"
265
- fi
266
- first_dev=0
267
- dev_json="$dev_json\"$dev\":{\"total\":$d_total,\"done\":$d_done,\"in_progress\":$d_in_progress,\"open\":$d_open}"
268
- done <<< "$devs"
269
- dev_json="$dev_json}"
270
-
271
- local blocked_json="["
272
- if [[ -n "$blocked_list" ]]; then
273
- local first_blocked=1
274
- while IFS= read -r bline; do
275
- [[ -z "$bline" ]] && continue
276
- if [[ $first_blocked -eq 0 ]]; then
277
- blocked_json="$blocked_json,"
278
- fi
279
- first_blocked=0
280
- blocked_json="$blocked_json\"$bline\""
281
- done <<< "$blocked_list"
282
- fi
283
- blocked_json="$blocked_json]"
284
-
285
- printf '{"epic_id":"%s","title":"%s","total":%d,"done":%d,"in_progress":%d,"open_count":%d,"percentage":%d,"children":%s,"by_developer":%s,"blocked":%s}\n' \
286
- "$issue_id" "$epic_title" "$total" "$done_count" "$in_progress_count" "$open_count" "$percentage" \
287
- "$children_json" "$dev_json" "$blocked_json"
96
+ printf '%s' "$response" | jq -c \
97
+ --arg epic_id "$issue_id" \
98
+ --arg title "$epic_title" '
99
+ (.data.children // []) as $children
100
+ | {
101
+ epic_id: $epic_id,
102
+ title: $title,
103
+ total: (.data.rollup.total // 0),
104
+ done: (.data.rollup.done // 0),
105
+ in_progress: (.data.rollup.in_progress // 0),
106
+ open_count: ((.data.rollup.total // 0) - (.data.rollup.done // 0) - (.data.rollup.in_progress // 0)),
107
+ percentage: (.data.rollup.percentage // 0),
108
+ children: [ $children[] | {
109
+ id: .id,
110
+ status: .status,
111
+ owner: (.assignee // ""),
112
+ title: (.title // .id),
113
+ blocked_by: ((.blocked_by // []) | join(","))
114
+ } ],
115
+ by_developer: (
116
+ [ $children[] | select((.assignee // "") != "") ]
117
+ | group_by(.assignee)
118
+ | map(
119
+ (.[0].assignee) as $dev
120
+ | length as $t
121
+ | ([.[] | select(.status == "done")] | length) as $d
122
+ | ([.[] | select(.status == "in_progress")] | length) as $ip
123
+ | { key: $dev, value: { total: $t, done: $d, in_progress: $ip, open: ($t - $d - $ip) } }
124
+ )
125
+ | from_entries
126
+ ),
127
+ blocked: [ $children[] | select(.blocked == true) | "\(.id) blocked by \((.blocked_by // []) | join(","))" ]
128
+ }' | tr -d '\r'
288
129
  return 0
289
130
  fi
290
131
 
291
- # Text output
132
+ # 6. Text output.
292
133
  echo "Epic: $issue_id — $epic_title"
293
134
  echo "Progress: $done_count/$total ($percentage%)"
294
- printf '%s' "$child_lines"
135
+ while IFS=$'\t' read -r c_id c_status c_owner c_title; do
136
+ [[ -z "$c_id" ]] && continue
137
+ local icon
138
+ icon="$(_epic_status_icon "$c_status")"
139
+ echo " $icon $c_id [${c_owner:-unassigned}] $c_title ($c_status)"
140
+ done < <(printf '%s' "$response" | jq -r '.data.children[] | [.id, .status, (.assignee // ""), (.title // .id)] | @tsv' | tr -d '\r')
295
141
 
296
142
  echo ""
297
143
  echo "By developer:"
298
- while IFS= read -r dev; do
299
- [[ -z "$dev" ]] && continue
300
- local d_done=0 d_in_progress=0 d_open=0 d_total=0
301
- while IFS= read -r entry; do
302
- [[ -z "$entry" ]] && continue
303
- local e_dev="${entry%%:*}"
304
- local e_status="${entry#*:}"
305
- if [[ "$e_dev" == "$dev" ]]; then
306
- d_total=$((d_total + 1))
307
- case "$e_status" in
308
- closed) d_done=$((d_done + 1)) ;;
309
- in_progress) d_in_progress=$((d_in_progress + 1)) ;;
310
- *) d_open=$((d_open + 1)) ;;
311
- esac
312
- fi
313
- done <<< "$dev_entries"
314
-
315
- local dev_parts=""
316
- dev_parts="$d_done/$d_total done"
317
- [[ $d_in_progress -gt 0 ]] && dev_parts="$dev_parts, $d_in_progress in progress"
318
- [[ $d_open -gt 0 ]] && dev_parts="$dev_parts, $d_open open"
319
- echo " $dev: $dev_parts"
320
- done <<< "$devs"
144
+ printf '%s' "$response" | jq -r '
145
+ [ (.data.children // [])[] | select((.assignee // "") != "") ]
146
+ | group_by(.assignee)
147
+ | map(
148
+ (.[0].assignee) as $dev
149
+ | length as $t
150
+ | ([.[] | select(.status == "done")] | length) as $d
151
+ | ([.[] | select(.status == "in_progress")] | length) as $ip
152
+ | ($t - $d - $ip) as $op
153
+ | " \($dev): \($d)/\($t) done"
154
+ + (if $ip > 0 then ", \($ip) in progress" else "" end)
155
+ + (if $op > 0 then ", \($op) open" else "" end)
156
+ )
157
+ | .[]' | tr -d '\r'
321
158
 
322
159
  echo ""
323
- if [[ -n "$blocked_list" ]]; then
160
+ local blocked_lines
161
+ blocked_lines="$(printf '%s' "$response" | jq -r '.data.children[] | select(.blocked == true) | " \(.id) blocked by \((.blocked_by // []) | join(","))"' | tr -d '\r')"
162
+ if [[ -n "$blocked_lines" ]]; then
324
163
  echo "Blocked:"
325
- while IFS= read -r bline; do
326
- [[ -z "$bline" ]] && continue
327
- echo " $bline"
328
- done <<< "$blocked_list"
164
+ printf '%s\n' "$blocked_lines"
329
165
  else
330
166
  echo "Blocked: none"
331
167
  fi