@soleur/opencode 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/agents.manifest.json +476 -0
- package/.claude-plugin/plugin.json +42 -0
- package/README.md +44 -2
- package/agents/community/.gitkeep +0 -0
- package/agents/engineering/cto.md +87 -0
- package/agents/engineering/design/ddd-architect.md +95 -0
- package/agents/engineering/discovery/agent-finder.md +214 -0
- package/agents/engineering/discovery/functional-discovery.md +182 -0
- package/agents/engineering/infra/infra-security.md +151 -0
- package/agents/engineering/infra/platform-strategist.md +109 -0
- package/agents/engineering/infra/terraform-architect.md +171 -0
- package/agents/engineering/prompt-engineer.md +37 -0
- package/agents/engineering/research/best-practices-researcher.md +115 -0
- package/agents/engineering/research/framework-docs-researcher.md +93 -0
- package/agents/engineering/research/git-history-analyzer.md +53 -0
- package/agents/engineering/research/learnings-researcher.md +285 -0
- package/agents/engineering/research/repo-research-analyst.md +118 -0
- package/agents/engineering/review/agent-native-reviewer.md +279 -0
- package/agents/engineering/review/architecture-strategist.md +61 -0
- package/agents/engineering/review/code-quality-analyst.md +92 -0
- package/agents/engineering/review/code-simplicity-reviewer.md +109 -0
- package/agents/engineering/review/data-integrity-guardian.md +82 -0
- package/agents/engineering/review/data-migration-expert.md +98 -0
- package/agents/engineering/review/deployment-verification-agent.md +183 -0
- package/agents/engineering/review/dhh-rails-reviewer.md +46 -0
- package/agents/engineering/review/kieran-rails-reviewer.md +87 -0
- package/agents/engineering/review/legacy-code-expert.md +55 -0
- package/agents/engineering/review/observability-coverage-reviewer.md +147 -0
- package/agents/engineering/review/pattern-recognition-specialist.md +59 -0
- package/agents/engineering/review/performance-oracle.md +130 -0
- package/agents/engineering/review/security-sentinel.md +167 -0
- package/agents/engineering/review/semgrep-sast.md +36 -0
- package/agents/engineering/review/test-design-reviewer.md +150 -0
- package/agents/engineering/review/user-impact-reviewer.md +70 -0
- package/agents/engineering/workflow/pr-comment-resolver.md +69 -0
- package/agents/finance/budget-analyst.md +29 -0
- package/agents/finance/cfo.md +44 -0
- package/agents/finance/financial-reporter.md +30 -0
- package/agents/finance/revenue-analyst.md +30 -0
- package/agents/legal/clo.md +115 -0
- package/agents/legal/legal-compliance-auditor.md +110 -0
- package/agents/legal/legal-document-generator.md +134 -0
- package/agents/marketing/analytics-analyst.md +25 -0
- package/agents/marketing/brand-architect.md +181 -0
- package/agents/marketing/cmo.md +76 -0
- package/agents/marketing/conversion-optimizer.md +19 -0
- package/agents/marketing/copywriter.md +20 -0
- package/agents/marketing/fact-checker.md +70 -0
- package/agents/marketing/growth-strategist.md +145 -0
- package/agents/marketing/paid-media-strategist.md +28 -0
- package/agents/marketing/pricing-strategist.md +17 -0
- package/agents/marketing/programmatic-seo-specialist.md +25 -0
- package/agents/marketing/retention-strategist.md +23 -0
- package/agents/marketing/seo-aeo-analyst.md +164 -0
- package/agents/operations/coo.md +50 -0
- package/agents/operations/ops-advisor.md +78 -0
- package/agents/operations/ops-provisioner.md +101 -0
- package/agents/operations/ops-research.md +52 -0
- package/agents/operations/service-automator.md +278 -0
- package/agents/product/business-validator.md +206 -0
- package/agents/product/competitive-intelligence.md +89 -0
- package/agents/product/cpo.md +63 -0
- package/agents/product/design/ux-design-lead.md +209 -0
- package/agents/product/spec-flow-analyzer.md +122 -0
- package/agents/sales/cro.md +44 -0
- package/agents/sales/deal-architect.md +29 -0
- package/agents/sales/outbound-strategist.md +29 -0
- package/agents/sales/pipeline-analyst.md +29 -0
- package/agents/support/cco.md +48 -0
- package/agents/support/community-manager.md +570 -0
- package/agents/support/ticket-triage.md +70 -0
- package/bin/soleur-opencode.js +29 -0
- package/commands/go.md +659 -0
- package/commands/help.md +273 -0
- package/commands/sync.md +977 -0
- package/commands/version.md +103 -0
- package/hooks/antigravity-hooks.json +84 -0
- package/hooks/antigravity-session-start.sh +15 -0
- package/hooks/bare-root-cwd-guard.sh +131 -0
- package/hooks/browser-snapshot-credential-guard.sh +200 -0
- package/hooks/codex-session-start.sh +9 -0
- package/hooks/compaction-state.sh +525 -0
- package/hooks/destructive-command-guard.sh +1387 -0
- package/hooks/devin-session-start.sh +90 -0
- package/hooks/ext-filter-detect.sh +171 -0
- package/hooks/hooks.json +133 -0
- package/hooks/lib/hook-tool-kind.sh +24 -0
- package/hooks/lib/log-rotation.sh +176 -0
- package/hooks/lib/shell-argv.pl +576 -0
- package/hooks/memory-backstop.sh +1114 -0
- package/hooks/operator-stage-approval.sh +391 -0
- package/hooks/stop-hook.sh +367 -0
- package/hooks/unkept-promise-hook.sh +383 -0
- package/hooks/version-banner.sh +127 -0
- package/hooks/welcome-hook.sh +50 -0
- package/install.js +108 -0
- package/lib/agent-registry.ts +154 -0
- package/lib/c4-canonical-cli.mjs +34 -0
- package/lib/c4-canonical.mjs +46 -0
- package/lib/c4-from-components.ts +554 -0
- package/lib/go-routing.ts +81 -0
- package/lib/grok-inspect-contract.ts +311 -0
- package/lib/harness-model-map.ts +101 -0
- package/lib/harness-parity.ts +726 -0
- package/lib/harness-tool-map.ts +180 -0
- package/lib/harness.ts +716 -0
- package/lib/heartbeat-live-reconcile.ts +1153 -0
- package/lib/heartbeat-manifest.ts +342 -0
- package/lib/kb-coverage.ts +252 -0
- package/lib/pr-merge-poll.ts +103 -0
- package/lib/workflow-fidelity.ts +388 -0
- package/opencode/INSTRUCTIONS.md +152 -0
- package/opencode/agents/engineering/cto.md +10 -0
- package/opencode/agents/engineering/design/ddd-architect.md +10 -0
- package/opencode/agents/engineering/discovery/agent-finder.md +10 -0
- package/opencode/agents/engineering/discovery/functional-discovery.md +10 -0
- package/opencode/agents/engineering/infra/infra-security.md +10 -0
- package/opencode/agents/engineering/infra/platform-strategist.md +10 -0
- package/opencode/agents/engineering/infra/terraform-architect.md +10 -0
- package/opencode/agents/engineering/prompt-engineer.md +10 -0
- package/opencode/agents/engineering/research/best-practices-researcher.md +10 -0
- package/opencode/agents/engineering/research/framework-docs-researcher.md +10 -0
- package/opencode/agents/engineering/research/git-history-analyzer.md +10 -0
- package/opencode/agents/engineering/research/learnings-researcher.md +10 -0
- package/opencode/agents/engineering/research/repo-research-analyst.md +10 -0
- package/opencode/agents/engineering/review/agent-native-reviewer.md +10 -0
- package/opencode/agents/engineering/review/architecture-strategist.md +10 -0
- package/opencode/agents/engineering/review/code-quality-analyst.md +10 -0
- package/opencode/agents/engineering/review/code-simplicity-reviewer.md +10 -0
- package/opencode/agents/engineering/review/data-integrity-guardian.md +10 -0
- package/opencode/agents/engineering/review/data-migration-expert.md +10 -0
- package/opencode/agents/engineering/review/deployment-verification-agent.md +10 -0
- package/opencode/agents/engineering/review/dhh-rails-reviewer.md +10 -0
- package/opencode/agents/engineering/review/kieran-rails-reviewer.md +10 -0
- package/opencode/agents/engineering/review/legacy-code-expert.md +10 -0
- package/opencode/agents/engineering/review/observability-coverage-reviewer.md +10 -0
- package/opencode/agents/engineering/review/pattern-recognition-specialist.md +10 -0
- package/opencode/agents/engineering/review/performance-oracle.md +10 -0
- package/opencode/agents/engineering/review/security-sentinel.md +10 -0
- package/opencode/agents/engineering/review/semgrep-sast.md +10 -0
- package/opencode/agents/engineering/review/test-design-reviewer.md +10 -0
- package/opencode/agents/engineering/review/user-impact-reviewer.md +10 -0
- package/opencode/agents/engineering/workflow/pr-comment-resolver.md +10 -0
- package/opencode/agents/finance/budget-analyst.md +10 -0
- package/opencode/agents/finance/cfo.md +10 -0
- package/opencode/agents/finance/financial-reporter.md +10 -0
- package/opencode/agents/finance/revenue-analyst.md +10 -0
- package/opencode/agents/legal/clo.md +10 -0
- package/opencode/agents/legal/legal-compliance-auditor.md +10 -0
- package/opencode/agents/legal/legal-document-generator.md +10 -0
- package/opencode/agents/marketing/analytics-analyst.md +10 -0
- package/opencode/agents/marketing/brand-architect.md +10 -0
- package/opencode/agents/marketing/cmo.md +10 -0
- package/opencode/agents/marketing/conversion-optimizer.md +10 -0
- package/opencode/agents/marketing/copywriter.md +10 -0
- package/opencode/agents/marketing/fact-checker.md +10 -0
- package/opencode/agents/marketing/growth-strategist.md +10 -0
- package/opencode/agents/marketing/paid-media-strategist.md +10 -0
- package/opencode/agents/marketing/pricing-strategist.md +10 -0
- package/opencode/agents/marketing/programmatic-seo-specialist.md +10 -0
- package/opencode/agents/marketing/retention-strategist.md +10 -0
- package/opencode/agents/marketing/seo-aeo-analyst.md +10 -0
- package/opencode/agents/operations/coo.md +10 -0
- package/opencode/agents/operations/ops-advisor.md +10 -0
- package/opencode/agents/operations/ops-provisioner.md +10 -0
- package/opencode/agents/operations/ops-research.md +10 -0
- package/opencode/agents/operations/service-automator.md +10 -0
- package/opencode/agents/product/business-validator.md +10 -0
- package/opencode/agents/product/competitive-intelligence.md +10 -0
- package/opencode/agents/product/cpo.md +10 -0
- package/opencode/agents/product/design/ux-design-lead.md +10 -0
- package/opencode/agents/product/spec-flow-analyzer.md +10 -0
- package/opencode/agents/sales/cro.md +10 -0
- package/opencode/agents/sales/deal-architect.md +10 -0
- package/opencode/agents/sales/outbound-strategist.md +10 -0
- package/opencode/agents/sales/pipeline-analyst.md +10 -0
- package/opencode/agents/support/cco.md +10 -0
- package/opencode/agents/support/community-manager.md +10 -0
- package/opencode/agents/support/ticket-triage.md +10 -0
- package/opencode/commands/go.md +10 -0
- package/opencode/commands/help.md +10 -0
- package/opencode/commands/sync.md +10 -0
- package/opencode/commands/version.md +11 -0
- package/opencode/opencode.json +8 -0
- package/opencode/plugin.ts +493 -0
- package/package.json +36 -4
- package/scripts/admin-merge-ready.sh +545 -0
- package/scripts/admin-merge-ready.test.sh +753 -0
- package/scripts/affected-tests.sh +908 -0
- package/scripts/affected-tests.test.sh +1797 -0
- package/scripts/alpha-metrics.sh +78 -0
- package/scripts/alpha-metrics.test.sh +115 -0
- package/scripts/ancestor-signal-helpers.baseline.txt +16 -0
- package/scripts/audit-flag-flip.sh +107 -0
- package/scripts/check-red-on-main.sh +316 -0
- package/scripts/ci-head-verdict.sh +363 -0
- package/scripts/cloud-detect.sh +193 -0
- package/scripts/delegation-verify.sh +657 -0
- package/scripts/delegation-verify.test.sh +1424 -0
- package/scripts/deploy-arm.sh +475 -0
- package/scripts/domain-model-drift.sh +419 -0
- package/scripts/emit-decision.sh +144 -0
- package/scripts/emit-decision.test.sh +144 -0
- package/scripts/ext-filter-divergence-corpus.txt +15 -0
- package/scripts/ext-filter-divergence.sh +189 -0
- package/scripts/generate-c4-from-components.ts +442 -0
- package/scripts/grok-fidelity-gate.sh +68 -0
- package/scripts/grok-pre-push-gate.sh +232 -0
- package/scripts/harness-discovery-smoke.ts +906 -0
- package/scripts/harness-parity-census.ts +92 -0
- package/scripts/inngest-luks-alert-armed-probe.ts +29 -0
- package/scripts/install-opencode-adapter.ts +520 -0
- package/scripts/lib/affected-tests/common.sh +365 -0
- package/scripts/lib/affected-tests/go.sh +157 -0
- package/scripts/lib/affected-tests/js.sh +188 -0
- package/scripts/lib/affected-tests/nx-turbo.sh +98 -0
- package/scripts/lib/affected-tests/python.sh +185 -0
- package/scripts/lib/context-sanitize.sh +49 -0
- package/scripts/lib/domain-model-lib.sh +180 -0
- package/scripts/lib/ext-filter-grammar.tsv +34 -0
- package/scripts/lib/operator-script.sh +1236 -0
- package/scripts/lib/proc.sh +580 -0
- package/scripts/lib/session-state.sh +748 -0
- package/scripts/lib/tmp-classify.sh +843 -0
- package/scripts/lib/wrapper-boundary.sh +47 -0
- package/scripts/lint-ratchet.py +1547 -0
- package/scripts/monitor-pr-checks.sh +441 -0
- package/scripts/pipeline-tally.sh +756 -0
- package/scripts/pipeline-tally.test.sh +740 -0
- package/scripts/plugin-version.sh +528 -0
- package/scripts/precommit-guard.sh +113 -0
- package/scripts/precommit-guard.test.sh +254 -0
- package/scripts/reconcile-live-heartbeats.ts +705 -0
- package/scripts/render-c4-model.sh +217 -0
- package/scripts/resolve-git-root.sh +55 -0
- package/scripts/resolve-regenerable-conflicts.sh +363 -0
- package/scripts/resolve-regenerable-conflicts.test.sh +780 -0
- package/scripts/ruff-requirements.txt +22 -0
- package/scripts/run-in-pid-namespace.sh +98 -0
- package/scripts/run-in-pid-namespace.test.sh +290 -0
- package/scripts/scan-ancestor-signal-helpers.py +556 -0
- package/scripts/scan-ancestor-signal-helpers.test.sh +670 -0
- package/scripts/setup-ralph-loop.sh +238 -0
- package/scripts/sync-cursor-name-map.ts +316 -0
- package/scripts/sync-grok-agent-compat.ts +188 -0
- package/scripts/sync-npm-package.ts +178 -0
- package/scripts/sync-opencode-adapter.ts +479 -0
- package/scripts/sync-pr-behind.sh +534 -0
- package/scripts/sync-pr-behind.test.sh +252 -0
- package/scripts/taste-profile-update.sh +180 -0
- package/scripts/taste-profile-update.test.sh +167 -0
- package/scripts/write-budget-marker.sh +23 -0
- package/scripts/write-kb-coverage.ts +113 -0
- package/skills/admin-ip-refresh/SKILL.md +81 -0
- package/skills/admin-ip-refresh/references/admin-ip-refresh-procedure.md +201 -0
- package/skills/agent-browser/SKILL.md +698 -0
- package/skills/agent-browser/scripts/playwright-mcp-profile-slot.sh +304 -0
- package/skills/agent-browser/scripts/playwright-mcp-redact-proxy.py +964 -0
- package/skills/agent-browser/scripts/redact-a11y-snapshot.py +443 -0
- package/skills/agent-browser/test/fixtures/capture-playwright-mcp-fixtures.py +217 -0
- package/skills/agent-browser/test/fixtures/fake-passthrough-proxy.py +24 -0
- package/skills/agent-browser/test/fixtures/fake-playwright-mcp.py +131 -0
- package/skills/agent-browser/test/fixtures/make-proxy-mutant.py +48 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/dialog-link-injection.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/empty-content.json +5 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-and-result.json +4 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-code-bool.json +6 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-code-string.json +6 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-message-tree.json +6 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-no-data.json +6 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-string.json +4 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-with-data.json +9 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/evaluate-escaped-object.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/invalid-utf8.json +4 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/link-result.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/list-two-results.json +6 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/meta-in-result.json +13 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/no-content.json +5 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/quoted-key.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/resource-block.json +18 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/result-and-method.json +11 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/result-string.json +3 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/run-code-escaped-one-row.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/run-code-escaped.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/structured-content.json +13 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/tools-list-no-tools.json +5 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/tree-no-credential.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/two-text-blocks.json +14 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/unparsable-line.json +3 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/value-contains-brace.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/value-contains-link-substring.json +10 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/close.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/evaluate.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/find.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/framing.json +6 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/initialize.json +14 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/navigate-link.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/navigate.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/roots-list-request.json +5 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/screenshot.json +17 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-filename.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-meta-json.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-meta-raw.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/tools-list.json +827 -0
- package/skills/agent-browser/test/fixtures/proxy-session-driver.py +138 -0
- package/skills/agent-browser/test/fixtures/session-facts.py +102 -0
- package/skills/agent-browser/test/playwright-mcp-lifetime.test.sh +1620 -0
- package/skills/agent-browser/test/playwright-mcp-redact-proxy.test.sh +1599 -0
- package/skills/agent-browser/test/redact-a11y-snapshot.test.sh +527 -0
- package/skills/agent-native-architecture/SKILL.md +479 -0
- package/skills/agent-native-architecture/references/action-parity-discipline.md +430 -0
- package/skills/agent-native-architecture/references/agent-execution-patterns.md +483 -0
- package/skills/agent-native-architecture/references/agent-native-testing.md +605 -0
- package/skills/agent-native-architecture/references/architecture-patterns.md +493 -0
- package/skills/agent-native-architecture/references/dynamic-context-injection.md +358 -0
- package/skills/agent-native-architecture/references/files-universal-interface.md +314 -0
- package/skills/agent-native-architecture/references/from-primitives-to-domain-tools.md +381 -0
- package/skills/agent-native-architecture/references/mcp-tool-design.md +524 -0
- package/skills/agent-native-architecture/references/mobile-patterns.md +897 -0
- package/skills/agent-native-architecture/references/product-implications.md +478 -0
- package/skills/agent-native-architecture/references/refactoring-to-prompt-native.md +342 -0
- package/skills/agent-native-architecture/references/self-modification.md +294 -0
- package/skills/agent-native-architecture/references/shared-workspace-architecture.md +700 -0
- package/skills/agent-native-architecture/references/system-prompt-design.md +263 -0
- package/skills/agent-native-audit/SKILL.md +300 -0
- package/skills/agent-native-audit/workflows/agent-native-audit.workflow.js +423 -0
- package/skills/andrew-kane-gem-writer/SKILL.md +189 -0
- package/skills/andrew-kane-gem-writer/references/database-adapters.md +231 -0
- package/skills/andrew-kane-gem-writer/references/module-organization.md +121 -0
- package/skills/andrew-kane-gem-writer/references/rails-integration.md +183 -0
- package/skills/andrew-kane-gem-writer/references/resources.md +126 -0
- package/skills/andrew-kane-gem-writer/references/testing-patterns.md +261 -0
- package/skills/architecture/SKILL.md +436 -0
- package/skills/architecture/references/adr-template.md +154 -0
- package/skills/architecture/references/likec4-reference.md +136 -0
- package/skills/architecture/references/nfr-reference.md +126 -0
- package/skills/archive-kb/SKILL.md +86 -0
- package/skills/archive-kb/scripts/archive-kb.sh +300 -0
- package/skills/archive-kb/test/archive-kb-partial-run.test.sh +276 -0
- package/skills/atdd-developer/SKILL.md +65 -0
- package/skills/brainstorm/SKILL.md +701 -0
- package/skills/brainstorm/references/brainstorm-brand-workshop.md +153 -0
- package/skills/brainstorm/references/brainstorm-domain-config.md +63 -0
- package/skills/brainstorm/references/brainstorm-validation-workshop.md +65 -0
- package/skills/brainstorm/references/ui-surface-terms.md +42 -0
- package/skills/brainstorm-techniques/SKILL.md +231 -0
- package/skills/brainstorm-techniques/references/decision-principles.md +150 -0
- package/skills/brainstorm-techniques/references/phase-boundaries.md +103 -0
- package/skills/brainstorm-techniques/references/standing-delegation.md +407 -0
- package/skills/brainstorm-techniques/references/technical-ruling-delegation.md +174 -0
- package/skills/campaign-calendar/SKILL.md +145 -0
- package/skills/cf-token-scope/SKILL.md +164 -0
- package/skills/cf-token-scope/references/widen-playbook.md +114 -0
- package/skills/cf-token-scope/scripts/cf-token-scope.sh +253 -0
- package/skills/changelog/SKILL.md +142 -0
- package/skills/code-to-prd/SKILL.md +66 -0
- package/skills/code-to-prd/references/banner-template.md +12 -0
- package/skills/code-to-prd/references/prd-template.md +41 -0
- package/skills/code-to-prd/scripts/code-to-prd.sh +634 -0
- package/skills/code-to-prd/test/code-to-prd.test.sh +447 -0
- package/skills/code-to-prd/test/fixture/.env.example +7 -0
- package/skills/code-to-prd/test/fixture/app/about/page.tsx +11 -0
- package/skills/code-to-prd/test/fixture/app/api/health/route.ts +10 -0
- package/skills/code-to-prd/test/fixture/app/page.tsx +14 -0
- package/skills/code-to-prd/test/fixture/next.config.mjs +3 -0
- package/skills/code-to-prd/test/fixture/package.json +6 -0
- package/skills/cohort-status/SKILL.md +62 -0
- package/skills/community/SKILL.md +210 -0
- package/skills/community/scripts/bsky-community.sh +644 -0
- package/skills/community/scripts/bsky-setup.sh +631 -0
- package/skills/community/scripts/community-router.sh +93 -0
- package/skills/community/scripts/discord-community.sh +487 -0
- package/skills/community/scripts/discord-setup.sh +784 -0
- package/skills/community/scripts/github-community.sh +706 -0
- package/skills/community/scripts/hn-community.sh +233 -0
- package/skills/community/scripts/lib/hmac-sha1-b64.sh +126 -0
- package/skills/community/scripts/linkedin-community.sh +750 -0
- package/skills/community/scripts/linkedin-setup.sh +758 -0
- package/skills/community/scripts/x-community.sh +890 -0
- package/skills/community/scripts/x-setup.sh +713 -0
- package/skills/community/test/community-argv.test.sh +2922 -0
- package/skills/community/test/github-community.test.sh +981 -0
- package/skills/competitive-analysis/SKILL.md +69 -0
- package/skills/competitive-analysis/references/peer-plugin-audit.md +218 -0
- package/skills/compound/SKILL.md +673 -0
- package/skills/compound/scripts/token-efficiency-report.sh +334 -0
- package/skills/compound/test/phase-16.test.sh +379 -0
- package/skills/compound-capture/SKILL.md +791 -0
- package/skills/compound-capture/assets/critical-pattern-template.md +37 -0
- package/skills/compound-capture/assets/resolution-template.md +116 -0
- package/skills/compound-capture/references/yaml-schema.md +67 -0
- package/skills/compound-capture/schema.yaml +181 -0
- package/skills/constraint-scaffold/SKILL.md +175 -0
- package/skills/constraint-scaffold/references/boundary-readme.template +35 -0
- package/skills/constraint-scaffold/references/constraint-gates-workflow.template +69 -0
- package/skills/constraint-scaffold/references/depcruise-config.template +230 -0
- package/skills/constraint-scaffold/references/fix-constraints-stage-a.template +240 -0
- package/skills/constraint-scaffold/references/fix-constraints-stage-b.template +313 -0
- package/skills/constraint-scaffold/references/shared-runner.template +135 -0
- package/skills/constraint-scaffold/scripts/constraint-scaffold.sh +589 -0
- package/skills/constraint-scaffold/test/bite-proof.test.sh +1338 -0
- package/skills/constraint-scaffold/test/boundary.test.sh +734 -0
- package/skills/constraint-scaffold/test/emit-fix-constraints.test.sh +193 -0
- package/skills/constraint-scaffold/test/generator.test.sh +93 -0
- package/skills/constraint-scaffold/test/parity.test.sh +285 -0
- package/skills/content-writer/SKILL.md +242 -0
- package/skills/content-writer/scripts/blog-jargon-scan.sh +42 -0
- package/skills/cron-delete/SKILL.md +56 -0
- package/skills/cron-list/SKILL.md +54 -0
- package/skills/deepen-plan/SKILL.md +937 -0
- package/skills/deepen-plan/workflows/deepen-plan.workflow.js +488 -0
- package/skills/deploy/SKILL.md +115 -0
- package/skills/deploy/references/hetzner-setup.md +65 -0
- package/skills/deploy/scripts/deploy.sh +37 -0
- package/skills/deploy-docs/SKILL.md +95 -0
- package/skills/dhh-rails-style/SKILL.md +203 -0
- package/skills/dhh-rails-style/references/architecture.md +717 -0
- package/skills/dhh-rails-style/references/controllers.md +323 -0
- package/skills/dhh-rails-style/references/frontend.md +556 -0
- package/skills/dhh-rails-style/references/gems.md +316 -0
- package/skills/dhh-rails-style/references/models.md +388 -0
- package/skills/dhh-rails-style/references/testing.md +354 -0
- package/skills/discord-content/SKILL.md +126 -0
- package/skills/docs-site/SKILL.md +214 -0
- package/skills/drain-labeled-backlog/SKILL.md +203 -0
- package/skills/drain-labeled-backlog/scripts/group-by-area.sh +208 -0
- package/skills/drain-labeled-backlog/workflows/drain-labeled-backlog.workflow.js +433 -0
- package/skills/drain-prs/SKILL.md +142 -0
- package/skills/drain-prs/scripts/triage-prs.sh +192 -0
- package/skills/drain-prs/test/argv-ceiling.test.sh +183 -0
- package/skills/dspy-ruby/SKILL.md +634 -0
- package/skills/dspy-ruby/assets/config-template.rb +359 -0
- package/skills/dspy-ruby/assets/module-template.rb +326 -0
- package/skills/dspy-ruby/assets/signature-template.rb +143 -0
- package/skills/dspy-ruby/references/core-concepts.md +268 -0
- package/skills/dspy-ruby/references/optimization.md +623 -0
- package/skills/dspy-ruby/references/providers.md +335 -0
- package/skills/eval-harness/README.md +194 -0
- package/skills/eval-harness/SKILL.md +157 -0
- package/skills/eval-harness/enums/go-routes.json +13 -0
- package/skills/eval-harness/enums/incident-threshold.json +5 -0
- package/skills/eval-harness/enums/lane.json +5 -0
- package/skills/eval-harness/enums/tool-selection.json +20 -0
- package/skills/eval-harness/enums/triage-levels.json +5 -0
- package/skills/eval-harness/gated-skills.json +34 -0
- package/skills/eval-harness/models.generated.json +5 -0
- package/skills/eval-harness/promptfooconfig-go-routing.yaml +35 -0
- package/skills/eval-harness/promptfooconfig-incident-threshold.yaml +32 -0
- package/skills/eval-harness/promptfooconfig-lane-inference.yaml +33 -0
- package/skills/eval-harness/promptfooconfig-ticket-triage.yaml +34 -0
- package/skills/eval-harness/promptfooconfig-tool-selection.yaml +33 -0
- package/skills/eval-harness/prompts/go-baseline.txt +6 -0
- package/skills/eval-harness/prompts/go-skill.txt +21 -0
- package/skills/eval-harness/prompts/incident-threshold-baseline.txt +6 -0
- package/skills/eval-harness/prompts/incident-threshold-skill.txt +12 -0
- package/skills/eval-harness/prompts/lane-inference-baseline.txt +6 -0
- package/skills/eval-harness/prompts/lane-inference-skill.txt +15 -0
- package/skills/eval-harness/prompts/tool-selection-baseline.txt +19 -0
- package/skills/eval-harness/prompts/tool-selection-skill.txt +22 -0
- package/skills/eval-harness/prompts/triage-baseline.txt +7 -0
- package/skills/eval-harness/prompts/triage-skill.txt +10 -0
- package/skills/eval-harness/scripts/eval-gate.cjs +318 -0
- package/skills/eval-harness/scripts/extract-block.cjs +87 -0
- package/skills/eval-harness/scripts/gate-classification.cjs +28 -0
- package/skills/eval-harness/scripts/gen-models.sh +41 -0
- package/skills/eval-harness/scripts/gen-skill-prompt.cjs +177 -0
- package/skills/eval-harness/scripts/measure-classification.cjs +30 -0
- package/skills/eval-harness/scripts/parse-label.cjs +93 -0
- package/skills/eval-harness/scripts/verdict.cjs +132 -0
- package/skills/eval-harness/tasks/go-routing.jsonl +8 -0
- package/skills/eval-harness/tasks/incident-threshold.jsonl +7 -0
- package/skills/eval-harness/tasks/lane-inference.jsonl +7 -0
- package/skills/eval-harness/tasks/ticket-triage.jsonl +6 -0
- package/skills/eval-harness/tasks/tool-selection.jsonl +15 -0
- package/skills/eval-harness/test/eval-gate.test.sh +132 -0
- package/skills/eval-harness/test/extract-block.test.sh +87 -0
- package/skills/eval-harness/test/gate-classification.test.sh +77 -0
- package/skills/eval-harness/test/gen-models.test.sh +75 -0
- package/skills/eval-harness/test/measure-classification.test.sh +74 -0
- package/skills/eval-harness/test/parse-label.test.sh +129 -0
- package/skills/eval-harness/test/registry-completeness.test.sh +109 -0
- package/skills/eval-harness/test/verdict.test.sh +123 -0
- package/skills/every-style-editor/SKILL.md +142 -0
- package/skills/every-style-editor/references/EVERY_WRITE_STYLE.md +529 -0
- package/skills/feature-tweet/SKILL.md +199 -0
- package/skills/feature-video/SKILL.md +412 -0
- package/skills/feature-video/scripts/check_deps.sh +281 -0
- package/skills/file-todos/SKILL.md +267 -0
- package/skills/file-todos/assets/todo-template.md +166 -0
- package/skills/fix-issue/SKILL.md +278 -0
- package/skills/fix-issue/references/agent-authored-exclusion.md +118 -0
- package/skills/fix-issue/references/exclude-label-jq-snippet.md +96 -0
- package/skills/flag-bootstrap/SETUP.md +142 -0
- package/skills/flag-create/SKILL.md +145 -0
- package/skills/flag-create/scripts/create.sh +273 -0
- package/skills/flag-delete/SKILL.md +167 -0
- package/skills/flag-delete/scripts/delete.sh +288 -0
- package/skills/flag-list/SKILL.md +99 -0
- package/skills/flag-list/scripts/list.sh +275 -0
- package/skills/flag-set-role/SKILL.md +229 -0
- package/skills/flag-set-role/scripts/flip.sh +774 -0
- package/skills/frontend-anti-slop/SKILL.md +106 -0
- package/skills/frontend-anti-slop/references/anti-patterns.md +199 -0
- package/skills/frontend-anti-slop/references/slop-rules.md +65 -0
- package/skills/frontend-anti-slop/scripts/tier1-scan.ts +525 -0
- package/skills/frontend-design/SKILL.md +77 -0
- package/skills/gdpr-gate/NOTICE +136 -0
- package/skills/gdpr-gate/SKILL.md +341 -0
- package/skills/gdpr-gate/references/fields.md +160 -0
- package/skills/gdpr-gate/references/layers/api-layer.md +355 -0
- package/skills/gdpr-gate/references/layers/auth-sessions.md +290 -0
- package/skills/gdpr-gate/references/layers/data-in-transit.md +331 -0
- package/skills/gdpr-gate/references/layers/data-lifecycle.md +341 -0
- package/skills/gdpr-gate/references/layers/frontend.md +249 -0
- package/skills/gdpr-gate/references/layers/testing-seeding.md +256 -0
- package/skills/gdpr-gate/references/leakage-vectors.md +248 -0
- package/skills/gdpr-gate/references/legacy/legal-consent-v1-prose.md +48 -0
- package/skills/gdpr-gate/references/legal-consent.md +140 -0
- package/skills/gdpr-gate/references/non-negotiables.md +60 -0
- package/skills/gdpr-gate/scripts/gdpr-gate.sh +271 -0
- package/skills/gdpr-gate/scripts/notice-frontmatter.sh +337 -0
- package/skills/gdpr-gate/scripts/path-denylist.txt +39 -0
- package/skills/gdpr-gate/scripts/repo-scan.sh +158 -0
- package/skills/gdpr-gate/scripts/vendor-drift-classify.sh +120 -0
- package/skills/gdpr-gate/scripts/vendor-pin-integrity.sh +237 -0
- package/skills/gemini-imagegen/SKILL.md +283 -0
- package/skills/gemini-imagegen/requirements.txt +5 -0
- package/skills/gemini-imagegen/scripts/_error_handling.py +146 -0
- package/skills/gemini-imagegen/scripts/compose_images.py +150 -0
- package/skills/gemini-imagegen/scripts/edit_image.py +137 -0
- package/skills/gemini-imagegen/scripts/gemini_images.py +256 -0
- package/skills/gemini-imagegen/scripts/generate_image.py +147 -0
- package/skills/gemini-imagegen/scripts/multi_turn_chat.py +223 -0
- package/skills/gemini-imagegen/scripts/test_error_handling.py +179 -0
- package/skills/git-worktree/SKILL.md +534 -0
- package/skills/git-worktree/scripts/git-repo-readiness-diag.sh +98 -0
- package/skills/git-worktree/scripts/worktree-manager.sh +4773 -0
- package/skills/git-worktree/test/create-from-origin-main.test.sh +116 -0
- package/skills/git-worktree/test/lease-protects-active.test.sh +1056 -0
- package/skills/git-worktree/test/no-repo-fail-loud.test.sh +88 -0
- package/skills/git-worktree/test/orphan-reaper-honest-count.test.sh +379 -0
- package/skills/git-worktree/test/reap-archive-persistence.test.sh +725 -0
- package/skills/git-worktree/test/stale-lock-sweep.test.sh +176 -0
- package/skills/go/SKILL.md +54 -0
- package/skills/growth/SKILL.md +164 -0
- package/skills/harvest-debt/SKILL.md +78 -0
- package/skills/harvest-debt/scripts/harvest-debt.sh +114 -0
- package/skills/heal-skill/SKILL.md +173 -0
- package/skills/help/SKILL.md +46 -0
- package/skills/incident/NOTICE +39 -0
- package/skills/incident/SKILL.md +395 -0
- package/skills/incident/scripts/dry-run.sh +448 -0
- package/skills/incident/scripts/redact-engine.py +412 -0
- package/skills/incident/scripts/redact-sentinel.sh +44 -0
- package/skills/incident/templates/pir.md +139 -0
- package/skills/incident/test/fixtures/dry-run-incident.json +26 -0
- package/skills/incident/test/fixtures/dry-run-secret-leak.json +26 -0
- package/skills/incident/test/fixtures/legacy-bash-scanner.sh +82 -0
- package/skills/incident/test/fixtures/positive-corpus.md +87 -0
- package/skills/incident/test/redact-sentinel.test.sh +1420 -0
- package/skills/invoice/SKILL.md +206 -0
- package/skills/kb-glossary/SKILL.md +71 -0
- package/skills/kb-glossary/references/glossary-format.md +111 -0
- package/skills/kb-glossary/references/rejected-request-register.md +115 -0
- package/skills/kb-search/SKILL.md +276 -0
- package/skills/kb-search/scripts/kb-search-cache.sh +123 -0
- package/skills/legal-audit/SKILL.md +161 -0
- package/skills/legal-generate/NOTICE +242 -0
- package/skills/legal-generate/SKILL.md +244 -0
- package/skills/legal-generate/references/templates/advisor-agreement/template.md +12 -0
- package/skills/legal-generate/references/templates/business-associate-agreement/template.md +112 -0
- package/skills/legal-generate/references/templates/cookie-notice/template.md +82 -0
- package/skills/legal-generate/references/templates/dpa-global/template.md +439 -0
- package/skills/legal-generate/references/templates/dpa-us/template.md +222 -0
- package/skills/legal-generate/references/templates/employee-offer-letter/template.md +75 -0
- package/skills/legal-generate/references/templates/master-services-agreement/template.md +252 -0
- package/skills/legal-generate/references/templates/mutual-nda/template.md +66 -0
- package/skills/legal-generate/references/templates/one-way-nda/template.md +38 -0
- package/skills/legal-generate/references/templates/privacy-policy-gdpr/template.md +427 -0
- package/skills/legal-generate/references/templates/privacy-policy-us/template.md +313 -0
- package/skills/legal-generate/references/templates/terms-of-use/template.md +75 -0
- package/skills/legal-generate/scripts/strip-vendor-credit.sh +96 -0
- package/skills/linear-fetch/SKILL.md +247 -0
- package/skills/linear-fetch/scripts/assert-no-linear-telemetry.sh +66 -0
- package/skills/linear-fetch/scripts/redact-linear-urls.sh +51 -0
- package/skills/linear-fetch/scripts/render-caller-template.sh +36 -0
- package/skills/linear-fetch/test/assert-no-linear-telemetry.test.sh +113 -0
- package/skills/linear-fetch/test/parity.test.sh +112 -0
- package/skills/linear-fetch/test/persist-safe-integration.test.sh +191 -0
- package/skills/linear-fetch/test/redact-linear-urls.test.sh +226 -0
- package/skills/merge-pr/SKILL.md +668 -0
- package/skills/model-launch-review/SKILL.md +255 -0
- package/skills/model-launch-review/scripts/audit-models.sh +501 -0
- package/skills/one-shot/SKILL.md +316 -0
- package/skills/one-shot/references/token-discipline.md +74 -0
- package/skills/operator-bootstrap/SKILL.md +412 -0
- package/skills/operator-bootstrap/template.sh +439 -0
- package/skills/operator-digest/SKILL.md +287 -0
- package/skills/operator-digest/assets/operator-digest.workflow.yml +175 -0
- package/skills/operator-digest/scripts/digest-scrub.sh +119 -0
- package/skills/operator-digest/scripts/provision-operator-digest-repo.sh +147 -0
- package/skills/operator-rephrase/SKILL.md +98 -0
- package/skills/pencil-setup/SKILL.md +201 -0
- package/skills/pencil-setup/scripts/check_deps.sh +556 -0
- package/skills/pencil-setup/scripts/copy_adapter.sh +80 -0
- package/skills/pencil-setup/scripts/package-lock.json +1172 -0
- package/skills/pencil-setup/scripts/package.json +14 -0
- package/skills/pencil-setup/scripts/pencil-error-enrichment.mjs +21 -0
- package/skills/pencil-setup/scripts/pencil-mcp-adapter.mjs +733 -0
- package/skills/pencil-setup/scripts/pencil-response-classify.mjs +38 -0
- package/skills/pencil-setup/scripts/pencil-save-gate.mjs +26 -0
- package/skills/pencil-setup/scripts/sanitize-filename.mjs +25 -0
- package/skills/plan/SKILL.md +1140 -0
- package/skills/plan/references/differential-migration-testing.md +185 -0
- package/skills/plan/references/plan-community-discovery.md +42 -0
- package/skills/plan/references/plan-founder-check.md +137 -0
- package/skills/plan/references/plan-functional-overlap.md +25 -0
- package/skills/plan/references/plan-host-fanout.md +51 -0
- package/skills/plan/references/plan-issue-templates.md +779 -0
- package/skills/plan/references/plan-network-outage-checklist.md +131 -0
- package/skills/plan/references/plan-scope-check.md +126 -0
- package/skills/plan/references/plan-sharp-edges.md +325 -0
- package/skills/plan/references/plan-signoff-lifecycle.md +12 -0
- package/skills/plan/references/webapp-cwv-observability.md +143 -0
- package/skills/plan-review/SKILL.md +91 -0
- package/skills/plan-review/lib/named-panel.mjs +58 -0
- package/skills/plan-review/workflows/plan-review.workflow.js +477 -0
- package/skills/postmerge/SKILL.md +642 -0
- package/skills/postmerge/references/deploy-status-debugging.md +138 -0
- package/skills/postmerge/references/graceful-degradation.md +20 -0
- package/skills/postmerge/references/sentry-error-count-delta.md +137 -0
- package/skills/preflight/SKILL.md +1553 -0
- package/skills/preflight/references/check-13-founder-check.md +286 -0
- package/skills/preflight/scripts/founder-check.py +1640 -0
- package/skills/preflight/scripts/parse-form-a.awk +107 -0
- package/skills/preflight/scripts/probe-verb-gate.sh +108 -0
- package/skills/product-roadmap/SKILL.md +357 -0
- package/skills/product-roadmap/scripts/roadmap-reconcile.sh +327 -0
- package/skills/provision-cloudflare/SKILL.md +67 -0
- package/skills/provision-cloudflare/scripts/provision-cloudflare.sh +271 -0
- package/skills/provision-doppler/SKILL.md +58 -0
- package/skills/provision-doppler/scripts/provision-doppler.sh +358 -0
- package/skills/provision-github/SKILL.md +59 -0
- package/skills/provision-github/scripts/provision-github.sh +300 -0
- package/skills/provision-hetzner/SKILL.md +76 -0
- package/skills/provision-hetzner/scripts/provision-hetzner.sh +262 -0
- package/skills/provision-hetzner/test/fixture/knowledge-base/legal/tenant-dpa-register.md +26 -0
- package/skills/provision-hetzner/test/fixture/provision-hetzner-dry-run.golden +34 -0
- package/skills/provision-hetzner/test/provision-hetzner-characterization.test.sh +227 -0
- package/skills/qa/SKILL.md +263 -0
- package/skills/qa/references/browser-preflight.md +22 -0
- package/skills/questionnaire-generate/SKILL.md +328 -0
- package/skills/questionnaire-generate/references/questionnaire.template +60 -0
- package/skills/rclone/SKILL.md +162 -0
- package/skills/rclone/scripts/check_setup.sh +63 -0
- package/skills/release-announce/SKILL.md +65 -0
- package/skills/release-docs/SKILL.md +92 -0
- package/skills/reproduce-bug/SKILL.md +358 -0
- package/skills/resolve-debt/SKILL.md +85 -0
- package/skills/resolve-debt/scripts/resolve-debt.py +584 -0
- package/skills/resolve-parallel/SKILL.md +45 -0
- package/skills/resolve-parallel/workflows/resolve-parallel.workflow.js +415 -0
- package/skills/resolve-pr-parallel/SKILL.md +68 -0
- package/skills/resolve-pr-parallel/workflows/resolve-pr-parallel.workflow.js +419 -0
- package/skills/resolve-todo-parallel/SKILL.md +57 -0
- package/skills/resolve-todo-parallel/workflows/resolve-todo-parallel.workflow.js +398 -0
- package/skills/review/SKILL.md +1621 -0
- package/skills/review/references/anti-slop-scanner-hook.md +40 -0
- package/skills/review/references/defect-classes-migrations.md +9 -0
- package/skills/review/references/generated-input-oracle-surfaces.md +15 -0
- package/skills/review/references/review-e2e-testing.md +116 -0
- package/skills/review/references/review-todo-structure.md +165 -0
- package/skills/review/references/risk-tier-and-fix-rounds.md +95 -0
- package/skills/review/references/semgrep-custom-rules.yaml +174 -0
- package/skills/review/references/ultrathink-phases.md +98 -0
- package/skills/review/references/wfs.md +6 -0
- package/skills/review/scripts/emit-review-trailer.sh +499 -0
- package/skills/review/scripts/ensure-semgrep.sh +99 -0
- package/skills/review/scripts/fix-round-seats.sh +197 -0
- package/skills/review/test/emit-review-trailer.test.sh +387 -0
- package/skills/review/test/ensure-semgrep.test.sh +111 -0
- package/skills/review/test/fix-round-seats.test.sh +237 -0
- package/skills/review/workflows/README.md +147 -0
- package/skills/review/workflows/review.workflow.js +667 -0
- package/skills/schedule/SKILL.md +796 -0
- package/skills/seo-aeo/SKILL.md +139 -0
- package/skills/seo-aeo/scripts/validate-csp.sh +233 -0
- package/skills/seo-aeo/scripts/validate-seo.sh +262 -0
- package/skills/ship/SKILL.md +3022 -0
- package/skills/ship/references/adr-ordinal-collision.md +47 -0
- package/skills/ship/references/affected-gate-contract.md +115 -0
- package/skills/ship/references/ci-run-diagnosis.md +29 -0
- package/skills/ship/references/ci-workflow-authoring.md +68 -0
- package/skills/ship/references/counsel-review-clo-attestation.md +38 -0
- package/skills/ship/references/cto-ruling-armed-push.md +5 -0
- package/skills/ship/references/decision-challenges-render.md +71 -0
- package/skills/ship/references/detached-battery-verdict.md +13 -0
- package/skills/ship/references/feature-tweet-draft.md +42 -0
- package/skills/ship/references/followthrough-issue-body-template.md +47 -0
- package/skills/ship/references/followthrough-stub-template.sh +92 -0
- package/skills/ship/references/merge-queue-dequeue.md +39 -0
- package/skills/ship/references/operator-holds.md +149 -0
- package/skills/ship/references/pipeline-tally-render.md +43 -0
- package/skills/ship/references/pr-overlap.md +153 -0
- package/skills/ship/references/queue-mode.md +7 -0
- package/skills/ship/references/ready-run-wait.md +36 -0
- package/skills/ship/references/red-on-main-quarantine.md +110 -0
- package/skills/ship/references/register-update-pr-pattern.md +88 -0
- package/skills/ship/references/settle-then-admin-merge.md +130 -0
- package/skills/ship/references/supabase-column-probe.md +13 -0
- package/skills/ship/scripts/auto-close-scan.sh +76 -0
- package/skills/ship/scripts/battery-owed.sh +403 -0
- package/skills/ship/scripts/hold-gate-eval.sh +410 -0
- package/skills/ship/scripts/net-issue-flow.sh +665 -0
- package/skills/ship/scripts/pr-overlap-preflight.sh +483 -0
- package/skills/ship/scripts/ship-pir-action-items-gate.sh +246 -0
- package/skills/ship/scripts/technical-ruling-gate.sh +444 -0
- package/skills/skill-creator/SKILL.md +299 -0
- package/skills/skill-creator/references/api-security.md +248 -0
- package/skills/skill-creator/references/authoring-levers.md +110 -0
- package/skills/skill-creator/references/be-clear-and-direct.md +587 -0
- package/skills/skill-creator/references/best-practices.md +431 -0
- package/skills/skill-creator/references/common-patterns.md +639 -0
- package/skills/skill-creator/references/core-principles.md +193 -0
- package/skills/skill-creator/references/executable-code.md +188 -0
- package/skills/skill-creator/references/iteration-and-testing.md +509 -0
- package/skills/skill-creator/references/official-spec.md +193 -0
- package/skills/skill-creator/references/recommended-structure.md +180 -0
- package/skills/skill-creator/references/skill-structure.md +396 -0
- package/skills/skill-creator/references/using-scripts.md +122 -0
- package/skills/skill-creator/references/using-templates.md +120 -0
- package/skills/skill-creator/references/workflows-and-validation.md +541 -0
- package/skills/skill-creator/scripts/init_skill.py +322 -0
- package/skills/skill-creator/scripts/package_skill.py +110 -0
- package/skills/skill-creator/scripts/quick_validate.py +64 -0
- package/skills/skill-creator/templates/router-skill.md +73 -0
- package/skills/skill-creator/templates/simple-skill.md +34 -0
- package/skills/skill-creator/workflows/add-reference.md +96 -0
- package/skills/skill-creator/workflows/add-script.md +104 -0
- package/skills/skill-creator/workflows/add-template.md +83 -0
- package/skills/skill-creator/workflows/add-workflow.md +128 -0
- package/skills/skill-creator/workflows/audit-skill.md +159 -0
- package/skills/skill-creator/workflows/create-domain-expertise-skill.md +623 -0
- package/skills/skill-creator/workflows/create-new-skill.md +207 -0
- package/skills/skill-creator/workflows/get-guidance.md +134 -0
- package/skills/skill-creator/workflows/upgrade-to-router.md +170 -0
- package/skills/skill-creator/workflows/verify-skill.md +226 -0
- package/skills/skill-security-scan/SKILL.md +145 -0
- package/skills/skill-security-scan/references/disclaimer.md +21 -0
- package/skills/skill-security-scan/references/first-party-allowlist.yaml +38 -0
- package/skills/skill-security-scan/references/override-artifact-schema.json +68 -0
- package/skills/skill-security-scan/references/override-mechanism.md +171 -0
- package/skills/skill-security-scan/references/redirect-domains.yaml +27 -0
- package/skills/skill-security-scan/references/regex-patterns.md +136 -0
- package/skills/skill-security-scan/references/rules/code-exec.yaml +70 -0
- package/skills/skill-security-scan/references/rules/filesystem-boundary.yaml +29 -0
- package/skills/skill-security-scan/references/rules/manifest.yaml +44 -0
- package/skills/skill-security-scan/references/rules/supply-chain.yaml +36 -0
- package/skills/skill-security-scan/references/scan-meta-schema.json +63 -0
- package/skills/skill-security-scan/references/test-fixtures/clean-soleur-style.skill.md +23 -0
- package/skills/skill-security-scan/references/test-fixtures/clean-third-party.skill.md +26 -0
- package/skills/skill-security-scan/references/test-fixtures/malicious-codeexec.skill.md +23 -0
- package/skills/skill-security-scan/references/test-fixtures/malicious-curl-pipe-bash.skill.md +69 -0
- package/skills/skill-security-scan/references/test-fixtures/malicious-prompt-injection.skill.md +17 -0
- package/skills/skill-security-scan/references/test-fixtures/malicious-telemetry-beacon.skill.md +23 -0
- package/skills/skill-security-scan/references/typosquat-targets.yaml +65 -0
- package/skills/skill-security-scan/scripts/check-codeexec.sh +41 -0
- package/skills/skill-security-scan/scripts/check-filesystem-boundary.sh +19 -0
- package/skills/skill-security-scan/scripts/check-prompt-injection.sh +95 -0
- package/skills/skill-security-scan/scripts/check-supply-chain.sh +219 -0
- package/skills/skill-security-scan/scripts/check-telemetry-surface.sh +126 -0
- package/skills/skill-security-scan/scripts/lib.sh +197 -0
- package/skills/skill-security-scan/scripts/parse-override.sh +231 -0
- package/skills/skill-security-scan/scripts/run-scan.sh +342 -0
- package/skills/skill-security-scan/scripts/run-self-test.sh +95 -0
- package/skills/skill-security-scan/test/argv-ceiling.test.sh +180 -0
- package/skills/skill-security-scan/test/meta-dir-reap.test.sh +411 -0
- package/skills/skill-security-scan/test/parse-override.test.sh +607 -0
- package/skills/skill-security-scan/test/redact-pii-ghs-shape.test.sh +251 -0
- package/skills/social-distribute/SKILL.md +485 -0
- package/skills/spec-templates/SKILL.md +274 -0
- package/skills/sync/SKILL.md +49 -0
- package/skills/test-browser/SKILL.md +464 -0
- package/skills/test-browser/references/feature-map.md +161 -0
- package/skills/test-fix-loop/SKILL.md +159 -0
- package/skills/test-fix-loop/references/determinism-checklist.md +154 -0
- package/skills/triage/SKILL.md +402 -0
- package/skills/trigger-cron/SKILL.md +136 -0
- package/skills/trigger-cron/scripts/trigger.sh +198 -0
- package/skills/user-set-role/SKILL.md +147 -0
- package/skills/user-set-role/scripts/set-role.sh +218 -0
- package/skills/user-story-writer/SKILL.md +77 -0
- package/skills/ux-audit/SKILL.md +251 -0
- package/skills/ux-audit/references/finding.schema.json +62 -0
- package/skills/ux-audit/references/route-list.yaml +95 -0
- package/skills/ux-audit/scripts/bot-fixture.ts +270 -0
- package/skills/ux-audit/scripts/bot-signin.ts +135 -0
- package/skills/ux-audit/scripts/dedup-hash.ts +46 -0
- package/skills/work/SKILL.md +1479 -0
- package/skills/work/references/end-of-work-resume.md +7 -0
- package/skills/work/references/hook-test-main-cwd.md +24 -0
- package/skills/work/references/key-principles.md +83 -0
- package/skills/work/references/property-testing.md +122 -0
- package/skills/work/references/sql-security-definer-rpc-scaffold.sql +78 -0
- package/skills/work/references/work-agent-teams.md +97 -0
- package/skills/work/references/work-lifecycle-parallel.md +147 -0
- package/skills/work/references/work-lockfile-bumps.md +97 -0
- package/skills/work/references/work-scratch-sandboxes.md +212 -0
- package/skills/work/references/work-subagent-fanout.md +61 -0
- package/skills/work/references/work-test-harness-sharp-edges.md +73 -0
- package/skills/xcode-test/SKILL.md +360 -0
|
@@ -0,0 +1,774 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Flip a Flagsmith per-role segment override + mirror to Doppler on prd flips.
|
|
3
|
+
#
|
|
4
|
+
# Contract: SKILL.md in the parent directory. ADR-038 v2 §"Fallback semantics"
|
|
5
|
+
# documents the env-var mirror invariant this script enforces.
|
|
6
|
+
#
|
|
7
|
+
# Usage: bash flip.sh <flag> <prd|dev> <on|off> [--org <orgId>] [--dry-run]
|
|
8
|
+
# bash flip.sh <flag> <prd|dev> on --detach-shared --org <memberId> [--control-org <id>]
|
|
9
|
+
#
|
|
10
|
+
# Every write run changes BOTH Flagsmith environments (dev and prd), whichever
|
|
11
|
+
# role segment it names. Run it in your OWN terminal: the ack below needs a person
|
|
12
|
+
# to type yes, and no flag skips it (#8486, ADR-249). An agent runs only
|
|
13
|
+
# `--dry-run` and prints the write command for the operator.
|
|
14
|
+
#
|
|
15
|
+
# Exit codes:
|
|
16
|
+
# 0 — success / dry-run clean
|
|
17
|
+
# 1 — fallback-fidelity rule violated, or the operator did not type yes at the
|
|
18
|
+
# ack (stdout: SOLEUR_BOOTSTRAP_ABORTED stage=ack; nothing mutated)
|
|
19
|
+
# 2 — prerequisite missing, a usage error, or an unknown flag (including the
|
|
20
|
+
# removed confirm-skip flag)
|
|
21
|
+
# 3 — Flagsmith API error
|
|
22
|
+
# 4 — Doppler write failed (partial state — operator must reconcile), or the
|
|
23
|
+
# WORM audit append failed before any mutation
|
|
24
|
+
# 64 — no TTY on stdin for a write run (stdout: SOLEUR_BOOTSTRAP_INPUT_REQUIRED);
|
|
25
|
+
# refused before any credential fetch or network call
|
|
26
|
+
|
|
27
|
+
set -euo pipefail
|
|
28
|
+
|
|
29
|
+
# (#7797) Refuse to run under shell tracing. UNCONDITIONAL — deliberately NOT
|
|
30
|
+
# gated on a non-emptiness test of the credential variable, because
|
|
31
|
+
# every credential this script handles (the Flagsmith management key, and the
|
|
32
|
+
# soleur/prd SUPABASE_SERVICE_ROLE_KEY the audit helper binds) is acquired by
|
|
33
|
+
# `doppler secrets get` BELOW this point, so a conditional arm would test an empty
|
|
34
|
+
# variable at guard time, open, and then trace the acquisition itself. The refusal
|
|
35
|
+
# prints on STDOUT because agent runtimes surface stdout and swallow stderr
|
|
36
|
+
# (knowledge-base/project/constitution.md > Code Style).
|
|
37
|
+
case "$-" in
|
|
38
|
+
*x*)
|
|
39
|
+
printf '[FATAL] refusing to run under xtrace: this script handles a live credential and -x would print it (see #7797)\n'
|
|
40
|
+
exit 78
|
|
41
|
+
;;
|
|
42
|
+
esac
|
|
43
|
+
|
|
44
|
+
# (#7873) `--disable` closes ~/.curlrc and `--noproxy '*'` closes the proxy vars,
|
|
45
|
+
# but neither touches the env that subverts TLS ITSELF. SSLKEYLOGFILE writes the
|
|
46
|
+
# session keys and the CA vars substitute the trust store, so a CURL_CA_BUNDLE
|
|
47
|
+
# MITM of these credentials works with every other guard fully intact.
|
|
48
|
+
unset SSLKEYLOGFILE CURL_CA_BUNDLE SSL_CERT_FILE SSL_CERT_DIR CURL_HOME \
|
|
49
|
+
HOSTALIASES LOCALDOMAIN RES_OPTIONS
|
|
50
|
+
|
|
51
|
+
# Shared WORM audit-append helper (PostgREST RPC; no DB-CLI binary). See #4581 PR-1.
|
|
52
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
53
|
+
source "$SCRIPT_DIR/../../../scripts/audit-flag-flip.sh"
|
|
54
|
+
|
|
55
|
+
# Human-presence gate (#8486, ADR-249). Every production write below waits on the
|
|
56
|
+
# operator-script library's class-2 ack, which has NO skip variable and no flag:
|
|
57
|
+
# it needs a person typing `yes` at a terminal, so an agent's tool subprocess
|
|
58
|
+
# (no TTY) is refused with exit 64 before any credential fetch. Clear anything an
|
|
59
|
+
# inherited environment could use to pre-empt the library's double-source guard
|
|
60
|
+
# or to stand in for its ack before sourcing it. (BASH_ENV runs before this
|
|
61
|
+
# script and cannot be cleared from inside it — recorded in ADR-249 as a
|
|
62
|
+
# hijack-class residual.)
|
|
63
|
+
unset _SOLEUR_OPERATOR_SCRIPT_LOADED SOLEUR_OP_ACKED
|
|
64
|
+
unset -f soleur_op_ack_or_die soleur_op_input_required soleur_op_aborted
|
|
65
|
+
# shellcheck source=../../../scripts/lib/operator-script.sh
|
|
66
|
+
source "$SCRIPT_DIR/../../../scripts/lib/operator-script.sh"
|
|
67
|
+
[[ ${SOLEUR_OP_LIB_API:-0} -eq 1 ]] || {
|
|
68
|
+
printf 'SOLEUR_BOOTSTRAP_LIB_INCOMPATIBLE need=1 got=%s\n' "${SOLEUR_OP_LIB_API:-0}"
|
|
69
|
+
exit 64
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# --- constants (project-level, captured 2026-05-22) -------------------------
|
|
73
|
+
# Note: Flagsmith Admin API is inconsistent — some endpoints want the numeric
|
|
74
|
+
# env_id (e.g. /environments/{int_id}/features/{int_id}/versions/), others
|
|
75
|
+
# want the env api_key string (e.g. /environments/{api_key}/featurestates/).
|
|
76
|
+
# Both forms documented here so the helpers can pick the right one.
|
|
77
|
+
readonly FLAGSMITH_PROJECT_ID=39082
|
|
78
|
+
readonly FLAGSMITH_ENV_DEV_ID=90722
|
|
79
|
+
readonly FLAGSMITH_ENV_PRD_ID=90721
|
|
80
|
+
readonly FLAGSMITH_ENV_DEV_KEY="PRHE5c9eWXYuRDFFPtbFxj"
|
|
81
|
+
readonly FLAGSMITH_ENV_PRD_KEY="QMEpRRzFx8kpEcY7nZmhJd"
|
|
82
|
+
readonly FLAGSMITH_API="https://api.flagsmith.com/api/v1"
|
|
83
|
+
# Edge SDK eval endpoint (client-side X-Environment-Key). Mirrors the production
|
|
84
|
+
# resolution path: flagsmith-nodejs getIdentityFlags() against edge.api.flagsmith.com
|
|
85
|
+
# (apps/web-platform/lib/feature-flags/server.ts DEFAULT_FLAGSMITH_API_URL).
|
|
86
|
+
readonly FLAGSMITH_EDGE_API="https://edge.api.flagsmith.com/api/v1"
|
|
87
|
+
# Synthetic non-member orgId — default control for the eval-layer control-negative
|
|
88
|
+
# assertion. Guaranteed to match no segment, so the flag must eval OFF for it; if it
|
|
89
|
+
# evals ON the flag is globally enabled (a real leak). Override with --control-org to
|
|
90
|
+
# assert against a specific real sibling org (e.g. the org sharing org-targeted).
|
|
91
|
+
readonly DEFAULT_CONTROL_ORG="00000000-0000-0000-0000-000000000000"
|
|
92
|
+
# Legacy shared per-org segment (ADR-043 §"Segment Design", pre per-feature scoping).
|
|
93
|
+
# `--detach-shared` resolves this BY NAME (never a hard-coded id) and removes the
|
|
94
|
+
# feature's override on it, migrating the feature onto its own `<flag>-orgs` segment.
|
|
95
|
+
readonly SHARED_SEGMENT_NAME="org-targeted"
|
|
96
|
+
|
|
97
|
+
# Map known flag-name → Doppler env-var name. Keep in sync with
|
|
98
|
+
# apps/web-platform/lib/feature-flags/server.ts RUNTIME_FLAGS.
|
|
99
|
+
# Co-editor: soleur:flag-delete (scripts/delete.sh) removes an entry from this
|
|
100
|
+
# map on delete by regex-matching `["<name>"]="FLAG_<X>"`. If you reshape this
|
|
101
|
+
# declaration (quoting, generated map, multi-line), update delete.sh's removal
|
|
102
|
+
# regex in lockstep or a deleted flag will leave a stale entry here.
|
|
103
|
+
declare -A FLAG_ENV_VARS=(
|
|
104
|
+
["kb-chat-sidebar"]="FLAG_KB_CHAT_SIDEBAR"
|
|
105
|
+
["team-workspace-invite"]="FLAG_TEAM_WORKSPACE_INVITE"
|
|
106
|
+
["byok-delegations"]="FLAG_BYOK_DELEGATIONS"
|
|
107
|
+
["c4-visualizer"]="FLAG_C4_VISUALIZER"
|
|
108
|
+
["debug-mode"]="FLAG_DEBUG_MODE"
|
|
109
|
+
["c4-edit"]="FLAG_C4_EDIT"
|
|
110
|
+
["command-palette"]="FLAG_COMMAND_PALETTE"
|
|
111
|
+
["support"]="FLAG_SUPPORT"
|
|
112
|
+
["support-live"]="FLAG_SUPPORT_LIVE"
|
|
113
|
+
["guided-tour"]="FLAG_GUIDED_TOUR"
|
|
114
|
+
["codex-engine"]="FLAG_CODEX_ENGINE"
|
|
115
|
+
["untrusted-content-classify"]="FLAG_UNTRUSTED_CONTENT_CLASSIFY"
|
|
116
|
+
["untrusted-content-wrap"]="FLAG_UNTRUSTED_CONTENT_WRAP"
|
|
117
|
+
["egress-audit-collection"]="FLAG_EGRESS_AUDIT_COLLECTION"
|
|
118
|
+
["agent-sandbox-unix-deny"]="FLAG_AGENT_SANDBOX_UNIX_DENY"
|
|
119
|
+
["new-sessions-paused"]="FLAG_NEW_SESSIONS_PAUSED"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# --- arg parsing ------------------------------------------------------------
|
|
123
|
+
DRY_RUN=0
|
|
124
|
+
TARGET_TYPE="role"
|
|
125
|
+
TARGET_ORG=""
|
|
126
|
+
CONTROL_ORG=""
|
|
127
|
+
DETACH_SHARED=0
|
|
128
|
+
FLAG=""
|
|
129
|
+
ROLE=""
|
|
130
|
+
VALUE=""
|
|
131
|
+
|
|
132
|
+
while [[ $# -gt 0 ]]; do
|
|
133
|
+
case "$1" in
|
|
134
|
+
--dry-run) DRY_RUN=1; shift ;;
|
|
135
|
+
# A value-taking option never swallows a flag as its value, so the defer
|
|
136
|
+
# hook's read-only escape and this parser cannot disagree about --dry-run.
|
|
137
|
+
--target|--org|--control-org)
|
|
138
|
+
[[ $# -ge 2 && "$2" != --* ]] || { echo "$1 needs a value that does not start with --" >&2; exit 2; }
|
|
139
|
+
case "$1" in
|
|
140
|
+
--target) TARGET_TYPE="$2" ;;
|
|
141
|
+
--org) TARGET_ORG="$2" ;;
|
|
142
|
+
--control-org) CONTROL_ORG="$2" ;;
|
|
143
|
+
esac
|
|
144
|
+
shift 2 ;;
|
|
145
|
+
--detach-shared) DETACH_SHARED=1; shift ;;
|
|
146
|
+
--*)
|
|
147
|
+
# Covers the confirm-skip flag this script used to accept for agent-driven
|
|
148
|
+
# use: it was removed (#8486), and no flag replaces it.
|
|
149
|
+
echo "unknown flag: $1 — flip.sh has no flag that skips its yes prompt (#8486)." >&2
|
|
150
|
+
echo "To apply a change, run the command without it in your own terminal and type yes at the prompt." >&2
|
|
151
|
+
exit 2 ;;
|
|
152
|
+
*)
|
|
153
|
+
if [[ -z "$FLAG" ]]; then FLAG="$1"
|
|
154
|
+
elif [[ -z "$ROLE" ]]; then ROLE="$1"
|
|
155
|
+
elif [[ -z "$VALUE" ]]; then VALUE="$1"
|
|
156
|
+
fi
|
|
157
|
+
shift ;;
|
|
158
|
+
esac
|
|
159
|
+
done
|
|
160
|
+
|
|
161
|
+
usage() {
|
|
162
|
+
echo "Usage: flip.sh <flag> <prd|dev> <on|off> [--org <orgId>] [--dry-run]" >&2
|
|
163
|
+
echo " flip.sh <flag> <prd|dev> on --detach-shared --org <memberId> [--control-org <id>]" >&2
|
|
164
|
+
echo "Known flags: ${!FLAG_ENV_VARS[*]}" >&2
|
|
165
|
+
exit 2
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
# Infer TARGET_TYPE=org when --org is provided (no need for explicit --target org).
|
|
169
|
+
[[ -n "$TARGET_ORG" ]] && TARGET_TYPE="org"
|
|
170
|
+
|
|
171
|
+
[[ -z "$FLAG" || -z "$ROLE" || -z "$VALUE" ]] && usage
|
|
172
|
+
[[ -z "${FLAG_ENV_VARS[$FLAG]:-}" ]] && { echo "unknown flag: $FLAG" >&2; usage; }
|
|
173
|
+
[[ "$ROLE" != "prd" && "$ROLE" != "dev" ]] && { echo "role must be prd|dev (got: $ROLE)" >&2; usage; }
|
|
174
|
+
[[ "$VALUE" != "on" && "$VALUE" != "off" ]] && { echo "value must be on|off (got: $VALUE)" >&2; usage; }
|
|
175
|
+
[[ "$TARGET_TYPE" != "role" && "$TARGET_TYPE" != "org" ]] && { echo "target must be role|org (got: $TARGET_TYPE)" >&2; usage; }
|
|
176
|
+
[[ "$TARGET_TYPE" == "org" && -z "$TARGET_ORG" ]] && { echo "--target org requires --org <orgId>" >&2; usage; }
|
|
177
|
+
[[ -n "$CONTROL_ORG" && -z "$TARGET_ORG" ]] && { echo "--control-org requires --org <orgId>" >&2; usage; }
|
|
178
|
+
|
|
179
|
+
if [[ "$TARGET_TYPE" == "org" ]]; then
|
|
180
|
+
[[ "$TARGET_ORG" =~ ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ ]] \
|
|
181
|
+
|| { echo "invalid orgId format (expected UUID): $TARGET_ORG" >&2; exit 2; }
|
|
182
|
+
[[ -z "$CONTROL_ORG" ]] && CONTROL_ORG="$DEFAULT_CONTROL_ORG"
|
|
183
|
+
[[ "$CONTROL_ORG" =~ ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ ]] \
|
|
184
|
+
|| { echo "invalid control orgId format (expected UUID): $CONTROL_ORG" >&2; exit 2; }
|
|
185
|
+
[[ "$CONTROL_ORG" == "$TARGET_ORG" ]] \
|
|
186
|
+
&& { echo "--control-org must differ from --org (got both = $TARGET_ORG)" >&2; exit 2; }
|
|
187
|
+
# The synthetic default only proves "not globally ON"; pass a real sibling org for a
|
|
188
|
+
# leak check against the shared org-targeted blast radius. [review: user-impact F4]
|
|
189
|
+
[[ "$CONTROL_ORG" == "$DEFAULT_CONTROL_ORG" ]] \
|
|
190
|
+
&& echo "⚠ control-negative uses the synthetic default org ($DEFAULT_CONTROL_ORG) — pass --control-org <real-sibling-uuid> for a stronger leak assertion." >&2
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
# --- no TTY, no write: refuse before any credential fetch or network call ---
|
|
194
|
+
# Every non-dry-run arm is a production write, role=dev included: the flip is
|
|
195
|
+
# applied in BOTH Flagsmith environments whichever role segment it names.
|
|
196
|
+
if [[ $DRY_RUN -eq 0 ]]; then [[ -t 0 ]] || soleur_op_input_required "destructive-write-ack(no-skip-variable-by-design)" ack; fi
|
|
197
|
+
|
|
198
|
+
ENV_VAR="${FLAG_ENV_VARS[$FLAG]}"
|
|
199
|
+
PROPOSED_ENABLED=$([[ "$VALUE" == "on" ]] && echo true || echo false)
|
|
200
|
+
SEGMENT_NAME="role-$ROLE"
|
|
201
|
+
|
|
202
|
+
# --- prerequisites ----------------------------------------------------------
|
|
203
|
+
command -v curl >/dev/null || { echo "missing: curl" >&2; exit 2; }
|
|
204
|
+
command -v doppler >/dev/null || { echo "missing: doppler" >&2; exit 2; }
|
|
205
|
+
command -v python3 >/dev/null || { echo "missing: python3" >&2; exit 2; }
|
|
206
|
+
|
|
207
|
+
TOKEN=$(doppler secrets get FLAGSMITH_MANAGEMENT_API_KEY -p soleur -c cli_ops --plain 2>/dev/null || true)
|
|
208
|
+
[[ -z "$TOKEN" ]] && { echo "FLAGSMITH_MANAGEMENT_API_KEY not in Doppler soleur/cli_ops" >&2; exit 2; }
|
|
209
|
+
|
|
210
|
+
# Token-shape guard: a newline in the key would inject a curl config directive on the stdin
|
|
211
|
+
# channel below. Never echoes the value.
|
|
212
|
+
_bearer_ok() { local LC_ALL=C; case "${1:-}" in ''|*[!A-Za-z0-9._~+/=-]*) return 1 ;; esac; }
|
|
213
|
+
_bearer_ok "$TOKEN" || { echo "FLAGSMITH_MANAGEMENT_API_KEY has an unexpected shape" >&2; exit 2; }
|
|
214
|
+
|
|
215
|
+
# --- helpers ----------------------------------------------------------------
|
|
216
|
+
# The Flagsmith management key travels on curl's stdin config channel, never on argv.
|
|
217
|
+
fs_api() {
|
|
218
|
+
curl --disable --noproxy '*' -sS -H "Content-Type: application/json" "$@" --config - \
|
|
219
|
+
< <(printf 'header = "Authorization: Api-Key %s"\n' "$TOKEN")
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
resolve_feature_id() {
|
|
223
|
+
local name="$1"
|
|
224
|
+
fs_api "${FLAGSMITH_API}/projects/${FLAGSMITH_PROJECT_ID}/features/?q=${name}" \
|
|
225
|
+
| python3 -c "
|
|
226
|
+
import json, sys
|
|
227
|
+
d = json.load(sys.stdin)
|
|
228
|
+
for f in d.get('results', []):
|
|
229
|
+
if f['name'] == '$name':
|
|
230
|
+
print(f['id'])
|
|
231
|
+
sys.exit(0)
|
|
232
|
+
print('not-found', file=sys.stderr)
|
|
233
|
+
sys.exit(3)
|
|
234
|
+
"
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
resolve_segment_id() {
|
|
238
|
+
local name="$1"
|
|
239
|
+
fs_api "${FLAGSMITH_API}/projects/${FLAGSMITH_PROJECT_ID}/segments/" \
|
|
240
|
+
| python3 -c "
|
|
241
|
+
import json, sys
|
|
242
|
+
d = json.load(sys.stdin)
|
|
243
|
+
for s in d.get('results', []):
|
|
244
|
+
if s['name'] == '$name':
|
|
245
|
+
print(s['id'])
|
|
246
|
+
sys.exit(0)
|
|
247
|
+
print('not-found', file=sys.stderr)
|
|
248
|
+
sys.exit(3)
|
|
249
|
+
"
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
# Find the feature_segment row id for (env, feature, segment), if any.
|
|
253
|
+
# Uses numeric env_id (the /features/feature-segments/ endpoint accepts int).
|
|
254
|
+
read_feature_segment_id() {
|
|
255
|
+
local env_id="$1" feature_id="$2" segment_id="$3"
|
|
256
|
+
[[ "$feature_id" =~ ^[0-9]+$ ]] || { echo "invalid feature id (expected an integer)" >&2; return 1; }
|
|
257
|
+
fs_api "${FLAGSMITH_API}/features/feature-segments/?environment=${env_id}&feature=${feature_id}" \
|
|
258
|
+
| python3 -c "
|
|
259
|
+
import json, sys
|
|
260
|
+
d = json.load(sys.stdin)
|
|
261
|
+
for fs in d.get('results', []):
|
|
262
|
+
if fs.get('segment') == int('$segment_id'):
|
|
263
|
+
print(fs['id']); sys.exit(0)
|
|
264
|
+
print('')
|
|
265
|
+
"
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
# Get the live (published, is_live) version uuid for (env, feature).
|
|
269
|
+
get_live_version_uuid() {
|
|
270
|
+
local env_id="$1" feature_id="$2"
|
|
271
|
+
[[ "$feature_id" =~ ^[0-9]+$ ]] || { echo "invalid feature id (expected an integer)" >&2; return 1; }
|
|
272
|
+
fs_api "${FLAGSMITH_API}/environments/${env_id}/features/${feature_id}/versions/" \
|
|
273
|
+
| python3 -c "
|
|
274
|
+
import json, sys
|
|
275
|
+
d = json.load(sys.stdin)
|
|
276
|
+
for v in d.get('results', []):
|
|
277
|
+
if v.get('is_live'):
|
|
278
|
+
print(v['uuid']); sys.exit(0)
|
|
279
|
+
sys.exit(3)
|
|
280
|
+
"
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
# Read enablement of (env, feature, segment). Outputs "true"/"false"/"missing".
|
|
284
|
+
# Uses the version-scoped featurestates endpoint which returns env-default +
|
|
285
|
+
# segment overrides in one call.
|
|
286
|
+
read_segment_state() {
|
|
287
|
+
local env_id="$1" feature_id="$2" segment_id="$3"
|
|
288
|
+
[[ "$feature_id" =~ ^[0-9]+$ ]] || { echo "missing"; return; }
|
|
289
|
+
local live_uuid
|
|
290
|
+
live_uuid=$(get_live_version_uuid "$env_id" "$feature_id") || { echo "missing"; return; }
|
|
291
|
+
fs_api "${FLAGSMITH_API}/environments/${env_id}/features/${feature_id}/versions/${live_uuid}/featurestates/" \
|
|
292
|
+
| python3 -c "
|
|
293
|
+
import json, sys
|
|
294
|
+
target = int('$segment_id')
|
|
295
|
+
d = json.load(sys.stdin)
|
|
296
|
+
for fs in d:
|
|
297
|
+
seg = fs.get('feature_segment')
|
|
298
|
+
seg_id_val = None
|
|
299
|
+
if isinstance(seg, dict):
|
|
300
|
+
seg_id_val = seg.get('segment')
|
|
301
|
+
if seg_id_val == target:
|
|
302
|
+
print(str(fs['enabled']).lower()); sys.exit(0)
|
|
303
|
+
print('missing')
|
|
304
|
+
"
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
# Push a new version with the override change. Detects create-vs-update via
|
|
308
|
+
# whether a feature_segment row already exists.
|
|
309
|
+
flip_segment_in_env() {
|
|
310
|
+
local env_id="$1" feature_id="$2" segment_id="$3" enabled="$4"
|
|
311
|
+
[[ "$feature_id" =~ ^[0-9]+$ ]] || { echo "invalid feature id (expected an integer)" >&2; return 1; }
|
|
312
|
+
|
|
313
|
+
local existing_fs_id
|
|
314
|
+
existing_fs_id=$(read_feature_segment_id "$env_id" "$feature_id" "$segment_id")
|
|
315
|
+
|
|
316
|
+
local body
|
|
317
|
+
if [[ -z "$existing_fs_id" ]]; then
|
|
318
|
+
# First-time override: feature_states_to_create.
|
|
319
|
+
body=$(printf '{"feature_states_to_create":[{"feature_segment":{"segment":%d},"enabled":%s,"feature_state_value":{"type":"unicode","string_value":null,"integer_value":null,"boolean_value":null}}],"feature_states_to_update":[],"segment_ids_to_delete_overrides":[],"publish_immediately":true}' "$segment_id" "$enabled")
|
|
320
|
+
else
|
|
321
|
+
# Existing override: feature_states_to_update referencing the fs_id.
|
|
322
|
+
body=$(printf '{"feature_states_to_create":[],"feature_states_to_update":[{"feature_segment":{"id":%d},"enabled":%s,"feature_state_value":{"type":"unicode","string_value":null,"integer_value":null,"boolean_value":null}}],"segment_ids_to_delete_overrides":[],"publish_immediately":true}' "$existing_fs_id" "$enabled")
|
|
323
|
+
fi
|
|
324
|
+
|
|
325
|
+
local resp
|
|
326
|
+
resp=$(fs_api -X POST "${FLAGSMITH_API}/environments/${env_id}/features/${feature_id}/versions/" -d "$body")
|
|
327
|
+
echo "$resp" | python3 -c '
|
|
328
|
+
import json, sys
|
|
329
|
+
d = json.load(sys.stdin)
|
|
330
|
+
if not d.get("uuid"):
|
|
331
|
+
print(json.dumps(d), file=sys.stderr)
|
|
332
|
+
sys.exit(3)
|
|
333
|
+
print("ok:", d["uuid"])
|
|
334
|
+
' || exit 3
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
doppler_mirror() {
|
|
338
|
+
local config="$1" value="$2"
|
|
339
|
+
# Write via stdin so the value never appears in process listing.
|
|
340
|
+
printf '%s' "$value" | doppler secrets set "${ENV_VAR}" -p soleur -c "$config" --silent || return 4
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
# The single gate every write arm passes (role, --org, --detach-shared). The ack
|
|
344
|
+
# has no skip variable and no flag (#8486, ADR-249); it must return BEFORE the
|
|
345
|
+
# arm's WORM audit append, which refuses to run without it.
|
|
346
|
+
gate_or_confirm() {
|
|
347
|
+
if [[ $DRY_RUN -eq 1 ]]; then
|
|
348
|
+
echo "(dry-run — exiting 0 without mutation)"
|
|
349
|
+
exit 0
|
|
350
|
+
fi
|
|
351
|
+
echo
|
|
352
|
+
soleur_op_ack_or_die "Apply the change above to production Flagsmith (dev + prd) now? Type yes: "
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
# audit_append <target> [before_override] [after_override]
|
|
356
|
+
# WORM append-before-flip. before/after default to the on/off semantics of $VALUE;
|
|
357
|
+
# pass explicit overrides for a mutation that does NOT change enablement (e.g. a
|
|
358
|
+
# --detach-shared segment-config change where the feature stays enabled=true).
|
|
359
|
+
audit_append() {
|
|
360
|
+
local audit_target="$1" before_override="${2:-}" after_override="${3:-}"
|
|
361
|
+
local actor audit_url audit_srk before_bool after_bool audit_id
|
|
362
|
+
actor=$(doppler secrets get OPERATOR_EMAIL -p soleur -c cli_ops --plain 2>/dev/null | tr '[:upper:]' '[:lower:]')
|
|
363
|
+
[[ -z "$actor" ]] && { echo "FATAL: OPERATOR_EMAIL not in Doppler soleur/cli_ops" >&2; exit 4; }
|
|
364
|
+
# `|| true` normalizes a Doppler auth/network failure to the exit-4 contract.
|
|
365
|
+
audit_url=$(doppler secrets get SUPABASE_URL -p soleur -c dev --plain 2>/dev/null) || true
|
|
366
|
+
audit_srk=$(doppler secrets get SUPABASE_SERVICE_ROLE_KEY -p soleur -c dev --plain 2>/dev/null) || true
|
|
367
|
+
[[ -z "$audit_url" || -z "$audit_srk" ]] && { echo "FATAL: SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEY not in Doppler soleur/dev" >&2; exit 4; }
|
|
368
|
+
before_bool=$([[ "$VALUE" == "on" ]] && echo "false" || echo "true")
|
|
369
|
+
after_bool=$([[ "$VALUE" == "on" ]] && echo "true" || echo "false")
|
|
370
|
+
[[ -n "$before_override" ]] && before_bool="$before_override"
|
|
371
|
+
[[ -n "$after_override" ]] && after_bool="$after_override"
|
|
372
|
+
# The WORM row is the compliance record — never append a malformed bool.
|
|
373
|
+
case "$before_bool" in true|false) ;; *) echo "FATAL: audit before_bool must be true|false (got: '$before_bool')" >&2; exit 4 ;; esac
|
|
374
|
+
case "$after_bool" in true|false) ;; *) echo "FATAL: audit after_bool must be true|false (got: '$after_bool')" >&2; exit 4 ;; esac
|
|
375
|
+
audit_id=$(audit_flag_flip_rpc "$audit_url" "$audit_srk" "$FLAG" "$ROLE" "$audit_target" "$VALUE" "$before_bool" "$after_bool" "$actor") || exit 4
|
|
376
|
+
echo " audit_id=$audit_id"
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
# Idempotently provision a feature's OWN org segment `<flag>-orgs` (ADR-043
|
|
380
|
+
# per-feature scoping, 2026-05-29): create the segment with the ALL→ANY/EQUAL-orgId
|
|
381
|
+
# envelope (zero conditions initially) if absent, then ensure an ON feature-state
|
|
382
|
+
# override for the feature on that segment in BOTH envs. Echoes the segment id on
|
|
383
|
+
# stdout (progress to stderr). Idempotent: re-running converges (no churn when the
|
|
384
|
+
# override is already ON). The per-org gate is the segment's conditions, added later.
|
|
385
|
+
provision_feature_segment() {
|
|
386
|
+
local flag="$1"
|
|
387
|
+
local seg_name="${flag}-orgs"
|
|
388
|
+
local seg_id feat_id env_id cur resp body
|
|
389
|
+
seg_id=$(resolve_segment_id "$seg_name" 2>/dev/null) || seg_id=""
|
|
390
|
+
if [[ -z "$seg_id" ]]; then
|
|
391
|
+
echo " → creating segment '$seg_name'…" >&2
|
|
392
|
+
# Same rule envelope as org-targeted (flip.sh source-of-truth), zero conditions.
|
|
393
|
+
body=$(python3 -c "
|
|
394
|
+
import json
|
|
395
|
+
print(json.dumps({
|
|
396
|
+
'name': '${seg_name}',
|
|
397
|
+
'project': ${FLAGSMITH_PROJECT_ID},
|
|
398
|
+
'rules': [{'type':'ALL','rules':[{'type':'ANY','rules':[],'conditions':[]}],'conditions':[]}],
|
|
399
|
+
}))")
|
|
400
|
+
# The body rides argv (-d "$body"), NOT stdin: stdin is curl's config channel (fs_api feeds the
|
|
401
|
+
# Api-Key header there), so a `-d @-` body would be read as config. A segment definition is not a credential.
|
|
402
|
+
resp=$(fs_api -X POST "${FLAGSMITH_API}/projects/${FLAGSMITH_PROJECT_ID}/segments/" -d "$body") \
|
|
403
|
+
|| { echo "failed to create segment $seg_name" >&2; return 3; }
|
|
404
|
+
seg_id=$(echo "$resp" | python3 -c 'import json,sys; d=json.load(sys.stdin); print(d.get("id",""))') \
|
|
405
|
+
|| { echo "segment create response not JSON: $resp" >&2; return 3; }
|
|
406
|
+
[[ -n "$seg_id" ]] || { echo "segment create returned no id: $resp" >&2; return 3; }
|
|
407
|
+
echo " created $seg_name segment_id=$seg_id" >&2
|
|
408
|
+
else
|
|
409
|
+
echo " → segment '$seg_name' exists (id=$seg_id)" >&2
|
|
410
|
+
fi
|
|
411
|
+
feat_id=$(resolve_feature_id "$flag") || { echo "feature '$flag' not found in Flagsmith" >&2; return 3; }
|
|
412
|
+
# ON override in BOTH envs (idempotent: only publish when not already ON).
|
|
413
|
+
for env_id in "$FLAGSMITH_ENV_DEV_ID" "$FLAGSMITH_ENV_PRD_ID"; do
|
|
414
|
+
cur=$(read_segment_state "$env_id" "$feat_id" "$seg_id")
|
|
415
|
+
if [[ "$cur" != "true" ]]; then
|
|
416
|
+
echo " → ON override for '$flag' on '$seg_name' (env $env_id)…" >&2
|
|
417
|
+
flip_segment_in_env "$env_id" "$feat_id" "$seg_id" true >/dev/null
|
|
418
|
+
fi
|
|
419
|
+
done
|
|
420
|
+
printf '%s' "$seg_id"
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
# Verification identity role trait. Deliberately a value that matches NO role
|
|
424
|
+
# segment (role-prd/role-dev match role=='prd'/'dev'), so eval-verify isolates the
|
|
425
|
+
# PER-ORG segment gate (`<flag>-orgs` / org-targeted match on orgId, role-independent)
|
|
426
|
+
# from any role-segment rollout. Without this, a flag carrying a role-<env>=ON override
|
|
427
|
+
# would evaluate enabled for EVERY identity and the control-negative assertion would
|
|
428
|
+
# fire spuriously. [review: code-reviewer "Important" / user-impact F4]
|
|
429
|
+
readonly FLAG_VERIFY_ROLE="__flag-verify__"
|
|
430
|
+
|
|
431
|
+
# Evaluate <flag> for a transient identity carrying the orgId trait, mirroring the
|
|
432
|
+
# production getIdentityFlags() path (edge.api.flagsmith.com /identities/). Echoes
|
|
433
|
+
# "true"/"false" ONLY on a 2xx; returns 3 on any transport OR HTTP error so the
|
|
434
|
+
# verify FAILS LOUD instead of fail-open. This is the FR8 load-bearing check: a correct
|
|
435
|
+
# segment membership is NOT proof the flag is enabled (override missing / one-env-only
|
|
436
|
+
# leaves it OFF) — re-verify reads the EVALUATED flag, not the membership set.
|
|
437
|
+
# [review P0: do NOT treat an error body's missing flag as "disabled".]
|
|
438
|
+
eval_flag_enabled() { # $1=env_key $2=flag $3=orgId
|
|
439
|
+
local env_key="$1" flag="$2" org="$3" body resp code payload
|
|
440
|
+
body=$(python3 -c "
|
|
441
|
+
import json
|
|
442
|
+
print(json.dumps({
|
|
443
|
+
'identifier': 'org:${org}:${FLAG_VERIFY_ROLE}',
|
|
444
|
+
'traits': [
|
|
445
|
+
{'trait_key':'role','trait_value':'${FLAG_VERIFY_ROLE}'},
|
|
446
|
+
{'trait_key':'orgId','trait_value':'${org}'},
|
|
447
|
+
],
|
|
448
|
+
'transient': True,
|
|
449
|
+
}))")
|
|
450
|
+
# --disable/--noproxy here too (#7898 review). env_key is the PUBLIC client-side
|
|
451
|
+
# Flagsmith key, so the leak value is low -- but the linter missed this call only
|
|
452
|
+
# because X-Environment-Key is absent from CURL_AUTH_HEADER, not because it is
|
|
453
|
+
# exempt, and "every credentialed curl in these files is confined" should be true
|
|
454
|
+
# as written rather than true-with-an-asterisk.
|
|
455
|
+
resp=$(curl --disable --noproxy '*' -sS -w '\n%{http_code}' -X POST "${FLAGSMITH_EDGE_API}/identities/" \
|
|
456
|
+
-H "X-Environment-Key: ${env_key}" -H "Content-Type: application/json" \
|
|
457
|
+
-d "$body") || { echo "eval request failed (curl transport) for org $org" >&2; return 3; }
|
|
458
|
+
code=$(printf '%s' "$resp" | tail -n1)
|
|
459
|
+
payload=$(printf '%s' "$resp" | sed '$d')
|
|
460
|
+
[[ "$code" =~ ^2[0-9][0-9]$ ]] \
|
|
461
|
+
|| { echo "eval HTTP $code for org $org (treating as UNVERIFIED, not disabled): $payload" >&2; return 3; }
|
|
462
|
+
printf '%s' "$payload" | FS_FLAG="$flag" python3 -c "
|
|
463
|
+
import json, os, sys
|
|
464
|
+
flag = os.environ['FS_FLAG']
|
|
465
|
+
try:
|
|
466
|
+
d = json.load(sys.stdin)
|
|
467
|
+
except Exception:
|
|
468
|
+
print('eval response not JSON', file=sys.stderr); sys.exit(3)
|
|
469
|
+
for f in d.get('flags', []):
|
|
470
|
+
if f.get('feature', {}).get('name') == flag:
|
|
471
|
+
print(str(bool(f.get('enabled'))).lower()); sys.exit(0)
|
|
472
|
+
print('false') # 2xx + flag absent -> authoritatively not enabled for this identity
|
|
473
|
+
"
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
# Poll eval until it matches the expected value (edge propagation is eventual).
|
|
477
|
+
# Echoes the final observed value; returns 0 on match within budget, 1 otherwise.
|
|
478
|
+
# Cadence overridable for tests (EVAL_POLL_TRIES / EVAL_POLL_SLEEP); live default
|
|
479
|
+
# ~24s budget. No sleep after the final attempt.
|
|
480
|
+
eval_until() { # $1=env_key $2=flag $3=org $4=expected
|
|
481
|
+
local got i tries="${EVAL_POLL_TRIES:-12}" naptime="${EVAL_POLL_SLEEP:-2}"
|
|
482
|
+
for i in $(seq 1 "$tries"); do
|
|
483
|
+
got=$(eval_flag_enabled "$1" "$2" "$3") || return 3
|
|
484
|
+
[[ "$got" == "$4" ]] && { printf '%s' "$got"; return 0; }
|
|
485
|
+
[[ "$i" -lt "$tries" ]] && sleep "$naptime"
|
|
486
|
+
done
|
|
487
|
+
printf '%s' "$got"; return 1
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
# Detach a feature's override from a SHARED segment (e.g. the legacy `org-targeted`)
|
|
491
|
+
# by publishing a new version with `segment_ids_to_delete_overrides:[<shared id>]`
|
|
492
|
+
# (empty create/update arrays) in BOTH envs. Idempotent: a no-op for any env where
|
|
493
|
+
# no override row exists, and a clean no-op if the shared segment is already gone.
|
|
494
|
+
# Echoes the resolved shared segment id (empty if absent). Progress to stderr.
|
|
495
|
+
detach_from_shared() { # $1=flag $2=shared_segment_name
|
|
496
|
+
local flag="$1" shared_name="$2"
|
|
497
|
+
local feat_id shared_id env_id existing_fs_id body resp
|
|
498
|
+
feat_id=$(resolve_feature_id "$flag") || { echo "feature '$flag' not found in Flagsmith" >&2; return 3; }
|
|
499
|
+
shared_id=$(resolve_segment_id "$shared_name" 2>/dev/null) || shared_id=""
|
|
500
|
+
if [[ -z "$shared_id" ]]; then
|
|
501
|
+
echo " → shared segment '$shared_name' not found — nothing to detach (no-op)" >&2
|
|
502
|
+
printf ''; return 0
|
|
503
|
+
fi
|
|
504
|
+
for env_id in "$FLAGSMITH_ENV_DEV_ID" "$FLAGSMITH_ENV_PRD_ID"; do
|
|
505
|
+
existing_fs_id=$(read_feature_segment_id "$env_id" "$feat_id" "$shared_id")
|
|
506
|
+
if [[ -z "$existing_fs_id" ]]; then
|
|
507
|
+
echo " → no '$flag' override on '$shared_name' in env $env_id (already detached)" >&2
|
|
508
|
+
continue
|
|
509
|
+
fi
|
|
510
|
+
echo " → detaching '$flag' from '$shared_name' in env $env_id…" >&2
|
|
511
|
+
body=$(printf '{"feature_states_to_create":[],"feature_states_to_update":[],"segment_ids_to_delete_overrides":[%d],"publish_immediately":true}' "$shared_id")
|
|
512
|
+
resp=$(fs_api -X POST "${FLAGSMITH_API}/environments/${env_id}/features/${feat_id}/versions/" -d "$body")
|
|
513
|
+
echo "$resp" | python3 -c '
|
|
514
|
+
import json, sys
|
|
515
|
+
d = json.load(sys.stdin)
|
|
516
|
+
if not d.get("uuid"):
|
|
517
|
+
print(json.dumps(d), file=sys.stderr); sys.exit(3)
|
|
518
|
+
' || { echo "detach POST failed (env $env_id): $resp" >&2; return 3; }
|
|
519
|
+
done
|
|
520
|
+
printf '%s' "$shared_id"
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
# --- detach-from-shared branch (#4617, ADR-043 §"Per-feature segment scoping") ---
|
|
524
|
+
# Migrate a feature OFF the legacy shared `org-targeted` segment. The feature must
|
|
525
|
+
# already be served by its own `<flag>-orgs` segment (provision via the --org path
|
|
526
|
+
# FIRST); this removes its override on the shared segment, then eval-verifies the
|
|
527
|
+
# feature STILL resolves enabled=true for the member org (now via <flag>-orgs) and
|
|
528
|
+
# enabled=false for a control org (no leak). No Doppler mirror (segment-config change).
|
|
529
|
+
if [[ $DETACH_SHARED -eq 1 ]]; then
|
|
530
|
+
[[ "$TARGET_TYPE" != "org" || -z "$TARGET_ORG" ]] \
|
|
531
|
+
&& { echo "--detach-shared requires --org <member-uuid> (the org to eval-verify stays enabled after detach)" >&2; exit 2; }
|
|
532
|
+
[[ "$VALUE" != "on" ]] \
|
|
533
|
+
&& { echo "--detach-shared requires value 'on' (the feature must remain enabled for the member after detach)" >&2; exit 2; }
|
|
534
|
+
ENV_KEY=$([[ "$ROLE" == "prd" ]] && echo "$FLAGSMITH_ENV_PRD_KEY" || echo "$FLAGSMITH_ENV_DEV_KEY")
|
|
535
|
+
|
|
536
|
+
echo "→ Detach '$FLAG' from shared segment '$SHARED_SEGMENT_NAME' (both envs)"
|
|
537
|
+
echo "→ Member to eval-verify stays enabled: $TARGET_ORG (control: $CONTROL_ORG)"
|
|
538
|
+
|
|
539
|
+
if [[ $DRY_RUN -eq 1 ]]; then
|
|
540
|
+
echo "(dry-run — would detach '$FLAG' from '$SHARED_SEGMENT_NAME' in both envs, then"
|
|
541
|
+
echo " eval-verify member enabled=true / control enabled=false; exiting 0 without mutation)"
|
|
542
|
+
exit 0
|
|
543
|
+
fi
|
|
544
|
+
gate_or_confirm
|
|
545
|
+
|
|
546
|
+
# Append-before-flip: WORM audit precedes the first Flagsmith mutation. Enablement
|
|
547
|
+
# is UNCHANGED (the feature stays ON, now served by <flag>-orgs) → before=after=true.
|
|
548
|
+
audit_append "detach:$SHARED_SEGMENT_NAME" true true
|
|
549
|
+
|
|
550
|
+
echo "→ Detaching from '$SHARED_SEGMENT_NAME'…"
|
|
551
|
+
detach_from_shared "$FLAG" "$SHARED_SEGMENT_NAME" >/dev/null || exit 3
|
|
552
|
+
|
|
553
|
+
# Eval-layer re-verify (the load-bearing check): the feature must STILL evaluate
|
|
554
|
+
# enabled for the member (served by <flag>-orgs now) and disabled for control.
|
|
555
|
+
echo "→ Eval-verify ($ROLE env): $FLAG for member $TARGET_ORG must STILL be enabled=true…"
|
|
556
|
+
GOT_TARGET=$(eval_until "$ENV_KEY" "$FLAG" "$TARGET_ORG" "true") || {
|
|
557
|
+
echo "EVAL VERIFY FAILED: $FLAG for member $TARGET_ORG expected enabled=true after detach, last observed=$GOT_TARGET" >&2
|
|
558
|
+
echo " (the detach dropped the member — its <flag>-orgs override is missing/one-env-only, OR the edge endpoint errored. UNVERIFIED — investigate; the member may have LOST the feature.)" >&2
|
|
559
|
+
exit 3
|
|
560
|
+
}
|
|
561
|
+
echo " ✓ member $TARGET_ORG: enabled=$GOT_TARGET"
|
|
562
|
+
|
|
563
|
+
echo "→ Eval-verify ($ROLE env): control org $CONTROL_ORG must settle to enabled=false (no leak)…"
|
|
564
|
+
GOT_CONTROL=$(eval_until "$ENV_KEY" "$FLAG" "$CONTROL_ORG" "false") || {
|
|
565
|
+
echo "EVAL VERIFY FAILED (control leak): $FLAG did not settle to disabled for control org $CONTROL_ORG within the propagation budget (last observed=$GOT_CONTROL) — the flag is reaching a non-targeted org, OR the edge endpoint errored. UNVERIFIED." >&2
|
|
566
|
+
exit 3
|
|
567
|
+
}
|
|
568
|
+
echo " ✓ control $CONTROL_ORG: enabled=$GOT_CONTROL"
|
|
569
|
+
|
|
570
|
+
echo
|
|
571
|
+
echo "✓ Done. '$FLAG' detached from '$SHARED_SEGMENT_NAME' + eval-verified (served by"
|
|
572
|
+
echo " ${FLAG}-orgs now). Segment changes are immediate; edge eval propagation completes within seconds."
|
|
573
|
+
exit 0
|
|
574
|
+
fi
|
|
575
|
+
|
|
576
|
+
# --- org-targeting branch (ADR-043 per-feature segment scoping) -------------
|
|
577
|
+
# When --org is provided, target the feature's OWN segment `<flag>-orgs` (NOT the
|
|
578
|
+
# shared org-targeted segment): provision it (segment + ON override both envs), then
|
|
579
|
+
# add/remove the org's EQUAL orgId condition, then re-verify by EVALUATING the flag
|
|
580
|
+
# for the target org (must be enabled) and a control org (must be disabled). No Doppler
|
|
581
|
+
# mirror — per-org segment membership is invisible to the FLAG_* env-var fallback
|
|
582
|
+
# (ADR-038/ADR-043: a per-org-only flag falls back OFF on a Flagsmith outage).
|
|
583
|
+
if [[ "$TARGET_TYPE" == "org" ]]; then
|
|
584
|
+
SEG_NAME="${FLAG}-orgs"
|
|
585
|
+
echo "→ Per-feature org segment: $SEG_NAME"
|
|
586
|
+
ENV_KEY=$([[ "$ROLE" == "prd" ]] && echo "$FLAGSMITH_ENV_PRD_KEY" || echo "$FLAGSMITH_ENV_DEV_KEY")
|
|
587
|
+
|
|
588
|
+
# Read current membership (segment may not exist yet -> empty).
|
|
589
|
+
PRE_SEG_ID=$(resolve_segment_id "$SEG_NAME" 2>/dev/null) || PRE_SEG_ID=""
|
|
590
|
+
CURRENT_ORGS=""
|
|
591
|
+
if [[ -n "$PRE_SEG_ID" ]]; then
|
|
592
|
+
PRE_JSON=$(fs_api "${FLAGSMITH_API}/projects/${FLAGSMITH_PROJECT_ID}/segments/${PRE_SEG_ID}/") \
|
|
593
|
+
|| { echo "failed to read segment $PRE_SEG_ID" >&2; exit 3; }
|
|
594
|
+
CURRENT_ORGS=$(echo "$PRE_JSON" | python3 -c "
|
|
595
|
+
import json, sys
|
|
596
|
+
seg = json.load(sys.stdin)
|
|
597
|
+
conditions = seg['rules'][0]['rules'][0]['conditions']
|
|
598
|
+
orgs = []
|
|
599
|
+
for c in conditions:
|
|
600
|
+
if c.get('operator') != 'EQUAL' or c.get('property') != 'orgId':
|
|
601
|
+
print(f\"unexpected condition: operator={c.get('operator')} property={c.get('property')}\", file=sys.stderr)
|
|
602
|
+
sys.exit(3)
|
|
603
|
+
orgs.append(c['value'])
|
|
604
|
+
print(','.join(orgs) if orgs else '')
|
|
605
|
+
") || { echo "failed to parse $SEG_NAME rules (unexpected structure)" >&2; exit 3; }
|
|
606
|
+
fi
|
|
607
|
+
|
|
608
|
+
# Compute new membership (add on / remove off). No early-exit on 'already present':
|
|
609
|
+
# the override may still be missing, so we always provision + eval-verify.
|
|
610
|
+
NEW_ORGS=$(CURRENT_ORGS="$CURRENT_ORGS" TARGET_ORG="$TARGET_ORG" VALUE="$VALUE" python3 -c "
|
|
611
|
+
import os
|
|
612
|
+
current = os.environ['CURRENT_ORGS']
|
|
613
|
+
target = os.environ['TARGET_ORG']
|
|
614
|
+
action = os.environ['VALUE']
|
|
615
|
+
orgs = [x for x in current.split(',') if x] if current else []
|
|
616
|
+
if action == 'on':
|
|
617
|
+
if target not in orgs: orgs.append(target)
|
|
618
|
+
else:
|
|
619
|
+
orgs = [o for o in orgs if o != target]
|
|
620
|
+
print(','.join(orgs))
|
|
621
|
+
") || exit 3
|
|
622
|
+
|
|
623
|
+
echo "→ Current membership of $SEG_NAME:"
|
|
624
|
+
if [[ -z "$CURRENT_ORGS" ]]; then echo " (empty)"; else echo "$CURRENT_ORGS" | tr ',' '\n' | sed 's/^/ /'; fi
|
|
625
|
+
echo "→ Proposed: $VALUE orgId $TARGET_ORG (control: $CONTROL_ORG)"
|
|
626
|
+
echo "→ New membership:"
|
|
627
|
+
if [[ -z "$NEW_ORGS" ]]; then echo " (empty — feature OFF for all via $SEG_NAME)"; else echo "$NEW_ORGS" | tr ',' '\n' | sed 's/^/ /'; fi
|
|
628
|
+
|
|
629
|
+
if [[ $DRY_RUN -eq 1 ]]; then
|
|
630
|
+
echo "(dry-run — would provision $SEG_NAME (+ON override both envs), set membership above, then eval-verify; exiting 0 without mutation)"
|
|
631
|
+
exit 0
|
|
632
|
+
fi
|
|
633
|
+
gate_or_confirm
|
|
634
|
+
|
|
635
|
+
# Append-before-flip: WORM audit precedes the first Flagsmith mutation (provision).
|
|
636
|
+
audit_append "org:$TARGET_ORG"
|
|
637
|
+
|
|
638
|
+
echo "→ Provisioning $SEG_NAME (segment + ON override both envs)…"
|
|
639
|
+
ORG_SEG_ID=$(provision_feature_segment "$FLAG") || exit 3
|
|
640
|
+
|
|
641
|
+
# Re-read immediately before the PUT to shrink the read-modify-write window (P1-1),
|
|
642
|
+
# then rebuild the conditions from the fresh read + target delta.
|
|
643
|
+
echo "→ Reading segment rules…"
|
|
644
|
+
SEG_JSON=$(fs_api "${FLAGSMITH_API}/projects/${FLAGSMITH_PROJECT_ID}/segments/${ORG_SEG_ID}/") \
|
|
645
|
+
|| { echo "failed to read segment $ORG_SEG_ID" >&2; exit 3; }
|
|
646
|
+
|
|
647
|
+
# Recompute the delta from the FRESH read (the segment may have just been created
|
|
648
|
+
# empty by provision_feature_segment). One EQUAL orgId condition per org in the ANY
|
|
649
|
+
# rule — NOT a single IN condition (org-targeted source-of-truth, flip.sh history).
|
|
650
|
+
UPDATED_JSON=$(echo "$SEG_JSON" | TARGET_ORG="$TARGET_ORG" VALUE="$VALUE" python3 -c "
|
|
651
|
+
import json, os, sys
|
|
652
|
+
seg = json.load(sys.stdin)
|
|
653
|
+
target = os.environ['TARGET_ORG']
|
|
654
|
+
action = os.environ['VALUE']
|
|
655
|
+
anyrule = seg['rules'][0]['rules'][0]
|
|
656
|
+
orgs = []
|
|
657
|
+
for c in anyrule['conditions']:
|
|
658
|
+
if c.get('operator') != 'EQUAL' or c.get('property') != 'orgId':
|
|
659
|
+
print(f\"unexpected condition: operator={c.get('operator')} property={c.get('property')}\", file=sys.stderr)
|
|
660
|
+
sys.exit(3)
|
|
661
|
+
orgs.append(c['value'])
|
|
662
|
+
if action == 'on':
|
|
663
|
+
if target not in orgs: orgs.append(target)
|
|
664
|
+
else:
|
|
665
|
+
orgs = [o for o in orgs if o != target]
|
|
666
|
+
anyrule['conditions'] = [{'operator':'EQUAL','property':'orgId','value':o} for o in orgs]
|
|
667
|
+
json.dump(seg, sys.stdout)
|
|
668
|
+
") || { echo "failed to build updated $SEG_NAME JSON (unexpected structure)" >&2; exit 3; }
|
|
669
|
+
|
|
670
|
+
echo "→ Writing updated $SEG_NAME membership to Flagsmith…"
|
|
671
|
+
# Body on argv, not stdin (stdin is curl's config channel); the segment JSON is not a credential.
|
|
672
|
+
RESP=$(fs_api -X PUT "${FLAGSMITH_API}/projects/${FLAGSMITH_PROJECT_ID}/segments/${ORG_SEG_ID}/" -d "$UPDATED_JSON") \
|
|
673
|
+
|| { echo "PUT segment failed" >&2; exit 3; }
|
|
674
|
+
echo "$RESP" | python3 -c "
|
|
675
|
+
import json, sys
|
|
676
|
+
seg = json.load(sys.stdin)
|
|
677
|
+
if 'id' not in seg:
|
|
678
|
+
print(json.dumps(seg), file=sys.stderr); sys.exit(3)
|
|
679
|
+
print(' updated segment id=' + str(seg['id']))
|
|
680
|
+
" || exit 3
|
|
681
|
+
|
|
682
|
+
# --- eval-layer re-verify (FR8, the load-bearing fix) ---------------------
|
|
683
|
+
# Membership-set equality is NOT sufficient (override-missing / one-env-only leaves
|
|
684
|
+
# the flag OFF while the org is 'in' the segment). Evaluate the flag for a transient
|
|
685
|
+
# identity carrying the orgId trait (production getIdentityFlags path), asserting:
|
|
686
|
+
# target org -> enabled == (VALUE==on) [the per-org enable actually works]
|
|
687
|
+
# control org -> enabled == false [no leak to a non-targeted org]
|
|
688
|
+
EXPECT_TARGET=$([[ "$VALUE" == "on" ]] && echo true || echo false)
|
|
689
|
+
echo "→ Eval-verify ($ROLE env): $FLAG for target $TARGET_ORG must be enabled=$EXPECT_TARGET…"
|
|
690
|
+
GOT_TARGET=$(eval_until "$ENV_KEY" "$FLAG" "$TARGET_ORG" "$EXPECT_TARGET") || {
|
|
691
|
+
echo "EVAL VERIFY FAILED: $FLAG for org $TARGET_ORG expected enabled=$EXPECT_TARGET, last observed=$GOT_TARGET" >&2
|
|
692
|
+
echo " (membership was written, but the flag does not EVALUATE as expected — missing/one-env override OR the edge endpoint errored; this is UNVERIFIED, investigate before relying on it)" >&2
|
|
693
|
+
exit 3
|
|
694
|
+
}
|
|
695
|
+
echo " ✓ target $TARGET_ORG: enabled=$GOT_TARGET"
|
|
696
|
+
|
|
697
|
+
# Poll until the control SETTLES to disabled — same propagation tolerance as the
|
|
698
|
+
# target check. The edge environment document is eventually consistent: right after a
|
|
699
|
+
# segment+override create, a non-member org can transiently read enabled=true for one
|
|
700
|
+
# refresh window before the segment's orgId condition propagates. A single-shot read
|
|
701
|
+
# here false-positives on that window. A genuine leak never settles to false → the
|
|
702
|
+
# poll exhausts its budget and fails loud (and an HTTP error returns 3 → exit 3).
|
|
703
|
+
echo "→ Eval-verify ($ROLE env): control org $CONTROL_ORG must settle to enabled=false (no leak)…"
|
|
704
|
+
GOT_CONTROL=$(eval_until "$ENV_KEY" "$FLAG" "$CONTROL_ORG" "false") || {
|
|
705
|
+
echo "EVAL VERIFY FAILED (control leak): $FLAG did not settle to disabled for control org $CONTROL_ORG within the propagation budget (last observed=$GOT_CONTROL) — the flag is reaching a non-targeted org, OR the edge endpoint errored. UNVERIFIED." >&2
|
|
706
|
+
exit 3
|
|
707
|
+
}
|
|
708
|
+
echo " ✓ control $CONTROL_ORG: enabled=$GOT_CONTROL"
|
|
709
|
+
|
|
710
|
+
echo
|
|
711
|
+
echo "✓ Done. $SEG_NAME membership updated + flag eval-verified. Segment changes are"
|
|
712
|
+
echo " immediate; edge eval propagation completes within seconds."
|
|
713
|
+
exit 0
|
|
714
|
+
fi
|
|
715
|
+
|
|
716
|
+
# --- resolve ---------------------------------------------------------------
|
|
717
|
+
echo "→ Resolving feature '$FLAG' + segment '$SEGMENT_NAME' in Flagsmith…"
|
|
718
|
+
FEATURE_ID=$(resolve_feature_id "$FLAG") || { echo "feature '$FLAG' not found in Flagsmith" >&2; exit 3; }
|
|
719
|
+
SEG_PRD=$(resolve_segment_id "role-prd") || exit 3
|
|
720
|
+
SEG_DEV=$(resolve_segment_id "role-dev") || exit 3
|
|
721
|
+
SEG_TARGET=$([[ "$ROLE" == "prd" ]] && echo "$SEG_PRD" || echo "$SEG_DEV")
|
|
722
|
+
|
|
723
|
+
echo " feature_id=$FEATURE_ID role-prd=$SEG_PRD role-dev=$SEG_DEV"
|
|
724
|
+
|
|
725
|
+
# --- read current state (both envs, both segments) -------------------------
|
|
726
|
+
echo "→ Current state:"
|
|
727
|
+
CUR_DEV_PRD=$(read_segment_state "$FLAGSMITH_ENV_DEV_ID" "$FEATURE_ID" "$SEG_PRD")
|
|
728
|
+
CUR_DEV_DEV=$(read_segment_state "$FLAGSMITH_ENV_DEV_ID" "$FEATURE_ID" "$SEG_DEV")
|
|
729
|
+
CUR_PRD_PRD=$(read_segment_state "$FLAGSMITH_ENV_PRD_ID" "$FEATURE_ID" "$SEG_PRD")
|
|
730
|
+
CUR_PRD_DEV=$(read_segment_state "$FLAGSMITH_ENV_PRD_ID" "$FEATURE_ID" "$SEG_DEV")
|
|
731
|
+
printf ' %-12s %-12s %-12s\n' "env" "role-prd" "role-dev"
|
|
732
|
+
printf ' %-12s %-12s %-12s\n' "dev" "$CUR_DEV_PRD" "$CUR_DEV_DEV"
|
|
733
|
+
printf ' %-12s %-12s %-12s\n' "prd" "$CUR_PRD_PRD" "$CUR_PRD_DEV"
|
|
734
|
+
|
|
735
|
+
# --- fallback-fidelity rule -------------------------------------------------
|
|
736
|
+
if [[ "$ROLE" == "dev" && "$VALUE" == "off" ]]; then
|
|
737
|
+
if [[ "$CUR_DEV_PRD" == "true" || "$CUR_PRD_PRD" == "true" ]]; then
|
|
738
|
+
echo >&2
|
|
739
|
+
echo "REJECTED: cannot set dev off while prd on — env-var fallback cannot represent" >&2
|
|
740
|
+
echo "this state (see ADR-038 v2 §Fallback semantics). Flip prd off first." >&2
|
|
741
|
+
exit 1
|
|
742
|
+
fi
|
|
743
|
+
fi
|
|
744
|
+
|
|
745
|
+
# --- proposed delta --------------------------------------------------------
|
|
746
|
+
echo "→ Proposed: $FLAG / role=$ROLE → $VALUE (in BOTH dev and prd envs)"
|
|
747
|
+
if [[ "$ROLE" == "prd" ]]; then
|
|
748
|
+
echo " + Doppler mirror: FLAG_${FLAG^^}=${VALUE} in soleur/dev AND soleur/prd"
|
|
749
|
+
echo " (replacing $(doppler secrets get $ENV_VAR -p soleur -c prd --plain 2>/dev/null || echo unset) / $(doppler secrets get $ENV_VAR -p soleur -c dev --plain 2>/dev/null || echo unset))"
|
|
750
|
+
fi
|
|
751
|
+
|
|
752
|
+
gate_or_confirm
|
|
753
|
+
|
|
754
|
+
# --- audit append (WORM) ---------------------------------------------------
|
|
755
|
+
audit_append "role:$ROLE"
|
|
756
|
+
|
|
757
|
+
# --- write Flagsmith (both envs) -------------------------------------------
|
|
758
|
+
echo "→ Writing Flagsmith dev env…"
|
|
759
|
+
flip_segment_in_env "$FLAGSMITH_ENV_DEV_ID" "$FEATURE_ID" "$SEG_TARGET" "$PROPOSED_ENABLED"
|
|
760
|
+
echo "→ Writing Flagsmith prd env…"
|
|
761
|
+
flip_segment_in_env "$FLAGSMITH_ENV_PRD_ID" "$FEATURE_ID" "$SEG_TARGET" "$PROPOSED_ENABLED"
|
|
762
|
+
|
|
763
|
+
# --- mirror Doppler (only on prd flips) -------------------------------------
|
|
764
|
+
if [[ "$ROLE" == "prd" ]]; then
|
|
765
|
+
DOPPLER_VAL=$([[ "$VALUE" == "on" ]] && echo 1 || echo 0)
|
|
766
|
+
echo "→ Doppler: setting ${ENV_VAR}=${DOPPLER_VAL} in soleur/dev…"
|
|
767
|
+
doppler_mirror dev "$DOPPLER_VAL" || { echo "Doppler dev write FAILED" >&2; exit 4; }
|
|
768
|
+
echo "→ Doppler: setting ${ENV_VAR}=${DOPPLER_VAL} in soleur/prd…"
|
|
769
|
+
doppler_mirror prd "$DOPPLER_VAL" || { echo "Doppler prd write FAILED (dev already updated — manual fix needed)" >&2; exit 4; }
|
|
770
|
+
fi
|
|
771
|
+
|
|
772
|
+
echo
|
|
773
|
+
echo "✓ Done. Cache TTL is 30s per role — propagation per replica completes within 30s."
|
|
774
|
+
exit 0
|