@soleur/opencode 0.0.0-stage → 3.354.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-plugin/agents.manifest.json +476 -0
- package/.claude-plugin/plugin.json +42 -0
- package/README.md +44 -2
- package/agents/community/.gitkeep +0 -0
- package/agents/engineering/cto.md +87 -0
- package/agents/engineering/design/ddd-architect.md +95 -0
- package/agents/engineering/discovery/agent-finder.md +214 -0
- package/agents/engineering/discovery/functional-discovery.md +182 -0
- package/agents/engineering/infra/infra-security.md +151 -0
- package/agents/engineering/infra/platform-strategist.md +109 -0
- package/agents/engineering/infra/terraform-architect.md +171 -0
- package/agents/engineering/prompt-engineer.md +37 -0
- package/agents/engineering/research/best-practices-researcher.md +115 -0
- package/agents/engineering/research/framework-docs-researcher.md +93 -0
- package/agents/engineering/research/git-history-analyzer.md +53 -0
- package/agents/engineering/research/learnings-researcher.md +285 -0
- package/agents/engineering/research/repo-research-analyst.md +118 -0
- package/agents/engineering/review/agent-native-reviewer.md +279 -0
- package/agents/engineering/review/architecture-strategist.md +61 -0
- package/agents/engineering/review/code-quality-analyst.md +92 -0
- package/agents/engineering/review/code-simplicity-reviewer.md +109 -0
- package/agents/engineering/review/data-integrity-guardian.md +82 -0
- package/agents/engineering/review/data-migration-expert.md +98 -0
- package/agents/engineering/review/deployment-verification-agent.md +183 -0
- package/agents/engineering/review/dhh-rails-reviewer.md +46 -0
- package/agents/engineering/review/kieran-rails-reviewer.md +87 -0
- package/agents/engineering/review/legacy-code-expert.md +55 -0
- package/agents/engineering/review/observability-coverage-reviewer.md +147 -0
- package/agents/engineering/review/pattern-recognition-specialist.md +59 -0
- package/agents/engineering/review/performance-oracle.md +130 -0
- package/agents/engineering/review/security-sentinel.md +167 -0
- package/agents/engineering/review/semgrep-sast.md +36 -0
- package/agents/engineering/review/test-design-reviewer.md +150 -0
- package/agents/engineering/review/user-impact-reviewer.md +70 -0
- package/agents/engineering/workflow/pr-comment-resolver.md +69 -0
- package/agents/finance/budget-analyst.md +29 -0
- package/agents/finance/cfo.md +44 -0
- package/agents/finance/financial-reporter.md +30 -0
- package/agents/finance/revenue-analyst.md +30 -0
- package/agents/legal/clo.md +115 -0
- package/agents/legal/legal-compliance-auditor.md +110 -0
- package/agents/legal/legal-document-generator.md +134 -0
- package/agents/marketing/analytics-analyst.md +25 -0
- package/agents/marketing/brand-architect.md +181 -0
- package/agents/marketing/cmo.md +76 -0
- package/agents/marketing/conversion-optimizer.md +19 -0
- package/agents/marketing/copywriter.md +20 -0
- package/agents/marketing/fact-checker.md +70 -0
- package/agents/marketing/growth-strategist.md +145 -0
- package/agents/marketing/paid-media-strategist.md +28 -0
- package/agents/marketing/pricing-strategist.md +17 -0
- package/agents/marketing/programmatic-seo-specialist.md +25 -0
- package/agents/marketing/retention-strategist.md +23 -0
- package/agents/marketing/seo-aeo-analyst.md +164 -0
- package/agents/operations/coo.md +50 -0
- package/agents/operations/ops-advisor.md +78 -0
- package/agents/operations/ops-provisioner.md +101 -0
- package/agents/operations/ops-research.md +52 -0
- package/agents/operations/service-automator.md +278 -0
- package/agents/product/business-validator.md +206 -0
- package/agents/product/competitive-intelligence.md +89 -0
- package/agents/product/cpo.md +63 -0
- package/agents/product/design/ux-design-lead.md +209 -0
- package/agents/product/spec-flow-analyzer.md +122 -0
- package/agents/sales/cro.md +44 -0
- package/agents/sales/deal-architect.md +29 -0
- package/agents/sales/outbound-strategist.md +29 -0
- package/agents/sales/pipeline-analyst.md +29 -0
- package/agents/support/cco.md +48 -0
- package/agents/support/community-manager.md +570 -0
- package/agents/support/ticket-triage.md +70 -0
- package/bin/soleur-opencode.js +29 -0
- package/commands/go.md +660 -0
- package/commands/help.md +273 -0
- package/commands/sync.md +977 -0
- package/commands/version.md +103 -0
- package/hooks/antigravity-hooks.json +84 -0
- package/hooks/antigravity-session-start.sh +15 -0
- package/hooks/bare-root-cwd-guard.sh +131 -0
- package/hooks/browser-snapshot-credential-guard.sh +200 -0
- package/hooks/codex-session-start.sh +9 -0
- package/hooks/compaction-state.sh +525 -0
- package/hooks/destructive-command-guard.sh +1387 -0
- package/hooks/devin-session-start.sh +90 -0
- package/hooks/ext-filter-detect.sh +171 -0
- package/hooks/hooks.json +133 -0
- package/hooks/lib/hook-tool-kind.sh +24 -0
- package/hooks/lib/log-rotation.sh +176 -0
- package/hooks/lib/shell-argv.pl +576 -0
- package/hooks/memory-backstop.sh +1114 -0
- package/hooks/operator-stage-approval.sh +391 -0
- package/hooks/stop-hook.sh +367 -0
- package/hooks/unkept-promise-hook.sh +383 -0
- package/hooks/version-banner.sh +127 -0
- package/hooks/welcome-hook.sh +50 -0
- package/install.js +29 -0
- package/lib/agent-registry.ts +154 -0
- package/lib/c4-canonical-cli.mjs +34 -0
- package/lib/c4-canonical.mjs +46 -0
- package/lib/c4-from-components.ts +554 -0
- package/lib/go-routing.ts +81 -0
- package/lib/grok-inspect-contract.ts +311 -0
- package/lib/harness-model-map.ts +101 -0
- package/lib/harness-parity.ts +726 -0
- package/lib/harness-tool-map.ts +180 -0
- package/lib/harness.ts +716 -0
- package/lib/heartbeat-live-reconcile.ts +1153 -0
- package/lib/heartbeat-manifest.ts +361 -0
- package/lib/kb-coverage.ts +252 -0
- package/lib/pr-merge-poll.ts +103 -0
- package/lib/workflow-fidelity.ts +388 -0
- package/opencode/INSTRUCTIONS.md +171 -0
- package/opencode/agents/engineering/cto.md +10 -0
- package/opencode/agents/engineering/design/ddd-architect.md +10 -0
- package/opencode/agents/engineering/discovery/agent-finder.md +10 -0
- package/opencode/agents/engineering/discovery/functional-discovery.md +10 -0
- package/opencode/agents/engineering/infra/infra-security.md +10 -0
- package/opencode/agents/engineering/infra/platform-strategist.md +10 -0
- package/opencode/agents/engineering/infra/terraform-architect.md +10 -0
- package/opencode/agents/engineering/prompt-engineer.md +10 -0
- package/opencode/agents/engineering/research/best-practices-researcher.md +10 -0
- package/opencode/agents/engineering/research/framework-docs-researcher.md +10 -0
- package/opencode/agents/engineering/research/git-history-analyzer.md +10 -0
- package/opencode/agents/engineering/research/learnings-researcher.md +10 -0
- package/opencode/agents/engineering/research/repo-research-analyst.md +10 -0
- package/opencode/agents/engineering/review/agent-native-reviewer.md +10 -0
- package/opencode/agents/engineering/review/architecture-strategist.md +10 -0
- package/opencode/agents/engineering/review/code-quality-analyst.md +10 -0
- package/opencode/agents/engineering/review/code-simplicity-reviewer.md +10 -0
- package/opencode/agents/engineering/review/data-integrity-guardian.md +10 -0
- package/opencode/agents/engineering/review/data-migration-expert.md +10 -0
- package/opencode/agents/engineering/review/deployment-verification-agent.md +10 -0
- package/opencode/agents/engineering/review/dhh-rails-reviewer.md +10 -0
- package/opencode/agents/engineering/review/kieran-rails-reviewer.md +10 -0
- package/opencode/agents/engineering/review/legacy-code-expert.md +10 -0
- package/opencode/agents/engineering/review/observability-coverage-reviewer.md +10 -0
- package/opencode/agents/engineering/review/pattern-recognition-specialist.md +10 -0
- package/opencode/agents/engineering/review/performance-oracle.md +10 -0
- package/opencode/agents/engineering/review/security-sentinel.md +10 -0
- package/opencode/agents/engineering/review/semgrep-sast.md +10 -0
- package/opencode/agents/engineering/review/test-design-reviewer.md +10 -0
- package/opencode/agents/engineering/review/user-impact-reviewer.md +10 -0
- package/opencode/agents/engineering/workflow/pr-comment-resolver.md +10 -0
- package/opencode/agents/finance/budget-analyst.md +10 -0
- package/opencode/agents/finance/cfo.md +10 -0
- package/opencode/agents/finance/financial-reporter.md +10 -0
- package/opencode/agents/finance/revenue-analyst.md +10 -0
- package/opencode/agents/legal/clo.md +10 -0
- package/opencode/agents/legal/legal-compliance-auditor.md +10 -0
- package/opencode/agents/legal/legal-document-generator.md +10 -0
- package/opencode/agents/marketing/analytics-analyst.md +10 -0
- package/opencode/agents/marketing/brand-architect.md +10 -0
- package/opencode/agents/marketing/cmo.md +10 -0
- package/opencode/agents/marketing/conversion-optimizer.md +10 -0
- package/opencode/agents/marketing/copywriter.md +10 -0
- package/opencode/agents/marketing/fact-checker.md +10 -0
- package/opencode/agents/marketing/growth-strategist.md +10 -0
- package/opencode/agents/marketing/paid-media-strategist.md +10 -0
- package/opencode/agents/marketing/pricing-strategist.md +10 -0
- package/opencode/agents/marketing/programmatic-seo-specialist.md +10 -0
- package/opencode/agents/marketing/retention-strategist.md +10 -0
- package/opencode/agents/marketing/seo-aeo-analyst.md +10 -0
- package/opencode/agents/operations/coo.md +10 -0
- package/opencode/agents/operations/ops-advisor.md +10 -0
- package/opencode/agents/operations/ops-provisioner.md +10 -0
- package/opencode/agents/operations/ops-research.md +10 -0
- package/opencode/agents/operations/service-automator.md +10 -0
- package/opencode/agents/product/business-validator.md +10 -0
- package/opencode/agents/product/competitive-intelligence.md +10 -0
- package/opencode/agents/product/cpo.md +10 -0
- package/opencode/agents/product/design/ux-design-lead.md +10 -0
- package/opencode/agents/product/spec-flow-analyzer.md +10 -0
- package/opencode/agents/sales/cro.md +10 -0
- package/opencode/agents/sales/deal-architect.md +10 -0
- package/opencode/agents/sales/outbound-strategist.md +10 -0
- package/opencode/agents/sales/pipeline-analyst.md +10 -0
- package/opencode/agents/support/cco.md +10 -0
- package/opencode/agents/support/community-manager.md +10 -0
- package/opencode/agents/support/ticket-triage.md +10 -0
- package/opencode/commands/go.md +10 -0
- package/opencode/commands/help.md +10 -0
- package/opencode/commands/sync.md +10 -0
- package/opencode/commands/version.md +11 -0
- package/opencode/opencode.json +8 -0
- package/opencode/plugin.ts +818 -0
- package/package.json +36 -4
- package/scripts/admin-merge-ready.sh +545 -0
- package/scripts/admin-merge-ready.test.sh +753 -0
- package/scripts/affected-tests.sh +908 -0
- package/scripts/affected-tests.test.sh +1797 -0
- package/scripts/alpha-metrics.sh +78 -0
- package/scripts/alpha-metrics.test.sh +115 -0
- package/scripts/ancestor-signal-helpers.baseline.txt +16 -0
- package/scripts/audit-flag-flip.sh +107 -0
- package/scripts/check-red-on-main.sh +316 -0
- package/scripts/ci-head-verdict.sh +411 -0
- package/scripts/cloud-detect.sh +193 -0
- package/scripts/delegation-verify.sh +657 -0
- package/scripts/delegation-verify.test.sh +1424 -0
- package/scripts/deploy-arm.sh +475 -0
- package/scripts/domain-model-drift.sh +419 -0
- package/scripts/emit-decision.sh +144 -0
- package/scripts/emit-decision.test.sh +144 -0
- package/scripts/ext-filter-divergence-corpus.txt +15 -0
- package/scripts/ext-filter-divergence.sh +189 -0
- package/scripts/generate-c4-from-components.ts +442 -0
- package/scripts/grok-fidelity-gate.sh +68 -0
- package/scripts/grok-pre-push-gate.sh +232 -0
- package/scripts/harness-discovery-smoke.ts +906 -0
- package/scripts/harness-parity-census.ts +92 -0
- package/scripts/inngest-luks-alert-armed-probe.ts +29 -0
- package/scripts/install-opencode-adapter.ts +509 -0
- package/scripts/lib/affected-tests/common.sh +365 -0
- package/scripts/lib/affected-tests/go.sh +157 -0
- package/scripts/lib/affected-tests/js.sh +188 -0
- package/scripts/lib/affected-tests/nx-turbo.sh +98 -0
- package/scripts/lib/affected-tests/python.sh +185 -0
- package/scripts/lib/context-sanitize.sh +49 -0
- package/scripts/lib/domain-model-lib.sh +180 -0
- package/scripts/lib/ext-filter-grammar.tsv +34 -0
- package/scripts/lib/operator-script.sh +1236 -0
- package/scripts/lib/proc.sh +580 -0
- package/scripts/lib/session-state.sh +748 -0
- package/scripts/lib/tmp-classify.sh +843 -0
- package/scripts/lib/wrapper-boundary.sh +47 -0
- package/scripts/lint-ratchet.py +1547 -0
- package/scripts/monitor-pr-checks.sh +441 -0
- package/scripts/pipeline-tally.sh +756 -0
- package/scripts/pipeline-tally.test.sh +740 -0
- package/scripts/plugin-version.sh +528 -0
- package/scripts/precommit-guard.sh +115 -0
- package/scripts/precommit-guard.test.sh +254 -0
- package/scripts/reconcile-live-heartbeats.ts +705 -0
- package/scripts/render-c4-model.sh +217 -0
- package/scripts/resolve-git-root.sh +55 -0
- package/scripts/resolve-regenerable-conflicts.sh +363 -0
- package/scripts/resolve-regenerable-conflicts.test.sh +780 -0
- package/scripts/ruff-requirements.txt +22 -0
- package/scripts/run-in-pid-namespace.sh +98 -0
- package/scripts/run-in-pid-namespace.test.sh +290 -0
- package/scripts/scan-ancestor-signal-helpers.py +556 -0
- package/scripts/scan-ancestor-signal-helpers.test.sh +670 -0
- package/scripts/setup-ralph-loop.sh +238 -0
- package/scripts/sync-cursor-name-map.ts +316 -0
- package/scripts/sync-grok-agent-compat.ts +188 -0
- package/scripts/sync-npm-package.ts +165 -0
- package/scripts/sync-opencode-adapter.ts +498 -0
- package/scripts/sync-pr-behind.sh +647 -0
- package/scripts/sync-pr-behind.test.sh +252 -0
- package/scripts/taste-profile-update.sh +180 -0
- package/scripts/taste-profile-update.test.sh +167 -0
- package/scripts/write-budget-marker.sh +23 -0
- package/scripts/write-kb-coverage.ts +113 -0
- package/skills/admin-ip-refresh/SKILL.md +81 -0
- package/skills/admin-ip-refresh/references/admin-ip-refresh-procedure.md +201 -0
- package/skills/agent-browser/SKILL.md +698 -0
- package/skills/agent-browser/scripts/playwright-mcp-profile-slot.sh +304 -0
- package/skills/agent-browser/scripts/playwright-mcp-redact-proxy.py +964 -0
- package/skills/agent-browser/scripts/redact-a11y-snapshot.py +443 -0
- package/skills/agent-browser/test/fixtures/capture-playwright-mcp-fixtures.py +217 -0
- package/skills/agent-browser/test/fixtures/fake-passthrough-proxy.py +24 -0
- package/skills/agent-browser/test/fixtures/fake-playwright-mcp.py +131 -0
- package/skills/agent-browser/test/fixtures/make-proxy-mutant.py +48 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/dialog-link-injection.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/empty-content.json +5 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-and-result.json +4 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-code-bool.json +6 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-code-string.json +6 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-message-tree.json +6 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-no-data.json +6 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-string.json +4 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-with-data.json +9 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/evaluate-escaped-object.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/invalid-utf8.json +4 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/link-result.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/list-two-results.json +6 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/meta-in-result.json +13 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/no-content.json +5 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/quoted-key.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/resource-block.json +18 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/result-and-method.json +11 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/result-string.json +3 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/run-code-escaped-one-row.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/run-code-escaped.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/structured-content.json +13 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/tools-list-no-tools.json +5 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/tree-no-credential.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/two-text-blocks.json +14 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/unparsable-line.json +3 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/value-contains-brace.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/value-contains-link-substring.json +10 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/close.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/evaluate.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/find.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/framing.json +6 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/initialize.json +14 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/navigate-link.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/navigate.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/roots-list-request.json +5 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/screenshot.json +17 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-filename.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-meta-json.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-meta-raw.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/tools-list.json +827 -0
- package/skills/agent-browser/test/fixtures/proxy-session-driver.py +138 -0
- package/skills/agent-browser/test/fixtures/session-facts.py +102 -0
- package/skills/agent-browser/test/playwright-mcp-lifetime.test.sh +1620 -0
- package/skills/agent-browser/test/playwright-mcp-redact-proxy.test.sh +1599 -0
- package/skills/agent-browser/test/redact-a11y-snapshot.test.sh +527 -0
- package/skills/agent-native-architecture/SKILL.md +479 -0
- package/skills/agent-native-architecture/references/action-parity-discipline.md +430 -0
- package/skills/agent-native-architecture/references/agent-execution-patterns.md +483 -0
- package/skills/agent-native-architecture/references/agent-native-testing.md +605 -0
- package/skills/agent-native-architecture/references/architecture-patterns.md +493 -0
- package/skills/agent-native-architecture/references/dynamic-context-injection.md +358 -0
- package/skills/agent-native-architecture/references/files-universal-interface.md +314 -0
- package/skills/agent-native-architecture/references/from-primitives-to-domain-tools.md +381 -0
- package/skills/agent-native-architecture/references/mcp-tool-design.md +524 -0
- package/skills/agent-native-architecture/references/mobile-patterns.md +897 -0
- package/skills/agent-native-architecture/references/product-implications.md +478 -0
- package/skills/agent-native-architecture/references/refactoring-to-prompt-native.md +342 -0
- package/skills/agent-native-architecture/references/self-modification.md +294 -0
- package/skills/agent-native-architecture/references/shared-workspace-architecture.md +700 -0
- package/skills/agent-native-architecture/references/system-prompt-design.md +263 -0
- package/skills/agent-native-audit/SKILL.md +300 -0
- package/skills/agent-native-audit/workflows/agent-native-audit.workflow.js +423 -0
- package/skills/andrew-kane-gem-writer/SKILL.md +189 -0
- package/skills/andrew-kane-gem-writer/references/database-adapters.md +231 -0
- package/skills/andrew-kane-gem-writer/references/module-organization.md +121 -0
- package/skills/andrew-kane-gem-writer/references/rails-integration.md +183 -0
- package/skills/andrew-kane-gem-writer/references/resources.md +126 -0
- package/skills/andrew-kane-gem-writer/references/testing-patterns.md +261 -0
- package/skills/architecture/SKILL.md +436 -0
- package/skills/architecture/references/adr-template.md +154 -0
- package/skills/architecture/references/likec4-reference.md +136 -0
- package/skills/architecture/references/nfr-reference.md +126 -0
- package/skills/archive-kb/SKILL.md +86 -0
- package/skills/archive-kb/scripts/archive-kb.sh +300 -0
- package/skills/archive-kb/test/archive-kb-partial-run.test.sh +276 -0
- package/skills/atdd-developer/SKILL.md +65 -0
- package/skills/brainstorm/SKILL.md +701 -0
- package/skills/brainstorm/references/brainstorm-brand-workshop.md +153 -0
- package/skills/brainstorm/references/brainstorm-domain-config.md +63 -0
- package/skills/brainstorm/references/brainstorm-validation-workshop.md +65 -0
- package/skills/brainstorm/references/ui-surface-terms.md +42 -0
- package/skills/brainstorm-techniques/SKILL.md +231 -0
- package/skills/brainstorm-techniques/references/decision-principles.md +150 -0
- package/skills/brainstorm-techniques/references/phase-boundaries.md +103 -0
- package/skills/brainstorm-techniques/references/standing-delegation.md +407 -0
- package/skills/brainstorm-techniques/references/technical-ruling-delegation.md +174 -0
- package/skills/campaign-calendar/SKILL.md +145 -0
- package/skills/cf-token-scope/SKILL.md +164 -0
- package/skills/cf-token-scope/references/widen-playbook.md +114 -0
- package/skills/cf-token-scope/scripts/cf-token-scope.sh +253 -0
- package/skills/changelog/SKILL.md +142 -0
- package/skills/code-to-prd/SKILL.md +66 -0
- package/skills/code-to-prd/references/banner-template.md +12 -0
- package/skills/code-to-prd/references/prd-template.md +41 -0
- package/skills/code-to-prd/scripts/code-to-prd.sh +634 -0
- package/skills/code-to-prd/test/code-to-prd.test.sh +447 -0
- package/skills/code-to-prd/test/fixture/.env.example +7 -0
- package/skills/code-to-prd/test/fixture/app/about/page.tsx +11 -0
- package/skills/code-to-prd/test/fixture/app/api/health/route.ts +10 -0
- package/skills/code-to-prd/test/fixture/app/page.tsx +14 -0
- package/skills/code-to-prd/test/fixture/next.config.mjs +3 -0
- package/skills/code-to-prd/test/fixture/package.json +6 -0
- package/skills/cohort-status/SKILL.md +62 -0
- package/skills/community/SKILL.md +210 -0
- package/skills/community/scripts/bsky-community.sh +644 -0
- package/skills/community/scripts/bsky-setup.sh +631 -0
- package/skills/community/scripts/community-router.sh +93 -0
- package/skills/community/scripts/discord-community.sh +487 -0
- package/skills/community/scripts/discord-setup.sh +784 -0
- package/skills/community/scripts/github-community.sh +706 -0
- package/skills/community/scripts/hn-community.sh +233 -0
- package/skills/community/scripts/lib/hmac-sha1-b64.sh +126 -0
- package/skills/community/scripts/linkedin-community.sh +750 -0
- package/skills/community/scripts/linkedin-setup.sh +758 -0
- package/skills/community/scripts/x-community.sh +890 -0
- package/skills/community/scripts/x-setup.sh +713 -0
- package/skills/community/test/community-argv.test.sh +2922 -0
- package/skills/community/test/github-community.test.sh +981 -0
- package/skills/competitive-analysis/SKILL.md +69 -0
- package/skills/competitive-analysis/references/peer-plugin-audit.md +218 -0
- package/skills/compound/SKILL.md +673 -0
- package/skills/compound/scripts/token-efficiency-report.sh +334 -0
- package/skills/compound/test/phase-16.test.sh +379 -0
- package/skills/compound-capture/SKILL.md +791 -0
- package/skills/compound-capture/assets/critical-pattern-template.md +37 -0
- package/skills/compound-capture/assets/resolution-template.md +116 -0
- package/skills/compound-capture/references/yaml-schema.md +67 -0
- package/skills/compound-capture/schema.yaml +181 -0
- package/skills/constraint-scaffold/SKILL.md +175 -0
- package/skills/constraint-scaffold/references/boundary-readme.template +35 -0
- package/skills/constraint-scaffold/references/constraint-gates-workflow.template +69 -0
- package/skills/constraint-scaffold/references/depcruise-config.template +230 -0
- package/skills/constraint-scaffold/references/fix-constraints-stage-a.template +240 -0
- package/skills/constraint-scaffold/references/fix-constraints-stage-b.template +313 -0
- package/skills/constraint-scaffold/references/shared-runner.template +135 -0
- package/skills/constraint-scaffold/scripts/constraint-scaffold.sh +589 -0
- package/skills/constraint-scaffold/test/bite-proof.test.sh +1338 -0
- package/skills/constraint-scaffold/test/boundary.test.sh +734 -0
- package/skills/constraint-scaffold/test/emit-fix-constraints.test.sh +193 -0
- package/skills/constraint-scaffold/test/generator.test.sh +93 -0
- package/skills/constraint-scaffold/test/parity.test.sh +285 -0
- package/skills/content-writer/SKILL.md +242 -0
- package/skills/content-writer/scripts/blog-jargon-scan.sh +42 -0
- package/skills/cron-delete/SKILL.md +56 -0
- package/skills/cron-list/SKILL.md +54 -0
- package/skills/deepen-plan/SKILL.md +937 -0
- package/skills/deepen-plan/workflows/deepen-plan.workflow.js +488 -0
- package/skills/deploy/SKILL.md +115 -0
- package/skills/deploy/references/hetzner-setup.md +67 -0
- package/skills/deploy/scripts/deploy.sh +37 -0
- package/skills/deploy-docs/SKILL.md +95 -0
- package/skills/dhh-rails-style/SKILL.md +203 -0
- package/skills/dhh-rails-style/references/architecture.md +717 -0
- package/skills/dhh-rails-style/references/controllers.md +323 -0
- package/skills/dhh-rails-style/references/frontend.md +556 -0
- package/skills/dhh-rails-style/references/gems.md +316 -0
- package/skills/dhh-rails-style/references/models.md +388 -0
- package/skills/dhh-rails-style/references/testing.md +354 -0
- package/skills/discord-content/SKILL.md +126 -0
- package/skills/docs-site/SKILL.md +214 -0
- package/skills/drain-labeled-backlog/SKILL.md +203 -0
- package/skills/drain-labeled-backlog/scripts/group-by-area.sh +208 -0
- package/skills/drain-labeled-backlog/workflows/drain-labeled-backlog.workflow.js +433 -0
- package/skills/drain-prs/SKILL.md +143 -0
- package/skills/drain-prs/scripts/triage-prs.sh +192 -0
- package/skills/drain-prs/test/argv-ceiling.test.sh +183 -0
- package/skills/dspy-ruby/SKILL.md +634 -0
- package/skills/dspy-ruby/assets/config-template.rb +359 -0
- package/skills/dspy-ruby/assets/module-template.rb +326 -0
- package/skills/dspy-ruby/assets/signature-template.rb +143 -0
- package/skills/dspy-ruby/references/core-concepts.md +268 -0
- package/skills/dspy-ruby/references/optimization.md +623 -0
- package/skills/dspy-ruby/references/providers.md +335 -0
- package/skills/eval-harness/README.md +194 -0
- package/skills/eval-harness/SKILL.md +157 -0
- package/skills/eval-harness/enums/go-routes.json +13 -0
- package/skills/eval-harness/enums/incident-threshold.json +5 -0
- package/skills/eval-harness/enums/lane.json +5 -0
- package/skills/eval-harness/enums/tool-selection.json +20 -0
- package/skills/eval-harness/enums/triage-levels.json +5 -0
- package/skills/eval-harness/gated-skills.json +34 -0
- package/skills/eval-harness/models.generated.json +5 -0
- package/skills/eval-harness/promptfooconfig-go-routing.yaml +35 -0
- package/skills/eval-harness/promptfooconfig-incident-threshold.yaml +32 -0
- package/skills/eval-harness/promptfooconfig-lane-inference.yaml +33 -0
- package/skills/eval-harness/promptfooconfig-ticket-triage.yaml +34 -0
- package/skills/eval-harness/promptfooconfig-tool-selection.yaml +33 -0
- package/skills/eval-harness/prompts/go-baseline.txt +6 -0
- package/skills/eval-harness/prompts/go-skill.txt +21 -0
- package/skills/eval-harness/prompts/incident-threshold-baseline.txt +6 -0
- package/skills/eval-harness/prompts/incident-threshold-skill.txt +12 -0
- package/skills/eval-harness/prompts/lane-inference-baseline.txt +6 -0
- package/skills/eval-harness/prompts/lane-inference-skill.txt +15 -0
- package/skills/eval-harness/prompts/tool-selection-baseline.txt +19 -0
- package/skills/eval-harness/prompts/tool-selection-skill.txt +22 -0
- package/skills/eval-harness/prompts/triage-baseline.txt +7 -0
- package/skills/eval-harness/prompts/triage-skill.txt +10 -0
- package/skills/eval-harness/scripts/eval-gate.cjs +318 -0
- package/skills/eval-harness/scripts/extract-block.cjs +87 -0
- package/skills/eval-harness/scripts/gate-classification.cjs +28 -0
- package/skills/eval-harness/scripts/gen-models.sh +41 -0
- package/skills/eval-harness/scripts/gen-skill-prompt.cjs +177 -0
- package/skills/eval-harness/scripts/measure-classification.cjs +30 -0
- package/skills/eval-harness/scripts/parse-label.cjs +93 -0
- package/skills/eval-harness/scripts/verdict.cjs +132 -0
- package/skills/eval-harness/tasks/go-routing.jsonl +8 -0
- package/skills/eval-harness/tasks/incident-threshold.jsonl +7 -0
- package/skills/eval-harness/tasks/lane-inference.jsonl +7 -0
- package/skills/eval-harness/tasks/ticket-triage.jsonl +6 -0
- package/skills/eval-harness/tasks/tool-selection.jsonl +15 -0
- package/skills/eval-harness/test/eval-gate.test.sh +132 -0
- package/skills/eval-harness/test/extract-block.test.sh +87 -0
- package/skills/eval-harness/test/gate-classification.test.sh +77 -0
- package/skills/eval-harness/test/gen-models.test.sh +75 -0
- package/skills/eval-harness/test/measure-classification.test.sh +74 -0
- package/skills/eval-harness/test/parse-label.test.sh +129 -0
- package/skills/eval-harness/test/registry-completeness.test.sh +109 -0
- package/skills/eval-harness/test/verdict.test.sh +123 -0
- package/skills/every-style-editor/SKILL.md +142 -0
- package/skills/every-style-editor/references/EVERY_WRITE_STYLE.md +529 -0
- package/skills/feature-tweet/SKILL.md +199 -0
- package/skills/feature-video/SKILL.md +412 -0
- package/skills/feature-video/scripts/check_deps.sh +281 -0
- package/skills/file-todos/SKILL.md +267 -0
- package/skills/file-todos/assets/todo-template.md +166 -0
- package/skills/fix-issue/SKILL.md +278 -0
- package/skills/fix-issue/references/agent-authored-exclusion.md +120 -0
- package/skills/fix-issue/references/exclude-label-jq-snippet.md +97 -0
- package/skills/flag-bootstrap/SETUP.md +142 -0
- package/skills/flag-create/SKILL.md +145 -0
- package/skills/flag-create/scripts/create.sh +273 -0
- package/skills/flag-delete/SKILL.md +167 -0
- package/skills/flag-delete/scripts/delete.sh +288 -0
- package/skills/flag-list/SKILL.md +99 -0
- package/skills/flag-list/scripts/list.sh +275 -0
- package/skills/flag-set-role/SKILL.md +229 -0
- package/skills/flag-set-role/scripts/flip.sh +774 -0
- package/skills/frontend-anti-slop/SKILL.md +106 -0
- package/skills/frontend-anti-slop/references/anti-patterns.md +199 -0
- package/skills/frontend-anti-slop/references/slop-rules.md +65 -0
- package/skills/frontend-anti-slop/scripts/tier1-scan.ts +525 -0
- package/skills/frontend-design/SKILL.md +77 -0
- package/skills/gdpr-gate/NOTICE +136 -0
- package/skills/gdpr-gate/SKILL.md +341 -0
- package/skills/gdpr-gate/references/fields.md +160 -0
- package/skills/gdpr-gate/references/layers/api-layer.md +355 -0
- package/skills/gdpr-gate/references/layers/auth-sessions.md +290 -0
- package/skills/gdpr-gate/references/layers/data-in-transit.md +331 -0
- package/skills/gdpr-gate/references/layers/data-lifecycle.md +341 -0
- package/skills/gdpr-gate/references/layers/frontend.md +249 -0
- package/skills/gdpr-gate/references/layers/testing-seeding.md +256 -0
- package/skills/gdpr-gate/references/leakage-vectors.md +248 -0
- package/skills/gdpr-gate/references/legacy/legal-consent-v1-prose.md +48 -0
- package/skills/gdpr-gate/references/legal-consent.md +140 -0
- package/skills/gdpr-gate/references/non-negotiables.md +60 -0
- package/skills/gdpr-gate/scripts/gdpr-gate.sh +271 -0
- package/skills/gdpr-gate/scripts/notice-frontmatter.sh +337 -0
- package/skills/gdpr-gate/scripts/path-denylist.txt +39 -0
- package/skills/gdpr-gate/scripts/repo-scan.sh +158 -0
- package/skills/gdpr-gate/scripts/vendor-drift-classify.sh +120 -0
- package/skills/gdpr-gate/scripts/vendor-pin-integrity.sh +237 -0
- package/skills/gemini-imagegen/SKILL.md +283 -0
- package/skills/gemini-imagegen/requirements.txt +5 -0
- package/skills/gemini-imagegen/scripts/_error_handling.py +146 -0
- package/skills/gemini-imagegen/scripts/compose_images.py +150 -0
- package/skills/gemini-imagegen/scripts/edit_image.py +137 -0
- package/skills/gemini-imagegen/scripts/gemini_images.py +256 -0
- package/skills/gemini-imagegen/scripts/generate_image.py +147 -0
- package/skills/gemini-imagegen/scripts/multi_turn_chat.py +223 -0
- package/skills/gemini-imagegen/scripts/test_error_handling.py +179 -0
- package/skills/git-worktree/SKILL.md +534 -0
- package/skills/git-worktree/scripts/git-repo-readiness-diag.sh +98 -0
- package/skills/git-worktree/scripts/worktree-manager.sh +4773 -0
- package/skills/git-worktree/test/create-from-origin-main.test.sh +116 -0
- package/skills/git-worktree/test/lease-protects-active.test.sh +1056 -0
- package/skills/git-worktree/test/no-repo-fail-loud.test.sh +88 -0
- package/skills/git-worktree/test/orphan-reaper-honest-count.test.sh +379 -0
- package/skills/git-worktree/test/reap-archive-persistence.test.sh +725 -0
- package/skills/git-worktree/test/stale-lock-sweep.test.sh +176 -0
- package/skills/go/SKILL.md +54 -0
- package/skills/growth/SKILL.md +164 -0
- package/skills/harvest-debt/SKILL.md +78 -0
- package/skills/harvest-debt/scripts/harvest-debt.sh +114 -0
- package/skills/heal-skill/SKILL.md +173 -0
- package/skills/help/SKILL.md +46 -0
- package/skills/incident/NOTICE +39 -0
- package/skills/incident/SKILL.md +395 -0
- package/skills/incident/scripts/dry-run.sh +448 -0
- package/skills/incident/scripts/redact-engine.py +412 -0
- package/skills/incident/scripts/redact-sentinel.sh +44 -0
- package/skills/incident/templates/pir.md +139 -0
- package/skills/incident/test/fixtures/dry-run-incident.json +26 -0
- package/skills/incident/test/fixtures/dry-run-secret-leak.json +26 -0
- package/skills/incident/test/fixtures/legacy-bash-scanner.sh +82 -0
- package/skills/incident/test/fixtures/positive-corpus.md +87 -0
- package/skills/incident/test/redact-sentinel.test.sh +1420 -0
- package/skills/invoice/SKILL.md +206 -0
- package/skills/kb-glossary/SKILL.md +71 -0
- package/skills/kb-glossary/references/glossary-format.md +111 -0
- package/skills/kb-glossary/references/rejected-request-register.md +115 -0
- package/skills/kb-search/SKILL.md +276 -0
- package/skills/kb-search/scripts/kb-search-cache.sh +123 -0
- package/skills/legal-audit/SKILL.md +161 -0
- package/skills/legal-generate/NOTICE +242 -0
- package/skills/legal-generate/SKILL.md +244 -0
- package/skills/legal-generate/references/templates/advisor-agreement/template.md +12 -0
- package/skills/legal-generate/references/templates/business-associate-agreement/template.md +112 -0
- package/skills/legal-generate/references/templates/cookie-notice/template.md +82 -0
- package/skills/legal-generate/references/templates/dpa-global/template.md +439 -0
- package/skills/legal-generate/references/templates/dpa-us/template.md +222 -0
- package/skills/legal-generate/references/templates/employee-offer-letter/template.md +75 -0
- package/skills/legal-generate/references/templates/master-services-agreement/template.md +252 -0
- package/skills/legal-generate/references/templates/mutual-nda/template.md +66 -0
- package/skills/legal-generate/references/templates/one-way-nda/template.md +38 -0
- package/skills/legal-generate/references/templates/privacy-policy-gdpr/template.md +427 -0
- package/skills/legal-generate/references/templates/privacy-policy-us/template.md +313 -0
- package/skills/legal-generate/references/templates/terms-of-use/template.md +75 -0
- package/skills/legal-generate/scripts/strip-vendor-credit.sh +96 -0
- package/skills/linear-fetch/SKILL.md +247 -0
- package/skills/linear-fetch/scripts/assert-no-linear-telemetry.sh +66 -0
- package/skills/linear-fetch/scripts/redact-linear-urls.sh +51 -0
- package/skills/linear-fetch/scripts/render-caller-template.sh +36 -0
- package/skills/linear-fetch/test/assert-no-linear-telemetry.test.sh +113 -0
- package/skills/linear-fetch/test/parity.test.sh +112 -0
- package/skills/linear-fetch/test/persist-safe-integration.test.sh +191 -0
- package/skills/linear-fetch/test/redact-linear-urls.test.sh +226 -0
- package/skills/merge-pr/SKILL.md +670 -0
- package/skills/model-launch-review/SKILL.md +255 -0
- package/skills/model-launch-review/scripts/audit-models.sh +501 -0
- package/skills/one-shot/SKILL.md +316 -0
- package/skills/one-shot/references/token-discipline.md +74 -0
- package/skills/operator-bootstrap/SKILL.md +412 -0
- package/skills/operator-bootstrap/template.sh +439 -0
- package/skills/operator-digest/SKILL.md +287 -0
- package/skills/operator-digest/assets/operator-digest.workflow.yml +175 -0
- package/skills/operator-digest/scripts/digest-scrub.sh +119 -0
- package/skills/operator-digest/scripts/provision-operator-digest-repo.sh +147 -0
- package/skills/operator-rephrase/SKILL.md +98 -0
- package/skills/pencil-setup/SKILL.md +201 -0
- package/skills/pencil-setup/scripts/check_deps.sh +556 -0
- package/skills/pencil-setup/scripts/copy_adapter.sh +80 -0
- package/skills/pencil-setup/scripts/package-lock.json +1172 -0
- package/skills/pencil-setup/scripts/package.json +14 -0
- package/skills/pencil-setup/scripts/pencil-error-enrichment.mjs +21 -0
- package/skills/pencil-setup/scripts/pencil-mcp-adapter.mjs +733 -0
- package/skills/pencil-setup/scripts/pencil-response-classify.mjs +38 -0
- package/skills/pencil-setup/scripts/pencil-save-gate.mjs +26 -0
- package/skills/pencil-setup/scripts/sanitize-filename.mjs +25 -0
- package/skills/plan/SKILL.md +1140 -0
- package/skills/plan/references/differential-migration-testing.md +185 -0
- package/skills/plan/references/plan-community-discovery.md +42 -0
- package/skills/plan/references/plan-founder-check.md +137 -0
- package/skills/plan/references/plan-functional-overlap.md +25 -0
- package/skills/plan/references/plan-host-fanout.md +51 -0
- package/skills/plan/references/plan-issue-templates.md +779 -0
- package/skills/plan/references/plan-network-outage-checklist.md +131 -0
- package/skills/plan/references/plan-scope-check.md +126 -0
- package/skills/plan/references/plan-sharp-edges.md +331 -0
- package/skills/plan/references/plan-signoff-lifecycle.md +12 -0
- package/skills/plan/references/webapp-cwv-observability.md +143 -0
- package/skills/plan-review/SKILL.md +91 -0
- package/skills/plan-review/lib/named-panel.mjs +58 -0
- package/skills/plan-review/workflows/plan-review.workflow.js +477 -0
- package/skills/postmerge/SKILL.md +642 -0
- package/skills/postmerge/references/deploy-status-debugging.md +141 -0
- package/skills/postmerge/references/graceful-degradation.md +20 -0
- package/skills/postmerge/references/sentry-error-count-delta.md +137 -0
- package/skills/preflight/SKILL.md +1553 -0
- package/skills/preflight/references/check-13-founder-check.md +286 -0
- package/skills/preflight/scripts/founder-check.py +1640 -0
- package/skills/preflight/scripts/parse-form-a.awk +107 -0
- package/skills/preflight/scripts/probe-verb-gate.sh +108 -0
- package/skills/product-roadmap/SKILL.md +357 -0
- package/skills/product-roadmap/scripts/roadmap-reconcile.sh +327 -0
- package/skills/provision-cloudflare/SKILL.md +67 -0
- package/skills/provision-cloudflare/scripts/provision-cloudflare.sh +271 -0
- package/skills/provision-doppler/SKILL.md +58 -0
- package/skills/provision-doppler/scripts/provision-doppler.sh +358 -0
- package/skills/provision-github/SKILL.md +59 -0
- package/skills/provision-github/scripts/provision-github.sh +300 -0
- package/skills/provision-hetzner/SKILL.md +76 -0
- package/skills/provision-hetzner/scripts/provision-hetzner.sh +262 -0
- package/skills/provision-hetzner/test/fixture/knowledge-base/legal/tenant-dpa-register.md +26 -0
- package/skills/provision-hetzner/test/fixture/provision-hetzner-dry-run.golden +34 -0
- package/skills/provision-hetzner/test/provision-hetzner-characterization.test.sh +227 -0
- package/skills/qa/SKILL.md +263 -0
- package/skills/qa/references/browser-preflight.md +22 -0
- package/skills/questionnaire-generate/SKILL.md +328 -0
- package/skills/questionnaire-generate/references/questionnaire.template +60 -0
- package/skills/rclone/SKILL.md +162 -0
- package/skills/rclone/scripts/check_setup.sh +63 -0
- package/skills/release-announce/SKILL.md +65 -0
- package/skills/release-docs/SKILL.md +92 -0
- package/skills/reproduce-bug/SKILL.md +358 -0
- package/skills/resolve-debt/SKILL.md +85 -0
- package/skills/resolve-debt/scripts/resolve-debt.py +584 -0
- package/skills/resolve-parallel/SKILL.md +45 -0
- package/skills/resolve-parallel/workflows/resolve-parallel.workflow.js +415 -0
- package/skills/resolve-pr-parallel/SKILL.md +68 -0
- package/skills/resolve-pr-parallel/workflows/resolve-pr-parallel.workflow.js +419 -0
- package/skills/resolve-todo-parallel/SKILL.md +57 -0
- package/skills/resolve-todo-parallel/workflows/resolve-todo-parallel.workflow.js +398 -0
- package/skills/review/SKILL.md +1620 -0
- package/skills/review/references/anti-slop-scanner-hook.md +40 -0
- package/skills/review/references/defect-classes-migrations.md +9 -0
- package/skills/review/references/generated-input-oracle-surfaces.md +15 -0
- package/skills/review/references/review-e2e-testing.md +116 -0
- package/skills/review/references/review-evidence-comment.md +36 -0
- package/skills/review/references/review-todo-structure.md +165 -0
- package/skills/review/references/risk-tier-and-fix-rounds.md +97 -0
- package/skills/review/references/semgrep-custom-rules.yaml +174 -0
- package/skills/review/references/ultrathink-phases.md +98 -0
- package/skills/review/references/wfs.md +6 -0
- package/skills/review/scripts/emit-review-trailer.sh +499 -0
- package/skills/review/scripts/ensure-semgrep.sh +99 -0
- package/skills/review/scripts/fix-round-seats.sh +197 -0
- package/skills/review/test/emit-review-trailer.test.sh +387 -0
- package/skills/review/test/ensure-semgrep.test.sh +111 -0
- package/skills/review/test/fix-round-seats.test.sh +237 -0
- package/skills/review/workflows/README.md +147 -0
- package/skills/review/workflows/review.workflow.js +667 -0
- package/skills/schedule/SKILL.md +796 -0
- package/skills/seo-aeo/SKILL.md +139 -0
- package/skills/seo-aeo/scripts/validate-csp.sh +233 -0
- package/skills/seo-aeo/scripts/validate-seo.sh +262 -0
- package/skills/ship/SKILL.md +3023 -0
- package/skills/ship/references/adr-ordinal-collision.md +47 -0
- package/skills/ship/references/affected-gate-contract.md +115 -0
- package/skills/ship/references/ci-run-diagnosis.md +29 -0
- package/skills/ship/references/ci-workflow-authoring.md +68 -0
- package/skills/ship/references/cleanup-merged-archival.md +39 -0
- package/skills/ship/references/counsel-review-clo-attestation.md +38 -0
- package/skills/ship/references/cto-ruling-armed-push.md +5 -0
- package/skills/ship/references/decision-challenges-render.md +71 -0
- package/skills/ship/references/delegated-act-authoring-form.md +7 -0
- package/skills/ship/references/detached-battery-verdict.md +13 -0
- package/skills/ship/references/epic-close-sweep.md +62 -0
- package/skills/ship/references/feature-tweet-draft.md +42 -0
- package/skills/ship/references/followthrough-issue-body-template.md +47 -0
- package/skills/ship/references/followthrough-stub-template.sh +92 -0
- package/skills/ship/references/merge-queue-dequeue.md +39 -0
- package/skills/ship/references/operator-holds.md +179 -0
- package/skills/ship/references/pipeline-tally-render.md +43 -0
- package/skills/ship/references/pr-overlap.md +153 -0
- package/skills/ship/references/queue-mode.md +7 -0
- package/skills/ship/references/ready-run-wait.md +36 -0
- package/skills/ship/references/red-on-main-quarantine.md +110 -0
- package/skills/ship/references/register-update-pr-pattern.md +88 -0
- package/skills/ship/references/settle-then-admin-merge.md +130 -0
- package/skills/ship/references/supabase-column-probe.md +13 -0
- package/skills/ship/scripts/auto-close-scan.sh +76 -0
- package/skills/ship/scripts/battery-owed.sh +403 -0
- package/skills/ship/scripts/hold-gate-eval.sh +410 -0
- package/skills/ship/scripts/net-issue-flow.sh +665 -0
- package/skills/ship/scripts/pr-overlap-preflight.sh +483 -0
- package/skills/ship/scripts/ship-pir-action-items-gate.sh +246 -0
- package/skills/ship/scripts/technical-ruling-gate.sh +444 -0
- package/skills/skill-creator/SKILL.md +299 -0
- package/skills/skill-creator/references/api-security.md +248 -0
- package/skills/skill-creator/references/authoring-levers.md +110 -0
- package/skills/skill-creator/references/be-clear-and-direct.md +587 -0
- package/skills/skill-creator/references/best-practices.md +431 -0
- package/skills/skill-creator/references/common-patterns.md +639 -0
- package/skills/skill-creator/references/core-principles.md +193 -0
- package/skills/skill-creator/references/executable-code.md +188 -0
- package/skills/skill-creator/references/iteration-and-testing.md +509 -0
- package/skills/skill-creator/references/official-spec.md +193 -0
- package/skills/skill-creator/references/recommended-structure.md +180 -0
- package/skills/skill-creator/references/skill-structure.md +396 -0
- package/skills/skill-creator/references/using-scripts.md +122 -0
- package/skills/skill-creator/references/using-templates.md +120 -0
- package/skills/skill-creator/references/workflows-and-validation.md +541 -0
- package/skills/skill-creator/scripts/init_skill.py +322 -0
- package/skills/skill-creator/scripts/package_skill.py +110 -0
- package/skills/skill-creator/scripts/quick_validate.py +64 -0
- package/skills/skill-creator/templates/router-skill.md +73 -0
- package/skills/skill-creator/templates/simple-skill.md +34 -0
- package/skills/skill-creator/workflows/add-reference.md +96 -0
- package/skills/skill-creator/workflows/add-script.md +104 -0
- package/skills/skill-creator/workflows/add-template.md +83 -0
- package/skills/skill-creator/workflows/add-workflow.md +128 -0
- package/skills/skill-creator/workflows/audit-skill.md +159 -0
- package/skills/skill-creator/workflows/create-domain-expertise-skill.md +623 -0
- package/skills/skill-creator/workflows/create-new-skill.md +207 -0
- package/skills/skill-creator/workflows/get-guidance.md +134 -0
- package/skills/skill-creator/workflows/upgrade-to-router.md +170 -0
- package/skills/skill-creator/workflows/verify-skill.md +226 -0
- package/skills/skill-security-scan/SKILL.md +145 -0
- package/skills/skill-security-scan/references/disclaimer.md +21 -0
- package/skills/skill-security-scan/references/first-party-allowlist.yaml +38 -0
- package/skills/skill-security-scan/references/override-artifact-schema.json +68 -0
- package/skills/skill-security-scan/references/override-mechanism.md +171 -0
- package/skills/skill-security-scan/references/redirect-domains.yaml +27 -0
- package/skills/skill-security-scan/references/regex-patterns.md +136 -0
- package/skills/skill-security-scan/references/rules/code-exec.yaml +70 -0
- package/skills/skill-security-scan/references/rules/filesystem-boundary.yaml +29 -0
- package/skills/skill-security-scan/references/rules/manifest.yaml +44 -0
- package/skills/skill-security-scan/references/rules/supply-chain.yaml +36 -0
- package/skills/skill-security-scan/references/scan-meta-schema.json +63 -0
- package/skills/skill-security-scan/references/test-fixtures/clean-soleur-style.skill.md +23 -0
- package/skills/skill-security-scan/references/test-fixtures/clean-third-party.skill.md +26 -0
- package/skills/skill-security-scan/references/test-fixtures/malicious-codeexec.skill.md +23 -0
- package/skills/skill-security-scan/references/test-fixtures/malicious-curl-pipe-bash.skill.md +69 -0
- package/skills/skill-security-scan/references/test-fixtures/malicious-prompt-injection.skill.md +17 -0
- package/skills/skill-security-scan/references/test-fixtures/malicious-telemetry-beacon.skill.md +23 -0
- package/skills/skill-security-scan/references/typosquat-targets.yaml +65 -0
- package/skills/skill-security-scan/scripts/check-codeexec.sh +41 -0
- package/skills/skill-security-scan/scripts/check-filesystem-boundary.sh +19 -0
- package/skills/skill-security-scan/scripts/check-prompt-injection.sh +95 -0
- package/skills/skill-security-scan/scripts/check-supply-chain.sh +219 -0
- package/skills/skill-security-scan/scripts/check-telemetry-surface.sh +126 -0
- package/skills/skill-security-scan/scripts/lib.sh +197 -0
- package/skills/skill-security-scan/scripts/parse-override.sh +231 -0
- package/skills/skill-security-scan/scripts/run-scan.sh +342 -0
- package/skills/skill-security-scan/scripts/run-self-test.sh +95 -0
- package/skills/skill-security-scan/test/argv-ceiling.test.sh +180 -0
- package/skills/skill-security-scan/test/meta-dir-reap.test.sh +411 -0
- package/skills/skill-security-scan/test/parse-override.test.sh +607 -0
- package/skills/skill-security-scan/test/redact-pii-ghs-shape.test.sh +251 -0
- package/skills/social-distribute/SKILL.md +485 -0
- package/skills/spec-templates/SKILL.md +274 -0
- package/skills/sync/SKILL.md +49 -0
- package/skills/test-browser/SKILL.md +464 -0
- package/skills/test-browser/references/feature-map.md +161 -0
- package/skills/test-fix-loop/SKILL.md +159 -0
- package/skills/test-fix-loop/references/determinism-checklist.md +154 -0
- package/skills/triage/SKILL.md +402 -0
- package/skills/trigger-cron/SKILL.md +136 -0
- package/skills/trigger-cron/scripts/trigger.sh +198 -0
- package/skills/user-set-role/SKILL.md +147 -0
- package/skills/user-set-role/scripts/set-role.sh +218 -0
- package/skills/user-story-writer/SKILL.md +77 -0
- package/skills/ux-audit/SKILL.md +251 -0
- package/skills/ux-audit/references/finding.schema.json +62 -0
- package/skills/ux-audit/references/route-list.yaml +95 -0
- package/skills/ux-audit/scripts/bot-fixture.ts +270 -0
- package/skills/ux-audit/scripts/bot-signin.ts +135 -0
- package/skills/ux-audit/scripts/dedup-hash.ts +46 -0
- package/skills/verification-scaffold/SKILL.md +73 -0
- package/skills/verification-scaffold/references/gitleaks.toml.template +13 -0
- package/skills/verification-scaffold/references/guardrail-audit.md +156 -0
- package/skills/verification-scaffold/references/guards/block-stash-hook.sh.template +72 -0
- package/skills/verification-scaffold/references/guards/commit-on-main-hook.sh.template +82 -0
- package/skills/verification-scaffold/references/guards/orphan-test-check.sh.template +102 -0
- package/skills/verification-scaffold/references/guards/precommit-guard.sh.template +115 -0
- package/skills/verification-scaffold/references/guards/secret-scan-hook.sh.template +176 -0
- package/skills/verification-scaffold/references/guards/verification-gates.yml.template +49 -0
- package/skills/verification-scaffold/references/guards/wrapper-boundary.sh.template +47 -0
- package/skills/verification-scaffold/references/guards.tsv +21 -0
- package/skills/verification-scaffold/references/verification-readme.template +55 -0
- package/skills/verification-scaffold/scripts/verification-scaffold.sh +785 -0
- package/skills/verification-scaffold/test/bite-proof.test.sh +159 -0
- package/skills/verification-scaffold/test/generator.test.sh +319 -0
- package/skills/verification-scaffold/test/manifest.test.sh +144 -0
- package/skills/work/SKILL.md +1480 -0
- package/skills/work/references/end-of-work-resume.md +7 -0
- package/skills/work/references/hook-test-main-cwd.md +24 -0
- package/skills/work/references/key-principles.md +83 -0
- package/skills/work/references/property-testing.md +122 -0
- package/skills/work/references/sql-security-definer-rpc-scaffold.sql +78 -0
- package/skills/work/references/work-agent-teams.md +97 -0
- package/skills/work/references/work-lifecycle-parallel.md +147 -0
- package/skills/work/references/work-lockfile-bumps.md +97 -0
- package/skills/work/references/work-scratch-sandboxes.md +212 -0
- package/skills/work/references/work-subagent-fanout.md +61 -0
- package/skills/work/references/work-test-harness-sharp-edges.md +73 -0
- package/skills/xcode-test/SKILL.md +360 -0
|
@@ -0,0 +1,1797 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# affected-tests.test.sh — the contract suite for the plugin-shipped stack-detecting
|
|
3
|
+
# affected test gate (#9307, deliverable 2).
|
|
4
|
+
#
|
|
5
|
+
# SUT: plugins/soleur/scripts/affected-tests.sh — invoked as
|
|
6
|
+
# bash affected-tests.sh [--full] [--base <ref>] [--explain] [--self-check]
|
|
7
|
+
# from the ROOT of a synthesized fixture repository.
|
|
8
|
+
#
|
|
9
|
+
# PROPERTY. The gate exits 0 only when at least one selected test ran and passed (or the
|
|
10
|
+
# whole diff is inert and it printed NO_TESTS_AFFECTED); it refuses loudly (rc 4) on a
|
|
11
|
+
# vacuous/undecidable selection; it delegates --affected/--full ONLY to a repo runner that
|
|
12
|
+
# registers --print-selection at a case-label or usage-table position; it fails closed on
|
|
13
|
+
# malformed config; it emits `AFFECTED_GATE ran=X of=N skipped=Y mode=… reason=… stack=… ci=…`
|
|
14
|
+
# on EVERY run path. See the plan's Guard Contract for the mutation matrices this suite
|
|
15
|
+
# pins — each matrix row below is tagged [G<n>-<row>].
|
|
16
|
+
#
|
|
17
|
+
# ASSEMBLY. Every run happens inside a synthesized fixture repo (cq-test-fixtures-
|
|
18
|
+
# synthesized-only) under one mktemp root, behind PATH-shimmed fake runners
|
|
19
|
+
# (vitest/jest/pytest/go/nx/turbo/node/python3 + the package managers) that LOG every
|
|
20
|
+
# invocation to $CALLS and validate argv VALUES — an argv shape this suite does not
|
|
21
|
+
# prescribe is a stub miss (rc 64), never a silent success. Base refs come from minted
|
|
22
|
+
# refs/remotes/origin/* refs; the shallow-clone row uses a real file:// --depth 1 clone.
|
|
23
|
+
#
|
|
24
|
+
# INSTRUMENT FIDELITY (G1-11). Rows that claim "the runner ran" assert against the
|
|
25
|
+
# invocation ledger, never against exit codes alone — a fake that printed success
|
|
26
|
+
# without recording itself would turn those rows red. The `log_nonempty` check is
|
|
27
|
+
# itself self-tested against an empty ledger.
|
|
28
|
+
#
|
|
29
|
+
# cloud coverage: run_gate ALWAYS invokes the SUT with `env -u CLAUDE_PLUGIN_ROOT`, so
|
|
30
|
+
# every row exercises the no-plugin-root path (the gate resolves its libs by
|
|
31
|
+
# BASH_SOURCE; no in-gate resolver exists — ADR-179's three-arm diagnostic lives
|
|
32
|
+
# in commands/go.md, not here).
|
|
33
|
+
#
|
|
34
|
+
# Authoring note (work/SKILL.md): never `producer | grep -q` under pipefail — every rc is
|
|
35
|
+
# captured on its own line before it is inspected.
|
|
36
|
+
|
|
37
|
+
set -uo pipefail
|
|
38
|
+
export TMPDIR="${TMPDIR:-/var/tmp}"
|
|
39
|
+
|
|
40
|
+
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
41
|
+
REPO_ROOT="$(cd "$DIR/../../.." && pwd)"
|
|
42
|
+
# AFFECTED_TESTS_SUT exists so the fixture plumbing itself can be smoke-tested
|
|
43
|
+
# against a stand-in without touching the real gate's path.
|
|
44
|
+
SUT="${AFFECTED_TESTS_SUT:-$DIR/affected-tests.sh}"
|
|
45
|
+
|
|
46
|
+
# assert_fixture_dir / git_fixture_env / print_results live here; sourcing arms the
|
|
47
|
+
# git-location tripwire (#7833) — this suite must never run with an inherited GIT_DIR.
|
|
48
|
+
# shellcheck source=plugins/soleur/test/test-helpers.sh
|
|
49
|
+
source "$DIR/../test/test-helpers.sh" || { echo "FATAL: could not source test-helpers.sh" >&2; exit 2; }
|
|
50
|
+
set +e -uo pipefail
|
|
51
|
+
|
|
52
|
+
TESTROOT="$(mktemp -d -t affected-gate.XXXXXXXX)" || { echo "FATAL: mktemp failed" >&2; exit 2; }
|
|
53
|
+
assert_fixture_dir "$TESTROOT"
|
|
54
|
+
trap 'rm -rf "$TESTROOT"' EXIT INT TERM HUP
|
|
55
|
+
|
|
56
|
+
WORLD="$TESTROOT/world" # all fixture repos + remotes live under here
|
|
57
|
+
BIN="$TESTROOT/bin" # PATH shims
|
|
58
|
+
CALLS="$TESTROOT/calls.log" # the invocation ledger every row asserts on
|
|
59
|
+
RUNS="$TESTROOT/runs.tsv" # per-run record: kind\trc\toutfile
|
|
60
|
+
mkdir -p "$WORLD/repos" "$BIN" "$TESTROOT/outs"
|
|
61
|
+
: > "$CALLS"; : > "$RUNS"
|
|
62
|
+
|
|
63
|
+
# Constructed git env: synthesized identity, isolated config, discovery ceiling pinned
|
|
64
|
+
# one level above $WORLD (ceiling = $TESTROOT), so no fixture git ever reads the
|
|
65
|
+
# operator's credentials, global config, or an ancestor repository.
|
|
66
|
+
git_fixture_env "$WORLD" || { echo "FATAL: git_fixture_env refused $WORLD" >&2; exit 2; }
|
|
67
|
+
|
|
68
|
+
# Host tool paths captured BEFORE the shim dir goes on PATH: the node/python3 shims
|
|
69
|
+
# delegate unrecognized argv to these so the SUT's own internal use keeps working.
|
|
70
|
+
REAL_NODE="$(command -v node || true)"
|
|
71
|
+
REAL_PY="$(command -v python3 || true)"
|
|
72
|
+
REAL_GIT="$(command -v git || true)" # census-failure row: the git shim delegates here
|
|
73
|
+
BASEPATH="$PATH"
|
|
74
|
+
|
|
75
|
+
pass() { PASS=$((PASS + 1)); printf ' [ok] %s\n' "$1"; }
|
|
76
|
+
fail() { FAIL=$((FAIL + 1)); printf ' [FAIL] %s\n' "$1" >&2; }
|
|
77
|
+
|
|
78
|
+
# Instrument self-test: both verdict helpers must move their counters.
|
|
79
|
+
_sp=$PASS; _sf=$FAIL
|
|
80
|
+
pass "instrument self-test" >/dev/null 2>&1
|
|
81
|
+
fail "instrument self-test" >/dev/null 2>&1
|
|
82
|
+
if (( PASS != _sp + 1 )) || (( FAIL != _sf + 1 )); then
|
|
83
|
+
echo "[FATAL] instrument self-test failed" >&2; exit 1
|
|
84
|
+
fi
|
|
85
|
+
PASS=$_sp; FAIL=$_sf
|
|
86
|
+
|
|
87
|
+
[[ -f "$SUT" ]] || {
|
|
88
|
+
printf '\n[FATAL] SUT missing at %s — nothing to test (expected RED while the gate is unwritten).\n' "$SUT" >&2
|
|
89
|
+
exit 1
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
# ================================ fakes =======================================
|
|
93
|
+
# Every fake: logs `<basename>\t<argv>` to $FAKE_LOG, validates argv VALUES against
|
|
94
|
+
# FAKE_*_EXPECT / FAKE_*_MAXW env pins, exits 64 on an unrouted or mis-shaped call,
|
|
95
|
+
# and takes its verdict rc from FAKE_*_RC. Logging happens BEFORE validation so a row
|
|
96
|
+
# asserting "never called" cannot be laundered by a 64.
|
|
97
|
+
|
|
98
|
+
cat > "$BIN/vitest" <<'SHIM'
|
|
99
|
+
#!/usr/bin/env bash
|
|
100
|
+
[[ -n "${FAKE_LOG:-}" ]] || { echo "stub vitest: FAKE_LOG unset" >&2; exit 64; }
|
|
101
|
+
printf 'vitest\t%s\n' "$*" >> "$FAKE_LOG"
|
|
102
|
+
for a in "$@"; do case "$a" in --version|-V|-v) echo "4.1.11"; exit 0 ;; --help) echo "vitest 4.1.11"; exit 0 ;; esac; done
|
|
103
|
+
[[ -z "${FAKE_VITEST_SLEEP:-}" ]] || sleep "$FAKE_VITEST_SLEEP"
|
|
104
|
+
mode=full
|
|
105
|
+
for a in "$@"; do [[ "$a" == "related" ]] && mode=related; done
|
|
106
|
+
# `vitest related <files> --run` is the #9307 contract — the subcommand form, and the
|
|
107
|
+
# --run flag is mandatory on the affected path (watch mode would hang the gate).
|
|
108
|
+
if [[ "$mode" == "related" ]]; then
|
|
109
|
+
case " $* " in
|
|
110
|
+
*" --run "*) : ;;
|
|
111
|
+
*) echo "stub vitest: 'related' without --run: $*" >&2; exit 64 ;;
|
|
112
|
+
esac
|
|
113
|
+
fi
|
|
114
|
+
maxw=""; prev=""
|
|
115
|
+
for a in "$@"; do
|
|
116
|
+
if [[ -n "$prev" ]]; then maxw="$a"; prev=""; continue; fi
|
|
117
|
+
case "$a" in
|
|
118
|
+
--maxWorkers|--max-workers|-w) prev="$a" ;;
|
|
119
|
+
--maxWorkers=*|--max-workers=*|-w=*) maxw="${a#*=}" ;;
|
|
120
|
+
esac
|
|
121
|
+
done
|
|
122
|
+
if [[ -n "${FAKE_VITEST_MAXW:-}" && "$maxw" != "$FAKE_VITEST_MAXW" ]]; then
|
|
123
|
+
echo "stub vitest: --maxWorkers=${maxw:-<absent>} want $FAKE_VITEST_MAXW" >&2; exit 64
|
|
124
|
+
fi
|
|
125
|
+
for want in ${FAKE_VITEST_EXPECT:-}; do
|
|
126
|
+
case " $* " in
|
|
127
|
+
*"$want"*) : ;;
|
|
128
|
+
*) echo "stub vitest: expected arg '$want' missing: $*" >&2; exit 64 ;;
|
|
129
|
+
esac
|
|
130
|
+
done
|
|
131
|
+
# The empty-selection answer is scoped to `related`: a selector that finds nothing
|
|
132
|
+
# is not the same verdict as a full run that found nothing.
|
|
133
|
+
if [[ "$mode" == "related" && "${FAKE_VITEST_NOTESTS:-0}" == "1" ]]; then
|
|
134
|
+
echo "No test files found"
|
|
135
|
+
exit "${FAKE_VITEST_RC:-0}"
|
|
136
|
+
fi
|
|
137
|
+
# Honest tally: the reported count must equal the fixture's own test-file
|
|
138
|
+
# count — a constant here would let ran= diverge from of= and launder the
|
|
139
|
+
# unit-integrity rows (D7/D16/D19a pin file units).
|
|
140
|
+
_n="$(find . -type f \( -name '*.test.*' -o -name '*.spec.*' \) \
|
|
141
|
+
-not -path './node_modules/*' 2>/dev/null | wc -l | tr -d ' ')"
|
|
142
|
+
[[ "$_n" =~ ^[0-9]+$ ]] && ((_n > 0)) || _n=1
|
|
143
|
+
echo "Test Files $_n passed ($_n)"
|
|
144
|
+
exit "${FAKE_VITEST_RC:-0}"
|
|
145
|
+
SHIM
|
|
146
|
+
|
|
147
|
+
cat > "$BIN/jest" <<'SHIM'
|
|
148
|
+
#!/usr/bin/env bash
|
|
149
|
+
[[ -n "${FAKE_LOG:-}" ]] || { echo "stub jest: FAKE_LOG unset" >&2; exit 64; }
|
|
150
|
+
printf 'jest\t%s\n' "$*" >> "$FAKE_LOG"
|
|
151
|
+
for a in "$@"; do case "$a" in --version|-V|-v) echo "30.0.5"; exit 0 ;; --help) echo "jest 30.0.5"; exit 0 ;; esac; done
|
|
152
|
+
rel=0
|
|
153
|
+
for a in "$@"; do [[ "$a" == "--findRelatedTests" ]] && rel=1; done
|
|
154
|
+
if [[ "${FAKE_JEST_REQUIRE_RELATED:-0}" == "1" && "$rel" == "0" ]]; then
|
|
155
|
+
echo "stub jest: expected --findRelatedTests in: $*" >&2; exit 64
|
|
156
|
+
fi
|
|
157
|
+
for want in ${FAKE_JEST_EXPECT:-}; do
|
|
158
|
+
case " $* " in
|
|
159
|
+
*"$want"*) : ;;
|
|
160
|
+
*) echo "stub jest: expected arg '$want' missing: $*" >&2; exit 64 ;;
|
|
161
|
+
esac
|
|
162
|
+
done
|
|
163
|
+
# Same honest-tally rule as the vitest stub — the __tests__ convention carries
|
|
164
|
+
# no .test./.spec. marker, so count the directory shape too (mirrors the
|
|
165
|
+
# adapter's census).
|
|
166
|
+
_n="$(find . -type f \( -name '*.test.*' -o -name '*.spec.*' \
|
|
167
|
+
-o -path '*/__tests__/*' \) -not -path './node_modules/*' 2>/dev/null \
|
|
168
|
+
| wc -l | tr -d ' ')"
|
|
169
|
+
[[ "$_n" =~ ^[0-9]+$ ]] && ((_n > 0)) || _n=1
|
|
170
|
+
echo "Test Suites: $_n passed, $_n total"
|
|
171
|
+
exit "${FAKE_JEST_RC:-0}"
|
|
172
|
+
SHIM
|
|
173
|
+
|
|
174
|
+
cat > "$BIN/pytest" <<'SHIM'
|
|
175
|
+
#!/usr/bin/env bash
|
|
176
|
+
[[ -n "${FAKE_LOG:-}" ]] || { echo "stub pytest: FAKE_LOG unset" >&2; exit 64; }
|
|
177
|
+
printf 'pytest\t%s\n' "$*" >> "$FAKE_LOG"
|
|
178
|
+
for a in "$@"; do
|
|
179
|
+
case "$a" in --version|-V|--help) echo "pytest 8.3.0"; exit 0 ;; esac
|
|
180
|
+
done
|
|
181
|
+
for want in ${FAKE_PYTEST_EXPECT:-}; do
|
|
182
|
+
case " $* " in
|
|
183
|
+
*"$want"*) : ;;
|
|
184
|
+
*) echo "stub pytest: expected arg '$want' missing: $*" >&2; exit 64 ;;
|
|
185
|
+
esac
|
|
186
|
+
done
|
|
187
|
+
rc="${FAKE_PYTEST_RC:-0}"
|
|
188
|
+
if [[ "$rc" == "5" ]]; then echo "no tests collected"; exit 5; fi
|
|
189
|
+
# pytest's own tally line: `collected N items` (testmon adds `/ M deselected`)
|
|
190
|
+
# — the gate pins ran to FILE units, so this line is realism, not the counter.
|
|
191
|
+
_nitems="${FAKE_PYTEST_COLLECTED:-1}"
|
|
192
|
+
if [[ -n "${FAKE_PYTEST_DESELECTED:-}" ]]; then
|
|
193
|
+
echo "collected $_nitems items / $FAKE_PYTEST_DESELECTED deselected"
|
|
194
|
+
else
|
|
195
|
+
echo "collected $_nitems items"
|
|
196
|
+
fi
|
|
197
|
+
echo "=== $_nitems passed in 0.01s ==="
|
|
198
|
+
exit "$rc"
|
|
199
|
+
SHIM
|
|
200
|
+
|
|
201
|
+
cat > "$BIN/go" <<'SHIM'
|
|
202
|
+
#!/usr/bin/env bash
|
|
203
|
+
[[ -n "${FAKE_LOG:-}" ]] || { echo "stub go: FAKE_LOG unset" >&2; exit 64; }
|
|
204
|
+
printf 'go\t%s\n' "$*" >> "$FAKE_LOG"
|
|
205
|
+
cmd="${1:-}"
|
|
206
|
+
case "$cmd" in
|
|
207
|
+
version) echo "go version go1.23.4 linux/amd64"; exit 0 ;;
|
|
208
|
+
env)
|
|
209
|
+
for a in "$@"; do
|
|
210
|
+
case "$a" in GOMOD) printf '%s\n' "${FAKE_GO_GOMOD:-$PWD/go.mod}" ;; esac
|
|
211
|
+
done
|
|
212
|
+
exit 0 ;;
|
|
213
|
+
list)
|
|
214
|
+
# FAKE_GO_DIR/graph rows are pipe-form: `ImportPath|deps deps|TestImports|XTestImports`
|
|
215
|
+
# — the same shape the real `go list -e -f` template emits.
|
|
216
|
+
graph="${FAKE_GO_DIR:-}/graph"
|
|
217
|
+
[[ -f "$graph" ]] || { echo "stub go: list with no FAKE_GO_DIR/graph" >&2; exit 64; }
|
|
218
|
+
has_f=0; has_deps=0; targets=""
|
|
219
|
+
prev=""
|
|
220
|
+
for a in "$@"; do
|
|
221
|
+
if [[ "$prev" == "-f" ]]; then has_f=1; prev=""; continue; fi
|
|
222
|
+
case "$a" in
|
|
223
|
+
list) : ;;
|
|
224
|
+
-f) prev="-f" ;;
|
|
225
|
+
-f*) has_f=1 ;;
|
|
226
|
+
-deps) has_deps=1 ;;
|
|
227
|
+
-*) : ;;
|
|
228
|
+
*) targets="$targets $a" ;;
|
|
229
|
+
esac
|
|
230
|
+
done
|
|
231
|
+
if (( has_f )); then
|
|
232
|
+
if [[ "$targets" == *"/..."* || -z "${targets// /}" ]]; then
|
|
233
|
+
while IFS= read -r line; do printf '%s\n' "$line"; done < "$graph"
|
|
234
|
+
else
|
|
235
|
+
# `go list -e -f '{{.ImportPath}}' <dir>` — a dir target (./x) resolves
|
|
236
|
+
# to the graph row whose import path ENDS in it; `.` is the module root
|
|
237
|
+
# (the shortest import path).
|
|
238
|
+
for t in $targets; do
|
|
239
|
+
td="${t#./}"
|
|
240
|
+
if [[ "$td" == "." ]]; then
|
|
241
|
+
awk -F'|' '{n=gsub(/\//,"/",$1); if(NR==1||n<min){min=n;best=$1}} END{if(best!="")print best; else exit 1}' "$graph" \
|
|
242
|
+
|| { echo "stub go: unknown dir $t" >&2; exit 64; }
|
|
243
|
+
else
|
|
244
|
+
awk -F'|' -v t="$td" 'length($1)>=length(t) && substr($1,length($1)-length(t)+1)==t{print $1; f=1} END{if(!f) exit 1}' "$graph" \
|
|
245
|
+
|| { echo "stub go: unknown dir $t" >&2; exit 64; }
|
|
246
|
+
fi
|
|
247
|
+
done
|
|
248
|
+
fi
|
|
249
|
+
exit 0
|
|
250
|
+
fi
|
|
251
|
+
if [[ "$targets" == *"/..."* || -z "${targets// /}" ]]; then
|
|
252
|
+
awk -F'|' '{print $1}' "$graph"
|
|
253
|
+
exit 0
|
|
254
|
+
fi
|
|
255
|
+
# named package(s): `go list -deps <pkg>` prints one import path per line
|
|
256
|
+
# (target plus the deps the graph row declares); plain `go list <pkg>` prints it.
|
|
257
|
+
for t in $targets; do
|
|
258
|
+
[[ "$t" == "list" ]] && continue
|
|
259
|
+
if (( has_deps )); then
|
|
260
|
+
awk -F'|' -v t="$t" '$1==t{print $1; n=split($2,d," "); for(i=1;i<=n;i++) print d[i]; f=1} END{if(!f) exit 1}' "$graph" \
|
|
261
|
+
|| { echo "stub go: unknown package $t" >&2; exit 64; }
|
|
262
|
+
else
|
|
263
|
+
awk -F'|' -v t="$t" '$1==t{print $1; f=1} END{if(!f) exit 1}' "$graph" \
|
|
264
|
+
|| { echo "stub go: unknown package $t" >&2; exit 64; }
|
|
265
|
+
fi
|
|
266
|
+
done
|
|
267
|
+
exit 0 ;;
|
|
268
|
+
test)
|
|
269
|
+
# `go test -p <n> [pkg …]` — emit the real `ok`/`?` per-package lines so
|
|
270
|
+
# the adapter can count *_test.go files under each reported package.
|
|
271
|
+
has_p=0; tgts=""; prev=""
|
|
272
|
+
for a in "$@"; do
|
|
273
|
+
if [[ -n "$prev" ]]; then prev=""; continue; fi
|
|
274
|
+
case "$a" in
|
|
275
|
+
-p|--parallel) prev="$a"; has_p=1 ;;
|
|
276
|
+
-p=*|--parallel=*) has_p=1 ;;
|
|
277
|
+
test) : ;;
|
|
278
|
+
-*) : ;;
|
|
279
|
+
*) tgts="$tgts $a" ;;
|
|
280
|
+
esac
|
|
281
|
+
done
|
|
282
|
+
if [[ "${FAKE_GO_REQUIRE_P:-1}" == "1" && "$has_p" == "0" ]]; then
|
|
283
|
+
echo "stub go: 'go test' without a -p worker cap: $*" >&2; exit 64
|
|
284
|
+
fi
|
|
285
|
+
for want in ${FAKE_GO_EXPECT:-}; do
|
|
286
|
+
case " $* " in
|
|
287
|
+
*"$want"*) : ;;
|
|
288
|
+
*) echo "stub go: expected package '$want' not under test: $*" >&2; exit 64 ;;
|
|
289
|
+
esac
|
|
290
|
+
done
|
|
291
|
+
[[ -n "${tgts// /}" ]] || tgts=" ./..."
|
|
292
|
+
for t in $tgts; do
|
|
293
|
+
if [[ "$t" == "./..." ]]; then
|
|
294
|
+
awk '{print "ok\t"$1"\t0.01s"}' "${FAKE_GO_DIR:-}/graph" 2>/dev/null || true
|
|
295
|
+
else
|
|
296
|
+
printf 'ok\t%s\t0.01s\n' "$t"
|
|
297
|
+
fi
|
|
298
|
+
done
|
|
299
|
+
exit "${FAKE_GO_TEST_RC:-0}" ;;
|
|
300
|
+
*) echo "stub go: unrouted call: $*" >&2; exit 64 ;;
|
|
301
|
+
esac
|
|
302
|
+
SHIM
|
|
303
|
+
|
|
304
|
+
cat > "$BIN/nx" <<'SHIM'
|
|
305
|
+
#!/usr/bin/env bash
|
|
306
|
+
[[ -n "${FAKE_LOG:-}" ]] || { echo "stub nx: FAKE_LOG unset" >&2; exit 64; }
|
|
307
|
+
printf 'nx\t%s\n' "$*" >> "$FAKE_LOG"
|
|
308
|
+
for a in "$@"; do case "$a" in --version|-V|-v) echo "22.4.0"; exit 0 ;; esac; done
|
|
309
|
+
case " $* " in
|
|
310
|
+
*" affected "*|*" affected"*)
|
|
311
|
+
for want in ${FAKE_NX_EXPECT:-}; do
|
|
312
|
+
case " $* " in
|
|
313
|
+
*"$want"*) : ;;
|
|
314
|
+
*) echo "stub nx: expected arg '$want' missing: $*" >&2; exit 64 ;;
|
|
315
|
+
esac
|
|
316
|
+
done
|
|
317
|
+
# FAKE_NX_LINE controls the summary line — e.g. "… for 10 tasks" must NOT
|
|
318
|
+
# read as a vacuous "0 tasks" verdict.
|
|
319
|
+
echo "${FAKE_NX_LINE:-nx affected: ran target test}"
|
|
320
|
+
exit "${FAKE_NX_RC:-0}" ;;
|
|
321
|
+
*" run-many "*|*" run-many"*)
|
|
322
|
+
echo "${FAKE_NX_FULL_LINE:-nx run-many: ran target test for 3 projects}"
|
|
323
|
+
exit "${FAKE_NX_FULL_RC:-0}" ;;
|
|
324
|
+
*" show "*)
|
|
325
|
+
for p in ${FAKE_NX_PROJECTS:-webapp}; do printf '%s\n' "$p"; done
|
|
326
|
+
exit 0 ;;
|
|
327
|
+
*) echo "stub nx: unrouted call: $*" >&2; exit 64 ;;
|
|
328
|
+
esac
|
|
329
|
+
SHIM
|
|
330
|
+
|
|
331
|
+
cat > "$BIN/turbo" <<'SHIM'
|
|
332
|
+
#!/usr/bin/env bash
|
|
333
|
+
[[ -n "${FAKE_LOG:-}" ]] || { echo "stub turbo: FAKE_LOG unset" >&2; exit 64; }
|
|
334
|
+
printf 'turbo\t%s\n' "$*" >> "$FAKE_LOG"
|
|
335
|
+
for a in "$@"; do case "$a" in --version|-V|-v) echo "2.5.6"; exit 0 ;; esac; done
|
|
336
|
+
has_run=0; has_test=0
|
|
337
|
+
for a in "$@"; do
|
|
338
|
+
[[ "$a" == "run" ]] && has_run=1
|
|
339
|
+
[[ "$a" == "test" || "$a" == test* ]] && has_test=1
|
|
340
|
+
done
|
|
341
|
+
if [[ "$has_run" == "1" && "$has_test" == "1" ]]; then
|
|
342
|
+
for want in ${FAKE_TURBO_EXPECT:-}; do
|
|
343
|
+
case " $* " in
|
|
344
|
+
*"$want"*) : ;;
|
|
345
|
+
*) echo "stub turbo: expected arg '$want' missing: $*" >&2; exit 64 ;;
|
|
346
|
+
esac
|
|
347
|
+
done
|
|
348
|
+
echo "turbo: tasks complete"
|
|
349
|
+
exit "${FAKE_TURBO_RC:-0}"
|
|
350
|
+
fi
|
|
351
|
+
echo "stub turbo: unrouted call: $*" >&2
|
|
352
|
+
exit 64
|
|
353
|
+
SHIM
|
|
354
|
+
|
|
355
|
+
cat > "$BIN/node" <<'SHIM'
|
|
356
|
+
#!/usr/bin/env bash
|
|
357
|
+
[[ -n "${FAKE_LOG:-}" ]] || { echo "stub node: FAKE_LOG unset" >&2; exit 64; }
|
|
358
|
+
printf 'node\t%s\n' "$*" >> "$FAKE_LOG"
|
|
359
|
+
case "${1:-}" in --version|-v) echo "v22.11.0"; exit 0 ;; esac
|
|
360
|
+
# `node <path>/vitest.mjs <args>` / `node <path>/jest.js <args>` — the first arg
|
|
361
|
+
# whose basename names a tool becomes the tool call; the tool shim logs it.
|
|
362
|
+
args=("$@"); i=0
|
|
363
|
+
while (( i < ${#args[@]} )); do
|
|
364
|
+
b="${args[i]##*/}"
|
|
365
|
+
case "$b" in
|
|
366
|
+
vitest*|vitest.mjs) exec vitest ${args[@]+"${args[@]:$((i+1))}"} ;;
|
|
367
|
+
jest*|jest.js|jest-cli*) exec jest ${args[@]+"${args[@]:$((i+1))}"} ;;
|
|
368
|
+
esac
|
|
369
|
+
i=$((i+1))
|
|
370
|
+
done
|
|
371
|
+
[[ -n "${REAL_NODE:-}" ]] && exec "$REAL_NODE" "$@"
|
|
372
|
+
echo "stub node: unrouted call: $*" >&2
|
|
373
|
+
exit 64
|
|
374
|
+
SHIM
|
|
375
|
+
|
|
376
|
+
cat > "$BIN/python3" <<'SHIM'
|
|
377
|
+
#!/usr/bin/env bash
|
|
378
|
+
[[ -n "${FAKE_LOG:-}" ]] || { echo "stub python3: FAKE_LOG unset" >&2; exit 64; }
|
|
379
|
+
printf 'python3\t%s\n' "$*" >> "$FAKE_LOG"
|
|
380
|
+
# The gate probes with isolated mode (`python3 -I -c …`): consume leading
|
|
381
|
+
# single-letter interpreter flags before routing on the payload.
|
|
382
|
+
while [[ "${1:-}" == -[a-zA-Z] && "${1:-}" != "-c" && "${1:-}" != "-m" && "${1:-}" != "-V" ]]; do shift; done
|
|
383
|
+
# `python3 -m pytest <args>` runs the pytest shim.
|
|
384
|
+
if [[ "${1:-}" == "-m" && "${2:-}" == "pytest" ]]; then
|
|
385
|
+
shift 2; exec pytest "$@"
|
|
386
|
+
fi
|
|
387
|
+
# Importability probes: `python3 -c 'import xdist'` (and find_spec spellings whose
|
|
388
|
+
# -c text names the module). Answers come from FAKE_PY_IMPORTABLE (space-separated
|
|
389
|
+
# module names that import cleanly); anything else delegates to the real python3 so
|
|
390
|
+
# the SUT's own config parsing keeps working.
|
|
391
|
+
if [[ "${1:-}" == "-c" && -n "${2:-}" ]]; then
|
|
392
|
+
script="$2"
|
|
393
|
+
mod=""
|
|
394
|
+
if [[ "$script" =~ (^|[^A-Za-z_])(pytest_testmon|testmon)([^A-Za-z_]|$) ]]; then mod="testmon";
|
|
395
|
+
elif [[ "$script" =~ (^|[^A-Za-z_])xdist([^A-Za-z_]|$) ]]; then mod="xdist";
|
|
396
|
+
elif [[ "$script" =~ (^|[^A-Za-z_])pytest([^A-Za-z_]|$) ]]; then mod="pytest"; fi
|
|
397
|
+
if [[ -n "$mod" ]]; then
|
|
398
|
+
case " ${FAKE_PY_IMPORTABLE:-} " in
|
|
399
|
+
*" $mod "*) exit 0 ;;
|
|
400
|
+
*) exit 1 ;;
|
|
401
|
+
esac
|
|
402
|
+
fi
|
|
403
|
+
fi
|
|
404
|
+
case "${1:-}" in --version|-V) echo "Python 3.15.0"; exit 0 ;; esac
|
|
405
|
+
[[ -n "${REAL_PY:-}" ]] && exec "$REAL_PY" "$@"
|
|
406
|
+
echo "stub python3: unrouted call: $*" >&2
|
|
407
|
+
exit 64
|
|
408
|
+
SHIM
|
|
409
|
+
|
|
410
|
+
# The package-manager shims share one body: they log, then either exec through to a
|
|
411
|
+
# shimmed tool (`npx vitest related …` → the vitest shim re-logs and validates) or
|
|
412
|
+
# answer a `test`/`run test` invocation from FAKE_PM_TEST_RC.
|
|
413
|
+
PM_BODY='#!/usr/bin/env bash
|
|
414
|
+
name="$(basename "$0")"
|
|
415
|
+
[[ -n "${FAKE_LOG:-}" ]] || { echo "stub $name: FAKE_LOG unset" >&2; exit 64; }
|
|
416
|
+
printf "%s\t%s\n" "$name" "$*" >> "$FAKE_LOG"
|
|
417
|
+
args=("$@"); i=0
|
|
418
|
+
while (( i < ${#args[@]} )); do
|
|
419
|
+
a="${args[i]}"
|
|
420
|
+
case "$a" in
|
|
421
|
+
-y|--yes|--|x|exec|dlx|-dlx) i=$((i+1)); continue ;;
|
|
422
|
+
vitest@*|jest@*|nx@*|turbo@*) exec "${a%%@*}" ${args[@]+"${args[@]:$((i+1))}"} ;;
|
|
423
|
+
vitest|jest|nx|turbo|pytest|go) exec "$a" ${args[@]+"${args[@]:$((i+1))}"} ;;
|
|
424
|
+
run)
|
|
425
|
+
nxt="${args[$((i+1))]:-}"
|
|
426
|
+
[[ "$nxt" == "test" ]] && exit "${FAKE_PM_TEST_RC:-0}"
|
|
427
|
+
i=$((i+2)); continue ;;
|
|
428
|
+
test) exit "${FAKE_PM_TEST_RC:-0}" ;;
|
|
429
|
+
install|i|add|remove|ci|create|init|info|view|ls|list|audit|outdated|pack|publish|version|whoami|pkg)
|
|
430
|
+
exit 0 ;;
|
|
431
|
+
*) i=$((i+1)) ;;
|
|
432
|
+
esac
|
|
433
|
+
done
|
|
434
|
+
echo "stub $name: unrouted call: $*" >&2
|
|
435
|
+
exit 64
|
|
436
|
+
'
|
|
437
|
+
for _pm in npm npx pnpm yarn bun bunx; do
|
|
438
|
+
printf '%s\n' "$PM_BODY" > "$BIN/$_pm"
|
|
439
|
+
done
|
|
440
|
+
chmod +x "$BIN"/*
|
|
441
|
+
unset _pm
|
|
442
|
+
|
|
443
|
+
# ============================ fixture builders ================================
|
|
444
|
+
|
|
445
|
+
# mk_repo <name> [default-branch] — repo at $WORLD/repos/<name> with a `feature`
|
|
446
|
+
# branch checked out. Base-ref resolution gets its refs WITHOUT a live remote:
|
|
447
|
+
# refs/remotes/origin/<br> + refs/remotes/origin/HEAD are minted directly, so
|
|
448
|
+
# `@{upstream}` fails (feature has none) and the origin/HEAD → origin/<br> arm wins.
|
|
449
|
+
mk_repo() { # prints the repo path
|
|
450
|
+
local name="$1" br="${2:-main}" r
|
|
451
|
+
r="$WORLD/repos/$name"
|
|
452
|
+
mkdir -p "$r"
|
|
453
|
+
printf 'fixture seed\n' > "$r/SEED.txt"
|
|
454
|
+
git -C "$r" init -q -b "$br"
|
|
455
|
+
git -C "$r" config user.email soleur-test@example.invalid
|
|
456
|
+
git -C "$r" config user.name "Soleur Fixture"
|
|
457
|
+
git -C "$r" config commit.gpgsign false
|
|
458
|
+
printf '%s' "$r"
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
# seal_base <repo> — commit the seeded base, mint origin refs, move to `feature`.
|
|
462
|
+
seal_base() {
|
|
463
|
+
local r="$1" br
|
|
464
|
+
assert_fixture_dir "$r"
|
|
465
|
+
# symbolic-ref, not rev-parse: HEAD is UNBORN until the commit below lands, and
|
|
466
|
+
# rev-parse --abbrev-ref errors out there while symbolic-ref still reads the
|
|
467
|
+
# branch HEAD points at.
|
|
468
|
+
br="$(git -C "$r" symbolic-ref --short -q HEAD)"
|
|
469
|
+
git -C "$r" add -A
|
|
470
|
+
git -C "$r" commit -q -m "base"
|
|
471
|
+
git -C "$r" update-ref "refs/remotes/origin/$br" HEAD
|
|
472
|
+
git -C "$r" symbolic-ref refs/remotes/origin/HEAD "refs/remotes/origin/$br"
|
|
473
|
+
git -C "$r" checkout -q -b feature
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
chg() { local r="$1" f="$2"; mkdir -p "$r/$(dirname "$f")"; printf '%b\n' "${3:-change}" > "$r/$f"; }
|
|
477
|
+
commit_chg() { assert_fixture_dir "$1"; chg "$1" "$2" "${3:-change}"; git -C "$1" add -A; git -C "$1" commit -q -m "change: $2"; }
|
|
478
|
+
|
|
479
|
+
seed_js() { # <repo> — vitest stack: package.json, src, a test file, node_modules stub
|
|
480
|
+
local r="$1"
|
|
481
|
+
mkdir -p "$r/src" "$r/node_modules/.bin"
|
|
482
|
+
cat > "$r/package.json" <<'JSON'
|
|
483
|
+
{
|
|
484
|
+
"name": "fixture-js",
|
|
485
|
+
"private": true,
|
|
486
|
+
"scripts": { "test": "vitest run" },
|
|
487
|
+
"devDependencies": { "vitest": "^4.0.0" }
|
|
488
|
+
}
|
|
489
|
+
JSON
|
|
490
|
+
printf 'node_modules/\n' > "$r/.gitignore"
|
|
491
|
+
printf 'export const a = 1;\n' > "$r/src/a.ts"
|
|
492
|
+
printf 'import { test } from "vitest";\ntest("a", () => {});\n' > "$r/src/a.test.ts"
|
|
493
|
+
ln -sf "$BIN/vitest" "$r/node_modules/.bin/vitest"
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
seed_js_nobin() { # <repo> — vitest stack but NO repo-local binary: used by the
|
|
497
|
+
# toolchain-missing row, where a .bin symlink would resolve the
|
|
498
|
+
# shim even on a stripped PATH.
|
|
499
|
+
local r="$1"
|
|
500
|
+
mkdir -p "$r/src"
|
|
501
|
+
cat > "$r/package.json" <<'JSON'
|
|
502
|
+
{
|
|
503
|
+
"name": "fixture-js",
|
|
504
|
+
"private": true,
|
|
505
|
+
"scripts": { "test": "vitest run" },
|
|
506
|
+
"devDependencies": { "vitest": "^4.0.0" }
|
|
507
|
+
}
|
|
508
|
+
JSON
|
|
509
|
+
printf 'node_modules/\n' > "$r/.gitignore"
|
|
510
|
+
printf 'export const a = 1;\n' > "$r/src/a.ts"
|
|
511
|
+
printf 'import { test } from "vitest";\ntest("a", () => {});\n' > "$r/src/a.test.ts"
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
seed_jest() { # <repo> — jest stack (jest.config.js present, no vitest config)
|
|
515
|
+
local r="$1"
|
|
516
|
+
mkdir -p "$r/src" "$r/node_modules/.bin"
|
|
517
|
+
cat > "$r/package.json" <<'JSON'
|
|
518
|
+
{
|
|
519
|
+
"name": "fixture-jest",
|
|
520
|
+
"private": true,
|
|
521
|
+
"scripts": { "test": "jest" },
|
|
522
|
+
"devDependencies": { "jest": "^30.0.0" }
|
|
523
|
+
}
|
|
524
|
+
JSON
|
|
525
|
+
printf 'node_modules/\n' > "$r/.gitignore"
|
|
526
|
+
printf 'module.exports = {};\n' > "$r/jest.config.js"
|
|
527
|
+
printf 'export const b = 1;\n' > "$r/src/b.ts"
|
|
528
|
+
printf 'test("b", () => {});\n' > "$r/src/b.test.ts"
|
|
529
|
+
ln -sf "$BIN/jest" "$r/node_modules/.bin/jest"
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
seed_python() { # <repo> — pytest stack
|
|
533
|
+
local r="$1"
|
|
534
|
+
mkdir -p "$r/src" "$r/tests"
|
|
535
|
+
cat > "$r/pyproject.toml" <<'TOML'
|
|
536
|
+
[project]
|
|
537
|
+
name = "fixture-py"
|
|
538
|
+
[tool.pytest.ini_options]
|
|
539
|
+
testpaths = ["tests"]
|
|
540
|
+
TOML
|
|
541
|
+
printf 'def f():\n return 1\n' > "$r/src/mod.py"
|
|
542
|
+
printf 'def test_a():\n assert True\n' > "$r/tests/test_a.py"
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
seed_go() { # <repo> <graph-dir> — go.mod + packages; writes <graph-dir>/graph as
|
|
546
|
+
# "pkg dep dep" lines the go shim answers `go list` from
|
|
547
|
+
local r="$1" g="$2"
|
|
548
|
+
mkdir -p "$r/internal/leaf" "$r/cmd/app" "$g"
|
|
549
|
+
printf 'module example.com/m\n\ngo 1.23\n' > "$r/go.mod"
|
|
550
|
+
printf 'package leaf\n\nfunc Leaf() int { return 1 }\n' > "$r/internal/leaf/leaf.go"
|
|
551
|
+
printf 'package leaf\n\nimport "testing"\n\nfunc TestLeaf(t *testing.T) {}\n' > "$r/internal/leaf/leaf_test.go"
|
|
552
|
+
printf 'package main\n\nimport _ "example.com/m/internal/leaf"\n\nfunc main() {}\n' > "$r/cmd/app/main.go"
|
|
553
|
+
# Pipe-form rows: ImportPath|Imports|TestImports|XTestImports — the shape
|
|
554
|
+
# `go list -e -f '{{.ImportPath}}|{{join .Imports " "}}|…'` emits.
|
|
555
|
+
cat > "$g/graph" <<'GRAPH'
|
|
556
|
+
example.com/m/cmd/app|example.com/m/internal/leaf fmt||
|
|
557
|
+
example.com/m/internal/leaf|||
|
|
558
|
+
GRAPH
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
seed_nx() { local r="$1"; seed_js "$r"; printf '{"npmScope":"fx","affected":{}}\n' > "$r/nx.json"; }
|
|
562
|
+
seed_turbo(){ local r="$1"; seed_js "$r"; printf '{"pipeline":{"test":{}}}\n' > "$r/turbo.json"; }
|
|
563
|
+
|
|
564
|
+
seed_ci() { # <repo> — a CI test workflow (must be committed in BASE: the workflows
|
|
565
|
+
# dir is itself a built-in global_path)
|
|
566
|
+
local r="$1"
|
|
567
|
+
mkdir -p "$r/.github/workflows"
|
|
568
|
+
cat > "$r/.github/workflows/ci.yml" <<'YML'
|
|
569
|
+
name: ci
|
|
570
|
+
on: pull_request
|
|
571
|
+
jobs:
|
|
572
|
+
test:
|
|
573
|
+
runs-on: ubuntu-latest
|
|
574
|
+
steps:
|
|
575
|
+
- run: npm test
|
|
576
|
+
YML
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
write_config() { # <repo> <json>
|
|
580
|
+
printf '%s\n' "$2" > "$1/.soleur-test-gate.json"
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
# The capability-positive runner (G2): registers --print-selection at BOTH the
|
|
584
|
+
# case-label position and the usage-table position, as scripts/test-all.sh does.
|
|
585
|
+
write_runner_capable() { # <repo>
|
|
586
|
+
local r="$1"
|
|
587
|
+
mkdir -p "$r/scripts"
|
|
588
|
+
cat > "$r/scripts/test-all.sh" <<'RUNNER'
|
|
589
|
+
#!/usr/bin/env bash
|
|
590
|
+
printf 'test-all\t%s\n' "$*" >> "${FAKE_LOG:?}"
|
|
591
|
+
usage() {
|
|
592
|
+
cat <<'USAGE'
|
|
593
|
+
Usage: bash scripts/test-all.sh [--affected] [--full]
|
|
594
|
+
--affected run the suites this diff selects
|
|
595
|
+
--full run the entire battery
|
|
596
|
+
--print-selection print the suites this diff selects; runs nothing
|
|
597
|
+
USAGE
|
|
598
|
+
}
|
|
599
|
+
for a in "$@"; do
|
|
600
|
+
case "$a" in
|
|
601
|
+
--print-selection) printf 'AFFECTED_SELECTED\tsuite\taffected\n'; exit 0 ;;
|
|
602
|
+
--affected) exit "${FAKE_RUNNER_AFFECTED_RC:-${FAKE_RUNNER_RC:-0}}" ;;
|
|
603
|
+
--full) exit "${FAKE_RUNNER_FULL_RC:-${FAKE_RUNNER_RC:-0}}" ;;
|
|
604
|
+
--capacity) printf 'measured_runs=0\n'; exit 0 ;;
|
|
605
|
+
--help|-h) usage; exit 0 ;;
|
|
606
|
+
*) echo "runner stub: unexpected argv: $a" >&2; exit 64 ;;
|
|
607
|
+
esac
|
|
608
|
+
done
|
|
609
|
+
exit "${FAKE_RUNNER_RC:-0}"
|
|
610
|
+
RUNNER
|
|
611
|
+
chmod +x "$r/scripts/test-all.sh"
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
# Usage-table-only registration (G2-5 must-PASS): --print-selection appears ONLY as
|
|
615
|
+
# a usage-table row; the dispatch loop matches it through a conditional, not a
|
|
616
|
+
# case label. A probe that insists on the `)` case-label spelling misses this.
|
|
617
|
+
write_runner_usage_table() { # <repo>
|
|
618
|
+
local r="$1"
|
|
619
|
+
mkdir -p "$r/scripts"
|
|
620
|
+
cat > "$r/scripts/test-all.sh" <<'RUNNER'
|
|
621
|
+
#!/usr/bin/env bash
|
|
622
|
+
printf 'test-all\t%s\n' "$*" >> "${FAKE_LOG:?}"
|
|
623
|
+
usage() {
|
|
624
|
+
cat <<'USAGE'
|
|
625
|
+
Usage: bash scripts/test-all.sh [--affected] [--full]
|
|
626
|
+
--affected run the suites this diff selects
|
|
627
|
+
--full run the entire battery
|
|
628
|
+
--print-selection print the suites this diff selects; runs nothing
|
|
629
|
+
USAGE
|
|
630
|
+
}
|
|
631
|
+
for a in "$@"; do
|
|
632
|
+
if [[ "$a" == "--print-selection" ]]; then
|
|
633
|
+
printf 'AFFECTED_SELECTED\tsuite\taffected\n'; exit 0
|
|
634
|
+
elif [[ "$a" == "--affected" ]]; then
|
|
635
|
+
exit "${FAKE_RUNNER_AFFECTED_RC:-${FAKE_RUNNER_RC:-0}}"
|
|
636
|
+
elif [[ "$a" == "--full" ]]; then
|
|
637
|
+
exit "${FAKE_RUNNER_FULL_RC:-${FAKE_RUNNER_RC:-0}}"
|
|
638
|
+
elif [[ "$a" == "--help" || "$a" == "-h" ]]; then
|
|
639
|
+
usage; exit 0
|
|
640
|
+
else
|
|
641
|
+
echo "runner stub: unexpected argv: $a" >&2; exit 64
|
|
642
|
+
fi
|
|
643
|
+
done
|
|
644
|
+
exit "${FAKE_RUNNER_RC:-0}"
|
|
645
|
+
RUNNER
|
|
646
|
+
chmod +x "$r/scripts/test-all.sh"
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
# A foreign scripts/test-all.sh (G2-1): a same-named script that belongs to the
|
|
650
|
+
# user's repo, advertises nothing, and must NEVER receive Soleur flags.
|
|
651
|
+
write_runner_foreign() { # <repo>
|
|
652
|
+
local r="$1"
|
|
653
|
+
mkdir -p "$r/scripts"
|
|
654
|
+
cat > "$r/scripts/test-all.sh" <<'RUNNER'
|
|
655
|
+
#!/usr/bin/env bash
|
|
656
|
+
printf 'foreign-runner\t%s\n' "$*" >> "${FAKE_LOG:-/dev/null}"
|
|
657
|
+
echo "ok"
|
|
658
|
+
exit 0
|
|
659
|
+
RUNNER
|
|
660
|
+
chmod +x "$r/scripts/test-all.sh"
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
# Foreign + comment-only mention (G2-4): the flag text appears in a comment —
|
|
664
|
+
# never at a case-label or usage-table position.
|
|
665
|
+
write_runner_comment_mention() { # <repo>
|
|
666
|
+
local r="$1"
|
|
667
|
+
mkdir -p "$r/scripts"
|
|
668
|
+
cat > "$r/scripts/test-all.sh" <<'RUNNER'
|
|
669
|
+
#!/usr/bin/env bash
|
|
670
|
+
# NOTE: --print-selection support is planned for a future release of this
|
|
671
|
+
# runner; until then it knows only how to run everything.
|
|
672
|
+
printf 'foreign-runner\t%s\n' "$*" >> "${FAKE_LOG:-/dev/null}"
|
|
673
|
+
echo "ok"
|
|
674
|
+
exit 0
|
|
675
|
+
RUNNER
|
|
676
|
+
chmod +x "$r/scripts/test-all.sh"
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
# ============================ run + assert plumbing ===========================
|
|
680
|
+
|
|
681
|
+
reset_fakes() {
|
|
682
|
+
: > "$CALLS"
|
|
683
|
+
export FAKE_VITEST_RC=0 FAKE_VITEST_EXPECT="" FAKE_VITEST_MAXW="" FAKE_VITEST_NOTESTS=0 FAKE_VITEST_SLEEP=""
|
|
684
|
+
export FAKE_JEST_RC=0 FAKE_JEST_EXPECT="" FAKE_JEST_REQUIRE_RELATED=0
|
|
685
|
+
export FAKE_PYTEST_RC=0 FAKE_PYTEST_EXPECT=""
|
|
686
|
+
export FAKE_PY_IMPORTABLE="pytest"
|
|
687
|
+
export FAKE_GO_TEST_RC=0 FAKE_GO_EXPECT="" FAKE_GO_DIR="" FAKE_GO_GOMOD="" FAKE_GO_REQUIRE_P=1
|
|
688
|
+
export FAKE_NX_RC=0 FAKE_NX_EXPECT="" FAKE_NX_PROJECTS="" FAKE_NX_LINE="" FAKE_NX_FULL_LINE="" FAKE_NX_FULL_RC=0
|
|
689
|
+
export FAKE_PYTEST_COLLECTED="" FAKE_PYTEST_DESELECTED=""
|
|
690
|
+
export FAKE_TURBO_RC=0 FAKE_TURBO_EXPECT=""
|
|
691
|
+
export FAKE_RUNNER_RC=0 FAKE_RUNNER_AFFECTED_RC="" FAKE_RUNNER_FULL_RC=""
|
|
692
|
+
export FAKE_PM_TEST_RC=0
|
|
693
|
+
export FAKE_GIT_FAIL_OTHERS=0
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
RSEQ=0; RC=0; OUT=""
|
|
697
|
+
_run_gate() { # <PATH> <repo> <kind:gate|explain|selfcheck> [gate args…]
|
|
698
|
+
local p="$1" repo="$2" kind="$3"; shift 3
|
|
699
|
+
RSEQ=$((RSEQ + 1))
|
|
700
|
+
( cd "$repo" && env -u CLAUDE_PLUGIN_ROOT \
|
|
701
|
+
PATH="$p" \
|
|
702
|
+
FAKE_LOG="$CALLS" FAKE_DIR="$TESTROOT" \
|
|
703
|
+
REAL_NODE="$REAL_NODE" REAL_PY="$REAL_PY" REAL_GIT="$REAL_GIT" \
|
|
704
|
+
bash "$SUT" "$@" ) > "$TESTROOT/outs/$RSEQ.out" 2>&1
|
|
705
|
+
RC=$?
|
|
706
|
+
OUT="$(cat "$TESTROOT/outs/$RSEQ.out")"
|
|
707
|
+
printf '%s\t%s\t%s\n' "$kind" "$RC" "$TESTROOT/outs/$RSEQ.out" >> "$RUNS"
|
|
708
|
+
}
|
|
709
|
+
run_gate() { _run_gate "$BIN:$BASEPATH" "$@"; }
|
|
710
|
+
run_gate_path() { local p="$1"; shift; _run_gate "$p" "$@"; }
|
|
711
|
+
|
|
712
|
+
# --- verdict helpers ----------------------------------------------------------
|
|
713
|
+
want_rc() {
|
|
714
|
+
if [[ "$RC" == "$1" ]]; then pass "$2"
|
|
715
|
+
else fail "$2 — rc=$RC want=$1 :: $(tail -n 3 "$TESTROOT/outs/$RSEQ.out" 2>/dev/null | tr '\n' '|')"; fi
|
|
716
|
+
}
|
|
717
|
+
want_rc_in() { # <label> <rc> <rc>…
|
|
718
|
+
local label="$1"; shift
|
|
719
|
+
local want; for want in "$@"; do [[ "$RC" == "$want" ]] && { pass "$label"; return; }; done
|
|
720
|
+
fail "$label — rc=$RC want one of {$*} :: $(tail -n 3 "$TESTROOT/outs/$RSEQ.out" 2>/dev/null | tr '\n' '|')"
|
|
721
|
+
}
|
|
722
|
+
want_out() { # <label> <ere>
|
|
723
|
+
if grep -qE "$2" <<<"$OUT"; then pass "$1"
|
|
724
|
+
else fail "$1 — output lacks /$2/ :: $(tail -n 5 "$TESTROOT/outs/$RSEQ.out" 2>/dev/null | tr '\n' '|')"; fi
|
|
725
|
+
}
|
|
726
|
+
want_no_out() { # <label> <ere>
|
|
727
|
+
if grep -qE "$2" <<<"$OUT"; then fail "$1 — output unexpectedly matches /$2/"
|
|
728
|
+
else pass "$1"; fi
|
|
729
|
+
}
|
|
730
|
+
want_call() { # <label> <ere> — the invocation ledger must show it
|
|
731
|
+
if grep -qE "$2" "$CALLS"; then pass "$1"
|
|
732
|
+
else fail "$1 — call log lacks /$2/ :: $(tr '\n' '|' < "$CALLS")"; fi
|
|
733
|
+
}
|
|
734
|
+
want_no_call() { # <label> <ere>
|
|
735
|
+
if grep -qE "$2" "$CALLS"; then fail "$1 — call log unexpectedly matches /$2/ :: $(tr '\n' '|' < "$CALLS")"
|
|
736
|
+
else pass "$1"; fi
|
|
737
|
+
}
|
|
738
|
+
log_nonempty() { [[ -s "${1:-$CALLS}" ]]; }
|
|
739
|
+
|
|
740
|
+
# AFFECTED_GATE marker field extractor: the LAST `AFFECTED_GATE ` line (the REFUSED
|
|
741
|
+
# and BUDGET_DEFERRED markers carry a `_` after GATE and never match the space).
|
|
742
|
+
gate_field() { # <field> -> value
|
|
743
|
+
sed -n 's/^.*AFFECTED_GATE \(.*\)$/\1/p' <<<"$OUT" | tail -n 1 \
|
|
744
|
+
| tr ' ' '\n' | sed -n "s/^$1=//p" | head -n 1
|
|
745
|
+
}
|
|
746
|
+
want_field() { # <label> <field> <expected-value-or-ere>
|
|
747
|
+
local got; got="$(gate_field "$2")"
|
|
748
|
+
if [[ "$got" =~ ^$3$ ]]; then pass "$1"
|
|
749
|
+
else fail "$1 — marker field $2='$got' want /^$3$/ :: $(grep -oE 'AFFECTED_GATE .*' <<<"$OUT" | tail -n 1)"; fi
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
# ================================ ROWS =======================================
|
|
753
|
+
|
|
754
|
+
printf '\n=== affected-tests.sh contract suite (#9307) ===\n'
|
|
755
|
+
|
|
756
|
+
# --- A1. SHOULD-PASS control: vitest repo, scoped diff, selected run -----------
|
|
757
|
+
# The unmutated fixture must be green before any mutation-row verdict means
|
|
758
|
+
# anything; a red control poisons every row below it.
|
|
759
|
+
R_CONTROL="$(mk_repo js-basic)"; seed_js "$R_CONTROL"; seal_base "$R_CONTROL"
|
|
760
|
+
commit_chg "$R_CONTROL" src/util.ts 'export const u = 1;'
|
|
761
|
+
reset_fakes
|
|
762
|
+
run_gate "$R_CONTROL" gate
|
|
763
|
+
CONTROL_OK=0
|
|
764
|
+
[[ "$RC" == "0" ]] && CONTROL_OK=1
|
|
765
|
+
want_rc 0 "A1 control: vitest fixture exits 0"
|
|
766
|
+
want_out "A1 control: human sentence" "Ran [0-9]+ of the repo's [0-9]+ test files affected by this change: passed\."
|
|
767
|
+
want_call "A1 control: vitest invoked in related form" 'vitest.*related'
|
|
768
|
+
want_call "A1 control: --run on the related call" 'vitest.*related.*--run'
|
|
769
|
+
want_call "A1 control: the changed file reaches the selector" 'related.*util\.ts'
|
|
770
|
+
want_field "A1 control: mode" mode 'selected'
|
|
771
|
+
want_field "A1 control: stack named" stack '.+'
|
|
772
|
+
want_out "A1 control: machine marker shape" 'AFFECTED_GATE ran=[0-9-]+ of=[0-9-]+ skipped=[0-9-]+ mode='
|
|
773
|
+
if [[ "$CONTROL_OK" != "1" ]]; then
|
|
774
|
+
printf ' [CONTROL-RED] the unmutated fixture failed — every verdict below is suspect\n' >&2
|
|
775
|
+
fi
|
|
776
|
+
|
|
777
|
+
# --- A2. --explain prints the selection and runs nothing -----------------------
|
|
778
|
+
reset_fakes
|
|
779
|
+
run_gate "$R_CONTROL" explain --explain
|
|
780
|
+
want_rc 0 "A2: --explain exits 0"
|
|
781
|
+
if [[ -s "$CALLS" ]]; then fail "A2: --explain invoked a runner: $(tr '\n' '|' < "$CALLS")"; else pass "A2: --explain ran nothing"; fi
|
|
782
|
+
# The marker alone always carries reason=/mode= — pin the EXPLAIN-only lines:
|
|
783
|
+
# the base dump, the per-path classification, and the SELECTION verdict.
|
|
784
|
+
want_out "A2: --explain prints the base dump" 'AFFECTED_EXPLAIN base='
|
|
785
|
+
want_out "A2: --explain lists per-path reasons" 'PATH src/util\.ts class=changed'
|
|
786
|
+
want_out "A2: --explain prints the selection verdict" 'SELECTION mode=selected reason='
|
|
787
|
+
|
|
788
|
+
# --- A3. --self-check ----------------------------------------------------------
|
|
789
|
+
reset_fakes
|
|
790
|
+
run_gate "$R_CONTROL" selfcheck --self-check
|
|
791
|
+
want_rc 0 "A3: --self-check exits 0"
|
|
792
|
+
want_out "A3: self-check marker" 'AFFECTED_GATE_SELF_CHECK ok'
|
|
793
|
+
|
|
794
|
+
# ============================ Guard 2 — capability probe =======================
|
|
795
|
+
|
|
796
|
+
# B1 [G2-2 inverse, G2-1 inverse]: a runner registering --print-selection at a
|
|
797
|
+
# case-label position IS delegated to, and the stack adapters stay dark.
|
|
798
|
+
R_CAP="$(mk_repo capable)"; write_runner_capable "$R_CAP"; seed_js "$R_CAP"; seal_base "$R_CAP"
|
|
799
|
+
commit_chg "$R_CAP" src/util.ts 'export const u = 1;'
|
|
800
|
+
reset_fakes
|
|
801
|
+
run_gate "$R_CAP" gate
|
|
802
|
+
want_rc 0 "B1: capability-positive runner delegates cleanly"
|
|
803
|
+
want_call "B1: runner received --affected" 'test-all.*--affected'
|
|
804
|
+
want_no_call "B1: adapters stay dark under delegation" 'vitest '
|
|
805
|
+
want_out "B1: a delegated pass still prints the verdict line" 'affected-suite gate passed\.'
|
|
806
|
+
|
|
807
|
+
# B1b: --full passthrough hands --full to the same runner.
|
|
808
|
+
reset_fakes
|
|
809
|
+
run_gate "$R_CAP" gate --full
|
|
810
|
+
want_rc 0 "B1b: --full passthrough exits 0"
|
|
811
|
+
want_call "B1b: runner received --full" 'test-all.*--full'
|
|
812
|
+
want_no_call "B1b: --full never reaches an adapter" 'vitest '
|
|
813
|
+
|
|
814
|
+
# B2 [G2-3]: test_command outranks even a capability-positive runner.
|
|
815
|
+
R_CUST="$(mk_repo custom-cmd)"; write_runner_capable "$R_CUST"; seed_js "$R_CUST"
|
|
816
|
+
mkdir -p "$R_CUST/scripts"
|
|
817
|
+
cat > "$R_CUST/scripts/custom.sh" <<'FIX'
|
|
818
|
+
#!/usr/bin/env bash
|
|
819
|
+
printf 'custom\tcwd=%s\n' "$PWD" >> "${FAKE_LOG:?}"
|
|
820
|
+
exit 0
|
|
821
|
+
FIX
|
|
822
|
+
chmod +x "$R_CUST/scripts/custom.sh"
|
|
823
|
+
write_config "$R_CUST" '{"test_command": "bash scripts/custom.sh"}'
|
|
824
|
+
seal_base "$R_CUST"
|
|
825
|
+
commit_chg "$R_CUST" src/util.ts 'export const u = 1;'
|
|
826
|
+
reset_fakes
|
|
827
|
+
run_gate "$R_CUST" gate
|
|
828
|
+
want_rc 0 "B2: test_command fixture exits 0"
|
|
829
|
+
want_call "B2: the custom command ran from the repo root" "custom cwd=$R_CUST"
|
|
830
|
+
want_no_call "B2: test_command bypasses the repo runner" 'test-all '
|
|
831
|
+
want_no_call "B2: test_command bypasses adapters" 'vitest '
|
|
832
|
+
want_field "B2: mode=custom" mode 'custom'
|
|
833
|
+
|
|
834
|
+
# B2b: --explain under test_command must answer BEFORE the arm runs — the custom
|
|
835
|
+
# command IS the whole selection; explain emits mode=custom + the command, rc 0.
|
|
836
|
+
reset_fakes
|
|
837
|
+
run_gate "$R_CUST" explain --explain
|
|
838
|
+
want_rc 0 "B2b: --explain under test_command exits 0"
|
|
839
|
+
want_out "B2b: explain reports mode=custom" 'mode=custom'
|
|
840
|
+
want_out "B2b: the configured command is printed" 'bash scripts/custom\.sh'
|
|
841
|
+
want_no_call "B2b: explain ran nothing (no command, no runner, no adapter)" 'custom|test-all|vitest'
|
|
842
|
+
|
|
843
|
+
# B3 [G2-1]: a foreign same-named runner is never handed Soleur flags — the diff
|
|
844
|
+
# falls through to the stack adapter.
|
|
845
|
+
R_FOR="$(mk_repo foreign)"; write_runner_foreign "$R_FOR"; seed_js "$R_FOR"; seal_base "$R_FOR"
|
|
846
|
+
commit_chg "$R_FOR" src/util.ts 'export const u = 1;'
|
|
847
|
+
reset_fakes
|
|
848
|
+
run_gate "$R_FOR" gate
|
|
849
|
+
want_rc 0 "B3: foreign-runner repo still gates green via the adapter"
|
|
850
|
+
want_no_call "B3: foreign runner never invoked" 'foreign-runner|test-all'
|
|
851
|
+
want_call "B3: the JS adapter ran instead" 'vitest.*related'
|
|
852
|
+
|
|
853
|
+
# B4 [G2-4]: a comment-only mention of --print-selection is not a registration.
|
|
854
|
+
R_COM="$(mk_repo comment-mention)"; write_runner_comment_mention "$R_COM"; seed_js "$R_COM"; seal_base "$R_COM"
|
|
855
|
+
commit_chg "$R_COM" src/util.ts 'export const u = 1;'
|
|
856
|
+
reset_fakes
|
|
857
|
+
run_gate "$R_COM" gate
|
|
858
|
+
want_rc 0 "B4: comment-mention repo gates green via the adapter"
|
|
859
|
+
want_no_call "B4: comment mention is not a capability" 'foreign-runner|test-all'
|
|
860
|
+
want_call "B4: the JS adapter ran instead" 'vitest.*related'
|
|
861
|
+
|
|
862
|
+
# B5 [G2-5 must-PASS]: usage-table-only registration still delegates.
|
|
863
|
+
R_UT="$(mk_repo usage-table)"; write_runner_usage_table "$R_UT"; seed_js "$R_UT"; seal_base "$R_UT"
|
|
864
|
+
commit_chg "$R_UT" src/util.ts 'export const u = 1;'
|
|
865
|
+
reset_fakes
|
|
866
|
+
run_gate "$R_UT" gate
|
|
867
|
+
want_rc 0 "B5: usage-table-only registration delegates cleanly"
|
|
868
|
+
want_call "B5: runner received --affected" 'test-all.*--affected'
|
|
869
|
+
want_no_call "B5: adapters stay dark under delegation" 'vitest '
|
|
870
|
+
|
|
871
|
+
# B6 — delegated runner exit-code mapping (contract: its 4 → 4 runner-refused,
|
|
872
|
+
# its 3 → 1 runner-signal; a plain 1 stays 1).
|
|
873
|
+
reset_fakes; export FAKE_RUNNER_AFFECTED_RC=4
|
|
874
|
+
run_gate "$R_CAP" gate
|
|
875
|
+
want_rc 4 "B6a: runner rc 4 maps to gate rc 4"
|
|
876
|
+
want_out "B6a: runner-refused reason" 'AFFECTED_GATE_REFUSED.*reason=runner-refused'
|
|
877
|
+
|
|
878
|
+
reset_fakes; export FAKE_RUNNER_AFFECTED_RC=3
|
|
879
|
+
run_gate "$R_CAP" gate
|
|
880
|
+
want_rc 1 "B6b: runner rc 3 maps to gate rc 1"
|
|
881
|
+
want_out "B6b: runner-signal reason" 'runner-signal'
|
|
882
|
+
|
|
883
|
+
reset_fakes; export FAKE_RUNNER_AFFECTED_RC=1
|
|
884
|
+
run_gate "$R_CAP" gate
|
|
885
|
+
want_rc 1 "B6c: runner rc 1 stays rc 1"
|
|
886
|
+
|
|
887
|
+
# A runner usage/refusal rc 2 is a REFUSAL, not a plain failure — it must map
|
|
888
|
+
# to rc 4 runner-refused the same as the runner's own 4.
|
|
889
|
+
reset_fakes; export FAKE_RUNNER_AFFECTED_RC=2
|
|
890
|
+
run_gate "$R_CAP" gate
|
|
891
|
+
want_rc 4 "B6d: runner rc 2 maps to gate rc 4"
|
|
892
|
+
want_out "B6d: runner-refused reason" 'AFFECTED_GATE_REFUSED.*reason=runner-refused'
|
|
893
|
+
|
|
894
|
+
# B7 — the Soleur repo itself delegates: --explain in the real tree must resolve
|
|
895
|
+
# to the repo runner or name its mode. Bounded at 120 s (the affected pre-pass is
|
|
896
|
+
# measured ~150 s contended on a busy host, seconds here). REAL_NODE/REAL_PY are
|
|
897
|
+
# passed exactly as run_gate does: the real repo carries a .soleur-test-gate.json,
|
|
898
|
+
# and the shimmed interpreters must delegate the config dump or the gate reads a
|
|
899
|
+
# fake-unparseable config and refuses — a harness artifact, not a gate defect.
|
|
900
|
+
reset_fakes
|
|
901
|
+
# Portability: bound with the gate's own affected_timeout chain (timeout →
|
|
902
|
+
# gtimeout → watchdog) — a bare `timeout` is absent on stock macOS.
|
|
903
|
+
# shellcheck source=plugins/soleur/scripts/lib/affected-tests/common.sh
|
|
904
|
+
source "$DIR/lib/affected-tests/common.sh"
|
|
905
|
+
( cd "$REPO_ROOT" && affected_timeout 120 \
|
|
906
|
+
env -u CLAUDE_PLUGIN_ROOT PATH="$BIN:$BASEPATH" FAKE_LOG="$CALLS" \
|
|
907
|
+
REAL_NODE="$REAL_NODE" REAL_PY="$REAL_PY" \
|
|
908
|
+
bash "$SUT" --explain ) > "$TESTROOT/outs/live.out" 2>&1
|
|
909
|
+
RC=$?; OUT="$(cat "$TESTROOT/outs/live.out")"
|
|
910
|
+
printf 'explain\t%s\t%s\n' "$RC" "$TESTROOT/outs/live.out" >> "$RUNS"
|
|
911
|
+
want_rc 0 "B7: --explain in the Soleur repo exits 0"
|
|
912
|
+
# mode=/delegat as bare alternation legs are vacuous (every marker carries
|
|
913
|
+
# mode=): require the EXPLAIN-only emitters — a delegation line or the
|
|
914
|
+
# adapter-arm explain dump.
|
|
915
|
+
want_out "B7: live explain resolves to the repo runner or names its mode" 'SELECTION mode=delegated|SELECTION mode=(selected|full|custom|none)|AFFECTED_EXPLAIN base='
|
|
916
|
+
|
|
917
|
+
# B8 [arch-P1 regression]: the capability probe runs BEFORE the gate's inert/
|
|
918
|
+
# global classification — an all-inert diff under a capability-positive runner
|
|
919
|
+
# still delegates, because the runner's own classifier (and its always-on
|
|
920
|
+
# ratchets, which are live edges on AGENTS.md/SKILL.md diffs) is strictly
|
|
921
|
+
# better informed. The gate-side inert shortcut is the adapter arm's alone.
|
|
922
|
+
R_MD_RUNNER="$(mk_repo inert-delegated)"; write_runner_capable "$R_MD_RUNNER"; seal_base "$R_MD_RUNNER"
|
|
923
|
+
commit_chg "$R_MD_RUNNER" README.md 'docs only'
|
|
924
|
+
reset_fakes
|
|
925
|
+
run_gate "$R_MD_RUNNER" gate
|
|
926
|
+
want_rc 0 "B8: md-only diff delegates to the capable runner"
|
|
927
|
+
want_call "B8: runner received --affected on an inert diff" 'test-all.*--affected'
|
|
928
|
+
want_no_out "B8: the gate's inert shortcut does not preempt delegation" 'NO_TESTS_AFFECTED|reason=inert'
|
|
929
|
+
want_field "B8: delegated marker reason" reason 'delegated'
|
|
930
|
+
|
|
931
|
+
# B8b: a global-paths diff delegates the same way — the runner's classifier
|
|
932
|
+
# outranks the gate's global table; only an operator --full maps to --full.
|
|
933
|
+
R_GLOB_RUNNER="$(mk_repo global-delegated)"; write_runner_capable "$R_GLOB_RUNNER"; seal_base "$R_GLOB_RUNNER"
|
|
934
|
+
commit_chg "$R_GLOB_RUNNER" package.json '{"name":"x","scripts":{"test":"true"}}'
|
|
935
|
+
reset_fakes
|
|
936
|
+
run_gate "$R_GLOB_RUNNER" gate
|
|
937
|
+
want_rc 0 "B8b: global-paths diff delegates"
|
|
938
|
+
want_call "B8b: runner received --affected, not --full" 'test-all.*--affected'
|
|
939
|
+
want_no_call "B8b: the gate's global table did not force --full" 'test-all.*--full'
|
|
940
|
+
|
|
941
|
+
# B8c: --explain under a capable runner reports the delegation and runs
|
|
942
|
+
# nothing — not even --print-selection (the runner's own pre-pass has a cost).
|
|
943
|
+
reset_fakes
|
|
944
|
+
run_gate "$R_MD_RUNNER" explain --explain
|
|
945
|
+
want_rc 0 "B8c: --explain under a capable runner exits 0"
|
|
946
|
+
want_out "B8c: explain reports the delegation" 'mode=delegated|test-all\.sh'
|
|
947
|
+
want_no_call "B8c: explain invoked nothing" 'test-all'
|
|
948
|
+
|
|
949
|
+
# ============================ Guard 3 — config fails closed ===================
|
|
950
|
+
|
|
951
|
+
mk_cfg_repo() { # <name> <json> — vitest repo whose config is committed in the BASE
|
|
952
|
+
# (a config sitting in the diff would itself be an unclassified change)
|
|
953
|
+
local r; r="$(mk_repo "$1")"; seed_js "$r"; write_config "$r" "$2"; seal_base "$r"
|
|
954
|
+
commit_chg "$r" src/util.ts 'export const u = 1;'
|
|
955
|
+
printf '%s' "$r"
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
# C1 [G3-3]: malformed JSON → rc 4 bad-config.
|
|
959
|
+
R_BADJSON="$(mk_cfg_repo cfg-malformed '{')"
|
|
960
|
+
reset_fakes; run_gate "$R_BADJSON" gate
|
|
961
|
+
want_rc 4 "C1: malformed config refused"
|
|
962
|
+
want_out "C1: bad-config reason" 'AFFECTED_GATE_REFUSED.*reason=bad-config'
|
|
963
|
+
|
|
964
|
+
# C2 [G3-1, G3-4]: unknown key → rc 4, and the reason must NAME the key — a
|
|
965
|
+
# validator that greps key names but never reads values cannot emit it.
|
|
966
|
+
R_UNK="$(mk_cfg_repo cfg-unknown '{"surprise_key": 1}')"
|
|
967
|
+
reset_fakes; run_gate "$R_UNK" gate
|
|
968
|
+
want_rc 4 "C2: unknown key refused"
|
|
969
|
+
want_out "C2: reason names the key" 'bad-config.*surprise_key|surprise_key.*bad-config'
|
|
970
|
+
|
|
971
|
+
# C3 [G3-2]: wrong type → rc 4 bad-config key=<k>.
|
|
972
|
+
R_WT="$(mk_cfg_repo cfg-wrong-type '{"budget_seconds": "900"}')"
|
|
973
|
+
reset_fakes; run_gate "$R_WT" gate
|
|
974
|
+
want_rc 4 "C3: string budget_seconds refused"
|
|
975
|
+
want_out "C3: reason names the key" 'bad-config.*budget_seconds|budget_seconds.*bad-config'
|
|
976
|
+
R_WT2="$(mk_cfg_repo cfg-wrong-type2 '{"global_paths": "src/**"}')"
|
|
977
|
+
reset_fakes; run_gate "$R_WT2" gate
|
|
978
|
+
want_rc 4 "C3b: string global_paths refused"
|
|
979
|
+
want_out "C3b: reason names the key" 'bad-config.*global_paths|global_paths.*bad-config'
|
|
980
|
+
|
|
981
|
+
# C4 [G3-5 must-PASS]: config carrying only max_workers is valid and caps width.
|
|
982
|
+
R_MW="$(mk_cfg_repo cfg-maxw '{"max_workers": 3}')"
|
|
983
|
+
reset_fakes; export FAKE_VITEST_MAXW=3
|
|
984
|
+
run_gate "$R_MW" gate
|
|
985
|
+
want_rc 0 "C4: max_workers-only config accepted"
|
|
986
|
+
want_call "C4: --maxWorkers honoured at the cap" 'vitest.*[Mm]ax.?[Ww]orkers'
|
|
987
|
+
|
|
988
|
+
# C5: inert_paths REPLACES built-ins — docs/** is inert, README.md no longer is.
|
|
989
|
+
# Inert-only diff: build the fixture WITHOUT the src/util.ts change mk_cfg_repo
|
|
990
|
+
# bakes in — the config sits in the base and the sole post-base change is inert.
|
|
991
|
+
R_INERT="$(mk_repo cfg-inert)"; seed_js "$R_INERT"
|
|
992
|
+
write_config "$R_INERT" '{"inert_paths": ["docs/**"]}'; seal_base "$R_INERT"
|
|
993
|
+
commit_chg "$R_INERT" docs/guide.txt 'docs'
|
|
994
|
+
reset_fakes; run_gate "$R_INERT" gate
|
|
995
|
+
want_rc 0 "C5a: all-inert custom diff exits 0"
|
|
996
|
+
want_out "C5a: NO_TESTS_AFFECTED" 'NO_TESTS_AFFECTED'
|
|
997
|
+
want_out "C5a: inert_rule reported" 'inert_rule='
|
|
998
|
+
R_INERT2="$(mk_cfg_repo cfg-inert2 '{"inert_paths": ["docs/**"]}')"
|
|
999
|
+
commit_chg "$R_INERT2" README.md 'readme change'
|
|
1000
|
+
reset_fakes; run_gate "$R_INERT2" gate
|
|
1001
|
+
want_no_out "C5b: README.md is NOT inert once inert_paths replaces the built-ins" 'NO_TESTS_AFFECTED'
|
|
1002
|
+
want_rc_in "C5b: replaced-inert README diff is refused or falls back to full" 0 4
|
|
1003
|
+
# `reason=|mode=full` is vacuous — every marker carries reason=. Pin the named
|
|
1004
|
+
# non-refusal reason set: on rc 0 the verdict must disclose WHY it ran what it ran.
|
|
1005
|
+
if [[ "$RC" == "0" ]]; then
|
|
1006
|
+
want_out "C5b: fallback discloses a named reason" 'reason=(ok|empty-selection|empty-diff|census-degraded|global-paths|full-requested|no-selector|custom-command|delegated)'
|
|
1007
|
+
fi
|
|
1008
|
+
|
|
1009
|
+
# C6: a global_paths hit (package.json is a built-in) forces the full command.
|
|
1010
|
+
R_GLOB="$(mk_repo global-hit)"; seed_js "$R_GLOB"; seal_base "$R_GLOB"
|
|
1011
|
+
cat > "$R_GLOB/package.json" <<'JSON'
|
|
1012
|
+
{ "name": "fixture-js", "private": true, "scripts": { "test": "vitest run" },
|
|
1013
|
+
"devDependencies": { "vitest": "^4.0.0" }, "x": 1 }
|
|
1014
|
+
JSON
|
|
1015
|
+
assert_fixture_dir "$R_GLOB"; git -C "$R_GLOB" add -A; git -C "$R_GLOB" commit -q -m "bump package.json"
|
|
1016
|
+
reset_fakes; run_gate "$R_GLOB" gate
|
|
1017
|
+
want_rc 0 "C6: global-path diff exits 0 via the full command"
|
|
1018
|
+
want_field "C6: mode=full" mode 'full'
|
|
1019
|
+
want_no_call "C6: no related selector under a global hit" 'related'
|
|
1020
|
+
# A bare 'vitest|npm|…' leg is satisfied by the toolchain's --version probe —
|
|
1021
|
+
# pin the invocation SHAPE of a test run, never the probe.
|
|
1022
|
+
want_call "C6: the full command did run" 'vitest run |npm test|pnpm test|yarn test|bun test'
|
|
1023
|
+
|
|
1024
|
+
# C7 [G1-7]: vacuous test_command values → rc 4 vacuous-test-command, each
|
|
1025
|
+
# spelling — INCLUDING the composition bypasses: a true-swallower tail
|
|
1026
|
+
# (`|| true`, `; exit 0`, `| true`), exit-inside-subcommand, comment padding.
|
|
1027
|
+
for _vac in 'true' ':' 'exit 0' 'echo' \
|
|
1028
|
+
'npm test || true' 'npm test || :' 'make test; exit 0' \
|
|
1029
|
+
'pytest && true' 'npm test | true' \
|
|
1030
|
+
"bash -c 'exit 0'" 'sh -c "exit 0"' 'true # comment' \
|
|
1031
|
+
'echo done; exit 0' 'env X=1 bash -c "exit 0"'; do
|
|
1032
|
+
_r="$(mk_cfg_repo "cfg-vac-$(printf '%s' "$_vac" | tr -c 'a-z0-9' '_' | head -c 40)" "{\"test_command\": \"$(printf '%s' "$_vac" | sed 's/"/\\"/g')\"}")"
|
|
1033
|
+
reset_fakes; run_gate "$_r" gate
|
|
1034
|
+
want_rc 4 "C7: test_command '$_vac' refused"
|
|
1035
|
+
want_out "C7: vacuous reason for '$_vac'" 'AFFECTED_GATE_REFUSED.*reason=vacuous-test-command'
|
|
1036
|
+
done
|
|
1037
|
+
unset _vac _r
|
|
1038
|
+
# C7b must-PASS: a real command still runs — `echo` mid-command is not a
|
|
1039
|
+
# swallower when the LAST segment is substantive.
|
|
1040
|
+
R_VACOK="$(mk_cfg_repo cfg-vac-ok '{"test_command": "echo running && bash scripts/custom.sh"}')"
|
|
1041
|
+
mkdir -p "$R_VACOK/scripts"
|
|
1042
|
+
cat > "$R_VACOK/scripts/custom.sh" <<'FIX'
|
|
1043
|
+
#!/usr/bin/env bash
|
|
1044
|
+
printf 'custom\tcwd=%s\n' "$PWD" >> "${FAKE_LOG:?}"
|
|
1045
|
+
exit 0
|
|
1046
|
+
FIX
|
|
1047
|
+
chmod +x "$R_VACOK/scripts/custom.sh"
|
|
1048
|
+
assert_fixture_dir "$R_VACOK"; git -C "$R_VACOK" add -A; git -C "$R_VACOK" commit -q -m "add custom runner"
|
|
1049
|
+
reset_fakes; run_gate "$R_VACOK" gate
|
|
1050
|
+
want_rc 0 "C7b: substantive test_command accepted"
|
|
1051
|
+
want_call "C7b: the custom command ran" 'custom cwd='
|
|
1052
|
+
|
|
1053
|
+
# C8: non-positive budget_seconds / max_workers fail closed as bad-config — a
|
|
1054
|
+
# zero budget would defer instantly and a non-positive width is nonsense.
|
|
1055
|
+
R_CL1="$(mk_cfg_repo cfg-budget-zero '{"budget_seconds": 0}')"
|
|
1056
|
+
reset_fakes; run_gate "$R_CL1" gate
|
|
1057
|
+
want_rc 4 "C8a: budget_seconds=0 refused"
|
|
1058
|
+
want_out "C8a: reason names the key" 'bad-config.*budget_seconds|budget_seconds.*bad-config'
|
|
1059
|
+
R_CL2="$(mk_cfg_repo cfg-maxw-neg '{"max_workers": -2}')"
|
|
1060
|
+
reset_fakes; run_gate "$R_CL2" gate
|
|
1061
|
+
want_rc 4 "C8b: negative max_workers refused"
|
|
1062
|
+
want_out "C8b: reason names the key" 'bad-config.*max_workers|max_workers.*bad-config'
|
|
1063
|
+
want_out "C8b: refusal carries a remedy pointer" 'remedy:'
|
|
1064
|
+
|
|
1065
|
+
# C9: a match-everything inert_paths pattern is a permanent green — refused as
|
|
1066
|
+
# bad-config, never silently applied. `*`/`**`/`**/*` and any pattern that
|
|
1067
|
+
# matches an arbitrary probe path all fail closed.
|
|
1068
|
+
_ipn=0
|
|
1069
|
+
for _ip in '*' '**' '**/*' '*/**'; do
|
|
1070
|
+
_ipn=$((_ipn + 1)) # `**/*` and `*/**` both sanitise to `____` — index the name
|
|
1071
|
+
_r="$(mk_cfg_repo "cfg-inertstar-${_ipn}" "{\"inert_paths\": [\"$_ip\"]}")"
|
|
1072
|
+
reset_fakes; run_gate "$_r" gate
|
|
1073
|
+
want_rc 4 "C9: inert_paths '$_ip' refused"
|
|
1074
|
+
want_out "C9: refusal names inert_paths for '$_ip'" 'bad-config.*inert_paths|inert_paths.*bad-config'
|
|
1075
|
+
want_no_call "C9: nothing ran for '$_ip'" 'vitest '
|
|
1076
|
+
done
|
|
1077
|
+
unset _ip _r _ipn
|
|
1078
|
+
# C9b must-PASS: a real inert pattern still classifies (docs/** inert over a
|
|
1079
|
+
# docs-only diff → NO_TESTS_AFFECTED, same shape as C5a).
|
|
1080
|
+
R_INOK="$(mk_repo cfg-inert-ok)"; seed_js "$R_INOK"
|
|
1081
|
+
write_config "$R_INOK" '{"inert_paths": ["docs/**", "notes.txt"]}'; seal_base "$R_INOK"
|
|
1082
|
+
commit_chg "$R_INOK" docs/guide.md 'docs'
|
|
1083
|
+
reset_fakes; run_gate "$R_INOK" gate
|
|
1084
|
+
want_rc 0 "C9b: a non-total inert_paths set is accepted"
|
|
1085
|
+
want_out "C9b: inert verdict" 'NO_TESTS_AFFECTED'
|
|
1086
|
+
|
|
1087
|
+
# C10: the jq config decoder actually DECODES — a jq-only host (no python3, no
|
|
1088
|
+
# node on PATH) must still honour test_command. Literal NULs in the jq program
|
|
1089
|
+
# were stripped by bash before this row existed; the pinned engine is real jq.
|
|
1090
|
+
JQBIN="$TESTROOT/bin-jqonly"; mkdir -p "$JQBIN"
|
|
1091
|
+
for _t in jq git bash env grep find sed awk sort sort tr cut head tail cat date \
|
|
1092
|
+
sleep ps getconf sysctl mktemp rm wc dirname basename uname comm ls \
|
|
1093
|
+
mkdir chmod touch printf od; do
|
|
1094
|
+
_p="$(command -v "$_t" 2>/dev/null || true)"
|
|
1095
|
+
[[ -n "$_p" && "$_p" != "$BIN"/* ]] && ln -sf "$_p" "$JQBIN/$_t"
|
|
1096
|
+
done
|
|
1097
|
+
for _t in vitest jest pytest go nx turbo npm npx pnpm yarn bun bunx; do
|
|
1098
|
+
ln -sf "$BIN/$_t" "$JQBIN/$_t"
|
|
1099
|
+
done
|
|
1100
|
+
if [[ -x "$JQBIN/jq" && -x "$JQBIN/git" && -x "$JQBIN/bash" ]]; then
|
|
1101
|
+
R_JQONLY="$(mk_repo jq-only)"; seed_js "$R_JQONLY"
|
|
1102
|
+
mkdir -p "$R_JQONLY/scripts"
|
|
1103
|
+
cat > "$R_JQONLY/scripts/custom.sh" <<'FIX'
|
|
1104
|
+
#!/usr/bin/env bash
|
|
1105
|
+
printf 'custom\tcwd=%s\n' "$PWD" >> "${FAKE_LOG:?}"
|
|
1106
|
+
exit 0
|
|
1107
|
+
FIX
|
|
1108
|
+
chmod +x "$R_JQONLY/scripts/custom.sh"
|
|
1109
|
+
write_config "$R_JQONLY" '{"test_command": "bash scripts/custom.sh"}'
|
|
1110
|
+
seal_base "$R_JQONLY"
|
|
1111
|
+
commit_chg "$R_JQONLY" src/util.ts 'export const u = 1;'
|
|
1112
|
+
reset_fakes
|
|
1113
|
+
run_gate_path "$JQBIN" "$R_JQONLY" gate
|
|
1114
|
+
want_rc 0 "C10: jq-only host parses the config"
|
|
1115
|
+
want_field "C10: jq decode reached the custom arm" mode 'custom'
|
|
1116
|
+
want_call "C10: the configured command ran" 'custom cwd='
|
|
1117
|
+
else
|
|
1118
|
+
fail "C10: cannot build the jq-only PATH fixture (missing jq/git/bash on host)"
|
|
1119
|
+
fi
|
|
1120
|
+
unset _t _p
|
|
1121
|
+
|
|
1122
|
+
# C10b: a top-level NON-OBJECT config must refuse identically on every decoder.
|
|
1123
|
+
# py/node exit 3 on `[]`; a bare jq `to_entries` over `[]` decodes to ZERO
|
|
1124
|
+
# records — a silent accept (no keys, no badkey) unless the jq arm checks
|
|
1125
|
+
# `type == "object"` first. rc 4 bad-config is the contract.
|
|
1126
|
+
if [[ -x "$JQBIN/jq" && -x "$JQBIN/git" && -x "$JQBIN/bash" ]]; then
|
|
1127
|
+
R_JQARR="$(mk_repo jq-array)"; seed_js "$R_JQARR"
|
|
1128
|
+
write_config "$R_JQARR" '[]'
|
|
1129
|
+
seal_base "$R_JQARR"
|
|
1130
|
+
commit_chg "$R_JQARR" src/util.ts 'export const u = 1;'
|
|
1131
|
+
reset_fakes
|
|
1132
|
+
run_gate_path "$JQBIN" "$R_JQARR" gate
|
|
1133
|
+
want_rc 4 "C10b: jq-only host refuses a top-level array config"
|
|
1134
|
+
want_out "C10b: bad-config reason" 'AFFECTED_GATE_REFUSED.*reason=bad-config'
|
|
1135
|
+
fi
|
|
1136
|
+
|
|
1137
|
+
# ============================ Guard 1 — no vacuous green =======================
|
|
1138
|
+
|
|
1139
|
+
# D1: an all-inert diff (README.md only) is the ONLY rc-0-with-zero-tests path.
|
|
1140
|
+
R_MD="$(mk_repo inert-md)"; seed_js "$R_MD"; seal_base "$R_MD"
|
|
1141
|
+
commit_chg "$R_MD" README.md 'docs only'
|
|
1142
|
+
reset_fakes; run_gate "$R_MD" gate
|
|
1143
|
+
want_rc 0 "D1: docs-only diff exits 0"
|
|
1144
|
+
want_out "D1: NO_TESTS_AFFECTED" 'NO_TESTS_AFFECTED'
|
|
1145
|
+
want_out "D1: human inert line" 'No tests ran: docs-only change\.'
|
|
1146
|
+
want_out "D1: inert_rule reported" 'inert_rule='
|
|
1147
|
+
if [[ -s "$CALLS" ]]; then fail "D1: inert diff invoked a runner: $(tr '\n' '|' < "$CALLS")"; else pass "D1: inert diff ran nothing"; fi
|
|
1148
|
+
|
|
1149
|
+
# D2 [G1-3]: inert-then-code — a first-path inert shortcut would swallow src/util.ts.
|
|
1150
|
+
R_MIX="$(mk_repo mixed)"; seed_js "$R_MIX"; seal_base "$R_MIX"
|
|
1151
|
+
commit_chg "$R_MIX" README.md 'docs'
|
|
1152
|
+
commit_chg "$R_MIX" src/util.ts 'export const u = 1;'
|
|
1153
|
+
reset_fakes; run_gate "$R_MIX" gate
|
|
1154
|
+
want_rc 0 "D2: mixed diff exits 0 via real selection"
|
|
1155
|
+
want_no_out "D2: mixed diff is never inert" 'NO_TESTS_AFFECTED'
|
|
1156
|
+
want_call "D2: the code file was selected" 'related.*util\.ts'
|
|
1157
|
+
|
|
1158
|
+
# D3 [G1-1]: the native selector returns nothing for a code diff → full fallback,
|
|
1159
|
+
# never a zero-test green. rc 0 is acceptable ONLY with mode=full and a named
|
|
1160
|
+
# reason; rc 4 is an acceptable loud refusal.
|
|
1161
|
+
R_EMPTY="$(mk_repo empty-sel)"; seed_js "$R_EMPTY"; seal_base "$R_EMPTY"
|
|
1162
|
+
commit_chg "$R_EMPTY" src/orphan.ts 'export const o = 1;'
|
|
1163
|
+
reset_fakes; export FAKE_VITEST_NOTESTS=1
|
|
1164
|
+
run_gate "$R_EMPTY" gate
|
|
1165
|
+
if [[ "$RC" == "0" ]]; then
|
|
1166
|
+
pass "D3: empty selection resolved to a completed run"
|
|
1167
|
+
# Contract: the full command ran and the fallback is DISCLOSED. mode=full is
|
|
1168
|
+
# one spelling; mode=selected with reason=empty-selection is accepted too —
|
|
1169
|
+
# a bare green with no named reason is the only failure.
|
|
1170
|
+
want_out "D3: the empty-selection fallback is disclosed" 'reason=[^ ]*(empty|no.test|select)|mode=full'
|
|
1171
|
+
elif [[ "$RC" == "4" ]]; then
|
|
1172
|
+
pass "D3: empty selection refused loudly (rc 4)"
|
|
1173
|
+
else
|
|
1174
|
+
fail "D3: empty selection produced rc=$RC — a zero-test rc 0 is the Guard-1 defect"
|
|
1175
|
+
fi
|
|
1176
|
+
|
|
1177
|
+
# D3b: an unclassifiable changed file (not inert, not global, not a test or source
|
|
1178
|
+
# path) yields an empty native selection on the Python adapter → full fallback.
|
|
1179
|
+
R_EMPTY2="$(mk_repo empty-sel-py)"; seed_python "$R_EMPTY2"; seal_base "$R_EMPTY2"
|
|
1180
|
+
commit_chg "$R_EMPTY2" assets/logo.svg '<svg/>'
|
|
1181
|
+
reset_fakes; run_gate "$R_EMPTY2" gate
|
|
1182
|
+
if [[ "$RC" == "0" ]]; then
|
|
1183
|
+
want_call "D3b: pytest ran the suite" 'pytest '
|
|
1184
|
+
# `reason=|mode=full` is vacuous — the disclosure must be a NAMED fallback
|
|
1185
|
+
# reason, not the mere presence of the marker's reason field.
|
|
1186
|
+
want_out "D3b: a named fallback reason is disclosed" 'reason=(empty-selection|no-selector)|mode=full'
|
|
1187
|
+
else
|
|
1188
|
+
want_rc_in "D3b: unclassifiable diff is refused or falls back" 4
|
|
1189
|
+
fi
|
|
1190
|
+
|
|
1191
|
+
# D4: tests exist but no stack resolves → rc 4 no-stack. The fixture file must
|
|
1192
|
+
# match the test-file census shape (`*.test.*`) — a plain `.rb` name is
|
|
1193
|
+
# invisible to it and would land on rc 7 no-tests instead, which is why the
|
|
1194
|
+
# filename carries the marker.
|
|
1195
|
+
R_RB="$(mk_repo no-stack)"; seal_base "$R_RB"
|
|
1196
|
+
mkdir -p "$R_RB/test"; printf 'def test_w; end\n' > "$R_RB/test/test_widget.test.rb"
|
|
1197
|
+
assert_fixture_dir "$R_RB"; git -C "$R_RB" add -A; git -C "$R_RB" commit -q -m "ruby tests"
|
|
1198
|
+
reset_fakes; run_gate "$R_RB" gate
|
|
1199
|
+
# Deterministic: the census sees a test file, no adapter resolves, no foreign
|
|
1200
|
+
# runner → rc 4 reason=no-stack. Never a hedged 4-or-7.
|
|
1201
|
+
want_rc 4 "D4: tests-exist-but-no-stack refuses (rc 4)"
|
|
1202
|
+
want_out "D4: no-stack reason" 'AFFECTED_GATE_REFUSED.*reason=no-stack'
|
|
1203
|
+
want_no_out "D4: never labelled passed" 'passed\.|tests verified'
|
|
1204
|
+
|
|
1205
|
+
# D5 [G1-2]: no runner on PATH at all → rc 4, never a silent 0. The fixture has
|
|
1206
|
+
# no node_modules/.bin — a repo-local copy would resolve the shim anyway.
|
|
1207
|
+
R_NOPATH="$(mk_repo no-path)"; seed_js_nobin "$R_NOPATH"; seal_base "$R_NOPATH"
|
|
1208
|
+
commit_chg "$R_NOPATH" src/util.ts 'export const u = 1;'
|
|
1209
|
+
reset_fakes
|
|
1210
|
+
run_gate_path "/usr/bin:/bin" "$R_NOPATH" gate
|
|
1211
|
+
want_rc 4 "D5: toolchain-missing PATH is refused"
|
|
1212
|
+
want_out "D5: refusal emitted" 'AFFECTED_GATE_REFUSED'
|
|
1213
|
+
|
|
1214
|
+
# D6: no test suite detected at all → rc 7 with a disclosed note, never "passed".
|
|
1215
|
+
R_NONE="$(mk_repo no-suite)"; seal_base "$R_NONE"
|
|
1216
|
+
commit_chg "$R_NONE" src/plain.txt 'data'
|
|
1217
|
+
reset_fakes; run_gate "$R_NONE" gate
|
|
1218
|
+
want_rc 7 "D6: no test suite detected → rc 7"
|
|
1219
|
+
want_no_out "D6: rc 7 is never labelled passed" 'passed\.|tests verified'
|
|
1220
|
+
|
|
1221
|
+
# D7: the changed set is committed ∪ staged ∪ unstaged ∪ untracked.
|
|
1222
|
+
R_UN="$(mk_repo union)"; seed_python "$R_UN"; seal_base "$R_UN"
|
|
1223
|
+
printf 'def test_a():\n assert 1 + 1 == 2\n' > "$R_UN/tests/test_a.py" # unstaged mod
|
|
1224
|
+
assert_fixture_dir "$R_UN"; printf 'def test_b():\n assert True\n' > "$R_UN/tests/test_b.py"; git -C "$R_UN" add tests/test_b.py # staged
|
|
1225
|
+
printf 'def test_c():\n assert True\n' > "$R_UN/tests/test_c.py" # untracked
|
|
1226
|
+
reset_fakes; export FAKE_PYTEST_EXPECT="test_a.py test_b.py test_c.py"
|
|
1227
|
+
run_gate "$R_UN" gate
|
|
1228
|
+
want_rc 0 "D7: staged+unstaged+untracked union runs green"
|
|
1229
|
+
want_call "D7: unstaged file selected" 'test_a\.py'
|
|
1230
|
+
want_call "D7: staged file selected" 'test_b\.py'
|
|
1231
|
+
want_call "D7: untracked file selected" 'test_c\.py'
|
|
1232
|
+
# Unit integrity: ran/of both count test FILES — the 3 changed test files ran
|
|
1233
|
+
# out of a 3-file census (pytest's `collected N items` is items, not the count).
|
|
1234
|
+
want_field "D7: ran is in file units" ran '3'
|
|
1235
|
+
want_field "D7: of is the repo census" of '3'
|
|
1236
|
+
|
|
1237
|
+
# D8: --base overrides resolution — only the LAST commit's files are selected.
|
|
1238
|
+
R_BASE="$(mk_repo base-flag)"; seed_js "$R_BASE"; seal_base "$R_BASE"
|
|
1239
|
+
commit_chg "$R_BASE" src/first.ts 'export const f = 1;'
|
|
1240
|
+
commit_chg "$R_BASE" src/second.ts 'export const s = 1;'
|
|
1241
|
+
reset_fakes
|
|
1242
|
+
run_gate "$R_BASE" gate --base HEAD~1
|
|
1243
|
+
want_rc 0 "D8: --base override exits 0"
|
|
1244
|
+
_rel="$(grep 'related' "$CALLS" | head -n 1)"
|
|
1245
|
+
if [[ "$_rel" == *second.ts* && "$_rel" != *first.ts* ]]; then
|
|
1246
|
+
pass "D8: selection is scoped to the --base ref"
|
|
1247
|
+
else
|
|
1248
|
+
fail "D8: related argv not scoped to HEAD~1: $_rel"
|
|
1249
|
+
fi
|
|
1250
|
+
unset _rel
|
|
1251
|
+
reset_fakes
|
|
1252
|
+
run_gate "$R_BASE" gate --base refs/heads/definitely-missing
|
|
1253
|
+
want_rc 4 "D8b: an unresolvable --base is refused"
|
|
1254
|
+
want_out "D8b: refusal emitted" 'AFFECTED_GATE_REFUSED'
|
|
1255
|
+
|
|
1256
|
+
# D9: shallow clone whose one bounded deepen cannot recover a base → rc 4
|
|
1257
|
+
# no-base-ref, never a silent full and never a silent empty.
|
|
1258
|
+
R_SHSRC="$(mk_repo shallow-src)"; seed_js "$R_SHSRC"
|
|
1259
|
+
assert_fixture_dir "$R_SHSRC"; git -C "$R_SHSRC" add -A; git -C "$R_SHSRC" commit -q -m "base"
|
|
1260
|
+
R_SHALLOW="$WORLD/repos/shallow"
|
|
1261
|
+
git clone -q --depth 1 "file://$R_SHSRC" "$R_SHALLOW" 2>/dev/null
|
|
1262
|
+
git -C "$R_SHALLOW" config user.email soleur-test@example.invalid
|
|
1263
|
+
git -C "$R_SHALLOW" config user.name "Soleur Fixture"
|
|
1264
|
+
git -C "$R_SHALLOW" config commit.gpgsign false
|
|
1265
|
+
git -C "$R_SHALLOW" remote set-url origin "$WORLD/remotes/definitely-missing.git"
|
|
1266
|
+
while IFS= read -r _ref; do git -C "$R_SHALLOW" update-ref -d "$_ref"; done \
|
|
1267
|
+
< <(git -C "$R_SHALLOW" for-each-ref --format='%(refname)' refs/remotes/)
|
|
1268
|
+
git -C "$R_SHALLOW" branch --unset-upstream 2>/dev/null || true
|
|
1269
|
+
reset_fakes
|
|
1270
|
+
run_gate "$R_SHALLOW" gate
|
|
1271
|
+
want_rc 4 "D9: shallow clone with unrecoverable base is refused"
|
|
1272
|
+
want_out "D9: no-base-ref reason" 'AFFECTED_GATE_REFUSED.*reason=no-base-ref'
|
|
1273
|
+
want_no_out "D9: never a silent full on a dead base" 'mode=full'
|
|
1274
|
+
|
|
1275
|
+
# D9b [census legs]: a census leg that fails toward empty must NEVER silently
|
|
1276
|
+
# narrow the selection — the untracked leg is the worst case (a dead
|
|
1277
|
+
# `ls-files --others` drops every NEW test file). The contract: forced full +
|
|
1278
|
+
# a disclosed reason=census-degraded. A scoped git shim fails ONLY the
|
|
1279
|
+
# untracked leg, and only while armed — all other git calls delegate to the
|
|
1280
|
+
# real binary.
|
|
1281
|
+
GITFAILBIN="$TESTROOT/bin-gitfail"; mkdir -p "$GITFAILBIN"
|
|
1282
|
+
ln -sf "$BIN"/* "$GITFAILBIN/"
|
|
1283
|
+
cat > "$GITFAILBIN/git" <<'SHIM'
|
|
1284
|
+
#!/usr/bin/env bash
|
|
1285
|
+
if [[ "${FAKE_GIT_FAIL_OTHERS:-0}" == "1" ]]; then
|
|
1286
|
+
_ls=0; _oth=0
|
|
1287
|
+
for _a in "$@"; do
|
|
1288
|
+
[[ "$_a" == "ls-files" ]] && _ls=1
|
|
1289
|
+
[[ "$_a" == "--others" ]] && _oth=1
|
|
1290
|
+
done
|
|
1291
|
+
if (( _ls && _oth )); then
|
|
1292
|
+
echo "stub git: simulated ls-files --others failure" >&2
|
|
1293
|
+
exit 1
|
|
1294
|
+
fi
|
|
1295
|
+
fi
|
|
1296
|
+
exec "${REAL_GIT:?REAL_GIT unset}" "$@"
|
|
1297
|
+
SHIM
|
|
1298
|
+
chmod +x "$GITFAILBIN/git"
|
|
1299
|
+
R_CFAIL="$(mk_repo census-degraded)"; seed_js "$R_CFAIL"; seal_base "$R_CFAIL"
|
|
1300
|
+
commit_chg "$R_CFAIL" src/util.ts 'export const u = 1;'
|
|
1301
|
+
# An UNTRACKED test file — exactly what a masked ls-files leg would drop.
|
|
1302
|
+
printf 'import { test } from "vitest";\ntest("n", () => {});\n' > "$R_CFAIL/src/new.test.ts"
|
|
1303
|
+
reset_fakes; export FAKE_GIT_FAIL_OTHERS=1
|
|
1304
|
+
run_gate_path "$GITFAILBIN:$BASEPATH" "$R_CFAIL" gate
|
|
1305
|
+
want_rc 0 "D9b: a failed untracked census leg degrades to full — never narrows"
|
|
1306
|
+
want_field "D9b: mode=full" mode 'full'
|
|
1307
|
+
want_out "D9b: census-degraded reason disclosed" 'reason=census-degraded'
|
|
1308
|
+
want_call "D9b: the full command ran" 'vitest run '
|
|
1309
|
+
want_no_call "D9b: no related selection on a degraded census" 'related'
|
|
1310
|
+
|
|
1311
|
+
# ============================ adapters =========================================
|
|
1312
|
+
|
|
1313
|
+
# D15: jest — --findRelatedTests <files>; the __tests__ convention counts in of=.
|
|
1314
|
+
R_JEST="$(mk_repo jest-repo)"; seed_jest "$R_JEST"
|
|
1315
|
+
mkdir -p "$R_JEST/src/__tests__"; printf 'test("x", () => {});\n' > "$R_JEST/src/__tests__/x.js"
|
|
1316
|
+
seal_base "$R_JEST"
|
|
1317
|
+
commit_chg "$R_JEST" src/util.ts 'export const u = 1;'
|
|
1318
|
+
reset_fakes; export FAKE_JEST_REQUIRE_RELATED=1
|
|
1319
|
+
run_gate "$R_JEST" gate
|
|
1320
|
+
want_rc 0 "D15: jest fixture exits 0"
|
|
1321
|
+
want_call "D15: jest selector is --findRelatedTests" 'jest.*--findRelatedTests'
|
|
1322
|
+
want_call "D15: the changed file reaches jest" 'findRelatedTests.*util\.ts'
|
|
1323
|
+
want_field "D15: of= counts __tests__ files too" of '2'
|
|
1324
|
+
|
|
1325
|
+
# D16 [G1-9]: go — a leaf change must pull the reverse-dependency closure; the
|
|
1326
|
+
# changed package alone is the mutation this row exists to refuse.
|
|
1327
|
+
R_GO="$(mk_repo go-repo)"; seed_go "$R_GO" "$TESTROOT/go-graph"; seal_base "$R_GO"
|
|
1328
|
+
commit_chg "$R_GO" internal/leaf/leaf.go 'package leaf\n\nfunc Leaf() int { return 2 }'
|
|
1329
|
+
reset_fakes; export FAKE_GO_DIR="$TESTROOT/go-graph" FAKE_GO_EXPECT="internal/leaf cmd/app"
|
|
1330
|
+
run_gate "$R_GO" gate
|
|
1331
|
+
want_rc 0 "D16: go fixture exits 0"
|
|
1332
|
+
want_call "D16: leaf package under test" 'go.*test.*internal/leaf'
|
|
1333
|
+
want_call "D16: reverse dependent under test" 'go.*test.*cmd/app'
|
|
1334
|
+
# Unit integrity: two PACKAGES ran but only one test FILE exists — ran is in
|
|
1335
|
+
# file units like of= (packages≠files was the original unit-divergence defect).
|
|
1336
|
+
want_field "D16: ran counts test files not packages" ran '1'
|
|
1337
|
+
want_field "D16: of is the repo census" of '1'
|
|
1338
|
+
|
|
1339
|
+
# D17: nx.json wins over package.json.
|
|
1340
|
+
R_NX="$(mk_repo nx-repo)"; seed_nx "$R_NX"; seal_base "$R_NX"
|
|
1341
|
+
commit_chg "$R_NX" src/util.ts 'export const u = 1;'
|
|
1342
|
+
reset_fakes; run_gate "$R_NX" gate
|
|
1343
|
+
want_rc 0 "D17: nx fixture exits 0"
|
|
1344
|
+
want_call "D17: nx affected ran" 'nx.*affected'
|
|
1345
|
+
want_no_call "D17: vitest stays dark under nx" 'vitest '
|
|
1346
|
+
|
|
1347
|
+
# D17b: a scheduler reporting "10 tasks" is NOT the vacuous "0 tasks" shape —
|
|
1348
|
+
# an unanchored '0 tasks' grep matches inside the count and false-flags the run
|
|
1349
|
+
# as empty, triggering a spurious full retry.
|
|
1350
|
+
reset_fakes; export FAKE_NX_LINE="Successfully ran target test for 10 tasks"
|
|
1351
|
+
run_gate "$R_NX" gate
|
|
1352
|
+
want_rc 0 "D17b: nx '10 tasks' run exits 0"
|
|
1353
|
+
want_call "D17b: nx affected ran" 'nx.*affected'
|
|
1354
|
+
want_no_call "D17b: no vacuous full-retry on a real task count" 'nx.*run-many'
|
|
1355
|
+
|
|
1356
|
+
# D17c: a NUMERIC ran=0 (`0 successful` — a spelling the phrase grep misses) is
|
|
1357
|
+
# vacuous the same as `0 tasks` → empty-selection full retry, never a pass.
|
|
1358
|
+
reset_fakes; export FAKE_NX_LINE="Tasks: 0 successful" FAKE_NX_FULL_LINE="nx run-many: ran target test for 3 projects"
|
|
1359
|
+
run_gate "$R_NX" gate
|
|
1360
|
+
want_rc 0 "D17c: nx numeric-zero selection retries full"
|
|
1361
|
+
want_call "D17c: the full retry ran" 'nx.*run-many'
|
|
1362
|
+
want_out "D17c: the escalation is disclosed" 'empty-selection|mode=full'
|
|
1363
|
+
|
|
1364
|
+
# D18: turbo.json wins over package.json.
|
|
1365
|
+
R_TB="$(mk_repo turbo-repo)"; seed_turbo "$R_TB"; seal_base "$R_TB"
|
|
1366
|
+
commit_chg "$R_TB" src/util.ts 'export const u = 1;'
|
|
1367
|
+
reset_fakes; run_gate "$R_TB" gate
|
|
1368
|
+
want_rc 0 "D18: turbo fixture exits 0"
|
|
1369
|
+
want_call "D18: turbo run test ran" 'turbo.*run.*test'
|
|
1370
|
+
want_no_call "D18: vitest stays dark under turbo" 'vitest '
|
|
1371
|
+
|
|
1372
|
+
# D10/D11: Python source diffs — pytest-testmon when importable, else full suite.
|
|
1373
|
+
R_PYSRC="$(mk_repo py-src)"; seed_python "$R_PYSRC"; seal_base "$R_PYSRC"
|
|
1374
|
+
commit_chg "$R_PYSRC" src/mod.py 'def f():\n return 2'
|
|
1375
|
+
reset_fakes; run_gate "$R_PYSRC" gate
|
|
1376
|
+
want_rc 0 "D10: source diff without testmon runs the suite"
|
|
1377
|
+
# 'pytest' bare matches the `python3 -c 'import pytest'` probe line too —
|
|
1378
|
+
# anchor on the tool's own ledger slot.
|
|
1379
|
+
want_call "D10: pytest invoked" 'pytest '
|
|
1380
|
+
want_field "D10: full-escalation ran equals the census" ran '1'
|
|
1381
|
+
want_field "D10: census" of '1'
|
|
1382
|
+
R_PYTM="$(mk_repo py-testmon)"; seed_python "$R_PYTM"; seal_base "$R_PYTM"
|
|
1383
|
+
commit_chg "$R_PYTM" src/mod.py 'def f():\n return 2'
|
|
1384
|
+
reset_fakes; export FAKE_PY_IMPORTABLE="pytest testmon pytest_testmon"
|
|
1385
|
+
run_gate "$R_PYTM" gate
|
|
1386
|
+
want_rc 0 "D11: testmon-importable source diff exits 0"
|
|
1387
|
+
# A bare `testmon` grep is satisfied by the import-PROBE ledger line
|
|
1388
|
+
# (`python3 -I -c 'import pytest_testmon'`) — pin the RUN argv instead:
|
|
1389
|
+
# `pytest\t--testmon …` (the pytest stub re-logs under either tool spelling).
|
|
1390
|
+
want_call "D11: the testmon selection ran (--testmon argv, not the probe)" 'pytest .*--testmon'
|
|
1391
|
+
# testmon's file count is not derivable from `collected N / M deselected`
|
|
1392
|
+
# (items, not files) — the honest marker is ran=-, and the pass verdict still
|
|
1393
|
+
# prints because a real run happened.
|
|
1394
|
+
want_field "D11: testmon ran is honestly unknown" ran '-'
|
|
1395
|
+
want_out "D11: pass verdict prints despite opaque tally" 'affected-suite gate passed\.'
|
|
1396
|
+
|
|
1397
|
+
# D12/D13: pytest -n auto iff pytest-xdist imports, else serial with a reason.
|
|
1398
|
+
R_PYXD="$(mk_repo py-xdist)"; seed_python "$R_PYXD"; seal_base "$R_PYXD"
|
|
1399
|
+
commit_chg "$R_PYXD" tests/test_a.py 'def test_a():\n assert 1 + 1 == 2'
|
|
1400
|
+
reset_fakes; export FAKE_PY_IMPORTABLE="pytest xdist"
|
|
1401
|
+
run_gate "$R_PYXD" gate
|
|
1402
|
+
want_rc 0 "D12: xdist fixture exits 0"
|
|
1403
|
+
want_call "D12: xdist parallelism enabled (-n auto or a width)" 'pytest.*-n(=| )?(auto|[0-9]+)'
|
|
1404
|
+
R_PYSE="$(mk_repo py-serial)"; seed_python "$R_PYSE"; seal_base "$R_PYSE"
|
|
1405
|
+
commit_chg "$R_PYSE" tests/test_a.py 'def test_a():\n assert 1 + 1 == 2'
|
|
1406
|
+
reset_fakes; export FAKE_PY_IMPORTABLE="pytest"
|
|
1407
|
+
run_gate "$R_PYSE" gate
|
|
1408
|
+
want_rc 0 "D13: serial fixture exits 0"
|
|
1409
|
+
want_no_call "D13: no -n width without xdist" 'pytest.*-n(=| )'
|
|
1410
|
+
if grep -qiE 'serial|xdist' <<<"$OUT"; then
|
|
1411
|
+
pass "D13: serial reason is stated"
|
|
1412
|
+
else
|
|
1413
|
+
fail "D13: serial run without a stated reason"
|
|
1414
|
+
fi
|
|
1415
|
+
|
|
1416
|
+
# D14 [G1-5]: pytest rc 5 (no tests collected) is NOT a pass.
|
|
1417
|
+
R_PY5="$(mk_repo py-rc5)"; seed_python "$R_PY5"; seal_base "$R_PY5"
|
|
1418
|
+
commit_chg "$R_PY5" tests/test_a.py 'def test_a():\n assert 1 + 1 == 2'
|
|
1419
|
+
reset_fakes; export FAKE_PYTEST_RC=5
|
|
1420
|
+
run_gate "$R_PY5" gate
|
|
1421
|
+
want_rc_in "D14: pytest rc 5 is never green" 1 4
|
|
1422
|
+
want_no_out "D14: no 'passed' claim on an empty collection" 'Ran .* passed\.|tests verified'
|
|
1423
|
+
|
|
1424
|
+
# D24: a bare pyproject.toml is a Python-PROJECT marker, not a pytest-SUITE
|
|
1425
|
+
# marker — without a pytest signal in a manifest and without test_*.py files,
|
|
1426
|
+
# the py adapter must not claim the repo.
|
|
1427
|
+
R_PYNO="$(mk_repo py-nosignal)"
|
|
1428
|
+
printf '[project]\nname = "fixture-lib"\n' > "$R_PYNO/pyproject.toml"
|
|
1429
|
+
mkdir -p "$R_PYNO/src"; printf 'def f():\n return 1\n' > "$R_PYNO/src/lib.py"
|
|
1430
|
+
seal_base "$R_PYNO"
|
|
1431
|
+
commit_chg "$R_PYNO" src/lib.py 'def f():\n return 2'
|
|
1432
|
+
reset_fakes; run_gate "$R_PYNO" gate
|
|
1433
|
+
want_rc 7 "D24: pytest-signal-free pyproject.toml is not a py detection"
|
|
1434
|
+
want_no_call "D24: pytest never invoked" 'pytest '
|
|
1435
|
+
|
|
1436
|
+
# D19 [G1-6]: multi-stack — an empty adapter cannot launder or sink the other.
|
|
1437
|
+
R_MS="$(mk_repo multi-stack)"; seed_python "$R_MS"
|
|
1438
|
+
seed_go "$R_MS" "$TESTROOT/go-graph-ms"
|
|
1439
|
+
seal_base "$R_MS"
|
|
1440
|
+
commit_chg "$R_MS" src/mod.py 'def f():\n return 3'
|
|
1441
|
+
reset_fakes; export FAKE_GO_DIR="$TESTROOT/go-graph-ms"
|
|
1442
|
+
run_gate "$R_MS" gate
|
|
1443
|
+
want_rc 0 "D19a: py-only diff in a py+go repo exits 0"
|
|
1444
|
+
want_call "D19a: pytest ran" 'pytest '
|
|
1445
|
+
# Both adapters produce honest FILE counts on this path — `ran=-` (opaque
|
|
1446
|
+
# tally) is a counted-path lie and must not satisfy this row.
|
|
1447
|
+
want_out "D19a: the run is counted non-empty" 'AFFECTED_GATE ran=[1-9][0-9]* of=[1-9][0-9]*'
|
|
1448
|
+
commit_chg "$R_MS" internal/leaf/leaf.go 'package leaf\n\nfunc Leaf() int { return 3 }'
|
|
1449
|
+
reset_fakes; export FAKE_PYTEST_RC=1 FAKE_GO_DIR="$TESTROOT/go-graph-ms" FAKE_GO_EXPECT="internal/leaf cmd/app"
|
|
1450
|
+
run_gate "$R_MS" gate
|
|
1451
|
+
want_rc 1 "D19b: one failing stack dominates the aggregate (1 > 4 > 6 > 0)"
|
|
1452
|
+
|
|
1453
|
+
# ============================ budget + CI ======================================
|
|
1454
|
+
|
|
1455
|
+
# D20a [P6]: CI workflow detected + budget expires → rc 6 BUDGET_DEFERRED.
|
|
1456
|
+
R_BUD="$(mk_repo budget-ci)"; seed_js "$R_BUD"; seed_ci "$R_BUD"
|
|
1457
|
+
write_config "$R_BUD" '{"budget_seconds": 1}'
|
|
1458
|
+
seal_base "$R_BUD"
|
|
1459
|
+
commit_chg "$R_BUD" src/util.ts 'export const u = 1;'
|
|
1460
|
+
reset_fakes; export FAKE_VITEST_SLEEP=5
|
|
1461
|
+
run_gate "$R_BUD" gate
|
|
1462
|
+
want_rc 6 "D20a: expired budget with CI detected defers"
|
|
1463
|
+
want_out "D20a: deferral marker" 'AFFECTED_GATE_BUDGET_DEFERRED'
|
|
1464
|
+
want_field "D20a: ci field names the workflow" ci '.*\.yml|.*workflows.*'
|
|
1465
|
+
|
|
1466
|
+
# D20b [G1-8]: a failure BEFORE expiry wins over deferral. A 1 s budget makes
|
|
1467
|
+
# this racy (gate startup can eat the deadline before the run starts), so this
|
|
1468
|
+
# arm gets a larger budget — the failure still lands first, deterministically.
|
|
1469
|
+
R_BUDF="$(mk_repo budget-fail-first)"; seed_js "$R_BUDF"; seed_ci "$R_BUDF"
|
|
1470
|
+
write_config "$R_BUDF" '{"budget_seconds": 120}'
|
|
1471
|
+
seal_base "$R_BUDF"
|
|
1472
|
+
commit_chg "$R_BUDF" src/util.ts 'export const u = 1;'
|
|
1473
|
+
reset_fakes; export FAKE_VITEST_RC=1
|
|
1474
|
+
run_gate "$R_BUDF" gate
|
|
1475
|
+
want_rc 1 "D20b: failure before budget expiry is rc 1"
|
|
1476
|
+
want_no_out "D20b: no deferral when a failure already landed" 'AFFECTED_GATE_BUDGET_DEFERRED'
|
|
1477
|
+
|
|
1478
|
+
# D20c [G1-4]: no CI detected → the budget is ignored and the run completes.
|
|
1479
|
+
R_NOCI="$(mk_repo budget-noci)"; seed_js "$R_NOCI"
|
|
1480
|
+
write_config "$R_NOCI" '{"budget_seconds": 1}'
|
|
1481
|
+
seal_base "$R_NOCI"
|
|
1482
|
+
commit_chg "$R_NOCI" src/util.ts 'export const u = 1;'
|
|
1483
|
+
reset_fakes; export FAKE_VITEST_SLEEP=3
|
|
1484
|
+
run_gate "$R_NOCI" gate
|
|
1485
|
+
want_rc 0 "D20c: budget ignored without CI — run completes"
|
|
1486
|
+
want_no_out "D20c: no deferral without CI" 'AFFECTED_GATE_BUDGET_DEFERRED'
|
|
1487
|
+
# `ci=none` is unconditional on every no-CI run's marker — pin the upfront
|
|
1488
|
+
# disclosure sentence itself, not the marker field.
|
|
1489
|
+
if grep -qiE 'no CI test workflow detected|budget not enforced' <<<"$OUT"; then
|
|
1490
|
+
pass "D20c: the no-CI disregard is disclosed"
|
|
1491
|
+
else
|
|
1492
|
+
fail "D20c: budget ignored without the upfront no-CI line"
|
|
1493
|
+
fi
|
|
1494
|
+
|
|
1495
|
+
# D20d: budget_ci: true forces deferral eligibility without a workflow file.
|
|
1496
|
+
R_BCI="$(mk_repo budget-ci-flag)"; seed_js "$R_BCI"
|
|
1497
|
+
write_config "$R_BCI" '{"budget_ci": true, "budget_seconds": 1}'
|
|
1498
|
+
seal_base "$R_BCI"
|
|
1499
|
+
commit_chg "$R_BCI" src/util.ts 'export const u = 1;'
|
|
1500
|
+
reset_fakes; export FAKE_VITEST_SLEEP=5
|
|
1501
|
+
run_gate "$R_BCI" gate
|
|
1502
|
+
want_rc 6 "D20d: budget_ci=true defers without a workflow file"
|
|
1503
|
+
want_out "D20d: deferral marker" 'AFFECTED_GATE_BUDGET_DEFERRED'
|
|
1504
|
+
|
|
1505
|
+
# D20e [trigger anchor]: `push`/`pull_request` as a SUBSTRING inside a run:
|
|
1506
|
+
# step is not a trigger declaration — `git push` and `pushd` in a deploy
|
|
1507
|
+
# workflow must not arm the budget. The workflow below carries a real
|
|
1508
|
+
# test-command shape too, so only the anchored `on:` check keeps this dark.
|
|
1509
|
+
R_CIPUSH="$(mk_repo ci-push-substring)"; seed_js "$R_CIPUSH"
|
|
1510
|
+
mkdir -p "$R_CIPUSH/.github/workflows"
|
|
1511
|
+
cat > "$R_CIPUSH/.github/workflows/deploy.yml" <<'YML'
|
|
1512
|
+
name: deploy
|
|
1513
|
+
on: workflow_dispatch
|
|
1514
|
+
jobs:
|
|
1515
|
+
deploy:
|
|
1516
|
+
runs-on: ubuntu-latest
|
|
1517
|
+
steps:
|
|
1518
|
+
- run: npm test
|
|
1519
|
+
- run: git push origin main
|
|
1520
|
+
- run: pushd dist && ./publish.sh
|
|
1521
|
+
YML
|
|
1522
|
+
write_config "$R_CIPUSH" '{"budget_seconds": 1}'
|
|
1523
|
+
seal_base "$R_CIPUSH"
|
|
1524
|
+
commit_chg "$R_CIPUSH" src/util.ts 'export const u = 1;'
|
|
1525
|
+
reset_fakes; export FAKE_VITEST_SLEEP=5
|
|
1526
|
+
run_gate "$R_CIPUSH" gate
|
|
1527
|
+
want_rc 0 "D20e: push-in-a-run-step is not a CI trigger — budget stays unarmed"
|
|
1528
|
+
want_no_out "D20e: no deferral without a declared trigger" 'AFFECTED_GATE_BUDGET_DEFERRED'
|
|
1529
|
+
want_field "D20e: ci field is none" ci 'none'
|
|
1530
|
+
want_out "D20e: the no-CI disregard is disclosed" 'no CI test workflow detected'
|
|
1531
|
+
|
|
1532
|
+
# D20f [trigger anchor, positive]: flow-style `on: [push]` IS a declaration.
|
|
1533
|
+
R_CIFLOW="$(mk_repo ci-flow)"; seed_js "$R_CIFLOW"
|
|
1534
|
+
mkdir -p "$R_CIFLOW/.github/workflows"
|
|
1535
|
+
cat > "$R_CIFLOW/.github/workflows/ci.yml" <<'YML'
|
|
1536
|
+
name: ci
|
|
1537
|
+
on: [push, workflow_dispatch]
|
|
1538
|
+
jobs:
|
|
1539
|
+
test:
|
|
1540
|
+
runs-on: ubuntu-latest
|
|
1541
|
+
steps:
|
|
1542
|
+
- run: npm test
|
|
1543
|
+
YML
|
|
1544
|
+
write_config "$R_CIFLOW" '{"budget_seconds": 1}'
|
|
1545
|
+
seal_base "$R_CIFLOW"
|
|
1546
|
+
commit_chg "$R_CIFLOW" src/util.ts 'export const u = 1;'
|
|
1547
|
+
reset_fakes; export FAKE_VITEST_SLEEP=5
|
|
1548
|
+
run_gate "$R_CIFLOW" gate
|
|
1549
|
+
want_rc 6 "D20f: flow-style on: [push] detects CI and defers"
|
|
1550
|
+
want_out "D20f: deferral marker" 'AFFECTED_GATE_BUDGET_DEFERRED'
|
|
1551
|
+
want_field "D20f: ci field names the workflow" ci '.*\.yml'
|
|
1552
|
+
|
|
1553
|
+
# D20g [trigger anchor, positive]: block-map `push:` key inside the `on:` block
|
|
1554
|
+
# — and a block-sequence `- pull_request` entry — both declare triggers.
|
|
1555
|
+
R_CIBLK="$(mk_repo ci-block)"; seed_js "$R_CIBLK"
|
|
1556
|
+
mkdir -p "$R_CIBLK/.github/workflows"
|
|
1557
|
+
cat > "$R_CIBLK/.github/workflows/ci.yml" <<'YML'
|
|
1558
|
+
name: ci
|
|
1559
|
+
on:
|
|
1560
|
+
push:
|
|
1561
|
+
branches: [main]
|
|
1562
|
+
jobs:
|
|
1563
|
+
test:
|
|
1564
|
+
runs-on: ubuntu-latest
|
|
1565
|
+
steps:
|
|
1566
|
+
- run: npm test
|
|
1567
|
+
YML
|
|
1568
|
+
write_config "$R_CIBLK" '{"budget_seconds": 1}'
|
|
1569
|
+
seal_base "$R_CIBLK"
|
|
1570
|
+
commit_chg "$R_CIBLK" src/util.ts 'export const u = 1;'
|
|
1571
|
+
reset_fakes; export FAKE_VITEST_SLEEP=5
|
|
1572
|
+
run_gate "$R_CIBLK" gate
|
|
1573
|
+
want_rc 6 "D20g: block-map push: trigger detects CI and defers"
|
|
1574
|
+
want_out "D20g: deferral marker" 'AFFECTED_GATE_BUDGET_DEFERRED'
|
|
1575
|
+
|
|
1576
|
+
R_CISEQ="$(mk_repo ci-seq)"; seed_js "$R_CISEQ"
|
|
1577
|
+
mkdir -p "$R_CISEQ/.github/workflows"
|
|
1578
|
+
cat > "$R_CISEQ/.github/workflows/ci.yml" <<'YML'
|
|
1579
|
+
name: ci
|
|
1580
|
+
on:
|
|
1581
|
+
- pull_request
|
|
1582
|
+
jobs:
|
|
1583
|
+
test:
|
|
1584
|
+
runs-on: ubuntu-latest
|
|
1585
|
+
steps:
|
|
1586
|
+
- run: npm test
|
|
1587
|
+
YML
|
|
1588
|
+
write_config "$R_CISEQ" '{"budget_seconds": 1}'
|
|
1589
|
+
seal_base "$R_CISEQ"
|
|
1590
|
+
commit_chg "$R_CISEQ" src/util.ts 'export const u = 1;'
|
|
1591
|
+
reset_fakes; export FAKE_VITEST_SLEEP=5
|
|
1592
|
+
run_gate "$R_CISEQ" gate
|
|
1593
|
+
want_rc 6 "D20h: block-sequence - pull_request trigger detects CI and defers"
|
|
1594
|
+
want_out "D20h: deferral marker" 'AFFECTED_GATE_BUDGET_DEFERRED'
|
|
1595
|
+
|
|
1596
|
+
# ============================ --full + G1-12 ===================================
|
|
1597
|
+
|
|
1598
|
+
# D21: --full on an adapter repo bypasses selection.
|
|
1599
|
+
reset_fakes; run_gate "$R_CONTROL" gate --full
|
|
1600
|
+
want_rc 0 "D21: --full on the vitest fixture exits 0"
|
|
1601
|
+
want_field "D21: mode=full" mode 'full'
|
|
1602
|
+
want_no_call "D21: --full never runs the related selector" 'related'
|
|
1603
|
+
# Bare tool-name legs are satisfied by the --version toolchain probe — pin the
|
|
1604
|
+
# run argv shape (the ledger is `name<TAB>argv`).
|
|
1605
|
+
want_call "D21: the full command ran" 'vitest run |npm test|pnpm test|yarn test|bun test'
|
|
1606
|
+
|
|
1607
|
+
# D22 [G1-12 must-PASS]: pnpm workspace, vitest config in a subdirectory,
|
|
1608
|
+
# non-default base branch (trunk, resolved via origin/HEAD).
|
|
1609
|
+
R_PNPM="$(mk_repo pnpm-ws trunk)"
|
|
1610
|
+
mkdir -p "$R_PNPM/packages/web/src" "$R_PNPM/node_modules/.bin"
|
|
1611
|
+
printf 'packages:\n - packages/*\n' > "$R_PNPM/pnpm-workspace.yaml"
|
|
1612
|
+
cat > "$R_PNPM/package.json" <<'JSON'
|
|
1613
|
+
{ "name": "fixture-pnpm", "private": true, "scripts": { "test": "vitest run" } }
|
|
1614
|
+
JSON
|
|
1615
|
+
cat > "$R_PNPM/packages/web/package.json" <<'JSON'
|
|
1616
|
+
{ "name": "@fx/web", "private": true, "scripts": { "test": "vitest run" } }
|
|
1617
|
+
JSON
|
|
1618
|
+
printf 'export default { test: {} };\n' > "$R_PNPM/packages/web/vitest.config.ts"
|
|
1619
|
+
printf 'export const w = 1;\n' > "$R_PNPM/packages/web/src/w.ts"
|
|
1620
|
+
printf 'test("w", () => {});\n' > "$R_PNPM/packages/web/src/w.test.ts"
|
|
1621
|
+
printf 'node_modules/\n' > "$R_PNPM/.gitignore"
|
|
1622
|
+
ln -sf "$BIN/vitest" "$R_PNPM/node_modules/.bin/vitest"
|
|
1623
|
+
seal_base "$R_PNPM"
|
|
1624
|
+
commit_chg "$R_PNPM" packages/web/src/util.ts 'export const u = 1;'
|
|
1625
|
+
reset_fakes; run_gate "$R_PNPM" gate
|
|
1626
|
+
want_rc 0 "D22: pnpm-workspace / subdir-config / trunk-base fixture exits 0"
|
|
1627
|
+
# A workspace may legitimately run the package's own test command rather than
|
|
1628
|
+
# `vitest related`; what is NOT acceptable is a green run with nothing invoked
|
|
1629
|
+
# or only a --version probe — pin the invocation shape, not the tool name.
|
|
1630
|
+
want_call "D22: a runner executed (related selection or the package test command)" 'vitest (run|related)|jest .*findRelatedTests|npm test|pnpm test|yarn test|bun test'
|
|
1631
|
+
|
|
1632
|
+
# F1–F3 [pm arms]: the lockfile-detected package manager supplies the exec
|
|
1633
|
+
# prefix when node_modules/.bin is absent — pnpm-lock.yaml → `pnpm exec`,
|
|
1634
|
+
# yarn.lock → `yarn <tool>`, bun.lock → `npx --no-install` (the no-`bun x`
|
|
1635
|
+
# rule: bunx auto-installs registry content, so it is never an exec prefix).
|
|
1636
|
+
# Assert the prefix on the RUN argv — the --version probe shares the prefix.
|
|
1637
|
+
seed_pm_repo() { # <name> <lockfile> — vitest stack, no .bin, pm lock committed
|
|
1638
|
+
local r; r="$(mk_repo "$1")"; seed_js_nobin "$r"
|
|
1639
|
+
printf '# fixture lock\n' > "$r/$2"
|
|
1640
|
+
seal_base "$r"; commit_chg "$r" src/util.ts 'export const u = 1;'
|
|
1641
|
+
printf '%s' "$r"
|
|
1642
|
+
}
|
|
1643
|
+
R_FPNPM="$(seed_pm_repo pm-pnpm pnpm-lock.yaml)"
|
|
1644
|
+
reset_fakes; run_gate "$R_FPNPM" gate
|
|
1645
|
+
want_rc 0 "F1: pnpm-lock fixture exits 0"
|
|
1646
|
+
want_call "F1: pnpm exec prefix on the related run" 'pnpm exec vitest related'
|
|
1647
|
+
want_call "F1: vitest ran through the prefix" 'vitest related'
|
|
1648
|
+
|
|
1649
|
+
R_FYARN="$(seed_pm_repo pm-yarn yarn.lock)"
|
|
1650
|
+
reset_fakes; run_gate "$R_FYARN" gate
|
|
1651
|
+
want_rc 0 "F2: yarn.lock fixture exits 0"
|
|
1652
|
+
want_call "F2: yarn prefix on the related run" 'yarn vitest related'
|
|
1653
|
+
want_call "F2: vitest ran through the prefix" 'vitest related'
|
|
1654
|
+
|
|
1655
|
+
R_FBUN="$(seed_pm_repo pm-bun bun.lock)"
|
|
1656
|
+
reset_fakes; run_gate "$R_FBUN" gate
|
|
1657
|
+
want_rc 0 "F3: bun.lock fixture exits 0"
|
|
1658
|
+
want_call "F3: bun resolves via npx --no-install, never bun x" 'npx --no-install vitest related'
|
|
1659
|
+
want_no_call "F3: bun x is never an exec prefix" 'bun x|bunx '
|
|
1660
|
+
|
|
1661
|
+
# ============================ env-hygiene + aux modes ==========================
|
|
1662
|
+
|
|
1663
|
+
# E1: forged budget env must NOT launder a deferral. A caller exporting
|
|
1664
|
+
# AFFECTED_BUDGET_ACTIVE=1 + a past AFFECTED_DEADLINE would make every bound
|
|
1665
|
+
# expire instantly on the OLD code (`${VAR:-0}` inherited env); the gate now
|
|
1666
|
+
# initialises both unconditionally and requires a detected CI for rc 6.
|
|
1667
|
+
R_FORGE="$(mk_repo env-forge)"; seed_js "$R_FORGE"; seal_base "$R_FORGE"
|
|
1668
|
+
commit_chg "$R_FORGE" src/util.ts 'export const u = 1;'
|
|
1669
|
+
reset_fakes
|
|
1670
|
+
( cd "$R_FORGE" && env -u CLAUDE_PLUGIN_ROOT \
|
|
1671
|
+
AFFECTED_BUDGET_ACTIVE=1 AFFECTED_DEADLINE=1 \
|
|
1672
|
+
PATH="$BIN:$BASEPATH" FAKE_LOG="$CALLS" FAKE_DIR="$TESTROOT" \
|
|
1673
|
+
REAL_NODE="$REAL_NODE" REAL_PY="$REAL_PY" \
|
|
1674
|
+
bash "$SUT" ) > "$TESTROOT/outs/forge.out" 2>&1
|
|
1675
|
+
RC=$?; OUT="$(cat "$TESTROOT/outs/forge.out")"
|
|
1676
|
+
printf 'gate\t%s\t%s\n' "$RC" "$TESTROOT/outs/forge.out" >> "$RUNS"
|
|
1677
|
+
want_rc 0 "E1: forged budget env does not defer"
|
|
1678
|
+
want_no_out "E1: no deferral marker" 'AFFECTED_GATE_BUDGET_DEFERRED'
|
|
1679
|
+
want_call "E1: the run actually executed" 'vitest (run|related)'
|
|
1680
|
+
|
|
1681
|
+
# E2: --self-check outside a git repo names the real cause, not no-base-ref.
|
|
1682
|
+
reset_fakes
|
|
1683
|
+
mkdir -p "$TESTROOT/not-a-repo"
|
|
1684
|
+
( cd "$TESTROOT/not-a-repo" && env -u CLAUDE_PLUGIN_ROOT \
|
|
1685
|
+
PATH="$BIN:$BASEPATH" FAKE_LOG="$CALLS" FAKE_DIR="$TESTROOT" \
|
|
1686
|
+
bash "$SUT" --self-check ) > "$TESTROOT/outs/norepo.out" 2>&1
|
|
1687
|
+
RC=$?; OUT="$(cat "$TESTROOT/outs/norepo.out")"
|
|
1688
|
+
printf 'selfcheck\t%s\t%s\n' "$RC" "$TESTROOT/outs/norepo.out" >> "$RUNS"
|
|
1689
|
+
want_rc 4 "E2: --self-check outside a repo refuses"
|
|
1690
|
+
want_out "E2: reason is not-a-repo" 'reason=not-a-repo'
|
|
1691
|
+
|
|
1692
|
+
# E3: --self-check downgrades `ok` when a detected stack's toolchain is missing
|
|
1693
|
+
# — a py repo on a PATH without pytest/python3 must not self-check green.
|
|
1694
|
+
TLBIN="$TESTROOT/bin-toolless"; mkdir -p "$TLBIN"
|
|
1695
|
+
for _t in git bash env grep find sed awk sort tr cut head tail cat date sleep \
|
|
1696
|
+
ps getconf sysctl mktemp rm wc dirname basename uname comm ls mkdir \
|
|
1697
|
+
chmod touch printf od jq node vitest jest nx turbo npm npx pnpm yarn bun; do
|
|
1698
|
+
_p="$(command -v "$_t" 2>/dev/null || true)"
|
|
1699
|
+
[[ -n "$_p" ]] && ln -sf "$_p" "$TLBIN/$_t"
|
|
1700
|
+
done
|
|
1701
|
+
R_SC_MISS="$(mk_repo selfcheck-missing)"; seed_python "$R_SC_MISS"; seal_base "$R_SC_MISS"
|
|
1702
|
+
reset_fakes
|
|
1703
|
+
run_gate_path "$TLBIN" "$R_SC_MISS" selfcheck --self-check
|
|
1704
|
+
want_rc 4 "E3: missing toolchain self-check refuses"
|
|
1705
|
+
want_out "E3: marker says toolchain-missing" 'AFFECTED_GATE_SELF_CHECK toolchain-missing|reason=toolchain-missing'
|
|
1706
|
+
|
|
1707
|
+
# E4: --help emits mode=help (contract vocabulary), not mode=none.
|
|
1708
|
+
reset_fakes; run_gate "$R_CONTROL" gate --help
|
|
1709
|
+
want_rc 0 "E4: --help exits 0"
|
|
1710
|
+
want_field "E4: mode is help" mode 'help'
|
|
1711
|
+
|
|
1712
|
+
# ============================ portability (source scan) ========================
|
|
1713
|
+
# The contract bans specific spellings in the gate source; scanned over the SUT and
|
|
1714
|
+
# every adapter lib it ships, with comment-only lines excluded.
|
|
1715
|
+
|
|
1716
|
+
_PORT_FILES=("$SUT")
|
|
1717
|
+
for _f in "$DIR"/lib/affected-tests/*.sh; do [[ -f "$_f" ]] && _PORT_FILES+=("$_f"); done
|
|
1718
|
+
port_hits=0
|
|
1719
|
+
# Banned spellings only: the self-match proc forms and the GNU-only flag shapes.
|
|
1720
|
+
# `timeout`/`nproc` are deliberately NOT banned — they are legitimate rungs of the
|
|
1721
|
+
# mandated affected_timeout / affected_ncpu chains; what the contract forbids is a
|
|
1722
|
+
# bare DEPENDENCY on them, which the helper-name assertions below pin instead.
|
|
1723
|
+
_BANNED='(pgrep|pkill)[[:space:]]+-[a-zA-Z]*f|ps[[:space:]][^|]*\|[[:space:]]*e?grep|sed[[:space:]][^|#]*-i([[:space:]]|$)|date[[:space:]]+-[a-zA-Z]*d|readlink[[:space:]]+-f|stat[[:space:]]+-[a-zA-Z]*c'
|
|
1724
|
+
for _f in "${_PORT_FILES[@]}"; do
|
|
1725
|
+
_hits="$(grep -avE '^[[:space:]]*#' "$_f" | grep -anE "$_BANNED" || true)"
|
|
1726
|
+
if [[ -n "$_hits" ]]; then port_hits=$((port_hits + 1)); fail "D23: portability violation in $_f: $(head -n 3 <<<"$_hits" | tr '\n' '|')"; fi
|
|
1727
|
+
done
|
|
1728
|
+
[[ "$port_hits" == "0" ]] && pass "D23: no banned spellings (self-match proc scans, GNU-only flags) in gate sources"
|
|
1729
|
+
# The mandated portability helpers exist under their contractual names
|
|
1730
|
+
# (common.sh: affected_ncpu, affected_running_gates, affected_timeout, affected_emit).
|
|
1731
|
+
_allsrc="$(cat "${_PORT_FILES[@]}" 2>/dev/null | tr -d "\0")"
|
|
1732
|
+
for _h in affected_ncpu affected_running_gates affected_timeout; do
|
|
1733
|
+
if grep -aq "$_h" <<<"$_allsrc"; then pass "D23b: helper $_h present"
|
|
1734
|
+
else fail "D23b: contractual helper $_h missing from gate sources"; fi
|
|
1735
|
+
done
|
|
1736
|
+
# finish() is the contractual single exit site; the emit side may be a named
|
|
1737
|
+
# helper or inline emission inside finish() — require finish() plus an emit path.
|
|
1738
|
+
if grep -aq 'finish()' <<<"$_allsrc" && grep -aqi 'emit' <<<"$_allsrc"; then
|
|
1739
|
+
pass "D23b: finish() exit site and emit path present"
|
|
1740
|
+
else
|
|
1741
|
+
fail "D23b: no finish() exit site or emit path found in gate sources"
|
|
1742
|
+
fi
|
|
1743
|
+
_head="$(sed -n '1,120p' "$SUT")"
|
|
1744
|
+
if grep -qw 'git' <<<"$_head" && grep -qE 'getconf|sysctl' <<<"$_head" && grep -qE 'timeout|gtimeout' <<<"$_head"; then
|
|
1745
|
+
pass "D23c: the script header lists its external binaries"
|
|
1746
|
+
else
|
|
1747
|
+
fail "D23c: header does not enumerate the external binaries it invokes"
|
|
1748
|
+
fi
|
|
1749
|
+
# Every `go list` invocation in the go adapter runs under a bound — an
|
|
1750
|
+
# unbounded list can park the gate on module resolution (network fetch).
|
|
1751
|
+
_golist_unb="$(grep -anE 'list -e' "$DIR/lib/affected-tests/go.sh" \
|
|
1752
|
+
| grep -av 'affected_timeout\|affected_bound' || true)"
|
|
1753
|
+
if [[ -z "$_golist_unb" ]]; then
|
|
1754
|
+
pass "D23d: every go list call is bounded (affected_timeout/affected_bound)"
|
|
1755
|
+
else
|
|
1756
|
+
fail "D23d: unbounded go list call in go.sh: $(head -n 3 <<<"$_golist_unb" | tr '\n' '|')"
|
|
1757
|
+
fi
|
|
1758
|
+
unset _f _hits _head _PORT_FILES port_hits _BANNED _allsrc _h _golist_unb
|
|
1759
|
+
|
|
1760
|
+
# ============================ cross-run contract ===============================
|
|
1761
|
+
# G1-10: every run path emits the marker — a second exit site that fires before the
|
|
1762
|
+
# emit leaves a run with no AFFECTED_GATE line, and these loops catch it.
|
|
1763
|
+
# ALL recorded kinds are swept (gate AND explain AND selfcheck) — explain and
|
|
1764
|
+
# self-check are exit paths too and must not slip a marker-less finish.
|
|
1765
|
+
_xfail=0
|
|
1766
|
+
while IFS=$'\t' read -r _kind _rc _of; do
|
|
1767
|
+
if ! grep -qE 'AFFECTED_GATE ' "$_of"; then
|
|
1768
|
+
_xfail=$((_xfail + 1)); fail "X1: run kind=$_kind rc=$_rc emitted no AFFECTED_GATE marker: $_of"
|
|
1769
|
+
fi
|
|
1770
|
+
done < "$RUNS"
|
|
1771
|
+
[[ "$_xfail" == "0" ]] && pass "X1: every run (gate/explain/selfcheck) emitted the AFFECTED_GATE marker (single finish() exit site)"
|
|
1772
|
+
_badword=0
|
|
1773
|
+
while IFS=$'\t' read -r _kind _rc _of; do
|
|
1774
|
+
if grep -qiE 'tests verified' "$_of"; then
|
|
1775
|
+
_badword=$((_badword + 1)); fail "X2: 'tests verified' wording emitted: $_of"
|
|
1776
|
+
fi
|
|
1777
|
+
done < "$RUNS"
|
|
1778
|
+
[[ "$_badword" == "0" ]] && pass "X2: no run ever says 'tests verified'"
|
|
1779
|
+
# G1-11: "the runner ran" verdicts read the invocation ledger, never exit codes
|
|
1780
|
+
# alone — prove the check itself bites on an empty ledger.
|
|
1781
|
+
: > "$TESTROOT/empty-ledger.log"
|
|
1782
|
+
if log_nonempty "$TESTROOT/empty-ledger.log"; then
|
|
1783
|
+
fail "X3: log_nonempty passed an empty ledger — the Guard-1-11 ledger check is blind"
|
|
1784
|
+
else
|
|
1785
|
+
pass "X3: log_nonempty refuses an empty ledger"
|
|
1786
|
+
fi
|
|
1787
|
+
printf 'x\n' > "$TESTROOT/nonempty-ledger.log"
|
|
1788
|
+
if log_nonempty "$TESTROOT/nonempty-ledger.log"; then
|
|
1789
|
+
pass "X3b: log_nonempty accepts a populated ledger"
|
|
1790
|
+
else
|
|
1791
|
+
fail "X3b: log_nonempty refuses a populated ledger"
|
|
1792
|
+
fi
|
|
1793
|
+
unset _kind _rc _of _xfail _badword
|
|
1794
|
+
|
|
1795
|
+
print_results "${AFFECTED_TESTS_MIN_ASSERTIONS:-60}"
|
|
1796
|
+
|
|
1797
|
+
|