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,494 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * PR shepherd — one bounded pass of the monitor-driven state machine.
5
+ *
6
+ * Each `runShepherdPass` call is ONE discrete pass: read PR/CI state, decide a
7
+ * single action, take at most the allowed Tier-A action, then return. It never
8
+ * loops in-process and never sits waiting; an external scheduler re-invokes it.
9
+ *
10
+ * Invariants (enforced by tests):
11
+ * - NEVER merges. There is no merge action and no `--auto` latch — handoff to
12
+ * the human is the only way a PR merges.
13
+ * - NEVER resolves Greptile threads. It may post a status REPLY; resolution
14
+ * stays with the semantic `/review` agent.
15
+ * - Tier-A (autonomous): `rerun --failed` for flaky required checks (capped).
16
+ * - Tier-B (opt-in, default OFF): rebase + force-with-lease via `autoRebase`.
17
+ * Lease rejection is a HARD-STOP + escalate, never auto-retry.
18
+ * - Tier-C (escalate): conflicts, unknown/unreadable required set, persistent
19
+ * failures, auth-scope failures, oscillation, budget exhaustion.
20
+ * - Merge-ready is declared only when the required set is KNOWN and all of it
21
+ * is green and the branch is not behind.
22
+ * - Before any mutating action, HEAD SHA is re-read; if it moved since the
23
+ * pass started, the action is aborted (the real concurrency guard).
24
+ *
25
+ * State persists via GitHub PR comments/labels and git only.
26
+ *
27
+ * @module pr-shepherd
28
+ */
29
+
30
+ const { classifyAuthError } = require('./adapters/pr-state-adapter');
31
+ const { fenceUntrusted } = require('./untrusted-content');
32
+
33
+ /** Non-erroring terminal states a pass can settle into. */
34
+ const TERMINAL_STATES = ['MERGE_READY', 'ESCALATE', 'PENDING', 'MERGED', 'CLOSED', 'NEEDS_REVIEW'];
35
+
36
+ // Review threads are classified BY MECHANISM, not by a bot-name list: a GitHub
37
+ // review THREAD is opened by a reviewer (a human OR any bot) and stays open until
38
+ // resolved. So any unresolved, non-outdated thread is actionable REGARDLESS of
39
+ // author — that is both the #365 fix (a CodeRabbit thread now blocks) and
40
+ // fail-closed for unknown bots (a new review bot we can't name still blocks). A
41
+ // name list here would drop unknown bots' threads → false MERGE_READY.
42
+
43
+ /**
44
+ * Normalize a review thread (or a flat comment object) into its list of
45
+ * `{ author, body }` comments. A thread carries ALL its comments, so a later
46
+ * reply on a bot-opened thread is visible (not just the first comment).
47
+ */
48
+ function threadComments(t) {
49
+ if (Array.isArray(t.comments) && t.comments.length > 0) {
50
+ return t.comments.map((c) => ({
51
+ author: String((c.author && c.author.login) || c.author || '').toLowerCase(),
52
+ body: String(c.body || ''),
53
+ }));
54
+ }
55
+ return [{ author: String(t.author || t.login || '').toLowerCase(), body: String(t.body || '') }];
56
+ }
57
+
58
+ /**
59
+ * Filter review threads to the ones that need attention: unresolved AND not
60
+ * outdated — AUTHOR-AGNOSTIC. Any open thread blocks (human OR any bot, known or
61
+ * unknown); resolving it is `/review`'s job. The `self` param is accepted for
62
+ * backward-compatible call sites but no longer filters (a thread is open
63
+ * regardless of who last replied).
64
+ *
65
+ * @param {object[]} threads
66
+ * @param {string} [_self] - unused (kept for call-site compatibility).
67
+ * @returns {object[]}
68
+ */
69
+ function actionableComments(threads, _self) {
70
+ return (Array.isArray(threads) ? threads : []).filter(
71
+ (t) => !(t.resolved || t.isResolved || t.outdated || t.isOutdated),
72
+ );
73
+ }
74
+
75
+ const SUCCESS_CONCLUSIONS = new Set(['SUCCESS', 'NEUTRAL', 'SKIPPED']);
76
+
77
+ function isGreen(check) {
78
+ const c = String(check.conclusion || '').toUpperCase();
79
+ return SUCCESS_CONCLUSIONS.has(c);
80
+ }
81
+
82
+ // Includes ERROR and STARTUP_FAILURE so a failing legacy commit STATUS
83
+ // (StatusContext, e.g. Vercel/Netlify deploy checks report state=ERROR/FAILURE)
84
+ // is classified as failing — not silently treated as "pending" — exactly like a
85
+ // CheckRun FAILURE. gh's statusCheckRollup normalizes a StatusContext `state`
86
+ // into the `conclusion` slot, so both check types flow through this one gate.
87
+ function isFailed(check) {
88
+ const c = String(check.conclusion || '').toUpperCase();
89
+ return c === 'FAILURE' || c === 'ERROR' || c === 'TIMED_OUT'
90
+ || c === 'CANCELLED' || c === 'ACTION_REQUIRED' || c === 'STARTUP_FAILURE'
91
+ // STALE (a required CheckRun whose run went stale vs HEAD) is a not-green
92
+ // terminal conclusion — without it a stale required check pends forever.
93
+ || c === 'STALE';
94
+ }
95
+
96
+ /**
97
+ * Build a decision result envelope.
98
+ *
99
+ * @param {string} state
100
+ * @param {object} extra
101
+ */
102
+ function result(state, extra = {}) {
103
+ return { state, actions: extra.actions || [], reason: extra.reason || '', ...extra };
104
+ }
105
+
106
+ /**
107
+ * Map a classified auth error to a decision envelope, or return `null` when the
108
+ * error is not an auth/rate-limit shape (caller should re-throw).
109
+ *
110
+ * @param {Error} error
111
+ * @param {object[]} actions
112
+ * @returns {object | null}
113
+ */
114
+ function authOutcome(error, actions) {
115
+ const auth = classifyAuthError(error);
116
+ if (!auth) return null;
117
+ if (auth.class === 'insufficient-scope') {
118
+ return result('HARD_STOP', {
119
+ actions,
120
+ authClass: 'insufficient-scope',
121
+ reason: 'Token lacks the permission required (branch protection / PR state). Retry will not recover; escalate to a human to widen the token scope.',
122
+ });
123
+ }
124
+ if (auth.class === 'rate-limit') {
125
+ return result('PENDING', {
126
+ actions,
127
+ authClass: 'rate-limit',
128
+ retryAfter: auth.retryAfter,
129
+ reason: 'Secondary rate limit hit; honor Retry-After then resume on the next pass.',
130
+ });
131
+ }
132
+ // 'expired' (401) — transient: pause and surface for re-auth.
133
+ return result('PENDING', {
134
+ actions,
135
+ authClass: 'expired',
136
+ reason: 'Token appears expired/unauthorized (transient); pause and surface for re-auth.',
137
+ });
138
+ }
139
+
140
+ /**
141
+ * Run an adapter call, mapping auth/rate-limit failures to a decision envelope
142
+ * via the documented taxonomy instead of letting them escape as generic errors.
143
+ * Non-auth errors are re-thrown.
144
+ *
145
+ * Returns `{ outcome }` when the call should short-circuit the pass, or
146
+ * `{ value }` with the call's resolved value otherwise.
147
+ *
148
+ * @param {() => Promise<*>} call
149
+ * @param {object[]} actions
150
+ * @returns {Promise<{ outcome: object } | { value: * }>}
151
+ */
152
+ async function guardAuth(call, actions) {
153
+ try {
154
+ return { value: await call() };
155
+ } catch (error) {
156
+ const outcome = authOutcome(error, actions);
157
+ if (outcome) return { outcome };
158
+ throw error;
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Handle a failed required check: rerun once (capped, idempotent) or escalate.
164
+ *
165
+ * @param {object} args
166
+ * @returns {Promise<object>} decision envelope.
167
+ */
168
+ async function handleFailedRequired({
169
+ failedRequired, rerunsUsed, rerunBudget, headUnchanged, adapter, actions, dryRun,
170
+ }) {
171
+ // Read-only pass (e.g. `--pull` signal gathering): report the failing state but
172
+ // take NO Tier-A action. Reruns/mutations belong to a plain `forge shepherd`.
173
+ if (dryRun) {
174
+ return result('PENDING', {
175
+ actions,
176
+ dryRun: true,
177
+ reason: `Required check '${failedRequired[0].name}' is failing. Read-only pass — not re-running (a rerun belongs to \`forge shepherd\`, not \`--pull\`).`,
178
+ failed: failedRequired.map((c) => c.name),
179
+ });
180
+ }
181
+ if (rerunsUsed >= rerunBudget) {
182
+ return result('ESCALATE', {
183
+ actions,
184
+ reason: `Rerun budget exhausted (${rerunsUsed}/${rerunBudget}). Required check still failing — escalating.`,
185
+ failed: failedRequired.map((c) => c.name),
186
+ });
187
+ }
188
+ if (!(await headUnchanged())) {
189
+ return result('PENDING', {
190
+ actions,
191
+ aborted: true,
192
+ reason: 'HEAD moved during the pass; aborted the rerun. Next scheduled pass will re-evaluate.',
193
+ });
194
+ }
195
+ const runId = failedRequired[0].databaseId || failedRequired[0].name;
196
+ await adapter.rerunFailedChecks({ runId });
197
+ actions.push({ type: 'rerun', runId });
198
+ return result('PENDING', {
199
+ actions,
200
+ reason: `Re-ran failed required check '${failedRequired[0].name}'. Awaiting next scheduled pass.`,
201
+ });
202
+ }
203
+
204
+ /**
205
+ * Handle a branch that is behind base: opt-in Tier-B rebase, or escalate.
206
+ *
207
+ * @param {object} args
208
+ * @returns {Promise<object>} decision envelope.
209
+ */
210
+ async function handleBehindBase({
211
+ behind, autoRebase, cleanTree, adapter, baseRef, headUnchanged, actions,
212
+ }) {
213
+ if (!autoRebase) {
214
+ return result('ESCALATE', {
215
+ actions,
216
+ reason: `Branch is ${behind} commit(s) behind base. Auto-rebase is opt-in (default OFF) — a human should rebase, or re-run with --auto-rebase.`,
217
+ behind,
218
+ });
219
+ }
220
+ if (!cleanTree) {
221
+ return result('ESCALATE', {
222
+ actions,
223
+ reason: 'Auto-rebase requested but the working tree is not clean. Escalating rather than rebasing over local changes.',
224
+ });
225
+ }
226
+ if (typeof adapter.rebaseOntoBase !== 'function') {
227
+ return result('ESCALATE', {
228
+ actions,
229
+ reason: 'Auto-rebase requested but no rebase capability is wired. Escalating.',
230
+ });
231
+ }
232
+ if (!(await headUnchanged())) {
233
+ return result('PENDING', {
234
+ actions,
235
+ aborted: true,
236
+ reason: 'HEAD moved during the pass; aborted the rebase. Next scheduled pass will re-evaluate.',
237
+ });
238
+ }
239
+ try {
240
+ await adapter.rebaseOntoBase({ baseRef });
241
+ actions.push({ type: 'rebase', baseRef });
242
+ return result('PENDING', {
243
+ actions,
244
+ reason: 'Rebased onto base and force-pushed with lease. Awaiting CI on the next scheduled pass.',
245
+ });
246
+ } catch (error) {
247
+ if (error?.leaseRejected) {
248
+ return result('ESCALATE', {
249
+ actions,
250
+ reason: 'Force-with-lease was rejected — a concurrent push exists. HARD-STOP: never re-arm the lease. A human must reconcile.',
251
+ });
252
+ }
253
+ return result('ESCALATE', {
254
+ actions,
255
+ reason: `Rebase failed: ${error.message}. Escalating to a human.`,
256
+ });
257
+ }
258
+ }
259
+
260
+ /**
261
+ * Terminal lifecycle outcome for a merged/closed PR, or null when still open.
262
+ * The external scheduler must stop re-invoking the shepherd once the PR lands or
263
+ * is closed; without this it would keep re-deciding forever on a landed PR.
264
+ *
265
+ * @param {string} state - raw PR state.
266
+ * @param {object[]} actions
267
+ * @returns {object|null}
268
+ */
269
+ function lifecycleOutcome(state, actions) {
270
+ const prState = String(state || 'OPEN').toUpperCase();
271
+ if (prState === 'MERGED') {
272
+ return result('MERGED', {
273
+ actions,
274
+ reason: 'PR is merged — shepherd work is complete; the scheduler should stop re-invoking this PR.',
275
+ });
276
+ }
277
+ if (prState === 'CLOSED') {
278
+ return result('CLOSED', {
279
+ actions,
280
+ reason: 'PR is closed without merging — shepherd work is complete; the scheduler should stop re-invoking this PR.',
281
+ });
282
+ }
283
+ return null;
284
+ }
285
+
286
+ /**
287
+ * True when every REQUIRED check is present and green. Empty required set is
288
+ * ready by definition — optional checks never gate merge readiness.
289
+ *
290
+ * @param {string[]} required
291
+ * @param {object[]} checks
292
+ * @returns {boolean}
293
+ */
294
+ function allRequiredChecksGreen(required, checks) {
295
+ return required.every((name) => checks.some((check) => check.name === name && isGreen(check)));
296
+ }
297
+
298
+ /**
299
+ * Build the capped, display-only sample of actionable threads. Prefers a
300
+ * non-self comment (more informative), else the first — display only; the thread
301
+ * is actionable regardless of author.
302
+ *
303
+ * The `body` is an UNTRUSTED external PR comment surfaced verbatim into the
304
+ * agent-facing NEEDS_REVIEW envelope, so it is provenance-fenced (a malicious
305
+ * comment must not be able to steer the /review agent). This is the display
306
+ * projection; the machine bundle/pull JSON keeps the raw body.
307
+ *
308
+ * @param {object[]} actionable
309
+ * @param {string} [self]
310
+ * @param {number} cap
311
+ * @returns {Array<{author: string, body: string}>}
312
+ */
313
+ function buildCommentSample(actionable, self, cap) {
314
+ const selfLower = String(self || '').toLowerCase();
315
+ return actionable.slice(0, cap).map((t) => {
316
+ const cs = threadComments(t);
317
+ const anchor = cs.find((c) => c.author && c.author !== selfLower) || cs[0] || {};
318
+ return {
319
+ author: anchor.author || '',
320
+ body: fenceUntrusted(String(anchor.body || '').slice(0, 200), { source: 'pr-review-comment' }),
321
+ };
322
+ });
323
+ }
324
+
325
+ /**
326
+ * Detect unresolved review feedback and hand off to /review — the shepherd
327
+ * DETECTS and hands off, NEVER resolves threads. Returns a NEEDS_REVIEW envelope
328
+ * (or an auth outcome), or null when nothing is actionable this pass.
329
+ *
330
+ * @param {object} args
331
+ * @returns {Promise<object|null>}
332
+ */
333
+ async function evaluateReviewFeedback({ adapter, owner, repo, pr, self, actions }) {
334
+ if (typeof adapter.readComments !== 'function') return null;
335
+ const commentsRead = await guardAuth(() => adapter.readComments({ owner, repo, pr }), actions);
336
+ if (commentsRead.outcome) return commentsRead.outcome;
337
+ const actionable = actionableComments(commentsRead.value, self);
338
+ if (actionable.length === 0) return null;
339
+ const CAP = 20;
340
+ const capped = actionable.length > CAP;
341
+ return result('NEEDS_REVIEW', {
342
+ actions,
343
+ commentCount: actionable.length,
344
+ capped,
345
+ sample: buildCommentSample(actionable, self, CAP),
346
+ reason: capped
347
+ ? `${actionable.length} unresolved review comments (showing the first ${CAP}). Too many to act on in one pass — handing off to /review. The shepherd never resolves threads.`
348
+ : `${actionable.length} unresolved review comment(s) need attention — handing off to /review. The shepherd never resolves threads.`,
349
+ });
350
+ }
351
+
352
+ /**
353
+ * Run a single bounded shepherd pass.
354
+ *
355
+ * @param {object} ctx
356
+ * @param {string} ctx.pr - PR number.
357
+ * @param {string} ctx.owner
358
+ * @param {string} ctx.repo
359
+ * @param {string} ctx.base - Base branch name (for protection lookup).
360
+ * @param {string} ctx.baseRef - Base ref for divergence (e.g. `origin/master`).
361
+ * @param {object} ctx.adapter - A validated pr-state adapter.
362
+ * @param {boolean} [ctx.autoRebase=false] - Opt-in Tier-B rebase.
363
+ * @param {boolean} [ctx.cleanTree=false] - Precondition for rebase.
364
+ * @param {number} [ctx.rerunBudget=3] - Max reruns across the shepherd session.
365
+ * @param {number} [ctx.rerunsUsed=0] - Reruns already spent.
366
+ * @returns {Promise<object>} decision envelope.
367
+ */
368
+ async function runShepherdPass(ctx) {
369
+ const {
370
+ pr,
371
+ owner,
372
+ repo,
373
+ base,
374
+ baseRef,
375
+ cwd,
376
+ adapter,
377
+ autoRebase = false,
378
+ cleanTree = false,
379
+ rerunBudget = 3,
380
+ rerunsUsed = 0,
381
+ // Read-only mode: compute the decision state but take NO mutating action
382
+ // (no rerun, no rebase). Used by `--pull` signal gathering. Additive and
383
+ // default OFF — existing callers are unaffected.
384
+ dryRun = false,
385
+ } = ctx;
386
+
387
+ const actions = [];
388
+
389
+ // Every read goes through the auth guard so 401/403-scope/rate-limit map to
390
+ // the documented PENDING/HARD_STOP states instead of escaping as a generic
391
+ // failure. Non-auth errors still propagate.
392
+
393
+ // --- Read PR/CI state FIRST so a merged/closed PR is detected as terminal
394
+ // even when the branch-protection (required-checks) read would fail with an
395
+ // auth/scope error — the scheduler must always get the terminal signal for a
396
+ // landed/closed PR. ---
397
+ const stateRead = await guardAuth(() => adapter.readState(pr), actions);
398
+ if (stateRead.outcome) return stateRead.outcome;
399
+ const startState = stateRead.value;
400
+ const startSha = startState.headSha;
401
+
402
+ // --- Lifecycle: a merged/closed PR is terminal. ---
403
+ const lifecycle = lifecycleOutcome(startState.state, actions);
404
+ if (lifecycle) return lifecycle;
405
+
406
+ // --- Required-checks set (only matters for non-terminal PRs); this is where
407
+ // auth/scope fails fast. ---
408
+ const requiredRead = await guardAuth(
409
+ () => adapter.readRequiredChecks({ owner, repo, base }),
410
+ actions,
411
+ );
412
+ if (requiredRead.outcome) return requiredRead.outcome;
413
+ const required = requiredRead.value;
414
+
415
+ // Unreadable required set → escalate, never declare merge-ready.
416
+ if (required === null) {
417
+ return result('ESCALATE', {
418
+ actions,
419
+ reason: 'Required-check set is unreadable (branch protection not accessible). Cannot determine merge readiness — escalating with the readable rollup.',
420
+ rollup: startState.checks,
421
+ });
422
+ }
423
+
424
+ const divergenceRead = await guardAuth(
425
+ () => adapter.readDivergence({ baseRef, cwd }),
426
+ actions,
427
+ );
428
+ if (divergenceRead.outcome) return divergenceRead.outcome;
429
+ const behind = divergenceRead.value.behind || 0;
430
+
431
+ const requiredChecks = startState.checks.filter((c) => required.includes(c.name));
432
+ const failedRequired = requiredChecks.filter(isFailed);
433
+ // Merge-readiness is evaluated against the REQUIRED set only (see helper).
434
+ const allRequiredGreen = allRequiredChecksGreen(required, startState.checks);
435
+
436
+ // Helper: re-read HEAD immediately before a mutating action. If HEAD moved
437
+ // since the pass started, abort (concurrency guard).
438
+ const headUnchanged = async () => {
439
+ const now = await adapter.readState(pr);
440
+ return now.headSha === startSha;
441
+ };
442
+
443
+ // --- Tier-C: hard conflict. ---
444
+ if (String(startState.mergeStateStatus).toUpperCase() === 'DIRTY') {
445
+ return result('ESCALATE', {
446
+ actions,
447
+ reason: 'Merge conflict (mergeStateStatus=DIRTY). A human must resolve the conflict.',
448
+ });
449
+ }
450
+
451
+ // --- Tier-A: flaky required check → rerun (capped, idempotent). ---
452
+ if (failedRequired.length > 0) {
453
+ return handleFailedRequired({
454
+ failedRequired, rerunsUsed, rerunBudget, headUnchanged, adapter, actions, dryRun,
455
+ });
456
+ }
457
+
458
+ // --- Behind base (Tier-B opt-in rebase, else escalate). In read-only mode we
459
+ // never rebase — force autoRebase off so the branch-behind path only escalates. ---
460
+ if (behind > 0) {
461
+ return handleBehindBase({
462
+ behind, autoRebase: dryRun ? false : autoRebase, cleanTree, adapter, baseRef, headUnchanged, actions,
463
+ });
464
+ }
465
+
466
+ // --- Review feedback: unresolved comments hand off to /review (never resolved
467
+ // here). Flood-capped so "too many comments" can't blow up a single pass. ---
468
+ const reviewOutcome = await evaluateReviewFeedback({
469
+ adapter, owner, repo, pr, self: ctx.self, actions,
470
+ });
471
+ if (reviewOutcome) return reviewOutcome;
472
+
473
+ // --- Terminal: all required green + not behind → merge-ready handoff. ---
474
+ if (allRequiredGreen) {
475
+ return result('MERGE_READY', {
476
+ actions,
477
+ reason: 'All required checks are green and the branch is up to date. Handing off to the human to merge in the GitHub UI — the shepherd never merges.',
478
+ });
479
+ }
480
+
481
+ // --- Otherwise: checks still pending/unknown → wait. ---
482
+ return result('PENDING', {
483
+ actions,
484
+ reason: 'Required checks are not all green yet (still pending) and nothing is actionable this pass. Awaiting the next scheduled pass.',
485
+ });
486
+ }
487
+
488
+ module.exports = {
489
+ runShepherdPass,
490
+ TERMINAL_STATES,
491
+ isGreen,
492
+ isFailed,
493
+ actionableComments,
494
+ };
@@ -0,0 +1,59 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * PR-state adapter contract validator.
5
+ *
6
+ * Mirrors the shape of `validateReviewAdapter` in lib/review-adapter.js but
7
+ * enforces its own `kind: 'pr-state'`. The pr-state adapter is a distinct SPI
8
+ * from the review adapter — it wraps read-only PR/CI state plus a small set of
9
+ * idempotent, reversible side-effects (rerun a failed check, post a status
10
+ * reply). It is never fed to `validateReviewAdapter`.
11
+ *
12
+ * State persists via GitHub PR comments/labels and git only.
13
+ *
14
+ * @module pr-state-validator
15
+ */
16
+
17
+ /** Methods every PR-state adapter must implement. */
18
+ const REQUIRED_PR_STATE_ADAPTER_METHODS = [
19
+ 'readState',
20
+ 'readRequiredChecks',
21
+ 'readDivergence',
22
+ 'rerunFailedChecks',
23
+ 'replyToThread',
24
+ ];
25
+
26
+ /**
27
+ * Validate that an object satisfies the PR-state adapter contract.
28
+ *
29
+ * @param {*} adapter - Candidate adapter.
30
+ * @returns {{ valid: boolean, errors: string[] }}
31
+ */
32
+ function validatePrStateAdapter(adapter) {
33
+ if (!adapter || typeof adapter !== 'object') {
34
+ return { valid: false, errors: ['adapter must be an object'] };
35
+ }
36
+
37
+ const errors = [];
38
+
39
+ if (!adapter.id || typeof adapter.id !== 'string') {
40
+ errors.push('id must be a non-empty string');
41
+ }
42
+
43
+ if (adapter.kind !== 'pr-state') {
44
+ errors.push('kind must be "pr-state"');
45
+ }
46
+
47
+ for (const method of REQUIRED_PR_STATE_ADAPTER_METHODS) {
48
+ if (typeof adapter[method] !== 'function') {
49
+ errors.push(`${method} must be a function`);
50
+ }
51
+ }
52
+
53
+ return { valid: errors.length === 0, errors };
54
+ }
55
+
56
+ module.exports = {
57
+ REQUIRED_PR_STATE_ADAPTER_METHODS,
58
+ validatePrStateAdapter,
59
+ };