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
@@ -27,7 +27,6 @@ const STAGES = Object.freeze({
27
27
  VALIDATE: 'validate',
28
28
  SHIP: 'ship',
29
29
  REVIEW: 'review',
30
- PREMERGE: 'premerge',
31
30
  });
32
31
 
33
32
  const BUDGET_MODES = Object.freeze({
@@ -326,12 +325,12 @@ const CATALOG = Object.freeze({
326
325
  install: { method: 'config', cmd: 'Self-hosted (qodo.ai)' },
327
326
  },
328
327
 
329
- // ── Merge ──
328
+ // ── Release automation (ship-time) ──
330
329
  changesets: {
331
330
  name: 'Changesets',
332
331
  type: 'cli',
333
332
  tier: 'free',
334
- stage: 'premerge',
333
+ stage: 'ship',
335
334
  description: 'Versioning and changelog management',
336
335
  detectWhen: ['file:package.json'],
337
336
  install: { method: 'npm', cmd: 'bun add -D @changesets/cli', dev: true },
@@ -340,7 +339,7 @@ const CATALOG = Object.freeze({
340
339
  name: 'Release Please',
341
340
  type: 'config',
342
341
  tier: 'free',
343
- stage: 'premerge',
342
+ stage: 'ship',
344
343
  description: 'Automated release PRs by Google',
345
344
  detectWhen: [],
346
345
  install: { method: 'config', cmd: 'GitHub Action (google-github-actions/release-please-action)' },
@@ -30,12 +30,7 @@ const DEFAULT_SUPPORT_STATUS = 'supported';
30
30
  const SURFACE_BY_AGENT_ID = Object.freeze({
31
31
  claude: 'cli-first',
32
32
  codex: 'cli-first',
33
- opencode: 'cli-first',
34
- cline: 'editor-native',
35
33
  cursor: 'editor-native',
36
- copilot: 'editor-native',
37
- kilocode: 'editor-native',
38
- roo: 'editor-native',
39
34
  });
40
35
 
41
36
  function isPlainObject(value) {
@@ -0,0 +1,186 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * PR-state bundle — the "everything ready for the agent" gather half of the
5
+ * monitor (kernel 9b24257f, increment 1).
6
+ *
7
+ * `gatherPrBundle` aggregates the COMPLETE, read-only state of an open PR into
8
+ * one structured object a fixer-agent can act on WITHOUT fetching anything
9
+ * itself: every unresolved review thread (any author), the merge state, the
10
+ * full CI rollup (tagged with required-check membership), branch divergence, and
11
+ * predicted merge conflicts.
12
+ *
13
+ * This module is pure aggregation over a validated pr-state adapter. It makes NO
14
+ * decisions and takes NO actions — it never merges, never resolves threads,
15
+ * never writes code. The decision state machine stays in lib/pr-shepherd.js; the
16
+ * agent dispatch, the continuous loop, and close-on-merge that will consume this
17
+ * bundle are follow-ups, not part of this increment.
18
+ *
19
+ * @module pr-bundle
20
+ */
21
+
22
+ const { isGreen, isFailed } = require('./pr-shepherd');
23
+
24
+ /**
25
+ * Map an unresolved review thread to the agent-input comment shape. The named
26
+ * `author`/`body` come from the thread opener (the canonical anchor), but the
27
+ * FULL `comments` array is carried too — so a later human reply on a
28
+ * bot-opened thread is never hidden from the fixer-agent (which fetches
29
+ * nothing and acts only on this input). Tolerates adapters/payloads that don't
30
+ * surface every field.
31
+ *
32
+ * @param {object} thread
33
+ * @returns {{ author: string, path: string|null, line: number|null, body: string, threadId: string|null, comments: {author: string, body: string}[] }}
34
+ */
35
+ function toUnresolvedComment(thread) {
36
+ const comments = (Array.isArray(thread.comments) ? thread.comments : []).map((c) => ({
37
+ author: String((c.author && c.author.login) || c.author || ''),
38
+ body: String(c.body || ''),
39
+ }));
40
+ const first = comments[0] || {};
41
+ return {
42
+ author: first.author || String(thread.author || ''),
43
+ path: thread.path || null,
44
+ line: typeof thread.line === 'number' ? thread.line : null,
45
+ body: first.body || String(thread.body || ''),
46
+ threadId: thread.threadId || thread.id || null,
47
+ comments,
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Read ALL unresolved review threads (any author) and map them to the agent
53
+ * comment shape.
54
+ *
55
+ * Returns `{ comments, available, error }` — NOT a bare array — so a consumer can
56
+ * tell three states apart that a `[]`-on-everything collapse would conflate:
57
+ * - capability absent → available:false, error:null (adapter has no readComments)
58
+ * - read succeeded → available:true, error:null (comments is authoritative)
59
+ * - read THREW → available:false, error:msg (comments unreadable — NOT "none")
60
+ *
61
+ * The third case is the critical one: a monitor must never treat "GitHub comments
62
+ * were unreadable" as "this PR has no unresolved feedback" and silently skip work.
63
+ * A failed read still degrades gracefully (the rest of the bundle survives), but
64
+ * the failure is surfaced rather than swallowed.
65
+ *
66
+ * @returns {Promise<{ comments: object[], available: boolean, error: string|null }>}
67
+ */
68
+ async function gatherUnresolvedComments(adapter, { owner, repo, pr }) {
69
+ if (typeof adapter.readComments !== 'function') {
70
+ return { comments: [], available: false, error: null };
71
+ }
72
+ try {
73
+ const threads = await adapter.readComments({ owner, repo, pr });
74
+ const comments = (Array.isArray(threads) ? threads : [])
75
+ .filter((t) => !(t.isResolved || t.resolved))
76
+ .map(toUnresolvedComment);
77
+ return { comments, available: true, error: null };
78
+ } catch (err) {
79
+ // A failed comment read must not sink the rest of the bundle, but it MUST be
80
+ // distinguishable from "no unresolved threads" — preserve the error signal.
81
+ return { comments: [], available: false, error: err.message || 'comment read failed' };
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Predict merge conflicts. `detectConflicts` is optional on the adapter; when
87
+ * absent or failing, report it as unsupported rather than throwing.
88
+ */
89
+ async function gatherConflicts(adapter, { baseRef, cwd }) {
90
+ if (typeof adapter.detectConflicts !== 'function') {
91
+ return { supported: false, reason: 'adapter has no detectConflicts capability' };
92
+ }
93
+ try {
94
+ return await adapter.detectConflicts({ baseRef, cwd });
95
+ } catch (error) {
96
+ return { supported: false, reason: error.message || 'conflict detection failed' };
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Tag the CI rollup with required-check membership and split out failing/pending.
102
+ *
103
+ * `requiredSet` is the branch-protection required set (array), or `null` when it
104
+ * is unreadable — in which case `required` is left `null` per check rather than
105
+ * `false`, so the agent is never told "nothing is required" on bad data.
106
+ *
107
+ * @param {object[]} checks - Normalized rollup from `readState`.
108
+ * @param {string[]|null} requiredSet
109
+ * @returns {{ checks: object[], failing: object[], pending: object[] }}
110
+ */
111
+ function buildCi(checks, requiredSet) {
112
+ const isRequired = (name) => (requiredSet === null ? null : requiredSet.includes(name));
113
+ const tagged = (Array.isArray(checks) ? checks : []).map((c) => ({
114
+ name: c.name || '',
115
+ status: c.status || '',
116
+ conclusion: c.conclusion || '',
117
+ required: isRequired(c.name || ''),
118
+ detailsUrl: c.detailsUrl || null,
119
+ }));
120
+ return {
121
+ checks: tagged,
122
+ failing: tagged.filter((c) => isFailed(c)),
123
+ pending: tagged.filter((c) => !isGreen(c) && !isFailed(c)),
124
+ };
125
+ }
126
+
127
+ /**
128
+ * Gather the complete read-only PR-state bundle for the monitor's fixer-agent.
129
+ *
130
+ * @param {object} ctx
131
+ * @param {string} ctx.pr
132
+ * @param {string} ctx.owner
133
+ * @param {string} ctx.repo
134
+ * @param {string} ctx.base - Base BRANCH name (for branch-protection lookup).
135
+ * @param {string} ctx.baseRef - Base REF for divergence/conflicts (e.g. `origin/master`).
136
+ * @param {string} [ctx.cwd]
137
+ * @param {object} ctx.adapter - A validated pr-state adapter.
138
+ * @returns {Promise<object>} the structured bundle.
139
+ */
140
+ async function gatherPrBundle({ pr, owner, repo, base, baseRef, cwd, adapter }) {
141
+ if (!adapter || typeof adapter.readState !== 'function') {
142
+ throw new Error('gatherPrBundle requires a pr-state adapter with readState');
143
+ }
144
+
145
+ const state = await adapter.readState(pr);
146
+ // NOTE: required-check lookup needs the base BRANCH name (`base`), not the
147
+ // remote ref (`baseRef`); passing the ref builds a bad protection path and
148
+ // silently yields a null required set.
149
+ const requiredRaw = await adapter.readRequiredChecks({ owner, repo, base });
150
+ const divergence = await adapter.readDivergence({ baseRef, cwd });
151
+ const comments = await gatherUnresolvedComments(adapter, { owner, repo, pr });
152
+ const conflicts = await gatherConflicts(adapter, { baseRef, cwd });
153
+
154
+ const requiredSet = requiredRaw === undefined ? null : requiredRaw;
155
+
156
+ return {
157
+ pr: String(pr),
158
+ owner,
159
+ repo,
160
+ base,
161
+ baseRef,
162
+ unresolvedComments: comments.comments,
163
+ // Availability signal for the comment read: when false with a non-null error,
164
+ // the threads were UNREADABLE (not absent) — a monitor must not treat the empty
165
+ // unresolvedComments list as "no feedback" and skip work.
166
+ unresolvedCommentsAvailable: comments.available,
167
+ unresolvedCommentsError: comments.error,
168
+ mergeState: {
169
+ mergeable: state.mergeable || 'UNKNOWN',
170
+ mergeStateStatus: state.mergeStateStatus || 'UNKNOWN',
171
+ state: String(state.state || 'OPEN').toUpperCase(),
172
+ },
173
+ ci: buildCi(state.checks, requiredSet),
174
+ branch: {
175
+ ahead: divergence.ahead || 0,
176
+ behind: divergence.behind || 0,
177
+ },
178
+ conflicts,
179
+ };
180
+ }
181
+
182
+ module.exports = {
183
+ gatherPrBundle,
184
+ buildCi,
185
+ toUnresolvedComment,
186
+ };
@@ -0,0 +1,175 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * PR-monitor Tier-2 auto-actions — the SAFE, fail-closed *action* half of the
5
+ * shepherd (issue addf5297, epic c2d398e5). Tier-1 (lib/pr-pull.js computeVerdict
6
+ * + the pr-monitor workflow) LABELS a PR's state but takes no action, so PRs that
7
+ * only need "master merged in" sit untended until a human/agent nudges them.
8
+ *
9
+ * This module decides — PURELY, from the SAME `forge shepherd <pr> --pull --json`
10
+ * payload the monitor already computes — whether the monitor may take one of two
11
+ * surface-safe actions:
12
+ * 1. `updateBranch` — merge base into an OTHERWISE-CLEAN-but-BEHIND PR (the
13
+ * "last mile" case). This is the highest-value, safest action: it clears the
14
+ * BEHIND churn without ever touching a PR that has a real blocker.
15
+ * 2. `rerunFlaky` — re-run a required check whose failure is INFRASTRUCTURAL
16
+ * (cancelled / timed-out / stale / startup-failure), never a real test
17
+ * FAILURE/ERROR.
18
+ *
19
+ * It NEVER merges, NEVER resolves review threads, NEVER force-pushes, NEVER edits
20
+ * code. It only decides; the workflow executes the `gh` calls (and owns
21
+ * per-head-SHA idempotency markers). Every gate below is fail-CLOSED: any missing
22
+ * field, degraded read, real failure, fork, draft, or unclassifiable signal
23
+ * yields `should:false`.
24
+ *
25
+ * @module pr-monitor/auto-actions
26
+ */
27
+
28
+ /**
29
+ * Check conclusions that are INFRASTRUCTURAL flakes — a re-run may legitimately
30
+ * turn them green. Mirrors lib/pr-shepherd.js `isFailed`'s not-green terminal
31
+ * conclusions MINUS the genuinely-broken ones (FAILURE/ERROR/ACTION_REQUIRED).
32
+ */
33
+ const INFRA_CONCLUSIONS = new Set(['CANCELLED', 'TIMED_OUT', 'STALE', 'STARTUP_FAILURE']);
34
+
35
+ /** Conclusions that mean the code is genuinely broken — NEVER auto-rerun these. */
36
+ const REAL_FAILURE_CONCLUSIONS = new Set(['FAILURE', 'ERROR', 'ACTION_REQUIRED']);
37
+
38
+ /**
39
+ * Pull the numeric Actions run id from a job/run "details" URL
40
+ * (`.../actions/runs/<run>/job/<job>` or `.../actions/runs/<run>`). Returns null
41
+ * when absent — a null run id fails the rerun decision closed.
42
+ *
43
+ * @param {string} url
44
+ * @returns {string | null}
45
+ */
46
+ function runIdFromUrl(url) {
47
+ const m = String(url || '').match(/\/runs\/(\d+)/);
48
+ return m ? m[1] : null;
49
+ }
50
+
51
+ /**
52
+ * A payload is DEGRADED (some verdict-relevant read failed or the head moved
53
+ * mid-gather) when its evidence lists unreadable sources or a torn read. Acting
54
+ * on a degraded gather could act on stale/false state, so both actions fail
55
+ * closed on it — even though `verdict==='BEHIND'` already implies a clean read,
56
+ * this stays an explicit, independent guard.
57
+ *
58
+ * @param {object} payload
59
+ * @returns {boolean}
60
+ */
61
+ function isDegraded(payload) {
62
+ const ev = (payload && payload.evidence) || {};
63
+ const unreadable = Array.isArray(ev.unreadable) ? ev.unreadable : [];
64
+ return unreadable.length > 0 || ev.tornRead === true;
65
+ }
66
+
67
+ /**
68
+ * Decide whether to auto-update (merge base into) an otherwise-clean-but-BEHIND
69
+ * PR. Fires ONLY for the "last mile" case:
70
+ * - verdict is exactly `BEHIND` (which itself guarantees rank-1 UNKNOWN and
71
+ * rank-2 BLOCKED-CONFLICT did NOT fire — i.e. the read was clean and there is
72
+ * no conflict);
73
+ * - the PR is NOT a draft and NOT a fork (a base-repo token cannot push a fork
74
+ * branch, and forks are out of scope);
75
+ * - the read is not degraded;
76
+ * - and the ONLY blocker is the behind-base one — every other blocker type
77
+ * (failing/missing/skipped/pending required checks, bot-status gates,
78
+ * unresolved threads, changes-requested / review-required, conflict) is
79
+ * absent. `blockers[]` is computed independently of the verdict precedence,
80
+ * so it still lists lower-precedence blockers that `BEHIND` masks — which is
81
+ * exactly why we key on it rather than on the single verdict string.
82
+ *
83
+ * @param {object} payload - the `--pull --json` payload.
84
+ * @param {{ isFork?: boolean }} [opts]
85
+ * @returns {{ should: boolean, reason: string }}
86
+ */
87
+ function decideUpdateBranch(payload, opts = {}) {
88
+ const skip = (reason) => ({ should: false, reason });
89
+ if (!payload || typeof payload !== 'object') return skip('no payload — fail closed');
90
+ if (opts.isFork) return skip('fork PR — a base-repo token cannot update a fork branch');
91
+ if (isDegraded(payload)) return skip('degraded/torn read — fail closed');
92
+ if (payload.verdict !== 'BEHIND') return skip(`verdict ${payload.verdict || 'UNKNOWN'} is not BEHIND`);
93
+ if (payload.draft === true) return skip('draft PR — not ready to advance');
94
+ if (!Array.isArray(payload.blockers)) return skip('blockers[] unavailable — fail closed');
95
+ const others = payload.blockers.filter((b) => b && b.type !== 'behind');
96
+ if (others.length > 0) {
97
+ return skip(`other blocker(s) present: ${others.map((b) => b.type).join(', ')}`);
98
+ }
99
+ return { should: true, reason: 'otherwise-clean-behind — only blocker is behind-base; merge base in' };
100
+ }
101
+
102
+ /**
103
+ * Decide whether to re-run flaky REQUIRED checks. Fires ONLY when EVERY failing
104
+ * required check is infrastructural (cancelled/timed-out/stale/startup-failure)
105
+ * with a derivable run id, and NONE is a real FAILURE/ERROR/ACTION_REQUIRED. A
106
+ * single real failure, an unclassifiable conclusion, a required-failing check
107
+ * with no matching `failures[]` entry, or a missing run id fails the WHOLE
108
+ * decision closed (never rerun a genuinely-broken PR, never loop on a real bug).
109
+ *
110
+ * @param {object} payload - the `--pull --json` payload.
111
+ * @returns {{ should: boolean, checks: Array<{name:string,conclusion:string,runId:string|null}>, runIds: string[], reason: string }}
112
+ */
113
+ function decideRerun(payload) {
114
+ const empty = (reason) => ({ should: false, checks: [], runIds: [], reason });
115
+ if (!payload || typeof payload !== 'object') return empty('no payload — fail closed');
116
+ if (isDegraded(payload)) return empty('degraded/torn read — fail closed');
117
+
118
+ const rc = payload.requiredChecks || {};
119
+ const failingNames = Array.isArray(rc.failing) ? rc.failing : [];
120
+ if (failingNames.length === 0) return empty('no failing required checks');
121
+
122
+ const failures = Array.isArray(payload.failures) ? payload.failures : [];
123
+ const conclByName = new Map();
124
+ const urlByName = new Map();
125
+ for (const f of failures) {
126
+ if (!f || !f.name) continue;
127
+ if (!conclByName.has(f.name)) {
128
+ conclByName.set(f.name, String(f.conclusion || '').toUpperCase());
129
+ urlByName.set(f.name, f.jobUrl || f.detailsUrl || null);
130
+ }
131
+ }
132
+
133
+ const picked = [];
134
+ for (const name of failingNames) {
135
+ const concl = conclByName.get(name);
136
+ if (!concl) return empty(`required check "${name}" has no known conclusion — cannot confirm flaky, fail closed`);
137
+ if (REAL_FAILURE_CONCLUSIONS.has(concl)) return empty(`required check "${name}" is a real failure (${concl}) — never rerun`);
138
+ if (!INFRA_CONCLUSIONS.has(concl)) return empty(`required check "${name}" conclusion ${concl} is not classified infrastructural — fail closed`);
139
+ picked.push({ name, conclusion: concl, runId: runIdFromUrl(urlByName.get(name)) });
140
+ }
141
+
142
+ const runIds = [...new Set(picked.map((p) => p.runId).filter(Boolean))];
143
+ if (runIds.length === 0) return empty('no run id derivable from failure jobUrl — fail closed');
144
+ return {
145
+ should: true,
146
+ checks: picked,
147
+ runIds,
148
+ reason: `all ${picked.length} failing required check(s) are infrastructural (${picked.map((p) => p.conclusion).join(', ')})`,
149
+ };
150
+ }
151
+
152
+ /**
153
+ * Compute the full auto-action decision from a `--pull --json` payload. Pure and
154
+ * independently testable — no I/O, no `gh`, no side effects.
155
+ *
156
+ * @param {object} payload
157
+ * @param {{ isFork?: boolean }} [opts]
158
+ * @returns {{ updateBranch: object, rerunFlaky: object }}
159
+ */
160
+ function decideAutoActions(payload, opts = {}) {
161
+ return {
162
+ updateBranch: decideUpdateBranch(payload, opts),
163
+ rerunFlaky: decideRerun(payload),
164
+ };
165
+ }
166
+
167
+ module.exports = {
168
+ decideAutoActions,
169
+ decideUpdateBranch,
170
+ decideRerun,
171
+ runIdFromUrl,
172
+ isDegraded,
173
+ INFRA_CONCLUSIONS,
174
+ REAL_FAILURE_CONCLUSIONS,
175
+ };
@@ -0,0 +1,195 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * PR-monitor differ — the PURE heart of the monitor. `diffSnapshots(prev, next)`
5
+ * returns the ordered candidate events for the transition between two normalized
6
+ * snapshots (see lib/pr-monitor/gather.js for the shape). No I/O, no clock, no
7
+ * randomness — same inputs always yield the same events, which is what makes the
8
+ * monitor deterministic and duplicate-free across restarts.
9
+ *
10
+ * Each event carries a CONTENT key (head sha, threadId, check name+sha, comment
11
+ * id, review author+commit, surface) so the journal can dedup by identity.
12
+ *
13
+ * @module pr-monitor/differ
14
+ */
15
+
16
+ const { EVENT_TYPES: T, makeEvent } = require('./events');
17
+
18
+ /** Index an array of objects by a key field into a Map. */
19
+ function indexBy(list, field) {
20
+ const map = new Map();
21
+ for (const item of (Array.isArray(list) ? list : [])) {
22
+ if (item?.[field] != null) map.set(String(item[field]), item);
23
+ }
24
+ return map;
25
+ }
26
+
27
+ /** True when every check is green (none failed, none pending). */
28
+ function allGreen(checks) {
29
+ const list = Array.isArray(checks) ? checks : [];
30
+ return list.length > 0 && list.every((c) => c.class === 'green');
31
+ }
32
+
33
+ /** head.pushed — the PR's head sha advanced. */
34
+ function diffHead(prev, next, out) {
35
+ if (next.headSha && prev.headSha !== next.headSha) {
36
+ out.push(makeEvent(T.HEAD_PUSHED, next.headSha, { from: prev.headSha || null, to: next.headSha }));
37
+ }
38
+ }
39
+
40
+ /** check.failed / check.recovered / checks.green — keyed by name+sha. */
41
+ function diffChecks(prev, next, out) {
42
+ const prevByName = indexBy(prev.checks, 'name');
43
+ for (const c of (next.checks || [])) {
44
+ const before = prevByName.get(String(c.name));
45
+ const wasFailed = before?.class === 'failed';
46
+ if (c.class === 'failed' && !wasFailed) {
47
+ out.push(makeEvent(T.CHECK_FAILED, `${c.name}:${next.headSha}`, { name: c.name }));
48
+ } else if (c.class === 'green' && wasFailed) {
49
+ out.push(makeEvent(T.CHECK_RECOVERED, `${c.name}:${next.headSha}`, { name: c.name }));
50
+ }
51
+ }
52
+ if (allGreen(next.checks) && !allGreen(prev.checks)) {
53
+ out.push(makeEvent(T.CHECKS_GREEN, next.headSha, {}));
54
+ }
55
+ }
56
+
57
+ /** thread.opened / thread.reply / thread.resolved — keyed by threadId. */
58
+ function diffThreads(prev, next, out) {
59
+ const prevById = indexBy(prev.threads, 'threadId');
60
+ for (const t of (next.threads || [])) {
61
+ const before = prevById.get(String(t.threadId));
62
+ if (!before) {
63
+ if (t.actionable) out.push(makeEvent(T.THREAD_OPENED, t.threadId, { path: t.path || null }));
64
+ continue;
65
+ }
66
+ if ((t.commentCount || 0) > (before.commentCount || 0)) {
67
+ out.push(makeEvent(T.THREAD_REPLY, `${t.threadId}:${t.commentCount}`, { threadId: t.threadId }));
68
+ }
69
+ if (t.isResolved && !before.isResolved) {
70
+ out.push(makeEvent(T.THREAD_RESOLVED, t.threadId, {}));
71
+ }
72
+ }
73
+ }
74
+
75
+ /** comment.posted — a new direct PR issue comment, keyed by comment id. */
76
+ function diffComments(prev, next, out) {
77
+ const prevIds = indexBy(prev.comments, 'id');
78
+ for (const c of (next.comments || [])) {
79
+ if (!prevIds.has(String(c.id))) {
80
+ out.push(makeEvent(T.COMMENT_POSTED, c.id, { author: c.author || '' }));
81
+ }
82
+ }
83
+ }
84
+
85
+ /** review.submitted — a new/updated review per author (state or commit changed). */
86
+ function diffReviews(prev, next, out) {
87
+ const prevByAuthor = indexBy(prev.reviews, 'author');
88
+ for (const r of (next.reviews || [])) {
89
+ const before = prevByAuthor.get(String(r.author));
90
+ const changed = !before
91
+ || before.submittedAt !== r.submittedAt
92
+ || before.commitOid !== r.commitOid;
93
+ if (changed) {
94
+ out.push(makeEvent(T.REVIEW_SUBMITTED, `${r.author}:${r.commitOid || r.submittedAt}`, {
95
+ author: r.author, verdict: r.state, commitOid: r.commitOid || null,
96
+ }));
97
+ }
98
+ }
99
+ }
100
+
101
+ /** conflict.appeared / conflict.cleared — keyed by head sha. */
102
+ function diffConflict(prev, next, out) {
103
+ if (next.conflicts === true && prev.conflicts !== true) {
104
+ out.push(makeEvent(T.CONFLICT_APPEARED, next.headSha, {}));
105
+ } else if (next.conflicts === false && prev.conflicts === true) {
106
+ out.push(makeEvent(T.CONFLICT_CLEARED, next.headSha, {}));
107
+ }
108
+ }
109
+
110
+ /** branch.behind — the branch fell behind its base. */
111
+ function diffBehind(prev, next, out) {
112
+ if ((next.behind || 0) > 0 && (prev.behind || 0) === 0) {
113
+ out.push(makeEvent(T.BRANCH_BEHIND, `${next.headSha}:${next.behind}`, { behind: next.behind }));
114
+ }
115
+ }
116
+
117
+ /** verdict.changed — the fail-closed merge verdict flipped. */
118
+ function diffVerdict(prev, next, out) {
119
+ const from = prev.verdict ? prev.verdict.state : null;
120
+ const to = next.verdict ? next.verdict.state : null;
121
+ if (from !== to) {
122
+ out.push(makeEvent(T.VERDICT_CHANGED, `${from}->${to}:${next.headSha}`, {
123
+ from, to, reason: next.verdict ? next.verdict.reason || null : null,
124
+ }));
125
+ }
126
+ }
127
+
128
+ /** pr.merged / pr.closed — terminal transitions from an OPEN PR. */
129
+ function diffPrState(prev, next, out) {
130
+ if (prev.prState === 'OPEN' && next.prState === 'MERGED') {
131
+ out.push(makeEvent(T.PR_MERGED, 'MERGED', {}));
132
+ } else if (prev.prState === 'OPEN' && next.prState === 'CLOSED') {
133
+ out.push(makeEvent(T.PR_CLOSED, 'CLOSED', {}));
134
+ }
135
+ }
136
+
137
+ /** monitor.degraded — a read surface newly became unreadable (fail-closed). */
138
+ function diffDegraded(prev, next, out) {
139
+ const prevSurfaces = new Set((prev.degraded || []).map((d) => d.surface));
140
+ for (const d of (next.degraded || [])) {
141
+ if (!prevSurfaces.has(d.surface)) {
142
+ out.push(makeEvent(T.MONITOR_DEGRADED, d.surface, { surface: d.surface, error: d.error || '' }));
143
+ }
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Baseline events for the FIRST pass (no prior snapshot): a single verdict
149
+ * baseline plus any already-terminal/degraded state — bounded, never a replay of
150
+ * every historical thread/check.
151
+ */
152
+ function diffBaseline(next, out) {
153
+ out.push(makeEvent(T.VERDICT_CHANGED, `baseline:${next.headSha}`, {
154
+ from: null, to: next.verdict ? next.verdict.state : null,
155
+ reason: next.verdict ? next.verdict.reason || null : null,
156
+ }));
157
+ if (next.prState === 'MERGED') out.push(makeEvent(T.PR_MERGED, 'MERGED', {}));
158
+ if (next.prState === 'CLOSED') out.push(makeEvent(T.PR_CLOSED, 'CLOSED', {}));
159
+ for (const d of (next.degraded || [])) {
160
+ out.push(makeEvent(T.MONITOR_DEGRADED, d.surface, { surface: d.surface, error: d.error || '' }));
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Diff two normalized snapshots into ordered candidate events. `prev === null`
166
+ * → first-pass baseline.
167
+ *
168
+ * @param {object|null} prev
169
+ * @param {object} next
170
+ * @returns {Array<{ type: string, key: string, data: object }>}
171
+ */
172
+ function diffSnapshots(prev, next) {
173
+ const out = [];
174
+ if (!prev) {
175
+ diffBaseline(next, out);
176
+ return out;
177
+ }
178
+ diffHead(prev, next, out);
179
+ diffChecks(prev, next, out);
180
+ diffThreads(prev, next, out);
181
+ diffComments(prev, next, out);
182
+ diffReviews(prev, next, out);
183
+ diffConflict(prev, next, out);
184
+ diffBehind(prev, next, out);
185
+ diffVerdict(prev, next, out);
186
+ diffPrState(prev, next, out);
187
+ diffDegraded(prev, next, out);
188
+ return out;
189
+ }
190
+
191
+ module.exports = {
192
+ diffSnapshots,
193
+ allGreen,
194
+ indexBy,
195
+ };