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,66 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { runIssueOperation } = require('../forge-issues');
|
|
4
|
+
const { SUBCOMMANDS } = require('./_issue');
|
|
5
|
+
|
|
6
|
+
const SUPPORTED_SUBCOMMANDS = ['create', 'list', 'show', 'close', 'update'];
|
|
7
|
+
|
|
8
|
+
function normalizeArgs(args = []) {
|
|
9
|
+
return args.filter(arg => arg !== '--');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function formatHelp() {
|
|
13
|
+
return [
|
|
14
|
+
'Usage: forge issues <subcommand> [...]',
|
|
15
|
+
'',
|
|
16
|
+
'Supported subcommands:',
|
|
17
|
+
' create Create an issue through Forge',
|
|
18
|
+
' list List issues through Forge',
|
|
19
|
+
' show Show a single issue through Forge',
|
|
20
|
+
' close Close an issue through Forge',
|
|
21
|
+
' update Update an issue through Forge',
|
|
22
|
+
].join('\n');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = {
|
|
26
|
+
name: 'issues',
|
|
27
|
+
description: 'Manage issues through the Forge authority layer',
|
|
28
|
+
usage: 'forge issues <subcommand> [...]',
|
|
29
|
+
// Undocumented back-compat alias for the canonical `forge issue` surface. It stays
|
|
30
|
+
// registered and executable, but `hidden: true` omits it from `forge --help` so the
|
|
31
|
+
// singular `issue` reads as the one canonical issue surface (kernel issue 450c6e34).
|
|
32
|
+
hidden: true,
|
|
33
|
+
flags: {},
|
|
34
|
+
handler: async (args, _flags, projectRoot, opts = {}) => {
|
|
35
|
+
const [subcommand, ...rest] = normalizeArgs(args);
|
|
36
|
+
|
|
37
|
+
if (!subcommand || subcommand === '--help' || subcommand === '-h') {
|
|
38
|
+
return {
|
|
39
|
+
success: true,
|
|
40
|
+
output: formatHelp(),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!SUPPORTED_SUBCOMMANDS.includes(subcommand)) {
|
|
45
|
+
return {
|
|
46
|
+
success: false,
|
|
47
|
+
error: `Unknown issue subcommand '${subcommand}'.\n\n${formatHelp()}`,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// --help for a specific subcommand: print its usage and return BEFORE dispatching
|
|
52
|
+
// to any backend. Otherwise --help is forwarded as an operation arg, which is
|
|
53
|
+
// absorbed by Beads but fails with a bare "Command failed" on the Kernel default
|
|
54
|
+
// (mirrors the short-circuit in lib/commands/_issue.js runIssueSubcommand).
|
|
55
|
+
if (rest.some(arg => arg === '--help' || arg === '-h')) {
|
|
56
|
+
const spec = SUBCOMMANDS[subcommand];
|
|
57
|
+
return {
|
|
58
|
+
success: true,
|
|
59
|
+
output: spec ? `${spec.usage}\n\n${spec.description}` : formatHelp(),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const runner = opts.runIssueOperation || runIssueOperation;
|
|
64
|
+
return runner(subcommand, rest, projectRoot, opts);
|
|
65
|
+
},
|
|
66
|
+
};
|
package/lib/commands/list.js
CHANGED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* merge command — opt-in conditional auto-merge.
|
|
5
|
+
*
|
|
6
|
+
* `forge merge --auto <pr>` is the ONLY path by which Forge will merge a PR on
|
|
7
|
+
* its own, and it stays OFF unless the user has explicitly opted in. It reads
|
|
8
|
+
* the `merge.auto` section of `.forge/config.yaml`:
|
|
9
|
+
*
|
|
10
|
+
* merge:
|
|
11
|
+
* auto:
|
|
12
|
+
* enabled: true # default false / absent → strict NO-OP
|
|
13
|
+
* rules: # ALL must pass (see lib/merge-rules.js)
|
|
14
|
+
* - checks_green # or scope it, e.g. for a docs-only task that
|
|
15
|
+
* # may take a coverage dip:
|
|
16
|
+
* # - checks_green: { ignore: ["Coverage"] }
|
|
17
|
+
* - threads_resolved
|
|
18
|
+
* - no_conflicts # recommended: never merge a conflicting branch
|
|
19
|
+
* - not_behind
|
|
20
|
+
* - settle_min: 10
|
|
21
|
+
*
|
|
22
|
+
* Flow: with no config (or `enabled` not true) the command is a strict NO-OP —
|
|
23
|
+
* it prints why and merges nothing, preserving the test-enforced
|
|
24
|
+
* never-auto-merge-by-default invariant. When enabled, it fetches the PR
|
|
25
|
+
* context via `gh`, evaluates the rules with the pure `evaluateMergeRules`, and
|
|
26
|
+
* merges ONLY when every rule passes. Two extra safety layers wrap that decision:
|
|
27
|
+
* a pre-flight guard that no-ops on an already merged/closed PR (idempotent
|
|
28
|
+
* re-runs), and a TOCTOU live re-check that re-fetches and re-evaluates right
|
|
29
|
+
* before merging so a stale snapshot can never merge a since-changed PR. The
|
|
30
|
+
* gh-fetch and the merge action are isolated behind injectable `fetchPrContext`
|
|
31
|
+
* / `mergePr` seams so the decision path is unit-testable without the network.
|
|
32
|
+
*
|
|
33
|
+
* A bring-your-own custom-predicate seam (registered via `forge add`) is a
|
|
34
|
+
* documented follow-up and intentionally NOT wired here. Further follow-ups
|
|
35
|
+
* documented in lib/merge-rules.js: opt-in `auto_update`, required-checks
|
|
36
|
+
* scoping for `checks_green`, a configurable merge `method`, and post-merge
|
|
37
|
+
* branch deletion.
|
|
38
|
+
*
|
|
39
|
+
* @module commands/merge
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
const { execFileSync } = require('node:child_process');
|
|
43
|
+
|
|
44
|
+
const { loadRawConfig } = require('../config-writer');
|
|
45
|
+
const { evaluateMergeRules } = require('../merge-rules');
|
|
46
|
+
|
|
47
|
+
/** Default `gh` runner. Only reached by the default fetch/merge seams (never in unit tests). */
|
|
48
|
+
function defaultGh(args, options = {}) {
|
|
49
|
+
return execFileSync('gh', args, { encoding: 'utf8', ...options }); // NOSONAR S4036 - hardcoded CLI (gh), args array (no shell), developer-tool context
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Parse `gh ... --json` output, returning `null` on any failure (callers fail closed). */
|
|
53
|
+
function ghJson(gh, args) {
|
|
54
|
+
try {
|
|
55
|
+
return JSON.parse(gh(args) || '{}');
|
|
56
|
+
} catch (_err) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Read the unresolved review-thread count via the GraphQL API. `reviewThreads`
|
|
63
|
+
* is not a valid `gh pr view --json` field, so this needs a dedicated query.
|
|
64
|
+
* Returns `undefined` on any failure so `threads_resolved` fails closed.
|
|
65
|
+
*/
|
|
66
|
+
function fetchUnresolvedThreadCount(gh, pr) {
|
|
67
|
+
try {
|
|
68
|
+
const repo = ghJson(gh, ['repo', 'view', '--json', 'owner,name']);
|
|
69
|
+
const owner = repo && repo.owner && repo.owner.login;
|
|
70
|
+
const name = repo && repo.name;
|
|
71
|
+
if (!owner || !name) return undefined;
|
|
72
|
+
// Paginate through ALL review threads — a PR can have >100, and the ones on
|
|
73
|
+
// later pages could be the unresolved/newest ones. Stopping at page 1 would
|
|
74
|
+
// both miss them and make a large PR un-mergeable. Loop on the GraphQL cursor
|
|
75
|
+
// until hasNextPage is false; any unreadable page → undefined (fail closed).
|
|
76
|
+
const query = 'query($o:String!,$n:String!,$pr:Int!,$after:String){repository(owner:$o,name:$n)'
|
|
77
|
+
+ '{pullRequest(number:$pr){reviewThreads(first:100,after:$after){pageInfo{hasNextPage endCursor}nodes{isResolved isOutdated}}}}}';
|
|
78
|
+
let after = '';
|
|
79
|
+
let count = 0;
|
|
80
|
+
for (let page = 0; page < 100; page += 1) { // 100-page cap = 10k threads backstop
|
|
81
|
+
const args = ['api', 'graphql', '-f', `query=${query}`,
|
|
82
|
+
'-F', `o=${owner}`, '-F', `n=${name}`, '-F', `pr=${Number(pr)}`];
|
|
83
|
+
if (after) args.push('-F', `after=${after}`);
|
|
84
|
+
const data = JSON.parse(gh(args) || '{}');
|
|
85
|
+
const threads = (((data.data || {}).repository || {}).pullRequest || {}).reviewThreads;
|
|
86
|
+
if (!threads) return undefined; // unreadable page → fail closed
|
|
87
|
+
count += (threads.nodes || []).filter((t) => t && t.isResolved === false && t.isOutdated === false).length;
|
|
88
|
+
if (!threads.pageInfo || !threads.pageInfo.hasNextPage) return count;
|
|
89
|
+
after = threads.pageInfo.endCursor;
|
|
90
|
+
if (!after) return undefined; // hasNextPage but no cursor → fail closed
|
|
91
|
+
}
|
|
92
|
+
return undefined; // exceeded the page cap → fail closed rather than undercount
|
|
93
|
+
} catch (_err) {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Default PR-context fetcher (the network seam). Assembles the shape consumed
|
|
100
|
+
* by `evaluateMergeRules` from `gh`. Anything it cannot read is left absent so
|
|
101
|
+
* the dependent rule fails closed rather than guessing. Fully replaced by
|
|
102
|
+
* `deps.fetchPrContext` in tests.
|
|
103
|
+
*
|
|
104
|
+
* @returns {object} prContext
|
|
105
|
+
*/
|
|
106
|
+
function defaultFetchPrContext({ pr, gh = defaultGh, now = Date.now() }) {
|
|
107
|
+
const view = ghJson(gh, ['pr', 'view', String(pr), '--json',
|
|
108
|
+
'number,state,isDraft,mergeable,mergeStateStatus,statusCheckRollup,reviews,comments,updatedAt']) || {};
|
|
109
|
+
|
|
110
|
+
const rollup = Array.isArray(view.statusCheckRollup) ? view.statusCheckRollup : null;
|
|
111
|
+
const checks = (rollup || []).map((c) => ({
|
|
112
|
+
name: c.name || c.context || '?',
|
|
113
|
+
conclusion: String(c.conclusion || c.state || c.status || ''),
|
|
114
|
+
}));
|
|
115
|
+
|
|
116
|
+
const comments = Array.isArray(view.comments)
|
|
117
|
+
? view.comments.map((c) => ({
|
|
118
|
+
author: (c.author && c.author.login) || '',
|
|
119
|
+
at: c.createdAt || c.submittedAt || '',
|
|
120
|
+
}))
|
|
121
|
+
: [];
|
|
122
|
+
|
|
123
|
+
const reviews = Array.isArray(view.reviews) ? view.reviews : [];
|
|
124
|
+
const approvals = reviews
|
|
125
|
+
.filter((r) => String(r.state).toUpperCase() === 'APPROVED')
|
|
126
|
+
.map((r) => ({ author: (r.author && r.author.login) || '' }));
|
|
127
|
+
|
|
128
|
+
// Derive from GitHub's mergeStateStatus / mergeable. Only a known set maps to
|
|
129
|
+
// a definite answer; anything else stays undefined so the dependent rule fails
|
|
130
|
+
// closed rather than guessing.
|
|
131
|
+
const mergeStateStatus = String(view.mergeStateStatus || '').toUpperCase();
|
|
132
|
+
const mergeable = String(view.mergeable || '').toUpperCase();
|
|
133
|
+
|
|
134
|
+
let behindBase;
|
|
135
|
+
if (mergeStateStatus === 'BEHIND') behindBase = true;
|
|
136
|
+
else if (['CLEAN', 'UNSTABLE', 'HAS_HOOKS', 'BLOCKED', 'DIRTY'].includes(mergeStateStatus)) behindBase = false;
|
|
137
|
+
|
|
138
|
+
// Conflict status: DIRTY (or mergeable=CONFLICTING) → conflicting; a clean set
|
|
139
|
+
// of states → not conflicting; UNKNOWN / missing / still-computing → undefined.
|
|
140
|
+
let conflicting;
|
|
141
|
+
if (mergeStateStatus === 'DIRTY' || mergeable === 'CONFLICTING') conflicting = true;
|
|
142
|
+
else if (['CLEAN', 'UNSTABLE', 'HAS_HOOKS', 'BLOCKED'].includes(mergeStateStatus)) conflicting = false;
|
|
143
|
+
|
|
144
|
+
const isDraft = typeof view.isDraft === 'boolean' ? view.isDraft : undefined;
|
|
145
|
+
const state = view.state ? String(view.state).toUpperCase() : undefined;
|
|
146
|
+
|
|
147
|
+
const stamps = [
|
|
148
|
+
...comments.map((c) => c.at),
|
|
149
|
+
...reviews.map((r) => r.submittedAt || r.createdAt || ''),
|
|
150
|
+
view.updatedAt || '',
|
|
151
|
+
].map((s) => Date.parse(s)).filter((n) => !Number.isNaN(n));
|
|
152
|
+
const lastActivityAt = stamps.length ? Math.max(...stamps) : undefined;
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
checks,
|
|
156
|
+
requiredChecksKnown: rollup !== null,
|
|
157
|
+
unresolvedThreads: fetchUnresolvedThreadCount(gh, pr),
|
|
158
|
+
behindBase,
|
|
159
|
+
conflicting,
|
|
160
|
+
isDraft,
|
|
161
|
+
state,
|
|
162
|
+
approvals,
|
|
163
|
+
comments,
|
|
164
|
+
lastActivityAt,
|
|
165
|
+
now,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Default merge action (squash). Fully replaced by `deps.mergePr` in tests. */
|
|
170
|
+
function defaultMergePr({ pr, gh = defaultGh }) {
|
|
171
|
+
gh(['pr', 'merge', String(pr), '--squash']);
|
|
172
|
+
return { merged: true, method: 'squash' };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Command handler.
|
|
177
|
+
*
|
|
178
|
+
* @param {string[]} args - Positional + flag args (first positional is the PR).
|
|
179
|
+
* @param {object} _flags - Parsed flags (unused; flags are read from args).
|
|
180
|
+
* @param {string} projectRoot - Project root.
|
|
181
|
+
* @param {object} [deps] - Injected seams for testing: loadConfig, fetchPrContext, mergePr, gh, now.
|
|
182
|
+
* @returns {Promise<object>} result envelope.
|
|
183
|
+
*/
|
|
184
|
+
async function handler(args, _flags, projectRoot, deps = {}) {
|
|
185
|
+
const argv = Array.isArray(args) ? args : [];
|
|
186
|
+
const positional = argv.filter((a) => !String(a).startsWith('--'));
|
|
187
|
+
const flags = new Set(argv.filter((a) => String(a).startsWith('--')));
|
|
188
|
+
const pr = positional[0];
|
|
189
|
+
const root = projectRoot || process.cwd();
|
|
190
|
+
|
|
191
|
+
if (!flags.has('--auto')) {
|
|
192
|
+
return { success: false, error: 'Usage: forge merge --auto <pr> (opt-in conditional auto-merge; OFF by default)' };
|
|
193
|
+
}
|
|
194
|
+
if (!pr) {
|
|
195
|
+
return { success: false, error: 'Usage: forge merge --auto <pr>' };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const loadConfig = deps.loadConfig || loadRawConfig;
|
|
199
|
+
let config;
|
|
200
|
+
try {
|
|
201
|
+
config = loadConfig(root) || {};
|
|
202
|
+
} catch (err) {
|
|
203
|
+
// A malformed .forge/config.yaml must NOT crash the command — fail closed:
|
|
204
|
+
// refuse to auto-merge and report, rather than throwing past the contract.
|
|
205
|
+
const reason = `Could not read merge config (${err.message}) — refusing to auto-merge (fail-closed).`;
|
|
206
|
+
process.stdout.write(`${reason}\n`);
|
|
207
|
+
return { success: false, merged: false, error: reason };
|
|
208
|
+
}
|
|
209
|
+
const auto = (config.merge && config.merge.auto) || {};
|
|
210
|
+
const enabled = auto.enabled === true;
|
|
211
|
+
const rules = Array.isArray(auto.rules) ? auto.rules : [];
|
|
212
|
+
|
|
213
|
+
// Invariant: absent config or `enabled` not true → strict NO-OP. Forge never
|
|
214
|
+
// auto-merges unless the user has explicitly opted in via .forge/config.yaml.
|
|
215
|
+
if (!enabled) {
|
|
216
|
+
const reason = 'Auto-merge is OPT-IN and OFF by default (merge.auto.enabled is not true in .forge/config.yaml). No action taken.';
|
|
217
|
+
process.stdout.write(`${reason}\n`);
|
|
218
|
+
return { success: true, merged: false, enabled: false, reason };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Opted in but no rules → refuse (fail-closed): an empty ruleset is vacuously
|
|
222
|
+
// "allowed", which would merge unconditionally. Treat that as misconfiguration.
|
|
223
|
+
if (rules.length === 0) {
|
|
224
|
+
const reason = 'merge.auto.enabled is true but no rules are configured — refusing to auto-merge (fail-closed). Add rules under merge.auto.rules.';
|
|
225
|
+
process.stdout.write(`${reason}\n`);
|
|
226
|
+
return { success: false, merged: false, enabled: true, reason };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const fetchPrContext = deps.fetchPrContext || defaultFetchPrContext;
|
|
230
|
+
const mergePr = deps.mergePr || defaultMergePr;
|
|
231
|
+
const gh = deps.gh || defaultGh;
|
|
232
|
+
|
|
233
|
+
let prContext;
|
|
234
|
+
try {
|
|
235
|
+
prContext = await fetchPrContext({ pr, projectRoot: root, gh, now: deps.now || Date.now() });
|
|
236
|
+
} catch (err) {
|
|
237
|
+
return { success: false, merged: false, error: `Failed to fetch PR context: ${err.message}` };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Pre-flight: a PR that is already merged or closed is terminal. Re-running the
|
|
241
|
+
// command must be an idempotent NO-OP — never an error and never a second merge
|
|
242
|
+
// attempt. (An absent/unknown state falls through to the fail-closed rules.)
|
|
243
|
+
const prState = prContext && prContext.state ? String(prContext.state).toUpperCase() : '';
|
|
244
|
+
if (prState && prState !== 'OPEN') {
|
|
245
|
+
const reason = `PR #${pr} is ${prState} (not OPEN) — nothing to merge. No action taken.`;
|
|
246
|
+
process.stdout.write(`${reason}\n`);
|
|
247
|
+
return { success: true, merged: false, enabled: true, state: prState, reason };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const { allowed, unmet } = evaluateMergeRules(prContext, rules);
|
|
251
|
+
|
|
252
|
+
if (!allowed) {
|
|
253
|
+
process.stdout.write(`Auto-merge conditions NOT met for PR #${pr} — ${unmet.length} unmet rule(s):\n`);
|
|
254
|
+
for (const item of unmet) {
|
|
255
|
+
process.stdout.write(` x ${item.rule} — ${item.reason}\n`);
|
|
256
|
+
}
|
|
257
|
+
return { success: true, merged: false, enabled: true, allowed: false, unmet, reason: 'auto-merge conditions not met' };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// TOCTOU guard: PR state can change between the first fetch and the merge — a
|
|
261
|
+
// new comment resets settle_min, a required check regresses, a thread opens.
|
|
262
|
+
// Re-pull LIVE data and re-evaluate immediately before merging so our custom
|
|
263
|
+
// rules (which GitHub's server-side branch protection does NOT enforce) are
|
|
264
|
+
// honored against the freshest possible state, never a stale snapshot.
|
|
265
|
+
let freshContext;
|
|
266
|
+
try {
|
|
267
|
+
freshContext = await fetchPrContext({ pr, projectRoot: root, gh, now: deps.now || Date.now() });
|
|
268
|
+
} catch (err) {
|
|
269
|
+
return { success: false, merged: false, error: `Failed to re-fetch PR context before merge: ${err.message}` };
|
|
270
|
+
}
|
|
271
|
+
// Re-apply the terminal-state guard on the FRESH context: the PR may have been
|
|
272
|
+
// merged or closed between the first fetch and now. Never merge a terminal PR.
|
|
273
|
+
const freshState = freshContext && freshContext.state ? String(freshContext.state).toUpperCase() : '';
|
|
274
|
+
if (freshState && freshState !== 'OPEN') {
|
|
275
|
+
const reason = `PR #${pr} became ${freshState} (not OPEN) before merge — nothing to merge. No action taken.`;
|
|
276
|
+
process.stdout.write(`${reason}\n`);
|
|
277
|
+
return { success: true, merged: false, enabled: true, state: freshState, reason };
|
|
278
|
+
}
|
|
279
|
+
const recheck = evaluateMergeRules(freshContext, rules);
|
|
280
|
+
if (!recheck.allowed) {
|
|
281
|
+
process.stdout.write(`Auto-merge ABORTED for PR #${pr} — state changed since first check; ${recheck.unmet.length} rule(s) now unmet:\n`);
|
|
282
|
+
for (const item of recheck.unmet) {
|
|
283
|
+
process.stdout.write(` x ${item.rule} — ${item.reason}\n`);
|
|
284
|
+
}
|
|
285
|
+
return { success: true, merged: false, enabled: true, allowed: false, unmet: recheck.unmet, reason: 'PR state changed before merge (live re-check failed)' };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
const mergeResult = await mergePr({ pr, projectRoot: root, gh });
|
|
290
|
+
process.stdout.write(`All ${rules.length} merge rule(s) passed — merged PR #${pr}.\n`);
|
|
291
|
+
return {
|
|
292
|
+
success: true,
|
|
293
|
+
merged: true,
|
|
294
|
+
enabled: true,
|
|
295
|
+
allowed: true,
|
|
296
|
+
reason: 'all merge rules passed',
|
|
297
|
+
...(mergeResult && typeof mergeResult === 'object' ? mergeResult : {}),
|
|
298
|
+
};
|
|
299
|
+
} catch (err) {
|
|
300
|
+
return { success: false, merged: false, error: `Merge failed: ${err.message}` };
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
module.exports = {
|
|
305
|
+
name: 'merge',
|
|
306
|
+
description: 'Opt-in conditional auto-merge: merge a PR only when all user-configured rules pass (OFF by default)',
|
|
307
|
+
usage: 'Usage: forge merge --auto <pr>',
|
|
308
|
+
handler,
|
|
309
|
+
// Exported seams for testing / reuse.
|
|
310
|
+
defaultFetchPrContext,
|
|
311
|
+
defaultMergePr,
|
|
312
|
+
};
|