forge-workflow 0.0.9 → 0.1.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/rules/{greptile-review-process.md → review-process.md} +56 -41
- package/.claude/scripts/{greptile-resolve.sh → review-resolve.sh} +13 -3
- package/.cursor/rules/permissions-guidance.mdc +2 -2
- package/.forge/hooks/check-tdd.js +3 -0
- package/.forge/hooks/forge-native-hook.js +245 -0
- package/.forge/protected-paths.yaml +157 -0
- package/AGENTS.md +151 -61
- package/CHANGELOG.md +681 -0
- package/CLAUDE.md +9 -106
- package/QUICKSTART.md +171 -0
- package/README.md +271 -363
- package/bin/forge-cmd.js +120 -9
- package/bin/forge-preflight.js +26 -5
- package/bin/forge.js +466 -489
- package/docs/INDEX.md +93 -0
- package/docs/PROJECT_DESIGN.md +685 -0
- package/docs/architecture/index.md +66 -0
- package/docs/architecture/notes/README.md +35 -0
- package/docs/architecture/subsystems/README.md +46 -0
- package/docs/{TOOLCHAIN.md → forge/TOOLCHAIN.md} +56 -47
- package/docs/forge/VALIDATION.md +82 -0
- package/docs/{AGENT_INSTALL_PROMPT.md → guides/AGENT_INSTALL_PROMPT.md} +3 -3
- package/docs/guides/BEADS_GITHUB_SYNC.md +32 -0
- package/docs/{ENHANCED_ONBOARDING.md → guides/ENHANCED_ONBOARDING.md} +16 -12
- package/docs/guides/GREPTILE_SETUP.md +46 -0
- package/docs/guides/MANUAL_REVIEW_GUIDE.md +58 -0
- package/docs/guides/MIGRATION.md +56 -0
- package/docs/guides/SETUP.md +118 -0
- package/docs/guides/SUPPORT.md +185 -0
- package/docs/guides/WORKFLOW_TEMPLATES.md +74 -0
- package/docs/guides/memory-backends.md +183 -0
- package/docs/reference/ADAPTERS.md +128 -0
- package/docs/reference/AGENT_SKILL_PARITY.md +175 -0
- package/docs/reference/COMMANDS.md +205 -0
- package/docs/reference/DECISION_DRIFT_GUARDS.md +97 -0
- package/docs/{EXAMPLES.md → reference/EXAMPLES.md} +7 -5
- package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +135 -0
- package/docs/reference/HERMES_INTEGRATION.md +118 -0
- package/docs/reference/INSIGHTS_RECAP.md +63 -0
- package/docs/reference/INSTALL.md +164 -0
- package/docs/reference/KERNEL_TAXONOMY_VALIDATION.md +161 -0
- package/docs/reference/PROTECTED_PATH_MANIFEST.md +25 -0
- package/docs/reference/RELEASE.md +68 -0
- package/docs/reference/RESEARCH_TEMPLATE.md +292 -0
- package/docs/{ROADMAP.md → reference/ROADMAP.md} +12 -9
- package/docs/reference/SKILLS.md +35 -0
- package/docs/reference/STATUS_BOARD.md +80 -0
- package/docs/reference/TEMPLATES.md +106 -0
- package/docs/reference/TOOLCHAIN.md +658 -0
- package/docs/reference/VALIDATION.md +82 -0
- package/docs/reference/agent-permissions.md +169 -0
- package/docs/reference/beads-to-kernel-migration-ux.md +61 -0
- package/docs/reference/control-plane-guarantees.md +125 -0
- package/docs/reference/dependency-chain.md +331 -0
- package/docs/reference/forge-kernel-issue-command-contract.md +161 -0
- package/docs/reference/forge-kernel-schema.md +72 -0
- package/docs/reference/kernel-conflict-evaluators.md +27 -0
- package/docs/reference/patch-md-format.md +77 -0
- package/docs/reference/protected-state-surfaces.md +59 -0
- package/docs/reference/shepherd.md +115 -0
- package/docs/reference/superpowers-analysis.md +320 -0
- package/docs/reference/superpowers-integration-options.md +404 -0
- package/docs/reference/test-environment.md +519 -0
- package/docs/reference/upgrade-safety.md +59 -0
- package/lefthook.yml +18 -0
- package/lib/adapter-cli.js +307 -0
- package/lib/adapters/beads-issue-adapter.js +127 -0
- package/lib/adapters/beads-kernel-compat.js +1042 -0
- package/lib/adapters/greptile-review-adapter.js +141 -0
- package/lib/adapters/kernel-issue-adapter.js +101 -0
- package/lib/adapters/pr-state-adapter.js +484 -0
- package/lib/adoption-profiles.js +126 -0
- package/lib/agents/README.md +2 -6
- package/lib/agents/claude.plugin.json +3 -8
- package/lib/agents/codex.plugin.json +9 -1
- package/lib/agents/cursor.plugin.json +2 -6
- package/lib/agents/hermes.plugin.json +22 -0
- package/lib/agents-config.js +39 -1236
- package/lib/audit-evidence.js +282 -0
- package/lib/beads-setup.js +225 -28
- package/lib/beads-sync-scaffold.js +36 -107
- package/lib/codex-skills.js +51 -1
- package/lib/commands/_issue.js +744 -70
- package/lib/commands/_manifest.js +91 -0
- package/lib/commands/_registry.js +85 -34
- package/lib/commands/_resolve-command-opts.js +261 -0
- package/lib/commands/_serve-security.js +270 -0
- package/lib/commands/adapter.js +12 -0
- package/lib/commands/add.js +118 -0
- package/lib/commands/audit.js +70 -0
- package/lib/commands/blocked.js +5 -0
- package/lib/commands/board.js +64 -0
- package/lib/commands/claim.js +21 -2
- package/lib/commands/claims.js +7 -0
- package/lib/commands/clean.js +485 -75
- package/lib/commands/close.js +2 -2
- package/lib/commands/comment.js +5 -0
- package/lib/commands/control.js +148 -0
- package/lib/commands/create.js +2 -2
- package/lib/commands/dev.js +185 -7
- package/lib/commands/doc-gate.js +336 -0
- package/lib/commands/doctor.js +156 -0
- package/lib/commands/explain.js +15 -0
- package/lib/commands/export.js +237 -0
- package/lib/commands/gate.js +192 -0
- package/lib/commands/hooks.js +242 -0
- package/lib/commands/inbox.js +118 -0
- package/lib/commands/init.js +598 -0
- package/lib/commands/insights.js +79 -0
- package/lib/commands/issue.js +12 -1
- package/lib/commands/issues.js +66 -0
- package/lib/commands/lint.js +5 -0
- package/lib/commands/list.js +2 -2
- package/lib/commands/merge.js +312 -0
- package/lib/commands/migrate.js +523 -0
- package/lib/commands/new.js +12 -0
- package/lib/commands/options.js +241 -0
- package/lib/commands/orient.js +13 -0
- package/lib/commands/orphans.js +5 -0
- package/lib/commands/patch.js +67 -0
- package/lib/commands/plan.js +436 -24
- package/lib/commands/preflight.js +211 -0
- package/lib/commands/prime.js +13 -0
- package/lib/commands/push.js +69 -2
- package/lib/commands/ready.js +2 -2
- package/lib/commands/recall.js +116 -0
- package/lib/commands/recap.js +61 -0
- package/lib/commands/recommend.js +22 -2
- package/lib/commands/release.js +91 -0
- package/lib/commands/remember.js +74 -0
- package/lib/commands/role.js +99 -0
- package/lib/commands/serve.js +581 -0
- package/lib/commands/setup.js +851 -979
- package/lib/commands/shepherd.js +436 -0
- package/lib/commands/ship.js +23 -1
- package/lib/commands/show.js +2 -2
- package/lib/commands/stage.js +192 -0
- package/lib/commands/stale.js +5 -0
- package/lib/commands/status.js +329 -11
- package/lib/commands/sync.js +34 -46
- package/lib/commands/team.js +15 -2
- package/lib/commands/test.js +58 -7
- package/lib/commands/update.js +2 -2
- package/lib/commands/upgrade.js +47 -0
- package/lib/commands/validate.js +56 -25
- package/lib/commands/worktree.js +308 -128
- package/lib/config-writer.js +202 -0
- package/lib/control-plane.js +236 -0
- package/lib/core/runtime-graph.js +946 -0
- package/lib/dep-guard/keyword-ripple.js +184 -0
- package/lib/deprecated-sync-cleanup.js +362 -0
- package/lib/detect-agent.js +2 -28
- package/lib/detect-worktree.js +42 -17
- package/lib/doc-gate/declaration.js +177 -0
- package/lib/doc-gate/detect.js +289 -0
- package/lib/doc-gate/gate.js +375 -0
- package/lib/doc-gate/okf-config.js +128 -0
- package/lib/doc-gate/okf.js +429 -0
- package/lib/docs-command.js +1161 -6
- package/lib/forge-issues.js +697 -0
- package/lib/forge-lock.js +262 -0
- package/lib/gate-events.js +193 -0
- package/lib/global-flags.js +74 -0
- package/lib/greptile-match.js +7 -63
- package/lib/harness-capability-matrix.js +380 -0
- package/lib/hook-global-installer.js +347 -0
- package/lib/hook-renderer.js +451 -0
- package/lib/inbox.js +391 -0
- package/lib/insights.js +397 -0
- package/lib/issue-adapter.js +156 -0
- package/lib/issue-backend.js +145 -0
- package/lib/issue-render.js +220 -0
- package/lib/issue-sync/authority.js +100 -0
- package/lib/issue-sync/github-pull.js +184 -0
- package/lib/issue-sync/import-primitives.js +98 -0
- package/lib/issue-sync/legacy-link-bridge.js +436 -0
- package/lib/issue-sync/link-store.js +292 -0
- package/lib/issue-sync/project-github.js +123 -0
- package/lib/issue-sync/reconcile.js +195 -0
- package/lib/issue-sync/schema.js +126 -0
- package/lib/kernel/backing-issue.js +305 -0
- package/lib/kernel/broker.js +1218 -0
- package/lib/kernel/cli-broker-factory.js +130 -0
- package/lib/kernel/conflict-signal.js +82 -0
- package/lib/kernel/evaluators.js +195 -0
- package/lib/kernel/fs-class.js +495 -0
- package/lib/kernel/issue-command-contract.js +559 -0
- package/lib/kernel/issue-id-resolver.js +186 -0
- package/lib/kernel/lease-enforcer.js +158 -0
- package/lib/kernel/migrations.js +333 -0
- package/lib/kernel/planning-buckets-schema.js +109 -0
- package/lib/kernel/projection-jsonl-writer.js +450 -0
- package/lib/kernel/readiness-model.js +329 -0
- package/lib/kernel/schema.js +356 -0
- package/lib/kernel/sqlite-driver.js +2504 -0
- package/lib/kernel/taxonomy-validator.js +394 -0
- package/lib/lefthook-check.js +8 -4
- package/lib/lefthook-wiring.js +413 -0
- package/lib/mcp-config-renderer.js +288 -0
- package/lib/memory/graphiti-mcp.js +106 -0
- package/lib/memory/router.js +387 -0
- package/lib/memory/typed-api.js +102 -0
- package/lib/memory-digest.js +195 -0
- package/lib/merge-rules.js +395 -0
- package/lib/migrate-dry-run.js +466 -0
- package/lib/orientation.js +863 -0
- package/lib/package-manager-remediation.js +103 -0
- package/lib/package-root.js +381 -0
- package/lib/patch-intent.js +890 -0
- package/lib/plugin-catalog.js +3 -4
- package/lib/plugin-manager.js +0 -5
- package/lib/pr-bundle.js +186 -0
- package/lib/pr-monitor/differ.js +195 -0
- package/lib/pr-monitor/events.js +0 -0
- package/lib/pr-monitor/gather.js +124 -0
- package/lib/pr-monitor/journal.js +299 -0
- package/lib/pr-monitor/monitor.js +146 -0
- package/lib/pr-monitor/render-sticky.js +157 -0
- package/lib/pr-monitor/watch-lifecycle.js +95 -0
- package/lib/pr-monitor/watch.js +247 -0
- package/lib/pr-pull.js +1273 -0
- package/lib/pr-shepherd.js +494 -0
- package/lib/pr-state-validator.js +59 -0
- package/lib/preflight/gates.js +237 -0
- package/lib/preflight/runner.js +116 -0
- package/lib/project-discovery.js +0 -53
- package/lib/project-memory.js +166 -0
- package/lib/protected-path-manifest.js +281 -0
- package/lib/protected-state-surfaces.js +387 -0
- package/lib/release-readiness.js +2089 -0
- package/lib/reset.js +59 -45
- package/lib/review-adapter.js +68 -0
- package/lib/rules-sync.js +260 -0
- package/lib/runtime-health.js +332 -23
- package/lib/safety-config-renderer.js +268 -0
- package/lib/setup-action-log.js +1 -7
- package/lib/setup.js +27 -65
- package/lib/shell-utils.js +76 -6
- package/lib/skills-sync.js +330 -0
- package/lib/smart-status/conflicts.js +205 -0
- package/lib/smart-status/scoring.js +191 -0
- package/lib/status/beads-snapshot.js +145 -0
- package/lib/status/presenter.js +216 -0
- package/lib/status/snapshot.js +186 -0
- package/lib/sync-backend.js +202 -0
- package/lib/untrusted-content.js +52 -0
- package/lib/upgrade-safety.js +199 -0
- package/lib/workflow/enforce-stage.js +298 -47
- package/lib/workflow/stage-transition.js +115 -0
- package/lib/workflow/stages.js +30 -6
- package/lib/workflow/state-manager.js +159 -14
- package/lib/workflow/state.js +23 -1
- package/lib/workflow-profiles.js +17 -5
- package/package.json +46 -36
- package/rules/documentation.md +19 -0
- package/rules/kernel-tracking.md +26 -0
- package/rules/security.md +22 -0
- package/rules/tdd.md +20 -0
- package/rules/workflow.md +27 -0
- package/scripts/auto-backing-issue.js +47 -0
- package/scripts/beads-context.sh +165 -22
- package/scripts/beads-migrate-to-dolt.sh +7 -0
- package/scripts/beads-upgrade-smoke.sh +284 -0
- package/scripts/behavioral-judge.sh +115 -11
- package/scripts/benchmark.js +349 -63
- package/scripts/bootstrap-windows-tools.sh +78 -0
- package/scripts/branch-protection.js +2 -3
- package/scripts/check-agents.js +34 -137
- package/scripts/commitlint.js +3 -1
- package/scripts/conflict-detect.sh +3 -0
- package/scripts/dep-guard-analyze.js +52 -17
- package/scripts/dep-guard-keyword-ripple.js +29 -0
- package/scripts/dep-guard-render-review.js +86 -0
- package/scripts/dep-guard.sh +64 -232
- package/scripts/file-index.sh +3 -0
- package/scripts/forge-team/lib/claim.sh +34 -18
- package/scripts/forge-team/lib/dashboard.sh +61 -86
- package/scripts/forge-team/lib/epic.sh +99 -263
- package/scripts/forge-team/lib/hooks.sh +26 -28
- package/scripts/forge-team/lib/identity.sh +4 -4
- package/scripts/forge-team/lib/sync-github.sh +144 -47
- package/scripts/forge-team/lib/verify.sh +93 -83
- package/scripts/forge-team/lib/workload.sh +41 -65
- package/scripts/forge-team/tests/claim.test.sh +25 -19
- package/scripts/forge-team/tests/dashboard.test.sh +31 -46
- package/scripts/forge-team/tests/epic.test.sh +52 -71
- package/scripts/forge-team/tests/hooks.test.sh +38 -50
- package/scripts/forge-team/tests/identity.test.sh +3 -3
- package/scripts/forge-team/tests/integration.test.sh +44 -66
- package/scripts/forge-team/tests/sync-github.test.sh +183 -79
- package/scripts/forge-team/tests/verify.test.sh +37 -46
- package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
- package/scripts/forge-team/tests/workload.test.sh +32 -66
- package/scripts/gen-command-manifest.js +153 -0
- package/scripts/gen-embedded-assets.mjs +129 -0
- package/scripts/install.ps1 +139 -0
- package/scripts/install.sh +268 -0
- package/scripts/lib/beads-migrate-to-dolt.mjs +503 -0
- package/scripts/lib/release-asset.mjs +84 -0
- package/scripts/parity-check.mjs +145 -0
- package/scripts/parity-check.test.mjs +58 -0
- package/scripts/pin-agentic-workflow-images.js +112 -0
- package/scripts/pr-coordinator.sh +3 -0
- package/scripts/preflight-sonar.eslint.config.mjs +44 -0
- package/scripts/preflight.sh +108 -0
- package/scripts/protected-state-check.js +104 -0
- package/scripts/smart-status-score.js +31 -0
- package/scripts/smart-status-sessions.js +51 -0
- package/scripts/smart-status.sh +117 -369
- package/scripts/spikes/config-race-bench.js +111 -0
- package/scripts/spikes/harness-capability-matrix.js +13 -0
- package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
- package/scripts/spikes/protected-path-manifest.js +20 -0
- package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
- package/scripts/sync-agent-skills.js +62 -0
- package/scripts/sync-agentic-workflow.js +48 -0
- package/scripts/sync-utils.sh +3 -0
- package/scripts/test-ci-shard.js +251 -0
- package/scripts/test-dashboard.js +188 -52
- package/scripts/test-full-suite.js +186 -0
- package/scripts/test-profile.js +278 -0
- package/scripts/test.js +302 -28
- package/scripts/validate.js +143 -0
- package/scripts/validate.sh +18 -1
- package/skills/claim-safety/SKILL.md +102 -0
- package/skills/claim-safety/evals/evals.json +46 -0
- package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +46 -52
- package/skills/dev/evals/evals.json +50 -0
- package/skills/hermes-forge/SKILL.md +185 -0
- package/skills/hermes-forge/evals/evals.json +46 -0
- package/skills/issue-basics/SKILL.md +111 -0
- package/skills/issue-basics/evals/evals.json +46 -0
- package/skills/kernel/SKILL.md +166 -0
- package/skills/kernel/evals/evals.json +50 -0
- package/skills/memory/SKILL.md +102 -0
- package/skills/parallel-deep-research/SKILL.md +14 -11
- package/skills/parallel-deep-research/evals/evals.json +11 -27
- package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +134 -159
- package/skills/plan/evals/evals.json +42 -0
- package/skills/research/SKILL.md +195 -0
- package/skills/research/evals/evals.json +42 -0
- package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
- package/skills/review/evals/evals.json +42 -0
- package/skills/rollback/SKILL.md +110 -0
- package/skills/rollback/evals/evals.json +46 -0
- package/skills/rollback/references/methods.md +204 -0
- package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
- package/skills/shepherd/SKILL.md +66 -0
- package/skills/shepherd/evals/evals.json +42 -0
- package/skills/ship/SKILL.md +251 -0
- package/skills/ship/evals/evals.json +42 -0
- package/skills/smith/SKILL.md +142 -0
- package/skills/smith/evals/evals.json +46 -0
- package/skills/smith/references/autonomy-and-gates.md +94 -0
- package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
- package/skills/sonarcloud/evals/evals.json +46 -0
- package/skills/sonarcloud-analysis/SKILL.md +18 -13
- package/skills/sonarcloud-analysis/evals/evals.json +11 -15
- package/skills/status/SKILL.md +102 -0
- package/skills/status/evals/evals.json +50 -0
- package/skills/triage-ready/SKILL.md +121 -0
- package/skills/triage-ready/evals/evals.json +42 -0
- package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
- package/skills/validate/evals/evals.json +42 -0
- package/skills/verify/SKILL.md +299 -0
- package/skills/verify/evals/evals.json +50 -0
- package/.claude/commands/dev.md +0 -345
- package/.claude/commands/plan.md +0 -566
- package/.claude/commands/premerge.md +0 -186
- package/.claude/commands/research.md +0 -42
- package/.claude/commands/review.md +0 -451
- package/.claude/commands/rollback.md +0 -721
- package/.claude/commands/ship.md +0 -213
- package/.claude/commands/sonarcloud.md +0 -152
- package/.claude/commands/status.md +0 -90
- package/.claude/commands/validate.md +0 -288
- package/.claude/commands/verify.md +0 -269
- package/.claude/rules/workflow.md +0 -121
- package/.cline/workflows/dev.md +0 -342
- package/.cline/workflows/plan.md +0 -563
- package/.cline/workflows/premerge.md +0 -183
- package/.cline/workflows/research.md +0 -39
- package/.cline/workflows/review.md +0 -448
- package/.cline/workflows/rollback.md +0 -718
- package/.cline/workflows/ship.md +0 -210
- package/.cline/workflows/sonarcloud.md +0 -146
- package/.cline/workflows/status.md +0 -87
- package/.cline/workflows/validate.md +0 -285
- package/.cline/workflows/verify.md +0 -266
- package/.codex/config.toml +0 -11
- package/.codex/skills/dev/SKILL.md +0 -345
- package/.codex/skills/plan/SKILL.md +0 -566
- package/.codex/skills/premerge/SKILL.md +0 -186
- package/.codex/skills/research/SKILL.md +0 -42
- package/.codex/skills/review/SKILL.md +0 -451
- package/.codex/skills/rollback/SKILL.md +0 -721
- package/.codex/skills/ship/SKILL.md +0 -213
- package/.codex/skills/sonarcloud/SKILL.md +0 -149
- package/.codex/skills/status/SKILL.md +0 -90
- package/.codex/skills/validate/SKILL.md +0 -288
- package/.codex/skills/verify/SKILL.md +0 -269
- package/.cursor/commands/dev.md +0 -342
- package/.cursor/commands/plan.md +0 -563
- package/.cursor/commands/premerge.md +0 -183
- package/.cursor/commands/research.md +0 -39
- package/.cursor/commands/review.md +0 -448
- package/.cursor/commands/ship.md +0 -210
- package/.cursor/commands/sonarcloud.md +0 -146
- package/.cursor/commands/status.md +0 -87
- package/.cursor/commands/validate.md +0 -285
- package/.cursor/commands/verify.md +0 -266
- package/.cursorrules +0 -149
- package/.github/prompts/premerge.prompt.md +0 -188
- package/.github/prompts/research.prompt.md +0 -44
- package/.github/prompts/rollback.prompt.md +0 -723
- package/.github/prompts/ship.prompt.md +0 -215
- package/.github/prompts/status.prompt.md +0 -92
- package/.github/prompts/verify.prompt.md +0 -271
- package/.github/workflows/beads-to-github.yml +0 -56
- package/.github/workflows/github-to-beads.yml +0 -97
- package/.kilocode/workflows/dev.md +0 -346
- package/.kilocode/workflows/plan.md +0 -567
- package/.kilocode/workflows/premerge.md +0 -187
- package/.kilocode/workflows/research.md +0 -43
- package/.kilocode/workflows/review.md +0 -452
- package/.kilocode/workflows/rollback.md +0 -722
- package/.kilocode/workflows/ship.md +0 -214
- package/.kilocode/workflows/sonarcloud.md +0 -150
- package/.kilocode/workflows/status.md +0 -91
- package/.kilocode/workflows/validate.md +0 -289
- package/.kilocode/workflows/verify.md +0 -270
- package/.opencode/commands/dev.md +0 -345
- package/.opencode/commands/plan.md +0 -566
- package/.opencode/commands/premerge.md +0 -186
- package/.opencode/commands/research.md +0 -42
- package/.opencode/commands/review.md +0 -451
- package/.opencode/commands/rollback.md +0 -721
- package/.opencode/commands/ship.md +0 -213
- package/.opencode/commands/sonarcloud.md +0 -149
- package/.opencode/commands/status.md +0 -90
- package/.opencode/commands/validate.md +0 -288
- package/.opencode/commands/verify.md +0 -269
- package/.roo/commands/dev.md +0 -346
- package/.roo/commands/plan.md +0 -567
- package/.roo/commands/premerge.md +0 -187
- package/.roo/commands/research.md +0 -43
- package/.roo/commands/review.md +0 -452
- package/.roo/commands/rollback.md +0 -722
- package/.roo/commands/ship.md +0 -214
- package/.roo/commands/sonarcloud.md +0 -150
- package/.roo/commands/status.md +0 -91
- package/.roo/commands/validate.md +0 -289
- package/.roo/commands/verify.md +0 -270
- package/docs/BEADS_GITHUB_SYNC.md +0 -255
- package/docs/GREPTILE_SETUP.md +0 -400
- package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
- package/docs/SETUP.md +0 -663
- package/docs/VALIDATION.md +0 -363
- package/lib/agents/cline.plugin.json +0 -29
- package/lib/agents/copilot.plugin.json +0 -24
- package/lib/agents/kilocode.plugin.json +0 -22
- package/lib/agents/opencode.plugin.json +0 -23
- package/lib/agents/roo.plugin.json +0 -30
- package/lib/beads-health-check.js +0 -143
- package/lib/commands/commands-reset.js +0 -147
- package/opencode.json +0 -67
- package/scripts/beads-context.test.js +0 -567
- package/scripts/github-beads-sync/comment.mjs +0 -64
- package/scripts/github-beads-sync/config.mjs +0 -148
- package/scripts/github-beads-sync/github-api.mjs +0 -131
- package/scripts/github-beads-sync/index.mjs +0 -332
- package/scripts/github-beads-sync/label-mapper.mjs +0 -54
- package/scripts/github-beads-sync/mapping.mjs +0 -78
- package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
- package/scripts/github-beads-sync/reverse-sync.mjs +0 -138
- package/scripts/github-beads-sync/run-bd.mjs +0 -161
- package/scripts/github-beads-sync/sanitize.mjs +0 -121
- package/scripts/github-beads-sync.config.json +0 -26
- package/scripts/sync-commands.js +0 -600
package/lib/plugin-catalog.js
CHANGED
|
@@ -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
|
-
// ──
|
|
328
|
+
// ── Release automation (ship-time) ──
|
|
330
329
|
changesets: {
|
|
331
330
|
name: 'Changesets',
|
|
332
331
|
type: 'cli',
|
|
333
332
|
tier: 'free',
|
|
334
|
-
stage: '
|
|
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: '
|
|
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)' },
|
package/lib/plugin-manager.js
CHANGED
|
@@ -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) {
|
package/lib/pr-bundle.js
ADDED
|
@@ -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
|
+
};
|