@soleur/opencode 0.0.0-stage → 3.354.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (832) hide show
  1. package/.claude-plugin/agents.manifest.json +476 -0
  2. package/.claude-plugin/plugin.json +42 -0
  3. package/README.md +44 -2
  4. package/agents/community/.gitkeep +0 -0
  5. package/agents/engineering/cto.md +87 -0
  6. package/agents/engineering/design/ddd-architect.md +95 -0
  7. package/agents/engineering/discovery/agent-finder.md +214 -0
  8. package/agents/engineering/discovery/functional-discovery.md +182 -0
  9. package/agents/engineering/infra/infra-security.md +151 -0
  10. package/agents/engineering/infra/platform-strategist.md +109 -0
  11. package/agents/engineering/infra/terraform-architect.md +171 -0
  12. package/agents/engineering/prompt-engineer.md +37 -0
  13. package/agents/engineering/research/best-practices-researcher.md +115 -0
  14. package/agents/engineering/research/framework-docs-researcher.md +93 -0
  15. package/agents/engineering/research/git-history-analyzer.md +53 -0
  16. package/agents/engineering/research/learnings-researcher.md +285 -0
  17. package/agents/engineering/research/repo-research-analyst.md +118 -0
  18. package/agents/engineering/review/agent-native-reviewer.md +279 -0
  19. package/agents/engineering/review/architecture-strategist.md +61 -0
  20. package/agents/engineering/review/code-quality-analyst.md +92 -0
  21. package/agents/engineering/review/code-simplicity-reviewer.md +109 -0
  22. package/agents/engineering/review/data-integrity-guardian.md +82 -0
  23. package/agents/engineering/review/data-migration-expert.md +98 -0
  24. package/agents/engineering/review/deployment-verification-agent.md +183 -0
  25. package/agents/engineering/review/dhh-rails-reviewer.md +46 -0
  26. package/agents/engineering/review/kieran-rails-reviewer.md +87 -0
  27. package/agents/engineering/review/legacy-code-expert.md +55 -0
  28. package/agents/engineering/review/observability-coverage-reviewer.md +147 -0
  29. package/agents/engineering/review/pattern-recognition-specialist.md +59 -0
  30. package/agents/engineering/review/performance-oracle.md +130 -0
  31. package/agents/engineering/review/security-sentinel.md +167 -0
  32. package/agents/engineering/review/semgrep-sast.md +36 -0
  33. package/agents/engineering/review/test-design-reviewer.md +150 -0
  34. package/agents/engineering/review/user-impact-reviewer.md +70 -0
  35. package/agents/engineering/workflow/pr-comment-resolver.md +69 -0
  36. package/agents/finance/budget-analyst.md +29 -0
  37. package/agents/finance/cfo.md +44 -0
  38. package/agents/finance/financial-reporter.md +30 -0
  39. package/agents/finance/revenue-analyst.md +30 -0
  40. package/agents/legal/clo.md +115 -0
  41. package/agents/legal/legal-compliance-auditor.md +110 -0
  42. package/agents/legal/legal-document-generator.md +134 -0
  43. package/agents/marketing/analytics-analyst.md +25 -0
  44. package/agents/marketing/brand-architect.md +181 -0
  45. package/agents/marketing/cmo.md +76 -0
  46. package/agents/marketing/conversion-optimizer.md +19 -0
  47. package/agents/marketing/copywriter.md +20 -0
  48. package/agents/marketing/fact-checker.md +70 -0
  49. package/agents/marketing/growth-strategist.md +145 -0
  50. package/agents/marketing/paid-media-strategist.md +28 -0
  51. package/agents/marketing/pricing-strategist.md +17 -0
  52. package/agents/marketing/programmatic-seo-specialist.md +25 -0
  53. package/agents/marketing/retention-strategist.md +23 -0
  54. package/agents/marketing/seo-aeo-analyst.md +164 -0
  55. package/agents/operations/coo.md +50 -0
  56. package/agents/operations/ops-advisor.md +78 -0
  57. package/agents/operations/ops-provisioner.md +101 -0
  58. package/agents/operations/ops-research.md +52 -0
  59. package/agents/operations/service-automator.md +278 -0
  60. package/agents/product/business-validator.md +206 -0
  61. package/agents/product/competitive-intelligence.md +89 -0
  62. package/agents/product/cpo.md +63 -0
  63. package/agents/product/design/ux-design-lead.md +209 -0
  64. package/agents/product/spec-flow-analyzer.md +122 -0
  65. package/agents/sales/cro.md +44 -0
  66. package/agents/sales/deal-architect.md +29 -0
  67. package/agents/sales/outbound-strategist.md +29 -0
  68. package/agents/sales/pipeline-analyst.md +29 -0
  69. package/agents/support/cco.md +48 -0
  70. package/agents/support/community-manager.md +570 -0
  71. package/agents/support/ticket-triage.md +70 -0
  72. package/bin/soleur-opencode.js +29 -0
  73. package/commands/go.md +660 -0
  74. package/commands/help.md +273 -0
  75. package/commands/sync.md +977 -0
  76. package/commands/version.md +103 -0
  77. package/hooks/antigravity-hooks.json +84 -0
  78. package/hooks/antigravity-session-start.sh +15 -0
  79. package/hooks/bare-root-cwd-guard.sh +131 -0
  80. package/hooks/browser-snapshot-credential-guard.sh +200 -0
  81. package/hooks/codex-session-start.sh +9 -0
  82. package/hooks/compaction-state.sh +525 -0
  83. package/hooks/destructive-command-guard.sh +1387 -0
  84. package/hooks/devin-session-start.sh +90 -0
  85. package/hooks/ext-filter-detect.sh +171 -0
  86. package/hooks/hooks.json +133 -0
  87. package/hooks/lib/hook-tool-kind.sh +24 -0
  88. package/hooks/lib/log-rotation.sh +176 -0
  89. package/hooks/lib/shell-argv.pl +576 -0
  90. package/hooks/memory-backstop.sh +1114 -0
  91. package/hooks/operator-stage-approval.sh +391 -0
  92. package/hooks/stop-hook.sh +367 -0
  93. package/hooks/unkept-promise-hook.sh +383 -0
  94. package/hooks/version-banner.sh +127 -0
  95. package/hooks/welcome-hook.sh +50 -0
  96. package/install.js +29 -0
  97. package/lib/agent-registry.ts +154 -0
  98. package/lib/c4-canonical-cli.mjs +34 -0
  99. package/lib/c4-canonical.mjs +46 -0
  100. package/lib/c4-from-components.ts +554 -0
  101. package/lib/go-routing.ts +81 -0
  102. package/lib/grok-inspect-contract.ts +311 -0
  103. package/lib/harness-model-map.ts +101 -0
  104. package/lib/harness-parity.ts +726 -0
  105. package/lib/harness-tool-map.ts +180 -0
  106. package/lib/harness.ts +716 -0
  107. package/lib/heartbeat-live-reconcile.ts +1153 -0
  108. package/lib/heartbeat-manifest.ts +361 -0
  109. package/lib/kb-coverage.ts +252 -0
  110. package/lib/pr-merge-poll.ts +103 -0
  111. package/lib/workflow-fidelity.ts +388 -0
  112. package/opencode/INSTRUCTIONS.md +171 -0
  113. package/opencode/agents/engineering/cto.md +10 -0
  114. package/opencode/agents/engineering/design/ddd-architect.md +10 -0
  115. package/opencode/agents/engineering/discovery/agent-finder.md +10 -0
  116. package/opencode/agents/engineering/discovery/functional-discovery.md +10 -0
  117. package/opencode/agents/engineering/infra/infra-security.md +10 -0
  118. package/opencode/agents/engineering/infra/platform-strategist.md +10 -0
  119. package/opencode/agents/engineering/infra/terraform-architect.md +10 -0
  120. package/opencode/agents/engineering/prompt-engineer.md +10 -0
  121. package/opencode/agents/engineering/research/best-practices-researcher.md +10 -0
  122. package/opencode/agents/engineering/research/framework-docs-researcher.md +10 -0
  123. package/opencode/agents/engineering/research/git-history-analyzer.md +10 -0
  124. package/opencode/agents/engineering/research/learnings-researcher.md +10 -0
  125. package/opencode/agents/engineering/research/repo-research-analyst.md +10 -0
  126. package/opencode/agents/engineering/review/agent-native-reviewer.md +10 -0
  127. package/opencode/agents/engineering/review/architecture-strategist.md +10 -0
  128. package/opencode/agents/engineering/review/code-quality-analyst.md +10 -0
  129. package/opencode/agents/engineering/review/code-simplicity-reviewer.md +10 -0
  130. package/opencode/agents/engineering/review/data-integrity-guardian.md +10 -0
  131. package/opencode/agents/engineering/review/data-migration-expert.md +10 -0
  132. package/opencode/agents/engineering/review/deployment-verification-agent.md +10 -0
  133. package/opencode/agents/engineering/review/dhh-rails-reviewer.md +10 -0
  134. package/opencode/agents/engineering/review/kieran-rails-reviewer.md +10 -0
  135. package/opencode/agents/engineering/review/legacy-code-expert.md +10 -0
  136. package/opencode/agents/engineering/review/observability-coverage-reviewer.md +10 -0
  137. package/opencode/agents/engineering/review/pattern-recognition-specialist.md +10 -0
  138. package/opencode/agents/engineering/review/performance-oracle.md +10 -0
  139. package/opencode/agents/engineering/review/security-sentinel.md +10 -0
  140. package/opencode/agents/engineering/review/semgrep-sast.md +10 -0
  141. package/opencode/agents/engineering/review/test-design-reviewer.md +10 -0
  142. package/opencode/agents/engineering/review/user-impact-reviewer.md +10 -0
  143. package/opencode/agents/engineering/workflow/pr-comment-resolver.md +10 -0
  144. package/opencode/agents/finance/budget-analyst.md +10 -0
  145. package/opencode/agents/finance/cfo.md +10 -0
  146. package/opencode/agents/finance/financial-reporter.md +10 -0
  147. package/opencode/agents/finance/revenue-analyst.md +10 -0
  148. package/opencode/agents/legal/clo.md +10 -0
  149. package/opencode/agents/legal/legal-compliance-auditor.md +10 -0
  150. package/opencode/agents/legal/legal-document-generator.md +10 -0
  151. package/opencode/agents/marketing/analytics-analyst.md +10 -0
  152. package/opencode/agents/marketing/brand-architect.md +10 -0
  153. package/opencode/agents/marketing/cmo.md +10 -0
  154. package/opencode/agents/marketing/conversion-optimizer.md +10 -0
  155. package/opencode/agents/marketing/copywriter.md +10 -0
  156. package/opencode/agents/marketing/fact-checker.md +10 -0
  157. package/opencode/agents/marketing/growth-strategist.md +10 -0
  158. package/opencode/agents/marketing/paid-media-strategist.md +10 -0
  159. package/opencode/agents/marketing/pricing-strategist.md +10 -0
  160. package/opencode/agents/marketing/programmatic-seo-specialist.md +10 -0
  161. package/opencode/agents/marketing/retention-strategist.md +10 -0
  162. package/opencode/agents/marketing/seo-aeo-analyst.md +10 -0
  163. package/opencode/agents/operations/coo.md +10 -0
  164. package/opencode/agents/operations/ops-advisor.md +10 -0
  165. package/opencode/agents/operations/ops-provisioner.md +10 -0
  166. package/opencode/agents/operations/ops-research.md +10 -0
  167. package/opencode/agents/operations/service-automator.md +10 -0
  168. package/opencode/agents/product/business-validator.md +10 -0
  169. package/opencode/agents/product/competitive-intelligence.md +10 -0
  170. package/opencode/agents/product/cpo.md +10 -0
  171. package/opencode/agents/product/design/ux-design-lead.md +10 -0
  172. package/opencode/agents/product/spec-flow-analyzer.md +10 -0
  173. package/opencode/agents/sales/cro.md +10 -0
  174. package/opencode/agents/sales/deal-architect.md +10 -0
  175. package/opencode/agents/sales/outbound-strategist.md +10 -0
  176. package/opencode/agents/sales/pipeline-analyst.md +10 -0
  177. package/opencode/agents/support/cco.md +10 -0
  178. package/opencode/agents/support/community-manager.md +10 -0
  179. package/opencode/agents/support/ticket-triage.md +10 -0
  180. package/opencode/commands/go.md +10 -0
  181. package/opencode/commands/help.md +10 -0
  182. package/opencode/commands/sync.md +10 -0
  183. package/opencode/commands/version.md +11 -0
  184. package/opencode/opencode.json +8 -0
  185. package/opencode/plugin.ts +818 -0
  186. package/package.json +36 -4
  187. package/scripts/admin-merge-ready.sh +545 -0
  188. package/scripts/admin-merge-ready.test.sh +753 -0
  189. package/scripts/affected-tests.sh +908 -0
  190. package/scripts/affected-tests.test.sh +1797 -0
  191. package/scripts/alpha-metrics.sh +78 -0
  192. package/scripts/alpha-metrics.test.sh +115 -0
  193. package/scripts/ancestor-signal-helpers.baseline.txt +16 -0
  194. package/scripts/audit-flag-flip.sh +107 -0
  195. package/scripts/check-red-on-main.sh +316 -0
  196. package/scripts/ci-head-verdict.sh +411 -0
  197. package/scripts/cloud-detect.sh +193 -0
  198. package/scripts/delegation-verify.sh +657 -0
  199. package/scripts/delegation-verify.test.sh +1424 -0
  200. package/scripts/deploy-arm.sh +475 -0
  201. package/scripts/domain-model-drift.sh +419 -0
  202. package/scripts/emit-decision.sh +144 -0
  203. package/scripts/emit-decision.test.sh +144 -0
  204. package/scripts/ext-filter-divergence-corpus.txt +15 -0
  205. package/scripts/ext-filter-divergence.sh +189 -0
  206. package/scripts/generate-c4-from-components.ts +442 -0
  207. package/scripts/grok-fidelity-gate.sh +68 -0
  208. package/scripts/grok-pre-push-gate.sh +232 -0
  209. package/scripts/harness-discovery-smoke.ts +906 -0
  210. package/scripts/harness-parity-census.ts +92 -0
  211. package/scripts/inngest-luks-alert-armed-probe.ts +29 -0
  212. package/scripts/install-opencode-adapter.ts +509 -0
  213. package/scripts/lib/affected-tests/common.sh +365 -0
  214. package/scripts/lib/affected-tests/go.sh +157 -0
  215. package/scripts/lib/affected-tests/js.sh +188 -0
  216. package/scripts/lib/affected-tests/nx-turbo.sh +98 -0
  217. package/scripts/lib/affected-tests/python.sh +185 -0
  218. package/scripts/lib/context-sanitize.sh +49 -0
  219. package/scripts/lib/domain-model-lib.sh +180 -0
  220. package/scripts/lib/ext-filter-grammar.tsv +34 -0
  221. package/scripts/lib/operator-script.sh +1236 -0
  222. package/scripts/lib/proc.sh +580 -0
  223. package/scripts/lib/session-state.sh +748 -0
  224. package/scripts/lib/tmp-classify.sh +843 -0
  225. package/scripts/lib/wrapper-boundary.sh +47 -0
  226. package/scripts/lint-ratchet.py +1547 -0
  227. package/scripts/monitor-pr-checks.sh +441 -0
  228. package/scripts/pipeline-tally.sh +756 -0
  229. package/scripts/pipeline-tally.test.sh +740 -0
  230. package/scripts/plugin-version.sh +528 -0
  231. package/scripts/precommit-guard.sh +115 -0
  232. package/scripts/precommit-guard.test.sh +254 -0
  233. package/scripts/reconcile-live-heartbeats.ts +705 -0
  234. package/scripts/render-c4-model.sh +217 -0
  235. package/scripts/resolve-git-root.sh +55 -0
  236. package/scripts/resolve-regenerable-conflicts.sh +363 -0
  237. package/scripts/resolve-regenerable-conflicts.test.sh +780 -0
  238. package/scripts/ruff-requirements.txt +22 -0
  239. package/scripts/run-in-pid-namespace.sh +98 -0
  240. package/scripts/run-in-pid-namespace.test.sh +290 -0
  241. package/scripts/scan-ancestor-signal-helpers.py +556 -0
  242. package/scripts/scan-ancestor-signal-helpers.test.sh +670 -0
  243. package/scripts/setup-ralph-loop.sh +238 -0
  244. package/scripts/sync-cursor-name-map.ts +316 -0
  245. package/scripts/sync-grok-agent-compat.ts +188 -0
  246. package/scripts/sync-npm-package.ts +165 -0
  247. package/scripts/sync-opencode-adapter.ts +498 -0
  248. package/scripts/sync-pr-behind.sh +647 -0
  249. package/scripts/sync-pr-behind.test.sh +252 -0
  250. package/scripts/taste-profile-update.sh +180 -0
  251. package/scripts/taste-profile-update.test.sh +167 -0
  252. package/scripts/write-budget-marker.sh +23 -0
  253. package/scripts/write-kb-coverage.ts +113 -0
  254. package/skills/admin-ip-refresh/SKILL.md +81 -0
  255. package/skills/admin-ip-refresh/references/admin-ip-refresh-procedure.md +201 -0
  256. package/skills/agent-browser/SKILL.md +698 -0
  257. package/skills/agent-browser/scripts/playwright-mcp-profile-slot.sh +304 -0
  258. package/skills/agent-browser/scripts/playwright-mcp-redact-proxy.py +964 -0
  259. package/skills/agent-browser/scripts/redact-a11y-snapshot.py +443 -0
  260. package/skills/agent-browser/test/fixtures/capture-playwright-mcp-fixtures.py +217 -0
  261. package/skills/agent-browser/test/fixtures/fake-passthrough-proxy.py +24 -0
  262. package/skills/agent-browser/test/fixtures/fake-playwright-mcp.py +131 -0
  263. package/skills/agent-browser/test/fixtures/make-proxy-mutant.py +48 -0
  264. package/skills/agent-browser/test/fixtures/odd-shapes/dialog-link-injection.json +10 -0
  265. package/skills/agent-browser/test/fixtures/odd-shapes/empty-content.json +5 -0
  266. package/skills/agent-browser/test/fixtures/odd-shapes/error-and-result.json +4 -0
  267. package/skills/agent-browser/test/fixtures/odd-shapes/error-code-bool.json +6 -0
  268. package/skills/agent-browser/test/fixtures/odd-shapes/error-code-string.json +6 -0
  269. package/skills/agent-browser/test/fixtures/odd-shapes/error-message-tree.json +6 -0
  270. package/skills/agent-browser/test/fixtures/odd-shapes/error-no-data.json +6 -0
  271. package/skills/agent-browser/test/fixtures/odd-shapes/error-string.json +4 -0
  272. package/skills/agent-browser/test/fixtures/odd-shapes/error-with-data.json +9 -0
  273. package/skills/agent-browser/test/fixtures/odd-shapes/evaluate-escaped-object.json +10 -0
  274. package/skills/agent-browser/test/fixtures/odd-shapes/invalid-utf8.json +4 -0
  275. package/skills/agent-browser/test/fixtures/odd-shapes/link-result.json +10 -0
  276. package/skills/agent-browser/test/fixtures/odd-shapes/list-two-results.json +6 -0
  277. package/skills/agent-browser/test/fixtures/odd-shapes/meta-in-result.json +13 -0
  278. package/skills/agent-browser/test/fixtures/odd-shapes/no-content.json +5 -0
  279. package/skills/agent-browser/test/fixtures/odd-shapes/quoted-key.json +10 -0
  280. package/skills/agent-browser/test/fixtures/odd-shapes/resource-block.json +18 -0
  281. package/skills/agent-browser/test/fixtures/odd-shapes/result-and-method.json +11 -0
  282. package/skills/agent-browser/test/fixtures/odd-shapes/result-string.json +3 -0
  283. package/skills/agent-browser/test/fixtures/odd-shapes/run-code-escaped-one-row.json +10 -0
  284. package/skills/agent-browser/test/fixtures/odd-shapes/run-code-escaped.json +10 -0
  285. package/skills/agent-browser/test/fixtures/odd-shapes/structured-content.json +13 -0
  286. package/skills/agent-browser/test/fixtures/odd-shapes/tools-list-no-tools.json +5 -0
  287. package/skills/agent-browser/test/fixtures/odd-shapes/tree-no-credential.json +10 -0
  288. package/skills/agent-browser/test/fixtures/odd-shapes/two-text-blocks.json +14 -0
  289. package/skills/agent-browser/test/fixtures/odd-shapes/unparsable-line.json +3 -0
  290. package/skills/agent-browser/test/fixtures/odd-shapes/value-contains-brace.json +10 -0
  291. package/skills/agent-browser/test/fixtures/odd-shapes/value-contains-link-substring.json +10 -0
  292. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/close.json +12 -0
  293. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/evaluate.json +12 -0
  294. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/find.json +12 -0
  295. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/framing.json +6 -0
  296. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/initialize.json +14 -0
  297. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/navigate-link.json +12 -0
  298. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/navigate.json +12 -0
  299. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/roots-list-request.json +5 -0
  300. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/screenshot.json +17 -0
  301. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-filename.json +12 -0
  302. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-meta-json.json +12 -0
  303. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-meta-raw.json +12 -0
  304. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot.json +12 -0
  305. package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/tools-list.json +827 -0
  306. package/skills/agent-browser/test/fixtures/proxy-session-driver.py +138 -0
  307. package/skills/agent-browser/test/fixtures/session-facts.py +102 -0
  308. package/skills/agent-browser/test/playwright-mcp-lifetime.test.sh +1620 -0
  309. package/skills/agent-browser/test/playwright-mcp-redact-proxy.test.sh +1599 -0
  310. package/skills/agent-browser/test/redact-a11y-snapshot.test.sh +527 -0
  311. package/skills/agent-native-architecture/SKILL.md +479 -0
  312. package/skills/agent-native-architecture/references/action-parity-discipline.md +430 -0
  313. package/skills/agent-native-architecture/references/agent-execution-patterns.md +483 -0
  314. package/skills/agent-native-architecture/references/agent-native-testing.md +605 -0
  315. package/skills/agent-native-architecture/references/architecture-patterns.md +493 -0
  316. package/skills/agent-native-architecture/references/dynamic-context-injection.md +358 -0
  317. package/skills/agent-native-architecture/references/files-universal-interface.md +314 -0
  318. package/skills/agent-native-architecture/references/from-primitives-to-domain-tools.md +381 -0
  319. package/skills/agent-native-architecture/references/mcp-tool-design.md +524 -0
  320. package/skills/agent-native-architecture/references/mobile-patterns.md +897 -0
  321. package/skills/agent-native-architecture/references/product-implications.md +478 -0
  322. package/skills/agent-native-architecture/references/refactoring-to-prompt-native.md +342 -0
  323. package/skills/agent-native-architecture/references/self-modification.md +294 -0
  324. package/skills/agent-native-architecture/references/shared-workspace-architecture.md +700 -0
  325. package/skills/agent-native-architecture/references/system-prompt-design.md +263 -0
  326. package/skills/agent-native-audit/SKILL.md +300 -0
  327. package/skills/agent-native-audit/workflows/agent-native-audit.workflow.js +423 -0
  328. package/skills/andrew-kane-gem-writer/SKILL.md +189 -0
  329. package/skills/andrew-kane-gem-writer/references/database-adapters.md +231 -0
  330. package/skills/andrew-kane-gem-writer/references/module-organization.md +121 -0
  331. package/skills/andrew-kane-gem-writer/references/rails-integration.md +183 -0
  332. package/skills/andrew-kane-gem-writer/references/resources.md +126 -0
  333. package/skills/andrew-kane-gem-writer/references/testing-patterns.md +261 -0
  334. package/skills/architecture/SKILL.md +436 -0
  335. package/skills/architecture/references/adr-template.md +154 -0
  336. package/skills/architecture/references/likec4-reference.md +136 -0
  337. package/skills/architecture/references/nfr-reference.md +126 -0
  338. package/skills/archive-kb/SKILL.md +86 -0
  339. package/skills/archive-kb/scripts/archive-kb.sh +300 -0
  340. package/skills/archive-kb/test/archive-kb-partial-run.test.sh +276 -0
  341. package/skills/atdd-developer/SKILL.md +65 -0
  342. package/skills/brainstorm/SKILL.md +701 -0
  343. package/skills/brainstorm/references/brainstorm-brand-workshop.md +153 -0
  344. package/skills/brainstorm/references/brainstorm-domain-config.md +63 -0
  345. package/skills/brainstorm/references/brainstorm-validation-workshop.md +65 -0
  346. package/skills/brainstorm/references/ui-surface-terms.md +42 -0
  347. package/skills/brainstorm-techniques/SKILL.md +231 -0
  348. package/skills/brainstorm-techniques/references/decision-principles.md +150 -0
  349. package/skills/brainstorm-techniques/references/phase-boundaries.md +103 -0
  350. package/skills/brainstorm-techniques/references/standing-delegation.md +407 -0
  351. package/skills/brainstorm-techniques/references/technical-ruling-delegation.md +174 -0
  352. package/skills/campaign-calendar/SKILL.md +145 -0
  353. package/skills/cf-token-scope/SKILL.md +164 -0
  354. package/skills/cf-token-scope/references/widen-playbook.md +114 -0
  355. package/skills/cf-token-scope/scripts/cf-token-scope.sh +253 -0
  356. package/skills/changelog/SKILL.md +142 -0
  357. package/skills/code-to-prd/SKILL.md +66 -0
  358. package/skills/code-to-prd/references/banner-template.md +12 -0
  359. package/skills/code-to-prd/references/prd-template.md +41 -0
  360. package/skills/code-to-prd/scripts/code-to-prd.sh +634 -0
  361. package/skills/code-to-prd/test/code-to-prd.test.sh +447 -0
  362. package/skills/code-to-prd/test/fixture/.env.example +7 -0
  363. package/skills/code-to-prd/test/fixture/app/about/page.tsx +11 -0
  364. package/skills/code-to-prd/test/fixture/app/api/health/route.ts +10 -0
  365. package/skills/code-to-prd/test/fixture/app/page.tsx +14 -0
  366. package/skills/code-to-prd/test/fixture/next.config.mjs +3 -0
  367. package/skills/code-to-prd/test/fixture/package.json +6 -0
  368. package/skills/cohort-status/SKILL.md +62 -0
  369. package/skills/community/SKILL.md +210 -0
  370. package/skills/community/scripts/bsky-community.sh +644 -0
  371. package/skills/community/scripts/bsky-setup.sh +631 -0
  372. package/skills/community/scripts/community-router.sh +93 -0
  373. package/skills/community/scripts/discord-community.sh +487 -0
  374. package/skills/community/scripts/discord-setup.sh +784 -0
  375. package/skills/community/scripts/github-community.sh +706 -0
  376. package/skills/community/scripts/hn-community.sh +233 -0
  377. package/skills/community/scripts/lib/hmac-sha1-b64.sh +126 -0
  378. package/skills/community/scripts/linkedin-community.sh +750 -0
  379. package/skills/community/scripts/linkedin-setup.sh +758 -0
  380. package/skills/community/scripts/x-community.sh +890 -0
  381. package/skills/community/scripts/x-setup.sh +713 -0
  382. package/skills/community/test/community-argv.test.sh +2922 -0
  383. package/skills/community/test/github-community.test.sh +981 -0
  384. package/skills/competitive-analysis/SKILL.md +69 -0
  385. package/skills/competitive-analysis/references/peer-plugin-audit.md +218 -0
  386. package/skills/compound/SKILL.md +673 -0
  387. package/skills/compound/scripts/token-efficiency-report.sh +334 -0
  388. package/skills/compound/test/phase-16.test.sh +379 -0
  389. package/skills/compound-capture/SKILL.md +791 -0
  390. package/skills/compound-capture/assets/critical-pattern-template.md +37 -0
  391. package/skills/compound-capture/assets/resolution-template.md +116 -0
  392. package/skills/compound-capture/references/yaml-schema.md +67 -0
  393. package/skills/compound-capture/schema.yaml +181 -0
  394. package/skills/constraint-scaffold/SKILL.md +175 -0
  395. package/skills/constraint-scaffold/references/boundary-readme.template +35 -0
  396. package/skills/constraint-scaffold/references/constraint-gates-workflow.template +69 -0
  397. package/skills/constraint-scaffold/references/depcruise-config.template +230 -0
  398. package/skills/constraint-scaffold/references/fix-constraints-stage-a.template +240 -0
  399. package/skills/constraint-scaffold/references/fix-constraints-stage-b.template +313 -0
  400. package/skills/constraint-scaffold/references/shared-runner.template +135 -0
  401. package/skills/constraint-scaffold/scripts/constraint-scaffold.sh +589 -0
  402. package/skills/constraint-scaffold/test/bite-proof.test.sh +1338 -0
  403. package/skills/constraint-scaffold/test/boundary.test.sh +734 -0
  404. package/skills/constraint-scaffold/test/emit-fix-constraints.test.sh +193 -0
  405. package/skills/constraint-scaffold/test/generator.test.sh +93 -0
  406. package/skills/constraint-scaffold/test/parity.test.sh +285 -0
  407. package/skills/content-writer/SKILL.md +242 -0
  408. package/skills/content-writer/scripts/blog-jargon-scan.sh +42 -0
  409. package/skills/cron-delete/SKILL.md +56 -0
  410. package/skills/cron-list/SKILL.md +54 -0
  411. package/skills/deepen-plan/SKILL.md +937 -0
  412. package/skills/deepen-plan/workflows/deepen-plan.workflow.js +488 -0
  413. package/skills/deploy/SKILL.md +115 -0
  414. package/skills/deploy/references/hetzner-setup.md +67 -0
  415. package/skills/deploy/scripts/deploy.sh +37 -0
  416. package/skills/deploy-docs/SKILL.md +95 -0
  417. package/skills/dhh-rails-style/SKILL.md +203 -0
  418. package/skills/dhh-rails-style/references/architecture.md +717 -0
  419. package/skills/dhh-rails-style/references/controllers.md +323 -0
  420. package/skills/dhh-rails-style/references/frontend.md +556 -0
  421. package/skills/dhh-rails-style/references/gems.md +316 -0
  422. package/skills/dhh-rails-style/references/models.md +388 -0
  423. package/skills/dhh-rails-style/references/testing.md +354 -0
  424. package/skills/discord-content/SKILL.md +126 -0
  425. package/skills/docs-site/SKILL.md +214 -0
  426. package/skills/drain-labeled-backlog/SKILL.md +203 -0
  427. package/skills/drain-labeled-backlog/scripts/group-by-area.sh +208 -0
  428. package/skills/drain-labeled-backlog/workflows/drain-labeled-backlog.workflow.js +433 -0
  429. package/skills/drain-prs/SKILL.md +143 -0
  430. package/skills/drain-prs/scripts/triage-prs.sh +192 -0
  431. package/skills/drain-prs/test/argv-ceiling.test.sh +183 -0
  432. package/skills/dspy-ruby/SKILL.md +634 -0
  433. package/skills/dspy-ruby/assets/config-template.rb +359 -0
  434. package/skills/dspy-ruby/assets/module-template.rb +326 -0
  435. package/skills/dspy-ruby/assets/signature-template.rb +143 -0
  436. package/skills/dspy-ruby/references/core-concepts.md +268 -0
  437. package/skills/dspy-ruby/references/optimization.md +623 -0
  438. package/skills/dspy-ruby/references/providers.md +335 -0
  439. package/skills/eval-harness/README.md +194 -0
  440. package/skills/eval-harness/SKILL.md +157 -0
  441. package/skills/eval-harness/enums/go-routes.json +13 -0
  442. package/skills/eval-harness/enums/incident-threshold.json +5 -0
  443. package/skills/eval-harness/enums/lane.json +5 -0
  444. package/skills/eval-harness/enums/tool-selection.json +20 -0
  445. package/skills/eval-harness/enums/triage-levels.json +5 -0
  446. package/skills/eval-harness/gated-skills.json +34 -0
  447. package/skills/eval-harness/models.generated.json +5 -0
  448. package/skills/eval-harness/promptfooconfig-go-routing.yaml +35 -0
  449. package/skills/eval-harness/promptfooconfig-incident-threshold.yaml +32 -0
  450. package/skills/eval-harness/promptfooconfig-lane-inference.yaml +33 -0
  451. package/skills/eval-harness/promptfooconfig-ticket-triage.yaml +34 -0
  452. package/skills/eval-harness/promptfooconfig-tool-selection.yaml +33 -0
  453. package/skills/eval-harness/prompts/go-baseline.txt +6 -0
  454. package/skills/eval-harness/prompts/go-skill.txt +21 -0
  455. package/skills/eval-harness/prompts/incident-threshold-baseline.txt +6 -0
  456. package/skills/eval-harness/prompts/incident-threshold-skill.txt +12 -0
  457. package/skills/eval-harness/prompts/lane-inference-baseline.txt +6 -0
  458. package/skills/eval-harness/prompts/lane-inference-skill.txt +15 -0
  459. package/skills/eval-harness/prompts/tool-selection-baseline.txt +19 -0
  460. package/skills/eval-harness/prompts/tool-selection-skill.txt +22 -0
  461. package/skills/eval-harness/prompts/triage-baseline.txt +7 -0
  462. package/skills/eval-harness/prompts/triage-skill.txt +10 -0
  463. package/skills/eval-harness/scripts/eval-gate.cjs +318 -0
  464. package/skills/eval-harness/scripts/extract-block.cjs +87 -0
  465. package/skills/eval-harness/scripts/gate-classification.cjs +28 -0
  466. package/skills/eval-harness/scripts/gen-models.sh +41 -0
  467. package/skills/eval-harness/scripts/gen-skill-prompt.cjs +177 -0
  468. package/skills/eval-harness/scripts/measure-classification.cjs +30 -0
  469. package/skills/eval-harness/scripts/parse-label.cjs +93 -0
  470. package/skills/eval-harness/scripts/verdict.cjs +132 -0
  471. package/skills/eval-harness/tasks/go-routing.jsonl +8 -0
  472. package/skills/eval-harness/tasks/incident-threshold.jsonl +7 -0
  473. package/skills/eval-harness/tasks/lane-inference.jsonl +7 -0
  474. package/skills/eval-harness/tasks/ticket-triage.jsonl +6 -0
  475. package/skills/eval-harness/tasks/tool-selection.jsonl +15 -0
  476. package/skills/eval-harness/test/eval-gate.test.sh +132 -0
  477. package/skills/eval-harness/test/extract-block.test.sh +87 -0
  478. package/skills/eval-harness/test/gate-classification.test.sh +77 -0
  479. package/skills/eval-harness/test/gen-models.test.sh +75 -0
  480. package/skills/eval-harness/test/measure-classification.test.sh +74 -0
  481. package/skills/eval-harness/test/parse-label.test.sh +129 -0
  482. package/skills/eval-harness/test/registry-completeness.test.sh +109 -0
  483. package/skills/eval-harness/test/verdict.test.sh +123 -0
  484. package/skills/every-style-editor/SKILL.md +142 -0
  485. package/skills/every-style-editor/references/EVERY_WRITE_STYLE.md +529 -0
  486. package/skills/feature-tweet/SKILL.md +199 -0
  487. package/skills/feature-video/SKILL.md +412 -0
  488. package/skills/feature-video/scripts/check_deps.sh +281 -0
  489. package/skills/file-todos/SKILL.md +267 -0
  490. package/skills/file-todos/assets/todo-template.md +166 -0
  491. package/skills/fix-issue/SKILL.md +278 -0
  492. package/skills/fix-issue/references/agent-authored-exclusion.md +120 -0
  493. package/skills/fix-issue/references/exclude-label-jq-snippet.md +97 -0
  494. package/skills/flag-bootstrap/SETUP.md +142 -0
  495. package/skills/flag-create/SKILL.md +145 -0
  496. package/skills/flag-create/scripts/create.sh +273 -0
  497. package/skills/flag-delete/SKILL.md +167 -0
  498. package/skills/flag-delete/scripts/delete.sh +288 -0
  499. package/skills/flag-list/SKILL.md +99 -0
  500. package/skills/flag-list/scripts/list.sh +275 -0
  501. package/skills/flag-set-role/SKILL.md +229 -0
  502. package/skills/flag-set-role/scripts/flip.sh +774 -0
  503. package/skills/frontend-anti-slop/SKILL.md +106 -0
  504. package/skills/frontend-anti-slop/references/anti-patterns.md +199 -0
  505. package/skills/frontend-anti-slop/references/slop-rules.md +65 -0
  506. package/skills/frontend-anti-slop/scripts/tier1-scan.ts +525 -0
  507. package/skills/frontend-design/SKILL.md +77 -0
  508. package/skills/gdpr-gate/NOTICE +136 -0
  509. package/skills/gdpr-gate/SKILL.md +341 -0
  510. package/skills/gdpr-gate/references/fields.md +160 -0
  511. package/skills/gdpr-gate/references/layers/api-layer.md +355 -0
  512. package/skills/gdpr-gate/references/layers/auth-sessions.md +290 -0
  513. package/skills/gdpr-gate/references/layers/data-in-transit.md +331 -0
  514. package/skills/gdpr-gate/references/layers/data-lifecycle.md +341 -0
  515. package/skills/gdpr-gate/references/layers/frontend.md +249 -0
  516. package/skills/gdpr-gate/references/layers/testing-seeding.md +256 -0
  517. package/skills/gdpr-gate/references/leakage-vectors.md +248 -0
  518. package/skills/gdpr-gate/references/legacy/legal-consent-v1-prose.md +48 -0
  519. package/skills/gdpr-gate/references/legal-consent.md +140 -0
  520. package/skills/gdpr-gate/references/non-negotiables.md +60 -0
  521. package/skills/gdpr-gate/scripts/gdpr-gate.sh +271 -0
  522. package/skills/gdpr-gate/scripts/notice-frontmatter.sh +337 -0
  523. package/skills/gdpr-gate/scripts/path-denylist.txt +39 -0
  524. package/skills/gdpr-gate/scripts/repo-scan.sh +158 -0
  525. package/skills/gdpr-gate/scripts/vendor-drift-classify.sh +120 -0
  526. package/skills/gdpr-gate/scripts/vendor-pin-integrity.sh +237 -0
  527. package/skills/gemini-imagegen/SKILL.md +283 -0
  528. package/skills/gemini-imagegen/requirements.txt +5 -0
  529. package/skills/gemini-imagegen/scripts/_error_handling.py +146 -0
  530. package/skills/gemini-imagegen/scripts/compose_images.py +150 -0
  531. package/skills/gemini-imagegen/scripts/edit_image.py +137 -0
  532. package/skills/gemini-imagegen/scripts/gemini_images.py +256 -0
  533. package/skills/gemini-imagegen/scripts/generate_image.py +147 -0
  534. package/skills/gemini-imagegen/scripts/multi_turn_chat.py +223 -0
  535. package/skills/gemini-imagegen/scripts/test_error_handling.py +179 -0
  536. package/skills/git-worktree/SKILL.md +534 -0
  537. package/skills/git-worktree/scripts/git-repo-readiness-diag.sh +98 -0
  538. package/skills/git-worktree/scripts/worktree-manager.sh +4773 -0
  539. package/skills/git-worktree/test/create-from-origin-main.test.sh +116 -0
  540. package/skills/git-worktree/test/lease-protects-active.test.sh +1056 -0
  541. package/skills/git-worktree/test/no-repo-fail-loud.test.sh +88 -0
  542. package/skills/git-worktree/test/orphan-reaper-honest-count.test.sh +379 -0
  543. package/skills/git-worktree/test/reap-archive-persistence.test.sh +725 -0
  544. package/skills/git-worktree/test/stale-lock-sweep.test.sh +176 -0
  545. package/skills/go/SKILL.md +54 -0
  546. package/skills/growth/SKILL.md +164 -0
  547. package/skills/harvest-debt/SKILL.md +78 -0
  548. package/skills/harvest-debt/scripts/harvest-debt.sh +114 -0
  549. package/skills/heal-skill/SKILL.md +173 -0
  550. package/skills/help/SKILL.md +46 -0
  551. package/skills/incident/NOTICE +39 -0
  552. package/skills/incident/SKILL.md +395 -0
  553. package/skills/incident/scripts/dry-run.sh +448 -0
  554. package/skills/incident/scripts/redact-engine.py +412 -0
  555. package/skills/incident/scripts/redact-sentinel.sh +44 -0
  556. package/skills/incident/templates/pir.md +139 -0
  557. package/skills/incident/test/fixtures/dry-run-incident.json +26 -0
  558. package/skills/incident/test/fixtures/dry-run-secret-leak.json +26 -0
  559. package/skills/incident/test/fixtures/legacy-bash-scanner.sh +82 -0
  560. package/skills/incident/test/fixtures/positive-corpus.md +87 -0
  561. package/skills/incident/test/redact-sentinel.test.sh +1420 -0
  562. package/skills/invoice/SKILL.md +206 -0
  563. package/skills/kb-glossary/SKILL.md +71 -0
  564. package/skills/kb-glossary/references/glossary-format.md +111 -0
  565. package/skills/kb-glossary/references/rejected-request-register.md +115 -0
  566. package/skills/kb-search/SKILL.md +276 -0
  567. package/skills/kb-search/scripts/kb-search-cache.sh +123 -0
  568. package/skills/legal-audit/SKILL.md +161 -0
  569. package/skills/legal-generate/NOTICE +242 -0
  570. package/skills/legal-generate/SKILL.md +244 -0
  571. package/skills/legal-generate/references/templates/advisor-agreement/template.md +12 -0
  572. package/skills/legal-generate/references/templates/business-associate-agreement/template.md +112 -0
  573. package/skills/legal-generate/references/templates/cookie-notice/template.md +82 -0
  574. package/skills/legal-generate/references/templates/dpa-global/template.md +439 -0
  575. package/skills/legal-generate/references/templates/dpa-us/template.md +222 -0
  576. package/skills/legal-generate/references/templates/employee-offer-letter/template.md +75 -0
  577. package/skills/legal-generate/references/templates/master-services-agreement/template.md +252 -0
  578. package/skills/legal-generate/references/templates/mutual-nda/template.md +66 -0
  579. package/skills/legal-generate/references/templates/one-way-nda/template.md +38 -0
  580. package/skills/legal-generate/references/templates/privacy-policy-gdpr/template.md +427 -0
  581. package/skills/legal-generate/references/templates/privacy-policy-us/template.md +313 -0
  582. package/skills/legal-generate/references/templates/terms-of-use/template.md +75 -0
  583. package/skills/legal-generate/scripts/strip-vendor-credit.sh +96 -0
  584. package/skills/linear-fetch/SKILL.md +247 -0
  585. package/skills/linear-fetch/scripts/assert-no-linear-telemetry.sh +66 -0
  586. package/skills/linear-fetch/scripts/redact-linear-urls.sh +51 -0
  587. package/skills/linear-fetch/scripts/render-caller-template.sh +36 -0
  588. package/skills/linear-fetch/test/assert-no-linear-telemetry.test.sh +113 -0
  589. package/skills/linear-fetch/test/parity.test.sh +112 -0
  590. package/skills/linear-fetch/test/persist-safe-integration.test.sh +191 -0
  591. package/skills/linear-fetch/test/redact-linear-urls.test.sh +226 -0
  592. package/skills/merge-pr/SKILL.md +670 -0
  593. package/skills/model-launch-review/SKILL.md +255 -0
  594. package/skills/model-launch-review/scripts/audit-models.sh +501 -0
  595. package/skills/one-shot/SKILL.md +316 -0
  596. package/skills/one-shot/references/token-discipline.md +74 -0
  597. package/skills/operator-bootstrap/SKILL.md +412 -0
  598. package/skills/operator-bootstrap/template.sh +439 -0
  599. package/skills/operator-digest/SKILL.md +287 -0
  600. package/skills/operator-digest/assets/operator-digest.workflow.yml +175 -0
  601. package/skills/operator-digest/scripts/digest-scrub.sh +119 -0
  602. package/skills/operator-digest/scripts/provision-operator-digest-repo.sh +147 -0
  603. package/skills/operator-rephrase/SKILL.md +98 -0
  604. package/skills/pencil-setup/SKILL.md +201 -0
  605. package/skills/pencil-setup/scripts/check_deps.sh +556 -0
  606. package/skills/pencil-setup/scripts/copy_adapter.sh +80 -0
  607. package/skills/pencil-setup/scripts/package-lock.json +1172 -0
  608. package/skills/pencil-setup/scripts/package.json +14 -0
  609. package/skills/pencil-setup/scripts/pencil-error-enrichment.mjs +21 -0
  610. package/skills/pencil-setup/scripts/pencil-mcp-adapter.mjs +733 -0
  611. package/skills/pencil-setup/scripts/pencil-response-classify.mjs +38 -0
  612. package/skills/pencil-setup/scripts/pencil-save-gate.mjs +26 -0
  613. package/skills/pencil-setup/scripts/sanitize-filename.mjs +25 -0
  614. package/skills/plan/SKILL.md +1140 -0
  615. package/skills/plan/references/differential-migration-testing.md +185 -0
  616. package/skills/plan/references/plan-community-discovery.md +42 -0
  617. package/skills/plan/references/plan-founder-check.md +137 -0
  618. package/skills/plan/references/plan-functional-overlap.md +25 -0
  619. package/skills/plan/references/plan-host-fanout.md +51 -0
  620. package/skills/plan/references/plan-issue-templates.md +779 -0
  621. package/skills/plan/references/plan-network-outage-checklist.md +131 -0
  622. package/skills/plan/references/plan-scope-check.md +126 -0
  623. package/skills/plan/references/plan-sharp-edges.md +331 -0
  624. package/skills/plan/references/plan-signoff-lifecycle.md +12 -0
  625. package/skills/plan/references/webapp-cwv-observability.md +143 -0
  626. package/skills/plan-review/SKILL.md +91 -0
  627. package/skills/plan-review/lib/named-panel.mjs +58 -0
  628. package/skills/plan-review/workflows/plan-review.workflow.js +477 -0
  629. package/skills/postmerge/SKILL.md +642 -0
  630. package/skills/postmerge/references/deploy-status-debugging.md +141 -0
  631. package/skills/postmerge/references/graceful-degradation.md +20 -0
  632. package/skills/postmerge/references/sentry-error-count-delta.md +137 -0
  633. package/skills/preflight/SKILL.md +1553 -0
  634. package/skills/preflight/references/check-13-founder-check.md +286 -0
  635. package/skills/preflight/scripts/founder-check.py +1640 -0
  636. package/skills/preflight/scripts/parse-form-a.awk +107 -0
  637. package/skills/preflight/scripts/probe-verb-gate.sh +108 -0
  638. package/skills/product-roadmap/SKILL.md +357 -0
  639. package/skills/product-roadmap/scripts/roadmap-reconcile.sh +327 -0
  640. package/skills/provision-cloudflare/SKILL.md +67 -0
  641. package/skills/provision-cloudflare/scripts/provision-cloudflare.sh +271 -0
  642. package/skills/provision-doppler/SKILL.md +58 -0
  643. package/skills/provision-doppler/scripts/provision-doppler.sh +358 -0
  644. package/skills/provision-github/SKILL.md +59 -0
  645. package/skills/provision-github/scripts/provision-github.sh +300 -0
  646. package/skills/provision-hetzner/SKILL.md +76 -0
  647. package/skills/provision-hetzner/scripts/provision-hetzner.sh +262 -0
  648. package/skills/provision-hetzner/test/fixture/knowledge-base/legal/tenant-dpa-register.md +26 -0
  649. package/skills/provision-hetzner/test/fixture/provision-hetzner-dry-run.golden +34 -0
  650. package/skills/provision-hetzner/test/provision-hetzner-characterization.test.sh +227 -0
  651. package/skills/qa/SKILL.md +263 -0
  652. package/skills/qa/references/browser-preflight.md +22 -0
  653. package/skills/questionnaire-generate/SKILL.md +328 -0
  654. package/skills/questionnaire-generate/references/questionnaire.template +60 -0
  655. package/skills/rclone/SKILL.md +162 -0
  656. package/skills/rclone/scripts/check_setup.sh +63 -0
  657. package/skills/release-announce/SKILL.md +65 -0
  658. package/skills/release-docs/SKILL.md +92 -0
  659. package/skills/reproduce-bug/SKILL.md +358 -0
  660. package/skills/resolve-debt/SKILL.md +85 -0
  661. package/skills/resolve-debt/scripts/resolve-debt.py +584 -0
  662. package/skills/resolve-parallel/SKILL.md +45 -0
  663. package/skills/resolve-parallel/workflows/resolve-parallel.workflow.js +415 -0
  664. package/skills/resolve-pr-parallel/SKILL.md +68 -0
  665. package/skills/resolve-pr-parallel/workflows/resolve-pr-parallel.workflow.js +419 -0
  666. package/skills/resolve-todo-parallel/SKILL.md +57 -0
  667. package/skills/resolve-todo-parallel/workflows/resolve-todo-parallel.workflow.js +398 -0
  668. package/skills/review/SKILL.md +1620 -0
  669. package/skills/review/references/anti-slop-scanner-hook.md +40 -0
  670. package/skills/review/references/defect-classes-migrations.md +9 -0
  671. package/skills/review/references/generated-input-oracle-surfaces.md +15 -0
  672. package/skills/review/references/review-e2e-testing.md +116 -0
  673. package/skills/review/references/review-evidence-comment.md +36 -0
  674. package/skills/review/references/review-todo-structure.md +165 -0
  675. package/skills/review/references/risk-tier-and-fix-rounds.md +97 -0
  676. package/skills/review/references/semgrep-custom-rules.yaml +174 -0
  677. package/skills/review/references/ultrathink-phases.md +98 -0
  678. package/skills/review/references/wfs.md +6 -0
  679. package/skills/review/scripts/emit-review-trailer.sh +499 -0
  680. package/skills/review/scripts/ensure-semgrep.sh +99 -0
  681. package/skills/review/scripts/fix-round-seats.sh +197 -0
  682. package/skills/review/test/emit-review-trailer.test.sh +387 -0
  683. package/skills/review/test/ensure-semgrep.test.sh +111 -0
  684. package/skills/review/test/fix-round-seats.test.sh +237 -0
  685. package/skills/review/workflows/README.md +147 -0
  686. package/skills/review/workflows/review.workflow.js +667 -0
  687. package/skills/schedule/SKILL.md +796 -0
  688. package/skills/seo-aeo/SKILL.md +139 -0
  689. package/skills/seo-aeo/scripts/validate-csp.sh +233 -0
  690. package/skills/seo-aeo/scripts/validate-seo.sh +262 -0
  691. package/skills/ship/SKILL.md +3023 -0
  692. package/skills/ship/references/adr-ordinal-collision.md +47 -0
  693. package/skills/ship/references/affected-gate-contract.md +115 -0
  694. package/skills/ship/references/ci-run-diagnosis.md +29 -0
  695. package/skills/ship/references/ci-workflow-authoring.md +68 -0
  696. package/skills/ship/references/cleanup-merged-archival.md +39 -0
  697. package/skills/ship/references/counsel-review-clo-attestation.md +38 -0
  698. package/skills/ship/references/cto-ruling-armed-push.md +5 -0
  699. package/skills/ship/references/decision-challenges-render.md +71 -0
  700. package/skills/ship/references/delegated-act-authoring-form.md +7 -0
  701. package/skills/ship/references/detached-battery-verdict.md +13 -0
  702. package/skills/ship/references/epic-close-sweep.md +62 -0
  703. package/skills/ship/references/feature-tweet-draft.md +42 -0
  704. package/skills/ship/references/followthrough-issue-body-template.md +47 -0
  705. package/skills/ship/references/followthrough-stub-template.sh +92 -0
  706. package/skills/ship/references/merge-queue-dequeue.md +39 -0
  707. package/skills/ship/references/operator-holds.md +179 -0
  708. package/skills/ship/references/pipeline-tally-render.md +43 -0
  709. package/skills/ship/references/pr-overlap.md +153 -0
  710. package/skills/ship/references/queue-mode.md +7 -0
  711. package/skills/ship/references/ready-run-wait.md +36 -0
  712. package/skills/ship/references/red-on-main-quarantine.md +110 -0
  713. package/skills/ship/references/register-update-pr-pattern.md +88 -0
  714. package/skills/ship/references/settle-then-admin-merge.md +130 -0
  715. package/skills/ship/references/supabase-column-probe.md +13 -0
  716. package/skills/ship/scripts/auto-close-scan.sh +76 -0
  717. package/skills/ship/scripts/battery-owed.sh +403 -0
  718. package/skills/ship/scripts/hold-gate-eval.sh +410 -0
  719. package/skills/ship/scripts/net-issue-flow.sh +665 -0
  720. package/skills/ship/scripts/pr-overlap-preflight.sh +483 -0
  721. package/skills/ship/scripts/ship-pir-action-items-gate.sh +246 -0
  722. package/skills/ship/scripts/technical-ruling-gate.sh +444 -0
  723. package/skills/skill-creator/SKILL.md +299 -0
  724. package/skills/skill-creator/references/api-security.md +248 -0
  725. package/skills/skill-creator/references/authoring-levers.md +110 -0
  726. package/skills/skill-creator/references/be-clear-and-direct.md +587 -0
  727. package/skills/skill-creator/references/best-practices.md +431 -0
  728. package/skills/skill-creator/references/common-patterns.md +639 -0
  729. package/skills/skill-creator/references/core-principles.md +193 -0
  730. package/skills/skill-creator/references/executable-code.md +188 -0
  731. package/skills/skill-creator/references/iteration-and-testing.md +509 -0
  732. package/skills/skill-creator/references/official-spec.md +193 -0
  733. package/skills/skill-creator/references/recommended-structure.md +180 -0
  734. package/skills/skill-creator/references/skill-structure.md +396 -0
  735. package/skills/skill-creator/references/using-scripts.md +122 -0
  736. package/skills/skill-creator/references/using-templates.md +120 -0
  737. package/skills/skill-creator/references/workflows-and-validation.md +541 -0
  738. package/skills/skill-creator/scripts/init_skill.py +322 -0
  739. package/skills/skill-creator/scripts/package_skill.py +110 -0
  740. package/skills/skill-creator/scripts/quick_validate.py +64 -0
  741. package/skills/skill-creator/templates/router-skill.md +73 -0
  742. package/skills/skill-creator/templates/simple-skill.md +34 -0
  743. package/skills/skill-creator/workflows/add-reference.md +96 -0
  744. package/skills/skill-creator/workflows/add-script.md +104 -0
  745. package/skills/skill-creator/workflows/add-template.md +83 -0
  746. package/skills/skill-creator/workflows/add-workflow.md +128 -0
  747. package/skills/skill-creator/workflows/audit-skill.md +159 -0
  748. package/skills/skill-creator/workflows/create-domain-expertise-skill.md +623 -0
  749. package/skills/skill-creator/workflows/create-new-skill.md +207 -0
  750. package/skills/skill-creator/workflows/get-guidance.md +134 -0
  751. package/skills/skill-creator/workflows/upgrade-to-router.md +170 -0
  752. package/skills/skill-creator/workflows/verify-skill.md +226 -0
  753. package/skills/skill-security-scan/SKILL.md +145 -0
  754. package/skills/skill-security-scan/references/disclaimer.md +21 -0
  755. package/skills/skill-security-scan/references/first-party-allowlist.yaml +38 -0
  756. package/skills/skill-security-scan/references/override-artifact-schema.json +68 -0
  757. package/skills/skill-security-scan/references/override-mechanism.md +171 -0
  758. package/skills/skill-security-scan/references/redirect-domains.yaml +27 -0
  759. package/skills/skill-security-scan/references/regex-patterns.md +136 -0
  760. package/skills/skill-security-scan/references/rules/code-exec.yaml +70 -0
  761. package/skills/skill-security-scan/references/rules/filesystem-boundary.yaml +29 -0
  762. package/skills/skill-security-scan/references/rules/manifest.yaml +44 -0
  763. package/skills/skill-security-scan/references/rules/supply-chain.yaml +36 -0
  764. package/skills/skill-security-scan/references/scan-meta-schema.json +63 -0
  765. package/skills/skill-security-scan/references/test-fixtures/clean-soleur-style.skill.md +23 -0
  766. package/skills/skill-security-scan/references/test-fixtures/clean-third-party.skill.md +26 -0
  767. package/skills/skill-security-scan/references/test-fixtures/malicious-codeexec.skill.md +23 -0
  768. package/skills/skill-security-scan/references/test-fixtures/malicious-curl-pipe-bash.skill.md +69 -0
  769. package/skills/skill-security-scan/references/test-fixtures/malicious-prompt-injection.skill.md +17 -0
  770. package/skills/skill-security-scan/references/test-fixtures/malicious-telemetry-beacon.skill.md +23 -0
  771. package/skills/skill-security-scan/references/typosquat-targets.yaml +65 -0
  772. package/skills/skill-security-scan/scripts/check-codeexec.sh +41 -0
  773. package/skills/skill-security-scan/scripts/check-filesystem-boundary.sh +19 -0
  774. package/skills/skill-security-scan/scripts/check-prompt-injection.sh +95 -0
  775. package/skills/skill-security-scan/scripts/check-supply-chain.sh +219 -0
  776. package/skills/skill-security-scan/scripts/check-telemetry-surface.sh +126 -0
  777. package/skills/skill-security-scan/scripts/lib.sh +197 -0
  778. package/skills/skill-security-scan/scripts/parse-override.sh +231 -0
  779. package/skills/skill-security-scan/scripts/run-scan.sh +342 -0
  780. package/skills/skill-security-scan/scripts/run-self-test.sh +95 -0
  781. package/skills/skill-security-scan/test/argv-ceiling.test.sh +180 -0
  782. package/skills/skill-security-scan/test/meta-dir-reap.test.sh +411 -0
  783. package/skills/skill-security-scan/test/parse-override.test.sh +607 -0
  784. package/skills/skill-security-scan/test/redact-pii-ghs-shape.test.sh +251 -0
  785. package/skills/social-distribute/SKILL.md +485 -0
  786. package/skills/spec-templates/SKILL.md +274 -0
  787. package/skills/sync/SKILL.md +49 -0
  788. package/skills/test-browser/SKILL.md +464 -0
  789. package/skills/test-browser/references/feature-map.md +161 -0
  790. package/skills/test-fix-loop/SKILL.md +159 -0
  791. package/skills/test-fix-loop/references/determinism-checklist.md +154 -0
  792. package/skills/triage/SKILL.md +402 -0
  793. package/skills/trigger-cron/SKILL.md +136 -0
  794. package/skills/trigger-cron/scripts/trigger.sh +198 -0
  795. package/skills/user-set-role/SKILL.md +147 -0
  796. package/skills/user-set-role/scripts/set-role.sh +218 -0
  797. package/skills/user-story-writer/SKILL.md +77 -0
  798. package/skills/ux-audit/SKILL.md +251 -0
  799. package/skills/ux-audit/references/finding.schema.json +62 -0
  800. package/skills/ux-audit/references/route-list.yaml +95 -0
  801. package/skills/ux-audit/scripts/bot-fixture.ts +270 -0
  802. package/skills/ux-audit/scripts/bot-signin.ts +135 -0
  803. package/skills/ux-audit/scripts/dedup-hash.ts +46 -0
  804. package/skills/verification-scaffold/SKILL.md +73 -0
  805. package/skills/verification-scaffold/references/gitleaks.toml.template +13 -0
  806. package/skills/verification-scaffold/references/guardrail-audit.md +156 -0
  807. package/skills/verification-scaffold/references/guards/block-stash-hook.sh.template +72 -0
  808. package/skills/verification-scaffold/references/guards/commit-on-main-hook.sh.template +82 -0
  809. package/skills/verification-scaffold/references/guards/orphan-test-check.sh.template +102 -0
  810. package/skills/verification-scaffold/references/guards/precommit-guard.sh.template +115 -0
  811. package/skills/verification-scaffold/references/guards/secret-scan-hook.sh.template +176 -0
  812. package/skills/verification-scaffold/references/guards/verification-gates.yml.template +49 -0
  813. package/skills/verification-scaffold/references/guards/wrapper-boundary.sh.template +47 -0
  814. package/skills/verification-scaffold/references/guards.tsv +21 -0
  815. package/skills/verification-scaffold/references/verification-readme.template +55 -0
  816. package/skills/verification-scaffold/scripts/verification-scaffold.sh +785 -0
  817. package/skills/verification-scaffold/test/bite-proof.test.sh +159 -0
  818. package/skills/verification-scaffold/test/generator.test.sh +319 -0
  819. package/skills/verification-scaffold/test/manifest.test.sh +144 -0
  820. package/skills/work/SKILL.md +1480 -0
  821. package/skills/work/references/end-of-work-resume.md +7 -0
  822. package/skills/work/references/hook-test-main-cwd.md +24 -0
  823. package/skills/work/references/key-principles.md +83 -0
  824. package/skills/work/references/property-testing.md +122 -0
  825. package/skills/work/references/sql-security-definer-rpc-scaffold.sql +78 -0
  826. package/skills/work/references/work-agent-teams.md +97 -0
  827. package/skills/work/references/work-lifecycle-parallel.md +147 -0
  828. package/skills/work/references/work-lockfile-bumps.md +97 -0
  829. package/skills/work/references/work-scratch-sandboxes.md +212 -0
  830. package/skills/work/references/work-subagent-fanout.md +61 -0
  831. package/skills/work/references/work-test-harness-sharp-edges.md +73 -0
  832. 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