@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,545 @@
1
+ #!/usr/bin/env bash
2
+ # admin-merge-ready.sh <PR> <head-sha> [--base BRANCH] [--wait [--timeout SEC]]
3
+ # admin-merge-ready.sh --help
4
+ #
5
+ # The ONLY permitted answer to "may this PR be admin-merged?" (#8500).
6
+ #
7
+ # WHY THIS EXISTS. An `--admin` merge bypasses the WHOLE `required_status_checks` ruleset
8
+ # rule (every required context, not just the up-to-date gate), so nothing server-side stops a
9
+ # red, pending or ABSENT merge. #8458 and #8439 were admin-merged that way. The obvious agent
10
+ # loop -- `gh pr checks --required` until nothing is pending -- lists only checks that EXIST: the
11
+ # aggregate `test` context is created only after every shard finishes, so it was absent (not
12
+ # pending) and the loop saw 25 of 26 required contexts green and merged. This script iterates
13
+ # the REQUIRED SET, never the present checks, so an uncreated required context reads ABSENT.
14
+ #
15
+ # HOW IT DECIDES.
16
+ # * The PR must be OPEN, its head must equal <head-sha>, and its base must equal --base
17
+ # (default `main`), re-checked on every poll. Callers merge with --match-head-commit.
18
+ # * Required set: `gh api repos/{owner}/{repo}/rules/branches/<base>` -- the UNION over every
19
+ # `required_status_checks` rule (today two rulesets: CI Required + CLA Required), deduped.
20
+ # An empty set is an ERROR, never "nothing required" -- that is the vacuous state. A rule
21
+ # type this script does not evaluate (e.g. `workflows`, `code_scanning`, `pull_request`,
22
+ # `required_deployments`) is also an ERROR: `--admin` bypasses it too, so ignoring it would
23
+ # be a silent pass. `merge_queue` (adopted #9454) is a KNOWN type that adds NO required
24
+ # context: it only re-runs the required set on a `merge_group` temp ref, so the required
25
+ # contexts it gates are exactly the ones read above. `--admin` skips the queue the same way it
26
+ # skips the required-status-checks rule (bypass_actors, mode pull_request; the adoption canary
27
+ # verifies it), and the readiness decision below is unchanged. Every OTHER unlisted type
28
+ # still errors.
29
+ # * Every required entry must be pinned to an app (`integration_id`). An unpinned entry could
30
+ # be satisfied by a legacy commit STATUS, which this script does not read; it is refused.
31
+ # * Check runs: `commits/<sha>/check-runs?filter=all`, paginated, restricted to
32
+ # `head_sha == <sha>`, matched by name AND `app.id == integration_id` (a run named like a
33
+ # context but posted by a different app, e.g. a github-actions run named for a GHAS-bound
34
+ # context, does not satisfy it).
35
+ # * Latest run per context = max check-run `id`, compared numerically. NOT `started_at`: a
36
+ # queued re-run has `started_at: null` and would sort as the OLDEST, letting a stale
37
+ # success mask a pending re-run. This mirrors GitHub, which uses the latest run per name+app.
38
+ # * Green = completed + success|skipped|neutral. GitHub docs, "About protected branches":
39
+ # "Required status checks must have a `successful`, `skipped`, or `neutral` status before
40
+ # collaborators can make changes to a protected branch." Do not "fix" skipped into a failure.
41
+ # * EXCEPT a skipped/neutral required context is only as good as its check suite: GitHub marks a
42
+ # job skipped when a `needs:` dependency failed and it lacks `if: always()`. If any other run
43
+ # in the same check suite (latest per name+app) concluded non-green, the context is FAILED
44
+ # (skipped-after-failure); if any is still running (a dependency being re-run), it is PENDING
45
+ # (skipped-while-suite-running). `--admin` is the only check, so this is stricter than
46
+ # GitHub on purpose: an unrelated failed job in the same workflow run also blocks.
47
+ # * Untrusted CI: if the PR edits `.github/workflows/` or `.github/actions/` (current or
48
+ # previous path of any file), its own runs could mint a genuine github-actions run under any
49
+ # required name, so it cannot certify itself: UNTRUSTED-CI, not-ready. Such a PR has no
50
+ # agent admin-merge path; the operator merges it by hand. The file list must be complete
51
+ # (its length must equal the PR's `changedFiles`, and the API caps at 3000), else ERROR.
52
+ # * Ready is decided POSITIVELY: every required context must be counted green. "No non-green
53
+ # line was printed" is not readiness -- an empty or unparseable API body would satisfy it.
54
+ #
55
+ # * The draft-era `test` row (ADR-276 S3, #9728). With CI_DRAFT_LIGHT on, a draft PR's `test` concludes red BY DESIGN,
56
+ # and after `gh pr ready` that row is the latest `test` row until the ready run's aggregator posts (38 to 51 minutes).
57
+ # Only when `test` is FAILED at the graded head does this script ask plugins/soleur/scripts/ci-head-verdict.sh, and the
58
+ # answer can only move `test` from FAILED to PENDING or ABSENT, never to green: pending-full and no-run -> PENDING (a
59
+ # `--wait` keeps waiting; no-run times out, never READY); stalled -> ABSENT, reason=stalled (terminal); awaiting-approval
60
+ # -> PENDING, reason=awaiting-approval (terminal); full-decided whose deciding run is not the run the newest `test` row
61
+ # belongs to (a manual re-run of a pre-ready run) -> FAILED, reason=stale-row (terminal); every other answer, an
62
+ # unreadable one, and a verdict for another head leave FAILED untouched. The recovery for stalled / stale-row is
63
+ # `gh pr ready --undo <PR>` then `gh pr ready <PR>` with a user token, never a re-run of the draft run.
64
+ #
65
+ # KNOWN LIMITS (stated, not silent).
66
+ # (a) A re-run started between this script's read and `gh pr merge` is not seen:
67
+ # `--match-head-commit` pins the head, not the checks. settle-then-admin-merge.md's merge
68
+ # block re-runs this script immediately before every merge attempt.
69
+ # (b) A check run created through the API (POST /check-runs) by any workflow token holding
70
+ # `checks: write` carries app 15368 and any name it likes -- including a workflow pushed to
71
+ # a side branch by an actor with write access. GitHub's own server-side check has the same
72
+ # weakness; UNTRUSTED-CI covers only workflows this PR itself edits.
73
+ # (c) If two workflows ever emit a job with the same required name, the newer run wins, as it
74
+ # does on GitHub. Probed 2026-09-22: no required name spans two check suites.
75
+ # (d) Classic branch protection is not read; this repo has none (`branches/main/protection`
76
+ # is 404) and its required checks live in rulesets (infra/github/*.tf).
77
+ #
78
+ # --green-sha <prior-sha> (was-green carryover).
79
+ # For a PR that is BEHIND after a green head: the operator authorizes --admin, the branch is
80
+ # updated (gh pr update-branch or equivalent), and the NEW head's checks have not settled.
81
+ # The certification is carried from <prior-sha> to <sha> only when the move between them is
82
+ # provably contentless:
83
+ # * <sha> is a 2-parent commit with .commit.verification.verified == true. Only GitHub can
84
+ # produce such a commit (update-branch / merge-upstream / web merge); a server-side merge
85
+ # cannot smuggle authored edits -- it fails on conflict instead of producing one.
86
+ # * parents[0] == <prior-sha>: the first parent is the head that was green.
87
+ # * compare(parents[1]...<base>) is ahead|identical: the second parent is an ancestor-or-
88
+ # equal of the base tip, so every byte the merge added came from <base>.
89
+ # Any failure of those three is not-ready (reason=green-carryover-*), NOT an error: the
90
+ # caller falls back to waiting for the new head's own checks. When the proof holds, the
91
+ # required-check gate runs against <prior-sha>'s check runs (which still exist -- check runs
92
+ # are keyed to the commit, not the branch) and the merge itself still uses
93
+ # --match-head-commit <sha>. UNTRUSTED-CI still refuses first: a workflow-editing PR cannot
94
+ # self-certify on ANY sha.
95
+ #
96
+ # --allow-local-merge (requires --green-sha; #9401).
97
+ # A SECOND carryover arm for a head that is NOT GitHub-signed -- a hand-made
98
+ # `git merge origin/<base>` push, which arrives unsigned (carryover-unverified under the
99
+ # arm above). (The #9401 disjoint-delta skip needs NO carryover: it leaves the green head
100
+ # untouched, so a plain invocation grades it directly; and a hook-produced sync merge can
101
+ # never satisfy this arm -- its delta overlaps the PR's files by construction.) The
102
+ # signature is replaced by
103
+ # a mechanical proof that the merge added nothing but base-side docs content:
104
+ # * still a 2-parent commit with parents[0] == <green-sha> -- a non-merge or a merge of a
105
+ # different head is refused as carryover-not-merge / carryover-first-parent, flag or not;
106
+ # * parents[1] is an ancestor-or-equal of <base> (same compare as the verified arm);
107
+ # * compare(<green-sha>...<sha>)'s file list EQUALS
108
+ # compare(<green-sha>...parents[1])'s as a set of (filename, status, previous_filename,
109
+ # patch) tuples -- a merge that resolved a conflict or authored an edit diverges from
110
+ # the base-side patch, a file it added beyond the base delta is smuggled, and a file it
111
+ # DROPPED (a subset-only check would certify a merge that reverts base content -- e.g.
112
+ # parents [G, tip] with G's tree) fails the equality;
113
+ # * every such file sits on the docs surface (knowledge-base/, docs/,
114
+ # plugins/soleur/skills/, or *.md) -- base-side CODE carried by an unsigned merge is not
115
+ # certifiable by a gate that cannot replay it;
116
+ # * none of them appear in the PR's own file list -- an overlap means merge-time
117
+ # resolution touched content the PR owns.
118
+ # Success grades <green-sha>'s contexts and reports reason=carryover-local-docs. Refusals
119
+ # are not-ready (carryover-local-not-clean / -nondocs / -overlap), never errors; a truncated
120
+ # (>=300-entry) or unparseable compare body is an error, like every other unreadable input.
121
+ #
122
+ # EXIT CODES (the contract every caller branches on).
123
+ # 0 ready every required context present and green on this head -> merge with
124
+ # --match-head-commit <sha>
125
+ # 1 not-ready something ABSENT/PENDING/FAILED (--wait: a FAILED is terminal), UNTRUSTED-CI,
126
+ # base mismatch, or --wait timeout (verdict=timeout) -> stop, never merge
127
+ # 2 error usage
128
+ # 3 error gh/jq missing, any API failure or unparseable body, empty required set,
129
+ # unpinned context, unsupported rule type, incomplete file list
130
+ # 4 stale PR not OPEN, or head moved off <sha> -> re-read the SHA and restart
131
+ # 130 interrupted (signal); marker verdict=error
132
+ # Every path except --help prints the marker as its LAST stdout line:
133
+ # SOLEUR_ADMIN_MERGE_READY verdict=<ready|not-ready|stale|timeout|error> pr=<N> sha=<sha>
134
+ # base=<branch> reason=<token> required=<n> absent=<json> pending=<json> failed=<json>
135
+ # Name arrays are compact JSON because context names contain spaces, parentheses, '#' and ':'.
136
+ # PR-controlled strings (file paths) are never printed.
137
+ #
138
+ # --wait polls every ADMIN_MERGE_READY_POLL_SECONDS (default 60; test seam, must be ^[0-9]+$).
139
+ # The budget is counted in 60-second POLLS (ceil(timeout/60)), so a 0-second test poll gives the
140
+ # same poll count as production. Emits one WAIT line when the non-green set changes, a HEARTBEAT
141
+ # every 5 unchanged polls, and exactly one marker at the end. Host it in the Monitor tool.
142
+ set -uo pipefail
143
+
144
+ readonly MARKER="SOLEUR_ADMIN_MERGE_READY"
145
+
146
+ usage() {
147
+ cat <<'EOF'
148
+ usage: admin-merge-ready.sh <PR> <head-sha> [--base BRANCH] [--wait [--timeout SEC]]
149
+ admin-merge-ready.sh <PR> <head-sha> --green-sha <prior-sha> [--allow-local-merge] [--base BRANCH]
150
+ admin-merge-ready.sh --help
151
+
152
+ Exits 0 only when every context required by the base branch's rulesets is PRESENT on
153
+ <head-sha> and its latest check run concluded success|skipped|neutral.
154
+ With --green-sha the gate runs against <prior-sha> instead: valid only when <head-sha>
155
+ is GitHub's own verified 2-parent merge of <prior-sha> and an ancestor-or-equal of the
156
+ base tip (was-green carryover; see the header). With --allow-local-merge an unsigned
157
+ local merge is also certified, but only when the delta it added over <prior-sha> is a
158
+ byte-identical replay of the base-side delta, docs-only, and disjoint from the PR's
159
+ own file list.
160
+
161
+ exit 0 ready | 1 not-ready|timeout | 2 usage error | 3 gh/jq/API/ruleset error | 4 stale (head moved / PR not open)
162
+
163
+ Last stdout line, on every path except --help:
164
+ SOLEUR_ADMIN_MERGE_READY verdict=<ready|not-ready|stale|timeout|error> pr=<N> sha=<sha> base=<branch> reason=<token> required=<n> absent=<json-array> pending=<json-array> failed=<json-array>
165
+ EOF
166
+ }
167
+
168
+ PR_OUT="?"; SHA_OUT="?"; BASE="?"; REASON="none"; REASON_OVERRIDE=""
169
+ N_REQ=0; J_ABSENT="[]"; J_PENDING="[]"; J_FAILED="[]"
170
+
171
+ marker() {
172
+ printf '%s verdict=%s pr=%s sha=%s base=%s reason=%s required=%s absent=%s pending=%s failed=%s\n' \
173
+ "$MARKER" "$1" "$PR_OUT" "$SHA_OUT" "$BASE" "$REASON" "$N_REQ" "$J_ABSENT" "$J_PENDING" "$J_FAILED"
174
+ }
175
+
176
+ usage_error() {
177
+ echo "admin-merge-ready: $1" >&2
178
+ usage >&2
179
+ REASON=usage
180
+ marker error
181
+ exit 2
182
+ }
183
+
184
+ # ── args ──────────────────────────────────────────────────────────────────────────────────────
185
+ if [[ "${1-}" == "--help" || "${1-}" == "-h" ]]; then usage; exit 0; fi
186
+ PR="${1-}"; SHA="${2-}"
187
+ [[ "$PR" =~ ^[0-9]+$ ]] || usage_error "PR must be a number"
188
+ PR_OUT="$PR"
189
+ [[ "$SHA" =~ ^[0-9a-f]{40}$ ]] || usage_error "head-sha must be a full 40-hex SHA"
190
+ SHA_OUT="$SHA"
191
+ shift 2
192
+ WAIT=0; TIMEOUT=3600; WANT_BASE="main"; GREEN_SHA=""; ALLOW_LOCAL=0
193
+ while (( $# > 0 )); do
194
+ case "$1" in
195
+ --wait) WAIT=1; shift ;;
196
+ --timeout)
197
+ if ! [[ "${2-}" =~ ^[0-9]+$ ]] || (( 10#$2 == 0 )); then usage_error "--timeout must be a positive integer"; fi
198
+ TIMEOUT=$((10#$2)); shift 2 ;;
199
+ --base)
200
+ if ! [[ "${2-}" =~ ^[A-Za-z0-9._/-]+$ ]]; then usage_error "--base must be a branch name"; fi
201
+ WANT_BASE="$2"; shift 2 ;;
202
+ --green-sha)
203
+ if ! [[ "${2-}" =~ ^[0-9a-f]{40}$ ]]; then usage_error "--green-sha must be a full 40-hex SHA"; fi
204
+ GREEN_SHA="$2"; shift 2 ;;
205
+ --allow-local-merge) ALLOW_LOCAL=1; shift ;;
206
+ *) usage_error "unknown argument" ;;
207
+ esac
208
+ done
209
+ [[ -z "$GREEN_SHA" || "$GREEN_SHA" != "$SHA" ]] \
210
+ || usage_error "--green-sha equals <head-sha> -- plain invocation already covers it"
211
+ [[ "$ALLOW_LOCAL" != "1" || -n "$GREEN_SHA" ]] \
212
+ || usage_error "--allow-local-merge requires --green-sha"
213
+ POLL_SECONDS="${ADMIN_MERGE_READY_POLL_SECONDS:-60}"
214
+ [[ "$POLL_SECONDS" =~ ^[0-9]+$ ]] || usage_error "ADMIN_MERGE_READY_POLL_SECONDS must be ^[0-9]+\$"
215
+
216
+ for bin in gh jq; do
217
+ command -v "$bin" >/dev/null 2>&1 || { echo "ERROR: $bin not found on PATH" >&2; REASON="missing-tool"; marker error; exit 3; }
218
+ done
219
+
220
+ WORK="$(mktemp -d)" || { echo "ERROR: mktemp failed" >&2; REASON=mktemp; marker error; exit 3; }
221
+ trap 'rm -rf "$WORK"' EXIT
222
+ trap 'REASON=interrupted; marker error; exit 130' INT TERM HUP
223
+
224
+ # ── one readiness read ───────────────────────────────────────────────────────────────────────
225
+ # Sets LINES (newline-joined non-green lines), N_REQ, J_ABSENT/J_PENDING/J_FAILED, MSG, REASON.
226
+ # Returns 0 ready | 1 not-ready | 4 stale | 3 error.
227
+ fail3() { MSG="ERROR: $1"; REASON="$2"; return 3; }
228
+
229
+ # resolve_head_verdict: V_STATE / V_RUN from ci-head-verdict.sh for THIS PR at THIS head ($SHA). Returns 1 (and leaves
230
+ # V_STATE empty) when it cannot answer: the caller then keeps today's reading. CI_HEAD_VERDICT_BIN is the test seam.
231
+ V_STATE=""; V_RUN="none"; V_WHY=""
232
+ resolve_head_verdict() {
233
+ local bin m
234
+ V_STATE=""; V_RUN="none"; V_WHY=""
235
+ bin="${CI_HEAD_VERDICT_BIN:-$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd -P)/ci-head-verdict.sh}"
236
+ [[ -r "$bin" ]] || return 1
237
+ m="$(bash "$bin" verdict "$PR" 2>/dev/null)" || true
238
+ m="$(grep -m1 '^SOLEUR_CI_HEAD_VERDICT ' <<<"$m")" || return 1
239
+ [[ "$m" == *" sha=$SHA "* ]] || return 1
240
+ V_RUN="${m#* run=}"; V_RUN="${V_RUN%% *}"; V_WHY="${m#* reason=}"; V_WHY="${V_WHY%% *}"
241
+ m="${m#*state=}"; m="${m%% *}"
242
+ case "$m" in n/a|full-decided|pending-full|no-run|stalled|awaiting-approval) V_STATE="$m" ;; *) return 1 ;; esac
243
+ }
244
+
245
+ # soften_test_row <classifier-json>: apply the head verdict to a FAILED `test` (see the header). Prints the adjusted JSON;
246
+ # sets REASON_OVERRIDE and MSG when the answer is terminal. Never produces a green context.
247
+ RECOVERY=""
248
+ soften_test_row() {
249
+ local out="$1" last_run=""
250
+ RECOVERY="after confirming in the Actions tab that no ready run is still in flight (the undo cancels it): gh pr ready --undo $PR, then gh pr ready $PR with a user token (never GITHUB_TOKEN; a manual re-run of the draft run is NOT a recovery)"
251
+ case "$V_STATE" in
252
+ pending-full)
253
+ jq -c '.failed -= ["test"] | .pending += ["test"]
254
+ | .lines |= map(if startswith("FAILED test (") then "PENDING test (draft-era-row-while-ready-run-pending)" else . end)' <<<"$out" > "$WORK/soft.json" ;;
255
+ no-run)
256
+ jq -c '.failed -= ["test"] | .pending += ["test"]
257
+ | .lines |= map(if startswith("FAILED test (") then "PENDING test (no-ready-run)" else . end)' <<<"$out" > "$WORK/soft.json" ;;
258
+ awaiting-approval)
259
+ REASON_OVERRIDE="awaiting-approval"
260
+ MSG="AWAITING-APPROVAL: the ready run is waiting for a maintainer to approve a fork workflow run; nothing starts until it is approved"
261
+ jq -c '.failed -= ["test"] | .pending += ["test"]
262
+ | .lines |= map(if startswith("FAILED test (") then "PENDING test (awaiting-approval: a maintainer must approve the fork workflow run)" else . end)' <<<"$out" > "$WORK/soft.json" ;;
263
+ stalled)
264
+ REASON_OVERRIDE="stalled"
265
+ MSG="STALLED: this PR was marked ready but no full CI run decided ($V_WHY: the stall window counts from the ready event). Recovery: $RECOVERY"
266
+ jq -c '.failed -= ["test"] | .absent += ["test"]
267
+ | .lines |= map(if startswith("FAILED test (") then "ABSENT test (stalled: no full CI run decided after the ready event)" else . end)' <<<"$out" > "$WORK/soft.json" ;;
268
+ full-decided)
269
+ last_run="$(jq -r --arg sha "$CHECK_SHA" '
270
+ [.[] | .check_runs[] | select(.head_sha == $sha and .name == "test" and .app.id == 15368)]
271
+ | if length == 0 then "" else (max_by(.id | tonumber) | (.details_url // "") | (capture("/actions/runs/(?<id>[0-9]+)(/|$)")? // {id: ""}) | .id) end' "$WORK/runs.json" 2>/dev/null)" || last_run=""
272
+ if [[ -n "$last_run" && "$V_RUN" != none && "$last_run" != "$V_RUN" ]]; then
273
+ REASON_OVERRIDE="stale-row"
274
+ MSG="STALE-ROW: the newest test row belongs to run $last_run, not the deciding run $V_RUN (a manual re-run of a pre-ready run). Recovery: $RECOVERY"
275
+ jq -c --arg l "$last_run" --arg r "$V_RUN" \
276
+ '.lines |= map(if startswith("FAILED test (") then "FAILED test (stale-row: the newest test row belongs to run \($l), not the deciding run \($r))" else . end)' <<<"$out" > "$WORK/soft.json"
277
+ else
278
+ printf '%s\n' "$out" > "$WORK/soft.json"
279
+ fi ;;
280
+ *) printf '%s\n' "$out" > "$WORK/soft.json" ;;
281
+ esac
282
+ }
283
+
284
+ check_once() {
285
+ LINES=""; MSG=""; REASON="none"; REASON_OVERRIDE=""; N_REQ=0; J_ABSENT="[]"; J_PENDING="[]"; J_FAILED="[]"
286
+ rm -f "$WORK"/*.json
287
+ local state head changed enc
288
+
289
+ gh pr view "$PR" --json state,headRefOid,baseRefName,changedFiles > "$WORK/pr.json" \
290
+ || { fail3 "gh pr view $PR failed" api-error; return; }
291
+ jq -e 'type == "object" and (.state|type) == "string" and (.headRefOid|type) == "string"
292
+ and (.baseRefName|type) == "string" and (.changedFiles|type) == "number"' "$WORK/pr.json" >/dev/null 2>&1 \
293
+ || { fail3 "unparseable gh pr view output" api-error; return; }
294
+ state=$(jq -r '.state' "$WORK/pr.json")
295
+ head=$(jq -r '.headRefOid' "$WORK/pr.json")
296
+ changed=$(jq -r '.changedFiles' "$WORK/pr.json")
297
+ BASE=$(jq -r '.baseRefName' "$WORK/pr.json")
298
+ [[ "$BASE" =~ ^[A-Za-z0-9._/-]+$ ]] || { BASE="?"; fail3 "PR base branch unreadable" api-error; return; }
299
+ if [[ "$state" != "OPEN" ]]; then MSG="NOT-OPEN: PR is not open"; REASON="not-open"; return 4; fi
300
+ if [[ "$head" != "$SHA" ]]; then MSG="STALE: the PR head moved off $SHA"; REASON="head-moved"; return 4; fi
301
+ if [[ "$BASE" != "$WANT_BASE" ]]; then
302
+ MSG="BASE-MISMATCH: the PR targets $BASE, not $WANT_BASE"; REASON="base-mismatch"; return 1
303
+ fi
304
+
305
+ gh api --paginate --slurp "repos/{owner}/{repo}/pulls/$PR/files?per_page=100" > "$WORK/files.json" \
306
+ || { fail3 "reading PR files failed" api-error; return; }
307
+ local nfiles nci
308
+ nfiles=$(jq -e 'if type == "array" and all(.[]; type == "array") then [.[][]] | length else error("shape") end' \
309
+ "$WORK/files.json" 2>/dev/null) || { fail3 "unparseable PR file list" api-error; return; }
310
+ if (( changed >= 3000 || nfiles != changed )); then
311
+ fail3 "PR file list incomplete ($nfiles listed, $changed changed; the API caps at 3000)" incomplete-files; return
312
+ fi
313
+ nci=$(jq -r '[.[][] | [.filename, (.previous_filename // "")][]
314
+ | select(test("^\\.github/(workflows|actions)/"))] | length' "$WORK/files.json") \
315
+ || { fail3 "unparseable PR file list" api-error; return; }
316
+ if (( nci > 0 )); then
317
+ MSG="UNTRUSTED-CI: this PR edits $nci workflow-definition path(s) under .github/, so its own check runs cannot certify it"
318
+ REASON="untrusted-ci"; return 1
319
+ fi
320
+
321
+ # --green-sha carryover: prove <sha> is GitHub's own contentless merge of the certified
322
+ # <green-sha> and the base branch, then grade <green-sha>'s check runs. Verified merges are
323
+ # server-side only (GitHub signs them; a server merge fails on conflict rather than
324
+ # absorbing edits), so verified+2-parents+parents[0]==green+parents[1] an ancestor-or-equal
325
+ # of <base> means the delta is exactly base content.
326
+ CHECK_SHA="$SHA"
327
+ if [[ -n "$GREEN_SHA" ]]; then
328
+ gh api "repos/{owner}/{repo}/commits/$SHA" > "$WORK/head.json" \
329
+ || { fail3 "reading head commit failed" api-error; return; }
330
+ local carry p1 cstatus
331
+ carry=$(jq -r --arg green "$GREEN_SHA" '
332
+ (.commit.verification.verified // false) as $v
333
+ | (.parents // []) as $p
334
+ | if ($p | length) != 2 then "carryover-not-merge"
335
+ elif $p[0].sha != $green then "carryover-first-parent"
336
+ elif $v != true then "carryover-unverified"
337
+ else "ok " + $p[1].sha end' "$WORK/head.json" 2>/dev/null) \
338
+ || { fail3 "unparseable head commit" api-error; return; }
339
+ # Only carryover-unverified is eligible for the local-merge arm: a head that
340
+ # is not a merge, or whose first parent is not <green-sha>, is the wrong
341
+ # SHAPE for carryover and refused the same with or without the flag.
342
+ if [[ "$carry" == ok\ * ]]; then
343
+ p1="${carry#ok }"
344
+ elif [[ "$carry" == "carryover-unverified" && "$ALLOW_LOCAL" == "1" ]]; then
345
+ p1=$(jq -r '.parents[1].sha // ""' "$WORK/head.json" 2>/dev/null) || p1=""
346
+ [[ "$p1" =~ ^[0-9a-f]{40}$ ]] || { fail3 "unparseable head commit" api-error; return; }
347
+ else
348
+ MSG="GREEN-CARRYOVER refused: head $SHA is not a verified GitHub merge of $GREEN_SHA ($carry)"
349
+ REASON="$carry"; return 1
350
+ fi
351
+ gh api "repos/{owner}/{repo}/compare/$p1...$BASE" > "$WORK/compare.json" \
352
+ || { fail3 "compare of merged parent against $BASE failed" api-error; return; }
353
+ cstatus=$(jq -r '.status // ""' "$WORK/compare.json" 2>/dev/null) \
354
+ || { fail3 "unparseable compare response" api-error; return; }
355
+ if [[ "$cstatus" != "ahead" && "$cstatus" != "identical" ]]; then
356
+ MSG="GREEN-CARRYOVER refused: merged parent $p1 is not an ancestor-or-equal of $BASE (status=$cstatus)"
357
+ REASON="carryover-not-base"; return 1
358
+ fi
359
+ if [[ "$carry" == ok\ * ]]; then
360
+ CHECK_SHA="$GREEN_SHA"
361
+ MSG="GREEN-CARRYOVER: head $SHA is a verified merge of $GREEN_SHA and $BASE@$p1; grading $GREEN_SHA's required contexts"
362
+ else
363
+ # --allow-local-merge arm (#9401): the signature is replaced by a diff
364
+ # proof -- every file the merge added over <green-sha> must be a byte-
365
+ # identical replay of the base-side delta, docs-only, and disjoint from
366
+ # the PR's own file list.
367
+ gh api "repos/{owner}/{repo}/compare/$GREEN_SHA...$SHA" > "$WORK/cmp_added.json" \
368
+ || { fail3 "compare of the merge's added delta failed" api-error; return; }
369
+ gh api "repos/{owner}/{repo}/compare/$GREEN_SHA...$p1" > "$WORK/cmp_base.json" \
370
+ || { fail3 "compare of the merged base tip failed" api-error; return; }
371
+ local lcl
372
+ # One jq, one error path: clean-replay, docs-only and PR-disjointness are
373
+ # a single predicate so a residual jq failure can never half-evaluate.
374
+ lcl=$(jq -rn --slurpfile added "$WORK/cmp_added.json" --slurpfile base "$WORK/cmp_base.json" \
375
+ --slurpfile prf "$WORK/files.json" '
376
+ # docs surface: sibling enumeration of the zero-conflict-surface
377
+ # classifier in ship/references/settle-then-admin-merge.md — different
378
+ # subject (merged delta vs the PR file list) but same surface; a move
379
+ # of the docs roots must touch both. NO apostrophes in jq comments —
380
+ # this program is single-quoted.
381
+ def docs: .filename | test("^(knowledge-base/|docs/|plugins/soleur/skills/)") or test("\\.md$");
382
+ ($added[0].files // null) as $af | ($base[0].files // null) as $bf
383
+ | ([$prf[0][][] | .filename, (.previous_filename // empty)] | unique) as $pf
384
+ | if $af == null or $bf == null then "error:unparseable"
385
+ # The compare API truncates files[] at 300 entries (docs.github.com
386
+ # REST compare commits) — at exactly 300 a complete list is
387
+ # indistinguishable from a truncated one, so fail closed.
388
+ elif ($af | length) >= 300 or ($bf | length) >= 300 then "error:incomplete"
389
+ # Bijection, not subset: a merge whose tree DROPPED base-side content
390
+ # (empty or partial $af) must not certify -- the admin merge would
391
+ # revert main. The tuple includes previous_filename so a rename source
392
+ # is bound, and every patch must be a real string.
393
+ elif any($af[] + $bf[]; (.patch | type) != "string")
394
+ or ([$af[] | [.filename, .status, (.previous_filename // ""), .patch]] | sort)
395
+ != ([$bf[] | [.filename, .status, (.previous_filename // ""), .patch]] | sort)
396
+ then "not-clean"
397
+ elif any($af[]; docs | not) then "nondocs"
398
+ elif any($af[]; [.filename, (.previous_filename // empty)]
399
+ | .[] | . as $f | ($pf | index($f)) != null) then "overlap"
400
+ else "ok" end' 2>/dev/null) || { fail3 "unparseable compare response" api-error; return; }
401
+ if [[ "$lcl" == error:* ]]; then
402
+ fail3 "merge-delta compare is unparseable or truncated (${lcl#error:})" incomplete-files; return
403
+ fi
404
+ if [[ "$lcl" != "ok" ]]; then
405
+ MSG="GREEN-CARRYOVER refused: the local merge's added delta is not a clean docs-only replay of the base side, disjoint from the PR's files ($lcl)"
406
+ REASON="carryover-local-$lcl"; return 1
407
+ fi
408
+ CHECK_SHA="$GREEN_SHA"
409
+ REASON="carryover-local-docs"
410
+ MSG="GREEN-CARRYOVER: head $SHA is a clean local merge of $GREEN_SHA and $BASE@$p1 (docs-only delta, disjoint from the PR's files; --allow-local-merge); grading $GREEN_SHA's required contexts"
411
+ fi
412
+ fi
413
+
414
+ enc=$(jq -rn --arg b "$BASE" '$b|@uri')
415
+ gh api --paginate --slurp "repos/{owner}/{repo}/rules/branches/$enc" > "$WORK/rules.json" \
416
+ || { fail3 "reading the ruleset required set failed" api-error; return; }
417
+ gh api --paginate --slurp "repos/{owner}/{repo}/commits/$CHECK_SHA/check-runs?per_page=100&filter=all" > "$WORK/runs.json" \
418
+ || { fail3 "reading check runs failed" api-error; return; }
419
+ jq -e 'type == "array" and all(.[]; type == "array")' "$WORK/rules.json" >/dev/null 2>&1 \
420
+ || { fail3 "unparseable ruleset response" api-error; return; }
421
+ jq -e 'type == "array" and all(.[]; type == "object" and (.check_runs|type) == "array")' "$WORK/runs.json" >/dev/null 2>&1 \
422
+ || { fail3 "unparseable check-runs response" api-error; return; }
423
+
424
+ local out
425
+ out=$(jq -c --slurpfile rules "$WORK/rules.json" --arg sha "$CHECK_SHA" '
426
+ ["deletion","non_fast_forward","creation","update","required_linear_history",
427
+ "required_signatures","required_status_checks","merge_queue"] as $known
428
+ | ($rules[0] | add // []) as $all
429
+ | ([$all[] | .type | select(. as $t | $known | index($t) | not)] | unique) as $unknown
430
+ | ([$all[] | select(.type == "required_status_checks") | .parameters.required_status_checks[]?]
431
+ | reduce .[] as $e ([]; if any(.[]; .context == $e.context and .integration_id == $e.integration_id)
432
+ then . else . + [$e] end)) as $req
433
+ | [.[] | .check_runs[] | select(.head_sha == $sha)] as $runs
434
+ | if ($unknown | length) > 0 then {error: ("unsupported ruleset rule type(s) " + ($unknown | tojson)), reason: "unsupported-rule"}
435
+ elif ($req | length) == 0 then {error: "required set unreadable or empty", reason: "empty-required"}
436
+ elif any($req[]; .integration_id == null) then
437
+ {error: ("unpinned required context(s) " + ([$req[] | select(.integration_id == null) | .context] | tojson) + " unsupported"), reason: "unpinned"}
438
+ else
439
+ # Latest run per (name, app, suite): the state of every job in a check suite.
440
+ ( $runs | group_by([.name, .app.id, .check_suite.id]) | map(max_by(.id | tonumber)) ) as $latest
441
+ | [ $req[] as $q
442
+ | [ $runs[] | select(.name == $q.context and .app.id == $q.integration_id) ] as $c
443
+ | if ($c | length) == 0 then {k: "ABSENT", n: $q.context}
444
+ else ($c | max_by(.id | tonumber)) as $l
445
+ | if $l.status == "completed" then
446
+ if $l.conclusion == "success" then {k: "GREEN", n: $q.context}
447
+ elif ($l.conclusion == "skipped" or $l.conclusion == "neutral") then
448
+ ( [ $latest[] | select(.check_suite.id != null and .check_suite.id == $l.check_suite.id
449
+ and .id != $l.id) ] ) as $sib
450
+ | if $l.check_suite.id == null then {k: "FAILED", n: $q.context, d: ($l.conclusion + " without a check suite")}
451
+ elif any($sib[]; .status == "completed"
452
+ and ((.conclusion // "") as $x | ["success","skipped","neutral"] | index($x) | not))
453
+ then {k: "FAILED", n: $q.context, d: ($l.conclusion + "-after-failure")}
454
+ elif any($sib[]; .status != "completed")
455
+ then {k: "PENDING", n: $q.context, d: ($l.conclusion + "-while-suite-running")}
456
+ else {k: "GREEN", n: $q.context} end
457
+ else {k: "FAILED", n: $q.context, d: ($l.conclusion // "null")} end
458
+ elif (["queued","in_progress","waiting","requested","pending"] | index($l.status)) then
459
+ {k: "PENDING", n: $q.context, d: $l.status}
460
+ else {k: "FAILED", n: $q.context, d: ("unknown status " + ($l.status | tostring))} end
461
+ end ] as $v
462
+ | { req: ($req | length),
463
+ green: [$v[] | select(.k == "GREEN") | .n] | length,
464
+ absent: [$v[] | select(.k == "ABSENT") | .n],
465
+ pending: [$v[] | select(.k == "PENDING") | .n],
466
+ failed: [$v[] | select(.k == "FAILED") | .n],
467
+ lines: [$v[] | select(.k != "GREEN")
468
+ | if .k == "ABSENT" then "ABSENT \(.n)"
469
+ elif .k == "PENDING" then "PENDING \(.n) (\(.d))"
470
+ else "FAILED \(.n) (\(.d))" end] }
471
+ end' "$WORK/runs.json") || { fail3 "classifying check runs failed" api-error; return; }
472
+
473
+ # Positive shape check on the classifier's own output before reading any field of it.
474
+ jq -e 'type == "object" and ((has("error") and (.error|type) == "string")
475
+ or ((.req|type) == "number" and (.green|type) == "number" and (.lines|type) == "array"))' \
476
+ <<<"$out" >/dev/null 2>&1 || { fail3 "classifier produced no verdict" api-error; return; }
477
+ if jq -e 'has("error")' <<<"$out" >/dev/null; then
478
+ MSG="ERROR: $(jq -r '.error' <<<"$out")"; REASON=$(jq -r '.reason' <<<"$out"); return 3
479
+ fi
480
+ # ADR-276 S3: a FAILED `test` at the graded head may be the draft run's row. Ask the resolver, only then.
481
+ if [[ "$CHECK_SHA" == "$SHA" ]] && jq -e '.failed | index("test") != null' <<<"$out" >/dev/null 2>&1 && resolve_head_verdict; then
482
+ soften_test_row "$out"; out="$(cat "$WORK/soft.json" 2>/dev/null)"
483
+ jq -e 'type == "object" and (.lines | type) == "array"' <<<"$out" >/dev/null 2>&1 \
484
+ || { fail3 "adjusting the test verdict failed" api-error; return; }
485
+ fi
486
+ N_REQ=$(jq -r '.req' <<<"$out")
487
+ J_ABSENT=$(jq -c '.absent' <<<"$out")
488
+ J_PENDING=$(jq -c '.pending' <<<"$out")
489
+ J_FAILED=$(jq -c '.failed' <<<"$out")
490
+ LINES=$(jq -r '.lines[]' <<<"$out")
491
+ if jq -e '.req > 0 and .green == .req and (.lines | length) == 0' <<<"$out" >/dev/null; then
492
+ # A carryover arm that already named itself (carryover-local-docs) keeps its
493
+ # reason; a plain or verified-carryover ready reads all-green as before.
494
+ [[ "$REASON" == "none" ]] && REASON="all-green"
495
+ return 0
496
+ fi
497
+ if [[ -z "$LINES" ]]; then fail3 "classifier counted $(jq -r '.green' <<<"$out") of $N_REQ green but named no culprit" api-error; return; fi
498
+ REASON="${REASON_OVERRIDE:-not-green}"; return 1
499
+ }
500
+
501
+ finish() { # <rc> <verdict>
502
+ if [[ -n "$MSG" ]]; then
503
+ if [[ "$1" == 3 ]]; then printf '%s\n' "$MSG" >&2; else printf '%s\n' "$MSG"; fi
504
+ fi
505
+ [[ -n "$LINES" ]] && printf '%s\n' "$LINES"
506
+ marker "$2"
507
+ exit "$1"
508
+ }
509
+
510
+ # ── single check ─────────────────────────────────────────────────────────────────────────────
511
+ if (( WAIT == 0 )); then
512
+ check_once; rc=$?
513
+ case "$rc" in
514
+ 0) finish 0 ready ;;
515
+ 1) finish 1 not-ready ;;
516
+ 4) finish 4 stale ;;
517
+ *) finish 3 error ;;
518
+ esac
519
+ fi
520
+
521
+ # ── --wait ───────────────────────────────────────────────────────────────────────────────────
522
+ MAX_POLLS=$(( (TIMEOUT + 59) / 60 ))
523
+ prev=""
524
+ for (( poll = 1; poll <= MAX_POLLS; poll++ )); do
525
+ check_once; rc=$?
526
+ case "$rc" in
527
+ 0) finish 0 ready ;;
528
+ 4) finish 4 stale ;;
529
+ 1) ;;
530
+ *) finish 3 error ;;
531
+ esac
532
+ # rc 1: a FAILED context, UNTRUSTED-CI or a base mismatch is terminal -- no point waiting.
533
+ if [[ "$J_FAILED" != "[]" || "$REASON" != "not-green" ]]; then finish 1 not-ready; fi
534
+ if [[ "$LINES" != "$prev" ]]; then
535
+ printf 'WAIT poll=%d/%d absent=%s pending=%s\n' "$poll" "$MAX_POLLS" "$J_ABSENT" "$J_PENDING"
536
+ prev="$LINES"
537
+ elif (( poll % 5 == 0 )); then
538
+ printf 'HEARTBEAT poll=%d/%d still waiting absent=%s pending=%s\n' \
539
+ "$poll" "$MAX_POLLS" "$J_ABSENT" "$J_PENDING"
540
+ fi
541
+ (( poll < MAX_POLLS )) && sleep "$POLL_SECONDS"
542
+ done
543
+ MSG="TIMEOUT: $MAX_POLLS polls spent with required contexts still absent or pending"
544
+ REASON=timeout
545
+ finish 1 timeout