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
@@ -0,0 +1,436 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * shepherd command — one bounded monitor pass over a pull request.
5
+ *
6
+ * `forge shepherd <pr> [--auto-rebase]` reads PR/CI state, takes at most one
7
+ * idempotent Tier-A action (rerun a flaky required check), and exits with a
8
+ * terminal state. It NEVER merges and NEVER resolves review threads. A
9
+ * `--watch` loop, if desired, lives in an external scheduler that re-invokes
10
+ * this command on an interval — there is no in-process polling loop here.
11
+ *
12
+ * `forge shepherd <pr> --bundle --json` instead prints the COMPLETE read-only
13
+ * PR-state bundle (all unresolved threads, merge state, CI, divergence,
14
+ * predicted conflicts) the monitor will hand to a fixer-agent. It still decides
15
+ * nothing and takes no action.
16
+ *
17
+ * `forge shepherd <pr> --pull --json` prints a COMPACT, bounded "why it failed +
18
+ * what to fix" payload: per-failed-check log excerpts (matrix-deduped) plus the
19
+ * unresolved review-thread fix-list (CodeRabbit included), alongside the decision
20
+ * state. All the `gh pr checks` / `gh run view --log-failed` / GraphQL work is
21
+ * done IN CODE so an agent gets one payload instead of running those by hand. It
22
+ * still NEVER merges and NEVER resolves threads.
23
+ *
24
+ * State persists via GitHub PR comments/labels and git only.
25
+ *
26
+ * @module commands/shepherd
27
+ */
28
+
29
+ const { execFileSync } = require('node:child_process');
30
+
31
+ const { runShepherdPass } = require('../pr-shepherd');
32
+ const { gatherPrBundle } = require('../pr-bundle');
33
+ const { gatherPullSignal, renderPullSummary } = require('../pr-pull');
34
+ const { PrStateAdapter } = require('../adapters/pr-state-adapter');
35
+ const { validatePrStateAdapter } = require('../pr-state-validator');
36
+ const { gatherMonitorSnapshot } = require('../pr-monitor/gather');
37
+ const { pollEvents } = require('../pr-monitor/monitor');
38
+ const { watchLoop } = require('../pr-monitor/watch');
39
+ const monitorJournal = require('../pr-monitor/journal');
40
+ const { EVENT_TYPES: T } = require('../pr-monitor/events');
41
+
42
+ const DEFAULT_RERUN_BUDGET = 3;
43
+
44
+ const defaultGhRunner = (cmd, a) => execFileSync(cmd, a, { encoding: 'utf8', timeout: 30000 });
45
+
46
+ /**
47
+ * Resolve owner/repo and base branch for the shepherd pass.
48
+ *
49
+ * The base branch is read from the PR itself (`gh pr view <pr> --json
50
+ * baseRefName`) rather than the current checkout's default branch, so PRs
51
+ * targeting `release/*`/`develop` are evaluated against the correct branch.
52
+ * `owner`/`name` come from the repository the PR is queried in — that IS the
53
+ * base repository. `cwd` (the worktree root) is threaded through so divergence
54
+ * is computed against the right checkout.
55
+ *
56
+ * @param {object} deps
57
+ * @returns {Promise<{ pr: string, owner: string, repo: string, base: string, baseRef: string, cwd?: string }>}
58
+ */
59
+ async function defaultBuildContext({ pr, gh, git, projectRoot }) {
60
+ const prJson = gh('gh', ['pr', 'view', String(pr), '--json', 'baseRefName']);
61
+ const prInfo = JSON.parse(prJson || '{}');
62
+ const base = prInfo.baseRefName || 'master';
63
+
64
+ const repoJson = gh('gh', ['repo', 'view', '--json', 'owner,name']);
65
+ const repo = JSON.parse(repoJson || '{}');
66
+ const owner = repo.owner?.login || '';
67
+ const name = repo.name || '';
68
+
69
+ let baseRemote;
70
+ try {
71
+ baseRemote = git('git', ['remote']).split(/\s+/).filter(Boolean)[0] || 'origin';
72
+ } catch (_err) {
73
+ baseRemote = 'origin';
74
+ }
75
+
76
+ return {
77
+ pr: String(pr),
78
+ owner,
79
+ repo: name,
80
+ base,
81
+ baseRef: `${baseRemote}/${base}`,
82
+ ...(projectRoot ? { cwd: projectRoot } : {}),
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Detect whether the working tree is clean (precondition for --auto-rebase).
88
+ *
89
+ * @param {Function} git
90
+ * @returns {boolean}
91
+ */
92
+ function isWorkingTreeClean(git) {
93
+ try {
94
+ return git('git', ['status', '--porcelain']).trim().length === 0;
95
+ } catch (_err) {
96
+ return false;
97
+ }
98
+ }
99
+
100
+ // Render a single pass action for the human-readable monitor line. Strings pass
101
+ // through; everything else is JSON-encoded, but JSON.stringify can throw on
102
+ // circular refs or BigInt, so surface the reason inline rather than crash the pass.
103
+ function formatAction(action) {
104
+ if (typeof action === 'string') {
105
+ return action;
106
+ }
107
+ try {
108
+ return JSON.stringify(action);
109
+ } catch (err) {
110
+ return `[unprintable action: ${err.message}]`;
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Build the DEFAULT `check.failed` enrichment hook for the events pull surface.
116
+ *
117
+ * The monitor design specifies that newly-failed checks are enriched with their
118
+ * failure log excerpts before the journal append. `pollEvents` accepts an
119
+ * `enrich` hook but `handleEvents` must supply the default one, or a plain
120
+ * `forge shepherd events` call would emit bare `check.failed` events with no
121
+ * `data.excerpt` (only direct monitor callers could attach them).
122
+ *
123
+ * The hook is BEST-EFFORT: it fetches the compact pull signal ONCE (only when a
124
+ * pass actually produced a `check.failed`), maps excerpts by check name, and
125
+ * decorates matching records. Any failure to gather excerpts leaves the events
126
+ * intact rather than aborting the pass — enrichment must never block the journal.
127
+ *
128
+ * @param {object} pullCtx - ctx forwarded to `gatherPull` (owner/repo/base/adapter/runGh/self).
129
+ * @returns {(records: object[]) => Promise<void>}
130
+ */
131
+ function makeCheckFailureEnricher(pullCtx) {
132
+ const gatherPull = pullCtx.gatherPull || gatherPullSignal;
133
+ return async (records) => {
134
+ if (!Array.isArray(records) || !records.some((r) => r.type === T.CHECK_FAILED)) return;
135
+ let failures;
136
+ try {
137
+ const pull = await gatherPull(pullCtx);
138
+ failures = Array.isArray(pull?.failures) ? pull.failures : [];
139
+ } catch (err) {
140
+ // best-effort: never let enrichment abort the pass, but surface the reason
141
+ console.error(`[shepherd] check-failure enrichment skipped: ${err.message}`);
142
+ return;
143
+ }
144
+ const byName = new Map(failures.map((f) => [f.name, f]));
145
+ for (const r of records) {
146
+ if (r.type !== T.CHECK_FAILED) continue;
147
+ const f = byName.get(r.data?.name);
148
+ if (!f) continue;
149
+ if (f.excerpt) r.data.excerpt = f.excerpt;
150
+ if (f.jobUrl) r.data.jobUrl = f.jobUrl;
151
+ }
152
+ };
153
+ }
154
+
155
+ /** Parse `--since <seq>` from the raw arg list (default 0). */
156
+ function parseSince(args) {
157
+ const i = (args || []).indexOf('--since');
158
+ if (i >= 0 && args[i + 1] != null) return Number.parseInt(args[i + 1], 10) || 0;
159
+ return 0;
160
+ }
161
+
162
+ /**
163
+ * Build the shared monitor context — journal `dir`, bounded `gather`, and the
164
+ * default `check.failed` enricher — that BOTH the `events` pull surface and the
165
+ * `watch` streaming loop feed to the monitor core. Injected `dir`/`gather`/
166
+ * `enrich` (tests, programmatic callers) short-circuit the live gh build.
167
+ *
168
+ * @param {string|number} pr
169
+ * @param {string} projectRoot
170
+ * @param {object} deps
171
+ * @returns {Promise<{ dir?: string, gather?: Function, enrich?: Function, error?: string }>}
172
+ */
173
+ async function buildMonitorContext(pr, projectRoot, deps) {
174
+ let dir = deps.dir;
175
+ let gather = deps.gather;
176
+ // enrich decorates newly-failed checks with log excerpts before the journal
177
+ // append. The caller MUST supply the default (not just forward an injected
178
+ // one), or a plain `forge shepherd events`/`watch` emits bare check.failed events.
179
+ let enrich = deps.enrich;
180
+ if (!gather || !dir) {
181
+ const gh = deps.gh || defaultGhRunner;
182
+ const git = deps.git || gh;
183
+ const buildContext = deps.buildContext || defaultBuildContext;
184
+ const ctx = await buildContext({ pr, gh, git, projectRoot });
185
+ const adapter = deps.adapter || new PrStateAdapter({ gh, git });
186
+ const validation = validatePrStateAdapter(adapter);
187
+ if (!validation.valid) {
188
+ return { error: `Invalid pr-state adapter: ${validation.errors.join('; ')}` };
189
+ }
190
+ dir = dir || monitorJournal.journalDir({ root: projectRoot || process.cwd(), repo: ctx.repo, pr: ctx.pr });
191
+ gather = gather || (() => gatherMonitorSnapshot({ ...ctx, adapter, self: deps.self }));
192
+ enrich = enrich || makeCheckFailureEnricher({
193
+ ...ctx,
194
+ adapter,
195
+ self: deps.self,
196
+ runGh: (ghArgs) => gh('gh', ghArgs),
197
+ gatherPull: deps.gatherPull,
198
+ });
199
+ } else if (!enrich && deps.gatherPull) {
200
+ // Injected gather (tests / programmatic callers) still gets the default
201
+ // enrichment when a pull-signal source is supplied.
202
+ enrich = makeCheckFailureEnricher({
203
+ pr, adapter: deps.adapter, self: deps.self, runGh: deps.runGh, gatherPull: deps.gatherPull,
204
+ });
205
+ }
206
+ return { dir, gather, enrich };
207
+ }
208
+
209
+ /**
210
+ * `forge shepherd events <pr> --since <seq> [--json]` — the agent-agnostic PULL
211
+ * surface. Runs one bounded gather+diff (inline, unless a watcher owns the PR),
212
+ * appends new events to the per-PR journal, and prints every journaled event
213
+ * with `seq > since` as NDJSON, one per line, to stdout. Nothing under .claude.
214
+ *
215
+ * @param {string[]} args
216
+ * @param {string} projectRoot
217
+ * @param {object} [deps]
218
+ * @returns {Promise<object>}
219
+ */
220
+ async function handleEvents(args, projectRoot, deps = {}) {
221
+ const rawArgs = args || [];
222
+ const sinceIdx = rawArgs.indexOf('--since');
223
+ const pr = rawArgs.find((a, idx) => !String(a).startsWith('--') && a !== 'events' && idx !== sinceIdx + 1);
224
+ if (!pr) {
225
+ return { success: false, error: 'Usage: forge shepherd events <pr> --since <seq> [--json]' };
226
+ }
227
+ const since = parseSince(rawArgs);
228
+
229
+ const built = await buildMonitorContext(pr, projectRoot, deps);
230
+ if (built.error) return { success: false, error: built.error };
231
+ const { dir, gather, enrich } = built;
232
+
233
+ const poll = deps.pollEvents || pollEvents;
234
+ const result = await poll({ dir, gather, since, now: deps.now, watcherRunning: deps.watcherRunning, enrich });
235
+ // `output` is the agent-agnostic pull surface: NDJSON, one event per line. The
236
+ // registry CLI dispatch prints `result.output` (same contract as --pull/--bundle),
237
+ // so this handler does NOT write to stdout itself (that would double-print).
238
+ const output = result.events.map((e) => JSON.stringify(e)).join('\n');
239
+ return { success: true, events: result.events, since: result.since, output };
240
+ }
241
+
242
+ /**
243
+ * Wire an AbortController to SIGINT/SIGTERM so a long-running watch loop stops
244
+ * cleanly on Ctrl-C. Returns the signal plus a `cleanup` that detaches the
245
+ * one-shot handlers (always called in a finally so the loop leaves no listeners).
246
+ *
247
+ * @returns {{ signal: object, cleanup: () => void }}
248
+ */
249
+ function wireSignals() {
250
+ const controller = new AbortController();
251
+ const onSignal = () => controller.abort();
252
+ process.once('SIGINT', onSignal);
253
+ process.once('SIGTERM', onSignal);
254
+ const cleanup = () => {
255
+ process.off('SIGINT', onSignal);
256
+ process.off('SIGTERM', onSignal);
257
+ };
258
+ return { signal: controller.signal, cleanup };
259
+ }
260
+
261
+ /**
262
+ * `forge shepherd watch <pr>` — the agent-agnostic PUSH surface. A long-running
263
+ * loop that every ~60s (jittered) runs ONE bounded monitor pass and STREAMS each
264
+ * new event as an NDJSON line to stdout, self-stopping on `pr.merged`/`pr.closed`.
265
+ * The loop streams live via the default stdout emit, so this handler returns NO
266
+ * `output` field (returning one would double-print). SIGINT/SIGTERM stop it clean.
267
+ *
268
+ * @param {string[]} args
269
+ * @param {string} projectRoot
270
+ * @param {object} [deps]
271
+ * @returns {Promise<object>}
272
+ */
273
+ async function handleWatch(args, projectRoot, deps = {}) {
274
+ const rawArgs = args || [];
275
+ const pr = rawArgs.find((a) => !String(a).startsWith('--') && a !== 'watch');
276
+ if (!pr) {
277
+ return { success: false, error: 'Usage: forge shepherd watch <pr>' };
278
+ }
279
+
280
+ const built = await buildMonitorContext(pr, projectRoot, deps);
281
+ if (built.error) return { success: false, error: built.error };
282
+ const { dir, gather, enrich } = built;
283
+
284
+ const loop = deps.watchLoop || watchLoop;
285
+ // Injected signal (tests) suppresses real process handlers; otherwise wire them.
286
+ const wired = deps.signal ? { signal: deps.signal, cleanup: () => {} } : wireSignals();
287
+ let result;
288
+ try {
289
+ result = await loop({
290
+ dir,
291
+ gather,
292
+ enrich,
293
+ now: deps.now,
294
+ emit: deps.emit,
295
+ sleep: deps.sleep,
296
+ rng: deps.rng,
297
+ intervalMs: deps.intervalMs,
298
+ maxPasses: deps.maxPasses,
299
+ lockOpts: deps.lockOpts,
300
+ signal: wired.signal,
301
+ watcherRunning: deps.watcherRunning,
302
+ writePid: deps.writePid,
303
+ removePid: deps.removePid,
304
+ });
305
+ } finally {
306
+ wired.cleanup();
307
+ }
308
+
309
+ return {
310
+ success: true,
311
+ started: result.started,
312
+ passes: result.passes,
313
+ stopped: result.stopped,
314
+ ...(result.reason ? { reason: result.reason } : {}),
315
+ };
316
+ }
317
+
318
+ /**
319
+ * Command handler.
320
+ *
321
+ * @param {string[]} args - Positional + flag args (first positional is the PR).
322
+ * @param {object} _flags - Parsed flags (unused; flags are read from args).
323
+ * @param {string} projectRoot - Project root.
324
+ * @param {object} [deps] - Injected dependencies for testing.
325
+ * @returns {Promise<object>} result envelope.
326
+ */
327
+ async function handler(args, _flags, projectRoot, deps = {}) {
328
+ const positional = (args || []).filter((a) => !String(a).startsWith('--'));
329
+ const flags = new Set((args || []).filter((a) => String(a).startsWith('--')));
330
+
331
+ // Subcommand routing: `events` is the monitor pull surface (its own arg shape);
332
+ // `watch` is the constant monitor push surface (long-running stream).
333
+ if (positional[0] === 'events') {
334
+ return handleEvents(args, projectRoot, deps);
335
+ }
336
+ if (positional[0] === 'watch') {
337
+ return handleWatch(args, projectRoot, deps);
338
+ }
339
+
340
+ const pr = positional[0];
341
+
342
+ if (!pr) {
343
+ return { success: false, error: 'Usage: forge shepherd <pr> [--auto-rebase] [--bundle --json] [--pull --json]' };
344
+ }
345
+
346
+ const gh = deps.gh || ((cmd, a) => execFileSync(cmd, a, { encoding: 'utf8', timeout: 30000 }));
347
+ const git = deps.git || gh;
348
+ const buildContext = deps.buildContext || defaultBuildContext;
349
+ const runPass = deps.runPass || runShepherdPass;
350
+ const gatherBundle = deps.gatherBundle || gatherPrBundle;
351
+ const gatherPull = deps.gatherPull || gatherPullSignal;
352
+
353
+ const autoRebase = flags.has('--auto-rebase');
354
+ const wantBundle = flags.has('--bundle');
355
+ const wantPull = flags.has('--pull');
356
+ const wantJson = flags.has('--json');
357
+
358
+ const ctx = await buildContext({ pr, gh, git, projectRoot });
359
+
360
+ const adapter = deps.adapter || new PrStateAdapter({ gh, git });
361
+ const validation = validatePrStateAdapter(adapter);
362
+ if (!validation.valid) {
363
+ return { success: false, error: `Invalid pr-state adapter: ${validation.errors.join('; ')}` };
364
+ }
365
+
366
+ // --pull: gather the COMPACT "why it failed + what to fix" payload (failed-check
367
+ // log excerpts, matrix-deduped, plus the review-thread fix-list). STRICTLY
368
+ // READ-ONLY — it computes the decision state via a dry-run pass but takes NO
369
+ // action (no Tier-A rerun, no rebase); acting belongs to plain `forge shepherd`.
370
+ // The `gh` calls to fetch logs run through an injected runner so this stays testable.
371
+ if (wantPull) {
372
+ const runGh = (ghArgs) => gh('gh', ghArgs);
373
+ const pull = await gatherPull({ ...ctx, adapter, runGh, runPass, self: deps.self });
374
+ // `output` is what the registry CLI dispatch (bin/forge.js) actually PRINTS —
375
+ // returning only `pull` silently dropped the whole payload on that path (it
376
+ // prints `result.output`, nothing else). `--json` → machine payload; default
377
+ // → the compact human WHY+fix summary. `pull` is kept for the legacy
378
+ // bin/forge-cmd.js path and for programmatic callers/tests.
379
+ const output = wantJson ? JSON.stringify(pull, null, 2) : renderPullSummary(pull);
380
+ return { success: true, pull, output };
381
+ }
382
+
383
+ // --bundle: gather the COMPLETE read-only PR-state bundle the monitor will
384
+ // hand to a fixer-agent, and return it for the CLI to print as JSON. This is
385
+ // the gather half only — it decides nothing and takes no action.
386
+ if (wantBundle) {
387
+ const bundle = await gatherBundle({ ...ctx, adapter });
388
+ // Same rationale as --pull: the registry dispatch prints `output`. The bundle
389
+ // is a machine payload, so it is always emitted as JSON.
390
+ return { success: true, bundle, output: JSON.stringify(bundle, null, 2) };
391
+ }
392
+
393
+ const result = await runPass({
394
+ ...ctx,
395
+ adapter,
396
+ autoRebase,
397
+ cleanTree: autoRebase ? isWorkingTreeClean(git) : false,
398
+ rerunBudget: deps.rerunBudget || DEFAULT_RERUN_BUDGET,
399
+ rerunsUsed: deps.rerunsUsed || 0,
400
+ });
401
+
402
+ // Surface the pass outcome so the monitor is legible when run interactively or
403
+ // tailed by a scheduler (the bounded state machine is otherwise silent).
404
+ const passActions = Array.isArray(result.actions) ? result.actions : [];
405
+ const reasonSuffix = result.reason ? ` — ${result.reason}` : '';
406
+ process.stdout.write(`Shepherd pass — PR #${pr}: ${result.state}${reasonSuffix}\n`);
407
+ for (const action of passActions) {
408
+ process.stdout.write(` • ${formatAction(action)}\n`);
409
+ }
410
+ if (!passActions.length) {
411
+ process.stdout.write(' • no actions this pass\n');
412
+ }
413
+
414
+ return {
415
+ success: result.state !== 'HARD_STOP',
416
+ state: result.state,
417
+ reason: result.reason,
418
+ actions: result.actions || [],
419
+ ...(result.authClass ? { authClass: result.authClass } : {}),
420
+ ...(result.retryAfter ? { retryAfter: result.retryAfter } : {}),
421
+ };
422
+ }
423
+
424
+ module.exports = {
425
+ name: 'shepherd',
426
+ description: 'Run one bounded monitor pass over a PR (rerun flaky checks, escalate, hand off — never merges)',
427
+ usage: 'Usage: forge shepherd <pr> [--auto-rebase] [--bundle --json] [--pull --json] | forge shepherd events <pr> --since <seq> [--json] | forge shepherd watch <pr>',
428
+ handler,
429
+ handleEvents,
430
+ handleWatch,
431
+ buildMonitorContext,
432
+ makeCheckFailureEnricher,
433
+ parseSince,
434
+ defaultBuildContext,
435
+ isWorkingTreeClean,
436
+ };
@@ -12,6 +12,8 @@ const { execFileSync } = require('node:child_process');
12
12
  const fs = require('node:fs');
13
13
  const path = require('node:path');
14
14
 
15
+ const { startPrWatcherDetached } = require('../pr-monitor/watch-lifecycle');
16
+
15
17
  function getExecOptions() {
16
18
  return { encoding: 'utf8', cwd: process.cwd(), timeout: 120000 };
17
19
  }
@@ -482,8 +484,26 @@ async function createPR(options) { // NOSONAR S3776
482
484
  }
483
485
  }
484
486
 
487
+ /**
488
+ * Best-effort, non-blocking auto-start of the constant PR monitor once a real PR
489
+ * exists. Skipped on a dry run or when no PR number is known. MUST NEVER fail
490
+ * ship: `startWatcher` (startPrWatcherDetached) already never throws, and this
491
+ * guard keeps even a surprise error from surfacing to the ship caller.
492
+ *
493
+ * @param {{ dryRun: boolean, prNumber?: string|number, startWatcher: Function }} params
494
+ * @returns {{ started: boolean, reason?: string }}
495
+ */
496
+ function maybeStartPrWatcher({ dryRun, prNumber, startWatcher }) {
497
+ if (dryRun || !prNumber) return { started: false, reason: 'skipped' };
498
+ try {
499
+ return startWatcher({ prNumber, cwd: process.cwd() });
500
+ } catch (err) {
501
+ return { started: false, reason: err.message };
502
+ }
503
+ }
504
+
485
505
  async function executeShip(options) {
486
- const { featureSlug, title, dryRun = false } = options || {};
506
+ const { featureSlug, title, dryRun = false, startWatcher = startPrWatcherDetached } = options || {};
487
507
 
488
508
  // Validate feature slug
489
509
  if (!featureSlug || typeof featureSlug !== 'string' || featureSlug.trim() === '') {
@@ -535,6 +555,7 @@ async function executeShip(options) {
535
555
  });
536
556
  const result = await createPR({ title, body: prBody, dryRun });
537
557
  if (!result.success) return result;
558
+ maybeStartPrWatcher({ dryRun, prNumber: result.prNumber, startWatcher });
538
559
  return {
539
560
  success: true,
540
561
  prUrl: result.prUrl,
@@ -567,6 +588,7 @@ module.exports = {
567
588
  output: lines.join('\n'),
568
589
  };
569
590
  },
591
+ maybeStartPrWatcher,
570
592
  extractKeyDecisions,
571
593
  extractTestScenarios,
572
594
  getTestCoverage,
@@ -1,5 +1,5 @@
1
1
  'use strict';
2
2
 
3
- const { makeAliasCommand } = require('./_issue');
3
+ const { createIssueSubcommand } = require('./_issue');
4
4
 
5
- module.exports = makeAliasCommand('show');
5
+ module.exports = createIssueSubcommand('show');
@@ -0,0 +1,192 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Forge Stage Command (f61601ab)
5
+ *
6
+ * Records the REAL workflow phase of an issue into the kernel_stage_runs table so
7
+ * the phase is queryable — instead of being guessed from status+claim (a
8
+ * claimed-open issue with a merged PR would otherwise still show "dev"). Mirrors
9
+ * the worktree-linkage registry: direct kernel writes, idempotent per
10
+ * (issue_id, stage).
11
+ *
12
+ * forge stage <issue-id> <stage> --start Open a stage (status=active)
13
+ * forge stage <issue-id> <stage> --complete Close a stage (status=done)
14
+ * forge stage <issue-id> --current Print the current stage
15
+ * forge stage <issue-id> --list Print the full stage history
16
+ *
17
+ * `<stage>` is one of the canonical workflow stages (plan|dev|validate|ship|review|verify).
18
+ * Read the current stage back on `forge show <id>` (data.current_stage).
19
+ *
20
+ * @module commands/stage
21
+ */
22
+
23
+ const { STAGE_IDS, normalizeStageId } = require('../workflow/stages');
24
+ const { resolveIssueId } = require('../kernel/issue-id-resolver');
25
+
26
+ const STAGE_FLAGS = ['--start', '--complete', '--list', '--current', '--json'];
27
+ const VALUE_FLAGS = ['--substage'];
28
+ const USAGE = 'Usage: forge stage <issue-id> <stage> --start|--complete (reads: --current | --list)';
29
+
30
+ /**
31
+ * Parse the stage command's own flags out of the raw args (the global flag parser
32
+ * only recognizes an allowlist, so — like `worktree` — this command extracts its
33
+ * own). Returns { positional, opts } or { error }.
34
+ */
35
+ function parseStageArgs(args = []) {
36
+ const positional = [];
37
+ const opts = { start: false, complete: false, list: false, current: false, json: false, substage: null };
38
+
39
+ for (let i = 0; i < args.length; i += 1) {
40
+ const arg = args[i];
41
+ if (typeof arg !== 'string') continue;
42
+
43
+ const valueFlag = VALUE_FLAGS.find(flag => arg === flag || arg.startsWith(`${flag}=`));
44
+ if (valueFlag) {
45
+ let value;
46
+ if (arg.startsWith(`${valueFlag}=`)) {
47
+ value = arg.slice(`${valueFlag}=`.length);
48
+ } else {
49
+ value = args[i + 1];
50
+ i += 1;
51
+ }
52
+ if (!value || value.startsWith('--')) {
53
+ return { error: `Missing value for ${valueFlag}. ${USAGE}` };
54
+ }
55
+ opts.substage = value;
56
+ continue;
57
+ }
58
+
59
+ if (STAGE_FLAGS.includes(arg)) {
60
+ opts[arg.slice(2)] = true;
61
+ continue;
62
+ }
63
+
64
+ if (arg.startsWith('--')) {
65
+ return { error: `Unknown flag: ${arg}. ${USAGE}` };
66
+ }
67
+
68
+ positional.push(arg);
69
+ }
70
+
71
+ return { positional, opts };
72
+ }
73
+
74
+ async function resolveDriver(projectRoot, opts) {
75
+ if (opts._kernelDriver) return opts._kernelDriver;
76
+ const { buildMigratedKernelIssueDeps } = require('../kernel/cli-broker-factory');
77
+ return (await buildMigratedKernelIssueDeps({ projectRoot })).kernelDriver;
78
+ }
79
+
80
+ function render(opts, data, humanLines) {
81
+ if (opts.json || process.env.FORGE_JSON === '1') {
82
+ return { success: true, output: JSON.stringify(data, null, 2) };
83
+ }
84
+ return { success: true, output: humanLines.join('\n'), ...data };
85
+ }
86
+
87
+ module.exports = {
88
+ name: 'stage',
89
+ description: 'Record or read an issue\'s real workflow stage (stage_runs)',
90
+ usage: USAGE,
91
+ flags: {
92
+ '--start': 'Open a stage run (status=active)',
93
+ '--complete': 'Complete a stage run (status=done)',
94
+ '--substage': 'Optional substage label to record',
95
+ '--current': 'Print the current stage (latest active, else latest completed)',
96
+ '--list': 'Print the full stage-run history for the issue',
97
+ '--json': 'Emit machine-readable JSON',
98
+ },
99
+
100
+ /**
101
+ * @param {string[]} args - Positional + flag args after `stage`
102
+ * @param {object} _flags - Global parsed flags (unused; this command parses its own)
103
+ * @param {string} projectRoot - Project root path
104
+ * @param {object} [opts] - DI options (may inject `_kernelDriver`)
105
+ * @returns {Promise<object>}
106
+ */
107
+ handler: async (args, _flags, projectRoot, opts = {}) => {
108
+ const parsed = parseStageArgs(args);
109
+ if (parsed.error) {
110
+ return { success: false, error: parsed.error };
111
+ }
112
+ const { positional, opts: stageOpts } = parsed;
113
+
114
+ const issueRef = positional[0];
115
+ if (!issueRef) {
116
+ return { success: false, error: `Missing issue id. ${USAGE}` };
117
+ }
118
+
119
+ let driver;
120
+ try {
121
+ driver = await resolveDriver(projectRoot, opts);
122
+ } catch (error) {
123
+ return { success: false, error: `Kernel unavailable: ${error.message}` };
124
+ }
125
+
126
+ // Resolve prefixes / display handles to a full issue id (same resolver the
127
+ // issue commands use), so `forge stage 1a2b3c4d dev --start` works.
128
+ const resolution = await resolveIssueId(
129
+ issueRef,
130
+ (needle, limit) => driver.findIssueIdsByPrefix(needle, limit, {}, {}),
131
+ );
132
+ if (resolution.error) {
133
+ return { success: false, error: resolution.error };
134
+ }
135
+ const issueId = resolution.id;
136
+
137
+ // Read paths -----------------------------------------------------------
138
+ if (stageOpts.current) {
139
+ const current = driver.getCurrentStage({ issue_id: issueId }, {});
140
+ const data = {
141
+ issue_id: issueId,
142
+ current_stage: current ? current.stage : null,
143
+ current_stage_status: current ? current.status : null,
144
+ };
145
+ const human = current
146
+ ? `${issueId}: ${current.stage} (${current.status})`
147
+ : `${issueId}: no stage recorded`;
148
+ return render(stageOpts, data, [human]);
149
+ }
150
+
151
+ if (stageOpts.list) {
152
+ const runs = driver.listStageRuns({ issue_id: issueId }, {});
153
+ const data = { issue_id: issueId, stage_runs: runs };
154
+ const human = runs.length === 0
155
+ ? [`${issueId}: no stage runs`]
156
+ : runs.map(run => ` ${run.stage.padEnd(9)} ${run.status.padEnd(7)} started ${run.started_at}${run.completed_at ? ` → completed ${run.completed_at}` : ''}`);
157
+ return render(stageOpts, data, [`${issueId} stage history:`, ...human]);
158
+ }
159
+
160
+ // Write paths ----------------------------------------------------------
161
+ const stage = normalizeStageId(positional[1]);
162
+ if (!stage) {
163
+ return {
164
+ success: false,
165
+ error: `Invalid or missing stage "${positional[1] ?? ''}". Expected one of: ${STAGE_IDS.join(', ')}. ${USAGE}`,
166
+ };
167
+ }
168
+
169
+ if (stageOpts.start && stageOpts.complete) {
170
+ return { success: false, error: `Pass only one of --start or --complete. ${USAGE}` };
171
+ }
172
+ const action = stageOpts.complete ? 'complete' : 'start';
173
+
174
+ let row;
175
+ try {
176
+ row = driver.recordStageRun(
177
+ { issue_id: issueId, stage, action, substage: stageOpts.substage || null },
178
+ {},
179
+ );
180
+ } catch (error) {
181
+ // A FOREIGN KEY failure means the issue id does not exist in the kernel.
182
+ if (/FOREIGN KEY/i.test(String(error.message))) {
183
+ return { success: false, error: `Issue ${issueId} not found in the kernel.` };
184
+ }
185
+ return { success: false, error: `Failed to record stage: ${error.message}` };
186
+ }
187
+
188
+ const data = { issue_id: issueId, action, stage_run: row };
189
+ const verb = action === 'complete' ? 'completed' : 'started';
190
+ return render(stageOpts, data, [`${issueId}: ${verb} stage ${stage} (${row.status})`]);
191
+ },
192
+ };
@@ -0,0 +1,5 @@
1
+ 'use strict';
2
+
3
+ const { createIssueSubcommand } = require('./_issue');
4
+
5
+ module.exports = createIssueSubcommand('stale');