forge-workflow 0.0.10 → 0.1.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +82 -5
- package/.forge/hooks/forge-native-hook.js +431 -0
- package/.forge/protected-paths.yaml +157 -0
- package/AGENTS.md +151 -61
- package/CHANGELOG.md +709 -0
- package/CLAUDE.md +9 -118
- package/QUICKSTART.md +175 -0
- package/README.md +275 -365
- package/bin/forge-cmd.js +120 -9
- package/bin/forge-preflight.js +26 -5
- package/bin/forge.js +532 -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/forge/TOOLCHAIN.md +670 -0
- 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 +121 -0
- package/docs/guides/SUPPORT.md +190 -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 +214 -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/{TOOLCHAIN.md → reference/TOOLCHAIN.md} +62 -47
- 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 +155 -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/activation/ensure-forge-home.js +135 -0
- package/lib/adapter-cli.js +307 -0
- package/lib/adapters/beads-issue-adapter.js +127 -0
- package/lib/adapters/beads-kernel-compat.js +1109 -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 +139 -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-detect.js +60 -0
- package/lib/beads-nudge.js +91 -0
- package/lib/beads-setup.js +121 -0
- package/lib/beads-sync-scaffold.js +25 -101
- package/lib/codex-skills.js +51 -1
- package/lib/commands/_aliases.js +248 -0
- package/lib/commands/_issue.js +780 -77
- package/lib/commands/_manifest.js +93 -0
- package/lib/commands/_registry.js +99 -34
- package/lib/commands/_resolve-command-opts.js +230 -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 +209 -0
- package/lib/commands/hooks.js +377 -0
- package/lib/commands/inbox.js +118 -0
- package/lib/commands/init.js +604 -0
- package/lib/commands/insights.js +79 -0
- package/lib/commands/issue.js +12 -1
- package/lib/commands/issues.js +17 -0
- package/lib/commands/lint.js +5 -0
- package/lib/commands/list.js +2 -2
- package/lib/commands/memory.js +81 -0
- package/lib/commands/merge.js +312 -0
- package/lib/commands/migrate.js +362 -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 +481 -29
- package/lib/commands/pr.js +88 -0
- package/lib/commands/preflight.js +211 -0
- package/lib/commands/prime.js +13 -0
- package/lib/commands/push.js +135 -2
- package/lib/commands/ready.js +2 -2
- package/lib/commands/recall.js +171 -0
- package/lib/commands/recap.js +75 -0
- package/lib/commands/recommend.js +0 -1
- package/lib/commands/release.js +104 -0
- package/lib/commands/remember.js +140 -0
- package/lib/commands/role.js +99 -0
- package/lib/commands/serve.js +581 -0
- package/lib/commands/setup.js +900 -971
- package/lib/commands/shepherd.js +501 -0
- package/lib/commands/ship.js +59 -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 +158 -21
- package/lib/commands/sync.js +34 -46
- package/lib/commands/team.js +4 -1
- package/lib/commands/test.js +43 -27
- package/lib/commands/update.js +2 -2
- package/lib/commands/upgrade.js +47 -0
- package/lib/commands/validate.js +43 -18
- package/lib/commands/worktree.js +362 -99
- package/lib/config-writer.js +202 -0
- package/lib/control-plane.js +236 -0
- package/lib/core/runtime-graph.js +977 -0
- package/lib/dep-guard/keyword-ripple.js +2 -2
- package/lib/deprecated-sync-cleanup.js +362 -0
- package/lib/detect-agent.js +2 -28
- package/lib/detect-worktree.js +35 -9
- 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 +382 -11
- package/lib/forge-lock.js +262 -0
- package/lib/gate-events.js +192 -0
- package/lib/global-flags.js +104 -0
- package/lib/greptile-match.js +7 -63
- package/lib/grounding/context-events.js +230 -0
- package/lib/grounding/read-first.js +112 -0
- package/lib/harness-capability-matrix.js +380 -0
- package/lib/hook-global-installer.js +347 -0
- package/lib/hook-renderer.js +541 -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/kernel/backing-issue.js +311 -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/owned-kernel.js +43 -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 +2540 -0
- package/lib/kernel/taxonomy-validator.js +394 -0
- package/lib/lefthook-check.js +3 -2
- 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/auto-actions.js +175 -0
- package/lib/pr-monitor/differ.js +195 -0
- package/lib/pr-monitor/digest.js +206 -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 +192 -0
- package/lib/pr-monitor/upsert-sticky.js +169 -0
- package/lib/pr-monitor/watch-lifecycle.js +95 -0
- package/lib/pr-monitor/watch.js +247 -0
- package/lib/pr-pull.js +1314 -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 +99 -497
- package/lib/protected-path-manifest.js +281 -0
- package/lib/protected-state-surfaces.js +387 -0
- package/lib/release-readiness.js +2105 -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 +241 -20
- 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/scoring.js +17 -3
- package/lib/status/beads-snapshot.js +45 -2
- package/lib/status/presenter.js +169 -18
- 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 +251 -0
- package/lib/workflow/enforce-stage.js +351 -45
- package/lib/workflow/stage-transition.js +115 -0
- package/lib/workflow/stages.js +30 -6
- package/lib/workflow/state-manager.js +11 -22
- package/lib/workflow/state.js +23 -1
- package/lib/workflow-profiles.js +17 -5
- package/package.json +37 -35
- 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 +81 -57
- package/scripts/beads-upgrade-smoke.sh +24 -3
- 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.sh +22 -3
- 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 +49 -84
- 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 +50 -83
- 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/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-auto-actions.js +93 -0
- package/scripts/pr-coordinator.sh +3 -0
- package/scripts/pr-verdict-label.js +50 -0
- package/scripts/preflight-sonar.eslint.config.mjs +44 -0
- package/scripts/preflight.sh +21 -94
- package/scripts/protected-state-check.js +104 -0
- package/scripts/smart-status.sh +60 -57
- 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-utils.sh +3 -0
- package/scripts/test-ci-shard.js +13 -6
- package/scripts/test.js +95 -12
- 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} +44 -50
- 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} +132 -157
- 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/{.github/prompts/ship.prompt.md → skills/ship/SKILL.md} +81 -45
- 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/{.github/prompts/status.prompt.md → skills/status/SKILL.md} +20 -10
- 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/verify.prompt.md +0 -271
- package/.github/workflows/beads-to-github.yml +0 -89
- package/.github/workflows/github-to-beads.yml +0 -100
- 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 -281
- 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-bootstrap.js +0 -225
- package/lib/beads-health-check.js +0 -188
- package/lib/commands/commands-reset.js +0 -147
- package/opencode.json +0 -67
- package/scripts/beads-context.test.js +0 -584
- 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 -356
- package/scripts/github-beads-sync/label-mapper.mjs +0 -54
- package/scripts/github-beads-sync/mapping.mjs +0 -132
- package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
- package/scripts/github-beads-sync/reverse-sync.mjs +0 -162
- 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,175 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PR-monitor Tier-2 auto-actions — the SAFE, fail-closed *action* half of the
|
|
5
|
+
* shepherd (issue addf5297, epic c2d398e5). Tier-1 (lib/pr-pull.js computeVerdict
|
|
6
|
+
* + the pr-monitor workflow) LABELS a PR's state but takes no action, so PRs that
|
|
7
|
+
* only need "master merged in" sit untended until a human/agent nudges them.
|
|
8
|
+
*
|
|
9
|
+
* This module decides — PURELY, from the SAME `forge shepherd <pr> --pull --json`
|
|
10
|
+
* payload the monitor already computes — whether the monitor may take one of two
|
|
11
|
+
* surface-safe actions:
|
|
12
|
+
* 1. `updateBranch` — merge base into an OTHERWISE-CLEAN-but-BEHIND PR (the
|
|
13
|
+
* "last mile" case). This is the highest-value, safest action: it clears the
|
|
14
|
+
* BEHIND churn without ever touching a PR that has a real blocker.
|
|
15
|
+
* 2. `rerunFlaky` — re-run a required check whose failure is INFRASTRUCTURAL
|
|
16
|
+
* (cancelled / timed-out / stale / startup-failure), never a real test
|
|
17
|
+
* FAILURE/ERROR.
|
|
18
|
+
*
|
|
19
|
+
* It NEVER merges, NEVER resolves review threads, NEVER force-pushes, NEVER edits
|
|
20
|
+
* code. It only decides; the workflow executes the `gh` calls (and owns
|
|
21
|
+
* per-head-SHA idempotency markers). Every gate below is fail-CLOSED: any missing
|
|
22
|
+
* field, degraded read, real failure, fork, draft, or unclassifiable signal
|
|
23
|
+
* yields `should:false`.
|
|
24
|
+
*
|
|
25
|
+
* @module pr-monitor/auto-actions
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Check conclusions that are INFRASTRUCTURAL flakes — a re-run may legitimately
|
|
30
|
+
* turn them green. Mirrors lib/pr-shepherd.js `isFailed`'s not-green terminal
|
|
31
|
+
* conclusions MINUS the genuinely-broken ones (FAILURE/ERROR/ACTION_REQUIRED).
|
|
32
|
+
*/
|
|
33
|
+
const INFRA_CONCLUSIONS = new Set(['CANCELLED', 'TIMED_OUT', 'STALE', 'STARTUP_FAILURE']);
|
|
34
|
+
|
|
35
|
+
/** Conclusions that mean the code is genuinely broken — NEVER auto-rerun these. */
|
|
36
|
+
const REAL_FAILURE_CONCLUSIONS = new Set(['FAILURE', 'ERROR', 'ACTION_REQUIRED']);
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Pull the numeric Actions run id from a job/run "details" URL
|
|
40
|
+
* (`.../actions/runs/<run>/job/<job>` or `.../actions/runs/<run>`). Returns null
|
|
41
|
+
* when absent — a null run id fails the rerun decision closed.
|
|
42
|
+
*
|
|
43
|
+
* @param {string} url
|
|
44
|
+
* @returns {string | null}
|
|
45
|
+
*/
|
|
46
|
+
function runIdFromUrl(url) {
|
|
47
|
+
const m = String(url || '').match(/\/runs\/(\d+)/);
|
|
48
|
+
return m ? m[1] : null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* A payload is DEGRADED (some verdict-relevant read failed or the head moved
|
|
53
|
+
* mid-gather) when its evidence lists unreadable sources or a torn read. Acting
|
|
54
|
+
* on a degraded gather could act on stale/false state, so both actions fail
|
|
55
|
+
* closed on it — even though `verdict==='BEHIND'` already implies a clean read,
|
|
56
|
+
* this stays an explicit, independent guard.
|
|
57
|
+
*
|
|
58
|
+
* @param {object} payload
|
|
59
|
+
* @returns {boolean}
|
|
60
|
+
*/
|
|
61
|
+
function isDegraded(payload) {
|
|
62
|
+
const ev = (payload && payload.evidence) || {};
|
|
63
|
+
const unreadable = Array.isArray(ev.unreadable) ? ev.unreadable : [];
|
|
64
|
+
return unreadable.length > 0 || ev.tornRead === true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Decide whether to auto-update (merge base into) an otherwise-clean-but-BEHIND
|
|
69
|
+
* PR. Fires ONLY for the "last mile" case:
|
|
70
|
+
* - verdict is exactly `BEHIND` (which itself guarantees rank-1 UNKNOWN and
|
|
71
|
+
* rank-2 BLOCKED-CONFLICT did NOT fire — i.e. the read was clean and there is
|
|
72
|
+
* no conflict);
|
|
73
|
+
* - the PR is NOT a draft and NOT a fork (a base-repo token cannot push a fork
|
|
74
|
+
* branch, and forks are out of scope);
|
|
75
|
+
* - the read is not degraded;
|
|
76
|
+
* - and the ONLY blocker is the behind-base one — every other blocker type
|
|
77
|
+
* (failing/missing/skipped/pending required checks, bot-status gates,
|
|
78
|
+
* unresolved threads, changes-requested / review-required, conflict) is
|
|
79
|
+
* absent. `blockers[]` is computed independently of the verdict precedence,
|
|
80
|
+
* so it still lists lower-precedence blockers that `BEHIND` masks — which is
|
|
81
|
+
* exactly why we key on it rather than on the single verdict string.
|
|
82
|
+
*
|
|
83
|
+
* @param {object} payload - the `--pull --json` payload.
|
|
84
|
+
* @param {{ isFork?: boolean }} [opts]
|
|
85
|
+
* @returns {{ should: boolean, reason: string }}
|
|
86
|
+
*/
|
|
87
|
+
function decideUpdateBranch(payload, opts = {}) {
|
|
88
|
+
const skip = (reason) => ({ should: false, reason });
|
|
89
|
+
if (!payload || typeof payload !== 'object') return skip('no payload — fail closed');
|
|
90
|
+
if (opts.isFork) return skip('fork PR — a base-repo token cannot update a fork branch');
|
|
91
|
+
if (isDegraded(payload)) return skip('degraded/torn read — fail closed');
|
|
92
|
+
if (payload.verdict !== 'BEHIND') return skip(`verdict ${payload.verdict || 'UNKNOWN'} is not BEHIND`);
|
|
93
|
+
if (payload.draft === true) return skip('draft PR — not ready to advance');
|
|
94
|
+
if (!Array.isArray(payload.blockers)) return skip('blockers[] unavailable — fail closed');
|
|
95
|
+
const others = payload.blockers.filter((b) => b && b.type !== 'behind');
|
|
96
|
+
if (others.length > 0) {
|
|
97
|
+
return skip(`other blocker(s) present: ${others.map((b) => b.type).join(', ')}`);
|
|
98
|
+
}
|
|
99
|
+
return { should: true, reason: 'otherwise-clean-behind — only blocker is behind-base; merge base in' };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Decide whether to re-run flaky REQUIRED checks. Fires ONLY when EVERY failing
|
|
104
|
+
* required check is infrastructural (cancelled/timed-out/stale/startup-failure)
|
|
105
|
+
* with a derivable run id, and NONE is a real FAILURE/ERROR/ACTION_REQUIRED. A
|
|
106
|
+
* single real failure, an unclassifiable conclusion, a required-failing check
|
|
107
|
+
* with no matching `failures[]` entry, or a missing run id fails the WHOLE
|
|
108
|
+
* decision closed (never rerun a genuinely-broken PR, never loop on a real bug).
|
|
109
|
+
*
|
|
110
|
+
* @param {object} payload - the `--pull --json` payload.
|
|
111
|
+
* @returns {{ should: boolean, checks: Array<{name:string,conclusion:string,runId:string|null}>, runIds: string[], reason: string }}
|
|
112
|
+
*/
|
|
113
|
+
function decideRerun(payload) {
|
|
114
|
+
const empty = (reason) => ({ should: false, checks: [], runIds: [], reason });
|
|
115
|
+
if (!payload || typeof payload !== 'object') return empty('no payload — fail closed');
|
|
116
|
+
if (isDegraded(payload)) return empty('degraded/torn read — fail closed');
|
|
117
|
+
|
|
118
|
+
const rc = payload.requiredChecks || {};
|
|
119
|
+
const failingNames = Array.isArray(rc.failing) ? rc.failing : [];
|
|
120
|
+
if (failingNames.length === 0) return empty('no failing required checks');
|
|
121
|
+
|
|
122
|
+
const failures = Array.isArray(payload.failures) ? payload.failures : [];
|
|
123
|
+
const conclByName = new Map();
|
|
124
|
+
const urlByName = new Map();
|
|
125
|
+
for (const f of failures) {
|
|
126
|
+
if (!f || !f.name) continue;
|
|
127
|
+
if (!conclByName.has(f.name)) {
|
|
128
|
+
conclByName.set(f.name, String(f.conclusion || '').toUpperCase());
|
|
129
|
+
urlByName.set(f.name, f.jobUrl || f.detailsUrl || null);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const picked = [];
|
|
134
|
+
for (const name of failingNames) {
|
|
135
|
+
const concl = conclByName.get(name);
|
|
136
|
+
if (!concl) return empty(`required check "${name}" has no known conclusion — cannot confirm flaky, fail closed`);
|
|
137
|
+
if (REAL_FAILURE_CONCLUSIONS.has(concl)) return empty(`required check "${name}" is a real failure (${concl}) — never rerun`);
|
|
138
|
+
if (!INFRA_CONCLUSIONS.has(concl)) return empty(`required check "${name}" conclusion ${concl} is not classified infrastructural — fail closed`);
|
|
139
|
+
picked.push({ name, conclusion: concl, runId: runIdFromUrl(urlByName.get(name)) });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const runIds = [...new Set(picked.map((p) => p.runId).filter(Boolean))];
|
|
143
|
+
if (runIds.length === 0) return empty('no run id derivable from failure jobUrl — fail closed');
|
|
144
|
+
return {
|
|
145
|
+
should: true,
|
|
146
|
+
checks: picked,
|
|
147
|
+
runIds,
|
|
148
|
+
reason: `all ${picked.length} failing required check(s) are infrastructural (${picked.map((p) => p.conclusion).join(', ')})`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Compute the full auto-action decision from a `--pull --json` payload. Pure and
|
|
154
|
+
* independently testable — no I/O, no `gh`, no side effects.
|
|
155
|
+
*
|
|
156
|
+
* @param {object} payload
|
|
157
|
+
* @param {{ isFork?: boolean }} [opts]
|
|
158
|
+
* @returns {{ updateBranch: object, rerunFlaky: object }}
|
|
159
|
+
*/
|
|
160
|
+
function decideAutoActions(payload, opts = {}) {
|
|
161
|
+
return {
|
|
162
|
+
updateBranch: decideUpdateBranch(payload, opts),
|
|
163
|
+
rerunFlaky: decideRerun(payload),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
module.exports = {
|
|
168
|
+
decideAutoActions,
|
|
169
|
+
decideUpdateBranch,
|
|
170
|
+
decideRerun,
|
|
171
|
+
runIdFromUrl,
|
|
172
|
+
isDegraded,
|
|
173
|
+
INFRA_CONCLUSIONS,
|
|
174
|
+
REAL_FAILURE_CONCLUSIONS,
|
|
175
|
+
};
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PR-monitor differ — the PURE heart of the monitor. `diffSnapshots(prev, next)`
|
|
5
|
+
* returns the ordered candidate events for the transition between two normalized
|
|
6
|
+
* snapshots (see lib/pr-monitor/gather.js for the shape). No I/O, no clock, no
|
|
7
|
+
* randomness — same inputs always yield the same events, which is what makes the
|
|
8
|
+
* monitor deterministic and duplicate-free across restarts.
|
|
9
|
+
*
|
|
10
|
+
* Each event carries a CONTENT key (head sha, threadId, check name+sha, comment
|
|
11
|
+
* id, review author+commit, surface) so the journal can dedup by identity.
|
|
12
|
+
*
|
|
13
|
+
* @module pr-monitor/differ
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const { EVENT_TYPES: T, makeEvent } = require('./events');
|
|
17
|
+
|
|
18
|
+
/** Index an array of objects by a key field into a Map. */
|
|
19
|
+
function indexBy(list, field) {
|
|
20
|
+
const map = new Map();
|
|
21
|
+
for (const item of (Array.isArray(list) ? list : [])) {
|
|
22
|
+
if (item?.[field] != null) map.set(String(item[field]), item);
|
|
23
|
+
}
|
|
24
|
+
return map;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** True when every check is green (none failed, none pending). */
|
|
28
|
+
function allGreen(checks) {
|
|
29
|
+
const list = Array.isArray(checks) ? checks : [];
|
|
30
|
+
return list.length > 0 && list.every((c) => c.class === 'green');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** head.pushed — the PR's head sha advanced. */
|
|
34
|
+
function diffHead(prev, next, out) {
|
|
35
|
+
if (next.headSha && prev.headSha !== next.headSha) {
|
|
36
|
+
out.push(makeEvent(T.HEAD_PUSHED, next.headSha, { from: prev.headSha || null, to: next.headSha }));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** check.failed / check.recovered / checks.green — keyed by name+sha. */
|
|
41
|
+
function diffChecks(prev, next, out) {
|
|
42
|
+
const prevByName = indexBy(prev.checks, 'name');
|
|
43
|
+
for (const c of (next.checks || [])) {
|
|
44
|
+
const before = prevByName.get(String(c.name));
|
|
45
|
+
const wasFailed = before?.class === 'failed';
|
|
46
|
+
if (c.class === 'failed' && !wasFailed) {
|
|
47
|
+
out.push(makeEvent(T.CHECK_FAILED, `${c.name}:${next.headSha}`, { name: c.name }));
|
|
48
|
+
} else if (c.class === 'green' && wasFailed) {
|
|
49
|
+
out.push(makeEvent(T.CHECK_RECOVERED, `${c.name}:${next.headSha}`, { name: c.name }));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (allGreen(next.checks) && !allGreen(prev.checks)) {
|
|
53
|
+
out.push(makeEvent(T.CHECKS_GREEN, next.headSha, {}));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** thread.opened / thread.reply / thread.resolved — keyed by threadId. */
|
|
58
|
+
function diffThreads(prev, next, out) {
|
|
59
|
+
const prevById = indexBy(prev.threads, 'threadId');
|
|
60
|
+
for (const t of (next.threads || [])) {
|
|
61
|
+
const before = prevById.get(String(t.threadId));
|
|
62
|
+
if (!before) {
|
|
63
|
+
if (t.actionable) out.push(makeEvent(T.THREAD_OPENED, t.threadId, { path: t.path || null }));
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if ((t.commentCount || 0) > (before.commentCount || 0)) {
|
|
67
|
+
out.push(makeEvent(T.THREAD_REPLY, `${t.threadId}:${t.commentCount}`, { threadId: t.threadId }));
|
|
68
|
+
}
|
|
69
|
+
if (t.isResolved && !before.isResolved) {
|
|
70
|
+
out.push(makeEvent(T.THREAD_RESOLVED, t.threadId, {}));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** comment.posted — a new direct PR issue comment, keyed by comment id. */
|
|
76
|
+
function diffComments(prev, next, out) {
|
|
77
|
+
const prevIds = indexBy(prev.comments, 'id');
|
|
78
|
+
for (const c of (next.comments || [])) {
|
|
79
|
+
if (!prevIds.has(String(c.id))) {
|
|
80
|
+
out.push(makeEvent(T.COMMENT_POSTED, c.id, { author: c.author || '' }));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** review.submitted — a new/updated review per author (state or commit changed). */
|
|
86
|
+
function diffReviews(prev, next, out) {
|
|
87
|
+
const prevByAuthor = indexBy(prev.reviews, 'author');
|
|
88
|
+
for (const r of (next.reviews || [])) {
|
|
89
|
+
const before = prevByAuthor.get(String(r.author));
|
|
90
|
+
const changed = !before
|
|
91
|
+
|| before.submittedAt !== r.submittedAt
|
|
92
|
+
|| before.commitOid !== r.commitOid;
|
|
93
|
+
if (changed) {
|
|
94
|
+
out.push(makeEvent(T.REVIEW_SUBMITTED, `${r.author}:${r.commitOid || r.submittedAt}`, {
|
|
95
|
+
author: r.author, verdict: r.state, commitOid: r.commitOid || null,
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** conflict.appeared / conflict.cleared — keyed by head sha. */
|
|
102
|
+
function diffConflict(prev, next, out) {
|
|
103
|
+
if (next.conflicts === true && prev.conflicts !== true) {
|
|
104
|
+
out.push(makeEvent(T.CONFLICT_APPEARED, next.headSha, {}));
|
|
105
|
+
} else if (next.conflicts === false && prev.conflicts === true) {
|
|
106
|
+
out.push(makeEvent(T.CONFLICT_CLEARED, next.headSha, {}));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** branch.behind — the branch fell behind its base. */
|
|
111
|
+
function diffBehind(prev, next, out) {
|
|
112
|
+
if ((next.behind || 0) > 0 && (prev.behind || 0) === 0) {
|
|
113
|
+
out.push(makeEvent(T.BRANCH_BEHIND, `${next.headSha}:${next.behind}`, { behind: next.behind }));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** verdict.changed — the fail-closed merge verdict flipped. */
|
|
118
|
+
function diffVerdict(prev, next, out) {
|
|
119
|
+
const from = prev.verdict ? prev.verdict.state : null;
|
|
120
|
+
const to = next.verdict ? next.verdict.state : null;
|
|
121
|
+
if (from !== to) {
|
|
122
|
+
out.push(makeEvent(T.VERDICT_CHANGED, `${from}->${to}:${next.headSha}`, {
|
|
123
|
+
from, to, reason: next.verdict ? next.verdict.reason || null : null,
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** pr.merged / pr.closed — terminal transitions from an OPEN PR. */
|
|
129
|
+
function diffPrState(prev, next, out) {
|
|
130
|
+
if (prev.prState === 'OPEN' && next.prState === 'MERGED') {
|
|
131
|
+
out.push(makeEvent(T.PR_MERGED, 'MERGED', {}));
|
|
132
|
+
} else if (prev.prState === 'OPEN' && next.prState === 'CLOSED') {
|
|
133
|
+
out.push(makeEvent(T.PR_CLOSED, 'CLOSED', {}));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** monitor.degraded — a read surface newly became unreadable (fail-closed). */
|
|
138
|
+
function diffDegraded(prev, next, out) {
|
|
139
|
+
const prevSurfaces = new Set((prev.degraded || []).map((d) => d.surface));
|
|
140
|
+
for (const d of (next.degraded || [])) {
|
|
141
|
+
if (!prevSurfaces.has(d.surface)) {
|
|
142
|
+
out.push(makeEvent(T.MONITOR_DEGRADED, d.surface, { surface: d.surface, error: d.error || '' }));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Baseline events for the FIRST pass (no prior snapshot): a single verdict
|
|
149
|
+
* baseline plus any already-terminal/degraded state — bounded, never a replay of
|
|
150
|
+
* every historical thread/check.
|
|
151
|
+
*/
|
|
152
|
+
function diffBaseline(next, out) {
|
|
153
|
+
out.push(makeEvent(T.VERDICT_CHANGED, `baseline:${next.headSha}`, {
|
|
154
|
+
from: null, to: next.verdict ? next.verdict.state : null,
|
|
155
|
+
reason: next.verdict ? next.verdict.reason || null : null,
|
|
156
|
+
}));
|
|
157
|
+
if (next.prState === 'MERGED') out.push(makeEvent(T.PR_MERGED, 'MERGED', {}));
|
|
158
|
+
if (next.prState === 'CLOSED') out.push(makeEvent(T.PR_CLOSED, 'CLOSED', {}));
|
|
159
|
+
for (const d of (next.degraded || [])) {
|
|
160
|
+
out.push(makeEvent(T.MONITOR_DEGRADED, d.surface, { surface: d.surface, error: d.error || '' }));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Diff two normalized snapshots into ordered candidate events. `prev === null`
|
|
166
|
+
* → first-pass baseline.
|
|
167
|
+
*
|
|
168
|
+
* @param {object|null} prev
|
|
169
|
+
* @param {object} next
|
|
170
|
+
* @returns {Array<{ type: string, key: string, data: object }>}
|
|
171
|
+
*/
|
|
172
|
+
function diffSnapshots(prev, next) {
|
|
173
|
+
const out = [];
|
|
174
|
+
if (!prev) {
|
|
175
|
+
diffBaseline(next, out);
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
diffHead(prev, next, out);
|
|
179
|
+
diffChecks(prev, next, out);
|
|
180
|
+
diffThreads(prev, next, out);
|
|
181
|
+
diffComments(prev, next, out);
|
|
182
|
+
diffReviews(prev, next, out);
|
|
183
|
+
diffConflict(prev, next, out);
|
|
184
|
+
diffBehind(prev, next, out);
|
|
185
|
+
diffVerdict(prev, next, out);
|
|
186
|
+
diffPrState(prev, next, out);
|
|
187
|
+
diffDegraded(prev, next, out);
|
|
188
|
+
return out;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
module.exports = {
|
|
192
|
+
diffSnapshots,
|
|
193
|
+
allGreen,
|
|
194
|
+
indexBy,
|
|
195
|
+
};
|