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
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PR shepherd — one bounded pass of the monitor-driven state machine.
|
|
5
|
+
*
|
|
6
|
+
* Each `runShepherdPass` call is ONE discrete pass: read PR/CI state, decide a
|
|
7
|
+
* single action, take at most the allowed Tier-A action, then return. It never
|
|
8
|
+
* loops in-process and never sits waiting; an external scheduler re-invokes it.
|
|
9
|
+
*
|
|
10
|
+
* Invariants (enforced by tests):
|
|
11
|
+
* - NEVER merges. There is no merge action and no `--auto` latch — handoff to
|
|
12
|
+
* the human is the only way a PR merges.
|
|
13
|
+
* - NEVER resolves Greptile threads. It may post a status REPLY; resolution
|
|
14
|
+
* stays with the semantic `/review` agent.
|
|
15
|
+
* - Tier-A (autonomous): `rerun --failed` for flaky required checks (capped).
|
|
16
|
+
* - Tier-B (opt-in, default OFF): rebase + force-with-lease via `autoRebase`.
|
|
17
|
+
* Lease rejection is a HARD-STOP + escalate, never auto-retry.
|
|
18
|
+
* - Tier-C (escalate): conflicts, unknown/unreadable required set, persistent
|
|
19
|
+
* failures, auth-scope failures, oscillation, budget exhaustion.
|
|
20
|
+
* - Merge-ready is declared only when the required set is KNOWN and all of it
|
|
21
|
+
* is green and the branch is not behind.
|
|
22
|
+
* - Before any mutating action, HEAD SHA is re-read; if it moved since the
|
|
23
|
+
* pass started, the action is aborted (the real concurrency guard).
|
|
24
|
+
*
|
|
25
|
+
* State persists via GitHub PR comments/labels and git only.
|
|
26
|
+
*
|
|
27
|
+
* @module pr-shepherd
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const { classifyAuthError } = require('./adapters/pr-state-adapter');
|
|
31
|
+
const { fenceUntrusted } = require('./untrusted-content');
|
|
32
|
+
|
|
33
|
+
/** Non-erroring terminal states a pass can settle into. */
|
|
34
|
+
const TERMINAL_STATES = ['MERGE_READY', 'ESCALATE', 'PENDING', 'MERGED', 'CLOSED', 'NEEDS_REVIEW'];
|
|
35
|
+
|
|
36
|
+
// Review threads are classified BY MECHANISM, not by a bot-name list: a GitHub
|
|
37
|
+
// review THREAD is opened by a reviewer (a human OR any bot) and stays open until
|
|
38
|
+
// resolved. So any unresolved, non-outdated thread is actionable REGARDLESS of
|
|
39
|
+
// author — that is both the #365 fix (a CodeRabbit thread now blocks) and
|
|
40
|
+
// fail-closed for unknown bots (a new review bot we can't name still blocks). A
|
|
41
|
+
// name list here would drop unknown bots' threads → false MERGE_READY.
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Normalize a review thread (or a flat comment object) into its list of
|
|
45
|
+
* `{ author, body }` comments. A thread carries ALL its comments, so a later
|
|
46
|
+
* reply on a bot-opened thread is visible (not just the first comment).
|
|
47
|
+
*/
|
|
48
|
+
function threadComments(t) {
|
|
49
|
+
if (Array.isArray(t.comments) && t.comments.length > 0) {
|
|
50
|
+
return t.comments.map((c) => ({
|
|
51
|
+
author: String((c.author && c.author.login) || c.author || '').toLowerCase(),
|
|
52
|
+
body: String(c.body || ''),
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
return [{ author: String(t.author || t.login || '').toLowerCase(), body: String(t.body || '') }];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Filter review threads to the ones that need attention: unresolved AND not
|
|
60
|
+
* outdated — AUTHOR-AGNOSTIC. Any open thread blocks (human OR any bot, known or
|
|
61
|
+
* unknown); resolving it is `/review`'s job. The `self` param is accepted for
|
|
62
|
+
* backward-compatible call sites but no longer filters (a thread is open
|
|
63
|
+
* regardless of who last replied).
|
|
64
|
+
*
|
|
65
|
+
* @param {object[]} threads
|
|
66
|
+
* @param {string} [_self] - unused (kept for call-site compatibility).
|
|
67
|
+
* @returns {object[]}
|
|
68
|
+
*/
|
|
69
|
+
function actionableComments(threads, _self) {
|
|
70
|
+
return (Array.isArray(threads) ? threads : []).filter(
|
|
71
|
+
(t) => !(t.resolved || t.isResolved || t.outdated || t.isOutdated),
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const SUCCESS_CONCLUSIONS = new Set(['SUCCESS', 'NEUTRAL', 'SKIPPED']);
|
|
76
|
+
|
|
77
|
+
function isGreen(check) {
|
|
78
|
+
const c = String(check.conclusion || '').toUpperCase();
|
|
79
|
+
return SUCCESS_CONCLUSIONS.has(c);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Includes ERROR and STARTUP_FAILURE so a failing legacy commit STATUS
|
|
83
|
+
// (StatusContext, e.g. Vercel/Netlify deploy checks report state=ERROR/FAILURE)
|
|
84
|
+
// is classified as failing — not silently treated as "pending" — exactly like a
|
|
85
|
+
// CheckRun FAILURE. gh's statusCheckRollup normalizes a StatusContext `state`
|
|
86
|
+
// into the `conclusion` slot, so both check types flow through this one gate.
|
|
87
|
+
function isFailed(check) {
|
|
88
|
+
const c = String(check.conclusion || '').toUpperCase();
|
|
89
|
+
return c === 'FAILURE' || c === 'ERROR' || c === 'TIMED_OUT'
|
|
90
|
+
|| c === 'CANCELLED' || c === 'ACTION_REQUIRED' || c === 'STARTUP_FAILURE'
|
|
91
|
+
// STALE (a required CheckRun whose run went stale vs HEAD) is a not-green
|
|
92
|
+
// terminal conclusion — without it a stale required check pends forever.
|
|
93
|
+
|| c === 'STALE';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Build a decision result envelope.
|
|
98
|
+
*
|
|
99
|
+
* @param {string} state
|
|
100
|
+
* @param {object} extra
|
|
101
|
+
*/
|
|
102
|
+
function result(state, extra = {}) {
|
|
103
|
+
return { state, actions: extra.actions || [], reason: extra.reason || '', ...extra };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Map a classified auth error to a decision envelope, or return `null` when the
|
|
108
|
+
* error is not an auth/rate-limit shape (caller should re-throw).
|
|
109
|
+
*
|
|
110
|
+
* @param {Error} error
|
|
111
|
+
* @param {object[]} actions
|
|
112
|
+
* @returns {object | null}
|
|
113
|
+
*/
|
|
114
|
+
function authOutcome(error, actions) {
|
|
115
|
+
const auth = classifyAuthError(error);
|
|
116
|
+
if (!auth) return null;
|
|
117
|
+
if (auth.class === 'insufficient-scope') {
|
|
118
|
+
return result('HARD_STOP', {
|
|
119
|
+
actions,
|
|
120
|
+
authClass: 'insufficient-scope',
|
|
121
|
+
reason: 'Token lacks the permission required (branch protection / PR state). Retry will not recover; escalate to a human to widen the token scope.',
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
if (auth.class === 'rate-limit') {
|
|
125
|
+
return result('PENDING', {
|
|
126
|
+
actions,
|
|
127
|
+
authClass: 'rate-limit',
|
|
128
|
+
retryAfter: auth.retryAfter,
|
|
129
|
+
reason: 'Secondary rate limit hit; honor Retry-After then resume on the next pass.',
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
// 'expired' (401) — transient: pause and surface for re-auth.
|
|
133
|
+
return result('PENDING', {
|
|
134
|
+
actions,
|
|
135
|
+
authClass: 'expired',
|
|
136
|
+
reason: 'Token appears expired/unauthorized (transient); pause and surface for re-auth.',
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Run an adapter call, mapping auth/rate-limit failures to a decision envelope
|
|
142
|
+
* via the documented taxonomy instead of letting them escape as generic errors.
|
|
143
|
+
* Non-auth errors are re-thrown.
|
|
144
|
+
*
|
|
145
|
+
* Returns `{ outcome }` when the call should short-circuit the pass, or
|
|
146
|
+
* `{ value }` with the call's resolved value otherwise.
|
|
147
|
+
*
|
|
148
|
+
* @param {() => Promise<*>} call
|
|
149
|
+
* @param {object[]} actions
|
|
150
|
+
* @returns {Promise<{ outcome: object } | { value: * }>}
|
|
151
|
+
*/
|
|
152
|
+
async function guardAuth(call, actions) {
|
|
153
|
+
try {
|
|
154
|
+
return { value: await call() };
|
|
155
|
+
} catch (error) {
|
|
156
|
+
const outcome = authOutcome(error, actions);
|
|
157
|
+
if (outcome) return { outcome };
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Handle a failed required check: rerun once (capped, idempotent) or escalate.
|
|
164
|
+
*
|
|
165
|
+
* @param {object} args
|
|
166
|
+
* @returns {Promise<object>} decision envelope.
|
|
167
|
+
*/
|
|
168
|
+
async function handleFailedRequired({
|
|
169
|
+
failedRequired, rerunsUsed, rerunBudget, headUnchanged, adapter, actions, dryRun,
|
|
170
|
+
}) {
|
|
171
|
+
// Read-only pass (e.g. `--pull` signal gathering): report the failing state but
|
|
172
|
+
// take NO Tier-A action. Reruns/mutations belong to a plain `forge shepherd`.
|
|
173
|
+
if (dryRun) {
|
|
174
|
+
return result('PENDING', {
|
|
175
|
+
actions,
|
|
176
|
+
dryRun: true,
|
|
177
|
+
reason: `Required check '${failedRequired[0].name}' is failing. Read-only pass — not re-running (a rerun belongs to \`forge shepherd\`, not \`--pull\`).`,
|
|
178
|
+
failed: failedRequired.map((c) => c.name),
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
if (rerunsUsed >= rerunBudget) {
|
|
182
|
+
return result('ESCALATE', {
|
|
183
|
+
actions,
|
|
184
|
+
reason: `Rerun budget exhausted (${rerunsUsed}/${rerunBudget}). Required check still failing — escalating.`,
|
|
185
|
+
failed: failedRequired.map((c) => c.name),
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
if (!(await headUnchanged())) {
|
|
189
|
+
return result('PENDING', {
|
|
190
|
+
actions,
|
|
191
|
+
aborted: true,
|
|
192
|
+
reason: 'HEAD moved during the pass; aborted the rerun. Next scheduled pass will re-evaluate.',
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
const runId = failedRequired[0].databaseId || failedRequired[0].name;
|
|
196
|
+
await adapter.rerunFailedChecks({ runId });
|
|
197
|
+
actions.push({ type: 'rerun', runId });
|
|
198
|
+
return result('PENDING', {
|
|
199
|
+
actions,
|
|
200
|
+
reason: `Re-ran failed required check '${failedRequired[0].name}'. Awaiting next scheduled pass.`,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Handle a branch that is behind base: opt-in Tier-B rebase, or escalate.
|
|
206
|
+
*
|
|
207
|
+
* @param {object} args
|
|
208
|
+
* @returns {Promise<object>} decision envelope.
|
|
209
|
+
*/
|
|
210
|
+
async function handleBehindBase({
|
|
211
|
+
behind, autoRebase, cleanTree, adapter, baseRef, headUnchanged, actions,
|
|
212
|
+
}) {
|
|
213
|
+
if (!autoRebase) {
|
|
214
|
+
return result('ESCALATE', {
|
|
215
|
+
actions,
|
|
216
|
+
reason: `Branch is ${behind} commit(s) behind base. Auto-rebase is opt-in (default OFF) — a human should rebase, or re-run with --auto-rebase.`,
|
|
217
|
+
behind,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
if (!cleanTree) {
|
|
221
|
+
return result('ESCALATE', {
|
|
222
|
+
actions,
|
|
223
|
+
reason: 'Auto-rebase requested but the working tree is not clean. Escalating rather than rebasing over local changes.',
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
if (typeof adapter.rebaseOntoBase !== 'function') {
|
|
227
|
+
return result('ESCALATE', {
|
|
228
|
+
actions,
|
|
229
|
+
reason: 'Auto-rebase requested but no rebase capability is wired. Escalating.',
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
if (!(await headUnchanged())) {
|
|
233
|
+
return result('PENDING', {
|
|
234
|
+
actions,
|
|
235
|
+
aborted: true,
|
|
236
|
+
reason: 'HEAD moved during the pass; aborted the rebase. Next scheduled pass will re-evaluate.',
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
try {
|
|
240
|
+
await adapter.rebaseOntoBase({ baseRef });
|
|
241
|
+
actions.push({ type: 'rebase', baseRef });
|
|
242
|
+
return result('PENDING', {
|
|
243
|
+
actions,
|
|
244
|
+
reason: 'Rebased onto base and force-pushed with lease. Awaiting CI on the next scheduled pass.',
|
|
245
|
+
});
|
|
246
|
+
} catch (error) {
|
|
247
|
+
if (error?.leaseRejected) {
|
|
248
|
+
return result('ESCALATE', {
|
|
249
|
+
actions,
|
|
250
|
+
reason: 'Force-with-lease was rejected — a concurrent push exists. HARD-STOP: never re-arm the lease. A human must reconcile.',
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
return result('ESCALATE', {
|
|
254
|
+
actions,
|
|
255
|
+
reason: `Rebase failed: ${error.message}. Escalating to a human.`,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Terminal lifecycle outcome for a merged/closed PR, or null when still open.
|
|
262
|
+
* The external scheduler must stop re-invoking the shepherd once the PR lands or
|
|
263
|
+
* is closed; without this it would keep re-deciding forever on a landed PR.
|
|
264
|
+
*
|
|
265
|
+
* @param {string} state - raw PR state.
|
|
266
|
+
* @param {object[]} actions
|
|
267
|
+
* @returns {object|null}
|
|
268
|
+
*/
|
|
269
|
+
function lifecycleOutcome(state, actions) {
|
|
270
|
+
const prState = String(state || 'OPEN').toUpperCase();
|
|
271
|
+
if (prState === 'MERGED') {
|
|
272
|
+
return result('MERGED', {
|
|
273
|
+
actions,
|
|
274
|
+
reason: 'PR is merged — shepherd work is complete; the scheduler should stop re-invoking this PR.',
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
if (prState === 'CLOSED') {
|
|
278
|
+
return result('CLOSED', {
|
|
279
|
+
actions,
|
|
280
|
+
reason: 'PR is closed without merging — shepherd work is complete; the scheduler should stop re-invoking this PR.',
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* True when every REQUIRED check is present and green. Empty required set is
|
|
288
|
+
* ready by definition — optional checks never gate merge readiness.
|
|
289
|
+
*
|
|
290
|
+
* @param {string[]} required
|
|
291
|
+
* @param {object[]} checks
|
|
292
|
+
* @returns {boolean}
|
|
293
|
+
*/
|
|
294
|
+
function allRequiredChecksGreen(required, checks) {
|
|
295
|
+
return required.every((name) => checks.some((check) => check.name === name && isGreen(check)));
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Build the capped, display-only sample of actionable threads. Prefers a
|
|
300
|
+
* non-self comment (more informative), else the first — display only; the thread
|
|
301
|
+
* is actionable regardless of author.
|
|
302
|
+
*
|
|
303
|
+
* The `body` is an UNTRUSTED external PR comment surfaced verbatim into the
|
|
304
|
+
* agent-facing NEEDS_REVIEW envelope, so it is provenance-fenced (a malicious
|
|
305
|
+
* comment must not be able to steer the /review agent). This is the display
|
|
306
|
+
* projection; the machine bundle/pull JSON keeps the raw body.
|
|
307
|
+
*
|
|
308
|
+
* @param {object[]} actionable
|
|
309
|
+
* @param {string} [self]
|
|
310
|
+
* @param {number} cap
|
|
311
|
+
* @returns {Array<{author: string, body: string}>}
|
|
312
|
+
*/
|
|
313
|
+
function buildCommentSample(actionable, self, cap) {
|
|
314
|
+
const selfLower = String(self || '').toLowerCase();
|
|
315
|
+
return actionable.slice(0, cap).map((t) => {
|
|
316
|
+
const cs = threadComments(t);
|
|
317
|
+
const anchor = cs.find((c) => c.author && c.author !== selfLower) || cs[0] || {};
|
|
318
|
+
return {
|
|
319
|
+
author: anchor.author || '',
|
|
320
|
+
body: fenceUntrusted(String(anchor.body || '').slice(0, 200), { source: 'pr-review-comment' }),
|
|
321
|
+
};
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Detect unresolved review feedback and hand off to /review — the shepherd
|
|
327
|
+
* DETECTS and hands off, NEVER resolves threads. Returns a NEEDS_REVIEW envelope
|
|
328
|
+
* (or an auth outcome), or null when nothing is actionable this pass.
|
|
329
|
+
*
|
|
330
|
+
* @param {object} args
|
|
331
|
+
* @returns {Promise<object|null>}
|
|
332
|
+
*/
|
|
333
|
+
async function evaluateReviewFeedback({ adapter, owner, repo, pr, self, actions }) {
|
|
334
|
+
if (typeof adapter.readComments !== 'function') return null;
|
|
335
|
+
const commentsRead = await guardAuth(() => adapter.readComments({ owner, repo, pr }), actions);
|
|
336
|
+
if (commentsRead.outcome) return commentsRead.outcome;
|
|
337
|
+
const actionable = actionableComments(commentsRead.value, self);
|
|
338
|
+
if (actionable.length === 0) return null;
|
|
339
|
+
const CAP = 20;
|
|
340
|
+
const capped = actionable.length > CAP;
|
|
341
|
+
return result('NEEDS_REVIEW', {
|
|
342
|
+
actions,
|
|
343
|
+
commentCount: actionable.length,
|
|
344
|
+
capped,
|
|
345
|
+
sample: buildCommentSample(actionable, self, CAP),
|
|
346
|
+
reason: capped
|
|
347
|
+
? `${actionable.length} unresolved review comments (showing the first ${CAP}). Too many to act on in one pass — handing off to /review. The shepherd never resolves threads.`
|
|
348
|
+
: `${actionable.length} unresolved review comment(s) need attention — handing off to /review. The shepherd never resolves threads.`,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Run a single bounded shepherd pass.
|
|
354
|
+
*
|
|
355
|
+
* @param {object} ctx
|
|
356
|
+
* @param {string} ctx.pr - PR number.
|
|
357
|
+
* @param {string} ctx.owner
|
|
358
|
+
* @param {string} ctx.repo
|
|
359
|
+
* @param {string} ctx.base - Base branch name (for protection lookup).
|
|
360
|
+
* @param {string} ctx.baseRef - Base ref for divergence (e.g. `origin/master`).
|
|
361
|
+
* @param {object} ctx.adapter - A validated pr-state adapter.
|
|
362
|
+
* @param {boolean} [ctx.autoRebase=false] - Opt-in Tier-B rebase.
|
|
363
|
+
* @param {boolean} [ctx.cleanTree=false] - Precondition for rebase.
|
|
364
|
+
* @param {number} [ctx.rerunBudget=3] - Max reruns across the shepherd session.
|
|
365
|
+
* @param {number} [ctx.rerunsUsed=0] - Reruns already spent.
|
|
366
|
+
* @returns {Promise<object>} decision envelope.
|
|
367
|
+
*/
|
|
368
|
+
async function runShepherdPass(ctx) {
|
|
369
|
+
const {
|
|
370
|
+
pr,
|
|
371
|
+
owner,
|
|
372
|
+
repo,
|
|
373
|
+
base,
|
|
374
|
+
baseRef,
|
|
375
|
+
cwd,
|
|
376
|
+
adapter,
|
|
377
|
+
autoRebase = false,
|
|
378
|
+
cleanTree = false,
|
|
379
|
+
rerunBudget = 3,
|
|
380
|
+
rerunsUsed = 0,
|
|
381
|
+
// Read-only mode: compute the decision state but take NO mutating action
|
|
382
|
+
// (no rerun, no rebase). Used by `--pull` signal gathering. Additive and
|
|
383
|
+
// default OFF — existing callers are unaffected.
|
|
384
|
+
dryRun = false,
|
|
385
|
+
} = ctx;
|
|
386
|
+
|
|
387
|
+
const actions = [];
|
|
388
|
+
|
|
389
|
+
// Every read goes through the auth guard so 401/403-scope/rate-limit map to
|
|
390
|
+
// the documented PENDING/HARD_STOP states instead of escaping as a generic
|
|
391
|
+
// failure. Non-auth errors still propagate.
|
|
392
|
+
|
|
393
|
+
// --- Read PR/CI state FIRST so a merged/closed PR is detected as terminal
|
|
394
|
+
// even when the branch-protection (required-checks) read would fail with an
|
|
395
|
+
// auth/scope error — the scheduler must always get the terminal signal for a
|
|
396
|
+
// landed/closed PR. ---
|
|
397
|
+
const stateRead = await guardAuth(() => adapter.readState(pr), actions);
|
|
398
|
+
if (stateRead.outcome) return stateRead.outcome;
|
|
399
|
+
const startState = stateRead.value;
|
|
400
|
+
const startSha = startState.headSha;
|
|
401
|
+
|
|
402
|
+
// --- Lifecycle: a merged/closed PR is terminal. ---
|
|
403
|
+
const lifecycle = lifecycleOutcome(startState.state, actions);
|
|
404
|
+
if (lifecycle) return lifecycle;
|
|
405
|
+
|
|
406
|
+
// --- Required-checks set (only matters for non-terminal PRs); this is where
|
|
407
|
+
// auth/scope fails fast. ---
|
|
408
|
+
const requiredRead = await guardAuth(
|
|
409
|
+
() => adapter.readRequiredChecks({ owner, repo, base }),
|
|
410
|
+
actions,
|
|
411
|
+
);
|
|
412
|
+
if (requiredRead.outcome) return requiredRead.outcome;
|
|
413
|
+
const required = requiredRead.value;
|
|
414
|
+
|
|
415
|
+
// Unreadable required set → escalate, never declare merge-ready.
|
|
416
|
+
if (required === null) {
|
|
417
|
+
return result('ESCALATE', {
|
|
418
|
+
actions,
|
|
419
|
+
reason: 'Required-check set is unreadable (branch protection not accessible). Cannot determine merge readiness — escalating with the readable rollup.',
|
|
420
|
+
rollup: startState.checks,
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const divergenceRead = await guardAuth(
|
|
425
|
+
() => adapter.readDivergence({ baseRef, cwd }),
|
|
426
|
+
actions,
|
|
427
|
+
);
|
|
428
|
+
if (divergenceRead.outcome) return divergenceRead.outcome;
|
|
429
|
+
const behind = divergenceRead.value.behind || 0;
|
|
430
|
+
|
|
431
|
+
const requiredChecks = startState.checks.filter((c) => required.includes(c.name));
|
|
432
|
+
const failedRequired = requiredChecks.filter(isFailed);
|
|
433
|
+
// Merge-readiness is evaluated against the REQUIRED set only (see helper).
|
|
434
|
+
const allRequiredGreen = allRequiredChecksGreen(required, startState.checks);
|
|
435
|
+
|
|
436
|
+
// Helper: re-read HEAD immediately before a mutating action. If HEAD moved
|
|
437
|
+
// since the pass started, abort (concurrency guard).
|
|
438
|
+
const headUnchanged = async () => {
|
|
439
|
+
const now = await adapter.readState(pr);
|
|
440
|
+
return now.headSha === startSha;
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
// --- Tier-C: hard conflict. ---
|
|
444
|
+
if (String(startState.mergeStateStatus).toUpperCase() === 'DIRTY') {
|
|
445
|
+
return result('ESCALATE', {
|
|
446
|
+
actions,
|
|
447
|
+
reason: 'Merge conflict (mergeStateStatus=DIRTY). A human must resolve the conflict.',
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// --- Tier-A: flaky required check → rerun (capped, idempotent). ---
|
|
452
|
+
if (failedRequired.length > 0) {
|
|
453
|
+
return handleFailedRequired({
|
|
454
|
+
failedRequired, rerunsUsed, rerunBudget, headUnchanged, adapter, actions, dryRun,
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// --- Behind base (Tier-B opt-in rebase, else escalate). In read-only mode we
|
|
459
|
+
// never rebase — force autoRebase off so the branch-behind path only escalates. ---
|
|
460
|
+
if (behind > 0) {
|
|
461
|
+
return handleBehindBase({
|
|
462
|
+
behind, autoRebase: dryRun ? false : autoRebase, cleanTree, adapter, baseRef, headUnchanged, actions,
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// --- Review feedback: unresolved comments hand off to /review (never resolved
|
|
467
|
+
// here). Flood-capped so "too many comments" can't blow up a single pass. ---
|
|
468
|
+
const reviewOutcome = await evaluateReviewFeedback({
|
|
469
|
+
adapter, owner, repo, pr, self: ctx.self, actions,
|
|
470
|
+
});
|
|
471
|
+
if (reviewOutcome) return reviewOutcome;
|
|
472
|
+
|
|
473
|
+
// --- Terminal: all required green + not behind → merge-ready handoff. ---
|
|
474
|
+
if (allRequiredGreen) {
|
|
475
|
+
return result('MERGE_READY', {
|
|
476
|
+
actions,
|
|
477
|
+
reason: 'All required checks are green and the branch is up to date. Handing off to the human to merge in the GitHub UI — the shepherd never merges.',
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// --- Otherwise: checks still pending/unknown → wait. ---
|
|
482
|
+
return result('PENDING', {
|
|
483
|
+
actions,
|
|
484
|
+
reason: 'Required checks are not all green yet (still pending) and nothing is actionable this pass. Awaiting the next scheduled pass.',
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
module.exports = {
|
|
489
|
+
runShepherdPass,
|
|
490
|
+
TERMINAL_STATES,
|
|
491
|
+
isGreen,
|
|
492
|
+
isFailed,
|
|
493
|
+
actionableComments,
|
|
494
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PR-state adapter contract validator.
|
|
5
|
+
*
|
|
6
|
+
* Mirrors the shape of `validateReviewAdapter` in lib/review-adapter.js but
|
|
7
|
+
* enforces its own `kind: 'pr-state'`. The pr-state adapter is a distinct SPI
|
|
8
|
+
* from the review adapter — it wraps read-only PR/CI state plus a small set of
|
|
9
|
+
* idempotent, reversible side-effects (rerun a failed check, post a status
|
|
10
|
+
* reply). It is never fed to `validateReviewAdapter`.
|
|
11
|
+
*
|
|
12
|
+
* State persists via GitHub PR comments/labels and git only.
|
|
13
|
+
*
|
|
14
|
+
* @module pr-state-validator
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** Methods every PR-state adapter must implement. */
|
|
18
|
+
const REQUIRED_PR_STATE_ADAPTER_METHODS = [
|
|
19
|
+
'readState',
|
|
20
|
+
'readRequiredChecks',
|
|
21
|
+
'readDivergence',
|
|
22
|
+
'rerunFailedChecks',
|
|
23
|
+
'replyToThread',
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Validate that an object satisfies the PR-state adapter contract.
|
|
28
|
+
*
|
|
29
|
+
* @param {*} adapter - Candidate adapter.
|
|
30
|
+
* @returns {{ valid: boolean, errors: string[] }}
|
|
31
|
+
*/
|
|
32
|
+
function validatePrStateAdapter(adapter) {
|
|
33
|
+
if (!adapter || typeof adapter !== 'object') {
|
|
34
|
+
return { valid: false, errors: ['adapter must be an object'] };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const errors = [];
|
|
38
|
+
|
|
39
|
+
if (!adapter.id || typeof adapter.id !== 'string') {
|
|
40
|
+
errors.push('id must be a non-empty string');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (adapter.kind !== 'pr-state') {
|
|
44
|
+
errors.push('kind must be "pr-state"');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
for (const method of REQUIRED_PR_STATE_ADAPTER_METHODS) {
|
|
48
|
+
if (typeof adapter[method] !== 'function') {
|
|
49
|
+
errors.push(`${method} must be a function`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { valid: errors.length === 0, errors };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = {
|
|
57
|
+
REQUIRED_PR_STATE_ADAPTER_METHODS,
|
|
58
|
+
validatePrStateAdapter,
|
|
59
|
+
};
|