@soleur/opencode 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (812) 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 +659 -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 +108 -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 +342 -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 +152 -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 +493 -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 +363 -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 +520 -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 +113 -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 +178 -0
  247. package/scripts/sync-opencode-adapter.ts +479 -0
  248. package/scripts/sync-pr-behind.sh +534 -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 +65 -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 +142 -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 +118 -0
  493. package/skills/fix-issue/references/exclude-label-jq-snippet.md +96 -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 +668 -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 +325 -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 +138 -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 +1621 -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-todo-structure.md +165 -0
  674. package/skills/review/references/risk-tier-and-fix-rounds.md +95 -0
  675. package/skills/review/references/semgrep-custom-rules.yaml +174 -0
  676. package/skills/review/references/ultrathink-phases.md +98 -0
  677. package/skills/review/references/wfs.md +6 -0
  678. package/skills/review/scripts/emit-review-trailer.sh +499 -0
  679. package/skills/review/scripts/ensure-semgrep.sh +99 -0
  680. package/skills/review/scripts/fix-round-seats.sh +197 -0
  681. package/skills/review/test/emit-review-trailer.test.sh +387 -0
  682. package/skills/review/test/ensure-semgrep.test.sh +111 -0
  683. package/skills/review/test/fix-round-seats.test.sh +237 -0
  684. package/skills/review/workflows/README.md +147 -0
  685. package/skills/review/workflows/review.workflow.js +667 -0
  686. package/skills/schedule/SKILL.md +796 -0
  687. package/skills/seo-aeo/SKILL.md +139 -0
  688. package/skills/seo-aeo/scripts/validate-csp.sh +233 -0
  689. package/skills/seo-aeo/scripts/validate-seo.sh +262 -0
  690. package/skills/ship/SKILL.md +3022 -0
  691. package/skills/ship/references/adr-ordinal-collision.md +47 -0
  692. package/skills/ship/references/affected-gate-contract.md +115 -0
  693. package/skills/ship/references/ci-run-diagnosis.md +29 -0
  694. package/skills/ship/references/ci-workflow-authoring.md +68 -0
  695. package/skills/ship/references/counsel-review-clo-attestation.md +38 -0
  696. package/skills/ship/references/cto-ruling-armed-push.md +5 -0
  697. package/skills/ship/references/decision-challenges-render.md +71 -0
  698. package/skills/ship/references/detached-battery-verdict.md +13 -0
  699. package/skills/ship/references/feature-tweet-draft.md +42 -0
  700. package/skills/ship/references/followthrough-issue-body-template.md +47 -0
  701. package/skills/ship/references/followthrough-stub-template.sh +92 -0
  702. package/skills/ship/references/merge-queue-dequeue.md +39 -0
  703. package/skills/ship/references/operator-holds.md +149 -0
  704. package/skills/ship/references/pipeline-tally-render.md +43 -0
  705. package/skills/ship/references/pr-overlap.md +153 -0
  706. package/skills/ship/references/queue-mode.md +7 -0
  707. package/skills/ship/references/ready-run-wait.md +36 -0
  708. package/skills/ship/references/red-on-main-quarantine.md +110 -0
  709. package/skills/ship/references/register-update-pr-pattern.md +88 -0
  710. package/skills/ship/references/settle-then-admin-merge.md +130 -0
  711. package/skills/ship/references/supabase-column-probe.md +13 -0
  712. package/skills/ship/scripts/auto-close-scan.sh +76 -0
  713. package/skills/ship/scripts/battery-owed.sh +403 -0
  714. package/skills/ship/scripts/hold-gate-eval.sh +410 -0
  715. package/skills/ship/scripts/net-issue-flow.sh +665 -0
  716. package/skills/ship/scripts/pr-overlap-preflight.sh +483 -0
  717. package/skills/ship/scripts/ship-pir-action-items-gate.sh +246 -0
  718. package/skills/ship/scripts/technical-ruling-gate.sh +444 -0
  719. package/skills/skill-creator/SKILL.md +299 -0
  720. package/skills/skill-creator/references/api-security.md +248 -0
  721. package/skills/skill-creator/references/authoring-levers.md +110 -0
  722. package/skills/skill-creator/references/be-clear-and-direct.md +587 -0
  723. package/skills/skill-creator/references/best-practices.md +431 -0
  724. package/skills/skill-creator/references/common-patterns.md +639 -0
  725. package/skills/skill-creator/references/core-principles.md +193 -0
  726. package/skills/skill-creator/references/executable-code.md +188 -0
  727. package/skills/skill-creator/references/iteration-and-testing.md +509 -0
  728. package/skills/skill-creator/references/official-spec.md +193 -0
  729. package/skills/skill-creator/references/recommended-structure.md +180 -0
  730. package/skills/skill-creator/references/skill-structure.md +396 -0
  731. package/skills/skill-creator/references/using-scripts.md +122 -0
  732. package/skills/skill-creator/references/using-templates.md +120 -0
  733. package/skills/skill-creator/references/workflows-and-validation.md +541 -0
  734. package/skills/skill-creator/scripts/init_skill.py +322 -0
  735. package/skills/skill-creator/scripts/package_skill.py +110 -0
  736. package/skills/skill-creator/scripts/quick_validate.py +64 -0
  737. package/skills/skill-creator/templates/router-skill.md +73 -0
  738. package/skills/skill-creator/templates/simple-skill.md +34 -0
  739. package/skills/skill-creator/workflows/add-reference.md +96 -0
  740. package/skills/skill-creator/workflows/add-script.md +104 -0
  741. package/skills/skill-creator/workflows/add-template.md +83 -0
  742. package/skills/skill-creator/workflows/add-workflow.md +128 -0
  743. package/skills/skill-creator/workflows/audit-skill.md +159 -0
  744. package/skills/skill-creator/workflows/create-domain-expertise-skill.md +623 -0
  745. package/skills/skill-creator/workflows/create-new-skill.md +207 -0
  746. package/skills/skill-creator/workflows/get-guidance.md +134 -0
  747. package/skills/skill-creator/workflows/upgrade-to-router.md +170 -0
  748. package/skills/skill-creator/workflows/verify-skill.md +226 -0
  749. package/skills/skill-security-scan/SKILL.md +145 -0
  750. package/skills/skill-security-scan/references/disclaimer.md +21 -0
  751. package/skills/skill-security-scan/references/first-party-allowlist.yaml +38 -0
  752. package/skills/skill-security-scan/references/override-artifact-schema.json +68 -0
  753. package/skills/skill-security-scan/references/override-mechanism.md +171 -0
  754. package/skills/skill-security-scan/references/redirect-domains.yaml +27 -0
  755. package/skills/skill-security-scan/references/regex-patterns.md +136 -0
  756. package/skills/skill-security-scan/references/rules/code-exec.yaml +70 -0
  757. package/skills/skill-security-scan/references/rules/filesystem-boundary.yaml +29 -0
  758. package/skills/skill-security-scan/references/rules/manifest.yaml +44 -0
  759. package/skills/skill-security-scan/references/rules/supply-chain.yaml +36 -0
  760. package/skills/skill-security-scan/references/scan-meta-schema.json +63 -0
  761. package/skills/skill-security-scan/references/test-fixtures/clean-soleur-style.skill.md +23 -0
  762. package/skills/skill-security-scan/references/test-fixtures/clean-third-party.skill.md +26 -0
  763. package/skills/skill-security-scan/references/test-fixtures/malicious-codeexec.skill.md +23 -0
  764. package/skills/skill-security-scan/references/test-fixtures/malicious-curl-pipe-bash.skill.md +69 -0
  765. package/skills/skill-security-scan/references/test-fixtures/malicious-prompt-injection.skill.md +17 -0
  766. package/skills/skill-security-scan/references/test-fixtures/malicious-telemetry-beacon.skill.md +23 -0
  767. package/skills/skill-security-scan/references/typosquat-targets.yaml +65 -0
  768. package/skills/skill-security-scan/scripts/check-codeexec.sh +41 -0
  769. package/skills/skill-security-scan/scripts/check-filesystem-boundary.sh +19 -0
  770. package/skills/skill-security-scan/scripts/check-prompt-injection.sh +95 -0
  771. package/skills/skill-security-scan/scripts/check-supply-chain.sh +219 -0
  772. package/skills/skill-security-scan/scripts/check-telemetry-surface.sh +126 -0
  773. package/skills/skill-security-scan/scripts/lib.sh +197 -0
  774. package/skills/skill-security-scan/scripts/parse-override.sh +231 -0
  775. package/skills/skill-security-scan/scripts/run-scan.sh +342 -0
  776. package/skills/skill-security-scan/scripts/run-self-test.sh +95 -0
  777. package/skills/skill-security-scan/test/argv-ceiling.test.sh +180 -0
  778. package/skills/skill-security-scan/test/meta-dir-reap.test.sh +411 -0
  779. package/skills/skill-security-scan/test/parse-override.test.sh +607 -0
  780. package/skills/skill-security-scan/test/redact-pii-ghs-shape.test.sh +251 -0
  781. package/skills/social-distribute/SKILL.md +485 -0
  782. package/skills/spec-templates/SKILL.md +274 -0
  783. package/skills/sync/SKILL.md +49 -0
  784. package/skills/test-browser/SKILL.md +464 -0
  785. package/skills/test-browser/references/feature-map.md +161 -0
  786. package/skills/test-fix-loop/SKILL.md +159 -0
  787. package/skills/test-fix-loop/references/determinism-checklist.md +154 -0
  788. package/skills/triage/SKILL.md +402 -0
  789. package/skills/trigger-cron/SKILL.md +136 -0
  790. package/skills/trigger-cron/scripts/trigger.sh +198 -0
  791. package/skills/user-set-role/SKILL.md +147 -0
  792. package/skills/user-set-role/scripts/set-role.sh +218 -0
  793. package/skills/user-story-writer/SKILL.md +77 -0
  794. package/skills/ux-audit/SKILL.md +251 -0
  795. package/skills/ux-audit/references/finding.schema.json +62 -0
  796. package/skills/ux-audit/references/route-list.yaml +95 -0
  797. package/skills/ux-audit/scripts/bot-fixture.ts +270 -0
  798. package/skills/ux-audit/scripts/bot-signin.ts +135 -0
  799. package/skills/ux-audit/scripts/dedup-hash.ts +46 -0
  800. package/skills/work/SKILL.md +1479 -0
  801. package/skills/work/references/end-of-work-resume.md +7 -0
  802. package/skills/work/references/hook-test-main-cwd.md +24 -0
  803. package/skills/work/references/key-principles.md +83 -0
  804. package/skills/work/references/property-testing.md +122 -0
  805. package/skills/work/references/sql-security-definer-rpc-scaffold.sql +78 -0
  806. package/skills/work/references/work-agent-teams.md +97 -0
  807. package/skills/work/references/work-lifecycle-parallel.md +147 -0
  808. package/skills/work/references/work-lockfile-bumps.md +97 -0
  809. package/skills/work/references/work-scratch-sandboxes.md +212 -0
  810. package/skills/work/references/work-subagent-fanout.md +61 -0
  811. package/skills/work/references/work-test-harness-sharp-edges.md +73 -0
  812. package/skills/xcode-test/SKILL.md +360 -0
@@ -0,0 +1,753 @@
1
+ #!/usr/bin/env bash
2
+ # shellcheck disable=SC2329,SC2015,SC2016 # row/mutation functions are dispatched indirectly; A && B || why is the assertion idiom; jq programs are single-quoted on purpose
3
+ # Suite for plugins/soleur/scripts/admin-merge-ready.sh (#8500) -- Guard 1 of the plan's Guard
4
+ # Contract (knowledge-base/project/plans/archive/20260922-120349-2026-09-22-feat-admin-merge-ready-script-plan.md).
5
+ #
6
+ # ── THE SEAM ─────────────────────────────────────────────────────────────────────────────────
7
+ # `gh` is a PATH stub that answers ONLY the four requests the SUT is expected to make, keyed on
8
+ # the FULL argv (including --paginate/--slurp and the query string), and refuses anything else
9
+ # with exit 64 after logging `STUB-MISS <argv>`. Every row asserts the log holds no STUB-MISS, so
10
+ # a SUT querying the wrong endpoint cannot turn a stub refusal into the exit 3 an error row wants.
11
+ # `sleep` is a PATH stub too: it logs its argument, so the --wait cadence is observable.
12
+ #
13
+ # ── THE FIXTURES ─────────────────────────────────────────────────────────────────────────────
14
+ # Every row is a jq edit of ONE real-shape base captured read-only from GitHub
15
+ # (test/fixtures/admin-merge-ready/, provenance in its README): 26 required contexts across two
16
+ # rulesets, 77 check runs, all required contexts green. No hand-written required set exists.
17
+ #
18
+ # ── MUTATION ROWS ────────────────────────────────────────────────────────────────────────────
19
+ # M-rows copy the SUT, apply one edit (asserted to have landed), and require BOTH that the named
20
+ # row FAILS against the mutant AND that the mutant exhibits the specific defect the row exists to
21
+ # catch (e.g. R2: the #8458 fixture now reads ready). A mutant that merely crashes is not a kill.
22
+ export TMPDIR="${TMPDIR:-/var/tmp}"
23
+ set -uo pipefail
24
+
25
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
26
+ REAL_SUT="$SCRIPT_DIR/admin-merge-ready.sh"
27
+ BASE_FX="$SCRIPT_DIR/../test/fixtures/admin-merge-ready"
28
+
29
+ passes=0; fails=0; CASES_RUN=0; FAILED=()
30
+ pass() { passes=$((passes + 1)); echo " PASS: $1"; }
31
+ fail() { fails=$((fails + 1)); FAILED+=("$1"); echo " FAIL: $1" >&2; }
32
+
33
+ _iv_p="$passes"; _iv_f="$fails"; _iv_n="${#FAILED[@]}"
34
+ { pass "self-test"; fail "self-test"; } >/dev/null 2>&1
35
+ if [[ "$passes" -ne $((_iv_p + 1)) || "$fails" -ne $((_iv_f + 1)) || "${#FAILED[@]}" -ne $((_iv_n + 1)) ]]; then
36
+ printf '[FATAL] instrument self-test: the verdict helpers did not both record\n' >&2; exit 1
37
+ fi
38
+ passes=0; fails=0; FAILED=(); CASES_RUN=0
39
+
40
+ [[ -f "$REAL_SUT" ]] || { echo "[FATAL] SUT not found at $REAL_SUT" >&2; exit 1; }
41
+ [[ -f "$BASE_FX/rules-branches-main.json" && -f "$BASE_FX/check-runs.json" ]] \
42
+ || { echo "[FATAL] base fixtures missing under $BASE_FX" >&2; exit 1; }
43
+ command -v jq >/dev/null || { echo "[FATAL] jq required" >&2; exit 1; }
44
+ command -v python3 >/dev/null || { echo "[FATAL] python3 required (mutation rows)" >&2; exit 1; }
45
+
46
+ assert_fixture_dir() {
47
+ case "${1-}" in
48
+ "") printf 'FATAL: fixture dir is EMPTY; git -C "" would operate on %s\n' "$PWD" >&2; exit 2 ;;
49
+ */../*|*/..) printf 'FATAL: fixture dir %s contains ..; refusing\n' "$1" >&2; exit 2 ;;
50
+ /proc/*|/sys/*|/dev/*) printf 'FATAL: fixture dir %s is a synthetic-fs path; refusing\n' "$1" >&2; exit 2 ;;
51
+ /|//|/.) printf 'FATAL: fixture dir resolves to the filesystem root; refusing\n' >&2; exit 2 ;;
52
+ /*) : ;;
53
+ *) printf 'FATAL: fixture dir %s is RELATIVE; refusing\n' "$1" >&2; exit 2 ;;
54
+ esac
55
+ }
56
+
57
+ SANDBOX="$(mktemp -d)"; assert_fixture_dir "$SANDBOX"
58
+ trap 'rm -rf "$SANDBOX"' EXIT
59
+
60
+ SHA=319c22bffa4c1785da52dd9af9471a9d8eef1310
61
+ # --green-sha carryover fixtures: GREEN is the certified prior head, P1 is the base-side
62
+ # parent the merge commit carries. Both are arbitrary-but-valid 40-hex.
63
+ GREEN=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
64
+ P1=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
65
+ PR=8534
66
+ SUT="$REAL_SUT"
67
+ TIMEOUT_BIN="$(command -v timeout)" || { echo "[FATAL] timeout(1) required" >&2; exit 1; }
68
+
69
+ # ── H6: fixture preconditions every row depends on ──────────────────────────────────────────
70
+ pre() { jq -e "$1" "$2" >/dev/null || { echo "[FATAL] H6 fixture precondition failed: $3 -- recapture the base (see README)" >&2; exit 1; }; }
71
+ pre '[.[0][] | select(.type=="required_status_checks") | .parameters.required_status_checks[]] | length >= 26' \
72
+ "$BASE_FX/rules-branches-main.json" ">=26 required entries"
73
+ pre '[.[0][] | select(.ruleset_id==13304872) | .parameters.required_status_checks[]?.context] | index("cla-evidence") != null' \
74
+ "$BASE_FX/rules-branches-main.json" "cla-evidence in ruleset 13304872"
75
+ pre '[.[0][] | select(.ruleset_id!=13304872) | .parameters.required_status_checks[]?.context] | index("cla-evidence") == null' \
76
+ "$BASE_FX/rules-branches-main.json" "cla-evidence ONLY in ruleset 13304872"
77
+ pre '[.[0][] | .type] | index("deletion") != null' "$BASE_FX/rules-branches-main.json" "a non-check rule type is present"
78
+ pre '[.[0].check_runs[] | select(.name=="test" and .app.id==15368 and .status=="completed" and .conclusion=="success")] | length == 1' \
79
+ "$BASE_FX/check-runs.json" "test present and green"
80
+ pre '[.[0].check_runs[] | select(.name=="CodeQL")] | all(.app.id == 57789) and length >= 1' \
81
+ "$BASE_FX/check-runs.json" "CodeQL run carries app 57789"
82
+ pre "[.[0].check_runs[].head_sha] | unique == [\"$SHA\"]" "$BASE_FX/check-runs.json" "every run carries the fixture head_sha"
83
+ [[ "$(grep -c -e 'https://' -e '@' "$BASE_FX"/*.json | awk -F: '{s+=$2} END {print s}')" == 0 ]] \
84
+ || { echo "[FATAL] H6: base fixtures carry URLs or emails" >&2; exit 1; }
85
+
86
+ # ── the stubs ───────────────────────────────────────────────────────────────────────────────
87
+ BIN="$SANDBOX/bin"; mkdir -p "$BIN"
88
+ cat > "$BIN/gh" <<'GH'
89
+ #!/usr/bin/env bash
90
+ # Per-poll files: <name>.<poll>.json wins over <name>.json; the poll counter advances on `pr view`.
91
+ args="$*"
92
+ printf '%s\n' "$args" >> "$STUB_LOG"
93
+ poll=$(cat "$FX/.polls" 2>/dev/null || echo 0)
94
+ pick() { if [[ -f "$FX/$1.$poll.json" ]]; then cat "$FX/$1.$poll.json"; else cat "$FX/$1.json"; fi; }
95
+ failing() { [[ -f "$FX/$1.fail" || -f "$FX/$1.fail.$poll" ]]; }
96
+ enc=$(cat "$FX/expect_base_enc" 2>/dev/null || echo main)
97
+ runs_url="repos/{owner}/{repo}/commits/$STUB_SHA/check-runs?per_page=100&filter=all"
98
+ green_url="repos/{owner}/{repo}/commits/$STUB_GREEN/check-runs?per_page=100&filter=all"
99
+ case "$args" in
100
+ "pr view $STUB_PR --json state,headRefOid,baseRefName,changedFiles")
101
+ poll=$((poll + 1)); echo "$poll" > "$FX/.polls"
102
+ failing pr && exit 1
103
+ pick pr ;;
104
+ "api --paginate --slurp repos/{owner}/{repo}/pulls/$STUB_PR/files?per_page=100")
105
+ failing files && exit 1
106
+ pick files ;;
107
+ "api repos/{owner}/{repo}/commits/$STUB_SHA")
108
+ failing head && exit 1
109
+ pick head ;;
110
+ "api repos/{owner}/{repo}/compare/"*"...main")
111
+ failing compare && exit 1
112
+ pick compare ;;
113
+ "api repos/{owner}/{repo}/compare/$STUB_GREEN...$STUB_SHA")
114
+ failing cmp_added && exit 1
115
+ pick cmp_added ;;
116
+ "api repos/{owner}/{repo}/compare/$STUB_GREEN...$STUB_P1")
117
+ failing cmp_base && exit 1
118
+ pick cmp_base ;;
119
+ "api --paginate --slurp repos/{owner}/{repo}/rules/branches/$enc")
120
+ failing rules && exit 1
121
+ pick rules ;;
122
+ "api --paginate --slurp $runs_url"|"api --paginate --slurp $green_url")
123
+ if failing runs; then pick runs | jq -c '[.[0]]'; exit 1; fi # page 1 printed, page 2 failed
124
+ pick runs ;;
125
+ "api --slurp $runs_url") # the no---paginate mutant: only the first page is ever read
126
+ pick runs | jq -c '[.[0]]' ;;
127
+ *) printf 'STUB-MISS %s\n' "$args" >> "$STUB_LOG"; echo "stub: unexpected gh argv: $args" >&2; exit 64 ;;
128
+ esac
129
+ GH
130
+ cat > "$BIN/sleep" <<'SLP'
131
+ #!/usr/bin/env bash
132
+ printf '%s\n' "$*" >> "$FX/sleeps"
133
+ SLP
134
+ # The head-verdict resolver (ADR-276 S3, #9728) is stubbed through the CI_HEAD_VERDICT_BIN seam: <dir>/vstate holds
135
+ # "<state> [run-id] [sha]" (default n/a), <dir>/vstate.fail makes it exit 3, <dir>/vlog logs each call. The real
136
+ # resolver has its own suite (plugins/soleur/test/ci-head-verdict.test.sh); the wiring suite pins that this script calls it.
137
+ cat > "$BIN/verdict.sh" <<'VS'
138
+ #!/usr/bin/env bash
139
+ printf '%s\n' "$*" >> "$FX/vlog"
140
+ # The REAL error shape: a state=error marker (outside the six-state set) AND exit 3, carrying the real sha like the resolver does.
141
+ [[ -f "$FX/vstate.fail" ]] && { printf 'SOLEUR_CI_HEAD_VERDICT state=error pr=%s sha=%s run=none reason=api-error\n' "$STUB_PR" "$STUB_SHA"; exit 3; }
142
+ st=n/a; rn=none; sh="$STUB_SHA"
143
+ [[ -f "$FX/vstate" ]] && read -r st rn sh < "$FX/vstate"
144
+ printf 'SOLEUR_CI_HEAD_VERDICT state=%s pr=%s sha=%s run=%s reason=stub\n' "${st:-n/a}" "$STUB_PR" "${sh:-$STUB_SHA}" "${rn:-none}"
145
+ VS
146
+ chmod +x "$BIN/gh" "$BIN/sleep" "$BIN/verdict.sh"
147
+ # A PATH with jq but no gh, for the missing-tool row.
148
+ NOGH="$SANDBOX/nogh"; mkdir -p "$NOGH"; ln -s "$(command -v jq)" "$NOGH/jq"
149
+
150
+ # ── row plumbing ────────────────────────────────────────────────────────────────────────────
151
+ mkrow() { # <name> -> prints dir; fresh copy of the base
152
+ local d="$SANDBOX/rows/$1"; assert_fixture_dir "$d"
153
+ rm -rf "$d"; mkdir -p "$d"
154
+ cp "$BASE_FX/rules-branches-main.json" "$d/rules.json"
155
+ cp "$BASE_FX/check-runs.json" "$d/runs.json"
156
+ printf '{"state":"OPEN","headRefOid":"%s","baseRefName":"main","changedFiles":1}\n' "$SHA" > "$d/pr.json"
157
+ echo '[[{"filename":"plugins/soleur/x.md"}]]' > "$d/files.json"
158
+ # --green-sha fixtures (only read by carryover rows): head SHA is a GitHub-verified
159
+ # merge of GREEN (first parent) and P1 (base side), and P1 is an ancestor of main.
160
+ printf '{"commit":{"verification":{"verified":true}},"parents":[{"sha":"%s"},{"sha":"%s"}]}\n' \
161
+ "$GREEN" "$P1" > "$d/head.json"
162
+ echo '{"status":"ahead"}' > "$d/compare.json"
163
+ # --allow-local-merge fixtures (only read by the L rows): the merge's added
164
+ # delta is ONE docs file (disjoint from the PR's plugins/soleur/x.md) that is
165
+ # a byte-identical replay of the base-side delta — the proof's PASS shape.
166
+ jq -nc '{status:"ahead", merge_base_commit:{sha:"dddddddddddddddddddddddddddddddddddddddd"},
167
+ files:[{filename:"docs/notes.md", status:"added", patch:"@@ -0,0 +1 @@\n+doc"}]}' \
168
+ > "$d/cmp_added.json"
169
+ cp "$d/cmp_added.json" "$d/cmp_base.json"
170
+ printf '%s' "$d"
171
+ }
172
+ # jqf <dir> <file> <filter> : edit a fixture file in place
173
+ jqf() {
174
+ assert_fixture_dir "$1"
175
+ local t; t=$(mktemp "$SANDBOX/jqf.XXXXXX")
176
+ jq "$3" "$1/$2" > "$t" && mv "$t" "$1/$2" || { echo "[FATAL] jq edit failed: $3" >&2; exit 1; }
177
+ }
178
+ # setfiles <dir> <json-pages> : set the PR file list and keep changedFiles consistent with it
179
+ setfiles() {
180
+ assert_fixture_dir "$1"
181
+ printf '%s\n' "$2" > "$1/files.json"
182
+ jqf "$1" pr.json ".changedFiles = $(jq '[.[][]] | length' <<<"$2")"
183
+ }
184
+ # runs <dir> <filter-over-check_runs-array> [outfile]
185
+ runs() {
186
+ assert_fixture_dir "$1"
187
+ local out="${3:-runs.json}"; [[ "$out" == runs.json ]] || cp "$1/runs.json" "$1/$out"
188
+ jqf "$1" "$out" "map(.check_runs |= ($2))"
189
+ }
190
+ drop() { runs "$1" "map(select(.name != \"$2\"))" "${3:-runs.json}"; }
191
+ setrun() { runs "$1" "map(if .name == \"$2\" then $3 else . end)" "${4:-runs.json}"; }
192
+
193
+ run() { # <dir> <sut-args...> ; RUN_PATH / RUN_UNSET_POLL / RUN_POLL override the environment
194
+ local d="$1"; shift
195
+ assert_fixture_dir "$d"
196
+ : > "$d/log"; rm -f "$d/.polls" "$d/sleeps" "$d/vlog"
197
+ local -a envv=(FX="$d" STUB_LOG="$d/log" STUB_PR="$PR" STUB_SHA="$SHA" STUB_GREEN="$GREEN" STUB_P1="$P1" PATH="${RUN_PATH:-$BIN:$PATH}" CI_HEAD_VERDICT_BIN="$BIN/verdict.sh")
198
+ if [[ -z "${RUN_UNSET_POLL:-}" ]]; then envv+=(ADMIN_MERGE_READY_POLL_SECONDS="${RUN_POLL:-0}"); fi
199
+ env -u ADMIN_MERGE_READY_POLL_SECONDS "${envv[@]}" "$TIMEOUT_BIN" 30 "$BASH" "$SUT" "$@" > "$d/out" 2> "$d/err"
200
+ echo $? > "$d/rc"
201
+ }
202
+ why() { echo " why: $*" >&2; return 1; }
203
+ rc_is() { [[ "$(cat "$1/rc")" == "$2" ]] || why "rc=$(cat "$1/rc"), want $2; out: $(tail -3 "$1/out" | tr '\n' '|') err: $(tail -2 "$1/err" | tr '\n' '|')"; }
204
+ line() { grep -Fxq -- "$2" "$1/out" || why "missing stdout line: $2"; }
205
+ noline() { ! grep -Fq -- "$2" "$1/out" || why "unexpected stdout text: $2"; }
206
+ tailis() { local l; l=$(tail -1 "$1/out"); [[ "$l" == *" $2" ]] || why "marker tail is '$l', want '* $2'"; }
207
+ verdict() { tail -1 "$1/out" | grep -c >/dev/null "^SOLEUR_ADMIN_MERGE_READY verdict=$2 " || why "last line is not verdict=$2: $(tail -1 "$1/out")"; }
208
+ reason() { tail -1 "$1/out" | grep -c >/dev/null " reason=$2 " || why "last line lacks reason=$2: $(tail -1 "$1/out")"; }
209
+ nomiss() { ! grep -q '^STUB-MISS' "$1/log" || why "stub refused: $(grep '^STUB-MISS' "$1/log" | head -1)"; }
210
+ polls() { [[ "$(cat "$1/.polls" 2>/dev/null || echo 0)" == "$2" ]] || why "polls=$(cat "$1/.polls" 2>/dev/null), want $2"; }
211
+ sleeps() { [[ "$(cat "$1/sleeps" 2>/dev/null | wc -l)" == "$2" ]] || why "sleeps=$(cat "$1/sleeps" 2>/dev/null | wc -l), want $2"; }
212
+ count() { [[ "$(grep -c -- "$2" "$1/out")" == "$3" ]] || why "count of '$2' = $(grep -c -- "$2" "$1/out"), want $3"; }
213
+ logs() { grep -Fq -- "$2" "$1/log" || why "stub log lacks request: $2"; }
214
+ nocall() { [[ ! -s "$1/log" ]] || why "gh was called: $(head -1 "$1/log")"; }
215
+
216
+ setv() { printf '%s\n' "${*:2}" > "$1/vstate"; } # setv <dir> <state> [run-id] [sha]
217
+ vcalls() { [[ -f "$1/vlog" ]] && wc -l < "$1/vlog" || echo 0; }
218
+ vcalled() { [[ "$(vcalls "$1")" == "$2" ]] || why "resolver calls=$(vcalls "$1"), want $2"; }
219
+ TEST_URL_RUN8='https://example.invalid/o/r/actions/runs/8/job/1'
220
+ TEST_URL_RUN6='https://example.invalid/o/r/actions/runs/6/job/2'
221
+
222
+ case_ok() { CASES_RUN=$((CASES_RUN + 1)); if "$2"; then pass "$1"; else fail "$1"; fi; }
223
+ # case_mutant <id> <row-fn> <defect-fn> <python-old> <python-new>
224
+ # The row must FAIL against the mutant AND <defect-fn> must confirm the mutant shows the defect.
225
+ case_mutant() {
226
+ CASES_RUN=$((CASES_RUN + 1))
227
+ local id="$1" fn="$2" defect="$3" m="$SANDBOX/mut-$1.sh"
228
+ cp "$REAL_SUT" "$m"
229
+ OLD="$4" NEW="$5" python3 - "$m" <<'PY' || { fail "$id (mutation did not land)"; return; }
230
+ import os, sys
231
+ p = sys.argv[1]; s = open(p).read(); o = os.environ["OLD"]
232
+ if s.count(o) != 1: sys.exit(1)
233
+ open(p, "w").write(s.replace(o, os.environ["NEW"]))
234
+ PY
235
+ cmp -s "$m" "$REAL_SUT" && { fail "$id (mutant identical to SUT)"; return; }
236
+ if ( SUT="$m"; "$fn" ) 2>/dev/null; then fail "$id (row $fn still passes against the mutant)"; return; fi
237
+ if "$defect"; then pass "$id (row $fn reds, mutant shows the defect)"
238
+ else fail "$id (row $fn reds, but the mutant did not show the defect -- a crash is not a kill)"; fi
239
+ }
240
+ rowdir() { printf '%s' "$SANDBOX/rows/$1"; }
241
+ rc_of() { cat "$(rowdir "$1")/rc"; }
242
+
243
+ READY_TAIL="required=26 absent=[] pending=[] failed=[]"
244
+
245
+ # ── rows ────────────────────────────────────────────────────────────────────────────────────
246
+ row_H1() { local d; d=$(mkrow H1); assert_fixture_dir "$d"; run "$d" "$PR" "$SHA"
247
+ rc_is "$d" 0 && nomiss "$d" && [[ "$(cat "$d/out")" == "SOLEUR_ADMIN_MERGE_READY verdict=ready pr=$PR sha=$SHA base=main reason=all-green $READY_TAIL" ]] \
248
+ || why "H1 stdout is not exactly the ready marker: $(cat "$d/out")"; }
249
+ row_H2() { local d; d=$(mkrow H2); assert_fixture_dir "$d"
250
+ setrun "$d" "e2e" '.conclusion = "skipped"'
251
+ setrun "$d" "markdown-lint" '.conclusion = "neutral"'
252
+ runs "$d" '. + [(.[] | select(.name == "lockfile-sync") | .id = (.id - 5) | .conclusion = "failure")]'
253
+ runs "$d" ". + [range(0;50) as \$i | {id: (9000000000 + \$i), name: \"extra-\\(\$i)\", head_sha: \"$SHA\", status: \"completed\", conclusion: \"failure\", started_at: null, app: {id: 15368}, check_suite: {id: 1}}]"
254
+ run "$d" "$PR" "$SHA"; rc_is "$d" 0 && nomiss "$d" && tailis "$d" "$READY_TAIL"; }
255
+ row_R1() { local d; d=$(mkrow R1); assert_fixture_dir "$d"; drop "$d" test; run "$d" "$PR" "$SHA"
256
+ rc_is "$d" 1 && nomiss "$d" && line "$d" "ABSENT test" && verdict "$d" not-ready && reason "$d" not-green \
257
+ && tailis "$d" 'required=26 absent=["test"] pending=[] failed=[]'; }
258
+ row_R3() { local d; d=$(mkrow R3); assert_fixture_dir "$d"; drop "$d" tenant-integration-required; drop "$d" cla-evidence; run "$d" "$PR" "$SHA"
259
+ rc_is "$d" 1 && nomiss "$d" && line "$d" "ABSENT tenant-integration-required" && line "$d" "ABSENT cla-evidence" \
260
+ && noline "$d" " enforce" && tailis "$d" 'required=26 absent=["tenant-integration-required","cla-evidence"] pending=[] failed=[]'; }
261
+ row_R4() { local d; d=$(mkrow R4); assert_fixture_dir "$d"
262
+ setrun "$d" enforce '.id = 99999999999'
263
+ runs "$d" '. + [(.[] | select(.name == "enforce") | .id = 100000000000 | .status = "queued" | .conclusion = null | .started_at = null)]'
264
+ run "$d" "$PR" "$SHA"; rc_is "$d" 1 && nomiss "$d" && line "$d" "PENDING enforce (queued)" \
265
+ && tailis "$d" 'required=26 absent=[] pending=["enforce"] failed=[]'; }
266
+ row_R5() { local d; d=$(mkrow R5); assert_fixture_dir "$d"; setrun "$d" test '.conclusion = "failure"'; run "$d" "$PR" "$SHA"
267
+ rc_is "$d" 1 && nomiss "$d" && line "$d" "FAILED test (failure)" && tailis "$d" 'required=26 absent=[] pending=[] failed=["test"]'; }
268
+ row_R6() { local c d; for c in stale cancelled bogus-conclusion; do d=$(mkrow "R6-$c"); assert_fixture_dir "$d"
269
+ setrun "$d" test ".conclusion = \"$c\""; run "$d" "$PR" "$SHA"
270
+ rc_is "$d" 1 && nomiss "$d" && line "$d" "FAILED test ($c)" || return 1; done; }
271
+ row_R7() { local d; d=$(mkrow R7); assert_fixture_dir "$d"; drop "$d" cla-evidence; run "$d" "$PR" "$SHA"
272
+ rc_is "$d" 1 && nomiss "$d" && line "$d" "ABSENT cla-evidence"; }
273
+ row_R8() { local d; d=$(mkrow R8); assert_fixture_dir "$d"; setrun "$d" CodeQL '.app.id = 15368'; run "$d" "$PR" "$SHA"
274
+ rc_is "$d" 1 && nomiss "$d" && line "$d" "ABSENT CodeQL"; }
275
+ row_R9() { local d
276
+ d=$(mkrow R9a); assert_fixture_dir "$d"; touch "$d/rules.fail"; run "$d" "$PR" "$SHA"
277
+ rc_is "$d" 3 && nomiss "$d" && verdict "$d" error && logs "$d" "rules/branches/main" || return 1
278
+ d=$(mkrow R9b); assert_fixture_dir "$d"; echo '[[]]' > "$d/rules.json"; run "$d" "$PR" "$SHA"
279
+ rc_is "$d" 3 && nomiss "$d" && verdict "$d" error && reason "$d" empty-required || return 1
280
+ d=$(mkrow R9c); assert_fixture_dir "$d"; jqf "$d" runs.json '[.[0] | .check_runs |= map(select(.name != "test"))] + [{total_count: 1, check_runs: [(.[0].check_runs[] | select(.name == "test"))]}]'
281
+ touch "$d/runs.fail"; run "$d" "$PR" "$SHA"
282
+ rc_is "$d" 3 && nomiss "$d" && verdict "$d" error && logs "$d" "check-runs" || return 1
283
+ d=$(mkrow R9d); assert_fixture_dir "$d"; setfiles "$d" '[[{"filename":".github/workflows/x.yml"}]]'; touch "$d/files.fail"; run "$d" "$PR" "$SHA"
284
+ rc_is "$d" 3 && nomiss "$d" && verdict "$d" error && logs "$d" "pulls/$PR/files" || return 1
285
+ d=$(mkrow R9e); assert_fixture_dir "$d"; touch "$d/pr.fail"; run "$d" "$PR" "$SHA"
286
+ rc_is "$d" 3 && nomiss "$d" && verdict "$d" error || return 1
287
+ d=$(mkrow R9f); assert_fixture_dir "$d"; echo '<html>502 Bad Gateway</html>' > "$d/runs.json"; run "$d" "$PR" "$SHA"
288
+ rc_is "$d" 3 && nomiss "$d" && verdict "$d" error || return 1
289
+ d=$(mkrow R9g); assert_fixture_dir "$d"; : > "$d/runs.json"; run "$d" "$PR" "$SHA"
290
+ rc_is "$d" 3 && nomiss "$d" && verdict "$d" error || return 1
291
+ d=$(mkrow R9h); assert_fixture_dir "$d"; : > "$d/files.json"; run "$d" "$PR" "$SHA"
292
+ rc_is "$d" 3 && nomiss "$d" && verdict "$d" error; }
293
+ row_R10() { local d; d=$(mkrow R10); assert_fixture_dir "$d"; jqf "$d" pr.json '.headRefOid = "0000000000000000000000000000000000000000"'; run "$d" "$PR" "$SHA"
294
+ rc_is "$d" 4 && nomiss "$d" && verdict "$d" stale && reason "$d" head-moved; }
295
+ row_R11() { local d; d=$(mkrow R11); assert_fixture_dir "$d"
296
+ jqf "$d" runs.json '[.[0] | .check_runs |= map(select(.name != "test"))] + [{total_count: 1, check_runs: [(.[0].check_runs[] | select(.name == "test"))]}]'
297
+ run "$d" "$PR" "$SHA"; rc_is "$d" 0 && nomiss "$d" && tailis "$d" "$READY_TAIL"; }
298
+ row_R12() { local d; d=$(mkrow R12); assert_fixture_dir "$d"; jqf "$d" rules.json '.[0][0].parameters.required_status_checks[0].integration_id = null'
299
+ run "$d" "$PR" "$SHA"; rc_is "$d" 3 && nomiss "$d" && verdict "$d" error && reason "$d" unpinned && grep -q 'unpinned required context(s) \["enforce"\]' "$d/err" \
300
+ || why "R12 does not name the unpinned context: $(cat "$d/err")"; }
301
+ row_R13() { local d; d=$(mkrow R13); assert_fixture_dir "$d"; jqf "$d" pr.json '.state = "MERGED"'; run "$d" "$PR" "$SHA"
302
+ rc_is "$d" 4 && nomiss "$d" && verdict "$d" stale && line "$d" "NOT-OPEN: PR is not open"; }
303
+ row_R14() { local d a; d=$(mkrow R14); assert_fixture_dir "$d"
304
+ for a in "" "abc $SHA" "$PR ${SHA:0:7}" "$PR $SHA --timeout 0" "$PR $SHA --bogus" "$PR $SHA --base bad;name"; do
305
+ # shellcheck disable=SC2086
306
+ run "$d" $a; rc_is "$d" 2 && verdict "$d" error && nocall "$d" || return 1
307
+ done
308
+ RUN_POLL=abc run "$d" "$PR" "$SHA" --wait; rc_is "$d" 2 && verdict "$d" error && nocall "$d"; }
309
+ row_R15() { local d; d=$(mkrow R15); assert_fixture_dir "$d"; jqf "$d" pr.json '.baseRefName = "feat/x"'; echo 'feat%2Fx' > "$d/expect_base_enc"
310
+ run "$d" "$PR" "$SHA" --base feat/x; rc_is "$d" 0 && nomiss "$d" && logs "$d" "rules/branches/feat%2Fx"; }
311
+ row_R16() { local d; d=$(mkrow R16); assert_fixture_dir "$d"
312
+ drop "$d" test runs.1.json; setrun "$d" test '.status = "in_progress" | .conclusion = null' runs.2.json
313
+ run "$d" "$PR" "$SHA" --wait; rc_is "$d" 0 && nomiss "$d" && polls "$d" 3 && count "$d" '^WAIT ' 2 && verdict "$d" ready \
314
+ && line "$d" 'WAIT poll=1/60 absent=["test"] pending=[]' && line "$d" 'WAIT poll=2/60 absent=[] pending=["test"]' && sleeps "$d" 2; }
315
+ row_R17() { local d; d=$(mkrow R17); assert_fixture_dir "$d"
316
+ drop "$d" test runs.1.json; setrun "$d" test '.conclusion = "failure"' runs.2.json; setrun "$d" test '.conclusion = "success"' runs.3.json
317
+ run "$d" "$PR" "$SHA" --wait; rc_is "$d" 1 && nomiss "$d" && polls "$d" 2 && verdict "$d" not-ready && line "$d" "FAILED test (failure)"; }
318
+ row_R18() { local d; d=$(mkrow R18); assert_fixture_dir "$d"; drop "$d" test runs.1.json
319
+ jq '.headRefOid = "1111111111111111111111111111111111111111"' "$d/pr.json" > "$d/pr.2.json"
320
+ run "$d" "$PR" "$SHA" --wait; rc_is "$d" 4 && nomiss "$d" && polls "$d" 2 && verdict "$d" stale; }
321
+ row_R19() { local d; d=$(mkrow R19); assert_fixture_dir "$d"; drop "$d" test
322
+ run "$d" "$PR" "$SHA" --wait --timeout 180; rc_is "$d" 1 && nomiss "$d" && polls "$d" 3 && sleeps "$d" 2 && verdict "$d" timeout \
323
+ && tailis "$d" 'required=26 absent=["test"] pending=[] failed=[]' || return 1
324
+ # A timeout that is not a multiple of 60 rounds UP: 90 s is two polls, never one or zero.
325
+ run "$d" "$PR" "$SHA" --wait --timeout 90; rc_is "$d" 1 && polls "$d" 2 && verdict "$d" timeout; }
326
+ row_R20() { local d; d=$(mkrow R20); assert_fixture_dir "$d"; drop "$d" test runs.1.json; touch "$d/rules.fail.2"
327
+ run "$d" "$PR" "$SHA" --wait; rc_is "$d" 3 && nomiss "$d" && polls "$d" 2 && verdict "$d" error; }
328
+ row_R21() { local d; d=$(mkrow R21); assert_fixture_dir "$d"
329
+ runs "$d" '. + [(.[] | select(.name == "CodeQL") | .id = (.id + 1000) | .app.id = 15368 | .conclusion = "failure")]'
330
+ run "$d" "$PR" "$SHA"; rc_is "$d" 0 && nomiss "$d"; }
331
+ row_R22() { local d; d=$(mkrow R22); assert_fixture_dir "$d"; setrun "$d" CodeQL '.conclusion = "failure"'
332
+ runs "$d" '. + [(.[] | select(.name == "CodeQL") | .id = (.id + 1000) | .app.id = 15368 | .conclusion = "success")]'
333
+ run "$d" "$PR" "$SHA"; rc_is "$d" 1 && nomiss "$d" && line "$d" "FAILED CodeQL (failure)"; }
334
+ row_R23() { local d; d=$(mkrow R23); assert_fixture_dir "$d"; drop "$d" test
335
+ jqf "$d" rules.json '.[0] |= map(if .ruleset_id == 13304872 and .type == "required_status_checks" then .parameters.required_status_checks += [{context: "test", integration_id: 15368}] else . end)'
336
+ run "$d" "$PR" "$SHA"; rc_is "$d" 1 && nomiss "$d" && count "$d" '^ABSENT test$' 1 && tailis "$d" 'required=26 absent=["test"] pending=[] failed=[]'; }
337
+ row_R24() { local d; d=$(mkrow R24); assert_fixture_dir "$d"; setrun "$d" test '.status = "mystery" | .conclusion = null'; run "$d" "$PR" "$SHA"
338
+ rc_is "$d" 1 && nomiss "$d" && line "$d" "FAILED test (unknown status mystery)"; }
339
+ row_R25() { local d i; d=$(mkrow R25); assert_fixture_dir "$d"
340
+ for i in 1 2 3 4 5 6 7; do setrun "$d" test '.status = "in_progress" | .conclusion = null' "runs.$i.json"; done
341
+ run "$d" "$PR" "$SHA" --wait; rc_is "$d" 0 && nomiss "$d" && polls "$d" 8 && count "$d" '^HEARTBEAT ' 1 \
342
+ && count "$d" '^HEARTBEAT poll=5/' 1 && count "$d" '^WAIT ' 1; }
343
+ row_R26() { local d p
344
+ for p in '[[{"filename":"README.md"}],[{"filename":".github/workflows/new.yml"}]]' \
345
+ '[[{"filename":".github/actions/setup/action.yml"}],[{"filename":"README.md"}]]' \
346
+ '[[{"filename":"ci/renamed.yml","previous_filename":".github/workflows/ci.yml"}]]'; do
347
+ d=$(mkrow R26); assert_fixture_dir "$d"; setfiles "$d" "$p"; run "$d" "$PR" "$SHA"
348
+ rc_is "$d" 1 && nomiss "$d" && verdict "$d" not-ready && reason "$d" untrusted-ci && grep -q '^UNTRUSTED-CI' "$d/out" \
349
+ && noline "$d" ".yml" || return 1
350
+ done
351
+ # Terminal under --wait: one poll, no waiting out the budget.
352
+ run "$d" "$PR" "$SHA" --wait; rc_is "$d" 1 && polls "$d" 1 && verdict "$d" not-ready && reason "$d" untrusted-ci; }
353
+ row_R27() { local c d suite
354
+ for c in skipped neutral; do
355
+ d=$(mkrow "R27-$c"); assert_fixture_dir "$d"
356
+ suite=$(jq '.[0].check_runs[] | select(.name == "tenant-integration-required") | .check_suite.id' "$d/runs.json")
357
+ setrun "$d" tenant-integration-required ".conclusion = \"$c\""
358
+ runs "$d" ". + [{id: 8000000000, name: \"some-shard\", head_sha: \"$SHA\", status: \"completed\", conclusion: \"failure\", started_at: null, app: {id: 15368}, check_suite: {id: $suite}}]"
359
+ run "$d" "$PR" "$SHA"; rc_is "$d" 1 && nomiss "$d" && line "$d" "FAILED tenant-integration-required ($c-after-failure)" || return 1
360
+ # The dependency is being RE-RUN: its latest run is in progress, the skip is not yet recreated.
361
+ d=$(mkrow "R27-$c-rerun"); assert_fixture_dir "$d"
362
+ setrun "$d" tenant-integration-required ".conclusion = \"$c\""
363
+ runs "$d" ". + [{id: 8000000000, name: \"some-shard\", head_sha: \"$SHA\", status: \"completed\", conclusion: \"failure\", started_at: null, app: {id: 15368}, check_suite: {id: $suite}}, {id: 999999999999, name: \"some-shard\", head_sha: \"$SHA\", status: \"in_progress\", conclusion: null, started_at: null, app: {id: 15368}, check_suite: {id: $suite}}]"
364
+ run "$d" "$PR" "$SHA"; rc_is "$d" 1 && nomiss "$d" && line "$d" "PENDING tenant-integration-required ($c-while-suite-running)" || return 1
365
+ d=$(mkrow "R27-$c-clean"); assert_fixture_dir "$d"; setrun "$d" tenant-integration-required ".conclusion = \"$c\""
366
+ run "$d" "$PR" "$SHA"; rc_is "$d" 0 && nomiss "$d" || return 1
367
+ done; }
368
+ row_R28() { local d; d=$(mkrow R28); assert_fixture_dir "$d"; drop "$d" cla-evidence
369
+ # Rules split over two pages, the CLA ruleset on page 2 only.
370
+ jqf "$d" rules.json '[[.[0][] | select(.ruleset_id != 13304872)], [.[0][] | select(.ruleset_id == 13304872)]]'
371
+ run "$d" "$PR" "$SHA"; rc_is "$d" 1 && nomiss "$d" && line "$d" "ABSENT cla-evidence"; }
372
+ row_R29() { local d; d=$(mkrow R29); assert_fixture_dir "$d"; jqf "$d" rules.json '.[0] += [{type: "workflows", ruleset_id: 1}]'
373
+ run "$d" "$PR" "$SHA"; rc_is "$d" 3 && nomiss "$d" && verdict "$d" error && reason "$d" unsupported-rule; }
374
+ row_R30() { local d; d=$(mkrow R30); assert_fixture_dir "$d"; jqf "$d" pr.json '.changedFiles = 2'
375
+ run "$d" "$PR" "$SHA"; rc_is "$d" 3 && nomiss "$d" && reason "$d" incomplete-files || return 1
376
+ d=$(mkrow R30b); assert_fixture_dir "$d"; setfiles "$d" "[[$(jq -nc '[range(0;3000) | {filename: "f\(.)"}] | .[]' | paste -sd,)]]"
377
+ run "$d" "$PR" "$SHA"; rc_is "$d" 3 && nomiss "$d" && reason "$d" incomplete-files; }
378
+ row_R31() { local d; d=$(mkrow R31); assert_fixture_dir "$d"; jqf "$d" pr.json '.baseRefName = "develop"'
379
+ run "$d" "$PR" "$SHA"; rc_is "$d" 1 && nomiss "$d" && verdict "$d" not-ready && reason "$d" base-mismatch; }
380
+ row_R32() { local d; d=$(mkrow R32); assert_fixture_dir "$d"; setrun "$d" test '.head_sha = "2222222222222222222222222222222222222222"'
381
+ run "$d" "$PR" "$SHA"; rc_is "$d" 1 && nomiss "$d" && line "$d" "ABSENT test"; }
382
+ row_R33() { local d; d=$(mkrow R33); assert_fixture_dir "$d"
383
+ RUN_PATH="$NOGH" run "$d" "$PR" "$SHA"; rc_is "$d" 3 && verdict "$d" error && reason "$d" missing-tool; }
384
+ row_R34() { local d; d=$(mkrow R34); assert_fixture_dir "$d"; drop "$d" test runs.1.json
385
+ RUN_UNSET_POLL=1 run "$d" "$PR" "$SHA" --wait; rc_is "$d" 0 && [[ "$(cat "$d/sleeps")" == "60" ]] || why "default poll interval is not 60: $(cat "$d/sleeps" 2>/dev/null)"; }
386
+ row_R35() { local d; d=$(mkrow R35); assert_fixture_dir "$d"
387
+ # The same context required from two different apps: both entries must be satisfied.
388
+ jqf "$d" rules.json '.[0] |= map(if .ruleset_id == 13304872 and .type == "required_status_checks" then .parameters.required_status_checks += [{context: "CodeQL", integration_id: 15368}] else . end)'
389
+ run "$d" "$PR" "$SHA"; rc_is "$d" 1 && nomiss "$d" && line "$d" "ABSENT CodeQL" && tailis "$d" 'required=27 absent=["CodeQL"] pending=[] failed=[]'; }
390
+ # R36/R37 (#9454): the POST-ADOPTION live rule shape -- the CI Required ruleset gains a
391
+ # `merge_queue` rule and LOSES its `CodeQL` required context (CodeQL is advisory: it cannot report on
392
+ # `merge_group`). The base capture above is the PRE-adoption shape (26 contexts incl. CodeQL/57789), so
393
+ # these rows derive the new shape from it rather than hand-writing a required set.
394
+ # R36: merge_queue present + CodeQL not required -> READY with required=25 (24 CI Required minus
395
+ # CodeQL = 23, plus the 2 CLA contexts); a FAILED advisory CodeQL run must not block.
396
+ # Before the fix the script errored `unsupported-rule` on the `merge_queue` type, so every
397
+ # admin-merge gate would have failed closed the moment the queue went live.
398
+ # R37: the allowlist stayed SPECIFIC -- merge_queue present alongside an unevaluated type
399
+ # (`workflows`) is still an `unsupported-rule` error.
400
+ _mq_rule='{type: "merge_queue", ruleset_id: 14145388, parameters: {merge_method: "SQUASH", grouping_strategy: "ALLGREEN", max_entries_to_merge: 1, min_entries_to_merge: 1, min_entries_to_merge_wait_minutes: 0, max_entries_to_build: 2, check_response_timeout_minutes: 60}}'
401
+ _post_adoption_rules() { # <dir>
402
+ jqf "$1" rules.json "(.[0] |= map(if .type == \"required_status_checks\" then .parameters.required_status_checks |= map(select(.context != \"CodeQL\")) else . end)) | (.[0] += [$_mq_rule])"
403
+ }
404
+ row_R36() { local d; d=$(mkrow R36); assert_fixture_dir "$d"; _post_adoption_rules "$d"
405
+ setrun "$d" CodeQL '.conclusion = "failure"'
406
+ run "$d" "$PR" "$SHA"; rc_is "$d" 0 && nomiss "$d" && verdict "$d" ready \
407
+ && tailis "$d" 'required=25 absent=[] pending=[] failed=[]'; }
408
+ row_R37() { local d; d=$(mkrow R37); assert_fixture_dir "$d"; _post_adoption_rules "$d"
409
+ jqf "$d" rules.json '.[0] += [{type: "workflows", ruleset_id: 1}]'
410
+ run "$d" "$PR" "$SHA"; rc_is "$d" 3 && nomiss "$d" && verdict "$d" error && reason "$d" unsupported-rule; }
411
+ row_help() { local d; d=$(mkrow help); assert_fixture_dir "$d"; run "$d" --help
412
+ rc_is "$d" 0 && grep -q SOLEUR_ADMIN_MERGE_READY "$d/out" && nocall "$d"; }
413
+
414
+ # ── --green-sha carryover rows ───────────────────────────────────────────────────────────────
415
+ # mkrow's head.json is already the valid shape: verified 2-parent commit [GREEN, P1] and
416
+ # compare.json says P1 is an ancestor-or-equal of main. Carryover rows mutate ONE property.
417
+ row_G1() { local d; d=$(mkrow G1); assert_fixture_dir "$d"
418
+ # The carryover grades GREEN's check runs: the fixture's head_sha must be GREEN.
419
+ jqf "$d" runs.json "map(.check_runs[].head_sha = \"$GREEN\")"
420
+ run "$d" "$PR" "$SHA" --green-sha "$GREEN"
421
+ rc_is "$d" 0 && nomiss "$d" && verdict "$d" ready \
422
+ && logs "$d" "api repos/{owner}/{repo}/commits/$SHA" && logs "$d" "compare/$P1...main" \
423
+ && logs "$d" "commits/$GREEN/check-runs" \
424
+ && ! grep -q "commits/$SHA/check-runs" "$d/log" \
425
+ && grep -Fq "GREEN-CARRYOVER: head $SHA" "$d/out" \
426
+ && tailis "$d" "$READY_TAIL" \
427
+ || why "G1: $(tail -3 "$d/out" | tr '\n' '|') err: $(tail -2 "$d/err" | tr '\n' '|')"; }
428
+ row_G2() { local d; d=$(mkrow G2); assert_fixture_dir "$d"
429
+ jqf "$d" head.json '.parents = [{"sha":"'$GREEN'"}]'
430
+ run "$d" "$PR" "$SHA" --green-sha "$GREEN"
431
+ rc_is "$d" 1 && nomiss "$d" && verdict "$d" not-ready && reason "$d" carryover-not-merge \
432
+ && grep -Fq "GREEN-CARRYOVER refused" "$d/out"; }
433
+ row_G3() { local d; d=$(mkrow G3); assert_fixture_dir "$d"
434
+ jqf "$d" head.json '.commit.verification.verified = false'
435
+ run "$d" "$PR" "$SHA" --green-sha "$GREEN"
436
+ rc_is "$d" 1 && nomiss "$d" && reason "$d" carryover-unverified; }
437
+ row_G4() { local d; d=$(mkrow G4); assert_fixture_dir "$d"
438
+ jqf "$d" head.json '.parents[0].sha = "cccccccccccccccccccccccccccccccccccccccc"'
439
+ run "$d" "$PR" "$SHA" --green-sha "$GREEN"
440
+ rc_is "$d" 1 && nomiss "$d" && reason "$d" carryover-first-parent; }
441
+ row_G5() { local d s; for s in behind diverged; do d=$(mkrow "G5-$s"); assert_fixture_dir "$d"
442
+ printf '{"status":"%s"}' "$s" > "$d/compare.json"
443
+ run "$d" "$PR" "$SHA" --green-sha "$GREEN"
444
+ rc_is "$d" 1 && nomiss "$d" && reason "$d" carryover-not-base || return 1; done; }
445
+ row_G6() { local d; d=$(mkrow G6); assert_fixture_dir "$d"
446
+ # Carryover proof holds but the PRIOR head's suite went red (a re-run): the gate must see it.
447
+ jqf "$d" runs.json "map(.check_runs[].head_sha = \"$GREEN\")"
448
+ setrun "$d" test '.conclusion = "failure"'
449
+ run "$d" "$PR" "$SHA" --green-sha "$GREEN"
450
+ rc_is "$d" 1 && nomiss "$d" && line "$d" "FAILED test (failure)"; }
451
+ row_G7() { local d; d=$(mkrow G7); assert_fixture_dir "$d"
452
+ # UNTRUSTED-CI refuses FIRST: the carryover endpoints are never even called.
453
+ setfiles "$d" '[[{"filename":".github/workflows/x.yml"}]]'
454
+ run "$d" "$PR" "$SHA" --green-sha "$GREEN"
455
+ rc_is "$d" 1 && reason "$d" untrusted-ci && ! grep -q 'compare/' "$d/log" \
456
+ || why "G7: $(tail -2 "$d/out" | tr '\n' '|')"; }
457
+ row_G8() { local d; d=$(mkrow G8); assert_fixture_dir "$d"
458
+ run "$d" "$PR" "$SHA" --green-sha abc123; rc_is "$d" 2 && nocall "$d" || return 1
459
+ run "$d" "$PR" "$SHA" --green-sha "$SHA"; rc_is "$d" 2 && nocall "$d"; }
460
+
461
+ # ── --allow-local-merge rows: the unsigned-carryover arm (#9401) ───────────────
462
+ # mkrow's cmp_added.json/cmp_base.json are a clean docs-only replay of one file
463
+ # (docs/notes.md, disjoint from the PR's plugins/soleur/x.md). mklocal() flips
464
+ # verification off so the head is an unsigned local merge; each row then mutates
465
+ # ONE property of the proof.
466
+ mklocal() { assert_fixture_dir "$1"; jqf "$1" head.json '.commit.verification.verified = false'; }
467
+ row_L1() { local d; d=$(mkrow L1); assert_fixture_dir "$d"
468
+ mklocal "$d"; jqf "$d" runs.json "map(.check_runs[].head_sha = \"$GREEN\")"
469
+ run "$d" "$PR" "$SHA" --green-sha "$GREEN" --allow-local-merge
470
+ rc_is "$d" 0 && nomiss "$d" && verdict "$d" ready && reason "$d" carryover-local-docs \
471
+ && logs "$d" "compare/$GREEN...$SHA" && logs "$d" "compare/$GREEN...$P1" \
472
+ && logs "$d" "commits/$GREEN/check-runs" \
473
+ && ! grep -q "commits/$SHA/check-runs" "$d/log" \
474
+ && grep -Fq "GREEN-CARRYOVER: head $SHA is a clean local merge" "$d/out" \
475
+ && tailis "$d" "$READY_TAIL" \
476
+ || why "L1: $(tail -3 "$d/out" | tr '\n' '|') err: $(tail -2 "$d/err" | tr '\n' '|')"; }
477
+ row_L2() { local d; d=$(mkrow L2); assert_fixture_dir "$d"
478
+ mklocal "$d"
479
+ # The runs must grade GREEN: a mutant that wrongly certifies must reach
480
+ # verdict=ready for the defect probe to see it (absent contexts would still
481
+ # read not-ready, masking the smuggling the mutation row exists to catch).
482
+ jqf "$d" runs.json "map(.check_runs[].head_sha = \"$GREEN\")"
483
+ # A file the merge added beyond the base-side delta is smuggled.
484
+ jqf "$d" cmp_added.json '.files += [{filename:"docs/smuggled.md", status:"added", patch:"@@ s"}]'
485
+ run "$d" "$PR" "$SHA" --green-sha "$GREEN" --allow-local-merge
486
+ rc_is "$d" 1 && nomiss "$d" && verdict "$d" not-ready && reason "$d" carryover-local-not-clean; }
487
+ row_L3() { local d; d=$(mkrow L3); assert_fixture_dir "$d"
488
+ mklocal "$d"
489
+ # Same path, divergent patch: conflict resolution or authored content.
490
+ jqf "$d" cmp_added.json '.files[0].patch = "@@ -0,0 +1 @@\\n+edited"'
491
+ run "$d" "$PR" "$SHA" --green-sha "$GREEN" --allow-local-merge
492
+ rc_is "$d" 1 && reason "$d" carryover-local-not-clean; }
493
+ row_L4() { local d; d=$(mkrow L4); assert_fixture_dir "$d"
494
+ mklocal "$d"
495
+ # The runs must grade GREEN: a mutant that wrongly certifies must reach
496
+ # verdict=ready for the defect probe to see it (absent contexts would still
497
+ # read not-ready, masking the smuggling the mutation row exists to catch).
498
+ jqf "$d" runs.json "map(.check_runs[].head_sha = \"$GREEN\")"
499
+ # A clean replay of base-side CODE is still refused: the gate cannot replay it.
500
+ jqf "$d" cmp_added.json '.files[0].filename = "src/x.ts"'
501
+ jqf "$d" cmp_base.json '.files[0].filename = "src/x.ts"'
502
+ run "$d" "$PR" "$SHA" --green-sha "$GREEN" --allow-local-merge
503
+ rc_is "$d" 1 && reason "$d" carryover-local-nondocs; }
504
+ row_L5() { local d; d=$(mkrow L5); assert_fixture_dir "$d"
505
+ mklocal "$d"
506
+ # The runs must grade GREEN: a mutant that wrongly certifies must reach
507
+ # verdict=ready for the defect probe to see it (absent contexts would still
508
+ # read not-ready, masking the smuggling the mutation row exists to catch).
509
+ jqf "$d" runs.json "map(.check_runs[].head_sha = \"$GREEN\")"
510
+ # The merged delta touches the PR's own file -> overlap.
511
+ jqf "$d" cmp_added.json '.files[0].filename = "plugins/soleur/x.md" | .files[0].status = "modified"'
512
+ jqf "$d" cmp_base.json '.files[0].filename = "plugins/soleur/x.md" | .files[0].status = "modified"'
513
+ run "$d" "$PR" "$SHA" --green-sha "$GREEN" --allow-local-merge
514
+ rc_is "$d" 1 && reason "$d" carryover-local-overlap; }
515
+ row_L6() { local d; d=$(mkrow L6); assert_fixture_dir "$d"
516
+ mklocal "$d"
517
+ # The runs must grade GREEN: a mutant that wrongly certifies must reach
518
+ # verdict=ready for the defect probe to see it (absent contexts would still
519
+ # read not-ready, masking the smuggling the mutation row exists to catch).
520
+ jqf "$d" runs.json "map(.check_runs[].head_sha = \"$GREEN\")"
521
+ # No flag: the unsigned head is refused by the verified arm, as before.
522
+ run "$d" "$PR" "$SHA" --green-sha "$GREEN"
523
+ rc_is "$d" 1 && nomiss "$d" && reason "$d" carryover-unverified \
524
+ && ! grep -q 'compare/' "$d/log" || why "L6: $(cat "$d/log")"; }
525
+ row_L7() { local d; d=$(mkrow L7); assert_fixture_dir "$d"
526
+ # The flag does not rescue a head that is not a 2-parent merge of GREEN.
527
+ jqf "$d" head.json '.parents = [{"sha":"'$GREEN'"}]'
528
+ run "$d" "$PR" "$SHA" --green-sha "$GREEN" --allow-local-merge
529
+ rc_is "$d" 1 && reason "$d" carryover-not-merge || return 1
530
+ d=$(mkrow L7b); assert_fixture_dir "$d"
531
+ jqf "$d" head.json '.commit.verification.verified = false | .parents[0].sha = "cccccccccccccccccccccccccccccccccccccccc"'
532
+ run "$d" "$PR" "$SHA" --green-sha "$GREEN" --allow-local-merge
533
+ rc_is "$d" 1 && reason "$d" carryover-first-parent; }
534
+ row_L8() { local d; d=$(mkrow L8); assert_fixture_dir "$d"
535
+ mklocal "$d"; printf '{"status":"behind"}' > "$d/compare.json"
536
+ run "$d" "$PR" "$SHA" --green-sha "$GREEN" --allow-local-merge
537
+ rc_is "$d" 1 && reason "$d" carryover-not-base \
538
+ && ! grep -q "compare/$GREEN" "$d/log" || why "L8: the delta compares must not run once p1 is off-base"; }
539
+ row_L9() { local d; d=$(mkrow L9); assert_fixture_dir "$d"
540
+ mklocal "$d"
541
+ # A >=300-entry compare delta cannot be proven complete -> error, not refuse.
542
+ jqf "$d" cmp_added.json '.files = [range(0;300) | {filename: "docs/f\(.).md", status:"added", patch:"@@ x"}]'
543
+ run "$d" "$PR" "$SHA" --green-sha "$GREEN" --allow-local-merge
544
+ rc_is "$d" 3 && nomiss "$d" && verdict "$d" error && reason "$d" incomplete-files; }
545
+ row_L10() { local d; d=$(mkrow L10); assert_fixture_dir "$d"
546
+ mklocal "$d"; jqf "$d" cmp_added.json 'del(.files[0].patch)'
547
+ run "$d" "$PR" "$SHA" --green-sha "$GREEN" --allow-local-merge
548
+ rc_is "$d" 1 && reason "$d" carryover-local-not-clean; }
549
+ row_L11() { local d; d=$(mkrow L11); assert_fixture_dir "$d"
550
+ mklocal "$d"; echo '<html>502 Bad Gateway</html>' > "$d/cmp_added.json"
551
+ run "$d" "$PR" "$SHA" --green-sha "$GREEN" --allow-local-merge
552
+ rc_is "$d" 3 && nomiss "$d" && verdict "$d" error; }
553
+ row_L12() { local d; d=$(mkrow L12); assert_fixture_dir "$d"
554
+ run "$d" "$PR" "$SHA" --allow-local-merge
555
+ rc_is "$d" 2 && verdict "$d" error && nocall "$d"; }
556
+ row_L13() { local d; d=$(mkrow L13); assert_fixture_dir "$d"
557
+ # Verified head + flag: the stronger VERIFIED arm wins — the flag is inert
558
+ # (elif ordering is load-bearing) and the delta compares never run.
559
+ jqf "$d" runs.json "map(.check_runs[].head_sha = \"$GREEN\")"
560
+ run "$d" "$PR" "$SHA" --green-sha "$GREEN" --allow-local-merge
561
+ rc_is "$d" 0 && nomiss "$d" && verdict "$d" ready && reason "$d" all-green \
562
+ && logs "$d" "compare/$P1...main" \
563
+ && ! grep -q "compare/$GREEN" "$d/log"; }
564
+ row_L14() { local d; d=$(mkrow L14); assert_fixture_dir "$d"
565
+ mklocal "$d"; jqf "$d" runs.json "map(.check_runs[].head_sha = \"$GREEN\")"
566
+ # A merge that DROPPED the base-side delta (e.g. -s ours: parents [G, tip]
567
+ # with G's tree) produces an EMPTY added delta — subset-wise fine, but the
568
+ # admin merge would revert main's content. The bijection refuses it.
569
+ jqf "$d" cmp_added.json '.files = []'
570
+ run "$d" "$PR" "$SHA" --green-sha "$GREEN" --allow-local-merge
571
+ rc_is "$d" 1 && nomiss "$d" && verdict "$d" not-ready \
572
+ && reason "$d" carryover-local-not-clean; }
573
+ row_L15() { local d; d=$(mkrow L15); assert_fixture_dir "$d"
574
+ mklocal "$d"
575
+ # A rename's SOURCE is part of the identity: same dest+patch but a different
576
+ # previous_filename means the merge replayed a different file.
577
+ jqf "$d" cmp_added.json '.files[0].status = "renamed" | .files[0].previous_filename = "docs/old.md"'
578
+ jqf "$d" cmp_base.json '.files[0].status = "renamed" | .files[0].previous_filename = "docs/other.md"'
579
+ run "$d" "$PR" "$SHA" --green-sha "$GREEN" --allow-local-merge
580
+ rc_is "$d" 1 && reason "$d" carryover-local-not-clean; }
581
+
582
+ # ── ADR-276 S3 (#9728): the draft-era red `test` row of a PR that was marked ready ─────────────
583
+ # Under CI_DRAFT_LIGHT a draft's `test` is red by design. The latest-row-per-context rule already makes a NEWER ready-run
584
+ # row decide (S3/S4 pin that), but until that row exists the draft row is the latest: read as FAILED it ends --wait at
585
+ # once, and read as the whole truth it hides a ready run that was never created. The script asks the resolver ONLY when
586
+ # `test` is FAILED at the graded head, and can only move the context to PENDING or ABSENT, never to green.
587
+ row_S1() { local d; d=$(mkrow S1); assert_fixture_dir "$d"; setrun "$d" test '.conclusion = "failure"'; setv "$d" pending-full 8
588
+ run "$d" "$PR" "$SHA"; rc_is "$d" 1 && nomiss "$d" && line "$d" "PENDING test (draft-era-row-while-ready-run-pending)" && verdict "$d" not-ready && reason "$d" not-green \
589
+ && tailis "$d" 'required=26 absent=[] pending=["test"] failed=[]' && vcalled "$d" 1 && grep -Fxq "verdict $PR" "$d/vlog" || why "S1: $(tail -2 "$d/out" | tr '\n' '|')"; }
590
+ row_S2() { local d; d=$(mkrow S2); assert_fixture_dir "$d"; setrun "$d" test '.conclusion = "failure"' runs.1.json; setv "$d" pending-full 8
591
+ run "$d" "$PR" "$SHA" --wait; rc_is "$d" 0 && nomiss "$d" && polls "$d" 2 && verdict "$d" ready && sleeps "$d" 1 && count "$d" '^WAIT ' 1 && line "$d" 'WAIT poll=1/60 absent=[] pending=["test"]'; }
592
+ row_S3() { local d; d=$(mkrow S3); assert_fixture_dir "$d"; setrun "$d" test '.conclusion = "failure"'
593
+ runs "$d" '. + [(.[] | select(.name == "test") | .id = (.id + 100) | .status = "queued" | .conclusion = null | .started_at = null)]'; setv "$d" pending-full 8
594
+ run "$d" "$PR" "$SHA"; rc_is "$d" 1 && nomiss "$d" && line "$d" "PENDING test (queued)" && tailis "$d" 'required=26 absent=[] pending=["test"] failed=[]' && vcalled "$d" 0; }
595
+ row_S4() { local d; d=$(mkrow S4); assert_fixture_dir "$d"
596
+ runs "$d" '. + [(.[] | select(.name == "test") | .id = (.id - 100) | .conclusion = "failure")]'; setv "$d" no-run
597
+ run "$d" "$PR" "$SHA"; rc_is "$d" 0 && nomiss "$d" && verdict "$d" ready && tailis "$d" "$READY_TAIL" && vcalled "$d" 0; }
598
+ row_S5() { local d; d=$(mkrow S5); assert_fixture_dir "$d"; setrun "$d" test '.conclusion = "failure"'; setv "$d" no-run
599
+ run "$d" "$PR" "$SHA"; rc_is "$d" 1 && nomiss "$d" && line "$d" "PENDING test (no-ready-run)" && reason "$d" not-green || return 1
600
+ run "$d" "$PR" "$SHA" --wait --timeout 120; rc_is "$d" 1 && polls "$d" 2 && verdict "$d" timeout && tailis "$d" 'required=26 absent=[] pending=["test"] failed=[]'; }
601
+ row_S6() { local d; d=$(mkrow S6); assert_fixture_dir "$d"; setrun "$d" test '.conclusion = "failure"'; setv "$d" stalled
602
+ run "$d" "$PR" "$SHA"; rc_is "$d" 1 && nomiss "$d" && verdict "$d" not-ready && reason "$d" stalled && line "$d" "ABSENT test (stalled: no full CI run decided after the ready event)" \
603
+ && tailis "$d" 'required=26 absent=["test"] pending=[] failed=[]' && grep -Fq "gh pr ready --undo $PR" "$d/out" && grep -Fq "gh pr ready $PR" "$d/out" \
604
+ && ! grep -Fq 'gh run rerun' "$d/out" || return 1
605
+ run "$d" "$PR" "$SHA" --wait; rc_is "$d" 1 && polls "$d" 1 && reason "$d" stalled; }
606
+ row_S7() { local d; d=$(mkrow S7); assert_fixture_dir "$d"; setrun "$d" test '.conclusion = "failure"'; setv "$d" awaiting-approval 8
607
+ run "$d" "$PR" "$SHA"; rc_is "$d" 1 && nomiss "$d" && reason "$d" awaiting-approval && line "$d" "PENDING test (awaiting-approval: a maintainer must approve the fork workflow run)" \
608
+ && tailis "$d" 'required=26 absent=[] pending=["test"] failed=[]' || return 1
609
+ run "$d" "$PR" "$SHA" --wait; rc_is "$d" 1 && polls "$d" 1 && reason "$d" awaiting-approval; }
610
+ row_S8() { local d; d=$(mkrow S8); assert_fixture_dir "$d"; setrun "$d" test ".conclusion = \"failure\" | .details_url = \"$TEST_URL_RUN8\""; setv "$d" full-decided 8
611
+ run "$d" "$PR" "$SHA"; rc_is "$d" 1 && nomiss "$d" && line "$d" "FAILED test (failure)" && reason "$d" not-green && tailis "$d" 'required=26 absent=[] pending=[] failed=["test"]' || return 1
612
+ run "$d" "$PR" "$SHA" --wait; rc_is "$d" 1 && polls "$d" 1 && verdict "$d" not-ready; }
613
+ row_S9() { local d; d=$(mkrow S9); assert_fixture_dir "$d"
614
+ setrun "$d" test ".details_url = \"$TEST_URL_RUN8\""
615
+ runs "$d" ". + [(.[] | select(.name == \"test\") | .id = (.id + 1) | .conclusion = \"failure\" | .details_url = \"$TEST_URL_RUN6\")]"; setv "$d" full-decided 8
616
+ run "$d" "$PR" "$SHA"; rc_is "$d" 1 && nomiss "$d" && verdict "$d" not-ready && reason "$d" stale-row && line "$d" "FAILED test (stale-row: the newest test row belongs to run 6, not the deciding run 8)" \
617
+ && tailis "$d" 'required=26 absent=[] pending=[] failed=["test"]' && grep -Fq "gh pr ready --undo $PR" "$d/out" || return 1
618
+ run "$d" "$PR" "$SHA" --wait; rc_is "$d" 1 && polls "$d" 1 && reason "$d" stale-row; }
619
+ row_S10() { local d; d=$(mkrow S10); assert_fixture_dir "$d"; setrun "$d" test '.conclusion = "failure"'; setv "$d" n/a
620
+ run "$d" "$PR" "$SHA"; rc_is "$d" 1 && nomiss "$d" && line "$d" "FAILED test (failure)" && reason "$d" not-green && vcalled "$d" 1; }
621
+ row_S11() { local d; d=$(mkrow S11); assert_fixture_dir "$d"; setrun "$d" test '.conclusion = "failure"'; setv "$d" pending-full 8; touch "$d/vstate.fail"
622
+ run "$d" "$PR" "$SHA"; rc_is "$d" 1 && nomiss "$d" && line "$d" "FAILED test (failure)" && reason "$d" not-green && vcalled "$d" 1; }
623
+ row_S12() { local d; d=$(mkrow S12); assert_fixture_dir "$d"; drop "$d" test; setv "$d" pending-full 8
624
+ run "$d" "$PR" "$SHA"; rc_is "$d" 1 && nomiss "$d" && line "$d" "ABSENT test" && tailis "$d" 'required=26 absent=["test"] pending=[] failed=[]' && vcalled "$d" 0; }
625
+ row_S13() { local d; d=$(mkrow S13); assert_fixture_dir "$d"
626
+ jqf "$d" runs.json "map(.check_runs[].head_sha = \"$GREEN\")"; setrun "$d" test '.conclusion = "failure"'; setv "$d" pending-full 8
627
+ run "$d" "$PR" "$SHA" --green-sha "$GREEN"; rc_is "$d" 1 && nomiss "$d" && line "$d" "FAILED test (failure)" && vcalled "$d" 0; }
628
+ row_S14() { local d; d=$(mkrow S14); assert_fixture_dir "$d"; setfiles "$d" '[[{"filename":".github/workflows/ci.yml"}]]'; setrun "$d" test '.conclusion = "failure"'; setv "$d" pending-full 8
629
+ run "$d" "$PR" "$SHA"; rc_is "$d" 1 && nomiss "$d" && reason "$d" untrusted-ci && grep -q '^UNTRUSTED-CI' "$d/out" && vcalled "$d" 0 || return 1
630
+ run "$d" "$PR" "$SHA" --wait; rc_is "$d" 1 && polls "$d" 1 && reason "$d" untrusted-ci; }
631
+ row_S15() { local d; d=$(mkrow S15); assert_fixture_dir "$d"; setrun "$d" test '.conclusion = "failure"'; setv "$d" pending-full 8 1111111111111111111111111111111111111111
632
+ run "$d" "$PR" "$SHA"; rc_is "$d" 1 && nomiss "$d" && line "$d" "FAILED test (failure)" && reason "$d" not-green; }
633
+ # S16: whatever the resolver says, a red or absent test is never READY (the guard cannot be a reject-everything stub: S4 passes).
634
+ row_S16() { local d st t
635
+ for st in n/a full-decided pending-full no-run stalled awaiting-approval; do for t in red absent; do
636
+ d=$(mkrow "S16-$st-$t"); assert_fixture_dir "$d"; setv "$d" "$st" 8
637
+ if [[ "$t" == red ]]; then setrun "$d" test '.conclusion = "failure"'; else drop "$d" test; fi
638
+ run "$d" "$PR" "$SHA"; rc_is "$d" 1 && nomiss "$d" && verdict "$d" not-ready || return 1
639
+ done; done; }
640
+
641
+ # defect probes for the mutation rows: the mutant must show the defect, not merely crash.
642
+ # R2's mutant iterates present checks, so the absent `test` is never named (the positive
643
+ # readiness count then refuses it as an error rather than a ready -- defence in depth).
644
+ dfx_R1_unnamed() { ! grep -Fxq 'ABSENT test' "$(rowdir R1)/out"; }
645
+ dfx_R11_absent() { [[ "$(rc_of R11)" == 1 ]] && grep -Fxq 'ABSENT test' "$(rowdir R11)/out"; }
646
+ dfx_H1_miss() { grep -q '^STUB-MISS' "$(rowdir H1)/log"; }
647
+ dfx_R4_green() { [[ "$(rc_of R4)" == 0 ]]; }
648
+ dfx_R27_green() { grep -q 'verdict=ready' "$SANDBOX/rows/R27-skipped/out"; }
649
+ dfx_R8_green() { [[ "$(rc_of R8)" == 0 ]]; }
650
+ dfx_R26_ready() { grep -q 'verdict=ready' "$(rowdir R26)/out"; }
651
+ dfx_R32_green() { [[ "$(rc_of R32)" == 0 ]]; }
652
+ dfx_R28_green() { [[ "$(rc_of R28)" == 0 ]]; }
653
+ dfx_R36_unsupported() { [[ "$(rc_of R36)" == 3 ]] && grep -q 'reason=unsupported-rule' "$(rowdir R36)/out"; }
654
+ dfx_L2_ready() { [[ "$(rc_of L2)" == 0 ]]; }
655
+ dfx_L4_ready() { [[ "$(rc_of L4)" == 0 ]]; }
656
+ dfx_L5_ready() { [[ "$(rc_of L5)" == 0 ]]; }
657
+ dfx_L6_ready() { [[ "$(rc_of L6)" == 0 ]]; }
658
+ dfx_L1_reason() { [[ "$(rc_of L1)" == 0 ]] && ! grep -q 'reason=carryover-local-docs' "$(rowdir L1)/out"; }
659
+
660
+ echo "== admin-merge-ready.sh (Guard 1)"
661
+ for r in S1 S2 S3 S4 S5 S6 S7 S8 S9 S10 S11 S12 S13 S14 S15 S16 H1 H2 R1 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15 R16 R17 R18 R19 R20 R21 R22 R23 R24 R25 R26 R27 R28 R29 R30 R31 R32 R33 R34 R35 R36 R37 G1 G2 G3 G4 G5 G6 G7 G8 L1 L2 L3 L4 L5 L6 L7 L8 L9 L10 L11 L12 L13 L14 L15 help; do
662
+ case_ok "$r" "row_$r"
663
+ done
664
+
665
+ echo "== mutation rows"
666
+ # R2 -- the dispatch row: iterate the PRESENT checks instead of the REQUIRED set (#8458).
667
+ case_mutant R2 row_R1 dfx_R1_unnamed '| [ $req[] as $q' '| [ ($runs | map({context: .name, integration_id: .app.id}) | unique)[] as $q'
668
+ # R11 -- drop --paginate from the check-runs call only: page 2 (which holds `test`) is never read.
669
+ case_mutant R11-paginate row_R11 dfx_R11_absent 'gh api --paginate --slurp "repos/{owner}/{repo}/commits/' 'gh api --slurp "repos/{owner}/{repo}/commits/'
670
+ # H3 -- query a different endpoint: the strict stub must refuse it, not answer from the fixture.
671
+ case_mutant H3-endpoint row_H1 dfx_H1_miss 'repos/{owner}/{repo}/rules/branches/$enc' 'repos/{owner}/{repo}/rulesets'
672
+ # R4 by timestamp: pick the latest run by started_at instead of id.
673
+ case_mutant R4-started_at row_R4 dfx_R4_green '($c | max_by(.id | tonumber)) as $l' '($c | max_by(.started_at // "")) as $l'
674
+ # R27: drop the skipped-after-failure rule.
675
+ case_mutant R27-rule row_R27 dfx_R27_green 'elif any($sib[]; .status == "completed"' 'elif false and any($sib[]; .status == "completed"'
676
+ # R8/R22: match by name only.
677
+ case_mutant R8-app row_R8 dfx_R8_green 'select(.name == $q.context and .app.id == $q.integration_id)' 'select(.name == $q.context)'
678
+ # R26: the untrusted-CI refusal narrowed to workflows only (.github/actions/ edits pass).
679
+ case_mutant R26-actions row_R26 dfx_R26_ready 'select(test("^\\.github/(workflows|actions)/"))' 'select(test("^\\.github/workflows/"))'
680
+ # R32: drop the head_sha restriction.
681
+ case_mutant R32-headsha row_R32 dfx_R32_green '| [.[] | .check_runs[] | select(.head_sha == $sha)] as $runs' '| [.[] | .check_runs[]] as $runs'
682
+ # R28: read only the first page of the rules.
683
+ case_mutant R28-rulespage row_R28 dfx_R28_green '($rules[0] | add // []) as $all' '($rules[0][0] // []) as $all'
684
+ # R36 -- drop merge_queue from the evaluated rule types: the live post-adoption shape errors.
685
+ case_mutant R36-known row_R36 dfx_R36_unsupported '"required_signatures","required_status_checks","merge_queue"] as $known' '"required_signatures","required_status_checks"] as $known'
686
+
687
+ # ── mutation rows for the --allow-local-merge arm (#9401) ─────────────────────
688
+ # LM-clean: drop the byte-identical patch bijection -- a smuggled file now certifies.
689
+ case_mutant LM-clean row_L2 dfx_L2_ready \
690
+ 'elif any($af[] + $bf[]; (.patch | type) != "string")
691
+ or ([$af[] | [.filename, .status, (.previous_filename // ""), .patch]] | sort)
692
+ != ([$bf[] | [.filename, .status, (.previous_filename // ""), .patch]] | sort)
693
+ then "not-clean"' \
694
+ 'elif false
695
+ then "not-clean"'
696
+ # LM-nondocs: drop the docs-surface filter -- a clean base-side CODE replay passes.
697
+ case_mutant LM-nondocs row_L4 dfx_L4_ready \
698
+ 'elif any($af[]; docs | not) then "nondocs"' \
699
+ 'elif false then "nondocs"'
700
+ # LM-overlap: drop the PR-file disjointness -- a merge touching a PR file passes.
701
+ case_mutant LM-overlap row_L5 dfx_L5_ready \
702
+ 'elif any($af[]; [.filename, (.previous_filename // empty)]
703
+ | .[] | . as $f | ($pf | index($f)) != null) then "overlap"' \
704
+ 'elif false then "overlap"'
705
+ # LM-flag: the arm reachable WITHOUT --allow-local-merge.
706
+ case_mutant LM-flag row_L6 dfx_L6_ready \
707
+ 'elif [[ "$carry" == "carryover-unverified" && "$ALLOW_LOCAL" == "1" ]]; then' \
708
+ 'elif [[ "$carry" == "carryover-unverified" ]]; then'
709
+ # LM-reason: a ready local-merge certified under a non-carryover reason token.
710
+ case_mutant LM-reason row_L1 dfx_L1_reason \
711
+ 'REASON="carryover-local-docs"' \
712
+ 'REASON="all-green"'
713
+
714
+ # ── ADR-276 S3 mutation rows: the head-verdict handling ────────────────────────────────────────
715
+ dfx_S1_failed() { grep -Fq 'FAILED test' "$(rowdir S1)/out"; }
716
+ dfx_S6_noabsent() { ! grep -Fq 'absent=["test"]' "$(rowdir S6)/out"; }
717
+ dfx_S9_nostale() { ! grep -Fq 'reason=stale-row' "$(rowdir S9)/out"; }
718
+ dfx_S13_called() { [[ "$(vcalls "$(rowdir S13)")" != 0 ]]; }
719
+ dfx_S15_softened() { grep -Fq 'PENDING test' "$(rowdir S15)/out"; }
720
+ dfx_S6_noreason() { ! grep -Fq 'reason=stalled' "$(rowdir S6)/out"; }
721
+ # MS1 -- pending-full no longer softens the draft row: --wait ends at once on a FAILED context again.
722
+ case_mutant MS1-pending row_S1 dfx_S1_failed ' pending-full)
723
+ jq -c' ' pending-full-off)
724
+ jq -c'
725
+ # MS2 -- stalled is reported as PENDING instead of ABSENT.
726
+ case_mutant MS2-stalled row_S6 dfx_S6_noabsent '.failed -= ["test"] | .absent += ["test"]' '.failed -= ["test"] | .pending += ["test"]'
727
+ # MS3 -- stalled loses its terminal reason: a --wait would burn the whole budget on a PR that will never run.
728
+ case_mutant MS3-reason row_S6 dfx_S6_noreason 'REASON_OVERRIDE="stalled"' 'REASON_OVERRIDE=""'
729
+ # MS4 -- a verdict for ANOTHER head is honoured.
730
+ case_mutant MS4-sha row_S15 dfx_S15_softened '[[ "$m" == *" sha=$SHA "* ]] || return 1' 'true'
731
+ # MS5 -- the carryover arm (grading another sha) consults the resolver for the PR head.
732
+ case_mutant MS5-carryover row_S13 dfx_S13_called '[[ "$CHECK_SHA" == "$SHA" ]] && jq -e' 'jq -e'
733
+ # MS6 -- the stale-row comparison inverted.
734
+ case_mutant MS6-stale row_S9 dfx_S9_nostale '"$last_run" != "$V_RUN"' '"$last_run" == "$V_RUN"'
735
+
736
+ # ── H4: anti-vacuity ─────────────────────────────────────────────────────────────────────────
737
+ echo
738
+ echo "cases_run=$CASES_RUN passes=$passes fails=$fails ledger=${#FAILED[@]}"
739
+ _min_cases=99
740
+ if [[ "$CASES_RUN" -lt "$_min_cases" ]]; then
741
+ printf '[FATAL] assertion floor: only %s case(s) ran, floor is %s\n' "$CASES_RUN" "$_min_cases" >&2; exit 1
742
+ fi
743
+ if [[ $((passes + fails)) -ne "$CASES_RUN" ]]; then
744
+ printf '[FATAL] %s verdicts for %s cases\n' "$((passes + fails))" "$CASES_RUN" >&2; exit 1
745
+ fi
746
+ if [[ "${#FAILED[@]}" -ne "$fails" ]]; then
747
+ printf '[FATAL] ledger/counter disagree: %s vs %s\n' "${#FAILED[@]}" "$fails" >&2; exit 1
748
+ fi
749
+ if [[ "${#FAILED[@]}" -ne 0 ]]; then
750
+ printf '[FATAL] %s failing assertion(s):\n' "${#FAILED[@]}" >&2
751
+ printf ' - %s\n' "${FAILED[@]}" >&2; exit 1
752
+ fi
753
+ echo "admin-merge-ready: all $passes cases passed"