@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,806 @@
1
+ #!/usr/bin/env node
2
+ // Changelog + SemVer plan from baseline tag..SHA.
3
+ //
4
+ // Usage:
5
+ // node plan.mjs --sha <sha> [--trunk main] [--json] [--strict] [--cloud-run] [--dark-flags <list.json>] [--prepared-metadata <file>]
6
+ //
7
+ // Exit: 0 ok · 1 git/inspect failed · 2 usage
8
+ import { createHash, randomUUID } from 'node:crypto'
9
+ import { existsSync, readFileSync } from 'node:fs'
10
+ import { join } from 'node:path'
11
+ import { spawnSync } from 'node:child_process'
12
+ import { isDeepStrictEqual } from 'node:util'
13
+ import { isMainModule } from '../../engineering-runtime/scripts/main-module.mjs'
14
+
15
+ const PLAN_USAGE = 'plan.mjs --sha <sha> [--trunk main] [--json] [--strict] [--cloud-run] [--dark-flags <list.json>] [--prepared-metadata <file>]'
16
+ const SEMVER_RE = /^v?(\d+)\.(\d+)\.(\d+)$/
17
+ const TITLE_RE = /^\s*([a-zA-Z]+)(?:\(([^)]*)\))?(!)?:\s*(.+)$/
18
+ const SECTION_BY_TYPE = {
19
+ feat: 'Added',
20
+ fix: 'Fixed',
21
+ perf: 'Changed',
22
+ refactor: 'Changed',
23
+ revert: 'Removed',
24
+ security: 'Security',
25
+ }
26
+ const INTERNAL_TYPES = new Set(['build', 'chore', 'ci', 'docs', 'style', 'test'])
27
+ const SECTION_ORDER = ['Breaking', 'Added', 'Changed', 'Removed', 'Fixed', 'Security']
28
+ export const DISTRIBUTION_MANIFEST = 'skills/distribution/manifest.json'
29
+
30
+ export function parseSemver(input) {
31
+ const match = SEMVER_RE.exec(String(input || '').trim())
32
+ if (!match) return null
33
+ return { major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3]) }
34
+ }
35
+
36
+ export function formatVersion(version) {
37
+ return `${version.major}.${version.minor}.${version.patch}`
38
+ }
39
+
40
+ export function compareVersions(left, right) {
41
+ for (const key of ['major', 'minor', 'patch']) {
42
+ if (left[key] !== right[key]) return left[key] < right[key] ? -1 : 1
43
+ }
44
+ return 0
45
+ }
46
+
47
+ export function latestReleaseTag(tags) {
48
+ const parsed = tags
49
+ .map((tag) => ({ tag, version: parseSemver(tag) }))
50
+ .filter((item) => item.version)
51
+ if (parsed.length === 0) return null
52
+ parsed.sort((left, right) => compareVersions(left.version, right.version))
53
+ return parsed[parsed.length - 1]
54
+ }
55
+
56
+ export function nextReleaseCandidateTag(finalTag, tags) {
57
+ const escaped = finalTag.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
58
+ const pattern = new RegExp(`^${escaped}-rc\\.([1-9]\\d*)$`)
59
+ const ordinals = tags
60
+ .map((tag) => pattern.exec(tag))
61
+ .filter(Boolean)
62
+ .map((match) => Number(match[1]))
63
+ return `${finalTag}-rc.${ordinals.length ? Math.max(...ordinals) + 1 : 1}`
64
+ }
65
+
66
+ export function classifySubject(subject, body = '') {
67
+ const title = String(subject || '').trim()
68
+ const match = TITLE_RE.exec(title)
69
+ const type = match ? match[1].toLowerCase() : 'other'
70
+ const breaking = Boolean(match && match[3]) || /(^|\n)BREAKING[ -]CHANGE:/.test(body)
71
+ const internal = INTERNAL_TYPES.has(type)
72
+ const section = breaking ? 'Breaking' : internal ? null : (SECTION_BY_TYPE[type] || 'Changed')
73
+ return {
74
+ title,
75
+ type,
76
+ breaking,
77
+ internal,
78
+ section,
79
+ scope: match ? match[2] || null : null,
80
+ summary: match ? match[4] : title,
81
+ }
82
+ }
83
+
84
+ export function bumpFor(changes, current, { strict = false } = {}) {
85
+ const zeroVer = !strict && current.major === 0
86
+ if (changes.some((change) => change.breaking)) {
87
+ return { bump: zeroVer ? 'minor' : 'major', reason: 'breaking change in the release content' }
88
+ }
89
+ if (changes.some((change) => change.type === 'feat')) {
90
+ return { bump: 'minor', reason: 'new feature in the release content' }
91
+ }
92
+ return { bump: 'patch', reason: 'fixes and internal changes only' }
93
+ }
94
+
95
+ export function nextVersion(current, bump) {
96
+ if (bump === 'major') return { major: current.major + 1, minor: 0, patch: 0 }
97
+ if (bump === 'minor') return { major: current.major, minor: current.minor + 1, patch: 0 }
98
+ return { major: current.major, minor: current.minor, patch: current.patch + 1 }
99
+ }
100
+
101
+ export function renderChangelog({ version, date, changes, darkFlags = null }) {
102
+ const groups = new Map(SECTION_ORDER.map((name) => [name, []]))
103
+ for (const change of changes) {
104
+ if (!change.section) continue
105
+ const list = groups.get(change.section)
106
+ if (list) list.push(change)
107
+ }
108
+ const lines = [`## [${version}] - ${date}`]
109
+ for (const name of SECTION_ORDER) {
110
+ const items = groups.get(name)
111
+ if (!items.length) continue
112
+ lines.push('', `### ${name}`)
113
+ for (const item of items) lines.push(`- ${item.summary}`)
114
+ }
115
+ // Dark launch (resolve-release only): features that shipped behind a default-off flag.
116
+ if (darkFlags?.length) {
117
+ lines.push('', '### Shipped dark')
118
+ for (const flag of darkFlags) lines.push(`- ${flag.summary} (flag \`${flag.name}\`, off)`)
119
+ }
120
+ if (lines.length === 1) {
121
+ lines.push('', '- See the commits included in this tag.')
122
+ }
123
+ return `${lines.join('\n')}\n`
124
+ }
125
+
126
+ function distributionVersion(text) {
127
+ try {
128
+ const manifest = JSON.parse(text)
129
+ const parsed = parseSemver(manifest.releaseVersion)
130
+ return parsed ? formatVersion(parsed) : null
131
+ } catch {
132
+ return null
133
+ }
134
+ }
135
+
136
+ export function discoverVersionFiles(root) {
137
+ const distributionPath = join(root, DISTRIBUTION_MANIFEST)
138
+ if (existsSync(distributionPath) && distributionVersion(readFileSync(distributionPath, 'utf8'))) {
139
+ return [DISTRIBUTION_MANIFEST]
140
+ }
141
+ const files = []
142
+ for (const name of ['package.json', 'package-lock.json', 'pyproject.toml', 'VERSION']) {
143
+ if (existsSync(join(root, name))) files.push(name)
144
+ }
145
+ return files
146
+ }
147
+
148
+ export function currentVersionFromFiles(root, files = discoverVersionFiles(root)) {
149
+ if (files.includes(DISTRIBUTION_MANIFEST)) {
150
+ return distributionVersion(readFileSync(join(root, DISTRIBUTION_MANIFEST), 'utf8'))
151
+ }
152
+ if (files.includes('package.json')) {
153
+ const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
154
+ const parsed = parseSemver(pkg.version)
155
+ if (parsed) return formatVersion(parsed)
156
+ }
157
+ if (files.includes('pyproject.toml')) {
158
+ const text = readFileSync(join(root, 'pyproject.toml'), 'utf8')
159
+ const match = text.match(/^version\s*=\s*"([^"]+)"/m)
160
+ const parsed = match && parseSemver(match[1])
161
+ if (parsed) return formatVersion(parsed)
162
+ }
163
+ if (files.includes('VERSION')) {
164
+ const parsed = parseSemver(readFileSync(join(root, 'VERSION'), 'utf8'))
165
+ if (parsed) return formatVersion(parsed)
166
+ }
167
+ return null
168
+ }
169
+
170
+ function git(args, cwd) {
171
+ const result = spawnSync('git', args, { encoding: 'utf8', cwd })
172
+ if (result.status !== 0) {
173
+ const err = new Error(result.stderr.trim() || `git ${args.join(' ')} failed`)
174
+ err.exitCode = 1
175
+ throw err
176
+ }
177
+ return result.stdout
178
+ }
179
+
180
+ function versionFilesAt(sha, cwd) {
181
+ const tree = new Set(git(['ls-tree', '-r', '--name-only', sha], cwd)
182
+ .split('\n')
183
+ .map((line) => line.trim())
184
+ .filter(Boolean))
185
+ if (tree.has(DISTRIBUTION_MANIFEST)) {
186
+ const version = distributionVersion(git(['show', `${sha}:${DISTRIBUTION_MANIFEST}`], cwd))
187
+ if (version) return [DISTRIBUTION_MANIFEST]
188
+ }
189
+ return ['package.json', 'package-lock.json', 'pyproject.toml', 'VERSION']
190
+ .filter((name) => tree.has(name))
191
+ }
192
+
193
+ function versionFromGitFiles(sha, files, cwd) {
194
+ const contents = (path) => git(['show', `${sha}:${path}`], cwd)
195
+ if (files.includes(DISTRIBUTION_MANIFEST)) {
196
+ return distributionVersion(contents(DISTRIBUTION_MANIFEST))
197
+ }
198
+ if (files.includes('package.json')) {
199
+ const parsed = parseSemver(JSON.parse(contents('package.json')).version)
200
+ if (parsed) return formatVersion(parsed)
201
+ }
202
+ if (files.includes('pyproject.toml')) {
203
+ const match = contents('pyproject.toml').match(/^version\s*=\s*"([^"]+)"/m)
204
+ const parsed = match && parseSemver(match[1])
205
+ if (parsed) return formatVersion(parsed)
206
+ }
207
+ if (files.includes('VERSION')) {
208
+ const parsed = parseSemver(contents('VERSION'))
209
+ if (parsed) return formatVersion(parsed)
210
+ }
211
+ return null
212
+ }
213
+
214
+ const FULL_SHA_RE = /^[0-9a-f]{40}$/
215
+ const sha256 = (text) => createHash('sha256').update(text).digest('hex')
216
+
217
+ function requirePrepared(condition, message) {
218
+ if (!condition) throw new Error(`prepared metadata: ${message}`)
219
+ }
220
+
221
+ function exactFields(value, fields, label) {
222
+ requirePrepared(value && typeof value === 'object' && !Array.isArray(value), `${label} must be an object`)
223
+ requirePrepared(Object.keys(value).every((key) => fields.includes(key)), `${label} has an unknown field`)
224
+ requirePrepared(fields.every((key) => Object.hasOwn(value, key)), `${label} is missing a required field`)
225
+ }
226
+
227
+ function requireSafePreparedVersion(actual, label) {
228
+ requirePrepared(typeof actual === 'string' && /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(actual)
229
+ && actual.split('.').every((part) => Number.isSafeInteger(Number(part))),
230
+ `${label} version must be canonical x.y.z with safe integer components`)
231
+ }
232
+
233
+ function validatePreparedInput(input) {
234
+ exactFields(input, ['schema', 'repository', 'version', 'metadataPr', 'cutSha', 'mergeSha', 'previousAttempt'], 'input')
235
+ requirePrepared(['prepared-release-metadata/v1', 'prepared-release-metadata/v2'].includes(input.schema), 'unsupported schema')
236
+ requirePrepared(typeof input.repository === 'string' && /^[A-Za-z0-9][A-Za-z0-9-]*\/[A-Za-z0-9][A-Za-z0-9._-]*$/.test(input.repository)
237
+ && !input.repository.endsWith('.git'), 'repository must be owner/repo')
238
+ requirePrepared(typeof input.version === 'string' && parseSemver(input.version)
239
+ && formatVersion(parseSemver(input.version)) === input.version, 'version must be canonical x.y.z')
240
+ requirePrepared(Number.isSafeInteger(input.metadataPr) && input.metadataPr > 0, 'metadataPr must be a positive PR number')
241
+ for (const key of ['cutSha', 'mergeSha']) requirePrepared(typeof input[key] === 'string' && FULL_SHA_RE.test(input[key]), `${key} must be a full commit SHA`)
242
+ const previous = input.previousAttempt
243
+ const v2 = input.schema === 'prepared-release-metadata/v2'
244
+ if (v2) requirePrepared(previous && ['pre-tag', 'cloudRunRC'].includes(previous.kind), 'previousAttempt.kind must be pre-tag or cloudRunRC')
245
+ const preTag = v2 && previous.kind === 'pre-tag'
246
+ exactFields(previous, [...(v2 ? ['kind'] : []), 'receiptId', 'receiptSha256',
247
+ ...(preTag ? ['sourceSha'] : ['candidateTag', 'candidateSha'])], 'previousAttempt')
248
+ requirePrepared(typeof previous.receiptId === 'string' && /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(previous.receiptId), 'invalid previous receiptId')
249
+ requirePrepared(typeof previous.receiptSha256 === 'string' && /^[0-9a-f]{64}$/.test(previous.receiptSha256), 'invalid previous receiptSha256')
250
+ if (preTag) {
251
+ requireSafePreparedVersion(input.version, 'input')
252
+ requirePrepared(typeof previous.sourceSha === 'string' && FULL_SHA_RE.test(previous.sourceSha), 'previous sourceSha must be a full commit SHA')
253
+ return
254
+ }
255
+ const escapedTag = `v${input.version}`.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
256
+ requirePrepared(typeof previous.candidateTag === 'string' && new RegExp(`^${escapedTag}-rc\\.([1-9]\\d*)$`).test(previous.candidateTag), 'previous candidateTag must be an RC for this version')
257
+ const ordinal = new RegExp(`^${escapedTag}-rc\\.([1-9]\\d*)$`).exec(previous.candidateTag)?.[1]
258
+ requirePrepared(ordinal && Number.isSafeInteger(Number(ordinal)), 'previous candidateTag RC ordinal is out of range')
259
+ requirePrepared(previous.candidateSha === input.mergeSha, 'previous candidateSha must equal the historical mergeSha')
260
+ }
261
+
262
+ function canonicalRepository(cwd) {
263
+ // Read the declared identity, not get-url's transport rewrite (used by offline git fixtures).
264
+ const url = git(['config', '--get', 'remote.origin.url'], cwd).trim()
265
+ const match = /^(?:https:\/\/github\.com\/|git@github\.com:|ssh:\/\/git@github\.com\/)([A-Za-z0-9-]+\/[A-Za-z0-9._-]+?)(?:\.git)?$/.exec(url)
266
+ requirePrepared(match, 'expected a canonical GitHub origin URL')
267
+ return match[1]
268
+ }
269
+
270
+ function checkedCommit(sha, cwd) {
271
+ const actual = git(['rev-parse', '--verify', `${sha}^{commit}`], cwd).trim()
272
+ requirePrepared(actual === sha, 'full commit SHA must identify a commit, not a tag object')
273
+ }
274
+
275
+ function requireAncestor(ancestor, descendant, label, cwd) {
276
+ const result = spawnSync('git', ['merge-base', '--is-ancestor', ancestor, descendant], { cwd, encoding: 'utf8' })
277
+ requirePrepared(result.status === 0, `${label} ancestor check ${result.status === 1 ? 'failed' : 'could not be inspected'}`)
278
+ }
279
+
280
+ function preparedVersionFilesAt(sha, cwd) {
281
+ const files = versionFilesAt(sha, cwd)
282
+ if (git(['ls-tree', sha, '--', 'uv.lock'], cwd).trim()) {
283
+ requirePrepared(files.includes('pyproject.toml'), 'uv.lock requires a root pyproject.toml')
284
+ files.push('uv.lock')
285
+ }
286
+ return files
287
+ }
288
+
289
+ const PREPARED_BLOB_LIMIT = 8 * 1024 * 1024
290
+
291
+ function preparedBlobAt(sha, path, cwd) {
292
+ const result = spawnSync('git', ['show', `${sha}:${path}`], {
293
+ cwd, timeout: 5_000, maxBuffer: PREPARED_BLOB_LIMIT,
294
+ })
295
+ requirePrepared(!result.error && result.status === 0 && result.stdout.length <= PREPARED_BLOB_LIMIT,
296
+ `${path} Git blob acquisition failed (bounded to five seconds and 8 MiB)`)
297
+ return result.stdout
298
+ }
299
+
300
+ function preparedTomlAt(sha, path, cwd) {
301
+ return parsePreparedToml(preparedBlobAt(sha, path, cwd), path, cwd)
302
+ }
303
+
304
+ function preparedNumberIdentity(source) {
305
+ requirePrepared(typeof source === 'string', 'JSON numeric source requires Node 22+')
306
+ // JSON.parse owns syntax; normalize only its original numeric token, never the rounded Number.
307
+ const [, sign, integer, fraction = '', power = '0'] = /^(-?)(\d+)(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/.exec(source)
308
+ const digits = `${integer}${fraction}`.replace(/^0+/, '')
309
+ if (!digits) return ['number', `${sign}0`, '0']
310
+ const exponent = Number(power)
311
+ // Extreme exponents retain lexical identity without allocating an enormous BigInt.
312
+ if (!Number.isSafeInteger(exponent)) return ['number-source', source]
313
+ let end = digits.length
314
+ while (digits[end - 1] === '0') end -= 1
315
+ const coefficient = digits.slice(0, end)
316
+ const scale = BigInt(exponent) - BigInt(fraction.length) + BigInt(digits.length - end)
317
+ return ['number', `${sign}${coefficient}`, String(scale)]
318
+ }
319
+
320
+ function preparedJsonAt(sha, path, cwd) {
321
+ const bytes = preparedBlobAt(sha, path, cwd)
322
+ let parsed
323
+ try {
324
+ const text = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(bytes)
325
+ parsed = JSON.parse(text, (_key, item, context) => {
326
+ if (typeof item === 'number') {
327
+ if (!Number.isFinite(item) || (Number.isInteger(item) && !Number.isSafeInteger(item))) {
328
+ throw new Error('unsupported JSON number')
329
+ }
330
+ return { data: item, canonical: preparedNumberIdentity(context?.source) }
331
+ }
332
+ // Tag every node so input arrays/objects cannot impersonate numeric identities.
333
+ if (Array.isArray(item)) {
334
+ return { data: item.map((child) => child.data), canonical: ['array', item.map((child) => child.canonical)] }
335
+ }
336
+ if (item !== null && typeof item === 'object') {
337
+ const entries = Object.entries(item)
338
+ return {
339
+ data: Object.fromEntries(entries.map(([key, child]) => [key, child.data])),
340
+ canonical: ['object', Object.fromEntries(entries.map(([key, child]) => [key, child.canonical]))],
341
+ }
342
+ }
343
+ return { data: item, canonical: [item === null ? 'null' : typeof item, item] }
344
+ })
345
+ } catch {
346
+ requirePrepared(false, `${path} JSON inspection failed (requires strict UTF-8, finite numbers, safe integers and original numeric source)`)
347
+ }
348
+ requirePrepared(parsed.data && typeof parsed.data === 'object' && !Array.isArray(parsed.data), `${path} JSON root must be an object`)
349
+ return parsed
350
+ }
351
+
352
+ function preparedNpmAt(sha, files, version, cwd) {
353
+ requirePrepared(files.includes('package.json'), 'pre-tag reuse requires a publishable root package.json')
354
+ requireRegularFile(sha, 'package.json', cwd)
355
+ const { data: pkg, canonical: pkgIdentity } = preparedJsonAt(sha, 'package.json', cwd)
356
+ requirePrepared(typeof pkg.name === 'string' && pkg.name.length <= 214
357
+ && /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/.test(pkg.name)
358
+ && !['node_modules', 'favicon.ico'].includes(pkg.name), 'package.json name must be a canonical safe npm package name (max 214 characters)')
359
+ requirePrepared(!Object.hasOwn(pkg, 'private') || pkg.private === false, 'package.json private must be absent or false')
360
+ requirePrepared(!Object.hasOwn(pkg, 'workspaces'), 'package.json workspaces are unsupported for pre-tag reuse')
361
+ const checkVersion = (actual, label) => {
362
+ requireSafePreparedVersion(actual, label)
363
+ if (version !== null) requirePrepared(actual === version, `${label} version ${actual} does not match ${version}`)
364
+ }
365
+ checkVersion(pkg.version, 'package.json')
366
+ if (!files.includes('package-lock.json')) return { pkg, lock: null, canonical: { pkg: pkgIdentity, lock: null } }
367
+ requireRegularFile(sha, 'package-lock.json', cwd)
368
+ const { data: lock, canonical: lockIdentity } = preparedJsonAt(sha, 'package-lock.json', cwd)
369
+ requirePrepared(Number.isInteger(lock.lockfileVersion) && [2, 3].includes(lock.lockfileVersion), 'package-lock.json has an unsupported lock format (expected integer 2 or 3)')
370
+ requirePrepared(lock.name === pkg.name, 'package-lock.json name does not match package.json')
371
+ checkVersion(lock.version, 'package-lock.json')
372
+ requirePrepared(lock.packages && typeof lock.packages === 'object' && !Array.isArray(lock.packages)
373
+ && Object.hasOwn(lock.packages, '') && lock.packages[''] && typeof lock.packages[''] === 'object'
374
+ && !Array.isArray(lock.packages['']), 'package-lock.json root identity is missing')
375
+ requirePrepared(lock.packages[''].name === pkg.name, 'package-lock.json root name does not match package.json')
376
+ checkVersion(lock.packages[''].version, 'package-lock.json root')
377
+ return { pkg, lock, canonical: { pkg: pkgIdentity, lock: lockIdentity } }
378
+ }
379
+
380
+ function parsePreparedToml(bytes, path, cwd) {
381
+ requirePrepared(bytes.length <= PREPARED_BLOB_LIMIT, `${path} exceeds TOML input limit`)
382
+ // Keep raw Git bytes through strict UTF-8 decoding; JSON numbers alone lose TOML types.
383
+ const result = spawnSync('python3', ['-I', '-S', '-c', `
384
+ import json, math, sys, tomllib
385
+ value = tomllib.loads(sys.stdin.buffer.read().decode('utf-8', errors='strict'))
386
+ def canonical(value):
387
+ if type(value) is dict:
388
+ return ['dict', {key: canonical(item) for key, item in value.items()}]
389
+ if type(value) is list:
390
+ return ['list', [canonical(item) for item in value]]
391
+ if type(value) is bool:
392
+ return ['bool', value]
393
+ if type(value) is int:
394
+ if abs(value) > 9007199254740991:
395
+ raise ValueError('integer exceeds lossless JSON range')
396
+ return ['int', str(value)]
397
+ if type(value) is float and math.isfinite(value):
398
+ return ['float', value.hex()]
399
+ if type(value) is str:
400
+ return ['str', value]
401
+ raise ValueError('unsupported TOML value')
402
+ json.dump({'data': value, 'canonical': canonical(value)}, sys.stdout, allow_nan=False)
403
+ `], { cwd, input: bytes, encoding: 'utf8', timeout: 5_000, maxBuffer: PREPARED_BLOB_LIMIT })
404
+ requirePrepared(!result.error && result.status === 0, `${path} TOML inspection failed (requires Python 3.11+ with tomllib; bounded, JSON-compatible TOML)`)
405
+ try {
406
+ return JSON.parse(result.stdout)
407
+ } catch {
408
+ requirePrepared(false, `${path} TOML parser returned invalid JSON`)
409
+ }
410
+ }
411
+
412
+ // Every node, including containers, is tagged, so repository data cannot impersonate a scalar.
413
+ function omitTomlPath(canonical, path) {
414
+ let node = canonical
415
+ for (const key of path.slice(0, -1)) node = node[1][key]
416
+ delete node[1][path.at(-1)]
417
+ }
418
+
419
+ function pyprojectIdentities(parsed) {
420
+ const { data, canonical: withoutVersions } = parsed
421
+ const project = data.project ?? data.tool?.poetry
422
+ const name = project?.name
423
+ const version = project?.version
424
+ const versions = []
425
+ for (const [section, path] of [[data.project, ['project', 'version']], [data.tool?.poetry, ['tool', 'poetry', 'version']]]) {
426
+ if (!section || !Object.hasOwn(section, 'version')) continue
427
+ requirePrepared(typeof section.version === 'string', 'pyproject has no static project version')
428
+ versions.push(section.version)
429
+ omitTomlPath(withoutVersions, path)
430
+ }
431
+ requirePrepared(versions.length > 0, 'pyproject has no static project version')
432
+ return { name, version, versions, withoutVersions }
433
+ }
434
+
435
+ function preparedPythonAt(sha, files, cwd) {
436
+ if (!files.includes('pyproject.toml')) return null
437
+ const project = pyprojectIdentities(preparedTomlAt(sha, 'pyproject.toml', cwd))
438
+ if (!files.includes('uv.lock')) return { project }
439
+ requirePrepared(typeof project.name === 'string' && project.name.trim().length > 0, 'pyproject.toml project name is missing')
440
+ requirePrepared(typeof project.version === 'string', 'pyproject.toml project version must be static')
441
+ const { data: lock, canonical: withoutRootVersion } = preparedTomlAt(sha, 'uv.lock', cwd)
442
+ requirePrepared(lock.version === 1 && withoutRootVersion[1].version[0] === 'int', 'uv.lock has an unsupported lock format version (expected integer 1)')
443
+ requirePrepared(Array.isArray(lock.package), 'uv.lock package root is missing')
444
+ // Include name matches so a same-name package with the wrong source cannot disappear.
445
+ const roots = lock.package.filter((pkg) => pkg.name === project.name
446
+ || pkg.source?.editable === '.' || pkg.source?.virtual === '.')
447
+ requirePrepared(roots.length === 1, 'uv.lock must identify exactly one project root')
448
+ const root = roots[0]
449
+ requirePrepared(root.name === project.name, 'uv.lock root name does not match pyproject.toml project name')
450
+ requirePrepared(isDeepStrictEqual(root.source, { editable: '.' }) || isDeepStrictEqual(root.source, { virtual: '.' }),
451
+ 'uv.lock root source must be exactly editable = "." or virtual = "."')
452
+ requirePrepared(typeof root.version === 'string' && parseSemver(root.version)
453
+ && formatVersion(parseSemver(root.version)) === root.version, 'uv.lock root version must be canonical x.y.z')
454
+ const version = root.version
455
+ omitTomlPath(withoutRootVersion, ['package', lock.package.indexOf(root), 'version'])
456
+ return { project, lock: withoutRootVersion, version, rootSource: root.source }
457
+ }
458
+
459
+ function requireRegularFile(sha, path, cwd) {
460
+ requirePrepared(git(['ls-tree', sha, '--', path], cwd).startsWith('100644 blob '), `expected a regular metadata file: ${path}`)
461
+ }
462
+
463
+ function releaseSection(sha, version, cwd) {
464
+ requireRegularFile(sha, 'CHANGELOG.md', cwd)
465
+ const text = git(['show', `${sha}:CHANGELOG.md`], cwd)
466
+ const headings = [...text.matchAll(/^## \[([^\]\n]+)\][^\n]*(?:\n|$)/gm)]
467
+ const matches = headings.filter((heading) => heading[1] === version)
468
+ requirePrepared(matches.length === 1, `CHANGELOG.md must contain exactly one [${version}] section`)
469
+ const heading = matches[0]
470
+ const date = /^## \[[^\]]+\] - (\d{4}-\d{2}-\d{2})\s*$/.exec(heading[0].trim())?.[1]
471
+ requirePrepared(date && new Date(`${date}T00:00:00Z`).toISOString().slice(0, 10) === date, 'changelog heading needs a valid release date')
472
+ const next = headings.find((item) => item.index > heading.index)
473
+ const markdown = `${text.slice(heading.index, next?.index ?? text.length).trimEnd()}\n`
474
+ requirePrepared(markdown.slice(heading[0].length).trim().length > 0, 'changelog release section is empty')
475
+ return { markdown, date }
476
+ }
477
+
478
+ function validateRootIdentities(sha, files, version, cwd, npm = null) {
479
+ requirePrepared(files.length > 0, 'no supported root version files')
480
+ requirePrepared(!files.includes(DISTRIBUTION_MANIFEST), 'distribution-aware cuts need a dedicated metadata-only verifier; reuse is unsupported')
481
+ let packageName = null
482
+ if (!npm && files.includes('package.json')) packageName = JSON.parse(git(['show', `${sha}:package.json`], cwd)).name
483
+ const check = (actual, label) => {
484
+ if (npm) requireSafePreparedVersion(actual, label)
485
+ requirePrepared(actual === version, `${label} version ${actual} does not match ${version}`)
486
+ }
487
+ const python = preparedPythonAt(sha, files, cwd)
488
+ for (const path of files) {
489
+ requireRegularFile(sha, path, cwd)
490
+ if (npm && ['package.json', 'package-lock.json'].includes(path)) continue
491
+ if (path === 'pyproject.toml') {
492
+ for (const actual of python.project.versions) check(actual, path)
493
+ continue
494
+ }
495
+ if (path === 'uv.lock') {
496
+ check(python.version, 'uv.lock root')
497
+ continue
498
+ }
499
+ const text = git(['show', `${sha}:${path}`], cwd)
500
+ if (path === 'VERSION') check(text.trim(), path)
501
+ else {
502
+ const json = JSON.parse(text)
503
+ check(json.version, path === 'package-lock.json' ? 'root lock' : path)
504
+ if (path !== 'package-lock.json') continue
505
+ if (packageName !== null) requirePrepared(json.name === packageName, 'root lock name does not match package.json')
506
+ if (Object.hasOwn(json, 'packages')) {
507
+ requirePrepared(json.packages && typeof json.packages === 'object' && !Array.isArray(json.packages)
508
+ && Object.hasOwn(json.packages, '') && json.packages[''] && typeof json.packages[''] === 'object', 'root lock identity is missing')
509
+ check(json.packages[''].version, 'root lock packages[""]')
510
+ if (Object.hasOwn(json.packages[''], 'name') && packageName !== null) {
511
+ requirePrepared(json.packages[''].name === packageName, 'root lock packages[""] name does not match package.json')
512
+ }
513
+ }
514
+ }
515
+ }
516
+ return python
517
+ }
518
+
519
+ function identityWithoutVersion(text, path, python, npm = null) {
520
+ if (path === 'VERSION') return '<release-version>'
521
+ if (path === 'pyproject.toml') return python.project.withoutVersions
522
+ if (path === 'uv.lock') return python.lock
523
+ if (npm) {
524
+ const canonical = structuredClone(path === 'package.json' ? npm.canonical.pkg : npm.canonical.lock)
525
+ delete canonical[1].version
526
+ if (path === 'package-lock.json') delete canonical[1].packages[1][''][1].version
527
+ return canonical
528
+ }
529
+ const json = JSON.parse(text)
530
+ delete json.version
531
+ if (path === 'package-lock.json' && json.packages?.['']) delete json.packages[''].version
532
+ return json
533
+ }
534
+
535
+ function verifyMetadataOnlyCut(input, files, cwd, preTag = false) {
536
+ const { cutSha, version } = input
537
+ const parents = git(['rev-list', '--parents', '-n', '1', cutSha], cwd).trim().split(' ')
538
+ requirePrepared(parents.length === 2, 'metadata-only cut must have exactly one parent')
539
+ const parent = parents[1]
540
+ const previousFiles = preparedVersionFilesAt(parent, cwd)
541
+ requirePrepared(JSON.stringify(previousFiles) === JSON.stringify(files), 'metadata-only cut must preserve the root identity file set')
542
+ const previousPython = preparedPythonAt(parent, files, cwd)
543
+ const cutPython = preparedPythonAt(cutSha, files, cwd)
544
+ const previousNpm = preTag ? preparedNpmAt(parent, files, null, cwd) : null
545
+ const cutNpm = preTag ? preparedNpmAt(cutSha, files, version, cwd) : null
546
+ if (preTag) {
547
+ if (files.includes('VERSION')) requireSafePreparedVersion(git(['show', `${parent}:VERSION`], cwd).trim(), 'VERSION')
548
+ for (const actual of previousPython?.project.versions ?? []) requireSafePreparedVersion(actual, 'pyproject.toml')
549
+ if (previousPython?.version) requireSafePreparedVersion(previousPython.version, 'uv.lock root')
550
+ }
551
+ const previousVersion = previousNpm ? previousNpm.pkg.version : files.includes('package.json')
552
+ ? versionFromGitFiles(parent, files, cwd)
553
+ : previousPython?.project.version ?? versionFromGitFiles(parent, files, cwd)
554
+ requirePrepared(previousVersion !== version, 'metadata-only cut did not prepare a new version')
555
+ const changed = git(['diff', '--no-renames', '--name-only', '-z', parent, cutSha], cwd).split('\0').filter(Boolean)
556
+ requirePrepared(changed.includes('CHANGELOG.md') && changed.every((path) => path === 'CHANGELOG.md' || files.includes(path)), 'original cut is not metadata-only')
557
+ for (const path of files) {
558
+ const structured = path === 'pyproject.toml' || path === 'uv.lock'
559
+ || (preTag && ['package.json', 'package-lock.json'].includes(path))
560
+ const oldText = structured ? null : git(['show', `${parent}:${path}`], cwd)
561
+ const newText = structured ? null : git(['show', `${cutSha}:${path}`], cwd)
562
+ const oldValue = identityWithoutVersion(oldText, path, previousPython, previousNpm)
563
+ const newValue = identityWithoutVersion(newText, path, cutPython, cutNpm)
564
+ requirePrepared(isDeepStrictEqual(oldValue, newValue), `original cut is not metadata-only: ${path} changes more than version identity`)
565
+ const oldMode = git(['ls-tree', parent, '--', path], cwd).split(' ')[0]
566
+ const newMode = git(['ls-tree', cutSha, '--', path], cwd).split(' ')[0]
567
+ requirePrepared(oldMode === '100644' && newMode === oldMode, `metadata-only cut requires regular root identity files: ${path}`)
568
+ }
569
+ }
570
+
571
+ function inspectRemoteTags(patterns, cwd) {
572
+ const result = spawnSync('git', ['ls-remote', '--tags', 'origin', ...patterns], {
573
+ cwd, encoding: 'utf8', timeout: 30_000, maxBuffer: 8 * 1024 * 1024,
574
+ env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
575
+ })
576
+ requirePrepared(result.status === 0, 'remote inspection failed; tag absence is unproven')
577
+ const refs = new Map()
578
+ for (const line of result.stdout.split('\n').filter(Boolean)) {
579
+ const match = /^([0-9a-f]{40})\t(refs\/tags\/[^\s]+)$/.exec(line)
580
+ requirePrepared(match && !refs.has(match[2]), 'remote inspection returned invalid or duplicate refs')
581
+ refs.set(match[2], match[1])
582
+ }
583
+ return refs
584
+ }
585
+
586
+ function planPreparedRelease({ sha, trunk, cloudRun, darkFlags, preparedMetadata: input, cwd, now }) {
587
+ validatePreparedInput(input)
588
+ const previous = input.previousAttempt
589
+ const preTag = input.schema === 'prepared-release-metadata/v2' && previous.kind === 'pre-tag'
590
+ requirePrepared(!preTag || cloudRun === false, 'pre-tag provenance requires cloudRun false')
591
+ requirePrepared(typeof sha === 'string' && FULL_SHA_RE.test(sha), '--sha must be the full frozen target commit SHA')
592
+ requirePrepared(trunk === 'main', 'trunk must be main')
593
+ requirePrepared(darkFlags === null, 'dark flags cannot rewrite prepared notes; verify reviewed notes at the cut stage')
594
+ const repository = canonicalRepository(cwd)
595
+ requirePrepared(repository.toLowerCase() === input.repository.toLowerCase(), `repository ${input.repository} does not match origin ${repository}`)
596
+ for (const commit of [sha, input.cutSha, input.mergeSha, ...(preTag ? [previous.sourceSha] : [])]) checkedCommit(commit, cwd)
597
+ requireAncestor(input.cutSha, input.mergeSha, 'cut to merge', cwd)
598
+ requireAncestor(input.mergeSha, sha, 'historical merge to target', cwd)
599
+ if (preTag) {
600
+ requireAncestor(input.mergeSha, previous.sourceSha, 'historical merge to previous source', cwd)
601
+ requireAncestor(previous.sourceSha, sha, 'previous source to target', cwd)
602
+ }
603
+ requirePrepared(git(['rev-parse', `${input.cutSha}^{tree}`], cwd) === git(['rev-parse', `${input.mergeSha}^{tree}`], cwd), 'cut and merge trees must be equal')
604
+ const files = preparedVersionFilesAt(input.cutSha, cwd)
605
+ const pythonIdentities = []
606
+ let preparedPackageName
607
+ for (const commit of [input.cutSha, ...(preTag ? [previous.sourceSha] : []), sha]) {
608
+ requirePrepared(JSON.stringify(preparedVersionFilesAt(commit, cwd)) === JSON.stringify(files), 'root version file set changed after preparation')
609
+ const npm = preTag ? preparedNpmAt(commit, files, input.version, cwd) : null
610
+ if (npm) {
611
+ preparedPackageName ??= npm.pkg.name
612
+ requirePrepared(npm.pkg.name === preparedPackageName, 'package.json name differs from prepared identity')
613
+ }
614
+ pythonIdentities.push(validateRootIdentities(commit, files, input.version, cwd, npm))
615
+ }
616
+ const cutPython = pythonIdentities[0]
617
+ const targetPython = pythonIdentities.at(-1)
618
+ if (cutPython) {
619
+ requirePrepared(targetPython.project.name === cutPython.project.name, 'pyproject.toml target project name differs from prepared identity')
620
+ requirePrepared(isDeepStrictEqual(targetPython.rootSource, cutPython.rootSource), 'uv.lock target root source mode differs from prepared identity')
621
+ }
622
+ verifyMetadataOnlyCut(input, files, cwd, preTag)
623
+ const original = releaseSection(input.cutSha, input.version, cwd)
624
+ const prepared = releaseSection(sha, input.version, cwd)
625
+ const tag = `v${input.version}`
626
+ const refs = inspectRemoteTags([`refs/tags/${tag}`, `refs/tags/${tag}^{}`, ...(preTag ? [] : [`refs/tags/${tag}-rc.*`])], cwd)
627
+ requirePrepared(!refs.has(`refs/tags/${tag}`) && !refs.has(`refs/tags/${tag}^{}`), 'final tag already exists remotely; version is spent')
628
+ let candidateTag = null
629
+ if (!preTag) {
630
+ const previousRef = `refs/tags/${previous.candidateTag}`
631
+ if (refs.has(previousRef)) {
632
+ requirePrepared((refs.get(`${previousRef}^{}`) || refs.get(previousRef)) === previous.candidateSha, 'previous candidate remote tag SHA does not match provenance')
633
+ }
634
+ const knownTags = [...refs.keys()].map((ref) => ref.slice('refs/tags/'.length))
635
+ knownTags.push(...git(['tag', '--list', `${tag}-rc.*`], cwd).trim().split('\n'), previous.candidateTag)
636
+ candidateTag = cloudRun ? nextReleaseCandidateTag(tag, knownTags) : null
637
+ }
638
+ if (candidateTag) {
639
+ requirePrepared(/^v\d+\.\d+\.\d+-rc\.[1-9]\d*$/.test(candidateTag)
640
+ && Number.isSafeInteger(Number(candidateTag.split('-rc.')[1])), 'RC ordinal is out of range')
641
+ const exact = inspectRemoteTags([`refs/tags/${tag}`, `refs/tags/${tag}^{}`, `refs/tags/${candidateTag}`, `refs/tags/${candidateTag}^{}`], cwd)
642
+ requirePrepared(exact.size === 0, 'final or proposed candidate tag already exists remotely; replan')
643
+ }
644
+ let receiptId
645
+ do { receiptId = randomUUID() } while (receiptId === previous.receiptId)
646
+ return {
647
+ trunk, sha, releaseSourceSha: sha, receiptId,
648
+ baselineTag: null, currentVersion: input.version, bump: 'none',
649
+ reason: 'reuse human-approved prepared metadata; no new version cut',
650
+ nextVersion: input.version, tag, candidateTag, versionFiles: files, changes: [],
651
+ date: prepared.date, changelogMarkdown: prepared.markdown,
652
+ ...(preTag ? { artifact: { kind: 'npm', packageName: preparedPackageName, version: input.version } } : {}),
653
+ metadata: {
654
+ mode: 'reuse-prepared', schema: input.schema, repository, metadataPr: input.metadataPr,
655
+ cutSha: input.cutSha, mergeSha: input.mergeSha,
656
+ originalSectionSha256: sha256(original.markdown), preparedSectionSha256: sha256(prepared.markdown),
657
+ previousAttempt: { ...previous },
658
+ remoteInspection: { remote: 'origin', finalTagAbsent: tag, candidateTagAbsent: candidateTag, inspectedAt: now.toISOString() },
659
+ },
660
+ }
661
+ }
662
+
663
+ function parseLog(stdout) {
664
+ return stdout
665
+ .split('\u001e')
666
+ .map((chunk) => chunk.trim())
667
+ .filter(Boolean)
668
+ .map((chunk) => {
669
+ const [subject, ...rest] = chunk.split('\n')
670
+ return classifySubject(subject, rest.join('\n'))
671
+ })
672
+ }
673
+
674
+ export function planRelease({
675
+ sha,
676
+ trunk = 'main',
677
+ strict = false,
678
+ cloudRun = false,
679
+ darkFlags = null,
680
+ preparedMetadata = null,
681
+ cwd = process.cwd(),
682
+ now = new Date(),
683
+ } = {}) {
684
+ if (!sha) {
685
+ const err = new Error(`usage: ${PLAN_USAGE}`)
686
+ err.exitCode = 2
687
+ throw err
688
+ }
689
+ if (preparedMetadata !== null) {
690
+ return planPreparedRelease({ sha, trunk, cloudRun, darkFlags, preparedMetadata, cwd, now })
691
+ }
692
+ git(['rev-parse', '--verify', sha], cwd)
693
+ const tags = git(['tag', '--merged', sha, '--list', 'v*'], cwd)
694
+ .split('\n')
695
+ .map((line) => line.trim())
696
+ .filter((tag) => parseSemver(tag))
697
+ const baseline = latestReleaseTag(tags)
698
+ const range = baseline ? `${baseline.tag}..${sha}` : sha
699
+ const logArgs = baseline
700
+ ? ['log', range, '--first-parent', '--format=%s%n%b%x1e']
701
+ : ['log', sha, '--first-parent', '-n', '50', '--format=%s%n%b%x1e']
702
+ const changes = parseLog(git(logArgs, cwd))
703
+ const files = versionFilesAt(sha, cwd)
704
+ const fileVersion = versionFromGitFiles(sha, files, cwd)
705
+ const current = parseSemver(fileVersion) || baseline?.version || { major: 0, minor: 0, patch: 0 }
706
+ const { bump, reason } = bumpFor(changes, current, { strict })
707
+ const next = nextVersion(current, bump)
708
+ const version = formatVersion(next)
709
+ const tag = `v${version}`
710
+ const candidateTag = cloudRun
711
+ ? nextReleaseCandidateTag(tag, git(['tag', '--list', `${tag}-rc.*`], cwd)
712
+ .split('\n')
713
+ .map((line) => line.trim())
714
+ .filter(Boolean))
715
+ : null
716
+ const date = now.toISOString().slice(0, 10)
717
+ return {
718
+ trunk,
719
+ sha,
720
+ baselineTag: baseline?.tag || null,
721
+ currentVersion: formatVersion(current),
722
+ bump,
723
+ reason,
724
+ nextVersion: version,
725
+ tag,
726
+ candidateTag,
727
+ versionFiles: files,
728
+ changes,
729
+ date,
730
+ changelogMarkdown: renderChangelog({ version, date, changes, darkFlags }),
731
+ ...(darkFlags ? { darkFlags } : {}),
732
+ }
733
+ }
734
+
735
+ // The `dark-flags.mjs list --json` output (or its `flags` array): [{ name, summary }].
736
+ function readDarkFlags(path) {
737
+ if (!path) {
738
+ const err = new Error('--dark-flags needs a file')
739
+ err.exitCode = 2
740
+ throw err
741
+ }
742
+ const parsed = JSON.parse(readFileSync(path, 'utf8'))
743
+ const flags = Array.isArray(parsed) ? parsed : parsed.flags
744
+ if (!Array.isArray(flags) || flags.some((flag) => !flag?.name || !flag?.summary)) {
745
+ const err = new Error('--dark-flags must list { name, summary } entries')
746
+ err.exitCode = 2
747
+ throw err
748
+ }
749
+ return flags.map(({ name, summary }) => ({ name, summary }))
750
+ }
751
+
752
+ function parseArgs(argv) {
753
+ const opts = { json: false, strict: false, cloudRun: false, trunk: 'main', sha: null, darkFlags: null, preparedMetadata: null }
754
+ for (let i = 0; i < argv.length; i += 1) {
755
+ const arg = argv[i]
756
+ if (arg === '--json') opts.json = true
757
+ else if (arg === '--strict') opts.strict = true
758
+ else if (arg === '--cloud-run') opts.cloudRun = true
759
+ else if (arg === '--sha') opts.sha = argv[++i]
760
+ else if (arg === '--trunk') opts.trunk = argv[++i]
761
+ else if (arg === '--dark-flags') opts.darkFlags = readDarkFlags(argv[++i])
762
+ else if (arg === '--prepared-metadata') {
763
+ const path = argv[++i]
764
+ if (!path || path.startsWith('--')) {
765
+ const err = new Error('--prepared-metadata needs a JSON file')
766
+ err.exitCode = 2
767
+ throw err
768
+ }
769
+ opts.preparedMetadata = JSON.parse(readFileSync(path, 'utf8'))
770
+ if (opts.preparedMetadata === null) throw new Error('--prepared-metadata must be an object')
771
+ }
772
+ else if (arg === '--help' || arg === '-h') opts.help = true
773
+ else {
774
+ const err = new Error(`unknown argument: ${arg}`)
775
+ err.exitCode = 2
776
+ throw err
777
+ }
778
+ }
779
+ if (opts.help) return opts
780
+ if (!opts.sha || !opts.trunk) {
781
+ const err = new Error(`usage: ${PLAN_USAGE}`)
782
+ err.exitCode = 2
783
+ throw err
784
+ }
785
+ return opts
786
+ }
787
+
788
+ export function main(argv = process.argv.slice(2)) {
789
+ const opts = parseArgs(argv)
790
+ if (opts.help) {
791
+ process.stdout.write(`${PLAN_USAGE}\n`)
792
+ return 0
793
+ }
794
+ const result = planRelease(opts)
795
+ process.stdout.write(opts.json ? `${JSON.stringify(result, null, 2)}\n` : `${result.tag}\n${result.changelogMarkdown}`)
796
+ return 0
797
+ }
798
+
799
+ if (isMainModule(import.meta.url)) {
800
+ try {
801
+ process.exitCode = main()
802
+ } catch (error) {
803
+ process.stderr.write(`${error.message}\n`)
804
+ process.exitCode = error.exitCode || 1
805
+ }
806
+ }