@1aboveio/skills 0.10.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 (397) hide show
  1. package/LICENSE +3 -0
  2. package/README.md +90 -0
  3. package/bin/1aboveio-skills.mjs +18 -0
  4. package/package.json +28 -0
  5. package/runtime/skills/distribution/generated/recipes.json +1189 -0
  6. package/runtime/skills/distribution/scripts/bundles.mjs +280 -0
  7. package/runtime/skills/engineering/engineering-runtime/scripts/main-module.mjs +80 -0
  8. package/skills/backend/airflow-dag-develop/LICENSE +3 -0
  9. package/skills/backend/airflow-dag-develop/SKILL.md +111 -0
  10. package/skills/backend/app-debug/LICENSE +3 -0
  11. package/skills/backend/app-debug/SKILL.md +109 -0
  12. package/skills/backend/app-debug/references/common-errors.md +128 -0
  13. package/skills/backend/python-backend/LICENSE +3 -0
  14. package/skills/backend/python-backend/SKILL.md +326 -0
  15. package/skills/cicd-pipeline/cloud-build/LICENSE +3 -0
  16. package/skills/cicd-pipeline/cloud-build/SKILL.md +707 -0
  17. package/skills/cicd-pipeline/cloud-debug/LICENSE +3 -0
  18. package/skills/cicd-pipeline/cloud-debug/SKILL.md +316 -0
  19. package/skills/cicd-pipeline/cloud-debug/references/build-failures.md +238 -0
  20. package/skills/cicd-pipeline/cloud-debug/references/deploy-failures.md +376 -0
  21. package/skills/cicd-pipeline/cloud-debug/references/pipeline-failures.md +378 -0
  22. package/skills/cicd-pipeline/cloud-deploy/LICENSE +3 -0
  23. package/skills/cicd-pipeline/cloud-deploy/SKILL.md +229 -0
  24. package/skills/cicd-pipeline/cloud-deploy/references/config-templates.md +257 -0
  25. package/skills/cicd-pipeline/docker/LICENSE +3 -0
  26. package/skills/cicd-pipeline/docker/SKILL.md +126 -0
  27. package/skills/cicd-pipeline/google-cloud/LICENSE +3 -0
  28. package/skills/cicd-pipeline/google-cloud/SKILL.md +118 -0
  29. package/skills/cicd-pipeline/google-cloud/references/gcs.md +469 -0
  30. package/skills/cicd-pipeline/google-cloud/references/iam.md +451 -0
  31. package/skills/cicd-pipeline/google-cloud/references/project.md +349 -0
  32. package/skills/cicd-pipeline/google-cloud/references/secrets.md +336 -0
  33. package/skills/cicd-pipeline/google-cloud/references/vpc.md +312 -0
  34. package/skills/cicd-pipeline/google-cloud/scripts/create-sa.sh +36 -0
  35. package/skills/cicd-pipeline/google-cloud/scripts/gcp-config.sh +31 -0
  36. package/skills/cicd-pipeline/google-cloud/scripts/grant-iap.sh +41 -0
  37. package/skills/cicd-pipeline/google-cloud/scripts/setup-secrets.sh +48 -0
  38. package/skills/cicd-pipeline/mergify/LICENSE +3 -0
  39. package/skills/cicd-pipeline/mergify/SKILL.md +138 -0
  40. package/skills/cicd-pipeline/mergify/assets/templates/mergify.yml +237 -0
  41. package/skills/cicd-pipeline/mergify/assets/templates/ruleset.json +46 -0
  42. package/skills/cicd-pipeline/mergify/references/branch-protection.md +277 -0
  43. package/skills/cicd-pipeline/mergify/references/configuration.md +183 -0
  44. package/skills/cicd-pipeline/mergify/references/diagnosis.md +73 -0
  45. package/skills/cicd-pipeline/mergify/references/traps.md +78 -0
  46. package/skills/cicd-pipeline/mergify/references/watch-contract.md +218 -0
  47. package/skills/cicd-pipeline/mergify/scripts/audit-core.mjs +131 -0
  48. package/skills/cicd-pipeline/mergify/scripts/audit.mjs +4 -0
  49. package/skills/cicd-pipeline/mergify/scripts/watch-pr-delivery-core.mjs +663 -0
  50. package/skills/cicd-pipeline/mergify/scripts/watch-pr-delivery.mjs +4 -0
  51. package/skills/cicd-pipeline/podman/LICENSE +3 -0
  52. package/skills/cicd-pipeline/podman/SKILL.md +70 -0
  53. package/skills/cicd-pipeline/podman/agents/openai.yaml +4 -0
  54. package/skills/cicd-pipeline/podman/assets/templates/podman-compose-socket-directory.yml +6 -0
  55. package/skills/cicd-pipeline/podman/assets/templates/podman-service-override.conf +3 -0
  56. package/skills/cicd-pipeline/podman/references/compose-compatibility.md +70 -0
  57. package/skills/cicd-pipeline/podman/references/networking-and-ports.md +74 -0
  58. package/skills/cicd-pipeline/podman/references/rootless-services-and-sockets.md +156 -0
  59. package/skills/cicd-pipeline/podman/references/troubleshooting.md +98 -0
  60. package/skills/engineering/e2e-test/LICENSE +3 -0
  61. package/skills/engineering/e2e-test/SKILL.md +156 -0
  62. package/skills/engineering/e2e-test/assets/ci-gates.cloudbuild.yaml +272 -0
  63. package/skills/engineering/e2e-test/assets/ci-gates.github.yml +451 -0
  64. package/skills/engineering/e2e-test/assets/e2e-workflow.yml +282 -0
  65. package/skills/engineering/e2e-test/references/authoring/auth-flows.md +159 -0
  66. package/skills/engineering/e2e-test/references/authoring/playwright-config.md +71 -0
  67. package/skills/engineering/e2e-test/references/authoring/playwright-patterns.md +219 -0
  68. package/skills/engineering/e2e-test/references/authoring/test-skipping.md +44 -0
  69. package/skills/engineering/e2e-test/references/ci-integration.md +121 -0
  70. package/skills/engineering/e2e-test/references/ci-playwright-container.md +280 -0
  71. package/skills/engineering/e2e-test/references/debugging.md +36 -0
  72. package/skills/engineering/e2e-test/references/presentation-sweep.md +131 -0
  73. package/skills/engineering/e2e-test/references/reviewing.md +39 -0
  74. package/skills/engineering/e2e-test/references/route-discovery.md +50 -0
  75. package/skills/engineering/e2e-test/references/route-manifest.md +44 -0
  76. package/skills/engineering/e2e-test/scripts/detect-routes-fastapi.py +290 -0
  77. package/skills/engineering/e2e-test/scripts/detect-routes-nextjs.mjs +200 -0
  78. package/skills/engineering/e2e-test/scripts/post-visual-evidence.mjs +158 -0
  79. package/skills/engineering/e2e-test/scripts/presentation-checks.mjs +171 -0
  80. package/skills/engineering/e2e-test/scripts/presentation-perceivability.mjs +179 -0
  81. package/skills/engineering/e2e-test/scripts/presentation-reachability.mjs +154 -0
  82. package/skills/engineering/e2e-test/scripts/presentation-render-health.mjs +141 -0
  83. package/skills/engineering/e2e-test/scripts/presentation-sweep.mjs +148 -0
  84. package/skills/engineering/e2e-test/scripts/presentation-temporal.mjs +127 -0
  85. package/skills/engineering/e2e-test/scripts/presentation-visual.mjs +84 -0
  86. package/skills/engineering/e2e-test/scripts/project-route-manifest.mjs +75 -0
  87. package/skills/engineering/e2e-test/scripts/validate-manifest.mjs +106 -0
  88. package/skills/engineering/engineering-runtime/LICENSE +3 -0
  89. package/skills/engineering/engineering-runtime/SKILL.md +10 -0
  90. package/skills/engineering/engineering-runtime/agents/openai.yaml +6 -0
  91. package/skills/engineering/engineering-runtime/coherence/workflow.json +553 -0
  92. package/skills/engineering/engineering-runtime/scripts/exact-head-artifact.mjs +131 -0
  93. package/skills/engineering/engineering-runtime/scripts/head-check-set.mjs +398 -0
  94. package/skills/engineering/engineering-runtime/scripts/main-module.mjs +80 -0
  95. package/skills/engineering/engineering-runtime/scripts/mergify-yaml.mjs +11 -0
  96. package/skills/engineering/engineering-runtime/scripts/package-lock.json +43 -0
  97. package/skills/engineering/engineering-runtime/scripts/package.json +10 -0
  98. package/skills/engineering/engineering-runtime/scripts/required-check-plan.mjs +223 -0
  99. package/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +576 -0
  100. package/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +166 -0
  101. package/skills/engineering/ensure-coverage/LICENSE +3 -0
  102. package/skills/engineering/ensure-coverage/SKILL.md +136 -0
  103. package/skills/engineering/ensure-coverage/evals/evals.json +125 -0
  104. package/skills/engineering/ensure-coverage/references/breadth/coverage-ledger.md +91 -0
  105. package/skills/engineering/ensure-coverage/references/breadth/inventory-contract.md +83 -0
  106. package/skills/engineering/ensure-coverage/references/breadth/surface-baseline.md +44 -0
  107. package/skills/engineering/ensure-coverage/references/breadth/surface-discovery.md +16 -0
  108. package/skills/engineering/ensure-coverage/references/depth/characterization.md +68 -0
  109. package/skills/engineering/ensure-coverage/references/depth/coverage.config.example.json +25 -0
  110. package/skills/engineering/ensure-coverage/references/depth/grading.md +35 -0
  111. package/skills/engineering/ensure-coverage/references/depth/mock-policy.md +87 -0
  112. package/skills/engineering/ensure-coverage/references/depth/test-smells.md +23 -0
  113. package/skills/engineering/ensure-coverage/references/enforcement/ci-contract.md +164 -0
  114. package/skills/engineering/ensure-coverage/references/enforcement/hooks.md +85 -0
  115. package/skills/engineering/ensure-coverage/references/examples/coverage-ledger.md +109 -0
  116. package/skills/engineering/ensure-coverage/references/examples/refund-flow.md +33 -0
  117. package/skills/engineering/ensure-coverage/references/presentation/axis.md +78 -0
  118. package/skills/engineering/ensure-coverage/references/presentation/runner-contract.md +74 -0
  119. package/skills/engineering/ensure-coverage/references/process/audit-mode.md +33 -0
  120. package/skills/engineering/ensure-coverage/references/process/output-template.md +139 -0
  121. package/skills/engineering/ensure-coverage/references/process/review-contract-template.md +119 -0
  122. package/skills/engineering/ensure-coverage/references/process/scope-class.md +178 -0
  123. package/skills/engineering/ensure-coverage/references/process/test-strategy.md +55 -0
  124. package/skills/engineering/ensure-coverage/schemas/coverage-config.schema.json +45 -0
  125. package/skills/engineering/ensure-coverage/schemas/coverage-file.schema.json +93 -0
  126. package/skills/engineering/ensure-coverage/scripts/adapters/nextjs-inventory.mjs +178 -0
  127. package/skills/engineering/ensure-coverage/scripts/check-quarantine-expiry.mjs +101 -0
  128. package/skills/engineering/ensure-coverage/scripts/ci-audit.mjs +358 -0
  129. package/skills/engineering/ensure-coverage/scripts/coverage-checklist.mjs +494 -0
  130. package/skills/engineering/ensure-coverage/scripts/coverage-ledger.mjs +663 -0
  131. package/skills/engineering/ensure-coverage/scripts/design-parity.mjs +591 -0
  132. package/skills/engineering/ensure-coverage/scripts/evidence-block.mjs +367 -0
  133. package/skills/engineering/ensure-coverage/scripts/lint-tests.mjs +269 -0
  134. package/skills/engineering/ensure-coverage/scripts/mock-policy-config.mjs +176 -0
  135. package/skills/engineering/ensure-coverage/scripts/package-lock.json +76 -0
  136. package/skills/engineering/ensure-coverage/scripts/package.json +19 -0
  137. package/skills/engineering/ensure-coverage/scripts/scope-class.mjs +554 -0
  138. package/skills/engineering/harness-runtime/LICENSE +3 -0
  139. package/skills/engineering/harness-runtime/SKILL.md +18 -0
  140. package/skills/engineering/harness-runtime/agents/openai.yaml +6 -0
  141. package/skills/engineering/harness-runtime/bin/discover-models.mjs +4 -0
  142. package/skills/engineering/harness-runtime/bin/model-catalog.mjs +4 -0
  143. package/skills/engineering/harness-runtime/contracts.md +15 -0
  144. package/skills/engineering/harness-runtime/discover-models.mjs +392 -0
  145. package/skills/engineering/harness-runtime/fixtures/native-question-schemas.json +33 -0
  146. package/skills/engineering/harness-runtime/fixtures/question-responses.json +54 -0
  147. package/skills/engineering/harness-runtime/index.mjs +767 -0
  148. package/skills/engineering/harness-runtime/model-catalog.mjs +787 -0
  149. package/skills/engineering/harness-runtime/native-question-contracts.md +37 -0
  150. package/skills/engineering/harness-runtime/references/model-catalog-seed.json +159 -0
  151. package/skills/engineering/harness-runtime/references/model-catalog.md +57 -0
  152. package/skills/engineering/implement-and-pr/LICENSE +3 -0
  153. package/skills/engineering/implement-and-pr/SKILL.md +176 -0
  154. package/skills/engineering/implement-and-pr/references/ci-iteration.md +10 -0
  155. package/skills/engineering/implement-and-pr/references/closeout.md +27 -0
  156. package/skills/engineering/implement-and-pr/references/contract-complete-fix-rounds.md +34 -0
  157. package/skills/engineering/implement-and-pr/references/evidence-rules.md +39 -0
  158. package/skills/engineering/implement-and-pr/references/incremental-plan.md +16 -0
  159. package/skills/engineering/implement-and-pr/references/self-review.md +23 -0
  160. package/skills/engineering/implement-and-pr/references/tdd-mode.md +18 -0
  161. package/skills/engineering/resolve-issues/LICENSE +3 -0
  162. package/skills/engineering/resolve-issues/SKILL.md +167 -0
  163. package/skills/engineering/resolve-issues/generated/workflow-repair-policy.json +448 -0
  164. package/skills/engineering/resolve-issues/references/breaker.md +82 -0
  165. package/skills/engineering/resolve-issues/references/deliverables.md +27 -0
  166. package/skills/engineering/resolve-issues/references/delivery.md +108 -0
  167. package/skills/engineering/resolve-issues/references/evidence-lane.md +21 -0
  168. package/skills/engineering/resolve-issues/references/exact-head-ci.md +287 -0
  169. package/skills/engineering/resolve-issues/references/fan-out.md +33 -0
  170. package/skills/engineering/resolve-issues/references/finalization.md +68 -0
  171. package/skills/engineering/resolve-issues/references/guarantees.md +10 -0
  172. package/skills/engineering/resolve-issues/references/high-risk.md +29 -0
  173. package/skills/engineering/resolve-issues/references/incidents/848/README.md +156 -0
  174. package/skills/engineering/resolve-issues/references/intake.md +86 -0
  175. package/skills/engineering/resolve-issues/references/integration-gate.md +53 -0
  176. package/skills/engineering/resolve-issues/references/interference.md +87 -0
  177. package/skills/engineering/resolve-issues/references/loop.md +134 -0
  178. package/skills/engineering/resolve-issues/references/model-catalog.md +9 -0
  179. package/skills/engineering/resolve-issues/references/postmortem.md +27 -0
  180. package/skills/engineering/resolve-issues/references/pre-flight-model-slots.md +41 -0
  181. package/skills/engineering/resolve-issues/references/pre-flight-recording-and-checkout.md +48 -0
  182. package/skills/engineering/resolve-issues/references/pre-flight.md +41 -0
  183. package/skills/engineering/resolve-issues/references/regression-checklist.md +26 -0
  184. package/skills/engineering/resolve-issues/references/run-state.md +288 -0
  185. package/skills/engineering/resolve-issues/references/sandboxed-testing.md +48 -0
  186. package/skills/engineering/resolve-issues/references/spawn-contract.md +96 -0
  187. package/skills/engineering/resolve-issues/references/terminal-evidence-journal.md +40 -0
  188. package/skills/engineering/resolve-issues/references/why.md +653 -0
  189. package/skills/engineering/resolve-issues/schemas/fix-round.schema.json +49 -0
  190. package/skills/engineering/resolve-issues/scripts/combine-and-verify.mjs +721 -0
  191. package/skills/engineering/resolve-issues/scripts/component-candidate.mjs +962 -0
  192. package/skills/engineering/resolve-issues/scripts/contract-revision.mjs +220 -0
  193. package/skills/engineering/resolve-issues/scripts/detect-delivery-mode.mjs +420 -0
  194. package/skills/engineering/resolve-issues/scripts/detect-target-branch.mjs +256 -0
  195. package/skills/engineering/resolve-issues/scripts/detect-workspace-mode.mjs +168 -0
  196. package/skills/engineering/resolve-issues/scripts/discover-models.mjs +9 -0
  197. package/skills/engineering/resolve-issues/scripts/doctrine.mjs +62 -0
  198. package/skills/engineering/resolve-issues/scripts/evidence-lifecycle-contract.mjs +191 -0
  199. package/skills/engineering/resolve-issues/scripts/exact-head-ci.mjs +413 -0
  200. package/skills/engineering/resolve-issues/scripts/exact-head-github-provider.mjs +332 -0
  201. package/skills/engineering/resolve-issues/scripts/finalize.mjs +488 -0
  202. package/skills/engineering/resolve-issues/scripts/fix-rounds.mjs +3307 -0
  203. package/skills/engineering/resolve-issues/scripts/fixtures/evidence-lifecycle-circular-1001.json +16 -0
  204. package/skills/engineering/resolve-issues/scripts/fixtures/evidence-lifecycle-valid-sequencing.json +51 -0
  205. package/skills/engineering/resolve-issues/scripts/fixtures/fmm-express-830-component-candidate.json +17 -0
  206. package/skills/engineering/resolve-issues/scripts/fixtures/head-check-set-1081.json +166 -0
  207. package/skills/engineering/resolve-issues/scripts/gate-value-series.mjs +92 -0
  208. package/skills/engineering/resolve-issues/scripts/guide-index.mjs +73 -0
  209. package/skills/engineering/resolve-issues/scripts/head-check-set.mjs +159 -0
  210. package/skills/engineering/resolve-issues/scripts/interference.mjs +427 -0
  211. package/skills/engineering/resolve-issues/scripts/model-catalog.mjs +9 -0
  212. package/skills/engineering/resolve-issues/scripts/next-operations.mjs +419 -0
  213. package/skills/engineering/resolve-issues/scripts/postmortem.mjs +909 -0
  214. package/skills/engineering/resolve-issues/scripts/preflight-questions.mjs +322 -0
  215. package/skills/engineering/resolve-issues/scripts/reconcile-contained-unit-prs.mjs +415 -0
  216. package/skills/engineering/resolve-issues/scripts/release-state-contract.mjs +697 -0
  217. package/skills/engineering/resolve-issues/scripts/report.mjs +494 -0
  218. package/skills/engineering/resolve-issues/scripts/required-check-plan.mjs +172 -0
  219. package/skills/engineering/resolve-issues/scripts/round-metadata.mjs +79 -0
  220. package/skills/engineering/resolve-issues/scripts/run-state-review6-cases.mjs +334 -0
  221. package/skills/engineering/resolve-issues/scripts/run-state.mjs +4784 -0
  222. package/skills/engineering/resolve-issues/scripts/sandbox-selftest.mjs +395 -0
  223. package/skills/engineering/resolve-issues/scripts/spawn-contract.mjs +290 -0
  224. package/skills/engineering/resolve-issues/scripts/terminal-dispositions.mjs +170 -0
  225. package/skills/engineering/resolve-issues/scripts/terminal-evidence-journal.mjs +293 -0
  226. package/skills/engineering/resolve-issues/scripts/unit-kind.mjs +197 -0
  227. package/skills/engineering/resolve-issues/scripts/unit-lifecycle.mjs +127 -0
  228. package/skills/engineering/resolve-issues/scripts/watch-delivery.mjs +893 -0
  229. package/skills/engineering/resolve-issues/scripts/workspaces.mjs +829 -0
  230. package/skills/engineering/resolve-issues/workflows/independent-review.workflow.js +290 -0
  231. package/skills/engineering/resolve-issues/workflows/prior-art-scan.workflow.js +80 -0
  232. package/skills/engineering/resolve-issues/workflows/workflow-smoke.mjs +102 -0
  233. package/skills/engineering/resolve-release/LICENSE +3 -0
  234. package/skills/engineering/resolve-release/SKILL.md +112 -0
  235. package/skills/engineering/resolve-release/references/assembly.md +137 -0
  236. package/skills/engineering/resolve-release/references/auto-when-green.md +56 -0
  237. package/skills/engineering/resolve-release/references/candidate.md +167 -0
  238. package/skills/engineering/resolve-release/references/exposure.md +178 -0
  239. package/skills/engineering/resolve-release/references/handoff.md +24 -0
  240. package/skills/engineering/resolve-release/references/postmortem.md +230 -0
  241. package/skills/engineering/resolve-release/references/preflight.md +207 -0
  242. package/skills/engineering/resolve-release/references/principles.md +94 -0
  243. package/skills/engineering/resolve-release/references/regression-checklist.md +36 -0
  244. package/skills/engineering/resolve-release/references/related-skills.md +12 -0
  245. package/skills/engineering/resolve-release/references/routing.md +149 -0
  246. package/skills/engineering/resolve-release/references/verified-sha-github-flow.md +285 -0
  247. package/skills/engineering/resolve-release/references/versioning.md +202 -0
  248. package/skills/engineering/resolve-release/references/why.md +53 -0
  249. package/skills/engineering/resolve-release/scripts/adapter-completion-artifact.mjs +389 -0
  250. package/skills/engineering/resolve-release/scripts/build-changes.mjs +209 -0
  251. package/skills/engineering/resolve-release/scripts/candidate-hygiene.mjs +407 -0
  252. package/skills/engineering/resolve-release/scripts/candidate-identity.mjs +904 -0
  253. package/skills/engineering/resolve-release/scripts/candidate-traffic.mjs +81 -0
  254. package/skills/engineering/resolve-release/scripts/checked-adapter-loader.mjs +612 -0
  255. package/skills/engineering/resolve-release/scripts/close-attempt.mjs +135 -0
  256. package/skills/engineering/resolve-release/scripts/closeout-release.mjs +161 -0
  257. package/skills/engineering/resolve-release/scripts/doctrine.mjs +106 -0
  258. package/skills/engineering/resolve-release/scripts/durable-processing.mjs +522 -0
  259. package/skills/engineering/resolve-release/scripts/ensure-target-green.mjs +659 -0
  260. package/skills/engineering/resolve-release/scripts/evidence-bundle.mjs +1014 -0
  261. package/skills/engineering/resolve-release/scripts/finalize-release.mjs +526 -0
  262. package/skills/engineering/resolve-release/scripts/fixtures/durable-processing-adapter.mjs +169 -0
  263. package/skills/engineering/resolve-release/scripts/green-gate.mjs +599 -0
  264. package/skills/engineering/resolve-release/scripts/isolated-adapter-evaluator.mjs +752 -0
  265. package/skills/engineering/resolve-release/scripts/metadata-pr-status.mjs +56 -0
  266. package/skills/engineering/resolve-release/scripts/metadata-sync.mjs +1538 -0
  267. package/skills/engineering/resolve-release/scripts/postmortem.mjs +381 -0
  268. package/skills/engineering/resolve-release/scripts/preflight-probes.mjs +498 -0
  269. package/skills/engineering/resolve-release/scripts/production-endpoints.mjs +326 -0
  270. package/skills/engineering/resolve-release/scripts/rc-circuit-breaker.mjs +272 -0
  271. package/skills/engineering/resolve-release/scripts/report.mjs +417 -0
  272. package/skills/engineering/resolve-release/scripts/reprobe-credentials.mjs +114 -0
  273. package/skills/engineering/resolve-release/scripts/revalidate-candidate.mjs +238 -0
  274. package/skills/engineering/resolve-release/scripts/review-packet.mjs +503 -0
  275. package/skills/engineering/resolve-release/scripts/rollback-floor.mjs +263 -0
  276. package/skills/engineering/resolve-release/scripts/version-assert.mjs +339 -0
  277. package/skills/engineering/resolve-release/scripts/version-postmortem.mjs +485 -0
  278. package/skills/engineering/resolve-release/scripts/version.mjs +1199 -0
  279. package/skills/engineering/resolve-release/scripts/watch-candidate-delivery.mjs +449 -0
  280. package/skills/engineering/resolve-release/vendor/ACORN-LICENSE +21 -0
  281. package/skills/engineering/resolve-release/vendor/README.md +60 -0
  282. package/skills/engineering/resolve-release/vendor/acorn.mjs +6233 -0
  283. package/skills/engineering/resolve-release/vendor/quickjs/core/LICENSE +21 -0
  284. package/skills/engineering/resolve-release/vendor/quickjs/core/README.md +341 -0
  285. package/skills/engineering/resolve-release/vendor/quickjs/core/README.template.md +70 -0
  286. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-TAV5CUKK.mjs +2 -0
  287. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-TAV5CUKK.mjs.map +1 -0
  288. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-V2S4ZYJR.mjs +7 -0
  289. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/chunk-V2S4ZYJR.mjs.map +1 -0
  290. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.d.mts +2033 -0
  291. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.d.ts +2033 -0
  292. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.js +7 -0
  293. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.js.map +1 -0
  294. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.mjs +2 -0
  295. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/index.mjs.map +1 -0
  296. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-ES6BEMUI.mjs +2 -0
  297. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-ES6BEMUI.mjs.map +1 -0
  298. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-asyncify-2EFITU5U.mjs +2 -0
  299. package/skills/engineering/resolve-release/vendor/quickjs/core/dist/module-asyncify-2EFITU5U.mjs.map +1 -0
  300. package/skills/engineering/resolve-release/vendor/quickjs/core/package.json +49 -0
  301. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/LICENSE +21 -0
  302. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/README.md +5 -0
  303. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.d.mts +549 -0
  304. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.d.ts +549 -0
  305. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.js +2 -0
  306. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.js.map +1 -0
  307. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.mjs +2 -0
  308. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/dist/index.mjs.map +1 -0
  309. package/skills/engineering/resolve-release/vendor/quickjs/ffi-types/package.json +36 -0
  310. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/LICENSE +47 -0
  311. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/README.md +82 -0
  312. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.browser.d.ts +11 -0
  313. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.browser.mjs +22 -0
  314. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cjs +21 -0
  315. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cloudflare.cjs +21 -0
  316. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.cloudflare.d.ts +11 -0
  317. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.d.ts +11 -0
  318. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.mjs +25 -0
  319. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/emscripten-module.wasm +0 -0
  320. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.d.mts +85 -0
  321. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.d.ts +85 -0
  322. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.js +2 -0
  323. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.js.map +1 -0
  324. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.mjs +2 -0
  325. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/ffi.mjs.map +1 -0
  326. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.d.mts +20 -0
  327. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.d.ts +20 -0
  328. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.js +2 -0
  329. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.js.map +1 -0
  330. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.mjs +2 -0
  331. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/dist/index.mjs.map +1 -0
  332. package/skills/engineering/resolve-release/vendor/quickjs/release-sync/package.json +61 -0
  333. package/skills/engineering/review-pr/LICENSE +3 -0
  334. package/skills/engineering/review-pr/SKILL.md +123 -0
  335. package/skills/engineering/review-pr/references/adversarial-reviewer-prompt.md +42 -0
  336. package/skills/engineering/review-pr/references/code-correctness.md +5 -0
  337. package/skills/engineering/review-pr/references/contract-freshness.md +9 -0
  338. package/skills/engineering/review-pr/references/coordination.md +18 -0
  339. package/skills/engineering/review-pr/references/domain-hazards.md +123 -0
  340. package/skills/engineering/review-pr/references/finding-themes.md +7 -0
  341. package/skills/engineering/review-pr/references/github-posting.md +98 -0
  342. package/skills/engineering/review-pr/references/golden-path-smoke.md +5 -0
  343. package/skills/engineering/review-pr/references/incremental-output.md +16 -0
  344. package/skills/engineering/review-pr/references/inputs-and-discovery.md +31 -0
  345. package/skills/engineering/review-pr/references/output-format.md +99 -0
  346. package/skills/engineering/review-pr/references/over-mock-screen.md +7 -0
  347. package/skills/engineering/review-pr/references/promotion-prs.md +16 -0
  348. package/skills/engineering/review-pr/references/re-review.md +18 -0
  349. package/skills/engineering/review-pr/references/review-method.md +199 -0
  350. package/skills/engineering/review-pr/references/review-mode.md +30 -0
  351. package/skills/engineering/review-pr/references/review-posture.md +53 -0
  352. package/skills/engineering/review-pr/references/round1-depth.md +62 -0
  353. package/skills/engineering/review-pr/references/scripts.md +17 -0
  354. package/skills/engineering/review-pr/references/workflow.md +16 -0
  355. package/skills/engineering/review-pr/schemas/findings.schema.json +282 -0
  356. package/skills/engineering/review-pr/scripts/finding-contract.mjs +285 -0
  357. package/skills/engineering/review-pr/scripts/post-review.mjs +405 -0
  358. package/skills/engineering/review-pr/scripts/pr-context.mjs +207 -0
  359. package/skills/engineering/review-pr/scripts/scan-diff.mjs +365 -0
  360. package/skills/engineering/review-pr/scripts/theme-contract.mjs +57 -0
  361. package/skills/engineering/smoke/LICENSE +3 -0
  362. package/skills/engineering/smoke/SKILL.md +131 -0
  363. package/skills/engineering/smoke/assets/smoke.manifest.example.json +53 -0
  364. package/skills/engineering/smoke/references/manifest.md +192 -0
  365. package/skills/engineering/smoke/scripts/smoke.mjs +713 -0
  366. package/skills/fullstack/better-auth/LICENSE +3 -0
  367. package/skills/fullstack/better-auth/SKILL.md +601 -0
  368. package/skills/fullstack/better-auth/references/feishu-api.md +270 -0
  369. package/skills/fullstack/monorepo/LICENSE +3 -0
  370. package/skills/fullstack/monorepo/SKILL.md +465 -0
  371. package/skills/fullstack/nextjs-fullstack/LICENSE +3 -0
  372. package/skills/fullstack/nextjs-fullstack/SKILL.md +210 -0
  373. package/skills/fullstack/nextjs-fullstack/conventions.md +318 -0
  374. package/skills/fullstack/nextjs-fullstack/frontend-conventions.md +61 -0
  375. package/skills/fullstack/nextjs-fullstack/nextjs16.md +287 -0
  376. package/skills/fullstack/nextjs-fullstack/server-actions.md +409 -0
  377. package/skills/fullstack/prisma-setup/LICENSE +3 -0
  378. package/skills/fullstack/prisma-setup/SKILL.md +180 -0
  379. package/skills/fullstack/prisma-setup/nextjs.md +258 -0
  380. package/skills/fullstack/prisma-setup/turborepo.md +301 -0
  381. package/skills/fullstack/shadcn/LICENSE +3 -0
  382. package/skills/fullstack/shadcn/SKILL.md +119 -0
  383. package/skills/fullstack/shadcn/assets/shadcn-small.png +0 -0
  384. package/skills/fullstack/shadcn/assets/shadcn.png +0 -0
  385. package/skills/fullstack/shadcn/cli.md +411 -0
  386. package/skills/fullstack/shadcn/customization.md +224 -0
  387. package/skills/fullstack/shadcn/evals/evals.json +90 -0
  388. package/skills/fullstack/shadcn/mcp.md +101 -0
  389. package/skills/fullstack/shadcn/rules/base-vs-radix.md +323 -0
  390. package/skills/fullstack/shadcn/rules/component-selection.md +67 -0
  391. package/skills/fullstack/shadcn/rules/composition.md +195 -0
  392. package/skills/fullstack/shadcn/rules/data-table.md +201 -0
  393. package/skills/fullstack/shadcn/rules/forms.md +255 -0
  394. package/skills/fullstack/shadcn/rules/icons.md +103 -0
  395. package/skills/fullstack/shadcn/rules/styling.md +167 -0
  396. package/skills/fullstack/zod-v4/LICENSE +3 -0
  397. package/skills/fullstack/zod-v4/SKILL.md +287 -0
@@ -0,0 +1,220 @@
1
+ // contract-revision — the MECHANIZED contract-freshness gate.
2
+ //
3
+ // The doctrine (SKILL.md "Contract-revision gate", run-state.md
4
+ // units[].contractRevision) says: a human-approved contract revision on the
5
+ // target branch supersedes older issue text, PR summaries, and prior verdicts;
6
+ // before every producer/reviewer spawn and on resume, the unit's HEAD must
7
+ // contain that revision, and a review is authoritative only if it ran against it.
8
+ //
9
+ // Prose alone leaves that a lookalike — the resolve-issues removal-path audit
10
+ // found `contractRevision`/`reviewedContractSha` written to the manifest with
11
+ // ZERO script readers: recorded, never enforced. This makes the load-bearing
12
+ // sentence real: "Git ancestry — not a cached boolean — answers whether unit HEAD
13
+ // contains the revision" is exactly `git merge-base --is-ancestor`, gathered live
14
+ // and failing closed. It reuses combine-and-verify's isAncestor rather than
15
+ // re-deriving the check.
16
+ //
17
+ // node contract-revision.mjs check --in <manifest.json> --unit <id> --head <ref>
18
+ // node contract-revision.mjs check --sha <approvedSha> --head <ref> [--reviewed <sha>]
19
+ // [--skip-ref-check] to skip artifact existence (refs kept outside this repo)
20
+ // exit 0 = FRESH (HEAD contains the approved revision → the spawn may proceed)
21
+ // exit 1 = STALE (HEAD lacks it → re-enter intake: rebase + whole-contract reconcile)
22
+ // exit 2 = the pointer itself is unusable — malformed shape, or a path-shaped ref
23
+ // that does not exist at the approved commit (fix the pointer; a rebase
24
+ // cannot repair a citation to a file nobody wrote)
25
+ // A FRESH unit whose reviewedContractSha ≠ the approved sha still owes a
26
+ // full-scope review before it may exit — reported, exit stays 0 (proceeding is
27
+ // correct; it's the *exit* gate, not the spawn gate, that blocks there).
28
+ //
29
+ // Named incident: seven review rounds implemented an obsolete route-based control
30
+ // plane (#936) before a human-approved Cloud Run Job contract (#946) landed on the
31
+ // target branch; the stale-contract loop had no mechanized way to notice.
32
+
33
+ import fs from 'node:fs'
34
+ import { execFileSync } from 'node:child_process'
35
+ import { isAncestor } from './combine-and-verify.mjs'
36
+ import { isMainModule } from '../../engineering-runtime/scripts/main-module.mjs'
37
+
38
+ // ---- pure, unit-testable core (no git) ----
39
+
40
+ // Shape check for units[].contractRevision. `sha` and at least one `ref` are
41
+ // required — a pointer with no commit or no artifact points at nothing. The
42
+ // provenance fields (source/approvedAt/supersedesReviewThroughRound) are typed
43
+ // when present but optional, so the validator accepts the real manifest shape
44
+ // without forcing every writer to fill provenance it may add later.
45
+ export function validateContractRevision(cr) {
46
+ const errors = []
47
+ if (cr == null) return { ok: true, errors } // absent is legal — not every unit has an approved revision yet
48
+ if (typeof cr !== 'object' || Array.isArray(cr)) return { ok: false, errors: ['contractRevision must be an object'] }
49
+ const allowed = ['sha', 'refs', 'source', 'approvedAt', 'supersedesReviewThroughRound']
50
+ for (const k of Object.keys(cr)) if (!allowed.includes(k)) errors.push(`contractRevision has unknown property: ${k}`)
51
+ if (typeof cr.sha !== 'string' || !cr.sha.trim()) errors.push('contractRevision.sha is required (the approved target-branch commit)')
52
+ if (!Array.isArray(cr.refs) || cr.refs.length === 0) errors.push('contractRevision.refs must be a non-empty array of the authoritative artifacts')
53
+ else if (!cr.refs.every((r) => typeof r === 'string' && r.trim())) errors.push('contractRevision.refs entries must be non-empty strings')
54
+ if (cr.source !== undefined && (typeof cr.source !== 'string' || !cr.source.trim())) errors.push('contractRevision.source must be a non-empty string when present')
55
+ if (cr.approvedAt !== undefined && (typeof cr.approvedAt !== 'string' || !cr.approvedAt.trim())) errors.push('contractRevision.approvedAt must be a non-empty string when present')
56
+ if (cr.supersedesReviewThroughRound !== undefined && (!Number.isInteger(cr.supersedesReviewThroughRound) || cr.supersedesReviewThroughRound < 0)) {
57
+ errors.push('contractRevision.supersedesReviewThroughRound must be a non-negative integer when present')
58
+ }
59
+ return { ok: errors.length === 0, errors }
60
+ }
61
+
62
+ // A ref must name an artifact that exists at the approved commit. Requiring a
63
+ // non-empty `refs` array (above) only proves someone typed something; it does not
64
+ // prove the design doc is there to build against. A contract citing a file that is
65
+ // not in the tree is a pointer to nothing — and it fails silently, because every
66
+ // downstream reader (producer, reviewer) is told to treat refs as the source
67
+ // authority and simply finds no source.
68
+ //
69
+ // Only PATH-shaped refs are checked. `refs` legitimately carries non-path pointers
70
+ // — the `(cli)` sentinel, `pr:#123`, `#849`, an https URL — and those are not
71
+ // answerable with `git cat-file`. Skipped refs are reported, never silently
72
+ // dropped: "checked 1 of 3" must be visible, or the gate reads as stronger than it is.
73
+ //
74
+ // Named incident: a unit that spent a review round correcting Review-Contract
75
+ // citations pointing at two ADR/design files that did not exist in the repo.
76
+ // A ref may carry a section anchor — the real epic-848 shape is
77
+ // `CONTEXT.md#Blacklist Repair Plan Preparation`, naming a heading inside the file.
78
+ // The anchor is for a human reader; git resolves the path only, so strip it before
79
+ // asking. (Missing this turns every anchored ref into a false "missing artifact",
80
+ // which is the failure mode that gets a new gate switched off in a day.) A leading
81
+ // `#` is different — that is an issue reference, not a path, and stays non-path.
82
+ const NON_PATH_REF = /^(\(|#|[a-z][a-z0-9+.-]*:)/i
83
+
84
+ export function refPath(ref) {
85
+ return String(ref).trim().split('#')[0].trim()
86
+ }
87
+
88
+ export function isPathRef(ref) {
89
+ return typeof ref === 'string' && ref.trim() !== '' && !NON_PATH_REF.test(ref.trim()) && refPath(ref) !== ''
90
+ }
91
+
92
+ // Pure: split refs into what this gate can answer and what it cannot, given the
93
+ // missing set gathered live.
94
+ export function classifyRefs(refs, missing = []) {
95
+ const all = Array.isArray(refs) ? refs : []
96
+ const checked = all.filter(isPathRef)
97
+ const skipped = all.filter((r) => !isPathRef(r))
98
+ const missingSet = checked.filter((r) => missing.includes(r))
99
+ return {
100
+ ok: missingSet.length === 0,
101
+ checked,
102
+ skipped,
103
+ missing: missingSet,
104
+ errors: missingSet.map(
105
+ (r) => `contractRevision.refs entry "${r}" does not exist at the approved commit — the contract cites an artifact that is not in the tree`,
106
+ ),
107
+ }
108
+ }
109
+
110
+ // The freshness verdict from decidable facts. Two independent questions:
111
+ // fresh — does HEAD contain the approved revision? (spawn gate)
112
+ // reviewCurrent — did the latest verdict run against that exact revision? (exit gate)
113
+ // A unit with no contractRevision is vacuously fresh — nothing supersedes it.
114
+ export function contractFreshness({ contractRevision, reviewedContractSha, headContainsRevision }) {
115
+ if (contractRevision == null) return { fresh: true, reviewCurrent: true, mustReenter: false, owesFullReview: false, reasons: [] }
116
+ const reasons = []
117
+ const fresh = headContainsRevision === true
118
+ const reviewCurrent = fresh && reviewedContractSha === contractRevision.sha
119
+ if (!fresh) reasons.push(`unit HEAD does not contain the approved contract revision ${contractRevision.sha.slice(0, 8)} — re-enter intake: rebase onto the target branch and reconcile the whole implementation to the current invariant list`)
120
+ else if (!reviewCurrent) reasons.push(`HEAD contains the approved revision but the last review ran against ${reviewedContractSha == null ? 'no recorded revision' : reviewedContractSha.slice(0, 8)}, not ${contractRevision.sha.slice(0, 8)} — the next review is full-scope and prior verdicts/CI/lastReviewedSha are historical only`)
121
+ return {
122
+ fresh,
123
+ reviewCurrent,
124
+ mustReenter: !fresh, // HEAD lacks it → the producer must rebase before any further spawn
125
+ owesFullReview: fresh && !reviewCurrent, // HEAD has it but review is stale against it
126
+ reasons,
127
+ }
128
+ }
129
+
130
+ // ---- live facts (side-effecting) ----
131
+
132
+ export function gatherFreshness(contractRevision, head, ancestry = isAncestor) {
133
+ return { headContainsRevision: contractRevision == null ? true : ancestry(contractRevision.sha, head) }
134
+ }
135
+
136
+ // Does `<sha>:<ref>` resolve to an object in this repo? Fails closed: any git
137
+ // error (unknown sha, path absent, not a repo) counts the ref as missing, because
138
+ // "we could not prove the artifact is there" and "the artifact is not there" have
139
+ // the same consequence for a spawn told to build against it.
140
+ export function refExistsAt(sha, ref) {
141
+ try {
142
+ execFileSync('git', ['cat-file', '-e', `${sha}:${ref}`], { stdio: 'ignore' })
143
+ return true
144
+ } catch {
145
+ return false
146
+ }
147
+ }
148
+
149
+ export function gatherMissingRefs(contractRevision, exists = refExistsAt) {
150
+ if (contractRevision == null) return []
151
+ return (contractRevision.refs || []).filter(isPathRef).filter((r) => !exists(contractRevision.sha, refPath(r)))
152
+ }
153
+
154
+ // ---- CLI ----
155
+
156
+ function parseArgs(argv) {
157
+ const f = {}
158
+ for (let i = 0; i < argv.length; i++) {
159
+ const a = argv[i]
160
+ if (a === '--in') f.in = argv[++i]
161
+ else if (a === '--unit') f.unit = argv[++i]
162
+ else if (a === '--head') f.head = argv[++i]
163
+ else if (a === '--sha') f.sha = argv[++i]
164
+ else if (a === '--reviewed') f.reviewed = argv[++i]
165
+ else if (a === '--skip-ref-check') f.skipRefCheck = true
166
+ else if (!f.cmd) f.cmd = a
167
+ }
168
+ return f
169
+ }
170
+
171
+ const USAGE = `contract-revision — the mechanized contract-freshness gate.
172
+ Usage:
173
+ contract-revision check --in <manifest.json> --unit <id> --head <ref>
174
+ contract-revision check --sha <approvedSha> --head <ref> [--reviewed <sha>] [--skip-ref-check]
175
+ Exit: 0 = FRESH (HEAD contains the approved revision), 1 = STALE (re-enter intake), 2 = pointer unusable. See the file header for the full contract.`
176
+
177
+ export function runCli(argv) {
178
+ const f = parseArgs(argv)
179
+ if (f.cmd === 'help' || f.cmd === '--help' || f.cmd === '-h') { console.log(USAGE); return 0 }
180
+ if (f.cmd !== 'check') { console.error('usage: contract-revision check (--in <manifest> --unit <id> | --sha <sha> [--reviewed <sha>]) --head <ref> [--skip-ref-check]'); return 2 }
181
+ if (!f.head) { console.error('--head <ref> is required (the unit branch HEAD to test)'); return 2 }
182
+
183
+ let contractRevision, reviewedContractSha
184
+ if (f.in) {
185
+ if (!f.unit) { console.error('--in requires --unit <id>'); return 2 }
186
+ const manifest = JSON.parse(fs.readFileSync(f.in, 'utf8'))
187
+ const unit = (manifest.units || []).find((u) => u.id === f.unit)
188
+ if (!unit) { console.error(`unit ${f.unit} not found in ${f.in}`); return 2 }
189
+ const shape = validateContractRevision(unit.contractRevision)
190
+ if (!shape.ok) { console.error('contractRevision is malformed:'); shape.errors.forEach((e) => console.error(` ✗ ${e}`)); return 2 }
191
+ contractRevision = unit.contractRevision ?? null
192
+ reviewedContractSha = unit.reviewedContractSha ?? null
193
+ } else if (f.sha) {
194
+ contractRevision = { sha: f.sha, refs: ['(cli)'] }
195
+ reviewedContractSha = f.reviewed ?? null
196
+ } else { console.error('provide either --in/--unit or --sha'); return 2 }
197
+
198
+ // Refs first: a contract whose artifacts are not in the tree cannot authorize a
199
+ // spawn no matter how the ancestry check comes out, and reporting it alongside a
200
+ // FRESH verdict would read as approval. Malformed pointer → 2, same as a shape failure.
201
+ if (!f.skipRefCheck && contractRevision != null) {
202
+ const refs = classifyRefs(contractRevision.refs, gatherMissingRefs(contractRevision))
203
+ if (refs.skipped.length) console.log(`contract-revision: refs — checked ${refs.checked.length}, skipped ${refs.skipped.length} non-path (${refs.skipped.join(', ')})`)
204
+ if (!refs.ok) {
205
+ console.error('contract-revision: ❌ contract cites artifacts missing at the approved commit:')
206
+ refs.errors.forEach((e) => console.error(` ✗ ${e}`))
207
+ console.error(' fix the pointer (or land the artifact) — do not spawn against a contract that is not in the tree')
208
+ return 2
209
+ }
210
+ }
211
+
212
+ const facts = gatherFreshness(contractRevision, f.head)
213
+ const v = contractFreshness({ contractRevision, reviewedContractSha, headContainsRevision: facts.headContainsRevision })
214
+ if (v.fresh && v.reviewCurrent) console.log('contract-revision: ✅ FRESH — HEAD contains the approved revision and the last review ran against it')
215
+ else if (v.fresh) { console.log('contract-revision: ⚠️ FRESH but review is stale — proceed, but the exit gate owes a full-scope review:'); v.reasons.forEach((r) => console.log(` · ${r}`)) }
216
+ else { console.log('contract-revision: ❌ STALE — do not spawn; re-enter intake:'); v.reasons.forEach((r) => console.log(` ✗ ${r}`)) }
217
+ return v.mustReenter ? 1 : 0
218
+ }
219
+
220
+ if (isMainModule(import.meta.url)) process.exit(runCli(process.argv.slice(2)))
@@ -0,0 +1,420 @@
1
+ // detect-delivery-mode — the MECHANIZED pre-flight delivery-mode probe.
2
+ //
3
+ // `deliveryMode` (direct | queue) decides which action delivers a merge-ready unit,
4
+ // and picking it wrong fails SILENTLY IN BOTH DIRECTIONS: `gh pr merge` on a
5
+ // merge-queue repo bypasses the queue or errors, and an enqueue on a direct repo
6
+ // does nothing at all. That silence is the epic-986 shape.
7
+ //
8
+ // Why this exists as a script and not prose: the first prose version of the rule
9
+ // said "a `.mergify.yml` carrying `queue_rules`, or a merge-queue-enabled ruleset
10
+ // ⇒ queue; otherwise direct". Dogfooding it on the repo that introduced it proved
11
+ // it wrong — this skills repo has NEITHER file and still merges through a live Mergify
12
+ // queue whose `default` rule is configured dashboard-side, outside the repo
13
+ // entirely. Both repos examined so far are configured that way, so a file-only
14
+ // check gets the common case backwards. A queue is a RUNTIME fact: ask it.
15
+ //
16
+ // node detect-delivery-mode.mjs # probe, recommend (JSON on stdout)
17
+ // node detect-delivery-mode.mjs --repo o/r # explicit repo (default: gh's current)
18
+ // node detect-delivery-mode.mjs --branch main # branch the ruleset/config applies to
19
+ // node detect-delivery-mode.mjs --help # usage + exit 0
20
+ //
21
+ // OUTPUT (stdout, always JSON):
22
+ // { mode: "queue"|"direct"|null, queueProvider: "mergify"|"github"|null,
23
+ // queueProviderReason, signals: [{name, found, provider?, detail}], probed, reason }
24
+ //
25
+ // EXIT CODES:
26
+ // 0 a mode was determined — and in queue mode, its PROVIDER too
27
+ // 2 UNDETERMINED or usage error — mode null, never a silent "direct". Undetermined
28
+ // covers three states, all reported in `reason`/`queueProviderReason`: no probe ran
29
+ // at all; only the FILE probes ran and were negative (file silence is not evidence
30
+ // of direct mode); and a queue whose provider is unknown or ambiguous.
31
+ // For the MODE there is deliberately no "probes disagree" code: any positive signal
32
+ // proves a queue, so mode signals cannot contradict each other — they only accumulate.
33
+ // That reasoning does NOT extend to the PROVIDER: a repo carrying both a .mergify.yml
34
+ // and a merge_queue ruleset gives two different answers about the same queue, and the
35
+ // mechanics differ per provider, so disagreement there is a human ask (provider null).
36
+ //
37
+ // The fail-closed property is exit 2. "No evidence of a queue" and "could not look
38
+ // for a queue" are different states, and collapsing them is how the wrong default
39
+ // gets recorded as though it had been established. The manifest's absent-mode
40
+ // default (`direct`, for manifests written before the gate) is a LEGACY read, not
41
+ // a licence to guess here.
42
+
43
+ import { execFileSync } from 'node:child_process'
44
+ import { realpathSync } from 'node:fs'
45
+ import { fileURLToPath } from 'node:url'
46
+
47
+ // ---- pure, unit-testable core (no network, no subprocesses) ----
48
+
49
+ // A queue is proved by ANY positive signal, because queue configuration lives in
50
+ // several places and no single one is authoritative:
51
+ // mergify-config .mergify.yml with queue_rules (repo-side Mergify)
52
+ // github-merge-queue a ruleset carrying a merge_queue rule (native GitHub)
53
+ // mergify-live-queue the Mergify API answers for this repo (dashboard-side)
54
+ // observed-queue-check a recent PR carries a queue check run (gh-only fallback)
55
+ // The last two are what a file-only check misses.
56
+ export const DELIVERY_SIGNALS = ['mergify-config', 'github-merge-queue', 'mergify-live-queue', 'observed-queue-check']
57
+
58
+ export function decideDeliveryMode(signals) {
59
+ const list = Array.isArray(signals) ? signals : []
60
+ const known = list.filter((s) => DELIVERY_SIGNALS.includes(s?.name))
61
+ const ran = known.filter((s) => s.ran !== false)
62
+ // No probe completed → we do not know. This is the whole point of the script:
63
+ // "could not look" must not read back as "looked and found nothing".
64
+ if (ran.length === 0) {
65
+ return { mode: null, probed: false, reason: 'no delivery-mode probe could run — mode is UNKNOWN; settle it with the human rather than defaulting', signals: known }
66
+ }
67
+ const positive = ran.filter((s) => s.found === true)
68
+ if (positive.length > 0) {
69
+ return { mode: 'queue', probed: true, reason: `a merge queue is live for this repo (${positive.map((s) => s.name).join(', ')}) — delivery is enqueue-then-watch, never \`gh pr merge\``, signals: known }
70
+ }
71
+ // Every probe that ran came back negative. A negative from the LIVE probes is
72
+ // worth more than a negative from the file probes, which is exactly the asymmetry
73
+ // the first version of this rule got wrong — so say which probes carried it.
74
+ const liveRan = ran.some((s) => s.name === 'mergify-live-queue' || s.name === 'observed-queue-check')
75
+ if (!liveRan) {
76
+ return { mode: null, probed: true, reason: 'only file-based probes ran and found nothing — queue config commonly lives outside the repo, so this is NOT evidence of direct mode; run the live probe or settle with the human', signals: known }
77
+ }
78
+ return { mode: 'direct', probed: true, reason: 'no merge queue answers for this repo — delivery is the legacy `gh pr merge` path', signals: known }
79
+ }
80
+
81
+ // The mode is not the whole answer. `queue` is TWO flows — Mergify and GitHub's native
82
+ // merge queue — and they differ in every mechanic the delivery step uses: how you enqueue
83
+ // (a PR comment vs an API call), where state is read, what the dequeue reasons are called,
84
+ // and whether a re-evaluation request or an acknowledgement exists at all. The prose used
85
+ // to offer "`@mergifyio queue`, or `gh pr merge --auto`" as if they were interchangeable
86
+ // spellings; they are not, and a run that picks the wrong one fails the same silent way
87
+ // picking the wrong MODE does.
88
+ //
89
+ // Note the asymmetry with mode: for mode, "any positive signal proves a queue" and signals
90
+ // cannot contradict each other. For provider they very much can — a repo carrying both a
91
+ // .mergify.yml and a merge_queue ruleset yields two answers, and choosing one by
92
+ // precedence would be a guess wearing a rule. That is a human ask, so it returns null.
93
+ export const SIGNAL_PROVIDERS = {
94
+ 'mergify-config': 'mergify',
95
+ 'mergify-live-queue': 'mergify',
96
+ 'github-merge-queue': 'github',
97
+ // 'observed-queue-check' carries its own — it matches both vocabularies, so the probe
98
+ // reports which one it actually saw rather than being mapped blind.
99
+ }
100
+
101
+ export function decideQueueProvider(signals) {
102
+ const positive = (Array.isArray(signals) ? signals : []).filter((s) => s?.ran !== false && s?.found === true)
103
+ const providers = [...new Set(positive.map((s) => s.provider ?? SIGNAL_PROVIDERS[s?.name] ?? null).filter(Boolean))]
104
+ if (providers.length === 1) {
105
+ return { provider: providers[0], reason: `queue mechanics are ${providers[0]}'s (${positive.filter((s) => (s.provider ?? SIGNAL_PROVIDERS[s.name]) === providers[0]).map((s) => s.name).join(', ')})` }
106
+ }
107
+ if (providers.length > 1) {
108
+ return { provider: null, reason: `AMBIGUOUS — signals point at more than one queue provider (${providers.join(', ')}); the mechanics differ per provider, so this is a human ask, not a precedence rule` }
109
+ }
110
+ return { provider: null, reason: 'a queue answers for this repo but no signal identified WHICH provider runs it — settle it with the human before delivery acts' }
111
+ }
112
+
113
+ // ---- the third axis: what merges this PR without the loop acting again? ----
114
+ //
115
+ // Mode and provider answer "how do I enqueue". They do NOT answer the question guarantee 4
116
+ // actually depends on: **can this PR reach the target branch without the loop performing a
117
+ // delivery action at all?** A repo can carry an auto-merge rule that fires the moment its
118
+ // conditions are satisfied, and the loop's own routine acts — marking a PR ready, pushing
119
+ // the commit that turns CI green, posting the review verdict — are exactly the things that
120
+ // satisfy them. Where that is true, `mergeShippable: false` is unenforceable: the loop
121
+ // never chooses to merge, it merges as a side effect of reviewing.
122
+ //
123
+ // Note what this axis is NOT. It is not "manual or auto" as alternatives — manual enqueue
124
+ // stays available on an auto repo, so the loop never loses the ability to enqueue. What
125
+ // changes is that a SECOND path exists which the loop does not drive. So the trigger
126
+ // describes what the REPO can do on its own, never what the loop is permitted to do.
127
+ //
128
+ // Grounded in a live config (a consumer harness repo, 2026-07-25):
129
+ // merge_protections_settings:
130
+ // auto_merge_conditions:
131
+ // - base = main
132
+ // - "-draft"
133
+ // - "#approved-reviews-by >= 1"
134
+ // - "#changes-requested-reviews-by = 0"
135
+ // - check-success = ci
136
+ // Its GitHub rulesets require zero approvals, so that one review condition IS the merge
137
+ // authorization — and the reviewer spawn posting its verdict as an approval is the act
138
+ // that merges. That is the case this probe exists to make visible before delivery starts.
139
+ export const ENQUEUE_TRIGGERS = ['manual', 'auto', 'hybrid']
140
+
141
+ // Conditions the LOOP can satisfy through its own ordinary work. These are the dangerous
142
+ // ones: a condition only a human can meet (a label a person applies, a schedule) leaves
143
+ // the merge outside the loop's reach, which is safe. A condition the loop trips by doing
144
+ // its job turns a routine step into a merge.
145
+ const LOOP_TRIPPABLE = [
146
+ { re: /approved-reviews-by/i, act: 'posting an approving review', kind: 'review' },
147
+ { re: /changes-requested-reviews-by/i, act: 'resolving or not filing a change request', kind: 'review' },
148
+ { re: /^\s*-?\s*-draft\b/i, act: 'marking the PR ready for review', kind: 'draft' },
149
+ { re: /check-success|status-success/i, act: 'pushing the commit that turns CI green', kind: 'ci' },
150
+ ]
151
+
152
+ // Deliberately a light structural read, not a YAML parser: this repo's scripts take no
153
+ // dependencies, and the question here is coarse (does an auto-merge block exist, and what
154
+ // does it list?). Anything it cannot parse confidently must read as UNKNOWN rather than as
155
+ // "no auto-merge" — see decideEnqueueTrigger's fail-closed direction.
156
+ export function parseAutoMergeConditions(yamlText) {
157
+ const text = String(yamlText ?? '')
158
+ const lines = text.split('\n')
159
+ const start = lines.findIndex((l) => /^\s*auto_merge_conditions\s*:/.test(l))
160
+ if (start === -1) return { present: false, conditions: [] }
161
+ const indent = (lines[start].match(/^\s*/) || [''])[0].length
162
+ const conditions = []
163
+ for (let i = start + 1; i < lines.length; i++) {
164
+ const line = lines[i]
165
+ if (!line.trim() || /^\s*#/.test(line)) continue
166
+ const ind = (line.match(/^\s*/) || [''])[0].length
167
+ if (ind <= indent) break
168
+ const m = line.match(/^\s*-\s*(.+?)\s*$/)
169
+ if (!m) break
170
+ conditions.push(m[1].replace(/^["']|["']$/g, ''))
171
+ }
172
+ return { present: true, conditions }
173
+ }
174
+
175
+ // Which of the auto-merge conditions can this loop satisfy by doing its ordinary work?
176
+ export function loopTrippableConditions(conditions) {
177
+ return (Array.isArray(conditions) ? conditions : []).flatMap((c) => {
178
+ const cond = String(c)
179
+ // A YAML alias (`*gate`) hides its condition behind an anchor defined elsewhere, and
180
+ // both real configs examined use one for exactly the CI check. Resolving anchors would
181
+ // mean writing a YAML parser; reporting the alias as harmless would mean deciding, on
182
+ // no evidence, that the thing we could not read is not one the loop trips. So it is
183
+ // reported as trippable-unknown — the same fail-closed direction as the trigger itself.
184
+ if (/^\*\S+$/.test(cond)) {
185
+ return [{ condition: cond, act: `satisfying \`${cond}\`, a YAML alias this probe cannot resolve`, kind: 'unresolved-alias' }]
186
+ }
187
+ const hit = LOOP_TRIPPABLE.find((t) => t.re.test(cond))
188
+ return hit ? [{ condition: cond, act: hit.act, kind: hit.kind }] : []
189
+ })
190
+ }
191
+
192
+ export function decideEnqueueTrigger(signals) {
193
+ const sig = (Array.isArray(signals) ? signals : []).find((s) => s?.name === 'mergify-auto-merge')
194
+ // Could not look. Fail closed toward the HAZARD, not toward convenience: assuming
195
+ // `manual` on a repo that auto-merges is how a run configured to merge nothing merges
196
+ // anyway, and nothing downstream would report it. Assuming the hazard costs at most an
197
+ // extra human confirmation.
198
+ if (!sig || sig.ran === false) {
199
+ return { trigger: null, autoMergeConditions: [], loopTrippable: [], reason: 'could not read the repo\'s auto-merge configuration — the trigger is UNKNOWN; treat as auto (fail closed) and settle it with the human before posting any review that could satisfy a merge condition' }
200
+ }
201
+ if (!sig.found) {
202
+ return { trigger: 'manual', autoMergeConditions: [], loopTrippable: [], reason: 'no auto-merge rule found — a merge happens only when this loop performs a delivery action' }
203
+ }
204
+ const conditions = Array.isArray(sig.conditions) ? sig.conditions : []
205
+ const trippable = loopTrippableConditions(conditions)
206
+ // `hybrid` when the manual command is also open: the loop keeps its own enqueue path,
207
+ // and the auto path exists beside it. The distinction matters for the MECHANICS the
208
+ // loop uses; for AUTHORIZATION `auto` and `hybrid` are identical, and every gate reads
209
+ // them that way (trigger !== 'manual').
210
+ const trigger = sig.manualAlsoAllowed === false ? 'auto' : 'hybrid'
211
+ return {
212
+ trigger,
213
+ autoMergeConditions: conditions,
214
+ loopTrippable: trippable,
215
+ reason: trippable.length
216
+ ? `this repo merges a PR on its own once its auto-merge conditions hold, and this loop can satisfy ${trippable.length} of them by ${trippable.map((t) => t.act).join('; ')} — those acts ARE the merge, so they inherit guarantee 4's authorization`
217
+ : 'an auto-merge rule exists, but no condition in it is one this loop trips through its ordinary work',
218
+ }
219
+ }
220
+
221
+ // ---- impure probes ----
222
+
223
+ function tryRun(cmd, args, opts = {}) {
224
+ try { return { ok: true, out: execFileSync(cmd, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], ...opts }) } }
225
+ catch (error) { return { ok: false, out: '', error } }
226
+ }
227
+
228
+ function probeMergifyConfig(repo, branch) {
229
+ for (const path of ['.mergify.yml', '.mergify/config.yml', '.mergify.yaml']) {
230
+ const r = tryRun('gh', ['api', `repos/${repo}/contents/${path}?ref=${branch}`, '--jq', '.content'])
231
+ if (!r.ok || !r.out.trim()) continue
232
+ let decoded = ''
233
+ try { decoded = Buffer.from(r.out.replace(/\s/g, ''), 'base64').toString('utf8') } catch { continue }
234
+ if (/^\s*queue_rules\s*:/m.test(decoded)) return { name: 'mergify-config', ran: true, found: true, detail: `${path} declares queue_rules` }
235
+ return { name: 'mergify-config', ran: true, found: false, detail: `${path} exists but declares no queue_rules` }
236
+ }
237
+ // Every candidate path failed to read. That is only a NEGATIVE if gh could actually
238
+ // talk to the repo; otherwise the probe did not run, and reporting it as "no
239
+ // .mergify.yml" fabricates evidence. Distinguish with a cheap reachability check.
240
+ const reachable = tryRun('gh', ['api', `repos/${repo}`, '--jq', '.name'])
241
+ if (!reachable.ok) return { name: 'mergify-config', ran: false, found: false, detail: `cannot read ${repo} via gh — probe did not run` }
242
+ return { name: 'mergify-config', ran: true, found: false, detail: 'no .mergify.yml in the repo' }
243
+ }
244
+
245
+ function probeGithubMergeQueue(repo, branch) {
246
+ const r = tryRun('gh', ['api', `repos/${repo}/rulesets`, '--jq', '.[].id'])
247
+ if (!r.ok) return { name: 'github-merge-queue', ran: false, found: false, detail: 'rulesets unreadable (permissions?)' }
248
+ const ids = r.out.split('\n').map((s) => s.trim()).filter(Boolean)
249
+ for (const id of ids) {
250
+ const d = tryRun('gh', ['api', `repos/${repo}/rulesets/${id}`, '--jq', '[.rules[].type] | join(",")'])
251
+ if (d.ok && d.out.includes('merge_queue')) return { name: 'github-merge-queue', ran: true, found: true, detail: `ruleset ${id} carries a merge_queue rule` }
252
+ }
253
+ return { name: 'github-merge-queue', ran: true, found: false, detail: `no merge_queue rule in ${ids.length} ruleset(s)` }
254
+ }
255
+
256
+ // The probe the file-only rule was missing. `mergify queue status` answers for a
257
+ // repo whose queue is configured dashboard-side and therefore appears in no file.
258
+ // The pure half of the live probe: given mergify's output, does it answer about the
259
+ // repo we ASKED about? This is where BLOCK-3 lived (a queue reported for the cwd's repo
260
+ // read as evidence about a different one), so it is separated to be testable without
261
+ // shelling out.
262
+ export function readLiveQueueAnswer(repo, out) {
263
+ const line = (out.split('\n').find((l) => l.includes('Merge Queue:')) || '').trim()
264
+ if (!line) return { found: false, detail: `no queue reported for ${repo}` }
265
+ if (!line.includes(repo)) return { found: false, detail: `queue reported for a DIFFERENT repo (${line}) — not evidence about ${repo}` }
266
+ return { found: true, detail: line }
267
+ }
268
+
269
+ function probeMergifyLiveQueue(repo) {
270
+ const r = tryRun('mergify', ['queue', 'status', '--repository', repo])
271
+ // No cwd-scoped fallback. `mergify queue status` without --repository answers for the
272
+ // CURRENT checkout, and the command fails non-zero precisely for repos Mergify does
273
+ // not manage — i.e. every direct-mode candidate. So the fallback turned "this repo has
274
+ // no queue" into "the repo I happen to be standing in has one", which is the silent
275
+ // wrong-mode failure this whole script exists to prevent. A failure is `ran: false`.
276
+ if (!r.ok) return { name: 'mergify-live-queue', ran: false, found: false, detail: `mergify queue status failed for ${repo} (unmanaged repo, CLI unavailable, or unauthenticated)` }
277
+ return { name: 'mergify-live-queue', ran: true, ...readLiveQueueAnswer(repo, r.out) }
278
+ }
279
+
280
+ // gh-only fallback for when the mergify CLI is not installed: a repo that merges
281
+ // through a queue leaves a queue check run on its recent PRs.
282
+ function probeObservedQueueCheck(repo) {
283
+ const r = tryRun('gh', ['pr', 'list', '--repo', repo, '--state', 'merged', '--limit', '5', '--json', 'statusCheckRollup', '--jq', '[.[].statusCheckRollup[]?.name] | join("|")'])
284
+ if (!r.ok) return { name: 'observed-queue-check', ran: false, found: false, detail: 'recent PR checks unreadable' }
285
+ const mergify = /Mergify Merge Queue/i.test(r.out)
286
+ const github = /merge_group/i.test(r.out)
287
+ const found = mergify || github
288
+ // Which vocabulary the check-run came from IS the provider evidence. Reporting only
289
+ // "a queue check exists" throws away the one bit that decides the mechanics.
290
+ const provider = mergify && github ? null : (mergify ? 'mergify' : (github ? 'github' : null))
291
+ return {
292
+ name: 'observed-queue-check', ran: true, found, ...(provider ? { provider } : {}),
293
+ detail: found ? `a recent merged PR carries a ${provider ?? 'merge-queue (both vocabularies seen)'} check` : 'no merge-queue check on the last 5 merged PRs',
294
+ }
295
+ }
296
+
297
+ // Read the repo's Mergify config for an auto-merge block. Reads the same candidate paths
298
+ // as probeMergifyConfig; kept separate because it answers a different question and must
299
+ // report `ran: false` on its own terms — a config it could not read is UNKNOWN, and
300
+ // decideEnqueueTrigger fails closed on that rather than reading it as "no auto-merge".
301
+ function probeMergifyAutoMerge(repo, branch) {
302
+ for (const path of ['.mergify.yml', '.mergify/config.yml', '.mergify.yaml']) {
303
+ const r = tryRun('gh', ['api', `repos/${repo}/contents/${path}?ref=${branch}`, '--jq', '.content'])
304
+ if (!r.ok || !r.out.trim()) continue
305
+ let decoded = ''
306
+ try { decoded = Buffer.from(r.out.replace(/\s/g, ''), 'base64').toString('utf8') } catch { continue }
307
+ const { present, conditions } = parseAutoMergeConditions(decoded)
308
+ // `commands_restrictions.queue` present means the manual command is gated but still
309
+ // available — the hybrid shape. Absence of the key does not prove the command is
310
+ // disabled, so the default stays "manual also allowed", which is the true reading for
311
+ // every Mergify repo observed so far.
312
+ const manualAlsoAllowed = true
313
+ if (!present) return { name: 'mergify-auto-merge', ran: true, found: false, detail: `${path} declares no auto_merge_conditions` }
314
+ return {
315
+ name: 'mergify-auto-merge', ran: true, found: true, conditions, manualAlsoAllowed,
316
+ detail: `${path} auto-merges on: ${conditions.join(' AND ') || '(no conditions parsed)'}`,
317
+ }
318
+ }
319
+ const reachable = tryRun('gh', ['api', `repos/${repo}`, '--jq', '.name'])
320
+ if (!reachable.ok) return { name: 'mergify-auto-merge', ran: false, found: false, detail: `cannot read ${repo} via gh — probe did not run` }
321
+ // No Mergify config file at all. That is a real negative for a repo-side auto-merge
322
+ // rule, but NOT for a dashboard-side one, which this probe cannot see — so it is
323
+ // reported as a negative with that caveat named in the CLI's reason.
324
+ return { name: 'mergify-auto-merge', ran: true, found: false, detail: 'no .mergify.yml in the repo (a dashboard-side auto-merge rule would not appear here)' }
325
+ }
326
+
327
+ export function gatherSignals({ repo, branch }) {
328
+ return [
329
+ probeMergifyConfig(repo, branch),
330
+ probeGithubMergeQueue(repo, branch),
331
+ probeMergifyLiveQueue(repo),
332
+ probeObservedQueueCheck(repo),
333
+ probeMergifyAutoMerge(repo, branch),
334
+ ]
335
+ }
336
+
337
+ function currentRepo() {
338
+ const r = tryRun('gh', ['repo', 'view', '--json', 'nameWithOwner', '--jq', '.nameWithOwner'])
339
+ return r.ok ? r.out.trim() : null
340
+ }
341
+
342
+ function parseArgs(argv) {
343
+ const f = {}
344
+ for (let i = 0; i < argv.length; i++) {
345
+ const a = argv[i]
346
+ if (a === '--repo') f.repo = argv[++i]
347
+ else if (a === '--branch') f.branch = argv[++i]
348
+ else if (a === '--help' || a === '-h') f.help = true
349
+ }
350
+ return f
351
+ }
352
+
353
+ export function runCli(argv) {
354
+ const f = parseArgs(argv)
355
+ if (f.help) {
356
+ console.log(`detect-delivery-mode [--repo <owner/name>] [--branch <branch>]
357
+ Probes three things pre-flight must settle before delivery acts:
358
+ deliveryMode direct | queue — which action delivers a merge-ready unit
359
+ queueProvider mergify | github — whose mechanics the queue uses
360
+ enqueueTrigger manual | auto | hybrid — whether the REPO can merge the PR with no
361
+ further action from this loop, and which auto-merge conditions this
362
+ loop can satisfy through its ordinary work (posting a review, marking
363
+ ready, turning CI green). Those acts ARE the merge where they appear.
364
+ exit 0 = all three determined · 2 = mode/provider UNDETERMINED or usage error · 3 = mode known but the trigger is UNKNOWN.
365
+ Nothing is guessed: "could not look" and "looked and found nothing" are different answers, and both the provider and the trigger fail closed.`)
366
+ return 0
367
+ }
368
+ const repo = f.repo || currentRepo()
369
+ if (!repo) { console.error('detect-delivery-mode: no --repo given and gh could not resolve the current repository'); return 2 }
370
+ const branch = f.branch || 'main'
371
+ const signals = gatherSignals({ repo, branch })
372
+ const decision = decideDeliveryMode(signals)
373
+ // The provider question only exists in queue mode; in direct mode there is no queue to
374
+ // have mechanics. Reported alongside the mode so pre-flight settles both in one probe.
375
+ const provider = decision.mode === 'queue' ? decideQueueProvider(signals) : { provider: null, reason: 'direct mode has no queue provider' }
376
+ // The trigger is reported in EVERY mode, including direct: an auto-merge rule merges a
377
+ // PR whether or not a queue is involved, so "direct mode" is not a reason to skip the
378
+ // question guarantee 4 depends on.
379
+ const trig = decideEnqueueTrigger(signals)
380
+ console.log(JSON.stringify({
381
+ repo, branch, ...decision,
382
+ queueProvider: provider.provider, queueProviderReason: provider.reason,
383
+ enqueueTrigger: trig.trigger, enqueueTriggerReason: trig.reason,
384
+ autoMergeConditions: trig.autoMergeConditions, loopTrippableConditions: trig.loopTrippable,
385
+ }, null, 2))
386
+ if (!decision.mode) return 2
387
+ // Fail closed on the trigger too, and for the same reason as the provider: a run that
388
+ // does not know whether its own review will merge the PR cannot be allowed to review
389
+ // first and find out. Exit 3 rather than 2 so the caller can tell "no mode" from "mode
390
+ // known, authorization semantics unknown" — the second is settled by a different answer.
391
+ if (!trig.trigger) return 3
392
+ // Fail CLOSED on the second half of the answer too. A queue whose provider is unknown
393
+ // cannot be delivered mechanically — the enqueue command itself is unknown — and
394
+ // returning 0 here would let pre-flight record a mode that delivery then refuses to act
395
+ // on, which is the wrong-mode silence one layer down.
396
+ if (decision.mode === 'queue' && !provider.provider) return 2
397
+ return 0
398
+ }
399
+
400
+ let launchedDirectly = false
401
+ let mainModuleError = null
402
+ try {
403
+ const { isMainModule } = await import('../../engineering-runtime/scripts/main-module.mjs')
404
+ if (typeof isMainModule !== 'function') throw new Error('main-module.mjs does not export isMainModule')
405
+ launchedDirectly = isMainModule(import.meta.url)
406
+ } catch (error) {
407
+ mainModuleError = error
408
+ try {
409
+ launchedDirectly = realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))
410
+ } catch {
411
+ launchedDirectly = false
412
+ }
413
+ }
414
+
415
+ if (launchedDirectly && mainModuleError) {
416
+ console.error(`detect-delivery-mode: cannot load engineering-runtime main-module helper: ${mainModuleError.message}`)
417
+ process.exitCode = 1
418
+ } else if (launchedDirectly) {
419
+ process.exit(runCli(process.argv.slice(2)))
420
+ }