@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,367 @@
1
+ // evidence-block — derive the Review Contract's volatile evidence, never transcribe it.
2
+ //
3
+ // A Review Contract carries two kinds of claim with very different lifetimes:
4
+ //
5
+ // DURABLE what this PR proves, which surfaces it touches, which rows are
6
+ // Partial/Unverified and why. Changes when the CLAIM changes.
7
+ // VOLATILE scan counts, CI job tables, test counts, gate results, SHAs.
8
+ // Invalidated by ANY commit.
9
+ //
10
+ // Hand-writing the volatile half is a defect generator: every fix round rewrites
11
+ // the document, and every rewrite is a fresh chance to overclaim or go stale. The
12
+ // observed incident: one PR carried five docs-only "record round N evidence"
13
+ // commits, each invalidated within the hour by the next, producing evidence-fidelity
14
+ // review findings for three consecutive rounds while the production diff was empty.
15
+ //
16
+ // So: the volatile block is GENERATED at a known SHA and STAMPED with it, and
17
+ // `validate` fails when the stamp is not the current HEAD. Nobody transcribes it,
18
+ // so nobody can transcribe it wrong; staleness becomes mechanical rather than a
19
+ // reviewer's catch. The contract prose references this file instead of restating it.
20
+ //
21
+ // node evidence-block.mjs collect --base <sha> [--head <sha>] [--source a,b,c]
22
+ // [--scan-diff <path to review-pr/scripts/scan-diff.mjs>]
23
+ // [--ingest <file.json>] [--no-ci] [-o evidence.json]
24
+ // [--exact-head <exact-head-artifact.json>]
25
+ // node evidence-block.mjs validate <evidence.json> [--source a,b,c]
26
+ //
27
+ // `--source` is COMMA-separated in a single argv token (`--source src,app,packages`).
28
+ // Space-separating it silently yields one bogus pathspec, and git exits 0 on a pathspec
29
+ // that matches nothing — so the production-diff claim would read EMPTY on every run and
30
+ // tell the orchestrator to skip the review round. Failing open is the one failure mode
31
+ // this file cannot afford, so the shape is spelled out here and in the CI templates.
32
+ //
33
+ // `collect` derives what it can run (git identity, scan-diff channels, CI jobs for
34
+ // the exact SHA, production-diff emptiness) and INGESTS what it cannot (repo-specific
35
+ // suite results — see --ingest below). Everything lands stamped with the same head.
36
+ //
37
+ // --ingest takes { "entries": [ { "name", "command", "result", "status", "sha"?,
38
+ // "reused"?, "note"? } ] }. `status` is Pass | Fail | Partial/Unverified. An entry
39
+ // whose sha differs from head must set `reused: true` and carry a note — that is the
40
+ // honest label for reusing a prior probe, and validate reports it rather than
41
+ // letting it read as fresh exact-head evidence.
42
+
43
+ import fs from 'node:fs'
44
+ import path from 'node:path'
45
+ import { execFileSync } from 'node:child_process'
46
+ import { isMainModule } from '../../engineering-runtime/scripts/main-module.mjs'
47
+
48
+ import { validateExactHeadArtifact } from '../../engineering-runtime/scripts/exact-head-artifact.mjs'
49
+ import { ciAbsence, observeActionsRuns, startupFailures } from '../../engineering-runtime/scripts/head-check-set.mjs'
50
+ export const EVIDENCE_BLOCK_VERSION = 1
51
+ export const INGEST_STATUSES = ['Pass', 'Fail', 'Partial/Unverified']
52
+
53
+ // ---- pure core ----
54
+
55
+ // A stamped entry is fresh only when it was produced at the head being claimed.
56
+ // Anything else must be explicitly labelled as reuse; silence is the failure mode
57
+ // this whole module exists to remove.
58
+ export function classifyEntry(entry, headSha) {
59
+ if (!entry || typeof entry !== 'object') return { ok: false, errors: ['ingested entry must be an object'] }
60
+ const errors = []
61
+ for (const field of ['name', 'command', 'result']) {
62
+ if (typeof entry[field] !== 'string' || !entry[field].trim()) errors.push(`ingested entry requires a non-empty ${field}`)
63
+ }
64
+ if (!INGEST_STATUSES.includes(entry.status)) errors.push(`ingested entry "${entry.name}" needs status one of ${INGEST_STATUSES.join(' | ')}`)
65
+ const sha = entry.sha || headSha
66
+ const fresh = sha === headSha
67
+ if (!fresh && !entry.reused) errors.push(`ingested entry "${entry.name}" was produced at ${sha}, not head ${headSha} — mark it reused:true with a note or re-run it`)
68
+ if (!fresh && entry.reused && !String(entry.note || '').trim()) errors.push(`reused entry "${entry.name}" requires a note saying what it covers and why reuse is honest`)
69
+ if (entry.reused && entry.status === 'Pass') errors.push(`reused entry "${entry.name}" cannot be Pass — reuse is Partial/Unverified by definition`)
70
+ return { ok: errors.length === 0, errors, fresh, sha }
71
+ }
72
+
73
+ export function validateBlock(block, { headSha, productionDiffEmpty = null } = {}) {
74
+ const errors = []
75
+ const warnings = []
76
+ if (!block || typeof block !== 'object') return { ok: false, errors: ['evidence block must be an object'], warnings }
77
+ if (block.version !== EVIDENCE_BLOCK_VERSION) errors.push(`unsupported evidence block version ${block.version}`)
78
+ if (!block.headSha) errors.push('evidence block requires a headSha stamp')
79
+ // The load-bearing check: a block that does not describe the current tree is not
80
+ // evidence about it. This is what a hand-written table could never enforce.
81
+ if (headSha && block.headSha && block.headSha !== headSha) {
82
+ errors.push(`evidence block is STALE: stamped ${block.headSha}, current HEAD is ${headSha} — re-run collect`)
83
+ }
84
+ for (const entry of block.ingested || []) {
85
+ const verdict = classifyEntry(entry, block.headSha)
86
+ errors.push(...verdict.errors)
87
+ if (verdict.ok && entry.reused) warnings.push(`reused (not fresh exact-head): ${entry.name} — ${entry.note}`)
88
+ if (verdict.ok && entry.status === 'Partial/Unverified' && !entry.reused) warnings.push(`Partial/Unverified: ${entry.name} — ${entry.note || 'no note given'}`)
89
+ }
90
+ // A WARNING and not an error, deliberately, and the distinction is #1082 AC4. A workflow
91
+ // GitHub refused to parse is red, but nothing declared it required, and turning that into
92
+ // an error here would make this block silently decide a merge policy that belongs to the
93
+ // human and to `.mergify.yml`. Silence is the only outcome that is not allowed.
94
+ for (const failure of block.ci?.startupFailures || []) {
95
+ warnings.push(
96
+ `WORKFLOW RAN ZERO JOBS at ${block.headSha}: run ${failure.runId} "${failure.name}" completed ${failure.conclusion} having executed no job at all`
97
+ + `${failure.nameEqualsPath ? ', and its name equals its file path' : ''}`
98
+ + ' — the signature of a workflow GitHub rejected at validation (#1081). It produces no check-run, so no check is red; this is not blocking here and it is not nothing.',
99
+ )
100
+ }
101
+ if (block.ci?.status === 'collected') {
102
+ for (const job of block.ci.skipped || []) warnings.push(`CI job SKIPPED (not passed): ${job}`)
103
+ if ((block.ci.failed || []).length) errors.push(`CI jobs failed at ${block.headSha}: ${block.ci.failed.join(', ')}`)
104
+ } else if (block.ci?.status) {
105
+ // Intentional: uncollected/unavailable CI is a WARNING, not an error, so
106
+ // `validate` still exits 0. This block is the *pre-spawn decidable check*, not
107
+ // the loop's exit gate — CI-green on the final HEAD is enforced separately (the
108
+ // loop exit condition), and a pre-spawn block legitimately runs before CI has
109
+ // been gathered. What must never read as green is a *collected* result with
110
+ // failures (errored above); "not yet collected" is honestly reported as such,
111
+ // never as pass, and the caller reads block.ci.status to see it.
112
+ warnings.push(`CI evidence ${block.ci.status} (not a pass — the exit gate, not this block, enforces CI-green on the final HEAD): ${block.ci.reason || 'no reason recorded'}`)
113
+ }
114
+ if (block.exactHead !== undefined) {
115
+ const verdict = validateExactHeadArtifact(block.exactHead, { expectedHeadSha: block.headSha })
116
+ errors.push(...verdict.errors.map((error) => `exact-head: ${error}`))
117
+ for (const job of block.exactHead.jobs || []) {
118
+ if (job.conclusion === 'skipped') warnings.push(`Exact-head job ${job.name} SKIPPED (not passed)`)
119
+ }
120
+ }
121
+ // Surfaced, never enforced: an empty production diff is the orchestrator's signal
122
+ // that a round is evidence-only and must not buy a full re-review round.
123
+ if (productionDiffEmpty === true) warnings.push('production diff is EMPTY since the last reviewed SHA — the first of the two evidence-lane signals; the lane also needs every surviving finding classed subject: evidence by the reviewer, and its fix runs as the constrained evidence-fix spawn, not an inline commit')
124
+ return { ok: errors.length === 0, errors, warnings }
125
+ }
126
+
127
+ export function renderMarkdown(block) {
128
+ const lines = [`<!-- generated by evidence-block.mjs — do not hand-edit; re-run collect -->`,
129
+ `**Evidence at \`${block.headSha}\`** (base \`${block.baseSha}\`, collected ${block.collectedAt})`, '']
130
+ lines.push(`- Scan \`${block.baseSha}...${block.headSha}\`: findings ${JSON.stringify(block.scan?.findingCounts ?? {})}, ${block.scan?.leadCount ?? 0} heuristic leads ${JSON.stringify(block.scan?.leadCounts ?? {})}`)
131
+ if (block.ci?.status === 'collected') {
132
+ lines.push(`- CI at this exact SHA: ${block.ci.success.length} succeeded, ${block.ci.skipped.length} skipped, ${block.ci.failed.length} failed`)
133
+ if (block.ci.skipped.length) lines.push(` - skipped (NOT passed): ${block.ci.skipped.join('; ')}`)
134
+ if (block.ci.failed.length) lines.push(` - failed: ${block.ci.failed.join('; ')}`)
135
+ } else lines.push(`- CI at this exact SHA: ${block.ci?.status || 'not collected'}${block.ci?.ciState ? ` (${block.ci.ciState})` : ''} — ${block.ci?.reason || ''}`)
136
+ for (const failure of block.ci?.startupFailures || []) {
137
+ lines.push(` - **workflow ran ZERO jobs**: run ${failure.runId} \`${failure.name}\` (${failure.conclusion})${failure.nameEqualsPath ? ', name equals file path' : ''} — rejected at validation; it produces no check-run, so nothing is red (#1081). Not blocking here.`)
138
+ }
139
+ if (block.exactHead) {
140
+ const provenance = block.exactHead.collectionMode === 'choreographed'
141
+ ? `collection choreographed; restoration ${block.exactHead.restoration?.proved ? 'proved' : 'unproved'}`
142
+ : `collection observed-existing-run; live head observed before and after provider evidence acquisition`
143
+ lines.push(`- Exact-head CI at this SHA: **${block.exactHead.summary?.outcome || 'unknown'}** — ${provenance}`)
144
+ for (const job of block.exactHead.jobs || []) {
145
+ if (job.conclusion === 'skipped') lines.push(` - ${job.name}: skipped (NOT passed)`)
146
+ }
147
+ }
148
+ if (block.productionDiff) lines.push(`- Production diff vs \`${block.productionDiff.since}\`: ${block.productionDiff.empty ? 'EMPTY (evidence-only round)' : 'non-empty'} over ${block.productionDiff.paths.join(', ')}`)
149
+ for (const entry of block.ingested || []) {
150
+ const tag = entry.reused ? ' _(reused, not fresh exact-head)_' : ''
151
+ lines.push(`- ${entry.name}: **${entry.status}** — \`${entry.command}\` → ${entry.result}${tag}${entry.note ? ` (${entry.note})` : ''}`)
152
+ }
153
+ return lines.join('\n')
154
+ }
155
+
156
+ // ---- impure edges (each isolated so the core above stays testable) ----
157
+
158
+ function git(args, cwd) {
159
+ return execFileSync('git', args, { cwd, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 }).trim()
160
+ }
161
+
162
+ // `collect` needs review-pr's scan-diff.mjs — a hop out of this skill into a sibling.
163
+ // The relative shape below covers both real layouts (a monorepo checkout, and skills
164
+ // installed side-by-side in ~/.claude/skills — Node resolves a symlinked module to its
165
+ // realpath, so a symlink farm lands in the monorepo case). It still misses when
166
+ // ensure-coverage is COPIED somewhere without review-pr, or under --preserve-symlinks.
167
+ //
168
+ // When it misses, say so and stop. The tempting alternative — carry on and record a
169
+ // scan channel of zero — is the `leadCount=0` incident with extra steps: "we did not
170
+ // scan" and "the scan found nothing" must never render identically, because the whole
171
+ // point of this file is that a reviewer can trust the numbers without re-deriving them.
172
+ export function resolveScanDiff(here, override, exists = fs.existsSync) {
173
+ if (override) {
174
+ if (exists(override)) return override
175
+ throw new Error(`--scan-diff points at ${override}, which does not exist`)
176
+ }
177
+ const candidate = path.resolve(here, '../../review-pr/scripts/scan-diff.mjs')
178
+ if (exists(candidate)) return candidate
179
+ throw new Error(
180
+ `cannot find review-pr's scan-diff.mjs (looked in ${candidate}). ` +
181
+ `The scan channel is load-bearing evidence, so this fails rather than reporting an empty scan. ` +
182
+ `Install review-pr beside ensure-coverage, or pass --scan-diff <path>.`,
183
+ )
184
+ }
185
+
186
+ export function collectScan(baseSha, headSha, cwd, scanDiffPath) {
187
+ const diff = git(['diff', `${baseSha}...${headSha}`], cwd)
188
+ const raw = execFileSync(process.execPath, [scanDiffPath], { input: diff, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 })
189
+ const parsed = JSON.parse(raw)
190
+ const leadCount = Object.values(parsed.counts || {}).reduce((sum, n) => sum + n, 0)
191
+ return { findingCounts: parsed.findingCounts || {}, findings: parsed.findings || [], leadCounts: parsed.counts || {}, leadCount }
192
+ }
193
+
194
+ // Best-effort: `gh` may be absent, unauthenticated, or the SHA may have no run yet.
195
+ // An unavailable CI channel is recorded as unavailable — never as "passed".
196
+ export function ghJson(args, cwd) {
197
+ const text = execFileSync('gh', args, { cwd, encoding: 'utf8', maxBuffer: 32 * 1024 * 1024 }).trim()
198
+ return text ? JSON.parse(text) : null
199
+ }
200
+
201
+ /**
202
+ * The CI channel, read as a QUESTION ABOUT THE SHA rather than about a run list.
203
+ *
204
+ * Two defects this closes, and they are the same defect seen from two sides (#1058, #1081):
205
+ *
206
+ * · "no workflow run found for <sha>" was the reported reason for three situations that
207
+ * call for opposite responses — the event was never delivered (waiting is futile), the
208
+ * run exists but no runner has picked it up (waiting is exactly right), and a run
209
+ * exists only because somebody dispatched it by hand (honest exact-head evidence that
210
+ * satisfies no queue condition). All three read as *still queued*. Now each carries
211
+ * `ciState` plus a reason a reader can act on without a second investigation.
212
+ * · A workflow GitHub refuses to parse contributes NOTHING to this channel: it produces
213
+ * a run with zero jobs and no check-run at all, so a job-by-job walk sees nothing and
214
+ * reports the SHA as clean. That is #1081, and it is why `startupFailures` is read
215
+ * from the run list rather than inferred from the jobs.
216
+ *
217
+ * `startupFailures` is a WARNING, never an error. It is not this block's business to
218
+ * decide that a red workflow nobody declared required blocks a merge — see head-check-set's
219
+ * header and #1082 AC4. What it must not be is silent.
220
+ *
221
+ * The run list moved from `gh run list` to the REST API for one reason: `path`. The
222
+ * corroborating half of the #1081 signature is a run whose `name` equals its file path,
223
+ * and `gh run list --json` cannot report the path.
224
+ */
225
+ export function collectCi(headSha, cwd, api = ghJson) {
226
+ try {
227
+ const runs = api(['api', `repos/{owner}/{repo}/actions/runs?head_sha=${headSha}&per_page=100`], cwd)?.workflow_runs || []
228
+ const success = [], skipped = [], failed = [], pending = []
229
+ const counted = runs.map((run) => {
230
+ let jobs = null
231
+ try { jobs = api(['api', `repos/{owner}/{repo}/actions/runs/${run.id}/jobs?per_page=100`], cwd) } catch { jobs = null }
232
+ for (const job of jobs?.jobs || []) {
233
+ if (job.conclusion === 'success') success.push(job.name)
234
+ else if (job.conclusion === 'skipped') skipped.push(job.name)
235
+ else if (job.conclusion == null) pending.push(job.name)
236
+ else failed.push(`${job.name} (${job.conclusion})`)
237
+ }
238
+ // `total_count`, never the length of a page we may have failed to read: "we did not
239
+ // look" and "it ran nothing" are the two readings this whole function separates.
240
+ return { ...run, jobCount: Number.isInteger(jobs?.total_count) ? jobs.total_count : null }
241
+ })
242
+ const observations = observeActionsRuns(counted)
243
+ const zeroJob = startupFailures(observations)
244
+ if (success.length || skipped.length || failed.length || pending.length) {
245
+ return { status: 'collected', success, skipped, failed, pending, startupFailures: zeroJob }
246
+ }
247
+ const absence = ciAbsence(observations)
248
+ return { status: 'unavailable', ciState: absence.reason, reason: `${absence.reason} for ${headSha} — ${absence.detail}`, startupFailures: zeroJob }
249
+ } catch (error) {
250
+ return { status: 'unavailable', ciState: 'unreadable', reason: String(error.message || error).split('\n')[0], startupFailures: [] }
251
+ }
252
+ }
253
+
254
+ export function productionDiffEmpty(sinceSha, headSha, sourcePaths, cwd) {
255
+ try {
256
+ git(['diff', '--quiet', `${sinceSha}..${headSha}`, '--', ...sourcePaths], cwd)
257
+ return true
258
+ } catch {
259
+ return false
260
+ }
261
+ }
262
+
263
+ export function collect({ base, head, cwd = process.cwd(), sourcePaths = [], since = null, withCi = true, ingest = [], exactHead = null, scanDiffPath, now = new Date().toISOString() }) {
264
+ const headSha = head || git(['rev-parse', 'HEAD'], cwd)
265
+ const baseSha = base || git(['merge-base', 'HEAD@{upstream}', 'HEAD'], cwd)
266
+ const block = {
267
+ version: EVIDENCE_BLOCK_VERSION,
268
+ headSha,
269
+ baseSha,
270
+ collectedAt: now,
271
+ scan: collectScan(baseSha, headSha, cwd, scanDiffPath),
272
+ ci: withCi ? collectCi(headSha, cwd) : { status: 'not-collected', reason: '--no-ci' },
273
+ ingested: ingest,
274
+ }
275
+ if (exactHead) block.exactHead = exactHead
276
+ if (since && sourcePaths.length) {
277
+ block.productionDiff = { since, paths: sourcePaths, empty: productionDiffEmpty(since, headSha, sourcePaths, cwd) }
278
+ }
279
+ return block
280
+ }
281
+
282
+ // ---- CLI ----
283
+
284
+ function parseArgs(argv) {
285
+ const positional = []
286
+ const opts = { ci: true }
287
+ for (let i = 0; i < argv.length; i++) {
288
+ const a = argv[i]
289
+ if (a === '--base') opts.base = argv[++i]
290
+ else if (a === '--head') opts.head = argv[++i]
291
+ else if (a === '--since') opts.since = argv[++i]
292
+ else if (a === '--source') opts.source = argv[++i]
293
+ else if (a === '--ingest') opts.ingest = argv[++i]
294
+ else if (a === '--exact-head') opts.exactHead = argv[++i]
295
+ else if (a === '--no-ci') opts.ci = false
296
+ else if (a === '-o' || a === '--out') opts.out = argv[++i]
297
+ else if (a === '--markdown') opts.markdown = true
298
+ else if (a === '--cwd') opts.cwd = argv[++i]
299
+ else if (a === '--scan-diff') opts.scanDiff = argv[++i]
300
+ else positional.push(a)
301
+ }
302
+ return { positional, opts }
303
+ }
304
+
305
+ function main(argv) {
306
+ const [cmd, ...rest] = argv
307
+ const { positional, opts } = parseArgs(rest)
308
+ const cwd = opts.cwd || process.cwd()
309
+ const sourcePaths = (opts.source || '').split(',').map((s) => s.trim()).filter(Boolean)
310
+ const here = path.dirname(decodeURIComponent(new URL(import.meta.url).pathname))
311
+
312
+ if (cmd === 'collect') {
313
+ if (!opts.base) { console.error('evidence-block collect: --base <sha> is required (the merge-base the PR is reviewed against)'); return 2 }
314
+ // Resolved inside `collect` only — `validate` and `render` never scan, so a repo
315
+ // without review-pr can still run the CI gate.
316
+ let scanDiffPath
317
+ try {
318
+ scanDiffPath = resolveScanDiff(here, opts.scanDiff)
319
+ } catch (e) {
320
+ console.error(`evidence-block collect: ${e.message}`)
321
+ return 2
322
+ }
323
+ let ingest = []
324
+ if (opts.ingest) {
325
+ const parsed = JSON.parse(fs.readFileSync(opts.ingest, 'utf8'))
326
+ ingest = Array.isArray(parsed) ? parsed : parsed.entries || []
327
+ }
328
+ const exactHead = opts.exactHead ? JSON.parse(fs.readFileSync(opts.exactHead, 'utf8')) : null
329
+ const block = collect({ base: opts.base, head: opts.head, cwd, sourcePaths, since: opts.since, withCi: opts.ci, ingest, exactHead, scanDiffPath })
330
+ const { ok, errors, warnings } = validateBlock(block, { headSha: block.headSha })
331
+ for (const w of warnings) console.error(`warn: ${w}`)
332
+ for (const e of errors) console.error(`error: ${e}`)
333
+ const out = opts.markdown ? renderMarkdown(block) : `${JSON.stringify(block, null, 2)}\n`
334
+ if (opts.out) fs.writeFileSync(opts.out, out.endsWith('\n') ? out : `${out}\n`)
335
+ else console.log(out)
336
+ return ok ? 0 : 1
337
+ }
338
+
339
+ if (cmd === 'validate') {
340
+ const file = positional[0]
341
+ if (!file) { console.error('evidence-block validate: <evidence.json> is required'); return 2 }
342
+ const block = JSON.parse(fs.readFileSync(file, 'utf8'))
343
+ const headSha = git(['rev-parse', 'HEAD'], cwd)
344
+ const empty = block.productionDiff?.since && sourcePaths.length
345
+ ? productionDiffEmpty(block.productionDiff.since, headSha, sourcePaths, cwd)
346
+ : null
347
+ const { ok, errors, warnings } = validateBlock(block, { headSha, productionDiffEmpty: empty })
348
+ for (const w of warnings) console.error(`warn: ${w}`)
349
+ for (const e of errors) console.error(`error: ${e}`)
350
+ if (ok) console.error(`evidence-block: valid at ${headSha}`)
351
+ return ok ? 0 : 1
352
+ }
353
+
354
+ if (cmd === 'render') {
355
+ const file = positional[0]
356
+ if (!file) { console.error('evidence-block render: <evidence.json> is required'); return 2 }
357
+ console.log(renderMarkdown(JSON.parse(fs.readFileSync(file, 'utf8'))))
358
+ return 0
359
+ }
360
+
361
+ console.error('evidence-block: unknown command. Use collect|validate|render (see header).')
362
+ return 2
363
+ }
364
+
365
+ if (isMainModule(import.meta.url)) {
366
+ process.exit(main(process.argv.slice(2)))
367
+ }
@@ -0,0 +1,269 @@
1
+ // lint-tests — static GATE lints over test files. The decidable subset of the
2
+ // skills' test-authoring doctrine, turned into deterministic checks that don't
3
+ // need an LLM: fast, free, robust, run in CI on every PR.
4
+ //
5
+ // node lint-tests.mjs --diff <base> # PR mode: only violations ADDED in base...HEAD (CI default)
6
+ // node lint-tests.mjs [--files a,b] # specific files
7
+ // node lint-tests.mjs # whole-tree scan of discovered test files
8
+ // --rules no-skip,no-fixed-timeout # run a subset (default: all gate rules)
9
+ // --json # machine output
10
+ //
11
+ // (Named lint-tests, not test-lint, so node's --test runner doesn't glob it as a
12
+ // test file via its `test-*` pattern and execute the CLI mid-suite.)
13
+ //
14
+ // SCOPE — what belongs here vs. not:
15
+ // GATE (here): syntactically decidable, ~zero false positives → safe to BLOCK.
16
+ // A noisy blocking lint gets disabled, so only near-certain rules live here.
17
+ // LEAD (NOT here): heuristic/judgment — "is this assertion meaningful?",
18
+ // over-mocking, asserting on an error page. Those stay LEADS for the reviewer
19
+ // (review-pr → scripts/scan-diff.mjs) and human/LLM judgment. Lint the
20
+ // decidable shell; leave the semantics to review.
21
+ //
22
+ // Adding a gate rule = append to RULES below (id + message + line patterns). Keep
23
+ // the false-positive bar high: if a legitimate test could trip it, it's a lead.
24
+
25
+ import fs from 'node:fs'
26
+ import path from 'node:path'
27
+ import { execSync } from 'node:child_process'
28
+ import { loadMockPolicyFromFile, extractMockTarget, classifyMockTarget } from './mock-policy-config.mjs'
29
+ import { isMainModule } from '../../engineering-runtime/scripts/main-module.mjs'
30
+
31
+ // Skip / focus / expected-fail forms. `.only`/focus silently disable every OTHER
32
+ // test; skip/xfail/fixme disable this one. All banned (see test-skipping.md).
33
+ export const SKIP_PATTERNS = [
34
+ { kind: 'skip', re: /\b(?:test|it|describe|suite|context)\s*\.\s*skip\b/ },
35
+ { kind: 'only', re: /\b(?:test|it|describe|suite|context)\s*\.\s*only\b/ },
36
+ { kind: 'fixme', re: /\b(?:test|it)\s*\.\s*fixme\b/ },
37
+ { kind: 'skip', re: /\b(?:xit|xdescribe|xtest)\s*\(/ },
38
+ { kind: 'only', re: /\b(?:fit|fdescribe|ftest)\s*\(/ },
39
+ { kind: 'skip', re: /@(?:pytest\.mark\.)?skip(?:if)?\b/ },
40
+ { kind: 'skip', re: /@unittest\.skip/ },
41
+ { kind: 'skip', re: /\bpytest\.skip\s*\(/ },
42
+ { kind: 'xfail', re: /@(?:pytest\.mark\.)?xfail\b/ },
43
+ ]
44
+
45
+ // Fixed sleeps — a timer instead of an explicit wait. The single biggest source of
46
+ // flake; doctrine is "never waitForTimeout()". Precise: these forms are ~never
47
+ // legitimate in a test (an aliased `cy.wait('@x')` or polling helper is not matched).
48
+ export const FIXED_TIMEOUT_PATTERNS = [
49
+ { kind: 'waitForTimeout', re: /\.waitForTimeout\s*\(/ },
50
+ { kind: 'cy.wait', re: /\bcy\.wait\s*\(\s*\d/ }, // numeric only; cy.wait('@alias') is fine
51
+ { kind: 'time.sleep', re: /\btime\.sleep\s*\(/ },
52
+ ]
53
+
54
+ // Empty test body — a `test`/`it` whose callback body is `{}`. A placeholder
55
+ // that passes vacuously and asserts nothing: the Missing-Assertion smell in its
56
+ // one decidable, ~zero-false-positive form. The BROADER "has statements but no
57
+ // assertion" case is NOT here — a test can assert through a helper, so a block
58
+ // with no `expect`/`assert` token may still assert; that stays a review LEAD
59
+ // (review-pr) and the test-smells doctrine ([../references/depth/test-smells.md]).
60
+ // Matches only single-line empty bodies (`() => {}` / `function () {}`), incl.
61
+ // `.skip`/`.each(...)`/`.concurrent` chains and Playwright `async ({ page })`
62
+ // fixtures; `describe`/`suite` are excluded (an empty suite is not a fake test).
63
+ // A multi-line empty body is rarer and deliberately left to the lead, not
64
+ // false-blocked.
65
+ export const EMPTY_TEST_PATTERNS = [
66
+ { kind: 'empty-test', re: /\b(?:it|test)\s*(?:\.\s*\w+\s*(?:\([^)]*\)\s*)?)?\(\s*[`'"][^`'"]*[`'"]\s*,\s*(?:async\s+)?(?:function\s*\*?\s*)?\([^)]*\)\s*(?:=>\s*)?\{\s*\}\s*\)?/ },
67
+ ]
68
+
69
+ // Swallowed assertion — an assertion matcher whose rejection is eaten by a
70
+ // chained `.catch(...)`, so the assertion can NEVER fail the test. The vacuous
71
+ // form behind the #237 login regression: `expect(locator).not.toBeVisible().catch(() => {})`
72
+ // passed whether or not the button was actually visible. Decidable and
73
+ // ~zero-false-positive: a Playwright web-first matcher (`.toXxx(...)`) directly
74
+ // chained to `.catch(` is never legitimate — a real assertion's rejection must
75
+ // surface, not be discarded. (Synchronous Jest matchers don't return a promise,
76
+ // so `.toBe(x).catch()` only appears as a deliberate swallow.) The broader
77
+ // "assertion guarded by a try/catch that re-passes" case stays a review LEAD
78
+ // (review-pr); only the directly-chained matcher form is gated here.
79
+ export const SWALLOWED_ASSERTION_PATTERNS = [
80
+ { kind: 'swallowed-catch', re: /\.to[A-Z]\w*\s*\([^)]*\)\s*\.catch\s*\(/ },
81
+ ]
82
+
83
+ export const RULES = [
84
+ { id: 'no-skip', message: 'test skipped/focused — seed/quarantine/route/delete instead (test-skipping.md)', patterns: SKIP_PATTERNS },
85
+ { id: 'no-fixed-timeout', message: 'fixed sleep — use an explicit wait (waitForResponse / expect.poll / web-first assertion), never a timer', patterns: FIXED_TIMEOUT_PATTERNS },
86
+ { id: 'no-empty-test', message: 'empty test body — a placeholder that asserts nothing (passes vacuously); write the behavior assertion or delete it (test-smells.md)', patterns: EMPTY_TEST_PATTERNS },
87
+ { id: 'no-swallowed-assertion', message: 'assertion failure swallowed by .catch() — the matcher can never fail (vacuous green); let it throw, or assert the real condition (test-smells.md)', patterns: SWALLOWED_ASSERTION_PATTERNS },
88
+ ]
89
+
90
+ // Default-on GATE for over-mocking — the decidable core of the depth axis
91
+ // (mock-policy.md), mechanized via the externals-allowlist inversion
92
+ // (mock-policy-config.mjs). A `match`-style rule (not a line pattern): it pulls
93
+ // the mock target, exempts declared externals, and flags a mock
94
+ // of a first-party internal seam. ZERO-TOLERANCE: default-on, no waiver, and
95
+ // never silently inert — over-mock is a hard gate (mock-policy.md), not an
96
+ // opt-in. `coverage.config.json` only *declares topology* (a monorepo's own
97
+ // package scope, generated dirs to exempt); it cannot turn the gate off. With
98
+ // no config the rule runs on conventional `@/`/`~/` defaults. Run it diff-scoped
99
+ // in the PR lane (catch new seams) AND whole-tree (no inherited seam is
100
+ // grandfathered — the tree stays red until they are removed).
101
+ export const MOCK_INTERNAL_SEAM_RULE = {
102
+ id: 'mock-internal-seam',
103
+ defaultOn: true,
104
+ message: 'mocks an internal seam — real internals, faked externals (mock-policy.md): replace it with a real-boundary test, or fake the external instead. No waivers; no inherited grace.',
105
+ match(raw, code, ctx) {
106
+ const target = extractMockTarget(code)
107
+ if (!target) return null
108
+ // classifyMockTarget falls back to DEFAULT_MOCK_POLICY when ctx has no
109
+ // policy, so the gate can never be silently disabled by omitting config.
110
+ const { internal, reason } = classifyMockTarget(target, ctx && ctx.policy)
111
+ if (!internal) return null
112
+ return { kind: 'internal-seam', detail: target, reason }
113
+ },
114
+ }
115
+
116
+ // Every known rule. RULES is the original authoring-gate set; ALL_RULES is the
117
+ // full registry. mock-internal-seam is default-on (defaultOn:true), so it is in
118
+ // the default selection too — it is a standard gate now, not an opt-in.
119
+ export const ALL_RULES = [...RULES, MOCK_INTERNAL_SEAM_RULE]
120
+
121
+ export function selectRules(ruleIds) {
122
+ if (ruleIds && ruleIds.length > 0) {
123
+ const set = new Set(ruleIds)
124
+ return ALL_RULES.filter((r) => set.has(r.id))
125
+ }
126
+ // Default: every rule that isn't explicitly opted out (defaultOn:false).
127
+ return ALL_RULES.filter((r) => r.defaultOn !== false)
128
+ }
129
+
130
+ // Strip line comments so a doc line or a commented-out example is not flagged.
131
+ // Best-effort (ignores strings/block comments) — live code has no leading `//`.
132
+ export function stripComment(line) {
133
+ return line.replace(/\/\/.*$/, '').replace(/(^|\s)#.*$/, '$1')
134
+ }
135
+
136
+ // Skips/timeouts only matter in tests; linting app code would false-flag a
137
+ // legitimate `.skip()`/`.only` array method or a real `sleep`.
138
+ export function isTestFile(p) {
139
+ if (!p) return false
140
+ return /\.(spec|test)\.[cm]?[jt]sx?$/.test(p)
141
+ || /(^|\/)(e2e|tests?|__tests__)\//.test(p)
142
+ || /(^|\/)test_[^/]*\.py$/.test(p)
143
+ || /_test\.py$/.test(p)
144
+ }
145
+
146
+ // Pure: run the selected rules over one line → findings (≤ one per rule). `ctx`
147
+ // carries the resolved mock policy (declared topology) for `match`-style rules;
148
+ // pattern rules ignore it. A `match` rule sees the raw line and the
149
+ // comment-stripped code (so a commented-out example never trips).
150
+ function scanLine(file, raw, lineNo, rules, ctx) {
151
+ const code = stripComment(raw)
152
+ const out = []
153
+ for (const rule of rules) {
154
+ if (typeof rule.match === 'function') {
155
+ const hit = rule.match(raw, code, ctx)
156
+ if (hit) {
157
+ out.push({
158
+ file, line: lineNo, rule: rule.id, kind: hit.kind || rule.id,
159
+ snippet: raw.trim().slice(0, 120),
160
+ ...(hit.detail ? { detail: hit.detail } : {}),
161
+ ...(hit.reason ? { reason: hit.reason } : {}),
162
+ })
163
+ }
164
+ continue
165
+ }
166
+ const hit = rule.patterns.find((p) => p.re.test(code))
167
+ if (hit) out.push({ file, line: lineNo, rule: rule.id, kind: hit.kind || rule.id, snippet: raw.trim().slice(0, 120) })
168
+ }
169
+ return out
170
+ }
171
+
172
+ export function scanLines(file, lines, ruleIds, ctx) {
173
+ const rules = selectRules(ruleIds)
174
+ const out = []
175
+ lines.forEach((raw, i) => out.push(...scanLine(file, raw, i + 1, rules, ctx)))
176
+ return out
177
+ }
178
+
179
+ export function scanText(file, text, ruleIds, ctx) {
180
+ return scanLines(file, (text || '').split(/\r?\n/), ruleIds, ctx)
181
+ }
182
+
183
+ // Pure: scan a unified diff, flagging only ADDED lines in test files (inherited
184
+ // violations on context lines are left alone — review-pr diff-discipline).
185
+ export function scanDiff(diffText, ruleIds, ctx) {
186
+ const rules = selectRules(ruleIds)
187
+ const out = []
188
+ let file = null
189
+ let newLine = 0
190
+ for (const raw of (diffText || '').split(/\r?\n/)) {
191
+ if (raw.startsWith('+++ ')) {
192
+ const m = raw.match(/^\+\+\+ (?:b\/)?(.+)$/)
193
+ file = m && m[1] !== '/dev/null' ? m[1] : null
194
+ continue
195
+ }
196
+ if (raw.startsWith('@@')) {
197
+ const m = raw.match(/\+(\d+)/)
198
+ newLine = m ? parseInt(m[1], 10) : 0
199
+ continue
200
+ }
201
+ if (raw.startsWith('+')) {
202
+ if (isTestFile(file)) out.push(...scanLine(file, raw.slice(1), newLine, rules, ctx))
203
+ newLine++
204
+ } else if (raw.startsWith('-') || raw.startsWith('\\')) {
205
+ // removed line / "No newline" marker — do not advance the new-file counter
206
+ } else {
207
+ newLine++ // context line
208
+ }
209
+ }
210
+ return out
211
+ }
212
+
213
+ // ---- CLI ----
214
+
215
+ function discover(dir, acc = []) {
216
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
217
+ if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue
218
+ const full = path.join(dir, entry.name)
219
+ if (entry.isDirectory()) discover(full, acc)
220
+ else if (isTestFile(full)) acc.push(full)
221
+ }
222
+ return acc
223
+ }
224
+
225
+ export function runCli(argv, { name = 'lint-tests' } = {}) {
226
+ const flags = {}
227
+ for (let i = 0; i < argv.length; i++) {
228
+ const a = argv[i]
229
+ if (a === '--json') flags.json = true
230
+ else if (a === '--diff') flags.diff = argv[++i]
231
+ else if (a === '--files') flags.files = argv[++i]
232
+ else if (a === '--rules') flags.rules = argv[++i].split(',').map((s) => s.trim()).filter(Boolean)
233
+ else if (a === '--config') flags.config = argv[++i]
234
+ }
235
+ const ruleIds = flags.rules || null
236
+ // Resolve the project's mock policy once — this only declares *topology*
237
+ // (first-party scopes, exempt externals); it cannot disable the gate.
238
+ // `--config <file>` reads THAT file; otherwise discover coverage.config.json
239
+ // in cwd; with neither, the conventional `@/`/`~/` defaults apply.
240
+ const ctx = { policy: loadMockPolicyFromFile(flags.config) }
241
+
242
+ let findings = []
243
+ if (flags.diff) {
244
+ const diff = execSync(`git diff ${flags.diff}...HEAD`, { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 })
245
+ findings = scanDiff(diff, ruleIds, ctx)
246
+ } else {
247
+ const files = flags.files
248
+ ? flags.files.split(',').map((s) => s.trim()).filter(Boolean)
249
+ : discover('.')
250
+ for (const f of files) {
251
+ if (fs.existsSync(f)) findings.push(...scanText(f, fs.readFileSync(f, 'utf8'), ruleIds, ctx))
252
+ }
253
+ }
254
+
255
+ if (flags.json) {
256
+ console.log(JSON.stringify({ ok: findings.length === 0, findings }, null, 2))
257
+ } else if (findings.length === 0) {
258
+ console.log(`${name}: ✓ no violations` + (flags.diff ? ' introduced in this change' : ' in scanned test files'))
259
+ } else {
260
+ console.log(`${name}: ✗ ${findings.length} violation(s):\n`)
261
+ for (const f of findings) {
262
+ const why = f.detail ? ` → ${f.detail}${f.reason ? ` (${f.reason})` : ''}` : ''
263
+ console.log(` ${f.file}:${f.line} [${f.rule}/${f.kind}] ${f.snippet}${why}`)
264
+ }
265
+ }
266
+ return findings.length === 0 ? 0 : 1
267
+ }
268
+
269
+ if (isMainModule(import.meta.url)) process.exit(runCli(process.argv.slice(2), { name: 'lint-tests' }))