forge-workflow 0.0.10 → 0.1.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (468) hide show
  1. package/.claude/rules/{greptile-review-process.md → review-process.md} +56 -41
  2. package/.claude/scripts/{greptile-resolve.sh → review-resolve.sh} +13 -3
  3. package/.cursor/rules/permissions-guidance.mdc +2 -2
  4. package/.forge/hooks/check-tdd.js +82 -5
  5. package/.forge/hooks/forge-native-hook.js +431 -0
  6. package/.forge/protected-paths.yaml +157 -0
  7. package/AGENTS.md +151 -61
  8. package/CHANGELOG.md +709 -0
  9. package/CLAUDE.md +9 -118
  10. package/QUICKSTART.md +175 -0
  11. package/README.md +275 -365
  12. package/bin/forge-cmd.js +120 -9
  13. package/bin/forge-preflight.js +26 -5
  14. package/bin/forge.js +532 -489
  15. package/docs/INDEX.md +93 -0
  16. package/docs/PROJECT_DESIGN.md +685 -0
  17. package/docs/architecture/index.md +66 -0
  18. package/docs/architecture/notes/README.md +35 -0
  19. package/docs/architecture/subsystems/README.md +46 -0
  20. package/docs/forge/TOOLCHAIN.md +670 -0
  21. package/docs/forge/VALIDATION.md +82 -0
  22. package/docs/{AGENT_INSTALL_PROMPT.md → guides/AGENT_INSTALL_PROMPT.md} +3 -3
  23. package/docs/guides/BEADS_GITHUB_SYNC.md +32 -0
  24. package/docs/{ENHANCED_ONBOARDING.md → guides/ENHANCED_ONBOARDING.md} +16 -12
  25. package/docs/guides/GREPTILE_SETUP.md +46 -0
  26. package/docs/guides/MANUAL_REVIEW_GUIDE.md +58 -0
  27. package/docs/guides/MIGRATION.md +56 -0
  28. package/docs/guides/SETUP.md +121 -0
  29. package/docs/guides/SUPPORT.md +190 -0
  30. package/docs/guides/WORKFLOW_TEMPLATES.md +74 -0
  31. package/docs/guides/memory-backends.md +183 -0
  32. package/docs/reference/ADAPTERS.md +128 -0
  33. package/docs/reference/AGENT_SKILL_PARITY.md +175 -0
  34. package/docs/reference/COMMANDS.md +214 -0
  35. package/docs/reference/DECISION_DRIFT_GUARDS.md +97 -0
  36. package/docs/{EXAMPLES.md → reference/EXAMPLES.md} +7 -5
  37. package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +135 -0
  38. package/docs/reference/HERMES_INTEGRATION.md +118 -0
  39. package/docs/reference/INSIGHTS_RECAP.md +63 -0
  40. package/docs/reference/INSTALL.md +164 -0
  41. package/docs/reference/KERNEL_TAXONOMY_VALIDATION.md +161 -0
  42. package/docs/reference/PROTECTED_PATH_MANIFEST.md +25 -0
  43. package/docs/reference/RELEASE.md +68 -0
  44. package/docs/reference/RESEARCH_TEMPLATE.md +292 -0
  45. package/docs/{ROADMAP.md → reference/ROADMAP.md} +12 -9
  46. package/docs/reference/SKILLS.md +35 -0
  47. package/docs/reference/STATUS_BOARD.md +80 -0
  48. package/docs/reference/TEMPLATES.md +106 -0
  49. package/docs/{TOOLCHAIN.md → reference/TOOLCHAIN.md} +62 -47
  50. package/docs/reference/VALIDATION.md +82 -0
  51. package/docs/reference/agent-permissions.md +169 -0
  52. package/docs/reference/beads-to-kernel-migration-ux.md +61 -0
  53. package/docs/reference/control-plane-guarantees.md +125 -0
  54. package/docs/reference/dependency-chain.md +331 -0
  55. package/docs/reference/forge-kernel-issue-command-contract.md +161 -0
  56. package/docs/reference/forge-kernel-schema.md +72 -0
  57. package/docs/reference/kernel-conflict-evaluators.md +27 -0
  58. package/docs/reference/patch-md-format.md +77 -0
  59. package/docs/reference/protected-state-surfaces.md +59 -0
  60. package/docs/reference/shepherd.md +155 -0
  61. package/docs/reference/superpowers-analysis.md +320 -0
  62. package/docs/reference/superpowers-integration-options.md +404 -0
  63. package/docs/reference/test-environment.md +519 -0
  64. package/docs/reference/upgrade-safety.md +59 -0
  65. package/lefthook.yml +18 -0
  66. package/lib/activation/ensure-forge-home.js +135 -0
  67. package/lib/adapter-cli.js +307 -0
  68. package/lib/adapters/beads-issue-adapter.js +127 -0
  69. package/lib/adapters/beads-kernel-compat.js +1109 -0
  70. package/lib/adapters/greptile-review-adapter.js +141 -0
  71. package/lib/adapters/kernel-issue-adapter.js +101 -0
  72. package/lib/adapters/pr-state-adapter.js +484 -0
  73. package/lib/adoption-profiles.js +139 -0
  74. package/lib/agents/README.md +2 -6
  75. package/lib/agents/claude.plugin.json +3 -8
  76. package/lib/agents/codex.plugin.json +9 -1
  77. package/lib/agents/cursor.plugin.json +2 -6
  78. package/lib/agents/hermes.plugin.json +22 -0
  79. package/lib/agents-config.js +39 -1236
  80. package/lib/audit-evidence.js +282 -0
  81. package/lib/beads-detect.js +60 -0
  82. package/lib/beads-nudge.js +91 -0
  83. package/lib/beads-setup.js +121 -0
  84. package/lib/beads-sync-scaffold.js +25 -101
  85. package/lib/codex-skills.js +51 -1
  86. package/lib/commands/_aliases.js +248 -0
  87. package/lib/commands/_issue.js +780 -77
  88. package/lib/commands/_manifest.js +93 -0
  89. package/lib/commands/_registry.js +99 -34
  90. package/lib/commands/_resolve-command-opts.js +230 -0
  91. package/lib/commands/_serve-security.js +270 -0
  92. package/lib/commands/adapter.js +12 -0
  93. package/lib/commands/add.js +118 -0
  94. package/lib/commands/audit.js +70 -0
  95. package/lib/commands/blocked.js +5 -0
  96. package/lib/commands/board.js +64 -0
  97. package/lib/commands/claim.js +21 -2
  98. package/lib/commands/claims.js +7 -0
  99. package/lib/commands/clean.js +485 -75
  100. package/lib/commands/close.js +2 -2
  101. package/lib/commands/comment.js +5 -0
  102. package/lib/commands/control.js +148 -0
  103. package/lib/commands/create.js +2 -2
  104. package/lib/commands/dev.js +185 -7
  105. package/lib/commands/doc-gate.js +336 -0
  106. package/lib/commands/doctor.js +156 -0
  107. package/lib/commands/explain.js +15 -0
  108. package/lib/commands/export.js +237 -0
  109. package/lib/commands/gate.js +209 -0
  110. package/lib/commands/hooks.js +377 -0
  111. package/lib/commands/inbox.js +118 -0
  112. package/lib/commands/init.js +604 -0
  113. package/lib/commands/insights.js +79 -0
  114. package/lib/commands/issue.js +12 -1
  115. package/lib/commands/issues.js +17 -0
  116. package/lib/commands/lint.js +5 -0
  117. package/lib/commands/list.js +2 -2
  118. package/lib/commands/memory.js +81 -0
  119. package/lib/commands/merge.js +312 -0
  120. package/lib/commands/migrate.js +362 -0
  121. package/lib/commands/new.js +12 -0
  122. package/lib/commands/options.js +241 -0
  123. package/lib/commands/orient.js +13 -0
  124. package/lib/commands/orphans.js +5 -0
  125. package/lib/commands/patch.js +67 -0
  126. package/lib/commands/plan.js +481 -29
  127. package/lib/commands/pr.js +88 -0
  128. package/lib/commands/preflight.js +211 -0
  129. package/lib/commands/prime.js +13 -0
  130. package/lib/commands/push.js +135 -2
  131. package/lib/commands/ready.js +2 -2
  132. package/lib/commands/recall.js +171 -0
  133. package/lib/commands/recap.js +75 -0
  134. package/lib/commands/recommend.js +0 -1
  135. package/lib/commands/release.js +104 -0
  136. package/lib/commands/remember.js +140 -0
  137. package/lib/commands/role.js +99 -0
  138. package/lib/commands/serve.js +581 -0
  139. package/lib/commands/setup.js +900 -971
  140. package/lib/commands/shepherd.js +501 -0
  141. package/lib/commands/ship.js +59 -1
  142. package/lib/commands/show.js +2 -2
  143. package/lib/commands/stage.js +192 -0
  144. package/lib/commands/stale.js +5 -0
  145. package/lib/commands/status.js +158 -21
  146. package/lib/commands/sync.js +34 -46
  147. package/lib/commands/team.js +4 -1
  148. package/lib/commands/test.js +43 -27
  149. package/lib/commands/update.js +2 -2
  150. package/lib/commands/upgrade.js +47 -0
  151. package/lib/commands/validate.js +43 -18
  152. package/lib/commands/worktree.js +362 -99
  153. package/lib/config-writer.js +202 -0
  154. package/lib/control-plane.js +236 -0
  155. package/lib/core/runtime-graph.js +977 -0
  156. package/lib/dep-guard/keyword-ripple.js +2 -2
  157. package/lib/deprecated-sync-cleanup.js +362 -0
  158. package/lib/detect-agent.js +2 -28
  159. package/lib/detect-worktree.js +35 -9
  160. package/lib/doc-gate/declaration.js +177 -0
  161. package/lib/doc-gate/detect.js +289 -0
  162. package/lib/doc-gate/gate.js +375 -0
  163. package/lib/doc-gate/okf-config.js +128 -0
  164. package/lib/doc-gate/okf.js +429 -0
  165. package/lib/docs-command.js +1161 -6
  166. package/lib/forge-issues.js +382 -11
  167. package/lib/forge-lock.js +262 -0
  168. package/lib/gate-events.js +192 -0
  169. package/lib/global-flags.js +104 -0
  170. package/lib/greptile-match.js +7 -63
  171. package/lib/grounding/context-events.js +230 -0
  172. package/lib/grounding/read-first.js +112 -0
  173. package/lib/harness-capability-matrix.js +380 -0
  174. package/lib/hook-global-installer.js +347 -0
  175. package/lib/hook-renderer.js +541 -0
  176. package/lib/inbox.js +391 -0
  177. package/lib/insights.js +397 -0
  178. package/lib/issue-adapter.js +156 -0
  179. package/lib/issue-backend.js +145 -0
  180. package/lib/issue-render.js +220 -0
  181. package/lib/kernel/backing-issue.js +311 -0
  182. package/lib/kernel/broker.js +1218 -0
  183. package/lib/kernel/cli-broker-factory.js +130 -0
  184. package/lib/kernel/conflict-signal.js +82 -0
  185. package/lib/kernel/evaluators.js +195 -0
  186. package/lib/kernel/fs-class.js +495 -0
  187. package/lib/kernel/issue-command-contract.js +559 -0
  188. package/lib/kernel/issue-id-resolver.js +186 -0
  189. package/lib/kernel/lease-enforcer.js +158 -0
  190. package/lib/kernel/migrations.js +333 -0
  191. package/lib/kernel/owned-kernel.js +43 -0
  192. package/lib/kernel/planning-buckets-schema.js +109 -0
  193. package/lib/kernel/projection-jsonl-writer.js +450 -0
  194. package/lib/kernel/readiness-model.js +329 -0
  195. package/lib/kernel/schema.js +356 -0
  196. package/lib/kernel/sqlite-driver.js +2540 -0
  197. package/lib/kernel/taxonomy-validator.js +394 -0
  198. package/lib/lefthook-check.js +3 -2
  199. package/lib/lefthook-wiring.js +413 -0
  200. package/lib/mcp-config-renderer.js +288 -0
  201. package/lib/memory/graphiti-mcp.js +106 -0
  202. package/lib/memory/router.js +387 -0
  203. package/lib/memory/typed-api.js +102 -0
  204. package/lib/memory-digest.js +195 -0
  205. package/lib/merge-rules.js +395 -0
  206. package/lib/migrate-dry-run.js +466 -0
  207. package/lib/orientation.js +863 -0
  208. package/lib/package-manager-remediation.js +103 -0
  209. package/lib/package-root.js +381 -0
  210. package/lib/patch-intent.js +890 -0
  211. package/lib/plugin-catalog.js +3 -4
  212. package/lib/plugin-manager.js +0 -5
  213. package/lib/pr-bundle.js +186 -0
  214. package/lib/pr-monitor/auto-actions.js +175 -0
  215. package/lib/pr-monitor/differ.js +195 -0
  216. package/lib/pr-monitor/digest.js +206 -0
  217. package/lib/pr-monitor/events.js +0 -0
  218. package/lib/pr-monitor/gather.js +124 -0
  219. package/lib/pr-monitor/journal.js +299 -0
  220. package/lib/pr-monitor/monitor.js +146 -0
  221. package/lib/pr-monitor/render-sticky.js +192 -0
  222. package/lib/pr-monitor/upsert-sticky.js +169 -0
  223. package/lib/pr-monitor/watch-lifecycle.js +95 -0
  224. package/lib/pr-monitor/watch.js +247 -0
  225. package/lib/pr-pull.js +1314 -0
  226. package/lib/pr-shepherd.js +494 -0
  227. package/lib/pr-state-validator.js +59 -0
  228. package/lib/preflight/gates.js +237 -0
  229. package/lib/preflight/runner.js +116 -0
  230. package/lib/project-discovery.js +0 -53
  231. package/lib/project-memory.js +99 -497
  232. package/lib/protected-path-manifest.js +281 -0
  233. package/lib/protected-state-surfaces.js +387 -0
  234. package/lib/release-readiness.js +2105 -0
  235. package/lib/reset.js +59 -45
  236. package/lib/review-adapter.js +68 -0
  237. package/lib/rules-sync.js +260 -0
  238. package/lib/runtime-health.js +241 -20
  239. package/lib/safety-config-renderer.js +268 -0
  240. package/lib/setup-action-log.js +1 -7
  241. package/lib/setup.js +27 -65
  242. package/lib/shell-utils.js +76 -6
  243. package/lib/skills-sync.js +330 -0
  244. package/lib/smart-status/scoring.js +17 -3
  245. package/lib/status/beads-snapshot.js +45 -2
  246. package/lib/status/presenter.js +169 -18
  247. package/lib/status/snapshot.js +186 -0
  248. package/lib/sync-backend.js +202 -0
  249. package/lib/untrusted-content.js +52 -0
  250. package/lib/upgrade-safety.js +251 -0
  251. package/lib/workflow/enforce-stage.js +351 -45
  252. package/lib/workflow/stage-transition.js +115 -0
  253. package/lib/workflow/stages.js +30 -6
  254. package/lib/workflow/state-manager.js +11 -22
  255. package/lib/workflow/state.js +23 -1
  256. package/lib/workflow-profiles.js +17 -5
  257. package/package.json +37 -35
  258. package/rules/documentation.md +19 -0
  259. package/rules/kernel-tracking.md +26 -0
  260. package/rules/security.md +22 -0
  261. package/rules/tdd.md +20 -0
  262. package/rules/workflow.md +27 -0
  263. package/scripts/auto-backing-issue.js +47 -0
  264. package/scripts/beads-context.sh +81 -57
  265. package/scripts/beads-upgrade-smoke.sh +24 -3
  266. package/scripts/bootstrap-windows-tools.sh +78 -0
  267. package/scripts/branch-protection.js +2 -3
  268. package/scripts/check-agents.js +34 -137
  269. package/scripts/commitlint.js +3 -1
  270. package/scripts/conflict-detect.sh +3 -0
  271. package/scripts/dep-guard.sh +22 -3
  272. package/scripts/file-index.sh +3 -0
  273. package/scripts/forge-team/lib/claim.sh +34 -18
  274. package/scripts/forge-team/lib/dashboard.sh +61 -86
  275. package/scripts/forge-team/lib/epic.sh +99 -263
  276. package/scripts/forge-team/lib/hooks.sh +26 -28
  277. package/scripts/forge-team/lib/identity.sh +4 -4
  278. package/scripts/forge-team/lib/sync-github.sh +49 -84
  279. package/scripts/forge-team/lib/verify.sh +93 -83
  280. package/scripts/forge-team/lib/workload.sh +41 -65
  281. package/scripts/forge-team/tests/claim.test.sh +25 -19
  282. package/scripts/forge-team/tests/dashboard.test.sh +31 -46
  283. package/scripts/forge-team/tests/epic.test.sh +52 -71
  284. package/scripts/forge-team/tests/hooks.test.sh +38 -50
  285. package/scripts/forge-team/tests/identity.test.sh +3 -3
  286. package/scripts/forge-team/tests/integration.test.sh +44 -66
  287. package/scripts/forge-team/tests/sync-github.test.sh +50 -83
  288. package/scripts/forge-team/tests/verify.test.sh +37 -46
  289. package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
  290. package/scripts/forge-team/tests/workload.test.sh +32 -66
  291. package/scripts/gen-command-manifest.js +153 -0
  292. package/scripts/gen-embedded-assets.mjs +129 -0
  293. package/scripts/install.ps1 +139 -0
  294. package/scripts/install.sh +268 -0
  295. package/scripts/lib/release-asset.mjs +84 -0
  296. package/scripts/parity-check.mjs +145 -0
  297. package/scripts/parity-check.test.mjs +58 -0
  298. package/scripts/pin-agentic-workflow-images.js +112 -0
  299. package/scripts/pr-auto-actions.js +93 -0
  300. package/scripts/pr-coordinator.sh +3 -0
  301. package/scripts/pr-verdict-label.js +50 -0
  302. package/scripts/preflight-sonar.eslint.config.mjs +44 -0
  303. package/scripts/preflight.sh +21 -94
  304. package/scripts/protected-state-check.js +104 -0
  305. package/scripts/smart-status.sh +60 -57
  306. package/scripts/spikes/config-race-bench.js +111 -0
  307. package/scripts/spikes/harness-capability-matrix.js +13 -0
  308. package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
  309. package/scripts/spikes/protected-path-manifest.js +20 -0
  310. package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
  311. package/scripts/sync-agent-skills.js +62 -0
  312. package/scripts/sync-utils.sh +3 -0
  313. package/scripts/test-ci-shard.js +13 -6
  314. package/scripts/test.js +95 -12
  315. package/skills/claim-safety/SKILL.md +102 -0
  316. package/skills/claim-safety/evals/evals.json +46 -0
  317. package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +44 -50
  318. package/skills/dev/evals/evals.json +50 -0
  319. package/skills/hermes-forge/SKILL.md +185 -0
  320. package/skills/hermes-forge/evals/evals.json +46 -0
  321. package/skills/issue-basics/SKILL.md +111 -0
  322. package/skills/issue-basics/evals/evals.json +46 -0
  323. package/skills/kernel/SKILL.md +166 -0
  324. package/skills/kernel/evals/evals.json +50 -0
  325. package/skills/memory/SKILL.md +102 -0
  326. package/skills/parallel-deep-research/SKILL.md +14 -11
  327. package/skills/parallel-deep-research/evals/evals.json +11 -27
  328. package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +132 -157
  329. package/skills/plan/evals/evals.json +42 -0
  330. package/skills/research/SKILL.md +195 -0
  331. package/skills/research/evals/evals.json +42 -0
  332. package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
  333. package/skills/review/evals/evals.json +42 -0
  334. package/skills/rollback/SKILL.md +110 -0
  335. package/skills/rollback/evals/evals.json +46 -0
  336. package/skills/rollback/references/methods.md +204 -0
  337. package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
  338. package/skills/shepherd/SKILL.md +66 -0
  339. package/skills/shepherd/evals/evals.json +42 -0
  340. package/{.github/prompts/ship.prompt.md → skills/ship/SKILL.md} +81 -45
  341. package/skills/ship/evals/evals.json +42 -0
  342. package/skills/smith/SKILL.md +142 -0
  343. package/skills/smith/evals/evals.json +46 -0
  344. package/skills/smith/references/autonomy-and-gates.md +94 -0
  345. package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
  346. package/skills/sonarcloud/evals/evals.json +46 -0
  347. package/skills/sonarcloud-analysis/SKILL.md +18 -13
  348. package/skills/sonarcloud-analysis/evals/evals.json +11 -15
  349. package/{.github/prompts/status.prompt.md → skills/status/SKILL.md} +20 -10
  350. package/skills/status/evals/evals.json +50 -0
  351. package/skills/triage-ready/SKILL.md +121 -0
  352. package/skills/triage-ready/evals/evals.json +42 -0
  353. package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
  354. package/skills/validate/evals/evals.json +42 -0
  355. package/skills/verify/SKILL.md +299 -0
  356. package/skills/verify/evals/evals.json +50 -0
  357. package/.claude/commands/dev.md +0 -345
  358. package/.claude/commands/plan.md +0 -566
  359. package/.claude/commands/premerge.md +0 -186
  360. package/.claude/commands/research.md +0 -42
  361. package/.claude/commands/review.md +0 -451
  362. package/.claude/commands/rollback.md +0 -721
  363. package/.claude/commands/ship.md +0 -213
  364. package/.claude/commands/sonarcloud.md +0 -152
  365. package/.claude/commands/status.md +0 -90
  366. package/.claude/commands/validate.md +0 -288
  367. package/.claude/commands/verify.md +0 -269
  368. package/.claude/rules/workflow.md +0 -121
  369. package/.cline/workflows/dev.md +0 -342
  370. package/.cline/workflows/plan.md +0 -563
  371. package/.cline/workflows/premerge.md +0 -183
  372. package/.cline/workflows/research.md +0 -39
  373. package/.cline/workflows/review.md +0 -448
  374. package/.cline/workflows/rollback.md +0 -718
  375. package/.cline/workflows/ship.md +0 -210
  376. package/.cline/workflows/sonarcloud.md +0 -146
  377. package/.cline/workflows/status.md +0 -87
  378. package/.cline/workflows/validate.md +0 -285
  379. package/.cline/workflows/verify.md +0 -266
  380. package/.codex/config.toml +0 -11
  381. package/.codex/skills/dev/SKILL.md +0 -345
  382. package/.codex/skills/plan/SKILL.md +0 -566
  383. package/.codex/skills/premerge/SKILL.md +0 -186
  384. package/.codex/skills/research/SKILL.md +0 -42
  385. package/.codex/skills/review/SKILL.md +0 -451
  386. package/.codex/skills/rollback/SKILL.md +0 -721
  387. package/.codex/skills/ship/SKILL.md +0 -213
  388. package/.codex/skills/sonarcloud/SKILL.md +0 -149
  389. package/.codex/skills/status/SKILL.md +0 -90
  390. package/.codex/skills/validate/SKILL.md +0 -288
  391. package/.codex/skills/verify/SKILL.md +0 -269
  392. package/.cursor/commands/dev.md +0 -342
  393. package/.cursor/commands/plan.md +0 -563
  394. package/.cursor/commands/premerge.md +0 -183
  395. package/.cursor/commands/research.md +0 -39
  396. package/.cursor/commands/review.md +0 -448
  397. package/.cursor/commands/ship.md +0 -210
  398. package/.cursor/commands/sonarcloud.md +0 -146
  399. package/.cursor/commands/status.md +0 -87
  400. package/.cursor/commands/validate.md +0 -285
  401. package/.cursor/commands/verify.md +0 -266
  402. package/.cursorrules +0 -149
  403. package/.github/prompts/premerge.prompt.md +0 -188
  404. package/.github/prompts/research.prompt.md +0 -44
  405. package/.github/prompts/rollback.prompt.md +0 -723
  406. package/.github/prompts/verify.prompt.md +0 -271
  407. package/.github/workflows/beads-to-github.yml +0 -89
  408. package/.github/workflows/github-to-beads.yml +0 -100
  409. package/.kilocode/workflows/dev.md +0 -346
  410. package/.kilocode/workflows/plan.md +0 -567
  411. package/.kilocode/workflows/premerge.md +0 -187
  412. package/.kilocode/workflows/research.md +0 -43
  413. package/.kilocode/workflows/review.md +0 -452
  414. package/.kilocode/workflows/rollback.md +0 -722
  415. package/.kilocode/workflows/ship.md +0 -214
  416. package/.kilocode/workflows/sonarcloud.md +0 -150
  417. package/.kilocode/workflows/status.md +0 -91
  418. package/.kilocode/workflows/validate.md +0 -289
  419. package/.kilocode/workflows/verify.md +0 -270
  420. package/.opencode/commands/dev.md +0 -345
  421. package/.opencode/commands/plan.md +0 -566
  422. package/.opencode/commands/premerge.md +0 -186
  423. package/.opencode/commands/research.md +0 -42
  424. package/.opencode/commands/review.md +0 -451
  425. package/.opencode/commands/rollback.md +0 -721
  426. package/.opencode/commands/ship.md +0 -213
  427. package/.opencode/commands/sonarcloud.md +0 -149
  428. package/.opencode/commands/status.md +0 -90
  429. package/.opencode/commands/validate.md +0 -288
  430. package/.opencode/commands/verify.md +0 -269
  431. package/.roo/commands/dev.md +0 -346
  432. package/.roo/commands/plan.md +0 -567
  433. package/.roo/commands/premerge.md +0 -187
  434. package/.roo/commands/research.md +0 -43
  435. package/.roo/commands/review.md +0 -452
  436. package/.roo/commands/rollback.md +0 -722
  437. package/.roo/commands/ship.md +0 -214
  438. package/.roo/commands/sonarcloud.md +0 -150
  439. package/.roo/commands/status.md +0 -91
  440. package/.roo/commands/validate.md +0 -289
  441. package/.roo/commands/verify.md +0 -270
  442. package/docs/BEADS_GITHUB_SYNC.md +0 -281
  443. package/docs/GREPTILE_SETUP.md +0 -400
  444. package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
  445. package/docs/SETUP.md +0 -663
  446. package/docs/VALIDATION.md +0 -363
  447. package/lib/agents/cline.plugin.json +0 -29
  448. package/lib/agents/copilot.plugin.json +0 -24
  449. package/lib/agents/kilocode.plugin.json +0 -22
  450. package/lib/agents/opencode.plugin.json +0 -23
  451. package/lib/agents/roo.plugin.json +0 -30
  452. package/lib/beads-bootstrap.js +0 -225
  453. package/lib/beads-health-check.js +0 -188
  454. package/lib/commands/commands-reset.js +0 -147
  455. package/opencode.json +0 -67
  456. package/scripts/beads-context.test.js +0 -584
  457. package/scripts/github-beads-sync/comment.mjs +0 -64
  458. package/scripts/github-beads-sync/config.mjs +0 -148
  459. package/scripts/github-beads-sync/github-api.mjs +0 -131
  460. package/scripts/github-beads-sync/index.mjs +0 -356
  461. package/scripts/github-beads-sync/label-mapper.mjs +0 -54
  462. package/scripts/github-beads-sync/mapping.mjs +0 -132
  463. package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
  464. package/scripts/github-beads-sync/reverse-sync.mjs +0 -162
  465. package/scripts/github-beads-sync/run-bd.mjs +0 -161
  466. package/scripts/github-beads-sync/sanitize.mjs +0 -121
  467. package/scripts/github-beads-sync.config.json +0 -26
  468. package/scripts/sync-commands.js +0 -600
@@ -0,0 +1,501 @@
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 { startPrWatcherDetached } = require('../pr-monitor/watch-lifecycle');
40
+ const monitorJournal = require('../pr-monitor/journal');
41
+ const { EVENT_TYPES: T } = require('../pr-monitor/events');
42
+ const { autoShepherdRailEnabled } = require('./ship');
43
+
44
+ const DEFAULT_RERUN_BUDGET = 3;
45
+
46
+ const defaultGhRunner = (cmd, a) => execFileSync(cmd, a, { encoding: 'utf8', timeout: 30000 });
47
+
48
+ /**
49
+ * Resolve owner/repo and base branch for the shepherd pass.
50
+ *
51
+ * The base branch is read from the PR itself (`gh pr view <pr> --json
52
+ * baseRefName`) rather than the current checkout's default branch, so PRs
53
+ * targeting `release/*`/`develop` are evaluated against the correct branch.
54
+ * `owner`/`name` come from the repository the PR is queried in — that IS the
55
+ * base repository. `cwd` (the worktree root) is threaded through so divergence
56
+ * is computed against the right checkout.
57
+ *
58
+ * @param {object} deps
59
+ * @returns {Promise<{ pr: string, owner: string, repo: string, base: string, baseRef: string, cwd?: string }>}
60
+ */
61
+ async function defaultBuildContext({ pr, gh, git, projectRoot }) {
62
+ const prJson = gh('gh', ['pr', 'view', String(pr), '--json', 'baseRefName']);
63
+ const prInfo = JSON.parse(prJson || '{}');
64
+ const base = prInfo.baseRefName || 'master';
65
+
66
+ const repoJson = gh('gh', ['repo', 'view', '--json', 'owner,name']);
67
+ const repo = JSON.parse(repoJson || '{}');
68
+ const owner = repo.owner?.login || '';
69
+ const name = repo.name || '';
70
+
71
+ let baseRemote;
72
+ try {
73
+ baseRemote = git('git', ['remote']).split(/\s+/).filter(Boolean)[0] || 'origin';
74
+ } catch (_err) {
75
+ baseRemote = 'origin';
76
+ }
77
+
78
+ return {
79
+ pr: String(pr),
80
+ owner,
81
+ repo: name,
82
+ base,
83
+ baseRef: `${baseRemote}/${base}`,
84
+ ...(projectRoot ? { cwd: projectRoot } : {}),
85
+ };
86
+ }
87
+
88
+ /**
89
+ * Detect whether the working tree is clean (precondition for --auto-rebase).
90
+ *
91
+ * @param {Function} git
92
+ * @returns {boolean}
93
+ */
94
+ function isWorkingTreeClean(git) {
95
+ try {
96
+ return git('git', ['status', '--porcelain']).trim().length === 0;
97
+ } catch (_err) {
98
+ return false;
99
+ }
100
+ }
101
+
102
+ // Render a single pass action for the human-readable monitor line. Strings pass
103
+ // through; everything else is JSON-encoded, but JSON.stringify can throw on
104
+ // circular refs or BigInt, so surface the reason inline rather than crash the pass.
105
+ function formatAction(action) {
106
+ if (typeof action === 'string') {
107
+ return action;
108
+ }
109
+ try {
110
+ return JSON.stringify(action);
111
+ } catch (err) {
112
+ return `[unprintable action: ${err.message}]`;
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Build the DEFAULT `check.failed` enrichment hook for the events pull surface.
118
+ *
119
+ * The monitor design specifies that newly-failed checks are enriched with their
120
+ * failure log excerpts before the journal append. `pollEvents` accepts an
121
+ * `enrich` hook but `handleEvents` must supply the default one, or a plain
122
+ * `forge shepherd events` call would emit bare `check.failed` events with no
123
+ * `data.excerpt` (only direct monitor callers could attach them).
124
+ *
125
+ * The hook is BEST-EFFORT: it fetches the compact pull signal ONCE (only when a
126
+ * pass actually produced a `check.failed`), maps excerpts by check name, and
127
+ * decorates matching records. Any failure to gather excerpts leaves the events
128
+ * intact rather than aborting the pass — enrichment must never block the journal.
129
+ *
130
+ * @param {object} pullCtx - ctx forwarded to `gatherPull` (owner/repo/base/adapter/runGh/self).
131
+ * @returns {(records: object[]) => Promise<void>}
132
+ */
133
+ function makeCheckFailureEnricher(pullCtx) {
134
+ const gatherPull = pullCtx.gatherPull || gatherPullSignal;
135
+ return async (records) => {
136
+ if (!Array.isArray(records) || !records.some((r) => r.type === T.CHECK_FAILED)) return;
137
+ let failures;
138
+ try {
139
+ const pull = await gatherPull(pullCtx);
140
+ failures = Array.isArray(pull?.failures) ? pull.failures : [];
141
+ } catch (err) {
142
+ // best-effort: never let enrichment abort the pass, but surface the reason
143
+ console.error(`[shepherd] check-failure enrichment skipped: ${err.message}`);
144
+ return;
145
+ }
146
+ const byName = new Map(failures.map((f) => [f.name, f]));
147
+ for (const r of records) {
148
+ if (r.type !== T.CHECK_FAILED) continue;
149
+ const f = byName.get(r.data?.name);
150
+ if (!f) continue;
151
+ if (f.excerpt) r.data.excerpt = f.excerpt;
152
+ if (f.jobUrl) r.data.jobUrl = f.jobUrl;
153
+ }
154
+ };
155
+ }
156
+
157
+ /** Parse `--since <seq>` from the raw arg list (default 0). */
158
+ function parseSince(args) {
159
+ const i = (args || []).indexOf('--since');
160
+ if (i >= 0 && args[i + 1] != null) return Number.parseInt(args[i + 1], 10) || 0;
161
+ return 0;
162
+ }
163
+
164
+ /**
165
+ * Build the shared monitor context — journal `dir`, bounded `gather`, and the
166
+ * default `check.failed` enricher — that BOTH the `events` pull surface and the
167
+ * `watch` streaming loop feed to the monitor core. Injected `dir`/`gather`/
168
+ * `enrich` (tests, programmatic callers) short-circuit the live gh build.
169
+ *
170
+ * @param {string|number} pr
171
+ * @param {string} projectRoot
172
+ * @param {object} deps
173
+ * @returns {Promise<{ dir?: string, gather?: Function, enrich?: Function, error?: string }>}
174
+ */
175
+ async function buildMonitorContext(pr, projectRoot, deps) {
176
+ let dir = deps.dir;
177
+ let gather = deps.gather;
178
+ // enrich decorates newly-failed checks with log excerpts before the journal
179
+ // append. The caller MUST supply the default (not just forward an injected
180
+ // one), or a plain `forge shepherd events`/`watch` emits bare check.failed events.
181
+ let enrich = deps.enrich;
182
+ if (!gather || !dir) {
183
+ const gh = deps.gh || defaultGhRunner;
184
+ const git = deps.git || gh;
185
+ const buildContext = deps.buildContext || defaultBuildContext;
186
+ const ctx = await buildContext({ pr, gh, git, projectRoot });
187
+ const adapter = deps.adapter || new PrStateAdapter({ gh, git });
188
+ const validation = validatePrStateAdapter(adapter);
189
+ if (!validation.valid) {
190
+ return { error: `Invalid pr-state adapter: ${validation.errors.join('; ')}` };
191
+ }
192
+ dir = dir || monitorJournal.journalDir({ root: projectRoot || process.cwd(), repo: ctx.repo, pr: ctx.pr });
193
+ gather = gather || (() => gatherMonitorSnapshot({ ...ctx, adapter, self: deps.self }));
194
+ enrich = enrich || makeCheckFailureEnricher({
195
+ ...ctx,
196
+ adapter,
197
+ self: deps.self,
198
+ runGh: (ghArgs) => gh('gh', ghArgs),
199
+ gatherPull: deps.gatherPull,
200
+ });
201
+ } else if (!enrich && deps.gatherPull) {
202
+ // Injected gather (tests / programmatic callers) still gets the default
203
+ // enrichment when a pull-signal source is supplied.
204
+ enrich = makeCheckFailureEnricher({
205
+ pr, adapter: deps.adapter, self: deps.self, runGh: deps.runGh, gatherPull: deps.gatherPull,
206
+ });
207
+ }
208
+ return { dir, gather, enrich };
209
+ }
210
+
211
+ /**
212
+ * `forge shepherd events <pr> --since <seq> [--json]` — the agent-agnostic PULL
213
+ * surface. Runs one bounded gather+diff (inline, unless a watcher owns the PR),
214
+ * appends new events to the per-PR journal, and prints every journaled event
215
+ * with `seq > since` as NDJSON, one per line, to stdout. Nothing under .claude.
216
+ *
217
+ * @param {string[]} args
218
+ * @param {string} projectRoot
219
+ * @param {object} [deps]
220
+ * @returns {Promise<object>}
221
+ */
222
+ async function handleEvents(args, projectRoot, deps = {}) {
223
+ const rawArgs = args || [];
224
+ const sinceIdx = rawArgs.indexOf('--since');
225
+ const pr = rawArgs.find((a, idx) => !String(a).startsWith('--') && a !== 'events' && idx !== sinceIdx + 1);
226
+ if (!pr) {
227
+ return { success: false, error: 'Usage: forge shepherd events <pr> --since <seq> [--json]' };
228
+ }
229
+ const since = parseSince(rawArgs);
230
+
231
+ const built = await buildMonitorContext(pr, projectRoot, deps);
232
+ if (built.error) return { success: false, error: built.error };
233
+ const { dir, gather, enrich } = built;
234
+
235
+ const poll = deps.pollEvents || pollEvents;
236
+ const result = await poll({ dir, gather, since, now: deps.now, watcherRunning: deps.watcherRunning, enrich });
237
+ // `output` is the agent-agnostic pull surface: NDJSON, one event per line. The
238
+ // registry CLI dispatch prints `result.output` (same contract as --pull/--bundle),
239
+ // so this handler does NOT write to stdout itself (that would double-print).
240
+ const output = result.events.map((e) => JSON.stringify(e)).join('\n');
241
+ return { success: true, events: result.events, since: result.since, output };
242
+ }
243
+
244
+ /**
245
+ * Wire an AbortController to SIGINT/SIGTERM so a long-running watch loop stops
246
+ * cleanly on Ctrl-C. Returns the signal plus a `cleanup` that detaches the
247
+ * one-shot handlers (always called in a finally so the loop leaves no listeners).
248
+ *
249
+ * @returns {{ signal: object, cleanup: () => void }}
250
+ */
251
+ function wireSignals() {
252
+ const controller = new AbortController();
253
+ const onSignal = () => controller.abort();
254
+ process.once('SIGINT', onSignal);
255
+ process.once('SIGTERM', onSignal);
256
+ const cleanup = () => {
257
+ process.off('SIGINT', onSignal);
258
+ process.off('SIGTERM', onSignal);
259
+ };
260
+ return { signal: controller.signal, cleanup };
261
+ }
262
+
263
+ /**
264
+ * `forge shepherd watch <pr>` — the agent-agnostic PUSH surface. A long-running
265
+ * loop that every ~60s (jittered) runs ONE bounded monitor pass and STREAMS each
266
+ * new event as an NDJSON line to stdout, self-stopping on `pr.merged`/`pr.closed`.
267
+ * The loop streams live via the default stdout emit, so this handler returns NO
268
+ * `output` field (returning one would double-print). SIGINT/SIGTERM stop it clean.
269
+ *
270
+ * @param {string[]} args
271
+ * @param {string} projectRoot
272
+ * @param {object} [deps]
273
+ * @returns {Promise<object>}
274
+ */
275
+ /**
276
+ * List every OPEN PR number via `gh pr list`. Fail-open: any error yields an
277
+ * empty list (adopt then arms nothing) rather than throwing.
278
+ *
279
+ * @param {Function} [exec] - gh runner (test injection).
280
+ * @returns {number[]}
281
+ */
282
+ function defaultListOpenPrs(exec = execFileSync) {
283
+ try {
284
+ const out = exec('gh', ['pr', 'list', '--state', 'open', '--json', 'number', '-q', '.[].number'], {
285
+ encoding: 'utf8', timeout: 20000, stdio: ['pipe', 'pipe', 'pipe'],
286
+ });
287
+ return String(out)
288
+ .split(/\r?\n/)
289
+ .map((s) => Number.parseInt(s.trim(), 10))
290
+ .filter((n) => Number.isInteger(n) && n > 0);
291
+ } catch {
292
+ return [];
293
+ }
294
+ }
295
+
296
+ /**
297
+ * `forge shepherd watch --adopt` — arm a detached watcher for EVERY currently-open
298
+ * PR (covers PRs created via gh/UI, or before this rail existed). Idempotent: the
299
+ * watch loop's PID/journal lock means an already-watched PR is not double-started.
300
+ * Fail-open per PR and overall — never throws. Honors the default-ON
301
+ * `rail.auto_shepherd` rail: when a maintainer has disabled it, adoption is a
302
+ * no-op — no PR listing, no watcher spawn — matching `forge push`/`forge ship`.
303
+ *
304
+ * @param {string} projectRoot
305
+ * @param {object} [deps]
306
+ * @returns {{ success: true, adopted: number[], total: number, reason?: string }}
307
+ */
308
+ function handleAdopt(projectRoot, deps = {}) {
309
+ const railEnabled = deps.railEnabled || autoShepherdRailEnabled;
310
+ // Gate BEFORE listing PRs / spawning watchers: a disabled rail must not spawn
311
+ // detached watchers. No-op result mirrors the fail-open (empty) adoption shape.
312
+ if (!railEnabled(projectRoot)) {
313
+ return { success: true, adopted: [], total: 0, reason: 'rail.auto_shepherd disabled' };
314
+ }
315
+ const listOpenPrs = deps.listOpenPrs || defaultListOpenPrs;
316
+ const startWatcher = deps.startWatcher || startPrWatcherDetached;
317
+ let prs;
318
+ try {
319
+ prs = listOpenPrs();
320
+ } catch {
321
+ prs = [];
322
+ }
323
+ if (!Array.isArray(prs)) prs = [];
324
+ const adopted = [];
325
+ for (const pr of prs) {
326
+ try {
327
+ const res = startWatcher({ prNumber: pr, cwd: projectRoot });
328
+ if (res?.started) adopted.push(pr);
329
+ } catch { /* fail-open per PR: one bad arm never blocks the rest */ }
330
+ }
331
+ return { success: true, adopted, total: prs.length };
332
+ }
333
+
334
+ async function handleWatch(args, projectRoot, deps = {}) {
335
+ const rawArgs = args || [];
336
+ // `--adopt` (no PR arg): arm a detached watcher for every open PR.
337
+ if (rawArgs.includes('--adopt')) {
338
+ return handleAdopt(projectRoot, deps);
339
+ }
340
+ const pr = rawArgs.find((a) => !String(a).startsWith('--') && a !== 'watch');
341
+ if (!pr) {
342
+ return { success: false, error: 'Usage: forge shepherd watch <pr> | forge shepherd watch --adopt' };
343
+ }
344
+
345
+ const built = await buildMonitorContext(pr, projectRoot, deps);
346
+ if (built.error) return { success: false, error: built.error };
347
+ const { dir, gather, enrich } = built;
348
+
349
+ const loop = deps.watchLoop || watchLoop;
350
+ // Injected signal (tests) suppresses real process handlers; otherwise wire them.
351
+ const wired = deps.signal ? { signal: deps.signal, cleanup: () => {} } : wireSignals();
352
+ let result;
353
+ try {
354
+ result = await loop({
355
+ dir,
356
+ gather,
357
+ enrich,
358
+ now: deps.now,
359
+ emit: deps.emit,
360
+ sleep: deps.sleep,
361
+ rng: deps.rng,
362
+ intervalMs: deps.intervalMs,
363
+ maxPasses: deps.maxPasses,
364
+ lockOpts: deps.lockOpts,
365
+ signal: wired.signal,
366
+ watcherRunning: deps.watcherRunning,
367
+ writePid: deps.writePid,
368
+ removePid: deps.removePid,
369
+ });
370
+ } finally {
371
+ wired.cleanup();
372
+ }
373
+
374
+ return {
375
+ success: true,
376
+ started: result.started,
377
+ passes: result.passes,
378
+ stopped: result.stopped,
379
+ ...(result.reason ? { reason: result.reason } : {}),
380
+ };
381
+ }
382
+
383
+ /**
384
+ * Command handler.
385
+ *
386
+ * @param {string[]} args - Positional + flag args (first positional is the PR).
387
+ * @param {object} _flags - Parsed flags (unused; flags are read from args).
388
+ * @param {string} projectRoot - Project root.
389
+ * @param {object} [deps] - Injected dependencies for testing.
390
+ * @returns {Promise<object>} result envelope.
391
+ */
392
+ async function handler(args, _flags, projectRoot, deps = {}) {
393
+ const positional = (args || []).filter((a) => !String(a).startsWith('--'));
394
+ const flags = new Set((args || []).filter((a) => String(a).startsWith('--')));
395
+
396
+ // Subcommand routing: `events` is the monitor pull surface (its own arg shape);
397
+ // `watch` is the constant monitor push surface (long-running stream).
398
+ if (positional[0] === 'events') {
399
+ return handleEvents(args, projectRoot, deps);
400
+ }
401
+ if (positional[0] === 'watch') {
402
+ return handleWatch(args, projectRoot, deps);
403
+ }
404
+
405
+ const pr = positional[0];
406
+
407
+ if (!pr) {
408
+ return { success: false, error: 'Usage: forge shepherd <pr> [--auto-rebase] [--bundle --json] [--pull --json]' };
409
+ }
410
+
411
+ const gh = deps.gh || ((cmd, a) => execFileSync(cmd, a, { encoding: 'utf8', timeout: 30000 }));
412
+ const git = deps.git || gh;
413
+ const buildContext = deps.buildContext || defaultBuildContext;
414
+ const runPass = deps.runPass || runShepherdPass;
415
+ const gatherBundle = deps.gatherBundle || gatherPrBundle;
416
+ const gatherPull = deps.gatherPull || gatherPullSignal;
417
+
418
+ const autoRebase = flags.has('--auto-rebase');
419
+ const wantBundle = flags.has('--bundle');
420
+ const wantPull = flags.has('--pull');
421
+ const wantJson = flags.has('--json');
422
+
423
+ const ctx = await buildContext({ pr, gh, git, projectRoot });
424
+
425
+ const adapter = deps.adapter || new PrStateAdapter({ gh, git });
426
+ const validation = validatePrStateAdapter(adapter);
427
+ if (!validation.valid) {
428
+ return { success: false, error: `Invalid pr-state adapter: ${validation.errors.join('; ')}` };
429
+ }
430
+
431
+ // --pull: gather the COMPACT "why it failed + what to fix" payload (failed-check
432
+ // log excerpts, matrix-deduped, plus the review-thread fix-list). STRICTLY
433
+ // READ-ONLY — it computes the decision state via a dry-run pass but takes NO
434
+ // action (no Tier-A rerun, no rebase); acting belongs to plain `forge shepherd`.
435
+ // The `gh` calls to fetch logs run through an injected runner so this stays testable.
436
+ if (wantPull) {
437
+ const runGh = (ghArgs) => gh('gh', ghArgs);
438
+ const pull = await gatherPull({ ...ctx, adapter, runGh, runPass, self: deps.self });
439
+ // `output` is what the registry CLI dispatch (bin/forge.js) actually PRINTS —
440
+ // returning only `pull` silently dropped the whole payload on that path (it
441
+ // prints `result.output`, nothing else). `--json` → machine payload; default
442
+ // → the compact human WHY+fix summary. `pull` is kept for the legacy
443
+ // bin/forge-cmd.js path and for programmatic callers/tests.
444
+ const output = wantJson ? JSON.stringify(pull, null, 2) : renderPullSummary(pull);
445
+ return { success: true, pull, output };
446
+ }
447
+
448
+ // --bundle: gather the COMPLETE read-only PR-state bundle the monitor will
449
+ // hand to a fixer-agent, and return it for the CLI to print as JSON. This is
450
+ // the gather half only — it decides nothing and takes no action.
451
+ if (wantBundle) {
452
+ const bundle = await gatherBundle({ ...ctx, adapter });
453
+ // Same rationale as --pull: the registry dispatch prints `output`. The bundle
454
+ // is a machine payload, so it is always emitted as JSON.
455
+ return { success: true, bundle, output: JSON.stringify(bundle, null, 2) };
456
+ }
457
+
458
+ const result = await runPass({
459
+ ...ctx,
460
+ adapter,
461
+ autoRebase,
462
+ cleanTree: autoRebase ? isWorkingTreeClean(git) : false,
463
+ rerunBudget: deps.rerunBudget || DEFAULT_RERUN_BUDGET,
464
+ rerunsUsed: deps.rerunsUsed || 0,
465
+ });
466
+
467
+ // Surface the pass outcome so the monitor is legible when run interactively or
468
+ // tailed by a scheduler (the bounded state machine is otherwise silent).
469
+ const passActions = Array.isArray(result.actions) ? result.actions : [];
470
+ const reasonSuffix = result.reason ? ` — ${result.reason}` : '';
471
+ process.stdout.write(`Shepherd pass — PR #${pr}: ${result.state}${reasonSuffix}\n`);
472
+ for (const action of passActions) {
473
+ process.stdout.write(` • ${formatAction(action)}\n`);
474
+ }
475
+ if (!passActions.length) {
476
+ process.stdout.write(' • no actions this pass\n');
477
+ }
478
+
479
+ return {
480
+ success: result.state !== 'HARD_STOP',
481
+ state: result.state,
482
+ reason: result.reason,
483
+ actions: result.actions || [],
484
+ ...(result.authClass ? { authClass: result.authClass } : {}),
485
+ ...(result.retryAfter ? { retryAfter: result.retryAfter } : {}),
486
+ };
487
+ }
488
+
489
+ module.exports = {
490
+ name: 'shepherd',
491
+ description: 'Run one bounded monitor pass over a PR (rerun flaky checks, escalate, hand off — never merges)',
492
+ usage: 'Usage: forge shepherd <pr> [--auto-rebase] [--bundle --json] [--pull --json] | forge shepherd events <pr> --since <seq> [--json] | forge shepherd watch <pr> | forge shepherd watch --adopt',
493
+ handler,
494
+ handleEvents,
495
+ handleWatch,
496
+ buildMonitorContext,
497
+ makeCheckFailureEnricher,
498
+ parseSince,
499
+ defaultBuildContext,
500
+ isWorkingTreeClean,
501
+ };
@@ -12,6 +12,34 @@ 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
+ const { getResolvedRuntimeGraph } = require('../core/runtime-graph');
17
+
18
+ const AUTO_SHEPHERD_RAIL = 'rail.auto_shepherd';
19
+
20
+ /**
21
+ * Whether the default-ON `rail.auto_shepherd` rail permits auto-starting the PR
22
+ * watcher. Reads the SAME resolved runtime graph the `forge gate enable|disable`
23
+ * surface writes (`workflow.gates.rail.auto_shepherd.enabled`), so the toggle is
24
+ * config-honest. FAIL-OPEN: only an explicit `enabled === false` disables it — a
25
+ * missing rail or any resolution error falls back to enabled (the default), and
26
+ * this NEVER throws (ship must not fail on a config read).
27
+ *
28
+ * @param {string} [projectRoot]
29
+ * @param {Function} [resolveGraph] - injectable graph resolver (tests).
30
+ * @returns {boolean}
31
+ */
32
+ function autoShepherdRailEnabled(projectRoot = process.cwd(), resolveGraph = getResolvedRuntimeGraph) {
33
+ try {
34
+ const graph = resolveGraph({ projectRoot });
35
+ const rail = [...(graph.rails || []), ...(graph.gates || [])]
36
+ .find(entry => entry.id === AUTO_SHEPHERD_RAIL);
37
+ return !(rail && rail.enabled === false);
38
+ } catch {
39
+ return true;
40
+ }
41
+ }
42
+
15
43
  function getExecOptions() {
16
44
  return { encoding: 'utf8', cwd: process.cwd(), timeout: 120000 };
17
45
  }
@@ -482,8 +510,35 @@ async function createPR(options) { // NOSONAR S3776
482
510
  }
483
511
  }
484
512
 
513
+ /**
514
+ * Best-effort, non-blocking auto-start of the constant PR monitor once a real PR
515
+ * exists. Skipped on a dry run or when no PR number is known. MUST NEVER fail
516
+ * ship: `startWatcher` (startPrWatcherDetached) already never throws, and this
517
+ * guard keeps even a surprise error from surfacing to the ship caller.
518
+ *
519
+ * Gated by the default-ON `rail.auto_shepherd` rail: when a maintainer has
520
+ * disabled it (`forge gate disable rail.auto_shepherd`), the watcher is skipped
521
+ * so the auto-start is honestly toggleable. The rail check is fail-open and
522
+ * wrapped in the same try/catch, so neither a disabled rail nor a config-read
523
+ * error ever fails ship.
524
+ *
525
+ * @param {{ dryRun: boolean, prNumber?: string|number, startWatcher: Function, railEnabled?: Function }} params
526
+ * @returns {{ started: boolean, reason?: string }}
527
+ */
528
+ function maybeStartPrWatcher({ dryRun, prNumber, startWatcher, railEnabled = autoShepherdRailEnabled }) {
529
+ if (dryRun || !prNumber) return { started: false, reason: 'skipped' };
530
+ try {
531
+ if (!railEnabled(process.cwd())) {
532
+ return { started: false, reason: 'rail.auto_shepherd disabled' };
533
+ }
534
+ return startWatcher({ prNumber, cwd: process.cwd() });
535
+ } catch (err) {
536
+ return { started: false, reason: err.message };
537
+ }
538
+ }
539
+
485
540
  async function executeShip(options) {
486
- const { featureSlug, title, dryRun = false } = options || {};
541
+ const { featureSlug, title, dryRun = false, startWatcher = startPrWatcherDetached, railEnabled = autoShepherdRailEnabled } = options || {};
487
542
 
488
543
  // Validate feature slug
489
544
  if (!featureSlug || typeof featureSlug !== 'string' || featureSlug.trim() === '') {
@@ -535,6 +590,7 @@ async function executeShip(options) {
535
590
  });
536
591
  const result = await createPR({ title, body: prBody, dryRun });
537
592
  if (!result.success) return result;
593
+ maybeStartPrWatcher({ dryRun, prNumber: result.prNumber, startWatcher, railEnabled });
538
594
  return {
539
595
  success: true,
540
596
  prUrl: result.prUrl,
@@ -567,6 +623,8 @@ module.exports = {
567
623
  output: lines.join('\n'),
568
624
  };
569
625
  },
626
+ maybeStartPrWatcher,
627
+ autoShepherdRailEnabled,
570
628
  extractKeyDecisions,
571
629
  extractTestScenarios,
572
630
  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');