@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,650 @@
1
+ #!/usr/bin/env python3
2
+ """Infer payment-pattern hypotheses inside a descriptor: pricing currency, preset menus, rate consistency, retail lists.
3
+
4
+ Discovery layer that runs before exact currency-amount-pair clustering. It reads a cleaned
5
+ authorization CSV plus a daily FX cache and, per descriptor and BIN-country group, reports:
6
+
7
+ * inferred wallet currency (the currency under which charges collapse onto the fewest peaks)
8
+ * the preset menu (peak set) and which known scheme it matches (10-scheme, 12-scheme)
9
+ * rate consistency: source-specific cross-rate tests for frozen-rate evidence
10
+ * a retail-list constant (list prices ending .99/.95/.98/.00 times one factor)
11
+ * off-menu groups (no peak structure)
12
+ * behaviour metrics: repeat cards, retry after decline, burst regularity, hour concentration,
13
+ address verification, 3-D Secure, prepaid share, issuer-program concentration
14
+
15
+ Every threshold is a CLI option and is echoed in the output. Stdlib only.
16
+ """
17
+ from __future__ import annotations
18
+
19
+ import argparse
20
+ import csv
21
+ import json
22
+ import math
23
+ import random
24
+ import statistics
25
+ import sys
26
+ import urllib.request
27
+ from collections import Counter, defaultdict
28
+ from datetime import date, datetime, timedelta
29
+ from pathlib import Path
30
+
31
+ PEGS = {"USD": 1.0, "SAR": 3.75, "AED": 3.6725, "QAR": 3.64, "KWD": 0.3066, "BHD": 0.376, "OMR": 0.3845, "HKD": 7.8, "JOD": 0.709}
32
+
33
+ COUNTRY_CURRENCY = {
34
+ "united states of america": "USD", "united states": "USD", "us": "USD", "usa": "USD",
35
+ "united kingdom": "GBP", "gb": "GBP", "uk": "GBP", "australia": "AUD", "new zealand": "NZD", "canada": "CAD",
36
+ "switzerland": "CHF", "poland": "PLN", "czech republic": "CZK", "czechia": "CZK", "hungary": "HUF", "norway": "NOK",
37
+ "sweden": "SEK", "denmark": "DKK", "romania": "RON", "bulgaria": "BGN", "iceland": "ISK", "brazil": "BRL",
38
+ "mexico": "MXN", "japan": "JPY", "india": "INR", "south africa": "ZAR", "turkey": "TRY", "israel": "ILS",
39
+ "singapore": "SGD", "hong kong": "HKD", "korea, republic of": "KRW", "south korea": "KRW", "thailand": "THB",
40
+ "malaysia": "MYR", "philippines": "PHP", "indonesia": "IDR", "saudi arabia": "SAR", "united arab emirates": "AED",
41
+ "qatar": "QAR", "kuwait": "KWD", "bahrain": "BHD", "oman": "OMR", "jordan": "JOD",
42
+ }
43
+ EURO_COUNTRIES = {
44
+ "germany", "italy", "france", "spain", "portugal", "greece", "slovakia", "finland", "ireland", "austria",
45
+ "netherlands", "belgium", "lithuania", "croatia", "slovenia", "estonia", "latvia", "cyprus", "luxembourg", "malta",
46
+ "moldova, republic of", "moldova", "montenegro", "kosovo",
47
+ }
48
+ for _c in EURO_COUNTRIES:
49
+ COUNTRY_CURRENCY[_c] = "EUR"
50
+
51
+ SCHEMES = {
52
+ "10": [5, 10, 15, 20, 25, 30, 40, 50, 60, 75, 80, 100, 150, 200, 250, 300, 400, 500, 750, 1000, 1500, 2000],
53
+ "12": [12, 24, 36, 48, 72, 96, 108, 144, 192, 288, 384, 480],
54
+ }
55
+ SCHEME_SCALES = [1, 5, 10, 25, 100, 400, 1000]
56
+
57
+
58
+ # ----------------------------------------------------------------------------- rates
59
+
60
+ def fetch_rates(start: str, end: str, base: str, symbols: list[str]) -> dict:
61
+ url = f"https://api.frankfurter.dev/v1/{start}..{end}?base={base}&symbols={','.join(sorted(set(symbols)))}"
62
+ with urllib.request.urlopen(url, timeout=60) as resp: # noqa: S310 - explicit user action
63
+ payload = json.load(resp)
64
+ return {"base": base, "source": url, "rates": payload["rates"]}
65
+
66
+
67
+ class Rates:
68
+ """Daily rates quoted as units of currency per one unit of base; prior-day carry up to ``carry`` days."""
69
+
70
+ def __init__(self, cache: dict, carry: int = 7):
71
+ self.base = cache.get("base", "USD")
72
+ self.rates = cache["rates"]
73
+ self.days = sorted(self.rates)
74
+ self.carry = carry
75
+ self.carried: Counter = Counter()
76
+
77
+ def rate(self, day: str, cur: str) -> float | None:
78
+ if cur == self.base:
79
+ return 1.0
80
+ if cur in PEGS and self.base == "USD":
81
+ return PEGS[cur]
82
+ if not self.days:
83
+ return None
84
+ d = date.fromisoformat(day)
85
+ for lag in range(self.carry + 1):
86
+ dd = (d - timedelta(days=lag)).isoformat()
87
+ if dd in self.rates and cur in self.rates[dd]:
88
+ if lag:
89
+ self.carried[cur] += 1
90
+ return self.rates[dd][cur]
91
+ return None
92
+
93
+ def market_move(self, cur: str, day_a: str, day_b: str, source: str | None = None) -> float | None:
94
+ source = self.base if source is None else source
95
+ if cur == source:
96
+ return 0.0
97
+ a, b = self.rate(day_a, cur), self.rate(day_b, cur)
98
+ src_a, src_b = self.rate(day_a, source), self.rate(day_b, source)
99
+ if not a or not b or not src_a or not src_b:
100
+ return None
101
+ return abs((b / src_b) / (a / src_a) - 1)
102
+
103
+
104
+ # ----------------------------------------------------------------------------- helpers
105
+
106
+ def find_peaks(values: list[float], tol: float, min_share: float) -> list[tuple[float, int]]:
107
+ """Group values within ±tol (relative) of the modal value, largest first; return (center, count)."""
108
+ if not values:
109
+ return []
110
+ counts = Counter(round(v, 2) for v in values)
111
+ n = len(values)
112
+ used: set[float] = set()
113
+ peaks = []
114
+ for v, _ in counts.most_common():
115
+ if v in used:
116
+ continue
117
+ grp = [w for w in counts if w not in used and abs(w - v) <= tol * max(v, 1e-9)]
118
+ cnt = sum(counts[w] for w in grp)
119
+ if cnt / n < min_share:
120
+ continue
121
+ used.update(grp)
122
+ center = sum(w * counts[w] for w in grp) / cnt
123
+ peaks.append((center, cnt))
124
+ return sorted(peaks, key=lambda p: -p[1])
125
+
126
+
127
+ def coverage(values: list[float], tol: float, min_share: float, top_k: int) -> tuple[float, list[tuple[float, int]]]:
128
+ peaks = find_peaks(values, tol, min_share)[:top_k]
129
+ return (sum(c for _, c in peaks) / len(values) if values else 0.0), peaks
130
+
131
+
132
+ def coverage_against(values: list[float], centers: list[float], tol: float) -> float:
133
+ """Share of values within tol of any given centre (used for the jittered control)."""
134
+ if not values or not centers:
135
+ return 0.0
136
+ return sum(1 for v in values if any(abs(v - c) <= tol * c for c in centers)) / len(values)
137
+
138
+
139
+ def day_drift(rows: list[dict], converted: list[float], centers: list[tuple[float, int]], tol: float, min_per_day: int = 3) -> float | None:
140
+ """Weighted mean, over the top peaks, of (max - min) / centre of the daily median converted value.
141
+
142
+ Under the true wallet currency a preset converts to the same local value every day; under a wrong
143
+ currency the value follows the cross rate day by day."""
144
+ total = 0.0
145
+ weight = 0
146
+ for center, cnt in centers[:5]:
147
+ per_day: dict[str, list[float]] = defaultdict(list)
148
+ for r, v in zip(rows, converted):
149
+ if abs(v - center) <= tol * center:
150
+ per_day[r["day"]].append(v)
151
+ meds = [statistics.median(v) for v in per_day.values() if len(v) >= min_per_day]
152
+ if len(meds) < 2:
153
+ continue
154
+ total += (max(meds) - min(meds)) / center * cnt
155
+ weight += cnt
156
+ return total / weight if weight else None
157
+
158
+
159
+ def jitter(values: list[float], rng: random.Random, lo: float, hi: float) -> list[float]:
160
+ return [v * (1 + rng.choice((-1, 1)) * rng.uniform(lo, hi)) for v in values]
161
+
162
+
163
+ def retail_shaped(x: float, tol: float = 0.006) -> bool:
164
+ r = round(x)
165
+ return any(g > 0 and abs(x - g) <= tol for g in (r, r - 0.01, r - 0.05, r - 0.02))
166
+
167
+
168
+ def match_scheme(peaks: list[tuple[float, int]], tol: float) -> tuple[str, int, float]:
169
+ """Return (scheme name, scale, share of peak attempts on the scheme)."""
170
+ name, scale, share, _ = match_scheme_peaks(peaks, tol)
171
+ return name, scale, share
172
+
173
+
174
+ def match_scheme_peaks(peaks: list[tuple[float, int]], tol: float) -> tuple[str, int, float, list[tuple[float, int]]]:
175
+ total = sum(c for _, c in peaks) or 1
176
+ best = ("none", 1, 0.0, [])
177
+ for name, menu in SCHEMES.items():
178
+ for scale in SCHEME_SCALES:
179
+ on = [(center, cnt) for center, cnt in peaks if any(abs(center - m * scale) <= tol * m * scale for m in menu)]
180
+ share = sum(c for _, c in on) / total
181
+ if share > best[2]:
182
+ best = (name, scale, share, on)
183
+ return best
184
+
185
+
186
+ # ----------------------------------------------------------------------------- core
187
+
188
+ def parse_rows(path: Path, cols: dict) -> list[dict]:
189
+ rows = []
190
+ with path.open(newline="", encoding="utf-8-sig") as fh:
191
+ for r in csv.DictReader(fh):
192
+ try:
193
+ amt = float(r[cols["amount"]])
194
+ except (KeyError, ValueError):
195
+ continue
196
+ ts = r.get(cols["date"], "")[:19]
197
+ rows.append({
198
+ "amount": amt,
199
+ "currency": r.get(cols["currency"], "USD").strip().upper() or "USD",
200
+ "day": ts[:10],
201
+ "ts": ts,
202
+ "descriptor": (r.get(cols["descriptor"], "") or "UNKNOWN_DESCRIPTOR").strip(),
203
+ "country": (r.get(cols["country"], "") or "UNKNOWN").strip(),
204
+ "response": (r.get(cols["response"], "") if cols.get("response") else "").strip(),
205
+ "account": (r.get(cols["account"], "") if cols.get("account") else "").strip(),
206
+ "card_type": (r.get(cols["card_type"], "") if cols.get("card_type") else "").strip(),
207
+ "avs": (r.get(cols["avs"], "") if cols.get("avs") else "").strip(),
208
+ "auth": (r.get(cols["auth"], "") if cols.get("auth") else "").strip(),
209
+ })
210
+ return rows
211
+
212
+
213
+ def infer_wallet(rows: list[dict], rates: Rates, candidates: list[str], opt, rng) -> dict:
214
+ """Per group: coverage under each candidate currency, control against the same centres, day drift."""
215
+ out = {}
216
+ for cur in candidates:
217
+ vals, ok = [], True
218
+ for r in rows:
219
+ rt = rates.rate(r["day"], cur)
220
+ src = rates.rate(r["day"], r["currency"])
221
+ if rt is None or src is None:
222
+ ok = False
223
+ break
224
+ vals.append(r["amount"] / src * rt)
225
+ if not ok or not vals:
226
+ continue
227
+ cov, peaks = coverage(vals, opt.peak_tol, opt.peak_min_share, opt.top_k)
228
+ ctrl = coverage_against(jitter(vals, rng, opt.jitter_lo, opt.jitter_hi), [c for c, _ in peaks], opt.peak_tol)
229
+ drift = day_drift(rows, vals, peaks, opt.peak_tol)
230
+ name, scale, sshare, on = match_scheme_peaks(peaks, opt.scheme_tol)
231
+ out[cur] = {"coverage": round(cov, 4), "control": round(ctrl, 4), "excess": round(cov - ctrl, 4), "day_drift": None if drift is None else round(drift, 4),
232
+ "peaks": [(round(c, 2), n) for c, n in peaks], "scheme": name, "scheme_scale": scale, "scheme_share": round(sshare, 4),
233
+ "scheme_coverage": round(sum(n for _, n in on) / len(vals), 4), "scheme_peaks": [(round(c, 2), n) for c, n in on]}
234
+ return out
235
+
236
+
237
+ def wallet_acceptable(info: dict, opt) -> bool:
238
+ if info["excess"] < opt.wallet_min_excess * 0.4:
239
+ return False
240
+ if info["coverage"] >= opt.wallet_min_coverage and info["excess"] >= opt.wallet_min_excess:
241
+ return True
242
+ return info["scheme_coverage"] >= opt.wallet_min_coverage_with_scheme and info["scheme_share"] >= opt.scheme_min_share
243
+
244
+
245
+ def choose_wallet(walls: dict, legal: str | None, opt) -> str | None:
246
+ """Prefer a currency whose peaks land on a known menu, then the lowest day drift, then legal tender, EUR, USD."""
247
+ ok = [(cur, info) for cur, info in walls.items() if wallet_acceptable(info, opt)]
248
+ if not ok:
249
+ return None
250
+ prio = {legal: 0, "EUR": 1, "USD": 2}
251
+
252
+ def key(kv):
253
+ cur, info = kv
254
+ drift = info["day_drift"] if info["day_drift"] is not None else 1.0
255
+ return (0 if info["scheme_share"] >= opt.scheme_min_share else 1, round(drift / opt.drift_bucket), prio.get(cur, 3), -info["coverage"])
256
+ return sorted(ok, key=key)[0][0]
257
+
258
+
259
+ def rate_consistency(rows: list[dict], rates: Rates, wallet: str, peaks: list, opt) -> dict:
260
+ """Per peak and source currency: modal processed amount by day versus the source-to-wallet cross-rate."""
261
+ result = {"frozen_peaks": 0, "live_peaks": 0, "frozen_attempts": 0, "tested_attempts": 0, "detail": []}
262
+ for center, _ in peaks:
263
+ by_source: dict[str, dict[str, Counter]] = defaultdict(lambda: defaultdict(Counter))
264
+ for r in rows:
265
+ src = rates.rate(r["day"], r["currency"])
266
+ rt = rates.rate(r["day"], wallet)
267
+ if not src or not rt:
268
+ continue
269
+ local = r["amount"] / src * rt
270
+ if abs(local - center) <= opt.peak_tol * center:
271
+ by_source[r["currency"]][r["day"]][round(r["amount"], 2)] += 1
272
+ for source, by_day in by_source.items():
273
+ days = sorted(d for d in by_day if sum(by_day[d].values()) >= opt.frozen_min_per_day)
274
+ if len(days) < opt.frozen_days:
275
+ continue
276
+ modal = [by_day[d].most_common(1)[0][0] for d in days]
277
+ attempts = sum(sum(by_day[d].values()) for d in days)
278
+ result["tested_attempts"] += attempts
279
+ move = rates.market_move(wallet, days[0], days[-1], source) or 0.0
280
+ distinct = len(set(modal))
281
+ frozen = distinct == 1 and move >= opt.frozen_market_move and source != wallet
282
+ if frozen:
283
+ result["frozen_peaks"] += 1
284
+ result["frozen_attempts"] += attempts
285
+ else:
286
+ result["live_peaks"] += 1
287
+ result["detail"].append({"peak": round(center, 2), "source_currency": source, "days": len(days), "modal_amounts": sorted(set(modal)), "market_move": round(move, 4), "frozen": frozen, "attempts": attempts})
288
+ return result
289
+
290
+
291
+ def retail_scan(rows: list[dict], opt, rng) -> dict:
292
+ """Find one constant k such that amount / k is retail-shaped for a large share of rows, with many distinct bases."""
293
+ amounts = [r["amount"] for r in rows]
294
+ empty = {"constant": None, "share": 0.0, "control": 0.0, "excess": 0.0, "distinct_bases": 0, "rows": len(amounts)}
295
+ if len(amounts) < opt.retail_min_rows:
296
+ return empty
297
+ best = (1.0, 0.0)
298
+ k = opt.retail_k_min
299
+ while k <= opt.retail_k_max + 1e-9:
300
+ share = sum(retail_shaped(a / k) for a in amounts) / len(amounts)
301
+ if share > best[1]:
302
+ best = (round(k, 4), share)
303
+ k += opt.retail_k_step
304
+ bases = Counter(round(a / best[0], 2) for a in amounts if retail_shaped(a / best[0]))
305
+ distinct = sum(1 for _, c in bases.items() if c >= 3)
306
+ ctrl = sum(retail_shaped(a / best[0]) for a in jitter(amounts, rng, 0.004, 0.02)) / len(amounts)
307
+ return {"constant": best[0], "share": round(best[1], 4), "control": round(ctrl, 4), "excess": round(best[1] - ctrl, 4), "distinct_bases": distinct, "rows": len(amounts)}
308
+
309
+
310
+ def retail_passes(res: dict, opt, strong: bool = False) -> bool:
311
+ if not res["constant"] or res["excess"] < opt.retail_min_excess:
312
+ return False
313
+ if strong:
314
+ return res["share"] >= opt.retail_min_share and res["distinct_bases"] >= opt.retail_strong_distinct
315
+ return res["share"] >= opt.retail_weak_share and res["distinct_bases"] >= opt.retail_min_distinct
316
+
317
+
318
+ def behaviour(rows: list[dict], opt) -> dict:
319
+ n = len(rows)
320
+ acc = Counter(r["account"] for r in rows if r["account"])
321
+ repeat = sum(1 for v in acc.values() if v >= 2) / len(acc) if acc else None
322
+ # retry after decline within window
323
+ by_acc: dict[str, list] = defaultdict(list)
324
+ for r in rows:
325
+ if r["account"] and r["ts"]:
326
+ by_acc[r["account"]].append(r)
327
+ declines = retries = 0
328
+ approvals = {v for v in opt.approval_values}
329
+ for lst in by_acc.values():
330
+ lst.sort(key=lambda r: r["ts"])
331
+ for i, r in enumerate(lst):
332
+ if opt.response_column and r["response"] not in approvals:
333
+ declines += 1
334
+ if i + 1 < len(lst):
335
+ try:
336
+ gap = (datetime.fromisoformat(lst[i + 1]["ts"]) - datetime.fromisoformat(r["ts"])).total_seconds()
337
+ except ValueError:
338
+ continue
339
+ if gap <= opt.retry_window_seconds:
340
+ retries += 1
341
+ hours = Counter(int(r["ts"][11:13]) for r in rows if len(r["ts"]) >= 13)
342
+ top3 = sum(v for _, v in hours.most_common(3)) / n if hours else None
343
+ # burst regularity: coefficient of variation of gaps inside the busiest hour-day
344
+ gaps = []
345
+ if hours:
346
+ buckets: dict[str, list] = defaultdict(list)
347
+ for r in rows:
348
+ if len(r["ts"]) >= 13:
349
+ buckets[r["ts"][:13]].append(r["ts"])
350
+ busiest = max(buckets.values(), key=len)
351
+ ts = sorted(datetime.fromisoformat(t) for t in busiest if len(t) >= 19)
352
+ gaps = [(ts[i + 1] - ts[i]).total_seconds() for i in range(len(ts) - 1)]
353
+ cv = (statistics.pstdev(gaps) / statistics.mean(gaps)) if len(gaps) >= 5 and statistics.mean(gaps) > 0 else None
354
+ avs = sum(1 for r in rows if r["avs"] and "not checked" not in r["avs"].lower()) / n if opt.avs_column else None
355
+ tds = sum(1 for r in rows if r["auth"] and r["auth"].lower() not in ("none", "", "n/a")) / n if opt.auth_column else None
356
+ prepaid = sum(1 for r in rows if "prepaid" in r["card_type"].lower()) / n if opt.card_type_column else None
357
+ bins = Counter(r["account"][:6] for r in rows if len(r["account"]) >= 6)
358
+ top5 = sum(v for _, v in bins.most_common(5)) / n if bins else None
359
+ approved = sum(1 for r in rows if r["response"] in approvals) / n if opt.response_column else None
360
+ fraud = sum(1 for r in rows if r["response"] in set(opt.suspected_fraud_values)) / n if opt.response_column else None
361
+ return {
362
+ "attempts": n, "cards": len(acc) or None, "repeat_card_share": repeat,
363
+ "retry_after_decline": (retries / declines) if declines else None, "declines": declines,
364
+ "top3_hour_share_utc": top3, "burst_gap_cv": cv, "avs_present_share": avs, "three_ds_share": tds,
365
+ "prepaid_share": prepaid, "top5_bin_share": top5, "approval_rate": approved, "suspected_fraud_share": fraud,
366
+ "first_day": min(r["day"] for r in rows), "last_day": max(r["day"] for r in rows),
367
+ }
368
+
369
+
370
+ def on_peak_rows(rows: list[dict], rates: Rates, wallet: str, centers: list[float], tol: float) -> tuple[list[dict], list[dict]]:
371
+ inside, outside = [], []
372
+ for r in rows:
373
+ src = rates.rate(r["day"], r["currency"])
374
+ rt = rates.rate(r["day"], wallet)
375
+ if src and rt and any(abs(r["amount"] / src * rt - c) <= tol * c for c in centers):
376
+ inside.append(r)
377
+ else:
378
+ outside.append(r)
379
+ return inside, outside
380
+
381
+
382
+ def extract_substreams(grows: list[dict], legal: str | None, rates: Rates, opt, rng) -> list[dict]:
383
+ """Peel streams off a country group: menu (wallet + scheme), retail list, then remainder as off-menu."""
384
+ subs = []
385
+ residual = list(grows)
386
+ for _round in range(opt.max_substreams):
387
+ if len(residual) < opt.min_group:
388
+ break
389
+ retail = retail_scan(residual, opt, rng)
390
+ if retail_passes(retail, opt, strong=True):
391
+ k = retail["constant"]
392
+ inside = [r for r in residual if retail_shaped(r["amount"] / k)]
393
+ residual = [r for r in residual if not retail_shaped(r["amount"] / k)]
394
+ subs.append({"stream": f"retail x{k}", "rows": inside, "retail": retail})
395
+ continue
396
+ cands = [c for c in dict.fromkeys([legal, *opt.candidate_currencies]) if c]
397
+ walls = infer_wallet(residual, rates, cands, opt, rng)
398
+ wallet = choose_wallet(walls, legal, opt)
399
+ if wallet:
400
+ info = walls[wallet]
401
+ scheme, scale, sshare = info["scheme"], info["scheme_scale"], info["scheme_share"]
402
+ use_peaks = info["scheme_peaks"] if sshare >= opt.scheme_min_share else info["peaks"]
403
+ inside, outside = on_peak_rows(residual, rates, wallet, [c for c, _ in use_peaks], opt.peak_tol)
404
+ cons = rate_consistency(inside, rates, wallet, use_peaks, opt)
405
+ frozen = bool(cons["tested_attempts"] and cons["frozen_attempts"] / cons["tested_attempts"] >= opt.frozen_share)
406
+ label = f"{wallet} menu-{scheme if sshare >= opt.scheme_min_share else 'none'}{' frozen' if frozen else ' live'}"
407
+ subs.append({"stream": label, "rows": inside, "wallet": wallet, "coverage": info["coverage"], "day_drift": info["day_drift"], "scheme": scheme,
408
+ "scheme_scale": scale, "scheme_share": round(sshare, 3), "peaks": use_peaks, "all_peaks": info["peaks"], "frozen": frozen, "rate_consistency": cons,
409
+ "alternatives": {c: i["coverage"] for c, i in walls.items() if c != wallet}})
410
+ residual = outside
411
+ continue
412
+ if retail_passes(retail, opt):
413
+ k = retail["constant"]
414
+ inside = [r for r in residual if retail_shaped(r["amount"] / k)]
415
+ residual = [r for r in residual if not retail_shaped(r["amount"] / k)]
416
+ subs.append({"stream": f"retail x{k}", "rows": inside, "retail": retail})
417
+ continue
418
+ break
419
+ if residual:
420
+ subs.append({"stream": "off-menu", "rows": residual})
421
+ return subs
422
+
423
+
424
+ # ----------------------------------------------------------------------------- presentation
425
+
426
+ def fixed_amount_characteristics(rows: list[dict]) -> list[str]:
427
+ charge_days: dict[tuple[str, float], set[str]] = defaultdict(set)
428
+ for row in rows:
429
+ charge_days[(row["currency"], row["amount"])].add(row["day"])
430
+ return ["fixed amount"] if any(len(days) >= 2 for days in charge_days.values()) else []
431
+
432
+
433
+ def payment_pattern_presentation(sdef: dict, rows: list[dict]) -> dict:
434
+ """Describe detected rows without splitting or renaming canonical streams."""
435
+ label = sdef["stream"]
436
+ wallet = sdef.get("wallet")
437
+ established_menu = bool(wallet and " menu-" in label and " menu-none" not in label)
438
+ repeated = bool(wallet and " menu-none" in label)
439
+ if label.startswith("retail x"):
440
+ basis = "retail-list price factor"
441
+ elif repeated:
442
+ basis = "unidentified repeated-amount pattern"
443
+ elif established_menu:
444
+ basis = "established menu"
445
+ else:
446
+ basis = label
447
+ frozen_by_source: Counter = Counter()
448
+ for detail in sdef.get("rate_consistency", {}).get("detail", []):
449
+ if detail["frozen"] and detail["source_currency"] != wallet:
450
+ frozen_by_source[detail["source_currency"]] += detail["attempts"]
451
+ parts = []
452
+ for currency, attempts in sorted(Counter(r["currency"] for r in rows).items()):
453
+ if established_menu:
454
+ pattern = "Direct top-up pattern" if currency == wallet else "FX-converted top-up pattern"
455
+ else:
456
+ pattern = "E-commerce pattern" if label.startswith("retail x") else "Other pattern"
457
+ parts.append({"pattern": pattern, "charge_currency": currency,
458
+ "inferred_top_up_currency": wallet if established_menu else None,
459
+ "attempts": attempts, "basis": basis,
460
+ "frozen_fx_attempts": frozen_by_source[currency] if established_menu and currency != wallet else 0})
461
+ return summarize_payment_patterns([{"by_charge_currency": parts,
462
+ "pricing_characteristics": fixed_amount_characteristics(rows)}])
463
+
464
+
465
+ def summarize_payment_patterns(presentations: list[dict]) -> dict:
466
+ """Aggregate display counts along the existing raw-label grouping only."""
467
+ combined = {}
468
+ pricing = set()
469
+ for presentation in presentations:
470
+ pricing.update(presentation["pricing_characteristics"])
471
+ for part in presentation["by_charge_currency"]:
472
+ key = (part["pattern"], part["charge_currency"], part["inferred_top_up_currency"], part["basis"])
473
+ if key not in combined:
474
+ combined[key] = {**part, "attempts": 0, "frozen_fx_attempts": 0}
475
+ combined[key]["attempts"] += part["attempts"]
476
+ combined[key]["frozen_fx_attempts"] += part["frozen_fx_attempts"]
477
+ parts = list(combined.values())
478
+ patterns: Counter = Counter()
479
+ currencies: Counter = Counter()
480
+ inferred = set()
481
+ for part in parts:
482
+ patterns[part["pattern"]] += part["attempts"]
483
+ currencies[part["charge_currency"]] += part["attempts"]
484
+ if part["inferred_top_up_currency"]:
485
+ inferred.add(part["inferred_top_up_currency"])
486
+ return {"pattern_counts": dict(sorted(patterns.items())),
487
+ "charge_currencies": dict(sorted(currencies.items())),
488
+ "inferred_top_up_currencies": sorted(inferred),
489
+ "by_charge_currency": parts, "pricing_characteristics": sorted(pricing)}
490
+
491
+
492
+ def presentation_cells(stream: dict) -> str:
493
+ presentation = stream.get("presentation")
494
+ if not presentation:
495
+ return "n/a | n/a | n/a | n/a"
496
+ patterns = []
497
+ for pattern, attempts in presentation["pattern_counts"].items():
498
+ sources = []
499
+ for part in presentation["by_charge_currency"]:
500
+ if part["pattern"] != pattern:
501
+ continue
502
+ source = f"{part['charge_currency']}: {part['attempts']}"
503
+ if part["frozen_fx_attempts"]:
504
+ source += f" ({part['frozen_fx_attempts']} frozen FX tested attempts)"
505
+ sources.append(source)
506
+ text = f"{pattern}: {attempts} [{', '.join(sources)}]"
507
+ patterns.append(text)
508
+ if any(p["basis"] == "unidentified repeated-amount pattern" for p in presentation["by_charge_currency"]):
509
+ patterns.append("unidentified repeated-amount pattern")
510
+ inferred = ", ".join(presentation["inferred_top_up_currencies"]) or "n/a"
511
+ charges = ", ".join(f"{cur}: {n}" for cur, n in presentation["charge_currencies"].items())
512
+ pricing = ", ".join(presentation["pricing_characteristics"]) or "n/a"
513
+ return f"{'; '.join(patterns)} | {inferred} | {charges} | {pricing}"
514
+
515
+
516
+ def analyse(rows: list[dict], rates: Rates, opt) -> dict:
517
+ rng = random.Random(opt.seed)
518
+ report = {"parameters": {k: v for k, v in vars(opt).items() if k not in ("csv_path", "output", "markdown", "fetch_rates", "rates")}, "descriptors": []}
519
+ by_desc: dict[str, list] = defaultdict(list)
520
+ for r in rows:
521
+ by_desc[r["descriptor"]].append(r)
522
+ for desc, drows in sorted(by_desc.items(), key=lambda x: -len(x[1])):
523
+ entry = {"descriptor": desc, "attempts": len(drows), "groups": [], "streams": []}
524
+ by_ctry: dict[str, list] = defaultdict(list)
525
+ for r in drows:
526
+ by_ctry[r["country"]].append(r)
527
+ stream_rows: dict[str, list] = defaultdict(list)
528
+ stream_presentations: dict[str, list] = defaultdict(list)
529
+ small = 0
530
+ for ctry, grows in sorted(by_ctry.items(), key=lambda x: -len(x[1])):
531
+ if len(grows) < opt.min_group:
532
+ small += len(grows)
533
+ stream_rows["small groups"].extend(grows)
534
+ stream_presentations["small groups"].append(payment_pattern_presentation({"stream": "small groups"}, grows))
535
+ continue
536
+ legal = COUNTRY_CURRENCY.get(ctry.lower())
537
+ subs = extract_substreams(grows, legal, rates, opt, rng)
538
+ group = {"country": ctry, "attempts": len(grows), "legal_tender": legal, "substreams": []}
539
+ for sdef in subs:
540
+ srows = sdef.pop("rows")
541
+ sdef["attempts"] = len(srows)
542
+ sdef["share_of_group"] = round(len(srows) / len(grows), 3)
543
+ sdef["behaviour"] = behaviour(srows, opt)
544
+ sdef["presentation"] = payment_pattern_presentation(sdef, srows)
545
+ group["substreams"].append(sdef)
546
+ stream_rows[sdef["stream"]].extend(srows)
547
+ stream_presentations[sdef["stream"]].append(sdef["presentation"])
548
+ entry["groups"].append(group)
549
+ for label, srows in sorted(stream_rows.items(), key=lambda kv: -len(kv[1])):
550
+ presentation = summarize_payment_patterns(stream_presentations[label])
551
+ presentation["pricing_characteristics"] = fixed_amount_characteristics(srows)
552
+ entry["streams"].append({"stream": label, "attempts": len(srows), "share_of_descriptor": round(len(srows) / len(drows), 3),
553
+ "countries": [c for c, _ in Counter(r["country"] for r in srows).most_common(6)], "behaviour": behaviour(srows, opt),
554
+ "presentation": presentation})
555
+ report["descriptors"].append(entry)
556
+ report["rates_carried_days"] = dict(rates.carried)
557
+ return report
558
+
559
+
560
+ def to_markdown(report: dict) -> str:
561
+ fmt = lambda v: "n/a" if v is None else f"{v:.0%}" # noqa: E731
562
+ out = ["# Payment patterns", "",
563
+ "Payment patterns describe amount evidence, not confirmed wallets, deposits, players, casinos or shops. "
564
+ "Inferred top-up currencies are distinct from actual charge currencies. Fixed amount is a supplementary pricing characteristic, "
565
+ "not proof of fixed FX or generated traffic. Raw live labels do not establish floating FX; "
566
+ "frozen FX counts refer only to source-specific cross-rate tests.", ""]
567
+ for d in report["descriptors"]:
568
+ out += [f"## {d['descriptor']} ({d['attempts']} attempts)", "", "| Raw stream | Payment pattern | Inferred top-up currencies | Actual charge currencies (attempts) | Pricing characteristic | Attempts | Share | Countries | Approval | Suspected fraud | Retry after decline | Repeat cards | Top-3-hour share | AVS present |", "|---|---|---|---|---|---|---|---|---|---|---|---|---|---|"]
569
+ for s in d["streams"]:
570
+ b = s["behaviour"]
571
+ out.append(f"| {s['stream']} | {presentation_cells(s)} | {s['attempts']} | {s['share_of_descriptor']:.0%} | {', '.join(s['countries'])} | {fmt(b['approval_rate'])} | {fmt(b['suspected_fraud_share'])} | {fmt(b['retry_after_decline'])} | {fmt(b['repeat_card_share'])} | {fmt(b['top3_hour_share_utc'])} | {fmt(b['avs_present_share'])} |")
572
+ out += ["", "| Country | n | Raw sub-stream | Payment pattern | Inferred top-up currencies | Actual charge currencies (attempts) | Pricing characteristic | Rows | Coverage | Day drift | Scheme share | Peaks |", "|---|---|---|---|---|---|---|---|---|---|---|---|"]
573
+ for g in d["groups"]:
574
+ for sdef in g["substreams"]:
575
+ peaks = " ".join(f"{c:g}x{n}" for c, n in sdef.get("peaks", [])[:6])
576
+ cov = sdef.get("coverage"); dr = sdef.get("day_drift"); ss = sdef.get("scheme_share")
577
+ out.append(f"| {g['country']} | {g['attempts']} | {sdef['stream']} | {presentation_cells(sdef)} | {sdef['attempts']} | {'' if cov is None else f'{cov:.0%}'} | {'' if dr is None else f'{dr:.2%}'} | {'' if ss is None else f'{ss:.0%}'} | {peaks} |")
578
+ out.append("")
579
+ return "\n".join(out)
580
+
581
+
582
+ def main(argv=None) -> None:
583
+ p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
584
+ p.add_argument("csv_path", type=Path)
585
+ p.add_argument("--rates", type=Path, required=True, help="daily FX cache JSON: {base, rates:{YYYY-MM-DD:{CCY:rate}}}")
586
+ p.add_argument("--fetch-rates", nargs=2, metavar=("START", "END"), help="download Frankfurter v1 rates into --rates before analysis")
587
+ p.add_argument("--amount-column", required=True)
588
+ p.add_argument("--currency-column", required=True)
589
+ p.add_argument("--date-column", required=True, help="timestamp or date column (ISO)")
590
+ p.add_argument("--descriptor-column", required=True)
591
+ p.add_argument("--bin-country-column", required=True)
592
+ p.add_argument("--response-column")
593
+ p.add_argument("--approval-values", nargs="*", default=["A001"])
594
+ p.add_argument("--suspected-fraud-values", nargs="*", default=["D102"])
595
+ p.add_argument("--account-column")
596
+ p.add_argument("--card-type-column")
597
+ p.add_argument("--avs-column")
598
+ p.add_argument("--auth-column")
599
+ p.add_argument("--candidate-currencies", nargs="*", default=["EUR", "USD", "GBP", "CHF"])
600
+ p.add_argument("--min-group", type=int, default=80)
601
+ p.add_argument("--peak-tol", type=float, default=0.012)
602
+ p.add_argument("--peak-min-share", type=float, default=0.02)
603
+ p.add_argument("--top-k", type=int, default=10)
604
+ p.add_argument("--jitter-lo", type=float, default=0.02)
605
+ p.add_argument("--jitter-hi", type=float, default=0.05)
606
+ p.add_argument("--wallet-min-coverage", type=float, default=0.45, help="share of a group on its top-k peaks needed to call it menu-driven")
607
+ p.add_argument("--wallet-min-excess", type=float, default=0.25)
608
+ p.add_argument("--wallet-min-coverage-with-scheme", type=float, default=0.15, help="lower coverage bar when the peaks sit on a known menu")
609
+ p.add_argument("--drift-bucket", type=float, default=0.004, help="day-drift values within one bucket are ties broken by currency priority")
610
+ p.add_argument("--scheme-min-share", type=float, default=0.5)
611
+ p.add_argument("--scheme-tol", type=float, default=0.015, help="relative tolerance when matching peaks to a menu scheme (cashier markups run 0-3%%)")
612
+ p.add_argument("--max-substreams", type=int, default=3)
613
+ p.add_argument("--frozen-days", type=int, default=4)
614
+ p.add_argument("--frozen-min-per-day", type=int, default=3)
615
+ p.add_argument("--frozen-market-move", type=float, default=0.001)
616
+ p.add_argument("--frozen-share", type=float, default=0.5)
617
+ p.add_argument("--retail-k-min", type=float, default=1.0)
618
+ p.add_argument("--retail-k-max", type=float, default=1.15)
619
+ p.add_argument("--retail-k-step", type=float, default=0.0025)
620
+ p.add_argument("--retail-min-excess", type=float, default=0.2)
621
+ p.add_argument("--retail-min-share", type=float, default=0.3)
622
+ p.add_argument("--retail-min-distinct", type=int, default=5, help="distinct retail base prices (>=3 rows each) needed")
623
+ p.add_argument("--retail-strong-distinct", type=int, default=8, help="distinct bases for retail to be tried before wallet inference")
624
+ p.add_argument("--retail-weak-share", type=float, default=0.2)
625
+ p.add_argument("--retail-min-rows", type=int, default=60)
626
+ p.add_argument("--retry-window-seconds", type=int, default=1800)
627
+ p.add_argument("--seed", type=int, default=7)
628
+ p.add_argument("--output", type=Path)
629
+ p.add_argument("--markdown", type=Path)
630
+ opt = p.parse_args(argv)
631
+ cols = {"amount": opt.amount_column, "currency": opt.currency_column, "date": opt.date_column, "descriptor": opt.descriptor_column, "country": opt.bin_country_column,
632
+ "response": opt.response_column, "account": opt.account_column, "card_type": opt.card_type_column, "avs": opt.avs_column, "auth": opt.auth_column}
633
+ rows = parse_rows(opt.csv_path, cols)
634
+ if opt.fetch_rates:
635
+ symbols = sorted({c for c in COUNTRY_CURRENCY.values() if c not in PEGS} | set(opt.candidate_currencies))
636
+ cache = fetch_rates(opt.fetch_rates[0], opt.fetch_rates[1], "USD", [s for s in symbols if s != "USD"])
637
+ opt.rates.write_text(json.dumps(cache, indent=1))
638
+ rates = Rates(json.loads(opt.rates.read_text()))
639
+ report = analyse(rows, rates, opt)
640
+ text = json.dumps(report, indent=1, default=str)
641
+ if opt.output:
642
+ opt.output.write_text(text)
643
+ else:
644
+ sys.stdout.write(text + "\n")
645
+ if opt.markdown:
646
+ opt.markdown.write_text(to_markdown(report))
647
+
648
+
649
+ if __name__ == "__main__":
650
+ main()