forge-workflow 0.0.9 → 0.1.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/rules/{greptile-review-process.md → review-process.md} +56 -41
- package/.claude/scripts/{greptile-resolve.sh → review-resolve.sh} +13 -3
- package/.cursor/rules/permissions-guidance.mdc +2 -2
- package/.forge/hooks/check-tdd.js +3 -0
- package/.forge/hooks/forge-native-hook.js +245 -0
- package/.forge/protected-paths.yaml +157 -0
- package/AGENTS.md +151 -61
- package/CHANGELOG.md +681 -0
- package/CLAUDE.md +9 -106
- package/QUICKSTART.md +171 -0
- package/README.md +271 -363
- package/bin/forge-cmd.js +120 -9
- package/bin/forge-preflight.js +26 -5
- package/bin/forge.js +466 -489
- package/docs/INDEX.md +93 -0
- package/docs/PROJECT_DESIGN.md +685 -0
- package/docs/architecture/index.md +66 -0
- package/docs/architecture/notes/README.md +35 -0
- package/docs/architecture/subsystems/README.md +46 -0
- package/docs/{TOOLCHAIN.md → forge/TOOLCHAIN.md} +56 -47
- package/docs/forge/VALIDATION.md +82 -0
- package/docs/{AGENT_INSTALL_PROMPT.md → guides/AGENT_INSTALL_PROMPT.md} +3 -3
- package/docs/guides/BEADS_GITHUB_SYNC.md +32 -0
- package/docs/{ENHANCED_ONBOARDING.md → guides/ENHANCED_ONBOARDING.md} +16 -12
- package/docs/guides/GREPTILE_SETUP.md +46 -0
- package/docs/guides/MANUAL_REVIEW_GUIDE.md +58 -0
- package/docs/guides/MIGRATION.md +56 -0
- package/docs/guides/SETUP.md +118 -0
- package/docs/guides/SUPPORT.md +185 -0
- package/docs/guides/WORKFLOW_TEMPLATES.md +74 -0
- package/docs/guides/memory-backends.md +183 -0
- package/docs/reference/ADAPTERS.md +128 -0
- package/docs/reference/AGENT_SKILL_PARITY.md +175 -0
- package/docs/reference/COMMANDS.md +205 -0
- package/docs/reference/DECISION_DRIFT_GUARDS.md +97 -0
- package/docs/{EXAMPLES.md → reference/EXAMPLES.md} +7 -5
- package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +135 -0
- package/docs/reference/HERMES_INTEGRATION.md +118 -0
- package/docs/reference/INSIGHTS_RECAP.md +63 -0
- package/docs/reference/INSTALL.md +164 -0
- package/docs/reference/KERNEL_TAXONOMY_VALIDATION.md +161 -0
- package/docs/reference/PROTECTED_PATH_MANIFEST.md +25 -0
- package/docs/reference/RELEASE.md +68 -0
- package/docs/reference/RESEARCH_TEMPLATE.md +292 -0
- package/docs/{ROADMAP.md → reference/ROADMAP.md} +12 -9
- package/docs/reference/SKILLS.md +35 -0
- package/docs/reference/STATUS_BOARD.md +80 -0
- package/docs/reference/TEMPLATES.md +106 -0
- package/docs/reference/TOOLCHAIN.md +658 -0
- package/docs/reference/VALIDATION.md +82 -0
- package/docs/reference/agent-permissions.md +169 -0
- package/docs/reference/beads-to-kernel-migration-ux.md +61 -0
- package/docs/reference/control-plane-guarantees.md +125 -0
- package/docs/reference/dependency-chain.md +331 -0
- package/docs/reference/forge-kernel-issue-command-contract.md +161 -0
- package/docs/reference/forge-kernel-schema.md +72 -0
- package/docs/reference/kernel-conflict-evaluators.md +27 -0
- package/docs/reference/patch-md-format.md +77 -0
- package/docs/reference/protected-state-surfaces.md +59 -0
- package/docs/reference/shepherd.md +115 -0
- package/docs/reference/superpowers-analysis.md +320 -0
- package/docs/reference/superpowers-integration-options.md +404 -0
- package/docs/reference/test-environment.md +519 -0
- package/docs/reference/upgrade-safety.md +59 -0
- package/lefthook.yml +18 -0
- package/lib/adapter-cli.js +307 -0
- package/lib/adapters/beads-issue-adapter.js +127 -0
- package/lib/adapters/beads-kernel-compat.js +1042 -0
- package/lib/adapters/greptile-review-adapter.js +141 -0
- package/lib/adapters/kernel-issue-adapter.js +101 -0
- package/lib/adapters/pr-state-adapter.js +484 -0
- package/lib/adoption-profiles.js +126 -0
- package/lib/agents/README.md +2 -6
- package/lib/agents/claude.plugin.json +3 -8
- package/lib/agents/codex.plugin.json +9 -1
- package/lib/agents/cursor.plugin.json +2 -6
- package/lib/agents/hermes.plugin.json +22 -0
- package/lib/agents-config.js +39 -1236
- package/lib/audit-evidence.js +282 -0
- package/lib/beads-setup.js +225 -28
- package/lib/beads-sync-scaffold.js +36 -107
- package/lib/codex-skills.js +51 -1
- package/lib/commands/_issue.js +744 -70
- package/lib/commands/_manifest.js +91 -0
- package/lib/commands/_registry.js +85 -34
- package/lib/commands/_resolve-command-opts.js +261 -0
- package/lib/commands/_serve-security.js +270 -0
- package/lib/commands/adapter.js +12 -0
- package/lib/commands/add.js +118 -0
- package/lib/commands/audit.js +70 -0
- package/lib/commands/blocked.js +5 -0
- package/lib/commands/board.js +64 -0
- package/lib/commands/claim.js +21 -2
- package/lib/commands/claims.js +7 -0
- package/lib/commands/clean.js +485 -75
- package/lib/commands/close.js +2 -2
- package/lib/commands/comment.js +5 -0
- package/lib/commands/control.js +148 -0
- package/lib/commands/create.js +2 -2
- package/lib/commands/dev.js +185 -7
- package/lib/commands/doc-gate.js +336 -0
- package/lib/commands/doctor.js +156 -0
- package/lib/commands/explain.js +15 -0
- package/lib/commands/export.js +237 -0
- package/lib/commands/gate.js +192 -0
- package/lib/commands/hooks.js +242 -0
- package/lib/commands/inbox.js +118 -0
- package/lib/commands/init.js +598 -0
- package/lib/commands/insights.js +79 -0
- package/lib/commands/issue.js +12 -1
- package/lib/commands/issues.js +66 -0
- package/lib/commands/lint.js +5 -0
- package/lib/commands/list.js +2 -2
- package/lib/commands/merge.js +312 -0
- package/lib/commands/migrate.js +523 -0
- package/lib/commands/new.js +12 -0
- package/lib/commands/options.js +241 -0
- package/lib/commands/orient.js +13 -0
- package/lib/commands/orphans.js +5 -0
- package/lib/commands/patch.js +67 -0
- package/lib/commands/plan.js +436 -24
- package/lib/commands/preflight.js +211 -0
- package/lib/commands/prime.js +13 -0
- package/lib/commands/push.js +69 -2
- package/lib/commands/ready.js +2 -2
- package/lib/commands/recall.js +116 -0
- package/lib/commands/recap.js +61 -0
- package/lib/commands/recommend.js +22 -2
- package/lib/commands/release.js +91 -0
- package/lib/commands/remember.js +74 -0
- package/lib/commands/role.js +99 -0
- package/lib/commands/serve.js +581 -0
- package/lib/commands/setup.js +851 -979
- package/lib/commands/shepherd.js +436 -0
- package/lib/commands/ship.js +23 -1
- package/lib/commands/show.js +2 -2
- package/lib/commands/stage.js +192 -0
- package/lib/commands/stale.js +5 -0
- package/lib/commands/status.js +329 -11
- package/lib/commands/sync.js +34 -46
- package/lib/commands/team.js +15 -2
- package/lib/commands/test.js +58 -7
- package/lib/commands/update.js +2 -2
- package/lib/commands/upgrade.js +47 -0
- package/lib/commands/validate.js +56 -25
- package/lib/commands/worktree.js +308 -128
- package/lib/config-writer.js +202 -0
- package/lib/control-plane.js +236 -0
- package/lib/core/runtime-graph.js +946 -0
- package/lib/dep-guard/keyword-ripple.js +184 -0
- package/lib/deprecated-sync-cleanup.js +362 -0
- package/lib/detect-agent.js +2 -28
- package/lib/detect-worktree.js +42 -17
- package/lib/doc-gate/declaration.js +177 -0
- package/lib/doc-gate/detect.js +289 -0
- package/lib/doc-gate/gate.js +375 -0
- package/lib/doc-gate/okf-config.js +128 -0
- package/lib/doc-gate/okf.js +429 -0
- package/lib/docs-command.js +1161 -6
- package/lib/forge-issues.js +697 -0
- package/lib/forge-lock.js +262 -0
- package/lib/gate-events.js +193 -0
- package/lib/global-flags.js +74 -0
- package/lib/greptile-match.js +7 -63
- package/lib/harness-capability-matrix.js +380 -0
- package/lib/hook-global-installer.js +347 -0
- package/lib/hook-renderer.js +451 -0
- package/lib/inbox.js +391 -0
- package/lib/insights.js +397 -0
- package/lib/issue-adapter.js +156 -0
- package/lib/issue-backend.js +145 -0
- package/lib/issue-render.js +220 -0
- package/lib/issue-sync/authority.js +100 -0
- package/lib/issue-sync/github-pull.js +184 -0
- package/lib/issue-sync/import-primitives.js +98 -0
- package/lib/issue-sync/legacy-link-bridge.js +436 -0
- package/lib/issue-sync/link-store.js +292 -0
- package/lib/issue-sync/project-github.js +123 -0
- package/lib/issue-sync/reconcile.js +195 -0
- package/lib/issue-sync/schema.js +126 -0
- package/lib/kernel/backing-issue.js +305 -0
- package/lib/kernel/broker.js +1218 -0
- package/lib/kernel/cli-broker-factory.js +130 -0
- package/lib/kernel/conflict-signal.js +82 -0
- package/lib/kernel/evaluators.js +195 -0
- package/lib/kernel/fs-class.js +495 -0
- package/lib/kernel/issue-command-contract.js +559 -0
- package/lib/kernel/issue-id-resolver.js +186 -0
- package/lib/kernel/lease-enforcer.js +158 -0
- package/lib/kernel/migrations.js +333 -0
- package/lib/kernel/planning-buckets-schema.js +109 -0
- package/lib/kernel/projection-jsonl-writer.js +450 -0
- package/lib/kernel/readiness-model.js +329 -0
- package/lib/kernel/schema.js +356 -0
- package/lib/kernel/sqlite-driver.js +2504 -0
- package/lib/kernel/taxonomy-validator.js +394 -0
- package/lib/lefthook-check.js +8 -4
- package/lib/lefthook-wiring.js +413 -0
- package/lib/mcp-config-renderer.js +288 -0
- package/lib/memory/graphiti-mcp.js +106 -0
- package/lib/memory/router.js +387 -0
- package/lib/memory/typed-api.js +102 -0
- package/lib/memory-digest.js +195 -0
- package/lib/merge-rules.js +395 -0
- package/lib/migrate-dry-run.js +466 -0
- package/lib/orientation.js +863 -0
- package/lib/package-manager-remediation.js +103 -0
- package/lib/package-root.js +381 -0
- package/lib/patch-intent.js +890 -0
- package/lib/plugin-catalog.js +3 -4
- package/lib/plugin-manager.js +0 -5
- package/lib/pr-bundle.js +186 -0
- package/lib/pr-monitor/differ.js +195 -0
- package/lib/pr-monitor/events.js +0 -0
- package/lib/pr-monitor/gather.js +124 -0
- package/lib/pr-monitor/journal.js +299 -0
- package/lib/pr-monitor/monitor.js +146 -0
- package/lib/pr-monitor/render-sticky.js +157 -0
- package/lib/pr-monitor/watch-lifecycle.js +95 -0
- package/lib/pr-monitor/watch.js +247 -0
- package/lib/pr-pull.js +1273 -0
- package/lib/pr-shepherd.js +494 -0
- package/lib/pr-state-validator.js +59 -0
- package/lib/preflight/gates.js +237 -0
- package/lib/preflight/runner.js +116 -0
- package/lib/project-discovery.js +0 -53
- package/lib/project-memory.js +166 -0
- package/lib/protected-path-manifest.js +281 -0
- package/lib/protected-state-surfaces.js +387 -0
- package/lib/release-readiness.js +2089 -0
- package/lib/reset.js +59 -45
- package/lib/review-adapter.js +68 -0
- package/lib/rules-sync.js +260 -0
- package/lib/runtime-health.js +332 -23
- package/lib/safety-config-renderer.js +268 -0
- package/lib/setup-action-log.js +1 -7
- package/lib/setup.js +27 -65
- package/lib/shell-utils.js +76 -6
- package/lib/skills-sync.js +330 -0
- package/lib/smart-status/conflicts.js +205 -0
- package/lib/smart-status/scoring.js +191 -0
- package/lib/status/beads-snapshot.js +145 -0
- package/lib/status/presenter.js +216 -0
- package/lib/status/snapshot.js +186 -0
- package/lib/sync-backend.js +202 -0
- package/lib/untrusted-content.js +52 -0
- package/lib/upgrade-safety.js +199 -0
- package/lib/workflow/enforce-stage.js +298 -47
- package/lib/workflow/stage-transition.js +115 -0
- package/lib/workflow/stages.js +30 -6
- package/lib/workflow/state-manager.js +159 -14
- package/lib/workflow/state.js +23 -1
- package/lib/workflow-profiles.js +17 -5
- package/package.json +46 -36
- package/rules/documentation.md +19 -0
- package/rules/kernel-tracking.md +26 -0
- package/rules/security.md +22 -0
- package/rules/tdd.md +20 -0
- package/rules/workflow.md +27 -0
- package/scripts/auto-backing-issue.js +47 -0
- package/scripts/beads-context.sh +165 -22
- package/scripts/beads-migrate-to-dolt.sh +7 -0
- package/scripts/beads-upgrade-smoke.sh +284 -0
- package/scripts/behavioral-judge.sh +115 -11
- package/scripts/benchmark.js +349 -63
- package/scripts/bootstrap-windows-tools.sh +78 -0
- package/scripts/branch-protection.js +2 -3
- package/scripts/check-agents.js +34 -137
- package/scripts/commitlint.js +3 -1
- package/scripts/conflict-detect.sh +3 -0
- package/scripts/dep-guard-analyze.js +52 -17
- package/scripts/dep-guard-keyword-ripple.js +29 -0
- package/scripts/dep-guard-render-review.js +86 -0
- package/scripts/dep-guard.sh +64 -232
- package/scripts/file-index.sh +3 -0
- package/scripts/forge-team/lib/claim.sh +34 -18
- package/scripts/forge-team/lib/dashboard.sh +61 -86
- package/scripts/forge-team/lib/epic.sh +99 -263
- package/scripts/forge-team/lib/hooks.sh +26 -28
- package/scripts/forge-team/lib/identity.sh +4 -4
- package/scripts/forge-team/lib/sync-github.sh +144 -47
- package/scripts/forge-team/lib/verify.sh +93 -83
- package/scripts/forge-team/lib/workload.sh +41 -65
- package/scripts/forge-team/tests/claim.test.sh +25 -19
- package/scripts/forge-team/tests/dashboard.test.sh +31 -46
- package/scripts/forge-team/tests/epic.test.sh +52 -71
- package/scripts/forge-team/tests/hooks.test.sh +38 -50
- package/scripts/forge-team/tests/identity.test.sh +3 -3
- package/scripts/forge-team/tests/integration.test.sh +44 -66
- package/scripts/forge-team/tests/sync-github.test.sh +183 -79
- package/scripts/forge-team/tests/verify.test.sh +37 -46
- package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
- package/scripts/forge-team/tests/workload.test.sh +32 -66
- package/scripts/gen-command-manifest.js +153 -0
- package/scripts/gen-embedded-assets.mjs +129 -0
- package/scripts/install.ps1 +139 -0
- package/scripts/install.sh +268 -0
- package/scripts/lib/beads-migrate-to-dolt.mjs +503 -0
- package/scripts/lib/release-asset.mjs +84 -0
- package/scripts/parity-check.mjs +145 -0
- package/scripts/parity-check.test.mjs +58 -0
- package/scripts/pin-agentic-workflow-images.js +112 -0
- package/scripts/pr-coordinator.sh +3 -0
- package/scripts/preflight-sonar.eslint.config.mjs +44 -0
- package/scripts/preflight.sh +108 -0
- package/scripts/protected-state-check.js +104 -0
- package/scripts/smart-status-score.js +31 -0
- package/scripts/smart-status-sessions.js +51 -0
- package/scripts/smart-status.sh +117 -369
- package/scripts/spikes/config-race-bench.js +111 -0
- package/scripts/spikes/harness-capability-matrix.js +13 -0
- package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
- package/scripts/spikes/protected-path-manifest.js +20 -0
- package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
- package/scripts/sync-agent-skills.js +62 -0
- package/scripts/sync-agentic-workflow.js +48 -0
- package/scripts/sync-utils.sh +3 -0
- package/scripts/test-ci-shard.js +251 -0
- package/scripts/test-dashboard.js +188 -52
- package/scripts/test-full-suite.js +186 -0
- package/scripts/test-profile.js +278 -0
- package/scripts/test.js +302 -28
- package/scripts/validate.js +143 -0
- package/scripts/validate.sh +18 -1
- package/skills/claim-safety/SKILL.md +102 -0
- package/skills/claim-safety/evals/evals.json +46 -0
- package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +46 -52
- package/skills/dev/evals/evals.json +50 -0
- package/skills/hermes-forge/SKILL.md +185 -0
- package/skills/hermes-forge/evals/evals.json +46 -0
- package/skills/issue-basics/SKILL.md +111 -0
- package/skills/issue-basics/evals/evals.json +46 -0
- package/skills/kernel/SKILL.md +166 -0
- package/skills/kernel/evals/evals.json +50 -0
- package/skills/memory/SKILL.md +102 -0
- package/skills/parallel-deep-research/SKILL.md +14 -11
- package/skills/parallel-deep-research/evals/evals.json +11 -27
- package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +134 -159
- package/skills/plan/evals/evals.json +42 -0
- package/skills/research/SKILL.md +195 -0
- package/skills/research/evals/evals.json +42 -0
- package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
- package/skills/review/evals/evals.json +42 -0
- package/skills/rollback/SKILL.md +110 -0
- package/skills/rollback/evals/evals.json +46 -0
- package/skills/rollback/references/methods.md +204 -0
- package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
- package/skills/shepherd/SKILL.md +66 -0
- package/skills/shepherd/evals/evals.json +42 -0
- package/skills/ship/SKILL.md +251 -0
- package/skills/ship/evals/evals.json +42 -0
- package/skills/smith/SKILL.md +142 -0
- package/skills/smith/evals/evals.json +46 -0
- package/skills/smith/references/autonomy-and-gates.md +94 -0
- package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
- package/skills/sonarcloud/evals/evals.json +46 -0
- package/skills/sonarcloud-analysis/SKILL.md +18 -13
- package/skills/sonarcloud-analysis/evals/evals.json +11 -15
- package/skills/status/SKILL.md +102 -0
- package/skills/status/evals/evals.json +50 -0
- package/skills/triage-ready/SKILL.md +121 -0
- package/skills/triage-ready/evals/evals.json +42 -0
- package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
- package/skills/validate/evals/evals.json +42 -0
- package/skills/verify/SKILL.md +299 -0
- package/skills/verify/evals/evals.json +50 -0
- package/.claude/commands/dev.md +0 -345
- package/.claude/commands/plan.md +0 -566
- package/.claude/commands/premerge.md +0 -186
- package/.claude/commands/research.md +0 -42
- package/.claude/commands/review.md +0 -451
- package/.claude/commands/rollback.md +0 -721
- package/.claude/commands/ship.md +0 -213
- package/.claude/commands/sonarcloud.md +0 -152
- package/.claude/commands/status.md +0 -90
- package/.claude/commands/validate.md +0 -288
- package/.claude/commands/verify.md +0 -269
- package/.claude/rules/workflow.md +0 -121
- package/.cline/workflows/dev.md +0 -342
- package/.cline/workflows/plan.md +0 -563
- package/.cline/workflows/premerge.md +0 -183
- package/.cline/workflows/research.md +0 -39
- package/.cline/workflows/review.md +0 -448
- package/.cline/workflows/rollback.md +0 -718
- package/.cline/workflows/ship.md +0 -210
- package/.cline/workflows/sonarcloud.md +0 -146
- package/.cline/workflows/status.md +0 -87
- package/.cline/workflows/validate.md +0 -285
- package/.cline/workflows/verify.md +0 -266
- package/.codex/config.toml +0 -11
- package/.codex/skills/dev/SKILL.md +0 -345
- package/.codex/skills/plan/SKILL.md +0 -566
- package/.codex/skills/premerge/SKILL.md +0 -186
- package/.codex/skills/research/SKILL.md +0 -42
- package/.codex/skills/review/SKILL.md +0 -451
- package/.codex/skills/rollback/SKILL.md +0 -721
- package/.codex/skills/ship/SKILL.md +0 -213
- package/.codex/skills/sonarcloud/SKILL.md +0 -149
- package/.codex/skills/status/SKILL.md +0 -90
- package/.codex/skills/validate/SKILL.md +0 -288
- package/.codex/skills/verify/SKILL.md +0 -269
- package/.cursor/commands/dev.md +0 -342
- package/.cursor/commands/plan.md +0 -563
- package/.cursor/commands/premerge.md +0 -183
- package/.cursor/commands/research.md +0 -39
- package/.cursor/commands/review.md +0 -448
- package/.cursor/commands/ship.md +0 -210
- package/.cursor/commands/sonarcloud.md +0 -146
- package/.cursor/commands/status.md +0 -87
- package/.cursor/commands/validate.md +0 -285
- package/.cursor/commands/verify.md +0 -266
- package/.cursorrules +0 -149
- package/.github/prompts/premerge.prompt.md +0 -188
- package/.github/prompts/research.prompt.md +0 -44
- package/.github/prompts/rollback.prompt.md +0 -723
- package/.github/prompts/ship.prompt.md +0 -215
- package/.github/prompts/status.prompt.md +0 -92
- package/.github/prompts/verify.prompt.md +0 -271
- package/.github/workflows/beads-to-github.yml +0 -56
- package/.github/workflows/github-to-beads.yml +0 -97
- package/.kilocode/workflows/dev.md +0 -346
- package/.kilocode/workflows/plan.md +0 -567
- package/.kilocode/workflows/premerge.md +0 -187
- package/.kilocode/workflows/research.md +0 -43
- package/.kilocode/workflows/review.md +0 -452
- package/.kilocode/workflows/rollback.md +0 -722
- package/.kilocode/workflows/ship.md +0 -214
- package/.kilocode/workflows/sonarcloud.md +0 -150
- package/.kilocode/workflows/status.md +0 -91
- package/.kilocode/workflows/validate.md +0 -289
- package/.kilocode/workflows/verify.md +0 -270
- package/.opencode/commands/dev.md +0 -345
- package/.opencode/commands/plan.md +0 -566
- package/.opencode/commands/premerge.md +0 -186
- package/.opencode/commands/research.md +0 -42
- package/.opencode/commands/review.md +0 -451
- package/.opencode/commands/rollback.md +0 -721
- package/.opencode/commands/ship.md +0 -213
- package/.opencode/commands/sonarcloud.md +0 -149
- package/.opencode/commands/status.md +0 -90
- package/.opencode/commands/validate.md +0 -288
- package/.opencode/commands/verify.md +0 -269
- package/.roo/commands/dev.md +0 -346
- package/.roo/commands/plan.md +0 -567
- package/.roo/commands/premerge.md +0 -187
- package/.roo/commands/research.md +0 -43
- package/.roo/commands/review.md +0 -452
- package/.roo/commands/rollback.md +0 -722
- package/.roo/commands/ship.md +0 -214
- package/.roo/commands/sonarcloud.md +0 -150
- package/.roo/commands/status.md +0 -91
- package/.roo/commands/validate.md +0 -289
- package/.roo/commands/verify.md +0 -270
- package/docs/BEADS_GITHUB_SYNC.md +0 -255
- package/docs/GREPTILE_SETUP.md +0 -400
- package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
- package/docs/SETUP.md +0 -663
- package/docs/VALIDATION.md +0 -363
- package/lib/agents/cline.plugin.json +0 -29
- package/lib/agents/copilot.plugin.json +0 -24
- package/lib/agents/kilocode.plugin.json +0 -22
- package/lib/agents/opencode.plugin.json +0 -23
- package/lib/agents/roo.plugin.json +0 -30
- package/lib/beads-health-check.js +0 -143
- package/lib/commands/commands-reset.js +0 -147
- package/opencode.json +0 -67
- package/scripts/beads-context.test.js +0 -567
- package/scripts/github-beads-sync/comment.mjs +0 -64
- package/scripts/github-beads-sync/config.mjs +0 -148
- package/scripts/github-beads-sync/github-api.mjs +0 -131
- package/scripts/github-beads-sync/index.mjs +0 -332
- package/scripts/github-beads-sync/label-mapper.mjs +0 -54
- package/scripts/github-beads-sync/mapping.mjs +0 -78
- package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
- package/scripts/github-beads-sync/reverse-sync.mjs +0 -138
- package/scripts/github-beads-sync/run-bd.mjs +0 -161
- package/scripts/github-beads-sync/sanitize.mjs +0 -121
- package/scripts/github-beads-sync.config.json +0 -26
- package/scripts/sync-commands.js +0 -600
package/scripts/test.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Cross-platform test runner
|
|
3
|
+
* Cross-platform test runner shared by the pre-push hook and local validation.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* It runs only the tests affected by known changes when possible and falls back
|
|
6
|
+
* to the full suite for package-level or unknown-file changes.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
const { execFileSync: defaultExecFileSync, spawnSync: defaultSpawnSync } = require('node:child_process');
|
|
@@ -22,8 +22,102 @@ const PACKAGE_LEVEL_PATHS = new Set([
|
|
|
22
22
|
'package-lock.json',
|
|
23
23
|
]);
|
|
24
24
|
|
|
25
|
+
const KNOWN_TARGETABLE_PREFIXES = [
|
|
26
|
+
'.cursor/',
|
|
27
|
+
'.codex/',
|
|
28
|
+
'.forge/',
|
|
29
|
+
'.github/agentic-workflows/',
|
|
30
|
+
'.github/workflows/',
|
|
31
|
+
'test/',
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
const ALWAYS_RUN_RISK_TEST_TARGETS = [
|
|
35
|
+
// Windows + concurrent filesystem locking has failed post-merge; keep this
|
|
36
|
+
// in the fast PR lane until enough full-matrix runs prove it stable.
|
|
37
|
+
'test/project-memory.test.js',
|
|
38
|
+
];
|
|
39
|
+
|
|
25
40
|
const isWindows = process.platform === 'win32';
|
|
26
41
|
|
|
42
|
+
// Wall-clock ceiling for a single spawned test lane. Bun's per-test `--timeout`
|
|
43
|
+
// races a JS timer and CANNOT preempt a synchronous blocking spawn (e.g. an
|
|
44
|
+
// `execFileSync` of git/bash that hangs during git mid-push state). Without this
|
|
45
|
+
// ceiling such a hang blocks `forge push` indefinitely (observed ~50 min on
|
|
46
|
+
// Windows, issue 8aef79e8). This kills the lane process so the push fails fast
|
|
47
|
+
// instead of hanging forever.
|
|
48
|
+
//
|
|
49
|
+
// The default is a practical 5 minutes for a single TARGETED lane: those run a
|
|
50
|
+
// small, mapped subset that completes in seconds, so 5 min is comfortably a
|
|
51
|
+
// fail-fast ceiling (not the old 15-min wait that made `forge push` feel
|
|
52
|
+
// wedged). Raise it with FORGE_TEST_TIMEOUT_MS for slow machines.
|
|
53
|
+
const DEFAULT_TEST_COMMAND_TIMEOUT_MS = 5 * 60 * 1000;
|
|
54
|
+
|
|
55
|
+
// Wall-clock budget for the FULL-SUITE fallback lane (`scripts/test-full-suite.js`),
|
|
56
|
+
// which runs on package-level, unmapped, or zero-resolved changes. Unlike a
|
|
57
|
+
// targeted lane, a healthy full suite legitimately takes 5-10 min, so the 5-min
|
|
58
|
+
// fail-fast ceiling would kill a good-but-slow full run. Kept at 10 min to stay
|
|
59
|
+
// aligned with the local-validation budget (VALIDATION_COMMAND_TIMEOUT_MS = 600000
|
|
60
|
+
// in lib/commands/validate.js and its "long enough subprocess timeout for the
|
|
61
|
+
// full local suite" regression test). FORGE_TEST_TIMEOUT_MS still overrides.
|
|
62
|
+
const DEFAULT_FULL_SUITE_TIMEOUT_MS = 10 * 60 * 1000;
|
|
63
|
+
|
|
64
|
+
// Conventional shell exit code for a command terminated by a timeout.
|
|
65
|
+
const TIMEOUT_EXIT_CODE = 124;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Reads and validates the FORGE_TEST_TIMEOUT_MS override, if any.
|
|
69
|
+
*
|
|
70
|
+
* @param {NodeJS.ProcessEnv} env Environment to read the override from.
|
|
71
|
+
* @returns {number|null} The positive integer override, or null when unset/invalid.
|
|
72
|
+
*/
|
|
73
|
+
function readTimeoutOverride(env) {
|
|
74
|
+
const parsed = Number.parseInt(env.FORGE_TEST_TIMEOUT_MS, 10);
|
|
75
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Resolves the wall-clock timeout for a targeted/e2e/edge-case lane, honoring
|
|
80
|
+
* FORGE_TEST_TIMEOUT_MS.
|
|
81
|
+
*
|
|
82
|
+
* @param {NodeJS.ProcessEnv} [env=process.env] Environment to read the override from.
|
|
83
|
+
* @returns {number} Timeout in milliseconds (defaults to DEFAULT_TEST_COMMAND_TIMEOUT_MS).
|
|
84
|
+
*/
|
|
85
|
+
function resolveCommandTimeoutMs(env = process.env) {
|
|
86
|
+
return readTimeoutOverride(env) ?? DEFAULT_TEST_COMMAND_TIMEOUT_MS;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Resolves the wall-clock budget for the full-suite fallback lane. An explicit
|
|
91
|
+
* FORGE_TEST_TIMEOUT_MS override wins; otherwise it uses the larger,
|
|
92
|
+
* validation-aligned budget so a healthy-but-slow full run is not failed fast.
|
|
93
|
+
*
|
|
94
|
+
* @param {NodeJS.ProcessEnv} [env=process.env] Environment to read the override from.
|
|
95
|
+
* @returns {number} Timeout in milliseconds (defaults to DEFAULT_FULL_SUITE_TIMEOUT_MS).
|
|
96
|
+
*/
|
|
97
|
+
function resolveFullSuiteTimeoutMs(env = process.env) {
|
|
98
|
+
return readTimeoutOverride(env) ?? DEFAULT_FULL_SUITE_TIMEOUT_MS;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* @typedef {Object} TestExecutionPlan
|
|
103
|
+
* @property {string[]} changedFiles
|
|
104
|
+
* @property {boolean} hasUnmappedFiles
|
|
105
|
+
* @property {boolean} hasUnknownChangedFiles
|
|
106
|
+
* @property {boolean} hasZeroResolvedTests
|
|
107
|
+
* @property {'targeted'|'full'} mode
|
|
108
|
+
* @property {string} reason
|
|
109
|
+
* @property {boolean} runE2E
|
|
110
|
+
* @property {boolean} runFullSuite
|
|
111
|
+
* @property {boolean} runTestEnv
|
|
112
|
+
* @property {boolean} runWorkflowTests
|
|
113
|
+
* @property {string[]} testTargets
|
|
114
|
+
*/
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Detects the package manager to use for running test commands in this checkout.
|
|
118
|
+
*
|
|
119
|
+
* @returns {'bun'|'pnpm'|'yarn'|'npm'} The package manager inferred from lockfiles.
|
|
120
|
+
*/
|
|
27
121
|
function detectPackageManager() {
|
|
28
122
|
if (fs.existsSync('bun.lockb') || fs.existsSync('bun.lock')) return 'bun';
|
|
29
123
|
if (fs.existsSync('pnpm-lock.yaml')) return 'pnpm';
|
|
@@ -31,6 +125,12 @@ function detectPackageManager() {
|
|
|
31
125
|
return 'npm';
|
|
32
126
|
}
|
|
33
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Removes Git hook-only environment variables before spawning nested Git-aware commands.
|
|
130
|
+
*
|
|
131
|
+
* @param {NodeJS.ProcessEnv} [sourceEnv=process.env] Environment variables to sanitize.
|
|
132
|
+
* @returns {NodeJS.ProcessEnv} A copy of the environment without hook-specific Git variables.
|
|
133
|
+
*/
|
|
34
134
|
function stripGitHookEnv(sourceEnv = process.env) {
|
|
35
135
|
const env = { ...sourceEnv };
|
|
36
136
|
for (const key of Object.keys(env)) {
|
|
@@ -43,21 +143,90 @@ function stripGitHookEnv(sourceEnv = process.env) {
|
|
|
43
143
|
return env;
|
|
44
144
|
}
|
|
45
145
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
146
|
+
/**
|
|
147
|
+
* Checks whether a changed path belongs to a known test-targetable area.
|
|
148
|
+
*
|
|
149
|
+
* @param {string} file Repository-relative changed file path.
|
|
150
|
+
* @returns {boolean} True when the path can be handled by targeted test selection.
|
|
151
|
+
*/
|
|
152
|
+
function isKnownTargetablePath(file) {
|
|
153
|
+
if (file === '.gitignore') {
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (file === 'README.md'
|
|
158
|
+
|| file === 'bin/forge.js'
|
|
159
|
+
|| file === 'QUICKSTART.md'
|
|
160
|
+
|| file === 'CHANGELOG.md'
|
|
161
|
+
|| file === 'AGENTS.md'
|
|
162
|
+
|| file === 'DEVELOPMENT.md'
|
|
163
|
+
|| file === 'docs/INDEX.md'
|
|
164
|
+
|| file === 'docs/PROJECT_DESIGN.md'
|
|
165
|
+
|| file.startsWith('docs/forge/')
|
|
166
|
+
|| file.startsWith('docs/guides/')
|
|
167
|
+
|| file.startsWith('docs/plans/')
|
|
168
|
+
|| file.startsWith('docs/reference/')
|
|
169
|
+
|| file.startsWith('docs/work/')) {
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return KNOWN_TARGETABLE_PREFIXES.some((prefix) => file.startsWith(prefix));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Determines whether the resolved test targets require workflow-specific validation.
|
|
178
|
+
*
|
|
179
|
+
* @param {string[]} testTargets Repository-relative test file paths.
|
|
180
|
+
* @returns {boolean} True when workflow tests are part of the target set.
|
|
181
|
+
*/
|
|
182
|
+
function includesWorkflowTarget(testTargets) {
|
|
183
|
+
return testTargets.some((target) => target === 'test/ci-workflow.test.js'
|
|
184
|
+
|| target === 'test/structural/agentic-workflow-sync.test.js'
|
|
185
|
+
|| target.startsWith('test/workflows/'));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Deduplicates test targets while preserving the original execution order.
|
|
190
|
+
*
|
|
191
|
+
* @param {string[]} testTargets Repository-relative test file paths.
|
|
192
|
+
* @returns {string[]} Unique test targets in first-seen order.
|
|
193
|
+
*/
|
|
194
|
+
function uniqueTestTargets(testTargets) {
|
|
195
|
+
return [...new Set(testTargets)];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function isExtraLaneOnlyPath(file) {
|
|
199
|
+
return file.startsWith('test/e2e/') || file.startsWith('test-env/');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Builds the execution plan used by PR, pre-push, and local validation test lanes.
|
|
204
|
+
*
|
|
205
|
+
* @param {string} projectRoot Absolute or relative repository root.
|
|
206
|
+
* @param {typeof defaultExecFileSync} [execFileSync=defaultExecFileSync] Command runner used to inspect Git state.
|
|
207
|
+
* @param {{sinceUpstream?: boolean}} [options={}] Test selection options.
|
|
208
|
+
* @returns {TestExecutionPlan} The computed test execution plan.
|
|
209
|
+
*/
|
|
210
|
+
function buildTestExecutionPlan(projectRoot, execFileSync = defaultExecFileSync, options = {}) {
|
|
211
|
+
const diffOptions = {
|
|
212
|
+
sinceUpstream: options.sinceUpstream !== false,
|
|
213
|
+
};
|
|
214
|
+
const changedFiles = getChangedFiles(execFileSync, diffOptions);
|
|
215
|
+
const affectedTestTargets = getAffectedTestFiles(projectRoot, execFileSync, fs, diffOptions);
|
|
49
216
|
|
|
50
217
|
let runFullSuite = false;
|
|
51
218
|
let runTestEnv = false;
|
|
52
219
|
let runE2E = false;
|
|
220
|
+
let runWorkflowTests = includesWorkflowTarget(affectedTestTargets);
|
|
53
221
|
let hasUnmappedFiles = false;
|
|
54
|
-
const hasUnknownChangedFiles = changedFiles.length === 0 &&
|
|
222
|
+
const hasUnknownChangedFiles = changedFiles.length === 0 && affectedTestTargets.length === 0;
|
|
55
223
|
|
|
56
224
|
for (const file of changedFiles) {
|
|
57
225
|
if (PACKAGE_LEVEL_PATHS.has(file) || file.startsWith('packages/')) {
|
|
58
226
|
runFullSuite = true;
|
|
59
227
|
runTestEnv = true;
|
|
60
228
|
runE2E = true;
|
|
229
|
+
runWorkflowTests = true;
|
|
61
230
|
break;
|
|
62
231
|
}
|
|
63
232
|
|
|
@@ -71,29 +240,80 @@ function classifyPushTests(projectRoot, execFileSync = defaultExecFileSync) {
|
|
|
71
240
|
continue;
|
|
72
241
|
}
|
|
73
242
|
|
|
74
|
-
if ((file.startsWith('lib/') || file.startsWith('scripts/')) && file.endsWith('.js')) {
|
|
243
|
+
if ((file.startsWith('lib/') || file.startsWith('scripts/')) && (file.endsWith('.js') || file.endsWith('.sh'))) {
|
|
75
244
|
runTestEnv = true;
|
|
245
|
+
if (file === 'scripts/behavioral-judge.sh') {
|
|
246
|
+
runWorkflowTests = true;
|
|
247
|
+
}
|
|
76
248
|
continue;
|
|
77
249
|
}
|
|
78
250
|
|
|
79
|
-
if (file.startsWith('
|
|
251
|
+
if (file.startsWith('.github/workflows/') || file.startsWith('.github/agentic-workflows/')) {
|
|
252
|
+
runWorkflowTests = true;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (isKnownTargetablePath(file)) {
|
|
80
256
|
continue;
|
|
81
257
|
}
|
|
82
258
|
|
|
83
259
|
hasUnmappedFiles = true;
|
|
84
260
|
}
|
|
85
261
|
|
|
262
|
+
const hasOnlyExtraLaneChanges = changedFiles.length > 0
|
|
263
|
+
&& changedFiles.every(isExtraLaneOnlyPath);
|
|
264
|
+
const hasZeroResolvedTests = changedFiles.length > 0
|
|
265
|
+
&& affectedTestTargets.length === 0
|
|
266
|
+
&& !runFullSuite
|
|
267
|
+
&& !hasOnlyExtraLaneChanges;
|
|
268
|
+
const shouldRunFullSuite = runFullSuite || hasUnmappedFiles || hasUnknownChangedFiles || hasZeroResolvedTests;
|
|
269
|
+
|
|
270
|
+
const reason = hasUnmappedFiles
|
|
271
|
+
? 'unmapped pushed files require full unit coverage'
|
|
272
|
+
: hasUnknownChangedFiles
|
|
273
|
+
? 'changed files could not be resolved safely'
|
|
274
|
+
: hasZeroResolvedTests
|
|
275
|
+
? 'known changes did not resolve runnable tests'
|
|
276
|
+
: runFullSuite
|
|
277
|
+
? 'package-level changes detected'
|
|
278
|
+
: 'known changes mapped to targeted tests';
|
|
279
|
+
|
|
86
280
|
return {
|
|
87
281
|
changedFiles,
|
|
88
282
|
hasUnmappedFiles,
|
|
89
283
|
hasUnknownChangedFiles,
|
|
284
|
+
hasZeroResolvedTests,
|
|
285
|
+
mode: shouldRunFullSuite ? 'full' : 'targeted',
|
|
286
|
+
reason,
|
|
90
287
|
runE2E,
|
|
91
|
-
runFullSuite:
|
|
288
|
+
runFullSuite: shouldRunFullSuite,
|
|
92
289
|
runTestEnv,
|
|
93
|
-
|
|
290
|
+
runWorkflowTests,
|
|
291
|
+
testTargets: shouldRunFullSuite
|
|
292
|
+
? affectedTestTargets
|
|
293
|
+
: uniqueTestTargets([...affectedTestTargets, ...ALWAYS_RUN_RISK_TEST_TARGETS]),
|
|
94
294
|
};
|
|
95
295
|
}
|
|
96
296
|
|
|
297
|
+
/**
|
|
298
|
+
* Classifies the pushed changes into the test plan enforced by the pre-push hook.
|
|
299
|
+
*
|
|
300
|
+
* @param {string} projectRoot Absolute or relative repository root.
|
|
301
|
+
* @param {typeof defaultExecFileSync} [execFileSync=defaultExecFileSync] Command runner used to inspect Git state.
|
|
302
|
+
* @returns {TestExecutionPlan} The pre-push test execution plan.
|
|
303
|
+
*/
|
|
304
|
+
function classifyPushTests(projectRoot, execFileSync = defaultExecFileSync) {
|
|
305
|
+
return buildTestExecutionPlan(projectRoot, execFileSync, { sinceUpstream: true });
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Runs a command and returns its exit status, throwing only when process spawning fails.
|
|
310
|
+
*
|
|
311
|
+
* @param {string} command Executable name.
|
|
312
|
+
* @param {string[]} args Command arguments.
|
|
313
|
+
* @param {import('node:child_process').SpawnSyncOptions} [options={}] Spawn options.
|
|
314
|
+
* @param {typeof defaultSpawnSync} [spawnSync=defaultSpawnSync] Process runner.
|
|
315
|
+
* @returns {number} Process exit status, or 1 when no status is reported.
|
|
316
|
+
*/
|
|
97
317
|
function runCommand(command, args, options = {}, spawnSync = defaultSpawnSync) {
|
|
98
318
|
const result = spawnSync(command, args, {
|
|
99
319
|
stdio: 'inherit',
|
|
@@ -102,46 +322,63 @@ function runCommand(command, args, options = {}, spawnSync = defaultSpawnSync) {
|
|
|
102
322
|
});
|
|
103
323
|
|
|
104
324
|
if (result.error) {
|
|
325
|
+
if (result.error.code === 'ETIMEDOUT') {
|
|
326
|
+
console.error('');
|
|
327
|
+
console.error('Test lane exceeded its wall-clock ceiling and was terminated.');
|
|
328
|
+
console.error('A single test likely hung (e.g. a spawned git/bash call that never returns).');
|
|
329
|
+
console.error('Adjust the ceiling with FORGE_TEST_TIMEOUT_MS. Failing the run instead of');
|
|
330
|
+
console.error('blocking the push. See issue 8aef79e8.');
|
|
331
|
+
console.error('');
|
|
332
|
+
return TIMEOUT_EXIT_CODE;
|
|
333
|
+
}
|
|
105
334
|
throw result.error;
|
|
106
335
|
}
|
|
107
336
|
|
|
108
337
|
return result.status ?? 1;
|
|
109
338
|
}
|
|
110
339
|
|
|
111
|
-
|
|
340
|
+
/**
|
|
341
|
+
* Executes a computed test plan and any extra targeted validation lanes.
|
|
342
|
+
*
|
|
343
|
+
* @param {TestExecutionPlan} plan Test plan to execute.
|
|
344
|
+
* @param {Object} [deps={}] Runtime dependencies for tests.
|
|
345
|
+
* @returns {number} Exit status for the executed plan.
|
|
346
|
+
*/
|
|
347
|
+
function runTestExecutionPlan(plan, deps = {}) {
|
|
112
348
|
const spawnSync = deps.spawnSync || defaultSpawnSync;
|
|
113
|
-
const execFileSync = deps.execFileSync || defaultExecFileSync;
|
|
114
349
|
const pkgManager = deps.pkgManager || detectPackageManager();
|
|
115
350
|
const env = deps.env || stripGitHookEnv(process.env);
|
|
116
|
-
const
|
|
351
|
+
const bunCommand = deps.bunCommand || env.BUN_EXE || process.env.BUN_EXE || 'bun';
|
|
352
|
+
const label = deps.label || 'tests';
|
|
353
|
+
const timeout = resolveCommandTimeoutMs(env);
|
|
354
|
+
const laneOptions = { env, killSignal: 'SIGKILL', timeout };
|
|
355
|
+
// The full-suite fallback gets a larger, validation-aligned budget so a
|
|
356
|
+
// healthy-but-slow full run is not failed fast by the targeted-lane ceiling.
|
|
357
|
+
const fullSuiteOptions = { env, killSignal: 'SIGKILL', timeout: resolveFullSuiteTimeoutMs(env) };
|
|
117
358
|
|
|
118
|
-
console.log(`Running
|
|
359
|
+
console.log(`Running ${label} (${pkgManager})...`);
|
|
119
360
|
|
|
120
361
|
try {
|
|
121
362
|
if (plan.runFullSuite) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
: plan.hasUnknownChangedFiles
|
|
125
|
-
? 'changed files could not be resolved safely'
|
|
126
|
-
: 'package-level changes detected';
|
|
127
|
-
console.log(` Mode: full suite (${reason})`);
|
|
128
|
-
const status = runCommand(pkgManager, ['run', 'test'], { env }, spawnSync);
|
|
363
|
+
console.log(` Mode: full suite (${plan.reason})`);
|
|
364
|
+
const status = runCommand('node', ['scripts/test-full-suite.js'], fullSuiteOptions, spawnSync);
|
|
129
365
|
if (status !== 0) return status;
|
|
130
366
|
} else if (plan.testTargets.length > 0) {
|
|
131
367
|
console.log(` Mode: targeted (${plan.testTargets.length} test file${plan.testTargets.length === 1 ? '' : 's'})`);
|
|
132
|
-
const
|
|
368
|
+
const command = pkgManager === 'bun' ? bunCommand : pkgManager;
|
|
369
|
+
const status = runCommand(command, ['run', 'test', ...plan.testTargets], laneOptions, spawnSync);
|
|
133
370
|
if (status !== 0) return status;
|
|
134
371
|
}
|
|
135
372
|
|
|
136
373
|
if (plan.runE2E) {
|
|
137
374
|
console.log(' Extra: running affected e2e tests');
|
|
138
|
-
const status = runCommand(
|
|
375
|
+
const status = runCommand(bunCommand, ['test', '--timeout', '15000', 'test/e2e/'], laneOptions, spawnSync);
|
|
139
376
|
if (status !== 0) return status;
|
|
140
377
|
}
|
|
141
378
|
|
|
142
|
-
if (plan.runTestEnv) {
|
|
379
|
+
if (!plan.runFullSuite && plan.runTestEnv) {
|
|
143
380
|
console.log(' Extra: running affected edge-case tests');
|
|
144
|
-
const status = runCommand(
|
|
381
|
+
const status = runCommand(bunCommand, ['test', '--timeout', '15000', 'test-env/'], laneOptions, spawnSync);
|
|
145
382
|
if (status !== 0) return status;
|
|
146
383
|
}
|
|
147
384
|
|
|
@@ -149,19 +386,56 @@ function runPrePushTests(projectRoot = process.cwd(), deps = {}) {
|
|
|
149
386
|
return 0;
|
|
150
387
|
} catch (error) {
|
|
151
388
|
console.error('');
|
|
152
|
-
console.error(`Failed to run
|
|
389
|
+
console.error(`Failed to run ${label}: ${error.message}`);
|
|
153
390
|
console.error('');
|
|
154
391
|
return 1;
|
|
155
392
|
}
|
|
156
393
|
}
|
|
157
394
|
|
|
395
|
+
/**
|
|
396
|
+
* Runs the pre-push test plan for the current checkout.
|
|
397
|
+
*
|
|
398
|
+
* @param {string} [projectRoot=process.cwd()] Repository root.
|
|
399
|
+
* @param {Object} [deps={}] Runtime dependencies for tests.
|
|
400
|
+
* @returns {number} Exit status for pre-push tests.
|
|
401
|
+
*/
|
|
402
|
+
function runPrePushTests(projectRoot = process.cwd(), deps = {}) {
|
|
403
|
+
const execFileSync = deps.execFileSync || defaultExecFileSync;
|
|
404
|
+
const plan = classifyPushTests(projectRoot, execFileSync);
|
|
405
|
+
return runTestExecutionPlan(plan, { ...deps, label: 'pre-push tests' });
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Runs the local validation test plan for the current checkout.
|
|
410
|
+
*
|
|
411
|
+
* @param {string} [projectRoot=process.cwd()] Repository root.
|
|
412
|
+
* @param {Object} [deps={}] Runtime dependencies for tests.
|
|
413
|
+
* @returns {number} Exit status for local validation tests.
|
|
414
|
+
*/
|
|
415
|
+
function runLocalValidationTests(projectRoot = process.cwd(), deps = {}) {
|
|
416
|
+
const execFileSync = deps.execFileSync || defaultExecFileSync;
|
|
417
|
+
const plan = buildTestExecutionPlan(projectRoot, execFileSync, { sinceUpstream: true });
|
|
418
|
+
return runTestExecutionPlan(plan, { ...deps, label: 'local validation tests' });
|
|
419
|
+
}
|
|
420
|
+
|
|
158
421
|
if (require.main === module) {
|
|
159
|
-
process.
|
|
422
|
+
const exitCode = process.argv.includes('--validate')
|
|
423
|
+
? runLocalValidationTests()
|
|
424
|
+
: runPrePushTests();
|
|
425
|
+
process.exit(exitCode);
|
|
160
426
|
}
|
|
161
427
|
|
|
162
428
|
module.exports = {
|
|
429
|
+
ALWAYS_RUN_RISK_TEST_TARGETS,
|
|
430
|
+
DEFAULT_FULL_SUITE_TIMEOUT_MS,
|
|
431
|
+
DEFAULT_TEST_COMMAND_TIMEOUT_MS,
|
|
432
|
+
buildTestExecutionPlan,
|
|
163
433
|
classifyPushTests,
|
|
164
434
|
detectPackageManager,
|
|
435
|
+
resolveCommandTimeoutMs,
|
|
436
|
+
resolveFullSuiteTimeoutMs,
|
|
437
|
+
runLocalValidationTests,
|
|
165
438
|
runPrePushTests,
|
|
439
|
+
runTestExecutionPlan,
|
|
166
440
|
stripGitHookEnv,
|
|
167
441
|
};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { spawnSync } = require('node:child_process');
|
|
5
|
+
|
|
6
|
+
const bunCommand = process.env.BUN_EXE || 'bun';
|
|
7
|
+
|
|
8
|
+
function color(code, text) {
|
|
9
|
+
return process.stdout.isTTY ? `\u001b[${code}m${text}\u001b[0m` : text;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function printHeader(title) {
|
|
13
|
+
const line = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━';
|
|
14
|
+
console.log('');
|
|
15
|
+
console.log(color('0;34', line));
|
|
16
|
+
console.log(color('0;34', `▶ ${title}`));
|
|
17
|
+
console.log(color('0;34', line));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function printStatus(kind, message) {
|
|
21
|
+
const palette = {
|
|
22
|
+
error: ['0;31', '✗'],
|
|
23
|
+
success: ['0;32', '✓'],
|
|
24
|
+
warning: ['1;33', '⚠'],
|
|
25
|
+
};
|
|
26
|
+
const [code, prefix] = palette[kind];
|
|
27
|
+
console.log(color(code, `${prefix} ${message}`));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function run(command, args, options = {}) {
|
|
31
|
+
const result = spawnSync(command, args, {
|
|
32
|
+
cwd: process.cwd(),
|
|
33
|
+
encoding: 'utf8',
|
|
34
|
+
stdio: options.captureOutput ? ['inherit', 'pipe', 'pipe'] : 'inherit',
|
|
35
|
+
shell: false,
|
|
36
|
+
env: options.env || process.env,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (result.error) {
|
|
40
|
+
throw result.error;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function combinedOutput(result) {
|
|
47
|
+
return [result.stdout, result.stderr].filter(Boolean).join('');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function hasBlockingAuditSeverity(output = '') {
|
|
51
|
+
if (typeof output !== 'string' || output.length === 0) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return [
|
|
56
|
+
/\b(?:critical|high)\s+severity\b/i,
|
|
57
|
+
/\bseverity\s*:?\s*(?:critical|high)\b/i,
|
|
58
|
+
].some((pattern) => pattern.test(output));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function main(options = {}) {
|
|
62
|
+
const runCommand = options.runCommand ?? run;
|
|
63
|
+
const activeBunCommand = options.bunCommand ?? bunCommand;
|
|
64
|
+
console.log('');
|
|
65
|
+
console.log(color('0;34', '╔═══════════════════════════════════════════╗'));
|
|
66
|
+
console.log(color('0;34', '║ Forge Quality Gate - Running Checks ║'));
|
|
67
|
+
console.log(color('0;34', '╚═══════════════════════════════════════════╝'));
|
|
68
|
+
|
|
69
|
+
printHeader('1/4: Type Check');
|
|
70
|
+
const typecheck = runCommand(activeBunCommand, ['run', 'typecheck'], { captureOutput: true });
|
|
71
|
+
const typecheckOutput = combinedOutput(typecheck);
|
|
72
|
+
if (typecheckOutput) {
|
|
73
|
+
process.stdout.write(typecheckOutput);
|
|
74
|
+
if (!typecheckOutput.endsWith('\n')) {
|
|
75
|
+
process.stdout.write('\n');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if ((typecheck.status ?? 1) !== 0) {
|
|
79
|
+
printStatus('error', 'Type check failed');
|
|
80
|
+
return 1;
|
|
81
|
+
}
|
|
82
|
+
if (/no typescript in project|skipping type check/i.test(typecheckOutput)) {
|
|
83
|
+
printStatus('warning', 'SKIPPED (no TypeScript in project)');
|
|
84
|
+
} else {
|
|
85
|
+
printStatus('success', 'Type check passed');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
printHeader('2/4: Lint');
|
|
89
|
+
const lint = runCommand(activeBunCommand, ['run', 'lint']);
|
|
90
|
+
if ((lint.status ?? 1) !== 0) {
|
|
91
|
+
printStatus('error', 'Lint failed');
|
|
92
|
+
return 1;
|
|
93
|
+
}
|
|
94
|
+
printStatus('success', 'Lint passed');
|
|
95
|
+
|
|
96
|
+
printHeader('3/4: Security Audit');
|
|
97
|
+
const audit = runCommand(activeBunCommand, ['audit'], { captureOutput: true });
|
|
98
|
+
const auditOutput = combinedOutput(audit);
|
|
99
|
+
if (auditOutput) {
|
|
100
|
+
process.stdout.write(auditOutput);
|
|
101
|
+
if (!auditOutput.endsWith('\n')) {
|
|
102
|
+
process.stdout.write('\n');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (hasBlockingAuditSeverity(auditOutput)) {
|
|
106
|
+
printStatus('error', 'Security audit found critical/high vulnerabilities');
|
|
107
|
+
return 1;
|
|
108
|
+
}
|
|
109
|
+
if ((audit.status ?? 1) === 0) {
|
|
110
|
+
printStatus('success', 'Security audit passed');
|
|
111
|
+
} else {
|
|
112
|
+
printStatus('warning', 'Security audit found issues (moderate/low — non-blocking)');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
printHeader('4/4: Tests');
|
|
116
|
+
const tests = runCommand('node', ['scripts/test.js', '--validate']);
|
|
117
|
+
if ((tests.status ?? 1) !== 0) {
|
|
118
|
+
printStatus('error', 'Tests failed');
|
|
119
|
+
return 1;
|
|
120
|
+
}
|
|
121
|
+
printStatus('success', 'All tests passed');
|
|
122
|
+
|
|
123
|
+
console.log('');
|
|
124
|
+
console.log(color('0;32', '╔═══════════════════════════════════════════╗'));
|
|
125
|
+
console.log(color('0;32', '║ ✓ All Checks Passed Successfully ║'));
|
|
126
|
+
console.log(color('0;32', '╚═══════════════════════════════════════════╝'));
|
|
127
|
+
console.log('');
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (require.main === module) {
|
|
132
|
+
process.exit(main());
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
module.exports = {
|
|
136
|
+
color,
|
|
137
|
+
combinedOutput,
|
|
138
|
+
hasBlockingAuditSeverity,
|
|
139
|
+
main,
|
|
140
|
+
printHeader,
|
|
141
|
+
printStatus,
|
|
142
|
+
run,
|
|
143
|
+
};
|
package/scripts/validate.sh
CHANGED
|
@@ -44,6 +44,23 @@ print_warning() {
|
|
|
44
44
|
echo -e "${YELLOW}⚠ $1${NC}"
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
run_validate_tests() {
|
|
48
|
+
if [[ "${OS:-}" == "Windows_NT" && ( -n "${MSYSTEM:-}" || "${OSTYPE:-}" == msys* || "${OSTYPE:-}" == cygwin* ) ]]; then
|
|
49
|
+
local bun_exe
|
|
50
|
+
bun_exe="$(where.exe bun 2>/dev/null | tr -d '\r' | grep -i 'bun\.exe$' | head -n 1)"
|
|
51
|
+
|
|
52
|
+
if [[ -n "$bun_exe" ]]; then
|
|
53
|
+
cmd.exe /d /c "set \"BUN_EXE=$bun_exe\" && node scripts\\test.js --validate"
|
|
54
|
+
return $?
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
cmd.exe /d /c "node scripts\\test.js --validate"
|
|
58
|
+
return $?
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
node scripts/test.js --validate
|
|
62
|
+
}
|
|
63
|
+
|
|
47
64
|
echo ""
|
|
48
65
|
echo -e "${BLUE}╔═══════════════════════════════════════════╗${NC}"
|
|
49
66
|
echo -e "${BLUE}║ Forge Quality Gate - Running Checks ║${NC}"
|
|
@@ -78,7 +95,7 @@ fi
|
|
|
78
95
|
|
|
79
96
|
# Step 4: Tests
|
|
80
97
|
print_header "4/4: Tests"
|
|
81
|
-
if
|
|
98
|
+
if run_validate_tests; then
|
|
82
99
|
print_success "All tests passed"
|
|
83
100
|
else
|
|
84
101
|
print_error "Tests failed"
|