@1aboveio/skills 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (633) hide show
  1. package/README.md +30 -2
  2. package/bin/cli.mjs +14 -0
  3. package/bin/select-core.mjs +172 -0
  4. package/package.json +146 -4
  5. package/skills/app-dev/airflow/LICENSE +3 -0
  6. package/skills/app-dev/airflow/SKILL.md +199 -0
  7. package/skills/app-dev/airflow/release-check.mjs +93 -0
  8. package/skills/app-dev/android-foundation/LICENSE +3 -0
  9. package/skills/app-dev/android-foundation/SKILL.md +192 -0
  10. package/skills/app-dev/android-foundation/assets/android-ci-job.yml +217 -0
  11. package/skills/app-dev/android-foundation/references/ci.md +87 -0
  12. package/skills/app-dev/android-foundation/references/gotchas.md +221 -0
  13. package/skills/app-dev/android-foundation/references/setup.md +164 -0
  14. package/skills/app-dev/android-foundation/release-check.mjs +93 -0
  15. package/skills/app-dev/android-foundation/scripts/device-run.sh +176 -0
  16. package/skills/app-dev/android-foundation/scripts/device-run.test.mjs +172 -0
  17. package/skills/app-dev/app-debug/LICENSE +3 -0
  18. package/skills/app-dev/app-debug/SKILL.md +110 -0
  19. package/skills/app-dev/app-debug/references/common-errors.md +128 -0
  20. package/skills/app-dev/app-debug/release-check.mjs +93 -0
  21. package/skills/app-dev/better-auth/LICENSE +3 -0
  22. package/skills/app-dev/better-auth/SKILL.md +598 -0
  23. package/skills/app-dev/better-auth/references/feishu-api.md +270 -0
  24. package/skills/app-dev/better-auth/release-check.mjs +93 -0
  25. package/skills/app-dev/data-catalog/LICENSE +3 -0
  26. package/skills/app-dev/data-catalog/SKILL.md +204 -0
  27. package/skills/app-dev/data-catalog/agents/openai.yaml +3 -0
  28. package/skills/app-dev/data-catalog/references/data-catalog-conventions.md +75 -0
  29. package/skills/app-dev/data-catalog/references/field-definitions.md +116 -0
  30. package/skills/app-dev/data-catalog/references/format.md +121 -0
  31. package/skills/app-dev/data-catalog/release-check.mjs +93 -0
  32. package/skills/app-dev/data-catalog/scripts/data-catalog.test.mjs +36 -0
  33. package/skills/app-dev/data-catalog/scripts/data_catalog.py +1392 -0
  34. package/skills/app-dev/data-catalog/scripts/persist-conventions-core.mjs +117 -0
  35. package/skills/app-dev/data-catalog/scripts/persist-conventions.mjs +17 -0
  36. package/skills/app-dev/data-catalog/scripts/test_data_catalog.py +571 -0
  37. package/skills/app-dev/ios-foundation/LICENSE +3 -0
  38. package/skills/app-dev/ios-foundation/SKILL.md +184 -0
  39. package/skills/app-dev/ios-foundation/references/setup.md +243 -0
  40. package/skills/app-dev/ios-foundation/release-check.mjs +93 -0
  41. package/skills/app-dev/monorepo/LICENSE +3 -0
  42. package/skills/app-dev/monorepo/SKILL.md +462 -0
  43. package/skills/app-dev/monorepo/release-check.mjs +93 -0
  44. package/skills/app-dev/nextjs-fullstack/LICENSE +3 -0
  45. package/skills/app-dev/nextjs-fullstack/SKILL.md +211 -0
  46. package/skills/app-dev/nextjs-fullstack/conventions.md +318 -0
  47. package/skills/app-dev/nextjs-fullstack/frontend-conventions.md +61 -0
  48. package/skills/app-dev/nextjs-fullstack/nextjs16.md +287 -0
  49. package/skills/app-dev/nextjs-fullstack/release-check.mjs +93 -0
  50. package/skills/app-dev/nextjs-fullstack/server-actions.md +409 -0
  51. package/skills/app-dev/prisma-setup/LICENSE +3 -0
  52. package/skills/app-dev/prisma-setup/SKILL.md +181 -0
  53. package/skills/app-dev/prisma-setup/nextjs.md +258 -0
  54. package/skills/app-dev/prisma-setup/release-check.mjs +93 -0
  55. package/skills/app-dev/prisma-setup/turborepo.md +301 -0
  56. package/skills/app-dev/pyspark/LICENSE +3 -0
  57. package/skills/app-dev/pyspark/SKILL.md +127 -0
  58. package/skills/app-dev/pyspark/assets/templates/etl.py +748 -0
  59. package/skills/app-dev/pyspark/assets/templates/utils/__init__.py +1 -0
  60. package/skills/app-dev/pyspark/assets/templates/utils/hudi_metadata.py +14 -0
  61. package/skills/app-dev/pyspark/references/diagnosis-and-profiling.md +169 -0
  62. package/skills/app-dev/pyspark/references/etl-contract.md +129 -0
  63. package/skills/app-dev/pyspark/references/production-validation.md +131 -0
  64. package/skills/app-dev/pyspark/references/reconciliation.md +38 -0
  65. package/skills/app-dev/pyspark/references/transformation-design.md +190 -0
  66. package/skills/app-dev/pyspark/references/velocity-feature-calculation.md +193 -0
  67. package/skills/app-dev/pyspark/release-check.mjs +93 -0
  68. package/skills/app-dev/pyspark/scripts/spark_eventlog_summary.py +223 -0
  69. package/skills/app-dev/python-backend/LICENSE +3 -0
  70. package/skills/app-dev/python-backend/SKILL.md +325 -0
  71. package/skills/app-dev/python-backend/assets/uv-install.sh +48 -0
  72. package/skills/app-dev/python-backend/references/uv-installs.md +83 -0
  73. package/skills/app-dev/python-backend/release-check.mjs +93 -0
  74. package/skills/app-dev/shadcn/LICENSE +3 -0
  75. package/skills/app-dev/shadcn/SKILL.md +120 -0
  76. package/skills/app-dev/shadcn/assets/shadcn-small.png +0 -0
  77. package/skills/app-dev/shadcn/assets/shadcn.png +0 -0
  78. package/skills/app-dev/shadcn/cli.md +411 -0
  79. package/skills/app-dev/shadcn/customization.md +224 -0
  80. package/skills/app-dev/shadcn/mcp.md +101 -0
  81. package/skills/app-dev/shadcn/release-check.mjs +93 -0
  82. package/skills/app-dev/shadcn/rules/base-vs-radix.md +323 -0
  83. package/skills/app-dev/shadcn/rules/component-selection.md +67 -0
  84. package/skills/app-dev/shadcn/rules/composition.md +195 -0
  85. package/skills/app-dev/shadcn/rules/data-table.md +201 -0
  86. package/skills/app-dev/shadcn/rules/forms.md +255 -0
  87. package/skills/app-dev/shadcn/rules/icons.md +103 -0
  88. package/skills/app-dev/shadcn/rules/styling.md +167 -0
  89. package/skills/app-dev/zod-v4/LICENSE +3 -0
  90. package/skills/app-dev/zod-v4/SKILL.md +286 -0
  91. package/skills/app-dev/zod-v4/release-check.mjs +93 -0
  92. package/skills/cicd/cloud-build/LICENSE +3 -0
  93. package/skills/cicd/cloud-build/SKILL.md +707 -0
  94. package/skills/cicd/cloud-build/release-check.mjs +93 -0
  95. package/skills/cicd/cloud-debug/LICENSE +3 -0
  96. package/skills/cicd/cloud-debug/SKILL.md +315 -0
  97. package/skills/cicd/cloud-debug/references/build-failures.md +238 -0
  98. package/skills/cicd/cloud-debug/references/deploy-failures.md +376 -0
  99. package/skills/cicd/cloud-debug/references/pipeline-failures.md +378 -0
  100. package/skills/cicd/cloud-debug/release-check.mjs +93 -0
  101. package/skills/cicd/cloud-deploy/LICENSE +3 -0
  102. package/skills/cicd/cloud-deploy/SKILL.md +230 -0
  103. package/skills/cicd/cloud-deploy/references/config-templates.md +257 -0
  104. package/skills/cicd/cloud-deploy/release-check.mjs +93 -0
  105. package/skills/cicd/docker/LICENSE +3 -0
  106. package/skills/cicd/docker/SKILL.md +151 -0
  107. package/skills/cicd/docker/release-check.mjs +93 -0
  108. package/skills/cicd/github-actions/LICENSE +3 -0
  109. package/skills/cicd/github-actions/SKILL.md +111 -0
  110. package/skills/cicd/github-actions/references/audit-profile-optimize.md +150 -0
  111. package/skills/cicd/github-actions/references/ci-case-studies.md +141 -0
  112. package/skills/cicd/github-actions/references/conditional-ci.md +214 -0
  113. package/skills/cicd/github-actions/references/debugging.md +13 -0
  114. package/skills/cicd/github-actions/references/pipeline.md +87 -0
  115. package/skills/cicd/github-actions/references/post-deploy-and-publishing.md +33 -0
  116. package/skills/cicd/github-actions/references/runners.md +253 -0
  117. package/skills/cicd/github-actions/references/security.md +29 -0
  118. package/skills/cicd/github-actions/references/speed-and-audit.md +115 -0
  119. package/skills/cicd/github-actions/references/two-pass-reading.md +92 -0
  120. package/skills/cicd/github-actions/release-check.mjs +93 -0
  121. package/skills/cicd/github-actions/scripts/ci-run-audit-core.mjs +612 -0
  122. package/skills/cicd/github-actions/scripts/ci-run-audit.mjs +4 -0
  123. package/skills/cicd/github-actions/scripts/ci-run-audit.test.mjs +637 -0
  124. package/skills/cicd/github-flow/LICENSE +3 -0
  125. package/skills/cicd/github-flow/SKILL.md +123 -0
  126. package/skills/cicd/github-flow/references/ci-conventions.md +14 -0
  127. package/skills/cicd/github-flow/release-check.mjs +93 -0
  128. package/skills/cicd/github-flow/scripts/flow-core.mjs +105 -0
  129. package/skills/cicd/github-flow/scripts/flow.mjs +81 -0
  130. package/skills/cicd/github-flow/scripts/flow.test.mjs +114 -0
  131. package/skills/cicd/github-flow/scripts/persist-conventions-core.mjs +117 -0
  132. package/skills/cicd/github-flow/scripts/persist-conventions.mjs +49 -0
  133. package/skills/cicd/github-flow/scripts/persist-conventions.test.mjs +108 -0
  134. package/skills/cicd/google-cloud/LICENSE +3 -0
  135. package/skills/cicd/google-cloud/SKILL.md +119 -0
  136. package/skills/cicd/google-cloud/references/gcs.md +469 -0
  137. package/skills/cicd/google-cloud/references/iam.md +451 -0
  138. package/skills/cicd/google-cloud/references/project.md +349 -0
  139. package/skills/cicd/google-cloud/references/secrets.md +336 -0
  140. package/skills/cicd/google-cloud/references/vpc.md +312 -0
  141. package/skills/cicd/google-cloud/release-check.mjs +93 -0
  142. package/skills/cicd/google-cloud/scripts/create-sa.sh +36 -0
  143. package/skills/cicd/google-cloud/scripts/gcp-config.sh +31 -0
  144. package/skills/cicd/google-cloud/scripts/grant-iap.sh +41 -0
  145. package/skills/cicd/google-cloud/scripts/setup-secrets.sh +48 -0
  146. package/skills/cicd/mergify/LICENSE +3 -0
  147. package/skills/cicd/mergify/SKILL.md +102 -0
  148. package/skills/cicd/mergify/assets/templates/mergify.yml +215 -0
  149. package/skills/cicd/mergify/assets/templates/ruleset.json +46 -0
  150. package/skills/cicd/mergify/references/branch-protection.md +282 -0
  151. package/skills/cicd/mergify/references/configuration.md +274 -0
  152. package/skills/cicd/mergify/references/diagnosis.md +109 -0
  153. package/skills/cicd/mergify/references/stacks.md +84 -0
  154. package/skills/cicd/mergify/references/traps.md +119 -0
  155. package/skills/cicd/mergify/references/watch-contract.md +274 -0
  156. package/skills/cicd/mergify/release-check.mjs +93 -0
  157. package/skills/cicd/mergify/scripts/audit-core.mjs +208 -0
  158. package/skills/cicd/mergify/scripts/audit.mjs +4 -0
  159. package/skills/cicd/mergify/scripts/audit.test.mjs +201 -0
  160. package/skills/cicd/mergify/scripts/entrypoints.test.mjs +106 -0
  161. package/skills/cicd/mergify/scripts/pr-620-mergify-comments.fixture.json +30 -0
  162. package/skills/cicd/mergify/scripts/stack-push-core.mjs +258 -0
  163. package/skills/cicd/mergify/scripts/stack-push.mjs +4 -0
  164. package/skills/cicd/mergify/scripts/stack-push.test.mjs +233 -0
  165. package/skills/cicd/mergify/scripts/watch-pr-delivery-core.mjs +1065 -0
  166. package/skills/cicd/mergify/scripts/watch-pr-delivery.mjs +4 -0
  167. package/skills/cicd/mergify/scripts/watch-pr-delivery.test.mjs +1826 -0
  168. package/skills/cicd/podman/LICENSE +3 -0
  169. package/skills/cicd/podman/SKILL.md +71 -0
  170. package/skills/cicd/podman/agents/openai.yaml +4 -0
  171. package/skills/cicd/podman/assets/templates/podman-compose-socket-directory.yml +6 -0
  172. package/skills/cicd/podman/assets/templates/podman-service-override.conf +3 -0
  173. package/skills/cicd/podman/references/compose-compatibility.md +70 -0
  174. package/skills/cicd/podman/references/networking-and-ports.md +74 -0
  175. package/skills/cicd/podman/references/rootless-services-and-sockets.md +156 -0
  176. package/skills/cicd/podman/references/troubleshooting.md +98 -0
  177. package/skills/cicd/podman/release-check.mjs +93 -0
  178. package/skills/content/article-review/LICENSE +3 -0
  179. package/skills/content/article-review/SKILL.md +66 -0
  180. package/skills/content/article-review/references/report-format.md +68 -0
  181. package/skills/content/article-review/references/template-guide.md +79 -0
  182. package/skills/content/article-review/references/template-wechat-payments.md +125 -0
  183. package/skills/content/article-review/release-check.mjs +93 -0
  184. package/skills/content/article-to-image/LICENSE +3 -0
  185. package/skills/content/article-to-image/SKILL.md +91 -0
  186. package/skills/content/article-to-image/references/formats.md +53 -0
  187. package/skills/content/article-to-image/release-check.mjs +93 -0
  188. package/skills/content/flowchart-svg/LICENSE +3 -0
  189. package/skills/content/flowchart-svg/SKILL.md +43 -0
  190. package/skills/content/flowchart-svg/release-check.mjs +93 -0
  191. package/skills/content/flowchart-svg/scripts/export_png.sh +8 -0
  192. package/skills/content/image-gen/LICENSE +3 -0
  193. package/skills/content/image-gen/SKILL.md +192 -0
  194. package/skills/content/image-gen/release-check.mjs +93 -0
  195. package/skills/content/image-gen/scripts/credentials.mjs +217 -0
  196. package/skills/content/image-gen/scripts/credentials.test.mjs +212 -0
  197. package/skills/content/image-gen/scripts/generate.mjs +240 -0
  198. package/skills/content/image-gen/scripts/options.mjs +293 -0
  199. package/skills/content/image-gen/scripts/options.test.mjs +245 -0
  200. package/skills/content/mermaid/LICENSE +3 -0
  201. package/skills/content/mermaid/SKILL.md +635 -0
  202. package/skills/content/mermaid/release-check.mjs +93 -0
  203. package/skills/content/wechat-article-writer/LICENSE +3 -0
  204. package/skills/content/wechat-article-writer/SKILL.md +92 -0
  205. package/skills/content/wechat-article-writer/references/style-selector.md +133 -0
  206. package/skills/content/wechat-article-writer/references/writing-rules.md +152 -0
  207. package/skills/content/wechat-article-writer/release-check.mjs +93 -0
  208. package/skills/content/wechat-article-writer/templates/wechat-comparison.md +52 -0
  209. package/skills/content/wechat-article-writer/templates/wechat-howto.md +51 -0
  210. package/skills/content/wechat-article-writer/templates/wechat-insight.md +50 -0
  211. package/skills/content/wechat-article-writer/templates/wechat-pitfall.md +52 -0
  212. package/skills/content/wechat-publisher/LICENSE +3 -0
  213. package/skills/content/wechat-publisher/SKILL.md +171 -0
  214. package/skills/content/wechat-publisher/references/api.md +105 -0
  215. package/skills/content/wechat-publisher/references/layout-rules.md +57 -0
  216. package/skills/content/wechat-publisher/references/wechat-safe-template.html +86 -0
  217. package/skills/content/wechat-publisher/release-check.mjs +93 -0
  218. package/skills/content/wechat-publisher/scripts/md_to_wechat_html.py +194 -0
  219. package/skills/content/wechat-publisher/scripts/publish.py +239 -0
  220. package/skills/content/wechat-search/LICENSE +3 -0
  221. package/skills/content/wechat-search/SKILL.md +211 -0
  222. package/skills/content/wechat-search/release-check.mjs +93 -0
  223. package/skills/content/wechat-search/scripts/start-chrome.sh +83 -0
  224. package/skills/content/xai-search/LICENSE +3 -0
  225. package/skills/content/xai-search/SKILL.md +79 -0
  226. package/skills/content/xai-search/release-check.mjs +93 -0
  227. package/skills/content/xai-search/scripts/xai_search.sh +187 -0
  228. package/skills/content/xai-search/tests/smoke_test.sh +36 -0
  229. package/skills/core/distill-writing/LICENSE +3 -0
  230. package/skills/core/distill-writing/SKILL.md +74 -0
  231. package/skills/core/distill-writing/references/extraction.md +95 -0
  232. package/skills/core/distill-writing/references/updating.md +46 -0
  233. package/skills/core/distill-writing/release-check.mjs +93 -0
  234. package/skills/core/doc-to-md/LICENSE +3 -0
  235. package/skills/core/doc-to-md/SKILL.md +78 -0
  236. package/skills/core/doc-to-md/release-check.mjs +93 -0
  237. package/skills/core/doc-to-md/scripts/convert.py +157 -0
  238. package/skills/core/editorial-review/LICENSE +3 -0
  239. package/skills/core/editorial-review/SKILL.md +79 -0
  240. package/skills/core/editorial-review/references/checklist.md +94 -0
  241. package/skills/core/editorial-review/release-check.mjs +93 -0
  242. package/skills/core/house-style/LICENSE +3 -0
  243. package/skills/core/house-style/SKILL.md +33 -0
  244. package/skills/core/house-style/references/defaults.md +66 -0
  245. package/skills/core/house-style/references/rules.md +267 -0
  246. package/skills/core/house-style/references/sources.md +27 -0
  247. package/skills/core/house-style/references/style-en.md +131 -0
  248. package/skills/core/house-style/references/style-zh.md +141 -0
  249. package/skills/core/house-style/references/terminology-zh.md +223 -0
  250. package/skills/core/house-style/release-check.mjs +93 -0
  251. package/skills/core/house-style/templates/brd.md +103 -0
  252. package/skills/core/house-style/templates/commercial-proposal.md +93 -0
  253. package/skills/core/house-style/templates/compliance-policy.md +109 -0
  254. package/skills/core/house-style/templates/gap-analysis.md +105 -0
  255. package/skills/core/house-style/templates/general.md +63 -0
  256. package/skills/core/house-style/templates/integration-guide.md +98 -0
  257. package/skills/core/house-style/templates/prd.md +81 -0
  258. package/skills/core/house-style/templates/risk-operations-procedure.md +94 -0
  259. package/skills/core/house-style/templates/technical-spec.md +96 -0
  260. package/skills/core/polish-cn/LICENSE +3 -0
  261. package/skills/core/polish-cn/SKILL.md +102 -0
  262. package/skills/core/polish-cn/references/brief.md +93 -0
  263. package/skills/core/polish-cn/references/models.md +76 -0
  264. package/skills/core/polish-cn/release-check.mjs +93 -0
  265. package/skills/core/polish-cn/scripts/polish-cn.test.mjs +244 -0
  266. package/skills/core/polish-cn/scripts/polish_blocks.py +489 -0
  267. package/skills/core/reg-parser/LICENSE +3 -0
  268. package/skills/core/reg-parser/SKILL.md +212 -0
  269. package/skills/core/reg-parser/references/checklist-schema.md +223 -0
  270. package/skills/core/reg-parser/references/checklists/hk-amlo-checklist.json +1306 -0
  271. package/skills/core/reg-parser/references/checklists/hk-amlo-checklist.md +469 -0
  272. package/skills/core/reg-parser/references/checklists/hk-fi-corporate-onboarding-cdd-checklist.json +634 -0
  273. package/skills/core/reg-parser/references/checklists/hk-fi-corporate-onboarding-cdd-checklist.md +233 -0
  274. package/skills/core/reg-parser/references/checklists/hk-mso-guideline-checklist.json +2194 -0
  275. package/skills/core/reg-parser/references/checklists/hk-mso-guideline-checklist.md +771 -0
  276. package/skills/core/reg-parser/references/checklists/kyc-aml-checklist.md +258 -0
  277. package/skills/core/reg-parser/references/checklists/visa-vars-checklist.md +1058 -0
  278. package/skills/core/reg-parser/references/mandates/hk-amlo-cap615.md +389 -0
  279. package/skills/core/reg-parser/references/mandates/visa-vars.md +205 -0
  280. package/skills/core/reg-parser/release-check.mjs +93 -0
  281. package/skills/core/reg-parser/scripts/compare_checklist.py +230 -0
  282. package/skills/core/reg-parser/scripts/dump_checklist.py +361 -0
  283. package/skills/core/reg-parser/scripts/preprocess-mandate.sh +187 -0
  284. package/skills/core/reg-parser/scripts/reg-parser.test.mjs +165 -0
  285. package/skills/core/reg-parser/scripts/test_quarantine.sh +105 -0
  286. package/skills/core/reg-parser/scripts/validate_checklist.py +563 -0
  287. package/skills/core/reg-parser/scripts/validate_mandate.py +273 -0
  288. package/skills/core/terminology/LICENSE +3 -0
  289. package/skills/core/terminology/SKILL.md +62 -0
  290. package/skills/core/terminology/references/format.md +50 -0
  291. package/skills/core/terminology/references/init.md +32 -0
  292. package/skills/core/terminology/release-check.mjs +93 -0
  293. package/skills/core/write-doc/LICENSE +3 -0
  294. package/skills/core/write-doc/SKILL.md +141 -0
  295. package/skills/core/write-doc/references/bilingual.md +60 -0
  296. package/skills/core/write-doc/release-check.mjs +93 -0
  297. package/skills/engineering/better-review/LICENSE +3 -0
  298. package/skills/engineering/better-review/SKILL.md +249 -0
  299. package/skills/engineering/better-review/agents/openai.yaml +9 -0
  300. package/skills/engineering/better-review/references/domain-hazards.md +53 -0
  301. package/skills/engineering/better-review/references/passes.md +147 -0
  302. package/skills/engineering/better-review/references/result.md +151 -0
  303. package/skills/engineering/better-review/release-check.mjs +93 -0
  304. package/skills/engineering/better-review/scripts/pr-context.mjs +212 -0
  305. package/skills/engineering/better-review/scripts/pr-context.test.mjs +168 -0
  306. package/skills/engineering/better-review/scripts/rereview-scope.mjs +503 -0
  307. package/skills/engineering/better-review/scripts/rereview-scope.test.mjs +587 -0
  308. package/skills/engineering/better-review/scripts/scan-diff.mjs +365 -0
  309. package/skills/engineering/better-review/scripts/scan-diff.test.mjs +414 -0
  310. package/skills/engineering/better-review/scripts/vendor-sync.test.mjs +96 -0
  311. package/skills/engineering/e2e-test/LICENSE +3 -0
  312. package/skills/engineering/e2e-test/SKILL.md +98 -0
  313. package/skills/engineering/e2e-test/assets/ci-gates.cloudbuild.yaml +24 -0
  314. package/skills/engineering/e2e-test/references/authoring/auth-flows.md +159 -0
  315. package/skills/engineering/e2e-test/references/authoring/playwright-config.md +73 -0
  316. package/skills/engineering/e2e-test/references/authoring/playwright-patterns.md +144 -0
  317. package/skills/engineering/e2e-test/references/ci-integration.md +25 -0
  318. package/skills/engineering/e2e-test/references/ci-playwright-container.md +290 -0
  319. package/skills/engineering/e2e-test/references/presentation-sweep.md +104 -0
  320. package/skills/engineering/e2e-test/references/reviewing.md +41 -0
  321. package/skills/engineering/e2e-test/references/route-manifest.md +51 -0
  322. package/skills/engineering/e2e-test/release-check.mjs +93 -0
  323. package/skills/engineering/e2e-test/scripts/detect-routes-nextjs.mjs +200 -0
  324. package/skills/engineering/e2e-test/scripts/presentation-checks.mjs +171 -0
  325. package/skills/engineering/e2e-test/scripts/presentation-checks.test.mjs +106 -0
  326. package/skills/engineering/e2e-test/scripts/presentation-perceivability.mjs +179 -0
  327. package/skills/engineering/e2e-test/scripts/presentation-perceivability.test.mjs +117 -0
  328. package/skills/engineering/e2e-test/scripts/presentation-reachability.mjs +154 -0
  329. package/skills/engineering/e2e-test/scripts/presentation-reachability.test.mjs +69 -0
  330. package/skills/engineering/e2e-test/scripts/presentation-render-health.mjs +141 -0
  331. package/skills/engineering/e2e-test/scripts/presentation-render-health.test.mjs +98 -0
  332. package/skills/engineering/e2e-test/scripts/presentation-sweep.mjs +147 -0
  333. package/skills/engineering/e2e-test/scripts/presentation-sweep.test.mjs +71 -0
  334. package/skills/engineering/e2e-test/scripts/presentation-temporal.mjs +127 -0
  335. package/skills/engineering/e2e-test/scripts/presentation-temporal.test.mjs +59 -0
  336. package/skills/engineering/e2e-test/scripts/project-route-manifest.mjs +82 -0
  337. package/skills/engineering/e2e-test/scripts/project-route-manifest.test.mjs +76 -0
  338. package/skills/engineering/e2e-test/scripts/validate-manifest.mjs +106 -0
  339. package/skills/engineering/engineering-runtime/LICENSE +3 -0
  340. package/skills/engineering/engineering-runtime/SKILL.md +11 -0
  341. package/skills/engineering/engineering-runtime/agents/openai.yaml +6 -0
  342. package/skills/engineering/engineering-runtime/coherence/workflow.json +597 -0
  343. package/skills/engineering/engineering-runtime/engineering-runtime.test.mjs +68 -0
  344. package/skills/engineering/engineering-runtime/release-check.mjs +93 -0
  345. package/skills/engineering/engineering-runtime/scripts/exact-head-artifact.mjs +131 -0
  346. package/skills/engineering/engineering-runtime/scripts/head-check-set.mjs +398 -0
  347. package/skills/engineering/engineering-runtime/scripts/invocation-policy.mjs +187 -0
  348. package/skills/engineering/engineering-runtime/scripts/invocation-policy.test.mjs +95 -0
  349. package/skills/engineering/engineering-runtime/scripts/main-module.mjs +79 -0
  350. package/skills/engineering/engineering-runtime/scripts/main-module.test.mjs +288 -0
  351. package/skills/engineering/engineering-runtime/scripts/mergify-yaml.mjs +11 -0
  352. package/skills/engineering/engineering-runtime/scripts/package-lock.json +43 -0
  353. package/skills/engineering/engineering-runtime/scripts/package.json +10 -0
  354. package/skills/engineering/engineering-runtime/scripts/required-check-plan.mjs +223 -0
  355. package/skills/engineering/engineering-runtime/scripts/required-check-plan.test.mjs +96 -0
  356. package/skills/engineering/engineering-runtime/scripts/review-completion.mjs +56 -0
  357. package/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +595 -0
  358. package/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +189 -0
  359. package/skills/engineering/engineering-runtime/scripts/yaml-mapping.mjs +18 -0
  360. package/skills/engineering/ensure-coverage/LICENSE +3 -0
  361. package/skills/engineering/ensure-coverage/SKILL.md +129 -0
  362. package/skills/engineering/ensure-coverage/references/breadth/coverage-ledger.md +92 -0
  363. package/skills/engineering/ensure-coverage/references/breadth/inventory-contract.md +85 -0
  364. package/skills/engineering/ensure-coverage/references/breadth/surface-baseline.md +44 -0
  365. package/skills/engineering/ensure-coverage/references/breadth/surface-discovery.md +16 -0
  366. package/skills/engineering/ensure-coverage/references/depth/characterization.md +68 -0
  367. package/skills/engineering/ensure-coverage/references/depth/coverage.config.example.json +19 -0
  368. package/skills/engineering/ensure-coverage/references/depth/grading.md +35 -0
  369. package/skills/engineering/ensure-coverage/references/depth/mock-policy.md +57 -0
  370. package/skills/engineering/ensure-coverage/references/depth/test-smells.md +39 -0
  371. package/skills/engineering/ensure-coverage/references/enforcement/ci-contract.md +44 -0
  372. package/skills/engineering/ensure-coverage/references/examples/coverage-ledger.md +110 -0
  373. package/skills/engineering/ensure-coverage/references/examples/refund-flow.md +35 -0
  374. package/skills/engineering/ensure-coverage/references/presentation/axis.md +64 -0
  375. package/skills/engineering/ensure-coverage/references/process/audit-mode.md +27 -0
  376. package/skills/engineering/ensure-coverage/references/process/output-template.md +139 -0
  377. package/skills/engineering/ensure-coverage/references/process/test-strategy.md +56 -0
  378. package/skills/engineering/ensure-coverage/release-check.mjs +93 -0
  379. package/skills/engineering/ensure-coverage/schemas/coverage-config.schema.json +67 -0
  380. package/skills/engineering/ensure-coverage/schemas/coverage-file.schema.json +140 -0
  381. package/skills/engineering/ensure-coverage/scripts/README.md +99 -0
  382. package/skills/engineering/ensure-coverage/scripts/adapters/nextjs-inventory.mjs +182 -0
  383. package/skills/engineering/ensure-coverage/scripts/adapters/nextjs-inventory.test.mjs +96 -0
  384. package/skills/engineering/ensure-coverage/scripts/check-quarantine-expiry.mjs +105 -0
  385. package/skills/engineering/ensure-coverage/scripts/check-quarantine-expiry.test.mjs +47 -0
  386. package/skills/engineering/ensure-coverage/scripts/ci-audit.mjs +137 -0
  387. package/skills/engineering/ensure-coverage/scripts/ci-audit.test.mjs +86 -0
  388. package/skills/engineering/ensure-coverage/scripts/coverage-ledger.mjs +674 -0
  389. package/skills/engineering/ensure-coverage/scripts/coverage-ledger.test.mjs +447 -0
  390. package/skills/engineering/ensure-coverage/scripts/design-parity.mjs +591 -0
  391. package/skills/engineering/ensure-coverage/scripts/design-parity.test.mjs +552 -0
  392. package/skills/engineering/ensure-coverage/scripts/lint-tests.mjs +282 -0
  393. package/skills/engineering/ensure-coverage/scripts/lint-tests.test.mjs +289 -0
  394. package/skills/engineering/ensure-coverage/scripts/mock-policy-config.mjs +176 -0
  395. package/skills/engineering/ensure-coverage/scripts/mock-policy-config.test.mjs +119 -0
  396. package/skills/engineering/ensure-coverage/scripts/package.json +19 -0
  397. package/skills/engineering/golden-path/LICENSE +3 -0
  398. package/skills/engineering/golden-path/SKILL.md +230 -0
  399. package/skills/engineering/golden-path/agents/openai.yaml +3 -0
  400. package/skills/engineering/golden-path/assets/golden-paths.example.json +57 -0
  401. package/skills/engineering/golden-path/references/contract.md +239 -0
  402. package/skills/engineering/golden-path/references/migration.md +111 -0
  403. package/skills/engineering/golden-path/release-check.mjs +93 -0
  404. package/skills/engineering/golden-path/scripts/golden-path-core.mjs +1779 -0
  405. package/skills/engineering/golden-path/scripts/golden-path.mjs +6 -0
  406. package/skills/engineering/golden-path/scripts/golden-path.test.mjs +1457 -0
  407. package/skills/engineering/harness-runtime/LICENSE +3 -0
  408. package/skills/engineering/harness-runtime/SKILL.md +19 -0
  409. package/skills/engineering/harness-runtime/agents/openai.yaml +6 -0
  410. package/skills/engineering/harness-runtime/bin/discover-models.mjs +4 -0
  411. package/skills/engineering/harness-runtime/bin/model-catalog.mjs +4 -0
  412. package/skills/engineering/harness-runtime/contracts.md +15 -0
  413. package/skills/engineering/harness-runtime/discover-models.mjs +392 -0
  414. package/skills/engineering/harness-runtime/discover-models.test.mjs +363 -0
  415. package/skills/engineering/harness-runtime/fixtures/native-question-schemas.json +33 -0
  416. package/skills/engineering/harness-runtime/fixtures/question-responses.json +54 -0
  417. package/skills/engineering/harness-runtime/harness-runtime.test.mjs +967 -0
  418. package/skills/engineering/harness-runtime/index.mjs +767 -0
  419. package/skills/engineering/harness-runtime/model-catalog-seed.test.mjs +829 -0
  420. package/skills/engineering/harness-runtime/model-catalog.mjs +787 -0
  421. package/skills/engineering/harness-runtime/model-catalog.test.mjs +366 -0
  422. package/skills/engineering/harness-runtime/native-question-contracts.md +37 -0
  423. package/skills/engineering/harness-runtime/references/model-catalog-seed.json +408 -0
  424. package/skills/engineering/harness-runtime/references/model-catalog.md +57 -0
  425. package/skills/engineering/harness-runtime/release-check.mjs +93 -0
  426. package/skills/engineering/resolve-issues/LICENSE +3 -0
  427. package/skills/engineering/resolve-issues/SKILL.md +250 -0
  428. package/skills/engineering/resolve-issues/agents/openai.yaml +9 -0
  429. package/skills/engineering/resolve-issues/plugin-hooks.json +17 -0
  430. package/skills/engineering/resolve-issues/references/canary.md +28 -0
  431. package/skills/engineering/resolve-issues/references/ci.md +105 -0
  432. package/skills/engineering/resolve-issues/references/done.md +25 -0
  433. package/skills/engineering/resolve-issues/references/dropbox.md +90 -0
  434. package/skills/engineering/resolve-issues/references/expire.md +318 -0
  435. package/skills/engineering/resolve-issues/references/exploration.md +202 -0
  436. package/skills/engineering/resolve-issues/references/implementation.md +303 -0
  437. package/skills/engineering/resolve-issues/references/preflight.md +445 -0
  438. package/skills/engineering/resolve-issues/references/review.md +228 -0
  439. package/skills/engineering/resolve-issues/references/shared-modules.md +44 -0
  440. package/skills/engineering/resolve-issues/references/task-plan.md +264 -0
  441. package/skills/engineering/resolve-issues/release-check.mjs +93 -0
  442. package/skills/engineering/resolve-issues/scripts/cleanup-runs.mjs +364 -0
  443. package/skills/engineering/resolve-issues/scripts/cleanup-runs.test.mjs +280 -0
  444. package/skills/engineering/resolve-issues/scripts/discover-models.mjs +9 -0
  445. package/skills/engineering/resolve-issues/scripts/dropbox.mjs +473 -0
  446. package/skills/engineering/resolve-issues/scripts/dropbox.test.mjs +346 -0
  447. package/skills/engineering/resolve-issues/scripts/findings-reuse.mjs +98 -0
  448. package/skills/engineering/resolve-issues/scripts/findings-reuse.test.mjs +88 -0
  449. package/skills/engineering/resolve-issues/scripts/github-reviews.mjs +384 -0
  450. package/skills/engineering/resolve-issues/scripts/github-reviews.test.mjs +264 -0
  451. package/skills/engineering/resolve-issues/scripts/install-hooks.mjs +135 -0
  452. package/skills/engineering/resolve-issues/scripts/install-hooks.test.mjs +79 -0
  453. package/skills/engineering/resolve-issues/scripts/issue-tree.mjs +162 -0
  454. package/skills/engineering/resolve-issues/scripts/issue-tree.test.mjs +107 -0
  455. package/skills/engineering/resolve-issues/scripts/manual-invocation.test.mjs +14 -0
  456. package/skills/engineering/resolve-issues/scripts/model-catalog.mjs +9 -0
  457. package/skills/engineering/resolve-issues/scripts/next-actions-capacity.test.mjs +149 -0
  458. package/skills/engineering/resolve-issues/scripts/next-actions.mjs +856 -0
  459. package/skills/engineering/resolve-issues/scripts/next-actions.test.mjs +915 -0
  460. package/skills/engineering/resolve-issues/scripts/plan-sync.mjs +269 -0
  461. package/skills/engineering/resolve-issues/scripts/plan-sync.test.mjs +156 -0
  462. package/skills/engineering/resolve-issues/scripts/preflight-models.mjs +736 -0
  463. package/skills/engineering/resolve-issues/scripts/preflight-models.test.mjs +448 -0
  464. package/skills/engineering/resolve-issues/scripts/ready-check.mjs +185 -0
  465. package/skills/engineering/resolve-issues/scripts/ready-check.test.mjs +217 -0
  466. package/skills/engineering/resolve-issues/scripts/rehydrate.mjs +98 -0
  467. package/skills/engineering/resolve-issues/scripts/rehydrate.test.mjs +91 -0
  468. package/skills/engineering/resolve-issues/scripts/review-verdict.mjs +509 -0
  469. package/skills/engineering/resolve-issues/scripts/review-verdict.test.mjs +1060 -0
  470. package/skills/engineering/resolve-issues/scripts/size-check.mjs +492 -0
  471. package/skills/engineering/resolve-issues/scripts/size-check.test.mjs +458 -0
  472. package/skills/engineering/resolve-release/LICENSE +3 -0
  473. package/skills/engineering/resolve-release/SKILL.md +215 -0
  474. package/skills/engineering/resolve-release/agents/openai.yaml +8 -0
  475. package/skills/engineering/resolve-release/flows/cloud-run-canary.md +13 -0
  476. package/skills/engineering/resolve-release/flows/cloud-run.md +7 -0
  477. package/skills/engineering/resolve-release/flows/root-tag-npm.md +13 -0
  478. package/skills/engineering/resolve-release/flows/workspace-npm.md +22 -0
  479. package/skills/engineering/resolve-release/references/canary.md +51 -0
  480. package/skills/engineering/resolve-release/references/candidate.md +41 -0
  481. package/skills/engineering/resolve-release/references/cut.md +131 -0
  482. package/skills/engineering/resolve-release/references/dark-launch.md +66 -0
  483. package/skills/engineering/resolve-release/references/flows.md +150 -0
  484. package/skills/engineering/resolve-release/references/preflight.md +154 -0
  485. package/skills/engineering/resolve-release/references/prepared-metadata.md +260 -0
  486. package/skills/engineering/resolve-release/references/promotion.md +82 -0
  487. package/skills/engineering/resolve-release/references/publish.md +232 -0
  488. package/skills/engineering/resolve-release/references/workspace-npm.md +103 -0
  489. package/skills/engineering/resolve-release/release-check.mjs +93 -0
  490. package/skills/engineering/resolve-release/scripts/apply.mjs +212 -0
  491. package/skills/engineering/resolve-release/scripts/apply.test.mjs +99 -0
  492. package/skills/engineering/resolve-release/scripts/dark-flags.mjs +134 -0
  493. package/skills/engineering/resolve-release/scripts/dark-flags.test.mjs +100 -0
  494. package/skills/engineering/resolve-release/scripts/flow.mjs +500 -0
  495. package/skills/engineering/resolve-release/scripts/flow.test.mjs +340 -0
  496. package/skills/engineering/resolve-release/scripts/green-head.mjs +231 -0
  497. package/skills/engineering/resolve-release/scripts/green-head.test.mjs +101 -0
  498. package/skills/engineering/resolve-release/scripts/npm-version-absence.mjs +138 -0
  499. package/skills/engineering/resolve-release/scripts/npm-version-absence.test.mjs +426 -0
  500. package/skills/engineering/resolve-release/scripts/plan-npm.test.mjs +659 -0
  501. package/skills/engineering/resolve-release/scripts/plan-toml-boundary.test.mjs +317 -0
  502. package/skills/engineering/resolve-release/scripts/plan-uv.test.mjs +291 -0
  503. package/skills/engineering/resolve-release/scripts/plan.mjs +806 -0
  504. package/skills/engineering/resolve-release/scripts/plan.test.mjs +428 -0
  505. package/skills/engineering/rush-issues/LICENSE +3 -0
  506. package/skills/engineering/rush-issues/SKILL.md +58 -0
  507. package/skills/engineering/rush-issues/agents/openai.yaml +8 -0
  508. package/skills/engineering/rush-issues/rehydrate.mjs +10 -0
  509. package/skills/engineering/rush-issues/release-check.mjs +93 -0
  510. package/skills/engineering/rush-release/LICENSE +3 -0
  511. package/skills/engineering/rush-release/SKILL.md +35 -0
  512. package/skills/engineering/rush-release/agents/openai.yaml +8 -0
  513. package/skills/engineering/rush-release/release-check.mjs +93 -0
  514. package/skills/engineering/test-quality/LICENSE +3 -0
  515. package/skills/engineering/test-quality/SKILL.md +79 -0
  516. package/skills/engineering/test-quality/references/audit.md +80 -0
  517. package/skills/engineering/test-quality/references/lint.md +24 -0
  518. package/skills/engineering/test-quality/references/principles.md +143 -0
  519. package/skills/engineering/test-quality/references/testing-conventions.md +9 -0
  520. package/skills/engineering/test-quality/release-check.mjs +93 -0
  521. package/skills/engineering/test-quality/scripts/lint-tests.mjs +574 -0
  522. package/skills/engineering/test-quality/scripts/lint-tests.test.mjs +591 -0
  523. package/skills/engineering/test-quality/scripts/mock-policy-config.mjs +176 -0
  524. package/skills/engineering/test-quality/scripts/mock-policy-config.test.mjs +119 -0
  525. package/skills/engineering/test-quality/scripts/persist-conventions-core.mjs +117 -0
  526. package/skills/engineering/test-quality/scripts/persist-conventions.mjs +17 -0
  527. package/skills/engineering/test-quality/scripts/persist-conventions.test.mjs +104 -0
  528. package/skills/risk-compliance/compliance-audit/LICENSE +3 -0
  529. package/skills/risk-compliance/compliance-audit/SKILL.md +439 -0
  530. package/skills/risk-compliance/compliance-audit/references/findings-schema.md +293 -0
  531. package/skills/risk-compliance/compliance-audit/references/normalization-rules.md +45 -0
  532. package/skills/risk-compliance/compliance-audit/references/vars-checklist.md +1068 -0
  533. package/skills/risk-compliance/compliance-audit/references/vars-findings-schema.md +398 -0
  534. package/skills/risk-compliance/compliance-audit/release-check.mjs +93 -0
  535. package/skills/risk-compliance/compliance-audit/scripts/compliance-audit.test.mjs +14 -0
  536. package/skills/risk-compliance/compliance-audit/scripts/export_tracker_xlsx.py +221 -0
  537. package/skills/risk-compliance/compliance-audit/scripts/reconcile_tracker.py +458 -0
  538. package/skills/risk-compliance/compliance-audit/scripts/test_export_tracker_xlsx.py +97 -0
  539. package/skills/risk-compliance/compliance-audit/scripts/test_validate_tracker.py +157 -0
  540. package/skills/risk-compliance/compliance-audit/scripts/validate_tracker.py +829 -0
  541. package/skills/risk-compliance/contract-compare/LICENSE +3 -0
  542. package/skills/risk-compliance/contract-compare/SKILL.md +199 -0
  543. package/skills/risk-compliance/contract-compare/references/comparison-dimensions.md +209 -0
  544. package/skills/risk-compliance/contract-compare/release-check.mjs +93 -0
  545. package/skills/risk-compliance/contract-review/LICENSE +3 -0
  546. package/skills/risk-compliance/contract-review/SKILL.md +240 -0
  547. package/skills/risk-compliance/contract-review/references/review-criteria.md +223 -0
  548. package/skills/risk-compliance/contract-review/release-check.mjs +93 -0
  549. package/skills/risk-compliance/doc-authenticity/LICENSE +3 -0
  550. package/skills/risk-compliance/doc-authenticity/SKILL.md +160 -0
  551. package/skills/risk-compliance/doc-authenticity/agents/openai.yaml +8 -0
  552. package/skills/risk-compliance/doc-authenticity/manual-invocation.test.mjs +12 -0
  553. package/skills/risk-compliance/doc-authenticity/references/agreement.md +66 -0
  554. package/skills/risk-compliance/doc-authenticity/references/bank-statement.md +150 -0
  555. package/skills/risk-compliance/doc-authenticity/references/forensic-signals.md +157 -0
  556. package/skills/risk-compliance/doc-authenticity/references/image-signals.md +114 -0
  557. package/skills/risk-compliance/doc-authenticity/references/invoice.md +66 -0
  558. package/skills/risk-compliance/doc-authenticity/references/issuer-profiles.md +86 -0
  559. package/skills/risk-compliance/doc-authenticity/references/verdict-and-report.md +95 -0
  560. package/skills/risk-compliance/doc-authenticity/references/verdict.schema.json +81 -0
  561. package/skills/risk-compliance/doc-authenticity/release-check.mjs +93 -0
  562. package/skills/risk-compliance/doc-authenticity/scripts/image_forensics.py +294 -0
  563. package/skills/risk-compliance/doc-authenticity/scripts/pdf_forensics.py +401 -0
  564. package/skills/risk-compliance/doc-authenticity/scripts/validate_codes.py +224 -0
  565. package/skills/risk-compliance/doc-authenticity/scripts/validate_verdict.py +308 -0
  566. package/skills/risk-compliance/doc-authenticity/scripts/validate_verdict.test.mjs +212 -0
  567. package/skills/risk-compliance/fraud-analysis/LICENSE +3 -0
  568. package/skills/risk-compliance/fraud-analysis/SKILL.md +114 -0
  569. package/skills/risk-compliance/fraud-analysis/references/archetypes/authorized-payment-scam.md +41 -0
  570. package/skills/risk-compliance/fraud-analysis/references/archetypes/first-party-fraud.md +44 -0
  571. package/skills/risk-compliance/fraud-analysis/references/archetypes/third-party-fraud.md +27 -0
  572. package/skills/risk-compliance/fraud-analysis/references/contexts/bank-transfer.md +24 -0
  573. package/skills/risk-compliance/fraud-analysis/references/contexts/card-payment.md +30 -0
  574. package/skills/risk-compliance/fraud-analysis/references/contexts/payment-collection.md +20 -0
  575. package/skills/risk-compliance/fraud-analysis/references/contexts/payout.md +20 -0
  576. package/skills/risk-compliance/fraud-analysis/references/feature-engineering.md +158 -0
  577. package/skills/risk-compliance/fraud-analysis/references/mechanisms/account-takeover.md +36 -0
  578. package/skills/risk-compliance/fraud-analysis/references/report-rationale.md +45 -0
  579. package/skills/risk-compliance/fraud-analysis/references/report-template.md +190 -0
  580. package/skills/risk-compliance/fraud-analysis/references/review-checklist.md +175 -0
  581. package/skills/risk-compliance/fraud-analysis/references/taxonomy.md +79 -0
  582. package/skills/risk-compliance/fraud-analysis/references/terminology.md +108 -0
  583. package/skills/risk-compliance/fraud-analysis/references/workflow.md +175 -0
  584. package/skills/risk-compliance/fraud-analysis/release-check.mjs +93 -0
  585. package/skills/risk-compliance/payment-analysis/LICENSE +3 -0
  586. package/skills/risk-compliance/payment-analysis/SKILL.md +180 -0
  587. package/skills/risk-compliance/payment-analysis/references/auth-rate-actions.md +30 -0
  588. package/skills/risk-compliance/payment-analysis/references/chargebacks.md +208 -0
  589. package/skills/risk-compliance/payment-analysis/references/chinese-polish.md +51 -0
  590. package/skills/risk-compliance/payment-analysis/references/event-layers.md +127 -0
  591. package/skills/risk-compliance/payment-analysis/references/fx.md +59 -0
  592. package/skills/risk-compliance/payment-analysis/references/journey.md +79 -0
  593. package/skills/risk-compliance/payment-analysis/references/metrics.md +67 -0
  594. package/skills/risk-compliance/payment-analysis/references/report-template.md +119 -0
  595. package/skills/risk-compliance/payment-analysis/references/terminology.md +95 -0
  596. package/skills/risk-compliance/payment-analysis/references/visualization.md +56 -0
  597. package/skills/risk-compliance/payment-analysis/release-check.mjs +93 -0
  598. package/skills/risk-compliance/report-writing/LICENSE +3 -0
  599. package/skills/risk-compliance/report-writing/SKILL.md +220 -0
  600. package/skills/risk-compliance/report-writing/references/audit-report-template.md +121 -0
  601. package/skills/risk-compliance/report-writing/references/external-assessment-template.md +167 -0
  602. package/skills/risk-compliance/report-writing/references/management-report-template.md +145 -0
  603. package/skills/risk-compliance/report-writing/references/vars-report-template.md +224 -0
  604. package/skills/risk-compliance/report-writing/release-check.mjs +93 -0
  605. package/skills/risk-compliance/similar-domain/LICENSE +3 -0
  606. package/skills/risk-compliance/similar-domain/SKILL.md +203 -0
  607. package/skills/risk-compliance/similar-domain/references/entities.md +67 -0
  608. package/skills/risk-compliance/similar-domain/references/fingerprints.md +134 -0
  609. package/skills/risk-compliance/similar-domain/references/pivots.md +82 -0
  610. package/skills/risk-compliance/similar-domain/references/report-template.md +93 -0
  611. package/skills/risk-compliance/similar-domain/references/scoring.md +130 -0
  612. package/skills/risk-compliance/similar-domain/references/terminology.md +41 -0
  613. package/skills/risk-compliance/similar-domain/references/workflow.md +85 -0
  614. package/skills/risk-compliance/similar-domain/release-check.mjs +93 -0
  615. package/skills/risk-compliance/tld-detection/LICENSE +3 -0
  616. package/skills/risk-compliance/tld-detection/SKILL.md +245 -0
  617. package/skills/risk-compliance/tld-detection/agents/openai.yaml +9 -0
  618. package/skills/risk-compliance/tld-detection/references/chinese-polish.md +55 -0
  619. package/skills/risk-compliance/tld-detection/references/clean.md +51 -0
  620. package/skills/risk-compliance/tld-detection/references/clustering.md +235 -0
  621. package/skills/risk-compliance/tld-detection/references/decision.md +189 -0
  622. package/skills/risk-compliance/tld-detection/references/fx-conversion.md +232 -0
  623. package/skills/risk-compliance/tld-detection/references/report-template.md +272 -0
  624. package/skills/risk-compliance/tld-detection/references/streams.md +141 -0
  625. package/skills/risk-compliance/tld-detection/references/terminology.md +95 -0
  626. package/skills/risk-compliance/tld-detection/references/workflow.md +95 -0
  627. package/skills/risk-compliance/tld-detection/release-check.mjs +93 -0
  628. package/skills/risk-compliance/tld-detection/scripts/detect_ladders.py +390 -0
  629. package/skills/risk-compliance/tld-detection/scripts/detect_ladders.test.mjs +14 -0
  630. package/skills/risk-compliance/tld-detection/scripts/detect_streams.py +650 -0
  631. package/skills/risk-compliance/tld-detection/scripts/detect_streams.test.mjs +14 -0
  632. package/skills/risk-compliance/tld-detection/tests/test_detect_ladders.py +134 -0
  633. package/skills/risk-compliance/tld-detection/tests/test_detect_streams.py +520 -0
@@ -0,0 +1,612 @@
1
+ // ci-run-audit core — audit a repo's GitHub Actions *run history* for waste. Report-only:
2
+ // it never edits a workflow, and every finding is a candidate for a human to judge.
3
+ //
4
+ // Where ensure-coverage's ci-audit.mjs reads CI *config* (are the gates wired?), this
5
+ // reads what CI actually *did* (what did it cost, and what did it catch?). Six checks:
6
+ //
7
+ // 1. re-proved trees a job passing again on a byte-identical tree (same git tree
8
+ // hash) it already passed: PR → queue → main re-runs
9
+ // 2. catch yield per job: code states it failed on (and never passed), how many of
10
+ // those were its alone, and how many were flakes (failed, then
11
+ // passed on the same tree)
12
+ // 3. overlap job A whose every real failure coincided with job B failing
13
+ // 4. setup share jobs whose time is mostly overhead: everything but their own
14
+ // work steps (checkout, install, containers, runner gaps)
15
+ // 5. critical path PR wait time p50/p90 and which job finishes last
16
+ // 6. stuck + queued jobs that ran past 6h, and time waiting for a runner
17
+ //
18
+ // Usage:
19
+ // node ci-run-audit.mjs --repo <owner/name> [--runs 300] [--save runs.json] [--json]
20
+ // node ci-run-audit.mjs --from runs.json [--json] # re-analyse a saved pull
21
+ // node ci-run-audit.mjs --repo <owner/name> --profile-run <runId> [--save runs.json] [--json]
22
+ // node ci-run-audit.mjs --from runs.json --profile-run <runId> [--json]
23
+ // Options: --ignore-job <regex> (added to the default aggregator filter),
24
+ // --min-trees <n> (sample floor for a "no sole catch" candidate, default 20).
25
+ // Needs an authenticated `gh`. Exit 0 with a report; 2 on usage or fetch errors.
26
+ //
27
+ // Import-only: `ci-run-audit.mjs` is the always-running CLI wrapper, so no main-module
28
+ // detection lives here (skills/main-guard-hygiene.test.mjs).
29
+
30
+ import { execFile } from 'node:child_process'
31
+ import { promisify } from 'node:util'
32
+ import fs from 'node:fs'
33
+
34
+ const run = promisify(execFile)
35
+
36
+ // Aggregator / umbrella jobs only restate other jobs' results; counting them would make
37
+ // every real failure look shared. Extend per repo with --ignore-job.
38
+ // "PR admission gate" is a roll-up; "smoke-gate" and "route-docs-gate" are real checks.
39
+ export const DEFAULT_IGNORE = /^(all-gates|ci|.*\b(umbrella|verdict|timing report)\b.*|.* gate)$/i
40
+ // Step names that are runner/setup overhead rather than the job's own check.
41
+ export const SETUP_STEP = /^(set up job|initiali[sz]e containers|stop containers|complete job|post |run actions\/(checkout|setup-|cache)|checkout|check out|setup |set up |install |restore |.*\bcache\b|.*pnpm\/action-setup|.*corepack)/i
42
+ const STUCK_MIN = 360 // GitHub's hosted-job ceiling; anything past it hung
43
+
44
+ const min = (a, b) => (Date.parse(b) - Date.parse(a)) / 60000
45
+ const median = (xs) => { if (!xs.length) return 0; const s = [...xs].sort((a, b) => a - b); const m = s.length >> 1; return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2 }
46
+ const pct = (xs, q) => { if (!xs.length) return 0; const s = [...xs].sort((a, b) => a - b); return s[Math.floor(q * (s.length - 1))] }
47
+
48
+ // Which lane a run belongs to. Merge-queue branches are recognised for Mergify and
49
+ // GitHub's native queue; everything else falls back to the event name.
50
+ export function lane(r) {
51
+ const b = r.branch || ''
52
+ if (b.startsWith('mergify/') || b.startsWith('gh-readonly-queue/')) return 'queue'
53
+ if (r.event === 'pull_request' || r.event === 'pull_request_target') return 'pr'
54
+ if (r.event === 'push' && (b === r.defaultBranch || b === 'main' || b === 'master')) return 'main'
55
+ return r.event
56
+ }
57
+
58
+ const timed = (j) => j.started && j.completed && j.conclusion !== 'skipped'
59
+ const dur = (j) => min(j.started, j.completed)
60
+ const workflowKey = (r) => JSON.stringify(r.workflowId != null ? ['id', r.workflowId]
61
+ : r.workflowPath ? ['path', r.workflowPath] : ['name', r.workflow])
62
+ const jobKey = (r, j) => JSON.stringify([workflowKey(r), j.name])
63
+
64
+ export function analyze(runs, { ignore = DEFAULT_IGNORE, minTrees = 20 } = {}) {
65
+ runs = [...runs].sort((a, b) => Date.parse(a.created) - Date.parse(b.created))
66
+ const jobsOf = (r) => r.jobs.filter((j) => timed(j) && dur(j) >= 0)
67
+ const gaps = dataGaps(runs)
68
+ const owners = new Map(), labels = new Map()
69
+ for (const r of runs) for (const j of r.jobs) {
70
+ if (!owners.has(j.name)) owners.set(j.name, new Set())
71
+ owners.get(j.name).add(workflowKey(r))
72
+ }
73
+ for (const r of runs) for (const j of r.jobs) labels.set(jobKey(r, j),
74
+ owners.get(j.name).size > 1 ? `${r.workflow} [${r.workflowId ?? r.workflowPath ?? r.workflow}] / ${j.name}` : j.name)
75
+
76
+ // 1. Re-proved trees. Keyed by workflow + job + tree, so a PR-only job and a main-only
77
+ // job never collide. Scheduled re-runs are deliberate (drift, dates, upstream), so they
78
+ // are reported apart and not counted as waste.
79
+ const firstPass = new Map()
80
+ let passMin = 0, reprovedMin = 0, scheduledRepeatMin = 0
81
+ const byLanePair = {}, byJob = {}
82
+ for (const r of runs) {
83
+ for (const j of jobsOf(r)) {
84
+ if (j.conclusion !== 'success' || dur(j) > STUCK_MIN) continue
85
+ const m = dur(j); passMin += m
86
+ const k = JSON.stringify([jobKey(r, j), r.tree])
87
+ if (!r.tree || !firstPass.has(k)) { if (r.tree) firstPass.set(k, lane(r)); continue }
88
+ if (lane(r) === 'schedule') { scheduledRepeatMin += m; continue }
89
+ reprovedMin += m
90
+ const pair = `${firstPass.get(k)}→${lane(r)}`
91
+ byLanePair[pair] = (byLanePair[pair] || 0) + m
92
+ const label = labels.get(jobKey(r, j))
93
+ byJob[label] = (byJob[label] || 0) + m
94
+ }
95
+ }
96
+
97
+ // 2 + 3. Yield and overlap, per code state (tree; sha if the tree is unknown). A job
98
+ // "really failed" on a tree only if it never passed there — fail-then-pass is a flake.
99
+ const states = new Map()
100
+ for (const r of runs) {
101
+ const key = r.tree ? `tree:${r.tree}` : r.sha ? `sha:${r.sha}` : r
102
+ if (!states.has(key)) states.set(key, new Map())
103
+ const s = states.get(key)
104
+ for (const j of r.jobs) {
105
+ if (!['success', 'failure'].includes(j.conclusion) || ignore.test(j.name)) continue
106
+ const identity = jobKey(r, j)
107
+ if (!s.has(identity)) s.set(identity, new Set())
108
+ s.get(identity).add(j.conclusion)
109
+ }
110
+ }
111
+ const y = new Map()
112
+ const Y = (n) => {
113
+ if (!y.has(n)) y.set(n, { job: labels.get(n), trees: 0, failed: 0, sole: 0, flaky: 0, coFail: {} })
114
+ return y.get(n)
115
+ }
116
+ for (const s of states.values()) {
117
+ const real = [...s].filter(([, c]) => c.has('failure') && !c.has('success')).map(([n]) => n)
118
+ for (const [n, c] of s) {
119
+ const e = Y(n); e.trees++
120
+ if (c.has('failure') && c.has('success')) e.flaky++
121
+ }
122
+ for (const n of real) {
123
+ const e = Y(n); e.failed++
124
+ if (real.length === 1) e.sole++
125
+ for (const o of real) if (o !== n) e.coFail[o] = (e.coFail[o] || 0) + 1
126
+ }
127
+ }
128
+ const yieldEntries = [...y.values()].sort((a, b) => b.trees - a.trees)
129
+ const yieldRows = yieldEntries.map((e) => ({ ...e,
130
+ coFail: Object.fromEntries(Object.entries(e.coFail).map(([n, count]) => [labels.get(n), count])),
131
+ }))
132
+ const noSoleCatch = yieldRows.filter((e) => e.trees >= minTrees && e.sole === 0)
133
+ const overlap = []
134
+ for (const e of yieldEntries) {
135
+ if (e.failed < 3) continue
136
+ for (const [o, n] of Object.entries(e.coFail)) {
137
+ if (n === e.failed) overlap.push({ job: e.job, alwaysWith: labels.get(o), failures: e.failed, otherFailures: y.get(o).failed })
138
+ }
139
+ }
140
+
141
+ // 4. Setup share, per workflow/job, over passing runs (a failed step skews the ratio).
142
+ // Measured as 1 − own-work / job time: in a short job much of the overhead sits
143
+ // *between* steps (runner start, post-steps), so summing named setup steps undercounts.
144
+ const setup = {}
145
+ for (const r of runs) for (const j of r.jobs) {
146
+ if (j.conclusion !== 'success' || dur(j) > STUCK_MIN || ignore.test(j.name)) continue
147
+ const timing = jobTiming(j)
148
+ if (!timing.completeTiming) {
149
+ gaps.timings.push({ runId: r.id, job: labels.get(jobKey(r, j)), reason: 'setup/work unavailable: missing, invalid, or inconsistent step/job timestamps' })
150
+ continue
151
+ }
152
+ const e = (setup[jobKey(r, j)] ||= { job: labels.get(jobKey(r, j)), n: 0, totalS: 0, setupS: 0, durs: [] })
153
+ e.n++; e.totalS += timing.elapsedS; e.setupS += timing.setupS; e.durs.push(timing.elapsedS / 60)
154
+ }
155
+ const setupRows = Object.values(setup)
156
+ .map((e) => ({ job: e.job, runs: e.n, medianMin: median(e.durs), setupShare: e.totalS ? e.setupS / e.totalS : 0, setupMin: e.setupS / 60 }))
157
+ .filter((e) => e.runs >= 3 && e.setupShare >= 0.7)
158
+ .sort((a, b) => b.setupMin - a.setupMin)
159
+
160
+ // 5. Critical path on the PR lane: per head sha, first run created → last job done.
161
+ const prBySha = new Map()
162
+ for (const r of runs) if (lane(r) === 'pr') {
163
+ const key = r.sha ?? r
164
+ if (!prBySha.has(key)) prBySha.set(key, [])
165
+ prBySha.get(key).push(r)
166
+ }
167
+ const waits = [], lastJob = {}
168
+ for (const rs of prBySha.values()) {
169
+ // Roll-up jobs finish last by construction; the job that set the wait is the last real one.
170
+ const js = rs.flatMap((r) => jobsOf(r).map((j) => ({ ...j, label: labels.get(jobKey(r, j)) })))
171
+ .filter((j) => dur(j) <= STUCK_MIN && !ignore.test(j.name))
172
+ if (!js.length) continue
173
+ const start = rs.map((r) => r.created).filter((t) => t && Number.isFinite(Date.parse(t)))
174
+ .sort((a, b) => Date.parse(a) - Date.parse(b))[0]
175
+ const last = js.reduce((a, j) => (Date.parse(j.completed) > Date.parse(a.completed) ? j : a))
176
+ const interval = seconds(start, last.completed)
177
+ if (interval === null || rs.some((r) => !r.created || !Number.isFinite(Date.parse(r.created)))) {
178
+ gaps.timings.push({ runId: rs[0].id, job: last.label, reason: 'PR creation-to-completion interval unavailable' })
179
+ continue
180
+ }
181
+ waits.push(interval / 60)
182
+ lastJob[last.label] = (lastJob[last.label] || 0) + 1
183
+ }
184
+
185
+ // 6. Stuck jobs and runner queue time.
186
+ const stuck = [], queued = []
187
+ for (const r of runs) for (const j of r.jobs) {
188
+ if (j.started && j.completed && dur(j) > STUCK_MIN) stuck.push({ workflow: r.workflow, job: j.name, hours: dur(j) / 60, conclusion: j.conclusion, started: j.started, branch: r.branch })
189
+ if (j.conclusion === 'skipped') continue
190
+ const interval = seconds(j.created, j.started)
191
+ if (interval !== null) queued.push(interval / 60)
192
+ else gaps.timings.push({ runId: r.id, job: labels.get(jobKey(r, j)), reason: 'created-to-start interval unavailable' })
193
+ }
194
+
195
+ return {
196
+ minTrees,
197
+ dataGaps: gaps,
198
+ window: { runs: runs.length, from: runs[0]?.created ?? null, to: runs.at(-1)?.created ?? null },
199
+ reproved: { identity: 'run-metadata-tree-candidates', testedCheckoutVerified: false,
200
+ passMin, reprovedMin, share: passMin ? reprovedMin / passMin : 0, scheduledRepeatMin, byLanePair, byJob },
201
+ yield: yieldRows, noSoleCatch, overlap,
202
+ setup: setupRows,
203
+ criticalPath: { prCommits: waits.length, p50: waits.length ? median(waits) : null, p90: waits.length ? pct(waits, 0.9) : null, lastJob },
204
+ stuck, queue: { samples: queued.length, p50: queued.length ? median(queued) : null, p90: queued.length ? pct(queued, 0.9) : null },
205
+ }
206
+ }
207
+
208
+ function dataGaps(runs) {
209
+ return {
210
+ unknownTrees: runs.filter((r) => !r.tree).map((r) => ({
211
+ runId: r.id, sha: r.sha ?? null, reason: r.treeUnknownReason ?? 'tree not recorded in saved data',
212
+ })),
213
+ testedCheckouts: runs.filter((r) => r.tree).map((r) => ({
214
+ runId: r.id, reason: 'Run tree metadata does not establish the tested checkout; PR synthetic merges and explicit checkout refs may differ. Matches are candidates, not reusable gate proof.',
215
+ })),
216
+ collection: runs.flatMap((r) => (r.collectionGaps || []).map((gap) => ({ runId: r.id, ...gap }))),
217
+ timings: [],
218
+ }
219
+ }
220
+
221
+ const seconds = (start, end) => {
222
+ if (!start || !end) return null
223
+ const s = (Date.parse(end) - Date.parse(start)) / 1000
224
+ return Number.isFinite(s) && s >= 0 ? s : null
225
+ }
226
+
227
+ function jobTiming(j) {
228
+ const steps = (j.steps || []).map((s) => ({
229
+ name: s.name, number: s.number ?? null, conclusion: s.conclusion ?? null,
230
+ started: s.started ?? null, completed: s.completed ?? null,
231
+ durationS: s.conclusion === 'skipped' ? 0 : seconds(s.started, s.completed),
232
+ kind: SETUP_STEP.test(s.name) ? 'setup-heuristic' : 'work',
233
+ })).sort((a, b) => (b.durationS ?? -1) - (a.durationS ?? -1))
234
+ const elapsedS = seconds(j.started, j.completed)
235
+ const intervals = steps.filter((s) => s.durationS > 0).sort((a, b) => Date.parse(a.started) - Date.parse(b.started))
236
+ const nonOverlapping = intervals.every((s, i) => i === 0 || Date.parse(s.started) >= Date.parse(intervals[i - 1].completed))
237
+ const measuredWork = steps.filter((s) => s.kind === 'work').reduce((n, s) => n + (s.durationS ?? 0), 0)
238
+ const completeTiming = steps.length > 0 && steps.every((s) => s.durationS !== null &&
239
+ (s.conclusion === 'skipped' || (seconds(j.started, s.started) !== null && seconds(s.completed, j.completed) !== null))) &&
240
+ elapsedS !== null && measuredWork <= elapsedS && nonOverlapping
241
+ return { steps, elapsedS, completeTiming, workS: completeTiming ? measuredWork : null,
242
+ setupS: completeTiming ? Math.max(0, elapsedS - measuredWork) : null }
243
+ }
244
+
245
+ // Setup is a name-based heuristic: elapsed minus timed non-setup work includes gaps.
246
+ // REST timestamps do not expose DAG eligibility or the instant a runner was requested.
247
+ export function profileRun(runs, runId) {
248
+ const matches = runs.filter((r) => String(r.id) === String(runId))
249
+ if (matches.length !== 1) throw new Error(matches.length ? `multiple records for run ${runId}` : `run ${runId} not found in saved data`)
250
+ const r = matches[0]
251
+ const jobs = r.jobs.map((j) => {
252
+ const { steps, elapsedS, completeTiming, workS, setupS } = jobTiming(j)
253
+ return {
254
+ id: j.id ?? null, name: j.name, attempt: j.attempt ?? r.attempt ?? null,
255
+ url: j.url ?? null, runUrl: j.runUrl ?? r.url ?? null, conclusion: j.conclusion ?? null,
256
+ status: j.status ?? null, created: j.created ?? null, started: j.started ?? null, completed: j.completed ?? null,
257
+ runnerId: j.runnerId ?? null, runnerName: j.runnerName ?? null,
258
+ runnerGroupId: j.runnerGroupId ?? null, runnerGroupName: j.runnerGroupName ?? null,
259
+ runnerLabels: j.runnerLabels ?? null,
260
+ dispatchDelayS: seconds(r.created, j.created), queueS: seconds(j.created, j.started),
261
+ elapsedS, workS, setupS,
262
+ timingGaps: [
263
+ ...(!completeTiming ? ['setup/work unavailable: missing, invalid, or inconsistent step/job timestamps'] : []),
264
+ ...(seconds(j.created, j.started) === null ? ['queue interval unavailable'] : []),
265
+ ...(seconds(r.created, j.created) === null ? ['dispatch interval unavailable'] : []),
266
+ ],
267
+ steps,
268
+ }
269
+ })
270
+ return {
271
+ mode: 'profile', runId: r.id, attempt: r.attempt ?? null, runUrl: r.url ?? null,
272
+ workflow: r.workflow, workflowId: r.workflowId ?? null, workflowPath: r.workflowPath ?? null,
273
+ event: r.event, branch: r.branch, sha: r.sha ?? null, tree: r.tree ?? null,
274
+ status: r.status ?? null, conclusion: r.conclusion ?? null,
275
+ created: r.created ?? null, started: r.started ?? null, dataGaps: dataGaps([r]),
276
+ timingNotes: {
277
+ units: 'seconds; null means unknown',
278
+ setup: 'Heuristic: job elapsed minus timed non-setup steps; includes setup, teardown and inter-step gaps. Step names classify setup, not measured resource use.',
279
+ queue: 'Job created to started is an observed delay, not proven runner queue time; REST does not separate runner availability, dispatch or DAG dependencies.',
280
+ dispatch: 'Run created to job created may include dispatch/DAG delay. On reruns it can include time before the selected attempt; it is not runner queue time.',
281
+ scope: 'Latest recorded attempt only; no logs, billing or workflow DAG collected. Recommendations only; preserve required gates.',
282
+ },
283
+ jobs,
284
+ }
285
+ }
286
+
287
+ export function renderProfile(p) {
288
+ const show = (s) => s === null ? 'unknown' : `${s.toFixed(1)}s`
289
+ const L = [`# CI run profile: ${p.workflow} / ${p.runId}`, '',
290
+ `Attempt: ${p.attempt ?? 'unknown'}; conclusion: ${p.conclusion ?? 'unknown'}; run: ${p.runUrl ?? 'unknown'}.`, '',
291
+ ...Object.values(p.timingNotes), '',
292
+ '| job | dispatch/DAG delay | created-to-start delay (queue uncertain) | setup heuristic | work | job elapsed | conclusion | runner |',
293
+ '|---|---|---|---|---|---|---|---|']
294
+ for (const j of p.jobs) L.push(`| ${j.name} | ${show(j.dispatchDelayS)} | ${show(j.queueS)} | ${show(j.setupS)} | ${show(j.workS)} | ${show(j.elapsedS)} | ${j.conclusion ?? 'unknown'} | ${j.runnerName ?? 'unknown'} (${j.runnerLabels?.join(', ') ?? 'labels unknown'}) |`)
295
+ for (const j of p.jobs) {
296
+ L.push('', `## ${j.name} (attempt ${j.attempt ?? 'unknown'})`, '', `Job: ${j.url ?? 'unknown'}; run: ${j.runUrl ?? 'unknown'}.`)
297
+ for (const gap of j.timingGaps) L.push(`- ${gap}`)
298
+ L.push('', '| step (longest first) | duration | classification | conclusion |', '|---|---|---|---|')
299
+ for (const s of j.steps) L.push(`| ${s.name} | ${show(s.durationS)} | ${s.kind} | ${s.conclusion ?? 'unknown'} |`)
300
+ }
301
+ appendGaps(L, p.dataGaps)
302
+ return L.join('\n')
303
+ }
304
+
305
+ function appendGaps(L, gaps) {
306
+ if (!gaps.unknownTrees.length && !gaps.testedCheckouts.length && !gaps.collection.length && !gaps.timings.length) return
307
+ L.push('', '## Data gaps', '')
308
+ L.push(`${gaps.unknownTrees.length} run(s) have no verified tree; excluded from identical-tree repeat evidence. Catch yield falls back to commit SHA, not verified tree equality.`)
309
+ for (const g of gaps.unknownTrees) L.push(`- Run ${g.runId}, SHA ${g.sha ?? 'unknown'}: ${g.reason}`)
310
+ if (gaps.testedCheckouts.length) L.push(`- ${gaps.testedCheckouts.length} run(s): ${gaps.testedCheckouts[0].reason}`)
311
+ for (const g of gaps.collection) L.push(`- Run ${g.runId}: ${g.reason}`)
312
+ if (gaps.timings.length) L.push(`- ${gaps.timings.length} unavailable timing measurement(s); excluded rather than counted as zero.`)
313
+ for (const g of gaps.timings.slice(0, 6)) L.push(`- Run ${g.runId}, ${g.job}: ${g.reason}`)
314
+ }
315
+
316
+ const f1 = (x) => x.toFixed(1)
317
+ const minutes = (m) => m === null ? 'unknown' : `${f1(m)}m`
318
+ const top = (o, n = 6) => Object.entries(o).sort((a, b) => b[1] - a[1]).slice(0, n)
319
+
320
+ export function render(a, repo = '') {
321
+ const L = []
322
+ L.push(`# CI run audit${repo ? ` — ${repo}` : ''}`, '')
323
+ L.push(`${a.window.runs} completed runs, ${a.window.from?.slice(0, 10)} → ${a.window.to?.slice(0, 10)}. Report-only: each row is a candidate with its evidence, not a verdict.`, '')
324
+
325
+ L.push('## 1. Re-proved trees', '')
326
+ L.push(`**${f1(a.reproved.reprovedMin)} of ${f1(a.reproved.passMin)} passing job-minutes (${Math.round(a.reproved.share * 100)}%)** matched prior workflow/job run-tree metadata; these are repeat candidates, not verified repeated tested checkouts.` +
327
+ (a.reproved.scheduledRepeatMin ? ` Scheduled repeats (${f1(a.reproved.scheduledRepeatMin)} min) are left out as deliberate.` : ''), '')
328
+ if (top(a.reproved.byLanePair).length) {
329
+ L.push('| first passed → again on | minutes |', '|---|---|')
330
+ for (const [k, v] of top(a.reproved.byLanePair)) L.push(`| ${k} | ${f1(v)} |`)
331
+ L.push('', '| job | re-proved minutes |', '|---|---|')
332
+ for (const [k, v] of top(a.reproved.byJob)) L.push(`| ${k} | ${f1(v)} |`)
333
+ L.push('', 'Inspect actual checkout refs and execution inputs before proposing reuse. A PR head tree can differ from its tested synthetic merge. Changed combined queue trees still need proof; main reuse requires complete trusted queue evidence. Date- or network-dependent jobs can legitimately repeat.')
334
+ }
335
+ L.push('')
336
+
337
+ L.push('## 2. Catch yield', '')
338
+ L.push('Per metadata code state (resolved run tree, otherwise commit SHA only; tested checkout unverified). *failed* = failed and never passed on that state; *sole* = the only job that failed there; *flaky* = failed and passed on the same state, an instability lead rather than a diagnosed test flake.', '')
339
+ L.push('| job | trees | failed | sole | flaky | fails most often with |', '|---|---|---|---|---|---|')
340
+ for (const e of a.yield) {
341
+ const w = top(e.coFail, 1)[0]
342
+ L.push(`| ${e.job} | ${e.trees} | ${e.failed} | ${e.sole} | ${e.flaky} | ${w ? `${w[0]} ×${w[1]}` : ''} |`)
343
+ }
344
+ L.push('')
345
+ if (a.noSoleCatch.length) {
346
+ L.push(`**No sole catch** in this window: ${a.noSoleCatch.map((e) => `\`${e.job}\` (${e.trees} trees${e.failed ? `, ${e.failed} shared failures` : ', never failed'})`).join(', ')}.`)
347
+ L.push('Candidates to merge into another job, move off the PR lane, or retire — unless the job guards something rare and costly (a security or money path), where zero catches is the point. Widen the window before deciding.', '')
348
+ } else if (!a.yield.some((e) => e.trees >= a.minTrees)) {
349
+ L.push(`No job ran on ${a.minTrees}+ code states, too few to call any job zero-yield. Widen the window (\`--runs\`) or lower \`--min-trees\`.`, '')
350
+ }
351
+
352
+ L.push('## 3. Overlap', '')
353
+ if (!a.overlap.length) L.push('No job with ≥3 failures whose every failure coincided with another job failing.', '')
354
+ else {
355
+ L.push('| job | every failure also failed | its failures | other job\'s failures |', '|---|---|---|---|')
356
+ for (const o of a.overlap) L.push(`| ${o.job} | ${o.alwaysWith} | ${o.failures} | ${o.otherFailures} |`)
357
+ L.push('', 'Either the two test the same thing, or both depend on one broken fixture (shared boot, DB, build). Read one failure log of each before merging them.', '')
358
+ }
359
+
360
+ L.push('## 4. Setup share', '')
361
+ if (!a.setup.length) L.push('No measured job meets the ≥70% setup candidate threshold across at least three runs.', '')
362
+ else {
363
+ L.push('| job | runs | median | setup share | setup minutes |', '|---|---|---|---|---|')
364
+ for (const e of a.setup) L.push(`| ${e.job} | ${e.runs} | ${f1(e.medianMin)}m | ${Math.round(e.setupShare * 100)}% | ${f1(e.setupMin)} |`)
365
+ L.push('', 'Jobs that are mostly setup are cheap to merge: several such checks can share one job, one checkout, one install.', '')
366
+ }
367
+
368
+ L.push('## 5. Critical path (PR lane)', '')
369
+ const c = a.criticalPath
370
+ L.push(`${c.prCommits} PR commits: wait p50 **${minutes(c.p50)}**, p90 **${minutes(c.p90)}**. Last job to finish:`, '')
371
+ for (const [k, v] of top(c.lastJob, 5)) L.push(`- ${k}: ${v}`)
372
+ L.push('', 'The last-finishing job is a lead, not the whole critical path. Inspect its prerequisites: reducing an upstream build or dispatch delay can shorten PR latency too. This timestamp report does not resolve the workflow DAG.', '')
373
+
374
+ L.push('## 6. Stuck jobs and runner queue', '')
375
+ L.push(`Queue wait (job created → started, not proven runner-only queue): ${a.queue.samples} measured job(s), p50 ${minutes(a.queue.p50)}, p90 ${minutes(a.queue.p90)}. REST does not separate runner queue from dispatch/DAG delay.`)
376
+ if (a.stuck.length) {
377
+ L.push('', `**${a.stuck.length} job(s) ran past 6h**, so they hung rather than ran slowly (check the runner, and set \`timeout-minutes\`):`, '')
378
+ for (const s of a.stuck.slice(0, 10)) L.push(`- ${s.workflow} / ${s.job}: ${f1(s.hours)}h, ${s.conclusion}, ${s.started.slice(0, 16)}, ${s.branch}`)
379
+ }
380
+ appendGaps(L, a.dataGaps)
381
+ L.push('')
382
+ return L.join('\n')
383
+ }
384
+
385
+ // ---- fetch (thin; everything above is pure) ----
386
+
387
+ const MAX_RUNS = 2000
388
+ const MAX_JOB_PAGES = 100 // 10,000 jobs; refuse instead of silently truncating.
389
+ const GH_TIMEOUT_MS = 60000
390
+
391
+ async function gh(path) {
392
+ try {
393
+ const { stdout } = await run('gh', ['api', path], {
394
+ timeout: GH_TIMEOUT_MS, killSignal: 'SIGKILL', maxBuffer: 16 * 1024 * 1024,
395
+ env: { ...process.env, GH_PROMPT_DISABLED: '1' },
396
+ })
397
+ return JSON.parse(stdout)
398
+ } catch (e) {
399
+ const status = /HTTP\s+(\d{3})/.exec(e.stderr || '')?.[1]
400
+ if (status) e.status = Number(status)
401
+ throw e
402
+ }
403
+ }
404
+
405
+ async function pool(items, n, fn) {
406
+ const out = new Array(items.length); let i = 0, failure
407
+ // Stop assigning work on error, but drain in-flight gh calls before returning.
408
+ await Promise.all(Array.from({ length: Math.min(n, items.length) }, async () => {
409
+ while (!failure && i < items.length) {
410
+ const k = i++
411
+ try { out[k] = await fn(items[k]) } catch (e) { failure ||= e }
412
+ }
413
+ }))
414
+ if (failure) throw failure
415
+ return out
416
+ }
417
+
418
+ function validRepo(repo) {
419
+ if (typeof repo !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9_.-]*\/[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(repo)) throw new Error('--repo must be owner/name')
420
+ }
421
+
422
+ function positiveInt(value, label, max = Number.MAX_SAFE_INTEGER) {
423
+ if (!/^[1-9]\d*$/.test(String(value)) || !Number.isSafeInteger(Number(value)) || Number(value) > max) throw new Error(`${label} must be an integer from 1 to ${max}`)
424
+ return Number(value)
425
+ }
426
+
427
+ function collector(repo, api) {
428
+ validRepo(repo)
429
+ const trees = new Map()
430
+ async function treeOf(r) {
431
+ if (r.head_commit?.tree_id) return { tree: r.head_commit.tree_id, treeSource: 'actions.head_commit.tree_id', treeUnknownReason: null }
432
+ if (!r.head_sha) return { tree: null, treeSource: null, treeUnknownReason: 'head SHA unavailable' }
433
+ // Cache the promise, including unavailable results, before concurrent runs await it.
434
+ if (!trees.has(r.head_sha)) trees.set(r.head_sha, (async () => {
435
+ try {
436
+ const commit = await api(`repos/${repo}/commits/${encodeURIComponent(r.head_sha)}`)
437
+ const tree = commit.commit?.tree?.sha
438
+ return tree ? { tree, treeSource: 'commits.commit.tree.sha', treeUnknownReason: null }
439
+ : { tree: null, treeSource: null, treeUnknownReason: 'commit response has no tree SHA' }
440
+ } catch (e) {
441
+ if (![404, 410, 422].includes(e.status)) throw e
442
+ return { tree: null, treeSource: null, treeUnknownReason: `commit unavailable (HTTP ${e.status}); deleted queue commits may no longer resolve` }
443
+ }
444
+ })())
445
+ return trees.get(r.head_sha)
446
+ }
447
+ async function collect(r, defaultBranch) {
448
+ positiveInt(r.id, 'run ID')
449
+ const attempt = r.run_attempt == null ? null : positiveInt(r.run_attempt, 'run attempt')
450
+ const endpoint = attempt === null ? `repos/${repo}/actions/runs/${r.id}/jobs?filter=latest&`
451
+ : `repos/${repo}/actions/runs/${r.id}/attempts/${attempt}/jobs?`
452
+ const jobs = []
453
+ let complete = false, total
454
+ for (let page = 1; page <= MAX_JOB_PAGES; page++) {
455
+ const result = await api(`${endpoint}per_page=100&page=${page}`)
456
+ if (!Array.isArray(result.jobs) || result.jobs.length > 100) throw new Error(`invalid jobs response for run ${r.id}`)
457
+ if (result.total_count != null) {
458
+ if (!Number.isSafeInteger(result.total_count) || result.total_count < 0 || result.total_count > MAX_JOB_PAGES * 100) throw new Error(`job pagination limit or invalid total for run ${r.id}`)
459
+ if (total !== undefined && total !== result.total_count) throw new Error(`job total changed during collection for run ${r.id}`)
460
+ total = result.total_count
461
+ }
462
+ jobs.push(...result.jobs)
463
+ if (total !== undefined && jobs.length >= total) {
464
+ if (jobs.length !== total) throw new Error(`inconsistent job total for run ${r.id}`)
465
+ complete = true; break
466
+ }
467
+ if (result.jobs.length < 100) {
468
+ if (total !== undefined && jobs.length < total) throw new Error(`incomplete jobs response for run ${r.id}`)
469
+ complete = true; break
470
+ }
471
+ }
472
+ if (!complete) throw new Error(`job pagination limit reached for run ${r.id}; refusing partial data`)
473
+ const seen = new Set()
474
+ for (const j of jobs) {
475
+ positiveInt(j.id, 'job ID')
476
+ if (seen.has(j.id)) throw new Error(`duplicate job ${j.id} while collecting run ${r.id}`)
477
+ seen.add(j.id)
478
+ if (attempt !== null && j.run_attempt != null && j.run_attempt !== attempt) throw new Error(`job attempt mismatch for run ${r.id}`)
479
+ }
480
+ return {
481
+ id: r.id, attempt, url: r.html_url ?? null,
482
+ workflow: r.name, workflowId: r.workflow_id ?? null, workflowPath: r.path ?? null,
483
+ event: r.event, branch: r.head_branch, defaultBranch,
484
+ status: r.status ?? null, conclusion: r.conclusion ?? null,
485
+ sha: r.head_sha ?? null, ...await treeOf(r), created: r.created_at, started: r.run_started_at ?? null,
486
+ collectionScope: 'latest-attempt',
487
+ collectionGaps: attempt === null ? [{ reason: 'run attempt unavailable; collected latest jobs only' }]
488
+ : attempt > 1 ? [{ reason: `only attempt ${attempt} collected; earlier attempts are not included` }] : [],
489
+ jobs: jobs.map((j) => ({
490
+ id: j.id, name: j.name, attempt: j.run_attempt ?? attempt, url: j.html_url ?? null, runUrl: r.html_url ?? null,
491
+ status: j.status ?? null, conclusion: j.conclusion ?? null,
492
+ created: j.created_at ?? null, started: j.started_at ?? null, completed: j.completed_at ?? null,
493
+ runnerId: j.runner_id ?? null, runnerName: j.runner_name ?? null,
494
+ runnerGroupId: j.runner_group_id ?? null, runnerGroupName: j.runner_group_name ?? null,
495
+ runnerLabels: j.labels ?? null,
496
+ steps: (j.steps || []).map((s) => ({
497
+ number: s.number ?? null, name: s.name, conclusion: s.conclusion ?? null,
498
+ started: s.started_at ?? null, completed: s.completed_at ?? null,
499
+ })),
500
+ })),
501
+ }
502
+ }
503
+ return collect
504
+ }
505
+
506
+ // The optional API function is the gh/provider boundary for fixture tests, never a CLI flag.
507
+ export async function fetchRuns(repo, want = 300, api = gh) {
508
+ want = positiveInt(want, '--runs', MAX_RUNS)
509
+ const collect = collector(repo, api)
510
+ const meta = await api(`repos/${repo}`)
511
+ const raw = []
512
+ for (let page = 1; raw.length < want && page <= 20; page++) {
513
+ const result = await api(`repos/${repo}/actions/runs?status=completed&per_page=100&page=${page}`)
514
+ if (!Array.isArray(result.workflow_runs) || result.workflow_runs.length > 100) throw new Error('invalid workflow runs response')
515
+ raw.push(...result.workflow_runs)
516
+ if (result.workflow_runs.length < 100) break
517
+ }
518
+ const selected = raw.slice(0, want)
519
+ if (new Set(selected.map((r) => String(r.id))).size !== selected.length) throw new Error('duplicate run ID during history pagination; refusing partial evidence')
520
+ const runs = await pool(selected, 8, (r) => collect(r, meta.default_branch))
521
+ validateRuns(runs)
522
+ return runs
523
+ }
524
+
525
+ export async function fetchRun(repo, runId, api = gh) {
526
+ runId = positiveInt(runId, '--profile-run')
527
+ const collect = collector(repo, api)
528
+ const meta = await api(`repos/${repo}`)
529
+ const raw = await api(`repos/${repo}/actions/runs/${runId}`)
530
+ if (String(raw.id) !== String(runId)) throw new Error('requested run ID does not match response')
531
+ const runs = [await collect(raw, meta.default_branch)]
532
+ validateRuns(runs)
533
+ return runs
534
+ }
535
+
536
+ function validateRuns(runs) {
537
+ if (!Array.isArray(runs)) throw new Error('saved data must be an array of runs')
538
+ const timestamp = (value, label) => {
539
+ if (value != null && (typeof value !== 'string' || !Number.isFinite(Date.parse(value)))) throw new Error(`invalid ${label} timestamp`)
540
+ }
541
+ for (const r of runs) {
542
+ if (!r || typeof r !== 'object' || !Array.isArray(r.jobs) || typeof r.workflow !== 'string') throw new Error('each run must have a workflow name and jobs array')
543
+ positiveInt(r.id, 'run ID')
544
+ if (r.attempt != null) positiveInt(r.attempt, 'run attempt')
545
+ if (r.tree != null && (typeof r.tree !== 'string' || !r.tree)) throw new Error('tree must be a nonempty hash string or null')
546
+ if (r.sha != null && typeof r.sha !== 'string') throw new Error('sha must be a string or null')
547
+ if (r.collectionGaps != null && (!Array.isArray(r.collectionGaps) || r.collectionGaps.some((g) => !g || typeof g.reason !== 'string'))) throw new Error('invalid collectionGaps')
548
+ timestamp(r.created, 'run created'); timestamp(r.started, 'run started')
549
+ for (const j of r.jobs) {
550
+ if (!j || typeof j.name !== 'string' || (j.steps != null && !Array.isArray(j.steps))) throw new Error('each job must have a name and optional steps array')
551
+ if (j.attempt != null) positiveInt(j.attempt, 'job attempt')
552
+ if (j.runnerLabels != null && (!Array.isArray(j.runnerLabels) || j.runnerLabels.some((s) => typeof s !== 'string'))) throw new Error('runnerLabels must be an array of strings or null')
553
+ timestamp(j.created, 'job created'); timestamp(j.started, 'job started'); timestamp(j.completed, 'job completed')
554
+ for (const s of j.steps || []) {
555
+ if (!s || typeof s.name !== 'string') throw new Error('each step must have a name')
556
+ timestamp(s.started, 'step started'); timestamp(s.completed, 'step completed')
557
+ }
558
+ }
559
+ }
560
+ }
561
+
562
+ const USAGE = 'usage: ci-run-audit.mjs (--repo <owner/name> [--runs 300] | --from f.json) [--profile-run <runId>] [--save f.json] [--json] [--ignore-job <regex>] [--min-trees 20]'
563
+
564
+ function args(argv) {
565
+ const o = { runs: 300, json: false, minTrees: 20 }, seen = new Set()
566
+ for (let i = 0; i < argv.length; i++) {
567
+ const a = argv[i]
568
+ if (seen.has(a)) throw new Error(`duplicate argument: ${a}`)
569
+ seen.add(a)
570
+ const v = () => {
571
+ const value = argv[++i]
572
+ if (!value || value.startsWith('-')) throw new Error(`missing value for ${a}`)
573
+ return value
574
+ }
575
+ if (a === '--repo') o.repo = v()
576
+ else if (a === '--runs') o.runs = positiveInt(v(), '--runs', MAX_RUNS)
577
+ else if (a === '--profile-run') o.profileRun = positiveInt(v(), '--profile-run')
578
+ else if (a === '--save') o.save = v()
579
+ else if (a === '--from') o.from = v()
580
+ else if (a === '--json') o.json = true
581
+ else if (a === '--ignore-job') o.ignore = v()
582
+ else if (a === '--min-trees') o.minTrees = positiveInt(v(), '--min-trees')
583
+ else if (a === '--help' || a === '-h') o.help = true
584
+ else throw new Error(`unknown argument: ${a}`)
585
+ }
586
+ if (o.help) return o
587
+ if (Boolean(o.repo) === Boolean(o.from)) throw new Error('provide exactly one of --repo or --from')
588
+ if (o.repo) validRepo(o.repo)
589
+ if (seen.has('--runs') && (o.from || o.profileRun)) throw new Error('--runs is only supported for live history collection')
590
+ if (o.profileRun && (seen.has('--ignore-job') || seen.has('--min-trees'))) throw new Error('--ignore-job and --min-trees are audit-only; profiles include all jobs')
591
+ o.ignoreRegex = o.ignore ? new RegExp(`${DEFAULT_IGNORE.source}|${o.ignore}`, 'i') : DEFAULT_IGNORE
592
+ return o
593
+ }
594
+
595
+ export async function runCli(argv) {
596
+ try {
597
+ const o = args(argv)
598
+ if (o.help) { console.error(USAGE); return 0 }
599
+ const runs = o.from ? JSON.parse(fs.readFileSync(o.from, 'utf8'))
600
+ : o.profileRun ? await fetchRun(o.repo, o.profileRun) : await fetchRuns(o.repo, o.runs)
601
+ validateRuns(runs)
602
+ const report = o.profileRun ? profileRun(runs, o.profileRun) : analyze(runs, { ignore: o.ignoreRegex, minTrees: o.minTrees })
603
+ const output = o.json ? JSON.stringify(report, null, 2) : o.profileRun ? renderProfile(report) : render(report, o.repo || o.from)
604
+ // Save the collected array, never the derived report; saved profiles remain re-auditable.
605
+ if (o.save) fs.writeFileSync(o.save, JSON.stringify(runs))
606
+ process.stdout.write(output + '\n')
607
+ return 0
608
+ } catch (e) {
609
+ console.error(`ci-run-audit: ${e.message}`)
610
+ return 2
611
+ }
612
+ }
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { runCli } from './ci-run-audit-core.mjs'
3
+
4
+ process.exitCode = await runCli(process.argv.slice(2))