@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,171 @@
1
+ // Presentation-axis instruments for e2e-test (issue #195).
2
+ //
3
+ // Real-rendered-page checks — import into a Playwright spec and tag it
4
+ // `@covers <route>` / `@level presentation:spatial` so the Coverage Ledger
5
+ // resolves it. The spine ships the Spatial/overflow invariant; later slices add
6
+ // perceivability (contrast), reachability, and temporal (layout shift).
7
+ //
8
+ // These drive a REAL browser. A DOM-only / JSDOM equivalent cannot see layout and
9
+ // is graded `Over-mocked` by the ledger — that is intentional.
10
+
11
+ // The condition-matrix floor: every browser route is checked at these viewports.
12
+ // Mirrors PRESENTATION_VIEWPORT_FLOOR in ensure-coverage's coverage-ledger.mjs.
13
+ export const PRESENTATION_VIEWPORT_FLOOR = [
14
+ { name: 'mobile', width: 375, height: 812 },
15
+ { name: 'desktop', width: 1280, height: 800 },
16
+ ]
17
+
18
+ // Pure predicate (no browser) — given element geometry and the viewport width,
19
+ // return the elements that overflow horizontally. Extracted so the detection
20
+ // logic is unit-testable without a browser; the Playwright wrapper collects the
21
+ // geometry and delegates here.
22
+ //
23
+ // elements: [{ selector, left, right, width, height }]
24
+ export function findHorizontalOverflow(elements, viewportWidth, { tolerancePx = 1, ignore = [] } = {}) {
25
+ const offenders = []
26
+ for (const el of elements) {
27
+ if (!el.width || !el.height) continue // not rendered
28
+ const overflowsRight = el.right - tolerancePx > viewportWidth
29
+ const overflowsLeft = el.left < -tolerancePx
30
+ if (!overflowsRight && !overflowsLeft) continue
31
+ if (ignore.some((ig) => (el.selector || '').includes(ig))) continue
32
+ offenders.push({
33
+ selector: el.selector,
34
+ overflowPx: Math.round(overflowsRight ? el.right - viewportWidth : -el.left),
35
+ })
36
+ }
37
+ return offenders
38
+ }
39
+
40
+ // In-browser collector, serialized into page.evaluate. Returns the viewport width
41
+ // and one record per body descendant.
42
+ function collectGeometry() {
43
+ const vw = document.documentElement.clientWidth
44
+ const sel = (el) => {
45
+ if (el.id) return '#' + el.id
46
+ let s = el.tagName.toLowerCase()
47
+ if (typeof el.className === 'string' && el.className.trim()) {
48
+ s += '.' + el.className.trim().split(/\s+/).join('.')
49
+ }
50
+ return s
51
+ }
52
+ const elements = [...document.querySelectorAll('body *')].map((el) => {
53
+ const r = el.getBoundingClientRect()
54
+ return { selector: sel(el), left: r.left, right: r.right, width: r.width, height: r.height }
55
+ })
56
+ return { vw, elements }
57
+ }
58
+
59
+ // Assert no unintended horizontal overflow at each floor viewport. Throws with the
60
+ // offending selectors + overflow amounts. `ignore` suppresses intentional cases by
61
+ // substring match on the selector (curate sparingly).
62
+ export async function assertNoHorizontalOverflow(page, { viewports = PRESENTATION_VIEWPORT_FLOOR, tolerancePx = 1, ignore = [] } = {}) {
63
+ const offenders = []
64
+ for (const vp of viewports) {
65
+ await page.setViewportSize({ width: vp.width, height: vp.height })
66
+ // Let layout settle (fonts/images) — real condition, not a fixed sleep.
67
+ await page.waitForLoadState('networkidle').catch(() => {})
68
+ const { vw, elements } = await page.evaluate(collectGeometry)
69
+ for (const o of findHorizontalOverflow(elements, vw, { tolerancePx, ignore })) {
70
+ offenders.push({ viewport: vp.name, width: vp.width, ...o })
71
+ }
72
+ }
73
+ if (offenders.length) {
74
+ const lines = offenders.map((o) => ` [${o.viewport} ${o.width}px] ${o.selector} overflows by ${o.overflowPx}px`)
75
+ throw new Error(`presentation:spatial — horizontal overflow detected:\n${lines.join('\n')}`)
76
+ }
77
+ }
78
+
79
+ // ---------- overlap detection (part of presentation:spatial) ----------
80
+
81
+ // Pure AABB intersection test with a tolerance (sub-pixel touching is not overlap).
82
+ export function rectsIntersect(a, b, tolerancePx = 1) {
83
+ return (
84
+ a.left < b.right - tolerancePx &&
85
+ a.right > b.left + tolerancePx &&
86
+ a.top < b.bottom - tolerancePx &&
87
+ a.bottom > b.top + tolerancePx
88
+ )
89
+ }
90
+
91
+ // Pure: find intersecting element pairs. `elements` is a flat list of rendered
92
+ // elements with geometry + an `interactive` flag; only sibling-ish *content*
93
+ // overlaps matter, so a pair where one element CONTAINS the other (nesting) is
94
+ // skipped — nesting is normal, collision is not.
95
+ function contains(outer, inner, tol = 1) {
96
+ return outer.left - tol <= inner.left && outer.right + tol >= inner.right &&
97
+ outer.top - tol <= inner.top && outer.bottom + tol >= inner.bottom
98
+ }
99
+ export function findOverlaps(elements, { tolerancePx = 1 } = {}) {
100
+ const els = elements.filter((e) => (e.rect?.width || 0) > 0 && (e.rect?.height || 0) > 0)
101
+ const pairs = []
102
+ for (let i = 0; i < els.length; i++) {
103
+ for (let j = i + 1; j < els.length; j++) {
104
+ const a = els[i], b = els[j]
105
+ if (!rectsIntersect(a.rect, b.rect, tolerancePx)) continue
106
+ if (contains(a.rect, b.rect, tolerancePx) || contains(b.rect, a.rect, tolerancePx)) continue // nesting
107
+ pairs.push({ a: a.selector, b: b.selector, aInteractive: !!a.interactive, bInteractive: !!b.interactive })
108
+ }
109
+ }
110
+ return pairs
111
+ }
112
+
113
+ // Pure: apply the GATED allowlist to overlap pairs. An allowlist entry needs a
114
+ // `match` (substring tested against either selector), a `category`, and a `reason`.
115
+ // GATE (mirrors the Surface Baseline Contract's exclusion gate): an entry may NOT
116
+ // suppress a pair where an **interactive** element is occluded — you cannot
117
+ // allowlist a reachable control into invisibility. Returns reported (still failing),
118
+ // suppressed (census), and rejected (bad allowlist usage — also failing).
119
+ export function applyOverlapAllowlist(pairs, allowlist = []) {
120
+ const reported = [], suppressed = [], rejected = []
121
+ for (const p of pairs) {
122
+ const entry = allowlist.find((e) => e.match && (p.a.includes(e.match) || p.b.includes(e.match)))
123
+ if (!entry) { reported.push(p); continue }
124
+ if (!entry.category || !entry.reason) { rejected.push({ ...p, why: 'allowlist entry needs category + reason' }); continue }
125
+ if (p.aInteractive || p.bInteractive) { rejected.push({ ...p, why: `cannot allowlist an overlap that occludes an interactive element (${entry.category})` }); continue }
126
+ suppressed.push({ ...p, category: entry.category, reason: entry.reason })
127
+ }
128
+ return { reported, suppressed, rejected }
129
+ }
130
+
131
+ // In-browser collector for overlap: geometry + interactivity per element.
132
+ function collectOverlapGeometry() {
133
+ const sel = (el) => {
134
+ if (el.id) return '#' + el.id
135
+ let s = el.tagName.toLowerCase()
136
+ if (typeof el.className === 'string' && el.className.trim()) s += '.' + el.className.trim().split(/\s+/).join('.')
137
+ return s
138
+ }
139
+ const INTERACTIVE = 'a,button,input,select,textarea,[role="button"],[role="link"],[tabindex]'
140
+ return [...document.querySelectorAll('body *')].map((el) => {
141
+ const r = el.getBoundingClientRect()
142
+ return {
143
+ selector: sel(el),
144
+ rect: { left: r.left, top: r.top, right: r.right, bottom: r.bottom, width: r.width, height: r.height },
145
+ interactive: el.matches(INTERACTIVE),
146
+ }
147
+ })
148
+ }
149
+
150
+ // Assert no unintended element overlap. Intentional z-stacks (carousels, badges,
151
+ // sticky headers) are suppressed only via the gated `allowlist`
152
+ // (`[{ match, category, reason }]`); the suppressions are returned as a visible
153
+ // census. Throws on real overlaps and on rejected (ungated/interactive) allowlist use.
154
+ export async function assertNoOverlap(page, { selector, tolerancePx = 1, allowlist = [], viewports = PRESENTATION_VIEWPORT_FLOOR } = {}) {
155
+ const failures = [], census = []
156
+ for (const vp of viewports) {
157
+ await page.setViewportSize({ width: vp.width, height: vp.height })
158
+ await page.waitForLoadState('networkidle').catch(() => {})
159
+ let elements = await page.evaluate(collectOverlapGeometry)
160
+ if (selector) elements = elements.filter((e) => e.selector.includes(selector))
161
+ const pairs = findOverlaps(elements, { tolerancePx })
162
+ const { reported, suppressed, rejected } = applyOverlapAllowlist(pairs, allowlist)
163
+ for (const r of [...reported, ...rejected]) failures.push({ viewport: vp.name, ...r })
164
+ for (const s of suppressed) census.push({ viewport: vp.name, ...s })
165
+ }
166
+ if (failures.length) {
167
+ const lines = failures.map((f) => ` [${f.viewport}] ${f.a} ⨯ ${f.b}${f.why ? ` — ${f.why}` : ''}`)
168
+ throw new Error(`presentation:spatial — element overlap detected:\n${lines.join('\n')}`)
169
+ }
170
+ return { census } // visible record of intentional, gated suppressions
171
+ }
@@ -0,0 +1,179 @@
1
+ // Presentation-axis Perceivability instrument for e2e-test (issue #196).
2
+ //
3
+ // "Can a human actually see/distinguish this control" — and its negative form,
4
+ // "is something that must be hidden actually gone" (privilege leak, stale item).
5
+ //
6
+ // Positive form uses axe-core's `color-contrast` rule against the REAL rendered
7
+ // page. Tag the spec `@covers <route>` / `@level presentation:perceivability`.
8
+ // A DOM-only / JSDOM equivalent is graded Over-mocked by the ledger.
9
+ //
10
+ // Abstain doctrine: axe abstains ("incomplete") on text over a background-image /
11
+ // gradient / partially-obscured element — exactly the case that hides an invisible
12
+ // control. An abstain is NOT a silent pass: it surfaces as a blocking AbstainError.
13
+ // Resolve it by reviewer attestation (verified: true, after manual check) OR by
14
+ // escalating to a stronger instrument (screenshot / AI-vision triage). AI-vision is
15
+ // triage-only — it may flag for human attestation but can never by itself promote
16
+ // an obligation to Covered (run-to-run non-determinism).
17
+
18
+ // Distinguishable error so callers/CI can tell an abstain from a hard violation.
19
+ export class AbstainError extends Error {
20
+ constructor(message, details) {
21
+ super(message)
22
+ this.name = 'AbstainError'
23
+ this.abstain = true
24
+ this.details = details
25
+ }
26
+ }
27
+
28
+ // ---------- pure WCAG contrast math (testable, no browser) ----------
29
+
30
+ // sRGB channel [0..255] → linearized component for relative luminance.
31
+ function linearize(c) {
32
+ const s = c / 255
33
+ return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4
34
+ }
35
+
36
+ // WCAG relative luminance of an [r,g,b] triple.
37
+ export function relativeLuminance([r, g, b]) {
38
+ return 0.2126 * linearize(r) + 0.7152 * linearize(g) + 0.0722 * linearize(b)
39
+ }
40
+
41
+ // WCAG contrast ratio between two [r,g,b] colors → 1..21.
42
+ export function contrastRatio(rgb1, rgb2) {
43
+ const l1 = relativeLuminance(rgb1)
44
+ const l2 = relativeLuminance(rgb2)
45
+ const [hi, lo] = l1 >= l2 ? [l1, l2] : [l2, l1]
46
+ return (hi + 0.05) / (lo + 0.05)
47
+ }
48
+
49
+ // Does fg-on-bg meet the WCAG threshold? large text & non-text UI components use
50
+ // 3:1; normal text uses 4.5:1 (AA). Returns the ratio, threshold, and pass.
51
+ export function meetsContrast(fg, bg, { large = false, nonText = false } = {}) {
52
+ const ratio = contrastRatio(fg, bg)
53
+ const threshold = nonText || large ? 3 : 4.5
54
+ return { ratio: Math.round(ratio * 100) / 100, threshold, pass: ratio >= threshold }
55
+ }
56
+
57
+ // ---------- pure axe-result classifier (testable, no browser) ----------
58
+
59
+ // Map an axe-core run to one of three Presentation outcomes for color-contrast.
60
+ // `pass` → satisfied; `fail` → hard violation; `abstain` → axe could not decide
61
+ // (blocking Unverified until attested/escalated).
62
+ export function classifyAxeContrast(axeResults = {}) {
63
+ const pick = (arr) => (arr || []).filter((r) => r.id === 'color-contrast')
64
+ const violations = pick(axeResults.violations)
65
+ const incomplete = pick(axeResults.incomplete)
66
+ if (violations.length) {
67
+ const nodes = violations.flatMap((v) => v.nodes || [])
68
+ return { status: 'fail', count: nodes.length, nodes }
69
+ }
70
+ if (incomplete.length) {
71
+ const nodes = incomplete.flatMap((v) => v.nodes || [])
72
+ return { status: 'abstain', count: nodes.length, nodes }
73
+ }
74
+ return { status: 'pass', count: 0, nodes: [] }
75
+ }
76
+
77
+ // ---------- pure visibility predicate (testable, no browser) ----------
78
+
79
+ // Is an element effectively hidden from a sighted user? Used by the negative form
80
+ // (a privilege-leaked control must be truly gone, not just visually small) and to
81
+ // AVOID false-flagging intentionally non-rendered state. `style` is the element's
82
+ // computed style snapshot; `rect` its bounding box.
83
+ export function isEffectivelyHidden({ style = {}, rect = {}, ariaHidden = false } = {}) {
84
+ if (style.display === 'none') return true
85
+ if (style.visibility === 'hidden' || style.visibility === 'collapse') return true
86
+ if (Number(style.opacity) === 0) return true
87
+ if (ariaHidden) return true
88
+ if ((rect.width || 0) === 0 || (rect.height || 0) === 0) return true
89
+ return false
90
+ }
91
+
92
+ // State-aware: a disabled control is *intended* to be low-contrast (WCAG exempts
93
+ // it), so a contrast check must skip it rather than false-flag.
94
+ //
95
+ // NOTE on where this runs: `assertContrast` delegates to axe-core, which already
96
+ // skips disabled controls natively — so the primary path does NOT call this. This
97
+ // predicate (and `assessElementContrast` below) is the **manual / escalation path**:
98
+ // when axe abstains and a reviewer escalates to per-element checking, they compute
99
+ // contrast for the specific element here, and this is where the disabled-exemption
100
+ // is enforced.
101
+ export function isExemptFromContrast({ disabled = false, ariaDisabled = false } = {}) {
102
+ return disabled || ariaDisabled
103
+ }
104
+
105
+ // Manual / escalation per-element contrast assessment. Composes the disabled
106
+ // exemption with the WCAG threshold check → 'exempt' | 'pass' | 'fail'. Pure and
107
+ // testable; used when axe abstains and a reviewer resolves a specific element by
108
+ // reading its computed fg/bg colors.
109
+ export function assessElementContrast(fg, bg, { large = false, nonText = false, disabled = false, ariaDisabled = false } = {}) {
110
+ if (isExemptFromContrast({ disabled, ariaDisabled })) return { status: 'exempt' }
111
+ const m = meetsContrast(fg, bg, { large, nonText })
112
+ return { status: m.pass ? 'pass' : 'fail', ratio: m.ratio, threshold: m.threshold }
113
+ }
114
+
115
+ // ---------- Playwright wrappers (need a real browser) ----------
116
+
117
+ // Assert the rendered page has no color-contrast violations. Requires
118
+ // @axe-core/playwright (peer dep). On a hard violation → throws Error; on an axe
119
+ // abstain ("incomplete") → throws AbstainError (blocking, resolve by attest/escalate).
120
+ export async function assertContrast(page, { axeBuilder, include, exclude } = {}) {
121
+ if (!axeBuilder) {
122
+ // Lazy import so the pure exports above are usable without the peer dep.
123
+ const mod = await import('@axe-core/playwright')
124
+ const AxeBuilder = mod.default || mod.AxeBuilder
125
+ axeBuilder = new AxeBuilder({ page })
126
+ }
127
+ let builder = axeBuilder.withRules(['color-contrast'])
128
+ if (include) builder = builder.include(include)
129
+ if (exclude) builder = builder.exclude(exclude)
130
+ const results = await builder.analyze()
131
+ const c = classifyAxeContrast(results)
132
+ if (c.status === 'fail') {
133
+ const where = c.nodes.map((n) => ' ' + (n.target || []).join(' ')).join('\n')
134
+ throw new Error(`presentation:perceivability — ${c.count} contrast violation(s):\n${where}`)
135
+ }
136
+ if (c.status === 'abstain') {
137
+ const where = c.nodes.map((n) => ' ' + (n.target || []).join(' ')).join('\n')
138
+ throw new AbstainError(
139
+ `presentation:perceivability — axe could not decide contrast for ${c.count} element(s) ` +
140
+ `(text over image/gradient/obscured). Blocking until reviewer attests or escalates ` +
141
+ `to a screenshot/AI-vision triage:\n${where}`,
142
+ c,
143
+ )
144
+ }
145
+ return c // { status: 'pass' }
146
+ }
147
+
148
+ // Negative form: a control gated to a role must NOT be present/visible for a user
149
+ // lacking that role (a visual privilege leak). `locator` should resolve to the
150
+ // gated control; asserts it is absent or effectively hidden. Uses the pure
151
+ // `isEffectivelyHidden` predicate against the element's computed style + box, which
152
+ // catches cases Playwright's `isVisible()` misses (e.g. opacity:0 with non-zero
153
+ // size — visually invisible but still a leak if it were the positive case).
154
+ export async function assertHiddenFromWrongRole(page, locator) {
155
+ const count = await locator.count()
156
+ if (count === 0) return // not rendered — good
157
+ const snap = await locator.first().evaluate((el) => {
158
+ const cs = getComputedStyle(el)
159
+ const r = el.getBoundingClientRect()
160
+ return {
161
+ style: { display: cs.display, visibility: cs.visibility, opacity: cs.opacity },
162
+ rect: { width: r.width, height: r.height },
163
+ ariaHidden: el.getAttribute('aria-hidden') === 'true',
164
+ }
165
+ })
166
+ if (!isEffectivelyHidden(snap)) {
167
+ throw new Error(`presentation:perceivability (negative) — privilege-gated control is visible to a user without the role: ${locator}`)
168
+ }
169
+ }
170
+
171
+ // Negative form: after a delete/optimistic mutation, the item must be gone from the
172
+ // rendered page (no ghost/stale row).
173
+ export async function assertRemovedAfterMutation(page, locator, { timeout = 5000 } = {}) {
174
+ await locator.waitFor({ state: 'detached', timeout }).catch(() => {})
175
+ const count = await locator.count()
176
+ if (count !== 0) {
177
+ throw new Error(`presentation:perceivability (negative) — item still rendered after its mutation (stale/ghost): ${locator}`)
178
+ }
179
+ }
@@ -0,0 +1,154 @@
1
+ // Presentation-axis Reachability & Affordance instrument for e2e-test (issue #197).
2
+ //
3
+ // "Can a user GET to a control and tell it's actionable" — plus the negative form,
4
+ // "things that must be blocked are blocked" (modal focus-trap, no click-through,
5
+ // wrong-role route not reachable). Tag specs `@level presentation:reachability`.
6
+ //
7
+ // Distinct from ensure-coverage's existing Route Reachability Protocol, which is
8
+ // GUARD-CHAIN reachability ("can the URL load past middleware/auth"). This is
9
+ // NAVIGATIONAL reachability ("can a user click their way here from an entry point").
10
+ // A route can be guard-reachable yet navigation-orphaned (nothing links to it).
11
+ //
12
+ // Affordance↔impeccable boundary: the DETERMINISTIC affordance checks live here
13
+ // (focus-indicator presence, ≥44px target size, nav-orphan). Holistic a11y
14
+ // resilience and the *aesthetics* of focus styling stay in the `impeccable` skill
15
+ // (its `harden` reference). This instrument proves the cue EXISTS; impeccable judges
16
+ // whether it's good. See impeccable/reference/harden.md.
17
+
18
+ // ---------- pure cores (no browser) ----------
19
+
20
+ // Navigational orphans = routes the app DEFINES (manifest) minus routes REACHABLE
21
+ // by crawling nav from an entry point. `ignore` suppresses intentionally
22
+ // unlinked-but-valid routes (deep-link targets, auth callbacks) by exact id or
23
+ // prefix ending in '*'.
24
+ export function findNavOrphans(manifestRoutes, reachableRoutes, { ignore = [] } = {}) {
25
+ const reachable = new Set(reachableRoutes)
26
+ const matchesIgnore = (r) => ignore.some((ig) => (ig.endsWith('*') ? r.startsWith(ig.slice(0, -1)) : r === ig))
27
+ return manifestRoutes.filter((r) => !reachable.has(r) && !matchesIgnore(r))
28
+ }
29
+
30
+ // Minimum interactive target size (WCAG 2.5.5 / 2.5.8). Returns pass + which
31
+ // dimension is short. Default 44px (24px is the relaxed AA floor — pass min).
32
+ export function meetsTargetSize({ width = 0, height = 0 } = {}, min = 44) {
33
+ const short = []
34
+ if (width < min) short.push('width')
35
+ if (height < min) short.push('height')
36
+ return { pass: short.length === 0, min, width, height, short }
37
+ }
38
+
39
+ // A focusable control must show a VISIBLE focus indicator: focusing it must change
40
+ // the outline or box-shadow vs its blurred state. Pure comparison of the two
41
+ // computed-style snapshots; absence of any change == no visible focus ring.
42
+ export function hasVisibleFocusIndicator(blurred = {}, focused = {}) {
43
+ const noOutline = (s) =>
44
+ !s.outlineStyle || s.outlineStyle === 'none' || parseFloat(s.outlineWidth || '0') === 0
45
+ // An outline change only counts if the FOCUSED outline is actually visible — a
46
+ // 0px-width color-only change (e.g. `0px solid red` → `0px solid blue`) paints
47
+ // nothing and must not pass.
48
+ const focusedHasOutline = !noOutline(focused)
49
+ const outlineVisibleAndChanged = focusedHasOutline && (
50
+ noOutline(blurred) ||
51
+ (blurred.outlineColor || '') !== (focused.outlineColor || '') ||
52
+ (blurred.outlineWidth || '') !== (focused.outlineWidth || '') ||
53
+ (blurred.outlineStyle || '') !== (focused.outlineStyle || '')
54
+ )
55
+ const shadowChanged = (blurred.boxShadow || 'none') !== (focused.boxShadow || 'none')
56
+ return Boolean(outlineVisibleAndChanged || shadowChanged)
57
+ }
58
+
59
+ // ---------- Playwright wrappers (need a real browser) ----------
60
+
61
+ // BFS-crawl the navigational graph from `start`, clicking same-origin links the
62
+ // rendered nav exposes, and return the set of reachable pathnames. Drives a real
63
+ // browser so JS-rendered / hover/scroll-revealed nav is exercised. Seed an
64
+ // authenticated context for guarded nav.
65
+ export async function crawlReachableRoutes(page, { start = '/', maxPages = 200 } = {}) {
66
+ const origin = new URL(start, page.url() || 'http://localhost').origin
67
+ const seen = new Set()
68
+ const queue = [new URL(start, origin).pathname]
69
+ while (queue.length && seen.size < maxPages) {
70
+ const path = queue.shift()
71
+ if (seen.has(path)) continue
72
+ seen.add(path)
73
+ await page.goto(new URL(path, origin).href).catch(() => {})
74
+ const hrefs = await page.$$eval('a[href]', (as) => as.map((a) => a.getAttribute('href') || ''))
75
+ for (const h of hrefs) {
76
+ let u
77
+ try { u = new URL(h, origin) } catch { continue }
78
+ if (u.origin !== origin) continue
79
+ if (!seen.has(u.pathname)) queue.push(u.pathname)
80
+ }
81
+ }
82
+ return [...seen]
83
+ }
84
+
85
+ // Assert no navigational orphans: every manifest route is reachable by crawling.
86
+ export async function assertNoNavOrphans(page, manifestRoutes, { start = '/', ignore = [] } = {}) {
87
+ const reachable = await crawlReachableRoutes(page, { start })
88
+ const orphans = findNavOrphans(manifestRoutes, reachable, { ignore })
89
+ if (orphans.length) {
90
+ throw new Error(`presentation:reachability — ${orphans.length} route(s) reachable by URL but linked from no nav:\n${orphans.map((o) => ' ' + o).join('\n')}`)
91
+ }
92
+ }
93
+
94
+ // Assert every matched interactive control meets the minimum target size.
95
+ export async function assertTargetSizes(page, selector = 'a, button, [role="button"], input, select', { min = 44 } = {}) {
96
+ const boxes = await page.$$eval(selector, (els) => els
97
+ .filter((el) => el.offsetParent !== null)
98
+ .map((el) => { const r = el.getBoundingClientRect(); return { tag: el.tagName.toLowerCase(), id: el.id, width: r.width, height: r.height } }))
99
+ const small = boxes.map((b) => ({ ...b, ...meetsTargetSize(b, min) })).filter((b) => !b.pass)
100
+ if (small.length) {
101
+ throw new Error(`presentation:reachability — ${small.length} control(s) below ${min}px target:\n${small.map((s) => ` ${s.tag}${s.id ? '#' + s.id : ''} ${Math.round(s.width)}x${Math.round(s.height)}`).join('\n')}`)
102
+ }
103
+ }
104
+
105
+ // Assert a control shows a visible focus indicator when focused.
106
+ export async function assertFocusVisible(page, locator) {
107
+ const read = () => locator.evaluate((el) => {
108
+ const s = getComputedStyle(el)
109
+ return { outlineStyle: s.outlineStyle, outlineWidth: s.outlineWidth, outlineColor: s.outlineColor, boxShadow: s.boxShadow }
110
+ })
111
+ const blurred = await read()
112
+ await locator.focus()
113
+ const focused = await read()
114
+ if (!hasVisibleFocusIndicator(blurred, focused)) {
115
+ throw new Error(`presentation:reachability (affordance) — control shows no visible focus indicator on focus: ${locator}`)
116
+ }
117
+ }
118
+
119
+ // Negative form: a modal must trap focus — tabbing never moves focus to an element
120
+ // outside the modal (no escape to the background). Containment is checked on the
121
+ // modal element the locator resolves to (no fragile re-derived global selector).
122
+ export async function assertModalTrapsFocus(page, modalLocator, { tabs = 12 } = {}) {
123
+ for (let i = 0; i < tabs; i++) {
124
+ await page.keyboard.press('Tab')
125
+ const inside = await modalLocator.evaluate((modal) => modal.contains(document.activeElement))
126
+ if (!inside) {
127
+ throw new Error('presentation:reachability (negative) — focus escaped the modal; focus is not trapped')
128
+ }
129
+ }
130
+ }
131
+
132
+ // Negative form: with a modal open, a background element must not be interactable
133
+ // (no click-through). Asserts the background is `inert`, `aria-hidden`, or covered
134
+ // (pointer-events:none) — the standard ways an open dialog neutralizes the page.
135
+ export async function assertNoBackgroundClickThrough(page, backgroundLocator) {
136
+ const interactable = await backgroundLocator.evaluate((el) => {
137
+ if (el.inert) return false
138
+ if (el.getAttribute('aria-hidden') === 'true') return false
139
+ if (getComputedStyle(el).pointerEvents === 'none') return false
140
+ return true // still interactable behind the modal — a click-through leak
141
+ })
142
+ if (interactable) {
143
+ throw new Error('presentation:reachability (negative) — background is still interactable behind the modal (click-through); expected inert / aria-hidden / pointer-events:none')
144
+ }
145
+ }
146
+
147
+ // Negative form: a privilege-gated route must NOT be navigationally reachable for a
148
+ // user lacking the role (it must not appear in the crawl set).
149
+ export async function assertRouteNotReachable(page, route, { start = '/' } = {}) {
150
+ const reachable = await crawlReachableRoutes(page, { start })
151
+ if (reachable.includes(route)) {
152
+ throw new Error(`presentation:reachability (negative) — privilege-gated route is reachable via nav for the wrong role: ${route}`)
153
+ }
154
+ }
@@ -0,0 +1,141 @@
1
+ // Presentation-axis SPINE instrument for e2e-test: render-health.
2
+ //
3
+ // The cheapest, most foundational presentation check — does the page render
4
+ // without a `pageerror` (uncaught exception), a `console.error`, a rendered error
5
+ // boundary, or an HTTP >= 500? An uncaught TypeError on mount fails here before
6
+ // any layout question is asked. This is the check that catches the class of bug a
7
+ // green functional journey sails past when its only test of the route intercepts
8
+ // the route's own API (the journey never exercises the real render path).
9
+ //
10
+ // Import into a Playwright spec and tag it `@covers <route>` /
11
+ // `@level presentation:render-health` so the Coverage Ledger resolves it. Drives a
12
+ // REAL browser — a DOM-only / JSDOM equivalent cannot observe a pageerror on mount
13
+ // and is graded `Over-mocked` by the ledger. Mirrors the contract in ensure-coverage
14
+ // references/presentation/runner-contract.md.
15
+
16
+ // ---- pure, unit-testable core (no browser) ----
17
+
18
+ // Default substrings that mark a console message or error as a framework/error-
19
+ // boundary render failure regardless of source. Repos extend via `extraFatal`.
20
+ export const DEFAULT_FATAL_MARKERS = [
21
+ 'Application error: a client-side exception',
22
+ 'Application error: a server-side exception',
23
+ 'Unhandled Runtime Error',
24
+ 'Cannot read properties of undefined',
25
+ 'Cannot read properties of null',
26
+ 'is not a function',
27
+ 'Hydration failed',
28
+ 'There was an error while hydrating',
29
+ 'Minified React error',
30
+ ]
31
+
32
+ // Decide whether a single captured console message is fatal. `type` is the console
33
+ // level ('error' | 'warning' | …). console.error is fatal by default; warnings are
34
+ // not. `ignore` suppresses known-benign messages by substring (curate sparingly).
35
+ export function isFatalConsole(type, text, { ignore = [] } = {}) {
36
+ if (type !== 'error') return false
37
+ if (ignore.some((ig) => text.includes(ig))) return false
38
+ return true
39
+ }
40
+
41
+ // A response is fatal if the document or any first-party request returns >= 500.
42
+ // Cross-origin (external) responses are ignored — externals are faked, not graded.
43
+ export function isFatalResponse(status, url, { origin } = {}) {
44
+ if (status < 500) return false
45
+ if (origin && !url.startsWith(origin) && !url.startsWith('/')) return false
46
+ return true
47
+ }
48
+
49
+ // Does any captured text match an error-boundary / framework-error marker?
50
+ export function hasFatalMarker(text, { extraFatal = [] } = {}) {
51
+ const markers = [...DEFAULT_FATAL_MARKERS, ...extraFatal]
52
+ return markers.some((m) => text.includes(m))
53
+ }
54
+
55
+ // Reduce a batch of captured signals into a verdict. Pure — the Playwright wrapper
56
+ // collects `signals` and delegates here so the decision logic is testable headless.
57
+ // signals: { pageerrors:[{message}], consoles:[{type,text}], responses:[{status,url}], bodyText? }
58
+ export function evaluateRenderHealth(signals = {}, opts = {}) {
59
+ const { ignore = [], extraFatal = [], origin } = opts
60
+ const offenders = []
61
+ for (const e of signals.pageerrors || []) {
62
+ offenders.push({ kind: 'pageerror', detail: e.message || String(e) })
63
+ }
64
+ for (const c of signals.consoles || []) {
65
+ if (isFatalConsole(c.type, c.text || '', { ignore })) {
66
+ offenders.push({ kind: 'console.error', detail: c.text })
67
+ }
68
+ }
69
+ for (const r of signals.responses || []) {
70
+ if (isFatalResponse(r.status, r.url || '', { origin })) {
71
+ offenders.push({ kind: 'http>=500', detail: `${r.status} ${r.url}` })
72
+ }
73
+ }
74
+ if (signals.bodyText && hasFatalMarker(signals.bodyText, { extraFatal })) {
75
+ offenders.push({ kind: 'error-boundary', detail: 'error overlay / boundary text present in DOM' })
76
+ }
77
+ return { healthy: offenders.length === 0, offenders }
78
+ }
79
+
80
+ // ---- browser wrapper (Playwright) ----
81
+
82
+ // Attach listeners BEFORE navigation, then drive: returns a handle whose
83
+ // `.assert()` throws with the concrete offender(s) if the page did not render
84
+ // healthy. Usage:
85
+ // const health = watchRenderHealth(page, { origin: baseURL })
86
+ // await page.goto('/tasks')
87
+ // await page.waitForLoadState('networkidle').catch(() => {})
88
+ // await health.assert()
89
+ export function watchRenderHealth(page, opts = {}) {
90
+ const signals = { pageerrors: [], consoles: [], responses: [] }
91
+ page.on('pageerror', (err) => signals.pageerrors.push({ message: err?.message || String(err) }))
92
+ page.on('console', (msg) => signals.consoles.push({ type: msg.type(), text: msg.text() }))
93
+ page.on('response', (res) => signals.responses.push({ status: res.status(), url: res.url() }))
94
+ return {
95
+ signals,
96
+ async evaluate() {
97
+ // Pull rendered body text so an error boundary that swallowed the throw is caught too.
98
+ try { signals.bodyText = await page.evaluate(() => document.body?.innerText || '') } catch { /* page gone */ }
99
+ return evaluateRenderHealth(signals, opts)
100
+ },
101
+ async assert() {
102
+ const { healthy, offenders } = await this.evaluate()
103
+ if (!healthy) {
104
+ const lines = offenders.map((o) => ` - [${o.kind}] ${o.detail}`).join('\n')
105
+ throw new Error(`presentation:render-health — page did not render cleanly:\n${lines}`)
106
+ }
107
+ },
108
+ }
109
+ }
110
+
111
+ // Continuous-invariant form: render-health must hold not only at LOAD but THROUGH an
112
+ // interaction — a `presentation:temporal` trigger, a `presentation:perceivability`
113
+ // post-delete mutation, or a mutation-form journey's submit. Attach the watcher,
114
+ // run the interaction, then assert nothing threw during it. COMPOSE with the other
115
+ // presentation instruments rather than re-driving the form:
116
+ //
117
+ // await assertHealthyThrough(page, async () => {
118
+ // await assertRemovedAfterMutation(page, page.getByRole('row', { name: 'X' }))
119
+ // }, { origin: baseURL })
120
+ //
121
+ // This is "mutate-health": render-health × the trigger/mutation pattern temporal and
122
+ // perceivability already use. The durable-state assertion (depth) and the visible
123
+ // post-state assertion (perceivability) stay as-is; this adds "…and nothing threw
124
+ // while it happened." Reuse one handle across several interactions for full lifecycle
125
+ // coverage, or call this per interaction.
126
+ export async function assertHealthyThrough(page, action, opts = {}) {
127
+ const health = watchRenderHealth(page, opts)
128
+ await action()
129
+ await health.assert()
130
+ }
131
+
132
+ // One-shot convenience: navigate and assert render-health in a single call.
133
+ export async function assertRenderHealthy(page, url, opts = {}) {
134
+ const health = watchRenderHealth(page, opts)
135
+ const res = await page.goto(url, { waitUntil: 'domcontentloaded' })
136
+ await page.waitForLoadState('networkidle').catch(() => {})
137
+ if (res && isFatalResponse(res.status(), res.url(), { origin: opts.origin })) {
138
+ throw new Error(`presentation:render-health — document returned ${res.status()} for ${url}`)
139
+ }
140
+ await health.assert()
141
+ }