@soleur/opencode 0.0.0-stage → 1.0.0
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 +659 -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 +108 -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 +342 -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 +152 -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 +493 -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 +363 -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 +520 -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 +113 -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 +178 -0
- package/scripts/sync-opencode-adapter.ts +479 -0
- package/scripts/sync-pr-behind.sh +534 -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 +65 -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 +142 -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 +118 -0
- package/skills/fix-issue/references/exclude-label-jq-snippet.md +96 -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 +668 -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 +325 -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 +138 -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 +1621 -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-todo-structure.md +165 -0
- package/skills/review/references/risk-tier-and-fix-rounds.md +95 -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 +3022 -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/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/detached-battery-verdict.md +13 -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 +149 -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/work/SKILL.md +1479 -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,1056 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# 2026-04-21 reproducer: cleanup_merged_worktrees must NOT reap a worktree
|
|
3
|
+
# whose `session-state.sh` lease is still active.
|
|
4
|
+
#
|
|
5
|
+
# Plan: knowledge-base/project/plans/2026-05-12-feat-bg-readiness-concurrency-hardening-plan.md
|
|
6
|
+
# Seed: knowledge-base/project/learnings/2026-04-21-concurrent-cleanup-merged-wipes-active-worktree.md
|
|
7
|
+
#
|
|
8
|
+
# Run via: bash plugins/soleur/skills/git-worktree/test/lease-protects-active.test.sh
|
|
9
|
+
|
|
10
|
+
set -uo pipefail
|
|
11
|
+
|
|
12
|
+
# Git-location tripwire (#7833). These suites drive worktree-manager.sh, which runs
|
|
13
|
+
# `git worktree remove`, `git branch -D` and `git reset --hard` -- the highest-damage git writes
|
|
14
|
+
# in the corpus. An inherited GIT_DIR aims all of them at the developer's real repository, so
|
|
15
|
+
# this file aborts rather than proceeding. Inline rather than sourcing test-helpers.sh, which
|
|
16
|
+
# would also import an assertion framework these suites do not use.
|
|
17
|
+
for _v in GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE GIT_COMMON_DIR GIT_OBJECT_DIRECTORY \
|
|
18
|
+
GIT_ALTERNATE_OBJECT_DIRECTORIES GIT_NAMESPACE GIT_TEMPLATE_DIR GIT_EXEC_PATH; do
|
|
19
|
+
if [[ -n "${!_v:-}" && "${SOLEUR_GIT_TRIPWIRE_ALLOW:-0}" != "1" ]]; then
|
|
20
|
+
printf 'FATAL: %s started with an inherited git-location environment (%s=%s).\n' \
|
|
21
|
+
"${BASH_SOURCE[0]}" "$_v" "${!_v}" >&2
|
|
22
|
+
printf 'Fix the ENTRY POINT: unset %s && <runner>\n' "$_v" >&2
|
|
23
|
+
exit 97
|
|
24
|
+
fi
|
|
25
|
+
done
|
|
26
|
+
unset _v
|
|
27
|
+
|
|
28
|
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../../.." && pwd)"
|
|
29
|
+
WM="$REPO_ROOT/plugins/soleur/skills/git-worktree/scripts/worktree-manager.sh"
|
|
30
|
+
SS="$REPO_ROOT/plugins/soleur/scripts/lib/session-state.sh"
|
|
31
|
+
|
|
32
|
+
PASS=0; FAIL=0
|
|
33
|
+
fail() { echo " FAIL: $1"; FAIL=$((FAIL+1)); }
|
|
34
|
+
pass() { echo " pass: $1"; PASS=$((PASS+1)); }
|
|
35
|
+
|
|
36
|
+
TMP=$(mktemp -d) || { echo "FATAL: mktemp -d failed — refusing to run with an empty \$TMP, every fixture path would resolve against /" >&2; exit 2; }
|
|
37
|
+
trap 'rm -rf "$TMP"' EXIT
|
|
38
|
+
|
|
39
|
+
# Resolved ONCE, with `pwd -P`: TMPDIR is routinely a symlink, and comparing an
|
|
40
|
+
# unresolved $TMP against a resolved toplevel false-FAILS every call.
|
|
41
|
+
TMP_REAL="$(cd "$TMP" && pwd -P)" || TMP_REAL=""
|
|
42
|
+
# An EMPTY TMP_REAL would silently disable the containment check below, because the
|
|
43
|
+
# pattern `"$TMP_REAL"/*` degrades to `/*`, which matches every absolute path — the
|
|
44
|
+
# guard would return 0 for a live repository. That is the same fail-open shape this
|
|
45
|
+
# whole helper exists to close, so it is asserted rather than assumed. `readonly`
|
|
46
|
+
# stops a later assignment from reintroducing it.
|
|
47
|
+
case "$TMP_REAL" in
|
|
48
|
+
/*) : ;;
|
|
49
|
+
*) echo "FATAL: could not resolve the fixture root '$TMP' to an absolute path — refusing, an empty root makes the containment check match everything" >&2
|
|
50
|
+
exit 2 ;;
|
|
51
|
+
esac
|
|
52
|
+
readonly TMP_REAL
|
|
53
|
+
|
|
54
|
+
# cd into a fixture, or refuse. Two arms, and the second is why `cd X || exit` is
|
|
55
|
+
# not enough:
|
|
56
|
+
# (a) the directory is absent -> cd fails.
|
|
57
|
+
# (b) the directory EXISTS but is not a repository -> cd SUCCEEDS, and git then
|
|
58
|
+
# walks UP to the enclosing repository. On 2026-08-20 that enclosing repo was
|
|
59
|
+
# a live worktree: `git worktree add` had created the path and then failed, so
|
|
60
|
+
# every guard keyed on cd's exit status passed and the fixture's commits landed
|
|
61
|
+
# on a real branch. Measured on main at 58486e1ae: HEAD 10b74df57 -> f5fd37bd6,
|
|
62
|
+
# carrying "victim", "victim2", "v9 change", "v12 change".
|
|
63
|
+
# So containment is asserted on the RESOLVED git toplevel, not on cd's exit status.
|
|
64
|
+
cdx() {
|
|
65
|
+
local target="$1" top
|
|
66
|
+
if ! cd "$target" 2>/dev/null; then
|
|
67
|
+
printf ' FAIL: fixture directory absent: %s\n' "$target" >&2
|
|
68
|
+
printf ' refusing to run fixture commands in %s — that is a LIVE repository.\n' "$PWD" >&2
|
|
69
|
+
: > "$TMP/.cdx-failed"
|
|
70
|
+
exit 90
|
|
71
|
+
fi
|
|
72
|
+
top="$(git rev-parse --show-toplevel 2>/dev/null)" || top=""
|
|
73
|
+
case "${top:-<none>}" in
|
|
74
|
+
"$TMP_REAL"|"$TMP_REAL"/*) return 0 ;;
|
|
75
|
+
esac
|
|
76
|
+
printf ' FAIL: %s exists, but its git toplevel is %s\n' "$target" "${top:-<not a git repository>}" >&2
|
|
77
|
+
printf ' which is OUTSIDE the fixture root %s — refusing, git would write to that repository.\n' "$TMP_REAL" >&2
|
|
78
|
+
: > "$TMP/.cdx-failed"
|
|
79
|
+
exit 90
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
# MERGE-RESOLUTION SELF-CHECK. This file was ALREADY partially hardened once — two sites carried
|
|
83
|
+
# explicit "Guarded cd" comments while five did not — and it regressed anyway; the reaper sites
|
|
84
|
+
# carry no fixture-commit symptom, so a regression there is silent. Two independent fixes to this
|
|
85
|
+
# file are in flight simultaneously, which makes "a merge drops one site back to a bare cd" a live
|
|
86
|
+
# possibility rather than a hypothetical. Assert the PROPERTY here instead of trusting review to
|
|
87
|
+
# notice. Excludes the `&&` forms, which short-circuit and are safe.
|
|
88
|
+
# Keyed on STRUCTURE, never on any guard WORDING, and evaluated on LOGICAL lines.
|
|
89
|
+
#
|
|
90
|
+
# Three corrections, each found by mutation rather than by reading:
|
|
91
|
+
# 1. Wording. A check greping for `cdx(` flags a site guarded correctly some other way; one
|
|
92
|
+
# greping for a guard PHRASE clears a site guarded with different wording. Not hypothetical
|
|
93
|
+
# -- a sibling session counted its own guarded sites with `grep -c <its guard phrase>`, got
|
|
94
|
+
# 5, and the answer was 7: it had worded its two reaper guards differently.
|
|
95
|
+
# 2. Reach. Anchoring at line start inspected 7 of this file 25 `cd "` sites -- the dominant
|
|
96
|
+
# `if ( cd "` form was invisible, while the message below claimed to refuse on ANY bare cd.
|
|
97
|
+
# A guard narrower than the claim it carries is this suite own subject, one level up.
|
|
98
|
+
# 3. Line shape. Most sites here are backslash-continued, so the `&&` that handles the failure
|
|
99
|
+
# sits on the NEXT physical line. Judging physical lines both misses a real defect and
|
|
100
|
+
# false-flags a correctly guarded two-line site.
|
|
101
|
+
#
|
|
102
|
+
# So: strip comments (prose naming `cd "` must not be inspected -- this block itself would
|
|
103
|
+
# match), join continuations into one logical line, then flag any `cd "` whose logical line
|
|
104
|
+
# neither carries `&&`/`||` nor makes cd itself the tested command. `if ( cd "$X" ... )` is NOT
|
|
105
|
+
# exempt merely for containing `if`: that tests the SUBSHELL, so a resolution dropping the `&&`
|
|
106
|
+
# leaves cd failure swallowed and the next command running in the wrong directory, which parses
|
|
107
|
+
# cleanly and is silent. That case is mutation-proven below.
|
|
108
|
+
_bare_cd="$(awk '
|
|
109
|
+
BEGIN { start_ln = 1 }
|
|
110
|
+
{
|
|
111
|
+
l = $0
|
|
112
|
+
sub(/(^|[[:space:]])#.*$/, "", l)
|
|
113
|
+
line = line l
|
|
114
|
+
if (sub(/\\[[:space:]]*$/, " ", line)) next
|
|
115
|
+
if (line ~ /(^|[^[:alnum:]_])cd "/ \
|
|
116
|
+
&& line !~ /&&/ && line !~ /\|\|/ \
|
|
117
|
+
&& line !~ /(^|[[:space:]])(if|while|until)[[:space:]]+!?[[:space:]]*cd[[:space:]]"/ \
|
|
118
|
+
&& line !~ /(^|[^[:alnum:]_])cdx[[:space:]]/) print start_ln ": " line
|
|
119
|
+
line = ""; start_ln = NR + 1
|
|
120
|
+
}
|
|
121
|
+
' "${BASH_SOURCE[0]}")"
|
|
122
|
+
if [[ -n "$_bare_cd" ]]; then
|
|
123
|
+
printf ' FAIL: unguarded cd site(s) in this suite — every fixture cd must go through cdx():\n' >&2
|
|
124
|
+
printf '%s\n' "$_bare_cd" >&2
|
|
125
|
+
printf ' A bare cd here runs git in the CALLER CWD when the fixture is missing.\n' >&2
|
|
126
|
+
exit 1
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
# ---------------------------------------------------------------------------
|
|
130
|
+
# Stand up a fake bare repo with two branches: main + feat-victim. Merge
|
|
131
|
+
# feat-victim into main so it qualifies as "merged" for cleanup_merged_worktrees.
|
|
132
|
+
# ---------------------------------------------------------------------------
|
|
133
|
+
BARE="$TMP/repo.git"
|
|
134
|
+
git init --bare -b main "$BARE" >/dev/null
|
|
135
|
+
|
|
136
|
+
# Seed a commit on main via a temporary clone
|
|
137
|
+
SEED="$TMP/seed"
|
|
138
|
+
git clone "$BARE" "$SEED" >/dev/null 2>&1
|
|
139
|
+
( cdx "$SEED"
|
|
140
|
+
git -c user.email=t@t -c user.name=t commit --allow-empty -m "seed" >/dev/null
|
|
141
|
+
git push origin main >/dev/null 2>&1
|
|
142
|
+
)
|
|
143
|
+
rm -rf "$SEED"
|
|
144
|
+
|
|
145
|
+
# Create a worktree directory simulating the layout worktree-manager.sh expects.
|
|
146
|
+
# We deliberately do NOT call `worktree-manager.sh feature` because that path
|
|
147
|
+
# performs network operations (git push -u origin) once Phase 4 wiring lands —
|
|
148
|
+
# and we want this test to exercise cleanup independently. Build the worktree
|
|
149
|
+
# state manually.
|
|
150
|
+
WT_PARENT="$TMP/wt-parent"
|
|
151
|
+
mkdir -p "$WT_PARENT/.worktrees"
|
|
152
|
+
|
|
153
|
+
# Anchor a fake "victim" checkout — the worktree that holds an active lease
|
|
154
|
+
# and which a sibling cleanup-merged invocation must NOT reap.
|
|
155
|
+
git -C "$BARE" worktree add -b feat-victim "$WT_PARENT/.worktrees/feat-victim" main >/dev/null 2>&1
|
|
156
|
+
( cdx "$WT_PARENT/.worktrees/feat-victim"
|
|
157
|
+
echo hi > a.txt
|
|
158
|
+
git -c user.email=t@t -c user.name=t add a.txt
|
|
159
|
+
# Date the commit older than the 10-min recent-commit grace so the lease
|
|
160
|
+
# is the ONLY protection — guarantees the test fails if the lease guard
|
|
161
|
+
# is removed (avoids vacuous green via the recent-commit code path).
|
|
162
|
+
GIT_COMMITTER_DATE="2025-01-01T00:00:00Z" \
|
|
163
|
+
git -c user.email=t@t -c user.name=t commit \
|
|
164
|
+
--date "2025-01-01T00:00:00Z" -m "victim change" >/dev/null
|
|
165
|
+
)
|
|
166
|
+
# Fast-forward main to the victim commit so `git branch --merged main` lists it.
|
|
167
|
+
VICTIM_SHA=$(git -C "$BARE" rev-parse refs/heads/feat-victim)
|
|
168
|
+
git -C "$BARE" update-ref refs/heads/main "$VICTIM_SHA"
|
|
169
|
+
|
|
170
|
+
# Anchor a sibling "actor" worktree — the session that runs cleanup-merged
|
|
171
|
+
# concurrently. This is what reproduces 2026-04-21: cleanup runs from
|
|
172
|
+
# session A while session B holds the lease on the victim.
|
|
173
|
+
git -C "$BARE" worktree add -b feat-actor "$WT_PARENT/.worktrees/feat-actor" main >/dev/null 2>&1
|
|
174
|
+
|
|
175
|
+
# ---------------------------------------------------------------------------
|
|
176
|
+
# Acquire a lease for feat-victim. The lease lives under git-common-dir,
|
|
177
|
+
# which for our fake bare repo is $BARE itself.
|
|
178
|
+
# ---------------------------------------------------------------------------
|
|
179
|
+
LEASE_ROOT="$BARE/soleur-session-state"
|
|
180
|
+
mkdir -p "$LEASE_ROOT/leases" "$LEASE_ROOT/locks" "$LEASE_ROOT/logs"
|
|
181
|
+
# Pre-stamp the #7409 first-run arming hold. Every scenario below asserts what
|
|
182
|
+
# the reaper does with a lease present or absent; the hold suppresses reaping
|
|
183
|
+
# entirely on a store's first armed run, so without this stamp "the victim
|
|
184
|
+
# survived" would pass for the wrong reason in every one of them. Scenario 12
|
|
185
|
+
# is where the hold itself is tested.
|
|
186
|
+
: > "$LEASE_ROOT/reaper-armed"
|
|
187
|
+
|
|
188
|
+
# Hold a real, alive PID for the lease. Background a long sleep.
|
|
189
|
+
sleep 300 &
|
|
190
|
+
HOLDER_PID=$!
|
|
191
|
+
trap 'kill "$HOLDER_PID" 2>/dev/null || true; rm -rf "$TMP"' EXIT
|
|
192
|
+
|
|
193
|
+
cat > "$LEASE_ROOT/leases/feat-victim.lease" <<EOF
|
|
194
|
+
pid=$HOLDER_PID
|
|
195
|
+
ppid=$$
|
|
196
|
+
skill=one-shot
|
|
197
|
+
started_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
198
|
+
expected_duration_min=240
|
|
199
|
+
hostname=$HOSTNAME
|
|
200
|
+
EOF
|
|
201
|
+
|
|
202
|
+
# ---------------------------------------------------------------------------
|
|
203
|
+
# Invoke worktree-manager.sh cleanup-merged from inside one of the fake
|
|
204
|
+
# worktrees. The worktree-manager.sh sources from $REPO_ROOT — that's the
|
|
205
|
+
# session-state.sh under test, even though GIT_ROOT will resolve to our
|
|
206
|
+
# fake bare repo via git rev-parse.
|
|
207
|
+
# ---------------------------------------------------------------------------
|
|
208
|
+
WT_VICTIM="$WT_PARENT/.worktrees/feat-victim"
|
|
209
|
+
WT_ACTOR="$WT_PARENT/.worktrees/feat-actor"
|
|
210
|
+
|
|
211
|
+
# Run cleanup-merged from the ACTOR worktree (a sibling). The existing
|
|
212
|
+
# "currently active" guard at worktree-manager.sh:795 only skips when PWD
|
|
213
|
+
# matches the worktree being considered — so feat-victim is NOT protected
|
|
214
|
+
# by that guard from a sibling session. Only the new lease guard protects it.
|
|
215
|
+
(
|
|
216
|
+
# `|| exit` IS LOAD-BEARING (#7546 review). A bare `cd` that FAILS leaves the subshell in the
|
|
217
|
+
# INHERITED cwd -- the real worktree `test-all.sh` was invoked from -- and the cleanup-merged
|
|
218
|
+
# below then runs against it. All seven `worktree add` calls in this file swallow failure with
|
|
219
|
+
# `>/dev/null 2>&1`, so $WT_ACTOR being absent is a reachable state (a leftover branch from a
|
|
220
|
+
# crashed run is enough). Measured 2026-08-20: this escaped the sandbox and committed
|
|
221
|
+
# `victim change`, `victim2 change`, `v9 change` and `v12 change` onto a live feature branch in
|
|
222
|
+
# another session's worktree, then checked that worktree out to main and pulled. The fixture
|
|
223
|
+
# names map one-to-one onto the four cd-failure sites in this file.
|
|
224
|
+
cdx "$WT_ACTOR"
|
|
225
|
+
SOLEUR_SESSION_STATE_ROOT="$LEASE_ROOT" \
|
|
226
|
+
bash "$WM" cleanup-merged >/tmp/cleanup-out.$$ 2>&1 || true
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
if [[ -d "$WT_VICTIM" ]]; then
|
|
230
|
+
pass "lease protected feat-victim worktree from sibling reap"
|
|
231
|
+
else
|
|
232
|
+
fail "feat-victim worktree was reaped by sibling despite active lease (output: $(cat /tmp/cleanup-out.$$ 2>/dev/null))"
|
|
233
|
+
fi
|
|
234
|
+
rm -f /tmp/cleanup-out.$$
|
|
235
|
+
|
|
236
|
+
kill "$HOLDER_PID" 2>/dev/null || true
|
|
237
|
+
|
|
238
|
+
# ---------------------------------------------------------------------------
|
|
239
|
+
# SCENARIO 2 (#5454, 2026-08-06): the acquirer has EXITED.
|
|
240
|
+
#
|
|
241
|
+
# The scenario above holds a live `sleep 300` as the lease pid. That is the one
|
|
242
|
+
# shape real usage never has — and it is why this suite was green for months
|
|
243
|
+
# while the guard protected nothing.
|
|
244
|
+
#
|
|
245
|
+
# `acquire_lease` records `pid=$$`, and every documented entry point is a
|
|
246
|
+
# short-lived process that exits within milliseconds of writing the file:
|
|
247
|
+
#
|
|
248
|
+
# bash <plugin-root>/scripts/lib/session-state.sh acquire_lease <worktree>
|
|
249
|
+
# bash .../worktree-manager.sh --yes create <branch>
|
|
250
|
+
#
|
|
251
|
+
# So in production the lease pid is ALWAYS dead moments after acquisition.
|
|
252
|
+
# `is_lease_active` gated on `kill -0`, `sweep_orphan_leases` deleted the file
|
|
253
|
+
# on a dead pid, and cleanup-merged consequently reaped the worktree, deleted
|
|
254
|
+
# the branch locally AND on origin, and closed the PR — twice in one afternoon,
|
|
255
|
+
# mid-run, on a leased worktree with hours of duration remaining.
|
|
256
|
+
#
|
|
257
|
+
# A fixture that instantiates only the passing member of a set is a sample, not
|
|
258
|
+
# a proof. This arm instantiates the member that actually occurs.
|
|
259
|
+
# ---------------------------------------------------------------------------
|
|
260
|
+
git -C "$BARE" worktree add -b feat-victim2 "$WT_PARENT/.worktrees/feat-victim2" main >/dev/null 2>&1
|
|
261
|
+
WT_VICTIM2="$WT_PARENT/.worktrees/feat-victim2"
|
|
262
|
+
( cdx "$WT_VICTIM2"
|
|
263
|
+
echo hi2 > b.txt
|
|
264
|
+
git -c user.email=t@t -c user.name=t add b.txt
|
|
265
|
+
# Older than the 10-minute recent-commit grace, so the LEASE is the only
|
|
266
|
+
# thing that can protect it — otherwise this passes via the grace window and
|
|
267
|
+
# proves nothing about the lease.
|
|
268
|
+
GIT_COMMITTER_DATE="2025-01-01T00:00:00Z" \
|
|
269
|
+
git -c user.email=t@t -c user.name=t commit \
|
|
270
|
+
--date "2025-01-01T00:00:00Z" -m "victim2 change" >/dev/null
|
|
271
|
+
)
|
|
272
|
+
VICTIM2_SHA=$(git -C "$BARE" rev-parse refs/heads/feat-victim2)
|
|
273
|
+
git -C "$BARE" update-ref refs/heads/main "$VICTIM2_SHA"
|
|
274
|
+
|
|
275
|
+
# Acquire through the REAL entry point in a child process, then let it exit —
|
|
276
|
+
# reproducing production rather than simulating it.
|
|
277
|
+
bash -c "
|
|
278
|
+
export SOLEUR_SESSION_STATE_ROOT='$LEASE_ROOT'
|
|
279
|
+
# shellcheck source=/dev/null
|
|
280
|
+
source '$SS'
|
|
281
|
+
acquire_lease feat-victim2 one-shot 240
|
|
282
|
+
" >/dev/null 2>&1
|
|
283
|
+
|
|
284
|
+
DEAD_PID=$(grep '^pid=' "$LEASE_ROOT/leases/feat-victim2.lease" 2>/dev/null | cut -d= -f2)
|
|
285
|
+
if [[ -z "$DEAD_PID" ]]; then
|
|
286
|
+
fail "scenario 2 setup: no lease file written for feat-victim2"
|
|
287
|
+
elif kill -0 "$DEAD_PID" 2>/dev/null; then
|
|
288
|
+
# Asserted, not assumed: a live pid here would exercise scenario 1 again and
|
|
289
|
+
# pass vacuously against the very defect this arm exists to pin.
|
|
290
|
+
fail "scenario 2 precondition: recorded pid $DEAD_PID is still alive — cannot reach the dead-acquirer path"
|
|
291
|
+
else
|
|
292
|
+
pass "scenario 2 precondition: the acquiring process has exited (pid $DEAD_PID is dead)"
|
|
293
|
+
(
|
|
294
|
+
# `|| exit` load-bearing -- see the note at the first cd site above.
|
|
295
|
+
cdx "$WT_ACTOR"
|
|
296
|
+
SOLEUR_SESSION_STATE_ROOT="$LEASE_ROOT" \
|
|
297
|
+
bash "$WM" cleanup-merged >/tmp/cleanup2-out.$$ 2>&1 || true
|
|
298
|
+
)
|
|
299
|
+
if [[ -d "$WT_VICTIM2" ]]; then
|
|
300
|
+
pass "a lease whose acquirer exited STILL protects the worktree from a sibling reap"
|
|
301
|
+
else
|
|
302
|
+
fail "worktree reaped despite a valid in-window lease whose acquirer had exited — this is #5454 (output: $(cat /tmp/cleanup2-out.$$ 2>/dev/null))"
|
|
303
|
+
fi
|
|
304
|
+
rm -f /tmp/cleanup2-out.$$
|
|
305
|
+
fi
|
|
306
|
+
|
|
307
|
+
# ---------------------------------------------------------------------------
|
|
308
|
+
# SCENARIO 3 (PR #7373, second half): `create` must ACQUIRE a lease.
|
|
309
|
+
#
|
|
310
|
+
# Scenarios 1-2 prove a lease PROTECTS a worktree. Neither proves one is ever
|
|
311
|
+
# WRITTEN by the entry point the autonomous pipeline actually calls. It was not:
|
|
312
|
+
# `create_worktree` (dispatched from `create`) had zero acquire_lease calls,
|
|
313
|
+
# while `create_for_feature` (dispatched from `feature|feat`) had one. one-shot
|
|
314
|
+
# Step 0b and work Phase 1 both invoke `--yes create`, so every worktree the
|
|
315
|
+
# pipeline made carried no lease at all — and the documented SOLEUR_SKILL_NAME /
|
|
316
|
+
# SOLEUR_EXPECTED_DURATION_MIN env vars were read by nobody on that path.
|
|
317
|
+
#
|
|
318
|
+
# A lease layer that protects but is never acquired protects nothing. The header
|
|
319
|
+
# on scenario 2 ("a fixture that instantiates only the passing member of a set is
|
|
320
|
+
# a sample, not a proof") applied to this file itself until this arm landed.
|
|
321
|
+
#
|
|
322
|
+
# Fixture note: `create_worktree` resolves its base through `resolve_base_ref`,
|
|
323
|
+
# which needs origin/<from> — so this arm needs a local bare repo WITH an origin
|
|
324
|
+
# remote, not the bare-repo-only shape scenarios 1 and 2 use.
|
|
325
|
+
# ---------------------------------------------------------------------------
|
|
326
|
+
UP3="$TMP/up3.git"; git init --bare -b main "$UP3" >/dev/null
|
|
327
|
+
S3="$TMP/s3"; git clone "$UP3" "$S3" >/dev/null 2>&1
|
|
328
|
+
( cd "$S3" && git -c user.email=t@t -c user.name=t commit --allow-empty -m seed >/dev/null \
|
|
329
|
+
&& git push origin main >/dev/null 2>&1 )
|
|
330
|
+
rm -rf "$S3"
|
|
331
|
+
LOCAL3="$TMP/local3.git"; git init --bare -b main "$LOCAL3" >/dev/null
|
|
332
|
+
( cd "$LOCAL3" && git remote add origin "$UP3" && git fetch --no-tags origin main:main >/dev/null 2>&1 )
|
|
333
|
+
|
|
334
|
+
LEASE_ROOT3="$LOCAL3/soleur-session-state"
|
|
335
|
+
# `if ( cd … && … )` rather than `&& pass || fail` (SC2015) or a bare `cd`
|
|
336
|
+
# (SC2164). The guarded cd is load-bearing, not style: this suite runs
|
|
337
|
+
# `set -uo pipefail` WITHOUT -e, so an unguarded `cd "$LOCAL3"` that fails would
|
|
338
|
+
# let the subshell continue and run `--yes create` against the DEVELOPER'S REAL
|
|
339
|
+
# REPO, creating a real worktree, branch and lease.
|
|
340
|
+
if ( cd "$LOCAL3" \
|
|
341
|
+
&& SOLEUR_SESSION_STATE_ROOT="$LEASE_ROOT3" \
|
|
342
|
+
SOLEUR_SKILL_NAME=one-shot SOLEUR_EXPECTED_DURATION_MIN=137 \
|
|
343
|
+
bash "$WM" --yes create feat-probe >"$TMP/create3.log" 2>&1 ); then
|
|
344
|
+
pass "scenario 3: --yes create exited 0"
|
|
345
|
+
else
|
|
346
|
+
fail "scenario 3: --yes create failed (output: $(cat "$TMP/create3.log"))"
|
|
347
|
+
fi
|
|
348
|
+
|
|
349
|
+
LEASE3="$LEASE_ROOT3/leases/feat-probe.lease"
|
|
350
|
+
# Assert the FILE, never the directory: session-state.sh mkdir -p's leases/ at
|
|
351
|
+
# source time regardless of whether anything is written, so a `[[ -d ]]` check
|
|
352
|
+
# false-passes against the bug (measured during plan deepening).
|
|
353
|
+
if [[ -f "$LEASE3" ]]; then
|
|
354
|
+
pass "scenario 3: --yes create wrote $LEASE3"
|
|
355
|
+
# The no-op stubs worktree-manager.sh installs when session-state.sh is absent
|
|
356
|
+
# return 0 and write nothing, which would satisfy an exit-0 check; a `pid=`
|
|
357
|
+
# line can only come from a real acquire_lease.
|
|
358
|
+
if grep -q '^pid=' "$LEASE3"; then
|
|
359
|
+
pass "scenario 3: lease carries a pid= line (real acquire_lease, not a stub)"
|
|
360
|
+
else
|
|
361
|
+
fail "scenario 3: lease file exists but has no pid= line"
|
|
362
|
+
fi
|
|
363
|
+
# The env vars must be READ, not merely set by this test. Half the point of
|
|
364
|
+
# #5454 is that SOLEUR_SKILL_NAME / SOLEUR_EXPECTED_DURATION_MIN were
|
|
365
|
+
# documented on this path and consumed by nobody — so asserting the file
|
|
366
|
+
# exists proves the fix only halfway. Measured: replacing both reads with the
|
|
367
|
+
# literals "unknown"/"240" left the suite fully GREEN without these two.
|
|
368
|
+
if grep -q '^skill=one-shot$' "$LEASE3"; then
|
|
369
|
+
pass "scenario 3: SOLEUR_SKILL_NAME reached the lease (skill=one-shot)"
|
|
370
|
+
else
|
|
371
|
+
fail "scenario 3: lease does not carry skill=one-shot — the env var is not being read \
|
|
372
|
+
(got: $(grep '^skill=' "$LEASE3" 2>/dev/null || echo NONE))"
|
|
373
|
+
fi
|
|
374
|
+
if grep -q '^expected_duration_min=137$' "$LEASE3"; then
|
|
375
|
+
pass "scenario 3: SOLEUR_EXPECTED_DURATION_MIN reached the lease (137, a NON-default value)"
|
|
376
|
+
else
|
|
377
|
+
fail "scenario 3: lease does not carry expected_duration_min=137 — the env var is not being read \
|
|
378
|
+
(got: $(grep '^expected_duration_min=' "$LEASE3" 2>/dev/null || echo NONE))"
|
|
379
|
+
fi
|
|
380
|
+
else
|
|
381
|
+
fail "scenario 3: NO lease written by --yes create — this is the second half (PR #7373) \
|
|
382
|
+
(dir: $(ls -A "$LEASE_ROOT3/leases" 2>/dev/null || echo MISSING))"
|
|
383
|
+
fi
|
|
384
|
+
|
|
385
|
+
# ---------------------------------------------------------------------------
|
|
386
|
+
# SCENARIO 4 (PR #7373, re-entry arm): `create` on an EXISTING worktree must also
|
|
387
|
+
# acquire.
|
|
388
|
+
#
|
|
389
|
+
# Scenario 3 covers fresh creation. But both creating functions have an early
|
|
390
|
+
# return for "worktree already exists" that skips the whole tail of the
|
|
391
|
+
# function — including the lease block. `--yes create` sets response=y there,
|
|
392
|
+
# so a RESUMED one-shot/work run takes exactly this path and, without the fix,
|
|
393
|
+
# holds no lease for its entire duration while a sibling cleanup-merged is free
|
|
394
|
+
# to reap it.
|
|
395
|
+
#
|
|
396
|
+
# The lease belongs to "worked in", not to "created". This arm pins that.
|
|
397
|
+
# ---------------------------------------------------------------------------
|
|
398
|
+
rm -f "$LEASE3"
|
|
399
|
+
# ASSERT the setup landed. This `rm` carries the entire discriminating power of
|
|
400
|
+
# scenario 4 — it reuses scenario 3's repo, lease root AND branch name, so if the
|
|
401
|
+
# removal silently stops happening the arm reads scenario 3's lease and reports
|
|
402
|
+
# "re-entry re-acquired" while the re-entry acquire is absent. Measured: with the
|
|
403
|
+
# rm neutered and the re-entry block deleted, the suite went fully GREEN.
|
|
404
|
+
if [[ ! -f "$LEASE3" ]]; then
|
|
405
|
+
pass "scenario 4 fixture: scenario 3's lease was removed before the re-entry run"
|
|
406
|
+
else
|
|
407
|
+
fail "scenario 4 fixture: lease still present after rm — the arm cannot discriminate"
|
|
408
|
+
fi
|
|
409
|
+
|
|
410
|
+
if ( cd "$LOCAL3" \
|
|
411
|
+
&& SOLEUR_SESSION_STATE_ROOT="$LEASE_ROOT3" \
|
|
412
|
+
SOLEUR_SKILL_NAME=one-shot SOLEUR_EXPECTED_DURATION_MIN=137 \
|
|
413
|
+
bash "$WM" --yes create feat-probe >"$TMP/create4.log" 2>&1 ); then
|
|
414
|
+
pass "scenario 4: --yes create on an existing worktree exited 0"
|
|
415
|
+
else
|
|
416
|
+
fail "scenario 4: re-entry create failed (output: $(cat "$TMP/create4.log"))"
|
|
417
|
+
fi
|
|
418
|
+
|
|
419
|
+
# Prove we actually took the early-return path rather than re-creating, else
|
|
420
|
+
# this arm silently duplicates scenario 3 and pins nothing new.
|
|
421
|
+
#
|
|
422
|
+
# Anchored on create_worktree's OWN literal, not the bare token 'already exists'
|
|
423
|
+
# (cq-assert-anchor-not-bare-token). git itself prints "fatal: a branch named
|
|
424
|
+
# 'feat-probe' already exists" when the fresh-create path runs, and copy_env_files
|
|
425
|
+
# prints "$env_file already exists, backing up" — so the bare token passes on the
|
|
426
|
+
# exact path this assertion exists to exclude. Measured: forcing the fresh-create
|
|
427
|
+
# path made the bare-token form emit a false `pass:`.
|
|
428
|
+
# The negative half is what pins the direction: the fresh-create banner must be ABSENT.
|
|
429
|
+
if grep -q 'Worktree already exists at:' "$TMP/create4.log" \
|
|
430
|
+
&& ! grep -q 'Creating worktree from' "$TMP/create4.log"; then
|
|
431
|
+
pass "scenario 4 precondition: took the worktree-already-exists early return"
|
|
432
|
+
else
|
|
433
|
+
fail "scenario 4 precondition: did NOT take the early return (output: $(cat "$TMP/create4.log"))"
|
|
434
|
+
fi
|
|
435
|
+
|
|
436
|
+
if [[ -f "$LEASE3" ]] && grep -q '^pid=' "$LEASE3"; then
|
|
437
|
+
pass "scenario 4: re-entry re-acquired the lease — a resumed session is protected"
|
|
438
|
+
else
|
|
439
|
+
fail "scenario 4: NO lease written on re-entry — a resumed session runs unleased and reapable \
|
|
440
|
+
(dir: $(ls -A "$LEASE_ROOT3/leases" 2>/dev/null || echo MISSING))"
|
|
441
|
+
fi
|
|
442
|
+
|
|
443
|
+
# ---------------------------------------------------------------------------
|
|
444
|
+
# SCENARIO 5 (PR #7373): the `feature` verb's arms too — BOTH of them.
|
|
445
|
+
#
|
|
446
|
+
# Parity with create_for_feature is this fix's whole thesis, and half of it was
|
|
447
|
+
# unpinned: deleting create_for_feature's early-return acquire left the suite
|
|
448
|
+
# fully GREEN, because no fixture exercised `feature|feat` at all.
|
|
449
|
+
# ---------------------------------------------------------------------------
|
|
450
|
+
if ( cd "$LOCAL3" \
|
|
451
|
+
&& SOLEUR_SESSION_STATE_ROOT="$LEASE_ROOT3" \
|
|
452
|
+
SOLEUR_SKILL_NAME=one-shot SOLEUR_EXPECTED_DURATION_MIN=137 \
|
|
453
|
+
bash "$WM" --yes feature probe5 >"$TMP/feature5.log" 2>&1 ); then
|
|
454
|
+
: # exit status is asserted via the lease below; a push to the fixture origin
|
|
455
|
+
# can legitimately warn without meaning the lease failed
|
|
456
|
+
fi
|
|
457
|
+
LEASE5="$LEASE_ROOT3/leases/feat-probe5.lease"
|
|
458
|
+
if [[ -f "$LEASE5" ]] && grep -q '^pid=' "$LEASE5"; then
|
|
459
|
+
pass "scenario 5: feature (fresh) acquired a lease"
|
|
460
|
+
else
|
|
461
|
+
fail "scenario 5: feature (fresh) wrote NO lease \
|
|
462
|
+
(dir: $(ls -A "$LEASE_ROOT3/leases" 2>/dev/null || echo MISSING))"
|
|
463
|
+
fi
|
|
464
|
+
|
|
465
|
+
rm -f "$LEASE5"
|
|
466
|
+
if ( cd "$LOCAL3" \
|
|
467
|
+
&& SOLEUR_SESSION_STATE_ROOT="$LEASE_ROOT3" \
|
|
468
|
+
SOLEUR_SKILL_NAME=one-shot SOLEUR_EXPECTED_DURATION_MIN=137 \
|
|
469
|
+
bash "$WM" --yes feature probe5 >"$TMP/feature5b.log" 2>&1 ); then
|
|
470
|
+
:
|
|
471
|
+
fi
|
|
472
|
+
if [[ -f "$LEASE5" ]] && grep -q '^pid=' "$LEASE5"; then
|
|
473
|
+
pass "scenario 5: feature (re-entry) re-acquired a lease"
|
|
474
|
+
else
|
|
475
|
+
fail "scenario 5: feature (re-entry) wrote NO lease — a resumed feature session is reapable \
|
|
476
|
+
(output: $(cat "$TMP/feature5b.log"))"
|
|
477
|
+
fi
|
|
478
|
+
|
|
479
|
+
# ---------------------------------------------------------------------------
|
|
480
|
+
# SCENARIO 6 (PR #7373, re-pointed by #7408): a name the lease layer cannot key
|
|
481
|
+
# on must SAY SO.
|
|
482
|
+
#
|
|
483
|
+
# _validate_worktree_name rejects anything outside [A-Za-z0-9._-]. Measured
|
|
484
|
+
# before this arm: `--yes create feat/probe` exited 0, wrote ZERO leases, and
|
|
485
|
+
# emitted nothing an orchestrating agent could see — #5454's exact signature.
|
|
486
|
+
#
|
|
487
|
+
# RE-POINTED by #7408. This arm used `feat/probe6`, and the premise it rested on
|
|
488
|
+
# — "the lease cannot be made to work for that shape here" — is now FALSE:
|
|
489
|
+
# #7408 derives the lease key through `_safe_worktree_name`, so `feat/probe6`
|
|
490
|
+
# keys as `feat-probe6` and leases cleanly. Left as written, this arm asserted
|
|
491
|
+
# that a FIXED bug was still broken, and it went red the moment the fix landed.
|
|
492
|
+
#
|
|
493
|
+
# The scenario's real subject is the RESIDUAL class: a legal git refname that is
|
|
494
|
+
# still unkeyable AFTER slugification. `+` is legal in a refname (`git
|
|
495
|
+
# check-ref-format` accepts `feat+probe6`) and is outside the validator's
|
|
496
|
+
# character set, so it survives the transform unkeyable — which is exactly the
|
|
497
|
+
# class #7408's new `reason=name-not-keyable` discriminator exists to report.
|
|
498
|
+
# Asserting the reason= value here also gives that discriminator its only test.
|
|
499
|
+
# ---------------------------------------------------------------------------
|
|
500
|
+
if ( cd "$LOCAL3" \
|
|
501
|
+
&& SOLEUR_SESSION_STATE_ROOT="$LEASE_ROOT3" \
|
|
502
|
+
SOLEUR_SKILL_NAME=one-shot SOLEUR_EXPECTED_DURATION_MIN=137 \
|
|
503
|
+
bash "$WM" --yes create feat+probe6 >"$TMP/create6.log" 2>&1 ); then
|
|
504
|
+
:
|
|
505
|
+
fi
|
|
506
|
+
if grep -q 'SOLEUR_WORKTREE_LEASE_ACQUIRE_FAILED' "$TMP/create6.log"; then
|
|
507
|
+
pass "scenario 6: an unleasable branch name emits the ACQUIRE_FAILED marker"
|
|
508
|
+
else
|
|
509
|
+
fail "scenario 6: unleasable name ran UNLEASED and SILENT — no marker on stdout \
|
|
510
|
+
(output: $(cat "$TMP/create6.log"))"
|
|
511
|
+
fi
|
|
512
|
+
if grep -q 'reason=name-not-keyable' "$TMP/create6.log"; then
|
|
513
|
+
pass "scenario 6b: the marker discriminates the residual class (reason=name-not-keyable)"
|
|
514
|
+
else
|
|
515
|
+
fail "scenario 6b: marker did not carry reason=name-not-keyable — the #7408 discriminator \
|
|
516
|
+
is unreported for a name that survives slugification unkeyable \
|
|
517
|
+
(output: $(cat "$TMP/create6.log"))"
|
|
518
|
+
fi
|
|
519
|
+
# A slash-bearing name, by contrast, must now lease CLEANLY — the #7408 fix.
|
|
520
|
+
# Without this the arm above could be satisfied by a transform that does nothing.
|
|
521
|
+
if ( cd "$LOCAL3" \
|
|
522
|
+
&& SOLEUR_SESSION_STATE_ROOT="$LEASE_ROOT3" \
|
|
523
|
+
SOLEUR_SKILL_NAME=one-shot SOLEUR_EXPECTED_DURATION_MIN=137 \
|
|
524
|
+
bash "$WM" --yes create feat/probe6 >"$TMP/create6c.log" 2>&1 ); then
|
|
525
|
+
:
|
|
526
|
+
fi
|
|
527
|
+
if grep -q 'SOLEUR_WORKTREE_LEASE_ACQUIRE_FAILED' "$TMP/create6c.log"; then
|
|
528
|
+
fail "scenario 6c: a SLASH-bearing branch still failed to lease — #7408's fix is not in effect \
|
|
529
|
+
(output: $(cat "$TMP/create6c.log"))"
|
|
530
|
+
else
|
|
531
|
+
pass "scenario 6c: a slash-bearing branch leases cleanly under its slug"
|
|
532
|
+
fi
|
|
533
|
+
|
|
534
|
+
# ---------------------------------------------------------------------------
|
|
535
|
+
# SCENARIO 7 (PR #7373): `switch|go` must lease too.
|
|
536
|
+
#
|
|
537
|
+
# The last entry point that took an EXISTING worktree and leased nothing. A
|
|
538
|
+
# session that switches in and works for hours held no lease for any of them,
|
|
539
|
+
# which is the same hole the two early-return arms had — `switch_worktree` just
|
|
540
|
+
# lives in a different function, so fixing the arms did not reach it.
|
|
541
|
+
#
|
|
542
|
+
# Reuses scenario 3's worktree: delete the lease, switch in, assert it returns.
|
|
543
|
+
# ---------------------------------------------------------------------------
|
|
544
|
+
rm -f "$LEASE3"
|
|
545
|
+
if [[ ! -f "$LEASE3" ]]; then
|
|
546
|
+
pass "scenario 7 fixture: lease removed before the switch"
|
|
547
|
+
else
|
|
548
|
+
fail "scenario 7 fixture: lease still present after rm — the arm cannot discriminate"
|
|
549
|
+
fi
|
|
550
|
+
|
|
551
|
+
if ( cd "$LOCAL3" \
|
|
552
|
+
&& SOLEUR_SESSION_STATE_ROOT="$LEASE_ROOT3" \
|
|
553
|
+
SOLEUR_SKILL_NAME=one-shot SOLEUR_EXPECTED_DURATION_MIN=137 \
|
|
554
|
+
bash "$WM" --yes switch feat-probe >"$TMP/switch7.log" 2>&1 ); then
|
|
555
|
+
:
|
|
556
|
+
fi
|
|
557
|
+
if [[ -f "$LEASE3" ]] && grep -q '^pid=' "$LEASE3"; then
|
|
558
|
+
pass "scenario 7: switch acquired a lease — entering a worktree is working in it"
|
|
559
|
+
else
|
|
560
|
+
fail "scenario 7: switch wrote NO lease — a switched-into session runs unleased and reapable \
|
|
561
|
+
(output: $(cat "$TMP/switch7.log"))"
|
|
562
|
+
fi
|
|
563
|
+
|
|
564
|
+
# ---------------------------------------------------------------------------
|
|
565
|
+
# SCENARIO 8 (#7409): the MARKETPLACE-INSTALL layout.
|
|
566
|
+
#
|
|
567
|
+
# Every scenario above runs `worktree-manager.sh` out of THIS repo, where
|
|
568
|
+
# `../../../../..` reaches a tree that happens to contain the lease library. A
|
|
569
|
+
# marketplace user has no such tree: the plugin is copied alone into
|
|
570
|
+
# ~/.claude/plugins/cache/<mkt>/soleur/<ver>/, so a path walk that leaves the
|
|
571
|
+
# plugin root resolves to nothing, the no-op stubs load, and the entire lock and
|
|
572
|
+
# lease layer is silently absent for that entire population (#7409).
|
|
573
|
+
#
|
|
574
|
+
# FIXTURE DISCIPLINE: copy ONLY plugins/soleur. Copying the repo makes this
|
|
575
|
+
# vacuous, because the bug *is* the absence of everything outside that
|
|
576
|
+
# directory — asserted below as a precondition, not assumed.
|
|
577
|
+
# ---------------------------------------------------------------------------
|
|
578
|
+
CACHE_ROOT="$TMP/cache/soleur-mkt/soleur/9.9.9"
|
|
579
|
+
mkdir -p "$CACHE_ROOT"
|
|
580
|
+
cp -r "$REPO_ROOT/plugins/soleur/." "$CACHE_ROOT/" 2>/dev/null
|
|
581
|
+
CACHE_WM="$CACHE_ROOT/skills/git-worktree/scripts/worktree-manager.sh"
|
|
582
|
+
|
|
583
|
+
if [[ -z "$(find "$CACHE_ROOT" -name '.claude' -type d -print -quit 2>/dev/null)" ]]; then
|
|
584
|
+
pass "scenario 8 fixture: the cache tree contains no .claude/ — a real marketplace layout"
|
|
585
|
+
else
|
|
586
|
+
fail "scenario 8 fixture: cache tree contains a .claude/ directory — the fixture cannot \
|
|
587
|
+
reproduce #7409 and every assertion below would pass vacuously"
|
|
588
|
+
fi
|
|
589
|
+
|
|
590
|
+
# A throwaway repo with an origin remote (create_worktree resolves its base
|
|
591
|
+
# through resolve_base_ref, which needs origin/<from>).
|
|
592
|
+
UP8="$TMP/up8.git"; git init --bare -b main "$UP8" >/dev/null
|
|
593
|
+
S8="$TMP/s8"; git clone "$UP8" "$S8" >/dev/null 2>&1
|
|
594
|
+
( cd "$S8" && git -c user.email=t@t -c user.name=t commit --allow-empty -m seed >/dev/null \
|
|
595
|
+
&& git push origin main >/dev/null 2>&1 )
|
|
596
|
+
rm -rf "$S8"
|
|
597
|
+
LOCAL8="$TMP/local8.git"; git init --bare -b main "$LOCAL8" >/dev/null
|
|
598
|
+
( cd "$LOCAL8" && git remote add origin "$UP8" && git fetch --no-tags origin main:main >/dev/null 2>&1 )
|
|
599
|
+
LEASE_ROOT8="$LOCAL8/soleur-session-state"
|
|
600
|
+
|
|
601
|
+
# Guarded cd — this suite runs `set -uo pipefail` WITHOUT -e, so an unguarded
|
|
602
|
+
# `cd` failure would run `--yes create` against the developer's real repo.
|
|
603
|
+
if ( cd "$LOCAL8" \
|
|
604
|
+
&& SOLEUR_SESSION_STATE_ROOT="$LEASE_ROOT8" \
|
|
605
|
+
SOLEUR_SKILL_NAME=one-shot SOLEUR_EXPECTED_DURATION_MIN=137 \
|
|
606
|
+
bash "$CACHE_WM" --yes create feat-cacheprobe >"$TMP/create8.log" 2>&1 ); then
|
|
607
|
+
: # Deliberately NO exit-code assertion: `--yes create` exits 0 in BOTH the
|
|
608
|
+
# broken and the fixed state (_acquire_worktree_lease early-returns 0 when the
|
|
609
|
+
# library is missing, and every call site is `|| true`). The exit code carries
|
|
610
|
+
# no information here; the lease FILE is the only discriminator.
|
|
611
|
+
fi
|
|
612
|
+
|
|
613
|
+
LEASE8="$LEASE_ROOT8/leases/feat-cacheprobe.lease"
|
|
614
|
+
if [[ -f "$LEASE8" ]] && grep -q '^pid=' "$LEASE8"; then
|
|
615
|
+
pass "scenario 8: a cache-install worktree-manager.sh resolved the lease library and wrote a lease"
|
|
616
|
+
else
|
|
617
|
+
fail "scenario 8: NO lease written from a marketplace-install layout — this is #7409; every \
|
|
618
|
+
installed user runs with no lock/lease layer (output: $(cat "$TMP/create8.log" 2>/dev/null))"
|
|
619
|
+
fi
|
|
620
|
+
# 137, never 240: 240 is the default at BOTH worktree-manager.sh's
|
|
621
|
+
# ${SOLEUR_EXPECTED_DURATION_MIN:-240} and session-state.sh's ${3:-240}, so
|
|
622
|
+
# asserting it passes whether the env var is read or ignored (the #5454 vacuity
|
|
623
|
+
# class). skill=one-shot is discriminating too — the default is `unknown`.
|
|
624
|
+
if grep -q '^skill=one-shot$' "$LEASE8" 2>/dev/null; then
|
|
625
|
+
pass "scenario 8: SOLEUR_SKILL_NAME reached the lease from a cache install"
|
|
626
|
+
else
|
|
627
|
+
fail "scenario 8: lease does not carry skill=one-shot \
|
|
628
|
+
(got: $(grep '^skill=' "$LEASE8" 2>/dev/null || echo NONE))"
|
|
629
|
+
fi
|
|
630
|
+
if grep -q '^expected_duration_min=137$' "$LEASE8" 2>/dev/null; then
|
|
631
|
+
pass "scenario 8: SOLEUR_EXPECTED_DURATION_MIN reached the lease (137, a NON-default value)"
|
|
632
|
+
else
|
|
633
|
+
fail "scenario 8: lease does not carry expected_duration_min=137 \
|
|
634
|
+
(got: $(grep '^expected_duration_min=' "$LEASE8" 2>/dev/null || echo NONE))"
|
|
635
|
+
fi
|
|
636
|
+
|
|
637
|
+
# --- Scenario 8b (T2): the mutation arm -----------------------------------
|
|
638
|
+
# Delete the library from the cache fixture. Scenario 8 must become
|
|
639
|
+
# unsatisfiable — otherwise scenario 8 is passing for some reason other than
|
|
640
|
+
# the library resolving, and pins nothing.
|
|
641
|
+
CACHE_LIB="$(find "$CACHE_ROOT" -name 'session-state.sh' -print -quit 2>/dev/null)"
|
|
642
|
+
if [[ -n "$CACHE_LIB" ]]; then
|
|
643
|
+
mv "$CACHE_LIB" "$CACHE_LIB.disabled"
|
|
644
|
+
if ( cd "$LOCAL8" \
|
|
645
|
+
&& SOLEUR_SESSION_STATE_ROOT="$LEASE_ROOT8" \
|
|
646
|
+
SOLEUR_SKILL_NAME=one-shot SOLEUR_EXPECTED_DURATION_MIN=137 \
|
|
647
|
+
bash "$CACHE_WM" --yes create feat-cachemutant >"$TMP/create8b.log" 2>&1 ); then
|
|
648
|
+
:
|
|
649
|
+
fi
|
|
650
|
+
if [[ -f "$LEASE_ROOT8/leases/feat-cachemutant.lease" ]]; then
|
|
651
|
+
fail "scenario 8b (mutation): a lease was written with the library REMOVED — scenario 8 is \
|
|
652
|
+
vacuous, it is not measuring library resolution"
|
|
653
|
+
else
|
|
654
|
+
pass "scenario 8b (mutation): removing the library from the cache fixture stops the lease \
|
|
655
|
+
being written — scenario 8 is non-vacuous"
|
|
656
|
+
fi
|
|
657
|
+
if grep -q 'SOLEUR_WORKTREE_LEASE_LIB_MISSING' "$TMP/create8b.log"; then
|
|
658
|
+
pass "scenario 8b (mutation): the absence is reported on stdout, not silent"
|
|
659
|
+
else
|
|
660
|
+
fail "scenario 8b (mutation): library absent but NO marker on stdout \
|
|
661
|
+
(output: $(cat "$TMP/create8b.log" 2>/dev/null))"
|
|
662
|
+
fi
|
|
663
|
+
# The OK marker's DIRECTION, not just its presence. Scenario 10 asserts it
|
|
664
|
+
# fires when the library resolves; without this arm nothing asserts it does
|
|
665
|
+
# NOT fire when the library is gone — so hoisting the echo above the
|
|
666
|
+
# `if [[ -f "$_SS_LIB" ]]` (making it unconditional) left the whole suite
|
|
667
|
+
# green while the marker claimed protection was on with the layer absent.
|
|
668
|
+
# That is the worst possible failure for a signal whose entire purpose is to
|
|
669
|
+
# make silence unambiguous, and it is the marketplace user's only channel.
|
|
670
|
+
if grep -q 'SOLEUR_WORKTREE_LEASE_LIB_OK' "$TMP/create8b.log"; then
|
|
671
|
+
fail "scenario 8b (mutation): the ..._LIB_OK marker fired with the library ABSENT — \
|
|
672
|
+
it is not conditioned on resolution and reports protection that is not there"
|
|
673
|
+
else
|
|
674
|
+
pass "scenario 8b (mutation): ..._LIB_OK is absent when the library is — the marker \
|
|
675
|
+
discriminates rather than always firing"
|
|
676
|
+
fi
|
|
677
|
+
mv "$CACHE_LIB.disabled" "$CACHE_LIB"
|
|
678
|
+
else
|
|
679
|
+
fail "scenario 8b (mutation): no session-state.sh found anywhere under the cache fixture — \
|
|
680
|
+
the library does not ship in the plugin, which is the #7409 defect itself"
|
|
681
|
+
fi
|
|
682
|
+
|
|
683
|
+
# ---------------------------------------------------------------------------
|
|
684
|
+
# SCENARIO 9 (#7409, T3): the DESTRUCTIVE direction, in a cache layout.
|
|
685
|
+
#
|
|
686
|
+
# This PR ARMS THE REAPER for the marketplace population. Pre-fix their library
|
|
687
|
+
# is missing, so `is_lease_active(){ return 0; }` makes cleanup-merged refuse to
|
|
688
|
+
# reap anything, ever. Post-fix the library resolves and an unrecoverable
|
|
689
|
+
# operation — delete the worktree, the local branch, the remote branch, close
|
|
690
|
+
# the PR — goes live for them for the first time. Scenarios 8/8b cover
|
|
691
|
+
# ACQUISITION; nothing covers REFUSAL from a cache layout. That asymmetry is the
|
|
692
|
+
# whole risk profile of this change.
|
|
693
|
+
# ---------------------------------------------------------------------------
|
|
694
|
+
BARE9="$TMP/repo9.git"; git init --bare -b main "$BARE9" >/dev/null
|
|
695
|
+
SEED9="$TMP/seed9"; git clone "$BARE9" "$SEED9" >/dev/null 2>&1
|
|
696
|
+
( cd "$SEED9" && git -c user.email=t@t -c user.name=t commit --allow-empty -m seed >/dev/null \
|
|
697
|
+
&& git push origin main >/dev/null 2>&1 )
|
|
698
|
+
rm -rf "$SEED9"
|
|
699
|
+
WT9="$TMP/wt9"; mkdir -p "$WT9/.worktrees"
|
|
700
|
+
git -C "$BARE9" worktree add -b feat-v9 "$WT9/.worktrees/feat-v9" main >/dev/null 2>&1
|
|
701
|
+
( cdx "$WT9/.worktrees/feat-v9"
|
|
702
|
+
echo hi9 > c.txt
|
|
703
|
+
git -c user.email=t@t -c user.name=t add c.txt
|
|
704
|
+
# Older than the 10-minute recent-commit grace, so the LEASE is the only thing
|
|
705
|
+
# that can protect it — otherwise this passes via the grace window and proves
|
|
706
|
+
# nothing about lease resolution from a cache install.
|
|
707
|
+
GIT_COMMITTER_DATE="2025-01-01T00:00:00Z" \
|
|
708
|
+
git -c user.email=t@t -c user.name=t commit \
|
|
709
|
+
--date "2025-01-01T00:00:00Z" -m "v9 change" >/dev/null
|
|
710
|
+
)
|
|
711
|
+
git -C "$BARE9" update-ref refs/heads/main "$(git -C "$BARE9" rev-parse refs/heads/feat-v9)"
|
|
712
|
+
git -C "$BARE9" worktree add -b feat-a9 "$WT9/.worktrees/feat-a9" main >/dev/null 2>&1
|
|
713
|
+
|
|
714
|
+
LEASE_ROOT9="$BARE9/soleur-session-state"
|
|
715
|
+
mkdir -p "$LEASE_ROOT9/leases"
|
|
716
|
+
: > "$LEASE_ROOT9/reaper-armed" # see the note at LEASE_ROOT above
|
|
717
|
+
cat > "$LEASE_ROOT9/leases/feat-v9.lease" <<EOF
|
|
718
|
+
pid=$$
|
|
719
|
+
ppid=$$
|
|
720
|
+
skill=one-shot
|
|
721
|
+
started_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
722
|
+
expected_duration_min=137
|
|
723
|
+
hostname=$HOSTNAME
|
|
724
|
+
EOF
|
|
725
|
+
|
|
726
|
+
if ( cd "$WT9/.worktrees/feat-a9" \
|
|
727
|
+
&& SOLEUR_SESSION_STATE_ROOT="$LEASE_ROOT9" \
|
|
728
|
+
bash "$CACHE_WM" cleanup-merged >"$TMP/cleanup9.log" 2>&1 ); then
|
|
729
|
+
:
|
|
730
|
+
fi
|
|
731
|
+
if [[ -d "$WT9/.worktrees/feat-v9" ]]; then
|
|
732
|
+
pass "scenario 9: from a CACHE install, the reaper refused to reap a leased worktree"
|
|
733
|
+
else
|
|
734
|
+
fail "scenario 9: a cache-install cleanup-merged REAPED a leased worktree — this PR arms an \
|
|
735
|
+
unrecoverable operation for every marketplace user (output: $(cat "$TMP/cleanup9.log" 2>/dev/null))"
|
|
736
|
+
fi
|
|
737
|
+
|
|
738
|
+
# Mutation arm: clear the lease and the SAME victim must now be reaped. Without
|
|
739
|
+
# this, scenario 9 is satisfied by cleanup-merged doing nothing for any reason at
|
|
740
|
+
# all — an early return, a lock it could not take, a fail-closed stub.
|
|
741
|
+
rm -f "$LEASE_ROOT9/leases/feat-v9.lease"
|
|
742
|
+
if ( cd "$WT9/.worktrees/feat-a9" \
|
|
743
|
+
&& SOLEUR_SESSION_STATE_ROOT="$LEASE_ROOT9" \
|
|
744
|
+
bash "$CACHE_WM" cleanup-merged >"$TMP/cleanup9b.log" 2>&1 ); then
|
|
745
|
+
:
|
|
746
|
+
fi
|
|
747
|
+
if [[ -d "$WT9/.worktrees/feat-v9" ]]; then
|
|
748
|
+
fail "scenario 9b (mutation): the victim survived with NO lease — scenario 9 is vacuous, the \
|
|
749
|
+
reaper is not reaping for some unrelated reason (output: $(cat "$TMP/cleanup9b.log" 2>/dev/null))"
|
|
750
|
+
else
|
|
751
|
+
pass "scenario 9b (mutation): with the lease cleared the victim IS reaped — scenario 9's \
|
|
752
|
+
refusal is attributable to the lease, not to an inert reaper"
|
|
753
|
+
fi
|
|
754
|
+
|
|
755
|
+
# ---------------------------------------------------------------------------
|
|
756
|
+
# SCENARIO 10 (#7409, T3b): the SKILL.md anchor hop.
|
|
757
|
+
#
|
|
758
|
+
# Every other scenario in this file invokes worktree-manager.sh by absolute path
|
|
759
|
+
# inside a fixture. A marketplace user never does that — their agent executes the
|
|
760
|
+
# `"${CLAUDE_PLUGIN_ROOT}/…"` form written in SKILL.md (bare since #7453). That hop is the link the
|
|
761
|
+
# whole fix has to traverse, and nothing tested it: the suite could be fully
|
|
762
|
+
# green while delivering nothing to the population #7409 is about.
|
|
763
|
+
# ---------------------------------------------------------------------------
|
|
764
|
+
NONSOLEUR="$TMP/not-soleur"; mkdir -p "$NONSOLEUR"
|
|
765
|
+
git init -b main "$NONSOLEUR" >/dev/null 2>&1
|
|
766
|
+
( cd "$NONSOLEUR" && git -c user.email=t@t -c user.name=t commit --allow-empty -m seed >/dev/null 2>&1 )
|
|
767
|
+
if [[ ! -e "$NONSOLEUR/plugins/soleur" ]]; then
|
|
768
|
+
pass "scenario 10 fixture: cwd is a non-Soleur repo (no ./plugins/soleur to fall back to)"
|
|
769
|
+
else
|
|
770
|
+
fail "scenario 10 fixture: cwd contains ./plugins/soleur — the anchor's default arm would \
|
|
771
|
+
resolve and the hop under test is bypassed"
|
|
772
|
+
fi
|
|
773
|
+
# `export` on its own line, NOT a `CLAUDE_PLUGIN_ROOT=… bash "${CLAUDE_PLUGIN_ROOT}/…"`
|
|
774
|
+
# prefix assignment: a command-prefix assignment populates the COMMAND's
|
|
775
|
+
# environment, but every expansion on that same command line is performed first,
|
|
776
|
+
# against the current shell — so the anchor would expand EMPTY and this scenario
|
|
777
|
+
# would silently test a root-anchored `/skills/…` path instead of the hop.
|
|
778
|
+
# Measured: written that way, the run failed with `bash: ./plugins/…: No such
|
|
779
|
+
# file or directory`, and the negative assertion below still reported `pass`.
|
|
780
|
+
if ( cd "$NONSOLEUR" \
|
|
781
|
+
&& export CLAUDE_PLUGIN_ROOT="$CACHE_ROOT" \
|
|
782
|
+
&& bash "${CLAUDE_PLUGIN_ROOT}/skills/git-worktree/scripts/worktree-manager.sh" \
|
|
783
|
+
list >"$TMP/anchor10.log" 2>&1 ); then
|
|
784
|
+
:
|
|
785
|
+
fi
|
|
786
|
+
# The anchor must have resolved to a REAL script. Without this, every assertion
|
|
787
|
+
# below is satisfied by the interpreter failing to find the file at all — which
|
|
788
|
+
# is precisely how the fixture bug above stayed invisible.
|
|
789
|
+
if grep -q 'No such file or directory' "$TMP/anchor10.log"; then
|
|
790
|
+
fail "scenario 10 precondition: the anchor did not resolve to a script — assertions below \
|
|
791
|
+
cannot discriminate (output: $(cat "$TMP/anchor10.log" 2>/dev/null))"
|
|
792
|
+
else
|
|
793
|
+
pass "scenario 10 precondition: the anchor resolved to a real script"
|
|
794
|
+
fi
|
|
795
|
+
if grep -q 'SOLEUR_WORKTREE_LEASE_LIB_OK' "$TMP/anchor10.log"; then
|
|
796
|
+
pass "scenario 10: reached through the SKILL.md anchor form, the library resolves (\
|
|
797
|
+
..._LIB_OK on stdout)"
|
|
798
|
+
else
|
|
799
|
+
fail "scenario 10: through the SKILL.md anchor form from a non-Soleur cwd the library did NOT \
|
|
800
|
+
resolve — the hop a marketplace user actually traverses is broken \
|
|
801
|
+
(output: $(cat "$TMP/anchor10.log" 2>/dev/null))"
|
|
802
|
+
fi
|
|
803
|
+
if grep -q 'SOLEUR_WORKTREE_LEASE_LIB_MISSING' "$TMP/anchor10.log"; then
|
|
804
|
+
fail "scenario 10: the MISSING marker was emitted through the anchor hop"
|
|
805
|
+
else
|
|
806
|
+
pass "scenario 10: no MISSING marker through the anchor hop"
|
|
807
|
+
fi
|
|
808
|
+
|
|
809
|
+
# ---------------------------------------------------------------------------
|
|
810
|
+
# SCENARIO 10b (#7453, ADR-179 A18): the same hop with the root UNSET, from a tree
|
|
811
|
+
# that plants its own `./plugins/soleur/…/worktree-manager.sh` decoy. The bare anchor
|
|
812
|
+
# must fail closed on a root-anchored `/skills/…` path and never run the decoy. The
|
|
813
|
+
# twin runs the PRE-migration default-arm hop from the same directory and MUST run
|
|
814
|
+
# the decoy — proving the decoy is reachable, so the empty ledger above means something.
|
|
815
|
+
# The default arm is assembled from pieces so this file never spells the rejected form.
|
|
816
|
+
# ---------------------------------------------------------------------------
|
|
817
|
+
DECOYTREE="$TMP/decoy-tree"; mkdir -p "$DECOYTREE/plugins/soleur/skills/git-worktree/scripts"
|
|
818
|
+
DECOY_LEDGER="$(mktemp -p "$TMP" decoy-ledger.XXXXXX)"
|
|
819
|
+
printf '#!/usr/bin/env bash\necho decoy-ran >> "%s"\n' "$DECOY_LEDGER" \
|
|
820
|
+
> "$DECOYTREE/plugins/soleur/skills/git-worktree/scripts/worktree-manager.sh"
|
|
821
|
+
chmod +x "$DECOYTREE/plugins/soleur/skills/git-worktree/scripts/worktree-manager.sh"
|
|
822
|
+
# The hop is the SHIPPED line, read out of SKILL.md — a hardcoded copy would stay green
|
|
823
|
+
# if SKILL.md reverted to the default arm. All four emissions must be byte-identical.
|
|
824
|
+
WT_SKILL="$REPO_ROOT/plugins/soleur/skills/git-worktree/SKILL.md"
|
|
825
|
+
LIST_LINES="$(grep -E '^bash "[$][{]CLAUDE_PLUGIN_ROOT[}]/skills/git-worktree/scripts/worktree-manager[.]sh" list$' "$WT_SKILL")"
|
|
826
|
+
if [[ "$(printf '%s\n' "$LIST_LINES" | grep -c .)" -ne 4 || "$(printf '%s\n' "$LIST_LINES" | sort -u | grep -c .)" -ne 1 ]]; then
|
|
827
|
+
printf '[FATAL] scenario 10b: expected 4 identical bare-anchor list lines in %s, got:\n%s\n' "$WT_SKILL" "$LIST_LINES" >&2; exit 1
|
|
828
|
+
fi
|
|
829
|
+
SHIPPED_LIST="$(printf '%s\n' "$LIST_LINES" | head -1)"
|
|
830
|
+
( cd "$DECOYTREE" && env -u CLAUDE_PLUGIN_ROOT -u GROK_PLUGIN_ROOT \
|
|
831
|
+
bash -c "$SHIPPED_LIST" \
|
|
832
|
+
>"$TMP/anchor10b.log" 2>&1 )
|
|
833
|
+
rc10b=$?
|
|
834
|
+
if [[ "$rc10b" -ne 0 ]] && grep -q '/skills/git-worktree/scripts/worktree-manager.sh: No such file' "$TMP/anchor10b.log" \
|
|
835
|
+
&& [[ ! -s "$DECOY_LEDGER" ]]; then
|
|
836
|
+
pass "scenario 10b: root unset — the bare anchor fails closed and the planted decoy never runs"
|
|
837
|
+
else
|
|
838
|
+
fail "scenario 10b: root unset — rc=$rc10b ledger=$(cat "$DECOY_LEDGER") out=$(cat "$TMP/anchor10b.log")"
|
|
839
|
+
fi
|
|
840
|
+
DEF=':-'
|
|
841
|
+
( cd "$DECOYTREE" && env -u CLAUDE_PLUGIN_ROOT -u GROK_PLUGIN_ROOT \
|
|
842
|
+
bash -c "bash \"\${CLAUDE_PLUGIN_ROOT${DEF}./plugins/soleur}/skills/git-worktree/scripts/worktree-manager.sh\" list" \
|
|
843
|
+
>/dev/null 2>&1 )
|
|
844
|
+
if grep -qx 'decoy-ran' "$DECOY_LEDGER"; then
|
|
845
|
+
pass "scenario 10b twin: the pre-migration default-arm hop DOES run the decoy"
|
|
846
|
+
else
|
|
847
|
+
fail "scenario 10b twin: the decoy did not run — scenario 10b's empty ledger proves nothing"
|
|
848
|
+
fi
|
|
849
|
+
|
|
850
|
+
# ---------------------------------------------------------------------------
|
|
851
|
+
# SCENARIO 11 (#7409, T6): OLD-tree / NEW-tree interoperation.
|
|
852
|
+
#
|
|
853
|
+
# During rollout, one worktree on disk predates the move and sources the library
|
|
854
|
+
# from .claude/hooks/lib/, while a sibling sources it from the plugin. Both
|
|
855
|
+
# anchor their lease store to `git rev-parse --git-common-dir`, so they share one
|
|
856
|
+
# set of lease files. If the two copies disagreed about the lease FORMAT, a new
|
|
857
|
+
# worktree would read an old worktree's lease as absent and reap it.
|
|
858
|
+
#
|
|
859
|
+
# AC10's byte-identity check proves the file is unchanged; it does NOT prove
|
|
860
|
+
# interop, which is the property the rollout actually depends on. This arm runs
|
|
861
|
+
# the ORIGIN/MAIN copy (genuinely old code) against the post-move copy.
|
|
862
|
+
# ---------------------------------------------------------------------------
|
|
863
|
+
# PINNED TO AN IMMUTABLE BLOB, never to a moving ref.
|
|
864
|
+
#
|
|
865
|
+
# The first draft resolved the pre-move path out of `origin/main`'s tree. That is
|
|
866
|
+
# correct for exactly as long as this change is unmerged: the moment it lands,
|
|
867
|
+
# `origin/main` IS this tree, `hooks/lib/session-state.sh` matches nothing, and
|
|
868
|
+
# all three assertions below fail — permanently, on main, for every branch cut
|
|
869
|
+
# from it. A proof-of-red pinned to a moving ref consumes its own fix, and it
|
|
870
|
+
# fires when the rollout SUCCEEDS. (The prior comment defended the empty lookup
|
|
871
|
+
# as anti-vacuity; loudness with no expiry is not a regression detector.)
|
|
872
|
+
#
|
|
873
|
+
# A blob SHA is reachable from history forever, so `git cat-file` keeps working
|
|
874
|
+
# after the path is gone. It also removes the `.claude/hooks/lib/session-state.sh`
|
|
875
|
+
# literal, which AC6 asserts is absent everywhere under plugins/soleur/skills/.
|
|
876
|
+
OLD_BLOB="864f3865590ee92b1fc9348c0b2bc6371091b8fa" # .claude/hooks/lib/session-state.sh @ pre-move main
|
|
877
|
+
OLD_LIB="$TMP/oldtree/legacy-session-state.sh"
|
|
878
|
+
mkdir -p "$(dirname "$OLD_LIB")"
|
|
879
|
+
if git -C "$REPO_ROOT" cat-file -e "$OLD_BLOB" 2>/dev/null \
|
|
880
|
+
&& git -C "$REPO_ROOT" cat-file blob "$OLD_BLOB" > "$OLD_LIB" 2>/dev/null \
|
|
881
|
+
&& [[ -s "$OLD_LIB" ]]; then
|
|
882
|
+
pass "scenario 11 fixture: recovered the pre-move library from blob ${OLD_BLOB:0:12}"
|
|
883
|
+
elif [[ -n "${CI:-}" ]]; then
|
|
884
|
+
# Under CI the checkout pins full depth, so reachability is contractual and an
|
|
885
|
+
# absent object is a real failure. Locally a blobless/shallow clone is ordinary,
|
|
886
|
+
# and degrading to a hard FAIL there would train people to ignore this suite.
|
|
887
|
+
fail "scenario 11 fixture: pre-move blob $OLD_BLOB unreachable under CI — \
|
|
888
|
+
the checkout is not full-depth, so old/new interop is unverified"
|
|
889
|
+
else
|
|
890
|
+
pass "scenario 11: SKIPPED — pre-move blob $OLD_BLOB not in this clone (blobless/shallow); \
|
|
891
|
+
interop unverified locally, enforced under CI"
|
|
892
|
+
fi
|
|
893
|
+
NEW_LIB="$REPO_ROOT/plugins/soleur/scripts/lib/session-state.sh"
|
|
894
|
+
SHARED11="$TMP/shared11"; mkdir -p "$SHARED11/leases"
|
|
895
|
+
# The two arms below are only meaningful when the fixture actually materialized.
|
|
896
|
+
# Without this guard a legitimate blobless-clone SKIP would report as two
|
|
897
|
+
# failures — the arm reporting a fixture problem as a product defect.
|
|
898
|
+
if [[ -s "$OLD_LIB" ]]; then
|
|
899
|
+
# OLD code acquires...
|
|
900
|
+
bash -c "
|
|
901
|
+
export SOLEUR_SESSION_STATE_ROOT='$SHARED11'
|
|
902
|
+
# shellcheck source=/dev/null
|
|
903
|
+
source '$OLD_LIB'
|
|
904
|
+
acquire_lease feat-interop one-shot 137
|
|
905
|
+
" >/dev/null 2>&1
|
|
906
|
+
if [[ -f "$SHARED11/leases/feat-interop.lease" ]]; then
|
|
907
|
+
pass "scenario 11: the pre-move library wrote a lease into the shared store"
|
|
908
|
+
else
|
|
909
|
+
fail "scenario 11: the pre-move library wrote no lease — fixture cannot discriminate"
|
|
910
|
+
fi
|
|
911
|
+
else
|
|
912
|
+
pass "scenario 11: acquire arm skipped (no pre-move fixture)"
|
|
913
|
+
fi
|
|
914
|
+
# ...and NEW code must honour it. `is_lease_active` is only trustworthy here
|
|
915
|
+
# because NEW_LIB genuinely exists: when it does not, worktree-manager.sh's
|
|
916
|
+
# fail-closed STUB also returns 0, so this would pass vacuously against the exact
|
|
917
|
+
# bug. Guard on the file, then call the real function.
|
|
918
|
+
if [[ ! -s "$OLD_LIB" ]]; then
|
|
919
|
+
pass "scenario 11: honour arm skipped (no pre-move fixture)"
|
|
920
|
+
elif [[ -f "$NEW_LIB" ]] && bash -c "
|
|
921
|
+
export SOLEUR_SESSION_STATE_ROOT='$SHARED11'
|
|
922
|
+
# shellcheck source=/dev/null
|
|
923
|
+
source '$NEW_LIB'
|
|
924
|
+
is_lease_active feat-interop
|
|
925
|
+
" >/dev/null 2>&1; then
|
|
926
|
+
pass "scenario 11: the post-move library honours a lease written by the pre-move library"
|
|
927
|
+
else
|
|
928
|
+
fail "scenario 11: the post-move library does NOT honour a pre-move lease — a mid-rollout \
|
|
929
|
+
worktree would be reaped by a sibling (new lib present: $([[ -f "$NEW_LIB" ]] && echo yes || echo NO))"
|
|
930
|
+
fi
|
|
931
|
+
|
|
932
|
+
# ---------------------------------------------------------------------------
|
|
933
|
+
# SCENARIO 12 (#7409): the one-time arming hold.
|
|
934
|
+
#
|
|
935
|
+
# This PR makes the reaper reachable for a population that never had it. Every
|
|
936
|
+
# worktree already on such a machine was created by a `create` that could not
|
|
937
|
+
# acquire a lease, so none of them can hold one — and cleanup-merged runs at
|
|
938
|
+
# session start. Without a hold, the first post-upgrade session sweeps the whole
|
|
939
|
+
# accumulated backlog at once, and the user's first notice of the feature is its
|
|
940
|
+
# aftermath.
|
|
941
|
+
#
|
|
942
|
+
# Both directions matter and they fail differently: a hold that never fires
|
|
943
|
+
# leaves the bulk reap live; a hold that never CLEARS makes cleanup dead
|
|
944
|
+
# forever, which is worse than not having it.
|
|
945
|
+
# ---------------------------------------------------------------------------
|
|
946
|
+
BARE12="$TMP/repo12.git"; git init --bare -b main "$BARE12" >/dev/null
|
|
947
|
+
S12="$TMP/s12"; git clone "$BARE12" "$S12" >/dev/null 2>&1
|
|
948
|
+
( cd "$S12" && git -c user.email=t@t -c user.name=t commit --allow-empty -m seed >/dev/null \
|
|
949
|
+
&& git push origin main >/dev/null 2>&1 )
|
|
950
|
+
rm -rf "$S12"
|
|
951
|
+
WT12="$TMP/wt12"; mkdir -p "$WT12/.worktrees"
|
|
952
|
+
git -C "$BARE12" worktree add -b feat-v12 "$WT12/.worktrees/feat-v12" main >/dev/null 2>&1
|
|
953
|
+
( cdx "$WT12/.worktrees/feat-v12"
|
|
954
|
+
echo hi12 > d.txt
|
|
955
|
+
git -c user.email=t@t -c user.name=t add d.txt
|
|
956
|
+
GIT_COMMITTER_DATE="2025-01-01T00:00:00Z" \
|
|
957
|
+
git -c user.email=t@t -c user.name=t commit \
|
|
958
|
+
--date "2025-01-01T00:00:00Z" -m "v12 change" >/dev/null
|
|
959
|
+
)
|
|
960
|
+
git -C "$BARE12" update-ref refs/heads/main "$(git -C "$BARE12" rev-parse refs/heads/feat-v12)"
|
|
961
|
+
git -C "$BARE12" worktree add -b feat-a12 "$WT12/.worktrees/feat-a12" main >/dev/null 2>&1
|
|
962
|
+
|
|
963
|
+
# Deliberately NOT pre-stamped: this is the only scenario that exercises a
|
|
964
|
+
# genuinely first-armed store, which is the state every marketplace user is in
|
|
965
|
+
# the moment they update.
|
|
966
|
+
LEASE_ROOT12="$BARE12/soleur-session-state"
|
|
967
|
+
mkdir -p "$LEASE_ROOT12/leases"
|
|
968
|
+
if [[ ! -f "$LEASE_ROOT12/reaper-armed" ]]; then
|
|
969
|
+
pass "scenario 12 fixture: the store has never armed the reaper (no stamp)"
|
|
970
|
+
else
|
|
971
|
+
fail "scenario 12 fixture: stamp already present — cannot exercise the first armed run"
|
|
972
|
+
fi
|
|
973
|
+
|
|
974
|
+
if ( cd "$WT12/.worktrees/feat-a12" \
|
|
975
|
+
&& SOLEUR_SESSION_STATE_ROOT="$LEASE_ROOT12" \
|
|
976
|
+
bash "$CACHE_WM" cleanup-merged >"$TMP/hold12.log" 2>&1 ); then
|
|
977
|
+
:
|
|
978
|
+
fi
|
|
979
|
+
# The victim is unleased and stale — without the hold it would be reaped here.
|
|
980
|
+
if [[ -d "$WT12/.worktrees/feat-v12" ]]; then
|
|
981
|
+
pass "scenario 12: the first armed run reaped nothing, even though the victim holds no lease"
|
|
982
|
+
else
|
|
983
|
+
fail "scenario 12: an UNLEASED stale worktree was reaped on the very first armed run — \
|
|
984
|
+
the bulk-reap-on-upgrade hold is not in effect (output: $(cat "$TMP/hold12.log" 2>/dev/null))"
|
|
985
|
+
fi
|
|
986
|
+
if grep -q 'SOLEUR_WORKTREE_REAPER_ARMED' "$TMP/hold12.log"; then
|
|
987
|
+
pass "scenario 12: the hold announces itself rather than silently doing nothing"
|
|
988
|
+
else
|
|
989
|
+
fail "scenario 12: the reaper armed with no marker — indistinguishable from a run \
|
|
990
|
+
that found nothing to do (output: $(cat "$TMP/hold12.log" 2>/dev/null))"
|
|
991
|
+
fi
|
|
992
|
+
if [[ -f "$LEASE_ROOT12/reaper-armed" ]]; then
|
|
993
|
+
pass "scenario 12: the run stamped the store"
|
|
994
|
+
else
|
|
995
|
+
fail "scenario 12: no stamp written — the hold would repeat forever and cleanup would \
|
|
996
|
+
never reap anything again"
|
|
997
|
+
fi
|
|
998
|
+
|
|
999
|
+
# SECOND run: the hold must be CLEARED. A hold that cannot clear is a worse
|
|
1000
|
+
# defect than the bulk reap it prevents, and nothing else in this suite would
|
|
1001
|
+
# notice — every other fixture is pre-stamped.
|
|
1002
|
+
if ( cd "$WT12/.worktrees/feat-a12" \
|
|
1003
|
+
&& SOLEUR_SESSION_STATE_ROOT="$LEASE_ROOT12" \
|
|
1004
|
+
bash "$CACHE_WM" cleanup-merged >"$TMP/hold12b.log" 2>&1 ); then
|
|
1005
|
+
:
|
|
1006
|
+
fi
|
|
1007
|
+
if [[ -d "$WT12/.worktrees/feat-v12" ]]; then
|
|
1008
|
+
fail "scenario 12b: the victim survived the SECOND run too — the hold never clears, so \
|
|
1009
|
+
cleanup-merged is now permanently inert (output: $(cat "$TMP/hold12b.log" 2>/dev/null))"
|
|
1010
|
+
else
|
|
1011
|
+
pass "scenario 12b: the second run reaps — the hold is one-shot, not a permanent disable"
|
|
1012
|
+
fi
|
|
1013
|
+
|
|
1014
|
+
echo
|
|
1015
|
+
echo "=== Results ==="
|
|
1016
|
+
echo "PASS: $PASS"
|
|
1017
|
+
echo "FAIL: $FAIL"
|
|
1018
|
+
|
|
1019
|
+
# ANTI-VACUITY FLOOR — same reasoning as session-state.test.sh. `FAIL -eq 0` is
|
|
1020
|
+
# a pure zero-check, so a run that asserts NOTHING exits 0. That is not
|
|
1021
|
+
# hypothetical here: both scenarios assert worktree SURVIVAL, which is satisfied
|
|
1022
|
+
# whenever cleanup-merged does nothing for any reason at all — an early `return`
|
|
1023
|
+
# on the fetch-prune path, a non-zero sweep aborting under `set -e`, a lock it
|
|
1024
|
+
# could not take. A floor cannot detect a no-op reap loop by itself, but it does
|
|
1025
|
+
# catch the case where the assertions were never reached.
|
|
1026
|
+
MIN_ASSERTIONS=42 # 3 -> 6 -> 9 -> 15 -> 17 (PR #7373 sc. 3-7) -> 40 (#7409 sc. 8-12) -> 42 (#7453 sc. 10b)
|
|
1027
|
+
# Calibrated to the MEASURED count, not to a round number below it. At 32 against a
|
|
1028
|
+
# 34-dispatch suite the floor carried exactly two assertions of slack — and scenario 9
|
|
1029
|
+
# + 9b is exactly two dispatches, so deleting the reaper-refusal arm (the one guarding
|
|
1030
|
+
# an unrecoverable delete) left the suite green. A floor with slack is a floor sized
|
|
1031
|
+
# to the block it fails to notice.
|
|
1032
|
+
# Count DISPATCHES (PASS + FAIL), not wins. Counting PASS alone conflates two
|
|
1033
|
+
# different things: "the suite did not run" and "the suite ran and found bugs".
|
|
1034
|
+
# It printed "only N assertions ran — the suite did not execute what it claims
|
|
1035
|
+
# to cover" on every genuine regression, which is false and misdirects whoever
|
|
1036
|
+
# reads the failure. It also let a miswired `fail()` (one that increments PASS)
|
|
1037
|
+
# slip through whenever the defect happened to produce the expected dispatch
|
|
1038
|
+
# count. Matches scripts/lint-diagnosis-claims.test.sh and
|
|
1039
|
+
# scripts/lint-workflow-step-env-refs.test.sh, which both count PASS + FAIL.
|
|
1040
|
+
# `exit 90` inside `( cdx ... )` ends only the SUBSHELL, so without this marker the
|
|
1041
|
+
# suite would carry on against a fixture it never built and report whatever the
|
|
1042
|
+
# surviving assertions happened to say. Checked BEFORE the vacuity floor: a run that
|
|
1043
|
+
# refused is not a run with too few assertions, and must not be described as one.
|
|
1044
|
+
if [[ -f "$TMP/.cdx-failed" ]]; then
|
|
1045
|
+
echo "FAIL: at least one fixture directory was absent or outside the fixture root."
|
|
1046
|
+
echo " The suite did not run as designed; its verdicts are not evidence."
|
|
1047
|
+
exit 1
|
|
1048
|
+
fi
|
|
1049
|
+
|
|
1050
|
+
if [[ $(( PASS + FAIL )) -lt "$MIN_ASSERTIONS" ]]; then
|
|
1051
|
+
echo "FAIL: only $(( PASS + FAIL )) assertions ran, expected >= $MIN_ASSERTIONS — the suite did not execute what it claims to cover."
|
|
1052
|
+
exit 1
|
|
1053
|
+
fi
|
|
1054
|
+
|
|
1055
|
+
[[ "$FAIL" -eq 0 ]] || exit 1
|
|
1056
|
+
exit 0
|