forge-workflow 0.0.10 → 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 (454) 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 +150 -61
  8. package/CHANGELOG.md +681 -0
  9. package/CLAUDE.md +9 -118
  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 +461 -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 +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/{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 +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 +121 -0
  81. package/lib/beads-sync-scaffold.js +25 -101
  82. package/lib/codex-skills.js +51 -1
  83. package/lib/commands/_issue.js +741 -77
  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 +17 -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 +0 -1
  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 +838 -972
  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 +158 -21
  140. package/lib/commands/sync.js +34 -46
  141. package/lib/commands/team.js +4 -1
  142. package/lib/commands/test.js +43 -27
  143. package/lib/commands/update.js +2 -2
  144. package/lib/commands/upgrade.js +47 -0
  145. package/lib/commands/validate.js +43 -18
  146. package/lib/commands/worktree.js +307 -100
  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 +2 -2
  151. package/lib/deprecated-sync-cleanup.js +362 -0
  152. package/lib/detect-agent.js +2 -28
  153. package/lib/detect-worktree.js +35 -9
  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 +382 -11
  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/kernel/backing-issue.js +305 -0
  174. package/lib/kernel/broker.js +1218 -0
  175. package/lib/kernel/cli-broker-factory.js +130 -0
  176. package/lib/kernel/conflict-signal.js +82 -0
  177. package/lib/kernel/evaluators.js +195 -0
  178. package/lib/kernel/fs-class.js +495 -0
  179. package/lib/kernel/issue-command-contract.js +559 -0
  180. package/lib/kernel/issue-id-resolver.js +186 -0
  181. package/lib/kernel/lease-enforcer.js +158 -0
  182. package/lib/kernel/migrations.js +333 -0
  183. package/lib/kernel/planning-buckets-schema.js +109 -0
  184. package/lib/kernel/projection-jsonl-writer.js +450 -0
  185. package/lib/kernel/readiness-model.js +329 -0
  186. package/lib/kernel/schema.js +356 -0
  187. package/lib/kernel/sqlite-driver.js +2504 -0
  188. package/lib/kernel/taxonomy-validator.js +394 -0
  189. package/lib/lefthook-check.js +3 -2
  190. package/lib/lefthook-wiring.js +413 -0
  191. package/lib/mcp-config-renderer.js +288 -0
  192. package/lib/memory/graphiti-mcp.js +106 -0
  193. package/lib/memory/router.js +387 -0
  194. package/lib/memory/typed-api.js +102 -0
  195. package/lib/memory-digest.js +195 -0
  196. package/lib/merge-rules.js +395 -0
  197. package/lib/migrate-dry-run.js +466 -0
  198. package/lib/orientation.js +863 -0
  199. package/lib/package-manager-remediation.js +103 -0
  200. package/lib/package-root.js +381 -0
  201. package/lib/patch-intent.js +890 -0
  202. package/lib/plugin-catalog.js +3 -4
  203. package/lib/plugin-manager.js +0 -5
  204. package/lib/pr-bundle.js +186 -0
  205. package/lib/pr-monitor/differ.js +195 -0
  206. package/lib/pr-monitor/events.js +0 -0
  207. package/lib/pr-monitor/gather.js +124 -0
  208. package/lib/pr-monitor/journal.js +299 -0
  209. package/lib/pr-monitor/monitor.js +146 -0
  210. package/lib/pr-monitor/render-sticky.js +157 -0
  211. package/lib/pr-monitor/watch-lifecycle.js +95 -0
  212. package/lib/pr-monitor/watch.js +247 -0
  213. package/lib/pr-pull.js +1273 -0
  214. package/lib/pr-shepherd.js +494 -0
  215. package/lib/pr-state-validator.js +59 -0
  216. package/lib/preflight/gates.js +237 -0
  217. package/lib/preflight/runner.js +116 -0
  218. package/lib/project-discovery.js +0 -53
  219. package/lib/project-memory.js +99 -497
  220. package/lib/protected-path-manifest.js +281 -0
  221. package/lib/protected-state-surfaces.js +387 -0
  222. package/lib/release-readiness.js +2089 -0
  223. package/lib/reset.js +59 -45
  224. package/lib/review-adapter.js +68 -0
  225. package/lib/rules-sync.js +260 -0
  226. package/lib/runtime-health.js +241 -20
  227. package/lib/safety-config-renderer.js +268 -0
  228. package/lib/setup-action-log.js +1 -7
  229. package/lib/setup.js +27 -65
  230. package/lib/shell-utils.js +76 -6
  231. package/lib/skills-sync.js +330 -0
  232. package/lib/smart-status/scoring.js +17 -3
  233. package/lib/status/beads-snapshot.js +45 -2
  234. package/lib/status/presenter.js +169 -18
  235. package/lib/status/snapshot.js +186 -0
  236. package/lib/sync-backend.js +202 -0
  237. package/lib/untrusted-content.js +52 -0
  238. package/lib/upgrade-safety.js +199 -0
  239. package/lib/workflow/enforce-stage.js +296 -47
  240. package/lib/workflow/stage-transition.js +115 -0
  241. package/lib/workflow/stages.js +30 -6
  242. package/lib/workflow/state-manager.js +11 -22
  243. package/lib/workflow/state.js +23 -1
  244. package/lib/workflow-profiles.js +17 -5
  245. package/package.json +37 -35
  246. package/rules/documentation.md +19 -0
  247. package/rules/kernel-tracking.md +26 -0
  248. package/rules/security.md +22 -0
  249. package/rules/tdd.md +20 -0
  250. package/rules/workflow.md +27 -0
  251. package/scripts/auto-backing-issue.js +47 -0
  252. package/scripts/beads-context.sh +81 -57
  253. package/scripts/beads-upgrade-smoke.sh +24 -3
  254. package/scripts/bootstrap-windows-tools.sh +78 -0
  255. package/scripts/branch-protection.js +2 -3
  256. package/scripts/check-agents.js +34 -137
  257. package/scripts/commitlint.js +3 -1
  258. package/scripts/conflict-detect.sh +3 -0
  259. package/scripts/dep-guard.sh +22 -3
  260. package/scripts/file-index.sh +3 -0
  261. package/scripts/forge-team/lib/claim.sh +34 -18
  262. package/scripts/forge-team/lib/dashboard.sh +61 -86
  263. package/scripts/forge-team/lib/epic.sh +99 -263
  264. package/scripts/forge-team/lib/hooks.sh +26 -28
  265. package/scripts/forge-team/lib/identity.sh +4 -4
  266. package/scripts/forge-team/lib/sync-github.sh +49 -84
  267. package/scripts/forge-team/lib/verify.sh +93 -83
  268. package/scripts/forge-team/lib/workload.sh +41 -65
  269. package/scripts/forge-team/tests/claim.test.sh +25 -19
  270. package/scripts/forge-team/tests/dashboard.test.sh +31 -46
  271. package/scripts/forge-team/tests/epic.test.sh +52 -71
  272. package/scripts/forge-team/tests/hooks.test.sh +38 -50
  273. package/scripts/forge-team/tests/identity.test.sh +3 -3
  274. package/scripts/forge-team/tests/integration.test.sh +44 -66
  275. package/scripts/forge-team/tests/sync-github.test.sh +50 -83
  276. package/scripts/forge-team/tests/verify.test.sh +37 -46
  277. package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
  278. package/scripts/forge-team/tests/workload.test.sh +32 -66
  279. package/scripts/gen-command-manifest.js +153 -0
  280. package/scripts/gen-embedded-assets.mjs +129 -0
  281. package/scripts/install.ps1 +139 -0
  282. package/scripts/install.sh +268 -0
  283. package/scripts/lib/release-asset.mjs +84 -0
  284. package/scripts/parity-check.mjs +145 -0
  285. package/scripts/parity-check.test.mjs +58 -0
  286. package/scripts/pin-agentic-workflow-images.js +112 -0
  287. package/scripts/pr-coordinator.sh +3 -0
  288. package/scripts/preflight-sonar.eslint.config.mjs +44 -0
  289. package/scripts/preflight.sh +21 -94
  290. package/scripts/protected-state-check.js +104 -0
  291. package/scripts/smart-status.sh +60 -57
  292. package/scripts/spikes/config-race-bench.js +111 -0
  293. package/scripts/spikes/harness-capability-matrix.js +13 -0
  294. package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
  295. package/scripts/spikes/protected-path-manifest.js +20 -0
  296. package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
  297. package/scripts/sync-agent-skills.js +62 -0
  298. package/scripts/sync-utils.sh +3 -0
  299. package/scripts/test-ci-shard.js +13 -6
  300. package/scripts/test.js +95 -12
  301. package/skills/claim-safety/SKILL.md +102 -0
  302. package/skills/claim-safety/evals/evals.json +46 -0
  303. package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +44 -50
  304. package/skills/dev/evals/evals.json +50 -0
  305. package/skills/hermes-forge/SKILL.md +185 -0
  306. package/skills/hermes-forge/evals/evals.json +46 -0
  307. package/skills/issue-basics/SKILL.md +111 -0
  308. package/skills/issue-basics/evals/evals.json +46 -0
  309. package/skills/kernel/SKILL.md +166 -0
  310. package/skills/kernel/evals/evals.json +50 -0
  311. package/skills/memory/SKILL.md +102 -0
  312. package/skills/parallel-deep-research/SKILL.md +14 -11
  313. package/skills/parallel-deep-research/evals/evals.json +11 -27
  314. package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +132 -157
  315. package/skills/plan/evals/evals.json +42 -0
  316. package/skills/research/SKILL.md +195 -0
  317. package/skills/research/evals/evals.json +42 -0
  318. package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
  319. package/skills/review/evals/evals.json +42 -0
  320. package/skills/rollback/SKILL.md +110 -0
  321. package/skills/rollback/evals/evals.json +46 -0
  322. package/skills/rollback/references/methods.md +204 -0
  323. package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
  324. package/skills/shepherd/SKILL.md +66 -0
  325. package/skills/shepherd/evals/evals.json +42 -0
  326. package/{.github/prompts/ship.prompt.md → skills/ship/SKILL.md} +81 -45
  327. package/skills/ship/evals/evals.json +42 -0
  328. package/skills/smith/SKILL.md +142 -0
  329. package/skills/smith/evals/evals.json +46 -0
  330. package/skills/smith/references/autonomy-and-gates.md +94 -0
  331. package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
  332. package/skills/sonarcloud/evals/evals.json +46 -0
  333. package/skills/sonarcloud-analysis/SKILL.md +18 -13
  334. package/skills/sonarcloud-analysis/evals/evals.json +11 -15
  335. package/{.github/prompts/status.prompt.md → skills/status/SKILL.md} +20 -10
  336. package/skills/status/evals/evals.json +50 -0
  337. package/skills/triage-ready/SKILL.md +121 -0
  338. package/skills/triage-ready/evals/evals.json +42 -0
  339. package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
  340. package/skills/validate/evals/evals.json +42 -0
  341. package/skills/verify/SKILL.md +299 -0
  342. package/skills/verify/evals/evals.json +50 -0
  343. package/.claude/commands/dev.md +0 -345
  344. package/.claude/commands/plan.md +0 -566
  345. package/.claude/commands/premerge.md +0 -186
  346. package/.claude/commands/research.md +0 -42
  347. package/.claude/commands/review.md +0 -451
  348. package/.claude/commands/rollback.md +0 -721
  349. package/.claude/commands/ship.md +0 -213
  350. package/.claude/commands/sonarcloud.md +0 -152
  351. package/.claude/commands/status.md +0 -90
  352. package/.claude/commands/validate.md +0 -288
  353. package/.claude/commands/verify.md +0 -269
  354. package/.claude/rules/workflow.md +0 -121
  355. package/.cline/workflows/dev.md +0 -342
  356. package/.cline/workflows/plan.md +0 -563
  357. package/.cline/workflows/premerge.md +0 -183
  358. package/.cline/workflows/research.md +0 -39
  359. package/.cline/workflows/review.md +0 -448
  360. package/.cline/workflows/rollback.md +0 -718
  361. package/.cline/workflows/ship.md +0 -210
  362. package/.cline/workflows/sonarcloud.md +0 -146
  363. package/.cline/workflows/status.md +0 -87
  364. package/.cline/workflows/validate.md +0 -285
  365. package/.cline/workflows/verify.md +0 -266
  366. package/.codex/config.toml +0 -11
  367. package/.codex/skills/dev/SKILL.md +0 -345
  368. package/.codex/skills/plan/SKILL.md +0 -566
  369. package/.codex/skills/premerge/SKILL.md +0 -186
  370. package/.codex/skills/research/SKILL.md +0 -42
  371. package/.codex/skills/review/SKILL.md +0 -451
  372. package/.codex/skills/rollback/SKILL.md +0 -721
  373. package/.codex/skills/ship/SKILL.md +0 -213
  374. package/.codex/skills/sonarcloud/SKILL.md +0 -149
  375. package/.codex/skills/status/SKILL.md +0 -90
  376. package/.codex/skills/validate/SKILL.md +0 -288
  377. package/.codex/skills/verify/SKILL.md +0 -269
  378. package/.cursor/commands/dev.md +0 -342
  379. package/.cursor/commands/plan.md +0 -563
  380. package/.cursor/commands/premerge.md +0 -183
  381. package/.cursor/commands/research.md +0 -39
  382. package/.cursor/commands/review.md +0 -448
  383. package/.cursor/commands/ship.md +0 -210
  384. package/.cursor/commands/sonarcloud.md +0 -146
  385. package/.cursor/commands/status.md +0 -87
  386. package/.cursor/commands/validate.md +0 -285
  387. package/.cursor/commands/verify.md +0 -266
  388. package/.cursorrules +0 -149
  389. package/.github/prompts/premerge.prompt.md +0 -188
  390. package/.github/prompts/research.prompt.md +0 -44
  391. package/.github/prompts/rollback.prompt.md +0 -723
  392. package/.github/prompts/verify.prompt.md +0 -271
  393. package/.github/workflows/beads-to-github.yml +0 -89
  394. package/.github/workflows/github-to-beads.yml +0 -100
  395. package/.kilocode/workflows/dev.md +0 -346
  396. package/.kilocode/workflows/plan.md +0 -567
  397. package/.kilocode/workflows/premerge.md +0 -187
  398. package/.kilocode/workflows/research.md +0 -43
  399. package/.kilocode/workflows/review.md +0 -452
  400. package/.kilocode/workflows/rollback.md +0 -722
  401. package/.kilocode/workflows/ship.md +0 -214
  402. package/.kilocode/workflows/sonarcloud.md +0 -150
  403. package/.kilocode/workflows/status.md +0 -91
  404. package/.kilocode/workflows/validate.md +0 -289
  405. package/.kilocode/workflows/verify.md +0 -270
  406. package/.opencode/commands/dev.md +0 -345
  407. package/.opencode/commands/plan.md +0 -566
  408. package/.opencode/commands/premerge.md +0 -186
  409. package/.opencode/commands/research.md +0 -42
  410. package/.opencode/commands/review.md +0 -451
  411. package/.opencode/commands/rollback.md +0 -721
  412. package/.opencode/commands/ship.md +0 -213
  413. package/.opencode/commands/sonarcloud.md +0 -149
  414. package/.opencode/commands/status.md +0 -90
  415. package/.opencode/commands/validate.md +0 -288
  416. package/.opencode/commands/verify.md +0 -269
  417. package/.roo/commands/dev.md +0 -346
  418. package/.roo/commands/plan.md +0 -567
  419. package/.roo/commands/premerge.md +0 -187
  420. package/.roo/commands/research.md +0 -43
  421. package/.roo/commands/review.md +0 -452
  422. package/.roo/commands/rollback.md +0 -722
  423. package/.roo/commands/ship.md +0 -214
  424. package/.roo/commands/sonarcloud.md +0 -150
  425. package/.roo/commands/status.md +0 -91
  426. package/.roo/commands/validate.md +0 -289
  427. package/.roo/commands/verify.md +0 -270
  428. package/docs/BEADS_GITHUB_SYNC.md +0 -281
  429. package/docs/GREPTILE_SETUP.md +0 -400
  430. package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
  431. package/docs/SETUP.md +0 -663
  432. package/docs/VALIDATION.md +0 -363
  433. package/lib/agents/cline.plugin.json +0 -29
  434. package/lib/agents/copilot.plugin.json +0 -24
  435. package/lib/agents/kilocode.plugin.json +0 -22
  436. package/lib/agents/opencode.plugin.json +0 -23
  437. package/lib/agents/roo.plugin.json +0 -30
  438. package/lib/beads-bootstrap.js +0 -225
  439. package/lib/beads-health-check.js +0 -188
  440. package/lib/commands/commands-reset.js +0 -147
  441. package/opencode.json +0 -67
  442. package/scripts/beads-context.test.js +0 -584
  443. package/scripts/github-beads-sync/comment.mjs +0 -64
  444. package/scripts/github-beads-sync/config.mjs +0 -148
  445. package/scripts/github-beads-sync/github-api.mjs +0 -131
  446. package/scripts/github-beads-sync/index.mjs +0 -356
  447. package/scripts/github-beads-sync/label-mapper.mjs +0 -54
  448. package/scripts/github-beads-sync/mapping.mjs +0 -132
  449. package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
  450. package/scripts/github-beads-sync/reverse-sync.mjs +0 -162
  451. package/scripts/github-beads-sync/run-bd.mjs +0 -161
  452. package/scripts/github-beads-sync/sanitize.mjs +0 -121
  453. package/scripts/github-beads-sync.config.json +0 -26
  454. package/scripts/sync-commands.js +0 -600
@@ -0,0 +1,484 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * PR-state adapter (`kind: 'pr-state'`).
5
+ *
6
+ * Wraps read-only GitHub/git inspection plus a small set of idempotent,
7
+ * reversible side-effects used by the PR shepherd:
8
+ * - `readState` → `gh pr view --json ...`
9
+ * - `readRequiredChecks` → `gh api repos/{o}/{r}/branches/{base}/protection/required_status_checks`
10
+ * - `readDivergence` → `git rev-list --left-right --count {baseRef}...HEAD`
11
+ * - `detectConflicts` → `git merge-tree --write-tree {baseRef} HEAD` (predict-only)
12
+ * - `rerunFailedChecks` → `gh run rerun <id> --failed`
13
+ * - `replyToThread` → shell-out to `.claude/scripts/review-resolve.sh reply` (reply ONLY,
14
+ * never resolve — resolution stays with the semantic `/review` agent)
15
+ *
16
+ * This adapter is its own SPI; it does NOT extend the review adapter and is
17
+ * validated by `validatePrStateAdapter` (lib/pr-state-validator.js).
18
+ *
19
+ * It contains no merge or rebase machinery. Divergence handling (rebase) lives
20
+ * in the core/CLI behind an opt-in flag and is injected as `rebaseOntoBase`
21
+ * when enabled — it is never a default capability of this read surface.
22
+ *
23
+ * @module adapters/pr-state-adapter
24
+ */
25
+
26
+ const { execFileSync } = require('node:child_process');
27
+
28
+ // NOTE: `reviewThreads` is NOT a valid `gh pr view --json` field — requesting it
29
+ // makes `gh` exit non-zero ("Unknown JSON field"), which crashed readState on every
30
+ // real PR. Review threads are read separately via GraphQL in readComments().
31
+ const PR_VIEW_FIELDS = [
32
+ 'headRefOid',
33
+ 'mergeable',
34
+ 'mergeStateStatus',
35
+ 'state',
36
+ 'statusCheckRollup',
37
+ // `reviewDecision` (REVIEW_REQUIRED / CHANGES_REQUESTED / APPROVED / '') and
38
+ // `isDraft` are PR-level MERGE BLOCKERS the pull-signal payload surfaces — a
39
+ // draft PR or a missing/negative review decision blocks merge even when every
40
+ // check is green. Both are valid `gh pr view --json` fields.
41
+ 'reviewDecision',
42
+ 'isDraft',
43
+ ].join(',');
44
+
45
+ /**
46
+ * Classify an error thrown by the `gh` runner so the core can react.
47
+ * Returns `null` when the error is not auth/rate related.
48
+ *
49
+ * @param {Error} error
50
+ * @returns {{ class: string, retryAfter?: number } | null}
51
+ */
52
+ function classifyAuthError(error) {
53
+ if (!error) return null;
54
+ const status = error.httpStatus
55
+ || (typeof error.status === 'number' ? error.status : undefined);
56
+ const text = `${error.stderr || ''} ${error.message || ''}`;
57
+ const retryAfter = Number(error.retryAfter) || undefined;
58
+
59
+ if (status === 401 || /HTTP 401|bad credentials|token expired/i.test(text)) {
60
+ return { class: 'expired' };
61
+ }
62
+ if (status === 403 || /HTTP 403/i.test(text)) {
63
+ if (retryAfter || /rate limit|secondary rate/i.test(text)) {
64
+ return { class: 'rate-limit', retryAfter };
65
+ }
66
+ return { class: 'insufficient-scope' };
67
+ }
68
+ return null;
69
+ }
70
+
71
+ class PrStateAdapter {
72
+ /**
73
+ * @param {object} [options]
74
+ * @param {Function} [options.gh] - Runner for `gh` (cmd, args[]) → string.
75
+ * @param {Function} [options.git] - Runner for `git` (cmd, args[]) → string.
76
+ */
77
+ constructor(options = {}) {
78
+ this.id = options.id || 'pr-state-adapter';
79
+ this.kind = 'pr-state';
80
+ this.name = options.name || this.id;
81
+ const defaultRunner = (cmd, args, opts = {}) => execFileSync(cmd, args, {
82
+ encoding: 'utf8',
83
+ timeout: options.timeout || 30000,
84
+ ...(opts.cwd ? { cwd: opts.cwd } : {}),
85
+ });
86
+ this._gh = options.gh || defaultRunner;
87
+ this._git = options.git || defaultRunner;
88
+ }
89
+
90
+ /**
91
+ * Read normalized PR/CI state.
92
+ *
93
+ * @param {string} pr - PR number or URL.
94
+ * @returns {Promise<{ headSha: string, mergeable: string, mergeStateStatus: string, checks: object[], threads: object[] }>}
95
+ */
96
+ async readState(pr) {
97
+ const raw = this._gh('gh', ['pr', 'view', String(pr), '--json', PR_VIEW_FIELDS]);
98
+ const data = JSON.parse(raw || '{}');
99
+ const rollup = Array.isArray(data.statusCheckRollup) ? data.statusCheckRollup : [];
100
+ return {
101
+ headSha: data.headRefOid || '',
102
+ state: String(data.state || 'OPEN').toUpperCase(),
103
+ mergeable: data.mergeable || 'UNKNOWN',
104
+ mergeStateStatus: data.mergeStateStatus || 'UNKNOWN',
105
+ // PR-level merge blockers (surfaced by the pull signal). `reviewDecision` is
106
+ // '' when no review is required; normalized to null so consumers can treat
107
+ // "not required" and "unknown" uniformly. `isDraft` blocks merge outright.
108
+ reviewDecision: data.reviewDecision || null,
109
+ isDraft: Boolean(data.isDraft),
110
+ // The rollup mixes two GraphQL types: a CheckRun (name/status/conclusion/
111
+ // detailsUrl) and a legacy commit StatusContext (context/state/targetUrl —
112
+ // Vercel/Netlify/other deploy+quality bots). Both are normalized into ONE
113
+ // shape here so the failing/pending classification treats them identically;
114
+ // `state` fills the conclusion slot for a StatusContext (SUCCESS/FAILURE/
115
+ // ERROR/PENDING) and `targetUrl` fills the details link.
116
+ checks: rollup.map((check) => ({
117
+ name: check.name || check.context || '',
118
+ status: check.status || check.state || '',
119
+ conclusion: check.conclusion || check.state || '',
120
+ databaseId: check.databaseId,
121
+ detailsUrl: check.detailsUrl || check.targetUrl,
122
+ })),
123
+ // gh pr view cannot return review threads; the shepherd reads them via
124
+ // readComments() (GraphQL). Kept for return-shape stability, always empty here.
125
+ threads: [],
126
+ };
127
+ }
128
+
129
+ /**
130
+ * Read the branch-protection required-checks set.
131
+ *
132
+ * Returns `null` when the protection endpoint is unreadable (e.g. 403
133
+ * insufficient scope, the branch is not protected, or the payload shape is
134
+ * unexpected) so the caller can escalate rather than guess. Re-throws non-auth
135
+ * errors.
136
+ *
137
+ * @param {{ owner: string, repo: string, base: string }} ctx
138
+ * @returns {Promise<string[] | null>}
139
+ */
140
+ async readRequiredChecks({ owner, repo, base }) {
141
+ const apiPath = `repos/${owner}/${repo}/branches/${encodeURIComponent(base)}/protection/required_status_checks`;
142
+ try {
143
+ const raw = this._gh('gh', ['api', apiPath]);
144
+ const data = JSON.parse(raw || '{}');
145
+ if (Array.isArray(data.contexts)) return data.contexts;
146
+ if (Array.isArray(data.checks)) return data.checks.map((c) => c.context).filter(Boolean);
147
+ // Unexpected/changed payload shape — treat as unreadable, not "no required
148
+ // checks", so merge readiness is never computed from bad data.
149
+ return null;
150
+ } catch (error) {
151
+ const auth = classifyAuthError(error);
152
+ if (auth) {
153
+ // Unreadable protection (auth/scope/not-protected) — caller escalates.
154
+ return null;
155
+ }
156
+ throw error;
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Read ahead/behind divergence against the base ref.
162
+ *
163
+ * `cwd` is threaded through to the git runner so divergence is computed
164
+ * against the target worktree/checkout, not the process directory.
165
+ *
166
+ * @param {{ baseRef: string, cwd?: string }} ctx
167
+ * @returns {Promise<{ behind: number, ahead: number }>}
168
+ */
169
+ async readDivergence({ baseRef, cwd }) {
170
+ const out = this._git(
171
+ 'git',
172
+ ['rev-list', '--left-right', '--count', `${baseRef}...HEAD`],
173
+ cwd ? { cwd } : undefined,
174
+ );
175
+ const [behindRaw = '0', aheadRaw = '0'] = String(out).trim().split(/\s+/);
176
+ return {
177
+ behind: Number.parseInt(behindRaw, 10) || 0,
178
+ ahead: Number.parseInt(aheadRaw, 10) || 0,
179
+ };
180
+ }
181
+
182
+ /**
183
+ * Re-run failed CI for a workflow run (Tier-A: idempotent, reversible).
184
+ *
185
+ * @param {{ runId: string }} ctx
186
+ * @returns {Promise<void>}
187
+ */
188
+ async rerunFailedChecks({ runId }) {
189
+ this._gh('gh', ['run', 'rerun', String(runId), '--failed']);
190
+ }
191
+
192
+ /**
193
+ * Fetch the base ref from its remote so a subsequent `readDivergence`/
194
+ * `detectConflicts` compares HEAD against the CURRENT `origin/<base>`, not a
195
+ * stale local remote-tracking ref (audit A6: a stale ref reports a false
196
+ * `behind=0` / false "no conflict"). `baseRef` is `<remote>/<branch>` (e.g.
197
+ * `origin/master`); the remote and branch are split back out for `git fetch`.
198
+ * Read-only against the working tree — it only updates remote-tracking refs.
199
+ *
200
+ * @param {{ baseRef: string, cwd?: string }} ctx
201
+ * @returns {Promise<void>}
202
+ */
203
+ async fetchBase({ baseRef, cwd }) {
204
+ const ref = String(baseRef || '');
205
+ const slash = ref.indexOf('/');
206
+ // No `<remote>/<branch>` shape → nothing safe to fetch; leave refs as-is.
207
+ if (slash <= 0) return;
208
+ const remote = ref.slice(0, slash);
209
+ const branch = ref.slice(slash + 1);
210
+ this._git('git', ['fetch', remote, branch], cwd ? { cwd } : undefined);
211
+ }
212
+
213
+ /**
214
+ * Post a status reply to a review thread via the existing shell helper.
215
+ * Reply ONLY — never resolve (resolution is the semantic `/review` agent's job).
216
+ *
217
+ * @param {{ pr: string, commentId: string, message: string, script?: string }} ctx
218
+ * @returns {Promise<void>}
219
+ */
220
+ async replyToThread({ pr, commentId, message, script }) {
221
+ const scriptPath = script || '.claude/scripts/review-resolve.sh';
222
+ this._gh('bash', [scriptPath, 'reply', String(pr), String(commentId), String(message)]);
223
+ }
224
+
225
+ /**
226
+ * Read review threads as actionable comments. Uses GraphQL because
227
+ * `gh pr view --json reviewThreads` is unsupported. Resolved/outdated threads
228
+ * are returned WITH flags so the core can filter them (and exclude bots/self).
229
+ *
230
+ * @param {{ owner: string, repo: string, pr: string }} ctx
231
+ * @returns {Promise<object[]>}
232
+ */
233
+ async readComments({ owner, repo, pr }) {
234
+ // `id`/`path`/`line` are surfaced so a consumer (e.g. the monitor bundle in
235
+ // lib/pr-bundle.js) can hand an agent the thread id to resolve and the
236
+ // file/line to act on — not just the body. Added fields are backward
237
+ // compatible; the shepherd's existing thread filtering ignores them.
238
+ //
239
+ // Both connections are FULLY paginated (cursors, not a first:100 cap): a
240
+ // large PR must never silently drop a thread or a later reply, or the bundle
241
+ // would declare "complete" on partial data and the monitor would skip work.
242
+ const threads = this._fetchAllReviewThreads({ owner, repo, pr });
243
+ return threads.map((t) => {
244
+ const allComments = (t.comments && t.comments.nodes) || [];
245
+ return {
246
+ threadId: t.id || '',
247
+ path: t.path || null,
248
+ line: typeof t.line === 'number' ? t.line : null,
249
+ isResolved: !!t.isResolved,
250
+ isOutdated: !!t.isOutdated,
251
+ comments: allComments.map(c => ({
252
+ author: (c.author && c.author.login) || '',
253
+ body: c.body || '',
254
+ // REST comment id (needed to REPLY to the thread). `fullDatabaseId` is
255
+ // the string-encoded integer id; null when GitHub omits it.
256
+ commentId: c.fullDatabaseId ? String(c.fullDatabaseId) : null,
257
+ })),
258
+ };
259
+ });
260
+ }
261
+
262
+ /**
263
+ * Page through ALL `reviewThreads` (and each thread's nested `comments`) using
264
+ * GraphQL cursors. The per-page size stays 100, but pagination continues until
265
+ * `hasNextPage` is false, so coverage no longer caps at 100 threads/comments.
266
+ * A guard bounds the loop against a pathological/looping cursor.
267
+ *
268
+ * @param {{ owner: string, repo: string, pr: string }} ctx
269
+ * @returns {object[]} raw thread nodes (comments.nodes carries the full chain)
270
+ */
271
+ _fetchAllReviewThreads({ owner, repo, pr }) {
272
+ const query = 'query($o:String!,$n:String!,$pr:Int!,$after:String){repository(owner:$o,name:$n){pullRequest(number:$pr){reviewThreads(first:100,after:$after){pageInfo{hasNextPage endCursor} nodes{id isResolved isOutdated path line comments(first:100){pageInfo{hasNextPage endCursor} nodes{fullDatabaseId author{login} body}}}}}}}';
273
+ return this._paginateConnection(
274
+ (after) => this._ghGraphqlPage(query, { owner, repo, pr }, after)
275
+ ?.data?.repository?.pullRequest?.reviewThreads,
276
+ (t) => {
277
+ // A thread whose first comment page is truncated → fetch the rest.
278
+ if (t.comments?.pageInfo?.hasNextPage) {
279
+ t.comments = { nodes: this._fetchAllThreadComments(t.id, t.comments) };
280
+ }
281
+ return t;
282
+ },
283
+ );
284
+ }
285
+
286
+ /**
287
+ * Run ONE `gh api graphql` page request for an owner/repo/pr-shaped query and
288
+ * return the parsed JSON (or `{}`). `after` appends the opaque cursor only when
289
+ * present — omitting it on the first page tells GraphQL to start from the top.
290
+ * Extracted so the arg-building scaffold lives in exactly one place.
291
+ *
292
+ * @param {string} query
293
+ * @param {{ owner: string, repo: string, pr: string }} vars
294
+ * @param {string|null} after
295
+ * @returns {object}
296
+ */
297
+ _ghGraphqlPage(query, { owner, repo, pr }, after) {
298
+ const args = [
299
+ 'api', 'graphql', '-f', `query=${query}`,
300
+ '-F', `o=${owner}`, '-F', `n=${repo}`, '-F', `pr=${pr}`,
301
+ ];
302
+ if (after) args.push('-f', `after=${after}`);
303
+ return JSON.parse(this._gh('gh', args) || '{}');
304
+ }
305
+
306
+ /**
307
+ * Page through a cursored GraphQL connection until `hasNextPage` is false,
308
+ * accumulating `mapNode(node)` for every node across all pages. `runPage(after)`
309
+ * performs one page request and returns the connection object
310
+ * (`{ pageInfo, nodes }`) or a falsy value when unreadable. A page guard bounds
311
+ * the loop against a pathological/looping cursor. Shared by every paginated
312
+ * read so the do-while cursor scaffold is defined exactly once.
313
+ *
314
+ * @param {(after: string|null) => ({ pageInfo?: object, nodes?: object[] }|null|undefined)} runPage
315
+ * @param {(node: object) => object} mapNode
316
+ * @returns {object[]}
317
+ */
318
+ _paginateConnection(runPage, mapNode) {
319
+ const all = [];
320
+ let after = null;
321
+ let guard = 0;
322
+ const MAX_PAGES = 1000;
323
+ do {
324
+ const conn = runPage(after);
325
+ for (const node of (conn?.nodes || [])) all.push(mapNode(node));
326
+ after = conn?.pageInfo?.hasNextPage ? conn.pageInfo.endCursor : null;
327
+ guard += 1;
328
+ } while (after && guard < MAX_PAGES);
329
+ return all;
330
+ }
331
+
332
+ /**
333
+ * Page through the remaining `comments` of a single review thread (the first
334
+ * page already arrived inline with the thread). Resolves the thread by node id.
335
+ *
336
+ * @param {string} threadId
337
+ * @param {{ nodes: object[], pageInfo: { hasNextPage: boolean, endCursor: string } }} initial
338
+ * @returns {object[]} the full comment-node list (first page + all later pages)
339
+ */
340
+ _fetchAllThreadComments(threadId, initial) {
341
+ const query = 'query($id:ID!,$after:String){node(id:$id){... on PullRequestReviewThread{comments(first:100,after:$after){pageInfo{hasNextPage endCursor} nodes{fullDatabaseId author{login} body}}}}}';
342
+ const acc = [...((initial && initial.nodes) || [])];
343
+ let after = initial && initial.pageInfo && initial.pageInfo.hasNextPage ? initial.pageInfo.endCursor : null;
344
+ let guard = 0;
345
+ const MAX_PAGES = 1000;
346
+ while (after && guard < MAX_PAGES) {
347
+ const raw = this._gh('gh', [
348
+ 'api', 'graphql', '-f', `query=${query}`,
349
+ '-f', `id=${threadId}`, '-f', `after=${after}`,
350
+ ]);
351
+ const data = JSON.parse(raw || '{}');
352
+ const conn = data.data && data.data.node && data.data.node.comments;
353
+ acc.push(...((conn && conn.nodes) || []));
354
+ after = conn && conn.pageInfo && conn.pageInfo.hasNextPage ? conn.pageInfo.endCursor : null;
355
+ guard += 1;
356
+ }
357
+ return acc;
358
+ }
359
+
360
+ /**
361
+ * Read the PR's plain ISSUE comments (NOT review threads) — the surface where
362
+ * status/deploy/quality bots post their summaries: SonarCloud's Quality-Gate
363
+ * comment, Vercel/Netlify deployment comments, Codecov coverage comments. These
364
+ * are regular PR comments, never resolvable review threads, so `readComments`
365
+ * (reviewThreads GraphQL) never sees them. Fully paginated by cursor so a long
366
+ * PR never drops a bot's latest comment. Returns `{ author, body, createdAt }`.
367
+ *
368
+ * @param {{ owner: string, repo: string, pr: string }} ctx
369
+ * @returns {Promise<Array<{ author: string, body: string, createdAt: string }>>}
370
+ */
371
+ async readIssueComments({ owner, repo, pr }) {
372
+ // `author{__typename login}` surfaces the GraphQL actor TYPE ('Bot' vs 'User')
373
+ // so a non-human direct comment can be detected GENERICALLY — by mechanism,
374
+ // not by a hardcoded bot-name list — which fails closed for unknown bots.
375
+ const query = 'query($o:String!,$n:String!,$pr:Int!,$after:String){repository(owner:$o,name:$n){pullRequest(number:$pr){comments(first:100,after:$after){pageInfo{hasNextPage endCursor} nodes{fullDatabaseId author{__typename login} body createdAt}}}}}';
376
+ return this._paginateConnection(
377
+ (after) => this._ghGraphqlPage(query, { owner, repo, pr }, after)
378
+ ?.data?.repository?.pullRequest?.comments,
379
+ (c) => ({
380
+ // `id` is the stable REST comment id — the PR monitor keys `comment.posted`
381
+ // on it so a re-read never re-emits an already-seen comment. Falls back to
382
+ // author+createdAt when GitHub omits it.
383
+ id: c.fullDatabaseId ? String(c.fullDatabaseId) : `${c.author?.login || ''}:${c.createdAt || ''}`,
384
+ author: c.author?.login || '',
385
+ authorTypename: c.author?.__typename || '',
386
+ body: c.body || '',
387
+ createdAt: c.createdAt || '',
388
+ }),
389
+ );
390
+ }
391
+
392
+ /**
393
+ * Read submitted PR REVIEWS (not inline threads) — the latest review per
394
+ * author, each with the commit it was submitted against. This is the
395
+ * review-at-head signal that catches the #365 race: a CodeRabbit review from
396
+ * an earlier commit whose `commit.oid` no longer matches HEAD is STALE, so the
397
+ * post-push re-review is still pending. Uses GraphQL because `gh pr view`
398
+ * cannot return a review's target commit oid. Fully paginated by cursor.
399
+ *
400
+ * @param {{ owner: string, repo: string, pr: string }} ctx
401
+ * @returns {Promise<Array<{ author: string, state: string, submittedAt: string|null, commitOid: string|null, body: string }>>}
402
+ */
403
+ async readReviews({ owner, repo, pr }) {
404
+ const query = 'query($o:String!,$n:String!,$pr:Int!,$after:String){repository(owner:$o,name:$n){pullRequest(number:$pr){reviews(first:100,after:$after){pageInfo{hasNextPage endCursor} nodes{author{__typename login} state submittedAt commit{oid} body}}}}}';
405
+ const all = this._paginateConnection(
406
+ (after) => this._ghGraphqlPage(query, { owner, repo, pr }, after)
407
+ ?.data?.repository?.pullRequest?.reviews,
408
+ (r) => ({
409
+ author: String(r.author?.login || '').toLowerCase(),
410
+ authorTypename: r.author?.__typename || '',
411
+ state: String(r.state || '').toUpperCase(),
412
+ submittedAt: r.submittedAt || null,
413
+ commitOid: r.commit?.oid || null,
414
+ body: r.body || '',
415
+ }),
416
+ );
417
+ // Keep only the LATEST review per author (first:100 yields oldest→newest, so
418
+ // a later entry supersedes an earlier one from the same login).
419
+ const latest = new Map();
420
+ for (const r of all) {
421
+ if (!r.author) continue;
422
+ latest.set(r.author, r);
423
+ }
424
+ return Array.from(latest.values());
425
+ }
426
+
427
+ /**
428
+ * Read the HEAD commit's committed timestamp (epoch ms), or null when
429
+ * unreadable. Anchors the pull-signal settle window to when the code last
430
+ * changed — so a freshly-pushed PR whose CI passes BEFORE the review bots have
431
+ * even run is REVIEW-PENDING, not CLEAN (#365 "never-ran" variant), and a bot
432
+ * comment older than the last push is treated as stale, not a live blocker.
433
+ *
434
+ * @param {{ pr: string }} ctx
435
+ * @returns {Promise<number|null>} epoch ms of the head commit, or null.
436
+ */
437
+ async readHeadCommitTime({ pr }) {
438
+ const raw = this._gh('gh', ['pr', 'view', String(pr), '--json', 'commits', '-q', '.commits[-1].committedDate']);
439
+ const t = Date.parse(String(raw || '').trim());
440
+ return Number.isFinite(t) ? t : null;
441
+ }
442
+
443
+ /**
444
+ * Predict files that would conflict when merging `baseRef` into HEAD WITHOUT
445
+ * touching the working tree, via `git merge-tree --write-tree`. Returns
446
+ * `{ supported: false, reason }` when conflict prediction is unavailable (git
447
+ * < 2.38, an unreadable ref, or a non-conflict error) so the bundle degrades
448
+ * gracefully rather than failing the whole gather.
449
+ *
450
+ * @param {{ baseRef: string, cwd?: string }} ctx
451
+ * @returns {Promise<{ supported: boolean, conflicted?: boolean, files?: string[], reason?: string }>}
452
+ */
453
+ async detectConflicts({ baseRef, cwd }) {
454
+ const opts = cwd ? { cwd } : undefined;
455
+ try {
456
+ // Exit 0 = clean merge. `--name-only` reduces the conflict report to bare
457
+ // paths; `--no-messages` suppresses the human-readable conflict prose.
458
+ this._git(
459
+ 'git',
460
+ ['merge-tree', '--write-tree', '--name-only', '--no-messages', baseRef, 'HEAD'],
461
+ opts,
462
+ );
463
+ return { supported: true, conflicted: false, files: [] };
464
+ } catch (error) {
465
+ // Exit 1 = mergeable-with-conflicts: stdout is the written tree OID on the
466
+ // first line followed by the conflicted paths. Any other status (e.g. 128
467
+ // for an unknown ref, or `--write-tree` unsupported on git < 2.38) means
468
+ // conflict prediction is unsupported in this environment.
469
+ const status = typeof error.status === 'number' ? error.status : undefined;
470
+ const stdout = String(error.stdout || '');
471
+ if (status === 1 && stdout.trim()) {
472
+ const lines = stdout.split('\n').map((s) => s.trim()).filter(Boolean);
473
+ return { supported: true, conflicted: true, files: lines.slice(1) };
474
+ }
475
+ return { supported: false, reason: error.message || 'git merge-tree unavailable' };
476
+ }
477
+ }
478
+ }
479
+
480
+ module.exports = {
481
+ PrStateAdapter,
482
+ classifyAuthError,
483
+ PR_VIEW_FIELDS,
484
+ };
@@ -0,0 +1,126 @@
1
+ const YAML = require('yaml');
2
+
3
+ const ADOPTION_VERSION = '0.0.15';
4
+ const RUNTIME_ANCESTRY = 'forge.runtimeGraph.currentCommandFlow@0.0.17';
5
+ const WORKFLOW_PHASES = Object.freeze(['plan', 'dev', 'validate', 'ship']);
6
+ const WORKFLOW_GATES = Object.freeze(['gate.plan-exit', 'gate.dev-exit', 'gate.validate-exit', 'gate.ship-entry']);
7
+
8
+ function enabledEntries(names, enabled = true) {
9
+ return Object.fromEntries(names.map(name => [name, { enabled }]));
10
+ }
11
+
12
+ function issueAdapter(enabled) {
13
+ if (!enabled) {
14
+ return {
15
+ enabled: false,
16
+ primary: 'none',
17
+ mirrors: [],
18
+ };
19
+ }
20
+
21
+ return {
22
+ enabled: true,
23
+ primary: 'kernel',
24
+ mirrors: ['github'],
25
+ };
26
+ }
27
+
28
+ function harnessAdapter(targets) {
29
+ return {
30
+ enabled: targets.length > 0,
31
+ targets,
32
+ };
33
+ }
34
+
35
+ function adoptionConfig({ gatesEnabled, issueEnabled, harnessTargets, protectedPaths, rails }) {
36
+ return {
37
+ ...(rails ? { rails } : {}),
38
+ workflow: {
39
+ phases: enabledEntries(WORKFLOW_PHASES),
40
+ gates: enabledEntries(WORKFLOW_GATES, gatesEnabled),
41
+ },
42
+ adapters: {
43
+ issue: issueAdapter(issueEnabled),
44
+ harness: harnessAdapter(harnessTargets),
45
+ },
46
+ protectedPaths,
47
+ };
48
+ }
49
+
50
+ const PROFILE_CONFIGS = Object.freeze({
51
+ minimal: {
52
+ description: 'Config-only adoption for a clean repository.',
53
+ config: adoptionConfig({
54
+ gatesEnabled: false,
55
+ issueEnabled: false,
56
+ harnessTargets: [],
57
+ protectedPaths: ['.forge/config.yaml'],
58
+ }),
59
+ },
60
+ standard: {
61
+ description: 'Default Forge command flow with Kernel issue tracking and GitHub mirror metadata.',
62
+ config: adoptionConfig({
63
+ gatesEnabled: true,
64
+ issueEnabled: true,
65
+ harnessTargets: ['codex', 'claude', 'cursor'],
66
+ protectedPaths: ['.forge/config.yaml', 'AGENTS.md'],
67
+ }),
68
+ },
69
+ full: {
70
+ description: 'Full adoption scaffold with all default rails explicit.',
71
+ config: adoptionConfig({
72
+ rails: {
73
+ tdd_intent: { enabled: true },
74
+ secret_scan: { enabled: true },
75
+ branch_protection: { enabled: true },
76
+ signed_commits: { enabled: true },
77
+ schema_integrity: { enabled: true },
78
+ },
79
+ gatesEnabled: true,
80
+ issueEnabled: true,
81
+ harnessTargets: ['codex', 'claude', 'cursor'],
82
+ protectedPaths: ['.forge/config.yaml', 'AGENTS.md', '.github/workflows/**'],
83
+ }),
84
+ },
85
+ });
86
+
87
+ const ADOPTION_PROFILE_NAMES = Object.freeze(Object.keys(PROFILE_CONFIGS));
88
+
89
+ function clone(value) {
90
+ return structuredClone(value);
91
+ }
92
+
93
+ function assertProfile(profile) {
94
+ if (!Object.hasOwn(PROFILE_CONFIGS, profile)) {
95
+ throw new Error(`Unknown adoption profile '${profile}'. Available profiles: ${ADOPTION_PROFILE_NAMES.join(', ')}`);
96
+ }
97
+ }
98
+
99
+ function buildAdoptionConfig(profile = 'standard') {
100
+ assertProfile(profile);
101
+ const definition = PROFILE_CONFIGS[profile];
102
+ return {
103
+ template: {
104
+ kind: 'forge.adoptionTemplate',
105
+ version: ADOPTION_VERSION,
106
+ profile,
107
+ description: definition.description,
108
+ ancestry: [
109
+ RUNTIME_ANCESTRY,
110
+ `forge.profile.${profile}@${ADOPTION_VERSION}`,
111
+ ],
112
+ },
113
+ ...clone(definition.config),
114
+ };
115
+ }
116
+
117
+ function renderAdoptionConfigYaml(profile = 'standard') {
118
+ return YAML.stringify(buildAdoptionConfig(profile));
119
+ }
120
+
121
+ module.exports = {
122
+ ADOPTION_PROFILE_NAMES,
123
+ ADOPTION_VERSION,
124
+ buildAdoptionConfig,
125
+ renderAdoptionConfigYaml,
126
+ };
@@ -94,17 +94,13 @@ Legacy `capabilities.hooks: true/false` values are still accepted and normalized
94
94
 
95
95
  ## Supported Agents
96
96
 
97
- Currently supported AI coding agents:
97
+ Forge currently supports Claude Code, Codex, and Cursor. Hermes support is planned.
98
98
 
99
99
  | Agent | ID | Description |
100
100
  |-------|----|-----------|
101
101
  | Claude Code | `claude` | Anthropic's CLI agent |
102
+ | Codex | `codex` | OpenAI's CLI agent |
102
103
  | Cursor | `cursor` | AI-first code editor |
103
- | Kilo Code | `kilocode` | VS Code extension |
104
- | GitHub Copilot | `copilot` | GitHub's AI assistant |
105
- | OpenCode | `opencode` | Open-source agent |
106
- | Cline | `cline` | VS Code agent extension |
107
- | Roo Code | `roo` | Cline fork with modes |
108
104
 
109
105
  ## Adding a New Agent
110
106
 
@@ -5,23 +5,18 @@
5
5
  "description": "Anthropic's CLI agent",
6
6
  "homepage": "https://claude.ai/code",
7
7
  "capabilities": {
8
- "commands": true,
8
+ "commands": false,
9
9
  "skills": true,
10
10
  "hooks": true
11
11
  },
12
12
  "directories": {
13
- "commands": ".claude/commands",
14
- "rules": ".claude/rules",
15
- "skills": ".claude/skills/forge-workflow",
13
+ "skills": ".claude/skills",
16
14
  "scripts": ".claude/scripts"
17
15
  },
18
16
  "files": {
19
- "rootConfig": "CLAUDE.md",
20
- "skillDefinition": ".claude/skills/forge-workflow/SKILL.md"
17
+ "rootConfig": "CLAUDE.md"
21
18
  },
22
19
  "setup": {
23
- "copyCommands": true,
24
- "copyRules": true,
25
20
  "copyScripts": true,
26
21
  "createSkill": true
27
22
  }
@@ -7,7 +7,15 @@
7
7
  "capabilities": {
8
8
  "commands": true,
9
9
  "skills": true,
10
- "hooks": false
10
+ "hooks": true,
11
+ "hookEvents": [
12
+ "SessionStart",
13
+ "UserPromptSubmit",
14
+ "PreToolUse",
15
+ "PermissionRequest",
16
+ "PostToolUse",
17
+ "Stop"
18
+ ]
11
19
  },
12
20
  "directories": {
13
21
  "skills": ".codex/skills"