@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,908 @@
1
+ #!/usr/bin/env bash
2
+ # affected-tests.sh — the plugin-generic, stack-detecting affected-only test
3
+ # gate (#9307 deliverable 2). One entry point for soleur:work Phase 2,
4
+ # soleur:ship Phase 4, soleur:review's lead gate and grok-pre-push-gate.sh, in
5
+ # ANY repository — including ones with no scripts/test-all.sh.
6
+ #
7
+ # Usage:
8
+ # bash affected-tests.sh [--full] [--base <ref>] [--explain] [--self-check]
9
+ #
10
+ # Dispatch precedence (exactly this order):
11
+ # 1. `test_command` in repo-root `.soleur-test-gate.json` — run via `bash -c`
12
+ # from the repo root; bypasses selection entirely.
13
+ # 2. A repo runner ADVERTISING the Soleur capability — `scripts/test-all.sh`
14
+ # whose `--print-selection` flag is registered at a case-label or
15
+ # usage-table position (a comment-only mention does NOT count). The probe
16
+ # runs BEFORE any gate-side classification: a capability-positive runner
17
+ # owns the diff decision (its own inert/global/empty handling is strictly
18
+ # better informed), so a resolved runner is ALWAYS delegated to —
19
+ # `--affected`, or `--full` only under this script's `--full`. A foreign
20
+ # script of that name is NEVER invoked with Soleur flags — it falls
21
+ # through to 3.
22
+ # 3. Stack adapters, in order: nx/turbo, JS/TS (vitest `related` SUBCOMMAND /
23
+ # jest --findRelatedTests), Python (pytest + testmon/xdist), Go (`go list`
24
+ # reverse-dependency closure). The gate's own base resolution, changed-set
25
+ # census and global/inert classification apply ONLY to this arm — under
26
+ # delegation the runner resolves its own base and classification, and
27
+ # `--base` steers the adapters alone.
28
+ #
29
+ # Exit contract — produced ONLY by finish() in lib/affected-tests/common.sh,
30
+ # the single exit site for the whole gate:
31
+ # 0 every selected test passed, OR NO_TESTS_AFFECTED for an all-inert diff
32
+ # 1 a selected test failed, or the delegated runner was signal-killed (rc 3
33
+ # maps to 1 reason=runner-signal); a failure seen before budget expiry
34
+ # wins over deferral
35
+ # 4 AFFECTED_GATE_REFUSED — no-stack, bad-config key=<k>,
36
+ # vacuous-test-command, no-base-ref, not-a-repo (--self-check outside a
37
+ # repository), toolchain-missing, runner-refused,
38
+ # foreign-runner (a foreign scripts/test-all.sh AND no adapter resolves)
39
+ # 6 AFFECTED_GATE_BUDGET_DEFERRED — budget expired AND a CI test workflow was
40
+ # detected; CI stays the authoritative gate
41
+ # 7 no test suite detected at all — disclosed note, never labelled passed
42
+ #
43
+ # Output contract:
44
+ # human: `Ran X of the repo's N test files affected by this change:
45
+ # passed.` (N is the repo-wide census, not the affected subset) —
46
+ # never the words "tests verified"; beside it the literal verdict
47
+ # `affected-suite gate passed.`
48
+ # inert: `NO_TESTS_AFFECTED inert_rule=<pat>` + `No tests ran: docs-only change.`
49
+ # marker: `AFFECTED_GATE ran=<X> of=<N> skipped=<Y>
50
+ # mode=<selected|full|custom|explain|none|help> reason=<…> stack=<…>
51
+ # ci=<file|none>` on every verdict path including refusals; counts
52
+ # are `-` on paths that produce no honest count (delegated runner,
53
+ # opaque test_command, refusals) — a delegated or custom rc 0 still
54
+ # prints `affected-suite gate passed.`
55
+ #
56
+ # `.soleur-test-gate.json` (repo root, committed, every key optional; unknown
57
+ # key / malformed JSON / wrong type → rc 4 reason=bad-config key=<k>):
58
+ # test_command string full-run command; true/`:`/`exit 0`/bare `echo`,
59
+ # or any command whose LAST ;/&&/||/| segment is a
60
+ # vacuous primitive (`cmd || true`, `cmd ; exit 0`,
61
+ # `bash -c 'exit 0'`) → rc 4 reason=vacuous-test-command
62
+ # (a documented denylist, not a proof)
63
+ # global_paths string[] REPLACES built-ins; any hit forces the full command
64
+ # inert_paths string[] REPLACES built-ins (`**/*.md` default); an all-inert
65
+ # diff → rc 0 NO_TESTS_AFFECTED; a mixed diff is never
66
+ # inert. A match-everything pattern (`*`, `**`,
67
+ # `**/*`, anything matching an arbitrary path) is a
68
+ # permanent green → rc 4 bad-config key=inert_paths
69
+ # budget_seconds number wall-clock cap, enforced ONLY when a CI test
70
+ # workflow is detected; default 900 (unmeasured);
71
+ # must be a positive integer
72
+ # max_workers number overrides the width formula; positive integer
73
+ # budget_ci boolean forces CI-deferral eligibility without a workflow
74
+ #
75
+ # Base-ref order (adapter arm only — a delegated runner resolves its own):
76
+ # --base > @{upstream} > origin/HEAD > origin/main >
77
+ # origin/master > merge-base over every remote-tracking ref (newest wins).
78
+ # Changed set = committed-vs-base ∪ staged ∪ unstaged ∪ untracked. A shallow
79
+ # clone gets ONE bounded deepen, then rc 4 reason=no-base-ref — never a silent
80
+ # full, never a silent empty.
81
+ #
82
+ # CI detection: `.github/workflows/*.yml|*.yaml` carrying a pull_request/push
83
+ # trigger AND a test-command shape, or `budget_ci: true`. No CI → the budget is
84
+ # ignored, run to completion, an upfront line says so.
85
+ #
86
+ # Worker width (unmeasured heuristic): max(1, ncpu/2) / (1 + other running
87
+ # affected-tests.sh processes) — counted on the argv SLOT, never pgrep -f or
88
+ # ps|grep; max_workers overrides. Maps to vitest/jest --maxWorkers, pytest -n,
89
+ # go test -p, nx --parallel, turbo --concurrency.
90
+ #
91
+ # Portability contract (asserted by affected-tests.test.sh): no bare `timeout`
92
+ # (timeout→gtimeout→bounded-watchdog in lib common.sh), no `nproc` outside the
93
+ # ncpu fallback chain, no `sed -i`, `date -d`, `readlink -f`, `stat -c`. Bash
94
+ # 3.2-safe: no `declare -A`, no `mapfile`, `${arr[@]+"${arr[@]}"}` under set -u.
95
+ #
96
+ # External binaries invoked across this script and its lib:
97
+ # core: bash git grep find sort tr cut head tail awk sed cat date sleep
98
+ # kill ps getconf sysctl nproc mktemp rm wc
99
+ # config: python3 node jq (first resolvable, schema is validated in bash)
100
+ # runners: scripts/test-all.sh (capability-probed), vitest jest npx npm pnpm
101
+ # yarn bun pytest go nx turbo
102
+ # bounds: timeout gtimeout (either may be absent — watchdog covers)
103
+ #
104
+ # Cloud (Devin): the gate resolves its libs by BASH_SOURCE and needs no plugin
105
+ # root — CLAUDE_PLUGIN_ROOT may be unset. A detected stack with no runnable
106
+ # toolchain → rc 4 reason=toolchain-missing.
107
+
108
+ set -euo pipefail
109
+
110
+ GATE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
111
+ # shellcheck source=plugins/soleur/scripts/lib/affected-tests/common.sh
112
+ source "$GATE_DIR/lib/affected-tests/common.sh"
113
+ # shellcheck source=plugins/soleur/scripts/lib/affected-tests/nx-turbo.sh
114
+ source "$GATE_DIR/lib/affected-tests/nx-turbo.sh"
115
+ # shellcheck source=plugins/soleur/scripts/lib/affected-tests/js.sh
116
+ source "$GATE_DIR/lib/affected-tests/js.sh"
117
+ # shellcheck source=plugins/soleur/scripts/lib/affected-tests/python.sh
118
+ source "$GATE_DIR/lib/affected-tests/python.sh"
119
+ # shellcheck source=plugins/soleur/scripts/lib/affected-tests/go.sh
120
+ source "$GATE_DIR/lib/affected-tests/go.sh"
121
+
122
+ # Scratch files (config dump, changed-set census, adapter logs) are registered
123
+ # in AFFECTED_SCRATCH by affected_mktemp — remove them on every exit shape,
124
+ # including the early refusal paths that never reach an adapter's own rm.
125
+ # EXIT alone leaves signal semantics intact — trapping INT/TERM on the same
126
+ # handler would swallow the signal and resume at the interrupted statement.
127
+ trap 'affected_scratch_clean' EXIT
128
+ trap 'affected_scratch_clean; exit 130' INT
129
+ trap 'affected_scratch_clean; exit 143' TERM
130
+
131
+ usage() {
132
+ sed -n '1,20p' "$0" | grep -E '^#' | sed 's/^# \{0,1\}//'
133
+ cat <<'EOF'
134
+ Usage:
135
+ bash affected-tests.sh [--full] [--base <ref>] [--explain] [--self-check]
136
+
137
+ --full bypass selection; run the detected full command
138
+ --base <ref> override base-ref resolution for the changed set
139
+ --explain print the selection and per-path reasons; run nothing
140
+ --self-check validate config + toolchain probe only; never runs tests
141
+ EOF
142
+ }
143
+
144
+ # --- args ---------------------------------------------------------------------
145
+ ARG_FULL=0
146
+ ARG_BASE=""
147
+ ARG_EXPLAIN=0
148
+ ARG_SELFCHECK=0
149
+ while (( $# > 0 )); do
150
+ case "$1" in
151
+ --full) ARG_FULL=1 ;;
152
+ --base) shift
153
+ (( $# > 0 )) || { echo "affected-tests: --base needs a ref" >&2
154
+ finish 4 none bad-usage - none - - -; }
155
+ ARG_BASE="$1" ;;
156
+ --explain) ARG_EXPLAIN=1 ;;
157
+ --self-check) ARG_SELFCHECK=1 ;;
158
+ -h|--help) usage; finish 0 help help - none - - - ;;
159
+ *) echo "affected-tests: unknown argument '$1' (try --help)" >&2
160
+ finish 4 none bad-usage - none - - - ;;
161
+ esac
162
+ shift
163
+ done
164
+
165
+ # --- repository root ------------------------------------------------------------
166
+ REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || true)"
167
+ if [[ -z "$REPO_ROOT" ]]; then
168
+ # Outside a repository the cause is not a missing base ref — name it.
169
+ (( ARG_SELFCHECK )) && finish 4 none not-a-repo - none - - -
170
+ finish 4 none no-base-ref - none - - -
171
+ fi
172
+ cd "$REPO_ROOT" || finish 4 none no-base-ref - none - - -
173
+
174
+ # --- CI detection (needed early: it arms the budget and fills ci=) ----------------
175
+ CI_NAME=none
176
+ affected_detect_ci() {
177
+ local f
178
+ for f in .github/workflows/*.yml .github/workflows/*.yaml; do
179
+ [[ -f "$f" ]] || continue
180
+ # pull_request or push trigger AND a test-command shape in the same file —
181
+ # a workflow that merely exists is not a gate to defer to. `#` comments are
182
+ # stripped BEFORE matching: a deploy workflow that only TALKS about tests
183
+ # in a comment is not a test gate to defer to.
184
+ #
185
+ # The trigger must be a yaml DECLARATION, not a substring — a bare
186
+ # `pull_request|push` grep reads `run: git push` or `pushd deploy/` inside a
187
+ # step as a CI test gate. Accepted shapes, tracked inside the `on:` block:
188
+ # on: push on: [push, pull_request] on: {push: …}
189
+ # on:\n push: on:\n pull_request: on:\n - push
190
+ sed 's/#.*//' "$f" | awk '
191
+ # `on:` is a TOP-LEVEL yaml key — column 0 only. An indented `on:` inside a
192
+ # `run: |` block scalar is script text, not a trigger declaration.
193
+ /^on[[:space:]]*:/ {
194
+ rest = $0
195
+ sub(/^on[[:space:]]*:[[:space:]]*/, "", rest)
196
+ # Scalar / flow sequence / flow map on the on: line itself.
197
+ if (rest ~ /(^|[][{},[:space:]])(pull_request|push)([]},:[:space:]]|$)/) found = 1
198
+ inon = 1
199
+ next
200
+ }
201
+ # The block ends at the next top-level (column-0) key; blank lines do not.
202
+ inon && /^[^[:space:]]/ { inon = 0 }
203
+ # Block-map key (`push:`/`pull_request:`) or sequence entry (`- push`).
204
+ inon && /^[[:space:]]*-?[[:space:]]*(pull_request|push)[[:space:]]*:/ { found = 1 }
205
+ inon && /^[[:space:]]*-[[:space:]]*(pull_request|push)[[:space:]]*$/ { found = 1 }
206
+ END { exit(!found) }
207
+ ' || continue
208
+ if grep -qE 'test-all\.sh|npm[[:space:]]+(run[[:space:]]+)?test|yarn[[:space:]]+test|pnpm[[:space:]]+(run[[:space:]]+)?test|bun[[:space:]]+test|vitest|jest|pytest|go[[:space:]]+test|cargo[[:space:]]+test|ctest|make[[:space:]]+test|deno[[:space:]]+test' < <(sed 's/#.*//' "$f"); then
209
+ CI_NAME="$(affected_marker_field "$f")"
210
+ return 0
211
+ fi
212
+ done
213
+ return 1
214
+ }
215
+
216
+ # --- config: .soleur-test-gate.json ------------------------------------------------
217
+ CFG_TEST_COMMAND=""
218
+ CFG_BUDGET_SECONDS=900 # default, unmeasured
219
+ CFG_MAX_WORKERS=""
220
+ CFG_BUDGET_CI=false
221
+ CFG_GLOBAL_SET=0
222
+ CFG_INERT_SET=0
223
+ CFG_GLOBAL_PATHS=()
224
+ CFG_INERT_PATHS=()
225
+
226
+ # Dump the JSON object as NUL-separated KEY\0TYPE\0VALUE\0 triples on stdout —
227
+ # array elements arrive as one `elem:<type>` record each, an empty array as a
228
+ # bare `array` record so present-but-empty stays distinct from absent. The
229
+ # SCHEMA is validated by the caller; these emitters only decode. rc 3 = the
230
+ # file is not a JSON object (malformed).
231
+ _affected_json_dump() {
232
+ local f="$1"
233
+ if command -v python3 >/dev/null 2>&1; then
234
+ # -I: isolated mode keeps the repo's cwd OFF sys.path — a repo-controlled
235
+ # pytest.py/sitecustomize.py must never execute during a config read.
236
+ python3 -I - "$f" <<'PYEOF'
237
+ import json, sys
238
+ try:
239
+ d = json.load(open(sys.argv[1]))
240
+ except Exception:
241
+ sys.exit(3)
242
+ if not isinstance(d, dict):
243
+ sys.exit(3)
244
+ def jt(v):
245
+ if isinstance(v, bool): return 'boolean'
246
+ if isinstance(v, (int, float)): return 'number'
247
+ if isinstance(v, str): return 'string'
248
+ if isinstance(v, list): return 'array'
249
+ if v is None: return 'null'
250
+ return 'object'
251
+ out = sys.stdout
252
+ for k, v in d.items():
253
+ if isinstance(v, list):
254
+ if not v:
255
+ out.write(k + '\0array\0\0')
256
+ for e in v:
257
+ out.write(k + '\0elem:' + jt(e) + '\0' + (e if isinstance(e, str) else json.dumps(e)) + '\0')
258
+ else:
259
+ out.write(k + '\0' + jt(v) + '\0' + (v if isinstance(v, str) else json.dumps(v)) + '\0')
260
+ PYEOF
261
+ return $?
262
+ fi
263
+ if command -v node >/dev/null 2>&1; then
264
+ node - "$f" <<'JSEOF'
265
+ const fs = require('fs');
266
+ let d;
267
+ try { d = JSON.parse(fs.readFileSync(process.argv[2], 'utf8')); } catch (e) { process.exit(3); }
268
+ if (typeof d !== 'object' || d === null || Array.isArray(d)) process.exit(3);
269
+ const jt = v => v === null ? 'null' : Array.isArray(v) ? 'array' : typeof v;
270
+ for (const [k, v] of Object.entries(d)) {
271
+ if (Array.isArray(v)) {
272
+ if (!v.length) process.stdout.write(k + '\0array\0\0');
273
+ for (const e of v) process.stdout.write(k + '\0elem:' + jt(e) + '\0' + (typeof e === 'string' ? e : JSON.stringify(e)) + '\0');
274
+ } else {
275
+ process.stdout.write(k + '\0' + jt(v) + '\0' + (typeof v === 'string' ? v : JSON.stringify(v)) + '\0');
276
+ }
277
+ }
278
+ JSEOF
279
+ return $?
280
+ fi
281
+ if command -v jq >/dev/null 2>&1; then
282
+ # Same contract as the py/node emitters: a non-object top level is not a
283
+ # config — `[]` decodes to ZERO records here (silent accept) while py/node
284
+ # exit 3. Refuse it identically before the dump.
285
+ jq -e 'type == "object"' "$f" >/dev/null 2>&1 || return 3
286
+ jq -j '
287
+ to_entries[] | .key as $k | (.value | type) as $t |
288
+ if $t == "array" then
289
+ if (.value | length) == 0 then ($k, "\u0000", "array", "\u0000", "\u0000")
290
+ else (.value[] | ($k, "\u0000", ("elem:" + (. | type)), "\u0000",
291
+ (if (. | type) == "string" then . else tojson end), "\u0000"))
292
+ end
293
+ else ($k, "\u0000", $t, "\u0000",
294
+ (if $t == "string" then .value else (.value | tojson) end), "\u0000")
295
+ end' "$f" 2>/dev/null
296
+ return $?
297
+ fi
298
+ # No JSON engine: a present config we cannot parse is a toolchain fact, and
299
+ # the fail direction is CLOSED.
300
+ return 2
301
+ }
302
+
303
+ # Load and validate. Fail-closed everywhere: malformed, unknown key, or wrong
304
+ # type → rc 4 reason=bad-config key=<k>; a vacuous test_command →
305
+ # reason=vacuous-test-command.
306
+ affected_load_config() {
307
+ local cfg=".soleur-test-gate.json"
308
+ [[ -f "$cfg" ]] || return 0
309
+ local tmp rc=0
310
+ affected_mktemp cfg || finish 4 none mktemp-failed - "$CI_NAME" - - -; tmp="$AFFECTED_TMP"
311
+ _affected_json_dump "$cfg" >"$tmp" || rc=$?
312
+ if (( rc == 2 )); then
313
+ finish 4 none "toolchain-missing" config "$CI_NAME" - - -
314
+ fi
315
+ if (( rc != 0 )); then
316
+ finish 4 none "bad-config key=$(affected_marker_field "$cfg")" config "$CI_NAME" - - -
317
+ fi
318
+
319
+ local k t v badkey=""
320
+ while IFS= read -r -d '' k && IFS= read -r -d '' t && IFS= read -r -d '' v; do
321
+ case "$k" in
322
+ test_command)
323
+ [[ "$t" == string ]] || { badkey="$k"; break; }
324
+ CFG_TEST_COMMAND="$v" ;;
325
+ global_paths|inert_paths)
326
+ if [[ "$t" == array ]]; then
327
+ [[ "$k" == global_paths ]] && CFG_GLOBAL_SET=1 || CFG_INERT_SET=1
328
+ elif [[ "$t" == elem:string ]]; then
329
+ [[ "$k" == global_paths ]] && { CFG_GLOBAL_SET=1; CFG_GLOBAL_PATHS+=("$v"); } \
330
+ || { CFG_INERT_SET=1; CFG_INERT_PATHS+=("$v"); }
331
+ else
332
+ badkey="$k"; break
333
+ fi ;;
334
+ budget_seconds)
335
+ [[ "$t" == number ]] || { badkey="$k"; break; }
336
+ v="${v%%.*}"
337
+ # Clamped: a zero/negative/decimal-floor-0 budget would defer instantly
338
+ # or arm a nonsense deadline — that is a config error, fail closed.
339
+ [[ "$v" =~ ^[0-9]+$ ]] && (( v >= 1 )) || { badkey="$k"; break; }
340
+ CFG_BUDGET_SECONDS="$v" ;;
341
+ max_workers)
342
+ [[ "$t" == number ]] || { badkey="$k"; break; }
343
+ v="${v%%.*}"
344
+ [[ "$v" =~ ^[0-9]+$ ]] && (( v >= 1 )) || { badkey="$k"; break; }
345
+ CFG_MAX_WORKERS="$v" ;;
346
+ budget_ci)
347
+ [[ "$t" == boolean ]] || { badkey="$k"; break; }
348
+ CFG_BUDGET_CI="$v" ;;
349
+ *) badkey="$k"; break ;;
350
+ esac
351
+ done <"$tmp"
352
+ if [[ -n "$badkey" ]]; then
353
+ finish 4 none "bad-config key=$(affected_marker_field "$badkey")" config "$CI_NAME" - - -
354
+ fi
355
+
356
+ if [[ -n "$CFG_TEST_COMMAND" ]]; then
357
+ # Vacuous escape hatches are refused — `true`, `:`, `exit 0`, a bare echo,
358
+ # and every composition that ends in a swallower: `cmd || true`,
359
+ # `cmd ; exit 0`, `cmd && true`, `cmd | true`, `bash -c 'exit 0'`,
360
+ # `true # comment`. The check is a documented DENYLIST (common.sh
361
+ # _affected_cmd_vacuous): comments are stripped, whitespace normalised, the
362
+ # command split on ;/&&/||/| — a vacuous LAST segment decides the whole
363
+ # command's rc, so it fails closed. What is not listed still runs verbatim.
364
+ if _affected_cmd_vacuous "$CFG_TEST_COMMAND"; then
365
+ finish 4 custom vacuous-test-command custom "$CI_NAME" - - -
366
+ fi
367
+ fi
368
+ }
369
+ affected_detect_ci || true # a miss is the common case; never fatal
370
+ affected_load_config
371
+ if [[ "$CI_NAME" == none && "$CFG_BUDGET_CI" == true ]]; then
372
+ CI_NAME="config:budget_ci"
373
+ fi
374
+
375
+ # --- self-check -------------------------------------------------------------------
376
+ if (( ARG_SELFCHECK )); then
377
+ # Config load + toolchain probe only; never runs tests. Bounded, not instant:
378
+ # every probe is `command -v` or a bounded --version, so the worst case is
379
+ # the per-stack timeout sum (~80 s over 4 stacks), the happy path seconds.
380
+ command -v git >/dev/null 2>&1 || finish 4 none toolchain-missing config "$CI_NAME" - - -
381
+ # A capability-positive runner owns the run under delegation — adapter
382
+ # toolchains are then unreachable, so a missing one is informational only.
383
+ _sc_runner=0
384
+ if [[ -f scripts/test-all.sh ]] \
385
+ && grep -Eq '^[[:space:]]*(--[[:alnum:]_-]+\|)*--print-selection([])|,[:space:]])' scripts/test-all.sh; then
386
+ _sc_runner=1
387
+ fi
388
+ # Probe each detected stack's toolchain: detection is free, and each tool()
389
+ # --version probe is already bounded.
390
+ _sc_trc=0; _sc_missing=0
391
+ for _sc_stack in nxt js py go; do
392
+ if "affected_${_sc_stack}_detect"; then
393
+ _sc_trc=0
394
+ "affected_${_sc_stack}_tool" || _sc_trc=$?
395
+ case "$_sc_trc" in
396
+ 0) echo "AFFECTED_GATE_SELF_CHECK stack=${_sc_stack} toolchain=ok" ;;
397
+ 2) echo "AFFECTED_GATE_SELF_CHECK stack=${_sc_stack} toolchain=no-test-tool" ;;
398
+ *) echo "AFFECTED_GATE_SELF_CHECK stack=${_sc_stack} toolchain=missing"
399
+ _sc_missing=1 ;;
400
+ esac
401
+ fi
402
+ done
403
+ # A detected stack whose toolchain cannot run is not "ok" WHEN IT CAN GATE —
404
+ # under delegation the adapters never run, so the gap is disclosed per-stack
405
+ # above but does not refuse the check.
406
+ if (( _sc_missing && _sc_runner == 0 )); then
407
+ echo "AFFECTED_GATE_SELF_CHECK toolchain-missing"
408
+ finish 4 none toolchain-missing - "$CI_NAME" - - -
409
+ fi
410
+ echo "AFFECTED_GATE_SELF_CHECK ok"
411
+ finish 0 none ok - "$CI_NAME" - - -
412
+ fi
413
+
414
+ # --- the custom-command arm -------------------------------------------------------
415
+ # --explain under test_command answers BEFORE the arm can run it: the custom
416
+ # command IS the whole selection, so explain emits mode=custom + the command.
417
+ if (( ARG_EXPLAIN )) && [[ -n "$CFG_TEST_COMMAND" ]]; then
418
+ echo "SELECTION mode=custom reason=custom-command"
419
+ printf 'COMMAND %s\n' "$CFG_TEST_COMMAND"
420
+ finish 0 explain custom-command custom "$CI_NAME" - - -
421
+ fi
422
+ if [[ -n "$CFG_TEST_COMMAND" ]]; then
423
+ if [[ "$CI_NAME" != none ]]; then
424
+ AFFECTED_BUDGET_ACTIVE=1
425
+ AFFECTED_DEADLINE=$(( $(affected_now) + CFG_BUDGET_SECONDS ))
426
+ else
427
+ echo "affected-tests: no CI test workflow detected — wall-clock budget not enforced; running to completion."
428
+ fi
429
+ rc=0
430
+ affected_bound bash -c "$CFG_TEST_COMMAND" || rc=$?
431
+ case "$rc" in
432
+ 0) finish 0 custom custom-command custom "$CI_NAME" - - - ;;
433
+ 124|137)
434
+ (( rc == 137 )) && echo "affected-tests: the command exited 137 (SIGKILL — OOM or kill escalation), not necessarily budget expiry; deferral still applies." >&2
435
+ if [[ "$CI_NAME" != none ]]; then
436
+ finish 6 custom budget-deferred custom "$CI_NAME" - - -
437
+ fi
438
+ finish 1 custom failed custom "$CI_NAME" - - - ;;
439
+ *) finish 1 custom failed custom "$CI_NAME" - - - ;;
440
+ esac
441
+ fi
442
+
443
+ # --- dispatch 2: the capability-positive repo runner -----------------------------------
444
+ # The probe reads the REGISTRATION, not the filename and never a comment: the
445
+ # flag must sit at a case-label or usage-table position — first token on the
446
+ # line (optionally inside a `a|b)` alternation) followed by a flag-table
447
+ # terminator.
448
+ #
449
+ # ORDERING IS THE CONTRACT: the probe runs BEFORE the gate's own base
450
+ # resolution, changed-set census and global/inert classification. A runner that
451
+ # registers --print-selection computes all of those itself, better informed —
452
+ # so a resolved runner is ALWAYS delegated to (--affected; --full only when the
453
+ # operator asked). Delegating an all-inert or global-paths diff is not waste:
454
+ # the runner's own classifier decides, and in this repo the always-on ratchets
455
+ # are live edges the gate's built-ins cannot see. Gate-side classification
456
+ # below is the adapter arm's alone.
457
+ RUNNER=""
458
+ if [[ -f scripts/test-all.sh ]] \
459
+ && grep -Eq '^[[:space:]]*(--[[:alnum:]_-]+\|)*--print-selection([])|,[:space:]])' scripts/test-all.sh; then
460
+ RUNNER="scripts/test-all.sh"
461
+ fi
462
+
463
+ if [[ -n "$RUNNER" ]]; then
464
+ _flag=--affected
465
+ (( ARG_FULL )) && _flag=--full
466
+ _dmode=selected
467
+ (( ARG_FULL )) && _dmode=full
468
+ if (( ARG_EXPLAIN )); then
469
+ # The runner owns the selection; invoking --print-selection here is left to
470
+ # the operator (it can cost real time), so explain reports the delegation.
471
+ echo "SELECTION mode=delegated runner=$RUNNER flag=$_flag"
472
+ echo "runner owns selection — inspect with: bash $RUNNER --print-selection"
473
+ finish 0 explain delegated runner "$CI_NAME" - - -
474
+ fi
475
+ if [[ "$CI_NAME" != none ]]; then
476
+ AFFECTED_BUDGET_ACTIVE=1
477
+ AFFECTED_DEADLINE=$(( $(affected_now) + CFG_BUDGET_SECONDS ))
478
+ else
479
+ echo "affected-tests: no CI test workflow detected — wall-clock budget not enforced; running to completion."
480
+ fi
481
+ rc=0
482
+ affected_bound bash "$RUNNER" "$_flag" || rc=$?
483
+ # Delegated-runner rc mapping: 4 (REFUSED — nothing ran) → 4
484
+ # reason=runner-refused; 3 (UNRESOLVED — killed/declined suites) → 1
485
+ # reason=runner-signal; 2 (usage) → 4 runner-refused; the rest pass through
486
+ # as a plain verdict. rc 124/137 means OUR bound expired — deferral only
487
+ # ever applies with a detected CI, which is the armed-budget condition.
488
+ case "$rc" in
489
+ 0) finish 0 "$_dmode" delegated runner "$CI_NAME" - - - ;;
490
+ 124|137)
491
+ (( rc == 137 )) && echo "affected-tests: the runner exited 137 (SIGKILL — OOM or kill escalation), not necessarily budget expiry; deferral still applies." >&2
492
+ if [[ "$AFFECTED_BUDGET_ACTIVE" == 1 && "$CI_NAME" != none ]]; then
493
+ finish 6 "$_dmode" budget-deferred runner "$CI_NAME" - - -
494
+ fi
495
+ finish 1 "$_dmode" failed runner "$CI_NAME" - - - ;;
496
+ 3) finish 1 "$_dmode" runner-signal runner "$CI_NAME" - - - ;;
497
+ 2|4) finish 4 "$_dmode" runner-refused runner "$CI_NAME" - - - ;;
498
+ *) finish 1 "$_dmode" failed runner "$CI_NAME" - - - ;;
499
+ esac
500
+ fi
501
+
502
+ # --- adapter arm: base ref + changed set (delegated runners never reach here) ---------
503
+ BASE=""
504
+ BASE_SRC=""
505
+ affected_resolve_base() {
506
+ if [[ -n "$ARG_BASE" ]]; then BASE="$ARG_BASE"; BASE_SRC="--base"; return 0; fi
507
+ if git rev-parse --verify --quiet '@{upstream}' >/dev/null 2>&1; then
508
+ BASE='@{upstream}'; BASE_SRC="upstream"; return 0
509
+ fi
510
+ if git rev-parse --verify --quiet 'origin/HEAD' >/dev/null 2>&1; then
511
+ BASE='origin/HEAD'; BASE_SRC="origin/HEAD"; return 0
512
+ fi
513
+ local b
514
+ for b in origin/main origin/master; do
515
+ if git rev-parse --verify --quiet "$b" >/dev/null 2>&1; then
516
+ BASE="$b"; BASE_SRC="$b"; return 0
517
+ fi
518
+ done
519
+ # merge-base fallback: newest fork point over every remote-tracking ref.
520
+ local best="" best_ts=0 ref mb ts
521
+ while IFS= read -r ref; do
522
+ mb="$(git merge-base HEAD "$ref" 2>/dev/null || true)"
523
+ [[ -n "$mb" ]] || continue
524
+ ts="$(git show -s --format=%ct "$mb" 2>/dev/null || true)"
525
+ [[ "$ts" =~ ^[0-9]+$ ]] || ts=0
526
+ if (( ts >= best_ts )); then best="$mb"; best_ts=$ts; fi
527
+ done < <(git for-each-ref --format='%(refname)' refs/remotes 2>/dev/null | head -n 50)
528
+ if [[ -n "$best" ]]; then BASE="$best"; BASE_SRC="merge-base"; return 0; fi
529
+ return 1
530
+ }
531
+
532
+ affected_resolve_base || {
533
+ # One bounded deepen on a shallow clone — then refuse, never a silent
534
+ # full and never a silent empty.
535
+ if [[ "$(git rev-parse --is-shallow-repository 2>/dev/null || true)" == "true" ]]; then
536
+ # --no-tags: the deepen buys commit history for the merge-base, never tag
537
+ # objects — and a bare `git fetch` in the battery closure is a tag-write
538
+ # surface the repo-write-boundary census refuses (battery-tag-authorship).
539
+ affected_timeout 60 git fetch --no-tags --deepen=200 >/dev/null 2>&1 || true
540
+ affected_resolve_base || finish 4 none no-base-ref - "$CI_NAME" - - -
541
+ else
542
+ finish 4 none no-base-ref - "$CI_NAME" - - -
543
+ fi
544
+ }
545
+
546
+ # Changed set: committed-vs-base ∪ staged ∪ unstaged ∪ untracked. Rename-aware
547
+ # (-M): the new path is what exists on disk. The committed arm is the one that
548
+ # can fail outright (unresolvable base) — it is captured ONCE: its rc is the
549
+ # fail-closed probe and its stdout the first leg of the union, so a bad --base
550
+ # refuses rather than reading as an empty diff and no diff runs twice.
551
+ AFFECTED_CHANGED=()
552
+ affected_mktemp changed || finish 4 none mktemp-failed - "$CI_NAME" - - -; CHANGED_TMP="$AFFECTED_TMP"
553
+ _base_rc=0
554
+ # -c core.quotePath=false: without it git emits non-ASCII paths C-quoted
555
+ # ("dir/\303\251.txt"), which never matches a glob and misses the file system.
556
+ _base_diff="$(git -c core.quotePath=false diff --name-only -M "${BASE}...HEAD" -- 2>/dev/null)" || _base_rc=$?
557
+ if (( _base_rc != 0 )); then
558
+ if [[ "$(git rev-parse --is-shallow-repository 2>/dev/null || true)" == "true" ]]; then
559
+ affected_timeout 60 git fetch --no-tags --deepen=200 >/dev/null 2>&1 || true
560
+ _base_rc=0
561
+ _base_diff="$(git -c core.quotePath=false diff --name-only -M "${BASE}...HEAD" -- 2>/dev/null)" || _base_rc=$?
562
+ fi
563
+ if (( _base_rc != 0 )); then
564
+ finish 4 none no-base-ref - "$CI_NAME" - - -
565
+ fi
566
+ fi
567
+ # Each census leg's rc is captured — never `|| true`. A leg that fails toward
568
+ # empty silently NARROWS the selection, and the untracked leg is the worst case
569
+ # (a failed `ls-files --others` drops every NEW test file from the union). Any
570
+ # failed leg makes the changed set undecidable → coverage-favoring forced full
571
+ # (reason=census-degraded), the same contract as an empty diff — never a silent
572
+ # narrow, never a refusal for a recoverable shape.
573
+ _census_rc=0
574
+ _leg_staged="$(git -c core.quotePath=false diff --name-only --cached -M -- 2>/dev/null)" \
575
+ || _census_rc=$?
576
+ _leg_unstaged="$(git -c core.quotePath=false diff --name-only -M -- 2>/dev/null)" \
577
+ || _census_rc=$?
578
+ _leg_untracked="$(git -c core.quotePath=false ls-files --others --exclude-standard 2>/dev/null)" \
579
+ || _census_rc=$?
580
+ {
581
+ printf '%s\n' "$_base_diff"
582
+ printf '%s\n' "$_leg_staged"
583
+ printf '%s\n' "$_leg_unstaged"
584
+ printf '%s\n' "$_leg_untracked"
585
+ } | grep -v '^[[:space:]]*$' | sort -u >"$CHANGED_TMP" || true
586
+ unset _base_rc _base_diff _leg_staged _leg_unstaged _leg_untracked
587
+ while IFS= read -r _p; do
588
+ [[ -n "$_p" ]] && AFFECTED_CHANGED+=("$_p")
589
+ done <"$CHANGED_TMP"
590
+
591
+ # --- classifiers ---------------------------------------------------------------------
592
+ # A pattern containing `/` matches the whole path; `**/` also matches zero
593
+ # directories (its `**/` prefix is stripped for the second try). A bare
594
+ # pattern matches the basename.
595
+ affected_glob_match() {
596
+ local p="$1" pat="$2" alt
597
+ if [[ "$pat" == */* ]]; then
598
+ [[ "$p" == $pat ]] && return 0
599
+ alt="${pat//\*\//}"
600
+ [[ "$alt" != "$pat" && "$p" == $alt ]] && return 0
601
+ return 1
602
+ fi
603
+ [[ "${p##*/}" == $pat ]]
604
+ }
605
+
606
+ GLOBAL_PATHS_BUILTIN=(
607
+ # manifests + lockfiles (any depth — a monorepo's nested manifests count too)
608
+ package.json package-lock.json npm-shrinkwrap.json yarn.lock pnpm-lock.yaml
609
+ pnpm-workspace.yaml bun.lock bun.lockb bunfig.toml
610
+ pyproject.toml poetry.lock Pipfile Pipfile.lock uv.lock requirements.txt
611
+ go.mod go.sum Cargo.toml Cargo.lock Gemfile Gemfile.lock composer.json
612
+ composer.lock
613
+ tsconfig tsconfig.json 'tsconfig.*' 'tsconfig*'
614
+ 'vitest.config.*' 'jest.config.*' conftest.py '.github/workflows/**'
615
+ )
616
+ INERT_PATHS_BUILTIN=('**/*.md')
617
+
618
+ GLOBAL_PATHS=("${GLOBAL_PATHS_BUILTIN[@]}")
619
+ INERT_PATHS=("${INERT_PATHS_BUILTIN[@]}")
620
+ (( CFG_GLOBAL_SET )) && GLOBAL_PATHS=(${CFG_GLOBAL_PATHS[@]+"${CFG_GLOBAL_PATHS[@]}"})
621
+ if (( CFG_INERT_SET )); then
622
+ # A match-everything inert pattern (`*`, `**`, `**/*` — anything matching an
623
+ # arbitrary path) is a PERMANENT green: every diff would classify all-inert
624
+ # and exit 0. Refuse the config rather than certify nothing. Probe with an
625
+ # improbable nested path through the same matcher the classifier uses.
626
+ for _ip in ${CFG_INERT_PATHS[@]+"${CFG_INERT_PATHS[@]}"}; do
627
+ if affected_glob_match "zz9q8/probe.nonexistent" "$_ip"; then
628
+ finish 4 none "bad-config key=inert_paths" config "$CI_NAME" - - -
629
+ fi
630
+ done
631
+ INERT_PATHS=(${CFG_INERT_PATHS[@]+"${CFG_INERT_PATHS[@]}"})
632
+ fi
633
+
634
+ GLOBAL_HIT=""
635
+ ALL_INERT=1
636
+ INERT_RULE="-"
637
+ for _p in ${AFFECTED_CHANGED[@]+"${AFFECTED_CHANGED[@]}"}; do
638
+ _hit=0
639
+ for _g in ${GLOBAL_PATHS[@]+"${GLOBAL_PATHS[@]}"}; do
640
+ if affected_glob_match "$_p" "$_g"; then
641
+ GLOBAL_HIT="$_p"; _hit=1; break
642
+ fi
643
+ done
644
+ if (( _hit == 0 )); then
645
+ _inert=0
646
+ for _i in ${INERT_PATHS[@]+"${INERT_PATHS[@]}"}; do
647
+ if affected_glob_match "$_p" "$_i"; then _inert=1; INERT_RULE="$_i"; break; fi
648
+ done
649
+ (( _inert )) || ALL_INERT=0
650
+ else
651
+ ALL_INERT=0
652
+ fi
653
+ done
654
+
655
+ # --- explain -------------------------------------------------------------------------
656
+ if (( ARG_EXPLAIN )); then
657
+ echo "AFFECTED_EXPLAIN base=${BASE:-none} base_source=${BASE_SRC:-none} changed=${#AFFECTED_CHANGED[@]}"
658
+ _xn=0
659
+ for _p in ${AFFECTED_CHANGED[@]+"${AFFECTED_CHANGED[@]}"}; do
660
+ _xn=$((_xn + 1))
661
+ if (( _xn > 200 )); then
662
+ # The PATH dump is capped — a giant diff gets the verdict and a count,
663
+ # not an unbounded listing.
664
+ echo "PATH … and $(( ${#AFFECTED_CHANGED[@]} - 200 )) more"
665
+ break
666
+ fi
667
+ _cls=changed; _rule=-
668
+ for _g in ${GLOBAL_PATHS[@]+"${GLOBAL_PATHS[@]}"}; do
669
+ affected_glob_match "$_p" "$_g" && { _cls=global; _rule="$_g"; break; }
670
+ done
671
+ if [[ "$_cls" == changed ]]; then
672
+ for _i in ${INERT_PATHS[@]+"${INERT_PATHS[@]}"}; do
673
+ affected_glob_match "$_p" "$_i" && { _cls=inert; _rule="$_i"; break; }
674
+ done
675
+ fi
676
+ echo "PATH $(affected_marker_field "$_p") class=${_cls} rule=$(affected_marker_field "$_rule")"
677
+ done
678
+ fi
679
+
680
+ # --- verdicts that need no runner ----------------------------------------------------
681
+ if (( _census_rc != 0 )); then
682
+ # A census leg failed — the partial union cannot be trusted for selection OR
683
+ # for inert/global classification. Coverage-favoring: forced full.
684
+ echo "affected-tests: a changed-set census leg failed (rc=$_census_rc) — the selection cannot be trusted; running the full command (reason=census-degraded)." >&2
685
+ ARG_FULL=1
686
+ EMPTY_REASON="census-degraded"
687
+ elif (( ${#AFFECTED_CHANGED[@]} == 0 )); then
688
+ # An empty diff is an undecidable selection — coverage-favoring, not a pass.
689
+ # (explain emits ONE SELECTION line at the block below; nothing extra here.)
690
+ ARG_FULL=1
691
+ EMPTY_REASON="empty-diff"
692
+ else
693
+ EMPTY_REASON=""
694
+ fi
695
+ unset _census_rc
696
+
697
+ if (( ALL_INERT )) && (( ${#AFFECTED_CHANGED[@]} > 0 )) && (( ARG_FULL == 0 )) && [[ -z "$GLOBAL_HIT" ]]; then
698
+ (( ARG_EXPLAIN )) && echo "SELECTION mode=none reason=inert inert_rule=$(affected_marker_field "$INERT_RULE")"
699
+ # UNIT NOTE: on this path of= counts CHANGED files (all inert), not the
700
+ # repo test-file census other paths emit — units differ by design here.
701
+ finish 0 none inert - "$CI_NAME" 0 "${#AFFECTED_CHANGED[@]}" "${#AFFECTED_CHANGED[@]}"
702
+ fi
703
+
704
+ MODE=selected
705
+ REASON=ok
706
+ if (( ARG_FULL )); then
707
+ MODE=full
708
+ REASON="${EMPTY_REASON:-full-requested}"
709
+ fi
710
+ if [[ -n "$GLOBAL_HIT" && "$ARG_FULL" == 0 ]]; then
711
+ MODE=full
712
+ REASON="global-paths"
713
+ fi
714
+
715
+ if (( ARG_EXPLAIN )); then
716
+ echo "SELECTION mode=${MODE} reason=${REASON} global_hit=$(affected_marker_field "${GLOBAL_HIT:--}") ci=${CI_NAME}"
717
+ finish 0 explain "$REASON" - "$CI_NAME" - - -
718
+ fi
719
+
720
+ # --- budget arming -----------------------------------------------------------------
721
+ if [[ "$CI_NAME" != none ]]; then
722
+ AFFECTED_BUDGET_ACTIVE=1
723
+ AFFECTED_DEADLINE=$(( $(affected_now) + CFG_BUDGET_SECONDS ))
724
+ else
725
+ echo "affected-tests: no CI test workflow detected — wall-clock budget not enforced; running to completion."
726
+ fi
727
+
728
+ WIDTH="$(affected_workers "$CFG_MAX_WORKERS")"
729
+
730
+ # --- dispatch 3: stack adapters ---------------------------------------------------------
731
+ # Worst-of aggregation with precedence 1 > 4 > 6 > 0; rc 7 only when every
732
+ # detected adapter reports no suite AND nothing else resolved the gate.
733
+ WORST=0
734
+ SAW_SUITE=0
735
+ STACKS=""
736
+ RAN_SUM=0
737
+ OF_SUM=0
738
+ RAN_KNOWN=1
739
+ OF_KNOWN=1
740
+ GATE_ESCALATED_FULL=0
741
+
742
+ _affected_rank() { # rank <rc> → precedence weight
743
+ case "$1" in
744
+ 1) echo 4 ;; 4) echo 3 ;; 6) echo 2 ;; 0) echo 1 ;; *) echo 4 ;;
745
+ esac
746
+ }
747
+ _affected_aggregate() { # aggregate <rc>
748
+ local rc="$1" cw ww
749
+ cw="$(_affected_rank "$WORST")"
750
+ ww="$(_affected_rank "$rc")"
751
+ (( ww > cw )) && WORST="$rc"
752
+ return 0
753
+ }
754
+ _affected_add_counts() { # add ran/of independently — each keeps `-` honesty
755
+ local r="$1" o="$2"
756
+ if [[ "$r" =~ ^[0-9]+$ ]]; then RAN_SUM=$((RAN_SUM + r)); else RAN_KNOWN=0; fi
757
+ if [[ "$o" =~ ^[0-9]+$ ]]; then OF_SUM=$((OF_SUM + o)); else OF_KNOWN=0; fi
758
+ }
759
+
760
+ # try_adapter <fn-name> [label] [precomputed-tool-rc] — detect, toolchain-check,
761
+ # select, run, retry-full on a vacuous selection (the empty-selection arm), then
762
+ # aggregate. <fn-name> is the suffix in affected_<fn-name>_* (hyphen-free,
763
+ # bash-3.2); <label> is what the marker prints. The optional third argument is a
764
+ # tool() rc the caller already probed — passing it skips a second bounded
765
+ # --version probe (nx/turbo's detect-probe needs the rc before deciding whether
766
+ # to fall through to js).
767
+ try_adapter() {
768
+ local name="$1" label="${2:-$1}"
769
+ # Per-adapter output flags reset HERE, not only inside adapters that set
770
+ # them — a js escalation must never stain a later adapter's marker.
771
+ ADP_ESCALATED_FULL=0
772
+ ADP_VACUOUS=0
773
+ if ! "affected_${name}_detect"; then
774
+ return 0
775
+ fi
776
+ local tool_rc="${3:-}"
777
+ if [[ -z "$tool_rc" ]]; then
778
+ tool_rc=0
779
+ "affected_${name}_tool" || tool_rc=$?
780
+ fi
781
+ if (( tool_rc == 2 )); then
782
+ return 0 # stack marker present but no test tool configured — not a suite
783
+ fi
784
+ if (( tool_rc != 0 )); then
785
+ echo "affected-tests: stack=$label detected but its toolchain is missing or unrunnable." >&2
786
+ _affected_aggregate 4
787
+ STACKS="${STACKS:+$STACKS+}$label"
788
+ AGG_REASON="toolchain-missing"
789
+ return 0
790
+ fi
791
+ STACKS="${STACKS:+$STACKS+}$label"
792
+
793
+ local run_mode="$MODE" arc=0
794
+ if [[ "$run_mode" == selected ]]; then
795
+ "affected_${name}_run" selected "$WIDTH" "$BASE" || arc=$?
796
+ if (( arc == 5 )) || [[ "${ADP_VACUOUS:-0}" == 1 ]]; then
797
+ # Empty native selection on a non-inert diff → the full command, loudly.
798
+ echo "affected-tests: stack=$label selected nothing for this diff — falling back to the full suite (reason=empty-selection)." >&2
799
+ arc=0
800
+ "affected_${name}_run" full "$WIDTH" "$BASE" || arc=$?
801
+ if (( arc == 5 )) || [[ "${ADP_VACUOUS:-0}" == 1 ]]; then
802
+ # A stack that runs nothing even in full mode has no suite here.
803
+ return 0
804
+ fi
805
+ REASON=empty-selection
806
+ run_mode=full
807
+ fi
808
+ else
809
+ "affected_${name}_run" full "$WIDTH" "$BASE" || arc=$?
810
+ if (( arc == 5 )) || [[ "${ADP_VACUOUS:-0}" == 1 ]]; then
811
+ return 0
812
+ fi
813
+ fi
814
+
815
+ SAW_SUITE=1
816
+ # An adapter that could not select (e.g. js scripts.test) ran FULL instead —
817
+ # the marker must not call that `mode=selected`.
818
+ (( ${ADP_ESCALATED_FULL:-0} )) && GATE_ESCALATED_FULL=1
819
+ _affected_add_counts "${ADP_RAN:--}" "${ADP_OF:--}"
820
+ case "$arc" in
821
+ 0) _affected_aggregate 0 ;;
822
+ 124|137)
823
+ (( arc == 137 )) && echo "affected-tests: stack=$label exited 137 (SIGKILL — OOM or kill escalation), not necessarily budget expiry; deferral still applies." >&2
824
+ if [[ "$AFFECTED_BUDGET_ACTIVE" == 1 && "$CI_NAME" != none ]]; then
825
+ _affected_aggregate 6
826
+ else
827
+ _affected_aggregate 1
828
+ fi ;;
829
+ *) _affected_aggregate 1 ;;
830
+ esac
831
+ return 0
832
+ }
833
+
834
+ AGG_REASON=""
835
+ # nx/turbo FIRST: a workspace scheduler's affected graph subsumes per-package
836
+ # JS selection — but only when it actually RAN a suite. A resolved tool whose
837
+ # affected graph produced nothing (vacuous in both selected and full) must not
838
+ # suppress the js adapter that could still find tests.
839
+ NXT_SUPPRESS=0
840
+ if affected_nxt_detect; then
841
+ _trc=0
842
+ affected_nxt_tool || _trc=$?
843
+ if (( _trc == 0 )); then
844
+ _saw_before=$SAW_SUITE
845
+ try_adapter nxt nx-turbo 0 # tool already resolved — skip the re-probe
846
+ (( SAW_SUITE > _saw_before )) && NXT_SUPPRESS=1
847
+ elif (( _trc == 4 )); then
848
+ echo "affected-tests: stack=nx-turbo detected but its toolchain is missing or unrunnable; falling through to js." >&2
849
+ fi
850
+ fi
851
+ (( NXT_SUPPRESS )) || try_adapter js
852
+ try_adapter py
853
+ try_adapter go
854
+
855
+ if (( SAW_SUITE == 0 )); then
856
+ # Nothing ran. Distinguish the refusals from the disclosed no-suite verdict:
857
+ # a toolchain-missing aggregate is already a refusal; a repo with test files
858
+ # but no resolvable stack refuses; no test files at all is rc 7.
859
+ if (( WORST == 4 )); then
860
+ finish 4 "$MODE" "${AGG_REASON:-toolchain-missing}" "${STACKS:--}" "$CI_NAME" - - -
861
+ fi
862
+ _tests_exist=0
863
+ # Process substitution, not `… | grep -q .`: under pipefail grep's early exit
864
+ # SIGPIPEs the producer and a match can read as a miss (#9217).
865
+ if grep -q . < <(affected_find_files any '*.test.*' '*.spec.*' 'test_*.py' '*_test.py' '*_test.go'); then
866
+ _tests_exist=1
867
+ fi
868
+ if (( _tests_exist )); then
869
+ # Tests exist but no runner resolved. A present-but-foreign
870
+ # scripts/test-all.sh gets the more actionable reason.
871
+ if [[ -f scripts/test-all.sh ]]; then
872
+ finish 4 "$MODE" foreign-runner - "$CI_NAME" - - -
873
+ fi
874
+ finish 4 "$MODE" no-stack - "$CI_NAME" - - -
875
+ fi
876
+ finish 7 none no-tests - "$CI_NAME" 0 0 0
877
+ fi
878
+
879
+ OUT_RAN="-"; OUT_OF="-"; SKIP_SUM="-"
880
+ (( RAN_KNOWN )) && OUT_RAN="$RAN_SUM"
881
+ (( OF_KNOWN )) && OUT_OF="$OF_SUM"
882
+ if (( RAN_KNOWN && OF_KNOWN )); then
883
+ if (( OF_SUM >= RAN_SUM )); then
884
+ SKIP_SUM=$((OF_SUM - RAN_SUM))
885
+ else
886
+ # ran>of can only mean the two counts use different units (an adapter
887
+ # counted items/packages where of counted files). Never emit a negative
888
+ # skipped — disclose the divergence instead.
889
+ SKIP_SUM="-"
890
+ echo "affected-tests: ran=$RAN_SUM exceeds the repo census of=$OF_SUM — count units diverged; skipped is not derivable." >&2
891
+ fi
892
+ fi
893
+ # Marker honesty: an adapter that escalated selected→full (no native selector)
894
+ # makes the emitted mode full with a named reason — never a selected claim
895
+ # over a whole-suite run. MODE itself is untouched: a later adapter's own
896
+ # selection is still scoped.
897
+ OUT_MODE="$MODE"
898
+ if (( GATE_ESCALATED_FULL )) && [[ "$OUT_MODE" == selected ]]; then
899
+ OUT_MODE=full
900
+ [[ "$REASON" == ok ]] && REASON=no-selector
901
+ fi
902
+ case "$WORST" in
903
+ 0) finish 0 "$OUT_MODE" "$REASON" "${STACKS:--}" "$CI_NAME" "$OUT_RAN" "$OUT_OF" "$SKIP_SUM" ;;
904
+ 4) finish 4 "$OUT_MODE" "${AGG_REASON:-failed}" "${STACKS:--}" "$CI_NAME" \
905
+ "$OUT_RAN" "$OUT_OF" "$SKIP_SUM" ;;
906
+ 6) finish 6 "$OUT_MODE" budget-deferred "${STACKS:--}" "$CI_NAME" "$OUT_RAN" "$OUT_OF" "$SKIP_SUM" ;;
907
+ *) finish 1 "$OUT_MODE" failed "${STACKS:--}" "$CI_NAME" "$OUT_RAN" "$OUT_OF" "$SKIP_SUM" ;;
908
+ esac