@soleur/opencode 0.0.0-stage → 3.354.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/agents.manifest.json +476 -0
- package/.claude-plugin/plugin.json +42 -0
- package/README.md +44 -2
- package/agents/community/.gitkeep +0 -0
- package/agents/engineering/cto.md +87 -0
- package/agents/engineering/design/ddd-architect.md +95 -0
- package/agents/engineering/discovery/agent-finder.md +214 -0
- package/agents/engineering/discovery/functional-discovery.md +182 -0
- package/agents/engineering/infra/infra-security.md +151 -0
- package/agents/engineering/infra/platform-strategist.md +109 -0
- package/agents/engineering/infra/terraform-architect.md +171 -0
- package/agents/engineering/prompt-engineer.md +37 -0
- package/agents/engineering/research/best-practices-researcher.md +115 -0
- package/agents/engineering/research/framework-docs-researcher.md +93 -0
- package/agents/engineering/research/git-history-analyzer.md +53 -0
- package/agents/engineering/research/learnings-researcher.md +285 -0
- package/agents/engineering/research/repo-research-analyst.md +118 -0
- package/agents/engineering/review/agent-native-reviewer.md +279 -0
- package/agents/engineering/review/architecture-strategist.md +61 -0
- package/agents/engineering/review/code-quality-analyst.md +92 -0
- package/agents/engineering/review/code-simplicity-reviewer.md +109 -0
- package/agents/engineering/review/data-integrity-guardian.md +82 -0
- package/agents/engineering/review/data-migration-expert.md +98 -0
- package/agents/engineering/review/deployment-verification-agent.md +183 -0
- package/agents/engineering/review/dhh-rails-reviewer.md +46 -0
- package/agents/engineering/review/kieran-rails-reviewer.md +87 -0
- package/agents/engineering/review/legacy-code-expert.md +55 -0
- package/agents/engineering/review/observability-coverage-reviewer.md +147 -0
- package/agents/engineering/review/pattern-recognition-specialist.md +59 -0
- package/agents/engineering/review/performance-oracle.md +130 -0
- package/agents/engineering/review/security-sentinel.md +167 -0
- package/agents/engineering/review/semgrep-sast.md +36 -0
- package/agents/engineering/review/test-design-reviewer.md +150 -0
- package/agents/engineering/review/user-impact-reviewer.md +70 -0
- package/agents/engineering/workflow/pr-comment-resolver.md +69 -0
- package/agents/finance/budget-analyst.md +29 -0
- package/agents/finance/cfo.md +44 -0
- package/agents/finance/financial-reporter.md +30 -0
- package/agents/finance/revenue-analyst.md +30 -0
- package/agents/legal/clo.md +115 -0
- package/agents/legal/legal-compliance-auditor.md +110 -0
- package/agents/legal/legal-document-generator.md +134 -0
- package/agents/marketing/analytics-analyst.md +25 -0
- package/agents/marketing/brand-architect.md +181 -0
- package/agents/marketing/cmo.md +76 -0
- package/agents/marketing/conversion-optimizer.md +19 -0
- package/agents/marketing/copywriter.md +20 -0
- package/agents/marketing/fact-checker.md +70 -0
- package/agents/marketing/growth-strategist.md +145 -0
- package/agents/marketing/paid-media-strategist.md +28 -0
- package/agents/marketing/pricing-strategist.md +17 -0
- package/agents/marketing/programmatic-seo-specialist.md +25 -0
- package/agents/marketing/retention-strategist.md +23 -0
- package/agents/marketing/seo-aeo-analyst.md +164 -0
- package/agents/operations/coo.md +50 -0
- package/agents/operations/ops-advisor.md +78 -0
- package/agents/operations/ops-provisioner.md +101 -0
- package/agents/operations/ops-research.md +52 -0
- package/agents/operations/service-automator.md +278 -0
- package/agents/product/business-validator.md +206 -0
- package/agents/product/competitive-intelligence.md +89 -0
- package/agents/product/cpo.md +63 -0
- package/agents/product/design/ux-design-lead.md +209 -0
- package/agents/product/spec-flow-analyzer.md +122 -0
- package/agents/sales/cro.md +44 -0
- package/agents/sales/deal-architect.md +29 -0
- package/agents/sales/outbound-strategist.md +29 -0
- package/agents/sales/pipeline-analyst.md +29 -0
- package/agents/support/cco.md +48 -0
- package/agents/support/community-manager.md +570 -0
- package/agents/support/ticket-triage.md +70 -0
- package/bin/soleur-opencode.js +29 -0
- package/commands/go.md +660 -0
- package/commands/help.md +273 -0
- package/commands/sync.md +977 -0
- package/commands/version.md +103 -0
- package/hooks/antigravity-hooks.json +84 -0
- package/hooks/antigravity-session-start.sh +15 -0
- package/hooks/bare-root-cwd-guard.sh +131 -0
- package/hooks/browser-snapshot-credential-guard.sh +200 -0
- package/hooks/codex-session-start.sh +9 -0
- package/hooks/compaction-state.sh +525 -0
- package/hooks/destructive-command-guard.sh +1387 -0
- package/hooks/devin-session-start.sh +90 -0
- package/hooks/ext-filter-detect.sh +171 -0
- package/hooks/hooks.json +133 -0
- package/hooks/lib/hook-tool-kind.sh +24 -0
- package/hooks/lib/log-rotation.sh +176 -0
- package/hooks/lib/shell-argv.pl +576 -0
- package/hooks/memory-backstop.sh +1114 -0
- package/hooks/operator-stage-approval.sh +391 -0
- package/hooks/stop-hook.sh +367 -0
- package/hooks/unkept-promise-hook.sh +383 -0
- package/hooks/version-banner.sh +127 -0
- package/hooks/welcome-hook.sh +50 -0
- package/install.js +29 -0
- package/lib/agent-registry.ts +154 -0
- package/lib/c4-canonical-cli.mjs +34 -0
- package/lib/c4-canonical.mjs +46 -0
- package/lib/c4-from-components.ts +554 -0
- package/lib/go-routing.ts +81 -0
- package/lib/grok-inspect-contract.ts +311 -0
- package/lib/harness-model-map.ts +101 -0
- package/lib/harness-parity.ts +726 -0
- package/lib/harness-tool-map.ts +180 -0
- package/lib/harness.ts +716 -0
- package/lib/heartbeat-live-reconcile.ts +1153 -0
- package/lib/heartbeat-manifest.ts +361 -0
- package/lib/kb-coverage.ts +252 -0
- package/lib/pr-merge-poll.ts +103 -0
- package/lib/workflow-fidelity.ts +388 -0
- package/opencode/INSTRUCTIONS.md +171 -0
- package/opencode/agents/engineering/cto.md +10 -0
- package/opencode/agents/engineering/design/ddd-architect.md +10 -0
- package/opencode/agents/engineering/discovery/agent-finder.md +10 -0
- package/opencode/agents/engineering/discovery/functional-discovery.md +10 -0
- package/opencode/agents/engineering/infra/infra-security.md +10 -0
- package/opencode/agents/engineering/infra/platform-strategist.md +10 -0
- package/opencode/agents/engineering/infra/terraform-architect.md +10 -0
- package/opencode/agents/engineering/prompt-engineer.md +10 -0
- package/opencode/agents/engineering/research/best-practices-researcher.md +10 -0
- package/opencode/agents/engineering/research/framework-docs-researcher.md +10 -0
- package/opencode/agents/engineering/research/git-history-analyzer.md +10 -0
- package/opencode/agents/engineering/research/learnings-researcher.md +10 -0
- package/opencode/agents/engineering/research/repo-research-analyst.md +10 -0
- package/opencode/agents/engineering/review/agent-native-reviewer.md +10 -0
- package/opencode/agents/engineering/review/architecture-strategist.md +10 -0
- package/opencode/agents/engineering/review/code-quality-analyst.md +10 -0
- package/opencode/agents/engineering/review/code-simplicity-reviewer.md +10 -0
- package/opencode/agents/engineering/review/data-integrity-guardian.md +10 -0
- package/opencode/agents/engineering/review/data-migration-expert.md +10 -0
- package/opencode/agents/engineering/review/deployment-verification-agent.md +10 -0
- package/opencode/agents/engineering/review/dhh-rails-reviewer.md +10 -0
- package/opencode/agents/engineering/review/kieran-rails-reviewer.md +10 -0
- package/opencode/agents/engineering/review/legacy-code-expert.md +10 -0
- package/opencode/agents/engineering/review/observability-coverage-reviewer.md +10 -0
- package/opencode/agents/engineering/review/pattern-recognition-specialist.md +10 -0
- package/opencode/agents/engineering/review/performance-oracle.md +10 -0
- package/opencode/agents/engineering/review/security-sentinel.md +10 -0
- package/opencode/agents/engineering/review/semgrep-sast.md +10 -0
- package/opencode/agents/engineering/review/test-design-reviewer.md +10 -0
- package/opencode/agents/engineering/review/user-impact-reviewer.md +10 -0
- package/opencode/agents/engineering/workflow/pr-comment-resolver.md +10 -0
- package/opencode/agents/finance/budget-analyst.md +10 -0
- package/opencode/agents/finance/cfo.md +10 -0
- package/opencode/agents/finance/financial-reporter.md +10 -0
- package/opencode/agents/finance/revenue-analyst.md +10 -0
- package/opencode/agents/legal/clo.md +10 -0
- package/opencode/agents/legal/legal-compliance-auditor.md +10 -0
- package/opencode/agents/legal/legal-document-generator.md +10 -0
- package/opencode/agents/marketing/analytics-analyst.md +10 -0
- package/opencode/agents/marketing/brand-architect.md +10 -0
- package/opencode/agents/marketing/cmo.md +10 -0
- package/opencode/agents/marketing/conversion-optimizer.md +10 -0
- package/opencode/agents/marketing/copywriter.md +10 -0
- package/opencode/agents/marketing/fact-checker.md +10 -0
- package/opencode/agents/marketing/growth-strategist.md +10 -0
- package/opencode/agents/marketing/paid-media-strategist.md +10 -0
- package/opencode/agents/marketing/pricing-strategist.md +10 -0
- package/opencode/agents/marketing/programmatic-seo-specialist.md +10 -0
- package/opencode/agents/marketing/retention-strategist.md +10 -0
- package/opencode/agents/marketing/seo-aeo-analyst.md +10 -0
- package/opencode/agents/operations/coo.md +10 -0
- package/opencode/agents/operations/ops-advisor.md +10 -0
- package/opencode/agents/operations/ops-provisioner.md +10 -0
- package/opencode/agents/operations/ops-research.md +10 -0
- package/opencode/agents/operations/service-automator.md +10 -0
- package/opencode/agents/product/business-validator.md +10 -0
- package/opencode/agents/product/competitive-intelligence.md +10 -0
- package/opencode/agents/product/cpo.md +10 -0
- package/opencode/agents/product/design/ux-design-lead.md +10 -0
- package/opencode/agents/product/spec-flow-analyzer.md +10 -0
- package/opencode/agents/sales/cro.md +10 -0
- package/opencode/agents/sales/deal-architect.md +10 -0
- package/opencode/agents/sales/outbound-strategist.md +10 -0
- package/opencode/agents/sales/pipeline-analyst.md +10 -0
- package/opencode/agents/support/cco.md +10 -0
- package/opencode/agents/support/community-manager.md +10 -0
- package/opencode/agents/support/ticket-triage.md +10 -0
- package/opencode/commands/go.md +10 -0
- package/opencode/commands/help.md +10 -0
- package/opencode/commands/sync.md +10 -0
- package/opencode/commands/version.md +11 -0
- package/opencode/opencode.json +8 -0
- package/opencode/plugin.ts +818 -0
- package/package.json +36 -4
- package/scripts/admin-merge-ready.sh +545 -0
- package/scripts/admin-merge-ready.test.sh +753 -0
- package/scripts/affected-tests.sh +908 -0
- package/scripts/affected-tests.test.sh +1797 -0
- package/scripts/alpha-metrics.sh +78 -0
- package/scripts/alpha-metrics.test.sh +115 -0
- package/scripts/ancestor-signal-helpers.baseline.txt +16 -0
- package/scripts/audit-flag-flip.sh +107 -0
- package/scripts/check-red-on-main.sh +316 -0
- package/scripts/ci-head-verdict.sh +411 -0
- package/scripts/cloud-detect.sh +193 -0
- package/scripts/delegation-verify.sh +657 -0
- package/scripts/delegation-verify.test.sh +1424 -0
- package/scripts/deploy-arm.sh +475 -0
- package/scripts/domain-model-drift.sh +419 -0
- package/scripts/emit-decision.sh +144 -0
- package/scripts/emit-decision.test.sh +144 -0
- package/scripts/ext-filter-divergence-corpus.txt +15 -0
- package/scripts/ext-filter-divergence.sh +189 -0
- package/scripts/generate-c4-from-components.ts +442 -0
- package/scripts/grok-fidelity-gate.sh +68 -0
- package/scripts/grok-pre-push-gate.sh +232 -0
- package/scripts/harness-discovery-smoke.ts +906 -0
- package/scripts/harness-parity-census.ts +92 -0
- package/scripts/inngest-luks-alert-armed-probe.ts +29 -0
- package/scripts/install-opencode-adapter.ts +509 -0
- package/scripts/lib/affected-tests/common.sh +365 -0
- package/scripts/lib/affected-tests/go.sh +157 -0
- package/scripts/lib/affected-tests/js.sh +188 -0
- package/scripts/lib/affected-tests/nx-turbo.sh +98 -0
- package/scripts/lib/affected-tests/python.sh +185 -0
- package/scripts/lib/context-sanitize.sh +49 -0
- package/scripts/lib/domain-model-lib.sh +180 -0
- package/scripts/lib/ext-filter-grammar.tsv +34 -0
- package/scripts/lib/operator-script.sh +1236 -0
- package/scripts/lib/proc.sh +580 -0
- package/scripts/lib/session-state.sh +748 -0
- package/scripts/lib/tmp-classify.sh +843 -0
- package/scripts/lib/wrapper-boundary.sh +47 -0
- package/scripts/lint-ratchet.py +1547 -0
- package/scripts/monitor-pr-checks.sh +441 -0
- package/scripts/pipeline-tally.sh +756 -0
- package/scripts/pipeline-tally.test.sh +740 -0
- package/scripts/plugin-version.sh +528 -0
- package/scripts/precommit-guard.sh +115 -0
- package/scripts/precommit-guard.test.sh +254 -0
- package/scripts/reconcile-live-heartbeats.ts +705 -0
- package/scripts/render-c4-model.sh +217 -0
- package/scripts/resolve-git-root.sh +55 -0
- package/scripts/resolve-regenerable-conflicts.sh +363 -0
- package/scripts/resolve-regenerable-conflicts.test.sh +780 -0
- package/scripts/ruff-requirements.txt +22 -0
- package/scripts/run-in-pid-namespace.sh +98 -0
- package/scripts/run-in-pid-namespace.test.sh +290 -0
- package/scripts/scan-ancestor-signal-helpers.py +556 -0
- package/scripts/scan-ancestor-signal-helpers.test.sh +670 -0
- package/scripts/setup-ralph-loop.sh +238 -0
- package/scripts/sync-cursor-name-map.ts +316 -0
- package/scripts/sync-grok-agent-compat.ts +188 -0
- package/scripts/sync-npm-package.ts +165 -0
- package/scripts/sync-opencode-adapter.ts +498 -0
- package/scripts/sync-pr-behind.sh +647 -0
- package/scripts/sync-pr-behind.test.sh +252 -0
- package/scripts/taste-profile-update.sh +180 -0
- package/scripts/taste-profile-update.test.sh +167 -0
- package/scripts/write-budget-marker.sh +23 -0
- package/scripts/write-kb-coverage.ts +113 -0
- package/skills/admin-ip-refresh/SKILL.md +81 -0
- package/skills/admin-ip-refresh/references/admin-ip-refresh-procedure.md +201 -0
- package/skills/agent-browser/SKILL.md +698 -0
- package/skills/agent-browser/scripts/playwright-mcp-profile-slot.sh +304 -0
- package/skills/agent-browser/scripts/playwright-mcp-redact-proxy.py +964 -0
- package/skills/agent-browser/scripts/redact-a11y-snapshot.py +443 -0
- package/skills/agent-browser/test/fixtures/capture-playwright-mcp-fixtures.py +217 -0
- package/skills/agent-browser/test/fixtures/fake-passthrough-proxy.py +24 -0
- package/skills/agent-browser/test/fixtures/fake-playwright-mcp.py +131 -0
- package/skills/agent-browser/test/fixtures/make-proxy-mutant.py +48 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/dialog-link-injection.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/empty-content.json +5 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-and-result.json +4 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-code-bool.json +6 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-code-string.json +6 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-message-tree.json +6 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-no-data.json +6 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-string.json +4 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-with-data.json +9 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/evaluate-escaped-object.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/invalid-utf8.json +4 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/link-result.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/list-two-results.json +6 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/meta-in-result.json +13 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/no-content.json +5 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/quoted-key.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/resource-block.json +18 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/result-and-method.json +11 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/result-string.json +3 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/run-code-escaped-one-row.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/run-code-escaped.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/structured-content.json +13 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/tools-list-no-tools.json +5 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/tree-no-credential.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/two-text-blocks.json +14 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/unparsable-line.json +3 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/value-contains-brace.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/value-contains-link-substring.json +10 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/close.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/evaluate.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/find.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/framing.json +6 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/initialize.json +14 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/navigate-link.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/navigate.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/roots-list-request.json +5 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/screenshot.json +17 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-filename.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-meta-json.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-meta-raw.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/tools-list.json +827 -0
- package/skills/agent-browser/test/fixtures/proxy-session-driver.py +138 -0
- package/skills/agent-browser/test/fixtures/session-facts.py +102 -0
- package/skills/agent-browser/test/playwright-mcp-lifetime.test.sh +1620 -0
- package/skills/agent-browser/test/playwright-mcp-redact-proxy.test.sh +1599 -0
- package/skills/agent-browser/test/redact-a11y-snapshot.test.sh +527 -0
- package/skills/agent-native-architecture/SKILL.md +479 -0
- package/skills/agent-native-architecture/references/action-parity-discipline.md +430 -0
- package/skills/agent-native-architecture/references/agent-execution-patterns.md +483 -0
- package/skills/agent-native-architecture/references/agent-native-testing.md +605 -0
- package/skills/agent-native-architecture/references/architecture-patterns.md +493 -0
- package/skills/agent-native-architecture/references/dynamic-context-injection.md +358 -0
- package/skills/agent-native-architecture/references/files-universal-interface.md +314 -0
- package/skills/agent-native-architecture/references/from-primitives-to-domain-tools.md +381 -0
- package/skills/agent-native-architecture/references/mcp-tool-design.md +524 -0
- package/skills/agent-native-architecture/references/mobile-patterns.md +897 -0
- package/skills/agent-native-architecture/references/product-implications.md +478 -0
- package/skills/agent-native-architecture/references/refactoring-to-prompt-native.md +342 -0
- package/skills/agent-native-architecture/references/self-modification.md +294 -0
- package/skills/agent-native-architecture/references/shared-workspace-architecture.md +700 -0
- package/skills/agent-native-architecture/references/system-prompt-design.md +263 -0
- package/skills/agent-native-audit/SKILL.md +300 -0
- package/skills/agent-native-audit/workflows/agent-native-audit.workflow.js +423 -0
- package/skills/andrew-kane-gem-writer/SKILL.md +189 -0
- package/skills/andrew-kane-gem-writer/references/database-adapters.md +231 -0
- package/skills/andrew-kane-gem-writer/references/module-organization.md +121 -0
- package/skills/andrew-kane-gem-writer/references/rails-integration.md +183 -0
- package/skills/andrew-kane-gem-writer/references/resources.md +126 -0
- package/skills/andrew-kane-gem-writer/references/testing-patterns.md +261 -0
- package/skills/architecture/SKILL.md +436 -0
- package/skills/architecture/references/adr-template.md +154 -0
- package/skills/architecture/references/likec4-reference.md +136 -0
- package/skills/architecture/references/nfr-reference.md +126 -0
- package/skills/archive-kb/SKILL.md +86 -0
- package/skills/archive-kb/scripts/archive-kb.sh +300 -0
- package/skills/archive-kb/test/archive-kb-partial-run.test.sh +276 -0
- package/skills/atdd-developer/SKILL.md +65 -0
- package/skills/brainstorm/SKILL.md +701 -0
- package/skills/brainstorm/references/brainstorm-brand-workshop.md +153 -0
- package/skills/brainstorm/references/brainstorm-domain-config.md +63 -0
- package/skills/brainstorm/references/brainstorm-validation-workshop.md +65 -0
- package/skills/brainstorm/references/ui-surface-terms.md +42 -0
- package/skills/brainstorm-techniques/SKILL.md +231 -0
- package/skills/brainstorm-techniques/references/decision-principles.md +150 -0
- package/skills/brainstorm-techniques/references/phase-boundaries.md +103 -0
- package/skills/brainstorm-techniques/references/standing-delegation.md +407 -0
- package/skills/brainstorm-techniques/references/technical-ruling-delegation.md +174 -0
- package/skills/campaign-calendar/SKILL.md +145 -0
- package/skills/cf-token-scope/SKILL.md +164 -0
- package/skills/cf-token-scope/references/widen-playbook.md +114 -0
- package/skills/cf-token-scope/scripts/cf-token-scope.sh +253 -0
- package/skills/changelog/SKILL.md +142 -0
- package/skills/code-to-prd/SKILL.md +66 -0
- package/skills/code-to-prd/references/banner-template.md +12 -0
- package/skills/code-to-prd/references/prd-template.md +41 -0
- package/skills/code-to-prd/scripts/code-to-prd.sh +634 -0
- package/skills/code-to-prd/test/code-to-prd.test.sh +447 -0
- package/skills/code-to-prd/test/fixture/.env.example +7 -0
- package/skills/code-to-prd/test/fixture/app/about/page.tsx +11 -0
- package/skills/code-to-prd/test/fixture/app/api/health/route.ts +10 -0
- package/skills/code-to-prd/test/fixture/app/page.tsx +14 -0
- package/skills/code-to-prd/test/fixture/next.config.mjs +3 -0
- package/skills/code-to-prd/test/fixture/package.json +6 -0
- package/skills/cohort-status/SKILL.md +62 -0
- package/skills/community/SKILL.md +210 -0
- package/skills/community/scripts/bsky-community.sh +644 -0
- package/skills/community/scripts/bsky-setup.sh +631 -0
- package/skills/community/scripts/community-router.sh +93 -0
- package/skills/community/scripts/discord-community.sh +487 -0
- package/skills/community/scripts/discord-setup.sh +784 -0
- package/skills/community/scripts/github-community.sh +706 -0
- package/skills/community/scripts/hn-community.sh +233 -0
- package/skills/community/scripts/lib/hmac-sha1-b64.sh +126 -0
- package/skills/community/scripts/linkedin-community.sh +750 -0
- package/skills/community/scripts/linkedin-setup.sh +758 -0
- package/skills/community/scripts/x-community.sh +890 -0
- package/skills/community/scripts/x-setup.sh +713 -0
- package/skills/community/test/community-argv.test.sh +2922 -0
- package/skills/community/test/github-community.test.sh +981 -0
- package/skills/competitive-analysis/SKILL.md +69 -0
- package/skills/competitive-analysis/references/peer-plugin-audit.md +218 -0
- package/skills/compound/SKILL.md +673 -0
- package/skills/compound/scripts/token-efficiency-report.sh +334 -0
- package/skills/compound/test/phase-16.test.sh +379 -0
- package/skills/compound-capture/SKILL.md +791 -0
- package/skills/compound-capture/assets/critical-pattern-template.md +37 -0
- package/skills/compound-capture/assets/resolution-template.md +116 -0
- package/skills/compound-capture/references/yaml-schema.md +67 -0
- package/skills/compound-capture/schema.yaml +181 -0
- package/skills/constraint-scaffold/SKILL.md +175 -0
- package/skills/constraint-scaffold/references/boundary-readme.template +35 -0
- package/skills/constraint-scaffold/references/constraint-gates-workflow.template +69 -0
- package/skills/constraint-scaffold/references/depcruise-config.template +230 -0
- package/skills/constraint-scaffold/references/fix-constraints-stage-a.template +240 -0
- package/skills/constraint-scaffold/references/fix-constraints-stage-b.template +313 -0
- package/skills/constraint-scaffold/references/shared-runner.template +135 -0
- package/skills/constraint-scaffold/scripts/constraint-scaffold.sh +589 -0
- package/skills/constraint-scaffold/test/bite-proof.test.sh +1338 -0
- package/skills/constraint-scaffold/test/boundary.test.sh +734 -0
- package/skills/constraint-scaffold/test/emit-fix-constraints.test.sh +193 -0
- package/skills/constraint-scaffold/test/generator.test.sh +93 -0
- package/skills/constraint-scaffold/test/parity.test.sh +285 -0
- package/skills/content-writer/SKILL.md +242 -0
- package/skills/content-writer/scripts/blog-jargon-scan.sh +42 -0
- package/skills/cron-delete/SKILL.md +56 -0
- package/skills/cron-list/SKILL.md +54 -0
- package/skills/deepen-plan/SKILL.md +937 -0
- package/skills/deepen-plan/workflows/deepen-plan.workflow.js +488 -0
- package/skills/deploy/SKILL.md +115 -0
- package/skills/deploy/references/hetzner-setup.md +67 -0
- package/skills/deploy/scripts/deploy.sh +37 -0
- package/skills/deploy-docs/SKILL.md +95 -0
- package/skills/dhh-rails-style/SKILL.md +203 -0
- package/skills/dhh-rails-style/references/architecture.md +717 -0
- package/skills/dhh-rails-style/references/controllers.md +323 -0
- package/skills/dhh-rails-style/references/frontend.md +556 -0
- package/skills/dhh-rails-style/references/gems.md +316 -0
- package/skills/dhh-rails-style/references/models.md +388 -0
- package/skills/dhh-rails-style/references/testing.md +354 -0
- package/skills/discord-content/SKILL.md +126 -0
- package/skills/docs-site/SKILL.md +214 -0
- package/skills/drain-labeled-backlog/SKILL.md +203 -0
- package/skills/drain-labeled-backlog/scripts/group-by-area.sh +208 -0
- package/skills/drain-labeled-backlog/workflows/drain-labeled-backlog.workflow.js +433 -0
- package/skills/drain-prs/SKILL.md +143 -0
- package/skills/drain-prs/scripts/triage-prs.sh +192 -0
- package/skills/drain-prs/test/argv-ceiling.test.sh +183 -0
- package/skills/dspy-ruby/SKILL.md +634 -0
- package/skills/dspy-ruby/assets/config-template.rb +359 -0
- package/skills/dspy-ruby/assets/module-template.rb +326 -0
- package/skills/dspy-ruby/assets/signature-template.rb +143 -0
- package/skills/dspy-ruby/references/core-concepts.md +268 -0
- package/skills/dspy-ruby/references/optimization.md +623 -0
- package/skills/dspy-ruby/references/providers.md +335 -0
- package/skills/eval-harness/README.md +194 -0
- package/skills/eval-harness/SKILL.md +157 -0
- package/skills/eval-harness/enums/go-routes.json +13 -0
- package/skills/eval-harness/enums/incident-threshold.json +5 -0
- package/skills/eval-harness/enums/lane.json +5 -0
- package/skills/eval-harness/enums/tool-selection.json +20 -0
- package/skills/eval-harness/enums/triage-levels.json +5 -0
- package/skills/eval-harness/gated-skills.json +34 -0
- package/skills/eval-harness/models.generated.json +5 -0
- package/skills/eval-harness/promptfooconfig-go-routing.yaml +35 -0
- package/skills/eval-harness/promptfooconfig-incident-threshold.yaml +32 -0
- package/skills/eval-harness/promptfooconfig-lane-inference.yaml +33 -0
- package/skills/eval-harness/promptfooconfig-ticket-triage.yaml +34 -0
- package/skills/eval-harness/promptfooconfig-tool-selection.yaml +33 -0
- package/skills/eval-harness/prompts/go-baseline.txt +6 -0
- package/skills/eval-harness/prompts/go-skill.txt +21 -0
- package/skills/eval-harness/prompts/incident-threshold-baseline.txt +6 -0
- package/skills/eval-harness/prompts/incident-threshold-skill.txt +12 -0
- package/skills/eval-harness/prompts/lane-inference-baseline.txt +6 -0
- package/skills/eval-harness/prompts/lane-inference-skill.txt +15 -0
- package/skills/eval-harness/prompts/tool-selection-baseline.txt +19 -0
- package/skills/eval-harness/prompts/tool-selection-skill.txt +22 -0
- package/skills/eval-harness/prompts/triage-baseline.txt +7 -0
- package/skills/eval-harness/prompts/triage-skill.txt +10 -0
- package/skills/eval-harness/scripts/eval-gate.cjs +318 -0
- package/skills/eval-harness/scripts/extract-block.cjs +87 -0
- package/skills/eval-harness/scripts/gate-classification.cjs +28 -0
- package/skills/eval-harness/scripts/gen-models.sh +41 -0
- package/skills/eval-harness/scripts/gen-skill-prompt.cjs +177 -0
- package/skills/eval-harness/scripts/measure-classification.cjs +30 -0
- package/skills/eval-harness/scripts/parse-label.cjs +93 -0
- package/skills/eval-harness/scripts/verdict.cjs +132 -0
- package/skills/eval-harness/tasks/go-routing.jsonl +8 -0
- package/skills/eval-harness/tasks/incident-threshold.jsonl +7 -0
- package/skills/eval-harness/tasks/lane-inference.jsonl +7 -0
- package/skills/eval-harness/tasks/ticket-triage.jsonl +6 -0
- package/skills/eval-harness/tasks/tool-selection.jsonl +15 -0
- package/skills/eval-harness/test/eval-gate.test.sh +132 -0
- package/skills/eval-harness/test/extract-block.test.sh +87 -0
- package/skills/eval-harness/test/gate-classification.test.sh +77 -0
- package/skills/eval-harness/test/gen-models.test.sh +75 -0
- package/skills/eval-harness/test/measure-classification.test.sh +74 -0
- package/skills/eval-harness/test/parse-label.test.sh +129 -0
- package/skills/eval-harness/test/registry-completeness.test.sh +109 -0
- package/skills/eval-harness/test/verdict.test.sh +123 -0
- package/skills/every-style-editor/SKILL.md +142 -0
- package/skills/every-style-editor/references/EVERY_WRITE_STYLE.md +529 -0
- package/skills/feature-tweet/SKILL.md +199 -0
- package/skills/feature-video/SKILL.md +412 -0
- package/skills/feature-video/scripts/check_deps.sh +281 -0
- package/skills/file-todos/SKILL.md +267 -0
- package/skills/file-todos/assets/todo-template.md +166 -0
- package/skills/fix-issue/SKILL.md +278 -0
- package/skills/fix-issue/references/agent-authored-exclusion.md +120 -0
- package/skills/fix-issue/references/exclude-label-jq-snippet.md +97 -0
- package/skills/flag-bootstrap/SETUP.md +142 -0
- package/skills/flag-create/SKILL.md +145 -0
- package/skills/flag-create/scripts/create.sh +273 -0
- package/skills/flag-delete/SKILL.md +167 -0
- package/skills/flag-delete/scripts/delete.sh +288 -0
- package/skills/flag-list/SKILL.md +99 -0
- package/skills/flag-list/scripts/list.sh +275 -0
- package/skills/flag-set-role/SKILL.md +229 -0
- package/skills/flag-set-role/scripts/flip.sh +774 -0
- package/skills/frontend-anti-slop/SKILL.md +106 -0
- package/skills/frontend-anti-slop/references/anti-patterns.md +199 -0
- package/skills/frontend-anti-slop/references/slop-rules.md +65 -0
- package/skills/frontend-anti-slop/scripts/tier1-scan.ts +525 -0
- package/skills/frontend-design/SKILL.md +77 -0
- package/skills/gdpr-gate/NOTICE +136 -0
- package/skills/gdpr-gate/SKILL.md +341 -0
- package/skills/gdpr-gate/references/fields.md +160 -0
- package/skills/gdpr-gate/references/layers/api-layer.md +355 -0
- package/skills/gdpr-gate/references/layers/auth-sessions.md +290 -0
- package/skills/gdpr-gate/references/layers/data-in-transit.md +331 -0
- package/skills/gdpr-gate/references/layers/data-lifecycle.md +341 -0
- package/skills/gdpr-gate/references/layers/frontend.md +249 -0
- package/skills/gdpr-gate/references/layers/testing-seeding.md +256 -0
- package/skills/gdpr-gate/references/leakage-vectors.md +248 -0
- package/skills/gdpr-gate/references/legacy/legal-consent-v1-prose.md +48 -0
- package/skills/gdpr-gate/references/legal-consent.md +140 -0
- package/skills/gdpr-gate/references/non-negotiables.md +60 -0
- package/skills/gdpr-gate/scripts/gdpr-gate.sh +271 -0
- package/skills/gdpr-gate/scripts/notice-frontmatter.sh +337 -0
- package/skills/gdpr-gate/scripts/path-denylist.txt +39 -0
- package/skills/gdpr-gate/scripts/repo-scan.sh +158 -0
- package/skills/gdpr-gate/scripts/vendor-drift-classify.sh +120 -0
- package/skills/gdpr-gate/scripts/vendor-pin-integrity.sh +237 -0
- package/skills/gemini-imagegen/SKILL.md +283 -0
- package/skills/gemini-imagegen/requirements.txt +5 -0
- package/skills/gemini-imagegen/scripts/_error_handling.py +146 -0
- package/skills/gemini-imagegen/scripts/compose_images.py +150 -0
- package/skills/gemini-imagegen/scripts/edit_image.py +137 -0
- package/skills/gemini-imagegen/scripts/gemini_images.py +256 -0
- package/skills/gemini-imagegen/scripts/generate_image.py +147 -0
- package/skills/gemini-imagegen/scripts/multi_turn_chat.py +223 -0
- package/skills/gemini-imagegen/scripts/test_error_handling.py +179 -0
- package/skills/git-worktree/SKILL.md +534 -0
- package/skills/git-worktree/scripts/git-repo-readiness-diag.sh +98 -0
- package/skills/git-worktree/scripts/worktree-manager.sh +4773 -0
- package/skills/git-worktree/test/create-from-origin-main.test.sh +116 -0
- package/skills/git-worktree/test/lease-protects-active.test.sh +1056 -0
- package/skills/git-worktree/test/no-repo-fail-loud.test.sh +88 -0
- package/skills/git-worktree/test/orphan-reaper-honest-count.test.sh +379 -0
- package/skills/git-worktree/test/reap-archive-persistence.test.sh +725 -0
- package/skills/git-worktree/test/stale-lock-sweep.test.sh +176 -0
- package/skills/go/SKILL.md +54 -0
- package/skills/growth/SKILL.md +164 -0
- package/skills/harvest-debt/SKILL.md +78 -0
- package/skills/harvest-debt/scripts/harvest-debt.sh +114 -0
- package/skills/heal-skill/SKILL.md +173 -0
- package/skills/help/SKILL.md +46 -0
- package/skills/incident/NOTICE +39 -0
- package/skills/incident/SKILL.md +395 -0
- package/skills/incident/scripts/dry-run.sh +448 -0
- package/skills/incident/scripts/redact-engine.py +412 -0
- package/skills/incident/scripts/redact-sentinel.sh +44 -0
- package/skills/incident/templates/pir.md +139 -0
- package/skills/incident/test/fixtures/dry-run-incident.json +26 -0
- package/skills/incident/test/fixtures/dry-run-secret-leak.json +26 -0
- package/skills/incident/test/fixtures/legacy-bash-scanner.sh +82 -0
- package/skills/incident/test/fixtures/positive-corpus.md +87 -0
- package/skills/incident/test/redact-sentinel.test.sh +1420 -0
- package/skills/invoice/SKILL.md +206 -0
- package/skills/kb-glossary/SKILL.md +71 -0
- package/skills/kb-glossary/references/glossary-format.md +111 -0
- package/skills/kb-glossary/references/rejected-request-register.md +115 -0
- package/skills/kb-search/SKILL.md +276 -0
- package/skills/kb-search/scripts/kb-search-cache.sh +123 -0
- package/skills/legal-audit/SKILL.md +161 -0
- package/skills/legal-generate/NOTICE +242 -0
- package/skills/legal-generate/SKILL.md +244 -0
- package/skills/legal-generate/references/templates/advisor-agreement/template.md +12 -0
- package/skills/legal-generate/references/templates/business-associate-agreement/template.md +112 -0
- package/skills/legal-generate/references/templates/cookie-notice/template.md +82 -0
- package/skills/legal-generate/references/templates/dpa-global/template.md +439 -0
- package/skills/legal-generate/references/templates/dpa-us/template.md +222 -0
- package/skills/legal-generate/references/templates/employee-offer-letter/template.md +75 -0
- package/skills/legal-generate/references/templates/master-services-agreement/template.md +252 -0
- package/skills/legal-generate/references/templates/mutual-nda/template.md +66 -0
- package/skills/legal-generate/references/templates/one-way-nda/template.md +38 -0
- package/skills/legal-generate/references/templates/privacy-policy-gdpr/template.md +427 -0
- package/skills/legal-generate/references/templates/privacy-policy-us/template.md +313 -0
- package/skills/legal-generate/references/templates/terms-of-use/template.md +75 -0
- package/skills/legal-generate/scripts/strip-vendor-credit.sh +96 -0
- package/skills/linear-fetch/SKILL.md +247 -0
- package/skills/linear-fetch/scripts/assert-no-linear-telemetry.sh +66 -0
- package/skills/linear-fetch/scripts/redact-linear-urls.sh +51 -0
- package/skills/linear-fetch/scripts/render-caller-template.sh +36 -0
- package/skills/linear-fetch/test/assert-no-linear-telemetry.test.sh +113 -0
- package/skills/linear-fetch/test/parity.test.sh +112 -0
- package/skills/linear-fetch/test/persist-safe-integration.test.sh +191 -0
- package/skills/linear-fetch/test/redact-linear-urls.test.sh +226 -0
- package/skills/merge-pr/SKILL.md +670 -0
- package/skills/model-launch-review/SKILL.md +255 -0
- package/skills/model-launch-review/scripts/audit-models.sh +501 -0
- package/skills/one-shot/SKILL.md +316 -0
- package/skills/one-shot/references/token-discipline.md +74 -0
- package/skills/operator-bootstrap/SKILL.md +412 -0
- package/skills/operator-bootstrap/template.sh +439 -0
- package/skills/operator-digest/SKILL.md +287 -0
- package/skills/operator-digest/assets/operator-digest.workflow.yml +175 -0
- package/skills/operator-digest/scripts/digest-scrub.sh +119 -0
- package/skills/operator-digest/scripts/provision-operator-digest-repo.sh +147 -0
- package/skills/operator-rephrase/SKILL.md +98 -0
- package/skills/pencil-setup/SKILL.md +201 -0
- package/skills/pencil-setup/scripts/check_deps.sh +556 -0
- package/skills/pencil-setup/scripts/copy_adapter.sh +80 -0
- package/skills/pencil-setup/scripts/package-lock.json +1172 -0
- package/skills/pencil-setup/scripts/package.json +14 -0
- package/skills/pencil-setup/scripts/pencil-error-enrichment.mjs +21 -0
- package/skills/pencil-setup/scripts/pencil-mcp-adapter.mjs +733 -0
- package/skills/pencil-setup/scripts/pencil-response-classify.mjs +38 -0
- package/skills/pencil-setup/scripts/pencil-save-gate.mjs +26 -0
- package/skills/pencil-setup/scripts/sanitize-filename.mjs +25 -0
- package/skills/plan/SKILL.md +1140 -0
- package/skills/plan/references/differential-migration-testing.md +185 -0
- package/skills/plan/references/plan-community-discovery.md +42 -0
- package/skills/plan/references/plan-founder-check.md +137 -0
- package/skills/plan/references/plan-functional-overlap.md +25 -0
- package/skills/plan/references/plan-host-fanout.md +51 -0
- package/skills/plan/references/plan-issue-templates.md +779 -0
- package/skills/plan/references/plan-network-outage-checklist.md +131 -0
- package/skills/plan/references/plan-scope-check.md +126 -0
- package/skills/plan/references/plan-sharp-edges.md +331 -0
- package/skills/plan/references/plan-signoff-lifecycle.md +12 -0
- package/skills/plan/references/webapp-cwv-observability.md +143 -0
- package/skills/plan-review/SKILL.md +91 -0
- package/skills/plan-review/lib/named-panel.mjs +58 -0
- package/skills/plan-review/workflows/plan-review.workflow.js +477 -0
- package/skills/postmerge/SKILL.md +642 -0
- package/skills/postmerge/references/deploy-status-debugging.md +141 -0
- package/skills/postmerge/references/graceful-degradation.md +20 -0
- package/skills/postmerge/references/sentry-error-count-delta.md +137 -0
- package/skills/preflight/SKILL.md +1553 -0
- package/skills/preflight/references/check-13-founder-check.md +286 -0
- package/skills/preflight/scripts/founder-check.py +1640 -0
- package/skills/preflight/scripts/parse-form-a.awk +107 -0
- package/skills/preflight/scripts/probe-verb-gate.sh +108 -0
- package/skills/product-roadmap/SKILL.md +357 -0
- package/skills/product-roadmap/scripts/roadmap-reconcile.sh +327 -0
- package/skills/provision-cloudflare/SKILL.md +67 -0
- package/skills/provision-cloudflare/scripts/provision-cloudflare.sh +271 -0
- package/skills/provision-doppler/SKILL.md +58 -0
- package/skills/provision-doppler/scripts/provision-doppler.sh +358 -0
- package/skills/provision-github/SKILL.md +59 -0
- package/skills/provision-github/scripts/provision-github.sh +300 -0
- package/skills/provision-hetzner/SKILL.md +76 -0
- package/skills/provision-hetzner/scripts/provision-hetzner.sh +262 -0
- package/skills/provision-hetzner/test/fixture/knowledge-base/legal/tenant-dpa-register.md +26 -0
- package/skills/provision-hetzner/test/fixture/provision-hetzner-dry-run.golden +34 -0
- package/skills/provision-hetzner/test/provision-hetzner-characterization.test.sh +227 -0
- package/skills/qa/SKILL.md +263 -0
- package/skills/qa/references/browser-preflight.md +22 -0
- package/skills/questionnaire-generate/SKILL.md +328 -0
- package/skills/questionnaire-generate/references/questionnaire.template +60 -0
- package/skills/rclone/SKILL.md +162 -0
- package/skills/rclone/scripts/check_setup.sh +63 -0
- package/skills/release-announce/SKILL.md +65 -0
- package/skills/release-docs/SKILL.md +92 -0
- package/skills/reproduce-bug/SKILL.md +358 -0
- package/skills/resolve-debt/SKILL.md +85 -0
- package/skills/resolve-debt/scripts/resolve-debt.py +584 -0
- package/skills/resolve-parallel/SKILL.md +45 -0
- package/skills/resolve-parallel/workflows/resolve-parallel.workflow.js +415 -0
- package/skills/resolve-pr-parallel/SKILL.md +68 -0
- package/skills/resolve-pr-parallel/workflows/resolve-pr-parallel.workflow.js +419 -0
- package/skills/resolve-todo-parallel/SKILL.md +57 -0
- package/skills/resolve-todo-parallel/workflows/resolve-todo-parallel.workflow.js +398 -0
- package/skills/review/SKILL.md +1620 -0
- package/skills/review/references/anti-slop-scanner-hook.md +40 -0
- package/skills/review/references/defect-classes-migrations.md +9 -0
- package/skills/review/references/generated-input-oracle-surfaces.md +15 -0
- package/skills/review/references/review-e2e-testing.md +116 -0
- package/skills/review/references/review-evidence-comment.md +36 -0
- package/skills/review/references/review-todo-structure.md +165 -0
- package/skills/review/references/risk-tier-and-fix-rounds.md +97 -0
- package/skills/review/references/semgrep-custom-rules.yaml +174 -0
- package/skills/review/references/ultrathink-phases.md +98 -0
- package/skills/review/references/wfs.md +6 -0
- package/skills/review/scripts/emit-review-trailer.sh +499 -0
- package/skills/review/scripts/ensure-semgrep.sh +99 -0
- package/skills/review/scripts/fix-round-seats.sh +197 -0
- package/skills/review/test/emit-review-trailer.test.sh +387 -0
- package/skills/review/test/ensure-semgrep.test.sh +111 -0
- package/skills/review/test/fix-round-seats.test.sh +237 -0
- package/skills/review/workflows/README.md +147 -0
- package/skills/review/workflows/review.workflow.js +667 -0
- package/skills/schedule/SKILL.md +796 -0
- package/skills/seo-aeo/SKILL.md +139 -0
- package/skills/seo-aeo/scripts/validate-csp.sh +233 -0
- package/skills/seo-aeo/scripts/validate-seo.sh +262 -0
- package/skills/ship/SKILL.md +3023 -0
- package/skills/ship/references/adr-ordinal-collision.md +47 -0
- package/skills/ship/references/affected-gate-contract.md +115 -0
- package/skills/ship/references/ci-run-diagnosis.md +29 -0
- package/skills/ship/references/ci-workflow-authoring.md +68 -0
- package/skills/ship/references/cleanup-merged-archival.md +39 -0
- package/skills/ship/references/counsel-review-clo-attestation.md +38 -0
- package/skills/ship/references/cto-ruling-armed-push.md +5 -0
- package/skills/ship/references/decision-challenges-render.md +71 -0
- package/skills/ship/references/delegated-act-authoring-form.md +7 -0
- package/skills/ship/references/detached-battery-verdict.md +13 -0
- package/skills/ship/references/epic-close-sweep.md +62 -0
- package/skills/ship/references/feature-tweet-draft.md +42 -0
- package/skills/ship/references/followthrough-issue-body-template.md +47 -0
- package/skills/ship/references/followthrough-stub-template.sh +92 -0
- package/skills/ship/references/merge-queue-dequeue.md +39 -0
- package/skills/ship/references/operator-holds.md +179 -0
- package/skills/ship/references/pipeline-tally-render.md +43 -0
- package/skills/ship/references/pr-overlap.md +153 -0
- package/skills/ship/references/queue-mode.md +7 -0
- package/skills/ship/references/ready-run-wait.md +36 -0
- package/skills/ship/references/red-on-main-quarantine.md +110 -0
- package/skills/ship/references/register-update-pr-pattern.md +88 -0
- package/skills/ship/references/settle-then-admin-merge.md +130 -0
- package/skills/ship/references/supabase-column-probe.md +13 -0
- package/skills/ship/scripts/auto-close-scan.sh +76 -0
- package/skills/ship/scripts/battery-owed.sh +403 -0
- package/skills/ship/scripts/hold-gate-eval.sh +410 -0
- package/skills/ship/scripts/net-issue-flow.sh +665 -0
- package/skills/ship/scripts/pr-overlap-preflight.sh +483 -0
- package/skills/ship/scripts/ship-pir-action-items-gate.sh +246 -0
- package/skills/ship/scripts/technical-ruling-gate.sh +444 -0
- package/skills/skill-creator/SKILL.md +299 -0
- package/skills/skill-creator/references/api-security.md +248 -0
- package/skills/skill-creator/references/authoring-levers.md +110 -0
- package/skills/skill-creator/references/be-clear-and-direct.md +587 -0
- package/skills/skill-creator/references/best-practices.md +431 -0
- package/skills/skill-creator/references/common-patterns.md +639 -0
- package/skills/skill-creator/references/core-principles.md +193 -0
- package/skills/skill-creator/references/executable-code.md +188 -0
- package/skills/skill-creator/references/iteration-and-testing.md +509 -0
- package/skills/skill-creator/references/official-spec.md +193 -0
- package/skills/skill-creator/references/recommended-structure.md +180 -0
- package/skills/skill-creator/references/skill-structure.md +396 -0
- package/skills/skill-creator/references/using-scripts.md +122 -0
- package/skills/skill-creator/references/using-templates.md +120 -0
- package/skills/skill-creator/references/workflows-and-validation.md +541 -0
- package/skills/skill-creator/scripts/init_skill.py +322 -0
- package/skills/skill-creator/scripts/package_skill.py +110 -0
- package/skills/skill-creator/scripts/quick_validate.py +64 -0
- package/skills/skill-creator/templates/router-skill.md +73 -0
- package/skills/skill-creator/templates/simple-skill.md +34 -0
- package/skills/skill-creator/workflows/add-reference.md +96 -0
- package/skills/skill-creator/workflows/add-script.md +104 -0
- package/skills/skill-creator/workflows/add-template.md +83 -0
- package/skills/skill-creator/workflows/add-workflow.md +128 -0
- package/skills/skill-creator/workflows/audit-skill.md +159 -0
- package/skills/skill-creator/workflows/create-domain-expertise-skill.md +623 -0
- package/skills/skill-creator/workflows/create-new-skill.md +207 -0
- package/skills/skill-creator/workflows/get-guidance.md +134 -0
- package/skills/skill-creator/workflows/upgrade-to-router.md +170 -0
- package/skills/skill-creator/workflows/verify-skill.md +226 -0
- package/skills/skill-security-scan/SKILL.md +145 -0
- package/skills/skill-security-scan/references/disclaimer.md +21 -0
- package/skills/skill-security-scan/references/first-party-allowlist.yaml +38 -0
- package/skills/skill-security-scan/references/override-artifact-schema.json +68 -0
- package/skills/skill-security-scan/references/override-mechanism.md +171 -0
- package/skills/skill-security-scan/references/redirect-domains.yaml +27 -0
- package/skills/skill-security-scan/references/regex-patterns.md +136 -0
- package/skills/skill-security-scan/references/rules/code-exec.yaml +70 -0
- package/skills/skill-security-scan/references/rules/filesystem-boundary.yaml +29 -0
- package/skills/skill-security-scan/references/rules/manifest.yaml +44 -0
- package/skills/skill-security-scan/references/rules/supply-chain.yaml +36 -0
- package/skills/skill-security-scan/references/scan-meta-schema.json +63 -0
- package/skills/skill-security-scan/references/test-fixtures/clean-soleur-style.skill.md +23 -0
- package/skills/skill-security-scan/references/test-fixtures/clean-third-party.skill.md +26 -0
- package/skills/skill-security-scan/references/test-fixtures/malicious-codeexec.skill.md +23 -0
- package/skills/skill-security-scan/references/test-fixtures/malicious-curl-pipe-bash.skill.md +69 -0
- package/skills/skill-security-scan/references/test-fixtures/malicious-prompt-injection.skill.md +17 -0
- package/skills/skill-security-scan/references/test-fixtures/malicious-telemetry-beacon.skill.md +23 -0
- package/skills/skill-security-scan/references/typosquat-targets.yaml +65 -0
- package/skills/skill-security-scan/scripts/check-codeexec.sh +41 -0
- package/skills/skill-security-scan/scripts/check-filesystem-boundary.sh +19 -0
- package/skills/skill-security-scan/scripts/check-prompt-injection.sh +95 -0
- package/skills/skill-security-scan/scripts/check-supply-chain.sh +219 -0
- package/skills/skill-security-scan/scripts/check-telemetry-surface.sh +126 -0
- package/skills/skill-security-scan/scripts/lib.sh +197 -0
- package/skills/skill-security-scan/scripts/parse-override.sh +231 -0
- package/skills/skill-security-scan/scripts/run-scan.sh +342 -0
- package/skills/skill-security-scan/scripts/run-self-test.sh +95 -0
- package/skills/skill-security-scan/test/argv-ceiling.test.sh +180 -0
- package/skills/skill-security-scan/test/meta-dir-reap.test.sh +411 -0
- package/skills/skill-security-scan/test/parse-override.test.sh +607 -0
- package/skills/skill-security-scan/test/redact-pii-ghs-shape.test.sh +251 -0
- package/skills/social-distribute/SKILL.md +485 -0
- package/skills/spec-templates/SKILL.md +274 -0
- package/skills/sync/SKILL.md +49 -0
- package/skills/test-browser/SKILL.md +464 -0
- package/skills/test-browser/references/feature-map.md +161 -0
- package/skills/test-fix-loop/SKILL.md +159 -0
- package/skills/test-fix-loop/references/determinism-checklist.md +154 -0
- package/skills/triage/SKILL.md +402 -0
- package/skills/trigger-cron/SKILL.md +136 -0
- package/skills/trigger-cron/scripts/trigger.sh +198 -0
- package/skills/user-set-role/SKILL.md +147 -0
- package/skills/user-set-role/scripts/set-role.sh +218 -0
- package/skills/user-story-writer/SKILL.md +77 -0
- package/skills/ux-audit/SKILL.md +251 -0
- package/skills/ux-audit/references/finding.schema.json +62 -0
- package/skills/ux-audit/references/route-list.yaml +95 -0
- package/skills/ux-audit/scripts/bot-fixture.ts +270 -0
- package/skills/ux-audit/scripts/bot-signin.ts +135 -0
- package/skills/ux-audit/scripts/dedup-hash.ts +46 -0
- package/skills/verification-scaffold/SKILL.md +73 -0
- package/skills/verification-scaffold/references/gitleaks.toml.template +13 -0
- package/skills/verification-scaffold/references/guardrail-audit.md +156 -0
- package/skills/verification-scaffold/references/guards/block-stash-hook.sh.template +72 -0
- package/skills/verification-scaffold/references/guards/commit-on-main-hook.sh.template +82 -0
- package/skills/verification-scaffold/references/guards/orphan-test-check.sh.template +102 -0
- package/skills/verification-scaffold/references/guards/precommit-guard.sh.template +115 -0
- package/skills/verification-scaffold/references/guards/secret-scan-hook.sh.template +176 -0
- package/skills/verification-scaffold/references/guards/verification-gates.yml.template +49 -0
- package/skills/verification-scaffold/references/guards/wrapper-boundary.sh.template +47 -0
- package/skills/verification-scaffold/references/guards.tsv +21 -0
- package/skills/verification-scaffold/references/verification-readme.template +55 -0
- package/skills/verification-scaffold/scripts/verification-scaffold.sh +785 -0
- package/skills/verification-scaffold/test/bite-proof.test.sh +159 -0
- package/skills/verification-scaffold/test/generator.test.sh +319 -0
- package/skills/verification-scaffold/test/manifest.test.sh +144 -0
- package/skills/work/SKILL.md +1480 -0
- package/skills/work/references/end-of-work-resume.md +7 -0
- package/skills/work/references/hook-test-main-cwd.md +24 -0
- package/skills/work/references/key-principles.md +83 -0
- package/skills/work/references/property-testing.md +122 -0
- package/skills/work/references/sql-security-definer-rpc-scaffold.sql +78 -0
- package/skills/work/references/work-agent-teams.md +97 -0
- package/skills/work/references/work-lifecycle-parallel.md +147 -0
- package/skills/work/references/work-lockfile-bumps.md +97 -0
- package/skills/work/references/work-scratch-sandboxes.md +212 -0
- package/skills/work/references/work-subagent-fanout.md +61 -0
- package/skills/work/references/work-test-harness-sharp-edges.md +73 -0
- package/skills/xcode-test/SKILL.md +360 -0
|
@@ -0,0 +1,748 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Soleur session state: cross-session locks + leases + headless visibility.
|
|
3
|
+
#
|
|
4
|
+
# Designed for N parallel CC sessions (foreground or `claude --bg`). Pays the
|
|
5
|
+
# 2026-04-21 concurrency bill so worktree reaping, fetch races, and merge-main
|
|
6
|
+
# collisions become safe side-effects.
|
|
7
|
+
#
|
|
8
|
+
# Plan: knowledge-base/project/plans/2026-05-12-feat-bg-readiness-concurrency-hardening-plan.md
|
|
9
|
+
# Canonical flock idiom: the `flock -w 5` append block in agent-token-tee.sh
|
|
10
|
+
# (search that file for `Append under flock`). Cited by content, not by line
|
|
11
|
+
# number: the previous `:160-170` range pointed at the jq_fail block, and a
|
|
12
|
+
# repo-relative line citation is unresolvable from the plugin cache anyway —
|
|
13
|
+
# which is the class of defect this file's relocation exists to fix.
|
|
14
|
+
# Location: ships INSIDE the plugin (ADR-178, #7409) so a marketplace install
|
|
15
|
+
# resolves it; .claude/hooks/** consumers reach in from the repo side.
|
|
16
|
+
|
|
17
|
+
# Guard against double-source within a single shell.
|
|
18
|
+
if [[ "${_SOLEUR_SESSION_STATE_LOADED:-}" == "1" ]]; then
|
|
19
|
+
return 0 2>/dev/null || true
|
|
20
|
+
fi
|
|
21
|
+
_SOLEUR_SESSION_STATE_LOADED=1
|
|
22
|
+
|
|
23
|
+
# Kill switch (matches SOLEUR_DISABLE_* idiom). When set to 1, every function
|
|
24
|
+
# in this module short-circuits to a no-op so operators can disable the lock
|
|
25
|
+
# layer in emergencies without surgery.
|
|
26
|
+
_session_state_disabled() {
|
|
27
|
+
[[ "${SOLEUR_DISABLE_SESSION_STATE:-}" == "1" ]]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# Path resolution. Tests override via SOLEUR_SESSION_STATE_ROOT; production
|
|
31
|
+
# anchors to git-common-dir so all worktrees of one repo share state.
|
|
32
|
+
_session_state_root() {
|
|
33
|
+
if [[ -n "${SOLEUR_SESSION_STATE_ROOT:-}" ]]; then
|
|
34
|
+
printf '%s\n' "$SOLEUR_SESSION_STATE_ROOT"
|
|
35
|
+
return 0
|
|
36
|
+
fi
|
|
37
|
+
local common
|
|
38
|
+
common=$(git rev-parse --git-common-dir 2>/dev/null) || {
|
|
39
|
+
printf '/tmp/soleur-session-state-orphan\n'
|
|
40
|
+
return 0
|
|
41
|
+
}
|
|
42
|
+
# Canonicalize per existing idiom.
|
|
43
|
+
( cd -P "$common" 2>/dev/null && printf '%s/soleur-session-state\n' "$(pwd -P)" )
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# Initialize state dirs (idempotent). Called at module load and on demand.
|
|
47
|
+
_session_state_init_dirs() {
|
|
48
|
+
local root
|
|
49
|
+
root=$(_session_state_root)
|
|
50
|
+
LOCK_DIR="$root/locks"
|
|
51
|
+
LEASE_DIR="$root/leases"
|
|
52
|
+
LOG_DIR="$root/logs"
|
|
53
|
+
mkdir -p "$LOCK_DIR" "$LEASE_DIR" "$LOG_DIR" 2>/dev/null || true
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# Hard-fail predicate. flock from util-linux is required. macOS polyfill
|
|
57
|
+
# deferred per plan §Risks #1.
|
|
58
|
+
_session_state_require_flock() {
|
|
59
|
+
if ! command -v flock >/dev/null 2>&1; then
|
|
60
|
+
echo "session-state: flock(1) not found. Soleur requires util-linux flock for cross-session locking." >&2
|
|
61
|
+
echo " macOS: brew install util-linux && add \$(brew --prefix util-linux)/sbin to PATH" >&2
|
|
62
|
+
return 99
|
|
63
|
+
fi
|
|
64
|
+
return 0
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# ---------------------------------------------------------------------------
|
|
68
|
+
# Locks
|
|
69
|
+
# ---------------------------------------------------------------------------
|
|
70
|
+
#
|
|
71
|
+
# Lock state is stashed in module-level associative-style vars. Because flock
|
|
72
|
+
# fd 9 must remain open for the lifetime of the critical section, we open it
|
|
73
|
+
# inline and stash the FD on a per-name basis so release_lock can close it.
|
|
74
|
+
# Bash does not let us programmatically open a numbered FD other than 9 in a
|
|
75
|
+
# portable way, so we serialize lock acquisitions in-process and hold fd 9
|
|
76
|
+
# for the duration. Callers that need nested locks open them in the natural
|
|
77
|
+
# (acquire-A, acquire-B, release-B, release-A) order.
|
|
78
|
+
|
|
79
|
+
# Per-name FD tracking. bash auto-assigns FDs via `exec {fd}>>file` which
|
|
80
|
+
# lets us hold multiple advisory locks (different files) at once. flock
|
|
81
|
+
# semantics are inode-bound, so cross-shell mutual exclusion is still
|
|
82
|
+
# correct against the same lock_file.
|
|
83
|
+
declare -gA _SESSION_LOCK_FDS 2>/dev/null || true
|
|
84
|
+
declare -gA _SESSION_LOCK_FILES 2>/dev/null || true
|
|
85
|
+
declare -gA _SESSION_LOCK_ACQUIRED 2>/dev/null || true
|
|
86
|
+
# Stash the started_at this shell wrote into each lease so `release_lease`
|
|
87
|
+
# can verify "I'm the recorded owner" before deleting. Closes the PID-reuse
|
|
88
|
+
# window where a new shell inheriting our pid would otherwise release.
|
|
89
|
+
declare -gA _LEASE_ACQUIRED_STARTED_AT 2>/dev/null || true
|
|
90
|
+
|
|
91
|
+
_acquire_lock_impl() {
|
|
92
|
+
local name="$1"
|
|
93
|
+
local timeout_s="${2:-30}"
|
|
94
|
+
local mode="${3:-x}" # x | s
|
|
95
|
+
|
|
96
|
+
_session_state_disabled && return 0
|
|
97
|
+
|
|
98
|
+
_session_state_require_flock || return 99
|
|
99
|
+
_session_state_init_dirs
|
|
100
|
+
|
|
101
|
+
# Idempotent: already held by this shell.
|
|
102
|
+
if [[ -n "${_SESSION_LOCK_FDS[$name]:-}" ]]; then
|
|
103
|
+
return 0
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
# The name reaches two filesystem paths (.lock and .holder) — constrain it
|
|
107
|
+
# before either is formed (the charset also rules out `/` traversal).
|
|
108
|
+
[[ "$name" =~ ^[A-Za-z0-9._-]+$ ]] || return 99
|
|
109
|
+
|
|
110
|
+
local lock_file="$LOCK_DIR/$name.lock"
|
|
111
|
+
# A non-regular lock path (fifo, dir) blocks in open(2) BEFORE the bounded
|
|
112
|
+
# flock below is ever exec'd — the exec boundary cannot cover a wait that
|
|
113
|
+
# precedes it. A symlink is refused outright: dangling, `exec {fd}>>` would
|
|
114
|
+
# create the linked-to path; resolved, the named lock would alias a foreign
|
|
115
|
+
# inode — both plantable on the cross-user /tmp orphan-fallback LOCK_DIR.
|
|
116
|
+
[[ -L "$lock_file" ]] && return 99
|
|
117
|
+
[[ -e "$lock_file" && ! -f "$lock_file" ]] && return 99
|
|
118
|
+
local fd
|
|
119
|
+
exec {fd}>>"$lock_file" || return 99
|
|
120
|
+
|
|
121
|
+
local flag="-x"
|
|
122
|
+
[[ "$mode" == "s" ]] && flag="-s"
|
|
123
|
+
|
|
124
|
+
# #7697: bound the wait at the EXEC boundary, not only inside flock. flock's
|
|
125
|
+
# own -w expiry is delivered via SIGALRM; a caller that inherited that
|
|
126
|
+
# signal as BLOCKED (an outer process sigprocmask'd it before exec —
|
|
127
|
+
# measured: `flock -w 3` still waiting at 6 s, a live holder then kept the
|
|
128
|
+
# repo-wide lock for 4.6 days) defeats it silently. `timeout -k` wraps the
|
|
129
|
+
# flock call: its deadline does not depend on this process's signal mask,
|
|
130
|
+
# and the trailing SIGKILL needs no cooperation at all. The flock CHILD
|
|
131
|
+
# shares this shell's fd, and flock(2) attaches to the open file
|
|
132
|
+
# description — an acquisition lands on our fd even though flock ran in a
|
|
133
|
+
# child, and a timeout kill releases nothing we hold.
|
|
134
|
+
local _to_cmd="" _slack="${SOLEUR_LOCK_BOUND_SLACK_S:-60}"
|
|
135
|
+
[[ "$_slack" =~ ^[0-9]+$ ]] || _slack=60
|
|
136
|
+
# timeout_s reaches $(( )) arithmetic AND `flock -w` — a non-digit value is
|
|
137
|
+
# an arithmetic name under `set -u` (unbound abort) or a silent 0 bound;
|
|
138
|
+
# normalize like test-contention's tc_acquire does for its copy.
|
|
139
|
+
[[ "$timeout_s" =~ ^[0-9]+$ ]] || timeout_s=30
|
|
140
|
+
if command -v timeout >/dev/null 2>&1; then _to_cmd=timeout
|
|
141
|
+
elif command -v gtimeout >/dev/null 2>&1; then _to_cmd=gtimeout; fi
|
|
142
|
+
|
|
143
|
+
local _flock_rc=0
|
|
144
|
+
if [[ -n "$_to_cmd" ]]; then
|
|
145
|
+
# Outer bound = declared wait + slack; a normal -w expiry arrives first as
|
|
146
|
+
# flock's own rc=1, so rc=124 from timeout means the inner expiry never
|
|
147
|
+
# fired — the #7697 blocked-mask case. 137 is the same event one rung
|
|
148
|
+
# later: timeout had to deliver the -k SIGKILL.
|
|
149
|
+
"$_to_cmd" -k 5 "$(( timeout_s + _slack ))" flock -w "$timeout_s" "$flag" "$fd" || _flock_rc=$?
|
|
150
|
+
if [[ "$_flock_rc" == "124" || "$_flock_rc" == "137" ]]; then
|
|
151
|
+
echo "[contention] LOCK_BOUND_FORCED: '$name' — flock -w ${timeout_s}s never returned; the exec-boundary bound fired at ${timeout_s}s+${_slack}s. A blocked signal mask (SIGALRM) defeats flock's own expiry — proceeding contended." >&2
|
|
152
|
+
_flock_rc=1
|
|
153
|
+
fi
|
|
154
|
+
else
|
|
155
|
+
[[ -z "${_SESSION_LOCK_BOUND_NOTED:-}" ]] && {
|
|
156
|
+
_SESSION_LOCK_BOUND_NOTED=1
|
|
157
|
+
echo "[note] session-state: timeout(1) absent — lock waits bounded only by flock -w (a blocked SIGALRM mask can still defeat it; #7697)" >&2
|
|
158
|
+
}
|
|
159
|
+
flock -w "$timeout_s" "$flag" "$fd" || _flock_rc=$?
|
|
160
|
+
fi
|
|
161
|
+
if [[ "$_flock_rc" != "0" ]]; then
|
|
162
|
+
eval "exec ${fd}>&-" 2>/dev/null || true
|
|
163
|
+
return 99
|
|
164
|
+
fi
|
|
165
|
+
|
|
166
|
+
_SESSION_LOCK_FDS[$name]="$fd"
|
|
167
|
+
_SESSION_LOCK_FILES[$name]="$lock_file"
|
|
168
|
+
_SESSION_LOCK_ACQUIRED[$name]="${EPOCHSECONDS:-0}"
|
|
169
|
+
|
|
170
|
+
# Advisory holder marker (#7697): waiters' heartbeats read this to tell a
|
|
171
|
+
# progressing holder from a wedged one. The reader treats a torn or absent
|
|
172
|
+
# file as holder=unreadable/none, never an error. Written via mktemp+mv:
|
|
173
|
+
# a plain `>` follows a planted symlink — and on the /tmp orphan-fallback
|
|
174
|
+
# LOCK_DIR a predictable name is cross-user plantable — and is non-atomic.
|
|
175
|
+
# Shared (-s) holders have no single holder identity, so they mint none.
|
|
176
|
+
if [[ "$mode" != "s" ]]; then
|
|
177
|
+
local _hf_tmp
|
|
178
|
+
# lint-trap-ownership: ok sourced library — an EXIT trap would clobber the consumer's; every path removes _hf_tmp and the SIGKILL-window residual is one ~150B file
|
|
179
|
+
_hf_tmp=$(mktemp "$LOCK_DIR/.holder.XXXXXXXX" 2>/dev/null) && {
|
|
180
|
+
{
|
|
181
|
+
printf 'pid=%s\n' "$$"
|
|
182
|
+
printf 'ppid=%s\n' "$PPID"
|
|
183
|
+
printf 'acquired_epoch=%s\n' "${_SESSION_LOCK_ACQUIRED[$name]}"
|
|
184
|
+
printf 'progress_epoch=%s\n' "${_SESSION_LOCK_ACQUIRED[$name]}"
|
|
185
|
+
} > "$_hf_tmp" 2>/dev/null \
|
|
186
|
+
&& mv -f "$_hf_tmp" "$LOCK_DIR/$name.holder" 2>/dev/null
|
|
187
|
+
} || true
|
|
188
|
+
[[ -n "${_hf_tmp:-}" && -e "$_hf_tmp" ]] && rm -f "$_hf_tmp" 2>/dev/null || true
|
|
189
|
+
fi
|
|
190
|
+
return 0
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
acquire_lock() {
|
|
194
|
+
_acquire_lock_impl "$1" "${2:-30}" "x"
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
acquire_lock_shared() {
|
|
198
|
+
_acquire_lock_impl "$1" "${2:-30}" "s"
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
release_lock() {
|
|
202
|
+
local name="$1"
|
|
203
|
+
_session_state_disabled && return 0
|
|
204
|
+
local fd="${_SESSION_LOCK_FDS[$name]:-}"
|
|
205
|
+
[[ -z "$fd" ]] && return 0
|
|
206
|
+
local lock_file="${_SESSION_LOCK_FILES[$name]:-}"
|
|
207
|
+
# The advisory holder marker (#7697) dies BEFORE our hold does: removing
|
|
208
|
+
# it while the fd is still open is provably removing OUR marker — after
|
|
209
|
+
# the close, a queued successor could already have minted its own and we
|
|
210
|
+
# would unlink that instead, leaving its hold markerless until release.
|
|
211
|
+
[[ -n "$lock_file" ]] && rm -f "${lock_file%.lock}.holder" 2>/dev/null || true
|
|
212
|
+
eval "exec ${fd}>&-" 2>/dev/null || true
|
|
213
|
+
unset "_SESSION_LOCK_FDS[$name]"
|
|
214
|
+
unset "_SESSION_LOCK_ACQUIRED[$name]"
|
|
215
|
+
unset "_SESSION_LOCK_FILES[$name]"
|
|
216
|
+
return 0
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
# stamp_lock_holder <name> [key=value ...] — refresh the holder marker's
|
|
220
|
+
# progress_epoch and set progress fields, in place. Call sites are the
|
|
221
|
+
# consumers' own progress points (test-all stamps once per suite boundary).
|
|
222
|
+
# NEVER mints: a marker is born at acquire and dies at release — stamping a
|
|
223
|
+
# lock this shell does not hold would fabricate holder identity, so it is a
|
|
224
|
+
# silent no-op. All failures are silent too: the marker is advisory.
|
|
225
|
+
stamp_lock_holder() {
|
|
226
|
+
# Guard BEFORE shift: a zero-arg call must be a silent no-op (the marker is
|
|
227
|
+
# advisory), and a bare `shift` with no args aborts a `set -e` caller.
|
|
228
|
+
[[ -n "${1:-}" ]] || return 0
|
|
229
|
+
local name="$1"
|
|
230
|
+
shift
|
|
231
|
+
_session_state_disabled && return 0
|
|
232
|
+
[[ -n "${_SESSION_LOCK_FDS[$name]:-}" ]] || return 0
|
|
233
|
+
[[ "$name" =~ ^[A-Za-z0-9._-]+$ ]] || return 0
|
|
234
|
+
local hf="$LOCK_DIR/$name.holder"
|
|
235
|
+
# Regular-file + not-a-link: a planted symlink must not be stamped through
|
|
236
|
+
# (the mint is mktemp+mv for the same reason), and anything that is not a
|
|
237
|
+
# marker we minted is left alone.
|
|
238
|
+
[[ -f "$hf" && ! -L "$hf" ]] || return 0
|
|
239
|
+
local _hf_tmp
|
|
240
|
+
# lint-trap-ownership: ok sourced library — same as the mint above; every path removes _hf_tmp, SIGKILL-window residual is a single file
|
|
241
|
+
_hf_tmp=$(mktemp "$LOCK_DIR/.holder.XXXXXXXX" 2>/dev/null) || return 0
|
|
242
|
+
{
|
|
243
|
+
printf 'pid=%s\n' "$$"
|
|
244
|
+
printf 'ppid=%s\n' "$PPID"
|
|
245
|
+
printf 'acquired_epoch=%s\n' "${_SESSION_LOCK_ACQUIRED[$name]:-}"
|
|
246
|
+
printf 'progress_epoch=%s\n' "${EPOCHSECONDS:-0}"
|
|
247
|
+
local kv
|
|
248
|
+
for kv in "$@"; do printf '%s\n' "${kv//$'\n'/ }"; done
|
|
249
|
+
} > "$_hf_tmp" 2>/dev/null && mv -f "$_hf_tmp" "$hf" 2>/dev/null || true
|
|
250
|
+
[[ -e "$_hf_tmp" ]] && rm -f "$_hf_tmp" 2>/dev/null || true
|
|
251
|
+
return 0
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
# with_lock <name> <timeout_s> -- <command> [args...]
|
|
255
|
+
#
|
|
256
|
+
# CLI-friendly wrapper for SKILL.md callers. Each `bash session-state.sh
|
|
257
|
+
# acquire_lock` invocation is a separate process whose fd 9 closes on exit,
|
|
258
|
+
# so the standalone acquire/release pattern doesn't serialize anything when
|
|
259
|
+
# invoked separately. This wrapper acquires the lock and runs the command
|
|
260
|
+
# in the same shell so the lock outlives the critical section.
|
|
261
|
+
#
|
|
262
|
+
# Returns the command's exit code on success, 99 on lock-acquire timeout.
|
|
263
|
+
with_lock() {
|
|
264
|
+
local name="$1"; shift
|
|
265
|
+
local timeout_s="$1"; shift
|
|
266
|
+
if [[ "$1" == "--" ]]; then shift; fi
|
|
267
|
+
if ! acquire_lock "$name" "$timeout_s"; then
|
|
268
|
+
headless_or_stderr warn "lock '$name' contended after ${timeout_s}s; aborting"
|
|
269
|
+
return 99
|
|
270
|
+
fi
|
|
271
|
+
local rc=0
|
|
272
|
+
"$@" || rc=$?
|
|
273
|
+
release_lock "$name"
|
|
274
|
+
return "$rc"
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
# ---------------------------------------------------------------------------
|
|
278
|
+
# Leases (durable, key=value, atomic-write)
|
|
279
|
+
# ---------------------------------------------------------------------------
|
|
280
|
+
|
|
281
|
+
# Validate that a worktree name is safe for filesystem-path interpolation.
|
|
282
|
+
# Rejects `..`, slashes, and any character outside [A-Za-z0-9._-]. Required
|
|
283
|
+
# because the CLI shim at the bottom of this file accepts arbitrary worktree
|
|
284
|
+
# names from operator/agent input; in-shell callers pass `$(basename "$PWD")`
|
|
285
|
+
# which is also safe but worth defense-in-depth.
|
|
286
|
+
_validate_worktree_name() {
|
|
287
|
+
local n="$1"
|
|
288
|
+
[[ "$n" =~ ^[A-Za-z0-9._-]+$ ]] || return 1
|
|
289
|
+
[[ "$n" == "." || "$n" == ".." ]] && return 1
|
|
290
|
+
return 0
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
_lease_file() {
|
|
294
|
+
_session_state_init_dirs
|
|
295
|
+
if ! _validate_worktree_name "$1"; then
|
|
296
|
+
headless_or_stderr warn "invalid worktree name for lease: $1"
|
|
297
|
+
return 1
|
|
298
|
+
fi
|
|
299
|
+
printf '%s/%s.lease\n' "$LEASE_DIR" "$1"
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
_lease_read_field() {
|
|
303
|
+
local lease_file="$1"
|
|
304
|
+
local key="$2"
|
|
305
|
+
[[ -f "$lease_file" ]] || return 1
|
|
306
|
+
# Simple key=value extractor. Values do not contain `=`.
|
|
307
|
+
grep "^${key}=" "$lease_file" 2>/dev/null | head -1 | cut -d= -f2-
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
# The parameter is named `worktree`, and since #8400 that name is narrower than the KEY
|
|
311
|
+
# SPACE. `cleanup_merged_worktrees` consults `is_lease_active` twice per candidate: once on
|
|
312
|
+
# `_safe_worktree_name "$branch"` and once on `basename "$worktree_path"`. Those coincide for
|
|
313
|
+
# the standard producer, but the branch-keyed probe is the one that runs for a MERGED BRANCH
|
|
314
|
+
# WITH NO WORKTREE — the cohort every worktree-path-gated guard used to skip. So a key here is
|
|
315
|
+
# an opaque reservation name that a caller chooses, not an assertion that a directory exists;
|
|
316
|
+
# `_validate_worktree_name` is what bounds it, and nothing in this file should start deriving
|
|
317
|
+
# it from the filesystem.
|
|
318
|
+
acquire_lease() {
|
|
319
|
+
local worktree="$1"
|
|
320
|
+
local skill="${2:-unknown}"
|
|
321
|
+
local expected_duration_min="${3:-240}"
|
|
322
|
+
|
|
323
|
+
_session_state_disabled && return 0
|
|
324
|
+
_session_state_init_dirs
|
|
325
|
+
|
|
326
|
+
local lease_file
|
|
327
|
+
lease_file=$(_lease_file "$worktree") || return 1
|
|
328
|
+
# Sanitize skill/duration on write so `_lease_read_field` (grep+cut) sees
|
|
329
|
+
# safe values. `=` and newline would corrupt the key=value parser; today
|
|
330
|
+
# only `=` is plausible (operator-supplied SOLEUR_SKILL_NAME), but the
|
|
331
|
+
# guard is cheap defense-in-depth and matches the documented invariant.
|
|
332
|
+
skill=$(printf '%s' "$skill" | tr -d '=\n')
|
|
333
|
+
if ! [[ "$expected_duration_min" =~ ^[0-9]+$ ]]; then
|
|
334
|
+
expected_duration_min=240
|
|
335
|
+
fi
|
|
336
|
+
|
|
337
|
+
local tmp
|
|
338
|
+
# Surfaced by #7409, PRE-EXISTING and deliberately left as-is. Moving this file
|
|
339
|
+
# made it a "new entrant" for lint-trap-tempfile-ownership rule (c), which is
|
|
340
|
+
# scoped to files changed vs the merge base — the pre-existing population is
|
|
341
|
+
# fenced by the high-water instead. So the finding is new; the code is not.
|
|
342
|
+
#
|
|
343
|
+
# The remedy rule (c) proposes — "add a single owning trap ... EXIT" — is
|
|
344
|
+
# ACTIVELY HARMFUL in this file specifically, for the reason recorded at
|
|
345
|
+
# `_register_lease_release_trap` below: EXIT is deliberately excluded there
|
|
346
|
+
# because `create_worktree` acquires the lease and registers its trap IN THE
|
|
347
|
+
# SAME shell that then exits normally on success, so an armed EXIT trap fired
|
|
348
|
+
# on that success and released the lease immediately. That is what made the
|
|
349
|
+
# lease layer unreachable in production. Arming one here to satisfy a linter
|
|
350
|
+
# would re-introduce it.
|
|
351
|
+
#
|
|
352
|
+
# The residual, stated honestly rather than waved away: `sweep_orphan_leases`
|
|
353
|
+
# globs `*.lease`, so a leaked `*.lease.XXXXXX` does NOT match it and nothing
|
|
354
|
+
# else reaps it. A SIGKILL (or power loss) in the window between this mktemp
|
|
355
|
+
# and the `mv` below — one `date` subshell and one heredoc write — therefore
|
|
356
|
+
# strands a single ~150-byte file in the repo's own scratch dir, permanently.
|
|
357
|
+
# Bounded and non-destructive, but not zero.
|
|
358
|
+
#
|
|
359
|
+
# An earlier draft of this comment said INT/TERM/HUP were "already covered by
|
|
360
|
+
# the multi-signal trap; only SIGKILL reaches this window." Both halves were
|
|
361
|
+
# false. `_register_lease_release_trap` is invoked by the CALLER, after this
|
|
362
|
+
# function has already returned, and only in trap mode — so during the window
|
|
363
|
+
# below no trap is armed at all. And even when one is, its body releases
|
|
364
|
+
# `<name>.lease`; it has never known anything about `<name>.lease.XXXXXX`. So
|
|
365
|
+
# ANY signal strands the file, and Ctrl-C during `create` is a routine
|
|
366
|
+
# operator action, which makes the accumulation rate higher than "power loss"
|
|
367
|
+
# implies. Still bounded, still non-destructive; just not as rare as claimed.
|
|
368
|
+
# lint-trap-ownership: ok an EXIT trap is documented-harmful here (see _register_lease_release_trap) — it would re-arm the release-on-success bug; residual is one ~150B stranded file per SIGKILL in a two-statement window
|
|
369
|
+
tmp=$(mktemp "${lease_file}.XXXXXX") || return 1
|
|
370
|
+
|
|
371
|
+
local started_at
|
|
372
|
+
started_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
373
|
+
|
|
374
|
+
cat > "$tmp" <<EOF
|
|
375
|
+
pid=$$
|
|
376
|
+
ppid=$PPID
|
|
377
|
+
skill=$skill
|
|
378
|
+
started_at=$started_at
|
|
379
|
+
expected_duration_min=$expected_duration_min
|
|
380
|
+
hostname=$HOSTNAME
|
|
381
|
+
EOF
|
|
382
|
+
# Atomic rename on same filesystem.
|
|
383
|
+
#
|
|
384
|
+
# CHECKED, and that is load-bearing. Every caller is the left side of an
|
|
385
|
+
# `acquire_lease ... || warn` list, which suspends errexit for the whole call,
|
|
386
|
+
# so an unchecked `mv` here meant ENOSPC / EROFS / EACCES returned SUCCESS
|
|
387
|
+
# with no lease on disk: the caller believed it was protected, the `|| warn`
|
|
388
|
+
# arm never fired, and cleanup-merged later found no file and reaped. That is
|
|
389
|
+
# a fail-open on an irreversible path (worktree + local branch + remote branch
|
|
390
|
+
# + the PR), reached without anything anywhere reporting a problem.
|
|
391
|
+
#
|
|
392
|
+
# Clean up the temp on failure too — sweep_orphan_leases globs `*.lease` and
|
|
393
|
+
# would never collect a stranded `*.lease.XXXXXX`.
|
|
394
|
+
if ! mv "$tmp" "$lease_file"; then
|
|
395
|
+
rm -f "$tmp"
|
|
396
|
+
return 1
|
|
397
|
+
fi
|
|
398
|
+
_LEASE_ACQUIRED_STARTED_AT[$worktree]="$started_at"
|
|
399
|
+
return 0
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
release_lease() {
|
|
403
|
+
local worktree="$1"
|
|
404
|
+
_session_state_disabled && return 0
|
|
405
|
+
local lease_file
|
|
406
|
+
lease_file=$(_lease_file "$worktree") || return 0
|
|
407
|
+
[[ -f "$lease_file" ]] || return 0
|
|
408
|
+
|
|
409
|
+
# Flohr rule: only release the lease if THIS shell is the recorded owner.
|
|
410
|
+
# Compares pid + hostname + started_at — the started_at field protects
|
|
411
|
+
# against PID-reuse after a crash (new shell inherits the dead shell's
|
|
412
|
+
# numeric pid; without started_at it would release someone else's lease).
|
|
413
|
+
local lease_pid lease_host lease_started
|
|
414
|
+
lease_pid=$(_lease_read_field "$lease_file" pid)
|
|
415
|
+
lease_host=$(_lease_read_field "$lease_file" hostname)
|
|
416
|
+
lease_started=$(_lease_read_field "$lease_file" started_at)
|
|
417
|
+
# Same host is a hard requirement: a shared LEASE_DIR can carry other machines'
|
|
418
|
+
# leases and this process cannot reason about their liveness at all.
|
|
419
|
+
[[ "$lease_host" == "$HOSTNAME" ]] || return 0
|
|
420
|
+
[[ -n "$lease_started" ]] || return 0
|
|
421
|
+
|
|
422
|
+
# TWO owners can release, and the second one is why this function used to do
|
|
423
|
+
# NOTHING. Measured before this change: the DOCUMENTED call —
|
|
424
|
+
# bash <plugin-root>/scripts/lib/session-state.sh release_lease "$(basename "$PWD")"
|
|
425
|
+
# which both one-shot and work tell you to run at the end — returned rc=0 and
|
|
426
|
+
# deleted no file, because a fresh process has a different `$$` and an empty
|
|
427
|
+
# `_LEASE_ACQUIRED_STARTED_AT`, so the in-process conjunction could never
|
|
428
|
+
# match. It reported success while leaking the lease. That is not a stricter
|
|
429
|
+
# guard; it is an unreachable one.
|
|
430
|
+
#
|
|
431
|
+
# in-process — pid == $$ AND the started_at we recorded at acquire time.
|
|
432
|
+
# This is the trap path, and started_at still guards PID reuse.
|
|
433
|
+
# post-exit — the recorded pid is DEAD. The session that held it is gone,
|
|
434
|
+
# so releasing is correct and is what the CLI call needs.
|
|
435
|
+
#
|
|
436
|
+
# A lease held by a DIFFERENT, still-LIVE process on this host matches neither
|
|
437
|
+
# arm and is refused — which is the property the original guard was reaching
|
|
438
|
+
# for and is the only one worth keeping.
|
|
439
|
+
local owner=""
|
|
440
|
+
if [[ "$lease_pid" == "$$" ]] \
|
|
441
|
+
&& [[ -n "${_LEASE_ACQUIRED_STARTED_AT[$worktree]:-}" ]] \
|
|
442
|
+
&& [[ "$lease_started" == "${_LEASE_ACQUIRED_STARTED_AT[$worktree]}" ]]; then
|
|
443
|
+
owner="in-process"
|
|
444
|
+
elif [[ -n "$lease_pid" ]] && ! kill -0 "$lease_pid" 2>/dev/null; then
|
|
445
|
+
owner="post-exit"
|
|
446
|
+
fi
|
|
447
|
+
|
|
448
|
+
if [[ -n "$owner" ]]; then
|
|
449
|
+
rm -f "$lease_file"
|
|
450
|
+
unset "_LEASE_ACQUIRED_STARTED_AT[$worktree]"
|
|
451
|
+
fi
|
|
452
|
+
return 0
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
# Returns 0 (active) iff hostname matches and age is inside the window computed
|
|
456
|
+
# by _lease_window_seconds (declared duration, clamped to [4h, 24h]). PID
|
|
457
|
+
# liveness is deliberately NOT required — see the note in is_lease_active.
|
|
458
|
+
# How long a lease is honoured, in seconds, from a raw expected_duration_min
|
|
459
|
+
# field. ONE definition, because is_lease_active and sweep_orphan_leases must
|
|
460
|
+
# agree exactly — and when this logic was duplicated they did not:
|
|
461
|
+
#
|
|
462
|
+
# * FLOOR 4h — a short declared duration should not make a lease reapable
|
|
463
|
+
# while the session is plainly still working.
|
|
464
|
+
# * CEILING 24h — the field is operator-supplied via SOLEUR_EXPECTED_DURATION_MIN
|
|
465
|
+
# and validated only as "digits". Unbounded, a typo (240000 for
|
|
466
|
+
# 240) silently disables cleanup-merged for MONTHS: the lease
|
|
467
|
+
# reads ACTIVE forever and, in the cleanup path, nothing else
|
|
468
|
+
# reaps it. Pinning the ceiling to the same 24h as the mtime
|
|
469
|
+
# sweep is what makes "the 24h sweep is the backstop" TRUE
|
|
470
|
+
# rather than merely claimed. Measured before the clamp: a
|
|
471
|
+
# 20-day-old lease with expected_duration_min=525600 read ACTIVE.
|
|
472
|
+
# * NON-NUMERIC -> 240 (the documented default), never bare arithmetic. Under
|
|
473
|
+
# `set -u`, `$(( abc * 60 ))` treats `abc` as an unset variable
|
|
474
|
+
# name and ERRORS; is_lease_active is called from inside an `if`,
|
|
475
|
+
# so `set -e` is suspended and that error was read as "no active
|
|
476
|
+
# lease" — i.e. a fresh lease with one corrupt field became
|
|
477
|
+
# REAPABLE. Fail closed on corrupt input, never open.
|
|
478
|
+
_lease_window_seconds() { # $1 raw expected_duration_min
|
|
479
|
+
local mins="${1:-}"
|
|
480
|
+
[[ "$mins" =~ ^[0-9]+$ ]] || mins=240
|
|
481
|
+
# `10#` forces base 10. WITHOUT it this fails OPEN, which is the whole class
|
|
482
|
+
# this function exists to close: `^[0-9]+$` ACCEPTS a leading zero, bash reads
|
|
483
|
+
# a leading-zero token as OCTAL, and 8/9 are not octal digits — so `08`/`09`/
|
|
484
|
+
# `090` pass validation and then ERROR in the arithmetic. `cap` is never
|
|
485
|
+
# assigned, the function prints nothing, and both callers read an empty window:
|
|
486
|
+
# `(( age < "" ))` is false (INACTIVE -> reapable) and `(( age >= "" ))` is
|
|
487
|
+
# true (the sweep deletes it). Measured on a 0-second-old lease: INACTIVE and
|
|
488
|
+
# SWEPT in the same pass — #5454's exact signature through a different door.
|
|
489
|
+
# `0700` does not error and is worse for being quiet: octal 448 min, not 700.
|
|
490
|
+
# T12 missed this because `abc` is REJECTED by the regex; the escaping input
|
|
491
|
+
# is one the regex ACCEPTS. Pinned by T12's `090` case.
|
|
492
|
+
local cap=$(( 10#$mins * 60 ))
|
|
493
|
+
(( cap < 14400 )) && cap=14400
|
|
494
|
+
(( cap > 86400 )) && cap=86400
|
|
495
|
+
printf '%s\n' "$cap"
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
is_lease_active() {
|
|
499
|
+
local worktree="$1"
|
|
500
|
+
_session_state_disabled && return 1
|
|
501
|
+
local lease_file
|
|
502
|
+
lease_file=$(_lease_file "$worktree")
|
|
503
|
+
[[ -f "$lease_file" ]] || return 1
|
|
504
|
+
|
|
505
|
+
local lease_pid lease_host lease_started lease_expected
|
|
506
|
+
lease_pid=$(_lease_read_field "$lease_file" pid)
|
|
507
|
+
lease_host=$(_lease_read_field "$lease_file" hostname)
|
|
508
|
+
lease_started=$(_lease_read_field "$lease_file" started_at)
|
|
509
|
+
lease_expected=$(_lease_read_field "$lease_file" expected_duration_min)
|
|
510
|
+
|
|
511
|
+
[[ -n "$lease_pid" ]] || return 1
|
|
512
|
+
[[ "$lease_host" == "$HOSTNAME" ]] || return 1
|
|
513
|
+
|
|
514
|
+
# NO `kill -0 "$lease_pid" || return 1` HERE — that line reaped two live
|
|
515
|
+
# worktrees on 2026-08-06 (#5454), and it could never have worked.
|
|
516
|
+
#
|
|
517
|
+
# `acquire_lease` records `pid=$$`, and every DOCUMENTED entry point is a
|
|
518
|
+
# short-lived process:
|
|
519
|
+
#
|
|
520
|
+
# bash <plugin-root>/scripts/lib/session-state.sh acquire_lease <worktree>
|
|
521
|
+
# bash .../worktree-manager.sh --yes create <branch>
|
|
522
|
+
#
|
|
523
|
+
# so `$$` is a bash that exits within milliseconds of writing the file.
|
|
524
|
+
# A pid-liveness gate therefore reported INACTIVE for every CLI-acquired
|
|
525
|
+
# lease the instant it was taken — the file existed, carried its full
|
|
526
|
+
# remaining duration, and protected nothing. cleanup-merged then removed
|
|
527
|
+
# the worktree, deleted the branch locally AND on origin, and closed the PR.
|
|
528
|
+
#
|
|
529
|
+
# A lease is a TIME-BOXED RESERVATION; the age cap below is the authority.
|
|
530
|
+
# PID liveness is only an optimisation for releasing early, and a session
|
|
531
|
+
# that finishes cleanly calls `release_lease` (which still verifies pid +
|
|
532
|
+
# hostname + started_at before releasing). The cost of this change is that
|
|
533
|
+
# a CRASHED session holds its worktree until the window closes, bounded by
|
|
534
|
+
# max(expected_duration, 4h) here and by the 24h mtime sweep below. That is
|
|
535
|
+
# the correct direction to fail: a worktree kept slightly too long is
|
|
536
|
+
# recoverable, a worktree reaped mid-run is not.
|
|
537
|
+
#
|
|
538
|
+
# The bound is the window computed by _lease_window_seconds, which is CLAMPED
|
|
539
|
+
# to 24h. Do not restate it as "and the 24h mtime sweep backstops this" without
|
|
540
|
+
# checking: an earlier draft of this comment said exactly that and it was FALSE
|
|
541
|
+
# in the path that matters — sweep_orphan_leases was reachable only from the
|
|
542
|
+
# `feature|feat` subcommand (create_for_feature), never from `create` and
|
|
543
|
+
# never from cleanup_merged_worktrees. (This sentence said "the `create`
|
|
544
|
+
# subcommand" until PR #7373; it was wrong in the same direction as the
|
|
545
|
+
# _register_lease_release_trap comment below — both assumed `create` did
|
|
546
|
+
# lease-work it did not do. As of PR #7373 `create` does sweep and acquire, so
|
|
547
|
+
# the corrected statement describes the pre-#5454 world.) That is now also
|
|
548
|
+
# wired (worktree-manager.sh calls the sweep at the top of cleanup), but the
|
|
549
|
+
# clamp is what makes the bound hold regardless.
|
|
550
|
+
#
|
|
551
|
+
# Pinned by T9/T9b (a dead acquirer stays active + unswept inside the
|
|
552
|
+
# window) and T10 (an expired lease is still inactive, so this is not an
|
|
553
|
+
# immortality bug) in session-state.test.sh.
|
|
554
|
+
|
|
555
|
+
# Age cap: see _lease_window_seconds. Computed there, not inline, because
|
|
556
|
+
# sweep_orphan_leases needs the IDENTICAL number and two copies already
|
|
557
|
+
# drifted once (one validated the field, one did not — the unvalidated copy
|
|
558
|
+
# fail-OPENED under `set -u`, which on this path means "reap it").
|
|
559
|
+
local cap
|
|
560
|
+
cap=$(_lease_window_seconds "$lease_expected")
|
|
561
|
+
|
|
562
|
+
# Anchor started_at to the strict ISO-8601-Z format we always write
|
|
563
|
+
# ourselves (date -u +%Y-%m-%dT%H:%M:%SZ). A malformed or natural-language
|
|
564
|
+
# value (e.g., "next year") fed to `date -d` would otherwise resolve to a
|
|
565
|
+
# future epoch — combined with the clock-skew "fresh" branch below that
|
|
566
|
+
# turns into a forever-active lease and a permanent reap block.
|
|
567
|
+
if [[ ! "$lease_started" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$ ]]; then
|
|
568
|
+
# Malformed or missing — treat as inactive; let sweep_orphan_leases
|
|
569
|
+
# reap by mtime instead of forever-blocking cleanup-merged.
|
|
570
|
+
return 1
|
|
571
|
+
fi
|
|
572
|
+
local started_epoch now_epoch age
|
|
573
|
+
started_epoch=$(date -d "$lease_started" +%s 2>/dev/null || echo "")
|
|
574
|
+
now_epoch=$(date +%s)
|
|
575
|
+
if [[ -z "$started_epoch" ]]; then
|
|
576
|
+
return 1
|
|
577
|
+
fi
|
|
578
|
+
age=$(( now_epoch - started_epoch ))
|
|
579
|
+
# Negative age = clock skew or future-stamped lease. Treat as inactive
|
|
580
|
+
# (not forever-fresh) to avoid the DoS vector.
|
|
581
|
+
if (( age < 0 )); then
|
|
582
|
+
return 1
|
|
583
|
+
fi
|
|
584
|
+
(( age < cap )) && return 0
|
|
585
|
+
return 1
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
# Sweep orphan leases. Deletes on: mtime > 24h, OR (pid dead AND same host AND
|
|
589
|
+
# past its own window). The pid-dead arm is deliberately NOT sufficient on its
|
|
590
|
+
# own — see is_lease_active for why a dead pid is not orphan evidence here.
|
|
591
|
+
sweep_orphan_leases() {
|
|
592
|
+
_session_state_disabled && return 0
|
|
593
|
+
_session_state_init_dirs
|
|
594
|
+
|
|
595
|
+
local now_epoch
|
|
596
|
+
now_epoch=$(date +%s)
|
|
597
|
+
local f mtime age lease_pid
|
|
598
|
+
shopt -s nullglob
|
|
599
|
+
for f in "$LEASE_DIR"/*.lease; do
|
|
600
|
+
mtime=$(stat -c %Y "$f" 2>/dev/null || echo 0)
|
|
601
|
+
age=$(( now_epoch - mtime ))
|
|
602
|
+
if (( age > 86400 )); then
|
|
603
|
+
rm -f "$f"
|
|
604
|
+
continue
|
|
605
|
+
fi
|
|
606
|
+
lease_pid=$(_lease_read_field "$f" pid) || true
|
|
607
|
+
if [[ -n "$lease_pid" ]] && ! kill -0 "$lease_pid" 2>/dev/null; then
|
|
608
|
+
# Dead PID — but only sweep if same hostname (don't reap remote-host
|
|
609
|
+
# leases that share the LEASE_DIR via shared filesystem).
|
|
610
|
+
local lease_host
|
|
611
|
+
lease_host=$(_lease_read_field "$f" hostname) || true
|
|
612
|
+
if [[ "$lease_host" == "$HOSTNAME" ]]; then
|
|
613
|
+
# ...AND only once the lease is past its own window. A dead pid alone
|
|
614
|
+
# is NOT orphan evidence: `acquire_lease` records `pid=$$`, which for
|
|
615
|
+
# every documented CLI entry point belongs to a process that exits
|
|
616
|
+
# immediately (see the long note in is_lease_active). Sweeping on a
|
|
617
|
+
# dead pid alone deleted the lease file BEFORE worktree-manager
|
|
618
|
+
# consulted is_lease_active — so the downstream check found no file
|
|
619
|
+
# and the reap proceeded. That is #5454, twice, on 2026-08-06.
|
|
620
|
+
#
|
|
621
|
+
# is_lease_active is the single source of truth for "still held";
|
|
622
|
+
# the 24h mtime cap above remains the backstop for a truly abandoned
|
|
623
|
+
# file. Pinned by T9b.
|
|
624
|
+
# `|| true` on every field read: `_lease_read_field` returns 1 for a
|
|
625
|
+
# missing field AND for a file that has vanished since the glob, and a
|
|
626
|
+
# sibling releasing its lease mid-sweep is an ordinary race, not an
|
|
627
|
+
# error. Bare assignments here abort the caller under `set -e` — and the
|
|
628
|
+
# caller is cleanup-merged, so the blast radius is every maintenance
|
|
629
|
+
# step after it. An empty value is handled below; a dead caller is not.
|
|
630
|
+
local lease_started lease_expected lease_cap started_epoch lease_age
|
|
631
|
+
lease_started=$(_lease_read_field "$f" started_at) || true
|
|
632
|
+
lease_expected=$(_lease_read_field "$f" expected_duration_min) || true
|
|
633
|
+
# Same window as is_lease_active, from the same function — these two
|
|
634
|
+
# MUST agree, and when the arithmetic was duplicated here they did not.
|
|
635
|
+
lease_cap=$(_lease_window_seconds "$lease_expected")
|
|
636
|
+
started_epoch=""
|
|
637
|
+
if [[ "$lease_started" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$ ]]; then
|
|
638
|
+
started_epoch=$(date -d "$lease_started" +%s 2>/dev/null || echo "")
|
|
639
|
+
fi
|
|
640
|
+
if [[ -z "$started_epoch" ]]; then
|
|
641
|
+
# Unparseable started_at: fall back to mtime, which the 24h cap
|
|
642
|
+
# above already governs. Leave it for that cap rather than guessing.
|
|
643
|
+
continue
|
|
644
|
+
fi
|
|
645
|
+
lease_age=$(( now_epoch - started_epoch ))
|
|
646
|
+
if (( lease_age >= lease_cap )); then
|
|
647
|
+
rm -f "$f"
|
|
648
|
+
fi
|
|
649
|
+
fi
|
|
650
|
+
fi
|
|
651
|
+
done
|
|
652
|
+
shopt -u nullglob
|
|
653
|
+
return 0
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
# Multi-signal trap helper. Body is unset-variable safe via local set +u.
|
|
657
|
+
_register_lease_release_trap() {
|
|
658
|
+
local worktree="$1"
|
|
659
|
+
# NOT `EXIT` — that is what made the lease layer unreachable in production.
|
|
660
|
+
#
|
|
661
|
+
# `create_worktree` acquires the lease and registers this trap IN THE SAME
|
|
662
|
+
# PROCESS (as of PR #7373 — before that it did NEITHER; only `create_for_feature`
|
|
663
|
+
# acquired, and `create` is what the pipeline invokes), and that process exits
|
|
664
|
+
# normally on success. With EXIT armed, the trap fired on that success and
|
|
665
|
+
# deleted the lease before the caller had even printed its path.
|
|
666
|
+
#
|
|
667
|
+
# Measured: the leases directory is EMPTY the instant
|
|
668
|
+
# `worktree-manager.sh --yes create` returns. That measurement was real, but
|
|
669
|
+
# it had TWO independent causes and this comment originally named only one:
|
|
670
|
+
#
|
|
671
|
+
# 1. EXIT released whatever was acquired (fixed by the signal list below).
|
|
672
|
+
# 2. `create` never acquired anything in the first place (fixed separately
|
|
673
|
+
# by adding this block to `create_worktree`).
|
|
674
|
+
#
|
|
675
|
+
# Fixing the trap alone left the pipeline path dark — the empty directory
|
|
676
|
+
# looked identical under both causes, which is exactly how cause 2 survived
|
|
677
|
+
# the fix for cause 1. So a worktree made the DOCUMENTED way (what one-shot
|
|
678
|
+
# and work both invoke) carried no lease at all, and `is_lease_active`
|
|
679
|
+
# returned at its `[[ -f "$lease_file" ]]` guard — every protection below that
|
|
680
|
+
# line was dead code for the path that matters.
|
|
681
|
+
#
|
|
682
|
+
# Normal process exit is not session end. That is this change's whole thesis,
|
|
683
|
+
# applied to the write side: the CLI process is short-lived BY DESIGN, and the
|
|
684
|
+
# session it acquired for outlives it by hours.
|
|
685
|
+
#
|
|
686
|
+
# The abnormal signals stay: those do mean the holder died, and releasing then
|
|
687
|
+
# is correct. Clean release is the caller's explicit `release_lease` (now
|
|
688
|
+
# reachable post-exit — see the owner arms there), plus the window as backstop.
|
|
689
|
+
# shellcheck disable=SC2064
|
|
690
|
+
trap "_lease_release_safe '$worktree'" INT TERM HUP
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
_lease_release_safe() {
|
|
694
|
+
# Unset-var safe — traps fire in odd scopes.
|
|
695
|
+
set +u
|
|
696
|
+
local worktree="${1:-}"
|
|
697
|
+
[[ -n "$worktree" ]] || return 0
|
|
698
|
+
release_lease "$worktree" 2>/dev/null || true
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
# ---------------------------------------------------------------------------
|
|
702
|
+
# Headless visibility helper (sub-PR 2 will reuse this).
|
|
703
|
+
# ---------------------------------------------------------------------------
|
|
704
|
+
# If fd 2 is not a TTY AND CLAUDECODE is set (we are running under `claude --bg`
|
|
705
|
+
# or another headless harness), append a single timestamped line to
|
|
706
|
+
# $LOG_DIR/$PPID.log. Otherwise echo to stderr. POSIX-atomic append for lines
|
|
707
|
+
# < 4KB.
|
|
708
|
+
headless_or_stderr() {
|
|
709
|
+
local level="${1:-warn}"
|
|
710
|
+
local msg="${2:-}"
|
|
711
|
+
_session_state_disabled && { echo "[$level] $msg" >&2; return 0; }
|
|
712
|
+
|
|
713
|
+
local hook="${SOLEUR_HOOK_NAME:-$(basename "${BASH_SOURCE[1]:-${BASH_SOURCE[0]}}" .sh)}"
|
|
714
|
+
|
|
715
|
+
if [[ ! -t 2 ]] && [[ -n "${CLAUDECODE:-}" ]]; then
|
|
716
|
+
_session_state_init_dirs
|
|
717
|
+
local ts
|
|
718
|
+
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
719
|
+
printf '[%s] [%s] [%s] %s\n' "$ts" "$level" "$hook" "$msg" >> "$LOG_DIR/${PPID}.log"
|
|
720
|
+
else
|
|
721
|
+
echo "[$level] $msg" >&2
|
|
722
|
+
fi
|
|
723
|
+
return 0
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
# Initialize state dirs once at source-time so consumers can rely on LOG_DIR
|
|
727
|
+
# and friends being readable.
|
|
728
|
+
_session_state_init_dirs
|
|
729
|
+
|
|
730
|
+
# Allow `bash session-state.sh <fn> <args>` as a CLI shim (used by SKILL.md
|
|
731
|
+
# Phase Exit hooks and ad-hoc operator invocations).
|
|
732
|
+
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
733
|
+
fn="${1:-}"
|
|
734
|
+
shift || true
|
|
735
|
+
case "$fn" in
|
|
736
|
+
acquire_lock|release_lock|acquire_lock_shared|acquire_lease|release_lease|is_lease_active|sweep_orphan_leases|headless_or_stderr|with_lock)
|
|
737
|
+
"$fn" "$@"
|
|
738
|
+
;;
|
|
739
|
+
"")
|
|
740
|
+
echo "usage: $0 <function> [args...]" >&2
|
|
741
|
+
exit 2
|
|
742
|
+
;;
|
|
743
|
+
*)
|
|
744
|
+
echo "session-state: unknown function: $fn" >&2
|
|
745
|
+
exit 2
|
|
746
|
+
;;
|
|
747
|
+
esac
|
|
748
|
+
fi
|