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
@@ -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,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
+ };
Binary file
@@ -0,0 +1,124 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * PR-monitor gather — turn ONE `gatherPrSnapshot` read (the SAME shared read the
5
+ * `--pull` verdict uses) into the normalized snapshot the differ compares. Because
6
+ * the verdict and the monitor events both derive from this one read, they can
7
+ * never disagree (the frame in docs/work/2026-07-13-pr-monitor/plan.md).
8
+ *
9
+ * The normalized snapshot is the diff subject:
10
+ * { repo, pr, headSha, prState, draft, verdict:{state,reason},
11
+ * checks:[{name,class}], threads:[{threadId,isResolved,isOutdated,commentCount,actionable,path}],
12
+ * reviews:[{author,state,commitOid,submittedAt}], comments:[{id,author}],
13
+ * behind, conflicts:(true|false|null), degraded:[{surface,error}] }
14
+ *
15
+ * @module pr-monitor/gather
16
+ */
17
+
18
+ const { gatherPrSnapshot } = require('../pr-pull');
19
+ const { isFailed, isGreen } = require('../pr-shepherd');
20
+
21
+ /** green | failed | pending — reuses the SAME predicates as the verdict core. */
22
+ function classifyCheck(check) {
23
+ if (isFailed(check)) return 'failed';
24
+ if (isGreen(check)) return 'green';
25
+ return 'pending';
26
+ }
27
+
28
+ function normalizeChecks(checks) {
29
+ return (Array.isArray(checks) ? checks : []).map((c) => ({
30
+ name: c.name || '',
31
+ class: classifyCheck(c),
32
+ }));
33
+ }
34
+
35
+ function normalizeThreads(threads) {
36
+ return (Array.isArray(threads) ? threads : []).map((t) => ({
37
+ threadId: t.threadId || '',
38
+ isResolved: Boolean(t.isResolved),
39
+ isOutdated: Boolean(t.isOutdated),
40
+ commentCount: Array.isArray(t.comments) ? t.comments.length : 0,
41
+ actionable: !t.isResolved && !t.isOutdated,
42
+ path: t.path || null,
43
+ }));
44
+ }
45
+
46
+ function normalizeReviews(reviews) {
47
+ return (Array.isArray(reviews) ? reviews : []).map((r) => ({
48
+ author: r.author || '',
49
+ state: r.state || '',
50
+ commitOid: r.commitOid || null,
51
+ submittedAt: r.submittedAt || null,
52
+ }));
53
+ }
54
+
55
+ function normalizeComments(comments) {
56
+ return (Array.isArray(comments) ? comments : []).map((c) => ({
57
+ id: c.id || `${c.author || ''}:${c.createdAt || ''}`,
58
+ author: c.author || '',
59
+ }));
60
+ }
61
+
62
+ function normalizeDegraded(degraded) {
63
+ return (Array.isArray(degraded) ? degraded : []).map((d) => ({
64
+ surface: d.source || d.surface || 'unknown',
65
+ error: d.error || '',
66
+ }));
67
+ }
68
+
69
+ /**
70
+ * Conflict prediction → tri-state boolean: `true`/`false` when supported,
71
+ * `null` when unknown (unsupported git, unreadable ref) so the differ never
72
+ * emits a false conflict.appeared/cleared on missing data.
73
+ */
74
+ function conflictBool(conflicts) {
75
+ if (conflicts?.supported === true) return Boolean(conflicts.conflicted);
76
+ return null;
77
+ }
78
+
79
+ /**
80
+ * Normalize a raw `gatherPrSnapshot` result into the monitor diff subject.
81
+ *
82
+ * @param {object} snap - gatherPrSnapshot result.
83
+ * @param {{ repo: string, pr: string|number }} ctx
84
+ * @returns {object}
85
+ */
86
+ function normalizeSnapshot(snap, ctx) {
87
+ const state = snap.state || {};
88
+ return {
89
+ repo: ctx.repo,
90
+ pr: String(ctx.pr),
91
+ headSha: state.headSha || '',
92
+ prState: String(state.state || 'OPEN').toUpperCase(),
93
+ draft: Boolean(snap.draft),
94
+ verdict: { state: snap.verdict || 'UNKNOWN', reason: null },
95
+ checks: normalizeChecks(state.checks),
96
+ threads: normalizeThreads(snap.threads),
97
+ reviews: normalizeReviews(snap.reviews),
98
+ comments: normalizeComments(snap.issueComments),
99
+ behind: snap.behind || 0,
100
+ conflicts: conflictBool(snap.conflicts),
101
+ degraded: normalizeDegraded(snap.degraded),
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Gather + normalize the monitor snapshot for a PR. `ctx.gatherSnapshot` is
107
+ * injectable for tests; production uses the shared `gatherPrSnapshot`.
108
+ *
109
+ * @param {object} ctx - the same ctx shape gatherPrSnapshot takes (pr, owner,
110
+ * repo, base, baseRef, cwd, self, adapter, now, settleWindowMs).
111
+ * @returns {Promise<object>} normalized snapshot.
112
+ */
113
+ async function gatherMonitorSnapshot(ctx) {
114
+ const gather = ctx.gatherSnapshot || gatherPrSnapshot;
115
+ const snap = await gather(ctx);
116
+ return normalizeSnapshot(snap, ctx);
117
+ }
118
+
119
+ module.exports = {
120
+ gatherMonitorSnapshot,
121
+ normalizeSnapshot,
122
+ classifyCheck,
123
+ conflictBool,
124
+ };