@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,829 @@
1
+ #!/usr/bin/env python3
2
+ """Validate a compliance audit tracker against its checklist and evidence.
3
+
4
+ Usage:
5
+ python3 validate_tracker.py <tracker.md> --checklist <checklist.md> [--evidence DIR] [--json] [--strict]
6
+
7
+ Checks (Step 9A — structural):
8
+ 1. row_coverage Every Control ID in checklist scope has exactly one tracker row.
9
+ 2. item_coverage Every numbered item from the checklist row appears as an "Control N:"
10
+ bullet in the row's Observations cell.
11
+ 3. rating_value Control Effectiveness Rating is one of the four canonical
12
+ outcomes: Satisfactory / Improvement Recommended /
13
+ Unsatisfactory / N/A.
14
+ 4. rubric_consistency Effectiveness rating obeys the VARS rubric:
15
+ - any mandatory item "Partial" or "Not addressed" => Unsatisfactory
16
+ - all mandatory met but a recommended item is missing/partial
17
+ (or a mandatory item can be strengthened) => Improvement Recommended
18
+ - all items Covered => Satisfactory
19
+ - rows rated N/A are exempt (scoping decision, not derivable).
20
+ 5. gap_xref Each gap referenced in Issue Description by "(Control N)"
21
+ or "[Control N: ...]" must correspond to an item marked
22
+ "Not addressed" or "Partial" in the same row's Observations
23
+ cell. (Recommendations may also reference a "Covered"
24
+ mandatory item flagged for strengthening.)
25
+
26
+ Checks (Step 9B — citation grounding, requires --evidence):
27
+ 6. citation_grounding For every "Covered in <doc> section <clause>" (or similar)
28
+ citation in Observations, the cited clause string must
29
+ appear in the corresponding evidence markdown file.
30
+
31
+ Exit codes:
32
+ 0 = no errors (warnings allowed unless --strict).
33
+ 1 = errors found.
34
+
35
+ Errors vs warnings:
36
+ - row_coverage missing rows -> ERROR
37
+ - item_coverage missing bullets -> ERROR
38
+ - rating_value not in canonical set -> ERROR
39
+ - rubric_consistency violations -> ERROR
40
+ - gap_xref dangling reference -> WARNING
41
+ - citation_grounding missing clause -> WARNING
42
+ - citation_grounding missing doc -> WARNING
43
+ """
44
+
45
+ from __future__ import annotations
46
+
47
+ import argparse
48
+ import json
49
+ import re
50
+ import sys
51
+ from pathlib import Path
52
+ from typing import Optional
53
+
54
+ # ---------------------------------------------------------------------------
55
+ # Item-ID normalization
56
+ # ---------------------------------------------------------------------------
57
+
58
+ _ITEM_ID_PUNCT = re.compile(r"[()\s]+")
59
+
60
+
61
+ def normalize_item_id(raw: str) -> str:
62
+ """Canonicalise an item identifier.
63
+
64
+ Examples
65
+ --------
66
+ "(2.a)" -> "2.a"
67
+ "Control 2a" -> "2a"
68
+ "1.b" -> "1.b"
69
+ """
70
+ s = raw.strip().lower()
71
+ s = re.sub(r"^control\s+", "", s)
72
+ s = _ITEM_ID_PUNCT.sub("", s)
73
+ # collapse "2.a" and "2a" to the same canonical form by stripping
74
+ # the dot when it sits between digit(s) and letter(s)
75
+ s = re.sub(r"^(\d+)\.([a-z])", r"\1\2", s)
76
+ s = re.sub(r"^(\d+)\.([ivx]+)$", r"\1.\2", s) # keep 3.i, 3.ii intact
77
+ return s
78
+
79
+
80
+ def ancestor_canons(raw_id: str) -> set[str]:
81
+ """Canonical ids of an item plus all its dotted ancestors.
82
+
83
+ "6.a.i" -> {"6", "6a", "6a.i"}. Used so a parent bullet (e.g. the tracker
84
+ listing "Control 6.a") satisfies coverage of its descendant checklist
85
+ items (6.a.i … 6.a.x), which auditors routinely roll up rather than
86
+ enumerate leaf-by-leaf.
87
+ """
88
+ parts = raw_id.split(".")
89
+ return {normalize_item_id(".".join(parts[:k])) for k in range(1, len(parts) + 1)}
90
+
91
+
92
+ # ---------------------------------------------------------------------------
93
+ # Checklist parsing
94
+ # ---------------------------------------------------------------------------
95
+
96
+ CONTROL_HEAD_RE = re.compile(r"^###\s+([A-Z][A-Z0-9]*\.C\d+(?:\.\d+)?):\s+(.+?)\s*$")
97
+ SEVERITY_RE = re.compile(r"^-\s+\*\*Severity:\*\*\s*(\w+)", re.IGNORECASE)
98
+ ITEM_LINE_RE = re.compile(
99
+ r"^\s*-\s+\[\s\]\s+\*\*\(([^)]+)\)\*\*\s*(\(Recommended\)\s*)?(.*)$",
100
+ re.IGNORECASE,
101
+ )
102
+ SUB_ITEM_LINE_RE = re.compile(
103
+ r"^\s+-\s+\[\s\]\s+\*\*\(([^)]+)\)\*\*\s*(\(Recommended\)\s*)?(.*)$",
104
+ re.IGNORECASE,
105
+ )
106
+
107
+
108
+ class ChecklistItem:
109
+ __slots__ = ("id", "canonical", "mandatory", "text")
110
+
111
+ def __init__(self, raw_id: str, mandatory: bool, text: str):
112
+ self.id = raw_id
113
+ self.canonical = normalize_item_id(raw_id)
114
+ self.mandatory = mandatory
115
+ self.text = text
116
+
117
+
118
+ class ChecklistControl:
119
+ __slots__ = ("id", "title", "severity", "items")
120
+
121
+ def __init__(self, ctrl_id: str, title: str, severity: str):
122
+ self.id = ctrl_id
123
+ self.title = title
124
+ self.severity = severity.lower() # "mandatory" or "recommended"
125
+ self.items: list[ChecklistItem] = []
126
+
127
+
128
+ def parse_checklist(path: Path) -> dict[str, ChecklistControl]:
129
+ controls: dict[str, ChecklistControl] = {}
130
+ current: Optional[ChecklistControl] = None
131
+ in_verification = False
132
+
133
+ for line in path.read_text(encoding="utf-8").splitlines():
134
+ m = CONTROL_HEAD_RE.match(line)
135
+ if m:
136
+ current = ChecklistControl(m.group(1), m.group(2).strip(), "mandatory")
137
+ controls[current.id] = current
138
+ in_verification = False
139
+ continue
140
+
141
+ if current is None:
142
+ continue
143
+
144
+ sev = SEVERITY_RE.match(line)
145
+ if sev:
146
+ current.severity = sev.group(1).lower()
147
+ continue
148
+
149
+ if line.strip().startswith("- **Verification:**"):
150
+ in_verification = True
151
+ continue
152
+ if line.strip().startswith("- **Evidence:**") or line.strip().startswith("- **Source:**"):
153
+ in_verification = False
154
+ continue
155
+
156
+ if not in_verification:
157
+ continue
158
+
159
+ m = ITEM_LINE_RE.match(line) or SUB_ITEM_LINE_RE.match(line)
160
+ if not m:
161
+ continue
162
+ raw_id, recommended_marker, text = m.group(1), m.group(2), m.group(3)
163
+ # An item is recommended if the control is recommended-severity, the line
164
+ # carries the (Recommended) marker, or the id is R-prefixed (R1, R1.a, …)
165
+ # — the last covers sub-items of a recommended parent, which inherit the
166
+ # prefix but not the inline marker.
167
+ is_rec_id = normalize_item_id(raw_id).startswith("r")
168
+ mandatory = current.severity == "mandatory" and not recommended_marker and not is_rec_id
169
+ current.items.append(ChecklistItem(raw_id, mandatory, text.strip()))
170
+
171
+ return controls
172
+
173
+
174
+ # ---------------------------------------------------------------------------
175
+ # Tracker parsing
176
+ # ---------------------------------------------------------------------------
177
+
178
+ TABLE_HEADER_RE = re.compile(r"^\|\s*Control ID\s*\|", re.IGNORECASE)
179
+ SEPARATOR_RE = re.compile(r"^\|[\s\-:|]+\|\s*$")
180
+ PHASES_RE = re.compile(r"\*\*Audit Phases Completed:\*\*\s*(.+?)\s*$", re.IGNORECASE)
181
+ ARCHETYPES_RE = re.compile(r"\*\*Archetypes In Scope:\*\*\s*(.+?)\s*$", re.IGNORECASE)
182
+ SCOPE_RE = re.compile(r"\*\*Scope:\*\*\s*(.+?)\s*$", re.IGNORECASE)
183
+
184
+
185
+ class TrackerRow:
186
+ __slots__ = ("control_id", "title", "archetype", "observations", "issue", "rating", "remediation", "complete_by", "status", "raw")
187
+
188
+ def __init__(self, cells: list[str]):
189
+ # Expected column order: Control ID | Control Title | Archetype | Observations
190
+ # | Issue Description | Control Effectiveness Rating | Proposed Remediation
191
+ # | Complete By | Remediation Status
192
+ # Pad cells defensively in case of trailing-empty stripping.
193
+ cells = cells + [""] * (9 - len(cells))
194
+ self.control_id = cells[0].strip()
195
+ self.title = cells[1].strip()
196
+ self.archetype = cells[2].strip()
197
+ self.observations = cells[3]
198
+ self.issue = cells[4]
199
+ self.rating = cells[5].strip()
200
+ self.remediation = cells[6]
201
+ self.complete_by = cells[7].strip()
202
+ self.status = cells[8]
203
+ self.raw = cells
204
+
205
+
206
+ def split_table_row(line: str) -> list[str]:
207
+ """Split a markdown table row on unescaped pipes; ``\\|`` stays inside its cell."""
208
+ body = line.strip()
209
+ if body.startswith("|"):
210
+ body = body[1:]
211
+ if body.endswith("|") and not body.endswith("\\|"):
212
+ body = body[:-1]
213
+ return [c.strip() for c in re.split(r"(?<!\\)\|", body)]
214
+
215
+
216
+ def parse_tracker(path: Path) -> tuple[list[str], list[str], Optional[str], list[TrackerRow]]:
217
+ """Return (phases, archetypes_in_scope, scope_string, rows)."""
218
+ text = path.read_text(encoding="utf-8")
219
+ phases: list[str] = []
220
+ archetypes: list[str] = []
221
+ scope: Optional[str] = None
222
+ rows: list[TrackerRow] = []
223
+
224
+ in_table = False
225
+ for line in text.splitlines():
226
+ m = PHASES_RE.search(line)
227
+ if m:
228
+ phase_str = m.group(1).strip().strip("[]")
229
+ phases = [p.strip() for p in re.split(r"[,;]", phase_str) if p.strip()]
230
+ continue
231
+
232
+ m = ARCHETYPES_RE.search(line)
233
+ if m:
234
+ arch_str = m.group(1).strip().strip("[]")
235
+ # Each token may be annotated, e.g. "AACQ (22 controls)". Keep only the
236
+ # leading archetype code so it matches a control's "AACQ.Cx.y" prefix;
237
+ # otherwise the scope set comes out empty and every row is flagged
238
+ # "outside declared scope" (and the missing-row check is disabled).
239
+ archetypes = [
240
+ am.group(1).upper()
241
+ for a in re.split(r"[,;]", arch_str)
242
+ if (am := re.match(r"\s*([A-Za-z]+)", a))
243
+ ]
244
+ continue
245
+
246
+ m = SCOPE_RE.search(line)
247
+ if m:
248
+ scope = m.group(1).strip()
249
+ continue
250
+
251
+ if not in_table:
252
+ if TABLE_HEADER_RE.match(line):
253
+ in_table = True
254
+ continue
255
+
256
+ if SEPARATOR_RE.match(line):
257
+ continue
258
+ if not line.strip().startswith("|"):
259
+ in_table = False
260
+ continue
261
+
262
+ cells = split_table_row(line)
263
+ if not cells or not cells[0]:
264
+ continue
265
+ rows.append(TrackerRow(cells))
266
+
267
+ return phases, archetypes, scope, rows
268
+
269
+
270
+ # ---------------------------------------------------------------------------
271
+ # Observation cell parsing
272
+ # ---------------------------------------------------------------------------
273
+
274
+ BULLET_SPLIT_RE = re.compile(r"\s*<br\s*/?>\s*|\n+", re.IGNORECASE)
275
+ BULLET_PREFIX_RE = re.compile(r"^[\s•\-\*]+")
276
+ OBS_BULLET_RE = re.compile(
277
+ r"^Control\s+([^:]+?):\s*(.*)$",
278
+ re.IGNORECASE,
279
+ )
280
+
281
+
282
+ class Observation:
283
+ __slots__ = ("item_id", "canonical", "status", "text")
284
+
285
+ def __init__(self, item_id: str, status: str, text: str):
286
+ self.item_id = item_id
287
+ self.canonical = normalize_item_id(item_id)
288
+ self.status = status # "covered" | "partial" | "not_addressed" | "unknown"
289
+ self.text = text
290
+
291
+
292
+ def classify_status(text: str) -> str:
293
+ low = text.lower().strip()
294
+ if "not addressed" in low:
295
+ return "not_addressed"
296
+ if low.startswith("partial") or "partial —" in low or "partial -" in low:
297
+ return "partial"
298
+ if "covered in" in low or low.startswith("covered"):
299
+ return "covered"
300
+ return "unknown"
301
+
302
+
303
+ def parse_observations(cell: str) -> list[Observation]:
304
+ out: list[Observation] = []
305
+ for chunk in BULLET_SPLIT_RE.split(cell):
306
+ chunk = BULLET_PREFIX_RE.sub("", chunk).strip()
307
+ if not chunk:
308
+ continue
309
+ m = OBS_BULLET_RE.match(chunk)
310
+ if not m:
311
+ continue
312
+ item_id = m.group(1).strip()
313
+ text = m.group(2).strip()
314
+ out.append(Observation(item_id, classify_status(text), text))
315
+ return out
316
+
317
+
318
+ # ---------------------------------------------------------------------------
319
+ # Issue Description gap parsing
320
+ # ---------------------------------------------------------------------------
321
+
322
+ # Matches both the legacy parenthetical form "(Control 1c)" and the enumerated
323
+ # bracket form "[Control 1.a: Partial]" / "[Control R1.a]". The item id is
324
+ # captured up to the first ":" , ")" or "]".
325
+ GAP_REF_RE = re.compile(r"[\[(]\s*Control\s+([^\]:)]+?)\s*[\]:)]", re.IGNORECASE)
326
+
327
+ # Split point between the two Issue Description groups.
328
+ REC_LABEL_RE = re.compile(r"\*{0,2}\s*recommendations\s*\*{0,2}\s*:", re.IGNORECASE)
329
+
330
+
331
+ def extract_gap_refs(cell: str) -> list[str]:
332
+ return [normalize_item_id(m) for m in GAP_REF_RE.findall(cell)]
333
+
334
+
335
+ def split_issue_sections(cell: str) -> tuple[str, str]:
336
+ """Split an Issue Description cell into (actions_required, recommendations).
337
+
338
+ Everything before the "Recommendations:" label is Actions Required; the
339
+ label and everything after is Recommendations. Works for both the legacy
340
+ inline format and the enumerated format.
341
+ """
342
+ m = REC_LABEL_RE.search(cell)
343
+ if m:
344
+ return cell[: m.start()], cell[m.start():]
345
+ return cell, ""
346
+
347
+
348
+ # ---------------------------------------------------------------------------
349
+ # Citation grounding
350
+ # ---------------------------------------------------------------------------
351
+
352
+ CITATION_RE = re.compile(
353
+ r"covered in\s+(?P<rest>[^•<]+?)(?=(?:\s+and\s+covered\s+in\s|<br|$|•))",
354
+ re.IGNORECASE,
355
+ )
356
+ # Clause marker is the word "section" (prescribed). Legacy "§" is still accepted
357
+ # so trackers written under the old convention keep grounding. The "section"
358
+ # form requires a following digit so document names containing the word
359
+ # "section" aren't mistaken for a clause marker.
360
+ CLAUSE_RE = re.compile(r"(?:§\s*|\b[Ss]ection\s+(?=\d))([^\s,;]+)")
361
+ CLAUSE_SPLIT_RE = re.compile(r"§|\b[Ss]ection\s+(?=\d)")
362
+ # Sentence-break markers that end a citation phrase. After the citation
363
+ # "Covered in X section 3.2(a)", any prose introduced by " — ", "; ", " -- ", or
364
+ # ". " (period + space) is descriptive, not part of the citation, and its
365
+ # clause references should NOT be treated as citations to ground.
366
+ CITATION_END_RE = re.compile(r"\s+(?:—|--|;|\.\s)")
367
+
368
+
369
+ def list_evidence_files(evidence_dir: Path) -> list[Path]:
370
+ return sorted([p for p in evidence_dir.rglob("*.md") if p.is_file()])
371
+
372
+
373
+ # Trackers cite the client's original filename (``merchant-agreement-v2.4.pdf``);
374
+ # doc-to-md keeps the stem, so the parsed copy is ``merchant-agreement-v2.4.md``.
375
+ ORIGINAL_EXT_RE = re.compile(
376
+ r"\.(?:pdf|docx?|pptx?|xlsx?|xlsm|csv|txt|rtf|md|html?|png|jpe?g|gif|tiff?|bmp|webp)$",
377
+ re.IGNORECASE,
378
+ )
379
+
380
+
381
+ def normalize_doc_token(s: str) -> str:
382
+ # \W keeps non-ASCII letters, so a CJK filename still has a token to match.
383
+ return re.sub(r"[\W_]+", "", s.casefold())
384
+
385
+
386
+ class AmbiguousEvidence(Exception):
387
+ """More than one parsed file shares the cited original filename's stem."""
388
+
389
+ def __init__(self, matches: list[Path]):
390
+ super().__init__(", ".join(str(m) for m in matches))
391
+ self.matches = matches
392
+
393
+
394
+ def find_evidence_file(citation: str, evidence_files: list[Path]) -> Optional[Path]:
395
+ """Match a doc reference to an evidence file.
396
+
397
+ An original filename (with extension) resolves only to the parsed markdown
398
+ with the same stem: no fuzzy fallback, so ``policy-v2.pdf`` never grounds
399
+ against ``policy-v2.4.md``. When several files share the stem, the one in a
400
+ folder named after the extension (``pdf/policy.md``) wins; otherwise
401
+ ``AmbiguousEvidence`` is raised. A reference without an extension falls
402
+ back to best-effort fuzzy matching.
403
+ """
404
+ cited = citation.strip()
405
+ ext = ORIGINAL_EXT_RE.search(cited)
406
+ cite_norm = normalize_doc_token(ORIGINAL_EXT_RE.sub("", cited))
407
+ if not cite_norm:
408
+ return None
409
+ exact = [p for p in evidence_files if normalize_doc_token(p.stem) == cite_norm]
410
+ if ext:
411
+ if len(exact) > 1:
412
+ suffix = ext.group(0)[1:].lower()
413
+ by_folder = [p for p in exact if p.parent.name.lower() == suffix]
414
+ if len(by_folder) == 1:
415
+ return by_folder[0]
416
+ raise AmbiguousEvidence(exact)
417
+ return exact[0] if exact else None
418
+ if exact:
419
+ return exact[0]
420
+ best = None
421
+ best_overlap = 0
422
+ for path in evidence_files:
423
+ stem = normalize_doc_token(path.stem)
424
+ if not stem:
425
+ continue
426
+ # use longest common contiguous run as a cheap similarity score
427
+ overlap = 0
428
+ for length in range(min(len(stem), len(cite_norm)), 3, -1):
429
+ for i in range(len(cite_norm) - length + 1):
430
+ if cite_norm[i : i + length] in stem:
431
+ overlap = length
432
+ break
433
+ if overlap:
434
+ break
435
+ if overlap > best_overlap:
436
+ best, best_overlap = path, overlap
437
+ return best
438
+
439
+
440
+ def is_clause_grounded(clause: str, body: str) -> bool:
441
+ """Return True if the clause string is anchored in the evidence body.
442
+
443
+ Tolerates nested clause references — a cited ``section 3.2(a)(i)`` still grounds
444
+ when the evidence has ``3.2(a)`` as a heading and ``(i)`` listed as a
445
+ sub-item underneath. This matches how regulatory and contractual prose
446
+ is actually structured: parent headings carry the section number, child
447
+ bullets carry only the inner marker.
448
+ """
449
+ needle = clause.strip().rstrip(".,;")
450
+ # "(section 2.1)" captures "2.1)": drop a closing paren with no opener.
451
+ while needle.endswith(")") and needle.count(")") > needle.count("("):
452
+ needle = needle[:-1]
453
+ if not needle:
454
+ return True
455
+ if _in_body(needle, body):
456
+ return True
457
+ # Walk up by stripping the trailing parenthetical group. If the parent
458
+ # appears in the body and the leaf (e.g. "(i)") is also somewhere in the
459
+ # body, accept the citation as grounded.
460
+ parent_match = re.search(r"\(([^()]*)\)$", needle)
461
+ if not parent_match:
462
+ return False
463
+ parent = needle[: parent_match.start()].rstrip(".")
464
+ leaf = needle[parent_match.start():]
465
+ if not parent:
466
+ return False
467
+ if _in_body(parent, body) and leaf in body:
468
+ return True
469
+ return False
470
+
471
+
472
+ def _in_body(clause: str, body: str) -> bool:
473
+ """Clause appears in the body as a whole number: ``1`` does not match ``2.1`` or ``10``."""
474
+ lead = r"(?<![\d.])" if clause[:1].isdigit() else ""
475
+ tail = r"(?![\d])" if clause[-1:].isdigit() else ""
476
+ return re.search(lead + re.escape(clause) + tail, body) is not None
477
+
478
+
479
+ def check_citation(text: str, evidence_files: list[Path]) -> list[tuple[str, str]]:
480
+ """Return list of (level, message) for citation issues in one bullet text."""
481
+ issues: list[tuple[str, str]] = []
482
+ for cite in CITATION_RE.finditer(text):
483
+ rest = cite.group("rest").strip().rstrip(".,;")
484
+ # Trim descriptive prose after the citation phrase. Without this, a
485
+ # bullet like "Covered in MA section 3.2(a) — also see section 99.99 for context"
486
+ # would falsely flag section 99.99 as a missing citation.
487
+ end = CITATION_END_RE.search(rest)
488
+ if end:
489
+ rest = rest[: end.start()].strip()
490
+ clauses = CLAUSE_RE.findall(rest)
491
+ # doc-name fragment is the part before the first clause marker
492
+ doc_part = CLAUSE_SPLIT_RE.split(rest, maxsplit=1)[0].strip().rstrip("(),. ")
493
+ try:
494
+ evidence = find_evidence_file(doc_part, evidence_files)
495
+ except AmbiguousEvidence as amb:
496
+ names = ", ".join(f"{p.parent.name}/{p.name}" for p in amb.matches)
497
+ issues.append(("warning", f"ambiguous document '{doc_part}' matches {names}; put each in a folder named after its extension"))
498
+ continue
499
+ if evidence is None:
500
+ issues.append(("warning", f"no evidence file matches '{doc_part}'"))
501
+ continue
502
+ if doc_part and not ORIGINAL_EXT_RE.search(doc_part):
503
+ issues.append(("warning", f"cites '{doc_part}' without a file extension; cite the original filename"))
504
+ body = evidence.read_text(encoding="utf-8", errors="replace")
505
+ for clause in clauses:
506
+ if not is_clause_grounded(clause, body):
507
+ # Use the clause as cited, only stripping trailing punctuation
508
+ # (not closing parens, which are part of identifiers like (i)).
509
+ shown = clause.strip().rstrip(".,;")
510
+ issues.append(
511
+ (
512
+ "warning",
513
+ f"clause 'section {shown}' not found in {doc_part or evidence.name}",
514
+ )
515
+ )
516
+ return issues
517
+
518
+
519
+ # ---------------------------------------------------------------------------
520
+ # Validation
521
+ # ---------------------------------------------------------------------------
522
+
523
+
524
+ # Canonical VARS effectiveness outcomes (strict set). N/A covers controls that
525
+ # are not applicable to the entity's scope.
526
+ CANONICAL_RATINGS = {"satisfactory", "improvement recommended", "unsatisfactory", "na"}
527
+ NA_VALUES = {"na", "n/a", "n.a.", "not applicable"}
528
+
529
+
530
+ def normalize_rating(raw: str) -> str:
531
+ """Lower-case + collapse the N/A variants to a single 'na' token."""
532
+ r = raw.strip().lower()
533
+ return "na" if r in NA_VALUES else r
534
+
535
+
536
+ def derive_expected_rating(observations: list[Observation], items: list[ChecklistItem]) -> Optional[str]:
537
+ """Apply the VARS effectiveness rubric to predict the rating.
538
+
539
+ Rubric (rated prior to remediation):
540
+ - Unsatisfactory — any MANDATORY item is not fulfilled, i.e.
541
+ "Partial" or "Not addressed" (these become
542
+ Actions Required).
543
+ - Improvement Recommended — all mandatory items fulfilled, but at least
544
+ one RECOMMENDED item is missing or partial
545
+ (these become Recommendations).
546
+ - Satisfactory — every item (mandatory and recommended) Covered.
547
+
548
+ Returns one of "Unsatisfactory", "Improvement Recommended", "Satisfactory",
549
+ or None if the input is incomplete (missing observations). N/A is never
550
+ derived — it is a human scoping decision applied to the row directly.
551
+ """
552
+ if not items:
553
+ return None
554
+
555
+ observed_by_canon: dict[str, Observation] = {obs.canonical: obs for obs in observations}
556
+
557
+ def effective_status(item: ChecklistItem) -> str:
558
+ """Status of an item, inheriting from the nearest bulleted ancestor.
559
+
560
+ Mirrors item_coverage: if a leaf (e.g. 3.b.i) has no direct bullet but
561
+ its parent (3.b) is bulleted, the leaf takes the parent's status. Returns
562
+ 'missing' when neither the item nor any ancestor is observed.
563
+ """
564
+ parts = item.id.split(".")
565
+ for k in range(len(parts), 0, -1): # self -> root, most specific first
566
+ canon = normalize_item_id(".".join(parts[:k]))
567
+ if canon in observed_by_canon:
568
+ return observed_by_canon[canon].status
569
+ return "missing"
570
+
571
+ UNMET = ("missing", "not_addressed", "partial")
572
+
573
+ # any mandatory item not fully covered (Partial or Not addressed) -> Unsatisfactory
574
+ if any(item.mandatory and effective_status(item) in UNMET for item in items):
575
+ return "Unsatisfactory"
576
+
577
+ # all mandatory covered; any recommended item missing or partial -> Improvement Recommended
578
+ if any((not item.mandatory) and effective_status(item) in UNMET for item in items):
579
+ return "Improvement Recommended"
580
+
581
+ return "Satisfactory"
582
+
583
+
584
+ def derive_scope(
585
+ controls: dict[str, ChecklistControl],
586
+ archetypes: list[str],
587
+ scope_str: Optional[str],
588
+ explicit_scope: list[str],
589
+ ) -> set[str]:
590
+ """Determine which Control IDs are in scope for this audit.
591
+
592
+ Precedence: --scope CLI flag > tracker "Scope: targeted: ..." > tracker
593
+ "Archetypes In Scope" > all controls.
594
+ """
595
+ if explicit_scope:
596
+ return {cid for cid in explicit_scope if cid in controls}
597
+
598
+ if scope_str and scope_str.lower().startswith("targeted"):
599
+ # form: "targeted: AACQ.C2.1, AACQ.C14.1"
600
+ rest = scope_str.split(":", 1)[1] if ":" in scope_str else ""
601
+ ids = [s.strip() for s in re.split(r"[,;]", rest) if s.strip()]
602
+ return {cid for cid in ids if cid in controls}
603
+
604
+ if archetypes:
605
+ return {cid for cid in controls if cid.split(".")[0].upper() in archetypes}
606
+
607
+ return set(controls.keys())
608
+
609
+
610
+ def validate(
611
+ tracker_path: Path,
612
+ checklist_path: Path,
613
+ evidence_dir: Optional[Path],
614
+ explicit_scope: Optional[list[str]] = None,
615
+ ) -> list[dict]:
616
+ findings: list[dict] = []
617
+ controls = parse_checklist(checklist_path)
618
+ phases, archetypes, scope_str, rows = parse_tracker(tracker_path)
619
+
620
+ if not rows:
621
+ findings.append(
622
+ {
623
+ "check": "tracker_parse",
624
+ "level": "error",
625
+ "control_id": None,
626
+ "message": "no tracker rows parsed (table not found or empty)",
627
+ }
628
+ )
629
+ return findings
630
+
631
+ in_scope = derive_scope(controls, archetypes, scope_str, explicit_scope or [])
632
+ rows_by_id = {r.control_id: r for r in rows}
633
+ evidence_files: list[Path] = list_evidence_files(evidence_dir) if evidence_dir else []
634
+
635
+ # 1. row_coverage — only check controls in scope
636
+ for ctrl_id in in_scope:
637
+ if ctrl_id not in rows_by_id:
638
+ findings.append(
639
+ {
640
+ "check": "row_coverage",
641
+ "level": "error",
642
+ "control_id": ctrl_id,
643
+ "message": f"checklist control {ctrl_id} has no tracker row",
644
+ }
645
+ )
646
+ for ctrl_id in rows_by_id:
647
+ if ctrl_id not in controls:
648
+ findings.append(
649
+ {
650
+ "check": "row_coverage",
651
+ "level": "error",
652
+ "control_id": ctrl_id,
653
+ "message": f"tracker row {ctrl_id} not present in checklist",
654
+ }
655
+ )
656
+ elif ctrl_id not in in_scope:
657
+ findings.append(
658
+ {
659
+ "check": "row_coverage",
660
+ "level": "warning",
661
+ "control_id": ctrl_id,
662
+ "message": f"tracker row {ctrl_id} present but outside declared scope",
663
+ }
664
+ )
665
+
666
+ for row in rows:
667
+ control = controls.get(row.control_id)
668
+ if control is None:
669
+ continue
670
+
671
+ observations = parse_observations(row.observations)
672
+ observed = {obs.canonical for obs in observations}
673
+ item_canon = {item.canonical for item in control.items}
674
+
675
+ # 2. item_coverage — a checklist item is covered if it OR any dotted
676
+ # ancestor is bulleted (parent bullets roll up their sub-items).
677
+ for item in control.items:
678
+ if not (ancestor_canons(item.id) & observed):
679
+ findings.append(
680
+ {
681
+ "check": "item_coverage",
682
+ "level": "error",
683
+ "control_id": row.control_id,
684
+ "message": f"item ({item.id}) missing from Observations",
685
+ }
686
+ )
687
+ for canon in observed - item_canon:
688
+ findings.append(
689
+ {
690
+ "check": "item_coverage",
691
+ "level": "warning",
692
+ "control_id": row.control_id,
693
+ "message": f"Observations bullet 'Control {canon}' has no matching checklist item",
694
+ }
695
+ )
696
+
697
+ # 3. rating_value — must be one of the four canonical outcomes
698
+ rating_norm = normalize_rating(row.rating) if row.rating else ""
699
+ if row.rating and rating_norm not in CANONICAL_RATINGS:
700
+ findings.append(
701
+ {
702
+ "check": "rating_value",
703
+ "level": "error",
704
+ "control_id": row.control_id,
705
+ "message": (
706
+ f"rating '{row.rating}' is not a canonical outcome "
707
+ "(Satisfactory / Improvement Recommended / Unsatisfactory / N/A)"
708
+ ),
709
+ }
710
+ )
711
+
712
+ # clause_marker — the deliverable writes "section 3.2", never "§3.2".
713
+ for label, cell in (("Observations", row.observations), ("Issue Description", row.issue)):
714
+ if "§" in cell:
715
+ findings.append(
716
+ {
717
+ "check": "clause_marker",
718
+ "level": "warning",
719
+ "control_id": row.control_id,
720
+ "message": f"{label} uses '§'; write 'section <clause>' instead",
721
+ }
722
+ )
723
+
724
+ # 4. rubric_consistency — N/A rows are exempt (scoping decision)
725
+ expected = derive_expected_rating(observations, control.items)
726
+ if expected and row.rating and rating_norm != "na" and rating_norm != expected.lower():
727
+ findings.append(
728
+ {
729
+ "check": "rubric_consistency",
730
+ "level": "error",
731
+ "control_id": row.control_id,
732
+ "message": f"rating '{row.rating}' inconsistent with rubric (expected '{expected}')",
733
+ }
734
+ )
735
+
736
+ # 5. gap_xref — Actions Required gaps must not be "Covered"; Recommendations
737
+ # may reference a Covered mandatory item flagged for strengthening.
738
+ actions_text, rec_text = split_issue_sections(row.issue)
739
+ for section, refs, enforce_not_covered in (
740
+ ("Actions Required", extract_gap_refs(actions_text), True),
741
+ ("Recommendations", extract_gap_refs(rec_text), False),
742
+ ):
743
+ for gap_canon in refs:
744
+ obs = next((o for o in observations if o.canonical == gap_canon), None)
745
+ if obs is None:
746
+ findings.append(
747
+ {
748
+ "check": "gap_xref",
749
+ "level": "warning",
750
+ "control_id": row.control_id,
751
+ "message": f"{section} references (Control {gap_canon}) which has no Observations bullet",
752
+ }
753
+ )
754
+ elif enforce_not_covered and obs.status == "covered":
755
+ findings.append(
756
+ {
757
+ "check": "gap_xref",
758
+ "level": "warning",
759
+ "control_id": row.control_id,
760
+ "message": f"{section} references (Control {gap_canon}) but Observation is 'Covered'",
761
+ }
762
+ )
763
+
764
+ # 6. citation_grounding
765
+ if evidence_files:
766
+ for obs in observations:
767
+ if obs.status not in ("covered", "partial"):
768
+ continue
769
+ for level, msg in check_citation(obs.text, evidence_files):
770
+ findings.append(
771
+ {
772
+ "check": "citation_grounding",
773
+ "level": level,
774
+ "control_id": row.control_id,
775
+ "message": f"({obs.item_id}) {msg}",
776
+ }
777
+ )
778
+
779
+ return findings
780
+
781
+
782
+ # ---------------------------------------------------------------------------
783
+ # CLI
784
+ # ---------------------------------------------------------------------------
785
+
786
+
787
+ def main() -> int:
788
+ ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
789
+ ap.add_argument("tracker", type=Path)
790
+ ap.add_argument("--checklist", type=Path, required=True)
791
+ ap.add_argument("--evidence", type=Path, default=None, help="evidence markdown directory (enables citation grounding)")
792
+ ap.add_argument(
793
+ "--scope",
794
+ type=str,
795
+ default=None,
796
+ help="comma-separated Control IDs to validate (overrides tracker-declared scope)",
797
+ )
798
+ ap.add_argument("--json", action="store_true", help="emit findings as JSON")
799
+ ap.add_argument("--strict", action="store_true", help="exit non-zero on warnings as well")
800
+ args = ap.parse_args()
801
+
802
+ explicit_scope = (
803
+ [s.strip() for s in args.scope.split(",") if s.strip()] if args.scope else None
804
+ )
805
+ findings = validate(args.tracker, args.checklist, args.evidence, explicit_scope)
806
+
807
+ errors = [f for f in findings if f["level"] == "error"]
808
+ warnings = [f for f in findings if f["level"] == "warning"]
809
+
810
+ if args.json:
811
+ json.dump({"errors": errors, "warnings": warnings}, sys.stdout, indent=2)
812
+ sys.stdout.write("\n")
813
+ else:
814
+ for f in findings:
815
+ tag = "ERROR" if f["level"] == "error" else "WARN"
816
+ ctl = f["control_id"] or "-"
817
+ print(f"[{tag}] [{f['check']}] {ctl}: {f['message']}", file=sys.stderr)
818
+ print(
819
+ f"\n{len(errors)} error(s), {len(warnings)} warning(s)",
820
+ file=sys.stderr,
821
+ )
822
+
823
+ if errors or (args.strict and warnings):
824
+ return 1
825
+ return 0
826
+
827
+
828
+ if __name__ == "__main__":
829
+ sys.exit(main())