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,484 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PR-state adapter (`kind: 'pr-state'`).
|
|
5
|
+
*
|
|
6
|
+
* Wraps read-only GitHub/git inspection plus a small set of idempotent,
|
|
7
|
+
* reversible side-effects used by the PR shepherd:
|
|
8
|
+
* - `readState` → `gh pr view --json ...`
|
|
9
|
+
* - `readRequiredChecks` → `gh api repos/{o}/{r}/branches/{base}/protection/required_status_checks`
|
|
10
|
+
* - `readDivergence` → `git rev-list --left-right --count {baseRef}...HEAD`
|
|
11
|
+
* - `detectConflicts` → `git merge-tree --write-tree {baseRef} HEAD` (predict-only)
|
|
12
|
+
* - `rerunFailedChecks` → `gh run rerun <id> --failed`
|
|
13
|
+
* - `replyToThread` → shell-out to `.claude/scripts/review-resolve.sh reply` (reply ONLY,
|
|
14
|
+
* never resolve — resolution stays with the semantic `/review` agent)
|
|
15
|
+
*
|
|
16
|
+
* This adapter is its own SPI; it does NOT extend the review adapter and is
|
|
17
|
+
* validated by `validatePrStateAdapter` (lib/pr-state-validator.js).
|
|
18
|
+
*
|
|
19
|
+
* It contains no merge or rebase machinery. Divergence handling (rebase) lives
|
|
20
|
+
* in the core/CLI behind an opt-in flag and is injected as `rebaseOntoBase`
|
|
21
|
+
* when enabled — it is never a default capability of this read surface.
|
|
22
|
+
*
|
|
23
|
+
* @module adapters/pr-state-adapter
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const { execFileSync } = require('node:child_process');
|
|
27
|
+
|
|
28
|
+
// NOTE: `reviewThreads` is NOT a valid `gh pr view --json` field — requesting it
|
|
29
|
+
// makes `gh` exit non-zero ("Unknown JSON field"), which crashed readState on every
|
|
30
|
+
// real PR. Review threads are read separately via GraphQL in readComments().
|
|
31
|
+
const PR_VIEW_FIELDS = [
|
|
32
|
+
'headRefOid',
|
|
33
|
+
'mergeable',
|
|
34
|
+
'mergeStateStatus',
|
|
35
|
+
'state',
|
|
36
|
+
'statusCheckRollup',
|
|
37
|
+
// `reviewDecision` (REVIEW_REQUIRED / CHANGES_REQUESTED / APPROVED / '') and
|
|
38
|
+
// `isDraft` are PR-level MERGE BLOCKERS the pull-signal payload surfaces — a
|
|
39
|
+
// draft PR or a missing/negative review decision blocks merge even when every
|
|
40
|
+
// check is green. Both are valid `gh pr view --json` fields.
|
|
41
|
+
'reviewDecision',
|
|
42
|
+
'isDraft',
|
|
43
|
+
].join(',');
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Classify an error thrown by the `gh` runner so the core can react.
|
|
47
|
+
* Returns `null` when the error is not auth/rate related.
|
|
48
|
+
*
|
|
49
|
+
* @param {Error} error
|
|
50
|
+
* @returns {{ class: string, retryAfter?: number } | null}
|
|
51
|
+
*/
|
|
52
|
+
function classifyAuthError(error) {
|
|
53
|
+
if (!error) return null;
|
|
54
|
+
const status = error.httpStatus
|
|
55
|
+
|| (typeof error.status === 'number' ? error.status : undefined);
|
|
56
|
+
const text = `${error.stderr || ''} ${error.message || ''}`;
|
|
57
|
+
const retryAfter = Number(error.retryAfter) || undefined;
|
|
58
|
+
|
|
59
|
+
if (status === 401 || /HTTP 401|bad credentials|token expired/i.test(text)) {
|
|
60
|
+
return { class: 'expired' };
|
|
61
|
+
}
|
|
62
|
+
if (status === 403 || /HTTP 403/i.test(text)) {
|
|
63
|
+
if (retryAfter || /rate limit|secondary rate/i.test(text)) {
|
|
64
|
+
return { class: 'rate-limit', retryAfter };
|
|
65
|
+
}
|
|
66
|
+
return { class: 'insufficient-scope' };
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
class PrStateAdapter {
|
|
72
|
+
/**
|
|
73
|
+
* @param {object} [options]
|
|
74
|
+
* @param {Function} [options.gh] - Runner for `gh` (cmd, args[]) → string.
|
|
75
|
+
* @param {Function} [options.git] - Runner for `git` (cmd, args[]) → string.
|
|
76
|
+
*/
|
|
77
|
+
constructor(options = {}) {
|
|
78
|
+
this.id = options.id || 'pr-state-adapter';
|
|
79
|
+
this.kind = 'pr-state';
|
|
80
|
+
this.name = options.name || this.id;
|
|
81
|
+
const defaultRunner = (cmd, args, opts = {}) => execFileSync(cmd, args, {
|
|
82
|
+
encoding: 'utf8',
|
|
83
|
+
timeout: options.timeout || 30000,
|
|
84
|
+
...(opts.cwd ? { cwd: opts.cwd } : {}),
|
|
85
|
+
});
|
|
86
|
+
this._gh = options.gh || defaultRunner;
|
|
87
|
+
this._git = options.git || defaultRunner;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Read normalized PR/CI state.
|
|
92
|
+
*
|
|
93
|
+
* @param {string} pr - PR number or URL.
|
|
94
|
+
* @returns {Promise<{ headSha: string, mergeable: string, mergeStateStatus: string, checks: object[], threads: object[] }>}
|
|
95
|
+
*/
|
|
96
|
+
async readState(pr) {
|
|
97
|
+
const raw = this._gh('gh', ['pr', 'view', String(pr), '--json', PR_VIEW_FIELDS]);
|
|
98
|
+
const data = JSON.parse(raw || '{}');
|
|
99
|
+
const rollup = Array.isArray(data.statusCheckRollup) ? data.statusCheckRollup : [];
|
|
100
|
+
return {
|
|
101
|
+
headSha: data.headRefOid || '',
|
|
102
|
+
state: String(data.state || 'OPEN').toUpperCase(),
|
|
103
|
+
mergeable: data.mergeable || 'UNKNOWN',
|
|
104
|
+
mergeStateStatus: data.mergeStateStatus || 'UNKNOWN',
|
|
105
|
+
// PR-level merge blockers (surfaced by the pull signal). `reviewDecision` is
|
|
106
|
+
// '' when no review is required; normalized to null so consumers can treat
|
|
107
|
+
// "not required" and "unknown" uniformly. `isDraft` blocks merge outright.
|
|
108
|
+
reviewDecision: data.reviewDecision || null,
|
|
109
|
+
isDraft: Boolean(data.isDraft),
|
|
110
|
+
// The rollup mixes two GraphQL types: a CheckRun (name/status/conclusion/
|
|
111
|
+
// detailsUrl) and a legacy commit StatusContext (context/state/targetUrl —
|
|
112
|
+
// Vercel/Netlify/other deploy+quality bots). Both are normalized into ONE
|
|
113
|
+
// shape here so the failing/pending classification treats them identically;
|
|
114
|
+
// `state` fills the conclusion slot for a StatusContext (SUCCESS/FAILURE/
|
|
115
|
+
// ERROR/PENDING) and `targetUrl` fills the details link.
|
|
116
|
+
checks: rollup.map((check) => ({
|
|
117
|
+
name: check.name || check.context || '',
|
|
118
|
+
status: check.status || check.state || '',
|
|
119
|
+
conclusion: check.conclusion || check.state || '',
|
|
120
|
+
databaseId: check.databaseId,
|
|
121
|
+
detailsUrl: check.detailsUrl || check.targetUrl,
|
|
122
|
+
})),
|
|
123
|
+
// gh pr view cannot return review threads; the shepherd reads them via
|
|
124
|
+
// readComments() (GraphQL). Kept for return-shape stability, always empty here.
|
|
125
|
+
threads: [],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Read the branch-protection required-checks set.
|
|
131
|
+
*
|
|
132
|
+
* Returns `null` when the protection endpoint is unreadable (e.g. 403
|
|
133
|
+
* insufficient scope, the branch is not protected, or the payload shape is
|
|
134
|
+
* unexpected) so the caller can escalate rather than guess. Re-throws non-auth
|
|
135
|
+
* errors.
|
|
136
|
+
*
|
|
137
|
+
* @param {{ owner: string, repo: string, base: string }} ctx
|
|
138
|
+
* @returns {Promise<string[] | null>}
|
|
139
|
+
*/
|
|
140
|
+
async readRequiredChecks({ owner, repo, base }) {
|
|
141
|
+
const apiPath = `repos/${owner}/${repo}/branches/${encodeURIComponent(base)}/protection/required_status_checks`;
|
|
142
|
+
try {
|
|
143
|
+
const raw = this._gh('gh', ['api', apiPath]);
|
|
144
|
+
const data = JSON.parse(raw || '{}');
|
|
145
|
+
if (Array.isArray(data.contexts)) return data.contexts;
|
|
146
|
+
if (Array.isArray(data.checks)) return data.checks.map((c) => c.context).filter(Boolean);
|
|
147
|
+
// Unexpected/changed payload shape — treat as unreadable, not "no required
|
|
148
|
+
// checks", so merge readiness is never computed from bad data.
|
|
149
|
+
return null;
|
|
150
|
+
} catch (error) {
|
|
151
|
+
const auth = classifyAuthError(error);
|
|
152
|
+
if (auth) {
|
|
153
|
+
// Unreadable protection (auth/scope/not-protected) — caller escalates.
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Read ahead/behind divergence against the base ref.
|
|
162
|
+
*
|
|
163
|
+
* `cwd` is threaded through to the git runner so divergence is computed
|
|
164
|
+
* against the target worktree/checkout, not the process directory.
|
|
165
|
+
*
|
|
166
|
+
* @param {{ baseRef: string, cwd?: string }} ctx
|
|
167
|
+
* @returns {Promise<{ behind: number, ahead: number }>}
|
|
168
|
+
*/
|
|
169
|
+
async readDivergence({ baseRef, cwd }) {
|
|
170
|
+
const out = this._git(
|
|
171
|
+
'git',
|
|
172
|
+
['rev-list', '--left-right', '--count', `${baseRef}...HEAD`],
|
|
173
|
+
cwd ? { cwd } : undefined,
|
|
174
|
+
);
|
|
175
|
+
const [behindRaw = '0', aheadRaw = '0'] = String(out).trim().split(/\s+/);
|
|
176
|
+
return {
|
|
177
|
+
behind: Number.parseInt(behindRaw, 10) || 0,
|
|
178
|
+
ahead: Number.parseInt(aheadRaw, 10) || 0,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Re-run failed CI for a workflow run (Tier-A: idempotent, reversible).
|
|
184
|
+
*
|
|
185
|
+
* @param {{ runId: string }} ctx
|
|
186
|
+
* @returns {Promise<void>}
|
|
187
|
+
*/
|
|
188
|
+
async rerunFailedChecks({ runId }) {
|
|
189
|
+
this._gh('gh', ['run', 'rerun', String(runId), '--failed']);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Fetch the base ref from its remote so a subsequent `readDivergence`/
|
|
194
|
+
* `detectConflicts` compares HEAD against the CURRENT `origin/<base>`, not a
|
|
195
|
+
* stale local remote-tracking ref (audit A6: a stale ref reports a false
|
|
196
|
+
* `behind=0` / false "no conflict"). `baseRef` is `<remote>/<branch>` (e.g.
|
|
197
|
+
* `origin/master`); the remote and branch are split back out for `git fetch`.
|
|
198
|
+
* Read-only against the working tree — it only updates remote-tracking refs.
|
|
199
|
+
*
|
|
200
|
+
* @param {{ baseRef: string, cwd?: string }} ctx
|
|
201
|
+
* @returns {Promise<void>}
|
|
202
|
+
*/
|
|
203
|
+
async fetchBase({ baseRef, cwd }) {
|
|
204
|
+
const ref = String(baseRef || '');
|
|
205
|
+
const slash = ref.indexOf('/');
|
|
206
|
+
// No `<remote>/<branch>` shape → nothing safe to fetch; leave refs as-is.
|
|
207
|
+
if (slash <= 0) return;
|
|
208
|
+
const remote = ref.slice(0, slash);
|
|
209
|
+
const branch = ref.slice(slash + 1);
|
|
210
|
+
this._git('git', ['fetch', remote, branch], cwd ? { cwd } : undefined);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Post a status reply to a review thread via the existing shell helper.
|
|
215
|
+
* Reply ONLY — never resolve (resolution is the semantic `/review` agent's job).
|
|
216
|
+
*
|
|
217
|
+
* @param {{ pr: string, commentId: string, message: string, script?: string }} ctx
|
|
218
|
+
* @returns {Promise<void>}
|
|
219
|
+
*/
|
|
220
|
+
async replyToThread({ pr, commentId, message, script }) {
|
|
221
|
+
const scriptPath = script || '.claude/scripts/review-resolve.sh';
|
|
222
|
+
this._gh('bash', [scriptPath, 'reply', String(pr), String(commentId), String(message)]);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Read review threads as actionable comments. Uses GraphQL because
|
|
227
|
+
* `gh pr view --json reviewThreads` is unsupported. Resolved/outdated threads
|
|
228
|
+
* are returned WITH flags so the core can filter them (and exclude bots/self).
|
|
229
|
+
*
|
|
230
|
+
* @param {{ owner: string, repo: string, pr: string }} ctx
|
|
231
|
+
* @returns {Promise<object[]>}
|
|
232
|
+
*/
|
|
233
|
+
async readComments({ owner, repo, pr }) {
|
|
234
|
+
// `id`/`path`/`line` are surfaced so a consumer (e.g. the monitor bundle in
|
|
235
|
+
// lib/pr-bundle.js) can hand an agent the thread id to resolve and the
|
|
236
|
+
// file/line to act on — not just the body. Added fields are backward
|
|
237
|
+
// compatible; the shepherd's existing thread filtering ignores them.
|
|
238
|
+
//
|
|
239
|
+
// Both connections are FULLY paginated (cursors, not a first:100 cap): a
|
|
240
|
+
// large PR must never silently drop a thread or a later reply, or the bundle
|
|
241
|
+
// would declare "complete" on partial data and the monitor would skip work.
|
|
242
|
+
const threads = this._fetchAllReviewThreads({ owner, repo, pr });
|
|
243
|
+
return threads.map((t) => {
|
|
244
|
+
const allComments = (t.comments && t.comments.nodes) || [];
|
|
245
|
+
return {
|
|
246
|
+
threadId: t.id || '',
|
|
247
|
+
path: t.path || null,
|
|
248
|
+
line: typeof t.line === 'number' ? t.line : null,
|
|
249
|
+
isResolved: !!t.isResolved,
|
|
250
|
+
isOutdated: !!t.isOutdated,
|
|
251
|
+
comments: allComments.map(c => ({
|
|
252
|
+
author: (c.author && c.author.login) || '',
|
|
253
|
+
body: c.body || '',
|
|
254
|
+
// REST comment id (needed to REPLY to the thread). `fullDatabaseId` is
|
|
255
|
+
// the string-encoded integer id; null when GitHub omits it.
|
|
256
|
+
commentId: c.fullDatabaseId ? String(c.fullDatabaseId) : null,
|
|
257
|
+
})),
|
|
258
|
+
};
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Page through ALL `reviewThreads` (and each thread's nested `comments`) using
|
|
264
|
+
* GraphQL cursors. The per-page size stays 100, but pagination continues until
|
|
265
|
+
* `hasNextPage` is false, so coverage no longer caps at 100 threads/comments.
|
|
266
|
+
* A guard bounds the loop against a pathological/looping cursor.
|
|
267
|
+
*
|
|
268
|
+
* @param {{ owner: string, repo: string, pr: string }} ctx
|
|
269
|
+
* @returns {object[]} raw thread nodes (comments.nodes carries the full chain)
|
|
270
|
+
*/
|
|
271
|
+
_fetchAllReviewThreads({ owner, repo, pr }) {
|
|
272
|
+
const query = 'query($o:String!,$n:String!,$pr:Int!,$after:String){repository(owner:$o,name:$n){pullRequest(number:$pr){reviewThreads(first:100,after:$after){pageInfo{hasNextPage endCursor} nodes{id isResolved isOutdated path line comments(first:100){pageInfo{hasNextPage endCursor} nodes{fullDatabaseId author{login} body}}}}}}}';
|
|
273
|
+
return this._paginateConnection(
|
|
274
|
+
(after) => this._ghGraphqlPage(query, { owner, repo, pr }, after)
|
|
275
|
+
?.data?.repository?.pullRequest?.reviewThreads,
|
|
276
|
+
(t) => {
|
|
277
|
+
// A thread whose first comment page is truncated → fetch the rest.
|
|
278
|
+
if (t.comments?.pageInfo?.hasNextPage) {
|
|
279
|
+
t.comments = { nodes: this._fetchAllThreadComments(t.id, t.comments) };
|
|
280
|
+
}
|
|
281
|
+
return t;
|
|
282
|
+
},
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Run ONE `gh api graphql` page request for an owner/repo/pr-shaped query and
|
|
288
|
+
* return the parsed JSON (or `{}`). `after` appends the opaque cursor only when
|
|
289
|
+
* present — omitting it on the first page tells GraphQL to start from the top.
|
|
290
|
+
* Extracted so the arg-building scaffold lives in exactly one place.
|
|
291
|
+
*
|
|
292
|
+
* @param {string} query
|
|
293
|
+
* @param {{ owner: string, repo: string, pr: string }} vars
|
|
294
|
+
* @param {string|null} after
|
|
295
|
+
* @returns {object}
|
|
296
|
+
*/
|
|
297
|
+
_ghGraphqlPage(query, { owner, repo, pr }, after) {
|
|
298
|
+
const args = [
|
|
299
|
+
'api', 'graphql', '-f', `query=${query}`,
|
|
300
|
+
'-F', `o=${owner}`, '-F', `n=${repo}`, '-F', `pr=${pr}`,
|
|
301
|
+
];
|
|
302
|
+
if (after) args.push('-f', `after=${after}`);
|
|
303
|
+
return JSON.parse(this._gh('gh', args) || '{}');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Page through a cursored GraphQL connection until `hasNextPage` is false,
|
|
308
|
+
* accumulating `mapNode(node)` for every node across all pages. `runPage(after)`
|
|
309
|
+
* performs one page request and returns the connection object
|
|
310
|
+
* (`{ pageInfo, nodes }`) or a falsy value when unreadable. A page guard bounds
|
|
311
|
+
* the loop against a pathological/looping cursor. Shared by every paginated
|
|
312
|
+
* read so the do-while cursor scaffold is defined exactly once.
|
|
313
|
+
*
|
|
314
|
+
* @param {(after: string|null) => ({ pageInfo?: object, nodes?: object[] }|null|undefined)} runPage
|
|
315
|
+
* @param {(node: object) => object} mapNode
|
|
316
|
+
* @returns {object[]}
|
|
317
|
+
*/
|
|
318
|
+
_paginateConnection(runPage, mapNode) {
|
|
319
|
+
const all = [];
|
|
320
|
+
let after = null;
|
|
321
|
+
let guard = 0;
|
|
322
|
+
const MAX_PAGES = 1000;
|
|
323
|
+
do {
|
|
324
|
+
const conn = runPage(after);
|
|
325
|
+
for (const node of (conn?.nodes || [])) all.push(mapNode(node));
|
|
326
|
+
after = conn?.pageInfo?.hasNextPage ? conn.pageInfo.endCursor : null;
|
|
327
|
+
guard += 1;
|
|
328
|
+
} while (after && guard < MAX_PAGES);
|
|
329
|
+
return all;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Page through the remaining `comments` of a single review thread (the first
|
|
334
|
+
* page already arrived inline with the thread). Resolves the thread by node id.
|
|
335
|
+
*
|
|
336
|
+
* @param {string} threadId
|
|
337
|
+
* @param {{ nodes: object[], pageInfo: { hasNextPage: boolean, endCursor: string } }} initial
|
|
338
|
+
* @returns {object[]} the full comment-node list (first page + all later pages)
|
|
339
|
+
*/
|
|
340
|
+
_fetchAllThreadComments(threadId, initial) {
|
|
341
|
+
const query = 'query($id:ID!,$after:String){node(id:$id){... on PullRequestReviewThread{comments(first:100,after:$after){pageInfo{hasNextPage endCursor} nodes{fullDatabaseId author{login} body}}}}}';
|
|
342
|
+
const acc = [...((initial && initial.nodes) || [])];
|
|
343
|
+
let after = initial && initial.pageInfo && initial.pageInfo.hasNextPage ? initial.pageInfo.endCursor : null;
|
|
344
|
+
let guard = 0;
|
|
345
|
+
const MAX_PAGES = 1000;
|
|
346
|
+
while (after && guard < MAX_PAGES) {
|
|
347
|
+
const raw = this._gh('gh', [
|
|
348
|
+
'api', 'graphql', '-f', `query=${query}`,
|
|
349
|
+
'-f', `id=${threadId}`, '-f', `after=${after}`,
|
|
350
|
+
]);
|
|
351
|
+
const data = JSON.parse(raw || '{}');
|
|
352
|
+
const conn = data.data && data.data.node && data.data.node.comments;
|
|
353
|
+
acc.push(...((conn && conn.nodes) || []));
|
|
354
|
+
after = conn && conn.pageInfo && conn.pageInfo.hasNextPage ? conn.pageInfo.endCursor : null;
|
|
355
|
+
guard += 1;
|
|
356
|
+
}
|
|
357
|
+
return acc;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Read the PR's plain ISSUE comments (NOT review threads) — the surface where
|
|
362
|
+
* status/deploy/quality bots post their summaries: SonarCloud's Quality-Gate
|
|
363
|
+
* comment, Vercel/Netlify deployment comments, Codecov coverage comments. These
|
|
364
|
+
* are regular PR comments, never resolvable review threads, so `readComments`
|
|
365
|
+
* (reviewThreads GraphQL) never sees them. Fully paginated by cursor so a long
|
|
366
|
+
* PR never drops a bot's latest comment. Returns `{ author, body, createdAt }`.
|
|
367
|
+
*
|
|
368
|
+
* @param {{ owner: string, repo: string, pr: string }} ctx
|
|
369
|
+
* @returns {Promise<Array<{ author: string, body: string, createdAt: string }>>}
|
|
370
|
+
*/
|
|
371
|
+
async readIssueComments({ owner, repo, pr }) {
|
|
372
|
+
// `author{__typename login}` surfaces the GraphQL actor TYPE ('Bot' vs 'User')
|
|
373
|
+
// so a non-human direct comment can be detected GENERICALLY — by mechanism,
|
|
374
|
+
// not by a hardcoded bot-name list — which fails closed for unknown bots.
|
|
375
|
+
const query = 'query($o:String!,$n:String!,$pr:Int!,$after:String){repository(owner:$o,name:$n){pullRequest(number:$pr){comments(first:100,after:$after){pageInfo{hasNextPage endCursor} nodes{fullDatabaseId author{__typename login} body createdAt}}}}}';
|
|
376
|
+
return this._paginateConnection(
|
|
377
|
+
(after) => this._ghGraphqlPage(query, { owner, repo, pr }, after)
|
|
378
|
+
?.data?.repository?.pullRequest?.comments,
|
|
379
|
+
(c) => ({
|
|
380
|
+
// `id` is the stable REST comment id — the PR monitor keys `comment.posted`
|
|
381
|
+
// on it so a re-read never re-emits an already-seen comment. Falls back to
|
|
382
|
+
// author+createdAt when GitHub omits it.
|
|
383
|
+
id: c.fullDatabaseId ? String(c.fullDatabaseId) : `${c.author?.login || ''}:${c.createdAt || ''}`,
|
|
384
|
+
author: c.author?.login || '',
|
|
385
|
+
authorTypename: c.author?.__typename || '',
|
|
386
|
+
body: c.body || '',
|
|
387
|
+
createdAt: c.createdAt || '',
|
|
388
|
+
}),
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Read submitted PR REVIEWS (not inline threads) — the latest review per
|
|
394
|
+
* author, each with the commit it was submitted against. This is the
|
|
395
|
+
* review-at-head signal that catches the #365 race: a CodeRabbit review from
|
|
396
|
+
* an earlier commit whose `commit.oid` no longer matches HEAD is STALE, so the
|
|
397
|
+
* post-push re-review is still pending. Uses GraphQL because `gh pr view`
|
|
398
|
+
* cannot return a review's target commit oid. Fully paginated by cursor.
|
|
399
|
+
*
|
|
400
|
+
* @param {{ owner: string, repo: string, pr: string }} ctx
|
|
401
|
+
* @returns {Promise<Array<{ author: string, state: string, submittedAt: string|null, commitOid: string|null, body: string }>>}
|
|
402
|
+
*/
|
|
403
|
+
async readReviews({ owner, repo, pr }) {
|
|
404
|
+
const query = 'query($o:String!,$n:String!,$pr:Int!,$after:String){repository(owner:$o,name:$n){pullRequest(number:$pr){reviews(first:100,after:$after){pageInfo{hasNextPage endCursor} nodes{author{__typename login} state submittedAt commit{oid} body}}}}}';
|
|
405
|
+
const all = this._paginateConnection(
|
|
406
|
+
(after) => this._ghGraphqlPage(query, { owner, repo, pr }, after)
|
|
407
|
+
?.data?.repository?.pullRequest?.reviews,
|
|
408
|
+
(r) => ({
|
|
409
|
+
author: String(r.author?.login || '').toLowerCase(),
|
|
410
|
+
authorTypename: r.author?.__typename || '',
|
|
411
|
+
state: String(r.state || '').toUpperCase(),
|
|
412
|
+
submittedAt: r.submittedAt || null,
|
|
413
|
+
commitOid: r.commit?.oid || null,
|
|
414
|
+
body: r.body || '',
|
|
415
|
+
}),
|
|
416
|
+
);
|
|
417
|
+
// Keep only the LATEST review per author (first:100 yields oldest→newest, so
|
|
418
|
+
// a later entry supersedes an earlier one from the same login).
|
|
419
|
+
const latest = new Map();
|
|
420
|
+
for (const r of all) {
|
|
421
|
+
if (!r.author) continue;
|
|
422
|
+
latest.set(r.author, r);
|
|
423
|
+
}
|
|
424
|
+
return Array.from(latest.values());
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Read the HEAD commit's committed timestamp (epoch ms), or null when
|
|
429
|
+
* unreadable. Anchors the pull-signal settle window to when the code last
|
|
430
|
+
* changed — so a freshly-pushed PR whose CI passes BEFORE the review bots have
|
|
431
|
+
* even run is REVIEW-PENDING, not CLEAN (#365 "never-ran" variant), and a bot
|
|
432
|
+
* comment older than the last push is treated as stale, not a live blocker.
|
|
433
|
+
*
|
|
434
|
+
* @param {{ pr: string }} ctx
|
|
435
|
+
* @returns {Promise<number|null>} epoch ms of the head commit, or null.
|
|
436
|
+
*/
|
|
437
|
+
async readHeadCommitTime({ pr }) {
|
|
438
|
+
const raw = this._gh('gh', ['pr', 'view', String(pr), '--json', 'commits', '-q', '.commits[-1].committedDate']);
|
|
439
|
+
const t = Date.parse(String(raw || '').trim());
|
|
440
|
+
return Number.isFinite(t) ? t : null;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Predict files that would conflict when merging `baseRef` into HEAD WITHOUT
|
|
445
|
+
* touching the working tree, via `git merge-tree --write-tree`. Returns
|
|
446
|
+
* `{ supported: false, reason }` when conflict prediction is unavailable (git
|
|
447
|
+
* < 2.38, an unreadable ref, or a non-conflict error) so the bundle degrades
|
|
448
|
+
* gracefully rather than failing the whole gather.
|
|
449
|
+
*
|
|
450
|
+
* @param {{ baseRef: string, cwd?: string }} ctx
|
|
451
|
+
* @returns {Promise<{ supported: boolean, conflicted?: boolean, files?: string[], reason?: string }>}
|
|
452
|
+
*/
|
|
453
|
+
async detectConflicts({ baseRef, cwd }) {
|
|
454
|
+
const opts = cwd ? { cwd } : undefined;
|
|
455
|
+
try {
|
|
456
|
+
// Exit 0 = clean merge. `--name-only` reduces the conflict report to bare
|
|
457
|
+
// paths; `--no-messages` suppresses the human-readable conflict prose.
|
|
458
|
+
this._git(
|
|
459
|
+
'git',
|
|
460
|
+
['merge-tree', '--write-tree', '--name-only', '--no-messages', baseRef, 'HEAD'],
|
|
461
|
+
opts,
|
|
462
|
+
);
|
|
463
|
+
return { supported: true, conflicted: false, files: [] };
|
|
464
|
+
} catch (error) {
|
|
465
|
+
// Exit 1 = mergeable-with-conflicts: stdout is the written tree OID on the
|
|
466
|
+
// first line followed by the conflicted paths. Any other status (e.g. 128
|
|
467
|
+
// for an unknown ref, or `--write-tree` unsupported on git < 2.38) means
|
|
468
|
+
// conflict prediction is unsupported in this environment.
|
|
469
|
+
const status = typeof error.status === 'number' ? error.status : undefined;
|
|
470
|
+
const stdout = String(error.stdout || '');
|
|
471
|
+
if (status === 1 && stdout.trim()) {
|
|
472
|
+
const lines = stdout.split('\n').map((s) => s.trim()).filter(Boolean);
|
|
473
|
+
return { supported: true, conflicted: true, files: lines.slice(1) };
|
|
474
|
+
}
|
|
475
|
+
return { supported: false, reason: error.message || 'git merge-tree unavailable' };
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
module.exports = {
|
|
481
|
+
PrStateAdapter,
|
|
482
|
+
classifyAuthError,
|
|
483
|
+
PR_VIEW_FIELDS,
|
|
484
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
const YAML = require('yaml');
|
|
2
|
+
|
|
3
|
+
const ADOPTION_VERSION = '0.0.15';
|
|
4
|
+
const RUNTIME_ANCESTRY = 'forge.runtimeGraph.currentCommandFlow@0.0.17';
|
|
5
|
+
const WORKFLOW_PHASES = Object.freeze(['plan', 'dev', 'validate', 'ship']);
|
|
6
|
+
const WORKFLOW_GATES = Object.freeze(['gate.plan-exit', 'gate.dev-exit', 'gate.validate-exit', 'gate.ship-entry']);
|
|
7
|
+
|
|
8
|
+
function enabledEntries(names, enabled = true) {
|
|
9
|
+
return Object.fromEntries(names.map(name => [name, { enabled }]));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function issueAdapter(enabled) {
|
|
13
|
+
if (!enabled) {
|
|
14
|
+
return {
|
|
15
|
+
enabled: false,
|
|
16
|
+
primary: 'none',
|
|
17
|
+
mirrors: [],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
enabled: true,
|
|
23
|
+
primary: 'kernel',
|
|
24
|
+
mirrors: ['github'],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function harnessAdapter(targets) {
|
|
29
|
+
return {
|
|
30
|
+
enabled: targets.length > 0,
|
|
31
|
+
targets,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function adoptionConfig({ gatesEnabled, issueEnabled, harnessTargets, protectedPaths, rails }) {
|
|
36
|
+
return {
|
|
37
|
+
...(rails ? { rails } : {}),
|
|
38
|
+
workflow: {
|
|
39
|
+
phases: enabledEntries(WORKFLOW_PHASES),
|
|
40
|
+
gates: enabledEntries(WORKFLOW_GATES, gatesEnabled),
|
|
41
|
+
},
|
|
42
|
+
adapters: {
|
|
43
|
+
issue: issueAdapter(issueEnabled),
|
|
44
|
+
harness: harnessAdapter(harnessTargets),
|
|
45
|
+
},
|
|
46
|
+
protectedPaths,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const PROFILE_CONFIGS = Object.freeze({
|
|
51
|
+
minimal: {
|
|
52
|
+
description: 'Config-only adoption for a clean repository.',
|
|
53
|
+
config: adoptionConfig({
|
|
54
|
+
gatesEnabled: false,
|
|
55
|
+
issueEnabled: false,
|
|
56
|
+
harnessTargets: [],
|
|
57
|
+
protectedPaths: ['.forge/config.yaml'],
|
|
58
|
+
}),
|
|
59
|
+
},
|
|
60
|
+
standard: {
|
|
61
|
+
description: 'Default Forge command flow with Kernel issue tracking and GitHub mirror metadata.',
|
|
62
|
+
config: adoptionConfig({
|
|
63
|
+
gatesEnabled: true,
|
|
64
|
+
issueEnabled: true,
|
|
65
|
+
harnessTargets: ['codex', 'claude', 'cursor'],
|
|
66
|
+
protectedPaths: ['.forge/config.yaml', 'AGENTS.md'],
|
|
67
|
+
}),
|
|
68
|
+
},
|
|
69
|
+
full: {
|
|
70
|
+
description: 'Full adoption scaffold with all default rails explicit.',
|
|
71
|
+
config: adoptionConfig({
|
|
72
|
+
rails: {
|
|
73
|
+
tdd_intent: { enabled: true },
|
|
74
|
+
secret_scan: { enabled: true },
|
|
75
|
+
branch_protection: { enabled: true },
|
|
76
|
+
signed_commits: { enabled: true },
|
|
77
|
+
schema_integrity: { enabled: true },
|
|
78
|
+
},
|
|
79
|
+
gatesEnabled: true,
|
|
80
|
+
issueEnabled: true,
|
|
81
|
+
harnessTargets: ['codex', 'claude', 'cursor'],
|
|
82
|
+
protectedPaths: ['.forge/config.yaml', 'AGENTS.md', '.github/workflows/**'],
|
|
83
|
+
}),
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const ADOPTION_PROFILE_NAMES = Object.freeze(Object.keys(PROFILE_CONFIGS));
|
|
88
|
+
|
|
89
|
+
function clone(value) {
|
|
90
|
+
return structuredClone(value);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function assertProfile(profile) {
|
|
94
|
+
if (!Object.hasOwn(PROFILE_CONFIGS, profile)) {
|
|
95
|
+
throw new Error(`Unknown adoption profile '${profile}'. Available profiles: ${ADOPTION_PROFILE_NAMES.join(', ')}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function buildAdoptionConfig(profile = 'standard') {
|
|
100
|
+
assertProfile(profile);
|
|
101
|
+
const definition = PROFILE_CONFIGS[profile];
|
|
102
|
+
return {
|
|
103
|
+
template: {
|
|
104
|
+
kind: 'forge.adoptionTemplate',
|
|
105
|
+
version: ADOPTION_VERSION,
|
|
106
|
+
profile,
|
|
107
|
+
description: definition.description,
|
|
108
|
+
ancestry: [
|
|
109
|
+
RUNTIME_ANCESTRY,
|
|
110
|
+
`forge.profile.${profile}@${ADOPTION_VERSION}`,
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
...clone(definition.config),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function renderAdoptionConfigYaml(profile = 'standard') {
|
|
118
|
+
return YAML.stringify(buildAdoptionConfig(profile));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = {
|
|
122
|
+
ADOPTION_PROFILE_NAMES,
|
|
123
|
+
ADOPTION_VERSION,
|
|
124
|
+
buildAdoptionConfig,
|
|
125
|
+
renderAdoptionConfigYaml,
|
|
126
|
+
};
|
package/lib/agents/README.md
CHANGED
|
@@ -94,17 +94,13 @@ Legacy `capabilities.hooks: true/false` values are still accepted and normalized
|
|
|
94
94
|
|
|
95
95
|
## Supported Agents
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
Forge currently supports Claude Code, Codex, and Cursor. Hermes support is planned.
|
|
98
98
|
|
|
99
99
|
| Agent | ID | Description |
|
|
100
100
|
|-------|----|-----------|
|
|
101
101
|
| Claude Code | `claude` | Anthropic's CLI agent |
|
|
102
|
+
| Codex | `codex` | OpenAI's CLI agent |
|
|
102
103
|
| Cursor | `cursor` | AI-first code editor |
|
|
103
|
-
| Kilo Code | `kilocode` | VS Code extension |
|
|
104
|
-
| GitHub Copilot | `copilot` | GitHub's AI assistant |
|
|
105
|
-
| OpenCode | `opencode` | Open-source agent |
|
|
106
|
-
| Cline | `cline` | VS Code agent extension |
|
|
107
|
-
| Roo Code | `roo` | Cline fork with modes |
|
|
108
104
|
|
|
109
105
|
## Adding a New Agent
|
|
110
106
|
|
|
@@ -5,23 +5,18 @@
|
|
|
5
5
|
"description": "Anthropic's CLI agent",
|
|
6
6
|
"homepage": "https://claude.ai/code",
|
|
7
7
|
"capabilities": {
|
|
8
|
-
"commands":
|
|
8
|
+
"commands": false,
|
|
9
9
|
"skills": true,
|
|
10
10
|
"hooks": true
|
|
11
11
|
},
|
|
12
12
|
"directories": {
|
|
13
|
-
"
|
|
14
|
-
"rules": ".claude/rules",
|
|
15
|
-
"skills": ".claude/skills/forge-workflow",
|
|
13
|
+
"skills": ".claude/skills",
|
|
16
14
|
"scripts": ".claude/scripts"
|
|
17
15
|
},
|
|
18
16
|
"files": {
|
|
19
|
-
"rootConfig": "CLAUDE.md"
|
|
20
|
-
"skillDefinition": ".claude/skills/forge-workflow/SKILL.md"
|
|
17
|
+
"rootConfig": "CLAUDE.md"
|
|
21
18
|
},
|
|
22
19
|
"setup": {
|
|
23
|
-
"copyCommands": true,
|
|
24
|
-
"copyRules": true,
|
|
25
20
|
"copyScripts": true,
|
|
26
21
|
"createSkill": true
|
|
27
22
|
}
|
|
@@ -7,7 +7,15 @@
|
|
|
7
7
|
"capabilities": {
|
|
8
8
|
"commands": true,
|
|
9
9
|
"skills": true,
|
|
10
|
-
"hooks":
|
|
10
|
+
"hooks": true,
|
|
11
|
+
"hookEvents": [
|
|
12
|
+
"SessionStart",
|
|
13
|
+
"UserPromptSubmit",
|
|
14
|
+
"PreToolUse",
|
|
15
|
+
"PermissionRequest",
|
|
16
|
+
"PostToolUse",
|
|
17
|
+
"Stop"
|
|
18
|
+
]
|
|
11
19
|
},
|
|
12
20
|
"directories": {
|
|
13
21
|
"skills": ".codex/skills"
|