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,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
+ };