diff --git a/bun.lock b/bun.lock index 350f1aea..4207adf0 100644 --- a/bun.lock +++ b/bun.lock @@ -32,6 +32,7 @@ "@tanstack/react-router": "1.170.18", "@tanstack/react-start": "1.168.32", "@vercel/analytics": "2.0.1", + "@vercel/oidc": "^3.8.0", "@vercel/speed-insights": "2.0.0", "class-variance-authority": "0.7.1", "clawhub-schema": "workspace:0.0.2", @@ -67,6 +68,7 @@ "zod": "4.4.3", }, "devDependencies": { + "@edge-runtime/vm": "^5.0.0", "@faker-js/faker": "^10.5.0", "@playwright/test": "^1.61.1", "@react-email/ui": "^6.9.0", @@ -82,6 +84,7 @@ "@typescript/native": "npm:typescript@7.0.2", "@vitejs/plugin-react": "6.0.3", "@vitest/coverage-v8": "4.1.10", + "convex-test": "^0.0.54", "jsdom": "29.1.1", "nitro": "3.0.260610-beta", "only-allow": "1.2.2", @@ -252,6 +255,10 @@ "@csstools/css-tokenizer": ["@csstools/css-tokenizer@4.0.0", "", {}, "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA=="], + "@edge-runtime/primitives": ["@edge-runtime/primitives@6.0.0", "", {}, "sha512-FqoxaBT+prPBHBwE1WXS1ocnu/VLTQyZ6NMUBAdbP7N2hsFTTxMC/jMu2D/8GAlMQfxeuppcPuCUk/HO3fpIvA=="], + + "@edge-runtime/vm": ["@edge-runtime/vm@5.0.0", "", { "dependencies": { "@edge-runtime/primitives": "6.0.0" } }, "sha512-NKBGBSIKUG584qrS1tyxVpX/AKJKQw5HgjYEnPLC0QsTw79JrGn+qUr8CXFb955Iy7GUdiiUv1rJ6JBGvaKb6w=="], + "@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], "@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], @@ -990,6 +997,12 @@ "@vercel/analytics": ["@vercel/analytics@2.0.1", "", { "peerDependencies": { "@remix-run/react": "^2", "@sveltejs/kit": "^1 || ^2", "next": ">= 13", "nuxt": ">= 3", "react": "^18 || ^19 || ^19.0.0-rc", "svelte": ">= 4", "vue": "^3", "vue-router": "^4" }, "optionalPeers": ["@remix-run/react", "@sveltejs/kit", "next", "nuxt", "react", "svelte", "vue", "vue-router"] }, "sha512-MTQG6V9qQrt1tsDeF+2Uoo5aPjqbVPys1xvnIftXSJYG2SrwXRHnqEvVoYID7BTruDz4lCd2Z7rM1BdkUehk2g=="], + "@vercel/cli-config": ["@vercel/cli-config@0.2.0", "", { "dependencies": { "xdg-app-paths": "5", "zod": "4.1.11" } }, "sha512-fJRRRB7734BDuXZ89yBEaA2ncYhH7bWX30mk04W80J6VAfQc+4iB8lyzAdaGpFV3/vNlkt9VZt+/uoQoWX6UsQ=="], + + "@vercel/cli-exec": ["@vercel/cli-exec@1.0.0", "", { "dependencies": { "execa": "5.1.1" } }, "sha512-kQF8LGie/Hbdq9/psJxLE7owRTcqMQMhgybU04gCeR7cbQAr5t8OrjefDNColJv1QSSucFt4pLwRiARVmlOnug=="], + + "@vercel/oidc": ["@vercel/oidc@3.8.0", "", { "dependencies": { "@vercel/cli-config": "0.2.0", "@vercel/cli-exec": "1.0.0", "jose": "^5.9.6" } }, "sha512-r00laGW6Pv778RoR6M2NxX91ycSj+PBwVo+fOb9Bif+F0IyUKt25zrvBzfEzQpeAzbqOgPZyQibEWDdDFApd+A=="], + "@vercel/speed-insights": ["@vercel/speed-insights@2.0.0", "", { "peerDependencies": { "@sveltejs/kit": "^1 || ^2", "next": ">= 13", "nuxt": ">= 3", "react": "^18 || ^19 || ^19.0.0-rc", "svelte": ">= 4", "vue": "^3", "vue-router": "^4" }, "optionalPeers": ["@sveltejs/kit", "next", "nuxt", "react", "svelte", "vue", "vue-router"] }, "sha512-jwkNcrTeafWxjmWq4AHBaptSqZiJkYU5adLC9QBSqeim0GcqDMgN5Ievh8OG1rJ6W3A4l1oiP7qr9CWxGuzu3w=="], "@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.3", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg=="], @@ -1104,6 +1117,8 @@ "convex-helpers": ["convex-helpers@0.1.120", "", { "peerDependencies": { "@standard-schema/spec": "^1.0.0", "convex": "^1.32.0", "hono": "^4.0.5", "react": "^17.0.2 || ^18.0.0 || ^19.0.0", "typescript": "^5.5 || ^6.0.0", "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["@standard-schema/spec", "hono", "react", "typescript", "zod"], "bin": { "convex-helpers": "bin.cjs" } }, "sha512-FPqtmN/10uoxmmhVq0ViNi08AnmHKT/eU1R8+ohmPZoHfg8noZEZCltApY/ASDdhq92o0kLZETggzBvMfHJDLQ=="], + "convex-test": ["convex-test@0.0.54", "", { "peerDependencies": { "convex": "^1.32.0" } }, "sha512-C0v2SQcuxrELAJRNzE6fQ686XDPor7UFoC22jcoJXeCRqhLo8ZIMZ4jyUHoo1UdAL2enCb0AbiprCVpLDN8u9Q=="], + "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], "cookie-es": ["cookie-es@3.1.1", "", {}, "sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg=="], @@ -1112,6 +1127,8 @@ "cose-base": ["cose-base@1.0.3", "", { "dependencies": { "layout-base": "^1.0.0" } }, "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg=="], + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + "crossws": ["crossws@0.4.9", "", { "peerDependencies": { "srvx": ">=0.11.5" }, "optionalPeers": ["srvx"] }, "sha512-iWx+1OMSG2aOHpjyf9AESOzkwsVdS49cXM9dVrI2PDhxU5l2RIWE/KG56gk4BbAnsMoycvniJ9OnOxO9LRzHVA=="], "css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="], @@ -1262,6 +1279,8 @@ "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + "execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="], + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], "exsolve": ["exsolve@1.1.0", "", {}, "sha512-D+42+T12DdIlJM3uepa55qGiL3sYdLBOxIl2ifQCzCHz4c7eiolaHsi3BIqEr7JxBzxv2pYZQX9kw16ziMcEmw=="], @@ -1294,6 +1313,8 @@ "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="], + "get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], + "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], @@ -1344,6 +1365,8 @@ "httpxy": ["httpxy@0.5.5", "", {}, "sha512-uDjmnPyp1q4Sgzf3w+J/Fc6UqcCEj0x4Wjp7OqK5dGhNeDgpyrAmnS6ey8QWrX3SWDon2DMKf9sBa5X9+CVyMA=="], + "human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="], + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], "ignore": ["ignore@7.0.6", "", {}, "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw=="], @@ -1370,10 +1393,14 @@ "is-potential-custom-element-name": ["is-potential-custom-element-name@1.0.1", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="], + "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], + "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], "isbot": ["isbot@5.1.41", "", {}, "sha512-9WFV/Vhh0FEj6CQ7MoHweEL9/vLKPjeoD2I2htbAjX7kbW7VJs3OCpWOVyd+JraNTWVU6/DRx2MZy2KaUNXHcg=="], + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="], "istanbul-lib-report": ["istanbul-lib-report@3.0.1", "", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="], @@ -1492,6 +1519,8 @@ "mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="], + "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], + "mermaid": ["mermaid@11.16.0", "", { "dependencies": { "@braintree/sanitize-url": "^7.1.2", "@iconify/utils": "^3.0.2", "@mermaid-js/parser": "^1.2.0", "@types/d3": "^7.4.3", "@upsetjs/venn.js": "^2.0.0", "cytoscape": "^3.33.3", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.14", "dayjs": "^1.11.20", "dompurify": "^3.3.3", "es-toolkit": "^1.45.1", "katex": "^0.16.45", "khroma": "^2.1.0", "marked": "^16.3.0", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" } }, "sha512-Zvm3kbstgdpvIJPPItlL7fppIZ3kibvc1oZIGxdvk9t6UFz6flv+Jw7FtRGKwfcI8OckmH04LqG6LlS6X4B1pA=="], "micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], @@ -1556,6 +1585,8 @@ "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], + "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], + "mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], "minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], @@ -1582,6 +1613,8 @@ "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], + "npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="], + "nypm": ["nypm@0.6.6", "", { "dependencies": { "citty": "^0.2.2", "pathe": "^2.0.3", "tinyexec": "^1.1.1" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-vRyr0r4cbBapw07Xw8xrj9Teq3o7MUD35rSaTcanDbW+aK2XHDgJFiU6ZTj2GBw7Q12ysdsyFss+Vdz4hQ0Y6Q=="], "oauth4webapi": ["oauth4webapi@3.8.6", "", {}, "sha512-iwemM91xz8nryHti2yTmg5fhyEMVOkOXwHNqbvcATjyajb5oQxCQzrNOA6uElRHuMhQQTKUyFKV9y/CNyg25BQ=="], @@ -1598,7 +1631,7 @@ "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="], - "onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], + "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], "oniguruma-parser": ["oniguruma-parser@0.12.2", "", {}, "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw=="], @@ -1608,6 +1641,8 @@ "ora": ["ora@9.4.1", "", { "dependencies": { "chalk": "^5.6.2", "cli-cursor": "^5.0.0", "cli-spinners": "^3.2.0", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.1.0", "log-symbols": "^7.0.1", "stdin-discarder": "^0.3.2", "string-width": "^8.1.0" } }, "sha512-6VlU9MLXbjVQD04AZCMX28hVtA5bUoadvUqO76MUCVA0ilwJbMiHsITRPfyVm6p/BC0Av/BXMujx39WCe1LEqw=="], + "os-paths": ["os-paths@4.4.0", "", {}, "sha512-wrAwOeXp1RRMFfQY8Sy7VaGVmPocaLwSFOYCGKSyo8qmJ+/yaafCl5BCA1IQZWqFSRBrKDYFeR9d/VyQzfH/jg=="], + "oxc-parser": ["oxc-parser@0.120.0", "", { "dependencies": { "@oxc-project/types": "^0.120.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.120.0", "@oxc-parser/binding-android-arm64": "0.120.0", "@oxc-parser/binding-darwin-arm64": "0.120.0", "@oxc-parser/binding-darwin-x64": "0.120.0", "@oxc-parser/binding-freebsd-x64": "0.120.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.120.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.120.0", "@oxc-parser/binding-linux-arm64-gnu": "0.120.0", "@oxc-parser/binding-linux-arm64-musl": "0.120.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.120.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.120.0", "@oxc-parser/binding-linux-riscv64-musl": "0.120.0", "@oxc-parser/binding-linux-s390x-gnu": "0.120.0", "@oxc-parser/binding-linux-x64-gnu": "0.120.0", "@oxc-parser/binding-linux-x64-musl": "0.120.0", "@oxc-parser/binding-openharmony-arm64": "0.120.0", "@oxc-parser/binding-wasm32-wasi": "0.120.0", "@oxc-parser/binding-win32-arm64-msvc": "0.120.0", "@oxc-parser/binding-win32-ia32-msvc": "0.120.0", "@oxc-parser/binding-win32-x64-msvc": "0.120.0" } }, "sha512-WyPWZlcIm+Fkte63FGfgFB8mAAk33aH9h5N9lphXVOHSXEBFFsmYdOBedVKly363aWABjZdaj/m9lBfEY4wt+w=="], "oxfmt": ["oxfmt@0.59.0", "", { "dependencies": { "tinypool": "2.1.0" }, "optionalDependencies": { "@oxfmt/binding-android-arm-eabi": "0.59.0", "@oxfmt/binding-android-arm64": "0.59.0", "@oxfmt/binding-darwin-arm64": "0.59.0", "@oxfmt/binding-darwin-x64": "0.59.0", "@oxfmt/binding-freebsd-x64": "0.59.0", "@oxfmt/binding-linux-arm-gnueabihf": "0.59.0", "@oxfmt/binding-linux-arm-musleabihf": "0.59.0", "@oxfmt/binding-linux-arm64-gnu": "0.59.0", "@oxfmt/binding-linux-arm64-musl": "0.59.0", "@oxfmt/binding-linux-ppc64-gnu": "0.59.0", "@oxfmt/binding-linux-riscv64-gnu": "0.59.0", "@oxfmt/binding-linux-riscv64-musl": "0.59.0", "@oxfmt/binding-linux-s390x-gnu": "0.59.0", "@oxfmt/binding-linux-x64-gnu": "0.59.0", "@oxfmt/binding-linux-x64-musl": "0.59.0", "@oxfmt/binding-openharmony-arm64": "0.59.0", "@oxfmt/binding-win32-arm64-msvc": "0.59.0", "@oxfmt/binding-win32-ia32-msvc": "0.59.0", "@oxfmt/binding-win32-x64-msvc": "0.59.0" }, "peerDependencies": { "svelte": "^5.0.0", "vite-plus": "*" }, "optionalPeers": ["svelte", "vite-plus"], "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-Xqk6cPZS1yMvVa7OAuenaDZUsgMDutvvbZ9/L5gSvAfW64+WN4HVhgipLj5rVERbYQt8fLs9TopyZ1rU1XEG/w=="], @@ -1628,6 +1663,8 @@ "path-data-parser": ["path-data-parser@0.1.0", "", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="], + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="], "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], @@ -1756,13 +1793,17 @@ "sharp": ["sharp@0.35.3", "", { "dependencies": { "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", "semver": "^7.8.5" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.35.3", "@img/sharp-darwin-x64": "0.35.3", "@img/sharp-freebsd-wasm32": "0.35.3", "@img/sharp-libvips-darwin-arm64": "1.3.2", "@img/sharp-libvips-darwin-x64": "1.3.2", "@img/sharp-libvips-linux-arm": "1.3.2", "@img/sharp-libvips-linux-arm64": "1.3.2", "@img/sharp-libvips-linux-ppc64": "1.3.2", "@img/sharp-libvips-linux-riscv64": "1.3.2", "@img/sharp-libvips-linux-s390x": "1.3.2", "@img/sharp-libvips-linux-x64": "1.3.2", "@img/sharp-libvips-linuxmusl-arm64": "1.3.2", "@img/sharp-libvips-linuxmusl-x64": "1.3.2", "@img/sharp-linux-arm": "0.35.3", "@img/sharp-linux-arm64": "0.35.3", "@img/sharp-linux-ppc64": "0.35.3", "@img/sharp-linux-riscv64": "0.35.3", "@img/sharp-linux-s390x": "0.35.3", "@img/sharp-linux-x64": "0.35.3", "@img/sharp-linuxmusl-arm64": "0.35.3", "@img/sharp-linuxmusl-x64": "0.35.3", "@img/sharp-webcontainers-wasm32": "0.35.3", "@img/sharp-win32-arm64": "0.35.3", "@img/sharp-win32-ia32": "0.35.3", "@img/sharp-win32-x64": "0.35.3" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q=="], + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + "shell-quote": ["shell-quote@1.10.0", "", {}, "sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA=="], "shiki": ["shiki@4.3.1", "", { "dependencies": { "@shikijs/core": "4.3.1", "@shikijs/engine-javascript": "4.3.1", "@shikijs/engine-oniguruma": "4.3.1", "@shikijs/langs": "4.3.1", "@shikijs/themes": "4.3.1", "@shikijs/types": "4.3.1", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-oR+qDVi2OjX1tmDpyv+3KviX01KzO6Af+0NNnKnsp9491UEGz2YpxTuJboS/6VhYpTdqzmuJBuiTlrAWWJAssw=="], "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], - "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], @@ -1804,6 +1845,8 @@ "strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="], + "strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="], + "stubborn-fs": ["stubborn-fs@2.0.0", "", { "dependencies": { "stubborn-utils": "^1.0.1" } }, "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA=="], "stubborn-utils": ["stubborn-utils@1.0.2", "", {}, "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg=="], @@ -1928,12 +1971,18 @@ "when-exit": ["when-exit@2.1.5", "", {}, "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg=="], + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + "which-pm-runs": ["which-pm-runs@1.1.0", "", {}, "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA=="], "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], "ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], + "xdg-app-paths": ["xdg-app-paths@5.5.1", "", { "dependencies": { "os-paths": "^4.0.1", "xdg-portable": "^7.2.0" } }, "sha512-hI3flOB4PLZIy5prbtTpirobtPE2ZtZ52szO+2mM9Efp6ErM398La+C1lIpNWDfNoQk+6Lsi6nMcCwVB7pxeMQ=="], + + "xdg-portable": ["xdg-portable@7.3.0", "", { "dependencies": { "os-paths": "^4.0.1" } }, "sha512-sqMMuL1rc0FmMBOzCpd0yuy9trqF2yTTVe+E9ogwCSWQCdDEtQUwrZPT6AxqtsFGRNxycgncbP/xmOOSPw5ZUw=="], + "xml-name-validator": ["xml-name-validator@5.0.0", "", {}, "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg=="], "xmlbuilder2": ["xmlbuilder2@4.0.3", "", { "dependencies": { "@oozcitak/dom": "^2.0.2", "@oozcitak/infra": "^2.0.2", "@oozcitak/util": "^10.0.0", "js-yaml": "^4.1.1" } }, "sha512-bx8Q1STctnNaaDymWnkfQLKofs0mGNN7rLLapJlGuV3VlvegD7Ls4ggMjE3aUSWItCCzU0PEv45lI87iSigiCA=="], @@ -2010,6 +2059,10 @@ "@types/ws/@types/node": ["@types/node@25.9.2", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw=="], + "@vercel/cli-config/zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="], + + "@vercel/oidc/jose": ["jose@5.10.0", "", {}, "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg=="], + "accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], "ast-v8-to-istanbul/js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="], @@ -2064,6 +2117,10 @@ "react-email/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + "restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], + + "restore-cursor/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + "rolldown/@oxc-project/types": ["@oxc-project/types@0.134.0", "", {}, "sha512-T0xuRRKrQFmocH8y+jGfpmSkGcheaJExY9lEihmR1Gm2aH+75B8CzgU2rABRQSzzDxLjZ15Sc0bRVLj5lVeNXQ=="], "strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], diff --git a/convex/_generated/api.d.ts b/convex/_generated/api.d.ts index 7d01d513..741f28ec 100644 --- a/convex/_generated/api.d.ts +++ b/convex/_generated/api.d.ts @@ -41,6 +41,7 @@ import type * as httpApiV1_packagesV1 from "../httpApiV1/packagesV1.js"; import type * as httpApiV1_promotionsV1 from "../httpApiV1/promotionsV1.js"; import type * as httpApiV1_publishersV1 from "../httpApiV1/publishersV1.js"; import type * as httpApiV1_shared from "../httpApiV1/shared.js"; +import type * as httpApiV1_skillsShCatalogV1 from "../httpApiV1/skillsShCatalogV1.js"; import type * as httpApiV1_skillsV1 from "../httpApiV1/skillsV1.js"; import type * as httpApiV1_starsV1 from "../httpApiV1/starsV1.js"; import type * as httpApiV1_transfersV1 from "../httpApiV1/transfersV1.js"; @@ -129,6 +130,8 @@ import type * as lib_skillTags from "../lib/skillTags.js"; import type * as lib_skillZip from "../lib/skillZip.js"; import type * as lib_skills_index from "../lib/skills/index.js"; import type * as lib_skills_slugResolution from "../lib/skills/slugResolution.js"; +import type * as lib_skillsShCatalogEnvironment from "../lib/skillsShCatalogEnvironment.js"; +import type * as lib_skillsShCatalogFixtures from "../lib/skillsShCatalogFixtures.js"; import type * as lib_staticPublishScan from "../lib/staticPublishScan.js"; import type * as lib_testSeed from "../lib/testSeed.js"; import type * as lib_tokens from "../lib/tokens.js"; @@ -163,6 +166,7 @@ import type * as skillCards from "../skillCards.js"; import type * as skillStatEvents from "../skillStatEvents.js"; import type * as skillTransfers from "../skillTransfers.js"; import type * as skills from "../skills.js"; +import type * as skillsShCatalog from "../skillsShCatalog.js"; import type * as stars from "../stars.js"; import type * as statsMaintenance from "../statsMaintenance.js"; import type * as telemetry from "../telemetry.js"; @@ -212,6 +216,7 @@ declare const fullApi: ApiFromModules<{ "httpApiV1/promotionsV1": typeof httpApiV1_promotionsV1; "httpApiV1/publishersV1": typeof httpApiV1_publishersV1; "httpApiV1/shared": typeof httpApiV1_shared; + "httpApiV1/skillsShCatalogV1": typeof httpApiV1_skillsShCatalogV1; "httpApiV1/skillsV1": typeof httpApiV1_skillsV1; "httpApiV1/starsV1": typeof httpApiV1_starsV1; "httpApiV1/transfersV1": typeof httpApiV1_transfersV1; @@ -300,6 +305,8 @@ declare const fullApi: ApiFromModules<{ "lib/skillZip": typeof lib_skillZip; "lib/skills/index": typeof lib_skills_index; "lib/skills/slugResolution": typeof lib_skills_slugResolution; + "lib/skillsShCatalogEnvironment": typeof lib_skillsShCatalogEnvironment; + "lib/skillsShCatalogFixtures": typeof lib_skillsShCatalogFixtures; "lib/staticPublishScan": typeof lib_staticPublishScan; "lib/testSeed": typeof lib_testSeed; "lib/tokens": typeof lib_tokens; @@ -334,6 +341,7 @@ declare const fullApi: ApiFromModules<{ skillStatEvents: typeof skillStatEvents; skillTransfers: typeof skillTransfers; skills: typeof skills; + skillsShCatalog: typeof skillsShCatalog; stars: typeof stars; statsMaintenance: typeof statsMaintenance; telemetry: typeof telemetry; diff --git a/convex/fixtures/skills-sh-500-2026-07-21.json b/convex/fixtures/skills-sh-500-2026-07-21.json new file mode 100644 index 00000000..7c9ef29c --- /dev/null +++ b/convex/fixtures/skills-sh-500-2026-07-21.json @@ -0,0 +1,6028 @@ +{ + "snapshotId": "skills-sh-500-2026-07-21", + "capturedAt": "2026-07-21T02:35:28.482Z", + "source": { + "project": "openclaw-foundation/clawhub", + "endpoint": "https://skills.sh/api/v1/skills", + "authentication": "vercel-oidc", + "requestedRows": 500 + }, + "selection": { + "rows": 500, + "nvidiaRows": 10, + "requiredCollisionIds": [ + "anthropics/skills/frontend-design", + "anthropics/claude-code/frontend-design" + ] + }, + "captureMetrics": { + "runtimeMs": 32317, + "skillsShFetches": 528, + "githubOwnerFetches": 69, + "listFetches": 2, + "searchFetches": 1, + "detailFetches": 525 + }, + "rows": [ + { + "externalId": "anthropics/skills/frontend-design", + "githubOwnerId": 76263028, + "owner": "anthropics", + "repo": "skills", + "slug": "frontend-design", + "displayName": "frontend-design", + "sourceUrl": "https://www.skills.sh/anthropics/skills/frontend-design", + "githubRepoUrl": "https://github.com/anthropics/skills", + "sourceContentHash": "665c585455decbcb02116bc928adf5148b9cd0f67b7eb4908c8c0d647c00f68a", + "installs": 686174 + }, + { + "externalId": "anthropics/claude-code/frontend-design", + "githubOwnerId": 76263028, + "owner": "anthropics", + "repo": "claude-code", + "slug": "frontend-design", + "displayName": "frontend-design", + "sourceUrl": "https://www.skills.sh/anthropics/claude-code/frontend-design", + "githubRepoUrl": "https://github.com/anthropics/claude-code", + "sourceContentHash": "bb4b5dd42d615dc6cef71bf709a483612e6b683e62b8121b398285e220cae004", + "installs": 51632 + }, + { + "externalId": "nvidia/skills/tao-setup-nvidia-gpu-host", + "githubOwnerId": 1728152, + "owner": "nvidia", + "repo": "skills", + "slug": "tao-setup-nvidia-gpu-host", + "displayName": "tao-setup-nvidia-gpu-host", + "sourceUrl": "https://www.skills.sh/nvidia/skills/tao-setup-nvidia-gpu-host", + "githubRepoUrl": "https://github.com/nvidia/skills", + "sourceContentHash": "6d08a6632299a282b80c96eeec604f88bfce79dbd3d73735e5751d9b1f1315cf", + "installs": 1400 + }, + { + "externalId": "nvidia/skills/rag-blueprint", + "githubOwnerId": 1728152, + "owner": "nvidia", + "repo": "skills", + "slug": "rag-blueprint", + "displayName": "rag-blueprint", + "sourceUrl": "https://www.skills.sh/nvidia/skills/rag-blueprint", + "githubRepoUrl": "https://github.com/nvidia/skills", + "sourceContentHash": "8b0a7a86c995dc9b18b3bb6a76e478e0bd147acf4c389d32ca403ed9ac447a14", + "installs": 2193 + }, + { + "externalId": "nvidia/skills/aiq-research", + "githubOwnerId": 1728152, + "owner": "nvidia", + "repo": "skills", + "slug": "aiq-research", + "displayName": "aiq-research", + "sourceUrl": "https://www.skills.sh/nvidia/skills/aiq-research", + "githubRepoUrl": "https://github.com/nvidia/skills", + "sourceContentHash": "7bd4892e689c3041f6cd100729693da0d7d151469ca67f51239b28b5f3af0531", + "installs": 2131 + }, + { + "externalId": "nvidia/skills/accelerated-computing-cudf", + "githubOwnerId": 1728152, + "owner": "nvidia", + "repo": "skills", + "slug": "accelerated-computing-cudf", + "displayName": "accelerated-computing-cudf", + "sourceUrl": "https://www.skills.sh/nvidia/skills/accelerated-computing-cudf", + "githubRepoUrl": "https://github.com/nvidia/skills", + "sourceContentHash": "1650ee554c2162449a96bae2252d74b2fac56e13031f2446083591b0c54cc46e", + "installs": 2121 + }, + { + "externalId": "nvidia/skills/cuopt-developer", + "githubOwnerId": 1728152, + "owner": "nvidia", + "repo": "skills", + "slug": "cuopt-developer", + "displayName": "cuopt-developer", + "sourceUrl": "https://www.skills.sh/nvidia/skills/cuopt-developer", + "githubRepoUrl": "https://github.com/nvidia/skills", + "sourceContentHash": "75aeea8aa31c74965acadc22a16cd50b03176734eaf9b2d0df0e32fc1dda84af", + "installs": 2069 + }, + { + "externalId": "nvidia/skills/cuopt-install", + "githubOwnerId": 1728152, + "owner": "nvidia", + "repo": "skills", + "slug": "cuopt-install", + "displayName": "cuopt-install", + "sourceUrl": "https://www.skills.sh/nvidia/skills/cuopt-install", + "githubRepoUrl": "https://github.com/nvidia/skills", + "sourceContentHash": "1d76c994566db1b610a7224e9df0025ebd6c1d3587806a85d1929d5b1f4a9d27", + "installs": 1996 + }, + { + "externalId": "nvidia/skills/aiq-deploy", + "githubOwnerId": 1728152, + "owner": "nvidia", + "repo": "skills", + "slug": "aiq-deploy", + "displayName": "aiq-deploy", + "sourceUrl": "https://www.skills.sh/nvidia/skills/aiq-deploy", + "githubRepoUrl": "https://github.com/nvidia/skills", + "sourceContentHash": "c64924da02424872d0358a41595a5c7f13ec85363185dbe11c08446185f1d1c9", + "installs": 1993 + }, + { + "externalId": "nvidia/skills/cudaq-guide", + "githubOwnerId": 1728152, + "owner": "nvidia", + "repo": "skills", + "slug": "cudaq-guide", + "displayName": "cudaq-guide", + "sourceUrl": "https://www.skills.sh/nvidia/skills/cudaq-guide", + "githubRepoUrl": "https://github.com/nvidia/skills", + "sourceContentHash": "eef2deedfee7d6dc2eca8026a3703284a08265cd2c2de24ef7a0d8428bb462b0", + "installs": 1939 + }, + { + "externalId": "nvidia/skills/deepstream-dev", + "githubOwnerId": 1728152, + "owner": "nvidia", + "repo": "skills", + "slug": "deepstream-dev", + "displayName": "deepstream-dev", + "sourceUrl": "https://www.skills.sh/nvidia/skills/deepstream-dev", + "githubRepoUrl": "https://github.com/nvidia/skills", + "sourceContentHash": "a2ed9d3faedf7ee991c4b48709ae2d715381f3156de3bf9339376f17e81b44b3", + "installs": 1890 + }, + { + "externalId": "nvidia/skills/deepstream-import-vision-model", + "githubOwnerId": 1728152, + "owner": "nvidia", + "repo": "skills", + "slug": "deepstream-import-vision-model", + "displayName": "deepstream-import-vision-model", + "sourceUrl": "https://www.skills.sh/nvidia/skills/deepstream-import-vision-model", + "githubRepoUrl": "https://github.com/nvidia/skills", + "sourceContentHash": "aad8e09290afad0b14814884efe4135678aea88e2a8664db8adf75557648a3cf", + "installs": 1850 + }, + { + "externalId": "vercel-labs/skills/find-skills", + "githubOwnerId": 108547162, + "owner": "vercel-labs", + "repo": "skills", + "slug": "find-skills", + "displayName": "find-skills", + "sourceUrl": "https://www.skills.sh/vercel-labs/skills/find-skills", + "githubRepoUrl": "https://github.com/vercel-labs/skills", + "sourceContentHash": "b146008599c31057cef1c145774cea5d5afb30e8f43fa802e47a4b461419aaaf", + "installs": 2595635 + }, + { + "externalId": "mattpocock/skills/grill-me", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "grill-me", + "displayName": "grill-me", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/grill-me", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "38451dd2683eb309b4176ac5b2648ff6f86e320145baed1ac6c2e34367c8cae6", + "installs": 609953 + }, + { + "externalId": "vercel-labs/agent-skills/vercel-react-best-practices", + "githubOwnerId": 108547162, + "owner": "vercel-labs", + "repo": "agent-skills", + "slug": "vercel-react-best-practices", + "displayName": "vercel-react-best-practices", + "sourceUrl": "https://www.skills.sh/vercel-labs/agent-skills/vercel-react-best-practices", + "githubRepoUrl": "https://github.com/vercel-labs/agent-skills", + "sourceContentHash": "ca7b0c0c6e5f2750043f7f0cd72d16ac4e2abc48f9b5500d047a4b77a2506212", + "installs": 566336 + }, + { + "externalId": "vercel-labs/agent-browser/agent-browser", + "githubOwnerId": 108547162, + "owner": "vercel-labs", + "repo": "agent-browser", + "slug": "agent-browser", + "displayName": "agent-browser", + "sourceUrl": "https://www.skills.sh/vercel-labs/agent-browser/agent-browser", + "githubRepoUrl": "https://github.com/vercel-labs/agent-browser", + "sourceContentHash": "ecc7641aea05f85ca3b11e7759d32aaf52fe05946ab4b63739c7bf78a41237a2", + "installs": 563353 + }, + { + "externalId": "mattpocock/skills/grill-with-docs", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "grill-with-docs", + "displayName": "grill-with-docs", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/grill-with-docs", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "8683d99a2d347d358662c4d4744affd38212913b85a69158118b1f6735e5da7e", + "installs": 516724 + }, + { + "externalId": "mattpocock/skills/improve-codebase-architecture", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "improve-codebase-architecture", + "displayName": "improve-codebase-architecture", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/improve-codebase-architecture", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "2823acbf7eb6674817713d21f449418c03dc4dbe33661760cff64255d40f8532", + "installs": 503246 + }, + { + "externalId": "mattpocock/skills/tdd", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "tdd", + "displayName": "tdd", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/tdd", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "9fc32d31fbec74e82d6b816e89f777f6d52953ba74d5a183cab85953ad73b160", + "installs": 483252 + }, + { + "externalId": "vercel-labs/agent-skills/web-design-guidelines", + "githubOwnerId": 108547162, + "owner": "vercel-labs", + "repo": "agent-skills", + "slug": "web-design-guidelines", + "displayName": "web-design-guidelines", + "sourceUrl": "https://www.skills.sh/vercel-labs/agent-skills/web-design-guidelines", + "githubRepoUrl": "https://github.com/vercel-labs/agent-skills", + "sourceContentHash": "f3bc47f890f42a44db1007ab390709ec368e4b8c089baee6b0007182236ac474", + "installs": 477627 + }, + { + "externalId": "microsoft/azure-skills/microsoft-foundry", + "githubOwnerId": 6154722, + "owner": "microsoft", + "repo": "azure-skills", + "slug": "microsoft-foundry", + "displayName": "microsoft-foundry", + "sourceUrl": "https://www.skills.sh/microsoft/azure-skills/microsoft-foundry", + "githubRepoUrl": "https://github.com/microsoft/azure-skills", + "sourceContentHash": "56818fa5fde43d2cb50a6d0ac18da59134c110e071445d279a9b065f16fe2e9f", + "installs": 468569 + }, + { + "externalId": "microsoft/azure-skills/azure-ai", + "githubOwnerId": 6154722, + "owner": "microsoft", + "repo": "azure-skills", + "slug": "azure-ai", + "displayName": "azure-ai", + "sourceUrl": "https://www.skills.sh/microsoft/azure-skills/azure-ai", + "githubRepoUrl": "https://github.com/microsoft/azure-skills", + "sourceContentHash": "907110c69e05643bbc4b61c0296e66eb9302f31598095ad9782f8e4d37290681", + "installs": 465018 + }, + { + "externalId": "microsoft/azure-skills/azure-deploy", + "githubOwnerId": 6154722, + "owner": "microsoft", + "repo": "azure-skills", + "slug": "azure-deploy", + "displayName": "azure-deploy", + "sourceUrl": "https://www.skills.sh/microsoft/azure-skills/azure-deploy", + "githubRepoUrl": "https://github.com/microsoft/azure-skills", + "sourceContentHash": "95c20b4524293f7ee36ef988051a0347734cd16a9624a4d71cfb56bb2f729bda", + "installs": 464725 + }, + { + "externalId": "microsoft/azure-skills/azure-diagnostics", + "githubOwnerId": 6154722, + "owner": "microsoft", + "repo": "azure-skills", + "slug": "azure-diagnostics", + "displayName": "azure-diagnostics", + "sourceUrl": "https://www.skills.sh/microsoft/azure-skills/azure-diagnostics", + "githubRepoUrl": "https://github.com/microsoft/azure-skills", + "sourceContentHash": "22050494a3242120f1fd7fa6c924f4c73bd7996aa8504d13daca9d95f00fe7be", + "installs": 464577 + }, + { + "externalId": "microsoft/azure-skills/azure-prepare", + "githubOwnerId": 6154722, + "owner": "microsoft", + "repo": "azure-skills", + "slug": "azure-prepare", + "displayName": "azure-prepare", + "sourceUrl": "https://www.skills.sh/microsoft/azure-skills/azure-prepare", + "githubRepoUrl": "https://github.com/microsoft/azure-skills", + "sourceContentHash": "335d39bdc023f2b8c154e7f1565651389ee8534424f2391a2b30e7574d128b31", + "installs": 464403 + }, + { + "externalId": "microsoft/azure-skills/azure-storage", + "githubOwnerId": 6154722, + "owner": "microsoft", + "repo": "azure-skills", + "slug": "azure-storage", + "displayName": "azure-storage", + "sourceUrl": "https://www.skills.sh/microsoft/azure-skills/azure-storage", + "githubRepoUrl": "https://github.com/microsoft/azure-skills", + "sourceContentHash": "385976ec4ac01e1a2805ce04a5985ef1c1fd3095be5fc24c820ccbf8338bcfa4", + "installs": 464091 + }, + { + "externalId": "microsoft/azure-skills/azure-validate", + "githubOwnerId": 6154722, + "owner": "microsoft", + "repo": "azure-skills", + "slug": "azure-validate", + "displayName": "azure-validate", + "sourceUrl": "https://www.skills.sh/microsoft/azure-skills/azure-validate", + "githubRepoUrl": "https://github.com/microsoft/azure-skills", + "sourceContentHash": "5d15e406aacdbf00e926216daf6fb6eff518e3b613010ca5e65e1bb3fa722691", + "installs": 463756 + }, + { + "externalId": "microsoft/azure-skills/entra-app-registration", + "githubOwnerId": 6154722, + "owner": "microsoft", + "repo": "azure-skills", + "slug": "entra-app-registration", + "displayName": "entra-app-registration", + "sourceUrl": "https://www.skills.sh/microsoft/azure-skills/entra-app-registration", + "githubRepoUrl": "https://github.com/microsoft/azure-skills", + "sourceContentHash": "92afd47494fa00ced3c89ba058da9e52f3ab7c671ef914894d8e6a31b3fe0cae", + "installs": 463645 + }, + { + "externalId": "microsoft/azure-skills/appinsights-instrumentation", + "githubOwnerId": 6154722, + "owner": "microsoft", + "repo": "azure-skills", + "slug": "appinsights-instrumentation", + "displayName": "appinsights-instrumentation", + "sourceUrl": "https://www.skills.sh/microsoft/azure-skills/appinsights-instrumentation", + "githubRepoUrl": "https://github.com/microsoft/azure-skills", + "sourceContentHash": "e644ffb280970010da937e77ccfc9b85e05c0b0a73baa4c39fb2de999bc45cf4", + "installs": 463563 + }, + { + "externalId": "microsoft/azure-skills/azure-compliance", + "githubOwnerId": 6154722, + "owner": "microsoft", + "repo": "azure-skills", + "slug": "azure-compliance", + "displayName": "azure-compliance", + "sourceUrl": "https://www.skills.sh/microsoft/azure-skills/azure-compliance", + "githubRepoUrl": "https://github.com/microsoft/azure-skills", + "sourceContentHash": "9c6450ee18beffd533c708cb95a78010755f3a514aa7a115a64586cfa9b02943", + "installs": 463482 + }, + { + "externalId": "microsoft/azure-skills/azure-resource-lookup", + "githubOwnerId": 6154722, + "owner": "microsoft", + "repo": "azure-skills", + "slug": "azure-resource-lookup", + "displayName": "azure-resource-lookup", + "sourceUrl": "https://www.skills.sh/microsoft/azure-skills/azure-resource-lookup", + "githubRepoUrl": "https://github.com/microsoft/azure-skills", + "sourceContentHash": "8e12da27e6899e067608ea8dc5743df7f04a60d687e44568965c2b04a4e61e0f", + "installs": 463473 + }, + { + "externalId": "microsoft/azure-skills/azure-aigateway", + "githubOwnerId": 6154722, + "owner": "microsoft", + "repo": "azure-skills", + "slug": "azure-aigateway", + "displayName": "azure-aigateway", + "sourceUrl": "https://www.skills.sh/microsoft/azure-skills/azure-aigateway", + "githubRepoUrl": "https://github.com/microsoft/azure-skills", + "sourceContentHash": "3f1bc79ab218f069849615bc603332c35f14acd5665379990758322024509260", + "installs": 463378 + }, + { + "externalId": "microsoft/azure-skills/azure-kusto", + "githubOwnerId": 6154722, + "owner": "microsoft", + "repo": "azure-skills", + "slug": "azure-kusto", + "displayName": "azure-kusto", + "sourceUrl": "https://www.skills.sh/microsoft/azure-skills/azure-kusto", + "githubRepoUrl": "https://github.com/microsoft/azure-skills", + "sourceContentHash": "6691a61b1a56f24eb7dc4ef6889182764c2c1c6b8a8394c838fe724bce128181", + "installs": 463310 + }, + { + "externalId": "microsoft/azure-skills/azure-resource-visualizer", + "githubOwnerId": 6154722, + "owner": "microsoft", + "repo": "azure-skills", + "slug": "azure-resource-visualizer", + "displayName": "azure-resource-visualizer", + "sourceUrl": "https://www.skills.sh/microsoft/azure-skills/azure-resource-visualizer", + "githubRepoUrl": "https://github.com/microsoft/azure-skills", + "sourceContentHash": "a952f6a675f1533e46ee927950e6be2663cb941ec2a1faf10b84fcda36355d91", + "installs": 463301 + }, + { + "externalId": "microsoft/azure-skills/azure-rbac", + "githubOwnerId": 6154722, + "owner": "microsoft", + "repo": "azure-skills", + "slug": "azure-rbac", + "displayName": "azure-rbac", + "sourceUrl": "https://www.skills.sh/microsoft/azure-skills/azure-rbac", + "githubRepoUrl": "https://github.com/microsoft/azure-skills", + "sourceContentHash": "4d9a271de2b49ccf48c83184557391fb4124e7f92e557eeb9fd5f61b41535a9a", + "installs": 461708 + }, + { + "externalId": "microsoft/azure-skills/azure-messaging", + "githubOwnerId": 6154722, + "owner": "microsoft", + "repo": "azure-skills", + "slug": "azure-messaging", + "displayName": "azure-messaging", + "sourceUrl": "https://www.skills.sh/microsoft/azure-skills/azure-messaging", + "githubRepoUrl": "https://github.com/microsoft/azure-skills", + "sourceContentHash": "22f768b2766bd34971968191751656d7fd30a366d388572202dd6a594e0db4e2", + "installs": 453051 + }, + { + "externalId": "mattpocock/skills/setup-matt-pocock-skills", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "setup-matt-pocock-skills", + "displayName": "setup-matt-pocock-skills", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/setup-matt-pocock-skills", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "35a8a37254994e3ff50ff3308f1bb88d301a90a6e6638b9e674371e20cc7d365", + "installs": 436463 + }, + { + "externalId": "remotion-dev/skills/remotion-best-practices", + "githubOwnerId": 85344006, + "owner": "remotion-dev", + "repo": "skills", + "slug": "remotion-best-practices", + "displayName": "remotion-best-practices", + "sourceUrl": "https://www.skills.sh/remotion-dev/skills/remotion-best-practices", + "githubRepoUrl": "https://github.com/remotion-dev/skills", + "sourceContentHash": "ef0e6ff870bf23cbf1b520c0c4d816e758bb2c746b5d0ba7fa7b801ef25b429b", + "installs": 436043 + }, + { + "externalId": "microsoft/azure-skills/azure-hosted-copilot-sdk", + "githubOwnerId": 6154722, + "owner": "microsoft", + "repo": "azure-skills", + "slug": "azure-hosted-copilot-sdk", + "displayName": "azure-hosted-copilot-sdk", + "sourceUrl": "https://www.skills.sh/microsoft/azure-skills/azure-hosted-copilot-sdk", + "githubRepoUrl": "https://github.com/microsoft/azure-skills", + "sourceContentHash": "2ffc86e419474b85424303ef45de3bedf1f56c3ca761f8d9ccfbf78f6f019c32", + "installs": 422384 + }, + { + "externalId": "mattpocock/skills/triage", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "triage", + "displayName": "triage", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/triage", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "332934855ccbe0215edd859b5cf92e6e5731c2d51d2144a8bd9372c7caf951a6", + "installs": 407475 + }, + { + "externalId": "microsoft/azure-skills/azure-compute", + "githubOwnerId": 6154722, + "owner": "microsoft", + "repo": "azure-skills", + "slug": "azure-compute", + "displayName": "azure-compute", + "sourceUrl": "https://www.skills.sh/microsoft/azure-skills/azure-compute", + "githubRepoUrl": "https://github.com/microsoft/azure-skills", + "sourceContentHash": "b5acd364634b744d81ff15b09a2000d2850a4fe8af54dfafdd4e9fcbc4dffab9", + "installs": 407004 + }, + { + "externalId": "mattpocock/skills/handoff", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "handoff", + "displayName": "handoff", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/handoff", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "4f7ef8b1c4e907fbf0de8e1533a16df3a991def76ef67dcb6c54fa6433d7f483", + "installs": 402441 + }, + { + "externalId": "microsoft/azure-skills/azure-cloud-migrate", + "githubOwnerId": 6154722, + "owner": "microsoft", + "repo": "azure-skills", + "slug": "azure-cloud-migrate", + "displayName": "azure-cloud-migrate", + "sourceUrl": "https://www.skills.sh/microsoft/azure-skills/azure-cloud-migrate", + "githubRepoUrl": "https://github.com/microsoft/azure-skills", + "sourceContentHash": "abe129c70f1729e48a8230c71b499d37c7056f07c38845e7d01dd0c2989a1625", + "installs": 396925 + }, + { + "externalId": "mattpocock/skills/prototype", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "prototype", + "displayName": "prototype", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/prototype", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "de363d558684c8601b1ee3af99aa80db67a9a4db61a2dde8c0c9d769d936d51d", + "installs": 388849 + }, + { + "externalId": "juliusbrussee/caveman/caveman", + "githubOwnerId": 104168679, + "owner": "juliusbrussee", + "repo": "caveman", + "slug": "caveman", + "displayName": "caveman", + "sourceUrl": "https://www.skills.sh/juliusbrussee/caveman/caveman", + "githubRepoUrl": "https://github.com/juliusbrussee/caveman", + "sourceContentHash": "bcfd56c8b859003e2d9cfcd6f7e736af1d02197cb2c7b8d6d65eae05e6ae7084", + "installs": 367037 + }, + { + "externalId": "mattpocock/skills/to-prd", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "to-prd", + "displayName": "to-prd", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/to-prd", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "3bcff1d61bd7999b66f118909822a8e6a0e3f6d583c1f00c2ec0a4a2a49d1b88", + "installs": 358698 + }, + { + "externalId": "mattpocock/skills/to-issues", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "to-issues", + "displayName": "to-issues", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/to-issues", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "edc6669d0765582a7cf6eca9c7eff3aa015526b3a00773fceb24725de3a759fe", + "installs": 345986 + }, + { + "externalId": "microsoft/azure-skills/azure-quotas", + "githubOwnerId": 6154722, + "owner": "microsoft", + "repo": "azure-skills", + "slug": "azure-quotas", + "displayName": "azure-quotas", + "sourceUrl": "https://www.skills.sh/microsoft/azure-skills/azure-quotas", + "githubRepoUrl": "https://github.com/microsoft/azure-skills", + "sourceContentHash": "b2a9214cc98fb351c10e0afa109132855b6360a7fd71de6608f719f76b66b552", + "installs": 333411 + }, + { + "externalId": "microsoft/azure-skills/azure-upgrade", + "githubOwnerId": 6154722, + "owner": "microsoft", + "repo": "azure-skills", + "slug": "azure-upgrade", + "displayName": "azure-upgrade", + "sourceUrl": "https://www.skills.sh/microsoft/azure-skills/azure-upgrade", + "githubRepoUrl": "https://github.com/microsoft/azure-skills", + "sourceContentHash": "ff7527eef5a8191d8ec3d23eb7a0c6f6a6b03eab6e12f2a7ffe60fe236d5c4b6", + "installs": 325899 + }, + { + "externalId": "anthropics/skills/skill-creator", + "githubOwnerId": 76263028, + "owner": "anthropics", + "repo": "skills", + "slug": "skill-creator", + "displayName": "skill-creator", + "sourceUrl": "https://www.skills.sh/anthropics/skills/skill-creator", + "githubRepoUrl": "https://github.com/anthropics/skills", + "sourceContentHash": "d3f99a0544bca333074f0e74289d3c44c049f49257fbcda36355c85bb314e268", + "installs": 321494 + }, + { + "externalId": "microsoft/azure-skills/azure-enterprise-infra-planner", + "githubOwnerId": 6154722, + "owner": "microsoft", + "repo": "azure-skills", + "slug": "azure-enterprise-infra-planner", + "displayName": "azure-enterprise-infra-planner", + "sourceUrl": "https://www.skills.sh/microsoft/azure-skills/azure-enterprise-infra-planner", + "githubRepoUrl": "https://github.com/microsoft/azure-skills", + "sourceContentHash": "bf5e44a26677cc2e65c02a4b779f8c455e308c82b132b9a5cf52e6ab259f187a", + "installs": 299958 + }, + { + "externalId": "supabase/agent-skills/supabase-postgres-best-practices", + "githubOwnerId": 54469796, + "owner": "supabase", + "repo": "agent-skills", + "slug": "supabase-postgres-best-practices", + "displayName": "supabase-postgres-best-practices", + "sourceUrl": "https://www.skills.sh/supabase/agent-skills/supabase-postgres-best-practices", + "githubRepoUrl": "https://github.com/supabase/agent-skills", + "sourceContentHash": "cf070ce131aeb2b030e16c8dd372bdb64330f6cc3c96d0e0ab9d6961f9a20dbc", + "installs": 297951 + }, + { + "externalId": "microsoft/azure-skills/azure-kubernetes", + "githubOwnerId": 6154722, + "owner": "microsoft", + "repo": "azure-skills", + "slug": "azure-kubernetes", + "displayName": "azure-kubernetes", + "sourceUrl": "https://www.skills.sh/microsoft/azure-skills/azure-kubernetes", + "githubRepoUrl": "https://github.com/microsoft/azure-skills", + "sourceContentHash": "c3e67fd80f7cc5d4a76c2730de0087c6637a7ae55bbbf18dfb7bfa608fe4876e", + "installs": 292849 + }, + { + "externalId": "obra/superpowers/brainstorming", + "githubOwnerId": 45416, + "owner": "obra", + "repo": "superpowers", + "slug": "brainstorming", + "displayName": "brainstorming", + "sourceUrl": "https://www.skills.sh/obra/superpowers/brainstorming", + "githubRepoUrl": "https://github.com/obra/superpowers", + "sourceContentHash": "a3782402a2ea2a0c57f1a79b1b0608f2b91bd098e12e8cb1ee8702fb4a882b68", + "installs": 287370 + }, + { + "externalId": "mattpocock/skills/teach", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "teach", + "displayName": "teach", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/teach", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "a44fbaa67b0bfe0e4ae8007ee255a6e5e63d86309eb4077bec950f5087782297", + "installs": 281873 + }, + { + "externalId": "nextlevelbuilder/ui-ux-pro-max-skill/ui-ux-pro-max", + "githubOwnerId": 246974152, + "owner": "nextlevelbuilder", + "repo": "ui-ux-pro-max-skill", + "slug": "ui-ux-pro-max", + "displayName": "ui-ux-pro-max", + "sourceUrl": "https://www.skills.sh/nextlevelbuilder/ui-ux-pro-max-skill/ui-ux-pro-max", + "githubRepoUrl": "https://github.com/nextlevelbuilder/ui-ux-pro-max-skill", + "sourceContentHash": "3bb6b55d6043d6b2996e382ce970bcf18bcf5ce1f192c7216f9580170abca2fc", + "installs": 276556 + }, + { + "externalId": "leonxlnx/taste-skill/design-taste-frontend", + "githubOwnerId": 219127460, + "owner": "leonxlnx", + "repo": "taste-skill", + "slug": "design-taste-frontend", + "displayName": "design-taste-frontend", + "sourceUrl": "https://www.skills.sh/leonxlnx/taste-skill/design-taste-frontend", + "githubRepoUrl": "https://github.com/leonxlnx/taste-skill", + "sourceContentHash": "32134157d57314d41e794e71014c853b24f16f20e396944e47ccc76f96fa01b0", + "installs": 274001 + }, + { + "externalId": "101-skills/skills/ai-video-generation", + "githubOwnerId": 301088244, + "owner": "101-skills", + "repo": "skills", + "slug": "ai-video-generation", + "displayName": "ai-video-generation", + "sourceUrl": "https://www.skills.sh/101-skills/skills/ai-video-generation", + "githubRepoUrl": "https://github.com/101-skills/skills", + "sourceContentHash": "5a37fd9c37d13f6768625b35191915cfa5d25a31c4c82540d42d53ddeba383a1", + "installs": 269984 + }, + { + "externalId": "101-skills/skills/ai-image-generation", + "githubOwnerId": 301088244, + "owner": "101-skills", + "repo": "skills", + "slug": "ai-image-generation", + "displayName": "ai-image-generation", + "sourceUrl": "https://www.skills.sh/101-skills/skills/ai-image-generation", + "githubRepoUrl": "https://github.com/101-skills/skills", + "sourceContentHash": "f11f3a5fc9193977f477c4df7e2f24bdd7ffaee06942af03fac53889bf621800", + "installs": 269237 + }, + { + "externalId": "101-skills/skills/ai-avatar-video", + "githubOwnerId": 301088244, + "owner": "101-skills", + "repo": "skills", + "slug": "ai-avatar-video", + "displayName": "ai-avatar-video", + "sourceUrl": "https://www.skills.sh/101-skills/skills/ai-avatar-video", + "githubRepoUrl": "https://github.com/101-skills/skills", + "sourceContentHash": "48d8981b21a937aec58fd18ba3167bc8922b7ed8d61ca03d111d548ad075e586", + "installs": 269180 + }, + { + "externalId": "101-skills/skills/remotion-render", + "githubOwnerId": 301088244, + "owner": "101-skills", + "repo": "skills", + "slug": "remotion-render", + "displayName": "remotion-render", + "sourceUrl": "https://www.skills.sh/101-skills/skills/remotion-render", + "githubRepoUrl": "https://github.com/101-skills/skills", + "sourceContentHash": "1877d82644a68066923fb8b40375221e9676d65300465f325fd4d1d34982d1f5", + "installs": 269155 + }, + { + "externalId": "101-skills/skills/twitter-automation", + "githubOwnerId": 301088244, + "owner": "101-skills", + "repo": "skills", + "slug": "twitter-automation", + "displayName": "twitter-automation", + "sourceUrl": "https://www.skills.sh/101-skills/skills/twitter-automation", + "githubRepoUrl": "https://github.com/101-skills/skills", + "sourceContentHash": "01c9056804df67c95d5cc0ae903d3cdadec66351a8c56678be5c60bba9a8ceb2", + "installs": 269154 + }, + { + "externalId": "microsoft/azure-skills/azure-cost", + "githubOwnerId": 6154722, + "owner": "microsoft", + "repo": "azure-skills", + "slug": "azure-cost", + "displayName": "azure-cost", + "sourceUrl": "https://www.skills.sh/microsoft/azure-skills/azure-cost", + "githubRepoUrl": "https://github.com/microsoft/azure-skills", + "sourceContentHash": "df6517ca2cf60edb204b216cd67ccc22910885273ca8584f71cbc94d4f6e48da", + "installs": 268293 + }, + { + "externalId": "vercel-labs/agent-skills/vercel-composition-patterns", + "githubOwnerId": 108547162, + "owner": "vercel-labs", + "repo": "agent-skills", + "slug": "vercel-composition-patterns", + "displayName": "vercel-composition-patterns", + "sourceUrl": "https://www.skills.sh/vercel-labs/agent-skills/vercel-composition-patterns", + "githubRepoUrl": "https://github.com/vercel-labs/agent-skills", + "sourceContentHash": "575757e3e25761c8c562d6e395d29f0b76c98b1273c0bd72d88e6ab1bc9c7d42", + "installs": 256354 + }, + { + "externalId": "roin-orca/skills/simple", + "githubOwnerId": 120920375, + "owner": "roin-orca", + "repo": "skills", + "slug": "simple", + "displayName": "simple", + "sourceUrl": "https://www.skills.sh/roin-orca/skills/simple", + "githubRepoUrl": "https://github.com/roin-orca/skills", + "sourceContentHash": "eb72d10074e9a4ea9963ef4f673f96d6e9170b92e81737048e0b0f6dceddc55c", + "installs": 255253 + }, + { + "externalId": "juliusbrussee/caveman/caveman-commit", + "githubOwnerId": 104168679, + "owner": "juliusbrussee", + "repo": "caveman", + "slug": "caveman-commit", + "displayName": "caveman-commit", + "sourceUrl": "https://www.skills.sh/juliusbrussee/caveman/caveman-commit", + "githubRepoUrl": "https://github.com/juliusbrussee/caveman", + "sourceContentHash": "1abb9081b8cf9cc26512ed42887567e67469c556e8781a9af11ea3cff0388a91", + "installs": 253096 + }, + { + "externalId": "juliusbrussee/caveman/caveman-review", + "githubOwnerId": 104168679, + "owner": "juliusbrussee", + "repo": "caveman", + "slug": "caveman-review", + "displayName": "caveman-review", + "sourceUrl": "https://www.skills.sh/juliusbrussee/caveman/caveman-review", + "githubRepoUrl": "https://github.com/juliusbrussee/caveman", + "sourceContentHash": "bfed0a8c257fab8e02e9d427d8b5ae6b24dd7091bd90f790a2227de00a8f8bf9", + "installs": 251227 + }, + { + "externalId": "juliusbrussee/caveman/caveman-compress", + "githubOwnerId": 104168679, + "owner": "juliusbrussee", + "repo": "caveman", + "slug": "caveman-compress", + "displayName": "caveman-compress", + "sourceUrl": "https://www.skills.sh/juliusbrussee/caveman/caveman-compress", + "githubRepoUrl": "https://github.com/juliusbrussee/caveman", + "sourceContentHash": "53e59315d95e316bbb08350a0f8e024d15242425c03127160b4b606704bc009f", + "installs": 249071 + }, + { + "externalId": "heygen-com/hyperframes/hyperframes-cli", + "githubOwnerId": 65394575, + "owner": "heygen-com", + "repo": "hyperframes", + "slug": "hyperframes-cli", + "displayName": "hyperframes-cli", + "sourceUrl": "https://www.skills.sh/heygen-com/hyperframes/hyperframes-cli", + "githubRepoUrl": "https://github.com/heygen-com/hyperframes", + "sourceContentHash": "b76dda34c940480c64a5c817379900fb387c7b896ca0c8fd62a6bb340c127a33", + "installs": 248576 + }, + { + "externalId": "heygen-com/hyperframes/hyperframes", + "githubOwnerId": 65394575, + "owner": "heygen-com", + "repo": "hyperframes", + "slug": "hyperframes", + "displayName": "hyperframes", + "sourceUrl": "https://www.skills.sh/heygen-com/hyperframes/hyperframes", + "githubRepoUrl": "https://github.com/heygen-com/hyperframes", + "sourceContentHash": "7ddbb928a3674e5e1821b13055e784f261edcd6ff9bf61e5f1fdffaab307a58b", + "installs": 247542 + }, + { + "externalId": "scrapegraphai/just-scrape/just-scrape", + "githubOwnerId": 171017415, + "owner": "scrapegraphai", + "repo": "just-scrape", + "slug": "just-scrape", + "displayName": "just-scrape", + "sourceUrl": "https://www.skills.sh/scrapegraphai/just-scrape/just-scrape", + "githubRepoUrl": "https://github.com/scrapegraphai/just-scrape", + "sourceContentHash": "7cec1fadf6d706b30e39f3e104eec1e5e384e790626b5ef8ca8b2f4555e07e69", + "installs": 244864 + }, + { + "externalId": "juliusbrussee/caveman/caveman-help", + "githubOwnerId": 104168679, + "owner": "juliusbrussee", + "repo": "caveman", + "slug": "caveman-help", + "displayName": "caveman-help", + "sourceUrl": "https://www.skills.sh/juliusbrussee/caveman/caveman-help", + "githubRepoUrl": "https://github.com/juliusbrussee/caveman", + "sourceContentHash": "234f84aecf60b79d6f6de155d66d45e1fa23ec8e3d40d0b2194fe4eb37c84c2a", + "installs": 243724 + }, + { + "externalId": "mattpocock/skills/diagnose", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "diagnose", + "displayName": "diagnose", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/diagnose", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "9e8a25a0dfc2da80a0867158a65a15368740bbe0ffec4126c7552b94b53d570f", + "installs": 233752 + }, + { + "externalId": "heygen-com/hyperframes/hyperframes-registry", + "githubOwnerId": 65394575, + "owner": "heygen-com", + "repo": "hyperframes", + "slug": "hyperframes-registry", + "displayName": "hyperframes-registry", + "sourceUrl": "https://www.skills.sh/heygen-com/hyperframes/hyperframes-registry", + "githubRepoUrl": "https://github.com/heygen-com/hyperframes", + "sourceContentHash": "7061c9980e85519280d3fa3bb871eaa5f6dcad9300a521945dc3fa468df42412", + "installs": 233318 + }, + { + "externalId": "mattpocock/skills/grilling", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "grilling", + "displayName": "grilling", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/grilling", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "5f8e034b4892cfa8b8c07fd1da21e00cf08aa34b0d0189c6f424410bff7d74ae", + "installs": 229915 + }, + { + "externalId": "mattpocock/skills/write-a-skill", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "write-a-skill", + "displayName": "write-a-skill", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/write-a-skill", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "03840258378476cbefa13fd3f7bf70db93ec1c3cdd7d0dc4edb9431e71a2a24d", + "installs": 225536 + }, + { + "externalId": "mattpocock/skills/zoom-out", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "zoom-out", + "displayName": "zoom-out", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/zoom-out", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "34f4bab271b9b9bbad01e08413d5a096ec84d1ee983d747de9219f77afc5e897", + "installs": 224920 + }, + { + "externalId": "mattpocock/skills/caveman", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "caveman", + "displayName": "caveman", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/caveman", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "7e2fa5f052ed8572bbe892d4c1baeea80ba8659d0b72e6432bfd7e8fb98abc0c", + "installs": 219813 + }, + { + "externalId": "mattpocock/skills/domain-modeling", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "domain-modeling", + "displayName": "domain-modeling", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/domain-modeling", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "5622194d032235c847b743c167cb42de137120ac2effd1937e2a71c5ed487d45", + "installs": 214810 + }, + { + "externalId": "leonxlnx/taste-skill/high-end-visual-design", + "githubOwnerId": 219127460, + "owner": "leonxlnx", + "repo": "taste-skill", + "slug": "high-end-visual-design", + "displayName": "high-end-visual-design", + "sourceUrl": "https://www.skills.sh/leonxlnx/taste-skill/high-end-visual-design", + "githubRepoUrl": "https://github.com/leonxlnx/taste-skill", + "sourceContentHash": "49c7b83a53374522fc0603e5b0d8bd2eadc1ad819356f4b56f08f5e73d91ee2d", + "installs": 212990 + }, + { + "externalId": "microsoft/azure-skills/airunway-aks-setup", + "githubOwnerId": 6154722, + "owner": "microsoft", + "repo": "azure-skills", + "slug": "airunway-aks-setup", + "displayName": "airunway-aks-setup", + "sourceUrl": "https://www.skills.sh/microsoft/azure-skills/airunway-aks-setup", + "githubRepoUrl": "https://github.com/microsoft/azure-skills", + "sourceContentHash": "edb67a2ca804fe88b3aac6b53e953ead6fa793d06c8e5e1a582b41a5d753748d", + "installs": 212848 + }, + { + "externalId": "leonxlnx/taste-skill/redesign-existing-projects", + "githubOwnerId": 219127460, + "owner": "leonxlnx", + "repo": "taste-skill", + "slug": "redesign-existing-projects", + "displayName": "redesign-existing-projects", + "sourceUrl": "https://www.skills.sh/leonxlnx/taste-skill/redesign-existing-projects", + "githubRepoUrl": "https://github.com/leonxlnx/taste-skill", + "sourceContentHash": "f6b0637adf3481f0fbd23a365a8e0773ed82b322c1df78a40d7a8b4cda65c9e3", + "installs": 209635 + }, + { + "externalId": "microsoft/azure-skills/azure-cost-optimization", + "githubOwnerId": 6154722, + "owner": "microsoft", + "repo": "azure-skills", + "slug": "azure-cost-optimization", + "displayName": "azure-cost-optimization", + "sourceUrl": "https://www.skills.sh/microsoft/azure-skills/azure-cost-optimization", + "githubRepoUrl": "https://github.com/microsoft/azure-skills", + "sourceContentHash": "d1d4d97635bd3cd7368e60c4faf6e01bef0029606d99474f15a7e22778962577", + "installs": 208568 + }, + { + "externalId": "mattpocock/skills/codebase-design", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "codebase-design", + "displayName": "codebase-design", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/codebase-design", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "4b46c5ee5f29310386ec35f5b34184aef8e23d1f6d9889a2ece8b8e429b7b880", + "installs": 208100 + }, + { + "externalId": "mattpocock/skills/writing-great-skills", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "writing-great-skills", + "displayName": "writing-great-skills", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/writing-great-skills", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "fb14b29302e6405c169b540247af11548f9d06fb0a9d3892d058b5354163827a", + "installs": 206303 + }, + { + "externalId": "mattpocock/skills/diagnosing-bugs", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "diagnosing-bugs", + "displayName": "diagnosing-bugs", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/diagnosing-bugs", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "3098440499d65036a30ae5ebec6fac90f3d53833472e582e32be50f90b459755", + "installs": 205568 + }, + { + "externalId": "juliusbrussee/caveman/cavecrew", + "githubOwnerId": 104168679, + "owner": "juliusbrussee", + "repo": "caveman", + "slug": "cavecrew", + "displayName": "cavecrew", + "sourceUrl": "https://www.skills.sh/juliusbrussee/caveman/cavecrew", + "githubRepoUrl": "https://github.com/juliusbrussee/caveman", + "sourceContentHash": "39a3d3083d2a8d5c4e68dc50c71e6de8e84edca8d703808c8614cb305758f63b", + "installs": 201740 + }, + { + "externalId": "juliusbrussee/caveman/caveman-stats", + "githubOwnerId": 104168679, + "owner": "juliusbrussee", + "repo": "caveman", + "slug": "caveman-stats", + "displayName": "caveman-stats", + "sourceUrl": "https://www.skills.sh/juliusbrussee/caveman/caveman-stats", + "githubRepoUrl": "https://github.com/juliusbrussee/caveman", + "sourceContentHash": "f4e73b84426bec4e8e167ff076efa25249b4beedc7f0fe8b709855b84329d484", + "installs": 201324 + }, + { + "externalId": "pbakaus/impeccable/impeccable", + "githubOwnerId": 43004, + "owner": "pbakaus", + "repo": "impeccable", + "slug": "impeccable", + "displayName": "impeccable", + "sourceUrl": "https://www.skills.sh/pbakaus/impeccable/impeccable", + "githubRepoUrl": "https://github.com/pbakaus/impeccable", + "sourceContentHash": "d328ad0d014ca35a8997e04191b89ce4240e74faa2a5f55d22ff1e20ec81b5d3", + "installs": 200105 + }, + { + "externalId": "leonxlnx/taste-skill/minimalist-ui", + "githubOwnerId": 219127460, + "owner": "leonxlnx", + "repo": "taste-skill", + "slug": "minimalist-ui", + "displayName": "minimalist-ui", + "sourceUrl": "https://www.skills.sh/leonxlnx/taste-skill/minimalist-ui", + "githubRepoUrl": "https://github.com/leonxlnx/taste-skill", + "sourceContentHash": "0b63afb03f68657b51042d075b0a7f8c2362bdb5bb1f77caab99693e90c91e21", + "installs": 194852 + }, + { + "externalId": "mattpocock/skills/ask-matt", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "ask-matt", + "displayName": "ask-matt", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/ask-matt", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "e13c68e505a522cdf00908890561efbacb4ecb79ebc0333db4b95f25bbf31e13", + "installs": 194756 + }, + { + "externalId": "obra/superpowers/systematic-debugging", + "githubOwnerId": 45416, + "owner": "obra", + "repo": "superpowers", + "slug": "systematic-debugging", + "displayName": "systematic-debugging", + "sourceUrl": "https://www.skills.sh/obra/superpowers/systematic-debugging", + "githubRepoUrl": "https://github.com/obra/superpowers", + "sourceContentHash": "5a1612d55671c15368fe7976f71eac61ff10cf2eec2f4bc0e5eadb1c6964652a", + "installs": 194020 + }, + { + "externalId": "obra/superpowers/writing-plans", + "githubOwnerId": 45416, + "owner": "obra", + "repo": "superpowers", + "slug": "writing-plans", + "displayName": "writing-plans", + "sourceUrl": "https://www.skills.sh/obra/superpowers/writing-plans", + "githubRepoUrl": "https://github.com/obra/superpowers", + "sourceContentHash": "03d2a03a4be740e2c785b1c5b6072027716f791b5021fbf78cb4b74aa7e275f9", + "installs": 191599 + }, + { + "externalId": "obra/superpowers/using-superpowers", + "githubOwnerId": 45416, + "owner": "obra", + "repo": "superpowers", + "slug": "using-superpowers", + "displayName": "using-superpowers", + "sourceUrl": "https://www.skills.sh/obra/superpowers/using-superpowers", + "githubRepoUrl": "https://github.com/obra/superpowers", + "sourceContentHash": "b8c84c130ce735a5dee0bfacda77cd1332eaa5a79f8677b58e523fd2edf7e46b", + "installs": 189851 + }, + { + "externalId": "microsoft/azure-skills/entra-agent-id", + "githubOwnerId": 6154722, + "owner": "microsoft", + "repo": "azure-skills", + "slug": "entra-agent-id", + "displayName": "entra-agent-id", + "sourceUrl": "https://www.skills.sh/microsoft/azure-skills/entra-agent-id", + "githubRepoUrl": "https://github.com/microsoft/azure-skills", + "sourceContentHash": "4f4fa62aed62180d55a3e5e7a3a26018ea6bc7ee71a40693680fd3e7a6ee7c77", + "installs": 187013 + }, + { + "externalId": "leonxlnx/taste-skill/full-output-enforcement", + "githubOwnerId": 219127460, + "owner": "leonxlnx", + "repo": "taste-skill", + "slug": "full-output-enforcement", + "displayName": "full-output-enforcement", + "sourceUrl": "https://www.skills.sh/leonxlnx/taste-skill/full-output-enforcement", + "githubRepoUrl": "https://github.com/leonxlnx/taste-skill", + "sourceContentHash": "933b89da884015e2a4189417fd2dad62610c9aeb1c80d0166567354594836b1c", + "installs": 186036 + }, + { + "externalId": "anthropics/skills/pptx", + "githubOwnerId": 76263028, + "owner": "anthropics", + "repo": "skills", + "slug": "pptx", + "displayName": "pptx", + "sourceUrl": "https://www.skills.sh/anthropics/skills/pptx", + "githubRepoUrl": "https://github.com/anthropics/skills", + "sourceContentHash": "fdbf2a80516ebb5243e5e3579e30ac63e5b75111dc0c5f96fe2dbf22b9dbf6f3", + "installs": 181366 + }, + { + "externalId": "leonxlnx/taste-skill/industrial-brutalist-ui", + "githubOwnerId": 219127460, + "owner": "leonxlnx", + "repo": "taste-skill", + "slug": "industrial-brutalist-ui", + "displayName": "industrial-brutalist-ui", + "sourceUrl": "https://www.skills.sh/leonxlnx/taste-skill/industrial-brutalist-ui", + "githubRepoUrl": "https://github.com/leonxlnx/taste-skill", + "sourceContentHash": "d915c228e1fd8ff18f9f417ddfff605f045a15acb85e15906dcb347ba6e2c941", + "installs": 181068 + }, + { + "externalId": "heygen-com/hyperframes/remotion-to-hyperframes", + "githubOwnerId": 65394575, + "owner": "heygen-com", + "repo": "hyperframes", + "slug": "remotion-to-hyperframes", + "displayName": "remotion-to-hyperframes", + "sourceUrl": "https://www.skills.sh/heygen-com/hyperframes/remotion-to-hyperframes", + "githubRepoUrl": "https://github.com/heygen-com/hyperframes", + "sourceContentHash": "9600c982951ef684df265dde84f63ce95b2341874fc126448f4f716abc80008b", + "installs": 180829 + }, + { + "externalId": "leonxlnx/taste-skill/stitch-design-taste", + "githubOwnerId": 219127460, + "owner": "leonxlnx", + "repo": "taste-skill", + "slug": "stitch-design-taste", + "displayName": "stitch-design-taste", + "sourceUrl": "https://www.skills.sh/leonxlnx/taste-skill/stitch-design-taste", + "githubRepoUrl": "https://github.com/leonxlnx/taste-skill", + "sourceContentHash": "e37419c24236cf2d983dcd39feb1907733a43f9567f8e5ad095d4c539ed851ba", + "installs": 179993 + }, + { + "externalId": "mattpocock/skills/implement", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "implement", + "displayName": "implement", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/implement", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "b49a049477c7eec7f8dd90745dbc3f27bf75617b832d5b5f33c55aab7638a12e", + "installs": 179941 + }, + { + "externalId": "leonxlnx/taste-skill/gpt-taste", + "githubOwnerId": 219127460, + "owner": "leonxlnx", + "repo": "taste-skill", + "slug": "gpt-taste", + "displayName": "gpt-taste", + "sourceUrl": "https://www.skills.sh/leonxlnx/taste-skill/gpt-taste", + "githubRepoUrl": "https://github.com/leonxlnx/taste-skill", + "sourceContentHash": "2699f333a4cf2c43ef49b7cdf66674b5a19d05c1ca6a9a848dd4ec0acc41984d", + "installs": 176362 + }, + { + "externalId": "supabase/agent-skills/supabase", + "githubOwnerId": 54469796, + "owner": "supabase", + "repo": "agent-skills", + "slug": "supabase", + "displayName": "supabase", + "sourceUrl": "https://www.skills.sh/supabase/agent-skills/supabase", + "githubRepoUrl": "https://github.com/supabase/agent-skills", + "sourceContentHash": "300bb57a6b1faa45ab42ef8b8fc99fed90686c870dc0346626ee907d0d1b6e7d", + "installs": 175847 + }, + { + "externalId": "obra/superpowers/requesting-code-review", + "githubOwnerId": 45416, + "owner": "obra", + "repo": "superpowers", + "slug": "requesting-code-review", + "displayName": "requesting-code-review", + "sourceUrl": "https://www.skills.sh/obra/superpowers/requesting-code-review", + "githubRepoUrl": "https://github.com/obra/superpowers", + "sourceContentHash": "afdd8fe75c6c48cef7589d69ed639a54a8844b66762de4a5f0035f49d8c5bcc3", + "installs": 173751 + }, + { + "externalId": "obra/superpowers/test-driven-development", + "githubOwnerId": 45416, + "owner": "obra", + "repo": "superpowers", + "slug": "test-driven-development", + "displayName": "test-driven-development", + "sourceUrl": "https://www.skills.sh/obra/superpowers/test-driven-development", + "githubRepoUrl": "https://github.com/obra/superpowers", + "sourceContentHash": "7c3b1fa71c313b7c51b451858ec855c0f22fd5925bf0ec9b385382c1bcd3d75b", + "installs": 172298 + }, + { + "externalId": "vercel-labs/agent-skills/vercel-react-native-skills", + "githubOwnerId": 108547162, + "owner": "vercel-labs", + "repo": "agent-skills", + "slug": "vercel-react-native-skills", + "displayName": "vercel-react-native-skills", + "sourceUrl": "https://www.skills.sh/vercel-labs/agent-skills/vercel-react-native-skills", + "githubRepoUrl": "https://github.com/vercel-labs/agent-skills", + "sourceContentHash": "41d24eafa7c3d82e270439808f7cfbc4d51aeb2d14f2809a2267c16275784d06", + "installs": 170159 + }, + { + "externalId": "coreyhaines31/marketingskills/seo-audit", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "seo-audit", + "displayName": "seo-audit", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/seo-audit", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "e49aea27acd89ed57eb912db2623b50fca3e570f7a604be0dbf8ba39cb023c4f", + "installs": 166448 + }, + { + "externalId": "leonxlnx/taste-skill/brandkit", + "githubOwnerId": 219127460, + "owner": "leonxlnx", + "repo": "taste-skill", + "slug": "brandkit", + "displayName": "brandkit", + "sourceUrl": "https://www.skills.sh/leonxlnx/taste-skill/brandkit", + "githubRepoUrl": "https://github.com/leonxlnx/taste-skill", + "sourceContentHash": "66c068d44001b4e6354c09e4b799f908ed1a7003211c7e7295331c122bea9f02", + "installs": 164434 + }, + { + "externalId": "mattpocock/skills/design-an-interface", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "design-an-interface", + "displayName": "design-an-interface", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/design-an-interface", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "9693f17d79ff21cead712a597f010c23c7c419eae687e91cff2890ca77ba69aa", + "installs": 161773 + }, + { + "externalId": "anthropics/skills/pdf", + "githubOwnerId": 76263028, + "owner": "anthropics", + "repo": "skills", + "slug": "pdf", + "displayName": "pdf", + "sourceUrl": "https://www.skills.sh/anthropics/skills/pdf", + "githubRepoUrl": "https://github.com/anthropics/skills", + "sourceContentHash": "7c96a2fd5ed6490df5282564198dba6a93ca5f576457908214cb2599e47a3da5", + "installs": 161587 + }, + { + "externalId": "leonxlnx/taste-skill/image-to-code", + "githubOwnerId": 219127460, + "owner": "leonxlnx", + "repo": "taste-skill", + "slug": "image-to-code", + "displayName": "image-to-code", + "sourceUrl": "https://www.skills.sh/leonxlnx/taste-skill/image-to-code", + "githubRepoUrl": "https://github.com/leonxlnx/taste-skill", + "sourceContentHash": "ce9e7282b05e6cf247352c0f68953b943755899eb14a2198d72c97c6e5ffa807", + "installs": 161424 + }, + { + "externalId": "leonxlnx/taste-skill/imagegen-frontend-web", + "githubOwnerId": 219127460, + "owner": "leonxlnx", + "repo": "taste-skill", + "slug": "imagegen-frontend-web", + "displayName": "imagegen-frontend-web", + "sourceUrl": "https://www.skills.sh/leonxlnx/taste-skill/imagegen-frontend-web", + "githubRepoUrl": "https://github.com/leonxlnx/taste-skill", + "sourceContentHash": "d5215faecc59120b499726e2c0d6fd802d47f7f69c9243e328120b4759ed8f60", + "installs": 160960 + }, + { + "externalId": "obra/superpowers/executing-plans", + "githubOwnerId": 45416, + "owner": "obra", + "repo": "superpowers", + "slug": "executing-plans", + "displayName": "executing-plans", + "sourceUrl": "https://www.skills.sh/obra/superpowers/executing-plans", + "githubRepoUrl": "https://github.com/obra/superpowers", + "sourceContentHash": "c2df3ab9dfbb243f9f82940450dcd7ea3ee0c94f63ef510afd6037d1ac54815c", + "installs": 160161 + }, + { + "externalId": "leonxlnx/taste-skill/imagegen-frontend-mobile", + "githubOwnerId": 219127460, + "owner": "leonxlnx", + "repo": "taste-skill", + "slug": "imagegen-frontend-mobile", + "displayName": "imagegen-frontend-mobile", + "sourceUrl": "https://www.skills.sh/leonxlnx/taste-skill/imagegen-frontend-mobile", + "githubRepoUrl": "https://github.com/leonxlnx/taste-skill", + "sourceContentHash": "9d08a2fd4f71d9fa9f6808506fed4a8231ab451e2016570ab80514074571c64a", + "installs": 157317 + }, + { + "externalId": "mattpocock/skills/request-refactor-plan", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "request-refactor-plan", + "displayName": "request-refactor-plan", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/request-refactor-plan", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "69cc7b718e214a4b7bdf19b8c577b11428fd5eec4ae4726fe67009bbfe378b4c", + "installs": 157153 + }, + { + "externalId": "coreyhaines31/marketingskills/copywriting", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "copywriting", + "displayName": "copywriting", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/copywriting", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "be7268c2e588bd006db413d5a8d6303be1cc9222ba9d3c6a22bb931001a6a1d3", + "installs": 156611 + }, + { + "externalId": "anthropics/skills/docx", + "githubOwnerId": 76263028, + "owner": "anthropics", + "repo": "skills", + "slug": "docx", + "displayName": "docx", + "sourceUrl": "https://www.skills.sh/anthropics/skills/docx", + "githubRepoUrl": "https://github.com/anthropics/skills", + "sourceContentHash": "e539ec0027a639516d6e61c14c4fbbbc1064a2d9f4283766c265cbd569e806fe", + "installs": 155409 + }, + { + "externalId": "lllllllama/rigorpilot-skills/ai-research-explore", + "githubOwnerId": 150129845, + "owner": "lllllllama", + "repo": "rigorpilot-skills", + "slug": "ai-research-explore", + "displayName": "ai-research-explore", + "sourceUrl": "https://www.skills.sh/lllllllama/rigorpilot-skills/ai-research-explore", + "githubRepoUrl": "https://github.com/lllllllama/rigorpilot-skills", + "sourceContentHash": "43eb2d09fab3661cfc6c7186407ff48e8c3fcef20341d910bad3d24f9db1db97", + "installs": 153729 + }, + { + "externalId": "lllllllama/rigorpilot-skills/analyze-project", + "githubOwnerId": 150129845, + "owner": "lllllllama", + "repo": "rigorpilot-skills", + "slug": "analyze-project", + "displayName": "analyze-project", + "sourceUrl": "https://www.skills.sh/lllllllama/rigorpilot-skills/analyze-project", + "githubRepoUrl": "https://github.com/lllllllama/rigorpilot-skills", + "sourceContentHash": "58c8d0bd7d52731628395c88a30c103b5b081ab8b8866aa4823a1874031cf6e1", + "installs": 153628 + }, + { + "externalId": "lllllllama/rigorpilot-skills/ai-research-reproduction", + "githubOwnerId": 150129845, + "owner": "lllllllama", + "repo": "rigorpilot-skills", + "slug": "ai-research-reproduction", + "displayName": "ai-research-reproduction", + "sourceUrl": "https://www.skills.sh/lllllllama/rigorpilot-skills/ai-research-reproduction", + "githubRepoUrl": "https://github.com/lllllllama/rigorpilot-skills", + "sourceContentHash": "1f3e374b52b06193c0ecd22e72519532c6836215a98db80e2a95ba064672bd2f", + "installs": 153418 + }, + { + "externalId": "lllllllama/rigorpilot-skills/explore-code", + "githubOwnerId": 150129845, + "owner": "lllllllama", + "repo": "rigorpilot-skills", + "slug": "explore-code", + "displayName": "explore-code", + "sourceUrl": "https://www.skills.sh/lllllllama/rigorpilot-skills/explore-code", + "githubRepoUrl": "https://github.com/lllllllama/rigorpilot-skills", + "sourceContentHash": "7065ef142e06f2552d4997b75b19b9c9b05da5c691a1f20901cb96c64c982155", + "installs": 153412 + }, + { + "externalId": "lllllllama/rigorpilot-skills/paper-context-resolver", + "githubOwnerId": 150129845, + "owner": "lllllllama", + "repo": "rigorpilot-skills", + "slug": "paper-context-resolver", + "displayName": "paper-context-resolver", + "sourceUrl": "https://www.skills.sh/lllllllama/rigorpilot-skills/paper-context-resolver", + "githubRepoUrl": "https://github.com/lllllllama/rigorpilot-skills", + "sourceContentHash": "66cfe5cbe0fdeaa0d6df7f83eef91779fa2ff5f744390393621407e2d228faac", + "installs": 153351 + }, + { + "externalId": "lllllllama/rigorpilot-skills/safe-debug", + "githubOwnerId": 150129845, + "owner": "lllllllama", + "repo": "rigorpilot-skills", + "slug": "safe-debug", + "displayName": "safe-debug", + "sourceUrl": "https://www.skills.sh/lllllllama/rigorpilot-skills/safe-debug", + "githubRepoUrl": "https://github.com/lllllllama/rigorpilot-skills", + "sourceContentHash": "bcd2868495bc0e011436f9c4aa67558906ce76560b41f01be262fb51044a1613", + "installs": 153344 + }, + { + "externalId": "lllllllama/rigorpilot-skills/repo-intake-and-plan", + "githubOwnerId": 150129845, + "owner": "lllllllama", + "repo": "rigorpilot-skills", + "slug": "repo-intake-and-plan", + "displayName": "repo-intake-and-plan", + "sourceUrl": "https://www.skills.sh/lllllllama/rigorpilot-skills/repo-intake-and-plan", + "githubRepoUrl": "https://github.com/lllllllama/rigorpilot-skills", + "sourceContentHash": "d7067f621e6afb7f90b12203254d6630baf46764b2fe0285e616595ffe52df21", + "installs": 153336 + }, + { + "externalId": "lllllllama/rigorpilot-skills/run-train", + "githubOwnerId": 150129845, + "owner": "lllllllama", + "repo": "rigorpilot-skills", + "slug": "run-train", + "displayName": "run-train", + "sourceUrl": "https://www.skills.sh/lllllllama/rigorpilot-skills/run-train", + "githubRepoUrl": "https://github.com/lllllllama/rigorpilot-skills", + "sourceContentHash": "e354f22593f8238c4a004d70f10b59f3d70ac6d39988d4f754ab849ac331d4cb", + "installs": 153334 + }, + { + "externalId": "lllllllama/rigorpilot-skills/env-and-assets-bootstrap", + "githubOwnerId": 150129845, + "owner": "lllllllama", + "repo": "rigorpilot-skills", + "slug": "env-and-assets-bootstrap", + "displayName": "env-and-assets-bootstrap", + "sourceUrl": "https://www.skills.sh/lllllllama/rigorpilot-skills/env-and-assets-bootstrap", + "githubRepoUrl": "https://github.com/lllllllama/rigorpilot-skills", + "sourceContentHash": "fa5652ae9a2e12c6ecf4c92a73aa2145d16dd5d42df7437f089d7cad28178d85", + "installs": 153325 + }, + { + "externalId": "lllllllama/rigorpilot-skills/minimal-run-and-audit", + "githubOwnerId": 150129845, + "owner": "lllllllama", + "repo": "rigorpilot-skills", + "slug": "minimal-run-and-audit", + "displayName": "minimal-run-and-audit", + "sourceUrl": "https://www.skills.sh/lllllllama/rigorpilot-skills/minimal-run-and-audit", + "githubRepoUrl": "https://github.com/lllllllama/rigorpilot-skills", + "sourceContentHash": "9a5bd70a9e6ddf6bf0d9806b3459a8f706131944c0aad52f23752f73b265c12f", + "installs": 153324 + }, + { + "externalId": "lllllllama/rigorpilot-skills/explore-run", + "githubOwnerId": 150129845, + "owner": "lllllllama", + "repo": "rigorpilot-skills", + "slug": "explore-run", + "displayName": "explore-run", + "sourceUrl": "https://www.skills.sh/lllllllama/rigorpilot-skills/explore-run", + "githubRepoUrl": "https://github.com/lllllllama/rigorpilot-skills", + "sourceContentHash": "d85445557b37eac696a9b58d10decb09511e9898971969e9449fe96cb987618e", + "installs": 153324 + }, + { + "externalId": "mattpocock/skills/qa", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "qa", + "displayName": "qa", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/qa", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "ff51ac368113871b3eafab4d8c05a5d2a72d9b4830ee7b8d7f3b0380ff432e10", + "installs": 152805 + }, + { + "externalId": "mattpocock/skills/ubiquitous-language", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "ubiquitous-language", + "displayName": "ubiquitous-language", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/ubiquitous-language", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "dee232499c3958b24090cd112b91da14f442b84f13bf6511f05b1c771e2d9c84", + "installs": 152655 + }, + { + "externalId": "obra/superpowers/subagent-driven-development", + "githubOwnerId": 45416, + "owner": "obra", + "repo": "superpowers", + "slug": "subagent-driven-development", + "displayName": "subagent-driven-development", + "sourceUrl": "https://www.skills.sh/obra/superpowers/subagent-driven-development", + "githubRepoUrl": "https://github.com/obra/superpowers", + "sourceContentHash": "d89e4bb2b8eb7a57ac2156268d407104555940711c39996b73a16138de1cd3d0", + "installs": 151921 + }, + { + "externalId": "emilkowalski/skills/emil-design-eng", + "githubOwnerId": 36730035, + "owner": "emilkowalski", + "repo": "skills", + "slug": "emil-design-eng", + "displayName": "emil-design-eng", + "sourceUrl": "https://www.skills.sh/emilkowalski/skills/emil-design-eng", + "githubRepoUrl": "https://github.com/emilkowalski/skills", + "sourceContentHash": "984f1f4af6275ef26f9cddb15fadcdeabe60cf6d285cfc1e32a9beedfa7acbeb", + "installs": 151045 + }, + { + "externalId": "obra/superpowers/verification-before-completion", + "githubOwnerId": 45416, + "owner": "obra", + "repo": "superpowers", + "slug": "verification-before-completion", + "displayName": "verification-before-completion", + "sourceUrl": "https://www.skills.sh/obra/superpowers/verification-before-completion", + "githubRepoUrl": "https://github.com/obra/superpowers", + "sourceContentHash": "9f704f1335e61a3ce258270d9f492cf487af55f1db0b9fc1bc5fa2173ebd38fa", + "installs": 150693 + }, + { + "externalId": "mattpocock/skills/obsidian-vault", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "obsidian-vault", + "displayName": "obsidian-vault", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/obsidian-vault", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "6337c45fa045162b7218ff4571befa99fa7aafdf824b5e0fa410f9724f4764fa", + "installs": 149975 + }, + { + "externalId": "mattpocock/skills/git-guardrails-claude-code", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "git-guardrails-claude-code", + "displayName": "git-guardrails-claude-code", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/git-guardrails-claude-code", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "1361a1c2e05256123d36ed4a17a3e71055e2dc6189cc8cb8cde632f7f27f129c", + "installs": 149973 + }, + { + "externalId": "heygen-com/hyperframes/hyperframes-core", + "githubOwnerId": 65394575, + "owner": "heygen-com", + "repo": "hyperframes", + "slug": "hyperframes-core", + "displayName": "hyperframes-core", + "sourceUrl": "https://www.skills.sh/heygen-com/hyperframes/hyperframes-core", + "githubRepoUrl": "https://github.com/heygen-com/hyperframes", + "sourceContentHash": "874a8867e00987cbf30a929ce023f2b900148a20a6d3f26a909ec05c7ca32d02", + "installs": 149911 + }, + { + "externalId": "heygen-com/hyperframes/hyperframes-animation", + "githubOwnerId": 65394575, + "owner": "heygen-com", + "repo": "hyperframes", + "slug": "hyperframes-animation", + "displayName": "hyperframes-animation", + "sourceUrl": "https://www.skills.sh/heygen-com/hyperframes/hyperframes-animation", + "githubRepoUrl": "https://github.com/heygen-com/hyperframes", + "sourceContentHash": "727ac865fec28e91e4a30e61a501fb89751bc4f7a6e8314bbcdcb001f68eeb7d", + "installs": 149746 + }, + { + "externalId": "heygen-com/hyperframes/hyperframes-creative", + "githubOwnerId": 65394575, + "owner": "heygen-com", + "repo": "hyperframes", + "slug": "hyperframes-creative", + "displayName": "hyperframes-creative", + "sourceUrl": "https://www.skills.sh/heygen-com/hyperframes/hyperframes-creative", + "githubRepoUrl": "https://github.com/heygen-com/hyperframes", + "sourceContentHash": "bff726d39ae4d9672a7b30d5e9e8a9a2492f0858b1d6f3c7228c2fd9f1a31881", + "installs": 149254 + }, + { + "externalId": "mattpocock/skills/edit-article", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "edit-article", + "displayName": "edit-article", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/edit-article", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "fdb4212bd88a9f1893df66f23f5438bdb7312172658718b58de146a2f1c1b9d9", + "installs": 147016 + }, + { + "externalId": "microsoft/azure-skills/azure-reliability", + "githubOwnerId": 6154722, + "owner": "microsoft", + "repo": "azure-skills", + "slug": "azure-reliability", + "displayName": "azure-reliability", + "sourceUrl": "https://www.skills.sh/microsoft/azure-skills/azure-reliability", + "githubRepoUrl": "https://github.com/microsoft/azure-skills", + "sourceContentHash": "24cab9255b904ec135328bff33e31e4b7890e4061603a194318677f6c118b0d2", + "installs": 146534 + }, + { + "externalId": "mattpocock/skills/setup-pre-commit", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "setup-pre-commit", + "displayName": "setup-pre-commit", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/setup-pre-commit", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "39ef99170eb1c38fd49e658f48e7c872e39f2b0ed32d0c20466e3e9118279910", + "installs": 144734 + }, + { + "externalId": "heygen-com/hyperframes/hyperframes-media", + "githubOwnerId": 65394575, + "owner": "heygen-com", + "repo": "hyperframes", + "slug": "hyperframes-media", + "displayName": "hyperframes-media", + "sourceUrl": "https://www.skills.sh/heygen-com/hyperframes/hyperframes-media", + "githubRepoUrl": "https://github.com/heygen-com/hyperframes", + "sourceContentHash": "198fc243630fdc21fbd0e6ca86a24194a19f40ac6f6a558a4b2605557896da30", + "installs": 144565 + }, + { + "externalId": "obra/superpowers/receiving-code-review", + "githubOwnerId": 45416, + "owner": "obra", + "repo": "superpowers", + "slug": "receiving-code-review", + "displayName": "receiving-code-review", + "sourceUrl": "https://www.skills.sh/obra/superpowers/receiving-code-review", + "githubRepoUrl": "https://github.com/obra/superpowers", + "sourceContentHash": "976093a40d900b304e7049900c989f2db9b362e732a7590d4e4b143b5fb0d7de", + "installs": 144002 + }, + { + "externalId": "obra/superpowers/writing-skills", + "githubOwnerId": 45416, + "owner": "obra", + "repo": "superpowers", + "slug": "writing-skills", + "displayName": "writing-skills", + "sourceUrl": "https://www.skills.sh/obra/superpowers/writing-skills", + "githubRepoUrl": "https://github.com/obra/superpowers", + "sourceContentHash": "8dac0608dbab9905a3ac7dcbb52cf796fec408a844feb53b9f2dc790394fe73d", + "installs": 142727 + }, + { + "externalId": "mattpocock/skills/scaffold-exercises", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "scaffold-exercises", + "displayName": "scaffold-exercises", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/scaffold-exercises", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "3a1e2a2dd40691e229bae49d18b61e0304f12b6fe270dade1aadcb984c02d77e", + "installs": 142277 + }, + { + "externalId": "obra/superpowers/dispatching-parallel-agents", + "githubOwnerId": 45416, + "owner": "obra", + "repo": "superpowers", + "slug": "dispatching-parallel-agents", + "displayName": "dispatching-parallel-agents", + "sourceUrl": "https://www.skills.sh/obra/superpowers/dispatching-parallel-agents", + "githubRepoUrl": "https://github.com/obra/superpowers", + "sourceContentHash": "375d7445c2b7fb800f526e5cf486d9eb90b1258b0a0efd55eb4eeb0a41deb05e", + "installs": 141007 + }, + { + "externalId": "mattpocock/skills/code-review", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "code-review", + "displayName": "code-review", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/code-review", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "146642733a3e0d59919450a7c012eb334b87fdf1859b7e879e631b4a2f5fa466", + "installs": 140802 + }, + { + "externalId": "lllllllama/ai-paper-reproduction-skill/paper-context-resolver", + "githubOwnerId": 150129845, + "owner": "lllllllama", + "repo": "ai-paper-reproduction-skill", + "slug": "paper-context-resolver", + "displayName": "paper-context-resolver", + "sourceUrl": "https://www.skills.sh/lllllllama/ai-paper-reproduction-skill/paper-context-resolver", + "githubRepoUrl": "https://github.com/lllllllama/ai-paper-reproduction-skill", + "sourceContentHash": "66cfe5cbe0fdeaa0d6df7f83eef91779fa2ff5f744390393621407e2d228faac", + "installs": 140511 + }, + { + "externalId": "obra/superpowers/using-git-worktrees", + "githubOwnerId": 45416, + "owner": "obra", + "repo": "superpowers", + "slug": "using-git-worktrees", + "displayName": "using-git-worktrees", + "sourceUrl": "https://www.skills.sh/obra/superpowers/using-git-worktrees", + "githubRepoUrl": "https://github.com/obra/superpowers", + "sourceContentHash": "e6306e69246610dc203c8639584542e16bfbb0b09d0adddac533ca009fa93180", + "installs": 140455 + }, + { + "externalId": "mattpocock/skills/writing-shape", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "writing-shape", + "displayName": "writing-shape", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/writing-shape", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "4d2744a5e19e30e8bc55174edda40b1043f9ec6946f82a021c3eeaa647e781d0", + "installs": 139966 + }, + { + "externalId": "mattpocock/skills/writing-fragments", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "writing-fragments", + "displayName": "writing-fragments", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/writing-fragments", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "d5815b131b90348e3bb1d62479f9d9228a7cf8b83a43609bce008dda67eaaa57", + "installs": 139921 + }, + { + "externalId": "lllllllama/ai-paper-reproduction-skill/repo-intake-and-plan", + "githubOwnerId": 150129845, + "owner": "lllllllama", + "repo": "ai-paper-reproduction-skill", + "slug": "repo-intake-and-plan", + "displayName": "repo-intake-and-plan", + "sourceUrl": "https://www.skills.sh/lllllllama/ai-paper-reproduction-skill/repo-intake-and-plan", + "githubRepoUrl": "https://github.com/lllllllama/ai-paper-reproduction-skill", + "sourceContentHash": "d7067f621e6afb7f90b12203254d6630baf46764b2fe0285e616595ffe52df21", + "installs": 139916 + }, + { + "externalId": "lllllllama/ai-paper-reproduction-skill/minimal-run-and-audit", + "githubOwnerId": 150129845, + "owner": "lllllllama", + "repo": "ai-paper-reproduction-skill", + "slug": "minimal-run-and-audit", + "displayName": "minimal-run-and-audit", + "sourceUrl": "https://www.skills.sh/lllllllama/ai-paper-reproduction-skill/minimal-run-and-audit", + "githubRepoUrl": "https://github.com/lllllllama/ai-paper-reproduction-skill", + "sourceContentHash": "9a5bd70a9e6ddf6bf0d9806b3459a8f706131944c0aad52f23752f73b265c12f", + "installs": 139861 + }, + { + "externalId": "lllllllama/ai-paper-reproduction-skill/env-and-assets-bootstrap", + "githubOwnerId": 150129845, + "owner": "lllllllama", + "repo": "ai-paper-reproduction-skill", + "slug": "env-and-assets-bootstrap", + "displayName": "env-and-assets-bootstrap", + "sourceUrl": "https://www.skills.sh/lllllllama/ai-paper-reproduction-skill/env-and-assets-bootstrap", + "githubRepoUrl": "https://github.com/lllllllama/ai-paper-reproduction-skill", + "sourceContentHash": "fa5652ae9a2e12c6ecf4c92a73aa2145d16dd5d42df7437f089d7cad28178d85", + "installs": 139859 + }, + { + "externalId": "mattpocock/skills/migrate-to-shoehorn", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "migrate-to-shoehorn", + "displayName": "migrate-to-shoehorn", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/migrate-to-shoehorn", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "a556e54775ab12f2ee8a9ac898fd70de4ae07fd3819039bc8fce334f9e56b54f", + "installs": 139855 + }, + { + "externalId": "mattpocock/skills/writing-beats", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "writing-beats", + "displayName": "writing-beats", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/writing-beats", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "246e6b3ba38640613e0547d254fe8d676798585be95b2f0fa02200a4fce9942c", + "installs": 139650 + }, + { + "externalId": "obra/superpowers/finishing-a-development-branch", + "githubOwnerId": 45416, + "owner": "obra", + "repo": "superpowers", + "slug": "finishing-a-development-branch", + "displayName": "finishing-a-development-branch", + "sourceUrl": "https://www.skills.sh/obra/superpowers/finishing-a-development-branch", + "githubRepoUrl": "https://github.com/obra/superpowers", + "sourceContentHash": "0c140b3e01a984ea1a3d2b79b83406b137847597eaa9a740f6a71a94bccafda4", + "installs": 138153 + }, + { + "externalId": "anthropics/skills/xlsx", + "githubOwnerId": 76263028, + "owner": "anthropics", + "repo": "skills", + "slug": "xlsx", + "displayName": "xlsx", + "sourceUrl": "https://www.skills.sh/anthropics/skills/xlsx", + "githubRepoUrl": "https://github.com/anthropics/skills", + "sourceContentHash": "6c80f7a4deecfcb94a22c7181e8fc09166d109aba2454b8ebeac51adf179588c", + "installs": 137519 + }, + { + "externalId": "heygen-com/hyperframes/media-use", + "githubOwnerId": 65394575, + "owner": "heygen-com", + "repo": "hyperframes", + "slug": "media-use", + "displayName": "media-use", + "sourceUrl": "https://www.skills.sh/heygen-com/hyperframes/media-use", + "githubRepoUrl": "https://github.com/heygen-com/hyperframes", + "sourceContentHash": "c2533cda22f6d20c143b5e6e247b2d2fe0e3ca4de8d94c72a4cdbcf2f35e5adc", + "installs": 131126 + }, + { + "externalId": "mattpocock/skills/resolving-merge-conflicts", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "resolving-merge-conflicts", + "displayName": "resolving-merge-conflicts", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/resolving-merge-conflicts", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "b1fd57782c4699ca4b19dd4322e45c84c3e9d210d3b73d65f53230fe56ee779c", + "installs": 131028 + }, + { + "externalId": "mattpocock/skills/research", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "research", + "displayName": "research", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/research", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "fb6fc6446ea4dc9db6e7863b89b77a0b4b0f1beca4a08c23d0d319fe96cd08c8", + "installs": 127755 + }, + { + "externalId": "getpaperclipai/paperclip/design-guide", + "githubOwnerId": 299072779, + "owner": "getpaperclipai", + "repo": "paperclip", + "slug": "design-guide", + "displayName": "design-guide", + "sourceUrl": "https://www.skills.sh/getpaperclipai/paperclip/design-guide", + "githubRepoUrl": "https://github.com/getpaperclipai/paperclip", + "sourceContentHash": "eceddf811d58d0cb2ea97f3f7fde5ab84ae5c031cdf87e04d4c3c2dc2f1965a3", + "installs": 126544 + }, + { + "externalId": "getpaperclipai/paperclip/paperclip-create-agent", + "githubOwnerId": 299072779, + "owner": "getpaperclipai", + "repo": "paperclip", + "slug": "paperclip-create-agent", + "displayName": "paperclip-create-agent", + "sourceUrl": "https://www.skills.sh/getpaperclipai/paperclip/paperclip-create-agent", + "githubRepoUrl": "https://github.com/getpaperclipai/paperclip", + "sourceContentHash": "6f943110ede00654dd57dae9669794b4ef848d68dc7400ba478f5ac40f47a1e7", + "installs": 126277 + }, + { + "externalId": "getpaperclipai/paperclip/para-memory-files", + "githubOwnerId": 299072779, + "owner": "getpaperclipai", + "repo": "paperclip", + "slug": "para-memory-files", + "displayName": "para-memory-files", + "sourceUrl": "https://www.skills.sh/getpaperclipai/paperclip/para-memory-files", + "githubRepoUrl": "https://github.com/getpaperclipai/paperclip", + "sourceContentHash": "9267b5b34072199c2f274e3d7bcea88b75b1f29cab9ced4c4fcde7fa57ffb9ab", + "installs": 126276 + }, + { + "externalId": "getpaperclipai/paperclip/paperclip-converting-plans-to-tasks", + "githubOwnerId": 299072779, + "owner": "getpaperclipai", + "repo": "paperclip", + "slug": "paperclip-converting-plans-to-tasks", + "displayName": "paperclip-converting-plans-to-tasks", + "sourceUrl": "https://www.skills.sh/getpaperclipai/paperclip/paperclip-converting-plans-to-tasks", + "githubRepoUrl": "https://github.com/getpaperclipai/paperclip", + "sourceContentHash": "c297f33bcb6368e691b452950da6461da30c2f1b37715467396cef6f04164844", + "installs": 126272 + }, + { + "externalId": "getpaperclipai/paperclip/paperclip", + "githubOwnerId": 299072779, + "owner": "getpaperclipai", + "repo": "paperclip", + "slug": "paperclip", + "displayName": "paperclip", + "sourceUrl": "https://www.skills.sh/getpaperclipai/paperclip/paperclip", + "githubRepoUrl": "https://github.com/getpaperclipai/paperclip", + "sourceContentHash": "26d74a70c16ac6f2193dfb9b0012e6c397ac1e3c1ac1431fb36492e17414c9bc", + "installs": 126271 + }, + { + "externalId": "getpaperclipai/paperclip/paperclip-board", + "githubOwnerId": 299072779, + "owner": "getpaperclipai", + "repo": "paperclip", + "slug": "paperclip-board", + "displayName": "paperclip-board", + "sourceUrl": "https://www.skills.sh/getpaperclipai/paperclip/paperclip-board", + "githubRepoUrl": "https://github.com/getpaperclipai/paperclip", + "sourceContentHash": "fa8738a5d8c66a4ee1e6ac9b4f92bebd3b338230ab1c7b85e2e114c843c06738", + "installs": 126270 + }, + { + "externalId": "arvindrk/extract-design-system/extract-design-system", + "githubOwnerId": 7334372, + "owner": "arvindrk", + "repo": "extract-design-system", + "slug": "extract-design-system", + "displayName": "extract-design-system", + "sourceUrl": "https://www.skills.sh/arvindrk/extract-design-system/extract-design-system", + "githubRepoUrl": "https://github.com/arvindrk/extract-design-system", + "sourceContentHash": "f6cd3a8445c977cc9f4600a4989fc09d4bb10b0af94865a2685111f0f024d552", + "installs": 125750 + }, + { + "externalId": "mattpocock/skills/wayfinder", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "wayfinder", + "displayName": "wayfinder", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/wayfinder", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "670f2ebe0aa854d6f8cda2659c6415621cf46307dcf06b42b48e4ba4d1b60385", + "installs": 119852 + }, + { + "externalId": "leonxlnx/taste-skill/design-taste-frontend-v1", + "githubOwnerId": 219127460, + "owner": "leonxlnx", + "repo": "taste-skill", + "slug": "design-taste-frontend-v1", + "displayName": "design-taste-frontend-v1", + "sourceUrl": "https://www.skills.sh/leonxlnx/taste-skill/design-taste-frontend-v1", + "githubRepoUrl": "https://github.com/leonxlnx/taste-skill", + "sourceContentHash": "8dbcb90df1b95f2c4af24575aa0dfeb55c8eb504d301e8bf09cb0444f6780333", + "installs": 119244 + }, + { + "externalId": "anthropics/skills/webapp-testing", + "githubOwnerId": 76263028, + "owner": "anthropics", + "repo": "skills", + "slug": "webapp-testing", + "displayName": "webapp-testing", + "sourceUrl": "https://www.skills.sh/anthropics/skills/webapp-testing", + "githubRepoUrl": "https://github.com/anthropics/skills", + "sourceContentHash": "cfb23ef0c399c36e9247142a7ab5ac0e03c03e1c9f047b0f7502232a2c50b564", + "installs": 118730 + }, + { + "externalId": "heygen-com/hyperframes/general-video", + "githubOwnerId": 65394575, + "owner": "heygen-com", + "repo": "hyperframes", + "slug": "general-video", + "displayName": "general-video", + "sourceUrl": "https://www.skills.sh/heygen-com/hyperframes/general-video", + "githubRepoUrl": "https://github.com/heygen-com/hyperframes", + "sourceContentHash": "1194ac1ea4e0c90dad61f189c621d60adc26bad0c9b7b24bf003aa32ab01507c", + "installs": 115984 + }, + { + "externalId": "coreyhaines31/marketingskills/marketing-psychology", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "marketing-psychology", + "displayName": "marketing-psychology", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/marketing-psychology", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "60c5bfa8ca6b5895304ba22e6f02ca0d9f32df785cffca2640b585a5e4f3ec11", + "installs": 114642 + }, + { + "externalId": "heygen-com/hyperframes/product-launch-video", + "githubOwnerId": 65394575, + "owner": "heygen-com", + "repo": "hyperframes", + "slug": "product-launch-video", + "displayName": "product-launch-video", + "sourceUrl": "https://www.skills.sh/heygen-com/hyperframes/product-launch-video", + "githubRepoUrl": "https://github.com/heygen-com/hyperframes", + "sourceContentHash": "436d08f6f934dd776e92f0a16ee07f5be47cc319e2347a4681983ea3fd48e857", + "installs": 114305 + }, + { + "externalId": "heygen-com/hyperframes/faceless-explainer", + "githubOwnerId": 65394575, + "owner": "heygen-com", + "repo": "hyperframes", + "slug": "faceless-explainer", + "displayName": "faceless-explainer", + "sourceUrl": "https://www.skills.sh/heygen-com/hyperframes/faceless-explainer", + "githubRepoUrl": "https://github.com/heygen-com/hyperframes", + "sourceContentHash": "5013dc9f544e385b11a857acde0888b74ee4963bb6c3698d130e4848be30c060", + "installs": 113885 + }, + { + "externalId": "heygen-com/hyperframes/motion-graphics", + "githubOwnerId": 65394575, + "owner": "heygen-com", + "repo": "hyperframes", + "slug": "motion-graphics", + "displayName": "motion-graphics", + "sourceUrl": "https://www.skills.sh/heygen-com/hyperframes/motion-graphics", + "githubRepoUrl": "https://github.com/heygen-com/hyperframes", + "sourceContentHash": "79a65357280bcdf044843b751a79ef65878636c000aceb86aed952a8efbba3d9", + "installs": 113445 + }, + { + "externalId": "firebase/agent-skills/firebase-basics", + "githubOwnerId": 1335026, + "owner": "firebase", + "repo": "agent-skills", + "slug": "firebase-basics", + "displayName": "firebase-basics", + "sourceUrl": "https://www.skills.sh/firebase/agent-skills/firebase-basics", + "githubRepoUrl": "https://github.com/firebase/agent-skills", + "sourceContentHash": "39bfb2feffa6026a392b6ffe9905e0396c2cad3e72644e98e65b4a40a91eb88a", + "installs": 110935 + }, + { + "externalId": "heygen-com/hyperframes/pr-to-video", + "githubOwnerId": 65394575, + "owner": "heygen-com", + "repo": "hyperframes", + "slug": "pr-to-video", + "displayName": "pr-to-video", + "sourceUrl": "https://www.skills.sh/heygen-com/hyperframes/pr-to-video", + "githubRepoUrl": "https://github.com/heygen-com/hyperframes", + "sourceContentHash": "2b9c1d44c2d9ee3212c91b5f05cde06cd7309b6d7b422e1248ab483a9e2a0b0b", + "installs": 110610 + }, + { + "externalId": "firebase/agent-skills/firebase-auth-basics", + "githubOwnerId": 1335026, + "owner": "firebase", + "repo": "agent-skills", + "slug": "firebase-auth-basics", + "displayName": "firebase-auth-basics", + "sourceUrl": "https://www.skills.sh/firebase/agent-skills/firebase-auth-basics", + "githubRepoUrl": "https://github.com/firebase/agent-skills", + "sourceContentHash": "5af064f890b82d0f5aa3d7e0c6ddb88f2ec946d15b882f7981d4fef3ca3b6e2b", + "installs": 110081 + }, + { + "externalId": "coreyhaines31/marketingskills/content-strategy", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "content-strategy", + "displayName": "content-strategy", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/content-strategy", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "ad2ea53db451e61b61b886b631a305ab5c8bfc7da54e961b61d494bfc8d09c52", + "installs": 110058 + }, + { + "externalId": "heygen-com/hyperframes/embedded-captions", + "githubOwnerId": 65394575, + "owner": "heygen-com", + "repo": "hyperframes", + "slug": "embedded-captions", + "displayName": "embedded-captions", + "sourceUrl": "https://www.skills.sh/heygen-com/hyperframes/embedded-captions", + "githubRepoUrl": "https://github.com/heygen-com/hyperframes", + "sourceContentHash": "5cf99e184818ef547db224c59c4a68e0bc01da149c8b04aadb75f16b891a86d8", + "installs": 108464 + }, + { + "externalId": "firebase/agent-skills/firebase-hosting-basics", + "githubOwnerId": 1335026, + "owner": "firebase", + "repo": "agent-skills", + "slug": "firebase-hosting-basics", + "displayName": "firebase-hosting-basics", + "sourceUrl": "https://www.skills.sh/firebase/agent-skills/firebase-hosting-basics", + "githubRepoUrl": "https://github.com/firebase/agent-skills", + "sourceContentHash": "12a2c18706172fad9206d671430363ab46312d7ad66d455c73e0be05873205b5", + "installs": 107501 + }, + { + "externalId": "firebase/agent-skills/firebase-app-hosting-basics", + "githubOwnerId": 1335026, + "owner": "firebase", + "repo": "agent-skills", + "slug": "firebase-app-hosting-basics", + "displayName": "firebase-app-hosting-basics", + "sourceUrl": "https://www.skills.sh/firebase/agent-skills/firebase-app-hosting-basics", + "githubRepoUrl": "https://github.com/firebase/agent-skills", + "sourceContentHash": "8f4b617f77b0bc2973714e07dcdbb6604ae139879645add094904dfb7b53c868", + "installs": 107099 + }, + { + "externalId": "firebase/agent-skills/firebase-data-connect", + "githubOwnerId": 1335026, + "owner": "firebase", + "repo": "agent-skills", + "slug": "firebase-data-connect", + "displayName": "firebase-data-connect", + "sourceUrl": "https://www.skills.sh/firebase/agent-skills/firebase-data-connect", + "githubRepoUrl": "https://github.com/firebase/agent-skills", + "sourceContentHash": "8eb8fba2dd0d337e3a4610b8121beb6a6f7c9dfcfe909b499fb26d52b7811f9c", + "installs": 106576 + }, + { + "externalId": "browser-act/skills/browser-act", + "githubOwnerId": 255086931, + "owner": "browser-act", + "repo": "skills", + "slug": "browser-act", + "displayName": "browser-act", + "sourceUrl": "https://www.skills.sh/browser-act/skills/browser-act", + "githubRepoUrl": "https://github.com/browser-act/skills", + "sourceContentHash": "2b8920f481b04c241eb66b02696c20f8c5bc4116c70f3288e896cd3261170e07", + "installs": 106293 + }, + { + "externalId": "coreyhaines31/marketingskills/programmatic-seo", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "programmatic-seo", + "displayName": "programmatic-seo", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/programmatic-seo", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "7a56d7881c58730c064b7c6d50bdbb5156b1360841d7d661ad35860759fad134", + "installs": 105637 + }, + { + "externalId": "coreyhaines31/marketingskills/marketing-ideas", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "marketing-ideas", + "displayName": "marketing-ideas", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/marketing-ideas", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "4e5d5ece175ec40318194d29d8d1da2d2aefccc3c81a9a17607d3ac33d989e89", + "installs": 102652 + }, + { + "externalId": "mattpocock/skills/to-spec", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "to-spec", + "displayName": "to-spec", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/to-spec", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "cd37a7e35c3812350c9f5c7bcebac95b3925dcff6a3b0655b3aea87229c52447", + "installs": 102245 + }, + { + "externalId": "heygen-com/hyperframes/slideshow", + "githubOwnerId": 65394575, + "owner": "heygen-com", + "repo": "hyperframes", + "slug": "slideshow", + "displayName": "slideshow", + "sourceUrl": "https://www.skills.sh/heygen-com/hyperframes/slideshow", + "githubRepoUrl": "https://github.com/heygen-com/hyperframes", + "sourceContentHash": "7b31f30f522435f18a89ab447b6f7f179a6021c44c501ef3d31a513d05ddfb74", + "installs": 101177 + }, + { + "externalId": "mattpocock/skills/to-tickets", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "to-tickets", + "displayName": "to-tickets", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/to-tickets", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "59da76363d04b448f3d1e24f020665e313acce3718c7f1eb5318e04bb5afc41f", + "installs": 100909 + }, + { + "externalId": "heygen-com/hyperframes/website-to-video", + "githubOwnerId": 65394575, + "owner": "heygen-com", + "repo": "hyperframes", + "slug": "website-to-video", + "displayName": "website-to-video", + "sourceUrl": "https://www.skills.sh/heygen-com/hyperframes/website-to-video", + "githubRepoUrl": "https://github.com/heygen-com/hyperframes", + "sourceContentHash": "65eee4dd3eda3f43d10e0a95541c741ae18a454d8246b106d61dff707ec1ea28", + "installs": 99725 + }, + { + "externalId": "microsoft/azure-skills/azure-observability", + "githubOwnerId": 6154722, + "owner": "microsoft", + "repo": "azure-skills", + "slug": "azure-observability", + "displayName": "azure-observability", + "sourceUrl": "https://www.skills.sh/microsoft/azure-skills/azure-observability", + "githubRepoUrl": "https://github.com/microsoft/azure-skills", + "sourceContentHash": "2abb5ab581d45e964a0c54a5ddac24f312e8e6cc86cf07f885beffeda34ca2f7", + "installs": 98244 + }, + { + "externalId": "coreyhaines31/marketingskills/copy-editing", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "copy-editing", + "displayName": "copy-editing", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/copy-editing", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "5e9cde415873c984aa8b14a0bcc30ae0449e6f796b7d0be97a9441c33fb32c54", + "installs": 97358 + }, + { + "externalId": "heygen-com/hyperframes/music-to-video", + "githubOwnerId": 65394575, + "owner": "heygen-com", + "repo": "hyperframes", + "slug": "music-to-video", + "displayName": "music-to-video", + "sourceUrl": "https://www.skills.sh/heygen-com/hyperframes/music-to-video", + "githubRepoUrl": "https://github.com/heygen-com/hyperframes", + "sourceContentHash": "7ca2624c4526b0962a45ba39ada1a7b362b5e1f982acedfcc344bdbc947a9719", + "installs": 95771 + }, + { + "externalId": "vercel-labs/agent-skills/deploy-to-vercel", + "githubOwnerId": 108547162, + "owner": "vercel-labs", + "repo": "agent-skills", + "slug": "deploy-to-vercel", + "displayName": "deploy-to-vercel", + "sourceUrl": "https://www.skills.sh/vercel-labs/agent-skills/deploy-to-vercel", + "githubRepoUrl": "https://github.com/vercel-labs/agent-skills", + "sourceContentHash": "03e0eaaa9bf13ba1e7ffa387f5893de6f324c0868c627001f179395a8feaa7c9", + "installs": 94941 + }, + { + "externalId": "coreyhaines31/marketingskills/ai-seo", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "ai-seo", + "displayName": "ai-seo", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/ai-seo", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "983f00693cbd94e282e6f6b3f26b31c72f4ecb2bbec9f45654a47c0286284ae6", + "installs": 94124 + }, + { + "externalId": "heygen-com/hyperframes/gsap", + "githubOwnerId": 65394575, + "owner": "heygen-com", + "repo": "hyperframes", + "slug": "gsap", + "displayName": "gsap", + "sourceUrl": "https://www.skills.sh/heygen-com/hyperframes/gsap", + "githubRepoUrl": "https://github.com/heygen-com/hyperframes", + "sourceContentHash": "ef2658b3705d3fd5c8e38d197b31521553a0bee3b40bda2ab70bf22bd6b88854", + "installs": 93191 + }, + { + "externalId": "microsoft/playwright-cli/playwright-cli", + "githubOwnerId": 6154722, + "owner": "microsoft", + "repo": "playwright-cli", + "slug": "playwright-cli", + "displayName": "playwright-cli", + "sourceUrl": "https://www.skills.sh/microsoft/playwright-cli/playwright-cli", + "githubRepoUrl": "https://github.com/microsoft/playwright-cli", + "sourceContentHash": "ca32d369efecdd52dcee8cf0afecfa21bce7b0e7acac1bc42bd078f46c489d14", + "installs": 92778 + }, + { + "externalId": "mattpocock/skills/review", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "review", + "displayName": "review", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/review", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "9b7ad036e79f0c2a6323a213abfdc2834580fbd9d312837a04399e1568435b35", + "installs": 92131 + }, + { + "externalId": "anthropics/skills/mcp-builder", + "githubOwnerId": 76263028, + "owner": "anthropics", + "repo": "skills", + "slug": "mcp-builder", + "displayName": "mcp-builder", + "sourceUrl": "https://www.skills.sh/anthropics/skills/mcp-builder", + "githubRepoUrl": "https://github.com/anthropics/skills", + "sourceContentHash": "46f45bf345f73cff0afd292053c96c83f0e3f18610924812de3eb45ff2904063", + "installs": 92038 + }, + { + "externalId": "higgsfield-ai/skills/higgsfield-generate", + "githubOwnerId": 146205480, + "owner": "higgsfield-ai", + "repo": "skills", + "slug": "higgsfield-generate", + "displayName": "higgsfield-generate", + "sourceUrl": "https://www.skills.sh/higgsfield-ai/skills/higgsfield-generate", + "githubRepoUrl": "https://github.com/higgsfield-ai/skills", + "sourceContentHash": "209bf76f277b4295e5a1598f0db2f60e5be77b985ea8711afc0e63eff77e49a1", + "installs": 91536 + }, + { + "externalId": "heygen-com/hyperframes/website-to-hyperframes", + "githubOwnerId": 65394575, + "owner": "heygen-com", + "repo": "hyperframes", + "slug": "website-to-hyperframes", + "displayName": "website-to-hyperframes", + "sourceUrl": "https://www.skills.sh/heygen-com/hyperframes/website-to-hyperframes", + "githubRepoUrl": "https://github.com/heygen-com/hyperframes", + "sourceContentHash": "efd61bd08f79921dba79e353c369c5c28185a47d215a4c2e33974fdbfe45cb1d", + "installs": 90405 + }, + { + "externalId": "heygen-com/hyperframes/hyperframes-keyframes", + "githubOwnerId": 65394575, + "owner": "heygen-com", + "repo": "hyperframes", + "slug": "hyperframes-keyframes", + "displayName": "hyperframes-keyframes", + "sourceUrl": "https://www.skills.sh/heygen-com/hyperframes/hyperframes-keyframes", + "githubRepoUrl": "https://github.com/heygen-com/hyperframes", + "sourceContentHash": "28fab4bfffdeaafa712bc87591259885a0fe3df182dfa1d72a4e23425954054d", + "installs": 90391 + }, + { + "externalId": "anthropics/skills/canvas-design", + "githubOwnerId": 76263028, + "owner": "anthropics", + "repo": "skills", + "slug": "canvas-design", + "displayName": "canvas-design", + "sourceUrl": "https://www.skills.sh/anthropics/skills/canvas-design", + "githubRepoUrl": "https://github.com/anthropics/skills", + "sourceContentHash": "57e25c208a40d3385e4dcadf40aa2b4cae512b856e6a5f5ecda136531d868cc4", + "installs": 88986 + }, + { + "externalId": "get-convex/agent-skills/convex-quickstart", + "githubOwnerId": 81530787, + "owner": "get-convex", + "repo": "agent-skills", + "slug": "convex-quickstart", + "displayName": "convex-quickstart", + "sourceUrl": "https://www.skills.sh/get-convex/agent-skills/convex-quickstart", + "githubRepoUrl": "https://github.com/get-convex/agent-skills", + "sourceContentHash": "1d09b07e6f691bcd9f1306b7150a701294c2c6cdd8f0cb937ba38ceec8e16b3d", + "installs": 88467 + }, + { + "externalId": "get-convex/agent-skills/convex-performance-audit", + "githubOwnerId": 81530787, + "owner": "get-convex", + "repo": "agent-skills", + "slug": "convex-performance-audit", + "displayName": "convex-performance-audit", + "sourceUrl": "https://www.skills.sh/get-convex/agent-skills/convex-performance-audit", + "githubRepoUrl": "https://github.com/get-convex/agent-skills", + "sourceContentHash": "55383f56da7cdbfba298b21381265adc8ad75171ad78f5b5e37a4334a2a3998c", + "installs": 88109 + }, + { + "externalId": "get-convex/agent-skills/convex-setup-auth", + "githubOwnerId": 81530787, + "owner": "get-convex", + "repo": "agent-skills", + "slug": "convex-setup-auth", + "displayName": "convex-setup-auth", + "sourceUrl": "https://www.skills.sh/get-convex/agent-skills/convex-setup-auth", + "githubRepoUrl": "https://github.com/get-convex/agent-skills", + "sourceContentHash": "e33cb5915f6b4f2a70cfd5a61d16160b4f60eeb7dfaf08ed45e3eafde53863aa", + "installs": 87770 + }, + { + "externalId": "get-convex/agent-skills/convex-migration-helper", + "githubOwnerId": 81530787, + "owner": "get-convex", + "repo": "agent-skills", + "slug": "convex-migration-helper", + "displayName": "convex-migration-helper", + "sourceUrl": "https://www.skills.sh/get-convex/agent-skills/convex-migration-helper", + "githubRepoUrl": "https://github.com/get-convex/agent-skills", + "sourceContentHash": "d5cd7cd577159ac4825218c73798078e335896bad1d3e10d60d73e7d00d39bd1", + "installs": 87728 + }, + { + "externalId": "get-convex/agent-skills/convex-create-component", + "githubOwnerId": 81530787, + "owner": "get-convex", + "repo": "agent-skills", + "slug": "convex-create-component", + "displayName": "convex-create-component", + "sourceUrl": "https://www.skills.sh/get-convex/agent-skills/convex-create-component", + "githubRepoUrl": "https://github.com/get-convex/agent-skills", + "sourceContentHash": "c75be5a43d36a1d117386a96f82a95d248e43da791580fafe637dbf8a09bc97a", + "installs": 87720 + }, + { + "externalId": "pbakaus/impeccable/polish", + "githubOwnerId": 43004, + "owner": "pbakaus", + "repo": "impeccable", + "slug": "polish", + "displayName": "polish", + "sourceUrl": "https://www.skills.sh/pbakaus/impeccable/polish", + "githubRepoUrl": "https://github.com/pbakaus/impeccable", + "sourceContentHash": "429e50f410dbe39b932e7fc834aab935377c92e6dc4f7f8a75200da92e868e67", + "installs": 86577 + }, + { + "externalId": "mattpocock/skills/loop-me", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "loop-me", + "displayName": "loop-me", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/loop-me", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "dbf05f8b1c1ca26101bd8417113bd82275ae4fa878bfc4cc187be3b24a58a432", + "installs": 86046 + }, + { + "externalId": "browser-use/browser-use/browser-use", + "githubOwnerId": 192012301, + "owner": "browser-use", + "repo": "browser-use", + "slug": "browser-use", + "displayName": "browser-use", + "sourceUrl": "https://www.skills.sh/browser-use/browser-use/browser-use", + "githubRepoUrl": "https://github.com/browser-use/browser-use", + "sourceContentHash": "93c92027a842071cf2f6c86e5ead1eeee658504cd079af797b6a014077774733", + "installs": 85792 + }, + { + "externalId": "coreyhaines31/marketingskills/ad-creative", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "ad-creative", + "displayName": "ad-creative", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/ad-creative", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "e9a363c5ea9d9941b34bee96b2339d0bed8e3dcbaf71730bf0ae60f5cb3542cf", + "installs": 84859 + }, + { + "externalId": "browser-act/skills/browser-act-skill-forge", + "githubOwnerId": 255086931, + "owner": "browser-act", + "repo": "skills", + "slug": "browser-act-skill-forge", + "displayName": "browser-act-skill-forge", + "sourceUrl": "https://www.skills.sh/browser-act/skills/browser-act-skill-forge", + "githubRepoUrl": "https://github.com/browser-act/skills", + "sourceContentHash": "f9dcc2336ba94da6b6e5b245afb442a472f379b9c3541396baa3bfb4b4bd823b", + "installs": 84156 + }, + { + "externalId": "pbakaus/impeccable/critique", + "githubOwnerId": 43004, + "owner": "pbakaus", + "repo": "impeccable", + "slug": "critique", + "displayName": "critique", + "sourceUrl": "https://www.skills.sh/pbakaus/impeccable/critique", + "githubRepoUrl": "https://github.com/pbakaus/impeccable", + "sourceContentHash": "dd207791eb5c7de761c520a3bfe4d8449f0b1d9a52f0ae3f2368d36db4a1ef1e", + "installs": 84000 + }, + { + "externalId": "pbakaus/impeccable/audit", + "githubOwnerId": 43004, + "owner": "pbakaus", + "repo": "impeccable", + "slug": "audit", + "displayName": "audit", + "sourceUrl": "https://www.skills.sh/pbakaus/impeccable/audit", + "githubRepoUrl": "https://github.com/pbakaus/impeccable", + "sourceContentHash": "3231433238acf0060a2d2d3c573c186b23103500676861029a6d2f2c862342b9", + "installs": 83222 + }, + { + "externalId": "coreyhaines31/marketingskills/cold-email", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "cold-email", + "displayName": "cold-email", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/cold-email", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "e4854a62bf51eecc3028f3cb655f30640d9abce448762aa109b9554f8dd5fac6", + "installs": 83182 + }, + { + "externalId": "anthropics/skills/web-artifacts-builder", + "githubOwnerId": 76263028, + "owner": "anthropics", + "repo": "skills", + "slug": "web-artifacts-builder", + "displayName": "web-artifacts-builder", + "sourceUrl": "https://www.skills.sh/anthropics/skills/web-artifacts-builder", + "githubRepoUrl": "https://github.com/anthropics/skills", + "sourceContentHash": "410aebde327dc6b4f7d01a446c1c6a1dbbb0ca0872708bfd73eb8a045c691b62", + "installs": 83097 + }, + { + "externalId": "pbakaus/impeccable/animate", + "githubOwnerId": 43004, + "owner": "pbakaus", + "repo": "impeccable", + "slug": "animate", + "displayName": "animate", + "sourceUrl": "https://www.skills.sh/pbakaus/impeccable/animate", + "githubRepoUrl": "https://github.com/pbakaus/impeccable", + "sourceContentHash": "04d00a6adfc6fda39bf2644a9f19ca653c408dfe234013cf492a928e1f78445e", + "installs": 83083 + }, + { + "externalId": "pbakaus/impeccable/adapt", + "githubOwnerId": 43004, + "owner": "pbakaus", + "repo": "impeccable", + "slug": "adapt", + "displayName": "adapt", + "sourceUrl": "https://www.skills.sh/pbakaus/impeccable/adapt", + "githubRepoUrl": "https://github.com/pbakaus/impeccable", + "sourceContentHash": "1e351fbeeb5fe35909a8a2f97f03e89029075c2cb39ff8a608743f337a46cd42", + "installs": 83066 + }, + { + "externalId": "vercel-labs/agent-skills/vercel-react-view-transitions", + "githubOwnerId": 108547162, + "owner": "vercel-labs", + "repo": "agent-skills", + "slug": "vercel-react-view-transitions", + "displayName": "vercel-react-view-transitions", + "sourceUrl": "https://www.skills.sh/vercel-labs/agent-skills/vercel-react-view-transitions", + "githubRepoUrl": "https://github.com/vercel-labs/agent-skills", + "sourceContentHash": "c8952fc9127fa0564d0cb71dccb4215a5608ac933b5831104f09173a97a46f85", + "installs": 82724 + }, + { + "externalId": "pbakaus/impeccable/clarify", + "githubOwnerId": 43004, + "owner": "pbakaus", + "repo": "impeccable", + "slug": "clarify", + "displayName": "clarify", + "sourceUrl": "https://www.skills.sh/pbakaus/impeccable/clarify", + "githubRepoUrl": "https://github.com/pbakaus/impeccable", + "sourceContentHash": "cd57858e257e15a13f3622c972991a9fb923dda92a12cfe99c44ce039380e469", + "installs": 82554 + }, + { + "externalId": "pbakaus/impeccable/optimize", + "githubOwnerId": 43004, + "owner": "pbakaus", + "repo": "impeccable", + "slug": "optimize", + "displayName": "optimize", + "sourceUrl": "https://www.skills.sh/pbakaus/impeccable/optimize", + "githubRepoUrl": "https://github.com/pbakaus/impeccable", + "sourceContentHash": "b12dc925b86af7c573c5f860eb5a2c99b0cbf4969d27021e7fef558b9c7b986d", + "installs": 82406 + }, + { + "externalId": "pbakaus/impeccable/colorize", + "githubOwnerId": 43004, + "owner": "pbakaus", + "repo": "impeccable", + "slug": "colorize", + "displayName": "colorize", + "sourceUrl": "https://www.skills.sh/pbakaus/impeccable/colorize", + "githubRepoUrl": "https://github.com/pbakaus/impeccable", + "sourceContentHash": "6d05e7ef645743beda25a161b6555d3ab6f334c2cd4ff73c55f8f95677bffa1f", + "installs": 82221 + }, + { + "externalId": "pbakaus/impeccable/bolder", + "githubOwnerId": 43004, + "owner": "pbakaus", + "repo": "impeccable", + "slug": "bolder", + "displayName": "bolder", + "sourceUrl": "https://www.skills.sh/pbakaus/impeccable/bolder", + "githubRepoUrl": "https://github.com/pbakaus/impeccable", + "sourceContentHash": "2664aa7a6ada54ccfc4288deae583d94020b9a88f91fbea8a6514d19eefbe4df", + "installs": 81339 + }, + { + "externalId": "pbakaus/impeccable/delight", + "githubOwnerId": 43004, + "owner": "pbakaus", + "repo": "impeccable", + "slug": "delight", + "displayName": "delight", + "sourceUrl": "https://www.skills.sh/pbakaus/impeccable/delight", + "githubRepoUrl": "https://github.com/pbakaus/impeccable", + "sourceContentHash": "732cf75070803810344c99c45bf8e9fe57d8f72431e77e5cba06bdd7b3e2a57d", + "installs": 81175 + }, + { + "externalId": "pbakaus/impeccable/distill", + "githubOwnerId": 43004, + "owner": "pbakaus", + "repo": "impeccable", + "slug": "distill", + "displayName": "distill", + "sourceUrl": "https://www.skills.sh/pbakaus/impeccable/distill", + "githubRepoUrl": "https://github.com/pbakaus/impeccable", + "sourceContentHash": "a08fa6f4bdf2d2bc326473da365d2fdef47b43cd4b0ca6c077bca284c6145962", + "installs": 80786 + }, + { + "externalId": "coreyhaines31/marketingskills/site-architecture", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "site-architecture", + "displayName": "site-architecture", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/site-architecture", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "b2b8db4979d253c479d09304f5fbe7c2bf3a1a10ebed1e058839920c4aadc82a", + "installs": 80689 + }, + { + "externalId": "pbakaus/impeccable/quieter", + "githubOwnerId": 43004, + "owner": "pbakaus", + "repo": "impeccable", + "slug": "quieter", + "displayName": "quieter", + "sourceUrl": "https://www.skills.sh/pbakaus/impeccable/quieter", + "githubRepoUrl": "https://github.com/pbakaus/impeccable", + "sourceContentHash": "63e84d6e6fe66e08f2ab17a95362462790d4901519732f6f6636c6bf3fd5e0af", + "installs": 79968 + }, + { + "externalId": "heygen-com/hyperframes/talking-head-recut", + "githubOwnerId": 65394575, + "owner": "heygen-com", + "repo": "hyperframes", + "slug": "talking-head-recut", + "displayName": "talking-head-recut", + "sourceUrl": "https://www.skills.sh/heygen-com/hyperframes/talking-head-recut", + "githubRepoUrl": "https://github.com/heygen-com/hyperframes", + "sourceContentHash": "1313d91587a6c6e8714aaef357d744d57861ebb9a02235a661eb36c7fc54baf3", + "installs": 79756 + }, + { + "externalId": "coreyhaines31/marketingskills/sales-enablement", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "sales-enablement", + "displayName": "sales-enablement", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/sales-enablement", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "5a07cec546acc7f162964874f4b35a6767b181e6cc547091842a169eba96e1ba", + "installs": 79075 + }, + { + "externalId": "coreyhaines31/marketingskills/churn-prevention", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "churn-prevention", + "displayName": "churn-prevention", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/churn-prevention", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "eeb53009fb5c3e93bda178d0a0b4697c037501ce17f75fd35f7dd5a49c78c4c6", + "installs": 78862 + }, + { + "externalId": "better-auth/skills/better-auth-best-practices", + "githubOwnerId": 163827765, + "owner": "better-auth", + "repo": "skills", + "slug": "better-auth-best-practices", + "displayName": "better-auth-best-practices", + "sourceUrl": "https://www.skills.sh/better-auth/skills/better-auth-best-practices", + "githubRepoUrl": "https://github.com/better-auth/skills", + "sourceContentHash": "2a42322e2e1e3763c9fa73807275267edee2cce64ade72f22ef074a9a12dccb9", + "installs": 76853 + }, + { + "externalId": "coreyhaines31/marketingskills/revops", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "revops", + "displayName": "revops", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/revops", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "574d96871e28432a1ddf08c0aa19dce30be940e5856b3c44ace82b732ba36360", + "installs": 75958 + }, + { + "externalId": "nexscope-ai/amazon-skills/amazon-product-research", + "githubOwnerId": 265748764, + "owner": "nexscope-ai", + "repo": "amazon-skills", + "slug": "amazon-product-research", + "displayName": "amazon-product-research", + "sourceUrl": "https://www.skills.sh/nexscope-ai/amazon-skills/amazon-product-research", + "githubRepoUrl": "https://github.com/nexscope-ai/amazon-skills", + "sourceContentHash": "6341a9431bf3147b837ce0c6a1e381d468607c372f652c0bd85a717166fc2ff2", + "installs": 75216 + }, + { + "externalId": "firebase/agent-skills/firebase-security-rules-auditor", + "githubOwnerId": 1335026, + "owner": "firebase", + "repo": "agent-skills", + "slug": "firebase-security-rules-auditor", + "displayName": "firebase-security-rules-auditor", + "sourceUrl": "https://www.skills.sh/firebase/agent-skills/firebase-security-rules-auditor", + "githubRepoUrl": "https://github.com/firebase/agent-skills", + "sourceContentHash": "904cab312b2b823c0cd39e56e0efc9354c47a599d964952efdff71809bd15b20", + "installs": 74645 + }, + { + "externalId": "higgsfield-ai/skills/higgsfield-product-photoshoot", + "githubOwnerId": 146205480, + "owner": "higgsfield-ai", + "repo": "skills", + "slug": "higgsfield-product-photoshoot", + "displayName": "higgsfield-product-photoshoot", + "sourceUrl": "https://www.skills.sh/higgsfield-ai/skills/higgsfield-product-photoshoot", + "githubRepoUrl": "https://github.com/higgsfield-ai/skills", + "sourceContentHash": "617f25d1a913c122739d2566819688eaf3d5b8cd77d1e8ce1a92f8f90eec6323", + "installs": 74462 + }, + { + "externalId": "higgsfield-ai/skills/higgsfield-soul-id", + "githubOwnerId": 146205480, + "owner": "higgsfield-ai", + "repo": "skills", + "slug": "higgsfield-soul-id", + "displayName": "higgsfield-soul-id", + "sourceUrl": "https://www.skills.sh/higgsfield-ai/skills/higgsfield-soul-id", + "githubRepoUrl": "https://github.com/higgsfield-ai/skills", + "sourceContentHash": "e39f45919a79c3d17732d512f5519b92a07a911f157f1bdb6fffa18dd0bb6e65", + "installs": 73540 + }, + { + "externalId": "firebase/agent-skills/firebase-ai-logic-basics", + "githubOwnerId": 1335026, + "owner": "firebase", + "repo": "agent-skills", + "slug": "firebase-ai-logic-basics", + "displayName": "firebase-ai-logic-basics", + "sourceUrl": "https://www.skills.sh/firebase/agent-skills/firebase-ai-logic-basics", + "githubRepoUrl": "https://github.com/firebase/agent-skills", + "sourceContentHash": "e55e46aa2e9347582b83a9f7ee1f21bd7028467e501e48003743bf7f03b2e989", + "installs": 73474 + }, + { + "externalId": "heygen-com/hyperframes/css-animations", + "githubOwnerId": 65394575, + "owner": "heygen-com", + "repo": "hyperframes", + "slug": "css-animations", + "displayName": "css-animations", + "sourceUrl": "https://www.skills.sh/heygen-com/hyperframes/css-animations", + "githubRepoUrl": "https://github.com/heygen-com/hyperframes", + "sourceContentHash": "df468749b06a8ec7369cfea69bf71bf65e46cfc376e837e439e26f4230852c6b", + "installs": 73041 + }, + { + "externalId": "higgsfield-ai/skills/higgsfield-marketplace-cards", + "githubOwnerId": 146205480, + "owner": "higgsfield-ai", + "repo": "skills", + "slug": "higgsfield-marketplace-cards", + "displayName": "higgsfield-marketplace-cards", + "sourceUrl": "https://www.skills.sh/higgsfield-ai/skills/higgsfield-marketplace-cards", + "githubRepoUrl": "https://github.com/higgsfield-ai/skills", + "sourceContentHash": "3730ac2b12b2df1f83d7fd4f5ae77e830a3cf280fd5cb624816d7a802e84f9bc", + "installs": 72404 + }, + { + "externalId": "coreyhaines31/marketingskills/lead-magnets", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "lead-magnets", + "displayName": "lead-magnets", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/lead-magnets", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "4dc63e4f7617082d9656e1478475d9b57ce4391c2eff501b97a63acc0de3308e", + "installs": 72120 + }, + { + "externalId": "heygen-com/hyperframes/tailwind", + "githubOwnerId": 65394575, + "owner": "heygen-com", + "repo": "hyperframes", + "slug": "tailwind", + "displayName": "tailwind", + "sourceUrl": "https://www.skills.sh/heygen-com/hyperframes/tailwind", + "githubRepoUrl": "https://github.com/heygen-com/hyperframes", + "sourceContentHash": "97bf0007f24e6275dcabc084c4d42f434ba48a84cec5a5b060036220239d6ddc", + "installs": 71255 + }, + { + "externalId": "firebase/agent-skills/firebase-firestore", + "githubOwnerId": 1335026, + "owner": "firebase", + "repo": "agent-skills", + "slug": "firebase-firestore", + "displayName": "firebase-firestore", + "sourceUrl": "https://www.skills.sh/firebase/agent-skills/firebase-firestore", + "githubRepoUrl": "https://github.com/firebase/agent-skills", + "sourceContentHash": "92609d631af05e495722300c278529a6f5e9ad7f945509652db98d4dd486ae92", + "installs": 71217 + }, + { + "externalId": "vercel-labs/agent-skills/vercel-cli-with-tokens", + "githubOwnerId": 108547162, + "owner": "vercel-labs", + "repo": "agent-skills", + "slug": "vercel-cli-with-tokens", + "displayName": "vercel-cli-with-tokens", + "sourceUrl": "https://www.skills.sh/vercel-labs/agent-skills/vercel-cli-with-tokens", + "githubRepoUrl": "https://github.com/vercel-labs/agent-skills", + "sourceContentHash": "eb16b20dcbe6ce51e0372083d624e0847af1b09487ec99249227d5a8eddebfc0", + "installs": 70965 + }, + { + "externalId": "microsoft/azure-skills/python-appservice-deploy", + "githubOwnerId": 6154722, + "owner": "microsoft", + "repo": "azure-skills", + "slug": "python-appservice-deploy", + "displayName": "python-appservice-deploy", + "sourceUrl": "https://www.skills.sh/microsoft/azure-skills/python-appservice-deploy", + "githubRepoUrl": "https://github.com/microsoft/azure-skills", + "sourceContentHash": "29942b783f54e930f0ac70371b364b7d532137c4053a273bb5aeac80e326f527", + "installs": 70919 + }, + { + "externalId": "heygen-com/hyperframes/animejs", + "githubOwnerId": 65394575, + "owner": "heygen-com", + "repo": "hyperframes", + "slug": "animejs", + "displayName": "animejs", + "sourceUrl": "https://www.skills.sh/heygen-com/hyperframes/animejs", + "githubRepoUrl": "https://github.com/heygen-com/hyperframes", + "sourceContentHash": "314c655fbc0df7eacc6f3853435ff8b03c2a771fad7435a58e2c4489a780d1b7", + "installs": 70706 + }, + { + "externalId": "anthropics/skills/theme-factory", + "githubOwnerId": 76263028, + "owner": "anthropics", + "repo": "skills", + "slug": "theme-factory", + "displayName": "theme-factory", + "sourceUrl": "https://www.skills.sh/anthropics/skills/theme-factory", + "githubRepoUrl": "https://github.com/anthropics/skills", + "sourceContentHash": "43ccc684ed8fc617bda737651f21f804f6c57ea7bd5d9e9f003b05c1957044cd", + "installs": 70015 + }, + { + "externalId": "heygen-com/hyperframes/three", + "githubOwnerId": 65394575, + "owner": "heygen-com", + "repo": "hyperframes", + "slug": "three", + "displayName": "three", + "sourceUrl": "https://www.skills.sh/heygen-com/hyperframes/three", + "githubRepoUrl": "https://github.com/heygen-com/hyperframes", + "sourceContentHash": "d40f783d8687f93b19657c193811c78304f84dd276ad344c38045e57b03de187", + "installs": 70005 + }, + { + "externalId": "heygen-com/hyperframes/lottie", + "githubOwnerId": 65394575, + "owner": "heygen-com", + "repo": "hyperframes", + "slug": "lottie", + "displayName": "lottie", + "sourceUrl": "https://www.skills.sh/heygen-com/hyperframes/lottie", + "githubRepoUrl": "https://github.com/heygen-com/hyperframes", + "sourceContentHash": "42174a1de29225618166626ed113bfd311a6b53083ddd514ab50991816f77b19", + "installs": 69740 + }, + { + "externalId": "anthropics/skills/doc-coauthoring", + "githubOwnerId": 76263028, + "owner": "anthropics", + "repo": "skills", + "slug": "doc-coauthoring", + "displayName": "doc-coauthoring", + "sourceUrl": "https://www.skills.sh/anthropics/skills/doc-coauthoring", + "githubRepoUrl": "https://github.com/anthropics/skills", + "sourceContentHash": "fd8325cb199c45d53792e9fbc144d72832900c77aa31bc3b781099d5cbe81266", + "installs": 69690 + }, + { + "externalId": "firebase/agent-skills/firebase-crashlytics", + "githubOwnerId": 1335026, + "owner": "firebase", + "repo": "agent-skills", + "slug": "firebase-crashlytics", + "displayName": "firebase-crashlytics", + "sourceUrl": "https://www.skills.sh/firebase/agent-skills/firebase-crashlytics", + "githubRepoUrl": "https://github.com/firebase/agent-skills", + "sourceContentHash": "d674b8730d37122f526390ee36d7c5c854cd9dba3636b90c6cc377562e478156", + "installs": 69526 + }, + { + "externalId": "heygen-com/hyperframes/waapi", + "githubOwnerId": 65394575, + "owner": "heygen-com", + "repo": "hyperframes", + "slug": "waapi", + "displayName": "waapi", + "sourceUrl": "https://www.skills.sh/heygen-com/hyperframes/waapi", + "githubRepoUrl": "https://github.com/heygen-com/hyperframes", + "sourceContentHash": "a497e9e3f2ffc5a4c38cbfdd86ff60a23a872173cff0df9489ffa10c3d256744", + "installs": 69378 + }, + { + "externalId": "firebase/agent-skills/xcode-project-setup", + "githubOwnerId": 1335026, + "owner": "firebase", + "repo": "agent-skills", + "slug": "xcode-project-setup", + "displayName": "xcode-project-setup", + "sourceUrl": "https://www.skills.sh/firebase/agent-skills/xcode-project-setup", + "githubRepoUrl": "https://github.com/firebase/agent-skills", + "sourceContentHash": "474d049ae8bd42168f922c0c895d180e293b902f6482be8c7658a1516861dcd7", + "installs": 69081 + }, + { + "externalId": "anthropics/skills/brand-guidelines", + "githubOwnerId": 76263028, + "owner": "anthropics", + "repo": "skills", + "slug": "brand-guidelines", + "displayName": "brand-guidelines", + "sourceUrl": "https://www.skills.sh/anthropics/skills/brand-guidelines", + "githubRepoUrl": "https://github.com/anthropics/skills", + "sourceContentHash": "ddf22e788d6aa33e818ec59be85d4fba5aeda23075518dafc412bb90b7cc63ce", + "installs": 68392 + }, + { + "externalId": "anthropics/skills/algorithmic-art", + "githubOwnerId": 76263028, + "owner": "anthropics", + "repo": "skills", + "slug": "algorithmic-art", + "displayName": "algorithmic-art", + "sourceUrl": "https://www.skills.sh/anthropics/skills/algorithmic-art", + "githubRepoUrl": "https://github.com/anthropics/skills", + "sourceContentHash": "8c15717769d76330df4387b85402a3754d858b97749ae259d3365ea9ea394f89", + "installs": 68028 + }, + { + "externalId": "coreyhaines31/marketingskills/social-content", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "social-content", + "displayName": "social-content", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/social-content", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "dffa1531f8f6fab8ec61cc2e17bf920b07dee70fda78aef36c3322d037404f7c", + "installs": 67509 + }, + { + "externalId": "mattpocock/skills/wizard", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "wizard", + "displayName": "wizard", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/wizard", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "23f15decfe5e5eff2e9c3c0e5e04378ca0a73bc88a5191dcdaf7f28d391ffdb0", + "installs": 67444 + }, + { + "externalId": "coreyhaines31/marketingskills/customer-research", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "customer-research", + "displayName": "customer-research", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/customer-research", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "ffedfd0aa833c1bc235daef9a7aba8091c5c8eb20fa727a345c33559ae653cd9", + "installs": 67051 + }, + { + "externalId": "pbakaus/impeccable/typeset", + "githubOwnerId": 43004, + "owner": "pbakaus", + "repo": "impeccable", + "slug": "typeset", + "displayName": "typeset", + "sourceUrl": "https://www.skills.sh/pbakaus/impeccable/typeset", + "githubRepoUrl": "https://github.com/pbakaus/impeccable", + "sourceContentHash": "9bc6cdf8a7f815f099686101e40f4d33c8236d9c28f691a49c11381a5afffff5", + "installs": 65726 + }, + { + "externalId": "degausai/wonda/wonda-cli", + "githubOwnerId": 219446448, + "owner": "degausai", + "repo": "wonda", + "slug": "wonda-cli", + "displayName": "wonda-cli", + "sourceUrl": "https://www.skills.sh/degausai/wonda/wonda-cli", + "githubRepoUrl": "https://github.com/degausai/wonda", + "sourceContentHash": "1e4e3d82fb06550c0dfd66758990a3a9b8238873c9c6d026094c0f2964e7d38f", + "installs": 64533 + }, + { + "externalId": "get-convex/agent-skills/convex", + "githubOwnerId": 81530787, + "owner": "get-convex", + "repo": "agent-skills", + "slug": "convex", + "displayName": "convex", + "sourceUrl": "https://www.skills.sh/get-convex/agent-skills/convex", + "githubRepoUrl": "https://github.com/get-convex/agent-skills", + "sourceContentHash": "8184f8c43b05ef7e3c880f14f75c15c32834f7bad820ac789f387455dafdedd0", + "installs": 64384 + }, + { + "externalId": "squirrelscan/skills/audit-website", + "githubOwnerId": 252655516, + "owner": "squirrelscan", + "repo": "skills", + "slug": "audit-website", + "displayName": "audit-website", + "sourceUrl": "https://www.skills.sh/squirrelscan/skills/audit-website", + "githubRepoUrl": "https://github.com/squirrelscan/skills", + "sourceContentHash": "244ac7b7ed5888b31da0102e9b8dc3aa9f8f7c00cf1034bc093595065d00d833", + "installs": 64145 + }, + { + "externalId": "pbakaus/impeccable/overdrive", + "githubOwnerId": 43004, + "owner": "pbakaus", + "repo": "impeccable", + "slug": "overdrive", + "displayName": "overdrive", + "sourceUrl": "https://www.skills.sh/pbakaus/impeccable/overdrive", + "githubRepoUrl": "https://github.com/pbakaus/impeccable", + "sourceContentHash": "4cda920b80f66b57117e989b48d53cddc5b57bc8daf9214fb7b1b219e707420c", + "installs": 63805 + }, + { + "externalId": "currents-dev/playwright-best-practices-skill/playwright-best-practices", + "githubOwnerId": 81007196, + "owner": "currents-dev", + "repo": "playwright-best-practices-skill", + "slug": "playwright-best-practices", + "displayName": "playwright-best-practices", + "sourceUrl": "https://www.skills.sh/currents-dev/playwright-best-practices-skill/playwright-best-practices", + "githubRepoUrl": "https://github.com/currents-dev/playwright-best-practices-skill", + "sourceContentHash": "b2af1dbb58444210e6954f729e7430773b6771b7ee9fb8cd753438197adb2c23", + "installs": 62940 + }, + { + "externalId": "mattpocock/skills/claude-handoff", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "claude-handoff", + "displayName": "claude-handoff", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/claude-handoff", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "37af6cfb3d32f651afbffa2c2387f10427040dbabf23c4303fd815f05413c289", + "installs": 62298 + }, + { + "externalId": "firebase/agent-skills/developing-genkit-js", + "githubOwnerId": 1335026, + "owner": "firebase", + "repo": "agent-skills", + "slug": "developing-genkit-js", + "displayName": "developing-genkit-js", + "sourceUrl": "https://www.skills.sh/firebase/agent-skills/developing-genkit-js", + "githubRepoUrl": "https://github.com/firebase/agent-skills", + "sourceContentHash": "a77ec23d62f741d487433a015f4c0411adad406d7ebc6758b020c5ea3278c5ed", + "installs": 62173 + }, + { + "externalId": "firebase/agent-skills/developing-genkit-dart", + "githubOwnerId": 1335026, + "owner": "firebase", + "repo": "agent-skills", + "slug": "developing-genkit-dart", + "displayName": "developing-genkit-dart", + "sourceUrl": "https://www.skills.sh/firebase/agent-skills/developing-genkit-dart", + "githubRepoUrl": "https://github.com/firebase/agent-skills", + "sourceContentHash": "2ca6e910a2f23bd80b2b761257fc864f02e376208658fe4aab1f8b2dc7a83e75", + "installs": 61909 + }, + { + "externalId": "coreyhaines31/marketingskills/pricing-strategy", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "pricing-strategy", + "displayName": "pricing-strategy", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/pricing-strategy", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "7567c252a1b76aeaa695afb56e353ee841051810072ce6ef9d4d2111ad613eaa", + "installs": 61667 + }, + { + "externalId": "coreyhaines31/marketingskills/product-marketing-context", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "product-marketing-context", + "displayName": "product-marketing-context", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/product-marketing-context", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "c2929bf4c4f104cbd59ce34cbdc4b5911eb72ead065f1b674bffa8f28002c3eb", + "installs": 61657 + }, + { + "externalId": "kepano/obsidian-skills/obsidian-markdown", + "githubOwnerId": 10565871, + "owner": "kepano", + "repo": "obsidian-skills", + "slug": "obsidian-markdown", + "displayName": "obsidian-markdown", + "sourceUrl": "https://www.skills.sh/kepano/obsidian-skills/obsidian-markdown", + "githubRepoUrl": "https://github.com/kepano/obsidian-skills", + "sourceContentHash": "eeaf2e16446fe6aa0c6e2e50dc84d6301de1647c44e43b37e018f8814bf8f3ae", + "installs": 60604 + }, + { + "externalId": "anthropics/skills/internal-comms", + "githubOwnerId": 76263028, + "owner": "anthropics", + "repo": "skills", + "slug": "internal-comms", + "displayName": "internal-comms", + "sourceUrl": "https://www.skills.sh/anthropics/skills/internal-comms", + "githubRepoUrl": "https://github.com/anthropics/skills", + "sourceContentHash": "521e90c1b596408e314cd7716322cf682438a0943bc12b7b9f0005f134a45ded", + "installs": 60564 + }, + { + "externalId": "nexscope-ai/ecommerce-skills/cross-border-ecommerce", + "githubOwnerId": 265748764, + "owner": "nexscope-ai", + "repo": "ecommerce-skills", + "slug": "cross-border-ecommerce", + "displayName": "cross-border-ecommerce", + "sourceUrl": "https://www.skills.sh/nexscope-ai/ecommerce-skills/cross-border-ecommerce", + "githubRepoUrl": "https://github.com/nexscope-ai/ecommerce-skills", + "sourceContentHash": "8e0af5c4c948eb3d8efad05aae4b27fbdce218a81022272b416a1c3227598ce9", + "installs": 60271 + }, + { + "externalId": "firebase/agent-skills/firebase-remote-config-basics", + "githubOwnerId": 1335026, + "owner": "firebase", + "repo": "agent-skills", + "slug": "firebase-remote-config-basics", + "displayName": "firebase-remote-config-basics", + "sourceUrl": "https://www.skills.sh/firebase/agent-skills/firebase-remote-config-basics", + "githubRepoUrl": "https://github.com/firebase/agent-skills", + "sourceContentHash": "ba911e46e387b20c62c4d86f67dfc8da266ab5faa92859b98a9c28d652d431b2", + "installs": 59346 + }, + { + "externalId": "expo/skills/building-native-ui", + "githubOwnerId": 12504344, + "owner": "expo", + "repo": "skills", + "slug": "building-native-ui", + "displayName": "building-native-ui", + "sourceUrl": "https://www.skills.sh/expo/skills/building-native-ui", + "githubRepoUrl": "https://github.com/expo/skills", + "sourceContentHash": "cb94e813f2e9cec29c383191181f00fd4dda06244f039a949396b1ba6d4850d6", + "installs": 58683 + }, + { + "externalId": "coreyhaines31/marketingskills/analytics-tracking", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "analytics-tracking", + "displayName": "analytics-tracking", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/analytics-tracking", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "757d45338d686b7717a4bd66f24f78167e0cc81ea32ff58d8487afd6a3519e0b", + "installs": 58295 + }, + { + "externalId": "coreyhaines31/marketingskills/page-cro", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "page-cro", + "displayName": "page-cro", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/page-cro", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "cb8d4afe44bf52e8c3eb0bf8bab8fdce88af436964fd7ef828327c07b84379f2", + "installs": 57924 + }, + { + "externalId": "intellectronica/agent-skills/notion-api", + "githubOwnerId": 46264, + "owner": "intellectronica", + "repo": "agent-skills", + "slug": "notion-api", + "displayName": "notion-api", + "sourceUrl": "https://www.skills.sh/intellectronica/agent-skills/notion-api", + "githubRepoUrl": "https://github.com/intellectronica/agent-skills", + "sourceContentHash": "e7eb4fb4633dbf2a156aebb67a95bdfdc46a1205f150e9d9a3183ec98697b647", + "installs": 57869 + }, + { + "externalId": "anthropics/skills/slack-gif-creator", + "githubOwnerId": 76263028, + "owner": "anthropics", + "repo": "skills", + "slug": "slack-gif-creator", + "displayName": "slack-gif-creator", + "sourceUrl": "https://www.skills.sh/anthropics/skills/slack-gif-creator", + "githubRepoUrl": "https://github.com/anthropics/skills", + "sourceContentHash": "39397a8ac133d1a3cedc6208c01476d9bf9a762f9e2053067e46758cb0d360b9", + "installs": 57194 + }, + { + "externalId": "google/agents-cli/google-agents-cli-adk-code", + "githubOwnerId": 1342004, + "owner": "google", + "repo": "agents-cli", + "slug": "google-agents-cli-adk-code", + "displayName": "google-agents-cli-adk-code", + "sourceUrl": "https://www.skills.sh/google/agents-cli/google-agents-cli-adk-code", + "githubRepoUrl": "https://github.com/google/agents-cli", + "sourceContentHash": "23a251ac372d2f1b82e31cba6dd594c9c8c78eec49d80513eb3f662df9441707", + "installs": 56940 + }, + { + "externalId": "google/agents-cli/google-agents-cli-workflow", + "githubOwnerId": 1342004, + "owner": "google", + "repo": "agents-cli", + "slug": "google-agents-cli-workflow", + "displayName": "google-agents-cli-workflow", + "sourceUrl": "https://www.skills.sh/google/agents-cli/google-agents-cli-workflow", + "githubRepoUrl": "https://github.com/google/agents-cli", + "sourceContentHash": "5097bbbae9d4e30418bbfedcf98bdb5bd2929ad1cb05e94cf47991c8fd1d2275", + "installs": 56715 + }, + { + "externalId": "google/agents-cli/google-agents-cli-eval", + "githubOwnerId": 1342004, + "owner": "google", + "repo": "agents-cli", + "slug": "google-agents-cli-eval", + "displayName": "google-agents-cli-eval", + "sourceUrl": "https://www.skills.sh/google/agents-cli/google-agents-cli-eval", + "githubRepoUrl": "https://github.com/google/agents-cli", + "sourceContentHash": "bd530f86a58a1c95808f3905249cd88ec87877a7405ad78ad65637a86ba32459", + "installs": 56684 + }, + { + "externalId": "google/agents-cli/google-agents-cli-scaffold", + "githubOwnerId": 1342004, + "owner": "google", + "repo": "agents-cli", + "slug": "google-agents-cli-scaffold", + "displayName": "google-agents-cli-scaffold", + "sourceUrl": "https://www.skills.sh/google/agents-cli/google-agents-cli-scaffold", + "githubRepoUrl": "https://github.com/google/agents-cli", + "sourceContentHash": "1c4e931249db5c2ca217d04526ff3339480535cbe2423d7f04bab40e937a20a7", + "installs": 56654 + }, + { + "externalId": "google/agents-cli/google-agents-cli-deploy", + "githubOwnerId": 1342004, + "owner": "google", + "repo": "agents-cli", + "slug": "google-agents-cli-deploy", + "displayName": "google-agents-cli-deploy", + "sourceUrl": "https://www.skills.sh/google/agents-cli/google-agents-cli-deploy", + "githubRepoUrl": "https://github.com/google/agents-cli", + "sourceContentHash": "b583d8f60b5d58e5cc73a407202bbb4b9e9c2d707cd905b99500ae7f7ec4ddd7", + "installs": 56645 + }, + { + "externalId": "google/agents-cli/google-agents-cli-observability", + "githubOwnerId": 1342004, + "owner": "google", + "repo": "agents-cli", + "slug": "google-agents-cli-observability", + "displayName": "google-agents-cli-observability", + "sourceUrl": "https://www.skills.sh/google/agents-cli/google-agents-cli-observability", + "githubRepoUrl": "https://github.com/google/agents-cli", + "sourceContentHash": "df7a5d366a4fa07791f37768765f147f3c281625d08b12e1cdbd6adb45edb3dd", + "installs": 56645 + }, + { + "externalId": "google/agents-cli/google-agents-cli-publish", + "githubOwnerId": 1342004, + "owner": "google", + "repo": "agents-cli", + "slug": "google-agents-cli-publish", + "displayName": "google-agents-cli-publish", + "sourceUrl": "https://www.skills.sh/google/agents-cli/google-agents-cli-publish", + "githubRepoUrl": "https://github.com/google/agents-cli", + "sourceContentHash": "722b9b092ec8992648f4065ea06bc1eb4271138f3f2e3cf91ffb3257014546cc", + "installs": 56569 + }, + { + "externalId": "coreyhaines31/marketingskills/launch-strategy", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "launch-strategy", + "displayName": "launch-strategy", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/launch-strategy", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "a5cad6611915df5b4bd45e656e3113a3736f7fd06dd88280f554561dd0762515", + "installs": 56505 + }, + { + "externalId": "coreyhaines31/marketingskills/schema-markup", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "schema-markup", + "displayName": "schema-markup", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/schema-markup", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "c615859aa70d2ddc7dc847a1d40c12a70b20844248a91370cfc0ea514ac3be76", + "installs": 55931 + }, + { + "externalId": "heygen-com/hyperframes/contribute-catalog", + "githubOwnerId": 65394575, + "owner": "heygen-com", + "repo": "hyperframes", + "slug": "contribute-catalog", + "displayName": "contribute-catalog", + "sourceUrl": "https://www.skills.sh/heygen-com/hyperframes/contribute-catalog", + "githubRepoUrl": "https://github.com/heygen-com/hyperframes", + "sourceContentHash": "31733d362afeb07d5716e267cf94ae487740c3e52f5e60ad714e085c95a03b73", + "installs": 55914 + }, + { + "externalId": "firecrawl/skills/firecrawl-build-search", + "githubOwnerId": 135057108, + "owner": "firecrawl", + "repo": "skills", + "slug": "firecrawl-build-search", + "displayName": "firecrawl-build-search", + "sourceUrl": "https://www.skills.sh/firecrawl/skills/firecrawl-build-search", + "githubRepoUrl": "https://github.com/firecrawl/skills", + "sourceContentHash": "3d64f419969e337cb4fb4a9d1a2352eacbf2ce8f72815db0d3891f5bef1bc482", + "installs": 55567 + }, + { + "externalId": "coreyhaines31/marketingskills/email-sequence", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "email-sequence", + "displayName": "email-sequence", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/email-sequence", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "7d5472522c26c7dfdc85813d258b9ed0947b5ff3ffbff0b5e40489ad4e806cbc", + "installs": 55560 + }, + { + "externalId": "firecrawl/skills/firecrawl-build-scrape", + "githubOwnerId": 135057108, + "owner": "firecrawl", + "repo": "skills", + "slug": "firecrawl-build-scrape", + "displayName": "firecrawl-build-scrape", + "sourceUrl": "https://www.skills.sh/firecrawl/skills/firecrawl-build-scrape", + "githubRepoUrl": "https://github.com/firecrawl/skills", + "sourceContentHash": "742381702d535c60133d82e4537302c963c811f8a7c2e046ee49a8128cdf1144", + "installs": 55541 + }, + { + "externalId": "firecrawl/skills/firecrawl-build-interact", + "githubOwnerId": 135057108, + "owner": "firecrawl", + "repo": "skills", + "slug": "firecrawl-build-interact", + "displayName": "firecrawl-build-interact", + "sourceUrl": "https://www.skills.sh/firecrawl/skills/firecrawl-build-interact", + "githubRepoUrl": "https://github.com/firecrawl/skills", + "sourceContentHash": "a09400f8b21db440b4ca7ec859398acd6e2c519f014d0db7586b0e0b9cd9c79f", + "installs": 55417 + }, + { + "externalId": "firecrawl/skills/firecrawl-build-onboarding", + "githubOwnerId": 135057108, + "owner": "firecrawl", + "repo": "skills", + "slug": "firecrawl-build-onboarding", + "displayName": "firecrawl-build-onboarding", + "sourceUrl": "https://www.skills.sh/firecrawl/skills/firecrawl-build-onboarding", + "githubRepoUrl": "https://github.com/firecrawl/skills", + "sourceContentHash": "25e5234602ed435dec9c96876e4064cedc5b3107530ffe244a80bf8a9b094b70", + "installs": 55397 + }, + { + "externalId": "pbakaus/impeccable/normalize", + "githubOwnerId": 43004, + "owner": "pbakaus", + "repo": "impeccable", + "slug": "normalize", + "displayName": "normalize", + "sourceUrl": "https://www.skills.sh/pbakaus/impeccable/normalize", + "githubRepoUrl": "https://github.com/pbakaus/impeccable", + "sourceContentHash": "6418b82404cffe5540ac29ab3ae1a58c259ef3fb2ae0f7fdda1f1eeac3ad70a0", + "installs": 55327 + }, + { + "externalId": "wshobson/agents/tailwind-design-system", + "githubOwnerId": 553618, + "owner": "wshobson", + "repo": "agents", + "slug": "tailwind-design-system", + "displayName": "tailwind-design-system", + "sourceUrl": "https://www.skills.sh/wshobson/agents/tailwind-design-system", + "githubRepoUrl": "https://github.com/wshobson/agents", + "sourceContentHash": "311ffae9330eff1c8114c6e0125f9c933f0a0e97e1a71601da4cf8363b30097f", + "installs": 55203 + }, + { + "externalId": "google-labs-code/stitch-skills/design-md", + "githubOwnerId": 161364575, + "owner": "google-labs-code", + "repo": "stitch-skills", + "slug": "design-md", + "displayName": "design-md", + "sourceUrl": "https://www.skills.sh/google-labs-code/stitch-skills/design-md", + "githubRepoUrl": "https://github.com/google-labs-code/stitch-skills", + "sourceContentHash": "bee75bd0e412811b5c1b778a2b79c191257afce42c1cf1d18f68388c0c7c065b", + "installs": 55135 + }, + { + "externalId": "coreyhaines31/marketingskills/paid-ads", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "paid-ads", + "displayName": "paid-ads", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/paid-ads", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "672bf0e716b1f8b0546e63e1739b86693c755af6d5ae43eb143106519d6eea60", + "installs": 55116 + }, + { + "externalId": "anthropics/skills/template-skill", + "githubOwnerId": 76263028, + "owner": "anthropics", + "repo": "skills", + "slug": "template-skill", + "displayName": "template-skill", + "sourceUrl": "https://www.skills.sh/anthropics/skills/template-skill", + "githubRepoUrl": "https://github.com/anthropics/skills", + "sourceContentHash": "619080fb158d282e55835131603a5114a03c77a1b4336af992f61e5c064d832f", + "installs": 54892 + }, + { + "externalId": "coreyhaines31/marketingskills/competitor-alternatives", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "competitor-alternatives", + "displayName": "competitor-alternatives", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/competitor-alternatives", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "564479067ff0d64545b2715069ec8fab3d08c47038d2c7cadff5e082730b5cf9", + "installs": 54603 + }, + { + "externalId": "wshobson/agents/typescript-advanced-types", + "githubOwnerId": 553618, + "owner": "wshobson", + "repo": "agents", + "slug": "typescript-advanced-types", + "displayName": "typescript-advanced-types", + "sourceUrl": "https://www.skills.sh/wshobson/agents/typescript-advanced-types", + "githubRepoUrl": "https://github.com/wshobson/agents", + "sourceContentHash": "5cfece01370706036b65eae88c2c7ad9fa6f7a72dfd7ef58d65c666e3d8ee1a2", + "installs": 54586 + }, + { + "externalId": "pbakaus/impeccable/extract", + "githubOwnerId": 43004, + "owner": "pbakaus", + "repo": "impeccable", + "slug": "extract", + "displayName": "extract", + "sourceUrl": "https://www.skills.sh/pbakaus/impeccable/extract", + "githubRepoUrl": "https://github.com/pbakaus/impeccable", + "sourceContentHash": "2339934e6e05afe5e1d2b106b9ffa8d69049ddb6f3ddeadd339f9d317b754d7e", + "installs": 54247 + }, + { + "externalId": "pbakaus/impeccable/onboard", + "githubOwnerId": 43004, + "owner": "pbakaus", + "repo": "impeccable", + "slug": "onboard", + "displayName": "onboard", + "sourceUrl": "https://www.skills.sh/pbakaus/impeccable/onboard", + "githubRepoUrl": "https://github.com/pbakaus/impeccable", + "sourceContentHash": "e06352a87716bfd6285ff1acad738ac8159671e227816be4ca583fba2a354251", + "installs": 54092 + }, + { + "externalId": "pbakaus/impeccable/frontend-design", + "githubOwnerId": 43004, + "owner": "pbakaus", + "repo": "impeccable", + "slug": "frontend-design", + "displayName": "frontend-design", + "sourceUrl": "https://www.skills.sh/pbakaus/impeccable/frontend-design", + "githubRepoUrl": "https://github.com/pbakaus/impeccable", + "sourceContentHash": "ea45713cfa46cddea7954cc7a13a57246b1f084729dc0d2fbfb7baeedfb62057", + "installs": 54075 + }, + { + "externalId": "coreyhaines31/marketingskills/onboarding-cro", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "onboarding-cro", + "displayName": "onboarding-cro", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/onboarding-cro", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "99f4eca4807c285239a9918d22062a3b447eefb6c54f923ab03d9f967eadec8f", + "installs": 54074 + }, + { + "externalId": "pbakaus/impeccable/harden", + "githubOwnerId": 43004, + "owner": "pbakaus", + "repo": "impeccable", + "slug": "harden", + "displayName": "harden", + "sourceUrl": "https://www.skills.sh/pbakaus/impeccable/harden", + "githubRepoUrl": "https://github.com/pbakaus/impeccable", + "sourceContentHash": "be411e60c6e5ee211ce30d4b8eab60d8c4118fccca0b62d94c7ddb2bb87e5e62", + "installs": 54024 + }, + { + "externalId": "neondatabase/agent-skills/neon-postgres", + "githubOwnerId": 77690634, + "owner": "neondatabase", + "repo": "agent-skills", + "slug": "neon-postgres", + "displayName": "neon-postgres", + "sourceUrl": "https://www.skills.sh/neondatabase/agent-skills/neon-postgres", + "githubRepoUrl": "https://github.com/neondatabase/agent-skills", + "sourceContentHash": "91c103acbf5b24b291511a62f109c1c1716d664afc5adba5fb82f3c8ee264cd8", + "installs": 53293 + }, + { + "externalId": "coreyhaines31/marketingskills/form-cro", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "form-cro", + "displayName": "form-cro", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/form-cro", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "04bde943082e8d5631958efea2d1496d87f7fecb8692a9f4441cc6fe6d58b950", + "installs": 53004 + }, + { + "externalId": "genkit-ai/skills/developing-genkit-js", + "githubOwnerId": 166032008, + "owner": "genkit-ai", + "repo": "skills", + "slug": "developing-genkit-js", + "displayName": "developing-genkit-js", + "sourceUrl": "https://www.skills.sh/genkit-ai/skills/developing-genkit-js", + "githubRepoUrl": "https://github.com/genkit-ai/skills", + "sourceContentHash": "85d2372a0c6b10dcaa4a76b7f36d5c722c13a13ce25d27d117f7dadaf3e780a5", + "installs": 52914 + }, + { + "externalId": "coreyhaines31/marketingskills/free-tool-strategy", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "free-tool-strategy", + "displayName": "free-tool-strategy", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/free-tool-strategy", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "b4962df51a049d461d2a4f99e1d620e90b9b7c0ef3a8e3e097033af5f34b0b4a", + "installs": 52764 + }, + { + "externalId": "coreyhaines31/marketingskills/referral-program", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "referral-program", + "displayName": "referral-program", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/referral-program", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "603a492b1ddaefb60deb5d6a9fa10afe36cd32c25954678cf417f2d247dbd8b7", + "installs": 52702 + }, + { + "externalId": "coreyhaines31/marketingskills/ab-test-setup", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "ab-test-setup", + "displayName": "ab-test-setup", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/ab-test-setup", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "52b3f9fffa7452c2e52d48aff00cb575428a97992d39bace92b2876bace2d8b9", + "installs": 52535 + }, + { + "externalId": "coreyhaines31/marketingskills/signup-flow-cro", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "signup-flow-cro", + "displayName": "signup-flow-cro", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/signup-flow-cro", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "24b8dcc04b2a1959ae9be70e8af09188403f5e6a162c77afb615eb45ac479df3", + "installs": 52256 + }, + { + "externalId": "vercel/turborepo/turborepo", + "githubOwnerId": 14985020, + "owner": "vercel", + "repo": "turborepo", + "slug": "turborepo", + "displayName": "turborepo", + "sourceUrl": "https://www.skills.sh/vercel/turborepo/turborepo", + "githubRepoUrl": "https://github.com/vercel/turborepo", + "sourceContentHash": "a004cb647398484ebdc0c4baf0058668d8551a43bfe6ad69ebe6d65f188a4112", + "installs": 51993 + }, + { + "externalId": "coreyhaines31/marketingskills/paywall-upgrade-cro", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "paywall-upgrade-cro", + "displayName": "paywall-upgrade-cro", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/paywall-upgrade-cro", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "f817eaaefe55809bd2427a4b468db2e2da758dc24818b44d0b67f84ba00c8e94", + "installs": 51855 + }, + { + "externalId": "msmps/opentui-skill/opentui", + "githubOwnerId": 7691252, + "owner": "msmps", + "repo": "opentui-skill", + "slug": "opentui", + "displayName": "opentui", + "sourceUrl": "https://www.skills.sh/msmps/opentui-skill/opentui", + "githubRepoUrl": "https://github.com/msmps/opentui-skill", + "sourceContentHash": "cd4bc46abe8d7e74d7414c31a9e583876e161eaf98da6c14285404c4bcba4a41", + "installs": 51801 + }, + { + "externalId": "kepano/obsidian-skills/obsidian-cli", + "githubOwnerId": 10565871, + "owner": "kepano", + "repo": "obsidian-skills", + "slug": "obsidian-cli", + "displayName": "obsidian-cli", + "sourceUrl": "https://www.skills.sh/kepano/obsidian-skills/obsidian-cli", + "githubRepoUrl": "https://github.com/kepano/obsidian-skills", + "sourceContentHash": "58fec9d58ebc0c4ddbbdecf49a43fbdb2748b81729382708f84269fa84104564", + "installs": 51552 + }, + { + "externalId": "coreyhaines31/marketingskills/popup-cro", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "popup-cro", + "displayName": "popup-cro", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/popup-cro", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "cd622ad0377b8ad2f208cd7edccb602a220e756587746fab19473e251426e188", + "installs": 51523 + }, + { + "externalId": "heygen-com/hyperframes/typegpu", + "githubOwnerId": 65394575, + "owner": "heygen-com", + "repo": "hyperframes", + "slug": "typegpu", + "displayName": "typegpu", + "sourceUrl": "https://www.skills.sh/heygen-com/hyperframes/typegpu", + "githubRepoUrl": "https://github.com/heygen-com/hyperframes", + "sourceContentHash": "768a9b983ac79fc283307cdd39f262ab4da5deeb3726ec65252fb7b4bc04f779", + "installs": 51364 + }, + { + "externalId": "anthropics/skills/claude-api", + "githubOwnerId": 76263028, + "owner": "anthropics", + "repo": "skills", + "slug": "claude-api", + "displayName": "claude-api", + "sourceUrl": "https://www.skills.sh/anthropics/skills/claude-api", + "githubRepoUrl": "https://github.com/anthropics/skills", + "sourceContentHash": "2e7a73447fb88092d638c1a4ed36d5788f118a0c6fccf134371f9bd261be61c2", + "installs": 51259 + }, + { + "externalId": "pbakaus/impeccable/teach-impeccable", + "githubOwnerId": 43004, + "owner": "pbakaus", + "repo": "impeccable", + "slug": "teach-impeccable", + "displayName": "teach-impeccable", + "sourceUrl": "https://www.skills.sh/pbakaus/impeccable/teach-impeccable", + "githubRepoUrl": "https://github.com/pbakaus/impeccable", + "sourceContentHash": "c8642a350125365f2b74842e52df213604af7006bdca9232a631bfc0b2f4cefa", + "installs": 51007 + }, + { + "externalId": "nozomio-labs/nia-skill/nia", + "githubOwnerId": 193512167, + "owner": "nozomio-labs", + "repo": "nia-skill", + "slug": "nia", + "displayName": "nia", + "sourceUrl": "https://www.skills.sh/nozomio-labs/nia-skill/nia", + "githubRepoUrl": "https://github.com/nozomio-labs/nia-skill", + "sourceContentHash": "e2648f53801c793dcf2ceb4db94de7b069629ae2765322f46dee193f5cd73aba", + "installs": 50981 + }, + { + "externalId": "101-skills/skills/agent-browser", + "githubOwnerId": 301088244, + "owner": "101-skills", + "repo": "skills", + "slug": "agent-browser", + "displayName": "agent-browser", + "sourceUrl": "https://www.skills.sh/101-skills/skills/agent-browser", + "githubRepoUrl": "https://github.com/101-skills/skills", + "sourceContentHash": "88bdf43f2e2341a9b404d322b89939847edcd0bf55f759aa0abb9adf3a65a82d", + "installs": 50918 + }, + { + "externalId": "kepano/obsidian-skills/obsidian-bases", + "githubOwnerId": 10565871, + "owner": "kepano", + "repo": "obsidian-skills", + "slug": "obsidian-bases", + "displayName": "obsidian-bases", + "sourceUrl": "https://www.skills.sh/kepano/obsidian-skills/obsidian-bases", + "githubRepoUrl": "https://github.com/kepano/obsidian-skills", + "sourceContentHash": "83e988a6a4eec9a06c8668199b8f1a4189324346cbc50a73ef1948f80d85a60d", + "installs": 50683 + }, + { + "externalId": "google-labs-code/stitch-skills/enhance-prompt", + "githubOwnerId": 161364575, + "owner": "google-labs-code", + "repo": "stitch-skills", + "slug": "enhance-prompt", + "displayName": "enhance-prompt", + "sourceUrl": "https://www.skills.sh/google-labs-code/stitch-skills/enhance-prompt", + "githubRepoUrl": "https://github.com/google-labs-code/stitch-skills", + "sourceContentHash": "57e6bbac520beddd814f560c1d0f268d70ca84a56d76f23d0120ddeb051374eb", + "installs": 49983 + }, + { + "externalId": "firebase/agent-skills/developing-genkit-go", + "githubOwnerId": 1335026, + "owner": "firebase", + "repo": "agent-skills", + "slug": "developing-genkit-go", + "displayName": "developing-genkit-go", + "sourceUrl": "https://www.skills.sh/firebase/agent-skills/developing-genkit-go", + "githubRepoUrl": "https://github.com/firebase/agent-skills", + "sourceContentHash": "020439b1105125a3a5757f33c433a383e4d166c27e5dc82febffbae7e3eb0b0e", + "installs": 49326 + }, + { + "externalId": "coreyhaines31/marketingskills/competitor-profiling", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "competitor-profiling", + "displayName": "competitor-profiling", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/competitor-profiling", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "3e0516f1cbe3a26d88aada46b3faa0f49d29c6c9224a255373077952c82ff5c0", + "installs": 49010 + }, + { + "externalId": "wind-information-co-ltd/wind-skills/wind-mcp-skill", + "githubOwnerId": 248631773, + "owner": "wind-information-co-ltd", + "repo": "wind-skills", + "slug": "wind-mcp-skill", + "displayName": "wind-mcp-skill", + "sourceUrl": "https://www.skills.sh/wind-information-co-ltd/wind-skills/wind-mcp-skill", + "githubRepoUrl": "https://github.com/wind-information-co-ltd/wind-skills", + "sourceContentHash": "629de77395cea19bd6c9f45f80d16d99dae3c7d89acfd7068e7ac505c06265ea", + "installs": 48870 + }, + { + "externalId": "google-labs-code/stitch-skills/stitch-loop", + "githubOwnerId": 161364575, + "owner": "google-labs-code", + "repo": "stitch-skills", + "slug": "stitch-loop", + "displayName": "stitch-loop", + "sourceUrl": "https://www.skills.sh/google-labs-code/stitch-skills/stitch-loop", + "githubRepoUrl": "https://github.com/google-labs-code/stitch-skills", + "sourceContentHash": "79a3084b78486368164f36503e43f2fab646b99a6d7a98b22d44da20725a8eb0", + "installs": 48560 + }, + { + "externalId": "emilkowalski/skills/review-animations", + "githubOwnerId": 36730035, + "owner": "emilkowalski", + "repo": "skills", + "slug": "review-animations", + "displayName": "review-animations", + "sourceUrl": "https://www.skills.sh/emilkowalski/skills/review-animations", + "githubRepoUrl": "https://github.com/emilkowalski/skills", + "sourceContentHash": "8993bb3677f1a0746d191068e0203b33daa25b88bb1ae3c2e692eec87747a996", + "installs": 48347 + }, + { + "externalId": "coreyhaines31/marketingskills/community-marketing", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "community-marketing", + "displayName": "community-marketing", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/community-marketing", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "c3fe7c5247f1cca9ba3c7adaa2c735ce407809d6ad9fcec943fffe0e05680390", + "installs": 47541 + }, + { + "externalId": "expo/skills/native-data-fetching", + "githubOwnerId": 12504344, + "owner": "expo", + "repo": "skills", + "slug": "native-data-fetching", + "displayName": "native-data-fetching", + "sourceUrl": "https://www.skills.sh/expo/skills/native-data-fetching", + "githubRepoUrl": "https://github.com/expo/skills", + "sourceContentHash": "ecc611707f26255c459cc89c7970805d365899533f0883d20ab880ba5b95e025", + "installs": 46824 + }, + { + "externalId": "solana-foundation/solana-dev-skill/solana-dev", + "githubOwnerId": 58729655, + "owner": "solana-foundation", + "repo": "solana-dev-skill", + "slug": "solana-dev", + "displayName": "solana-dev", + "sourceUrl": "https://www.skills.sh/solana-foundation/solana-dev-skill/solana-dev", + "githubRepoUrl": "https://github.com/solana-foundation/solana-dev-skill", + "sourceContentHash": "8789da5ba4b25612763513091d0dfbeb5c7320d98f2f9391dd225ecf4a0cbf3c", + "installs": 46665 + }, + { + "externalId": "kepano/obsidian-skills/json-canvas", + "githubOwnerId": 10565871, + "owner": "kepano", + "repo": "obsidian-skills", + "slug": "json-canvas", + "displayName": "json-canvas", + "sourceUrl": "https://www.skills.sh/kepano/obsidian-skills/json-canvas", + "githubRepoUrl": "https://github.com/kepano/obsidian-skills", + "sourceContentHash": "b530b7d3363caf52d71a633b10965759004be6dac898b909d07e8202861e4506", + "installs": 46613 + }, + { + "externalId": "juliusbrussee/caveman/compress", + "githubOwnerId": 104168679, + "owner": "juliusbrussee", + "repo": "caveman", + "slug": "compress", + "displayName": "compress", + "sourceUrl": "https://www.skills.sh/juliusbrussee/caveman/compress", + "githubRepoUrl": "https://github.com/juliusbrussee/caveman", + "sourceContentHash": "81fbfc3beedcdd9be0b5744955a23fa894c215db10fd6ce284092d0bc75939b5", + "installs": 46464 + }, + { + "externalId": "coreyhaines31/marketingskills/image", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "image", + "displayName": "image", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/image", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "488d24d682bc097d587d72e89275cc754cddff5e7535cf44d1503e79266904ff", + "installs": 46245 + }, + { + "externalId": "coreyhaines31/marketingskills/video", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "video", + "displayName": "video", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/video", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "ccbec14c441a0e62cc28b87d24cf51472ed9a72468f671b0e6da4c2a4f069d52", + "installs": 46116 + }, + { + "externalId": "coreyhaines31/marketingskills/directory-submissions", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "directory-submissions", + "displayName": "directory-submissions", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/directory-submissions", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "88926b1b6c69059d53aba86fcdeed30d5e4a9016bae5ac79f10f81b0ccf995c1", + "installs": 45968 + }, + { + "externalId": "agentix-cloud/skills/agentix-ceo", + "githubOwnerId": 269657558, + "owner": "agentix-cloud", + "repo": "skills", + "slug": "agentix-ceo", + "displayName": "agentix-ceo", + "sourceUrl": "https://www.skills.sh/agentix-cloud/skills/agentix-ceo", + "githubRepoUrl": "https://github.com/agentix-cloud/skills", + "sourceContentHash": "ce17e8f73b7a197b8270490572308cb58579a30562a87f2e663288e049e657db", + "installs": 45957 + }, + { + "externalId": "kepano/obsidian-skills/defuddle", + "githubOwnerId": 10565871, + "owner": "kepano", + "repo": "obsidian-skills", + "slug": "defuddle", + "displayName": "defuddle", + "sourceUrl": "https://www.skills.sh/kepano/obsidian-skills/defuddle", + "githubRepoUrl": "https://github.com/kepano/obsidian-skills", + "sourceContentHash": "538dd8aaa673910e8b75a13a8a401b63b28a5a1c392374052191fd72b6e7017a", + "installs": 45660 + }, + { + "externalId": "google-labs-code/stitch-skills/shadcn-ui", + "githubOwnerId": 161364575, + "owner": "google-labs-code", + "repo": "stitch-skills", + "slug": "shadcn-ui", + "displayName": "shadcn-ui", + "sourceUrl": "https://www.skills.sh/google-labs-code/stitch-skills/shadcn-ui", + "githubRepoUrl": "https://github.com/google-labs-code/stitch-skills", + "sourceContentHash": "4dd06ae456bc83009ef15e761c0a2344b30d48e6a433be29517a4e6ba9135069", + "installs": 45396 + }, + { + "externalId": "expo/skills/expo-tailwind-setup", + "githubOwnerId": 12504344, + "owner": "expo", + "repo": "skills", + "slug": "expo-tailwind-setup", + "displayName": "expo-tailwind-setup", + "sourceUrl": "https://www.skills.sh/expo/skills/expo-tailwind-setup", + "githubRepoUrl": "https://github.com/expo/skills", + "sourceContentHash": "b65ed870773565217aca458528aede2c8313e4b06739203194d63b26f786a751", + "installs": 44681 + }, + { + "externalId": "jakubkrehel/make-interfaces-feel-better/make-interfaces-feel-better", + "githubOwnerId": 87530089, + "owner": "jakubkrehel", + "repo": "make-interfaces-feel-better", + "slug": "make-interfaces-feel-better", + "displayName": "make-interfaces-feel-better", + "sourceUrl": "https://www.skills.sh/jakubkrehel/make-interfaces-feel-better/make-interfaces-feel-better", + "githubRepoUrl": "https://github.com/jakubkrehel/make-interfaces-feel-better", + "sourceContentHash": "d250bbd5c2e2fb9ec6cd498cec294c2ae2c0c45593173496cf0b4b9e4822890a", + "installs": 44382 + }, + { + "externalId": "expo/skills/expo-dev-client", + "githubOwnerId": 12504344, + "owner": "expo", + "repo": "skills", + "slug": "expo-dev-client", + "displayName": "expo-dev-client", + "sourceUrl": "https://www.skills.sh/expo/skills/expo-dev-client", + "githubRepoUrl": "https://github.com/expo/skills", + "sourceContentHash": "2c51bdc5b8b211b707a1669277b2e551500fafb728d5a8f37d0796f762fb0f93", + "installs": 43676 + }, + { + "externalId": "mattpocock/skills/decision-mapping", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "decision-mapping", + "displayName": "decision-mapping", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/decision-mapping", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "338565e2d9b3432b998ad5732a0e11abaa8050502147bcd7db1960566860cd02", + "installs": 43534 + }, + { + "externalId": "heygen-com/hyperframes/figma", + "githubOwnerId": 65394575, + "owner": "heygen-com", + "repo": "hyperframes", + "slug": "figma", + "displayName": "figma", + "sourceUrl": "https://www.skills.sh/heygen-com/hyperframes/figma", + "githubRepoUrl": "https://github.com/heygen-com/hyperframes", + "sourceContentHash": "d7afc3b675a52d40c7883e9c7b72b68a280b89270b6609b15cecc216241b6bf3", + "installs": 43447 + }, + { + "externalId": "expo/skills/upgrading-expo", + "githubOwnerId": 12504344, + "owner": "expo", + "repo": "skills", + "slug": "upgrading-expo", + "displayName": "upgrading-expo", + "sourceUrl": "https://www.skills.sh/expo/skills/upgrading-expo", + "githubRepoUrl": "https://github.com/expo/skills", + "sourceContentHash": "4bc5245ef1d02bdfe287137a82c060eef2ff3786bed51934789a3dcba068ded4", + "installs": 42652 + }, + { + "externalId": "expo/skills/expo-deployment", + "githubOwnerId": 12504344, + "owner": "expo", + "repo": "skills", + "slug": "expo-deployment", + "displayName": "expo-deployment", + "sourceUrl": "https://www.skills.sh/expo/skills/expo-deployment", + "githubRepoUrl": "https://github.com/expo/skills", + "sourceContentHash": "099992dabdf225569cc48d4d7973d03df6b8c56dbe902e0160d15bf8f649ff53", + "installs": 40601 + }, + { + "externalId": "wshobson/agents/nodejs-backend-patterns", + "githubOwnerId": 553618, + "owner": "wshobson", + "repo": "agents", + "slug": "nodejs-backend-patterns", + "displayName": "nodejs-backend-patterns", + "sourceUrl": "https://www.skills.sh/wshobson/agents/nodejs-backend-patterns", + "githubRepoUrl": "https://github.com/wshobson/agents", + "sourceContentHash": "92dfeaec18b14df48242ab558b47be20d42ffc8343c74940bb55fd48f167a4ee", + "installs": 40227 + }, + { + "externalId": "pbakaus/impeccable/arrange", + "githubOwnerId": 43004, + "owner": "pbakaus", + "repo": "impeccable", + "slug": "arrange", + "displayName": "arrange", + "sourceUrl": "https://www.skills.sh/pbakaus/impeccable/arrange", + "githubRepoUrl": "https://github.com/pbakaus/impeccable", + "sourceContentHash": "ca455de6caf54f8ffc52dafbf1f7fe3d8f31d8a5481d332199d3916d24c41418", + "installs": 39831 + }, + { + "externalId": "github/awesome-copilot/git-commit", + "githubOwnerId": 9919, + "owner": "github", + "repo": "awesome-copilot", + "slug": "git-commit", + "displayName": "git-commit", + "sourceUrl": "https://www.skills.sh/github/awesome-copilot/git-commit", + "githubRepoUrl": "https://github.com/github/awesome-copilot", + "sourceContentHash": "d46e05950eac7b9cb114741ef49c7bc08d238da5cafbfee8c0f8665137609af1", + "installs": 39677 + }, + { + "externalId": "expo/skills/expo-cicd-workflows", + "githubOwnerId": 12504344, + "owner": "expo", + "repo": "skills", + "slug": "expo-cicd-workflows", + "displayName": "expo-cicd-workflows", + "sourceUrl": "https://www.skills.sh/expo/skills/expo-cicd-workflows", + "githubRepoUrl": "https://github.com/expo/skills", + "sourceContentHash": "3bf7a96878cd802b3f8ec2f962a3f74f7c3e0693c3b07fd3f93abdc9d5ec6bc9", + "installs": 39620 + }, + { + "externalId": "op7418/humanizer-zh/humanizer-zh", + "githubOwnerId": 13505770, + "owner": "op7418", + "repo": "humanizer-zh", + "slug": "humanizer-zh", + "displayName": "humanizer-zh", + "sourceUrl": "https://www.skills.sh/op7418/humanizer-zh/humanizer-zh", + "githubRepoUrl": "https://github.com/op7418/humanizer-zh", + "sourceContentHash": "5839d1bc92383d71b4099da898c9bd0155215a900bdfe4d964597f96e67c631a", + "installs": 39214 + }, + { + "externalId": "pexoai/pexo-skills/pexo-agent", + "githubOwnerId": 265463178, + "owner": "pexoai", + "repo": "pexo-skills", + "slug": "pexo-agent", + "displayName": "pexo-agent", + "sourceUrl": "https://www.skills.sh/pexoai/pexo-skills/pexo-agent", + "githubRepoUrl": "https://github.com/pexoai/pexo-skills", + "sourceContentHash": "5920922a4b7af3a5c93a64c23d601e4bcbca6b04dae2c9da25ef789c4e978ba5", + "installs": 39004 + }, + { + "externalId": "coreyhaines31/marketingskills/co-marketing", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "co-marketing", + "displayName": "co-marketing", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/co-marketing", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "aeff49286457fa85919d176d1f7b457d713e50a5c8e2f66fa17bfdbb0438f9c3", + "installs": 38947 + }, + { + "externalId": "alchaincyf/huashu-design/huashu-design", + "githubOwnerId": 127714341, + "owner": "alchaincyf", + "repo": "huashu-design", + "slug": "huashu-design", + "displayName": "huashu-design", + "sourceUrl": "https://www.skills.sh/alchaincyf/huashu-design/huashu-design", + "githubRepoUrl": "https://github.com/alchaincyf/huashu-design", + "sourceContentHash": "a9fcb44720ca385fa94d79b4862273c1f72c18f8331f924d4cc8a614d70085f8", + "installs": 38904 + }, + { + "externalId": "skills-collective/skills/ai-image-generation", + "githubOwnerId": 304328805, + "owner": "skills-collective", + "repo": "skills", + "slug": "ai-image-generation", + "displayName": "ai-image-generation", + "sourceUrl": "https://www.skills.sh/skills-collective/skills/ai-image-generation", + "githubRepoUrl": "https://github.com/skills-collective/skills", + "sourceContentHash": "eaab5c3e71522fc3a59ae950b875d8f071d202377ee6e3fda38d27dcf40d3fb7", + "installs": 38837 + }, + { + "externalId": "coreyhaines31/marketingskills/cro", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "cro", + "displayName": "cro", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/cro", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "9cfd73e4e8f3cbf6da63fd5cef63503b5ba4c57a8f812e177921b8fc76fd18f7", + "installs": 38831 + }, + { + "externalId": "cloudflare/skills/cloudflare", + "githubOwnerId": 314135, + "owner": "cloudflare", + "repo": "skills", + "slug": "cloudflare", + "displayName": "cloudflare", + "sourceUrl": "https://www.skills.sh/cloudflare/skills/cloudflare", + "githubRepoUrl": "https://github.com/cloudflare/skills", + "sourceContentHash": "1d1ce7e73976570e1c71117b5d71cf64d708f5a5cea0d93216584ddb1f2483a5", + "installs": 38829 + }, + { + "externalId": "skills-collective/skills/ai-music", + "githubOwnerId": 304328805, + "owner": "skills-collective", + "repo": "skills", + "slug": "ai-music", + "displayName": "ai-music", + "sourceUrl": "https://www.skills.sh/skills-collective/skills/ai-music", + "githubRepoUrl": "https://github.com/skills-collective/skills", + "sourceContentHash": "44585b0aa5fbbfb0ed7868c3703565ac903999ab89ddcd9d543affd4e46b7561", + "installs": 38438 + }, + { + "externalId": "skills-collective/skills/ai-video-generation", + "githubOwnerId": 304328805, + "owner": "skills-collective", + "repo": "skills", + "slug": "ai-video-generation", + "displayName": "ai-video-generation", + "sourceUrl": "https://www.skills.sh/skills-collective/skills/ai-video-generation", + "githubRepoUrl": "https://github.com/skills-collective/skills", + "sourceContentHash": "390af8a7373d5ea440ee1a5a6c7195facd3dd376afcfe0ab43680e61d8958e4a", + "installs": 38394 + }, + { + "externalId": "skills-collective/skills/image-to-video", + "githubOwnerId": 304328805, + "owner": "skills-collective", + "repo": "skills", + "slug": "image-to-video", + "displayName": "image-to-video", + "sourceUrl": "https://www.skills.sh/skills-collective/skills/image-to-video", + "githubRepoUrl": "https://github.com/skills-collective/skills", + "sourceContentHash": "cfb10dff9204f8e631a74ec8414cfcaa60ae8ffe01f83e70850b66f079ee40cc", + "installs": 38390 + }, + { + "externalId": "coreyhaines31/marketingskills/social", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "social", + "displayName": "social", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/social", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "f9edf237b2d71d7d46f0df3262a9d67860346dbb0ce9f5eb7711183bdbc70e9c", + "installs": 38358 + }, + { + "externalId": "coreyhaines31/marketingskills/product-marketing", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "product-marketing", + "displayName": "product-marketing", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/product-marketing", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "f40efe0027fc987f07a6b8ae6c31e62b9f698ec6fa2f31ad00d352992dd1e33a", + "installs": 38197 + }, + { + "externalId": "skills-collective/skills/video-edit", + "githubOwnerId": 304328805, + "owner": "skills-collective", + "repo": "skills", + "slug": "video-edit", + "displayName": "video-edit", + "sourceUrl": "https://www.skills.sh/skills-collective/skills/video-edit", + "githubRepoUrl": "https://github.com/skills-collective/skills", + "sourceContentHash": "5d10803cf6b49f1b1a55e832eeb5181c825bdcd10c9d31ec72031d6b6f0f334c", + "installs": 38107 + }, + { + "externalId": "vercel-labs/agent-skills/vercel-optimize", + "githubOwnerId": 108547162, + "owner": "vercel-labs", + "repo": "agent-skills", + "slug": "vercel-optimize", + "displayName": "vercel-optimize", + "sourceUrl": "https://www.skills.sh/vercel-labs/agent-skills/vercel-optimize", + "githubRepoUrl": "https://github.com/vercel-labs/agent-skills", + "sourceContentHash": "ad0ef9c5c980f824e2ec19cba5c45426aba923a17e71fbaf6d1830574b62cc44", + "installs": 37952 + }, + { + "externalId": "othmanadi/planning-with-files/planning-with-files", + "githubOwnerId": 78882424, + "owner": "othmanadi", + "repo": "planning-with-files", + "slug": "planning-with-files", + "displayName": "planning-with-files", + "sourceUrl": "https://www.skills.sh/othmanadi/planning-with-files/planning-with-files", + "githubRepoUrl": "https://github.com/othmanadi/planning-with-files", + "sourceContentHash": "56bfc05e0bbc2c905e7b88126cc70b15992d0e9920d06ec24f283df3f5689a30", + "installs": 37917 + }, + { + "externalId": "addyosmani/web-quality-skills/accessibility", + "githubOwnerId": 110953, + "owner": "addyosmani", + "repo": "web-quality-skills", + "slug": "accessibility", + "displayName": "accessibility", + "sourceUrl": "https://www.skills.sh/addyosmani/web-quality-skills/accessibility", + "githubRepoUrl": "https://github.com/addyosmani/web-quality-skills", + "sourceContentHash": "572b90a9470c0d962727855a0ceabc1faea701309fb9ae35c2e4b4ccbf1231e1", + "installs": 37849 + }, + { + "externalId": "expo/skills/expo-api-routes", + "githubOwnerId": 12504344, + "owner": "expo", + "repo": "skills", + "slug": "expo-api-routes", + "displayName": "expo-api-routes", + "sourceUrl": "https://www.skills.sh/expo/skills/expo-api-routes", + "githubRepoUrl": "https://github.com/expo/skills", + "sourceContentHash": "1c24993dc7918f66d63652555677b03430f15392b347f871ddccec6209e219e7", + "installs": 37582 + }, + { + "externalId": "emilkowalski/skills/animation-vocabulary", + "githubOwnerId": 36730035, + "owner": "emilkowalski", + "repo": "skills", + "slug": "animation-vocabulary", + "displayName": "animation-vocabulary", + "sourceUrl": "https://www.skills.sh/emilkowalski/skills/animation-vocabulary", + "githubRepoUrl": "https://github.com/emilkowalski/skills", + "sourceContentHash": "16bfb5a10b42c4c2dfeb15c3705f9a2de774b33bf4ae055305130d4abff4b16b", + "installs": 37248 + }, + { + "externalId": "greensock/gsap-skills/gsap-core", + "githubOwnerId": 2386673, + "owner": "greensock", + "repo": "gsap-skills", + "slug": "gsap-core", + "displayName": "gsap-core", + "sourceUrl": "https://www.skills.sh/greensock/gsap-skills/gsap-core", + "githubRepoUrl": "https://github.com/greensock/gsap-skills", + "sourceContentHash": "54ea04b61bcdfe19bf09620f032a801c3ff11fdde2fd9270e7f7a5f484cdd8f5", + "installs": 37176 + }, + { + "externalId": "coreyhaines31/marketingskills/analytics", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "analytics", + "displayName": "analytics", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/analytics", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "6949547e6b55f263f01166818a79ee44d551246f006e0814b44407cbce9e7ccf", + "installs": 37022 + }, + { + "externalId": "firebase/agent-skills/firebase-firestore-standard", + "githubOwnerId": 1335026, + "owner": "firebase", + "repo": "agent-skills", + "slug": "firebase-firestore-standard", + "displayName": "firebase-firestore-standard", + "sourceUrl": "https://www.skills.sh/firebase/agent-skills/firebase-firestore-standard", + "githubRepoUrl": "https://github.com/firebase/agent-skills", + "sourceContentHash": "144a67d0bc25ed53c7069c87f21126f3b88ba87695958ad8fc4019bf94cb3608", + "installs": 36733 + }, + { + "externalId": "coreyhaines31/marketingskills/pricing", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "pricing", + "displayName": "pricing", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/pricing", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "af95f9c7bce34f4296f46e8aa317343a679a9941bb12282aed8e6ff5253d8cad", + "installs": 36568 + }, + { + "externalId": "cloudflare/skills/wrangler", + "githubOwnerId": 314135, + "owner": "cloudflare", + "repo": "skills", + "slug": "wrangler", + "displayName": "wrangler", + "sourceUrl": "https://www.skills.sh/cloudflare/skills/wrangler", + "githubRepoUrl": "https://github.com/cloudflare/skills", + "sourceContentHash": "572a54107e930cff92ad894f65e71dc7cd74ac415833b54a7621dcf9f2474a31", + "installs": 36471 + }, + { + "externalId": "coreyhaines31/marketingskills/ads", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "ads", + "displayName": "ads", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/ads", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "b2df6619a9421ce81f4b7a60ec7e9cf5c0d22eb0566cdd3d14fab4e82293fe55", + "installs": 36342 + }, + { + "externalId": "resciencelab/opc-skills/seo-geo", + "githubOwnerId": 216806946, + "owner": "resciencelab", + "repo": "opc-skills", + "slug": "seo-geo", + "displayName": "seo-geo", + "sourceUrl": "https://www.skills.sh/resciencelab/opc-skills/seo-geo", + "githubRepoUrl": "https://github.com/resciencelab/opc-skills", + "sourceContentHash": "727732683de9dc04027b0b42df9ae94e5586e6cf784fe4906bdf0d928533fb37", + "installs": 36180 + }, + { + "externalId": "coreyhaines31/marketingskills/emails", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "emails", + "displayName": "emails", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/emails", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "5b4b1dd08e968d35247f10ed62fa5e6d23838ca1e070a78aadb830ad32a660da", + "installs": 35956 + }, + { + "externalId": "firebase/agent-skills/firebase-firestore-enterprise-native-mode", + "githubOwnerId": 1335026, + "owner": "firebase", + "repo": "agent-skills", + "slug": "firebase-firestore-enterprise-native-mode", + "displayName": "firebase-firestore-enterprise-native-mode", + "sourceUrl": "https://www.skills.sh/firebase/agent-skills/firebase-firestore-enterprise-native-mode", + "githubRepoUrl": "https://github.com/firebase/agent-skills", + "sourceContentHash": "2c99010ce9dd7051308427b13a81546fc48c35eaf97462ff7521b829e737f1f7", + "installs": 35795 + }, + { + "externalId": "coreyhaines31/marketingskills/launch", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "launch", + "displayName": "launch", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/launch", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "b67caafb646f1d15db3d5f2fd785c7df5062e3961aee14e9bbf196ca2b6313a7", + "installs": 35704 + }, + { + "externalId": "coreyhaines31/marketingskills/competitors", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "competitors", + "displayName": "competitors", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/competitors", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "97072ba8dfc0632a3908a92b7ee6779b03a654b97cfb95228511323edce98ede", + "installs": 35616 + }, + { + "externalId": "coreyhaines31/marketingskills/schema", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "schema", + "displayName": "schema", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/schema", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "741af7970a6d88c481bd27fad9761abcb6cad22c13fa28f2948eaa15ad00bda1", + "installs": 35556 + }, + { + "externalId": "greensock/gsap-skills/gsap-scrolltrigger", + "githubOwnerId": 2386673, + "owner": "greensock", + "repo": "gsap-skills", + "slug": "gsap-scrolltrigger", + "displayName": "gsap-scrolltrigger", + "sourceUrl": "https://www.skills.sh/greensock/gsap-skills/gsap-scrolltrigger", + "githubRepoUrl": "https://github.com/greensock/gsap-skills", + "sourceContentHash": "b1997611820c8b7fdac73312492b1af00308649af0e5ca3426ef905f1903a98d", + "installs": 35530 + }, + { + "externalId": "hugmouse/skills/value", + "githubOwnerId": 44648612, + "owner": "hugmouse", + "repo": "skills", + "slug": "value", + "displayName": "value", + "sourceUrl": "https://www.skills.sh/hugmouse/skills/value", + "githubRepoUrl": "https://github.com/hugmouse/skills", + "sourceContentHash": "c386d792321ee1767e093dd5202ec9e4f628cc30e6a1168a6f9e4cad66444702", + "installs": 35510 + }, + { + "externalId": "samber/cc-skills-golang/golang-code-style", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-code-style", + "displayName": "golang-code-style", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-code-style", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "9b8298e0340402c2c309bfda9483847aed71d2cbf4cfb53156c5cb1a0a6298c5", + "installs": 35298 + }, + { + "externalId": "samber/cc-skills-golang/golang-error-handling", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-error-handling", + "displayName": "golang-error-handling", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-error-handling", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "88a9cc9703ff298817999d3f206087350445e0c1e30096e08004fc7abb77c104", + "installs": 34857 + }, + { + "externalId": "coreyhaines31/marketingskills/onboarding", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "onboarding", + "displayName": "onboarding", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/onboarding", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "817fea07cc29a9715f2315ad5a3f1bca10a6ad6929a473fd471af09c08cc4d6d", + "installs": 34803 + }, + { + "externalId": "samber/cc-skills-golang/golang-performance", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-performance", + "displayName": "golang-performance", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-performance", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "e0f48df1469d2cfc412772632ee2a65653f027fe3db094361cfb8499591dcd6c", + "installs": 34570 + }, + { + "externalId": "samber/cc-skills-golang/golang-testing", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-testing", + "displayName": "golang-testing", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-testing", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "2cfba574752711d13321c097925e1cd893ccef0193c67b86bcdbe890e87f899b", + "installs": 34553 + }, + { + "externalId": "samber/cc-skills-golang/golang-design-patterns", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-design-patterns", + "displayName": "golang-design-patterns", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-design-patterns", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "f67f37c15ca97cff4d59fa564c700ac57106b016046fa6f8c9f672ea880fe708", + "installs": 34521 + }, + { + "externalId": "replicas-group/skill/replicas-agent", + "githubOwnerId": 268698679, + "owner": "replicas-group", + "repo": "skill", + "slug": "replicas-agent", + "displayName": "replicas-agent", + "sourceUrl": "https://www.skills.sh/replicas-group/skill/replicas-agent", + "githubRepoUrl": "https://github.com/replicas-group/skill", + "sourceContentHash": "07a744e38e606afc9d5ca666eed5a7d4aca6a5c916f2469a9bbba5d6a97ee809", + "installs": 34365 + }, + { + "externalId": "coreyhaines31/marketingskills/ab-testing", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "ab-testing", + "displayName": "ab-testing", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/ab-testing", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "d7b241331d1030c1752a19abbe4f0970315274b82f9573b55acffd19eb466409", + "installs": 34362 + }, + { + "externalId": "google-labs-code/stitch-skills/remotion", + "githubOwnerId": 161364575, + "owner": "google-labs-code", + "repo": "stitch-skills", + "slug": "remotion", + "displayName": "remotion", + "sourceUrl": "https://www.skills.sh/google-labs-code/stitch-skills/remotion", + "githubRepoUrl": "https://github.com/google-labs-code/stitch-skills", + "sourceContentHash": "45c1d1b6d4217c8ccee52b5d3b61028ed668a72d76cf9a422bc19368c8242493", + "installs": 34278 + }, + { + "externalId": "samber/cc-skills-golang/golang-security", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-security", + "displayName": "golang-security", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-security", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "73c1c45d192f0e94daeb4c08b8464b9647a64b0378789e9d47b601c93d44872c", + "installs": 34266 + }, + { + "externalId": "greensock/gsap-skills/gsap-performance", + "githubOwnerId": 2386673, + "owner": "greensock", + "repo": "gsap-skills", + "slug": "gsap-performance", + "displayName": "gsap-performance", + "sourceUrl": "https://www.skills.sh/greensock/gsap-skills/gsap-performance", + "githubRepoUrl": "https://github.com/greensock/gsap-skills", + "sourceContentHash": "ae9bfd168e3385ea47ed2c0a214272d1ac821580ea9dc996674dbbe0dfd0a93f", + "installs": 34253 + }, + { + "externalId": "momentic-ai/skills/momentic-test", + "githubOwnerId": 139182344, + "owner": "momentic-ai", + "repo": "skills", + "slug": "momentic-test", + "displayName": "momentic-test", + "sourceUrl": "https://www.skills.sh/momentic-ai/skills/momentic-test", + "githubRepoUrl": "https://github.com/momentic-ai/skills", + "sourceContentHash": "601fdfb2b4d582a4822659c446c6e237dfa180ab60aa947fbe90972574bd97c5", + "installs": 34236 + }, + { + "externalId": "momentic-ai/skills/momentic-result-classification", + "githubOwnerId": 139182344, + "owner": "momentic-ai", + "repo": "skills", + "slug": "momentic-result-classification", + "displayName": "momentic-result-classification", + "sourceUrl": "https://www.skills.sh/momentic-ai/skills/momentic-result-classification", + "githubRepoUrl": "https://github.com/momentic-ai/skills", + "sourceContentHash": "a03b8fb48e5de51d89f0c0bf788b148cb7156eacc241f7eb5af39faf08eb841f", + "installs": 34227 + }, + { + "externalId": "101-skills/skills/web-search", + "githubOwnerId": 301088244, + "owner": "101-skills", + "repo": "skills", + "slug": "web-search", + "displayName": "web-search", + "sourceUrl": "https://www.skills.sh/101-skills/skills/web-search", + "githubRepoUrl": "https://github.com/101-skills/skills", + "sourceContentHash": "4eca27aa1379ba5d0810a944648fec4d898caeab1c3e2bfacdb66da504031ee8", + "installs": 34226 + }, + { + "externalId": "greensock/gsap-skills/gsap-timeline", + "githubOwnerId": 2386673, + "owner": "greensock", + "repo": "gsap-skills", + "slug": "gsap-timeline", + "displayName": "gsap-timeline", + "sourceUrl": "https://www.skills.sh/greensock/gsap-skills/gsap-timeline", + "githubRepoUrl": "https://github.com/greensock/gsap-skills", + "sourceContentHash": "8f298b6e82151cf937ff7f94e2fb1a0cd505fc7f498f8dfbac3368f7cffef274", + "installs": 34130 + }, + { + "externalId": "101-skills/skills/python-executor", + "githubOwnerId": 301088244, + "owner": "101-skills", + "repo": "skills", + "slug": "python-executor", + "displayName": "python-executor", + "sourceUrl": "https://www.skills.sh/101-skills/skills/python-executor", + "githubRepoUrl": "https://github.com/101-skills/skills", + "sourceContentHash": "81e4b2f65cdeb73772a9c06be4aef10b5c2c21d3bdc58b01541e0a5d047bfb87", + "installs": 34090 + }, + { + "externalId": "101-skills/skills/agent-tools", + "githubOwnerId": 301088244, + "owner": "101-skills", + "repo": "skills", + "slug": "agent-tools", + "displayName": "agent-tools", + "sourceUrl": "https://www.skills.sh/101-skills/skills/agent-tools", + "githubRepoUrl": "https://github.com/101-skills/skills", + "sourceContentHash": "e7e2e34a16650d2736b829aeb54c570d2ae9ba8c7ce7959364e7868038b6a8e1", + "installs": 34089 + }, + { + "externalId": "101-skills/skills/infsh-cli", + "githubOwnerId": 301088244, + "owner": "101-skills", + "repo": "skills", + "slug": "infsh-cli", + "displayName": "infsh-cli", + "sourceUrl": "https://www.skills.sh/101-skills/skills/infsh-cli", + "githubRepoUrl": "https://github.com/101-skills/skills", + "sourceContentHash": "d66cdb53eca21b67498f48e19d56bc59a7fe7bb94b4f1d38ffc93e4703b46cbd", + "installs": 34083 + }, + { + "externalId": "firebase/agent-skills/firebase-ai-logic", + "githubOwnerId": 1335026, + "owner": "firebase", + "repo": "agent-skills", + "slug": "firebase-ai-logic", + "displayName": "firebase-ai-logic", + "sourceUrl": "https://www.skills.sh/firebase/agent-skills/firebase-ai-logic", + "githubRepoUrl": "https://github.com/firebase/agent-skills", + "sourceContentHash": "511d979ef49f0beb7d0d45c73c7d3f9d59eba1b955fbff9ca745edd5a9cb207a", + "installs": 34080 + }, + { + "externalId": "coreyhaines31/marketingskills/signup", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "signup", + "displayName": "signup", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/signup", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "f6e61cdeb665a455394ae70036bb03bab12e8f7d88f6c99c4a35059b880b3799", + "installs": 34030 + }, + { + "externalId": "addyosmani/web-quality-skills/seo", + "githubOwnerId": 110953, + "owner": "addyosmani", + "repo": "web-quality-skills", + "slug": "seo", + "displayName": "seo", + "sourceUrl": "https://www.skills.sh/addyosmani/web-quality-skills/seo", + "githubRepoUrl": "https://github.com/addyosmani/web-quality-skills", + "sourceContentHash": "43c0fe04da521f604c7b2bbb34f157896c115398f5bc17a4f1de6b4ead5ca779", + "installs": 34019 + }, + { + "externalId": "coreyhaines31/marketingskills/free-tools", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "free-tools", + "displayName": "free-tools", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/free-tools", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "bb75cbc2f73fac3406dc7acd9a431d4039b611896f0e2ac2fcb9c1dd6c7e5876", + "installs": 34002 + }, + { + "externalId": "coreyhaines31/marketingskills/referrals", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "referrals", + "displayName": "referrals", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/referrals", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "26efc4f2b6ef468fefa56605bbb59aeb9bb907f39fa260bccbaa523b6a40fa75", + "installs": 33924 + }, + { + "externalId": "samber/cc-skills-golang/golang-concurrency", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-concurrency", + "displayName": "golang-concurrency", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-concurrency", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "7ba2bdd472b7b306cfa975b60cdf1cc9585dbc85ddcd8367ee2162d84549acdb", + "installs": 33897 + }, + { + "externalId": "samber/cc-skills-golang/golang-naming", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-naming", + "displayName": "golang-naming", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-naming", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "1f91443f14b71ece0994209517fb9c2f63b4b647664498c65d772c2b023b5b9d", + "installs": 33775 + }, + { + "externalId": "coreyhaines31/marketingskills/popups", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "popups", + "displayName": "popups", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/popups", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "fa5ebf0db85ce0487f95d6a091791f3b5424d4763aae43eaa514952ef96813e5", + "installs": 33753 + }, + { + "externalId": "anysearch-ai/anysearch-skill/anysearch", + "githubOwnerId": 271186954, + "owner": "anysearch-ai", + "repo": "anysearch-skill", + "slug": "anysearch", + "displayName": "anysearch", + "sourceUrl": "https://www.skills.sh/anysearch-ai/anysearch-skill/anysearch", + "githubRepoUrl": "https://github.com/anysearch-ai/anysearch-skill", + "sourceContentHash": "ada4f807902463c6d409ee0a2814390a656774bf3d766c84ee22361715122636", + "installs": 33734 + }, + { + "externalId": "samber/cc-skills-golang/golang-data-structures", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-data-structures", + "displayName": "golang-data-structures", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-data-structures", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "d30e33ba25917fa8558a63a7bc2b59690cb0589c153dd9b2c565ba54681e252b", + "installs": 33708 + }, + { + "externalId": "samber/cc-skills-golang/golang-documentation", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-documentation", + "displayName": "golang-documentation", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-documentation", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "4ddf9295cb959ec759755a06e15e30cb7f31e2f4a6fb6acc457a09e7fb08104f", + "installs": 33695 + }, + { + "externalId": "coreyhaines31/marketingskills/paywalls", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "paywalls", + "displayName": "paywalls", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/paywalls", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "fc48adfda8a0a159dec1b786070ecf83788ec181191c145ccd5038e379c91a73", + "installs": 33684 + }, + { + "externalId": "samber/cc-skills-golang/golang-context", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-context", + "displayName": "golang-context", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-context", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "8bcd50fcb27ce633b9945f00360d81f81f59065a75b592c1fe4c09d2e8e9b799", + "installs": 33673 + }, + { + "externalId": "samber/cc-skills-golang/golang-database", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-database", + "displayName": "golang-database", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-database", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "1f5d606c404b568a0be07f8276da6c0e48adff7e55251d22712ae2845c602e1b", + "installs": 33623 + }, + { + "externalId": "samber/cc-skills-golang/golang-modernize", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-modernize", + "displayName": "golang-modernize", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-modernize", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "6e88e9966c462a0d06e90806cdf0bb349a7b6b28aa2419c230548ddbe667d156", + "installs": 33594 + }, + { + "externalId": "samber/cc-skills-golang/golang-safety", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-safety", + "displayName": "golang-safety", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-safety", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "972fcfe9f2a8f3ba08de318b70a69996291dcf85a8428b24ea9d8979e9c64826", + "installs": 33586 + }, + { + "externalId": "coreyhaines31/marketingskills/aso", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "aso", + "displayName": "aso", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/aso", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "804be01ef55805c65699a9e90da756160438b2dc33b147d2fd65c6203b1ffc4c", + "installs": 33544 + }, + { + "externalId": "samber/cc-skills-golang/golang-project-layout", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-project-layout", + "displayName": "golang-project-layout", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-project-layout", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "e799c4e2cef9a74383d6784b2d59d262b124b05ee67448808c5d582897c4b62d", + "installs": 33507 + }, + { + "externalId": "greensock/gsap-skills/gsap-plugins", + "githubOwnerId": 2386673, + "owner": "greensock", + "repo": "gsap-skills", + "slug": "gsap-plugins", + "displayName": "gsap-plugins", + "sourceUrl": "https://www.skills.sh/greensock/gsap-skills/gsap-plugins", + "githubRepoUrl": "https://github.com/greensock/gsap-skills", + "sourceContentHash": "53f97dcc40f530b8127ea2052d2d86387598154f7b66ff3c571d983fcb4dda18", + "installs": 33486 + }, + { + "externalId": "firebase/agent-skills/developing-genkit-python", + "githubOwnerId": 1335026, + "owner": "firebase", + "repo": "agent-skills", + "slug": "developing-genkit-python", + "displayName": "developing-genkit-python", + "sourceUrl": "https://www.skills.sh/firebase/agent-skills/developing-genkit-python", + "githubRepoUrl": "https://github.com/firebase/agent-skills", + "sourceContentHash": "1389398bf1feccf305dcc77379bcab2a3ef74f872c5fe5c42b5a6618f1f34f1f", + "installs": 33467 + }, + { + "externalId": "samber/cc-skills-golang/golang-troubleshooting", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-troubleshooting", + "displayName": "golang-troubleshooting", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-troubleshooting", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "44dcc70432749ff903c9b0366769bfc2018eff48ff2c886ece79c2db3515a360", + "installs": 33449 + }, + { + "externalId": "samber/cc-skills-golang/golang-lint", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-lint", + "displayName": "golang-lint", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-lint", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "ec8b2127f841658b29332b928e4a0e3e41718715b4150275c4fe19bb764321df", + "installs": 33433 + }, + { + "externalId": "samber/cc-skills-golang/golang-popular-libraries", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-popular-libraries", + "displayName": "golang-popular-libraries", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-popular-libraries", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "2214a5ca274427a5b58dac37a7ad630603a7ec0f035fa3dcda39a96b8790716e", + "installs": 33376 + }, + { + "externalId": "samber/cc-skills-golang/golang-structs-interfaces", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-structs-interfaces", + "displayName": "golang-structs-interfaces", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-structs-interfaces", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "080c39896e15a2e6dca0711c33d4d20afd0ff30e5f743ea0335aa12f560e0f94", + "installs": 33341 + }, + { + "externalId": "samber/cc-skills-golang/golang-observability", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-observability", + "displayName": "golang-observability", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-observability", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "ff3fa064dbb5c13285cc78e83cee31a98ce486f0fb9b1667bc489e09b2bb87a6", + "installs": 33323 + }, + { + "externalId": "samber/cc-skills-golang/golang-dependency-management", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-dependency-management", + "displayName": "golang-dependency-management", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-dependency-management", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "7f611456e307e36e7b51776193bbb8aa0531571d605c77918b3b595c7eca58d1", + "installs": 33310 + }, + { + "externalId": "samber/cc-skills-golang/golang-dependency-injection", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-dependency-injection", + "displayName": "golang-dependency-injection", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-dependency-injection", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "b16817a945e6ea0ca78c99afd2061154afcb226e485da21820975e423246f289", + "installs": 33263 + }, + { + "externalId": "samber/cc-skills-golang/golang-benchmark", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-benchmark", + "displayName": "golang-benchmark", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-benchmark", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "4da60bf3de3964b965ba97c226fc78ef6d70ad26792a931631a4648ecf7471a7", + "installs": 33226 + }, + { + "externalId": "samber/cc-skills-golang/golang-cli", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-cli", + "displayName": "golang-cli", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-cli", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "911566f61dd6fb1250d1e527b73e4c8195839762f5313a083b6515114a77a3cd", + "installs": 33218 + }, + { + "externalId": "samber/cc-skills-golang/golang-grpc", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-grpc", + "displayName": "golang-grpc", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-grpc", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "2f290ee21f938fa2118efce4d19c8743f9636847415a399478707d65dd43bc7c", + "installs": 33204 + }, + { + "externalId": "samber/cc-skills-golang/golang-continuous-integration", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-continuous-integration", + "displayName": "golang-continuous-integration", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-continuous-integration", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "62b9251bb23ae6e53a10c1ed3a42b91c05626efa7f9df7fa123d38a1272e9e28", + "installs": 33107 + }, + { + "externalId": "samber/cc-skills-golang/golang-stretchr-testify", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-stretchr-testify", + "displayName": "golang-stretchr-testify", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-stretchr-testify", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "7c6a19e73f7c7202edac54d770a537c3162039ca7e99033f51d31b5bca0c1043", + "installs": 33084 + }, + { + "externalId": "samber/cc-skills-golang/golang-stay-updated", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-stay-updated", + "displayName": "golang-stay-updated", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-stay-updated", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "984732a082d55d444c631827d694c8a2378bb52c1a245f1b11b043b616a08bbd", + "installs": 32897 + }, + { + "externalId": "greensock/gsap-skills/gsap-utils", + "githubOwnerId": 2386673, + "owner": "greensock", + "repo": "gsap-skills", + "slug": "gsap-utils", + "displayName": "gsap-utils", + "sourceUrl": "https://www.skills.sh/greensock/gsap-skills/gsap-utils", + "githubRepoUrl": "https://github.com/greensock/gsap-skills", + "sourceContentHash": "c020319818c823ad19766a142c300d0b2ae644dd3fd685b09cf7a0d069edf67b", + "installs": 32858 + }, + { + "externalId": "samber/cc-skills-golang/golang-samber-lo", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-samber-lo", + "displayName": "golang-samber-lo", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-samber-lo", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "5ee42b546b5f749d9bf9750e695b08426c8c4f5325554aee8496c2f060190967", + "installs": 32853 + }, + { + "externalId": "samber/cc-skills-golang/golang-samber-do", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-samber-do", + "displayName": "golang-samber-do", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-samber-do", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "6aeb126f40e3d1d5658e2f15fc5e5312ee76741cf857d8493b0b6d45e87bc6f1", + "installs": 32786 + }, + { + "externalId": "samber/cc-skills-golang/golang-samber-oops", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-samber-oops", + "displayName": "golang-samber-oops", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-samber-oops", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "a991c3ed3b4bf96cfa13568e6b3308410c0748929865501d56f94a2e3ae2dacd", + "installs": 32755 + }, + { + "externalId": "samber/cc-skills-golang/golang-samber-slog", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-samber-slog", + "displayName": "golang-samber-slog", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-samber-slog", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "1abd7fe5f7bde00010e56604f7a7c39e2534d6acbb772740e4b84791a6fc6ab7", + "installs": 32753 + }, + { + "externalId": "samber/cc-skills-golang/golang-samber-mo", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-samber-mo", + "displayName": "golang-samber-mo", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-samber-mo", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "2747831fc17ea01e40fc6641877505e8385326e5a890a417819be228d104aca2", + "installs": 32735 + }, + { + "externalId": "samber/cc-skills-golang/golang-samber-ro", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-samber-ro", + "displayName": "golang-samber-ro", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-samber-ro", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "25383d42a2414915086ea89498acaaf73ed189250e0a530863b4797860443083", + "installs": 32723 + }, + { + "externalId": "samber/cc-skills-golang/golang-samber-hot", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-samber-hot", + "displayName": "golang-samber-hot", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-samber-hot", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "95bf4f26ef8340e099532d52f2116246227c068498c56046694ee0395377f45b", + "installs": 32720 + }, + { + "externalId": "greensock/gsap-skills/gsap-react", + "githubOwnerId": 2386673, + "owner": "greensock", + "repo": "gsap-skills", + "slug": "gsap-react", + "displayName": "gsap-react", + "sourceUrl": "https://www.skills.sh/greensock/gsap-skills/gsap-react", + "githubRepoUrl": "https://github.com/greensock/gsap-skills", + "sourceContentHash": "462bf98687e2e404b99e6eb319c389427e91261b02887a2e99989d58c94dfe5f", + "installs": 32405 + }, + { + "externalId": "charon-fan/agent-playbook/self-improving-agent", + "githubOwnerId": 264218814, + "owner": "charon-fan", + "repo": "agent-playbook", + "slug": "self-improving-agent", + "displayName": "self-improving-agent", + "sourceUrl": "https://www.skills.sh/charon-fan/agent-playbook/self-improving-agent", + "githubRepoUrl": "https://github.com/charon-fan/agent-playbook", + "sourceContentHash": "36f42fd40babbbe4a28626f2338982a6f82a6bbf92c6a06a2d2633f88d7a5337", + "installs": 32110 + }, + { + "externalId": "jimliu/baoyu-skills/baoyu-post-to-wechat", + "githubOwnerId": 648674, + "owner": "jimliu", + "repo": "baoyu-skills", + "slug": "baoyu-post-to-wechat", + "displayName": "baoyu-post-to-wechat", + "sourceUrl": "https://www.skills.sh/jimliu/baoyu-skills/baoyu-post-to-wechat", + "githubRepoUrl": "https://github.com/jimliu/baoyu-skills", + "sourceContentHash": "6e4f77aecbfb890e362c83052132f47a05d325099124d64a26a4059ee7fb19cd", + "installs": 31593 + }, + { + "externalId": "samber/cc-skills-golang/golang-swagger", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-swagger", + "displayName": "golang-swagger", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-swagger", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "c19874e5fc9702daee4d13baa9cdca3691ebd44c38fe98dfc01df0aa03997210", + "installs": 31388 + }, + { + "externalId": "samber/cc-skills-golang/golang-graphql", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-graphql", + "displayName": "golang-graphql", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-graphql", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "6f539d0a83e8789dccb13b3505f82a9facee456c12ffce2a1f15f7ad6890d9a1", + "installs": 31351 + }, + { + "externalId": "samber/cc-skills-golang/golang-spf13-cobra", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-spf13-cobra", + "displayName": "golang-spf13-cobra", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-spf13-cobra", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "44bf1df1d6a6cf6f14b1a917ceaa9f992b1d7be4fd880744b13d93cd36f49515", + "installs": 31332 + }, + { + "externalId": "vercel-labs/agent-skills/writing-guidelines", + "githubOwnerId": 108547162, + "owner": "vercel-labs", + "repo": "agent-skills", + "slug": "writing-guidelines", + "displayName": "writing-guidelines", + "sourceUrl": "https://www.skills.sh/vercel-labs/agent-skills/writing-guidelines", + "githubRepoUrl": "https://github.com/vercel-labs/agent-skills", + "sourceContentHash": "25aa3a33a97bddbcb1847ce4a6169106ec25e5b19b15b99c66e75ab383866ef2", + "installs": 31283 + }, + { + "externalId": "samber/cc-skills-golang/golang-spf13-viper", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-spf13-viper", + "displayName": "golang-spf13-viper", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-spf13-viper", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "e84fc5b2e1e3f295e95195ac8d88cb1aafa35eb6df8112576b1586fbb766ba80", + "installs": 31257 + }, + { + "externalId": "samber/cc-skills-golang/golang-uber-fx", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-uber-fx", + "displayName": "golang-uber-fx", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-uber-fx", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "d96558f69275aaa60089c2f334c19c9aaa1d4bbfa5f36557cf7dd6c0bfe59b88", + "installs": 31211 + }, + { + "externalId": "greensock/gsap-skills/gsap-frameworks", + "githubOwnerId": 2386673, + "owner": "greensock", + "repo": "gsap-skills", + "slug": "gsap-frameworks", + "displayName": "gsap-frameworks", + "sourceUrl": "https://www.skills.sh/greensock/gsap-skills/gsap-frameworks", + "githubRepoUrl": "https://github.com/greensock/gsap-skills", + "sourceContentHash": "2d9d52c4d97f4f41c8260febc151dfb3db2f28fed6a07cd07f4481022151b68b", + "installs": 31206 + }, + { + "externalId": "samber/cc-skills-golang/golang-google-wire", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-google-wire", + "displayName": "golang-google-wire", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-google-wire", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "bf8faafe009f3eaf3639d0c5d4e9d9108fc3209d3c1e4a6abca96b79d1e8840b", + "installs": 31190 + }, + { + "externalId": "mattpocock/skills/setup-ts-deep-modules", + "githubOwnerId": 28293365, + "owner": "mattpocock", + "repo": "skills", + "slug": "setup-ts-deep-modules", + "displayName": "setup-ts-deep-modules", + "sourceUrl": "https://www.skills.sh/mattpocock/skills/setup-ts-deep-modules", + "githubRepoUrl": "https://github.com/mattpocock/skills", + "sourceContentHash": "6596fef591b8eb74284d9cf5007cc56b90f75ebaeb6d24d5a84dce3adb47a835", + "installs": 31189 + }, + { + "externalId": "samber/cc-skills-golang/golang-uber-dig", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-uber-dig", + "displayName": "golang-uber-dig", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-uber-dig", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "b9b784a7eb196381303b693237d85f41cace8a616bbc0900bc40932a4806c152", + "installs": 31178 + }, + { + "externalId": "firecrawl/firecrawl-workflows/firecrawl-deep-research", + "githubOwnerId": 135057108, + "owner": "firecrawl", + "repo": "firecrawl-workflows", + "slug": "firecrawl-deep-research", + "displayName": "firecrawl-deep-research", + "sourceUrl": "https://www.skills.sh/firecrawl/firecrawl-workflows/firecrawl-deep-research", + "githubRepoUrl": "https://github.com/firecrawl/firecrawl-workflows", + "sourceContentHash": "efd94afe789535fbc53451aedf9d7b8013bb379c617b0e926ec05fe7909cb265", + "installs": 30713 + }, + { + "externalId": "expo/skills/use-dom", + "githubOwnerId": 12504344, + "owner": "expo", + "repo": "skills", + "slug": "use-dom", + "displayName": "use-dom", + "sourceUrl": "https://www.skills.sh/expo/skills/use-dom", + "githubRepoUrl": "https://github.com/expo/skills", + "sourceContentHash": "4bae0cc95cbaebeae83fac79b02010b90166765bc74603d4962fffaf901d334b", + "installs": 30673 + }, + { + "externalId": "pbakaus/impeccable/shape", + "githubOwnerId": 43004, + "owner": "pbakaus", + "repo": "impeccable", + "slug": "shape", + "displayName": "shape", + "sourceUrl": "https://www.skills.sh/pbakaus/impeccable/shape", + "githubRepoUrl": "https://github.com/pbakaus/impeccable", + "sourceContentHash": "2c5d6cf888a40f6b9f8a97857ebbc01de004a4352e998ee2a9a9eb331c607cbf", + "installs": 30582 + }, + { + "externalId": "antfu/skills/vite", + "githubOwnerId": 11247099, + "owner": "antfu", + "repo": "skills", + "slug": "vite", + "displayName": "vite", + "sourceUrl": "https://www.skills.sh/antfu/skills/vite", + "githubRepoUrl": "https://github.com/antfu/skills", + "sourceContentHash": "425b1566283a0248268db0963cc8b359f593e5a577feee9009a445bafd2c8f06", + "installs": 30518 + }, + { + "externalId": "firecrawl/firecrawl-workflows/firecrawl-research-papers", + "githubOwnerId": 135057108, + "owner": "firecrawl", + "repo": "firecrawl-workflows", + "slug": "firecrawl-research-papers", + "displayName": "firecrawl-research-papers", + "sourceUrl": "https://www.skills.sh/firecrawl/firecrawl-workflows/firecrawl-research-papers", + "githubRepoUrl": "https://github.com/firecrawl/firecrawl-workflows", + "sourceContentHash": "e4318d250d605110f1a348afbba600b1b30d153a1bf87ccc859a1a7e4447b33b", + "installs": 30232 + }, + { + "externalId": "wshobson/agents/brand-landingpage", + "githubOwnerId": 553618, + "owner": "wshobson", + "repo": "agents", + "slug": "brand-landingpage", + "displayName": "brand-landingpage", + "sourceUrl": "https://www.skills.sh/wshobson/agents/brand-landingpage", + "githubRepoUrl": "https://github.com/wshobson/agents", + "sourceContentHash": "6084c46e9b4e5ffb32e2bdfc8c273553552bff7093f84e0b77bab1222c197a1a", + "installs": 30084 + }, + { + "externalId": "samber/cc-skills-golang/golang-how-to", + "githubOwnerId": 2951285, + "owner": "samber", + "repo": "cc-skills-golang", + "slug": "golang-how-to", + "displayName": "golang-how-to", + "sourceUrl": "https://www.skills.sh/samber/cc-skills-golang/golang-how-to", + "githubRepoUrl": "https://github.com/samber/cc-skills-golang", + "sourceContentHash": "917c2f30f367fb1cb037cc8b3349209d6b4e914dad6aec5cc9a3a7cfc465612e", + "installs": 30072 + }, + { + "externalId": "jimliu/baoyu-skills/baoyu-image-gen", + "githubOwnerId": 648674, + "owner": "jimliu", + "repo": "baoyu-skills", + "slug": "baoyu-image-gen", + "displayName": "baoyu-image-gen", + "sourceUrl": "https://www.skills.sh/jimliu/baoyu-skills/baoyu-image-gen", + "githubRepoUrl": "https://github.com/jimliu/baoyu-skills", + "sourceContentHash": "349dec081be100ed8e4a7d6445480ff6ac74d6eb9c7189c6c37387bde8c375f7", + "installs": 30006 + }, + { + "externalId": "firecrawl/firecrawl-workflows/firecrawl-website-design-clone", + "githubOwnerId": 135057108, + "owner": "firecrawl", + "repo": "firecrawl-workflows", + "slug": "firecrawl-website-design-clone", + "displayName": "firecrawl-website-design-clone", + "sourceUrl": "https://www.skills.sh/firecrawl/firecrawl-workflows/firecrawl-website-design-clone", + "githubRepoUrl": "https://github.com/firecrawl/firecrawl-workflows", + "sourceContentHash": "d6f2b5aecd2ff2fb10356df5e0cf428c96227a3aad606ac7c4c1bc906f9e3a12", + "installs": 29892 + }, + { + "externalId": "cloudflare/skills/workers-best-practices", + "githubOwnerId": 314135, + "owner": "cloudflare", + "repo": "skills", + "slug": "workers-best-practices", + "displayName": "workers-best-practices", + "sourceUrl": "https://www.skills.sh/cloudflare/skills/workers-best-practices", + "githubRepoUrl": "https://github.com/cloudflare/skills", + "sourceContentHash": "80a8005748e412cdf3b83eb07fc960a5017e907aa05730448a57669488a8dc13", + "installs": 29843 + }, + { + "externalId": "antibrow/anti-detect-browser-skills/anti-detect-browser", + "githubOwnerId": 268527319, + "owner": "antibrow", + "repo": "anti-detect-browser-skills", + "slug": "anti-detect-browser", + "displayName": "anti-detect-browser", + "sourceUrl": "https://www.skills.sh/antibrow/anti-detect-browser-skills/anti-detect-browser", + "githubRepoUrl": "https://github.com/antibrow/anti-detect-browser-skills", + "sourceContentHash": "cbe00f95e60176d3436caa214675c43d91f1052d41879443bbb766187dfb9d5a", + "installs": 29832 + }, + { + "externalId": "antfu/skills/vue", + "githubOwnerId": 11247099, + "owner": "antfu", + "repo": "skills", + "slug": "vue", + "displayName": "vue", + "sourceUrl": "https://www.skills.sh/antfu/skills/vue", + "githubRepoUrl": "https://github.com/antfu/skills", + "sourceContentHash": "ee98cd1b30fc24ff0ad4bfc0f44ec4d5d809fc2bfda43c6b0df42bd909a0265e", + "installs": 29806 + }, + { + "externalId": "better-auth/skills/create-auth-skill", + "githubOwnerId": 163827765, + "owner": "better-auth", + "repo": "skills", + "slug": "create-auth-skill", + "displayName": "create-auth-skill", + "sourceUrl": "https://www.skills.sh/better-auth/skills/create-auth-skill", + "githubRepoUrl": "https://github.com/better-auth/skills", + "sourceContentHash": "41c380e57117059c7d0d5da4c9babbf161a233df2e0ed79a20158aacf7782238", + "installs": 29753 + }, + { + "externalId": "firecrawl/firecrawl-workflows/firecrawl-market-research", + "githubOwnerId": 135057108, + "owner": "firecrawl", + "repo": "firecrawl-workflows", + "slug": "firecrawl-market-research", + "displayName": "firecrawl-market-research", + "sourceUrl": "https://www.skills.sh/firecrawl/firecrawl-workflows/firecrawl-market-research", + "githubRepoUrl": "https://github.com/firecrawl/firecrawl-workflows", + "sourceContentHash": "28e42a0b97dc436ea36e626263ca389f2c10a5502d91661c0789d8d0df18091b", + "installs": 29604 + }, + { + "externalId": "heygen-com/hyperframes/graphic-overlays", + "githubOwnerId": 65394575, + "owner": "heygen-com", + "repo": "hyperframes", + "slug": "graphic-overlays", + "displayName": "graphic-overlays", + "sourceUrl": "https://www.skills.sh/heygen-com/hyperframes/graphic-overlays", + "githubRepoUrl": "https://github.com/heygen-com/hyperframes", + "sourceContentHash": "a0a346f6d04603105decdf1dca7b93d3421b9af08b06f8fe289c11635ac73c7a", + "installs": 29390 + }, + { + "externalId": "wshobson/agents/python-performance-optimization", + "githubOwnerId": 553618, + "owner": "wshobson", + "repo": "agents", + "slug": "python-performance-optimization", + "displayName": "python-performance-optimization", + "sourceUrl": "https://www.skills.sh/wshobson/agents/python-performance-optimization", + "githubRepoUrl": "https://github.com/wshobson/agents", + "sourceContentHash": "09dd958d2e9eed377434b9f84047e2987fc6cd73668ea941ede81d01498a578b", + "installs": 29385 + }, + { + "externalId": "firecrawl/firecrawl-workflows/firecrawl-seo-audit", + "githubOwnerId": 135057108, + "owner": "firecrawl", + "repo": "firecrawl-workflows", + "slug": "firecrawl-seo-audit", + "displayName": "firecrawl-seo-audit", + "sourceUrl": "https://www.skills.sh/firecrawl/firecrawl-workflows/firecrawl-seo-audit", + "githubRepoUrl": "https://github.com/firecrawl/firecrawl-workflows", + "sourceContentHash": "ac0cad709f3902081e24bd5cc81864999bf6a86c238896bd6a18e56d1b0ddc28", + "installs": 29344 + }, + { + "externalId": "firecrawl/firecrawl-workflows/firecrawl-knowledge-base", + "githubOwnerId": 135057108, + "owner": "firecrawl", + "repo": "firecrawl-workflows", + "slug": "firecrawl-knowledge-base", + "displayName": "firecrawl-knowledge-base", + "sourceUrl": "https://www.skills.sh/firecrawl/firecrawl-workflows/firecrawl-knowledge-base", + "githubRepoUrl": "https://github.com/firecrawl/firecrawl-workflows", + "sourceContentHash": "6284249b06e3faba3e18ba78ac25ab749896d75d1d3e08fc6bea7332d7ff9b40", + "installs": 29337 + }, + { + "externalId": "jimliu/baoyu-skills/baoyu-markdown-to-html", + "githubOwnerId": 648674, + "owner": "jimliu", + "repo": "baoyu-skills", + "slug": "baoyu-markdown-to-html", + "displayName": "baoyu-markdown-to-html", + "sourceUrl": "https://www.skills.sh/jimliu/baoyu-skills/baoyu-markdown-to-html", + "githubRepoUrl": "https://github.com/jimliu/baoyu-skills", + "sourceContentHash": "80c5b9adf36deb7fd96d0872798a1eeaa440ef0559abd681ca5a16545b4ad60a", + "installs": 29284 + }, + { + "externalId": "firecrawl/firecrawl-workflows/firecrawl-dashboard-reporting", + "githubOwnerId": 135057108, + "owner": "firecrawl", + "repo": "firecrawl-workflows", + "slug": "firecrawl-dashboard-reporting", + "displayName": "firecrawl-dashboard-reporting", + "sourceUrl": "https://www.skills.sh/firecrawl/firecrawl-workflows/firecrawl-dashboard-reporting", + "githubRepoUrl": "https://github.com/firecrawl/firecrawl-workflows", + "sourceContentHash": "637b604e27856e1ded877c2dd1cd91ba026839a82b5114853f35ecd3d0967ff3", + "installs": 29273 + }, + { + "externalId": "firecrawl/firecrawl-workflows/firecrawl-lead-gen", + "githubOwnerId": 135057108, + "owner": "firecrawl", + "repo": "firecrawl-workflows", + "slug": "firecrawl-lead-gen", + "displayName": "firecrawl-lead-gen", + "sourceUrl": "https://www.skills.sh/firecrawl/firecrawl-workflows/firecrawl-lead-gen", + "githubRepoUrl": "https://github.com/firecrawl/firecrawl-workflows", + "sourceContentHash": "0e08827b7a310d325dc80d4eb9c224b2ddfe4b30159dd2023b820d56476f3c3f", + "installs": 29227 + }, + { + "externalId": "firecrawl/firecrawl-workflows/firecrawl-workflows", + "githubOwnerId": 135057108, + "owner": "firecrawl", + "repo": "firecrawl-workflows", + "slug": "firecrawl-workflows", + "displayName": "firecrawl-workflows", + "sourceUrl": "https://www.skills.sh/firecrawl/firecrawl-workflows/firecrawl-workflows", + "githubRepoUrl": "https://github.com/firecrawl/firecrawl-workflows", + "sourceContentHash": "3ccc12f1a1b1d4a11dac8afcc4349607604ba9d54356784b88f4274b6eed08ed", + "installs": 29198 + }, + { + "externalId": "firecrawl/firecrawl-workflows/firecrawl-lead-research", + "githubOwnerId": 135057108, + "owner": "firecrawl", + "repo": "firecrawl-workflows", + "slug": "firecrawl-lead-research", + "displayName": "firecrawl-lead-research", + "sourceUrl": "https://www.skills.sh/firecrawl/firecrawl-workflows/firecrawl-lead-research", + "githubRepoUrl": "https://github.com/firecrawl/firecrawl-workflows", + "sourceContentHash": "d4221ba8bf83fda835e947d017630a9b032c27ca2f168a9a6e3ddd71cc1ea249", + "installs": 29191 + }, + { + "externalId": "firecrawl/firecrawl-workflows/firecrawl-shop", + "githubOwnerId": 135057108, + "owner": "firecrawl", + "repo": "firecrawl-workflows", + "slug": "firecrawl-shop", + "displayName": "firecrawl-shop", + "sourceUrl": "https://www.skills.sh/firecrawl/firecrawl-workflows/firecrawl-shop", + "githubRepoUrl": "https://github.com/firecrawl/firecrawl-workflows", + "sourceContentHash": "b0283d80c89d0dd2a4a1f30e7f021cd1392e178b72a3a8a8b0f2dee15273b273", + "installs": 29187 + }, + { + "externalId": "firecrawl/firecrawl-workflows/firecrawl-qa", + "githubOwnerId": 135057108, + "owner": "firecrawl", + "repo": "firecrawl-workflows", + "slug": "firecrawl-qa", + "displayName": "firecrawl-qa", + "sourceUrl": "https://www.skills.sh/firecrawl/firecrawl-workflows/firecrawl-qa", + "githubRepoUrl": "https://github.com/firecrawl/firecrawl-workflows", + "sourceContentHash": "1a04d5b380ac6d4e795c00f4b649b058cf089b29c48e1654863756616bf72ee7", + "installs": 29166 + }, + { + "externalId": "firecrawl/firecrawl-workflows/firecrawl-competitive-intel", + "githubOwnerId": 135057108, + "owner": "firecrawl", + "repo": "firecrawl-workflows", + "slug": "firecrawl-competitive-intel", + "displayName": "firecrawl-competitive-intel", + "sourceUrl": "https://www.skills.sh/firecrawl/firecrawl-workflows/firecrawl-competitive-intel", + "githubRepoUrl": "https://github.com/firecrawl/firecrawl-workflows", + "sourceContentHash": "4ffcfde5a3745682e7cc8c1e9282e68f369b48dd5edbe82e1d3b49347bfd24ca", + "installs": 29155 + }, + { + "externalId": "firecrawl/firecrawl-workflows/firecrawl-knowledge-ingest", + "githubOwnerId": 135057108, + "owner": "firecrawl", + "repo": "firecrawl-workflows", + "slug": "firecrawl-knowledge-ingest", + "displayName": "firecrawl-knowledge-ingest", + "sourceUrl": "https://www.skills.sh/firecrawl/firecrawl-workflows/firecrawl-knowledge-ingest", + "githubRepoUrl": "https://github.com/firecrawl/firecrawl-workflows", + "sourceContentHash": "17c93863bdf871c244d70da0b743965d1de324649e125e0524707728455e84f2", + "installs": 29146 + }, + { + "externalId": "firecrawl/firecrawl-workflows/firecrawl-company-directories", + "githubOwnerId": 135057108, + "owner": "firecrawl", + "repo": "firecrawl-workflows", + "slug": "firecrawl-company-directories", + "displayName": "firecrawl-company-directories", + "sourceUrl": "https://www.skills.sh/firecrawl/firecrawl-workflows/firecrawl-company-directories", + "githubRepoUrl": "https://github.com/firecrawl/firecrawl-workflows", + "sourceContentHash": "396df23c5def5410a20d03e16d631df2acde418d64b30c784d98846974d3d9df", + "installs": 29120 + }, + { + "externalId": "firecrawl/firecrawl-workflows/firecrawl-demo-walkthrough", + "githubOwnerId": 135057108, + "owner": "firecrawl", + "repo": "firecrawl-workflows", + "slug": "firecrawl-demo-walkthrough", + "displayName": "firecrawl-demo-walkthrough", + "sourceUrl": "https://www.skills.sh/firecrawl/firecrawl-workflows/firecrawl-demo-walkthrough", + "githubRepoUrl": "https://github.com/firecrawl/firecrawl-workflows", + "sourceContentHash": "f965820032bc6922cc83c038f0e693bbc44cf02f80abbf743b2b7f013fcec9af", + "installs": 29115 + }, + { + "externalId": "jimliu/baoyu-skills/baoyu-infographic", + "githubOwnerId": 648674, + "owner": "jimliu", + "repo": "baoyu-skills", + "slug": "baoyu-infographic", + "displayName": "baoyu-infographic", + "sourceUrl": "https://www.skills.sh/jimliu/baoyu-skills/baoyu-infographic", + "githubRepoUrl": "https://github.com/jimliu/baoyu-skills", + "sourceContentHash": "1b2776fcce20cc76c504bdf2d7bf1b7caa06deb33c3dc95d5aea054545410930", + "installs": 29115 + }, + { + "externalId": "jimliu/baoyu-skills/baoyu-cover-image", + "githubOwnerId": 648674, + "owner": "jimliu", + "repo": "baoyu-skills", + "slug": "baoyu-cover-image", + "displayName": "baoyu-cover-image", + "sourceUrl": "https://www.skills.sh/jimliu/baoyu-skills/baoyu-cover-image", + "githubRepoUrl": "https://github.com/jimliu/baoyu-skills", + "sourceContentHash": "cba8b09407aba1db6f35c26ae645309cca2907f2cab8cdb12f42b5b3590af48b", + "installs": 28925 + }, + { + "externalId": "jimliu/baoyu-skills/baoyu-article-illustrator", + "githubOwnerId": 648674, + "owner": "jimliu", + "repo": "baoyu-skills", + "slug": "baoyu-article-illustrator", + "displayName": "baoyu-article-illustrator", + "sourceUrl": "https://www.skills.sh/jimliu/baoyu-skills/baoyu-article-illustrator", + "githubRepoUrl": "https://github.com/jimliu/baoyu-skills", + "sourceContentHash": "d7c16547486012ec25b56af5db93ed75911effb5065f670d333b013957151ccc", + "installs": 28737 + }, + { + "externalId": "jimliu/baoyu-skills/baoyu-xhs-images", + "githubOwnerId": 648674, + "owner": "jimliu", + "repo": "baoyu-skills", + "slug": "baoyu-xhs-images", + "displayName": "baoyu-xhs-images", + "sourceUrl": "https://www.skills.sh/jimliu/baoyu-skills/baoyu-xhs-images", + "githubRepoUrl": "https://github.com/jimliu/baoyu-skills", + "sourceContentHash": "7c62f3d262de63de6c4c4976cd8e7dbf2300dde782967d57b40b018675c1e643", + "installs": 28344 + }, + { + "externalId": "antfu/skills/vitest", + "githubOwnerId": 11247099, + "owner": "antfu", + "repo": "skills", + "slug": "vitest", + "displayName": "vitest", + "sourceUrl": "https://www.skills.sh/antfu/skills/vitest", + "githubRepoUrl": "https://github.com/antfu/skills", + "sourceContentHash": "337d36a184af3fd1946f50050d0f595774117dee3b0f50238e511937adf62aa2", + "installs": 28215 + }, + { + "externalId": "jimliu/baoyu-skills/baoyu-slide-deck", + "githubOwnerId": 648674, + "owner": "jimliu", + "repo": "baoyu-skills", + "slug": "baoyu-slide-deck", + "displayName": "baoyu-slide-deck", + "sourceUrl": "https://www.skills.sh/jimliu/baoyu-skills/baoyu-slide-deck", + "githubRepoUrl": "https://github.com/jimliu/baoyu-skills", + "sourceContentHash": "3a783e027650d9e9af1e3781ce7c2f9b134dd4054573b50f8174bc1f2d06cc2b", + "installs": 28027 + }, + { + "externalId": "coreyhaines31/marketingskills/sms", + "githubOwnerId": 34802794, + "owner": "coreyhaines31", + "repo": "marketingskills", + "slug": "sms", + "displayName": "sms", + "sourceUrl": "https://www.skills.sh/coreyhaines31/marketingskills/sms", + "githubRepoUrl": "https://github.com/coreyhaines31/marketingskills", + "sourceContentHash": "c4c576957e66360408e92eb571b60fdbb0a559663595ea30e7c72944adc32fd9", + "installs": 27838 + }, + { + "externalId": "wshobson/agents/python-testing-patterns", + "githubOwnerId": 553618, + "owner": "wshobson", + "repo": "agents", + "slug": "python-testing-patterns", + "displayName": "python-testing-patterns", + "sourceUrl": "https://www.skills.sh/wshobson/agents/python-testing-patterns", + "githubRepoUrl": "https://github.com/wshobson/agents", + "sourceContentHash": "f8123205cff02f5b81a5483754de45204cfb1093ea6ddb8b3006fb94b41a60dd", + "installs": 27746 + }, + { + "externalId": "jimliu/baoyu-skills/baoyu-url-to-markdown", + "githubOwnerId": 648674, + "owner": "jimliu", + "repo": "baoyu-skills", + "slug": "baoyu-url-to-markdown", + "displayName": "baoyu-url-to-markdown", + "sourceUrl": "https://www.skills.sh/jimliu/baoyu-skills/baoyu-url-to-markdown", + "githubRepoUrl": "https://github.com/jimliu/baoyu-skills", + "sourceContentHash": "c4e6f73553f76dd5e0249ccec274633154a13296d5fe7a806cb4eb8d60f599bb", + "installs": 27516 + }, + { + "externalId": "emilkowalski/skills/apple-design", + "githubOwnerId": 36730035, + "owner": "emilkowalski", + "repo": "skills", + "slug": "apple-design", + "displayName": "apple-design", + "sourceUrl": "https://www.skills.sh/emilkowalski/skills/apple-design", + "githubRepoUrl": "https://github.com/emilkowalski/skills", + "sourceContentHash": "8d5d6949abe176aed835dc259e91470b6ee219f2bc2af92784a7f072a2e50ed0", + "installs": 27309 + }, + { + "externalId": "mcollina/skills/fastify-best-practices", + "githubOwnerId": 52195, + "owner": "mcollina", + "repo": "skills", + "slug": "fastify-best-practices", + "displayName": "fastify-best-practices", + "sourceUrl": "https://www.skills.sh/mcollina/skills/fastify-best-practices", + "githubRepoUrl": "https://github.com/mcollina/skills", + "sourceContentHash": "c59058db6ad39805512534c3b0ed4054e8cf9981cffdad5e6fd1c22e3ec76135", + "installs": 27127 + }, + { + "externalId": "jimliu/baoyu-skills/baoyu-comic", + "githubOwnerId": 648674, + "owner": "jimliu", + "repo": "baoyu-skills", + "slug": "baoyu-comic", + "displayName": "baoyu-comic", + "sourceUrl": "https://www.skills.sh/jimliu/baoyu-skills/baoyu-comic", + "githubRepoUrl": "https://github.com/jimliu/baoyu-skills", + "sourceContentHash": "ae4c0808871671fbc148b7ad29a16b8b394bd439fa9290a646425057200a1af5", + "installs": 26937 + }, + { + "externalId": "clerk/skills/clerk-nextjs-patterns", + "githubOwnerId": 49538330, + "owner": "clerk", + "repo": "skills", + "slug": "clerk-nextjs-patterns", + "displayName": "clerk-nextjs-patterns", + "sourceUrl": "https://www.skills.sh/clerk/skills/clerk-nextjs-patterns", + "githubRepoUrl": "https://github.com/clerk/skills", + "sourceContentHash": "5bd3027e056bdbc0e6488934932d5bfa624e3da46cb47b5f2101d273be0d2f63", + "installs": 26920 + }, + { + "externalId": "avdlee/swiftui-agent-skill/swiftui-expert-skill", + "githubOwnerId": 4329185, + "owner": "avdlee", + "repo": "swiftui-agent-skill", + "slug": "swiftui-expert-skill", + "displayName": "swiftui-expert-skill", + "sourceUrl": "https://www.skills.sh/avdlee/swiftui-agent-skill/swiftui-expert-skill", + "githubRepoUrl": "https://github.com/avdlee/swiftui-agent-skill", + "sourceContentHash": "4560a6a67eae15c92f71e6c1570c65d2407cc63198155e1774f51977cb805ce4", + "installs": 26903 + }, + { + "externalId": "pbakaus/impeccable/layout", + "githubOwnerId": 43004, + "owner": "pbakaus", + "repo": "impeccable", + "slug": "layout", + "displayName": "layout", + "sourceUrl": "https://www.skills.sh/pbakaus/impeccable/layout", + "githubRepoUrl": "https://github.com/pbakaus/impeccable", + "sourceContentHash": "1a644471aee4f7f6eb4a6ed3084c0c5a5400664bff7d939363a69cffb5051e21", + "installs": 26818 + } + ] +} diff --git a/convex/http.ts b/convex/http.ts index 7bab99ee..c6f434b3 100644 --- a/convex/http.ts +++ b/convex/http.ts @@ -59,6 +59,7 @@ import { verifyDocsSessionV1Http, whoamiV1Http, contentRightsV1Http, + skillsShCatalogTestV1Http, } from "./httpApiV1"; import { preflightHandler } from "./httpPreflight"; import { installRateLimitedRoutes } from "./lib/httpRouteRateLimit"; @@ -324,6 +325,18 @@ http.route({ handler: contentRightsV1Http, }); +http.route({ + path: "/api/v1/operator/skills-sh/catalog-test", + method: "GET", + handler: skillsShCatalogTestV1Http, +}); + +http.route({ + path: "/api/v1/operator/skills-sh/catalog-test", + method: "POST", + handler: skillsShCatalogTestV1Http, +}); + http.route({ pathPrefix: `${ApiRoutes.contentRights}/`, method: "POST", diff --git a/convex/httpApiV1.ts b/convex/httpApiV1.ts index 70a1489d..2a1a12b5 100644 --- a/convex/httpApiV1.ts +++ b/convex/httpApiV1.ts @@ -27,6 +27,7 @@ import { promotionsPostRouterV1Handler, } from "./httpApiV1/promotionsV1"; import { createPublisherV1Handler } from "./httpApiV1/publishersV1"; +import { skillsShCatalogTestV1Handler } from "./httpApiV1/skillsShCatalogV1"; import { exportSkillsV1Handler, listSkillsV1Handler, @@ -67,6 +68,7 @@ export const listBundlePluginsV1Http = httpAction(listBundlePluginsV1Handler); export const verifyDocsSessionV1Http = httpAction(verifyDocsSessionV1Handler); export const createPublisherV1Http = httpAction(createPublisherV1Handler); export const contentRightsV1Http = httpAction(contentRightsV1Handler); +export const skillsShCatalogTestV1Http = httpAction(skillsShCatalogTestV1Handler); export const catalogFeedV1Http = httpAction(catalogFeedV1Handler); export const catalogSkillsFeedV1Http = httpAction(catalogSkillsFeedV1Handler); export const promotionsFeedV1Http = httpAction(promotionsFeedV1Handler); @@ -116,6 +118,7 @@ export const __handlers = { verifyDocsSessionV1Handler, createPublisherV1Handler, contentRightsV1Handler, + skillsShCatalogTestV1Handler, catalogFeedV1Handler, catalogSkillsFeedV1Handler, searchSkillsV1Handler, diff --git a/convex/httpApiV1/skillsShCatalogV1.test.ts b/convex/httpApiV1/skillsShCatalogV1.test.ts new file mode 100644 index 00000000..ed760702 --- /dev/null +++ b/convex/httpApiV1/skillsShCatalogV1.test.ts @@ -0,0 +1,377 @@ +/* @vitest-environment node */ +import { createHash } from "node:crypto"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +vi.mock("../lib/httpRateLimit", () => ({ + applyRateLimit: vi.fn(async () => ({ ok: true, headers: {} })), +})); + +vi.mock("../lib/githubAuth", () => ({ + buildGitHubApiHeaders: vi.fn(async () => ({ Authorization: "Bearer placeholder" })), +})); + +vi.mock("./shared", async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + requireApiTokenUserOrResponse: vi.fn(), + requireAdminOrResponse: vi.fn(), + }; +}); + +const { requireAdminOrResponse, requireApiTokenUserOrResponse } = await import("./shared"); +const { buildGitHubApiHeaders } = await import("../lib/githubAuth"); +const { skillsShCatalogTestV1Handler } = await import("./skillsShCatalogV1"); + +function sha256(value: string) { + return createHash("sha256").update(value).digest("hex"); +} + +function artifact(externalId: string, content: string) { + const fileHash = sha256(content); + return { + externalId, + artifactContentHash: sha256(`SKILL.md\0${fileHash}\n`), + files: [ + { + path: "SKILL.md", + contentBase64: Buffer.from(content).toString("base64"), + sha256: fileHash, + contentType: "text/markdown", + }, + ], + }; +} + +describe("skills.sh catalog Test HTTP API", () => { + beforeEach(() => { + vi.mocked(requireApiTokenUserOrResponse).mockResolvedValue({ + ok: true, + user: { handle: "catalog-operator" }, + userId: "users:operator", + } as never); + vi.mocked(requireAdminOrResponse).mockReturnValue({ ok: true } as never); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it("deletes uploaded files for admissions the mutation skips", async () => { + const storedIds = ["storage:linked", "storage:skipped"]; + const store = vi.fn(async () => storedIds.shift()!); + const deleteStorage = vi.fn(async () => undefined); + const runAction = vi.fn(async (_ref, args: Record) => { + expect(args).toMatchObject({ + externalIds: ["nvidia/skills/aiq-deploy", "nvidia/skills/aiq-toolkit"], + }); + return { + requested: 2, + admitted: 1, + skipped: 1, + admittedExternalIds: ["nvidia/skills/aiq-deploy"], + }; + }); + const ctx = { + runQuery: vi.fn(async () => ({ + environment: "test", + deploymentName: "academic-chihuahua-392", + buildSha: "test-sha", + control: {}, + })), + runAction, + storage: { + store, + delete: deleteStorage, + }, + } as never; + const request = new Request("https://academic-chihuahua-392.convex.site/api/v1/ops", { + method: "POST", + body: JSON.stringify({ + operation: "admit", + runId: "skillsShCatalogRuns:test", + externalIds: ["nvidia/skills/aiq-deploy", "nvidia/skills/aiq-toolkit"], + artifacts: [ + artifact("nvidia/skills/aiq-deploy", "# Linked"), + artifact("nvidia/skills/aiq-toolkit", "# Skipped"), + ], + }), + }); + + const response = await skillsShCatalogTestV1Handler(ctx, request); + + expect(response.status).toBe(200); + expect(await response.json()).toMatchObject({ admitted: 1, skipped: 1 }); + expect(store).toHaveBeenCalledTimes(2); + expect(deleteStorage).toHaveBeenCalledTimes(1); + expect(deleteStorage).toHaveBeenCalledWith("storage:skipped"); + }); + + it("does not report a committed admission as failed when skipped-file cleanup fails", async () => { + const storedIds = ["storage:linked", "storage:skipped"]; + const store = vi.fn(async () => storedIds.shift()!); + const deleteStorage = vi.fn(async () => { + throw new Error("temporary storage cleanup outage"); + }); + const ctx = { + runQuery: vi.fn(async () => ({ + environment: "test", + deploymentName: "academic-chihuahua-392", + buildSha: "test-sha", + control: {}, + })), + runAction: vi.fn(async () => ({ + requested: 2, + admitted: 1, + skipped: 1, + admittedExternalIds: ["nvidia/skills/aiq-deploy"], + })), + storage: { + store, + delete: deleteStorage, + }, + } as never; + const request = new Request("https://academic-chihuahua-392.convex.site/api/v1/ops", { + method: "POST", + body: JSON.stringify({ + operation: "admit", + runId: "skillsShCatalogRuns:test", + externalIds: ["nvidia/skills/aiq-deploy", "nvidia/skills/aiq-toolkit"], + artifacts: [ + artifact("nvidia/skills/aiq-deploy", "# Linked"), + artifact("nvidia/skills/aiq-toolkit", "# Skipped"), + ], + }), + }); + + const response = await skillsShCatalogTestV1Handler(ctx, request); + + expect(response.status).toBe(200); + expect(await response.json()).toMatchObject({ admitted: 1, skipped: 1 }); + expect(deleteStorage).toHaveBeenCalledWith("storage:skipped"); + }); + + it("reuses authenticated staging-live owner ids without a GitHub fetch", async () => { + const githubFetch = vi.fn(); + vi.stubGlobal("fetch", githubFetch); + const ctx = { + runQuery: vi + .fn() + .mockResolvedValueOnce({ + environment: "test", + deploymentName: "academic-chihuahua-392", + buildSha: "test-sha", + control: {}, + }) + .mockResolvedValueOnce({ + provenance: "stored-authenticated-staging-live", + owners: [ + { owner: "anthropics", login: "anthropics", id: 76_263_028 }, + { owner: "nvidia", login: "nvidia", id: 1_728_152 }, + ], + missingOwners: [], + }), + } as never; + const request = new Request("https://academic-chihuahua-392.convex.site/api/v1/ops", { + method: "POST", + body: JSON.stringify({ + operation: "resolve-owners", + owners: ["nvidia", "anthropics"], + }), + }); + + const response = await skillsShCatalogTestV1Handler(ctx, request); + + expect(response.status).toBe(200); + expect(await response.json()).toEqual({ + authentication: "clawhub-github-authenticated", + provenance: "stored-authenticated-staging-live", + fetches: 0, + reused: 2, + owners: [ + { owner: "anthropics", login: "anthropics", id: 76_263_028 }, + { owner: "nvidia", login: "nvidia", id: 1_728_152 }, + ], + }); + expect(githubFetch).not.toHaveBeenCalled(); + expect(buildGitHubApiHeaders).not.toHaveBeenCalled(); + }); + + it("fetches only owners missing from authenticated staging-live state", async () => { + const githubFetch = vi.fn(async (url: string, init?: RequestInit) => { + expect(init?.headers).toMatchObject({ Authorization: "Bearer placeholder" }); + const owner = url.split("/").at(-1)!; + return new Response( + JSON.stringify({ + id: owner === "nvidia" ? 1_728_152 : 76_263_028, + login: owner, + }), + ); + }); + vi.stubGlobal("fetch", githubFetch); + const ctx = { + runQuery: vi + .fn() + .mockResolvedValueOnce({ + environment: "test", + deploymentName: "academic-chihuahua-392", + buildSha: "test-sha", + control: {}, + }) + .mockResolvedValueOnce({ + provenance: "stored-authenticated-staging-live", + owners: [{ owner: "nvidia", login: "nvidia", id: 1_728_152 }], + missingOwners: ["anthropics"], + }) + .mockResolvedValueOnce({ + provenance: "stored-authenticated-staging-live-assignment-check", + checked: 1, + }), + } as never; + const request = new Request("https://academic-chihuahua-392.convex.site/api/v1/ops", { + method: "POST", + body: JSON.stringify({ + operation: "resolve-owners", + owners: ["nvidia", "anthropics"], + }), + }); + + const response = await skillsShCatalogTestV1Handler(ctx, request); + + expect(response.status).toBe(200); + const body = await response.json(); + expect(body).toEqual({ + authentication: "clawhub-github-authenticated", + provenance: "stored-authenticated-staging-live+live-github", + fetches: 1, + reused: 1, + owners: [ + { owner: "anthropics", login: "anthropics", id: 76_263_028 }, + { owner: "nvidia", login: "nvidia", id: 1_728_152 }, + ], + }); + expect(githubFetch).toHaveBeenCalledTimes(1); + expect(buildGitHubApiHeaders).toHaveBeenCalledWith({ + userAgent: "clawhub/skills-sh-catalog-test", + allowAnonymous: false, + useGitHubApp: false, + }); + expect(JSON.stringify(body)).not.toContain("Bearer placeholder"); + }); + + it("reports only non-secret HTTP status when authenticated owner lookup fails", async () => { + vi.stubGlobal( + "fetch", + vi.fn(async () => new Response("token=secret-response-body", { status: 404 })), + ); + const ctx = { + runQuery: vi + .fn() + .mockResolvedValueOnce({ + environment: "test", + deploymentName: "academic-chihuahua-392", + buildSha: "test-sha", + control: {}, + }) + .mockResolvedValueOnce({ + provenance: "stored-authenticated-staging-live", + owners: [], + missingOwners: ["neondatabase"], + }), + } as never; + const request = new Request("https://academic-chihuahua-392.convex.site/api/v1/ops", { + method: "POST", + body: JSON.stringify({ + operation: "resolve-owners", + owners: ["neondatabase"], + }), + }); + + const response = await skillsShCatalogTestV1Handler(ctx, request); + const body = await response.text(); + + expect(response.status).toBe(400); + expect(body).toBe("Authenticated GitHub owner lookup failed with HTTP 404: neondatabase"); + expect(body).not.toContain("secret-response-body"); + expect(body).not.toContain("Bearer placeholder"); + }); + + it("fails closed when a new owner lacks authenticated GitHub access", async () => { + vi.mocked(buildGitHubApiHeaders).mockRejectedValueOnce( + new Error("GitHub API authentication is not configured"), + ); + const githubFetch = vi.fn(); + vi.stubGlobal("fetch", githubFetch); + const ctx = { + runQuery: vi + .fn() + .mockResolvedValueOnce({ + environment: "test", + deploymentName: "academic-chihuahua-392", + buildSha: "test-sha", + control: {}, + }) + .mockResolvedValueOnce({ + provenance: "stored-authenticated-staging-live", + owners: [], + missingOwners: ["new-owner"], + }), + } as never; + const request = new Request("https://academic-chihuahua-392.convex.site/api/v1/ops", { + method: "POST", + body: JSON.stringify({ + operation: "resolve-owners", + owners: ["new-owner"], + }), + }); + + const response = await skillsShCatalogTestV1Handler(ctx, request); + + expect(response.status).toBe(400); + expect(await response.text()).toBe("GitHub API authentication is not configured"); + expect(githubFetch).not.toHaveBeenCalled(); + }); + + it("rejects a fetched owner id already assigned to another login", async () => { + const githubFetch = vi.fn( + async () => + new Response(JSON.stringify({ id: 1_728_152, login: "renamed-nvidia" }), { + status: 200, + }), + ); + vi.stubGlobal("fetch", githubFetch); + const ctx = { + runQuery: vi + .fn() + .mockResolvedValueOnce({ + environment: "test", + deploymentName: "academic-chihuahua-392", + buildSha: "test-sha", + control: {}, + }) + .mockResolvedValueOnce({ + provenance: "stored-authenticated-staging-live", + owners: [], + missingOwners: ["renamed-nvidia"], + }) + .mockRejectedValueOnce( + new Error("Authenticated GitHub owner id 1728152 is already assigned to another owner"), + ), + } as never; + const request = new Request("https://academic-chihuahua-392.convex.site/api/v1/ops", { + method: "POST", + body: JSON.stringify({ + operation: "resolve-owners", + owners: ["renamed-nvidia"], + }), + }); + + const response = await skillsShCatalogTestV1Handler(ctx, request); + + expect(response.status).toBe(400); + expect(await response.text()).toBe( + "Authenticated GitHub owner id 1728152 is already assigned to another owner", + ); + expect(githubFetch).toHaveBeenCalledTimes(1); + }); +}); diff --git a/convex/httpApiV1/skillsShCatalogV1.ts b/convex/httpApiV1/skillsShCatalogV1.ts new file mode 100644 index 00000000..37e2a0bc --- /dev/null +++ b/convex/httpApiV1/skillsShCatalogV1.ts @@ -0,0 +1,334 @@ +import { internal } from "../_generated/api"; +import type { Id } from "../_generated/dataModel"; +import type { ActionCtx } from "../_generated/server"; +import { buildGitHubApiHeaders } from "../lib/githubAuth"; +import { applyRateLimit } from "../lib/httpRateLimit"; +import { json, requireAdminOrResponse, requireApiTokenUserOrResponse, text } from "./shared"; + +const internalRefs = internal as unknown as { + skillsShCatalog: { + admitRealScansInternal: unknown; + assertFreshGitHubOwnerAssignmentsInternal: unknown; + getStagingLiveControlInternal: unknown; + processStagingLiveBatchInternal: unknown; + resolveKnownGitHubOwnersInternal: unknown; + startStagingLiveRunInternal: unknown; + }; +}; +const MAX_GITHUB_OWNER_RESOLUTIONS = 500; +const GITHUB_OWNER_RESOLUTION_CONCURRENCY = 8; + +async function runMutationRef( + ctx: ActionCtx, + ref: unknown, + args: Record, +): Promise { + return (await ctx.runMutation(ref as never, args as never)) as T; +} + +async function runActionRef(ctx: ActionCtx, ref: unknown, args: Record) { + return (await ctx.runAction(ref as never, args as never)) as T; +} + +async function runQueryRef( + ctx: ActionCtx, + ref: unknown, + args: Record, +): Promise { + return (await ctx.runQuery(ref as never, args as never)) as T; +} + +function asRecord(value: unknown): Record | null { + return value && typeof value === "object" && !Array.isArray(value) + ? (value as Record) + : null; +} + +function requireString(record: Record, key: string) { + const value = record[key]; + if (typeof value !== "string" || !value.trim()) throw new Error(`${key} is required`); + return value; +} + +function requireNumber(record: Record, key: string) { + const value = record[key]; + if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`${key} is required`); + return value; +} + +async function sha256Hex(bytes: Uint8Array) { + const digest = await crypto.subtle.digest("SHA-256", new Uint8Array(bytes).buffer); + return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join(""); +} + +function decodeBase64(value: string) { + const binary = atob(value); + return Uint8Array.from(binary, (character) => character.charCodeAt(0)); +} + +function normalizeGitHubOwners(ownersValue: unknown) { + if (!Array.isArray(ownersValue)) throw new Error("owners is required"); + const owners = Array.from( + new Set( + ownersValue.map((owner) => { + if (typeof owner !== "string" || !owner.trim()) { + throw new Error("owner must be a non-empty string"); + } + return owner.trim().toLowerCase(); + }), + ), + ).sort(); + if (owners.length < 1 || owners.length > MAX_GITHUB_OWNER_RESOLUTIONS) { + throw new Error(`owners must contain between 1 and ${MAX_GITHUB_OWNER_RESOLUTIONS} entries`); + } + return owners; +} + +async function fetchAuthenticatedGitHubOwners(owners: string[]) { + if (owners.length === 0) return []; + const headers = await buildGitHubApiHeaders({ + userAgent: "clawhub/skills-sh-catalog-test", + allowAnonymous: false, + useGitHubApp: false, + }); + const resolved: Array<{ owner: string; id: number; login: string }> = []; + for (let offset = 0; offset < owners.length; offset += GITHUB_OWNER_RESOLUTION_CONCURRENCY) { + const batch = owners.slice(offset, offset + GITHUB_OWNER_RESOLUTION_CONCURRENCY); + resolved.push( + ...(await Promise.all( + batch.map(async (owner) => { + const response = await fetch( + `https://api.github.com/users/${encodeURIComponent(owner)}`, + { + headers, + }, + ); + if (!response.ok) { + throw new Error( + `Authenticated GitHub owner lookup failed with HTTP ${response.status}: ${owner}`, + ); + } + const payload = (await response.json()) as { id?: unknown; login?: unknown }; + const id = typeof payload.id === "number" ? payload.id : Number.NaN; + const login = typeof payload.login === "string" ? payload.login.trim().toLowerCase() : ""; + if (!Number.isSafeInteger(id) || id <= 0 || login !== owner) { + throw new Error( + `Authenticated GitHub owner lookup returned invalid identity: ${owner}`, + ); + } + return { owner, id, login }; + }), + )), + ); + } + return resolved; +} + +async function resolveAuthenticatedGitHubOwners(ctx: ActionCtx, ownersValue: unknown) { + const owners = normalizeGitHubOwners(ownersValue); + const known = await runQueryRef<{ + provenance: "stored-authenticated-staging-live"; + owners: Array<{ owner: string; id: number; login: string }>; + missingOwners: string[]; + }>(ctx, internalRefs.skillsShCatalog.resolveKnownGitHubOwnersInternal, { owners }); + const fetched = await fetchAuthenticatedGitHubOwners(known.missingOwners); + if (fetched.length > 0) { + await runQueryRef(ctx, internalRefs.skillsShCatalog.assertFreshGitHubOwnerAssignmentsInternal, { + owners: fetched.map(({ owner, id }) => ({ owner, id })), + }); + } + const resolved = [...known.owners, ...fetched].sort((left, right) => + left.owner.localeCompare(right.owner), + ); + if (resolved.length !== owners.length) { + throw new Error("GitHub owner resolution did not return complete coverage"); + } + return { + authentication: "clawhub-github-authenticated" as const, + provenance: + known.owners.length === 0 + ? ("live-github" as const) + : fetched.length === 0 + ? ("stored-authenticated-staging-live" as const) + : ("stored-authenticated-staging-live+live-github" as const), + fetches: fetched.length, + reused: known.owners.length, + owners: resolved, + }; +} + +async function storeArtifactFiles( + ctx: ActionCtx, + artifactsValue: unknown, +): Promise<{ + artifacts: Array<{ + externalId: string; + artifactContentHash: string; + files: Array<{ + path: string; + size: number; + storageId: Id<"_storage">; + sha256: string; + contentType?: string; + }>; + }>; + storageIds: Id<"_storage">[]; +}> { + if (!Array.isArray(artifactsValue) || artifactsValue.length < 1 || artifactsValue.length > 10) { + throw new Error("artifacts must contain between 1 and 10 entries"); + } + const storageIds: Id<"_storage">[] = []; + const artifacts = []; + const externalIds = new Set(); + try { + for (const artifactValue of artifactsValue) { + const artifact = asRecord(artifactValue); + if (!artifact) throw new Error("artifact must be an object"); + const externalId = requireString(artifact, "externalId").trim().toLowerCase(); + if (externalIds.has(externalId)) throw new Error(`duplicate artifact: ${externalId}`); + externalIds.add(externalId); + const filesValue = artifact.files; + if (!Array.isArray(filesValue) || filesValue.length < 1 || filesValue.length > 100) { + throw new Error("artifact files must contain between 1 and 100 entries"); + } + const files = []; + const filePaths = new Set(); + for (const fileValue of filesValue) { + const file = asRecord(fileValue); + if (!file) throw new Error("artifact file must be an object"); + const path = requireString(file, "path"); + if (filePaths.has(path)) throw new Error(`duplicate artifact file path: ${path}`); + filePaths.add(path); + const contentBase64 = requireString(file, "contentBase64"); + const bytes = decodeBase64(contentBase64); + const declaredSha256 = requireString(file, "sha256").toLowerCase(); + if ((await sha256Hex(bytes)) !== declaredSha256) { + throw new Error(`artifact file hash mismatch: ${path}`); + } + const contentType = + typeof file.contentType === "string" && file.contentType.trim() + ? file.contentType.trim() + : undefined; + const storageId = await ctx.storage.store( + new Blob([bytes], { type: contentType ?? "application/octet-stream" }), + ); + storageIds.push(storageId); + files.push({ + path, + size: bytes.byteLength, + storageId, + sha256: declaredSha256, + ...(contentType ? { contentType } : {}), + }); + } + files.sort((left, right) => left.path.localeCompare(right.path)); + const artifactContentHash = requireString(artifact, "artifactContentHash").toLowerCase(); + const manifest = files.map((file) => `${file.path}\0${file.sha256}\n`).join(""); + const computedArtifactHash = await sha256Hex(new TextEncoder().encode(manifest)); + if (computedArtifactHash !== artifactContentHash) { + throw new Error(`artifact manifest hash mismatch: ${externalId}`); + } + artifacts.push({ + externalId, + artifactContentHash, + files, + }); + } + } catch (error) { + await Promise.allSettled( + storageIds.map(async (storageId) => await ctx.storage.delete(storageId)), + ); + throw error; + } + return { artifacts, storageIds }; +} + +export async function skillsShCatalogTestV1Handler(ctx: ActionCtx, request: Request) { + const rate = await applyRateLimit(ctx, request, request.method === "GET" ? "read" : "write"); + if (!rate.ok) return rate.response; + const auth = await requireApiTokenUserOrResponse(ctx, request, rate.headers); + if (!auth.ok) return auth.response; + const admin = requireAdminOrResponse(auth.user, rate.headers); + if (!admin.ok) return admin.response; + + try { + const staging = await runQueryRef<{ + environment: "test"; + deploymentName: string | null; + buildSha: string | null; + control: Record; + }>(ctx, internalRefs.skillsShCatalog.getStagingLiveControlInternal, {}); + if (request.method === "GET") return json(staging, 200, rate.headers); + if (request.method !== "POST") return text("Not found", 404, rate.headers); + + const body = asRecord(await request.json()); + if (!body) return text("Invalid JSON", 400, rate.headers); + const operation = requireString(body, "operation"); + if (operation === "resolve-owners") { + return json(await resolveAuthenticatedGitHubOwners(ctx, body.owners), 200, rate.headers); + } + if (operation === "start") { + const result = await runMutationRef( + ctx, + internalRefs.skillsShCatalog.startStagingLiveRunInternal, + { + actor: auth.user.handle, + reason: requireString(body, "reason"), + snapshotId: requireString(body, "snapshotId"), + sourceCapturedAt: requireString(body, "sourceCapturedAt"), + snapshotCaptureFetches: requireNumber(body, "snapshotCaptureFetches"), + fixtureLength: requireNumber(body, "fixtureLength"), + }, + ); + return json(result, 200, rate.headers); + } + if (operation === "batch") { + if (!Array.isArray(body.rows)) throw new Error("rows is required"); + const result = await runMutationRef( + ctx, + internalRefs.skillsShCatalog.processStagingLiveBatchInternal, + { + runId: requireString(body, "runId"), + cursor: requireNumber(body, "cursor"), + rows: body.rows, + }, + ); + return json(result, 200, rate.headers); + } + if (operation === "admit") { + if (!Array.isArray(body.externalIds)) throw new Error("externalIds is required"); + const stored = await storeArtifactFiles(ctx, body.artifacts); + let result: { + admittedExternalIds: string[]; + [key: string]: unknown; + }; + try { + result = await runActionRef(ctx, internalRefs.skillsShCatalog.admitRealScansInternal, { + runId: requireString(body, "runId"), + externalIds: body.externalIds, + actorUserId: auth.userId, + artifacts: stored.artifacts, + }); + } catch (error) { + await Promise.allSettled( + stored.storageIds.map(async (storageId) => await ctx.storage.delete(storageId)), + ); + throw error; + } + const admittedExternalIds = new Set(result.admittedExternalIds); + const unlinkedStorageIds = stored.artifacts + .filter((artifact) => !admittedExternalIds.has(artifact.externalId)) + .flatMap((artifact) => artifact.files.map((file) => file.storageId)); + await Promise.allSettled( + unlinkedStorageIds.map(async (storageId) => await ctx.storage.delete(storageId)), + ); + return json(result, 200, rate.headers); + } + return text("Unknown operation", 400, rate.headers); + } catch (error) { + const message = error instanceof Error ? error.message : "skills.sh Test operation failed"; + const unavailable = + message.includes("permanent Test") || message.includes("available only in permanent Test"); + return text(unavailable ? "Not found" : message, unavailable ? 404 : 400, rate.headers); + } +} diff --git a/convex/lib/retentionPolicy.test.ts b/convex/lib/retentionPolicy.test.ts index 7fe577e0..65fbb476 100644 --- a/convex/lib/retentionPolicy.test.ts +++ b/convex/lib/retentionPolicy.test.ts @@ -53,6 +53,12 @@ describe("retention policies", () => { }); }); + it("keeps skills.sh scan attempts as durable exact-hash deduplication records", () => { + expect(getRetentionPolicy("skillsShCatalogScanAttempts")).toMatchObject({ + classification: "permanent", + }); + }); + it("documents publisher abuse signals as durable review evidence", () => { expect(getRetentionPolicy("publisherAbuseSignals")).toMatchObject({ classification: "permanent", diff --git a/convex/lib/retentionPolicy.ts b/convex/lib/retentionPolicy.ts index c6ddcb84..a8732830 100644 --- a/convex/lib/retentionPolicy.ts +++ b/convex/lib/retentionPolicy.ts @@ -215,6 +215,12 @@ export const RETENTION_POLICIES = { promotions: permanent("Curated promotional offers; ended records stay for launch-page history."), auditLogs: permanent("Audit logs are durable compliance/security history."), systemSettings: permanent("Durable operator-controlled system settings."), + skillsShCatalogControls: permanent("Durable skills.sh catalog operator controls."), + skillsShCatalogRuns: permanent("Skills.sh catalog run, cursor, and rollback audit history."), + skillsShCatalogEntries: permanent("Normalized unclaimed skills.sh catalog identities."), + skillsShCatalogScanAttempts: permanent( + "Exact-hash skills.sh scan attempts are durable audit and deduplication history.", + ), publisherAbuseScoreRuns: permanent("Abuse scoring run history."), publisherAbuseTemporalScanSamples: ephemeral( "Exact temporal percentile samples are temporary scan working state.", diff --git a/convex/lib/skillsShCatalogEnvironment.test.ts b/convex/lib/skillsShCatalogEnvironment.test.ts new file mode 100644 index 00000000..91e9eb0e --- /dev/null +++ b/convex/lib/skillsShCatalogEnvironment.test.ts @@ -0,0 +1,116 @@ +/* @vitest-environment node */ +import { describe, expect, it } from "vitest"; +import { getSkillsShFixtureEnvironmentPolicy } from "./skillsShCatalogEnvironment"; + +describe("skills.sh fixture environment policy", () => { + it("allows only local development or the exact cron-disabled Test deployment", () => { + expect( + getSkillsShFixtureEnvironmentPolicy({ + CONVEX_CLOUD_URL: "http://127.0.0.1:3210", + }), + ).toEqual({ allowed: true, environment: "local" }); + + expect( + getSkillsShFixtureEnvironmentPolicy({ + CLAWHUB_DEPLOYMENT_NAME: "academic-chihuahua-392", + CLAWHUB_DISABLE_CRONS: "1", + CLAWHUB_ENV: "test", + CONVEX_CLOUD_URL: "https://academic-chihuahua-392.convex.cloud", + }), + ).toEqual({ allowed: true, environment: "test" }); + }); + + it("rejects previews, production, and incomplete Test markers", () => { + expect( + getSkillsShFixtureEnvironmentPolicy({ + CLAWHUB_PREVIEW: "1", + CONVEX_DEPLOYMENT: "anonymous:clawhub", + }), + ).toMatchObject({ allowed: false, environment: "preview" }); + + expect( + getSkillsShFixtureEnvironmentPolicy({ + CLAWHUB_DEPLOYMENT_NAME: "academic-chihuahua-392", + CLAWHUB_ENV: "test", + }), + ).toMatchObject({ allowed: false, environment: "test" }); + + expect( + getSkillsShFixtureEnvironmentPolicy({ + CLAWHUB_DEPLOYMENT_NAME: "wry-manatee-359", + CLAWHUB_DISABLE_CRONS: "1", + CLAWHUB_ENV: "test", + CONVEX_CLOUD_URL: "https://academic-chihuahua-392.convex.cloud", + }), + ).toMatchObject({ allowed: false, environment: "test" }); + + expect( + getSkillsShFixtureEnvironmentPolicy({ + CONVEX_DEPLOYMENT: "prod:wry-manatee-359", + }), + ).toMatchObject({ allowed: false, environment: "production" }); + + expect( + getSkillsShFixtureEnvironmentPolicy({ + CLAWHUB_DEPLOYMENT_NAME: "wry-manatee-359", + CONVEX_CLOUD_URL: "http://127.0.0.1:3210", + }), + ).toMatchObject({ allowed: false, environment: "production" }); + + expect( + getSkillsShFixtureEnvironmentPolicy({ + CLAWHUB_DEPLOYMENT_NAME: "academic-chihuahua-392", + CLAWHUB_DISABLE_CRONS: "1", + CLAWHUB_ENV: "test", + CONVEX_DEPLOYMENT: "prod:wry-manatee-359", + CONVEX_CLOUD_URL: "https://academic-chihuahua-392.convex.cloud", + }), + ).toMatchObject({ allowed: false, environment: "production" }); + + expect( + getSkillsShFixtureEnvironmentPolicy({ + CLAWHUB_DEPLOYMENT_NAME: "academic-chihuahua-392", + CLAWHUB_DISABLE_CRONS: "1", + CLAWHUB_ENV: "test", + CONVEX_CLOUD_URL: "https://preview-project.convex.cloud", + }), + ).toMatchObject({ allowed: false, environment: "test" }); + + expect( + getSkillsShFixtureEnvironmentPolicy({ + CLAWHUB_DEPLOYMENT_NAME: "academic-chihuahua-392", + CLAWHUB_DISABLE_CRONS: "1", + CLAWHUB_ENV: "test", + CLAWHUB_PREVIEW: "1", + CONVEX_CLOUD_URL: "https://academic-chihuahua-392.convex.cloud", + }), + ).toMatchObject({ allowed: false, environment: "preview" }); + }); + + it("does not treat CLI-only local deployment markers as runtime proof", () => { + expect( + getSkillsShFixtureEnvironmentPolicy({ + CONVEX_DEPLOYMENT: "local:clawhub", + }), + ).toMatchObject({ allowed: false, environment: "unknown" }); + }); + + it("rejects conflicting populated Convex runtime URLs", () => { + expect( + getSkillsShFixtureEnvironmentPolicy({ + CLAWHUB_DEPLOYMENT_NAME: "academic-chihuahua-392", + CLAWHUB_DISABLE_CRONS: "1", + CLAWHUB_ENV: "test", + CONVEX_CLOUD_URL: "https://academic-chihuahua-392.convex.cloud", + CONVEX_SITE_URL: "https://preview-project.convex.site", + }), + ).toMatchObject({ allowed: false, environment: "test" }); + + expect( + getSkillsShFixtureEnvironmentPolicy({ + CONVEX_CLOUD_URL: "http://127.0.0.1:3210", + CONVEX_SITE_URL: "https://academic-chihuahua-392.convex.site", + }), + ).toMatchObject({ allowed: false, environment: "unknown" }); + }); +}); diff --git a/convex/lib/skillsShCatalogEnvironment.ts b/convex/lib/skillsShCatalogEnvironment.ts new file mode 100644 index 00000000..d0a4b184 --- /dev/null +++ b/convex/lib/skillsShCatalogEnvironment.ts @@ -0,0 +1,130 @@ +type SkillsShCatalogEnvironment = { + CLAWHUB_DEPLOYMENT_NAME?: string; + CLAWHUB_DISABLE_CRONS?: string; + CLAWHUB_ENV?: string; + CLAWHUB_PREVIEW?: string; + CONVEX_CLOUD_URL?: string; + CONVEX_DEPLOYMENT?: string; + CONVEX_SITE_URL?: string; + DEV_AUTH_CONVEX_DEPLOYMENT?: string; +}; + +export type SkillsShFixtureEnvironmentPolicy = + | { + allowed: true; + environment: "local" | "test"; + } + | { + allowed: false; + environment: "preview" | "test" | "production" | "unknown"; + reason: string; + }; + +const TEST_DEPLOYMENT = "academic-chihuahua-392"; +const TEST_CLOUD_URL = `https://${TEST_DEPLOYMENT}.convex.cloud`; +const TEST_SITE_URL = `https://${TEST_DEPLOYMENT}.convex.site`; + +function populatedRuntimeUrls(env: SkillsShCatalogEnvironment) { + return [ + { actual: env.CONVEX_CLOUD_URL?.trim(), expectedTest: TEST_CLOUD_URL }, + { actual: env.CONVEX_SITE_URL?.trim(), expectedTest: TEST_SITE_URL }, + ].filter((entry): entry is { actual: string; expectedTest: string } => Boolean(entry.actual)); +} + +function isLocalRuntimeUrl(value: string | undefined) { + if (!value) return false; + try { + const hostname = new URL(value).hostname; + return ( + hostname === "localhost" || + hostname === "127.0.0.1" || + hostname === "0.0.0.0" || + hostname === "[::1]" + ); + } catch { + return false; + } +} + +export function getSkillsShFixtureEnvironmentPolicy( + env: SkillsShCatalogEnvironment = process.env, +): SkillsShFixtureEnvironmentPolicy { + if (env.CLAWHUB_PREVIEW === "1") { + return { + allowed: false, + environment: "preview", + reason: "skills.sh catalog fixture work is disabled in Preview", + }; + } + + const deployment = env.CONVEX_DEPLOYMENT?.trim() || env.DEV_AUTH_CONVEX_DEPLOYMENT?.trim() || ""; + if (deployment.startsWith("prod:")) { + return { + allowed: false, + environment: "production", + reason: "skills.sh catalog fixture work is disabled in production", + }; + } + + if (env.CLAWHUB_ENV === "test") { + if (env.CLAWHUB_DISABLE_CRONS !== "1") { + return { + allowed: false, + environment: "test", + reason: "skills.sh Test fixture work requires CLAWHUB_DISABLE_CRONS=1", + }; + } + if (env.CLAWHUB_DEPLOYMENT_NAME !== TEST_DEPLOYMENT) { + return { + allowed: false, + environment: "test", + reason: `skills.sh Test fixture work requires CLAWHUB_DEPLOYMENT_NAME=${TEST_DEPLOYMENT}`, + }; + } + const runtimeUrls = populatedRuntimeUrls(env); + if ( + runtimeUrls.length === 0 || + runtimeUrls.some((entry) => entry.actual !== entry.expectedTest) + ) { + return { + allowed: false, + environment: "test", + reason: `skills.sh Test fixture work requires the ${TEST_DEPLOYMENT} Convex runtime URL`, + }; + } + return { allowed: true, environment: "test" }; + } + + if (env.CLAWHUB_DEPLOYMENT_NAME) { + return { + allowed: false, + environment: "production", + reason: "skills.sh catalog fixture work is disabled in production", + }; + } + const runtimeUrls = populatedRuntimeUrls(env); + if (runtimeUrls.length > 0 && runtimeUrls.every((entry) => isLocalRuntimeUrl(entry.actual))) { + return { allowed: true, environment: "local" }; + } + return { + allowed: false, + environment: "unknown", + reason: "skills.sh catalog fixture work requires an explicit local or Test environment", + }; +} + +export function assertSkillsShFixtureEnvironmentAllowed( + env: SkillsShCatalogEnvironment = process.env, +) { + const policy = getSkillsShFixtureEnvironmentPolicy(env); + if (!policy.allowed) throw new Error(policy.reason); + return policy; +} + +export function assertSkillsShCatalogControlMutationAllowed( + env: SkillsShCatalogEnvironment = process.env, +) { + if (env.CLAWHUB_PREVIEW === "1") { + throw new Error("skills.sh catalog control mutations are disabled in Preview"); + } +} diff --git a/convex/lib/skillsShCatalogFixtures.ts b/convex/lib/skillsShCatalogFixtures.ts new file mode 100644 index 00000000..02fceb57 --- /dev/null +++ b/convex/lib/skillsShCatalogFixtures.ts @@ -0,0 +1,184 @@ +import frozenSnapshot from "../fixtures/skills-sh-500-2026-07-21.json"; + +export type SkillsShCatalogFixtureRow = { + externalId: string; + githubOwnerId: number; + owner: string; + repo: string; + slug: string; + displayName: string; + sourceUrl: string; + githubRepoUrl: string; + sourceContentHash: string; + installs: number; +}; + +type SkillsShCatalogFixture = { + snapshotId: string; + sourceKind: "fixture" | "frozen-snapshot"; + capturedAt: string | null; + snapshotCaptureFetches: number; + length: number; + rowAt: (index: number) => SkillsShCatalogFixtureRow; + findByExternalId: (externalId: string) => SkillsShCatalogFixtureRow | null; +}; + +const AIQ_DEPLOY: SkillsShCatalogFixtureRow = { + externalId: "nvidia/skills/aiq-deploy", + githubOwnerId: 1_728_152, + owner: "nvidia", + repo: "skills", + slug: "aiq-deploy", + displayName: "AIQ Deploy", + sourceUrl: "https://www.skills.sh/nvidia/skills/aiq-deploy", + githubRepoUrl: "https://github.com/NVIDIA/skills", + sourceContentHash: "c087fae29cc3882e03116c37921a7e092c42b15a04aa9a6c503e000a3e260ab5", + installs: 100, +}; + +const AIQ_DEPLOY_V2: SkillsShCatalogFixtureRow = { + ...AIQ_DEPLOY, + displayName: "AIQ Deploy Updated", + sourceContentHash: "d087fae29cc3882e03116c37921a7e092c42b15a04aa9a6c503e000a3e260ab5", + installs: 101, +}; + +const FROZEN_ROWS = frozenSnapshot.rows satisfies SkillsShCatalogFixtureRow[]; + +function fromRows( + metadata: Omit, + rows: readonly SkillsShCatalogFixtureRow[], +): SkillsShCatalogFixture { + const byExternalId = new Map(rows.map((row) => [row.externalId, row])); + return { + ...metadata, + length: rows.length, + rowAt(index) { + const row = rows[index]; + if (!row) throw new Error(`skills.sh fixture row ${index} is out of bounds`); + return row; + }, + findByExternalId(externalId) { + return byExternalId.get(externalId) ?? null; + }, + }; +} + +function changedFrozenRows() { + return FROZEN_ROWS.map((row, index) => { + if (index === 0) { + return { + ...row, + displayName: `${row.displayName} Updated`, + sourceContentHash: `${row.sourceContentHash.slice(0, -1)}${ + row.sourceContentHash.endsWith("0") ? "1" : "0" + }`, + installs: row.installs + 1, + }; + } + if (index === 1) { + return { + ...row, + installs: row.installs + 1, + }; + } + return row; + }); +} + +const SYNTHETIC_DISCOVERY_LENGTH = 20_000; + +function syntheticDiscoveryRow(index: number): SkillsShCatalogFixtureRow { + if (!Number.isInteger(index) || index < 0 || index >= SYNTHETIC_DISCOVERY_LENGTH) { + throw new Error(`skills.sh synthetic fixture row ${index} is out of bounds`); + } + const suffix = index.toString().padStart(5, "0"); + return { + externalId: `synthetic-owner/synthetic-repo/skill-${suffix}`, + githubOwnerId: 9_999_999, + owner: "synthetic-owner", + repo: "synthetic-repo", + slug: `skill-${suffix}`, + displayName: `Synthetic Skill ${suffix}`, + sourceUrl: `https://www.skills.sh/synthetic-owner/synthetic-repo/skill-${suffix}`, + githubRepoUrl: "https://github.com/synthetic-owner/synthetic-repo", + sourceContentHash: index.toString(16).padStart(64, "0"), + installs: index, + }; +} + +const FIXTURES = { + "nvidia-small-v1": fromRows( + { + snapshotId: "nvidia-small-v1", + sourceKind: "fixture", + capturedAt: null, + snapshotCaptureFetches: 0, + }, + [ + AIQ_DEPLOY, + { ...AIQ_DEPLOY }, + { + externalId: "nvidia/skills/cuda-agent", + githubOwnerId: 1_728_152, + owner: "nvidia", + repo: "skills", + slug: "cuda-agent", + displayName: "CUDA Agent", + sourceUrl: "https://www.skills.sh/nvidia/skills/cuda-agent", + githubRepoUrl: "https://github.com/NVIDIA/skills", + sourceContentHash: "b087fae29cc3882e03116c37921a7e092c42b15a04aa9a6c503e000a3e260ab5", + installs: 50, + }, + ], + ), + "nvidia-small-v2": fromRows( + { + snapshotId: "nvidia-small-v2", + sourceKind: "fixture", + capturedAt: null, + snapshotCaptureFetches: 0, + }, + [AIQ_DEPLOY_V2], + ), + "skills-sh-500-2026-07-21": fromRows( + { + snapshotId: frozenSnapshot.snapshotId, + sourceKind: "frozen-snapshot", + capturedAt: frozenSnapshot.capturedAt, + snapshotCaptureFetches: frozenSnapshot.captureMetrics.skillsShFetches, + }, + FROZEN_ROWS, + ), + "skills-sh-500-2026-07-21-v2": fromRows( + { + snapshotId: `${frozenSnapshot.snapshotId}-v2`, + sourceKind: "frozen-snapshot", + capturedAt: frozenSnapshot.capturedAt, + snapshotCaptureFetches: frozenSnapshot.captureMetrics.skillsShFetches, + }, + changedFrozenRows(), + ), + "synthetic-20000-v1": { + snapshotId: "synthetic-20000-v1", + sourceKind: "fixture", + capturedAt: null, + snapshotCaptureFetches: 0, + length: SYNTHETIC_DISCOVERY_LENGTH, + rowAt: syntheticDiscoveryRow, + findByExternalId(externalId) { + const match = /^synthetic-owner\/synthetic-repo\/skill-(\d{5})$/.exec(externalId); + if (!match) return null; + const index = Number(match[1]); + return index < SYNTHETIC_DISCOVERY_LENGTH ? syntheticDiscoveryRow(index) : null; + }, + }, +} satisfies Record; + +export type SkillsShCatalogFixtureId = keyof typeof FIXTURES; + +export function getSkillsShCatalogFixture( + fixtureId: SkillsShCatalogFixtureId, +): SkillsShCatalogFixture { + return FIXTURES[fixtureId]; +} diff --git a/convex/schema.ts b/convex/schema.ts index 41a75d8b..e415678c 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -715,6 +715,7 @@ const securityScanJobSourceValidator = v.union( v.literal("backfill"), v.literal("bulk-rescan"), v.literal("manual"), + v.literal("skills-sh-catalog-test"), ); const skillCardGenerationJobStatusValidator = v.union( v.literal("queued"), @@ -780,6 +781,7 @@ const skillScanRequestSourceKindValidator = v.union( v.literal("upload"), v.literal("published"), v.literal("github"), + v.literal("skills-sh-catalog"), ); const skills = defineTable({ @@ -1917,6 +1919,7 @@ const skillScanRequests = defineTable({ skillId: v.optional(v.id("skills")), skillVersionId: v.optional(v.id("skillVersions")), githubSkillScanId: v.optional(v.id("githubSkillScans")), + skillsShCatalogAttemptId: v.optional(v.id("skillsShCatalogScanAttempts")), files: packageFilesValidator, fileChunkCount: v.optional(v.number()), fileManifestBytes: v.optional(v.number()), @@ -2751,6 +2754,181 @@ const systemSettings = defineTable({ updatedByUserId: v.optional(v.id("users")), }).index("by_key_and_updated_at", ["key", "updatedAt"]); +const skillsShCatalogControls = defineTable({ + key: v.literal("global"), + mode: v.union(v.literal("off"), v.literal("fixture"), v.literal("staging-live")), + discoveryEnabled: v.boolean(), + writesEnabled: v.boolean(), + scanPlanningEnabled: v.boolean(), + scanAdmissionEnabled: v.boolean(), + publicVisibilityEnabled: v.boolean(), + paused: v.boolean(), + maxEntriesPerRun: v.number(), + maxEntriesPerBatch: v.number(), + maxWritesPerBatch: v.number(), + maxPlannedScans: v.number(), + maxScanAdmissionsPerBatch: v.number(), + maxScanAdmissionsPerRun: v.number(), + maxScanAdmissionsPerDay: v.number(), + maxCatalogQueued: v.number(), + maxCatalogInFlight: v.number(), + maxNativeQueued: v.number(), + maxNativeInFlight: v.number(), + realScanAllowlist: v.array(v.string()), + updatedBy: v.string(), + reason: v.string(), + updatedAt: v.number(), +}).index("by_key", ["key"]); + +const skillsShCatalogRunCountsValidator = v.object({ + observed: v.number(), + wouldInsert: v.number(), + wouldUpdate: v.number(), + inserted: v.number(), + updated: v.number(), + unchanged: v.number(), + rejected: v.number(), + scansPlanned: v.number(), + scansAdmitted: v.number(), + scansCompleted: v.number(), + scansCanceled: v.number(), +}); + +const skillsShCatalogRuns = defineTable({ + fixtureId: v.union( + v.literal("nvidia-small-v1"), + v.literal("nvidia-small-v2"), + v.literal("skills-sh-500-2026-07-21"), + v.literal("skills-sh-500-2026-07-21-v2"), + v.literal("synthetic-20000-v1"), + v.literal("skills-sh-test-live-500"), + ), + snapshotId: v.string(), + sourceKind: v.union( + v.literal("fixture"), + v.literal("frozen-snapshot"), + v.literal("staging-live"), + ), + sourceCapturedAt: v.optional(v.string()), + snapshotCaptureFetches: v.number(), + dryRun: v.boolean(), + status: v.union( + v.literal("running"), + v.literal("paused"), + v.literal("completed"), + v.literal("budget-exhausted"), + v.literal("failed"), + v.literal("canceling"), + v.literal("canceled"), + ), + cursor: v.number(), + scanCursor: v.number(), + fixtureLength: v.number(), + counts: skillsShCatalogRunCountsValidator, + budgets: v.object({ + maxEntriesPerRun: v.number(), + maxEntriesPerBatch: v.number(), + maxWritesPerBatch: v.number(), + maxPlannedScans: v.number(), + maxScanAdmissionsPerBatch: v.number(), + maxScanAdmissionsPerRun: v.number(), + maxScanAdmissionsPerDay: v.number(), + }), + operations: v.object({ + functionCalls: v.number(), + dbReads: v.number(), + dbWrites: v.number(), + }), + actor: v.string(), + reason: v.string(), + lastError: v.optional(v.string()), + batchesProcessed: v.number(), + scanAdmissionBatches: v.number(), + lastBatchWrites: v.number(), + lastBatchReads: v.number(), + startedAt: v.number(), + completedAt: v.optional(v.number()), + updatedAt: v.number(), +}) + .index("by_started_at", ["startedAt"]) + .index("by_status_and_updated_at", ["status", "updatedAt"]); + +const skillsShCatalogEntries = defineTable({ + externalId: v.string(), + sourceKind: v.union( + v.literal("fixture"), + v.literal("frozen-snapshot"), + v.literal("staging-live"), + ), + githubOwnerId: v.number(), + owner: v.string(), + repo: v.string(), + slug: v.string(), + displayName: v.string(), + sourceUrl: v.string(), + githubRepoUrl: v.string(), + sourceContentHash: v.string(), + installs: v.number(), + sourceSnapshotId: v.string(), + publicVisible: v.boolean(), + scanStatus: v.union( + v.literal("not-planned"), + v.literal("planned"), + v.literal("queued"), + v.literal("clean"), + v.literal("suspicious"), + v.literal("malicious"), + v.literal("failed"), + v.literal("canceled"), + ), + firstObservedAt: v.number(), + lastObservedAt: v.number(), + createdAt: v.number(), + updatedAt: v.number(), +}) + .index("by_external_id", ["externalId"]) + .index("by_owner_and_source_kind_and_github_owner_id", ["owner", "sourceKind", "githubOwnerId"]) + .index("by_source_kind_and_github_owner_id_and_owner", ["sourceKind", "githubOwnerId", "owner"]) + .index("by_scan_status_and_updated_at", ["scanStatus", "updatedAt"]); + +const skillsShCatalogScanAttempts = defineTable({ + entryId: v.id("skillsShCatalogEntries"), + runId: v.id("skillsShCatalogRuns"), + externalId: v.string(), + sourceContentHash: v.string(), + artifactContentHash: v.optional(v.string()), + skillScanRequestId: v.optional(v.id("skillScanRequests")), + securityScanJobId: v.optional(v.id("securityScanJobs")), + source: v.union(v.literal("skills-sh-catalog-fixture"), v.literal("skills-sh-catalog-test")), + dispatchKind: v.union(v.literal("deterministic"), v.literal("real")), + priority: v.literal("low"), + status: v.union( + v.literal("queued"), + v.literal("running"), + v.literal("succeeded"), + v.literal("failed"), + v.literal("canceled"), + ), + verdict: v.optional( + v.union( + v.literal("clean"), + v.literal("suspicious"), + v.literal("malicious"), + v.literal("failed"), + ), + ), + completedAt: v.optional(v.number()), + createdAt: v.number(), + updatedAt: v.number(), +}) + .index("by_entry_and_source_content_hash", ["entryId", "sourceContentHash"]) + .index("by_run", ["runId"]) + .index("by_run_and_status", ["runId", "status"]) + .index("by_run_dispatch_kind_status_created_at", ["runId", "dispatchKind", "status", "createdAt"]) + .index("by_created_at", ["createdAt"]) + .index("by_status_and_created_at", ["status", "createdAt"]) + .index("by_dispatch_kind_and_status_and_created_at", ["dispatchKind", "status", "createdAt"]); + const publisherAbuseScoreRuns = defineTable({ modelVersion: v.string(), modelConfig: publisherAbuseModelConfigValidator, @@ -3429,6 +3607,10 @@ export default defineSchema({ promotions, auditLogs, systemSettings, + skillsShCatalogControls, + skillsShCatalogRuns, + skillsShCatalogEntries, + skillsShCatalogScanAttempts, publisherAbuseScoreRuns, publisherAbuseTemporalScanSamples, publisherAbuseTemporalScanCandidates, diff --git a/convex/securityScan.test.ts b/convex/securityScan.test.ts index aa811207..84eddde7 100644 --- a/convex/securityScan.test.ts +++ b/convex/securityScan.test.ts @@ -57,7 +57,7 @@ const claimCodexScanJobLeasesHandler = ( { token: string; workerId: string; - lane?: "priority" | "shared"; + lane?: "priority" | "shared" | "catalog"; limit?: number; leaseMs?: number; }, @@ -76,7 +76,7 @@ const hydrateCodexScanJobHandler = ( const claimQueuedJobsInternalHandler = ( claimQueuedJobsInternal as unknown as WrappedHandler< - { workerId: string; lane?: "priority" | "shared"; limit: number; leaseMs?: number }, + { workerId: string; lane?: "priority" | "shared" | "catalog"; limit: number; leaseMs?: number }, Array > )._handler; @@ -698,14 +698,33 @@ function makeFailedScanRecoveryCtx( job.updatedAt < updatedBefore, ) .sort((a, b) => a.updatedAt - b.updatedAt || a._id.localeCompare(b._id)); - return { + let filtered = matched; + const builder = { + filter: vi.fn( + ( + predicate: (q: { + field: (field: string) => { field: string }; + neq: (left: { field: string }, right: unknown) => unknown; + }) => unknown, + ) => { + predicate({ + field: (field) => ({ field }), + neq: (left, right) => { + filtered = filtered.filter((job) => job[left.field as keyof ScanJob] !== right); + return true; + }, + }); + return builder; + }, + ), order: vi.fn((direction: string) => { expect(direction).toBe("asc"); return { - take: vi.fn(async (limit: number) => matched.slice(0, limit)), + take: vi.fn(async (limit: number) => filtered.slice(0, limit)), }; }), }; + return builder; }, ), }; @@ -1066,12 +1085,52 @@ function makeCancelCtx(jobs: ScanJob[], targets: Map = new Map( }; } -function makeClaimCtx(jobs: ScanJob[]) { +function makeClaimCtx( + jobs: ScanJob[], + docs: Record> = {}, + catalogControl: Record | null = null, +) { const patches: Array<{ id: string; patch: Record }> = []; const patch = vi.fn(async (id: string, doc: Record) => { patches.push({ id, patch: doc }); + docs[id] = { ...docs[id], ...doc }; }); const query = vi.fn((tableName: string) => { + if (tableName === "skillsShCatalogControls") { + return { + withIndex: vi.fn(() => ({ + unique: vi.fn(async () => catalogControl), + })), + }; + } + if (tableName === "skillsShCatalogScanAttempts") { + return { + withIndex: vi.fn( + ( + _indexName: string, + buildRange: (q: { eq: (field: string, value: unknown) => unknown }) => unknown, + ) => { + const equals = new Map(); + const range = { + eq(field: string, value: unknown) { + equals.set(field, value); + return range; + }, + }; + buildRange(range); + const attempts = Object.values(docs).filter( + (doc) => + typeof doc._id === "string" && + doc._id.startsWith("skillsShCatalogScanAttempts:") && + Array.from(equals.entries()).every(([field, value]) => doc[field] === value), + ); + return { + take: vi.fn(async (limit: number) => attempts.slice(0, limit)), + }; + }, + ), + }; + } expect(tableName).toBe("securityScanJobs"); return { withIndex: vi.fn( @@ -1132,7 +1191,7 @@ function makeClaimCtx(jobs: ScanJob[]) { db: { query, patch, - get: vi.fn(), + get: vi.fn(async (id: string) => docs[id] ?? null), insert: vi.fn(), replace: vi.fn(), delete: vi.fn(), @@ -2558,6 +2617,86 @@ describe("securityScan", () => { ); }); + it("does not revive catalog attempts through generic failed-job recovery", async () => { + const ctx = makeFailedScanRecoveryCtx( + [ + makeScanJob({ + _id: "securityScanJobs:catalog-failed", + status: "failed", + source: "skills-sh-catalog-test", + targetKind: "skillScanRequest", + skillVersionId: undefined, + skillScanRequestId: "skillScanRequests:catalog", + attempts: 3, + updatedAt: 150, + }), + ], + { + "skillScanRequests:catalog": { + _id: "skillScanRequests:catalog", + sourceKind: "skills-sh-catalog", + skillsShCatalogAttemptId: "skillsShCatalogScanAttempts:catalog", + status: "failed", + }, + "skillsShCatalogScanAttempts:catalog": { + _id: "skillsShCatalogScanAttempts:catalog", + runId: "skillsShCatalogRuns:catalog", + status: "failed", + }, + }, + ); + + const result = await requeueFailedSecurityScanJobsInternalHandler(ctx.ctx, { + dryRun: false, + failedAfter: 100, + failedBefore: 200, + }); + + expect(result).toMatchObject({ + matched: 0, + requeued: 0, + hasMore: false, + }); + expect(ctx.patches).toEqual([]); + }); + + it("does not let catalog failures hide native recovery candidates", async () => { + const ctx = makeFailedScanRecoveryCtx([ + makeScanJob({ + _id: "securityScanJobs:catalog-failed", + status: "failed", + source: "skills-sh-catalog-test", + updatedAt: 100, + }), + makeScanJob({ + _id: "securityScanJobs:native-failed", + status: "failed", + source: "manual", + updatedAt: 101, + }), + ]); + + const result = await requeueFailedSecurityScanJobsInternalHandler(ctx.ctx, { + dryRun: false, + failedAfter: 100, + failedBefore: 200, + limit: 1, + }); + + expect(result).toMatchObject({ + matched: 1, + requeued: 1, + hasMore: false, + sampleJobIds: ["securityScanJobs:native-failed"], + }); + expect(ctx.patches).toEqual([ + expect.objectContaining({ + id: "securityScanJobs:native-failed", + patch: expect.objectContaining({ status: "queued" }), + }), + ]); + }); + it("restores failed GitHub-backed scans to pending while requeueing", async () => { const ctx = makeFailedScanRecoveryCtx( [ @@ -3228,6 +3367,13 @@ describe("securityScan", () => { securityScanJobId: "securityScanJobs:github", files: [{ storageId: "storage:github-1" }], }, + { + _id: "skillScanRequests:catalog", + sourceKind: "skills-sh-catalog", + securityScanJobId: "securityScanJobs:catalog", + skillsShCatalogAttemptId: "skillsShCatalogScanAttempts:catalog", + files: [{ storageId: "storage:catalog-1" }], + }, ]; const githubFileChunk = { _id: "skillScanRequestFileChunks:github", @@ -3248,6 +3394,33 @@ describe("securityScan", () => { return { take }; }, ); + const docs: Record> = { + "skillsShCatalogScanAttempts:catalog": { + _id: "skillsShCatalogScanAttempts:catalog", + entryId: "skillsShCatalogEntries:catalog", + runId: "skillsShCatalogRuns:catalog", + sourceContentHash: "source-hash", + status: "queued", + }, + "skillsShCatalogEntries:catalog": { + _id: "skillsShCatalogEntries:catalog", + sourceContentHash: "source-hash", + scanStatus: "queued", + }, + "skillsShCatalogRuns:catalog": { + _id: "skillsShCatalogRuns:catalog", + status: "completed", + counts: { + scansCompleted: 0, + scansCanceled: 0, + }, + operations: { + functionCalls: 0, + dbReads: 0, + dbWrites: 0, + }, + }, + }; const ctx = { db: { query: vi.fn((tableName: string) => { @@ -3260,12 +3433,17 @@ describe("securityScan", () => { }; }), insert: vi.fn(async () => "noop"), - patch: vi.fn(async () => undefined), + patch: vi.fn(async (id: string, value: Record) => { + docs[id] = { ...docs[id], ...value }; + }), replace: vi.fn(async () => undefined), - get: vi.fn(async (id: string) => ({ - _id: id, - targetKind: "skillScanRequest", - })), + get: vi.fn( + async (id: string) => + docs[id] ?? { + _id: id, + targetKind: "skillScanRequest", + }, + ), delete: vi.fn(async (id: string) => { deletedDocs.push(id); }), @@ -3285,10 +3463,10 @@ describe("securityScan", () => { expect(result).toEqual({ ok: true, - deletedRequests: 3, + deletedRequests: 4, deferredRequests: 0, - deletedJobs: 3, - deletedFiles: 4, + deletedJobs: 4, + deletedFiles: 5, done: true, }); expect(deletedStorage).toEqual([ @@ -3296,6 +3474,7 @@ describe("securityScan", () => { "storage:upload-2", "storage:github-1", "storage:github-2", + "storage:catalog-1", ]); expect(deletedDocs).toEqual([ "securityScanJobs:upload", @@ -3305,7 +3484,23 @@ describe("securityScan", () => { "securityScanJobs:github", "skillScanRequestFileChunks:github", "skillScanRequests:github", + "securityScanJobs:catalog", + "skillScanRequests:catalog", ]); + expect(docs["skillsShCatalogScanAttempts:catalog"]).toMatchObject({ + status: "failed", + verdict: "failed", + completedAt: expect.any(Number), + }); + expect(docs["skillsShCatalogEntries:catalog"]).toMatchObject({ + scanStatus: "failed", + }); + expect(docs["skillsShCatalogRuns:catalog"]).toMatchObject({ + counts: { + scansCompleted: 1, + scansCanceled: 0, + }, + }); }); it("prunes one bounded GitHub file chunk before deleting the parent request", async () => { @@ -3580,6 +3775,393 @@ describe("securityScan", () => { expect(claimed.map((job) => job._id)).toEqual(["securityScanJobs:publish"]); }); + it("lets the catalog lane claim only the lowest-priority catalog source", async () => { + vi.stubEnv("CLAWHUB_ENV", "test"); + vi.stubEnv("CLAWHUB_DISABLE_CRONS", "1"); + vi.stubEnv("CLAWHUB_DEPLOYMENT_NAME", "academic-chihuahua-392"); + vi.stubEnv("CONVEX_CLOUD_URL", "https://academic-chihuahua-392.convex.cloud"); + const { ctx } = makeClaimCtx( + [ + makeScanJob({ + _id: "securityScanJobs:manual", + source: "manual", + priority: 100, + createdAt: 1, + nextRunAt: 1, + }), + makeScanJob({ + _id: "securityScanJobs:publish", + source: "publish", + priority: 0, + createdAt: 2, + nextRunAt: 2, + }), + makeScanJob({ + _id: "securityScanJobs:catalog", + source: "skills-sh-catalog-test", + skillScanRequestId: "skillScanRequests:catalog", + priority: -100, + createdAt: 3, + nextRunAt: 3, + }), + ], + { + "skillScanRequests:catalog": { + _id: "skillScanRequests:catalog", + sourceKind: "skills-sh-catalog", + skillsShCatalogAttemptId: "skillsShCatalogScanAttempts:catalog", + }, + "skillsShCatalogScanAttempts:catalog": { + _id: "skillsShCatalogScanAttempts:catalog", + runId: "skillsShCatalogRuns:catalog", + skillScanRequestId: "skillScanRequests:catalog", + securityScanJobId: "securityScanJobs:catalog", + status: "queued", + }, + "skillsShCatalogRuns:catalog": { + _id: "skillsShCatalogRuns:catalog", + status: "completed", + }, + }, + { + key: "global", + mode: "staging-live", + paused: false, + scanAdmissionEnabled: true, + maxNativeQueued: 10, + maxNativeInFlight: 10, + maxCatalogQueued: 10, + maxCatalogInFlight: 1, + }, + ); + + const claimed = await claimQueuedJobsInternalHandler(ctx, { + workerId: "catalog-worker", + lane: "catalog", + limit: 3, + leaseMs: 60_000, + }); + + expect(claimed.map((job) => job._id)).toEqual(["securityScanJobs:catalog"]); + }); + + it("skips paused catalog runs without starving later runnable jobs", async () => { + vi.stubEnv("CLAWHUB_ENV", "test"); + vi.stubEnv("CLAWHUB_DISABLE_CRONS", "1"); + vi.stubEnv("CLAWHUB_DEPLOYMENT_NAME", "academic-chihuahua-392"); + vi.stubEnv("CONVEX_CLOUD_URL", "https://academic-chihuahua-392.convex.cloud"); + const jobs = ["paused", "active"].map((kind, index) => + makeScanJob({ + _id: `securityScanJobs:${kind}`, + source: "skills-sh-catalog-test", + targetKind: "skillScanRequest", + skillScanRequestId: `skillScanRequests:${kind}`, + createdAt: index, + nextRunAt: index, + }), + ); + const docs = Object.fromEntries( + jobs.flatMap((job, index) => { + const kind = index === 0 ? "paused" : "active"; + return [ + [ + `skillScanRequests:${kind}`, + { + _id: `skillScanRequests:${kind}`, + sourceKind: "skills-sh-catalog", + skillsShCatalogAttemptId: `skillsShCatalogScanAttempts:${kind}`, + }, + ], + [ + `skillsShCatalogScanAttempts:${kind}`, + { + _id: `skillsShCatalogScanAttempts:${kind}`, + runId: `skillsShCatalogRuns:${kind}`, + skillScanRequestId: `skillScanRequests:${kind}`, + securityScanJobId: job._id, + status: "queued", + }, + ], + [ + `skillsShCatalogRuns:${kind}`, + { + _id: `skillsShCatalogRuns:${kind}`, + status: kind === "paused" ? "paused" : "completed", + }, + ], + ]; + }), + ); + const { ctx } = makeClaimCtx(jobs, docs, { + key: "global", + mode: "staging-live", + paused: false, + scanAdmissionEnabled: true, + maxNativeQueued: 10, + maxNativeInFlight: 10, + maxCatalogQueued: 1, + maxCatalogInFlight: 1, + }); + + const claimed = await claimQueuedJobsInternalHandler(ctx, { + workerId: "catalog-worker", + lane: "catalog", + limit: 1, + }); + + expect(claimed.map((job) => job._id)).toEqual(["securityScanJobs:active"]); + }); + + it("stops catalog claims when the native queue is unhealthy", async () => { + vi.stubEnv("CLAWHUB_ENV", "test"); + vi.stubEnv("CLAWHUB_DISABLE_CRONS", "1"); + vi.stubEnv("CLAWHUB_DEPLOYMENT_NAME", "academic-chihuahua-392"); + vi.stubEnv("CONVEX_CLOUD_URL", "https://academic-chihuahua-392.convex.cloud"); + const { ctx } = makeClaimCtx( + [ + makeScanJob({ + _id: "securityScanJobs:native", + source: "manual", + status: "queued", + }), + makeScanJob({ + _id: "securityScanJobs:catalog", + source: "skills-sh-catalog-test", + targetKind: "skillScanRequest", + skillScanRequestId: "skillScanRequests:catalog", + }), + ], + { + "skillScanRequests:catalog": { + _id: "skillScanRequests:catalog", + sourceKind: "skills-sh-catalog", + skillsShCatalogAttemptId: "skillsShCatalogScanAttempts:catalog", + }, + "skillsShCatalogScanAttempts:catalog": { + _id: "skillsShCatalogScanAttempts:catalog", + runId: "skillsShCatalogRuns:catalog", + skillScanRequestId: "skillScanRequests:catalog", + securityScanJobId: "securityScanJobs:catalog", + status: "queued", + }, + "skillsShCatalogRuns:catalog": { + _id: "skillsShCatalogRuns:catalog", + status: "completed", + }, + }, + { + key: "global", + mode: "staging-live", + paused: false, + scanAdmissionEnabled: true, + maxNativeQueued: 0, + maxNativeInFlight: 10, + maxCatalogQueued: 10, + maxCatalogInFlight: 1, + }, + ); + + const claimed = await claimQueuedJobsInternalHandler(ctx, { + workerId: "catalog-worker", + lane: "catalog", + limit: 1, + }); + + expect(claimed).toEqual([]); + }); + + it("ignores deterministic attempts when checking real catalog claim capacity", async () => { + vi.stubEnv("CLAWHUB_ENV", "test"); + vi.stubEnv("CLAWHUB_DISABLE_CRONS", "1"); + vi.stubEnv("CLAWHUB_DEPLOYMENT_NAME", "academic-chihuahua-392"); + vi.stubEnv("CONVEX_CLOUD_URL", "https://academic-chihuahua-392.convex.cloud"); + const catalogJob = makeScanJob({ + _id: "securityScanJobs:catalog", + source: "skills-sh-catalog-test", + targetKind: "skillScanRequest", + skillScanRequestId: "skillScanRequests:catalog", + }); + const { ctx } = makeClaimCtx( + [catalogJob], + { + "skillScanRequests:catalog": { + _id: "skillScanRequests:catalog", + sourceKind: "skills-sh-catalog", + skillsShCatalogAttemptId: "skillsShCatalogScanAttempts:catalog", + }, + "skillsShCatalogScanAttempts:catalog": { + _id: "skillsShCatalogScanAttempts:catalog", + runId: "skillsShCatalogRuns:catalog", + dispatchKind: "real", + skillScanRequestId: "skillScanRequests:catalog", + securityScanJobId: catalogJob._id, + status: "queued", + }, + "skillsShCatalogScanAttempts:deterministic-queued": { + _id: "skillsShCatalogScanAttempts:deterministic-queued", + dispatchKind: "deterministic", + status: "queued", + }, + "skillsShCatalogScanAttempts:deterministic-running": { + _id: "skillsShCatalogScanAttempts:deterministic-running", + dispatchKind: "deterministic", + status: "running", + }, + "skillsShCatalogRuns:catalog": { + _id: "skillsShCatalogRuns:catalog", + status: "completed", + }, + }, + { + key: "global", + mode: "staging-live", + paused: false, + scanAdmissionEnabled: true, + maxNativeQueued: 10, + maxNativeInFlight: 10, + maxCatalogQueued: 1, + maxCatalogInFlight: 1, + }, + ); + + const claimed = await claimQueuedJobsInternalHandler(ctx, { + workerId: "catalog-worker", + lane: "catalog", + limit: 1, + }); + + expect(claimed.map((job) => job._id)).toEqual(["securityScanJobs:catalog"]); + }); + + it("drains an admitted catalog backlog above a lowered queue cap", async () => { + vi.stubEnv("CLAWHUB_ENV", "test"); + vi.stubEnv("CLAWHUB_DISABLE_CRONS", "1"); + vi.stubEnv("CLAWHUB_DEPLOYMENT_NAME", "academic-chihuahua-392"); + vi.stubEnv("CONVEX_CLOUD_URL", "https://academic-chihuahua-392.convex.cloud"); + const jobs = [0, 1].map((index) => + makeScanJob({ + _id: `securityScanJobs:catalog-${index}`, + source: "skills-sh-catalog-test", + targetKind: "skillScanRequest", + skillScanRequestId: `skillScanRequests:catalog-${index}`, + createdAt: index, + nextRunAt: index, + }), + ); + const docs = Object.fromEntries( + jobs.flatMap((job, index) => [ + [ + `skillScanRequests:catalog-${index}`, + { + _id: `skillScanRequests:catalog-${index}`, + sourceKind: "skills-sh-catalog", + skillsShCatalogAttemptId: `skillsShCatalogScanAttempts:catalog-${index}`, + }, + ], + [ + `skillsShCatalogScanAttempts:catalog-${index}`, + { + _id: `skillsShCatalogScanAttempts:catalog-${index}`, + runId: "skillsShCatalogRuns:catalog", + dispatchKind: "real", + skillScanRequestId: `skillScanRequests:catalog-${index}`, + securityScanJobId: job._id, + status: "queued", + }, + ], + ]), + ); + docs["skillsShCatalogRuns:catalog"] = { + _id: "skillsShCatalogRuns:catalog", + status: "completed", + }; + const { ctx } = makeClaimCtx(jobs, docs, { + key: "global", + mode: "staging-live", + paused: false, + scanAdmissionEnabled: true, + maxNativeQueued: 10, + maxNativeInFlight: 10, + maxCatalogQueued: 1, + maxCatalogInFlight: 1, + }); + + const claimed = await claimQueuedJobsInternalHandler(ctx, { + workerId: "catalog-worker", + lane: "catalog", + limit: 2, + }); + + expect(claimed.map((job) => job._id)).toEqual(["securityScanJobs:catalog-0"]); + expect(docs["skillsShCatalogScanAttempts:catalog-0"]).toMatchObject({ + status: "running", + }); + expect(docs["skillsShCatalogScanAttempts:catalog-1"]).toMatchObject({ + status: "queued", + }); + }); + + it("caps catalog claims at the configured in-flight capacity", async () => { + vi.stubEnv("CLAWHUB_ENV", "test"); + vi.stubEnv("CLAWHUB_DISABLE_CRONS", "1"); + vi.stubEnv("CLAWHUB_DEPLOYMENT_NAME", "academic-chihuahua-392"); + vi.stubEnv("CONVEX_CLOUD_URL", "https://academic-chihuahua-392.convex.cloud"); + const jobs = [0, 1].map((index) => + makeScanJob({ + _id: `securityScanJobs:catalog-${index}`, + source: "skills-sh-catalog-test", + targetKind: "skillScanRequest", + skillScanRequestId: `skillScanRequests:catalog-${index}`, + createdAt: index, + nextRunAt: index, + }), + ); + const docs = Object.fromEntries( + jobs.flatMap((job, index) => [ + [ + `skillScanRequests:catalog-${index}`, + { + _id: `skillScanRequests:catalog-${index}`, + sourceKind: "skills-sh-catalog", + skillsShCatalogAttemptId: `skillsShCatalogScanAttempts:catalog-${index}`, + }, + ], + [ + `skillsShCatalogScanAttempts:catalog-${index}`, + { + _id: `skillsShCatalogScanAttempts:catalog-${index}`, + runId: "skillsShCatalogRuns:catalog", + skillScanRequestId: `skillScanRequests:catalog-${index}`, + securityScanJobId: job._id, + status: "queued", + }, + ], + ]), + ); + docs["skillsShCatalogRuns:catalog"] = { + _id: "skillsShCatalogRuns:catalog", + status: "completed", + }; + const { ctx } = makeClaimCtx(jobs, docs, { + key: "global", + mode: "staging-live", + paused: false, + scanAdmissionEnabled: true, + maxNativeQueued: 10, + maxNativeInFlight: 10, + maxCatalogQueued: 10, + maxCatalogInFlight: 1, + }); + + const claimed = await claimQueuedJobsInternalHandler(ctx, { + workerId: "catalog-worker", + lane: "catalog", + limit: 2, + }); + + expect(claimed).toHaveLength(1); + }); + it("caps each Codex scan claim request", async () => { const { ctx } = makeClaimCtx( Array.from({ length: 600 }, (_, index) => @@ -3668,6 +4250,133 @@ describe("securityScan", () => { ]); }); + it("requeues the existing catalog attempt when its worker lease expires", async () => { + const { ctx, patches } = makeClaimCtx( + [ + makeScanJob({ + _id: "securityScanJobs:catalog-expired", + status: "running", + source: "skills-sh-catalog-test", + targetKind: "skillScanRequest", + skillScanRequestId: "skillScanRequests:catalog", + leaseToken: "expired", + leaseExpiresAt: Date.now() - 1, + workerId: "stale-worker", + }), + ], + { + "skillScanRequests:catalog": { + _id: "skillScanRequests:catalog", + sourceKind: "skills-sh-catalog", + skillsShCatalogAttemptId: "skillsShCatalogScanAttempts:catalog", + status: "running", + }, + "skillsShCatalogScanAttempts:catalog": { + _id: "skillsShCatalogScanAttempts:catalog", + runId: "skillsShCatalogRuns:catalog", + skillScanRequestId: "skillScanRequests:catalog", + securityScanJobId: "securityScanJobs:catalog-expired", + status: "running", + }, + "skillsShCatalogRuns:catalog": { + _id: "skillsShCatalogRuns:catalog", + status: "completed", + }, + }, + ); + + await expect(requeueExpiredCodexScanJobsInternalHandler(ctx, {})).resolves.toEqual({ + requeued: 1, + }); + expect(patches).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: "skillScanRequests:catalog", + patch: expect.objectContaining({ status: "queued" }), + }), + expect.objectContaining({ + id: "skillsShCatalogScanAttempts:catalog", + patch: expect.objectContaining({ status: "queued" }), + }), + ]), + ); + }); + + it("terminalizes an active catalog attempt when an expired lease belongs to a canceled run", async () => { + const { ctx, patches } = makeClaimCtx( + [ + makeScanJob({ + _id: "securityScanJobs:catalog-expired-canceled", + status: "running", + source: "skills-sh-catalog-test", + targetKind: "skillScanRequest", + skillScanRequestId: "skillScanRequests:catalog", + leaseToken: "expired", + leaseExpiresAt: Date.now() - 1, + workerId: "stale-worker", + }), + ], + { + "skillsShCatalogEntries:catalog": { + _id: "skillsShCatalogEntries:catalog", + sourceContentHash: "source-hash", + scanStatus: "queued", + publicVisible: false, + }, + "skillScanRequests:catalog": { + _id: "skillScanRequests:catalog", + sourceKind: "skills-sh-catalog", + skillsShCatalogAttemptId: "skillsShCatalogScanAttempts:catalog", + status: "running", + }, + "skillsShCatalogScanAttempts:catalog": { + _id: "skillsShCatalogScanAttempts:catalog", + entryId: "skillsShCatalogEntries:catalog", + runId: "skillsShCatalogRuns:catalog", + sourceContentHash: "source-hash", + skillScanRequestId: "skillScanRequests:catalog", + securityScanJobId: "securityScanJobs:catalog-expired-canceled", + status: "running", + }, + "skillsShCatalogRuns:catalog": { + _id: "skillsShCatalogRuns:catalog", + status: "canceled", + counts: { + scansCompleted: 0, + scansCanceled: 0, + }, + operations: { + functionCalls: 0, + dbReads: 0, + dbWrites: 0, + }, + }, + }, + ); + + await expect(requeueExpiredCodexScanJobsInternalHandler(ctx, {})).resolves.toEqual({ + requeued: 0, + }); + expect(patches).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: "skillsShCatalogScanAttempts:catalog", + patch: expect.objectContaining({ + status: "canceled", + completedAt: expect.any(Number), + }), + }), + expect.objectContaining({ + id: "skillsShCatalogEntries:catalog", + patch: expect.objectContaining({ + scanStatus: "canceled", + publicVisible: false, + }), + }), + ]), + ); + }); + it("requeues hydration failures without consuming a scanner attempt", async () => { vi.stubEnv("SECURITY_SCAN_EVENT_DISPATCH_ENABLED", "0"); const { ctx, patches } = makeFailurePersistenceCtx({ @@ -3711,6 +4420,128 @@ describe("securityScan", () => { ); }); + it("requeues hydration failures on the existing catalog attempt", async () => { + vi.stubEnv("SECURITY_SCAN_EVENT_DISPATCH_ENABLED", "0"); + const { ctx, patches } = makeFailurePersistenceCtx({ + "securityScanJobs:catalog": { + ...claimedJob, + _id: "securityScanJobs:catalog", + source: "skills-sh-catalog-test", + leaseToken: "lease", + workerId: "worker-1", + targetKind: "skillScanRequest", + skillVersionId: undefined, + skillScanRequestId: "skillScanRequests:catalog", + attempts: 2, + }, + "skillScanRequests:catalog": { + _id: "skillScanRequests:catalog", + sourceKind: "skills-sh-catalog", + skillsShCatalogAttemptId: "skillsShCatalogScanAttempts:catalog", + status: "running", + }, + "skillsShCatalogScanAttempts:catalog": { + _id: "skillsShCatalogScanAttempts:catalog", + runId: "skillsShCatalogRuns:catalog", + skillScanRequestId: "skillScanRequests:catalog", + securityScanJobId: "securityScanJobs:catalog", + status: "running", + }, + "skillsShCatalogRuns:catalog": { + _id: "skillsShCatalogRuns:catalog", + status: "completed", + }, + }); + + await expect( + requeueJobLeaseInternalHandler(ctx, { + jobId: "securityScanJobs:catalog", + leaseToken: "lease", + workerId: "worker-1", + }), + ).resolves.toMatchObject({ ok: true }); + expect(patches).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: "skillScanRequests:catalog", + patch: expect.objectContaining({ status: "queued" }), + }), + expect.objectContaining({ + id: "skillsShCatalogScanAttempts:catalog", + patch: expect.objectContaining({ status: "queued" }), + }), + ]), + ); + }); + + it("terminalizes an active catalog attempt when hydration retry is denied by cancellation", async () => { + vi.stubEnv("SECURITY_SCAN_EVENT_DISPATCH_ENABLED", "0"); + const { ctx, records } = makeFailurePersistenceCtx({ + "securityScanJobs:catalog": { + ...claimedJob, + _id: "securityScanJobs:catalog", + source: "skills-sh-catalog-test", + leaseToken: "lease", + workerId: "worker-1", + targetKind: "skillScanRequest", + skillVersionId: undefined, + skillScanRequestId: "skillScanRequests:catalog", + attempts: 2, + }, + "skillsShCatalogEntries:catalog": { + _id: "skillsShCatalogEntries:catalog", + sourceContentHash: "source-hash", + scanStatus: "queued", + publicVisible: false, + }, + "skillScanRequests:catalog": { + _id: "skillScanRequests:catalog", + sourceKind: "skills-sh-catalog", + skillsShCatalogAttemptId: "skillsShCatalogScanAttempts:catalog", + status: "running", + }, + "skillsShCatalogScanAttempts:catalog": { + _id: "skillsShCatalogScanAttempts:catalog", + entryId: "skillsShCatalogEntries:catalog", + runId: "skillsShCatalogRuns:catalog", + sourceContentHash: "source-hash", + skillScanRequestId: "skillScanRequests:catalog", + securityScanJobId: "securityScanJobs:catalog", + status: "running", + }, + "skillsShCatalogRuns:catalog": { + _id: "skillsShCatalogRuns:catalog", + status: "canceled", + counts: { + scansCompleted: 0, + scansCanceled: 0, + }, + operations: { + functionCalls: 0, + dbReads: 0, + dbWrites: 0, + }, + }, + }); + + await expect( + requeueJobLeaseInternalHandler(ctx, { + jobId: "securityScanJobs:catalog", + leaseToken: "lease", + workerId: "worker-1", + }), + ).resolves.toMatchObject({ ok: true }); + expect(records.get("securityScanJobs:catalog")).toMatchObject({ status: "failed" }); + expect(records.get("skillsShCatalogScanAttempts:catalog")).toMatchObject({ + status: "canceled", + completedAt: expect.any(Number), + }); + expect(records.get("skillsShCatalogEntries:catalog")).toMatchObject({ + scanStatus: "canceled", + publicVisible: false, + }); + }); + it("reports queued scan position for manual scan requests", async () => { const targetJob = makeScanJob({ _id: "securityScanJobs:target", @@ -3939,6 +4770,167 @@ describe("securityScan", () => { expect(stored.issues[0]?.finding?.length).toBeLessThan(longSnippet.length); }); + it("keeps the scan request and job retryable when catalog result persistence fails", async () => { + vi.stubEnv("SECURITY_SCAN_WORKER_TOKEN", "placeholder"); + const runQuery = vi.fn(async () => ({ + job: { + _id: "securityScanJobs:catalog", + targetKind: "skillScanRequest", + leaseToken: "placeholder", + }, + scanRequest: { + _id: "skillScanRequests:catalog", + sourceKind: "skills-sh-catalog", + skillsShCatalogAttemptId: "skillsShCatalogScanAttempts:catalog", + sha256hash: "artifact-hash", + }, + })); + const runMutation = vi.fn(async (_ref: unknown, args: Record) => { + if ("attemptId" in args) throw new Error("catalog result unavailable"); + return { ok: true }; + }); + + await expect( + completeCodexScanJobHandler( + { runMutation, runQuery }, + { + token: "placeholder", + jobId: "securityScanJobs:catalog", + leaseToken: "placeholder", + llmAnalysis: { status: "clean", checkedAt: 123 }, + }, + ), + ).rejects.toThrow("catalog result unavailable"); + + expect(runMutation).toHaveBeenCalledTimes(1); + expect(runMutation).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + attemptId: "skillsShCatalogScanAttempts:catalog", + artifactContentHash: "artifact-hash", + verdict: "clean", + }), + ); + }); + + it("terminalizes a catalog attempt, request, and job through one mutation", async () => { + vi.stubEnv("SECURITY_SCAN_WORKER_TOKEN", "placeholder"); + const runQuery = vi.fn(async () => ({ + job: { + _id: "securityScanJobs:catalog", + targetKind: "skillScanRequest", + leaseToken: "lease-token", + }, + scanRequest: { + _id: "skillScanRequests:catalog", + sourceKind: "skills-sh-catalog", + skillsShCatalogAttemptId: "skillsShCatalogScanAttempts:catalog", + sha256hash: "artifact-hash", + }, + })); + const runMutation = vi.fn(async () => ({ ok: true })); + + await completeCodexScanJobHandler( + { runMutation, runQuery }, + { + token: "placeholder", + jobId: "securityScanJobs:catalog", + leaseToken: "lease-token", + runId: "clawscan-run", + llmAnalysis: { status: "clean", checkedAt: 123 }, + }, + ); + + expect(runMutation).toHaveBeenCalledTimes(2); + expect(runMutation).toHaveBeenNthCalledWith( + 1, + expect.anything(), + expect.objectContaining({ + attemptId: "skillsShCatalogScanAttempts:catalog", + scanId: "skillScanRequests:catalog", + jobId: "securityScanJobs:catalog", + leaseToken: "lease-token", + artifactContentHash: "artifact-hash", + verdict: "clean", + runId: "clawscan-run", + llmAnalysis: { status: "clean", checkedAt: 123 }, + }), + ); + expect(runMutation).toHaveBeenNthCalledWith(2, expect.anything(), {}); + }); + + it("acknowledges a committed catalog completion when queue refill fails", async () => { + vi.stubEnv("SECURITY_SCAN_WORKER_TOKEN", "placeholder"); + const runQuery = vi.fn(async () => ({ + job: { + _id: "securityScanJobs:catalog", + targetKind: "skillScanRequest", + leaseToken: "lease-token", + }, + scanRequest: { + _id: "skillScanRequests:catalog", + sourceKind: "skills-sh-catalog", + skillsShCatalogAttemptId: "skillsShCatalogScanAttempts:catalog", + sha256hash: "artifact-hash", + }, + })); + const runMutation = vi + .fn() + .mockResolvedValueOnce({ ok: true, applied: true }) + .mockRejectedValueOnce(new Error("dispatch unavailable")); + + await expect( + completeCodexScanJobHandler( + { runMutation, runQuery }, + { + token: "placeholder", + jobId: "securityScanJobs:catalog", + leaseToken: "lease-token", + runId: "clawscan-run", + llmAnalysis: { status: "clean", checkedAt: 123 }, + }, + ), + ).resolves.toEqual({ ok: true, applied: true }); + + expect(runMutation).toHaveBeenCalledTimes(2); + }); + + it("routes a repeated terminal catalog callback to idempotent completion", async () => { + vi.stubEnv("SECURITY_SCAN_WORKER_TOKEN", "placeholder"); + const runQuery = vi.fn(async () => ({ + job: { + _id: "securityScanJobs:catalog", + targetKind: "skillScanRequest", + status: "succeeded", + }, + scanRequest: { + _id: "skillScanRequests:catalog", + sourceKind: "skills-sh-catalog", + skillsShCatalogAttemptId: "skillsShCatalogScanAttempts:catalog", + sha256hash: "artifact-hash", + }, + })); + const runMutation = vi + .fn() + .mockResolvedValueOnce({ ok: true, applied: true, publicVisible: false }) + .mockResolvedValueOnce({ ok: true }); + + await expect( + completeCodexScanJobHandler( + { runMutation, runQuery }, + { + token: "placeholder", + jobId: "securityScanJobs:catalog", + leaseToken: "expired-lease-token", + runId: "clawscan-run", + llmAnalysis: { status: "clean", checkedAt: 123 }, + }, + ), + ).resolves.toEqual({ ok: true, applied: true, publicVisible: false }); + + expect(runMutation).toHaveBeenCalledTimes(2); + }); + it("clears legacy plugin SkillSpector results when no new analysis is produced", async () => { vi.stubEnv("SECURITY_SCAN_WORKER_TOKEN", "worker-secret"); const runQuery = vi.fn(async () => ({ @@ -4580,6 +5572,209 @@ describe("securityScan", () => { expectNoLeakedWorkerErrorSecrets(requestError); }); + it("retries catalog worker failures on the same admitted attempt", async () => { + const { ctx, records } = makeFailurePersistenceCtx({ + "securityScanJobs:catalog": { + _id: "securityScanJobs:catalog", + attempts: 1, + leaseToken: "lease", + nextRunAt: 123, + source: "skills-sh-catalog-test", + skillScanRequestId: "skillScanRequests:catalog", + status: "running", + targetKind: "skillScanRequest", + }, + "skillScanRequests:catalog": { + _id: "skillScanRequests:catalog", + sourceKind: "skills-sh-catalog", + skillsShCatalogAttemptId: "skillsShCatalogScanAttempts:catalog", + status: "running", + }, + "skillsShCatalogScanAttempts:catalog": { + _id: "skillsShCatalogScanAttempts:catalog", + runId: "skillsShCatalogRuns:catalog", + skillScanRequestId: "skillScanRequests:catalog", + securityScanJobId: "securityScanJobs:catalog", + status: "running", + }, + "skillsShCatalogRuns:catalog": { + _id: "skillsShCatalogRuns:catalog", + status: "completed", + }, + }); + + const result = await failJobInternalHandler(ctx, { + jobId: "securityScanJobs:catalog", + leaseToken: "lease", + error: "transient worker failure", + }); + + expect(result).toEqual({ ok: true, retry: true }); + expect(records.get("skillsShCatalogScanAttempts:catalog")).toMatchObject({ + status: "queued", + }); + expect(records.get("skillScanRequests:catalog")).toMatchObject({ + status: "queued", + }); + }); + + it("does not apply a second catalog terminal transition after retry exhaustion", async () => { + vi.stubEnv("SECURITY_SCAN_WORKER_TOKEN", "placeholder"); + const runMutation = vi.fn(async (_ref: unknown, args: Record) => { + if ("jobId" in args && "leaseToken" in args && "error" in args) { + return { ok: true, retry: false }; + } + return { ok: true }; + }); + const runQuery = vi.fn(async () => ({ + job: { + _id: "securityScanJobs:catalog", + targetKind: "skillScanRequest", + }, + scanRequest: { + _id: "skillScanRequests:catalog", + sourceKind: "skills-sh-catalog", + skillsShCatalogAttemptId: "skillsShCatalogScanAttempts:catalog", + sha256hash: "artifact-hash", + }, + })); + + await failCodexScanJobHandler( + { runMutation, runQuery }, + { + token: "placeholder", + jobId: "securityScanJobs:catalog", + leaseToken: "placeholder", + error: "terminal worker failure", + }, + ); + + expect( + runMutation.mock.calls.some(([, args]) => "attemptId" in (args as Record)), + ).toBe(false); + expect(runMutation).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + scanId: "skillScanRequests:catalog", + error: "terminal worker failure", + }), + ); + }); + + it("terminalizes the linked catalog attempt when the worker retry budget is exhausted", async () => { + const { ctx, records } = makeFailurePersistenceCtx({ + "securityScanJobs:catalog": { + _id: "securityScanJobs:catalog", + attempts: 3, + leaseToken: "lease", + nextRunAt: 123, + source: "skills-sh-catalog-test", + skillScanRequestId: "skillScanRequests:catalog", + status: "running", + targetKind: "skillScanRequest", + }, + "skillsShCatalogEntries:catalog": { + _id: "skillsShCatalogEntries:catalog", + sourceContentHash: "source-hash", + scanStatus: "queued", + publicVisible: false, + }, + "skillScanRequests:catalog": { + _id: "skillScanRequests:catalog", + sourceKind: "skills-sh-catalog", + skillsShCatalogAttemptId: "skillsShCatalogScanAttempts:catalog", + status: "running", + }, + "skillsShCatalogScanAttempts:catalog": { + _id: "skillsShCatalogScanAttempts:catalog", + entryId: "skillsShCatalogEntries:catalog", + runId: "skillsShCatalogRuns:catalog", + sourceContentHash: "source-hash", + skillScanRequestId: "skillScanRequests:catalog", + securityScanJobId: "securityScanJobs:catalog", + status: "running", + }, + "skillsShCatalogRuns:catalog": { + _id: "skillsShCatalogRuns:catalog", + status: "completed", + counts: { + scansCompleted: 0, + scansCanceled: 0, + }, + operations: { + functionCalls: 0, + dbReads: 0, + dbWrites: 0, + }, + }, + }); + + const result = await failJobInternalHandler(ctx, { + jobId: "securityScanJobs:catalog", + leaseToken: "lease", + error: "terminal worker failure", + }); + + expect(result).toEqual({ ok: true, retry: false }); + expect(records.get("skillsShCatalogScanAttempts:catalog")).toMatchObject({ + status: "failed", + verdict: "failed", + completedAt: expect.any(Number), + }); + expect(records.get("skillsShCatalogEntries:catalog")).toMatchObject({ + scanStatus: "failed", + publicVisible: false, + }); + expect(records.get("skillsShCatalogRuns:catalog")).toMatchObject({ + counts: expect.objectContaining({ scansCompleted: 1 }), + }); + }); + + it("does not retry catalog work after its run is canceled", async () => { + const { ctx, records } = makeFailurePersistenceCtx({ + "securityScanJobs:catalog": { + _id: "securityScanJobs:catalog", + attempts: 1, + leaseToken: "lease", + nextRunAt: 123, + source: "skills-sh-catalog-test", + skillScanRequestId: "skillScanRequests:catalog", + status: "running", + targetKind: "skillScanRequest", + }, + "skillScanRequests:catalog": { + _id: "skillScanRequests:catalog", + sourceKind: "skills-sh-catalog", + skillsShCatalogAttemptId: "skillsShCatalogScanAttempts:catalog", + status: "running", + }, + "skillsShCatalogScanAttempts:catalog": { + _id: "skillsShCatalogScanAttempts:catalog", + runId: "skillsShCatalogRuns:catalog", + skillScanRequestId: "skillScanRequests:catalog", + securityScanJobId: "securityScanJobs:catalog", + status: "canceled", + }, + "skillsShCatalogRuns:catalog": { + _id: "skillsShCatalogRuns:catalog", + status: "canceled", + }, + }); + + const result = await failJobInternalHandler(ctx, { + jobId: "securityScanJobs:catalog", + leaseToken: "lease", + error: "worker stopped after cancellation", + }); + + expect(result).toEqual({ ok: true, retry: false }); + expect(records.get("securityScanJobs:catalog")).toMatchObject({ status: "failed" }); + expect(records.get("skillScanRequests:catalog")).toMatchObject({ status: "failed" }); + expect(records.get("skillsShCatalogScanAttempts:catalog")).toMatchObject({ + status: "canceled", + }); + }); + it("sanitizes worker errors before patching failed scan result records", async () => { const { ctx, records } = makeFailurePersistenceCtx({ "githubSkillScans:1": { diff --git a/convex/securityScan.ts b/convex/securityScan.ts index a0f5e645..6d1ce558 100644 --- a/convex/securityScan.ts +++ b/convex/securityScan.ts @@ -21,6 +21,7 @@ import { MAX_SKILL_SCAN_REQUEST_MANIFEST_BYTES, serializedSkillScanRequestFilesBytes, } from "./lib/skillScanRequestFiles"; +import { getSkillsShFixtureEnvironmentPolicy } from "./lib/skillsShCatalogEnvironment"; import { redactWorkerPublicText } from "./lib/workerTextRedaction"; import { requestSecurityScanDispatch } from "./securityScanDispatch"; @@ -138,10 +139,21 @@ const jobSourceValidator = v.union( v.literal("backfill"), v.literal("bulk-rescan"), v.literal("manual"), + v.literal("skills-sh-catalog-test"), ); -type SecurityScanJobSource = "publish" | "vt-update" | "backfill" | "bulk-rescan" | "manual"; -const codexScanWorkerLaneValidator = v.union(v.literal("priority"), v.literal("shared")); +type SecurityScanJobSource = + | "publish" + | "vt-update" + | "backfill" + | "bulk-rescan" + | "manual" + | "skills-sh-catalog-test"; +const codexScanWorkerLaneValidator = v.union( + v.literal("priority"), + v.literal("shared"), + v.literal("catalog"), +); type CodexScanQueueHealth = { snapshotAt: number; @@ -158,6 +170,7 @@ const CLAIM_SOURCE_ORDER: SecurityScanJobSource[] = [ "backfill", "vt-update", "bulk-rescan", + "skills-sh-catalog-test", ]; const SOURCE_PRIORITY: Record = { @@ -166,6 +179,7 @@ const SOURCE_PRIORITY: Record = { backfill: 3, "vt-update": 2, "bulk-rescan": 1, + "skills-sh-catalog-test": 0, }; function higherPrioritySource( @@ -310,6 +324,12 @@ const githubSkillScanStatusValidator = v.union( v.literal("pending"), v.literal("failed"), ); +const catalogScanVerdictValidator = v.union( + v.literal("clean"), + v.literal("suspicious"), + v.literal("malicious"), + v.literal("failed"), +); const internalRefs = internal as unknown as { packages: { @@ -329,6 +349,7 @@ const internalRefs = internal as unknown as { getSkillScanRequestForUserInternal: unknown; getJobTargetInternal: unknown; recordGitHubSkillScanResultInternal: unknown; + completeCatalogSkillScanJobInternal: unknown; recordSkillScanRequestFailedInternal: unknown; recordSkillScanRequestSucceededInternal: unknown; requeueJobLeaseInternal: unknown; @@ -1258,6 +1279,42 @@ async function enqueueSkillScanRequestJob( return jobId; } +export async function enqueueSkillsShCatalogScanRequest( + ctx: MutationCtx, + args: { + actorUserId: Id<"users">; + attemptId: Id<"skillsShCatalogScanAttempts">; + slug: string; + displayName: string; + artifactContentHash: string; + files: Doc<"skillScanRequests">["files"]; + }, +) { + const now = Date.now(); + const requestId = await ctx.db.insert("skillScanRequests", { + actorUserId: args.actorUserId, + sourceKind: "skills-sh-catalog", + update: false, + writtenBack: false, + status: "queued", + requestedJobSource: "skills-sh-catalog-test", + requestedJobPriority: -100, + slug: args.slug, + displayName: args.displayName, + skillsShCatalogAttemptId: args.attemptId, + files: args.files, + sha256hash: args.artifactContentHash, + expiresAt: skillScanRequestExpiresAt(now), + createdAt: now, + updatedAt: now, + }); + const jobId = await enqueueSkillScanRequestJob(ctx, requestId, { + source: "skills-sh-catalog-test", + priority: -100, + }); + return { requestId, jobId }; +} + export const prepareGitHubSkillScanRequestInternal = internalMutation({ args: { skillId: v.id("skills"), @@ -1868,6 +1925,227 @@ export const recordSkillScanRequestSucceededInternal = internalMutation({ }, }); +export const completeCatalogSkillScanJobInternal = internalMutation({ + args: { + attemptId: v.id("skillsShCatalogScanAttempts"), + scanId: v.id("skillScanRequests"), + jobId: v.id("securityScanJobs"), + leaseToken: v.string(), + artifactContentHash: v.string(), + verdict: catalogScanVerdictValidator, + runId: v.optional(v.string()), + llmAnalysis: llmAnalysisValidator, + skillSpectorAnalysis: v.optional(skillSpectorAnalysisValidator), + }, + handler: async (ctx, args) => { + const environment = getSkillsShFixtureEnvironmentPolicy(); + if (!environment.allowed || environment.environment !== "test") { + throw new ConvexError("catalog scan completion requires the permanent Test environment"); + } + const [job, request, attempt] = await Promise.all([ + ctx.db.get(args.jobId), + ctx.db.get(args.scanId), + ctx.db.get(args.attemptId), + ]); + if ( + !job || + job.source !== "skills-sh-catalog-test" || + job.targetKind !== "skillScanRequest" || + job.skillScanRequestId !== args.scanId + ) { + throw new ConvexError("Catalog scan job linkage mismatch"); + } + if ( + !request || + request.sourceKind !== "skills-sh-catalog" || + request.securityScanJobId !== args.jobId || + request.skillsShCatalogAttemptId !== args.attemptId + ) { + throw new ConvexError("Catalog scan request linkage mismatch"); + } + if ( + !attempt || + attempt.dispatchKind !== "real" || + attempt.skillScanRequestId !== args.scanId || + attempt.securityScanJobId !== args.jobId + ) { + throw new ConvexError("Catalog scan attempt linkage mismatch"); + } + const artifactContentHash = args.artifactContentHash.toLowerCase(); + if ( + !attempt.artifactContentHash || + attempt.artifactContentHash !== artifactContentHash || + request.sha256hash !== artifactContentHash + ) { + throw new ConvexError("Catalog scan artifact hash mismatch"); + } + if ( + attempt.status === "succeeded" || + attempt.status === "failed" || + attempt.status === "canceled" + ) { + const expectedStatus = args.verdict === "failed" ? "failed" : "succeeded"; + if ( + attempt.status === expectedStatus && + attempt.verdict === args.verdict && + request.status === expectedStatus && + job.status === expectedStatus && + (expectedStatus !== "failed" || + (request.lastError === "Catalog scan analysis failed" && + job.lastError === "Catalog scan analysis failed")) + ) { + return { ok: true as const, applied: true as const, publicVisible: false as const }; + } + if ( + attempt.status === "canceled" && + request.status === "failed" && + job.status === "failed" && + request.lastError === job.lastError + ) { + if (request.lastError === "Catalog run canceled before scan completion") { + return { ok: true as const, applied: false as const, reason: "run-canceled" as const }; + } + if (request.lastError === "Catalog source changed before scan completion") { + return { ok: true as const, applied: false as const, reason: "stale-attempt" as const }; + } + } + throw new ConvexError("Catalog scan terminal result mismatch"); + } + if ( + job.leaseToken !== args.leaseToken || + job.status !== "running" || + (attempt.status !== "queued" && attempt.status !== "running") + ) { + throw new ConvexError("Catalog scan job lease mismatch"); + } + + const [run, entry] = await Promise.all([ + ctx.db.get(attempt.runId), + ctx.db.get(attempt.entryId), + ]); + const now = Date.now(); + const terminalizeWithoutResult = async (reason: "run-canceled" | "stale-attempt") => { + const entryStillCurrent = entry?.sourceContentHash === attempt.sourceContentHash; + await ctx.db.patch(attempt._id, { + status: "canceled", + completedAt: now, + updatedAt: now, + }); + if (entryStillCurrent) { + await ctx.db.patch(entry._id, { + scanStatus: "canceled", + publicVisible: false, + updatedAt: now, + }); + } + await ctx.db.patch(request._id, { + status: "failed", + lastError: + reason === "run-canceled" + ? "Catalog run canceled before scan completion" + : "Catalog source changed before scan completion", + completedAt: now, + updatedAt: now, + }); + await ctx.db.patch(job._id, { + status: "failed", + lastError: + reason === "run-canceled" + ? "Catalog run canceled before scan completion" + : "Catalog source changed before scan completion", + completedAt: now, + leaseToken: undefined, + leaseExpiresAt: undefined, + updatedAt: now, + }); + if (run) { + const [queued, running] = await Promise.all([ + ctx.db + .query("skillsShCatalogScanAttempts") + .withIndex("by_run_and_status", (q) => q.eq("runId", run._id).eq("status", "queued")) + .first(), + ctx.db + .query("skillsShCatalogScanAttempts") + .withIndex("by_run_and_status", (q) => q.eq("runId", run._id).eq("status", "running")) + .first(), + ]); + await ctx.db.patch(run._id, { + ...(reason === "run-canceled" + ? { status: queued || running ? ("canceling" as const) : ("canceled" as const) } + : {}), + counts: { + ...run.counts, + scansCanceled: run.counts.scansCanceled + 1, + }, + operations: { + functionCalls: run.operations.functionCalls + 1, + dbReads: run.operations.dbReads + 7, + dbWrites: run.operations.dbWrites + (entryStillCurrent ? 5 : 4), + }, + updatedAt: now, + }); + } + return { ok: true as const, applied: false as const, reason }; + }; + + if (run?.status === "canceling" || run?.status === "canceled") { + return await terminalizeWithoutResult("run-canceled"); + } + if (!entry || entry.sourceContentHash !== attempt.sourceContentHash) { + return await terminalizeWithoutResult("stale-attempt"); + } + + const scanFailed = args.verdict === "failed"; + await ctx.db.patch(attempt._id, { + status: scanFailed ? "failed" : "succeeded", + verdict: args.verdict, + completedAt: now, + updatedAt: now, + }); + await ctx.db.patch(entry._id, { + scanStatus: args.verdict, + publicVisible: false, + updatedAt: now, + }); + await ctx.db.patch(request._id, { + status: scanFailed ? "failed" : "succeeded", + lastError: scanFailed ? "Catalog scan analysis failed" : undefined, + llmAnalysis: args.llmAnalysis, + ...(args.skillSpectorAnalysis + ? { skillSpectorAnalysis: capSkillSpectorAnalysisForStorage(args.skillSpectorAnalysis) } + : {}), + writtenBack: request.writtenBack, + runId: args.runId, + completedAt: now, + updatedAt: now, + }); + await ctx.db.patch(job._id, { + status: scanFailed ? "failed" : "succeeded", + lastError: scanFailed ? "Catalog scan analysis failed" : undefined, + runId: args.runId, + completedAt: now, + leaseToken: undefined, + leaseExpiresAt: undefined, + updatedAt: now, + }); + if (run) { + await ctx.db.patch(run._id, { + counts: { + ...run.counts, + scansCompleted: run.counts.scansCompleted + 1, + }, + operations: { + functionCalls: run.operations.functionCalls + 1, + dbReads: run.operations.dbReads + 5, + dbWrites: run.operations.dbWrites + 5, + }, + updatedAt: now, + }); + } + return { ok: true as const, applied: true as const, publicVisible: false as const }; + }, +}); + export const recordSkillScanRequestFailedInternal = internalMutation({ args: { scanId: v.id("skillScanRequests"), @@ -1945,12 +2223,32 @@ export const pruneExpiredSkillScanRequestsInternal = internalMutation({ let deletedRequests = 0; let deferredRequests = 0; for (const request of requests) { - if (request.securityScanJobId) { - const job = await ctx.db.get(request.securityScanJobId); - if (job?.targetKind === "skillScanRequest") { - await ctx.db.delete(job._id); - deletedJobs += 1; - } + const job = request.securityScanJobId ? await ctx.db.get(request.securityScanJobId) : null; + if ( + request.sourceKind === "skills-sh-catalog" && + job?.targetKind === "skillScanRequest" && + (job.status === "queued" || job.status === "running") + ) { + deferredRequests += 1; + continue; + } + if (request.sourceKind === "skills-sh-catalog" && request.skillsShCatalogAttemptId) { + const attempt = await ctx.db.get(request.skillsShCatalogAttemptId); + const run = attempt ? await ctx.db.get(attempt.runId) : null; + await terminalizeBlockedCatalogRetry( + ctx, + { + kind: "blocked", + request, + ...(attempt ? { attempt } : {}), + ...(run ? { run } : {}), + }, + now, + ); + } + if (job?.targetKind === "skillScanRequest") { + await ctx.db.delete(job._id); + deletedJobs += 1; } const fileChunks = request.sourceKind === "github" @@ -1977,7 +2275,11 @@ export const pruneExpiredSkillScanRequestsInternal = internalMutation({ deferredRequests += 1; continue; } - if (request.sourceKind === "upload" || request.sourceKind === "github") { + if ( + request.sourceKind === "upload" || + request.sourceKind === "github" || + request.sourceKind === "skills-sh-catalog" + ) { for (const file of [...request.files, ...fileChunks.flatMap((chunk) => chunk.files)]) { try { await ctx.storage.delete(file.storageId); @@ -2369,9 +2671,35 @@ export const claimQueuedJobsInternal = internalMutation({ const ready: Doc<"securityScanJobs">[] = []; const claimedIds = new Set>(); const remainingCapacity = () => capacity - ready.length; - const addReadyJobs = (jobs: Doc<"securityScanJobs">[]) => { + let catalogGate: + | { + control: Doc<"skillsShCatalogControls">; + health: Awaited>; + } + | null + | undefined; + const loadCatalogGate = async () => { + if (catalogGate !== undefined) return catalogGate; + const environment = getSkillsShFixtureEnvironmentPolicy(); + if (!environment.allowed || environment.environment !== "test") { + catalogGate = null; + return catalogGate; + } + const control = await ctx.db + .query("skillsShCatalogControls") + .withIndex("by_key", (q) => q.eq("key", "global")) + .unique(); + catalogGate = control + ? { + control, + health: await readCatalogClaimHealth(ctx, control), + } + : null; + return catalogGate; + }; + const addReadyJobs = (jobs: Doc<"securityScanJobs">[], stopAtCapacity = true) => { for (const job of jobs) { - if (remainingCapacity() === 0) break; + if (stopAtCapacity && remainingCapacity() === 0) break; if (claimedIds.has(job._id) || job.nextRunAt > now) continue; claimedIds.add(job._id); ready.push(job); @@ -2379,39 +2707,97 @@ export const claimQueuedJobsInternal = internalMutation({ }; const takeReadySourceJobs = async (source: SecurityScanJobSource) => { if (remainingCapacity() === 0) return []; + let takeLimit = remainingCapacity(); + if (source === "skills-sh-catalog-test") { + const gate = await loadCatalogGate(); + if ( + !gate || + gate.control.mode !== "staging-live" || + gate.control.paused || + !gate.control.scanAdmissionEnabled || + !gate.health.claimable + ) { + return []; + } + // Scan a bounded window independent of the current admission cap so paused + // or canceled jobs cannot hide later runnable backlog after the cap is lowered. + takeLimit = MAX_CODEX_SCAN_CLAIM_LIMIT; + } return await ctx.db .query("securityScanJobs") .withIndex("by_status_source_next_run_at", (q) => q.eq("status", "queued").eq("source", source).lte("nextRunAt", now), ) .order("asc") - .take(remainingCapacity()); + .take(takeLimit); }; - addReadyJobs(await takeReadySourceJobs("manual")); + if (args.lane === "catalog") { + addReadyJobs(await takeReadySourceJobs("skills-sh-catalog-test"), false); + } else { + addReadyJobs(await takeReadySourceJobs("manual")); - if (remainingCapacity() > 0) { - addReadyJobs( - await ctx.db - .query("securityScanJobs") - .withIndex("by_status_malicious_signal_next_run_at", (q) => - q.eq("status", "queued").eq("hasMaliciousSignal", true).lte("nextRunAt", now), - ) - .order("asc") - .take(remainingCapacity()), - ); - } + if (remainingCapacity() > 0) { + addReadyJobs( + await ctx.db + .query("securityScanJobs") + .withIndex("by_status_malicious_signal_next_run_at", (q) => + q.eq("status", "queued").eq("hasMaliciousSignal", true).lte("nextRunAt", now), + ) + .order("asc") + .take(remainingCapacity()), + ); + } - // Shared workers remain work-conserving and may help priority work. The dedicated - // priority lane never claims bulk sources, which guarantees reserved fast-path capacity. - for (const source of CLAIM_SOURCE_ORDER) { - addReadyJobs(await takeReadySourceJobs(source)); - if (remainingCapacity() === 0) break; - if (args.lane === "priority" && source === "publish") break; + // Shared workers remain work-conserving and may help priority work. The dedicated + // priority lane never claims bulk sources, which guarantees reserved fast-path capacity. + for (const source of CLAIM_SOURCE_ORDER) { + addReadyJobs(await takeReadySourceJobs(source), source !== "skills-sh-catalog-test"); + if (remainingCapacity() === 0) break; + if (args.lane === "priority" && source === "publish") break; + } } const claimed = []; + let catalogClaims = 0; for (const job of ready) { + if (claimed.length >= capacity) break; + let catalogAttemptId: Id<"skillsShCatalogScanAttempts"> | null = null; + if (job.source === "skills-sh-catalog-test") { + if (!job.skillScanRequestId) { + continue; + } + const request = await ctx.db.get(job.skillScanRequestId); + const attempt = request?.skillsShCatalogAttemptId + ? await ctx.db.get(request.skillsShCatalogAttemptId) + : null; + const run = attempt ? await ctx.db.get(attempt.runId) : null; + await loadCatalogGate(); + const control = catalogGate?.control ?? null; + const health = catalogGate?.health ?? null; + if ( + !request || + request.sourceKind !== "skills-sh-catalog" || + !attempt || + attempt.skillScanRequestId !== request._id || + attempt.securityScanJobId !== job._id || + attempt.status !== "queued" || + !run || + run.status === "paused" || + run.status === "canceling" || + run.status === "canceled" || + run.status === "failed" || + !control || + control.mode !== "staging-live" || + control.paused || + !control.scanAdmissionEnabled || + !health?.claimable || + health.catalogInFlight + catalogClaims >= control.maxCatalogInFlight + ) { + continue; + } + catalogAttemptId = attempt._id; + } const leaseToken = crypto.randomUUID(); await ctx.db.patch(job._id, { status: "running", @@ -2429,6 +2815,13 @@ export const claimQueuedJobsInternal = internalMutation({ updatedAt: now, }); } + if (catalogAttemptId) { + await ctx.db.patch(catalogAttemptId, { + status: "running", + updatedAt: now, + }); + catalogClaims += 1; + } claimed.push({ ...job, status: "running" as const, @@ -2442,6 +2835,169 @@ export const claimQueuedJobsInternal = internalMutation({ }, }); +async function readCatalogClaimHealth(ctx: MutationCtx, control: Doc<"skillsShCatalogControls">) { + const nativeSources = ["publish", "vt-update", "backfill", "bulk-rescan", "manual"] as const; + const [nativeQueuedBySource, nativeRunningBySource, catalogQueued, catalogRunning] = + await Promise.all([ + Promise.all( + nativeSources.map(async (source) => + ctx.db + .query("securityScanJobs") + .withIndex("by_status_source_created_at", (q) => + q.eq("status", "queued").eq("source", source), + ) + .take(control.maxNativeQueued + 1), + ), + ), + Promise.all( + nativeSources.map(async (source) => + ctx.db + .query("securityScanJobs") + .withIndex("by_status_source_created_at", (q) => + q.eq("status", "running").eq("source", source), + ) + .take(control.maxNativeInFlight + 1), + ), + ), + ctx.db + .query("skillsShCatalogScanAttempts") + .withIndex("by_dispatch_kind_and_status_and_created_at", (q) => + q.eq("dispatchKind", "real").eq("status", "queued"), + ) + .take(control.maxCatalogQueued + 1), + ctx.db + .query("skillsShCatalogScanAttempts") + .withIndex("by_dispatch_kind_and_status_and_created_at", (q) => + q.eq("dispatchKind", "real").eq("status", "running"), + ) + .take(control.maxCatalogInFlight + 1), + ]); + const nativeQueued = Math.min( + control.maxNativeQueued + 1, + nativeQueuedBySource.reduce((count, jobs) => count + jobs.length, 0), + ); + const nativeInFlight = Math.min( + control.maxNativeInFlight + 1, + nativeRunningBySource.reduce((count, jobs) => count + jobs.length, 0), + ); + return { + nativeQueued, + nativeInFlight, + catalogQueued: catalogQueued.length, + catalogInFlight: catalogRunning.length, + // Queued depth is an admission limit, not a drain limit. Already admitted work + // must remain claimable after an operator lowers maxCatalogQueued. + claimable: + nativeQueued <= control.maxNativeQueued && + nativeInFlight <= control.maxNativeInFlight && + catalogRunning.length <= control.maxCatalogInFlight, + }; +} + +type CatalogRetryDecision = + | { kind: "not-catalog" } + | { + kind: "allowed"; + attemptId: Id<"skillsShCatalogScanAttempts">; + requestId: Id<"skillScanRequests">; + } + | { + kind: "blocked"; + request?: Doc<"skillScanRequests">; + attempt?: Doc<"skillsShCatalogScanAttempts">; + run?: Doc<"skillsShCatalogRuns">; + }; + +async function prepareCatalogRetry( + ctx: MutationCtx, + job: Doc<"securityScanJobs">, + now: number, + retryAllowed = true, +): Promise { + if (job.source !== "skills-sh-catalog-test") return { kind: "not-catalog" }; + if (job.targetKind !== "skillScanRequest" || !job.skillScanRequestId) { + return { kind: "blocked" }; + } + const request = await ctx.db.get(job.skillScanRequestId); + if (!request || request.sourceKind !== "skills-sh-catalog" || !request.skillsShCatalogAttemptId) { + return { kind: "blocked", ...(request ? { request } : {}) }; + } + const attempt = await ctx.db.get(request.skillsShCatalogAttemptId); + if ( + !attempt || + attempt.skillScanRequestId !== request._id || + attempt.securityScanJobId !== job._id + ) { + return { kind: "blocked", request }; + } + const run = await ctx.db.get(attempt.runId); + if ( + !retryAllowed || + (attempt.status !== "queued" && attempt.status !== "running") || + !run || + run.status === "canceling" || + run.status === "canceled" || + run.status === "failed" + ) { + return { kind: "blocked", request, attempt, ...(run ? { run } : {}) }; + } + await ctx.db.patch(attempt._id, { + status: "queued", + updatedAt: now, + }); + return { + kind: "allowed", + attemptId: attempt._id, + requestId: request._id, + }; +} + +async function terminalizeBlockedCatalogRetry( + ctx: MutationCtx, + decision: Extract, + now: number, +) { + const attempt = decision.attempt; + if (!attempt || (attempt.status !== "queued" && attempt.status !== "running")) return; + + const canceled = decision.run?.status === "canceling" || decision.run?.status === "canceled"; + await ctx.db.patch(attempt._id, { + status: canceled ? "canceled" : "failed", + verdict: canceled ? undefined : "failed", + completedAt: now, + updatedAt: now, + }); + + const entry = await ctx.db.get(attempt.entryId); + const entryShouldBeTerminalized = + entry?.sourceContentHash === attempt.sourceContentHash && + (entry.scanStatus === "planned" || entry.scanStatus === "queued"); + if (entryShouldBeTerminalized) { + await ctx.db.patch(entry._id, { + scanStatus: canceled ? "canceled" : "failed", + publicVisible: false, + updatedAt: now, + }); + } + + if (decision.run) { + await ctx.db.patch(decision.run._id, { + counts: { + ...decision.run.counts, + scansCompleted: decision.run.counts.scansCompleted + (canceled ? 0 : 1), + scansCanceled: decision.run.counts.scansCanceled + (canceled ? 1 : 0), + }, + operations: { + ...decision.run.operations, + functionCalls: decision.run.operations.functionCalls + 1, + dbReads: decision.run.operations.dbReads + 4, + dbWrites: decision.run.operations.dbWrites + (entryShouldBeTerminalized ? 3 : 2), + }, + updatedAt: now, + }); + } +} + export const requeueExpiredCodexScanJobsInternal = internalMutation({ args: { limit: v.optional(v.number()), @@ -2459,7 +3015,30 @@ export const requeueExpiredCodexScanJobsInternal = internalMutation({ Math.min(args.limit ?? MAX_EXPIRED_CODEX_SCAN_LEASE_REQUEUES, MAX_CODEX_SCAN_CLAIM_LIMIT), ), ); + let requeued = 0; for (const job of jobs) { + const catalogRetry = await prepareCatalogRetry(ctx, job, now); + if (catalogRetry.kind === "blocked") { + await terminalizeBlockedCatalogRetry(ctx, catalogRetry, now); + await ctx.db.patch(job._id, { + status: "failed", + lastError: "Catalog scan retry blocked by inactive or invalid linkage", + completedAt: now, + leaseToken: undefined, + leaseExpiresAt: undefined, + workerId: undefined, + updatedAt: now, + }); + if (catalogRetry.request) { + await ctx.db.patch(catalogRetry.request._id, { + status: "failed", + lastError: "Catalog scan retry blocked by inactive or invalid linkage", + completedAt: now, + updatedAt: now, + }); + } + continue; + } await ctx.db.patch(job._id, { status: "queued", leaseToken: undefined, @@ -2468,9 +3047,18 @@ export const requeueExpiredCodexScanJobsInternal = internalMutation({ nextRunAt: now, updatedAt: now, }); + if (catalogRetry.kind === "allowed") { + await ctx.db.patch(catalogRetry.requestId, { + status: "queued", + lastError: undefined, + completedAt: undefined, + updatedAt: now, + }); + } + requeued += 1; } - if (jobs.length > 0) await requestSecurityScanDispatch(ctx); - return { requeued: jobs.length }; + if (requeued > 0) await requestSecurityScanDispatch(ctx); + return { requeued }; }, }); @@ -2500,11 +3088,13 @@ export const requeueFailedSecurityScanJobsInternal = internalMutation({ .gte("updatedAt", args.failedAfter) .lt("updatedAt", args.failedBefore), ) + .filter((q) => q.neq(q.field("source"), "skills-sh-catalog-test")) .order("asc") .take(limit + 1); const matched = jobs.slice(0, limit); const bySource: Partial> = {}; const byTargetKind: Partial["targetKind"], number>> = {}; + let requeued = 0; for (const job of matched) { bySource[job.source] = (bySource[job.source] ?? 0) + 1; @@ -2553,13 +3143,14 @@ export const requeueFailedSecurityScanJobsInternal = internalMutation({ } } } + requeued += 1; } - if (!args.dryRun && matched.length > 0) await requestSecurityScanDispatch(ctx); + if (!args.dryRun && requeued > 0) await requestSecurityScanDispatch(ctx); return { dryRun: args.dryRun, matched: matched.length, - requeued: args.dryRun ? 0 : matched.length, + requeued: args.dryRun ? 0 : requeued, hasMore: jobs.length > limit, bySource, byTargetKind, @@ -2672,8 +3263,12 @@ export const failJobInternal = internalMutation({ const job = await ctx.db.get(args.jobId); if (!job || job.leaseToken !== args.leaseToken) throw new ConvexError("Lease mismatch"); const now = Date.now(); - const retry = job.attempts < MAX_ATTEMPTS; + const catalogRetry = await prepareCatalogRetry(ctx, job, now, job.attempts < MAX_ATTEMPTS); + const retry = job.attempts < MAX_ATTEMPTS && catalogRetry.kind !== "blocked"; const error = sanitizeWorkerErrorDetail(args.error, 2000); + if (catalogRetry.kind === "blocked") { + await terminalizeBlockedCatalogRetry(ctx, catalogRetry, now); + } await ctx.db.patch(args.jobId, { status: retry ? "queued" : "failed", lastError: error, @@ -2712,22 +3307,39 @@ export const requeueJobLeaseInternal = internalMutation({ throw new ConvexError("Lease mismatch"); } const now = Date.now(); + const catalogRetry = await prepareCatalogRetry(ctx, job, now); + const retry = catalogRetry.kind !== "blocked"; + if (catalogRetry.kind === "blocked") { + await terminalizeBlockedCatalogRetry(ctx, catalogRetry, now); + } await ctx.db.patch(job._id, { - status: "queued", - attempts: Math.max(0, job.attempts - 1), + status: retry ? "queued" : "failed", + attempts: retry ? Math.max(0, job.attempts - 1) : job.attempts, + ...(retry + ? {} + : { + lastError: "Catalog scan retry blocked by inactive or invalid linkage", + completedAt: now, + }), leaseToken: undefined, leaseExpiresAt: undefined, workerId: undefined, - nextRunAt: now + 60_000, + nextRunAt: retry ? now + 60_000 : job.nextRunAt, updatedAt: now, }); if (job.targetKind === "skillScanRequest" && job.skillScanRequestId) { await ctx.db.patch(job.skillScanRequestId, { - status: "queued", + status: retry ? "queued" : "failed", + ...(retry + ? {} + : { + lastError: "Catalog scan retry blocked by inactive or invalid linkage", + completedAt: now, + }), updatedAt: now, }); } - await requestSecurityScanDispatch(ctx); + if (retry) await requestSecurityScanDispatch(ctx); return { ok: true as const, nextRunAt: now + 60_000 }; }, }); @@ -2951,7 +3563,13 @@ export const completeCodexScanJob = action({ }, ); if (!target) throw new ConvexError("Job not found"); - if (target.job.leaseToken !== args.leaseToken) throw new ConvexError("Lease mismatch"); + const isCatalogScanRequest = + target.job.targetKind === "skillScanRequest" && + target.scanRequest?.sourceKind === "skills-sh-catalog" && + Boolean(target.scanRequest.skillsShCatalogAttemptId); + if (!isCatalogScanRequest && target.job.leaseToken !== args.leaseToken) { + throw new ConvexError("Lease mismatch"); + } if (target.job.targetKind === "skillVersion" && target.version) { if (args.skillSpectorAnalysis) { @@ -3007,6 +3625,36 @@ export const completeCodexScanJob = action({ }); writtenBack = true; } + if ( + target.scanRequest.sourceKind === "skills-sh-catalog" && + target.scanRequest.skillsShCatalogAttemptId + ) { + const result = await runMutationRef<{ ok: true }>( + ctx, + internalRefs.securityScan.completeCatalogSkillScanJobInternal, + { + attemptId: target.scanRequest.skillsShCatalogAttemptId, + scanId: target.scanRequest._id, + jobId: args.jobId, + leaseToken: args.leaseToken, + artifactContentHash: target.scanRequest.sha256hash ?? "", + verdict: githubSkillScanStatusFromLlmAnalysis(args.llmAnalysis), + runId: args.runId, + llmAnalysis: args.llmAnalysis, + skillSpectorAnalysis, + }, + ); + try { + await runMutationRef( + ctx, + internalRefs.securityScanDispatch.requestSecurityScanDispatchInternal, + {}, + ); + } catch { + console.warn("security scan dispatch request failed after catalog completion"); + } + return result; + } await runMutationRef(ctx, internalRefs.securityScan.recordSkillScanRequestSucceededInternal, { scanId: target.scanRequest._id, jobId: args.jobId, diff --git a/convex/skillsShCatalog.test.ts b/convex/skillsShCatalog.test.ts new file mode 100644 index 00000000..981ec00c --- /dev/null +++ b/convex/skillsShCatalog.test.ts @@ -0,0 +1,2566 @@ +/// +/* @vitest-environment edge-runtime */ +import { convexTest } from "convex-test"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { internal } from "./_generated/api"; +import type { Doc, Id } from "./_generated/dataModel"; +import frozenSnapshot from "./fixtures/skills-sh-500-2026-07-21.json"; +import schema from "./schema"; + +const modules = import.meta.glob("./**/*.ts"); + +const LOCAL_ENV = { + CONVEX_CLOUD_URL: "http://127.0.0.1:3210", +}; + +const TEST_ENV = { + CLAWHUB_DEPLOYMENT_NAME: "academic-chihuahua-392", + CLAWHUB_DISABLE_CRONS: "1", + CLAWHUB_ENV: "test", + CONVEX_CLOUD_URL: "https://academic-chihuahua-392.convex.cloud", +}; + +const BASE_CONTROL = { + actor: "codex-test", + reason: "exercise the dark skills.sh catalog gate", + confirm: "enable-skills-sh-fixture-control", + mode: "fixture" as const, + discoveryEnabled: true, + writesEnabled: true, + scanPlanningEnabled: true, + scanAdmissionEnabled: true, + maxEntriesPerRun: 500, + maxEntriesPerBatch: 125, + maxWritesPerBatch: 100, + maxPlannedScans: 500, + maxScanAdmissionsPerBatch: 50, + maxScanAdmissionsPerRun: 500, + maxScanAdmissionsPerDay: 500, + maxCatalogQueued: 50, + maxCatalogInFlight: 10, + maxNativeQueued: 0, + maxNativeInFlight: 0, + realScanAllowlist: [] as string[], +}; + +type CatalogTest = ReturnType; +type RunSummary = Pick< + Doc<"skillsShCatalogRuns">, + "status" | "cursor" | "fixtureLength" | "counts" | "snapshotCaptureFetches" +> & { + operationsAreEstimates: boolean; + budgetConsumed: { + batchesProcessed: number; + }; +}; + +function useEnvironment(env: Record) { + for (const [name, value] of Object.entries(env)) vi.stubEnv(name, value); +} + +async function sha256Hex(value: string | Blob) { + const bytes = + typeof value === "string" + ? new TextEncoder().encode(value) + : new Uint8Array(await value.arrayBuffer()); + const digest = await crypto.subtle.digest("SHA-256", bytes); + return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join(""); +} + +async function storeTestArtifact( + t: CatalogTest, + externalId: string, + content: string, + path = "SKILL.md", +) { + return await storeTestArtifactFiles(t, externalId, [{ path, content }]); +} + +async function storeTestArtifactFiles( + t: CatalogTest, + externalId: string, + inputs: Array<{ path: string; content: string }>, +) { + const files = []; + for (const input of inputs) { + const blob = new Blob([input.content], { type: "text/markdown" }); + const storageId = await t.run(async (ctx) => await ctx.storage.store(blob)); + files.push({ + path: input.path, + size: blob.size, + storageId, + sha256: await sha256Hex(blob), + contentType: "text/markdown", + }); + } + files.sort((left, right) => left.path.localeCompare(right.path)); + return { + externalId, + artifactContentHash: await sha256Hex( + files.map((file) => `${file.path}\0${file.sha256}\n`).join(""), + ), + files, + }; +} + +async function processToTerminal( + t: CatalogTest, + runId: Id<"skillsShCatalogRuns">, + maxBatches = 200, +) { + for (let batch = 1; batch <= maxBatches; batch += 1) { + const result = (await t.mutation(internal.skillsShCatalog.processFixtureBatchInternal, { + runId, + })) as RunSummary; + if (result.status !== "running") return result; + } + throw new Error(`skills.sh run ${runId} exceeded ${maxBatches} batches`); +} + +async function collectEntries(t: CatalogTest) { + const entries: Doc<"skillsShCatalogEntries">[] = []; + let cursor: string | null = null; + do { + const result = (await t.query(internal.skillsShCatalog.listEntriesPageInternal, { + paginationOpts: { cursor, numItems: 100 }, + })) as { + page: Doc<"skillsShCatalogEntries">[]; + isDone: boolean; + continueCursor: string; + }; + entries.push(...result.page); + cursor = result.isDone ? null : result.continueCursor; + } while (cursor); + return entries; +} + +async function insertCatalogEntry( + t: CatalogTest, + input: { + externalId: string; + owner: string; + githubOwnerId: number; + sourceKind?: "fixture" | "frozen-snapshot" | "staging-live"; + }, +) { + const [, repo, slug] = input.externalId.split("/"); + await t.run(async (ctx) => { + await ctx.db.insert("skillsShCatalogEntries", { + externalId: input.externalId, + sourceKind: input.sourceKind ?? "staging-live", + githubOwnerId: input.githubOwnerId, + owner: input.owner, + repo, + slug, + displayName: slug, + sourceUrl: `https://skills.sh/${input.externalId}`, + githubRepoUrl: `https://github.com/${input.owner}/${repo}`, + sourceContentHash: `hash-${input.externalId}`, + installs: 1, + sourceSnapshotId: "authenticated-live-snapshot", + publicVisible: false, + scanStatus: "planned", + firstObservedAt: 1, + lastObservedAt: 1, + createdAt: 1, + updatedAt: 1, + }); + }); +} + +async function collectAttempts(t: CatalogTest, runId: Id<"skillsShCatalogRuns">) { + const attempts: Doc<"skillsShCatalogScanAttempts">[] = []; + let cursor: string | null = null; + do { + const result = (await t.query(internal.skillsShCatalog.listRunScanAttemptsPageInternal, { + runId, + paginationOpts: { cursor, numItems: 100 }, + })) as { + page: Doc<"skillsShCatalogScanAttempts">[]; + isDone: boolean; + continueCursor: string; + }; + attempts.push(...result.page); + cursor = result.isDone ? null : result.continueCursor; + } while (cursor); + return attempts; +} + +async function collectNativeState(t: CatalogTest) { + const skills: Doc<"skills">[] = []; + const jobs: Doc<"securityScanJobs">[] = []; + let skillCursor: string | null = null; + let jobCursor: string | null = null; + do { + const result = (await t.query(internal.skillsShCatalog.listNativeSkillsIsolationPageInternal, { + paginationOpts: { cursor: skillCursor, numItems: 100 }, + })) as { + page: Doc<"skills">[]; + isDone: boolean; + continueCursor: string; + }; + skills.push(...result.page); + skillCursor = result.isDone ? null : result.continueCursor; + } while (skillCursor); + do { + const result = (await t.query( + internal.skillsShCatalog.listNativeScanJobsIsolationPageInternal, + { + paginationOpts: { cursor: jobCursor, numItems: 100 }, + }, + )) as { + page: Doc<"securityScanJobs">[]; + isDone: boolean; + continueCursor: string; + }; + jobs.push(...result.page); + jobCursor = result.isDone ? null : result.continueCursor; + } while (jobCursor); + return { skills, jobs }; +} + +describe("skills.sh catalog overload control plane", () => { + afterEach(() => { + vi.useRealTimers(); + vi.unstubAllEnvs(); + }); + + it("resolves only complete authenticated staging-live owner mappings", async () => { + useEnvironment(TEST_ENV); + const t = convexTest(schema, modules); + await insertCatalogEntry(t, { + externalId: "nvidia/skills/aiq-deploy", + owner: "nvidia", + githubOwnerId: 1_728_152, + }); + await insertCatalogEntry(t, { + externalId: "nvidia/skills/aiq-toolkit", + owner: "nvidia", + githubOwnerId: 1_728_152, + }); + await insertCatalogEntry(t, { + externalId: "anthropics/skills/frontend-design", + owner: "anthropics", + githubOwnerId: 76_263_028, + sourceKind: "fixture", + }); + + await expect( + t.query(internal.skillsShCatalog.resolveKnownGitHubOwnersInternal, { + owners: [" NVIDIA ", "anthropics", "missing-owner", "nvidia"], + }), + ).resolves.toEqual({ + provenance: "stored-authenticated-staging-live", + owners: [{ owner: "nvidia", login: "nvidia", id: 1_728_152 }], + missingOwners: ["anthropics", "missing-owner"], + }); + }); + + it("rejects conflicting authenticated staging-live owner ids", async () => { + useEnvironment(TEST_ENV); + const t = convexTest(schema, modules); + await insertCatalogEntry(t, { + externalId: "nvidia/skills/aiq-deploy", + owner: "nvidia", + githubOwnerId: 1_728_152, + }); + await insertCatalogEntry(t, { + externalId: "nvidia/other/aiq-deploy", + owner: "nvidia", + githubOwnerId: 9_999_999, + }); + + await expect( + t.query(internal.skillsShCatalog.resolveKnownGitHubOwnersInternal, { + owners: ["nvidia"], + }), + ).rejects.toThrow("Conflicting authenticated GitHub owner ids for nvidia"); + }); + + it("rejects a fresh owner assignment that reuses an established owner id", async () => { + useEnvironment(TEST_ENV); + const t = convexTest(schema, modules); + await insertCatalogEntry(t, { + externalId: "nvidia/skills/aiq-deploy", + owner: "nvidia", + githubOwnerId: 1_728_152, + }); + + await expect( + t.query(internal.skillsShCatalog.assertFreshGitHubOwnerAssignmentsInternal, { + owners: [{ owner: "renamed-nvidia", id: 1_728_152 }], + }), + ).rejects.toThrow("Authenticated GitHub owner id 1728152 is already assigned to another owner"); + await expect( + t.query(internal.skillsShCatalog.assertFreshGitHubOwnerAssignmentsInternal, { + owners: [{ owner: "new-owner", id: 9_999_999 }], + }), + ).resolves.toEqual({ + provenance: "stored-authenticated-staging-live-assignment-check", + checked: 1, + }); + }); + + it("fails closed without controls and rejects spoofed Preview/Test environments", async () => { + useEnvironment(LOCAL_ENV); + const t = convexTest(schema, modules); + const initial = await t.query(internal.skillsShCatalog.getStatusInternal, {}); + expect(initial.control).toMatchObject({ + mode: "off", + discoveryEnabled: false, + writesEnabled: false, + scanAdmissionEnabled: false, + publicVisibilityEnabled: false, + paused: true, + }); + await expect( + t.mutation(internal.skillsShCatalog.startFixtureRunInternal, { + fixtureId: "nvidia-small-v1", + actor: "codex-test", + reason: "must fail closed", + }), + ).rejects.toThrow("controls are disabled"); + + vi.stubEnv("CLAWHUB_PREVIEW", "1"); + vi.stubEnv("CLAWHUB_ENV", "test"); + vi.stubEnv("CLAWHUB_DEPLOYMENT_NAME", "academic-chihuahua-392"); + vi.stubEnv("CLAWHUB_DISABLE_CRONS", "1"); + vi.stubEnv("CONVEX_CLOUD_URL", "https://academic-chihuahua-392.convex.cloud"); + await expect( + t.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + ...BASE_CONTROL, + maxScanAdmissionsPerRun: 10, + maxScanAdmissionsPerDay: 10, + }), + ).rejects.toThrow("disabled in Preview"); + }); + + it("terminates explicitly when the discovery budget is exhausted", async () => { + useEnvironment(LOCAL_ENV); + const t = convexTest(schema, modules); + await t.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + ...BASE_CONTROL, + scanAdmissionEnabled: false, + maxEntriesPerRun: 1, + maxEntriesPerBatch: 1, + maxPlannedScans: 1, + maxScanAdmissionsPerBatch: 0, + maxScanAdmissionsPerRun: 0, + maxScanAdmissionsPerDay: 0, + maxCatalogQueued: 0, + maxCatalogInFlight: 0, + }); + const { runId } = await t.mutation(internal.skillsShCatalog.startFixtureRunInternal, { + fixtureId: "nvidia-small-v1", + actor: "codex-test", + reason: "prove budget exhaustion is terminal", + }); + const run = await t.mutation(internal.skillsShCatalog.processFixtureBatchInternal, { runId }); + expect(run).toMatchObject({ + status: "budget-exhausted", + cursor: 1, + fixtureLength: 3, + counts: { + observed: 1, + inserted: 1, + scansPlanned: 1, + }, + }); + const repeated = await t.mutation(internal.skillsShCatalog.processFixtureBatchInternal, { + runId, + }); + expect(repeated).toMatchObject({ + status: "budget-exhausted", + cursor: 1, + counts: { observed: 1 }, + }); + }); + + it("rejects a fixture observation that collides with a live-source entry", async () => { + useEnvironment(LOCAL_ENV); + const t = convexTest(schema, modules); + const row = frozenSnapshot.rows[0]!; + await t.run(async (ctx) => { + await ctx.db.insert("skillsShCatalogEntries", { + ...row, + sourceKind: "staging-live", + sourceSnapshotId: "skills-sh-test-live-500:existing", + publicVisible: false, + scanStatus: "not-planned", + firstObservedAt: 1, + lastObservedAt: 1, + createdAt: 1, + updatedAt: 1, + }); + }); + await t.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + ...BASE_CONTROL, + scanAdmissionEnabled: false, + maxEntriesPerRun: 1, + maxEntriesPerBatch: 1, + maxPlannedScans: 1, + maxScanAdmissionsPerBatch: 0, + maxScanAdmissionsPerRun: 0, + maxScanAdmissionsPerDay: 0, + maxCatalogQueued: 0, + maxCatalogInFlight: 0, + }); + const { runId } = await t.mutation(internal.skillsShCatalog.startFixtureRunInternal, { + fixtureId: "skills-sh-500-2026-07-21", + actor: "codex-test", + reason: "preserve live source ownership", + }); + + const result = await t.mutation(internal.skillsShCatalog.processFixtureBatchInternal, { + runId, + }); + + expect(result).toMatchObject({ + counts: { observed: 1, rejected: 1, inserted: 0, updated: 0, scansPlanned: 0 }, + }); + expect((await collectEntries(t))[0]).toMatchObject({ + sourceKind: "staging-live", + sourceSnapshotId: "skills-sh-test-live-500:existing", + updatedAt: 1, + }); + }); + + it("rejects a live observation that collides with a fixture-source entry", async () => { + useEnvironment(TEST_ENV); + const t = convexTest(schema, modules); + const row = frozenSnapshot.rows[0]!; + await t.run(async (ctx) => { + await ctx.db.insert("skillsShCatalogEntries", { + ...row, + sourceKind: "frozen-snapshot", + sourceSnapshotId: "skills-sh-500-2026-07-21", + publicVisible: false, + scanStatus: "not-planned", + firstObservedAt: 1, + lastObservedAt: 1, + createdAt: 1, + updatedAt: 1, + }); + }); + await t.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + ...BASE_CONTROL, + mode: "staging-live", + maxEntriesPerBatch: 1, + maxScanAdmissionsPerBatch: 1, + maxScanAdmissionsPerRun: 1, + maxScanAdmissionsPerDay: 1, + maxCatalogQueued: 1, + maxCatalogInFlight: 1, + }); + const { runId } = await t.mutation(internal.skillsShCatalog.startStagingLiveRunInternal, { + actor: "codex-test", + reason: "preserve fixture source ownership", + snapshotId: "skills-sh-test-live-500:new", + sourceCapturedAt: "2026-07-21T00:00:00.000Z", + snapshotCaptureFetches: 1, + fixtureLength: 500, + }); + + const result = await t.mutation(internal.skillsShCatalog.processStagingLiveBatchInternal, { + runId, + cursor: 0, + rows: [row], + }); + + expect(result).toMatchObject({ + counts: { observed: 1, rejected: 1, inserted: 0, updated: 0, scansPlanned: 0 }, + }); + expect((await collectEntries(t))[0]).toMatchObject({ + sourceKind: "frozen-snapshot", + sourceSnapshotId: "skills-sh-500-2026-07-21", + updatedAt: 1, + }); + }); + + it("plans a changed hash once when the previous hash is still unadmitted", async () => { + useEnvironment(LOCAL_ENV); + const t = convexTest(schema, modules); + await t.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + ...BASE_CONTROL, + scanAdmissionEnabled: false, + maxScanAdmissionsPerBatch: 0, + maxScanAdmissionsPerRun: 0, + maxScanAdmissionsPerDay: 0, + maxCatalogQueued: 0, + maxCatalogInFlight: 0, + }); + const first = await t.mutation(internal.skillsShCatalog.startFixtureRunInternal, { + fixtureId: "nvidia-small-v1", + actor: "codex-test", + reason: "leave the first hash planned and unadmitted", + }); + await processToTerminal(t, first.runId); + + const changed = await t.mutation(internal.skillsShCatalog.startFixtureRunInternal, { + fixtureId: "nvidia-small-v2", + actor: "codex-test", + reason: "plan the changed hash exactly once", + }); + const changedRun = await processToTerminal(t, changed.runId); + expect(changedRun.counts).toMatchObject({ + observed: 1, + wouldUpdate: 1, + updated: 1, + scansPlanned: 1, + scansAdmitted: 0, + }); + + const repeated = await t.mutation(internal.skillsShCatalog.startFixtureRunInternal, { + fixtureId: "nvidia-small-v2", + actor: "codex-test", + reason: "do not replan the unchanged hash", + }); + const repeatedRun = await processToTerminal(t, repeated.runId); + expect(repeatedRun.counts).toMatchObject({ + observed: 1, + unchanged: 1, + scansPlanned: 0, + scansAdmitted: 0, + }); + expect(await collectAttempts(t, first.runId)).toHaveLength(0); + expect(await collectAttempts(t, changed.runId)).toHaveLength(0); + expect(await collectAttempts(t, repeated.runId)).toHaveLength(0); + }); + + it("replans unchanged fixture content after its prior attempt was canceled", async () => { + useEnvironment(LOCAL_ENV); + const t = convexTest(schema, modules); + await t.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + ...BASE_CONTROL, + maxEntriesPerRun: 2, + maxEntriesPerBatch: 2, + maxPlannedScans: 2, + maxScanAdmissionsPerBatch: 1, + maxScanAdmissionsPerRun: 1, + maxScanAdmissionsPerDay: 2, + maxCatalogQueued: 1, + maxCatalogInFlight: 1, + }); + const first = await t.mutation(internal.skillsShCatalog.startFixtureRunInternal, { + fixtureId: "nvidia-small-v1", + actor: "codex-test", + reason: "cancel one fixture attempt", + }); + await processToTerminal(t, first.runId); + const [entry] = await collectEntries(t); + await t.mutation(internal.skillsShCatalog.admitFixtureScansInternal, { + runId: first.runId, + externalIds: [entry!.externalId], + dispatchKind: "deterministic", + }); + await t.mutation(internal.skillsShCatalog.cancelCatalogRunInternal, { + runId: first.runId, + limit: 10, + }); + + const repeated = await t.mutation(internal.skillsShCatalog.startFixtureRunInternal, { + fixtureId: "nvidia-small-v1", + actor: "codex-test", + reason: "replan canceled fixture content", + }); + const repeatedRun = await processToTerminal(t, repeated.runId); + + expect(repeatedRun.counts).toMatchObject({ + observed: 2, + unchanged: 2, + scansPlanned: 1, + }); + expect( + (await collectEntries(t)).find((row) => row.externalId === entry!.externalId), + ).toMatchObject({ + scanStatus: "planned", + }); + const readmitted = await t.mutation(internal.skillsShCatalog.admitFixtureScansInternal, { + runId: repeated.runId, + externalIds: [entry!.externalId], + dispatchKind: "deterministic", + }); + expect(readmitted).toMatchObject({ admitted: 1, skipped: 0 }); + expect(await collectAttempts(t, first.runId)).toHaveLength(1); + expect(await collectAttempts(t, repeated.runId)).toMatchObject([ + { status: "queued", sourceContentHash: entry!.sourceContentHash }, + ]); + }); + + it("replans unchanged staging content after its prior attempt was canceled", async () => { + useEnvironment(TEST_ENV); + const t = convexTest(schema, modules); + const row = frozenSnapshot.rows.find( + (candidate) => candidate.externalId === "nvidia/skills/aiq-deploy", + )!; + const actorUserId = await t.run(async (ctx) => { + return await ctx.db.insert("users", { + handle: "catalog-retry-operator", + displayName: "Catalog Retry Operator", + role: "admin", + }); + }); + const artifact = await storeTestArtifact(t, row.externalId, "staging retry artifact"); + await t.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + ...BASE_CONTROL, + mode: "staging-live", + maxEntriesPerRun: 500, + maxEntriesPerBatch: 1, + maxPlannedScans: 500, + maxScanAdmissionsPerBatch: 1, + maxScanAdmissionsPerRun: 1, + maxScanAdmissionsPerDay: 2, + maxCatalogQueued: 1, + maxCatalogInFlight: 1, + realScanAllowlist: [row.externalId], + }); + const first = await t.mutation(internal.skillsShCatalog.startStagingLiveRunInternal, { + actor: "codex-test", + reason: "cancel one staging attempt", + snapshotId: "skills-sh-test-live-500:canceled-first", + sourceCapturedAt: "2026-07-21T00:00:00.000Z", + snapshotCaptureFetches: 1, + fixtureLength: 500, + }); + await t.mutation(internal.skillsShCatalog.processStagingLiveBatchInternal, { + runId: first.runId, + cursor: 0, + rows: [row], + }); + await t.action(internal.skillsShCatalog.admitRealScansInternal, { + runId: first.runId, + externalIds: [row.externalId], + actorUserId, + artifacts: [artifact], + }); + await t.mutation(internal.skillsShCatalog.cancelCatalogRunInternal, { + runId: first.runId, + limit: 10, + }); + + const repeated = await t.mutation(internal.skillsShCatalog.startStagingLiveRunInternal, { + actor: "codex-test", + reason: "replan canceled staging content", + snapshotId: "skills-sh-test-live-500:canceled-repeat", + sourceCapturedAt: "2026-07-21T00:05:00.000Z", + snapshotCaptureFetches: 1, + fixtureLength: 500, + }); + const repeatedRun = await t.mutation(internal.skillsShCatalog.processStagingLiveBatchInternal, { + runId: repeated.runId, + cursor: 0, + rows: [row], + }); + + expect(repeatedRun.counts).toMatchObject({ + observed: 1, + unchanged: 1, + scansPlanned: 1, + }); + expect( + (await collectEntries(t)).find((entry) => entry.externalId === row.externalId), + ).toMatchObject({ + scanStatus: "planned", + sourceSnapshotId: "skills-sh-test-live-500:canceled-repeat", + }); + const readmitted = await t.action(internal.skillsShCatalog.admitRealScansInternal, { + runId: repeated.runId, + externalIds: [row.externalId], + actorUserId, + artifacts: [artifact], + }); + expect(readmitted).toMatchObject({ admitted: 1, skipped: 0 }); + expect(await collectAttempts(t, first.runId)).toHaveLength(1); + expect(await collectAttempts(t, repeated.runId)).toMatchObject([ + { status: "queued", sourceContentHash: row.sourceContentHash }, + ]); + }); + + it("plans a bounded 20,000-row discovery without writing entries or enqueueing scans", async () => { + useEnvironment(LOCAL_ENV); + const t = convexTest(schema, modules); + await t.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + ...BASE_CONTROL, + writesEnabled: false, + scanAdmissionEnabled: false, + maxEntriesPerRun: 20_000, + maxEntriesPerBatch: 250, + maxPlannedScans: 20_000, + maxScanAdmissionsPerBatch: 0, + maxScanAdmissionsPerRun: 0, + maxScanAdmissionsPerDay: 0, + maxCatalogQueued: 0, + maxCatalogInFlight: 0, + }); + const { runId } = await t.mutation(internal.skillsShCatalog.startFixtureRunInternal, { + fixtureId: "synthetic-20000-v1", + actor: "codex-test", + reason: "prove discovery cannot enqueue scans", + dryRun: true, + }); + + const run = await processToTerminal(t, runId); + expect(run).toMatchObject({ + status: "completed", + cursor: 20_000, + fixtureLength: 20_000, + counts: { + observed: 20_000, + wouldInsert: 20_000, + inserted: 0, + scansPlanned: 20_000, + scansAdmitted: 0, + scansCompleted: 0, + }, + budgetConsumed: { + batchesProcessed: 80, + }, + operationsAreEstimates: true, + }); + expect(await collectEntries(t)).toHaveLength(0); + expect(await collectAttempts(t, runId)).toHaveLength(0); + expect( + await t.run(async (ctx) => await ctx.db.query("securityScanJobs").collect()), + ).toHaveLength(0); + }, 60_000); + + it("persists and completes 500 rows, then reruns idempotently and rescans only a changed source hash", async () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date("2026-07-21T03:00:00.000Z")); + useEnvironment(LOCAL_ENV); + const t = convexTest(schema, modules); + await t.mutation(internal.skillsShCatalog.configureFixtureControlInternal, BASE_CONTROL); + const nativeBefore = await collectNativeState(t); + const { runId } = await t.mutation(internal.skillsShCatalog.startFixtureRunInternal, { + fixtureId: "skills-sh-500-2026-07-21", + actor: "codex-test", + reason: "first frozen 500 run", + }); + + const firstBatch = await t.mutation(internal.skillsShCatalog.processFixtureBatchInternal, { + runId, + }); + expect(firstBatch.status).toBe("running"); + const cursorBeforePause = firstBatch.cursor; + const countsBeforePause = firstBatch.counts; + await t.mutation(internal.skillsShCatalog.setFixtureRunPausedInternal, { runId, paused: true }); + await expect( + t.mutation(internal.skillsShCatalog.processFixtureBatchInternal, { + runId, + }), + ).rejects.toThrow("run is paused"); + const paused = await t.query(internal.skillsShCatalog.getRunInternal, { + runId, + }); + expect(paused).toMatchObject({ + cursor: cursorBeforePause, + counts: countsBeforePause, + }); + await t.mutation(internal.skillsShCatalog.setFixtureRunPausedInternal, { + runId, + paused: false, + }); + const firstRun = await processToTerminal(t, runId); + expect(firstRun).toMatchObject({ + status: "completed", + cursor: 500, + counts: { + observed: 500, + wouldInsert: 500, + wouldUpdate: 0, + inserted: 500, + updated: 0, + unchanged: 0, + rejected: 0, + scansPlanned: 500, + scansAdmitted: 0, + }, + snapshotCaptureFetches: 528, + }); + + const entries = await collectEntries(t); + expect(entries).toHaveLength(500); + expect(entries.filter((entry) => entry.owner === "nvidia")).toHaveLength(10); + expect(entries.map((entry) => entry.externalId)).toEqual( + expect.arrayContaining([ + "anthropics/skills/frontend-design", + "anthropics/claude-code/frontend-design", + ]), + ); + expect(entries.every((entry) => !entry.publicVisible)).toBe(true); + + for (let offset = 0; offset < entries.length; offset += 49) { + const externalIds = entries.slice(offset, offset + 49).map((entry) => entry.externalId); + const admission = await t.mutation(internal.skillsShCatalog.admitFixtureScansInternal, { + runId, + externalIds, + dispatchKind: "deterministic", + }); + expect(admission).toMatchObject({ + admitted: externalIds.length, + skipped: 0, + }); + const completion = await t.mutation( + internal.skillsShCatalog.completeDeterministicScansInternal, + { runId, limit: externalIds.length }, + ); + expect(completion).toMatchObject({ + matched: externalIds.length, + completed: externalIds.length, + canceled: 0, + }); + } + const completedFirstRun = await t.query(internal.skillsShCatalog.getRunInternal, { runId }); + expect(completedFirstRun?.counts).toMatchObject({ + scansAdmitted: 500, + scansCompleted: 500, + }); + expect(await collectAttempts(t, runId)).toHaveLength(500); + + const repeatedAt = new Date("2026-07-21T04:00:00.000Z"); + vi.setSystemTime(repeatedAt); + const repeated = await t.mutation(internal.skillsShCatalog.startFixtureRunInternal, { + fixtureId: "skills-sh-500-2026-07-21", + actor: "codex-test", + reason: "identical frozen rerun", + }); + const repeatedRun = await processToTerminal(t, repeated.runId); + expect(repeatedRun.counts).toEqual({ + observed: 500, + wouldInsert: 0, + wouldUpdate: 0, + inserted: 0, + updated: 0, + unchanged: 500, + rejected: 0, + scansPlanned: 0, + scansAdmitted: 0, + scansCompleted: 0, + scansCanceled: 0, + }); + expect(await collectAttempts(t, repeated.runId)).toHaveLength(0); + expect( + (await collectEntries(t)).every((entry) => entry.lastObservedAt === repeatedAt.getTime()), + ).toBe(true); + + vi.setSystemTime(new Date("2026-07-22T03:00:00.000Z")); + const changed = await t.mutation(internal.skillsShCatalog.startFixtureRunInternal, { + fixtureId: "skills-sh-500-2026-07-21-v2", + actor: "codex-test", + reason: "changed frozen rerun", + }); + const changedRun = await processToTerminal(t, changed.runId); + expect(changedRun.counts).toEqual({ + observed: 500, + wouldInsert: 0, + wouldUpdate: 2, + inserted: 0, + updated: 2, + unchanged: 498, + rejected: 0, + scansPlanned: 1, + scansAdmitted: 0, + scansCompleted: 0, + scansCanceled: 0, + }); + const changedEntries = await collectEntries(t); + const planned = changedEntries.filter((entry) => entry.scanStatus === "planned"); + expect(planned).toHaveLength(1); + await t.mutation(internal.skillsShCatalog.admitFixtureScansInternal, { + runId: changed.runId, + externalIds: [planned[0]!.externalId], + dispatchKind: "deterministic", + }); + await t.mutation(internal.skillsShCatalog.completeDeterministicScansInternal, { + runId: changed.runId, + limit: 1, + }); + const changedAttempts = await collectAttempts(t, changed.runId); + expect(changedAttempts).toHaveLength(1); + expect(changedAttempts[0]?.sourceContentHash).toBe(planned[0]?.sourceContentHash); + expect(changedAttempts[0]?.artifactContentHash).toBeUndefined(); + + vi.setSystemTime(new Date("2026-07-23T03:00:00.000Z")); + const reverted = await t.mutation(internal.skillsShCatalog.startFixtureRunInternal, { + fixtureId: "skills-sh-500-2026-07-21", + actor: "codex-test", + reason: "reuse the original exact-hash verdict", + }); + const revertedRun = await processToTerminal(t, reverted.runId); + expect(revertedRun.counts).toMatchObject({ + observed: 500, + wouldUpdate: 2, + updated: 2, + unchanged: 498, + scansPlanned: 0, + scansAdmitted: 0, + }); + expect(await collectAttempts(t, reverted.runId)).toHaveLength(0); + const allEntries = await collectEntries(t); + const revertedEntry = allEntries.find((entry) => entry.externalId === planned[0]!.externalId); + const originalEntry = entries.find((entry) => entry.externalId === planned[0]!.externalId); + expect(revertedEntry).toMatchObject({ + sourceContentHash: originalEntry?.sourceContentHash, + scanStatus: "clean", + publicVisible: false, + }); + expect(allEntries).toHaveLength(500); + expect(allEntries.every((entry) => !entry.publicVisible)).toBe(true); + expect(await collectNativeState(t)).toEqual(nativeBefore); + }, 60_000); + + it("rejects real Test admission from a fixture run after controls switch to staging-live", async () => { + useEnvironment(TEST_ENV); + const t = convexTest(schema, modules); + const allowlist = frozenSnapshot.rows.slice(0, 10).map((row) => row.externalId); + const actorUserId = await t.run(async (ctx) => { + return await ctx.db.insert("users", { + handle: "catalog-test-operator", + displayName: "Catalog Test Operator", + role: "admin", + }); + }); + const storedArtifact = await storeTestArtifact(t, allowlist[0]!, "catalog test artifact"); + const artifacts = allowlist.map((externalId) => ({ + ...storedArtifact, + externalId, + })); + await t.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + ...BASE_CONTROL, + maxScanAdmissionsPerBatch: 10, + maxScanAdmissionsPerRun: 10, + maxScanAdmissionsPerDay: 10, + maxCatalogQueued: 10, + maxCatalogInFlight: 1, + realScanAllowlist: allowlist, + }); + const { runId } = await t.mutation(internal.skillsShCatalog.startFixtureRunInternal, { + fixtureId: "skills-sh-500-2026-07-21", + actor: "codex-test", + reason: "production-shaped Test seam", + }); + const run = await processToTerminal(t, runId); + expect(run.counts).toMatchObject({ + observed: 500, + scansPlanned: 500, + scansAdmitted: 0, + }); + await expect( + t.action(internal.skillsShCatalog.admitRealScansInternal, { + runId, + externalIds: [allowlist[0]!], + actorUserId, + artifacts: [artifacts[0]!], + }), + ).rejects.toThrow("requires staging-live controls"); + await t.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + ...BASE_CONTROL, + mode: "staging-live", + maxScanAdmissionsPerBatch: 10, + maxScanAdmissionsPerRun: 10, + maxScanAdmissionsPerDay: 10, + maxCatalogQueued: 10, + maxCatalogInFlight: 1, + realScanAllowlist: allowlist, + }); + await expect( + t.action(internal.skillsShCatalog.admitRealScansInternal, { + runId, + externalIds: allowlist, + actorUserId, + artifacts, + }), + ).rejects.toThrow("requires a staging-live run"); + expect( + await t.query(internal.skillsShCatalog.listRealScanQueueInternal, { + limit: 10, + }), + ).toEqual([]); + expect(await t.run(async (ctx) => await ctx.db.query("securityScanJobs").collect())).toEqual( + [], + ); + }, 30_000); + + it("rejects fixture discovery after controls switch to staging-live", async () => { + useEnvironment(TEST_ENV); + const t = convexTest(schema, modules); + await t.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + ...BASE_CONTROL, + maxScanAdmissionsPerRun: 10, + maxScanAdmissionsPerDay: 10, + }); + const { runId } = await t.mutation(internal.skillsShCatalog.startFixtureRunInternal, { + fixtureId: "nvidia-small-v1", + actor: "codex-test", + reason: "fixture run before staging-live switch", + }); + await t.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + ...BASE_CONTROL, + mode: "staging-live", + maxScanAdmissionsPerRun: 10, + maxScanAdmissionsPerDay: 10, + }); + + await expect( + t.mutation(internal.skillsShCatalog.startFixtureRunInternal, { + fixtureId: "nvidia-small-v1", + actor: "codex-test", + reason: "fixture start under staging-live controls", + }), + ).rejects.toThrow("fixture work requires fixture controls"); + await expect( + t.mutation(internal.skillsShCatalog.processFixtureBatchInternal, { runId }), + ).rejects.toThrow("fixture work requires fixture controls"); + expect(await collectEntries(t)).toEqual([]); + }); + + it("rejects deterministic fixture scan lifecycle after a staging-live mode switch", async () => { + useEnvironment(TEST_ENV); + const t = convexTest(schema, modules); + await t.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + ...BASE_CONTROL, + maxEntriesPerRun: 2, + maxEntriesPerBatch: 2, + maxPlannedScans: 2, + maxScanAdmissionsPerBatch: 2, + maxScanAdmissionsPerRun: 2, + maxScanAdmissionsPerDay: 2, + maxCatalogQueued: 2, + maxCatalogInFlight: 1, + }); + const { runId } = await t.mutation(internal.skillsShCatalog.startFixtureRunInternal, { + fixtureId: "synthetic-20000-v1", + actor: "codex-test", + reason: "fixture attempts before staging-live switch", + }); + await processToTerminal(t, runId); + const entries = await collectEntries(t); + await t.mutation(internal.skillsShCatalog.admitFixtureScansInternal, { + runId, + externalIds: [entries[0]!.externalId], + dispatchKind: "deterministic", + }); + const [attempt] = await collectAttempts(t, runId); + await t.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + ...BASE_CONTROL, + mode: "staging-live", + maxEntriesPerRun: 2, + maxEntriesPerBatch: 2, + maxPlannedScans: 2, + maxScanAdmissionsPerBatch: 2, + maxScanAdmissionsPerRun: 2, + maxScanAdmissionsPerDay: 2, + maxCatalogQueued: 2, + maxCatalogInFlight: 1, + }); + + await expect( + t.mutation(internal.skillsShCatalog.admitFixtureScansInternal, { + runId, + externalIds: [entries[1]!.externalId], + dispatchKind: "deterministic", + }), + ).rejects.toThrow("fixture work requires fixture controls"); + await expect( + t.mutation(internal.skillsShCatalog.markScanAttemptRunningInternal, { + attemptId: attempt!._id, + }), + ).rejects.toThrow("fixture work requires fixture controls"); + await expect( + t.mutation(internal.skillsShCatalog.completeDeterministicScansInternal, { + runId, + limit: 1, + }), + ).rejects.toThrow("fixture work requires fixture controls"); + await expect( + t.mutation(internal.skillsShCatalog.recordFixtureScanResultInternal, { + attemptId: attempt!._id, + sourceContentHash: attempt!.sourceContentHash, + verdict: "clean", + }), + ).rejects.toThrow("fixture work requires fixture controls"); + expect((await collectAttempts(t, runId))[0]).toMatchObject({ status: "queued" }); + expect((await collectEntries(t)).every((entry) => !entry.publicVisible)).toBe(true); + }); + + it("rejects non-admin real scan admission", async () => { + useEnvironment(TEST_ENV); + const t = convexTest(schema, modules); + const actorUserId = await t.run( + async (ctx) => + await ctx.db.insert("users", { + handle: "catalog-test-user", + displayName: "Catalog Test User", + role: "user", + }), + ); + await t.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + ...BASE_CONTROL, + mode: "staging-live", + maxEntriesPerBatch: 1, + maxScanAdmissionsPerBatch: 1, + maxScanAdmissionsPerRun: 1, + maxScanAdmissionsPerDay: 1, + maxCatalogQueued: 1, + maxCatalogInFlight: 1, + realScanAllowlist: ["nvidia/skills/aiq-deploy"], + }); + const { runId } = await t.mutation(internal.skillsShCatalog.startStagingLiveRunInternal, { + actor: "catalog-test-user", + reason: "reject non-admin admission", + snapshotId: "skills-sh-test-live-500:non-admin", + sourceCapturedAt: "2026-07-21T00:00:00.000Z", + snapshotCaptureFetches: 1, + fixtureLength: 500, + }); + await t.mutation(internal.skillsShCatalog.processStagingLiveBatchInternal, { + runId, + cursor: 0, + rows: [frozenSnapshot.rows.find((row) => row.externalId === "nvidia/skills/aiq-deploy")!], + }); + const artifact = await storeTestArtifact(t, "nvidia/skills/aiq-deploy", "non-admin artifact"); + + await expect( + t.action(internal.skillsShCatalog.admitRealScansInternal, { + runId, + externalIds: ["nvidia/skills/aiq-deploy"], + actorUserId, + artifacts: [artifact], + }), + ).rejects.toThrow("requires an admin operator"); + expect(await collectAttempts(t, runId)).toEqual([]); + expect(await t.run(async (ctx) => await ctx.db.query("securityScanJobs").collect())).toEqual( + [], + ); + }); + + it("validates every stored real artifact before writing scan state", async () => { + useEnvironment(TEST_ENV); + const t = convexTest(schema, modules); + const actorUserId = await t.run( + async (ctx) => + await ctx.db.insert("users", { + handle: "catalog-artifact-admin", + displayName: "Catalog Artifact Admin", + role: "admin", + }), + ); + await t.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + ...BASE_CONTROL, + mode: "staging-live", + maxEntriesPerBatch: 1, + maxScanAdmissionsPerBatch: 1, + maxScanAdmissionsPerRun: 1, + maxScanAdmissionsPerDay: 1, + maxCatalogQueued: 1, + maxCatalogInFlight: 1, + realScanAllowlist: ["nvidia/skills/aiq-deploy"], + }); + const { runId } = await t.mutation(internal.skillsShCatalog.startStagingLiveRunInternal, { + actor: "catalog-artifact-admin", + reason: "validate stored artifact bytes", + snapshotId: "skills-sh-test-live-500:artifact-validation", + sourceCapturedAt: "2026-07-21T00:00:00.000Z", + snapshotCaptureFetches: 1, + fixtureLength: 500, + }); + await t.mutation(internal.skillsShCatalog.processStagingLiveBatchInternal, { + runId, + cursor: 0, + rows: [frozenSnapshot.rows.find((row) => row.externalId === "nvidia/skills/aiq-deploy")!], + }); + const valid = await storeTestArtifact(t, "nvidia/skills/aiq-deploy", "verified artifact bytes"); + const invalidArtifacts = [ + { + ...valid, + files: [{ ...valid.files[0]!, path: "../SKILL.md" }], + }, + { + ...valid, + files: [valid.files[0]!, { ...valid.files[0]! }], + }, + { + ...valid, + files: [{ ...valid.files[0]!, size: valid.files[0]!.size + 1 }], + }, + { + ...valid, + files: [{ ...valid.files[0]!, sha256: "0".repeat(64) }], + }, + { + ...valid, + artifactContentHash: "0".repeat(64), + }, + ]; + + for (const artifact of invalidArtifacts) { + await expect( + t.action(internal.skillsShCatalog.admitRealScansInternal, { + runId, + externalIds: ["nvidia/skills/aiq-deploy"], + actorUserId, + artifacts: [artifact], + }), + ).rejects.toThrow(); + expect(await collectAttempts(t, runId)).toEqual([]); + expect(await t.run(async (ctx) => await ctx.db.query("securityScanJobs").collect())).toEqual( + [], + ); + } + }); + + it("charges retry budgets only for newly admitted scan attempts", async () => { + useEnvironment(LOCAL_ENV); + const t = convexTest(schema, modules); + await t.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + ...BASE_CONTROL, + maxEntriesPerRun: 2, + maxEntriesPerBatch: 2, + maxPlannedScans: 2, + maxScanAdmissionsPerBatch: 2, + maxScanAdmissionsPerRun: 2, + maxScanAdmissionsPerDay: 2, + maxCatalogQueued: 2, + maxCatalogInFlight: 1, + }); + const { runId } = await t.mutation(internal.skillsShCatalog.startFixtureRunInternal, { + fixtureId: "synthetic-20000-v1", + actor: "codex-test", + reason: "idempotent admission retry budget", + }); + await processToTerminal(t, runId); + const entries = await collectEntries(t); + expect(entries).toHaveLength(2); + + await t.mutation(internal.skillsShCatalog.admitFixtureScansInternal, { + runId, + externalIds: [entries[0]!.externalId], + dispatchKind: "deterministic", + }); + const retried = await t.mutation(internal.skillsShCatalog.admitFixtureScansInternal, { + runId, + externalIds: entries.map((entry) => entry.externalId), + dispatchKind: "deterministic", + }); + expect(retried).toMatchObject({ requested: 2, admitted: 1, skipped: 1 }); + expect(await collectAttempts(t, runId)).toHaveLength(2); + }); + + it("applies a lowered current daily admission cap to an existing run", async () => { + useEnvironment(LOCAL_ENV); + const t = convexTest(schema, modules); + await t.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + ...BASE_CONTROL, + maxEntriesPerRun: 2, + maxEntriesPerBatch: 2, + maxPlannedScans: 2, + maxScanAdmissionsPerBatch: 1, + maxScanAdmissionsPerRun: 2, + maxScanAdmissionsPerDay: 2, + maxCatalogQueued: 2, + maxCatalogInFlight: 1, + }); + const { runId } = await t.mutation(internal.skillsShCatalog.startFixtureRunInternal, { + fixtureId: "synthetic-20000-v1", + actor: "codex-test", + reason: "current daily cap overrides stale run budget", + }); + await processToTerminal(t, runId); + const entries = await collectEntries(t); + await t.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + ...BASE_CONTROL, + maxEntriesPerRun: 2, + maxEntriesPerBatch: 2, + maxPlannedScans: 2, + maxScanAdmissionsPerBatch: 1, + maxScanAdmissionsPerRun: 2, + maxScanAdmissionsPerDay: 1, + maxCatalogQueued: 2, + maxCatalogInFlight: 1, + }); + await t.mutation(internal.skillsShCatalog.admitFixtureScansInternal, { + runId, + externalIds: [entries[0]!.externalId], + dispatchKind: "deterministic", + }); + + await expect( + t.mutation(internal.skillsShCatalog.admitFixtureScansInternal, { + runId, + externalIds: [entries[1]!.externalId], + dispatchKind: "deterministic", + }), + ).rejects.toThrow("daily scan-admission budget exceeded"); + expect(await collectAttempts(t, runId)).toHaveLength(1); + }); + + it("applies lowered current batch and run admission caps to an existing run", async () => { + useEnvironment(LOCAL_ENV); + const t = convexTest(schema, modules); + await t.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + ...BASE_CONTROL, + maxEntriesPerRun: 2, + maxEntriesPerBatch: 2, + maxPlannedScans: 2, + maxScanAdmissionsPerBatch: 2, + maxScanAdmissionsPerRun: 2, + maxScanAdmissionsPerDay: 2, + maxCatalogQueued: 2, + maxCatalogInFlight: 1, + }); + const { runId } = await t.mutation(internal.skillsShCatalog.startFixtureRunInternal, { + fixtureId: "synthetic-20000-v1", + actor: "codex-test", + reason: "current batch and run caps override stale run budgets", + }); + await processToTerminal(t, runId); + const entries = await collectEntries(t); + await t.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + ...BASE_CONTROL, + maxEntriesPerRun: 2, + maxEntriesPerBatch: 2, + maxPlannedScans: 2, + maxScanAdmissionsPerBatch: 1, + maxScanAdmissionsPerRun: 1, + maxScanAdmissionsPerDay: 2, + maxCatalogQueued: 2, + maxCatalogInFlight: 1, + }); + + await expect( + t.mutation(internal.skillsShCatalog.admitFixtureScansInternal, { + runId, + externalIds: entries.map((entry) => entry.externalId), + dispatchKind: "deterministic", + }), + ).rejects.toThrow("externalIds.length"); + await t.mutation(internal.skillsShCatalog.admitFixtureScansInternal, { + runId, + externalIds: [entries[0]!.externalId], + dispatchKind: "deterministic", + }); + await expect( + t.mutation(internal.skillsShCatalog.admitFixtureScansInternal, { + runId, + externalIds: [entries[1]!.externalId], + dispatchKind: "deterministic", + }), + ).rejects.toThrow("run scan-admission budget exceeded"); + expect(await collectAttempts(t, runId)).toHaveLength(1); + }); + + it("completes deterministic work when real in-flight capacity is zero", async () => { + useEnvironment(LOCAL_ENV); + const t = convexTest(schema, modules); + await t.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + ...BASE_CONTROL, + maxEntriesPerRun: 1, + maxEntriesPerBatch: 1, + maxPlannedScans: 1, + maxScanAdmissionsPerBatch: 1, + maxScanAdmissionsPerRun: 1, + maxScanAdmissionsPerDay: 1, + maxCatalogQueued: 1, + maxCatalogInFlight: 0, + }); + const { runId } = await t.mutation(internal.skillsShCatalog.startFixtureRunInternal, { + fixtureId: "synthetic-20000-v1", + actor: "codex-test", + reason: "deterministic completion does not consume real in-flight capacity", + }); + await processToTerminal(t, runId); + const [entry] = await collectEntries(t); + await t.mutation(internal.skillsShCatalog.admitFixtureScansInternal, { + runId, + externalIds: [entry!.externalId], + dispatchKind: "deterministic", + }); + + const completed = await t.mutation( + internal.skillsShCatalog.completeDeterministicScansInternal, + { + runId, + limit: 1, + }, + ); + expect(completed).toMatchObject({ matched: 1, completed: 1, canceled: 0 }); + }); + + it("does not complete queued deterministic work while the run is paused", async () => { + useEnvironment(LOCAL_ENV); + const t = convexTest(schema, modules); + await t.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + ...BASE_CONTROL, + maxEntriesPerRun: 3, + maxEntriesPerBatch: 1, + maxPlannedScans: 3, + maxScanAdmissionsPerBatch: 1, + maxScanAdmissionsPerRun: 1, + maxScanAdmissionsPerDay: 1, + maxCatalogQueued: 1, + maxCatalogInFlight: 1, + }); + const { runId } = await t.mutation(internal.skillsShCatalog.startFixtureRunInternal, { + fixtureId: "synthetic-20000-v1", + actor: "codex-test", + reason: "pause queued deterministic completion", + }); + await t.mutation(internal.skillsShCatalog.processFixtureBatchInternal, { runId }); + const [entry] = await collectEntries(t); + await t.mutation(internal.skillsShCatalog.admitFixtureScansInternal, { + runId, + externalIds: [entry!.externalId], + dispatchKind: "deterministic", + }); + await t.mutation(internal.skillsShCatalog.setFixtureRunPausedInternal, { + runId, + paused: true, + }); + + await expect( + t.mutation(internal.skillsShCatalog.completeDeterministicScansInternal, { + runId, + limit: 1, + }), + ).rejects.toThrow("Cannot complete scans for paused run"); + expect((await collectAttempts(t, runId))[0]).toMatchObject({ status: "queued" }); + expect((await collectEntries(t))[0]).toMatchObject({ scanStatus: "queued" }); + }); + + it("blocks queued starts and late results while a catalog run is canceling", async () => { + useEnvironment(LOCAL_ENV); + const t = convexTest(schema, modules); + await t.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + ...BASE_CONTROL, + maxEntriesPerRun: 3, + maxEntriesPerBatch: 3, + maxPlannedScans: 3, + maxScanAdmissionsPerBatch: 3, + maxScanAdmissionsPerRun: 3, + maxScanAdmissionsPerDay: 3, + maxCatalogQueued: 3, + maxCatalogInFlight: 3, + }); + const { runId } = await t.mutation(internal.skillsShCatalog.startFixtureRunInternal, { + fixtureId: "synthetic-20000-v1", + actor: "codex-test", + reason: "partial cancellation lifecycle", + }); + await processToTerminal(t, runId); + const entries = await collectEntries(t); + await t.mutation(internal.skillsShCatalog.admitFixtureScansInternal, { + runId, + externalIds: entries.map((entry) => entry.externalId), + dispatchKind: "deterministic", + }); + const attempts = await collectAttempts(t, runId); + expect(attempts).toHaveLength(3); + await t.mutation(internal.skillsShCatalog.markScanAttemptRunningInternal, { + attemptId: attempts[2]!._id, + }); + + const partial = await t.mutation(internal.skillsShCatalog.cancelCatalogRunInternal, { + runId, + limit: 1, + }); + expect(partial).toMatchObject({ canceled: 1, hasMore: true, status: "canceling" }); + await expect( + t.mutation(internal.skillsShCatalog.markScanAttemptRunningInternal, { + attemptId: attempts[1]!._id, + }), + ).rejects.toThrow("Cannot start scan for canceling run"); + await expect( + t.mutation(internal.skillsShCatalog.admitFixtureScansInternal, { + runId, + externalIds: [entries[1]!.externalId], + dispatchKind: "deterministic", + }), + ).rejects.toThrow("Cannot admit scans for canceling run"); + + const lateResult = await t.mutation(internal.skillsShCatalog.recordFixtureScanResultInternal, { + attemptId: attempts[2]!._id, + sourceContentHash: attempts[2]!.sourceContentHash, + verdict: "clean", + }); + expect(lateResult).toEqual({ applied: false, reason: "run-canceled" }); + const finished = await t.mutation(internal.skillsShCatalog.cancelCatalogRunInternal, { + runId, + limit: 10, + }); + expect(finished).toMatchObject({ canceled: 1, hasMore: false, status: "canceled" }); + expect( + (await collectAttempts(t, runId)).every((attempt) => attempt.status === "canceled"), + ).toBe(true); + expect((await collectEntries(t)).every((entry) => !entry.publicVisible)).toBe(true); + }); + + it("enforces queue health and concurrent caps, then cancels only catalog state", async () => { + useEnvironment(LOCAL_ENV); + const t = convexTest(schema, modules); + const seeded = await t.run(async (ctx) => { + const ownerUserId = await ctx.db.insert("users", { + handle: "native-owner", + displayName: "Native Owner", + }); + const nativeSkillId = await ctx.db.insert("skills", { + slug: "native-skill", + displayName: "Native Skill", + ownerUserId, + tags: {}, + stats: { + downloads: 0, + stars: 0, + versions: 0, + comments: 0, + }, + createdAt: 1, + updatedAt: 1, + }); + const nativeCompletedJobId = await ctx.db.insert("securityScanJobs", { + targetKind: "skillVersion", + status: "succeeded", + source: "manual", + priority: 0, + hasMaliciousSignal: false, + waitForVtUntil: 0, + nextRunAt: 0, + attempts: 1, + completedAt: 1, + createdAt: 1, + updatedAt: 1, + }); + return { nativeSkillId, nativeCompletedJobId }; + }); + await t.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + ...BASE_CONTROL, + maxEntriesPerRun: 3, + maxEntriesPerBatch: 3, + maxPlannedScans: 2, + maxScanAdmissionsPerBatch: 1, + maxScanAdmissionsPerRun: 1, + maxScanAdmissionsPerDay: 1, + maxCatalogQueued: 1, + maxCatalogInFlight: 1, + }); + const { runId } = await t.mutation(internal.skillsShCatalog.startFixtureRunInternal, { + fixtureId: "nvidia-small-v1", + actor: "codex-test", + reason: "queue and concurrency proof", + }); + await processToTerminal(t, runId); + const entries = await collectEntries(t); + expect(entries).toHaveLength(2); + + const blockingJobId = await t.run(async (ctx) => { + return await ctx.db.insert("securityScanJobs", { + targetKind: "skillVersion", + status: "queued", + source: "manual", + priority: 0, + hasMaliciousSignal: false, + waitForVtUntil: 0, + nextRunAt: 0, + attempts: 0, + createdAt: 2, + updatedAt: 2, + }); + }); + await expect( + t.mutation(internal.skillsShCatalog.admitFixtureScansInternal, { + runId, + externalIds: [entries[0]!.externalId], + dispatchKind: "deterministic", + }), + ).rejects.toThrow("blocked by queue health"); + await t.run(async (ctx) => await ctx.db.delete(blockingJobId)); + const nativeBefore = await collectNativeState(t); + + const concurrent = await Promise.allSettled( + entries.map((entry) => + t.mutation(internal.skillsShCatalog.admitFixtureScansInternal, { + runId, + externalIds: [entry.externalId], + dispatchKind: "deterministic", + }), + ), + ); + expect(concurrent.filter((result) => result.status === "fulfilled")).toHaveLength(1); + expect(concurrent.filter((result) => result.status === "rejected")).toHaveLength(1); + const attempts = await collectAttempts(t, runId); + expect(attempts).toHaveLength(1); + await t.mutation(internal.skillsShCatalog.markScanAttemptRunningInternal, { + attemptId: attempts[0]!._id, + }); + const canceled = await t.mutation(internal.skillsShCatalog.cancelCatalogRunInternal, { + runId, + limit: 10, + }); + expect(canceled.canceled).toBe(1); + await t.mutation(internal.skillsShCatalog.disableCatalogInternal, { + actor: "codex-test", + reason: "prove reversible rollback", + confirm: "disable-skills-sh-catalog", + }); + const status = await t.query(internal.skillsShCatalog.getStatusInternal, {}); + expect(status.control).toMatchObject({ + mode: "off", + discoveryEnabled: false, + writesEnabled: false, + scanPlanningEnabled: false, + scanAdmissionEnabled: false, + publicVisibilityEnabled: false, + paused: true, + }); + expect(status.entries.every((entry) => !entry.publicVisible)).toBe(true); + expect(await collectNativeState(t)).toEqual(nativeBefore); + expect(await t.run(async (ctx) => await ctx.db.get(seeded.nativeSkillId))).toMatchObject({ + slug: "native-skill", + updatedAt: 1, + }); + expect(await t.run(async (ctx) => await ctx.db.get(seeded.nativeCompletedJobId))).toMatchObject( + { status: "succeeded", updatedAt: 1 }, + ); + }); + + it("persists live Test batches dark and links an admitted artifact to the real scan queue", async () => { + useEnvironment(TEST_ENV); + const t = convexTest(schema, modules); + const actorUserId = await t.run(async (ctx) => { + return await ctx.db.insert("users", { + handle: "catalog-test-operator", + displayName: "Catalog Test Operator", + role: "admin", + }); + }); + await t.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + ...BASE_CONTROL, + mode: "staging-live", + maxEntriesPerRun: 500, + maxEntriesPerBatch: 100, + maxPlannedScans: 500, + maxScanAdmissionsPerBatch: 1, + maxScanAdmissionsPerRun: 1, + maxScanAdmissionsPerDay: 1, + maxCatalogQueued: 1, + maxCatalogInFlight: 1, + maxNativeQueued: 0, + maxNativeInFlight: 0, + realScanAllowlist: ["nvidia/skills/aiq-deploy"], + }); + + const rows = frozenSnapshot.rows.map((row) => ({ ...row })); + const { runId } = await t.mutation(internal.skillsShCatalog.startStagingLiveRunInternal, { + actor: "catalog-test-operator", + reason: "prove exact live Test batching", + snapshotId: "skills-sh-test-live-500:test", + sourceCapturedAt: "2026-07-21T00:00:00.000Z", + snapshotCaptureFetches: 528, + fixtureLength: rows.length, + }); + for (let cursor = 0; cursor < rows.length; cursor += 50) { + await t.mutation(internal.skillsShCatalog.processStagingLiveBatchInternal, { + runId, + cursor, + rows: rows.slice(cursor, cursor + 50), + }); + } + + const run = await t.query(internal.skillsShCatalog.getRunInternal, { runId }); + expect(run).toMatchObject({ + status: "completed", + cursor: 500, + counts: { + observed: 500, + inserted: 500, + scansPlanned: 500, + scansAdmitted: 0, + }, + }); + expect((await collectEntries(t)).every((entry) => !entry.publicVisible)).toBe(true); + + const artifact = await storeTestArtifact(t, "nvidia/skills/aiq-deploy", "hello catalog"); + const admitted = await t.action(internal.skillsShCatalog.admitRealScansInternal, { + runId, + externalIds: ["nvidia/skills/aiq-deploy"], + actorUserId, + artifacts: [artifact], + }); + expect(admitted).toMatchObject({ admitted: 1, skipped: 0 }); + + const [attempt] = await collectAttempts(t, runId); + expect(attempt).toMatchObject({ + dispatchKind: "real", + source: "skills-sh-catalog-test", + artifactContentHash: artifact.artifactContentHash, + }); + const linked = await t.run(async (ctx) => { + const request = attempt?.skillScanRequestId + ? await ctx.db.get(attempt.skillScanRequestId) + : null; + const job = attempt?.securityScanJobId ? await ctx.db.get(attempt.securityScanJobId) : null; + return { request, job }; + }); + expect(linked.request).toMatchObject({ + actorUserId, + sourceKind: "skills-sh-catalog", + status: "queued", + sha256hash: artifact.artifactContentHash, + skillsShCatalogAttemptId: attempt?._id, + }); + expect(linked.request).not.toHaveProperty("skillId"); + expect(linked.job).toMatchObject({ + targetKind: "skillScanRequest", + source: "skills-sh-catalog-test", + priority: -100, + status: "queued", + skillScanRequestId: linked.request?._id, + }); + + const operationsBeforeStaleResult = (await t.query(internal.skillsShCatalog.getRunInternal, { + runId, + }))!.operations; + await t.run(async (ctx) => { + const entry = await ctx.db.get(attempt!.entryId); + await ctx.db.patch(entry!._id, { + sourceContentHash: "changed-after-admission", + updatedAt: Date.now(), + }); + await ctx.db.patch(attempt!.securityScanJobId!, { + status: "running", + leaseToken: "lease-token", + leaseExpiresAt: Date.now() + 60_000, + workerId: "catalog-worker", + updatedAt: Date.now(), + }); + }); + const staleResult = await t.mutation( + internal.securityScan.completeCatalogSkillScanJobInternal, + { + attemptId: attempt!._id, + scanId: attempt!.skillScanRequestId!, + jobId: attempt!.securityScanJobId!, + leaseToken: "lease-token", + artifactContentHash: attempt!.artifactContentHash!, + verdict: "clean", + runId: "clawscan-run", + llmAnalysis: { status: "clean", checkedAt: Date.now() }, + }, + ); + expect(staleResult).toEqual({ ok: true, applied: false, reason: "stale-attempt" }); + const staleRun = await t.query(internal.skillsShCatalog.getRunInternal, { runId }); + expect(staleRun).toMatchObject({ + counts: { + scansAdmitted: 1, + scansCanceled: 1, + scansCompleted: 0, + }, + }); + expect(staleRun!.operations.dbWrites).toBe(operationsBeforeStaleResult.dbWrites + 4); + expect((await collectAttempts(t, runId))[0]).toMatchObject({ + status: "canceled", + }); + expect( + await t.run(async (ctx) => await ctx.db.get(attempt!.skillScanRequestId!)), + ).toMatchObject({ + status: "failed", + lastError: "Catalog source changed before scan completion", + }); + expect(await t.run(async (ctx) => await ctx.db.get(attempt!.securityScanJobId!))).toMatchObject( + { + status: "failed", + lastError: "Catalog source changed before scan completion", + }, + ); + }); + + it("defers expiry cleanup for an active catalog job and accepts its later result", async () => { + useEnvironment(TEST_ENV); + const t = convexTest(schema, modules); + const actorUserId = await t.run( + async (ctx) => + await ctx.db.insert("users", { + handle: "catalog-expiry-operator", + displayName: "Catalog Expiry Operator", + role: "admin", + }), + ); + await t.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + ...BASE_CONTROL, + mode: "staging-live", + maxEntriesPerRun: 500, + maxEntriesPerBatch: 1, + maxScanAdmissionsPerBatch: 1, + maxScanAdmissionsPerRun: 1, + maxScanAdmissionsPerDay: 1, + maxCatalogQueued: 1, + maxCatalogInFlight: 1, + realScanAllowlist: ["nvidia/skills/aiq-deploy"], + }); + const { runId } = await t.mutation(internal.skillsShCatalog.startStagingLiveRunInternal, { + actor: "catalog-expiry-operator", + reason: "prove active request expiry is deferred", + snapshotId: "skills-sh-test-live-500:active-expiry", + sourceCapturedAt: "2026-07-21T00:00:00.000Z", + snapshotCaptureFetches: 528, + fixtureLength: 500, + }); + await t.mutation(internal.skillsShCatalog.processStagingLiveBatchInternal, { + runId, + cursor: 0, + rows: [frozenSnapshot.rows.find((row) => row.externalId === "nvidia/skills/aiq-deploy")!], + }); + const artifact = await storeTestArtifact( + t, + "nvidia/skills/aiq-deploy", + "active expiry artifact", + ); + await t.action(internal.skillsShCatalog.admitRealScansInternal, { + runId, + externalIds: ["nvidia/skills/aiq-deploy"], + actorUserId, + artifacts: [artifact], + }); + const [attempt] = await collectAttempts(t, runId); + await t.run(async (ctx) => { + await ctx.db.patch(attempt!.skillScanRequestId!, { + status: "running", + expiresAt: 0, + updatedAt: Date.now(), + }); + await ctx.db.patch(attempt!.securityScanJobId!, { + status: "running", + leaseToken: "lease-token", + leaseExpiresAt: Date.now() + 60_000, + workerId: "catalog-worker", + updatedAt: Date.now(), + }); + await ctx.db.patch(attempt!._id, { + status: "running", + updatedAt: Date.now(), + }); + }); + + const pruned = await t.mutation(internal.securityScan.pruneExpiredSkillScanRequestsInternal, { + batchSize: 10, + }); + expect(pruned).toMatchObject({ + deletedRequests: 0, + deferredRequests: 1, + deletedJobs: 0, + deletedFiles: 0, + done: false, + }); + expect( + await t.run(async (ctx) => await ctx.db.get(attempt!.skillScanRequestId!)), + ).toMatchObject({ status: "running" }); + expect(await t.run(async (ctx) => await ctx.db.get(attempt!.securityScanJobId!))).toMatchObject( + { status: "running" }, + ); + expect((await collectAttempts(t, runId))[0]).toMatchObject({ status: "running" }); + + const result = await t.mutation(internal.securityScan.completeCatalogSkillScanJobInternal, { + attemptId: attempt!._id, + scanId: attempt!.skillScanRequestId!, + jobId: attempt!.securityScanJobId!, + leaseToken: "lease-token", + artifactContentHash: attempt!.artifactContentHash!, + verdict: "clean", + runId: "clawscan-run", + llmAnalysis: { status: "clean", checkedAt: Date.now() }, + }); + expect(result).toEqual({ ok: true, applied: true, publicVisible: false }); + expect((await collectAttempts(t, runId))[0]).toMatchObject({ + status: "succeeded", + verdict: "clean", + }); + expect(await t.run(async (ctx) => await ctx.db.get(attempt!.entryId))).toMatchObject({ + scanStatus: "clean", + publicVisible: false, + }); + expect( + await t.run(async (ctx) => await ctx.db.get(attempt!.skillScanRequestId!)), + ).toMatchObject({ + status: "succeeded", + runId: "clawscan-run", + llmAnalysis: { status: "clean" }, + }); + const completedJob = await t.run(async (ctx) => await ctx.db.get(attempt!.securityScanJobId!)); + expect(completedJob).toMatchObject({ + status: "succeeded", + runId: "clawscan-run", + }); + expect(completedJob).not.toHaveProperty("leaseToken"); + expect(completedJob).not.toHaveProperty("leaseExpiresAt"); + expect(await t.query(internal.skillsShCatalog.getRunInternal, { runId })).toMatchObject({ + counts: { scansCompleted: 1 }, + }); + }); + + it("keeps a running real job active until its terminal callback after cancellation", async () => { + useEnvironment(TEST_ENV); + const t = convexTest(schema, modules); + const actorUserId = await t.run( + async (ctx) => + await ctx.db.insert("users", { + handle: "catalog-cancel-operator", + displayName: "Catalog Cancel Operator", + role: "admin", + }), + ); + await t.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + ...BASE_CONTROL, + mode: "staging-live", + maxEntriesPerRun: 500, + maxEntriesPerBatch: 1, + maxScanAdmissionsPerBatch: 1, + maxScanAdmissionsPerRun: 1, + maxScanAdmissionsPerDay: 1, + maxCatalogQueued: 1, + maxCatalogInFlight: 1, + realScanAllowlist: ["nvidia/skills/aiq-deploy"], + }); + const { runId } = await t.mutation(internal.skillsShCatalog.startStagingLiveRunInternal, { + actor: "catalog-cancel-operator", + reason: "defer running real cancellation", + snapshotId: "skills-sh-test-live-500:cancel-running", + sourceCapturedAt: "2026-07-21T00:00:00.000Z", + snapshotCaptureFetches: 528, + fixtureLength: 500, + }); + await t.mutation(internal.skillsShCatalog.processStagingLiveBatchInternal, { + runId, + cursor: 0, + rows: [frozenSnapshot.rows.find((row) => row.externalId === "nvidia/skills/aiq-deploy")!], + }); + const artifact = await storeTestArtifact( + t, + "nvidia/skills/aiq-deploy", + "running cancellation artifact", + ); + await t.action(internal.skillsShCatalog.admitRealScansInternal, { + runId, + externalIds: ["nvidia/skills/aiq-deploy"], + actorUserId, + artifacts: [artifact], + }); + const [attempt] = await collectAttempts(t, runId); + await t.run(async (ctx) => { + await ctx.db.patch(attempt!.skillScanRequestId!, { + status: "running", + updatedAt: Date.now(), + }); + await ctx.db.patch(attempt!.securityScanJobId!, { + status: "running", + leaseToken: "active-cancel-lease", + leaseExpiresAt: Date.now() + 60_000, + updatedAt: Date.now(), + }); + await ctx.db.patch(attempt!._id, { + status: "running", + updatedAt: Date.now(), + }); + }); + + const canceling = await t.mutation(internal.skillsShCatalog.cancelCatalogRunInternal, { + runId, + limit: 10, + }); + expect(canceling).toMatchObject({ canceled: 0, hasMore: true, status: "canceling" }); + expect((await collectAttempts(t, runId))[0]).toMatchObject({ status: "running" }); + expect(await t.run(async (ctx) => await ctx.db.get(attempt!.securityScanJobId!))).toMatchObject( + { + status: "running", + }, + ); + + const terminal = await t.mutation(internal.skillsShCatalog.recordRealScanResultInternal, { + attemptId: attempt!._id, + artifactContentHash: attempt!.artifactContentHash!, + verdict: "clean", + }); + expect(terminal).toEqual({ applied: false, reason: "run-canceled" }); + expect((await collectAttempts(t, runId))[0]).toMatchObject({ status: "canceled" }); + expect(await t.query(internal.skillsShCatalog.getRunInternal, { runId })).toMatchObject({ + status: "canceled", + counts: { scansCanceled: 1 }, + }); + }); + + it("terminalizes an expired running real job during catalog cancellation", async () => { + useEnvironment(TEST_ENV); + const t = convexTest(schema, modules); + const actorUserId = await t.run( + async (ctx) => + await ctx.db.insert("users", { + handle: "catalog-expired-cancel-operator", + displayName: "Catalog Expired Cancel Operator", + role: "admin", + }), + ); + await t.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + ...BASE_CONTROL, + mode: "staging-live", + maxEntriesPerRun: 500, + maxEntriesPerBatch: 1, + maxScanAdmissionsPerBatch: 1, + maxScanAdmissionsPerRun: 1, + maxScanAdmissionsPerDay: 1, + maxCatalogQueued: 1, + maxCatalogInFlight: 1, + realScanAllowlist: ["nvidia/skills/aiq-deploy"], + }); + const { runId } = await t.mutation(internal.skillsShCatalog.startStagingLiveRunInternal, { + actor: "catalog-expired-cancel-operator", + reason: "terminalize expired running real cancellation", + snapshotId: "skills-sh-test-live-500:cancel-expired-running", + sourceCapturedAt: "2026-07-21T00:00:00.000Z", + snapshotCaptureFetches: 528, + fixtureLength: 500, + }); + await t.mutation(internal.skillsShCatalog.processStagingLiveBatchInternal, { + runId, + cursor: 0, + rows: [frozenSnapshot.rows.find((row) => row.externalId === "nvidia/skills/aiq-deploy")!], + }); + const artifact = await storeTestArtifact( + t, + "nvidia/skills/aiq-deploy", + "expired running cancellation artifact", + ); + await t.action(internal.skillsShCatalog.admitRealScansInternal, { + runId, + externalIds: ["nvidia/skills/aiq-deploy"], + actorUserId, + artifacts: [artifact], + }); + const [attempt] = await collectAttempts(t, runId); + await t.run(async (ctx) => { + await ctx.db.patch(attempt!.skillScanRequestId!, { + status: "running", + updatedAt: Date.now(), + }); + await ctx.db.patch(attempt!.securityScanJobId!, { + status: "running", + leaseToken: "expired-cancel-lease", + leaseExpiresAt: Date.now() - 1, + workerId: "expired-catalog-worker", + updatedAt: Date.now(), + }); + await ctx.db.patch(attempt!._id, { + status: "running", + updatedAt: Date.now(), + }); + }); + + const canceled = await t.mutation(internal.skillsShCatalog.cancelCatalogRunInternal, { + runId, + limit: 10, + }); + + expect(canceled).toMatchObject({ canceled: 1, hasMore: false, status: "canceled" }); + expect((await collectAttempts(t, runId))[0]).toMatchObject({ status: "canceled" }); + expect(await t.run(async (ctx) => await ctx.db.get(attempt!.entryId))).toMatchObject({ + scanStatus: "canceled", + publicVisible: false, + }); + expect( + await t.run(async (ctx) => await ctx.db.get(attempt!.skillScanRequestId!)), + ).toMatchObject({ + status: "failed", + lastError: "Catalog run canceled after scan lease expired", + }); + const job = await t.run(async (ctx) => await ctx.db.get(attempt!.securityScanJobId!)); + expect(job).toMatchObject({ + status: "failed", + lastError: "Catalog run canceled after scan lease expired", + }); + expect(job).not.toHaveProperty("leaseToken"); + expect(job).not.toHaveProperty("leaseExpiresAt"); + expect(job).not.toHaveProperty("workerId"); + }); + + it("fixture queue cleanup leaves real staging attempts untouched", async () => { + useEnvironment(TEST_ENV); + const t = convexTest(schema, modules); + const seeded = await t.run(async (ctx) => { + const counts = { + observed: 1, + wouldInsert: 1, + wouldUpdate: 0, + inserted: 1, + updated: 0, + unchanged: 0, + rejected: 0, + scansPlanned: 1, + scansAdmitted: 1, + scansCompleted: 0, + scansCanceled: 0, + }; + const budgets = { + maxEntriesPerRun: 1, + maxEntriesPerBatch: 1, + maxWritesPerBatch: 10, + maxPlannedScans: 1, + maxScanAdmissionsPerBatch: 1, + maxScanAdmissionsPerRun: 1, + maxScanAdmissionsPerDay: 2, + }; + const operations = { functionCalls: 1, dbReads: 1, dbWrites: 1 }; + const deterministicRunId = await ctx.db.insert("skillsShCatalogRuns", { + fixtureId: "nvidia-small-v1", + snapshotId: "nvidia-small-v1", + sourceKind: "fixture", + snapshotCaptureFetches: 0, + dryRun: false, + status: "completed", + cursor: 1, + scanCursor: 1, + fixtureLength: 1, + counts, + budgets, + operations, + actor: "codex-test", + reason: "fixture cleanup regression", + batchesProcessed: 1, + scanAdmissionBatches: 1, + lastBatchWrites: 1, + lastBatchReads: 1, + startedAt: 1, + completedAt: 1, + updatedAt: 1, + }); + const realRunId = await ctx.db.insert("skillsShCatalogRuns", { + fixtureId: "skills-sh-test-live-500", + snapshotId: "skills-sh-test-live-500:cleanup-regression", + sourceKind: "staging-live", + sourceCapturedAt: "2026-07-21T00:00:00.000Z", + snapshotCaptureFetches: 1, + dryRun: false, + status: "completed", + cursor: 1, + scanCursor: 1, + fixtureLength: 1, + counts, + budgets, + operations, + actor: "codex-test", + reason: "real cleanup regression", + batchesProcessed: 1, + scanAdmissionBatches: 1, + lastBatchWrites: 1, + lastBatchReads: 1, + startedAt: 2, + completedAt: 2, + updatedAt: 2, + }); + const deterministicEntryId = await ctx.db.insert("skillsShCatalogEntries", { + externalId: "fixture/fixture/deterministic", + sourceKind: "fixture", + githubOwnerId: 1, + owner: "fixture", + repo: "fixture", + slug: "deterministic", + displayName: "Deterministic", + sourceUrl: "https://example.invalid/deterministic", + githubRepoUrl: "https://github.com/fixture/fixture", + sourceContentHash: "deterministic-hash", + installs: 0, + sourceSnapshotId: "nvidia-small-v1", + publicVisible: false, + scanStatus: "queued", + firstObservedAt: 1, + lastObservedAt: 1, + createdAt: 1, + updatedAt: 1, + }); + const realEntryId = await ctx.db.insert("skillsShCatalogEntries", { + externalId: "nvidia/skills/aiq-deploy", + sourceKind: "staging-live", + githubOwnerId: 1_728_152, + owner: "nvidia", + repo: "skills", + slug: "aiq-deploy", + displayName: "AIQ Deploy", + sourceUrl: "https://skills.sh/nvidia/skills/aiq-deploy", + githubRepoUrl: "https://github.com/nvidia/skills", + sourceContentHash: "real-hash", + installs: 0, + sourceSnapshotId: "skills-sh-test-live-500:cleanup-regression", + publicVisible: false, + scanStatus: "queued", + firstObservedAt: 2, + lastObservedAt: 2, + createdAt: 2, + updatedAt: 2, + }); + const deterministicAttemptId = await ctx.db.insert("skillsShCatalogScanAttempts", { + entryId: deterministicEntryId, + runId: deterministicRunId, + externalId: "fixture/fixture/deterministic", + sourceContentHash: "deterministic-hash", + source: "skills-sh-catalog-fixture", + dispatchKind: "deterministic", + priority: "low", + status: "queued", + createdAt: 1, + updatedAt: 1, + }); + const realAttemptId = await ctx.db.insert("skillsShCatalogScanAttempts", { + entryId: realEntryId, + runId: realRunId, + externalId: "nvidia/skills/aiq-deploy", + sourceContentHash: "real-hash", + artifactContentHash: "a".repeat(64), + source: "skills-sh-catalog-test", + dispatchKind: "real", + priority: "low", + status: "queued", + createdAt: 2, + updatedAt: 2, + }); + return { + deterministicAttemptId, + deterministicEntryId, + deterministicRunId, + realAttemptId, + realEntryId, + realRunId, + }; + }); + + const result = await t.mutation(internal.skillsShCatalog.cancelQueuedFixtureScansInternal, { + limit: 100, + }); + + expect(result).toEqual({ matched: 1, canceled: 1 }); + expect( + await t.run(async (ctx) => await ctx.db.get(seeded.deterministicAttemptId)), + ).toMatchObject({ status: "canceled" }); + expect(await t.run(async (ctx) => await ctx.db.get(seeded.realAttemptId))).toMatchObject({ + status: "queued", + }); + expect(await t.run(async (ctx) => await ctx.db.get(seeded.deterministicEntryId))).toMatchObject( + { + scanStatus: "canceled", + }, + ); + expect(await t.run(async (ctx) => await ctx.db.get(seeded.realEntryId))).toMatchObject({ + scanStatus: "queued", + }); + expect(await t.run(async (ctx) => await ctx.db.get(seeded.deterministicRunId))).toMatchObject({ + counts: { scansCanceled: 1 }, + }); + expect(await t.run(async (ctx) => await ctx.db.get(seeded.realRunId))).toMatchObject({ + counts: { scansCanceled: 0 }, + }); + }); + + it("rejects real admission when only six writes remain in the batch budget", async () => { + useEnvironment(TEST_ENV); + const t = convexTest(schema, modules); + const actorUserId = await t.run( + async (ctx) => + await ctx.db.insert("users", { + handle: "catalog-budget-operator", + displayName: "Catalog Budget Operator", + role: "admin", + }), + ); + await t.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + ...BASE_CONTROL, + mode: "staging-live", + maxEntriesPerBatch: 1, + maxWritesPerBatch: 6, + maxScanAdmissionsPerBatch: 1, + maxScanAdmissionsPerRun: 1, + maxScanAdmissionsPerDay: 1, + maxCatalogQueued: 1, + maxCatalogInFlight: 1, + realScanAllowlist: ["nvidia/skills/aiq-deploy"], + }); + const { runId } = await t.mutation(internal.skillsShCatalog.startStagingLiveRunInternal, { + actor: "catalog-budget-operator", + reason: "prove admission write reservation", + snapshotId: "skills-sh-test-live-500:write-budget", + sourceCapturedAt: "2026-07-21T00:00:00.000Z", + snapshotCaptureFetches: 528, + fixtureLength: 500, + }); + await t.mutation(internal.skillsShCatalog.processStagingLiveBatchInternal, { + runId, + cursor: 0, + rows: [frozenSnapshot.rows.find((row) => row.externalId === "nvidia/skills/aiq-deploy")!], + }); + const artifact = await storeTestArtifact(t, "nvidia/skills/aiq-deploy", "budget artifact"); + + await expect( + t.action(internal.skillsShCatalog.admitRealScansInternal, { + runId, + externalIds: ["nvidia/skills/aiq-deploy"], + actorUserId, + artifacts: [artifact], + }), + ).rejects.toThrow("scan-admission write budget exceeded"); + expect(await collectAttempts(t, runId)).toEqual([]); + expect(await t.run(async (ctx) => await ctx.db.query("securityScanJobs").collect())).toEqual( + [], + ); + }); + + it("terminalizes a failed exact hash without automatically replanning it", async () => { + useEnvironment(TEST_ENV); + const t = convexTest(schema, modules); + const actorUserId = await t.run( + async (ctx) => + await ctx.db.insert("users", { + handle: "catalog-six-write-operator", + displayName: "Catalog Six Write Operator", + role: "admin", + }), + ); + await t.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + ...BASE_CONTROL, + mode: "staging-live", + maxEntriesPerBatch: 1, + maxWritesPerBatch: 7, + maxScanAdmissionsPerBatch: 1, + maxScanAdmissionsPerRun: 1, + maxScanAdmissionsPerDay: 1, + maxCatalogQueued: 1, + maxCatalogInFlight: 1, + realScanAllowlist: ["nvidia/skills/aiq-deploy"], + }); + const { runId } = await t.mutation(internal.skillsShCatalog.startStagingLiveRunInternal, { + actor: "catalog-six-write-operator", + reason: "prove exact admission write reservation", + snapshotId: "skills-sh-test-live-500:six-write-budget", + sourceCapturedAt: "2026-07-21T00:00:00.000Z", + snapshotCaptureFetches: 528, + fixtureLength: 500, + }); + await t.mutation(internal.skillsShCatalog.processStagingLiveBatchInternal, { + runId, + cursor: 0, + rows: [frozenSnapshot.rows.find((row) => row.externalId === "nvidia/skills/aiq-deploy")!], + }); + const artifact = await storeTestArtifactFiles(t, "nvidia/skills/aiq-deploy", [ + { path: "SKILL.md", content: "six write artifact" }, + { path: "references/context.md", content: "second embedded artifact file" }, + ]); + + const result = await t.action(internal.skillsShCatalog.admitRealScansInternal, { + runId, + externalIds: ["nvidia/skills/aiq-deploy"], + actorUserId, + artifacts: [artifact], + }); + + expect(result).toMatchObject({ requested: 1, admitted: 1, skipped: 0 }); + expect(await collectAttempts(t, runId)).toHaveLength(1); + expect( + await t.run(async (ctx) => await ctx.db.query("securityScanJobs").collect()), + ).toHaveLength(1); + + const [attempt] = await collectAttempts(t, runId); + await t.run(async (ctx) => { + await ctx.db.patch(attempt!.securityScanJobId!, { + status: "running", + leaseToken: "lease-token", + leaseExpiresAt: Date.now() + 60_000, + workerId: "catalog-worker", + updatedAt: Date.now(), + }); + await ctx.db.patch(attempt!._id, { + status: "running", + updatedAt: Date.now(), + }); + }); + const completed = await t.mutation(internal.securityScan.completeCatalogSkillScanJobInternal, { + attemptId: attempt!._id, + scanId: attempt!.skillScanRequestId!, + jobId: attempt!.securityScanJobId!, + leaseToken: "lease-token", + artifactContentHash: attempt!.artifactContentHash!, + verdict: "failed", + runId: "clawscan-run", + llmAnalysis: { status: "error", checkedAt: Date.now() }, + }); + + expect(completed).toEqual({ ok: true, applied: true, publicVisible: false }); + expect((await collectAttempts(t, runId))[0]).toMatchObject({ + status: "failed", + verdict: "failed", + }); + expect(await t.run(async (ctx) => await ctx.db.get(attempt!.entryId))).toMatchObject({ + scanStatus: "failed", + publicVisible: false, + }); + expect( + await t.run(async (ctx) => await ctx.db.get(attempt!.skillScanRequestId!)), + ).toMatchObject({ + status: "failed", + lastError: "Catalog scan analysis failed", + }); + expect(await t.run(async (ctx) => await ctx.db.get(attempt!.securityScanJobId!))).toMatchObject( + { + status: "failed", + lastError: "Catalog scan analysis failed", + }, + ); + const unchanged = await t.mutation(internal.skillsShCatalog.startStagingLiveRunInternal, { + actor: "catalog-six-write-operator", + reason: "do not automatically retry a failed exact hash", + snapshotId: "skills-sh-test-live-500:failed-unchanged", + sourceCapturedAt: "2026-07-21T00:05:00.000Z", + snapshotCaptureFetches: 528, + fixtureLength: 500, + }); + const unchangedRun = await t.mutation( + internal.skillsShCatalog.processStagingLiveBatchInternal, + { + runId: unchanged.runId, + cursor: 0, + rows: [frozenSnapshot.rows.find((row) => row.externalId === "nvidia/skills/aiq-deploy")!], + }, + ); + expect(unchangedRun.counts).toMatchObject({ + observed: 1, + unchanged: 1, + scansPlanned: 0, + scansAdmitted: 0, + }); + expect(await collectAttempts(t, unchanged.runId)).toEqual([]); + expect(await t.run(async (ctx) => await ctx.db.get(attempt!.entryId))).toMatchObject({ + scanStatus: "failed", + publicVisible: false, + }); + const runAfterCompletion = await t.query(internal.skillsShCatalog.getRunInternal, { runId }); + const repeated = await t.mutation(internal.securityScan.completeCatalogSkillScanJobInternal, { + attemptId: attempt!._id, + scanId: attempt!.skillScanRequestId!, + jobId: attempt!.securityScanJobId!, + leaseToken: "lease-token", + artifactContentHash: attempt!.artifactContentHash!, + verdict: "failed", + runId: "clawscan-run", + llmAnalysis: { status: "error", checkedAt: Date.now() }, + }); + expect(repeated).toEqual({ ok: true, applied: true, publicVisible: false }); + expect(await t.query(internal.skillsShCatalog.getRunInternal, { runId })).toEqual( + runAfterCompletion, + ); + await expect( + t.mutation(internal.securityScan.completeCatalogSkillScanJobInternal, { + attemptId: attempt!._id, + scanId: attempt!.skillScanRequestId!, + jobId: attempt!.securityScanJobId!, + leaseToken: "lease-token", + artifactContentHash: attempt!.artifactContentHash!, + verdict: "clean", + runId: "clawscan-run", + llmAnalysis: { status: "clean", checkedAt: Date.now() }, + }), + ).rejects.toThrow("Catalog scan terminal result mismatch"); + }); + + it("fails closed when an active real attempt has no run", async () => { + useEnvironment(TEST_ENV); + const t = convexTest(schema, modules); + const seeded = await t.run(async (ctx) => { + const runId = await ctx.db.insert("skillsShCatalogRuns", { + fixtureId: "skills-sh-test-live-500", + snapshotId: "skills-sh-test-live-500:missing-run", + sourceKind: "staging-live", + sourceCapturedAt: "2026-07-21T00:00:00.000Z", + snapshotCaptureFetches: 1, + dryRun: false, + status: "completed", + cursor: 1, + scanCursor: 1, + fixtureLength: 500, + counts: { + observed: 1, + wouldInsert: 1, + wouldUpdate: 0, + inserted: 1, + updated: 0, + unchanged: 0, + rejected: 0, + scansPlanned: 1, + scansAdmitted: 1, + scansCompleted: 0, + scansCanceled: 0, + }, + budgets: { + maxEntriesPerRun: 500, + maxEntriesPerBatch: 1, + maxWritesPerBatch: 10, + maxPlannedScans: 500, + maxScanAdmissionsPerBatch: 1, + maxScanAdmissionsPerRun: 1, + maxScanAdmissionsPerDay: 1, + }, + operations: { functionCalls: 1, dbReads: 1, dbWrites: 1 }, + actor: "catalog-test-admin", + reason: "missing run regression", + batchesProcessed: 1, + scanAdmissionBatches: 1, + lastBatchWrites: 1, + lastBatchReads: 1, + startedAt: 1, + completedAt: 1, + updatedAt: 1, + }); + const entryId = await ctx.db.insert("skillsShCatalogEntries", { + externalId: "nvidia/skills/aiq-deploy", + sourceKind: "staging-live", + githubOwnerId: 1_728_152, + owner: "nvidia", + repo: "skills", + slug: "aiq-deploy", + displayName: "AIQ Deploy", + sourceUrl: "https://skills.sh/nvidia/skills/aiq-deploy", + githubRepoUrl: "https://github.com/nvidia/skills", + sourceContentHash: "source-hash", + installs: 1, + sourceSnapshotId: "skills-sh-test-live-500:missing-run", + publicVisible: false, + scanStatus: "queued", + firstObservedAt: 1, + lastObservedAt: 1, + createdAt: 1, + updatedAt: 1, + }); + const attemptId = await ctx.db.insert("skillsShCatalogScanAttempts", { + entryId, + runId, + externalId: "nvidia/skills/aiq-deploy", + sourceContentHash: "source-hash", + artifactContentHash: "a".repeat(64), + source: "skills-sh-catalog-test", + dispatchKind: "real", + priority: "low", + status: "running", + createdAt: 1, + updatedAt: 1, + }); + await ctx.db.delete(runId); + return { attemptId, entryId }; + }); + + await expect( + t.mutation(internal.skillsShCatalog.recordRealScanResultInternal, { + attemptId: seeded.attemptId, + artifactContentHash: "a".repeat(64), + verdict: "clean", + }), + ).rejects.toThrow("run not found"); + expect(await t.run(async (ctx) => await ctx.db.get(seeded.attemptId))).toMatchObject({ + status: "running", + }); + expect(await t.run(async (ctx) => await ctx.db.get(seeded.entryId))).toMatchObject({ + scanStatus: "queued", + publicVisible: false, + }); + }); +}); diff --git a/convex/skillsShCatalog.ts b/convex/skillsShCatalog.ts new file mode 100644 index 00000000..e57a8fb4 --- /dev/null +++ b/convex/skillsShCatalog.ts @@ -0,0 +1,2193 @@ +import { paginationOptsValidator } from "convex/server"; +import { ConvexError, type Infer, v } from "convex/values"; +import { internal } from "./_generated/api"; +import type { Doc, Id } from "./_generated/dataModel"; +import type { ActionCtx, MutationCtx, QueryCtx } from "./_generated/server"; +import { internalAction, internalMutation, internalQuery } from "./functions"; +import { + assertSkillsShCatalogControlMutationAllowed, + assertSkillsShFixtureEnvironmentAllowed, + getSkillsShFixtureEnvironmentPolicy, +} from "./lib/skillsShCatalogEnvironment"; +import { + getSkillsShCatalogFixture, + type SkillsShCatalogFixtureRow, +} from "./lib/skillsShCatalogFixtures"; +import { validateFilePath } from "./lib/skillZip"; +import { enqueueSkillsShCatalogScanRequest } from "./securityScan"; + +const CONTROL_KEY = "global"; +const ENABLE_FIXTURE_CONFIRM = "enable-skills-sh-fixture-control"; +const DISABLE_CATALOG_CONFIRM = "disable-skills-sh-catalog"; +const STATUS_LIMIT = 50; +const MAX_DISCOVERY_ROWS = 20_000; +const MAX_ENTRIES_PER_BATCH = 250; +const MAX_WRITES_PER_BATCH = 100; +const MAX_SCAN_ADMISSIONS_PER_BATCH = 100; +const MAX_SCAN_ADMISSIONS_PER_RUN = 500; +const MAX_REAL_TEST_ADMISSIONS = 10; +const MAX_DETERMINISTIC_COMPLETIONS_PER_BATCH = 50; + +const fixtureIdValidator = v.union( + v.literal("nvidia-small-v1"), + v.literal("nvidia-small-v2"), + v.literal("skills-sh-500-2026-07-21"), + v.literal("skills-sh-500-2026-07-21-v2"), + v.literal("synthetic-20000-v1"), +); +const scanVerdictValidator = v.union( + v.literal("clean"), + v.literal("suspicious"), + v.literal("malicious"), + v.literal("failed"), +); +const dispatchKindValidator = v.union(v.literal("deterministic"), v.literal("real")); +const stagingLiveRowValidator = v.object({ + externalId: v.string(), + githubOwnerId: v.number(), + owner: v.string(), + repo: v.string(), + slug: v.string(), + displayName: v.string(), + sourceUrl: v.string(), + githubRepoUrl: v.string(), + sourceContentHash: v.string(), + installs: v.number(), +}); +const scanRequestFileValidator = v.object({ + path: v.string(), + size: v.number(), + storageId: v.id("_storage"), + sha256: v.string(), + contentType: v.optional(v.string()), +}); +const stagingLiveArtifactValidator = v.object({ + externalId: v.string(), + artifactContentHash: v.string(), + files: v.array(scanRequestFileValidator), +}); +type StagingLiveArtifact = Infer; + +const DEFAULT_CONTROL = { + mode: "off" as const, + discoveryEnabled: false, + writesEnabled: false, + scanPlanningEnabled: false, + scanAdmissionEnabled: false, + publicVisibilityEnabled: false, + paused: true, + maxEntriesPerRun: 0, + maxEntriesPerBatch: 0, + maxWritesPerBatch: 0, + maxPlannedScans: 0, + maxScanAdmissionsPerBatch: 0, + maxScanAdmissionsPerRun: 0, + maxScanAdmissionsPerDay: 0, + maxCatalogQueued: 0, + maxCatalogInFlight: 0, + maxNativeQueued: 0, + maxNativeInFlight: 0, + realScanAllowlist: [] as string[], + updatedBy: null, + reason: null, + updatedAt: null, +}; + +type OperationCounts = { + functionCalls: number; + dbReads: number; + dbWrites: number; +}; + +function normalizeIdentity(row: SkillsShCatalogFixtureRow) { + const owner = row.owner.trim().toLowerCase(); + const repo = row.repo.trim().toLowerCase(); + const slug = row.slug.trim().toLowerCase(); + return { + ...row, + owner, + repo, + slug, + externalId: `${owner}/${repo}/${slug}`, + sourceContentHash: row.sourceContentHash.trim().toLowerCase(), + }; +} + +async function getControlDoc(ctx: Pick) { + return await ctx.db + .query("skillsShCatalogControls") + .withIndex("by_key", (q) => q.eq("key", CONTROL_KEY)) + .unique(); +} + +function summarizeControl(control: Doc<"skillsShCatalogControls"> | null) { + if (!control) return DEFAULT_CONTROL; + return { + mode: control.mode, + discoveryEnabled: control.discoveryEnabled, + writesEnabled: control.writesEnabled, + scanPlanningEnabled: control.scanPlanningEnabled, + scanAdmissionEnabled: control.scanAdmissionEnabled, + publicVisibilityEnabled: control.publicVisibilityEnabled, + paused: control.paused, + maxEntriesPerRun: control.maxEntriesPerRun, + maxEntriesPerBatch: control.maxEntriesPerBatch, + maxWritesPerBatch: control.maxWritesPerBatch, + maxPlannedScans: control.maxPlannedScans, + maxScanAdmissionsPerBatch: control.maxScanAdmissionsPerBatch, + maxScanAdmissionsPerRun: control.maxScanAdmissionsPerRun, + maxScanAdmissionsPerDay: control.maxScanAdmissionsPerDay, + maxCatalogQueued: control.maxCatalogQueued, + maxCatalogInFlight: control.maxCatalogInFlight, + maxNativeQueued: control.maxNativeQueued, + maxNativeInFlight: control.maxNativeInFlight, + realScanAllowlist: control.realScanAllowlist, + updatedBy: control.updatedBy, + reason: control.reason, + updatedAt: control.updatedAt, + }; +} + +function assertIntegerInRange(name: string, value: number, min: number, max: number) { + if (!Number.isInteger(value) || value < min || value > max) { + throw new ConvexError(`${name} must be an integer between ${min} and ${max}`); + } +} + +function assertCatalogActive(control: Doc<"skillsShCatalogControls"> | null) { + if (!control || control.mode === "off") { + throw new ConvexError("skills.sh catalog controls are disabled"); + } + if (control.paused) throw new ConvexError("skills.sh catalog is paused"); + return control; +} + +function assertDiscoveryEnabled(control: Doc<"skillsShCatalogControls"> | null) { + const active = assertCatalogActive(control); + if (!active.discoveryEnabled) { + throw new ConvexError("skills.sh catalog discovery is disabled"); + } + return active; +} + +function assertWritesEnabled(control: Doc<"skillsShCatalogControls"> | null) { + const active = assertCatalogActive(control); + if (!active.writesEnabled) { + throw new ConvexError("skills.sh catalog writes are disabled"); + } + return active; +} + +function assertScanAdmissionEnabled(control: Doc<"skillsShCatalogControls"> | null) { + const active = assertCatalogActive(control); + if (!active.scanAdmissionEnabled) { + throw new ConvexError("skills.sh catalog scan admission is disabled"); + } + return active; +} + +function assertFixtureMode(control: Doc<"skillsShCatalogControls"> | null) { + const active = assertCatalogActive(control); + if (active.mode !== "fixture") { + throw new ConvexError("skills.sh fixture work requires fixture controls"); + } + return active; +} + +function assertFixtureRun(run: Doc<"skillsShCatalogRuns">) { + if (run.sourceKind === "staging-live" || run.fixtureId === "skills-sh-test-live-500") { + throw new ConvexError("skills.sh fixture work requires a fixture run"); + } +} + +export const configureFixtureControlInternal = internalMutation({ + args: { + actor: v.string(), + reason: v.string(), + confirm: v.string(), + mode: v.optional(v.union(v.literal("fixture"), v.literal("staging-live"))), + discoveryEnabled: v.boolean(), + writesEnabled: v.boolean(), + scanPlanningEnabled: v.boolean(), + scanAdmissionEnabled: v.boolean(), + maxEntriesPerRun: v.number(), + maxEntriesPerBatch: v.number(), + maxWritesPerBatch: v.number(), + maxPlannedScans: v.number(), + maxScanAdmissionsPerBatch: v.number(), + maxScanAdmissionsPerRun: v.number(), + maxScanAdmissionsPerDay: v.number(), + maxCatalogQueued: v.number(), + maxCatalogInFlight: v.number(), + maxNativeQueued: v.number(), + maxNativeInFlight: v.number(), + realScanAllowlist: v.array(v.string()), + }, + handler: async (ctx, args) => { + const policy = assertSkillsShFixtureEnvironmentAllowed(); + if (args.confirm !== ENABLE_FIXTURE_CONFIRM) { + throw new ConvexError(`Pass confirm="${ENABLE_FIXTURE_CONFIRM}" to enable fixture controls.`); + } + const mode = args.mode ?? "fixture"; + if (mode === "staging-live" && policy.environment !== "test") { + throw new ConvexError( + "skills.sh staging-live controls require the permanent Test environment", + ); + } + assertIntegerInRange("maxEntriesPerRun", args.maxEntriesPerRun, 1, MAX_DISCOVERY_ROWS); + assertIntegerInRange("maxEntriesPerBatch", args.maxEntriesPerBatch, 1, MAX_ENTRIES_PER_BATCH); + assertIntegerInRange("maxWritesPerBatch", args.maxWritesPerBatch, 1, MAX_WRITES_PER_BATCH); + assertIntegerInRange("maxPlannedScans", args.maxPlannedScans, 0, MAX_DISCOVERY_ROWS); + assertIntegerInRange( + "maxScanAdmissionsPerBatch", + args.maxScanAdmissionsPerBatch, + 0, + MAX_SCAN_ADMISSIONS_PER_BATCH, + ); + assertIntegerInRange( + "maxScanAdmissionsPerRun", + args.maxScanAdmissionsPerRun, + 0, + MAX_SCAN_ADMISSIONS_PER_RUN, + ); + assertIntegerInRange( + "maxScanAdmissionsPerDay", + args.maxScanAdmissionsPerDay, + 0, + MAX_SCAN_ADMISSIONS_PER_RUN, + ); + assertIntegerInRange("maxCatalogQueued", args.maxCatalogQueued, 0, MAX_SCAN_ADMISSIONS_PER_RUN); + assertIntegerInRange( + "maxCatalogInFlight", + args.maxCatalogInFlight, + 0, + MAX_SCAN_ADMISSIONS_PER_RUN, + ); + assertIntegerInRange("maxNativeQueued", args.maxNativeQueued, 0, 10_000); + assertIntegerInRange("maxNativeInFlight", args.maxNativeInFlight, 0, 1_000); + if (args.scanPlanningEnabled && args.maxPlannedScans === 0) { + throw new ConvexError("scan planning requires maxPlannedScans greater than zero"); + } + if (args.writesEnabled && args.maxWritesPerBatch < 2) { + throw new ConvexError("catalog writes require maxWritesPerBatch of at least two"); + } + if ( + args.scanAdmissionEnabled && + (args.maxScanAdmissionsPerBatch === 0 || + args.maxScanAdmissionsPerRun === 0 || + args.maxScanAdmissionsPerDay === 0 || + args.maxCatalogQueued === 0) + ) { + throw new ConvexError("scan admission requires non-zero batch, run, day, and queue budgets"); + } + const realScanAllowlist = Array.from( + new Set(args.realScanAllowlist.map((externalId) => externalId.trim().toLowerCase())), + ).filter(Boolean); + if (realScanAllowlist.length > MAX_REAL_TEST_ADMISSIONS) { + throw new ConvexError(`realScanAllowlist cannot exceed ${MAX_REAL_TEST_ADMISSIONS} skills`); + } + if ( + policy.environment === "test" && + (args.maxEntriesPerRun > 500 || + args.maxScanAdmissionsPerRun > MAX_REAL_TEST_ADMISSIONS || + args.maxScanAdmissionsPerDay > MAX_REAL_TEST_ADMISSIONS) + ) { + throw new ConvexError("skills.sh Test controls are capped at 500 discoveries and 10 scans"); + } + + const now = Date.now(); + const existing = await getControlDoc(ctx); + const next = { + mode, + discoveryEnabled: args.discoveryEnabled, + writesEnabled: args.writesEnabled, + scanPlanningEnabled: args.scanPlanningEnabled, + scanAdmissionEnabled: args.scanAdmissionEnabled, + publicVisibilityEnabled: false, + paused: false, + maxEntriesPerRun: args.maxEntriesPerRun, + maxEntriesPerBatch: args.maxEntriesPerBatch, + maxWritesPerBatch: args.maxWritesPerBatch, + maxPlannedScans: args.maxPlannedScans, + maxScanAdmissionsPerBatch: args.maxScanAdmissionsPerBatch, + maxScanAdmissionsPerRun: args.maxScanAdmissionsPerRun, + maxScanAdmissionsPerDay: args.maxScanAdmissionsPerDay, + maxCatalogQueued: args.maxCatalogQueued, + maxCatalogInFlight: args.maxCatalogInFlight, + maxNativeQueued: args.maxNativeQueued, + maxNativeInFlight: args.maxNativeInFlight, + realScanAllowlist, + updatedBy: args.actor.trim(), + reason: args.reason.trim(), + updatedAt: now, + }; + if (existing) await ctx.db.patch(existing._id, next); + else await ctx.db.insert("skillsShCatalogControls", { key: CONTROL_KEY, ...next }); + return { ...next, environment: policy.environment }; + }, +}); + +export const disableCatalogInternal = internalMutation({ + args: { + actor: v.string(), + reason: v.string(), + confirm: v.string(), + }, + handler: async (ctx, args) => { + assertSkillsShCatalogControlMutationAllowed(); + if (args.confirm !== DISABLE_CATALOG_CONFIRM) { + throw new ConvexError(`Pass confirm="${DISABLE_CATALOG_CONFIRM}" to disable the catalog.`); + } + const now = Date.now(); + const existing = await getControlDoc(ctx); + const next = { + ...DEFAULT_CONTROL, + maxEntriesPerRun: existing?.maxEntriesPerRun ?? 0, + maxEntriesPerBatch: existing?.maxEntriesPerBatch ?? 0, + maxWritesPerBatch: existing?.maxWritesPerBatch ?? 0, + maxPlannedScans: existing?.maxPlannedScans ?? 0, + maxScanAdmissionsPerBatch: existing?.maxScanAdmissionsPerBatch ?? 0, + maxScanAdmissionsPerRun: existing?.maxScanAdmissionsPerRun ?? 0, + maxScanAdmissionsPerDay: existing?.maxScanAdmissionsPerDay ?? 0, + maxCatalogQueued: existing?.maxCatalogQueued ?? 0, + maxCatalogInFlight: existing?.maxCatalogInFlight ?? 0, + maxNativeQueued: existing?.maxNativeQueued ?? 0, + maxNativeInFlight: existing?.maxNativeInFlight ?? 0, + updatedBy: args.actor.trim(), + reason: args.reason.trim(), + updatedAt: now, + }; + if (existing) await ctx.db.patch(existing._id, next); + else { + await ctx.db.insert("skillsShCatalogControls", { + key: CONTROL_KEY, + ...next, + updatedBy: args.actor.trim(), + reason: args.reason.trim(), + updatedAt: now, + }); + } + return next; + }, +}); + +export const startFixtureRunInternal = internalMutation({ + args: { + fixtureId: fixtureIdValidator, + actor: v.string(), + reason: v.string(), + dryRun: v.optional(v.boolean()), + }, + handler: async (ctx, args) => { + assertSkillsShFixtureEnvironmentAllowed(); + const control = assertFixtureMode(assertDiscoveryEnabled(await getControlDoc(ctx))); + const fixture = getSkillsShCatalogFixture(args.fixtureId); + const now = Date.now(); + const runId = await ctx.db.insert("skillsShCatalogRuns", { + fixtureId: args.fixtureId, + snapshotId: fixture.snapshotId, + sourceKind: fixture.sourceKind, + ...(fixture.capturedAt ? { sourceCapturedAt: fixture.capturedAt } : {}), + snapshotCaptureFetches: fixture.snapshotCaptureFetches, + dryRun: args.dryRun ?? false, + status: "running", + cursor: 0, + scanCursor: 0, + fixtureLength: fixture.length, + counts: emptyCounts(), + budgets: { + maxEntriesPerRun: control.maxEntriesPerRun, + maxEntriesPerBatch: control.maxEntriesPerBatch, + maxWritesPerBatch: control.maxWritesPerBatch, + maxPlannedScans: control.maxPlannedScans, + maxScanAdmissionsPerBatch: control.maxScanAdmissionsPerBatch, + maxScanAdmissionsPerRun: control.maxScanAdmissionsPerRun, + maxScanAdmissionsPerDay: control.maxScanAdmissionsPerDay, + }, + operations: { + functionCalls: 1, + dbReads: 1, + dbWrites: 1, + }, + actor: args.actor.trim(), + reason: args.reason.trim(), + batchesProcessed: 0, + scanAdmissionBatches: 0, + lastBatchWrites: 1, + lastBatchReads: 1, + startedAt: now, + updatedAt: now, + }); + return { runId }; + }, +}); + +export const startStagingLiveRunInternal = internalMutation({ + args: { + actor: v.string(), + reason: v.string(), + snapshotId: v.string(), + sourceCapturedAt: v.string(), + snapshotCaptureFetches: v.number(), + fixtureLength: v.number(), + }, + handler: async (ctx, args) => { + const environment = assertSkillsShFixtureEnvironmentAllowed(); + const control = assertDiscoveryEnabled(await getControlDoc(ctx)); + if (environment.environment !== "test" || control.mode !== "staging-live") { + throw new ConvexError("skills.sh live runs require permanent Test staging-live controls"); + } + if (args.fixtureLength !== 500) { + throw new ConvexError("skills.sh live Test runs require exactly 500 rows"); + } + assertIntegerInRange("snapshotCaptureFetches", args.snapshotCaptureFetches, 1, 2_000); + const now = Date.now(); + const runId = await ctx.db.insert("skillsShCatalogRuns", { + fixtureId: "skills-sh-test-live-500", + snapshotId: args.snapshotId.trim(), + sourceKind: "staging-live", + sourceCapturedAt: args.sourceCapturedAt, + snapshotCaptureFetches: args.snapshotCaptureFetches, + dryRun: false, + status: "running", + cursor: 0, + scanCursor: 0, + fixtureLength: args.fixtureLength, + counts: emptyCounts(), + budgets: { + maxEntriesPerRun: control.maxEntriesPerRun, + maxEntriesPerBatch: control.maxEntriesPerBatch, + maxWritesPerBatch: control.maxWritesPerBatch, + maxPlannedScans: control.maxPlannedScans, + maxScanAdmissionsPerBatch: control.maxScanAdmissionsPerBatch, + maxScanAdmissionsPerRun: control.maxScanAdmissionsPerRun, + maxScanAdmissionsPerDay: control.maxScanAdmissionsPerDay, + }, + operations: { + functionCalls: 1, + dbReads: 1, + dbWrites: 1, + }, + actor: args.actor.trim(), + reason: args.reason.trim(), + batchesProcessed: 0, + scanAdmissionBatches: 0, + lastBatchWrites: 1, + lastBatchReads: 1, + startedAt: now, + updatedAt: now, + }); + return { runId }; + }, +}); + +export const processStagingLiveBatchInternal = internalMutation({ + args: { + runId: v.id("skillsShCatalogRuns"), + cursor: v.number(), + rows: v.array(stagingLiveRowValidator), + }, + handler: async (ctx, args) => { + const environment = assertSkillsShFixtureEnvironmentAllowed(); + const control = await getControlDoc(ctx); + const run = await ctx.db.get(args.runId); + if (!run) throw new ConvexError("skills.sh catalog run not found"); + if ( + environment.environment !== "test" || + control?.mode !== "staging-live" || + run.sourceKind !== "staging-live" || + run.fixtureId !== "skills-sh-test-live-500" + ) { + throw new ConvexError("skills.sh live batch requires permanent Test staging-live state"); + } + assertDiscoveryEnabled(control); + assertWritesEnabled(control); + if (run.status === "paused") throw new ConvexError("skills.sh catalog run is paused"); + if (run.status !== "running") return summarizeRun(run); + if (args.cursor !== run.cursor) { + throw new ConvexError(`skills.sh live batch cursor mismatch: expected ${run.cursor}`); + } + assertIntegerInRange("rows.length", args.rows.length, 1, run.budgets.maxEntriesPerBatch); + if (args.cursor + args.rows.length > run.fixtureLength) { + throw new ConvexError("skills.sh live batch exceeds the declared 500-row snapshot"); + } + + let cursor = run.cursor; + let writesUsed = 0; + let readsUsed = 2; + const counts = { ...run.counts }; + const now = Date.now(); + for (const inputRow of args.rows) { + if (counts.observed >= run.budgets.maxEntriesPerRun) break; + const row = normalizeIdentity(inputRow); + if (row.externalId !== inputRow.externalId.trim().toLowerCase()) { + throw new ConvexError(`skills.sh live row identity mismatch: ${inputRow.externalId}`); + } + const existing = await ctx.db + .query("skillsShCatalogEntries") + .withIndex("by_external_id", (q) => q.eq("externalId", row.externalId)) + .unique(); + readsUsed += 1; + if (existing && fixtureObservationConflicts(existing, row, "staging-live")) { + counts.observed += 1; + counts.rejected += 1; + cursor += 1; + continue; + } + const observationUnchanged = existing ? sameFixtureObservation(existing, row) : false; + const contentChanged = existing + ? existing.sourceContentHash !== row.sourceContentHash + : false; + const existingAttempt = + existing && control.scanPlanningEnabled + ? await ctx.db + .query("skillsShCatalogScanAttempts") + .withIndex("by_entry_and_source_content_hash", (q) => + q.eq("entryId", existing._id).eq("sourceContentHash", row.sourceContentHash), + ) + .filter((q) => q.neq(q.field("status"), "canceled")) + .order("desc") + .first() + : null; + if (existing && control.scanPlanningEnabled) readsUsed += 1; + const shouldPlanScan = + control.scanPlanningEnabled && + counts.scansPlanned < run.budgets.maxPlannedScans && + // Exact-hash terminal attempts are durable. Only canceled work is eligible + // for automatic replanning; failed hashes require an explicit retry policy. + !existingAttempt && + (!existing || contentChanged || existing.scanStatus !== "planned"); + if (writesUsed + 2 > run.budgets.maxWritesPerBatch) break; + + counts.observed += 1; + cursor += 1; + if (shouldPlanScan) counts.scansPlanned += 1; + if (existing) { + if (observationUnchanged) counts.unchanged += 1; + else { + counts.wouldUpdate += 1; + counts.updated += 1; + } + await ctx.db.patch(existing._id, { + sourceKind: "staging-live", + githubOwnerId: row.githubOwnerId, + owner: row.owner, + repo: row.repo, + slug: row.slug, + displayName: row.displayName, + sourceUrl: row.sourceUrl, + githubRepoUrl: row.githubRepoUrl, + sourceContentHash: row.sourceContentHash, + installs: row.installs, + sourceSnapshotId: run.snapshotId, + publicVisible: false, + scanStatus: shouldPlanScan + ? "planned" + : contentChanged && existingAttempt + ? scanStatusFromAttempt(existingAttempt) + : contentChanged + ? "not-planned" + : existing.scanStatus, + lastObservedAt: now, + updatedAt: now, + }); + } else { + counts.wouldInsert += 1; + counts.inserted += 1; + await ctx.db.insert("skillsShCatalogEntries", { + externalId: row.externalId, + sourceKind: "staging-live", + githubOwnerId: row.githubOwnerId, + owner: row.owner, + repo: row.repo, + slug: row.slug, + displayName: row.displayName, + sourceUrl: row.sourceUrl, + githubRepoUrl: row.githubRepoUrl, + sourceContentHash: row.sourceContentHash, + installs: row.installs, + sourceSnapshotId: run.snapshotId, + publicVisible: false, + scanStatus: shouldPlanScan ? "planned" : "not-planned", + firstObservedAt: now, + lastObservedAt: now, + createdAt: now, + updatedAt: now, + }); + } + writesUsed += 1; + } + + if (cursor !== args.cursor + args.rows.length) { + throw new ConvexError("skills.sh live batch exceeded its write or discovery budget"); + } + const completed = cursor >= run.fixtureLength; + const budgetExhausted = !completed && counts.observed >= run.budgets.maxEntriesPerRun; + const terminal = completed || budgetExhausted; + const patch = { + cursor, + counts, + status: completed + ? ("completed" as const) + : budgetExhausted + ? ("budget-exhausted" as const) + : ("running" as const), + completedAt: terminal ? now : undefined, + batchesProcessed: run.batchesProcessed + 1, + lastBatchWrites: writesUsed + 1, + lastBatchReads: readsUsed, + operations: addOperations(run.operations, { + functionCalls: 1, + dbReads: readsUsed, + dbWrites: writesUsed + 1, + }), + updatedAt: now, + }; + await ctx.db.patch(run._id, patch); + return summarizeRun({ ...run, ...patch }); + }, +}); + +export const processFixtureBatchInternal = internalMutation({ + args: { + runId: v.id("skillsShCatalogRuns"), + }, + handler: async (ctx, args) => { + assertSkillsShFixtureEnvironmentAllowed(); + const control = await getControlDoc(ctx); + const run = await ctx.db.get(args.runId); + if (!run) throw new ConvexError("skills.sh catalog run not found"); + assertFixtureMode(control); + assertFixtureRun(run); + assertDiscoveryEnabled(control); + if (!run.dryRun) assertWritesEnabled(control); + if (run.status === "paused") throw new ConvexError("skills.sh catalog run is paused"); + if (run.status !== "running") return summarizeRun(run); + + if (run.fixtureId === "skills-sh-test-live-500") { + throw new ConvexError("skills.sh fixture work requires a fixture run"); + } + const fixture = getSkillsShCatalogFixture(run.fixtureId); + let cursor = run.cursor; + let writesUsed = 0; + let readsUsed = 2; + let entriesProcessed = 0; + const counts = { ...run.counts }; + const now = Date.now(); + + while ( + cursor < fixture.length && + counts.observed < run.budgets.maxEntriesPerRun && + entriesProcessed < run.budgets.maxEntriesPerBatch + ) { + const row = normalizeIdentity(fixture.rowAt(cursor)); + const existing = await ctx.db + .query("skillsShCatalogEntries") + .withIndex("by_external_id", (q) => q.eq("externalId", row.externalId)) + .unique(); + readsUsed += 1; + if (existing && fixtureObservationConflicts(existing, row, fixture.sourceKind)) { + counts.observed += 1; + counts.rejected += 1; + cursor += 1; + entriesProcessed += 1; + continue; + } + + const observationUnchanged = existing ? sameFixtureObservation(existing, row) : false; + const contentChanged = existing + ? existing.sourceContentHash !== row.sourceContentHash + : false; + const existingAttempt = + existing && control?.scanPlanningEnabled + ? await ctx.db + .query("skillsShCatalogScanAttempts") + .withIndex("by_entry_and_source_content_hash", (q) => + q.eq("entryId", existing._id).eq("sourceContentHash", row.sourceContentHash), + ) + .filter((q) => q.neq(q.field("status"), "canceled")) + .order("desc") + .first() + : null; + if (existing && control?.scanPlanningEnabled) readsUsed += 1; + const shouldPlanScan = + Boolean(control?.scanPlanningEnabled) && + counts.scansPlanned < run.budgets.maxPlannedScans && + // Exact-hash terminal attempts are durable. Only canceled work is eligible + // for automatic replanning; failed hashes require an explicit retry policy. + !existingAttempt && + (!existing || contentChanged || existing.scanStatus !== "planned"); + const entryWriteRequired = !run.dryRun; + if (entryWriteRequired && writesUsed + 2 > run.budgets.maxWritesPerBatch) break; + + counts.observed += 1; + cursor += 1; + entriesProcessed += 1; + if (shouldPlanScan) counts.scansPlanned += 1; + if (existing) { + if (observationUnchanged) counts.unchanged += 1; + else { + counts.wouldUpdate += 1; + if (!run.dryRun) counts.updated += 1; + } + if (entryWriteRequired) { + await ctx.db.patch(existing._id, { + sourceKind: fixture.sourceKind, + githubOwnerId: row.githubOwnerId, + owner: row.owner, + repo: row.repo, + slug: row.slug, + displayName: row.displayName, + sourceUrl: row.sourceUrl, + githubRepoUrl: row.githubRepoUrl, + sourceContentHash: row.sourceContentHash, + installs: row.installs, + sourceSnapshotId: fixture.snapshotId, + // This gate has no publication seam; every catalog write reasserts dark visibility. + publicVisible: false, + scanStatus: shouldPlanScan + ? "planned" + : contentChanged && existingAttempt + ? scanStatusFromAttempt(existingAttempt) + : contentChanged + ? "not-planned" + : existing.scanStatus, + lastObservedAt: now, + updatedAt: now, + }); + writesUsed += 1; + } + continue; + } + + counts.wouldInsert += 1; + if (!run.dryRun) { + await ctx.db.insert("skillsShCatalogEntries", { + externalId: row.externalId, + sourceKind: fixture.sourceKind, + githubOwnerId: row.githubOwnerId, + owner: row.owner, + repo: row.repo, + slug: row.slug, + displayName: row.displayName, + sourceUrl: row.sourceUrl, + githubRepoUrl: row.githubRepoUrl, + sourceContentHash: row.sourceContentHash, + installs: row.installs, + sourceSnapshotId: fixture.snapshotId, + publicVisible: false, + scanStatus: shouldPlanScan ? "planned" : "not-planned", + firstObservedAt: now, + lastObservedAt: now, + createdAt: now, + updatedAt: now, + }); + writesUsed += 1; + counts.inserted += 1; + } + } + + const completed = cursor >= fixture.length; + const budgetExhausted = !completed && counts.observed >= run.budgets.maxEntriesPerRun; + const terminal = completed || budgetExhausted; + const batchWrites = writesUsed + 1; + const patch = { + cursor, + counts, + status: completed + ? ("completed" as const) + : budgetExhausted + ? ("budget-exhausted" as const) + : ("running" as const), + completedAt: terminal ? now : undefined, + batchesProcessed: run.batchesProcessed + 1, + lastBatchWrites: batchWrites, + lastBatchReads: readsUsed, + operations: addOperations(run.operations, { + functionCalls: 1, + dbReads: readsUsed, + dbWrites: batchWrites, + }), + updatedAt: now, + }; + await ctx.db.patch(run._id, patch); + return summarizeRun({ ...run, ...patch }); + }, +}); + +export const setFixtureRunPausedInternal = internalMutation({ + args: { + runId: v.id("skillsShCatalogRuns"), + paused: v.boolean(), + }, + handler: async (ctx, args) => { + assertSkillsShFixtureEnvironmentAllowed(); + const run = await ctx.db.get(args.runId); + if (!run) throw new ConvexError("skills.sh catalog run not found"); + if ( + run.status === "completed" || + run.status === "budget-exhausted" || + run.status === "failed" || + run.status === "canceling" || + run.status === "canceled" + ) { + throw new ConvexError(`Cannot change pause state for ${run.status} run`); + } + if (!args.paused) assertDiscoveryEnabled(await getControlDoc(ctx)); + const status = args.paused ? ("paused" as const) : ("running" as const); + await ctx.db.patch(run._id, { + status, + operations: addOperations(run.operations, { + functionCalls: 1, + dbReads: args.paused ? 1 : 2, + dbWrites: 1, + }), + updatedAt: Date.now(), + }); + return { runId: run._id, status }; + }, +}); + +async function readQueueHealth(ctx: MutationCtx, control: Doc<"skillsShCatalogControls">) { + const nativeSources = ["publish", "vt-update", "backfill", "bulk-rescan", "manual"] as const; + const [nativeQueuedBySource, nativeRunningBySource, catalogQueued, catalogRunning] = + await Promise.all([ + Promise.all( + nativeSources.map(async (source) => + ctx.db + .query("securityScanJobs") + .withIndex("by_status_source_created_at", (q) => + q.eq("status", "queued").eq("source", source), + ) + .take(control.maxNativeQueued + 1), + ), + ), + Promise.all( + nativeSources.map(async (source) => + ctx.db + .query("securityScanJobs") + .withIndex("by_status_source_created_at", (q) => + q.eq("status", "running").eq("source", source), + ) + .take(control.maxNativeInFlight + 1), + ), + ), + ctx.db + .query("skillsShCatalogScanAttempts") + .withIndex("by_status_and_created_at", (q) => q.eq("status", "queued")) + .take(control.maxCatalogQueued + 1), + ctx.db + .query("skillsShCatalogScanAttempts") + .withIndex("by_status_and_created_at", (q) => q.eq("status", "running")) + .take(control.maxCatalogInFlight + 1), + ]); + const nativeQueued = Math.min( + control.maxNativeQueued + 1, + nativeQueuedBySource.reduce((count, jobs) => count + jobs.length, 0), + ); + const nativeInFlight = Math.min( + control.maxNativeInFlight + 1, + nativeRunningBySource.reduce((count, jobs) => count + jobs.length, 0), + ); + return { + nativeQueued, + nativeInFlight, + catalogQueued: catalogQueued.length, + catalogInFlight: catalogRunning.length, + healthy: + nativeQueued <= control.maxNativeQueued && + nativeInFlight <= control.maxNativeInFlight && + catalogQueued.length <= control.maxCatalogQueued && + catalogRunning.length <= control.maxCatalogInFlight, + }; +} + +type AdmitScansArgs = { + runId: Id<"skillsShCatalogRuns">; + externalIds: string[]; + dispatchKind: "deterministic" | "real"; + actorUserId?: Id<"users">; + artifacts?: StagingLiveArtifact[]; +}; + +async function admitScans(ctx: MutationCtx, args: AdmitScansArgs) { + const environment = assertSkillsShFixtureEnvironmentAllowed(); + const control = assertScanAdmissionEnabled(await getControlDoc(ctx)); + const run = await ctx.db.get(args.runId); + if (!run) throw new ConvexError("skills.sh catalog run not found"); + if ( + run.status === "paused" || + run.status === "canceling" || + run.status === "canceled" || + run.status === "failed" + ) { + throw new ConvexError(`Cannot admit scans for ${run.status} run`); + } + const externalIds = Array.from( + new Set(args.externalIds.map((externalId) => externalId.trim().toLowerCase())), + ).filter(Boolean); + const effectiveBatchAdmissionLimit = Math.min( + run.budgets.maxScanAdmissionsPerBatch, + control.maxScanAdmissionsPerBatch, + ); + assertIntegerInRange("externalIds.length", externalIds.length, 1, effectiveBatchAdmissionLimit); + if (args.dispatchKind === "deterministic") { + assertFixtureMode(control); + assertFixtureRun(run); + } else { + if (control.mode !== "staging-live") { + throw new ConvexError("real skills.sh scan admission requires staging-live controls"); + } + if (run.sourceKind !== "staging-live" || run.fixtureId !== "skills-sh-test-live-500") { + throw new ConvexError("real skills.sh scan admission requires a staging-live run"); + } + if (environment.environment !== "test") { + throw new ConvexError( + "real skills.sh scan admission requires the permanent Test environment", + ); + } + if (externalIds.length > MAX_REAL_TEST_ADMISSIONS) { + throw new ConvexError(`real Test scan admission cannot exceed ${MAX_REAL_TEST_ADMISSIONS}`); + } + const allowlist = new Set(control.realScanAllowlist); + const denied = externalIds.find((externalId) => !allowlist.has(externalId)); + if (denied) throw new ConvexError(`real Test scan admission is not allowlisted: ${denied}`); + if (!args.actorUserId) { + throw new ConvexError("real Test scan admission requires an authenticated operator"); + } + const actor = await ctx.db.get(args.actorUserId); + if (actor?.role !== "admin") { + throw new ConvexError("real Test scan admission requires an admin operator"); + } + } + const artifactInputs = args.artifacts ?? []; + const artifacts = new Map( + artifactInputs.map((artifact) => [artifact.externalId.trim().toLowerCase(), artifact]), + ); + if ( + args.dispatchKind === "real" && + (artifactInputs.length !== externalIds.length || artifacts.size !== externalIds.length) + ) { + throw new ConvexError("real Test scan admission requires exactly one artifact per skill"); + } + + const queueHealth = await readQueueHealth(ctx, control); + if (!queueHealth.healthy) { + throw new ConvexError("skills.sh catalog scan admission is blocked by queue health"); + } + const dayStart = new Date(); + dayStart.setUTCHours(0, 0, 0, 0); + const effectiveDailyAdmissionLimit = Math.min( + run.budgets.maxScanAdmissionsPerDay, + control.maxScanAdmissionsPerDay, + ); + const effectiveRunAdmissionLimit = Math.min( + run.budgets.maxScanAdmissionsPerRun, + control.maxScanAdmissionsPerRun, + ); + const admittedToday = await ctx.db + .query("skillsShCatalogScanAttempts") + .withIndex("by_created_at", (q) => q.gte("createdAt", dayStart.getTime())) + .take(effectiveDailyAdmissionLimit + 1); + + const fixture = + run.fixtureId === "skills-sh-test-live-500" ? null : getSkillsShCatalogFixture(run.fixtureId); + const now = Date.now(); + let admitted = 0; + let skipped = 0; + const admittedExternalIds: string[] = []; + let readsUsed = args.dispatchKind === "real" ? 8 : 7; + let writesUsed = 0; + const runWriteCount = 1; + for (const externalId of externalIds) { + const fixtureRow = fixture?.findByExternalId(externalId); + const sourceRow = fixtureRow ? normalizeIdentity(fixtureRow) : null; + if (!sourceRow && run.sourceKind !== "staging-live") { + skipped += 1; + continue; + } + const entry = await ctx.db + .query("skillsShCatalogEntries") + .withIndex("by_external_id", (q) => q.eq("externalId", externalId)) + .unique(); + readsUsed += 1; + if ( + !entry || + (sourceRow && entry.sourceContentHash !== sourceRow.sourceContentHash) || + (run.sourceKind === "staging-live" && entry.sourceSnapshotId !== run.snapshotId) || + entry.scanStatus !== "planned" + ) { + skipped += 1; + continue; + } + const existingAttempt = await ctx.db + .query("skillsShCatalogScanAttempts") + .withIndex("by_entry_and_source_content_hash", (q) => + q.eq("entryId", entry._id).eq("sourceContentHash", entry.sourceContentHash), + ) + .filter((q) => q.neq(q.field("status"), "canceled")) + .order("desc") + .first(); + readsUsed += 1; + if (existingAttempt) { + skipped += 1; + continue; + } + if (run.counts.scansAdmitted + admitted >= effectiveRunAdmissionLimit) { + throw new ConvexError("skills.sh catalog run scan-admission budget exceeded"); + } + if (admittedToday.length + admitted >= effectiveDailyAdmissionLimit) { + throw new ConvexError("skills.sh catalog daily scan-admission budget exceeded"); + } + if (queueHealth.catalogQueued + admitted >= control.maxCatalogQueued) { + throw new ConvexError("skills.sh catalog queued-scan budget exceeded"); + } + const artifact = artifacts.get(externalId); + if (args.dispatchKind === "real") { + if ( + !artifact || + !/^[a-f0-9]{64}$/i.test(artifact.artifactContentHash) || + artifact.files.length === 0 + ) { + throw new ConvexError( + `real Test scan admission requires a fetched artifact: ${externalId}`, + ); + } + } + // skillScanRequests embeds its validated file manifest in one document, so file count + // does not change the six real-admission writes before the final run patch. + const admissionWriteCost = args.dispatchKind === "real" ? 6 : 2; + if (writesUsed + admissionWriteCost + runWriteCount > run.budgets.maxWritesPerBatch) { + throw new ConvexError("skills.sh catalog scan-admission write budget exceeded"); + } + const attemptId = await insertCatalogScanAttempt(ctx, { + entryId: entry._id, + runId: run._id, + externalId, + sourceContentHash: entry.sourceContentHash, + dispatchKind: args.dispatchKind, + artifactContentHash: artifact?.artifactContentHash.toLowerCase(), + now, + }); + if (args.dispatchKind === "real" && args.actorUserId && artifact) { + const linked = await enqueueSkillsShCatalogScanRequest(ctx, { + actorUserId: args.actorUserId, + attemptId, + slug: entry.slug, + displayName: entry.displayName, + artifactContentHash: artifact.artifactContentHash.toLowerCase(), + files: artifact.files, + }); + await ctx.db.patch(attemptId, { + skillScanRequestId: linked.requestId, + securityScanJobId: linked.jobId, + updatedAt: now, + }); + writesUsed += 4; + } + await ctx.db.patch(entry._id, { + scanStatus: "queued", + publicVisible: false, + updatedAt: now, + }); + writesUsed += 2; + admitted += 1; + admittedExternalIds.push(externalId); + } + const nextCounts = { + ...run.counts, + scansAdmitted: run.counts.scansAdmitted + admitted, + }; + await ctx.db.patch(run._id, { + counts: nextCounts, + scanCursor: run.scanCursor + admitted, + scanAdmissionBatches: run.scanAdmissionBatches + 1, + operations: addOperations(run.operations, { + functionCalls: 1, + dbReads: readsUsed, + dbWrites: writesUsed + runWriteCount, + }), + updatedAt: now, + }); + return { + requested: externalIds.length, + admitted, + skipped, + admittedExternalIds, + queueHealth, + counts: nextCounts, + }; +} + +export const admitFixtureScansInternal = internalMutation({ + args: { + runId: v.id("skillsShCatalogRuns"), + externalIds: v.array(v.string()), + dispatchKind: dispatchKindValidator, + actorUserId: v.optional(v.id("users")), + artifacts: v.optional(v.array(stagingLiveArtifactValidator)), + }, + handler: async (ctx, args) => { + if (args.dispatchKind !== "deterministic") { + throw new ConvexError("real skills.sh scan admission requires stored artifact validation"); + } + return await admitScans(ctx, args); + }, +}); + +export const admitValidatedRealScansInternal = internalMutation({ + args: { + runId: v.id("skillsShCatalogRuns"), + externalIds: v.array(v.string()), + actorUserId: v.id("users"), + artifacts: v.array(stagingLiveArtifactValidator), + }, + handler: async (ctx, args) => { + return await admitScans(ctx, { + ...args, + dispatchKind: "real", + }); + }, +}); + +export const admitRealScansInternal: ReturnType = internalAction({ + args: { + runId: v.id("skillsShCatalogRuns"), + externalIds: v.array(v.string()), + actorUserId: v.id("users"), + artifacts: v.array(stagingLiveArtifactValidator), + }, + handler: async (ctx, args) => { + const environment = assertSkillsShFixtureEnvironmentAllowed(); + if (environment.environment !== "test") { + throw new ConvexError( + "real skills.sh scan admission requires the permanent Test environment", + ); + } + const externalIds = Array.from( + new Set(args.externalIds.map((externalId) => externalId.trim().toLowerCase())), + ).filter(Boolean); + assertIntegerInRange("externalIds.length", externalIds.length, 1, MAX_REAL_TEST_ADMISSIONS); + const artifactInputs = args.artifacts; + const artifacts = new Map( + artifactInputs.map((artifact) => [artifact.externalId.trim().toLowerCase(), artifact]), + ); + if (artifactInputs.length !== externalIds.length || artifacts.size !== externalIds.length) { + throw new ConvexError("real Test scan admission requires exactly one artifact per skill"); + } + const validatedArtifacts = await validateRealScanArtifacts(ctx, externalIds, artifacts); + return await ctx.runMutation(internal.skillsShCatalog.admitValidatedRealScansInternal, { + runId: args.runId, + externalIds, + actorUserId: args.actorUserId, + artifacts: Array.from(validatedArtifacts.values()), + }); + }, +}); + +export const markScanAttemptRunningInternal = internalMutation({ + args: { + attemptId: v.id("skillsShCatalogScanAttempts"), + }, + handler: async (ctx, args) => { + assertSkillsShFixtureEnvironmentAllowed(); + const control = assertScanAdmissionEnabled(await getControlDoc(ctx)); + const queueHealth = await readQueueHealth(ctx, control); + if (!queueHealth.healthy || queueHealth.catalogInFlight >= control.maxCatalogInFlight) { + throw new ConvexError("skills.sh catalog scan start is blocked by queue health"); + } + const attempt = await ctx.db.get(args.attemptId); + if (!attempt || attempt.status !== "queued") { + return { started: false }; + } + const run = await ctx.db.get(attempt.runId); + if (attempt.dispatchKind === "deterministic") { + assertFixtureMode(control); + if (!run) throw new ConvexError("skills.sh fixture scan run not found"); + assertFixtureRun(run); + } + if ( + run && + (run.status === "paused" || + run.status === "canceling" || + run.status === "canceled" || + run.status === "failed") + ) { + throw new ConvexError(`Cannot start scan for ${run.status} run`); + } + const now = Date.now(); + await ctx.db.patch(attempt._id, { status: "running", updatedAt: now }); + if (run) { + await ctx.db.patch(run._id, { + operations: addOperations(run.operations, { + functionCalls: 1, + dbReads: 7, + dbWrites: 2, + }), + updatedAt: now, + }); + } + return { started: true }; + }, +}); + +export const completeDeterministicScansInternal = internalMutation({ + args: { + runId: v.id("skillsShCatalogRuns"), + limit: v.number(), + }, + handler: async (ctx, args) => { + assertSkillsShFixtureEnvironmentAllowed(); + const control = assertScanAdmissionEnabled(await getControlDoc(ctx)); + assertIntegerInRange("limit", args.limit, 1, MAX_DETERMINISTIC_COMPLETIONS_PER_BATCH); + const run = await ctx.db.get(args.runId); + if (!run) throw new ConvexError("skills.sh catalog run not found"); + assertFixtureMode(control); + assertFixtureRun(run); + if ( + run.status === "paused" || + run.status === "canceling" || + run.status === "canceled" || + run.status === "failed" + ) { + throw new ConvexError(`Cannot complete scans for ${run.status} run`); + } + const queueHealth = await readQueueHealth(ctx, control); + if (!queueHealth.healthy) { + throw new ConvexError("skills.sh deterministic scan completion is blocked by queue health"); + } + const attempts = await ctx.db + .query("skillsShCatalogScanAttempts") + .withIndex("by_run_dispatch_kind_status_created_at", (q) => + q.eq("runId", run._id).eq("dispatchKind", "deterministic").eq("status", "queued"), + ) + .order("asc") + .take(args.limit); + const now = Date.now(); + let completed = 0; + let canceled = 0; + let readsUsed = 7; + let writesUsed = 0; + for (const attempt of attempts) { + const entry = await ctx.db.get(attempt.entryId); + readsUsed += 1; + if (!entry || entry.sourceContentHash !== attempt.sourceContentHash) { + await ctx.db.patch(attempt._id, { + status: "canceled", + completedAt: now, + updatedAt: now, + }); + writesUsed += 1; + canceled += 1; + continue; + } + await ctx.db.patch(attempt._id, { + status: "succeeded", + verdict: "clean", + completedAt: now, + updatedAt: now, + }); + await ctx.db.patch(entry._id, { + scanStatus: "clean", + publicVisible: false, + updatedAt: now, + }); + writesUsed += 2; + completed += 1; + } + const counts = { + ...run.counts, + scansCompleted: run.counts.scansCompleted + completed, + scansCanceled: run.counts.scansCanceled + canceled, + }; + await ctx.db.patch(run._id, { + counts, + operations: addOperations(run.operations, { + functionCalls: 1, + dbReads: readsUsed, + dbWrites: writesUsed + 1, + }), + updatedAt: now, + }); + return { + matched: attempts.length, + completed, + canceled, + counts, + queueHealth, + }; + }, +}); + +export const recordRealScanResultInternal = internalMutation({ + args: { + attemptId: v.id("skillsShCatalogScanAttempts"), + artifactContentHash: v.string(), + verdict: scanVerdictValidator, + }, + handler: async (ctx, args) => { + const environment = assertSkillsShFixtureEnvironmentAllowed(); + if (environment.environment !== "test") { + throw new ConvexError("real skills.sh scan results require the permanent Test environment"); + } + const attempt = await ctx.db.get(args.attemptId); + if (!attempt) throw new ConvexError("skills.sh real scan attempt not found"); + if (attempt.dispatchKind !== "real") { + throw new ConvexError("skills.sh real scan callback requires a real attempt"); + } + if (attempt.status !== "queued" && attempt.status !== "running") { + return { applied: false, reason: "attempt-not-active" }; + } + if ( + !attempt.artifactContentHash || + attempt.artifactContentHash !== args.artifactContentHash.toLowerCase() + ) { + throw new ConvexError("skills.sh real scan artifact hash mismatch"); + } + const run = await ctx.db.get(attempt.runId); + if (!run) throw new ConvexError("skills.sh real scan run not found"); + if (run.status === "canceling" || run.status === "canceled") { + const now = Date.now(); + const canceledOperations = await cancelAttempt(ctx, attempt, now, { + allowRunningRealJob: true, + }); + const hasMore = await hasActiveScanAttemptsForRun(ctx, run._id); + await ctx.db.patch(run._id, { + status: hasMore ? "canceling" : "canceled", + counts: { + ...run.counts, + scansCanceled: run.counts.scansCanceled + (canceledOperations.canceled ? 1 : 0), + }, + operations: addOperations(run.operations, { + functionCalls: 1, + dbReads: 4 + canceledOperations.dbReads, + dbWrites: 1 + canceledOperations.dbWrites, + }), + updatedAt: now, + }); + return { applied: false, reason: "run-canceled" }; + } + const entry = await ctx.db.get(attempt.entryId); + if (!entry || entry.sourceContentHash !== attempt.sourceContentHash) { + const now = Date.now(); + await ctx.db.patch(attempt._id, { + status: "canceled", + completedAt: now, + updatedAt: now, + }); + await ctx.db.patch(run._id, { + counts: { + ...run.counts, + scansCanceled: run.counts.scansCanceled + 1, + }, + operations: addOperations(run.operations, { + functionCalls: 1, + dbReads: 3, + dbWrites: 2, + }), + updatedAt: now, + }); + return { applied: false, reason: "stale-attempt" }; + } + const now = Date.now(); + const succeeded = args.verdict !== "failed"; + await ctx.db.patch(attempt._id, { + status: succeeded ? "succeeded" : "failed", + verdict: args.verdict, + completedAt: now, + updatedAt: now, + }); + await ctx.db.patch(entry._id, { + scanStatus: args.verdict, + publicVisible: false, + updatedAt: now, + }); + await ctx.db.patch(run._id, { + counts: { + ...run.counts, + scansCompleted: run.counts.scansCompleted + 1, + }, + operations: addOperations(run.operations, { + functionCalls: 1, + dbReads: 3, + dbWrites: 3, + }), + updatedAt: now, + }); + return { applied: true, publicVisible: false }; + }, +}); + +export const recordFixtureScanResultInternal = internalMutation({ + args: { + attemptId: v.id("skillsShCatalogScanAttempts"), + sourceContentHash: v.string(), + verdict: scanVerdictValidator, + }, + handler: async (ctx, args) => { + assertSkillsShFixtureEnvironmentAllowed(); + assertFixtureMode(await getControlDoc(ctx)); + const attempt = await ctx.db.get(args.attemptId); + if (!attempt) throw new ConvexError("skills.sh fixture scan attempt not found"); + if (attempt.status !== "queued" && attempt.status !== "running") { + return { applied: false, reason: "attempt-not-active" }; + } + if (attempt.dispatchKind !== "deterministic") { + throw new ConvexError("real skills.sh scans require the external artifact-fetch integration"); + } + if (attempt.sourceContentHash !== args.sourceContentHash) { + throw new ConvexError("skills.sh fixture source observation hash mismatch"); + } + const run = await ctx.db.get(attempt.runId); + if (!run) throw new ConvexError("skills.sh fixture scan run not found"); + assertFixtureRun(run); + if (run.status === "canceling" || run.status === "canceled") { + const now = Date.now(); + const canceledOperations = await cancelAttempt(ctx, attempt, now); + const hasMore = await hasActiveScanAttemptsForRun(ctx, run._id); + await ctx.db.patch(run._id, { + status: hasMore ? "canceling" : "canceled", + counts: { + ...run.counts, + scansCanceled: run.counts.scansCanceled + (canceledOperations.canceled ? 1 : 0), + }, + operations: addOperations(run.operations, { + functionCalls: 1, + dbReads: 4 + canceledOperations.dbReads, + dbWrites: 1 + canceledOperations.dbWrites, + }), + updatedAt: now, + }); + return { applied: false, reason: "run-canceled" }; + } + const entry = await ctx.db.get(attempt.entryId); + if (!entry || entry.sourceContentHash !== args.sourceContentHash) { + const now = Date.now(); + await ctx.db.patch(attempt._id, { + status: "canceled", + completedAt: now, + updatedAt: now, + }); + await ctx.db.patch(run._id, { + counts: { + ...run.counts, + scansCanceled: run.counts.scansCanceled + 1, + }, + operations: addOperations(run.operations, { + functionCalls: 1, + dbReads: 3, + dbWrites: 2, + }), + updatedAt: now, + }); + return { applied: false, reason: "stale-attempt" }; + } + + const now = Date.now(); + const succeeded = args.verdict !== "failed"; + await ctx.db.patch(attempt._id, { + status: succeeded ? "succeeded" : "failed", + verdict: args.verdict, + completedAt: now, + updatedAt: now, + }); + await ctx.db.patch(entry._id, { + scanStatus: args.verdict, + publicVisible: false, + updatedAt: now, + }); + await ctx.db.patch(run._id, { + counts: { + ...run.counts, + scansCompleted: run.counts.scansCompleted + 1, + }, + operations: addOperations(run.operations, { + functionCalls: 1, + dbReads: 3, + dbWrites: 3, + }), + updatedAt: now, + }); + return { applied: true, publicVisible: false }; + }, +}); + +export const cancelCatalogRunInternal = internalMutation({ + args: { + runId: v.id("skillsShCatalogRuns"), + limit: v.number(), + }, + handler: async (ctx, args) => { + assertSkillsShFixtureEnvironmentAllowed(); + assertIntegerInRange("limit", args.limit, 1, 100); + const run = await ctx.db.get(args.runId); + if (!run) throw new ConvexError("skills.sh catalog run not found"); + const [queued, running] = await Promise.all([ + ctx.db + .query("skillsShCatalogScanAttempts") + .withIndex("by_run_and_status", (q) => q.eq("runId", run._id).eq("status", "queued")) + .take(args.limit + 1), + ctx.db + .query("skillsShCatalogScanAttempts") + .withIndex("by_run_and_status", (q) => q.eq("runId", run._id).eq("status", "running")) + .take(args.limit + 1), + ]); + const active = [...queued, ...running].slice(0, args.limit); + const now = Date.now(); + let attemptReads = 0; + let attemptWrites = 0; + let canceled = 0; + let deferred = 0; + for (const attempt of active) { + const operations = await cancelAttempt(ctx, attempt, now); + attemptReads += operations.dbReads; + attemptWrites += operations.dbWrites; + if (operations.canceled) canceled += 1; + else deferred += 1; + } + const hasMore = queued.length + running.length > active.length || deferred > 0; + const counts = { + ...run.counts, + scansCanceled: run.counts.scansCanceled + canceled, + }; + await ctx.db.patch(run._id, { + status: hasMore ? "canceling" : "canceled", + counts, + operations: addOperations(run.operations, { + functionCalls: 1, + dbReads: 3 + attemptReads, + dbWrites: 1 + attemptWrites, + }), + updatedAt: now, + }); + return { + canceled, + hasMore, + status: hasMore ? ("canceling" as const) : ("canceled" as const), + counts, + }; + }, +}); + +export const cancelQueuedFixtureScansInternal = internalMutation({ + args: { + limit: v.number(), + }, + handler: async (ctx, args) => { + assertSkillsShFixtureEnvironmentAllowed(); + assertIntegerInRange("limit", args.limit, 1, 100); + const queued = await ctx.db + .query("skillsShCatalogScanAttempts") + .withIndex("by_dispatch_kind_and_status_and_created_at", (q) => + q.eq("dispatchKind", "deterministic").eq("status", "queued"), + ) + .order("asc") + .take(args.limit); + const now = Date.now(); + let canceled = 0; + for (const attempt of queued) { + const result = await cancelAttempt(ctx, attempt, now); + if (!result.canceled) continue; + canceled += 1; + const run = await ctx.db.get(attempt.runId); + if (run) { + await ctx.db.patch(run._id, { + counts: { + ...run.counts, + scansCanceled: run.counts.scansCanceled + 1, + }, + updatedAt: now, + }); + } + } + return { matched: queued.length, canceled }; + }, +}); + +export const listRealScanQueueInternal = internalQuery({ + args: { + limit: v.number(), + }, + handler: async (ctx, args) => { + assertIntegerInRange("limit", args.limit, 1, MAX_REAL_TEST_ADMISSIONS); + const queued = await ctx.db + .query("skillsShCatalogScanAttempts") + .withIndex("by_dispatch_kind_and_status_and_created_at", (q) => + q.eq("dispatchKind", "real").eq("status", "queued"), + ) + .order("asc") + .take(args.limit); + return queued.map((attempt) => ({ + ...attempt, + requiresArtifactFetch: !attempt.artifactContentHash, + })); + }, +}); + +export const listEntriesPageInternal = internalQuery({ + args: { + paginationOpts: paginationOptsValidator, + }, + handler: async (ctx, args) => { + return await ctx.db + .query("skillsShCatalogEntries") + .withIndex("by_external_id") + .paginate(args.paginationOpts); + }, +}); + +export const resolveKnownGitHubOwnersInternal = internalQuery({ + args: { + owners: v.array(v.string()), + }, + handler: async (ctx, args) => { + const environment = assertSkillsShFixtureEnvironmentAllowed(); + if (environment.environment !== "test") { + throw new ConvexError( + "skills.sh stored owner resolution is available only in permanent Test", + ); + } + const owners = Array.from( + new Set( + args.owners.map((owner) => { + const normalized = owner.trim().toLowerCase(); + if (!normalized) throw new ConvexError("GitHub owner must be a non-empty string"); + return normalized; + }), + ), + ).sort(); + assertIntegerInRange("owners.length", owners.length, 1, 500); + + const resolved: Array<{ owner: string; login: string; id: number }> = []; + const missingOwners: string[] = []; + for (const owner of owners) { + // Staging-live rows preserve the immutable GitHub identity established by + // the authenticated first import. A mutable login must not silently + // reassign existing catalog ownership during an identical refresh. + const [first, last] = await Promise.all([ + ctx.db + .query("skillsShCatalogEntries") + .withIndex("by_owner_and_source_kind_and_github_owner_id", (q) => + q.eq("owner", owner).eq("sourceKind", "staging-live"), + ) + .order("asc") + .first(), + ctx.db + .query("skillsShCatalogEntries") + .withIndex("by_owner_and_source_kind_and_github_owner_id", (q) => + q.eq("owner", owner).eq("sourceKind", "staging-live"), + ) + .order("desc") + .first(), + ]); + if (first?.githubOwnerId !== last?.githubOwnerId) { + throw new ConvexError(`Conflicting authenticated GitHub owner ids for ${owner}`); + } + const id = first?.githubOwnerId; + if (!id) { + missingOwners.push(owner); + continue; + } + if (!Number.isSafeInteger(id) || id <= 0) { + throw new ConvexError(`Invalid authenticated GitHub owner id for ${owner}`); + } + resolved.push({ owner, login: owner, id }); + } + return { + provenance: "stored-authenticated-staging-live" as const, + owners: resolved, + missingOwners, + }; + }, +}); + +export const assertFreshGitHubOwnerAssignmentsInternal = internalQuery({ + args: { + owners: v.array( + v.object({ + owner: v.string(), + id: v.number(), + }), + ), + }, + handler: async (ctx, args) => { + const environment = assertSkillsShFixtureEnvironmentAllowed(); + if (environment.environment !== "test") { + throw new ConvexError( + "skills.sh owner assignment validation is available only in permanent Test", + ); + } + assertIntegerInRange("owners.length", args.owners.length, 1, 500); + + const byOwner = new Map(); + const byId = new Map(); + for (const assignment of args.owners) { + const owner = assignment.owner.trim().toLowerCase(); + if (!owner) throw new ConvexError("GitHub owner must be a non-empty string"); + if (!Number.isSafeInteger(assignment.id) || assignment.id <= 0) { + throw new ConvexError(`Invalid authenticated GitHub owner id for ${owner}`); + } + const existingId = byOwner.get(owner); + if (existingId !== undefined && existingId !== assignment.id) { + throw new ConvexError(`Conflicting authenticated GitHub owner ids for ${owner}`); + } + const existingOwner = byId.get(assignment.id); + if (existingOwner !== undefined && existingOwner !== owner) { + throw new ConvexError( + `Authenticated GitHub owner id ${assignment.id} is assigned to multiple owners`, + ); + } + byOwner.set(owner, assignment.id); + byId.set(assignment.id, owner); + } + + for (const [owner, id] of byOwner) { + const [first, last] = await Promise.all([ + ctx.db + .query("skillsShCatalogEntries") + .withIndex("by_source_kind_and_github_owner_id_and_owner", (q) => + q.eq("sourceKind", "staging-live").eq("githubOwnerId", id), + ) + .order("asc") + .first(), + ctx.db + .query("skillsShCatalogEntries") + .withIndex("by_source_kind_and_github_owner_id_and_owner", (q) => + q.eq("sourceKind", "staging-live").eq("githubOwnerId", id), + ) + .order("desc") + .first(), + ]); + if ((first !== null && first.owner !== owner) || (last !== null && last.owner !== owner)) { + throw new ConvexError( + `Authenticated GitHub owner id ${id} is already assigned to another owner`, + ); + } + } + return { + provenance: "stored-authenticated-staging-live-assignment-check" as const, + checked: byOwner.size, + }; + }, +}); + +export const getRunInternal = internalQuery({ + args: { + runId: v.id("skillsShCatalogRuns"), + }, + handler: async (ctx, args) => { + const run = await ctx.db.get(args.runId); + return run ? summarizeRun(run) : null; + }, +}); + +export const getStagingLiveControlInternal = internalQuery({ + args: {}, + handler: async (ctx) => { + const environment = assertSkillsShFixtureEnvironmentAllowed(); + if (environment.environment !== "test") { + throw new ConvexError("skills.sh staging control is available only in permanent Test"); + } + return { + environment: environment.environment, + deploymentName: process.env.CLAWHUB_DEPLOYMENT_NAME ?? null, + buildSha: process.env.APP_BUILD_SHA ?? null, + control: summarizeControl(await getControlDoc(ctx)), + }; + }, +}); + +export const listRunScanAttemptsPageInternal = internalQuery({ + args: { + runId: v.id("skillsShCatalogRuns"), + paginationOpts: paginationOptsValidator, + }, + handler: async (ctx, args) => { + return await ctx.db + .query("skillsShCatalogScanAttempts") + .withIndex("by_run", (q) => q.eq("runId", args.runId)) + .paginate(args.paginationOpts); + }, +}); + +export const listScanAttemptsPageInternal = internalQuery({ + args: { + paginationOpts: paginationOptsValidator, + }, + handler: async (ctx, args) => { + return await ctx.db + .query("skillsShCatalogScanAttempts") + .withIndex("by_created_at") + .paginate(args.paginationOpts); + }, +}); + +export const listNativeSkillsIsolationPageInternal = internalQuery({ + args: { + paginationOpts: paginationOptsValidator, + }, + handler: async (ctx, args) => { + return await ctx.db.query("skills").paginate(args.paginationOpts); + }, +}); + +export const listNativeScanJobsIsolationPageInternal = internalQuery({ + args: { + paginationOpts: paginationOptsValidator, + }, + handler: async (ctx, args) => { + return await ctx.db.query("securityScanJobs").paginate(args.paginationOpts); + }, +}); + +export const getIsolationDigestInternal = internalQuery({ + args: {}, + handler: async (ctx) => { + const [skills, nativeQueued, nativeRunning, nativeSucceeded, nativeFailed] = await Promise.all([ + ctx.db.query("skills").take(1_001), + ctx.db + .query("securityScanJobs") + .withIndex("by_status_and_next_run_at", (q) => q.eq("status", "queued")) + .take(251), + ctx.db + .query("securityScanJobs") + .withIndex("by_status_and_updated_at", (q) => q.eq("status", "running")) + .take(251), + ctx.db + .query("securityScanJobs") + .withIndex("by_status_and_updated_at", (q) => q.eq("status", "succeeded")) + .take(251), + ctx.db + .query("securityScanJobs") + .withIndex("by_status_and_updated_at", (q) => q.eq("status", "failed")) + .take(251), + ]); + return { + nativeSkills: { + count: skills.length, + isEstimate: skills.length > 1_000, + updatedAtSum: skills.reduce((sum, skill) => sum + skill.updatedAt, 0), + }, + nativeScanJobs: { + queued: nativeQueued.length, + running: nativeRunning.length, + succeeded: nativeSucceeded.length, + failed: nativeFailed.length, + }, + }; + }, +}); + +export const getStatusInternal = internalQuery({ + args: {}, + handler: async (ctx) => { + const [control, runs, entries, scanAttempts] = await Promise.all([ + getControlDoc(ctx), + ctx.db.query("skillsShCatalogRuns").withIndex("by_started_at").order("desc").take(20), + ctx.db.query("skillsShCatalogEntries").withIndex("by_external_id").take(STATUS_LIMIT), + ctx.db + .query("skillsShCatalogScanAttempts") + .withIndex("by_created_at") + .order("desc") + .take(STATUS_LIMIT), + ]); + return { + environment: getSkillsShFixtureEnvironmentPolicy(), + control: summarizeControl(control), + runs: runs.map(summarizeRun), + entries: entries.map((entry) => ({ + ...entry, + resolution: { + externalRoute: `/skills-sh/${entry.externalId}`, + installRef: `skills-sh:${entry.externalId}`, + installable: false, + }, + })), + scanAttempts, + limits: { + runs: 20, + entries: STATUS_LIMIT, + scanAttempts: STATUS_LIMIT, + }, + }; + }, +}); + +function emptyCounts() { + return { + observed: 0, + wouldInsert: 0, + wouldUpdate: 0, + inserted: 0, + updated: 0, + unchanged: 0, + rejected: 0, + scansPlanned: 0, + scansAdmitted: 0, + scansCompleted: 0, + scansCanceled: 0, + }; +} + +function sameFixtureObservation( + existing: Doc<"skillsShCatalogEntries">, + row: ReturnType, +) { + return ( + existing.githubOwnerId === row.githubOwnerId && + existing.owner === row.owner && + existing.repo === row.repo && + existing.slug === row.slug && + existing.displayName === row.displayName && + existing.sourceUrl === row.sourceUrl && + existing.githubRepoUrl === row.githubRepoUrl && + existing.sourceContentHash === row.sourceContentHash && + existing.installs === row.installs + ); +} + +function scanStatusFromAttempt( + attempt: Doc<"skillsShCatalogScanAttempts">, +): Doc<"skillsShCatalogEntries">["scanStatus"] { + if (attempt.status === "queued" || attempt.status === "running") return "queued"; + if (attempt.status === "canceled") return "canceled"; + return attempt.verdict ?? "failed"; +} + +function fixtureObservationConflicts( + existing: Doc<"skillsShCatalogEntries">, + row: ReturnType, + sourceKind: Doc<"skillsShCatalogEntries">["sourceKind"], +) { + return existing.sourceKind !== sourceKind || existing.githubOwnerId !== row.githubOwnerId; +} + +async function sha256Hex(bytes: Uint8Array) { + const digest = await crypto.subtle.digest("SHA-256", new Uint8Array(bytes).buffer); + return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join(""); +} + +async function validateRealScanArtifacts( + ctx: ActionCtx, + externalIds: string[], + artifacts: Map, +) { + const validated = new Map(); + for (const externalId of externalIds) { + const artifact = artifacts.get(externalId); + if (!artifact || !/^[a-f0-9]{64}$/i.test(artifact.artifactContentHash)) { + throw new ConvexError(`real Test scan admission requires a fetched artifact: ${externalId}`); + } + const paths = new Set(); + const files = []; + for (const file of artifact.files) { + if (file.path !== file.path.trim() || !validateFilePath(file.path) || paths.has(file.path)) { + throw new ConvexError( + `real Test scan artifact has an unsafe or duplicate path: ${file.path}`, + ); + } + paths.add(file.path); + if ( + !Number.isSafeInteger(file.size) || + file.size < 0 || + !/^[a-f0-9]{64}$/i.test(file.sha256) + ) { + throw new ConvexError(`real Test scan artifact has invalid file metadata: ${file.path}`); + } + const blob = await ctx.storage.get(file.storageId); + if (!blob) { + throw new ConvexError(`real Test scan artifact file is missing from storage: ${file.path}`); + } + const bytes = new Uint8Array(await blob.arrayBuffer()); + if (bytes.byteLength !== file.size) { + throw new ConvexError(`real Test scan artifact file size mismatch: ${file.path}`); + } + const sha256 = await sha256Hex(bytes); + if (sha256 !== file.sha256.toLowerCase()) { + throw new ConvexError(`real Test scan artifact file hash mismatch: ${file.path}`); + } + files.push({ ...file, sha256 }); + } + if (files.length === 0) { + throw new ConvexError(`real Test scan admission requires a fetched artifact: ${externalId}`); + } + files.sort((left, right) => left.path.localeCompare(right.path)); + const manifest = files.map((file) => `${file.path}\0${file.sha256}\n`).join(""); + const artifactContentHash = await sha256Hex(new TextEncoder().encode(manifest)); + if (artifactContentHash !== artifact.artifactContentHash.toLowerCase()) { + throw new ConvexError(`real Test scan artifact manifest hash mismatch: ${externalId}`); + } + validated.set(externalId, { + externalId, + artifactContentHash, + files, + }); + } + return validated; +} + +async function insertCatalogScanAttempt( + ctx: MutationCtx, + args: { + entryId: Id<"skillsShCatalogEntries">; + runId: Id<"skillsShCatalogRuns">; + externalId: string; + sourceContentHash: string; + dispatchKind: "deterministic" | "real"; + artifactContentHash?: string; + now: number; + }, +) { + return await ctx.db.insert("skillsShCatalogScanAttempts", { + entryId: args.entryId, + runId: args.runId, + externalId: args.externalId, + sourceContentHash: args.sourceContentHash, + ...(args.artifactContentHash ? { artifactContentHash: args.artifactContentHash } : {}), + source: args.dispatchKind === "real" ? "skills-sh-catalog-test" : "skills-sh-catalog-fixture", + dispatchKind: args.dispatchKind, + priority: "low", + status: "queued", + createdAt: args.now, + updatedAt: args.now, + }); +} + +async function cancelAttempt( + ctx: MutationCtx, + attempt: Doc<"skillsShCatalogScanAttempts">, + now: number, + options: { + allowRunningRealJob?: boolean; + } = {}, +) { + let dbReads = 1; + let dbWrites = 0; + if (attempt.securityScanJobId) { + const job = await ctx.db.get(attempt.securityScanJobId); + dbReads += 1; + const activeRealJobLease = + attempt.dispatchKind === "real" && + job?.source === "skills-sh-catalog-test" && + job.status === "running" && + typeof job.leaseExpiresAt === "number" && + job.leaseExpiresAt > now; + if (activeRealJobLease && !options.allowRunningRealJob) { + return { canceled: false, dbReads, dbWrites }; + } + if ( + job?.source === "skills-sh-catalog-test" && + (job.status === "queued" || job.status === "running") + ) { + const cancellationError = + job.status === "running" && !activeRealJobLease && !options.allowRunningRealJob + ? "Catalog run canceled after scan lease expired" + : "Catalog run canceled before scan completion"; + if (job.status === "queued") { + await ctx.db.delete(job._id); + } else { + await ctx.db.patch(job._id, { + status: "failed", + lastError: cancellationError, + completedAt: now, + leaseToken: undefined, + leaseExpiresAt: undefined, + workerId: undefined, + updatedAt: now, + }); + } + dbWrites += 1; + if (attempt.skillScanRequestId) { + const request = await ctx.db.get(attempt.skillScanRequestId); + dbReads += 1; + if (request) { + await ctx.db.patch(request._id, { + status: "failed", + lastError: + job.status === "queued" + ? "Catalog run canceled before scan start" + : cancellationError, + completedAt: now, + updatedAt: now, + }); + dbWrites += 1; + } + } + } + } + await ctx.db.patch(attempt._id, { + status: "canceled", + completedAt: now, + updatedAt: now, + }); + dbWrites += 1; + const entry = await ctx.db.get(attempt.entryId); + if (entry?.scanStatus === "queued" && entry.sourceContentHash === attempt.sourceContentHash) { + await ctx.db.patch(entry._id, { + scanStatus: "canceled", + publicVisible: false, + updatedAt: now, + }); + return { canceled: true, dbReads, dbWrites: dbWrites + 1 }; + } + return { canceled: true, dbReads, dbWrites }; +} + +async function hasActiveScanAttemptsForRun(ctx: MutationCtx, runId: Id<"skillsShCatalogRuns">) { + const [queued, running] = await Promise.all([ + ctx.db + .query("skillsShCatalogScanAttempts") + .withIndex("by_run_and_status", (q) => q.eq("runId", runId).eq("status", "queued")) + .first(), + ctx.db + .query("skillsShCatalogScanAttempts") + .withIndex("by_run_and_status", (q) => q.eq("runId", runId).eq("status", "running")) + .first(), + ]); + return Boolean(queued || running); +} + +function addOperations(current: OperationCounts, added: OperationCounts) { + return { + functionCalls: current.functionCalls + added.functionCalls, + dbReads: current.dbReads + added.dbReads, + dbWrites: current.dbWrites + added.dbWrites, + }; +} + +function summarizeRun(run: Doc<"skillsShCatalogRuns">) { + return { + _id: run._id, + fixtureId: run.fixtureId, + snapshotId: run.snapshotId, + sourceKind: run.sourceKind, + sourceCapturedAt: run.sourceCapturedAt, + snapshotCaptureFetches: run.snapshotCaptureFetches, + dryRun: run.dryRun, + status: run.status, + cursor: run.cursor, + scanCursor: run.scanCursor, + fixtureLength: run.fixtureLength, + counts: run.counts, + budgets: run.budgets, + operations: run.operations, + actor: run.actor, + reason: run.reason, + lastError: run.lastError, + errors: run.lastError ? [run.lastError] : [], + operationsAreEstimates: true, + budgetConsumed: { + entriesObserved: run.counts.observed, + scansPlanned: run.counts.scansPlanned, + scansAdmitted: run.counts.scansAdmitted, + batchesProcessed: run.batchesProcessed, + scanAdmissionBatches: run.scanAdmissionBatches, + lastBatchWrites: run.lastBatchWrites, + lastBatchReads: run.lastBatchReads, + }, + startedAt: run.startedAt, + completedAt: run.completedAt, + runtimeMs: (run.completedAt ?? run.updatedAt) - run.startedAt, + updatedAt: run.updatedAt, + }; +} diff --git a/package.json b/package.json index f6a2a258..aab1b992 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,8 @@ "seed:test:validate-snapshot": "bun scripts/staging-seed/validate-sanitized-snapshot.ts", "setup:worktree": "bun scripts/setup-worktree.ts", "skill-cards:worker": "bun scripts/skill-cards/run-skill-card-worker.ts", + "skills-sh:capture-500": "bun scripts/skills-sh-catalog/capture-frozen-snapshot.ts", + "skills-sh:prove-500": "bun scripts/skills-sh-catalog/prove-500.ts", "skills:install": "npx --yes skills@1.5.16 add openclaw/design-system --skill openclaw-design openclaw-brand openclaw-design-system openclaw-marketing-pages openclaw-design-audit --agent codex --copy --yes", "test": "vitest run", "test:e2e": "vitest run -c vitest.e2e.config.ts", @@ -111,6 +113,7 @@ "@tanstack/react-router": "1.170.18", "@tanstack/react-start": "1.168.32", "@vercel/analytics": "2.0.1", + "@vercel/oidc": "^3.8.0", "@vercel/speed-insights": "2.0.0", "class-variance-authority": "0.7.1", "clawhub-schema": "workspace:0.0.2", @@ -146,6 +149,7 @@ "zod": "4.4.3" }, "devDependencies": { + "@edge-runtime/vm": "^5.0.0", "@faker-js/faker": "^10.5.0", "@playwright/test": "^1.61.1", "@react-email/ui": "^6.9.0", @@ -161,6 +165,7 @@ "@typescript/native": "npm:typescript@7.0.2", "@vitejs/plugin-react": "6.0.3", "@vitest/coverage-v8": "4.1.10", + "convex-test": "^0.0.54", "jsdom": "29.1.1", "nitro": "3.0.260610-beta", "only-allow": "1.2.2", diff --git a/proof/claw-556/skills-sh-500-local-proof.json b/proof/claw-556/skills-sh-500-local-proof.json new file mode 100644 index 00000000..a3e248a8 --- /dev/null +++ b/proof/claw-556/skills-sh-500-local-proof.json @@ -0,0 +1,292 @@ +{ + "verdict": "pass", + "generatedAt": "2026-07-21T09:02:16.188Z", + "deployment": { + "kind": "disposable-local-convex", + "name": "local-amantus-clawdhub-7449", + "cloudUrl": "http://127.0.0.1:3210", + "permanentTestDeployed": false, + "productionDeployed": false + }, + "runtime": { + "elapsedMs": 42507, + "memory": { + "backendPid": 97459, + "backendRssStartKiB": 816368, + "backendRssPeakKiB": 891456, + "backendRssEndKiB": 863968, + "driverRssStartBytes": 51904512, + "driverRssPeakBytes": 75415552, + "driverRssEndBytes": 75415552, + "driverHeapPeakBytes": 6130176 + }, + "clientCalls": { + "mutation": 279, + "query": 297, + "expectedErrors": 8, + "unexpectedErrors": 0 + }, + "sourceFetchesDuringProof": 0, + "frozenSnapshotCaptureFetches": 528 + }, + "defaultState": { + "controls": { + "discoveryEnabled": false, + "maxCatalogInFlight": 0, + "maxCatalogQueued": 0, + "maxEntriesPerBatch": 0, + "maxEntriesPerRun": 0, + "maxNativeInFlight": 0, + "maxNativeQueued": 0, + "maxPlannedScans": 0, + "maxScanAdmissionsPerBatch": 0, + "maxScanAdmissionsPerDay": 0, + "maxScanAdmissionsPerRun": 0, + "maxWritesPerBatch": 0, + "mode": "off", + "paused": true, + "publicVisibilityEnabled": false, + "realScanAllowlist": [], + "reason": null, + "scanAdmissionEnabled": false, + "scanPlanningEnabled": false, + "updatedAt": null, + "updatedBy": null, + "writesEnabled": false + }, + "schedulesPresent": false, + "catalogScanAttempts": 0, + "realScanAttempts": 0 + }, + "discovery20000": { + "observed": 20000, + "plannedScans": 20000, + "wouldInsert": 20000, + "persistedInserts": 0, + "authoritativePersistedEntries": 0, + "authoritativeScanAttempts": 0, + "authoritativeNativeScanJobs": 0, + "batches": 200, + "operationEstimates": { + "dbReads": 20401, + "dbWrites": 201, + "functionCalls": 201 + } + }, + "frozen500": { + "capture": { + "snapshotId": "skills-sh-500-2026-07-21", + "capturedAt": "2026-07-21T02:35:28.482Z", + "sourceFetches": 528 + }, + "configuredBudgets": { + "maxEntriesPerBatch": 125, + "maxEntriesPerRun": 500, + "maxPlannedScans": 500, + "maxScanAdmissionsPerBatch": 49, + "maxScanAdmissionsPerDay": 500, + "maxScanAdmissionsPerRun": 500, + "maxWritesPerBatch": 100 + }, + "firstRun": { + "expectedActions": { + "wouldInsert": 500, + "wouldUpdate": 0, + "scansPlanned": 500 + }, + "persistedActions": { + "inserted": 500, + "updated": 0, + "authoritativeEntries": 500, + "authoritativeAttempts": 496, + "completedDeterministicAttempts": 496 + }, + "counts": { + "inserted": 500, + "observed": 500, + "rejected": 0, + "scansAdmitted": 496, + "scansCanceled": 0, + "scansCompleted": 496, + "scansPlanned": 500, + "unchanged": 0, + "updated": 0, + "wouldInsert": 500, + "wouldUpdate": 0 + }, + "batches": { + "batchesProcessed": 6, + "entriesObserved": 500, + "lastBatchReads": 7, + "lastBatchWrites": 6, + "scanAdmissionBatches": 11, + "scansAdmitted": 496, + "scansPlanned": 500 + }, + "operationEstimates": { + "dbReads": 2163, + "dbWrites": 2515, + "functionCalls": 31 + } + }, + "pauseResume": { + "pausedCursor": 99, + "pausedObserved": 99, + "resumedFinalCursor": 500, + "resumedFinalObserved": 500, + "replayedCompletedBatch": false + }, + "identicalRerun": { + "counts": { + "inserted": 0, + "observed": 500, + "rejected": 0, + "scansAdmitted": 0, + "scansCanceled": 0, + "scansCompleted": 0, + "scansPlanned": 0, + "unchanged": 500, + "updated": 0, + "wouldInsert": 0, + "wouldUpdate": 0 + }, + "authoritativeAttempts": 0, + "operationEstimates": { + "dbReads": 1023, + "dbWrites": 507, + "functionCalls": 7 + } + }, + "changedRerun": { + "counts": { + "inserted": 0, + "observed": 500, + "rejected": 0, + "scansAdmitted": 1, + "scansCanceled": 0, + "scansCompleted": 1, + "scansPlanned": 1, + "unchanged": 498, + "updated": 2, + "wouldInsert": 0, + "wouldUpdate": 2 + }, + "authoritativeAttempts": 1, + "exactSourceHashRescan": "665c585455decbcb02116bc928adf5148b9cd0f67b7eb4908c8c0d647c00f680", + "artifactHash": null, + "hashSemantics": "sourceContentHash is the upstream observation; deterministic fixture completion is not an independent ClawScan artifact hash", + "operationEstimates": { + "dbReads": 1040, + "dbWrites": 513, + "functionCalls": 9 + } + }, + "finalAuthoritativeEntries": 500, + "allEntriesDark": true + }, + "concurrency": { + "sameRun": { + "fulfilled": 1, + "rejected": 1, + "cap": 1 + }, + "queueHealth": { + "queuedAtThresholdRejected": true, + "inFlightAtThresholdRejected": true, + "maxCatalogQueued": 1, + "maxCatalogInFlight": 1, + "maxNativeQueued": 0, + "maxNativeInFlight": 0, + "queuedThresholdObserved": { + "nativeQueued": 0, + "nativeInFlight": 0, + "catalogQueued": 1, + "catalogInFlight": 0 + }, + "inFlightThresholdObserved": { + "nativeQueued": 0, + "nativeInFlight": 0, + "catalogQueued": 1, + "catalogInFlight": 1 + } + }, + "crossRunDaily": { + "fulfilled": 1, + "rejected": 1, + "cap": 499, + "attemptsBeforeRace": 498 + }, + "finalCatalogAttemptsToday": 500 + }, + "admissionContainment": { + "deterministicOnlyInLocalProof": true, + "realDispatchOutsideStagingLiveRejected": true, + "realDispatchRejection": "[Request ID: add843a2d41d4f84] Server Error\nUncaught ConvexError: real skills.sh scan admission requires staging-live controls\n at handler (../convex/skillsShCatalog.ts:915:14)\n at async handler (../../node_modules/convex-helpers/server/customFunctions.js:268:27)\n" + }, + "lifecycleContainment": { + "canceledRunningAttempts": 1, + "canceledQueuedAttempts": 1, + "canceledAttemptReadback": [ + { + "dispatchKind": "deterministic", + "status": "canceled" + }, + { + "dispatchKind": "deterministic", + "status": "canceled" + } + ], + "canceledRunAdmissionRejected": true, + "canceledRunAdmissionRejection": "[Request ID: 286aefc65cc575ac] Server Error\nUncaught ConvexError: Cannot admit scans for canceled run\n at handler (../convex/skillsShCatalog.ts:902:24)\n at async handler (../../node_modules/convex-helpers/server/customFunctions.js:268:27)\n", + "killSwitchAdmissionRejected": true, + "killSwitchAdmissionRejection": "[Request ID: b4c1e019ae188e8a] Server Error\nUncaught ConvexError: skills.sh catalog controls are disabled\n at assertCatalogActive (../convex/skillsShCatalog.ts:157:14)\n at assertScanAdmissionEnabled (../convex/skillsShCatalog.ts:179:14)\n at handler (../convex/skillsShCatalog.ts:893:24)\n at async handler (../../node_modules/convex-helpers/server/customFunctions.js:268:27)\n" + }, + "rollback": { + "controls": { + "discoveryEnabled": false, + "maxCatalogInFlight": 10, + "maxCatalogQueued": 50, + "maxEntriesPerBatch": 125, + "maxEntriesPerRun": 500, + "maxNativeInFlight": 0, + "maxNativeQueued": 0, + "maxPlannedScans": 500, + "maxScanAdmissionsPerBatch": 49, + "maxScanAdmissionsPerDay": 500, + "maxScanAdmissionsPerRun": 500, + "maxWritesPerBatch": 100, + "mode": "off", + "paused": true, + "publicVisibilityEnabled": false, + "realScanAllowlist": [], + "reason": "visibility and control rollback proof", + "scanAdmissionEnabled": false, + "scanPlanningEnabled": false, + "updatedAt": 1784624535953, + "updatedBy": "claw-556-local-proof", + "writesEnabled": false + }, + "schedulesPresent": false, + "nativeBefore": { + "skills": 0, + "scanJobs": 0, + "sha256": "3da07503745b0261278284c398b652c73edeb004df305c460686e5b0b12c05b0" + }, + "nativeAfter": { + "skills": 0, + "scanJobs": 0, + "sha256": "3da07503745b0261278284c398b652c73edeb004df305c460686e5b0b12c05b0" + }, + "nativeStateUnchanged": true + }, + "limits": { + "operationCountsAreEstimates": true, + "realTestQueueIntegration": "not deployed; real attempts require an allowlisted Test control and an external artifact fetch before completion", + "statusPreviewLimit": { + "entries": 50, + "runs": 20, + "scanAttempts": 50 + }, + "authoritativeReadback": "all entry, attempt, native skill, and native scan-job counts above use paginated internal queries" + } +} diff --git a/proof/claw-556/skills-sh-500-test-proof.json b/proof/claw-556/skills-sh-500-test-proof.json new file mode 100644 index 00000000..835c7d73 --- /dev/null +++ b/proof/claw-556/skills-sh-500-test-proof.json @@ -0,0 +1,376 @@ +{ + "schemaVersion": 1, + "issue": "CLAW-556", + "generatedAt": "2026-07-21T14:23:00Z", + "question": "Does the permanent Test staging seam fetch and process exactly 500 live skills through the ClawHub Vercel OIDC boundary without broad scan admission, public visibility, scheduling, or native-state changes?", + "verdict": "pass_for_review", + "sourceRevision": { + "gitSha": "06e8ff1a2b71f9d544623f85c4284446ad7878de", + "branch": "pe/claw-556-skills-sh-catalog-control-plane", + "convexDeployment": "academic-chihuahua-392", + "convexBuildSha": "06e8ff1a2b71f9d544623f85c4284446ad7878de", + "convexReadbackAt": "2026-07-21T14:09:48Z", + "liveProofInvocationSha": "f74f895a0c0fe13c3a49de7f9ff0a9e6a9df0446", + "hardeningScopeAfterLiveProof": "Artifact byte/hash validation, authoritative staging identity checks, direct real-dispatch rejection, admin authorization, missing-run callback rejection, source-kind collision rejection, allowlist normalization, terminal retry synchronization, strict fixture-versus-staging-live mode isolation, bounded multi-file write accounting, backlog draining, and terminal catalog-attempt synchronization. Discovery, normalization, persistence batching, cursoring, and the 500-row source contract were unchanged." + }, + "vercel": { + "project": "openclaw-foundation/clawhub", + "verifiedOidcIdentity": { + "ownerId": "team_pLdjXbfy0XvPRiNmAygTjTSH", + "projectId": "prj_UVAJPNPYrBwTEkPJwkpEySsge8Mc", + "environment": "test" + }, + "liveInvocation": { + "deploymentId": "dpl_9ndfyttJWaTC3LZibUgL371TstFx", + "url": "clawhub-nh4plivpm-openclaw-foundation.vercel.app", + "target": "test", + "sourceSha": "f74f895a0c0fe13c3a49de7f9ff0a9e6a9df0446", + "proofCompleted": true, + "deploymentState": "ERROR", + "deploymentLimit": "The one-shot proof build intentionally did not emit the normal application output after the bounded invocation, so Vercel marked that invocation deployment as ERROR after the proof response was captured." + }, + "finalInertDeployment": { + "deploymentId": "dpl_FvMrDG1W3PCDs2v5PggkF4Bnt7sV", + "url": "clawhub-368srhyyu-openclaw-foundation.vercel.app", + "target": "test", + "sourceSha": "06e8ff1a2b71f9d544623f85c4284446ad7878de", + "readyState": "READY", + "createdAt": "2026-07-21T14:20:41.046Z", + "runtime": "nodejs24.x", + "memorySizeMiB": 2048, + "timeoutSeconds": 300, + "liveInvocationMarkerPresent": false, + "externalProtectedStatuses": { + "/ops/skills-sh/catalog-test": 302, + "/api/v1/operator/skills-sh/catalog-test": 302 + }, + "deploymentProtectionEnabled": true, + "newCatalogRunsAfterDeploy": 0, + "sourceBinding": { + "gitCommitSha": "06e8ff1a2b71f9d544623f85c4284446ad7878de", + "purpose": "clean inert exact-head deployment" + } + } + }, + "liveSnapshot": { + "snapshotId": "skills-sh-test-live-500:5dbb0caa415371e5", + "capturedAt": "2026-07-21T09:55:56.277Z", + "rows": 500, + "nvidiaRows": 10, + "requiredCollisionIds": [ + "anthropics/skills/frontend-design", + "anthropics/claude-code/frontend-design" + ], + "skillsShFetches": { + "total": 507, + "list": 2, + "search": 1, + "detail": 504, + "runtimeMs": 38317, + "skippedIncompleteDetails": 1 + }, + "githubOwnerResolution": { + "authenticatedFetches": 70, + "immutableOwnerIdsRequiredForEverySelectedRow": true, + "placeholderOwnerIdsAccepted": false + } + }, + "configuredLimits": { + "discoveryRows": 500, + "entriesPerBatch": 50, + "entriesPerRun": 500, + "writesPerBatch": 100, + "plannedScansPerRun": 500, + "explicitRealScanAllowlistCeiling": 10, + "realScanAdmissionsPerBatch": 1, + "realScanAdmissionsPerRun": 1, + "realScanAdmissionsPerDay": 1, + "nativeQueuedThreshold": 3, + "nativeInFlightThreshold": 0, + "catalogQueuedThreshold": 1, + "catalogInFlightThreshold": 1, + "schedulesEnabled": false, + "publicVisibilityEnabled": false + }, + "liveRun": { + "runId": "h17w3pzcbww82myvmz8a1msnxh8azbss", + "status": "completed", + "cursor": 500, + "runtimeMs": 16148, + "batchesProcessed": 10, + "expectedActions": { + "observed": 500, + "inserts": 0, + "updates": 0, + "unchanged": 500, + "scanPlans": 0, + "realScanAdmissions": 0 + }, + "persistedActions": { + "catalogEntries": 500, + "inserted": 0, + "updated": 0, + "unchanged": 500, + "scanAttempts": 0, + "realScanJobs": 0 + }, + "consumedBudgets": { + "entriesObserved": 500, + "batchesProcessed": 10, + "scansPlanned": 0, + "scanAdmissionBatches": 0, + "scansAdmitted": 0, + "lastBatchReadsEstimate": 102, + "lastBatchWritesEstimate": 51 + }, + "operationEstimates": { + "dbReads": 1021, + "dbWrites": 511, + "functionCalls": 11 + }, + "errors": [] + }, + "identicalRerun": { + "runId": "h17qgyp7yhwt5g4dvfyppdw0gd8ayt02", + "status": "completed", + "cursor": 500, + "runtimeMs": 15979, + "batchesProcessed": 10, + "observed": 500, + "unchanged": 500, + "inserted": 0, + "updated": 0, + "scanPlans": 0, + "scanAttempts": 0, + "realScanJobs": 0, + "errors": [] + }, + "changedInputRun": { + "runId": "h17n18bmyq5tr5mdka784mm0dn8aycxe", + "status": "completed", + "runtimeMs": 16635, + "observed": 500, + "persistedMetadataUpdates": 453, + "unchanged": 47, + "changedSourceHashes": 3, + "scanPlans": 3, + "scanAttempts": 0, + "realScanJobs": 0, + "changedExternalIds": [ + "alchaincyf/huashu-design/huashu-design", + "getpaperclipai/paperclip/paperclip-converting-plans-to-tasks", + "getpaperclipai/paperclip/para-memory-files" + ], + "errors": [] + }, + "pauseCancelKillSwitch": { + "runId": "h17thn76pzx9phr0w0g9rxgrw58aznq9", + "cursorAfterFirstBatch": 50, + "pausedBatchRejected": true, + "cursorAfterResumeBatch": 100, + "batchesAfterResume": 2, + "completedBatchReplayObserved": false, + "finalStatus": "canceled", + "attemptsCanceled": 0, + "discoveryRejectedAfterKillSwitch": true, + "admissionRejectedAfterKillSwitch": true + }, + "admission": { + "requested": 0, + "admitted": 0, + "skipped": 0, + "realQueueAfterRun": 0, + "catalogAttemptsAfterRun": 0, + "nativeQueueBefore": { + "queued": 3, + "inFlight": 0 + }, + "nativeQueueAfter": { + "queued": 3, + "inFlight": 0 + }, + "reasonForZeroAdmissions": "The selected NVIDIA and representative collision candidates exposed incomplete source file bodies. The real path requires a complete fetched artifact and exact artifact hash, so admission failed closed rather than weakening validation." + }, + "runtime": { + "totalElapsedMs": 75008, + "rssStartBytes": 160522240, + "rssEndBytes": 269778944, + "rssDeltaBytes": 109256704, + "heapUsedStartBytes": 19133656, + "heapUsedEndBytes": 98823704, + "heapUsedDeltaBytes": 79690048, + "sourceFetches": 507, + "githubFetches": 70, + "errors": 0, + "operationCountsAreEstimates": true + }, + "authoritativeReadback": { + "nativeSkills": { + "beforeCount": 67616, + "afterCount": 67616, + "beforeSha256": "267d17957e61a49f37dd009a5d88194b3587daf86281e253522ff1b7c9ad80f7", + "afterSha256": "267d17957e61a49f37dd009a5d88194b3587daf86281e253522ff1b7c9ad80f7", + "identical": true, + "pagesReadAfter": 68 + }, + "nativeSecurityScanJobs": { + "beforeCount": 3, + "afterCount": 3, + "beforeSha256": "3c08230f417303a66c0678e7c6192864d045bca72581ab1b51a9908c64f36ddc", + "afterSha256": "3c08230f417303a66c0678e7c6192864d045bca72581ab1b51a9908c64f36ddc", + "identical": true, + "sources": { + "publish": 3 + }, + "statuses": { + "queued": 3 + } + }, + "catalog": { + "entries": 500, + "entriesPublicVisible": 0, + "entriesInstallable": 0, + "scanStatusPlanned": 500, + "scanAttempts": 0, + "realScanQueue": 0 + } + }, + "finalExactHeadReadback": { + "capturedAt": "2026-07-21T14:22:02Z", + "convexDeployment": "academic-chihuahua-392", + "convexBuildSha": "06e8ff1a2b71f9d544623f85c4284446ad7878de", + "vercelDeploymentId": "dpl_FvMrDG1W3PCDs2v5PggkF4Bnt7sV", + "vercelSourceSha": "06e8ff1a2b71f9d544623f85c4284446ad7878de", + "latestCatalogRunUpdatedAt": 1784627791018, + "latestCatalogRunPredatesFinalDeploy": true, + "control": { + "mode": "off", + "paused": true, + "discoveryEnabled": false, + "writesEnabled": false, + "scanPlanningEnabled": false, + "scanAdmissionEnabled": false, + "publicVisibilityEnabled": false, + "realScanAllowlist": [] + }, + "nativeSkills": { + "count": 67616, + "pagesRead": 68, + "pageJsonSha256": "987ae4e1dd9bb5539b7febd1223f6a3eb8080cdbad759d3d7033cbe54f6d42f1" + }, + "nativeSecurityScanJobs": { + "count": 3, + "pagesRead": 1, + "cliJsonSha256": "e22b14a16593fb8be93959a91161da2a51f6fa52c62c570fea4425e5641b0759", + "sources": { + "publish": 3 + }, + "statuses": { + "queued": 3 + }, + "catalogAttributed": 0 + }, + "catalog": { + "entries": 500, + "entryPagesRead": 1, + "entriesCliJsonSha256": "eede6eb7beff0ce4959b38b2e64faf55c44dcff1f27a9ed5ba36affc89afba66", + "sourceKinds": { + "staging-live": 500 + }, + "scanStatuses": { + "planned": 500 + }, + "entriesPublicVisible": 0, + "entriesInstallable": 0, + "scanAttempts": 0, + "attemptPagesRead": 1, + "attemptsCliJsonSha256": "2e2fdeefcc79db019282c22b2db56c47c9019a8b47a40a9083c53746c92f7caa", + "realScanAttempts": 0 + }, + "hashNote": "These fresh hashes cover complete paginated Test readback at exact head 06e8ff1a. They are serialization-specific snapshots and are not compared numerically with the in-run ConvexHttpClient hashes; authoritative counts, source/status distributions, and the unchanged latest run timestamp establish that the inert deployments created no catalog or native work." + }, + "finalControls": { + "mode": "off", + "paused": true, + "discoveryEnabled": false, + "writesEnabled": false, + "scanPlanningEnabled": false, + "scanAdmissionEnabled": false, + "publicVisibilityEnabled": false, + "realScanAllowlist": [], + "schedulesPresent": false, + "githubTokenPresentInConvex": false, + "temporaryOperatorTokensRevoked": true, + "temporaryInvocationFilesRemoved": true, + "productionTouched": false + }, + "alternateAuthenticatedRetry": { + "attemptedAt": "2026-07-21T14:11:43Z", + "requiredAnotherLiveFetch": false, + "sourceFetches": 0, + "catalogRunsCreated": 0, + "scanAttemptsCreated": 0, + "productionTouched": false, + "diagnosis": [ + "The protected Test route could not be reached through vercel curl because the project automation bypass secret is intentionally unavailable locally.", + "Two build-time OIDC retries failed before source authorization: one referenced the wrong token function name and one lacked the explicit Test build marker.", + "A third deployment restored and ran the normal build command, so it produced no source fetch or catalog mutation." + ], + "cleanup": { + "controlMode": "off", + "paused": true, + "discoveryEnabled": false, + "writesEnabled": false, + "scanPlanningEnabled": false, + "scanAdmissionEnabled": false, + "publicVisibilityEnabled": false, + "realScanAllowlist": [], + "temporaryOperatorTokenRevoked": true, + "temporaryInvocationFilePresent": false, + "normalBuildCommandRestored": true + } + }, + "overloadAcceptance": { + "local20000RowProofArtifact": "proof/claw-556/skills-sh-500-local-proof.json", + "discovery20000RowsCreatesAutomaticNativeJobs": 0, + "discovery20000RowsCreatesCatalogAttempts": 0, + "permanentTestFetchRows": 500, + "permanentTestRealScanCeiling": 10, + "permanentTestRealScansObserved": 0, + "discoveryWritesAdmissionVisibilityIndependentlyControlled": true, + "unhealthyOrExhaustedAdmissionFailsClosed": true, + "dailySchedulingDefaultOff": true + }, + "hardeningValidation": { + "focusedTests": { + "files": 6, + "testsPassed": 152 + }, + "convexCodegenWithTypeScript": "passed", + "ciStatic": "passed", + "ciUnit": { + "filesPassed": 382, + "filesSkipped": 1, + "testsPassed": 4949, + "testsSkipped": 1, + "statementsPercent": 85.89, + "branchesPercent": 76.71, + "functionsPercent": 86.95, + "linesPercent": 88.59 + }, + "ciTypesBuild": "passed", + "structuredAutoreview": { + "branchFindingAccepted": "Fixture work could advance under staging-live controls.", + "fix": "Fixture start, processing, deterministic admission, start, completion, and callback now require fixture mode and a fixture run.", + "finalLocalReview": "clean after final exact-head readback correction" + }, + "liveFetchRepeatedAfterHardening": false, + "reasonLiveFetchWasNotRepeated": "The post-proof changes only tightened staging admission, artifact validation, environment authorization, callback/retry synchronization, source-kind collision handling, fixture-versus-live mode isolation, bounded write accounting, and backlog draining. The exact 500-row source and discovery path was unchanged; exact head 06e8ff1a was deployed inert to both permanent Test runtimes and read back with zero new runs, attempts, or jobs." + }, + "limits": [ + "The skills.sh detail contract did not provide complete file bodies for the selected real-scan candidates, so this gate proves fail-closed artifact admission rather than a terminal real ClawScan verdict.", + "The live 500-row invocation ran at f74f895a; final hardened descendant 06e8ff1a was deployed to both exact Test runtimes with the live-fetch marker absent. The proof records this split explicitly rather than claiming a second live fetch.", + "Vercel local-upload deployments do not expose a gitSource object. The source SHA is bound by the clean detached worktree, the fixed one-shot invoker assertion, the Convex APP_BUILD_SHA readback, and the final conventional commit chain.", + "Database operation counts are code-path estimates. Runtime, memory, fetch counts, full paginated table counts, and canonical SHA-256 comparisons are measured." + ] +} diff --git a/scripts/security/run-codex-scan-worker-clawscan.test.ts b/scripts/security/run-codex-scan-worker-clawscan.test.ts index a0381da6..32d0a81e 100644 --- a/scripts/security/run-codex-scan-worker-clawscan.test.ts +++ b/scripts/security/run-codex-scan-worker-clawscan.test.ts @@ -1,5 +1,6 @@ /* @vitest-environment node */ import { execFile } from "node:child_process"; +import { createHash } from "node:crypto"; import { chmod, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; @@ -10,6 +11,7 @@ import { processJob } from "./run-codex-scan-worker"; const tempDirs: string[] = []; const execFileAsync = promisify(execFile); +const sha256 = (content: string) => createHash("sha256").update(content).digest("hex"); afterEach(async () => { vi.restoreAllMocks(); @@ -45,7 +47,7 @@ function skillVersionJob(jobId: string): ClaimedJob { files: [ { path: "SKILL.md", - sha256: "abc123", + sha256: sha256("# Skill"), size: 42, url: "data:text/plain,%23%20Skill", }, @@ -86,7 +88,7 @@ function fileTarget(path: string, content: string): ClaimedJob["target"] { files: [ { path, - sha256: "artifact-sha", + sha256: sha256(content), size: Buffer.byteLength(content), url: `data:text/plain,${encodeURIComponent(content)}`, }, diff --git a/scripts/security/run-codex-scan-worker.test.ts b/scripts/security/run-codex-scan-worker.test.ts index c390bd5f..62cebab0 100644 --- a/scripts/security/run-codex-scan-worker.test.ts +++ b/scripts/security/run-codex-scan-worker.test.ts @@ -385,7 +385,7 @@ describe("run-codex-scan-worker diagnostics", () => { files: [ { path: "SKILL.md", - sha256: "abc123", + sha256: "e2151f8490121dc5e6fd36c1d4e00b6da5593595e3eb8ece76c1d0ec3f310979", size: 42, url: "data:text/plain,%23%20Skill", }, @@ -409,7 +409,42 @@ describe("run-codex-scan-worker diagnostics", () => { source: "publish", targetKind: "skillVersion", }); - expect(metadata.target.files).toEqual([{ path: "SKILL.md", sha256: "abc123", size: 42 }]); + expect(metadata.target.files).toEqual([ + { + path: "SKILL.md", + sha256: "e2151f8490121dc5e6fd36c1d4e00b6da5593595e3eb8ece76c1d0ec3f310979", + size: 42, + }, + ]); + }); + + it("rejects downloaded bytes that do not match the stored file hash", async () => { + const workspace = await tempDir(); + await expect( + writeArtifactWorkspace( + { + job: { + _id: "catalog-job", + hasMaliciousSignal: false, + leaseToken: "test-auth-token", + source: "skills-sh-catalog-test", + targetKind: "skillScanRequest", + waitForVtUntil: 0, + }, + target: { + files: [ + { + path: "SKILL.md", + sha256: "0".repeat(64), + size: 7, + url: "data:text/plain,%23%20Skill", + }, + ], + }, + }, + workspace, + ), + ).rejects.toThrow("Downloaded artifact hash mismatch for artifact file SKILL.md"); }); it("materializes zero-byte directory markers with descendant files", async () => { diff --git a/scripts/security/run-codex-scan-worker.ts b/scripts/security/run-codex-scan-worker.ts index b87b5eb2..6de1db7f 100644 --- a/scripts/security/run-codex-scan-worker.ts +++ b/scripts/security/run-codex-scan-worker.ts @@ -1,4 +1,5 @@ import { spawn } from "node:child_process"; +import { createHash } from "node:crypto"; import { mkdirSync, readFileSync } from "node:fs"; import { appendFile, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; @@ -237,7 +238,8 @@ function parseArgs() { return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined; }; const laneValue = get("--lane") ?? process.env.CODEX_SECURITY_SCAN_LANE; - const lane: "priority" | "shared" = laneValue === "priority" ? "priority" : "shared"; + const lane: "priority" | "shared" | "catalog" = + laneValue === "priority" || laneValue === "catalog" ? laneValue : "shared"; return { batchLimit: numberFrom( get("--batch-limit") ?? get("--limit") ?? process.env.CODEX_SECURITY_SCAN_LIMIT, @@ -757,7 +759,14 @@ export async function writeArtifactWorkspace(job: ClaimedJob, workspace: string) const { file, out } = candidate; await mkdir(dirname(out), { recursive: true }); - await writeFile(out, await download(file.url, { kind: "file", path: file.path })); + const bytes = await download(file.url, { kind: "file", path: file.path }); + const actualSha256 = createHash("sha256").update(bytes).digest("hex"); + if (actualSha256 !== file.sha256.toLowerCase()) { + throw new Error( + `Downloaded artifact hash mismatch for artifact file ${safeWorkerArtifactPathLabel(file.path)}`, + ); + } + await writeFile(out, bytes); } if (job.target.clawpackUrl) { diff --git a/scripts/skills-sh-catalog/capture-frozen-snapshot.ts b/scripts/skills-sh-catalog/capture-frozen-snapshot.ts new file mode 100644 index 00000000..3ff6b556 --- /dev/null +++ b/scripts/skills-sh-catalog/capture-frozen-snapshot.ts @@ -0,0 +1,215 @@ +#!/usr/bin/env bun +import { mkdir, writeFile } from "node:fs/promises"; +import { dirname, resolve } from "node:path"; +import { + fetchSkillsShCatalogDetail, + fetchSkillsShCatalogPage, + searchSkillsShCatalog, + type SkillsShCatalogListRow, +} from "../../server/skillsShCatalogSource"; +import { resolveAuthenticatedGitHubOwner } from "./github-owner-resolution"; + +const SNAPSHOT_ID = "skills-sh-500-2026-07-21"; +const ROW_LIMIT = 500; +const DETAIL_CANDIDATE_LIMIT = 525; +const DETAIL_CONCURRENCY = 8; +const OUTPUT_PATH = resolve("convex/fixtures/skills-sh-500-2026-07-21.json"); +const REQUIRED_COLLISION_IDS = [ + "anthropics/skills/frontend-design", + "anthropics/claude-code/frontend-design", +] as const; + +type FrozenRow = { + externalId: string; + githubOwnerId: number; + owner: string; + repo: string; + slug: string; + displayName: string; + sourceUrl: string; + githubRepoUrl: string; + sourceContentHash: string; + installs: number; +}; + +function isGitHubRow(row: SkillsShCatalogListRow) { + const normalizedSource = row.source.trim().toLowerCase(); + const normalizedSlug = row.slug.trim().toLowerCase(); + return ( + row.sourceType === "github" && + normalizedSource.split("/").length === 2 && + /^[a-z0-9][a-z0-9-]*$/.test(normalizedSlug) && + row.id.trim().toLowerCase() === `${normalizedSource}/${normalizedSlug}` + ); +} + +function normalizeRow(row: SkillsShCatalogListRow) { + const [owner = "", repo = ""] = row.source.split("/"); + return { + ...row, + id: `${owner.trim().toLowerCase()}/${repo.trim().toLowerCase()}/${row.slug + .trim() + .toLowerCase()}`, + owner: owner.trim().toLowerCase(), + repo: repo.trim().toLowerCase(), + slug: row.slug.trim().toLowerCase(), + }; +} + +async function mapWithConcurrency( + values: readonly T[], + concurrency: number, + mapper: (value: T, index: number) => Promise, +) { + const results = Array.from({ length: values.length }); + let nextIndex = 0; + async function worker() { + while (nextIndex < values.length) { + const index = nextIndex; + nextIndex += 1; + results[index] = await mapper(values[index] as T, index); + } + } + await Promise.all(Array.from({ length: Math.min(concurrency, values.length) }, () => worker())); + return results; +} + +async function main() { + const startedAt = Date.now(); + const candidates = new Map>(); + let listFetches = 0; + for (let page = 0; candidates.size < ROW_LIMIT + 100; page += 1) { + const response = await fetchSkillsShCatalogPage({ page, perPage: ROW_LIMIT }); + listFetches += 1; + for (const row of response.data) { + if (!isGitHubRow(row)) continue; + const normalized = normalizeRow(row); + candidates.set(normalized.id, normalized); + } + if (!response.pagination.hasMore) break; + } + + const nvidia = await searchSkillsShCatalog({ + query: "nvidia", + owner: "nvidia", + limit: 200, + }); + const nvidiaRows = nvidia.data + .filter(isGitHubRow) + .map(normalizeRow) + .filter((row) => row.owner === "nvidia") + .slice(0, 10); + + const requiredIds = new Set([ + ...REQUIRED_COLLISION_IDS, + ...nvidiaRows.map((row) => row.id), + ]); + for (const row of nvidiaRows) candidates.set(row.id, row); + for (const id of requiredIds) { + if (!candidates.has(id)) { + throw new Error(`Required skills.sh fixture row is missing: ${id}`); + } + } + + const detailCandidates = [ + ...Array.from(requiredIds, (id) => candidates.get(id)!), + ...Array.from(candidates.values()).filter((row) => !requiredIds.has(row.id)), + ].slice(0, DETAIL_CANDIDATE_LIMIT); + if (detailCandidates.length !== DETAIL_CANDIDATE_LIMIT) { + throw new Error( + `Expected ${DETAIL_CANDIDATE_LIMIT} detail candidates, received ${detailCandidates.length}`, + ); + } + + const details = await mapWithConcurrency( + detailCandidates, + DETAIL_CONCURRENCY, + async (row) => await fetchSkillsShCatalogDetail(row.id), + ); + + const selected = detailCandidates + .map((row, index) => ({ row, detail: details[index] })) + .filter( + ( + candidate, + ): candidate is { + row: ReturnType; + detail: NonNullable<(typeof details)[number]> & { hash: string }; + } => + candidate.detail !== null && + candidate.detail.id.toLowerCase() === candidate.row.id && + typeof candidate.detail.hash === "string" && + /^[a-f0-9]{64}$/i.test(candidate.detail.hash), + ) + .slice(0, ROW_LIMIT); + if (selected.length !== ROW_LIMIT) { + throw new Error(`Only ${selected.length} rows had canonical ids and exact content hashes`); + } + for (const id of requiredIds) { + if (!selected.some((candidate) => candidate.row.id === id)) { + throw new Error(`Required skills.sh fixture row lacks an exact detail hash: ${id}`); + } + } + + const owners = Array.from(new Set(selected.map(({ row }) => row.owner))).sort(); + const ownerIds = new Map( + owners.map((owner) => { + const resolved = resolveAuthenticatedGitHubOwner(owner); + return [owner, resolved.id] as const; + }), + ); + const rows: FrozenRow[] = selected.map(({ row, detail }) => { + return { + externalId: row.id, + githubOwnerId: ownerIds.get(row.owner)!, + owner: row.owner, + repo: row.repo, + slug: row.slug, + displayName: row.name.trim() || row.slug, + sourceUrl: row.url, + githubRepoUrl: row.installUrl ?? `https://github.com/${row.owner}/${row.repo}`, + sourceContentHash: detail.hash.toLowerCase(), + installs: row.installs, + }; + }); + + const capturedAt = new Date().toISOString(); + const payload = { + snapshotId: SNAPSHOT_ID, + capturedAt, + source: { + project: "openclaw-foundation/clawhub", + endpoint: "https://skills.sh/api/v1/skills", + authentication: "vercel-oidc", + requestedRows: ROW_LIMIT, + }, + selection: { + rows: rows.length, + nvidiaRows: rows.filter((row) => row.owner === "nvidia").length, + requiredCollisionIds: REQUIRED_COLLISION_IDS, + }, + captureMetrics: { + runtimeMs: Date.now() - startedAt, + skillsShFetches: listFetches + 1 + details.length, + githubOwnerFetches: owners.length, + listFetches, + searchFetches: 1, + detailFetches: details.length, + }, + rows, + }; + await mkdir(dirname(OUTPUT_PATH), { recursive: true }); + await writeFile(OUTPUT_PATH, `${JSON.stringify(payload, null, 2)}\n`); + console.log( + JSON.stringify({ + outputPath: OUTPUT_PATH, + snapshotId: SNAPSHOT_ID, + rows: rows.length, + nvidiaRows: payload.selection.nvidiaRows, + collisionIds: REQUIRED_COLLISION_IDS, + captureMetrics: payload.captureMetrics, + }), + ); +} + +await main(); diff --git a/scripts/skills-sh-catalog/github-owner-resolution.ts b/scripts/skills-sh-catalog/github-owner-resolution.ts new file mode 100644 index 00000000..2fa3b56c --- /dev/null +++ b/scripts/skills-sh-catalog/github-owner-resolution.ts @@ -0,0 +1,20 @@ +import { spawnSync } from "node:child_process"; + +export function resolveAuthenticatedGitHubOwner(ownerInput: string) { + const owner = ownerInput.trim().toLowerCase(); + if (!owner) throw new Error("GitHub owner is required"); + const result = spawnSync("gh", ["api", `users/${owner}`, "--jq", "{id,login}"], { + encoding: "utf8", + stdio: ["ignore", "pipe", "pipe"], + }); + if (result.status !== 0) { + throw new Error(`Authenticated GitHub owner lookup failed: ${owner}`); + } + const payload = JSON.parse(result.stdout) as { id?: unknown; login?: unknown }; + const id = typeof payload.id === "number" ? payload.id : Number.NaN; + const login = typeof payload.login === "string" ? payload.login.trim().toLowerCase() : ""; + if (!Number.isSafeInteger(id) || id <= 0 || login !== owner) { + throw new Error(`Authenticated GitHub owner lookup returned invalid identity: ${owner}`); + } + return { owner, id, login }; +} diff --git a/scripts/skills-sh-catalog/prove-500.ts b/scripts/skills-sh-catalog/prove-500.ts new file mode 100644 index 00000000..22646d86 --- /dev/null +++ b/scripts/skills-sh-catalog/prove-500.ts @@ -0,0 +1,1049 @@ +import { execFileSync } from "node:child_process"; +import { createHash } from "node:crypto"; +import { mkdir, readFile, writeFile } from "node:fs/promises"; +import { dirname, resolve } from "node:path"; +import { ConvexHttpClient } from "convex/browser"; +import type { FunctionArgs, FunctionReference, FunctionReturnType } from "convex/server"; +import { internal } from "../../convex/_generated/api"; +import type { Doc } from "../../convex/_generated/dataModel"; +import frozenSnapshot from "../../convex/fixtures/skills-sh-500-2026-07-21.json"; + +const OUTPUT_PATH = resolve(process.cwd(), "proof/claw-556/skills-sh-500-local-proof.json"); +const LOCAL_CONFIG_PATH = resolve(process.cwd(), ".convex/local/default/config.json"); + +type LocalConfig = { + adminKey: string; + deploymentName: string; + ports: { + cloud: number; + site: number; + }; +}; + +type CallKind = "mutation" | "query"; +type InternalQuery = FunctionReference<"query", "internal">; +type InternalMutation = FunctionReference<"mutation", "internal">; +type AdminConvexClient = { + setAdminAuth(adminKey: string): void; + query( + query: Query, + args: FunctionArgs, + ): Promise>; + mutation( + mutation: Mutation, + args: FunctionArgs, + ): Promise>; +}; +type RunReadback = FunctionReturnType; + +const calls = { + mutation: 0, + query: 0, + expectedErrors: 0, + unexpectedErrors: 0, +}; + +const memory = { + backendPid: null as number | null, + backendRssStartKiB: 0, + backendRssPeakKiB: 0, + backendRssEndKiB: 0, + driverRssStartBytes: process.memoryUsage().rss, + driverRssPeakBytes: process.memoryUsage().rss, + driverRssEndBytes: 0, + driverHeapPeakBytes: process.memoryUsage().heapUsed, +}; + +function assert(condition: unknown, message: string): asserts condition { + if (!condition) throw new Error(message); +} + +function readNumber(command: string[]): number { + try { + return ( + Number( + execFileSync(command[0]!, command.slice(1), { + encoding: "utf8", + stdio: ["ignore", "pipe", "ignore"], + }).trim(), + ) || 0 + ); + } catch { + return 0; + } +} + +function sampleMemory() { + const usage = process.memoryUsage(); + memory.driverRssPeakBytes = Math.max(memory.driverRssPeakBytes, usage.rss); + memory.driverHeapPeakBytes = Math.max(memory.driverHeapPeakBytes, usage.heapUsed); + if (memory.backendPid) { + const rss = readNumber(["ps", "-o", "rss=", "-p", String(memory.backendPid)]); + memory.backendRssPeakKiB = Math.max(memory.backendRssPeakKiB, rss); + } +} + +async function measured(kind: CallKind, operation: () => Promise) { + calls[kind] += 1; + try { + return await operation(); + } catch (error) { + calls.unexpectedErrors += 1; + throw error; + } finally { + sampleMemory(); + } +} + +async function expectedFailure(kind: CallKind, operation: () => Promise, message: string) { + calls[kind] += 1; + try { + await operation(); + } catch (error) { + const detail = error instanceof Error ? error.message : String(error); + assert(detail.includes(message), `expected error containing "${message}"`); + calls.expectedErrors += 1; + sampleMemory(); + return detail; + } + throw new Error(`expected operation to fail with "${message}"`); +} + +function reclassifyExpectedRaceFailures( + results: PromiseSettledResult[], + expectedMessage: string, +) { + const rejected = results.filter( + (result): result is PromiseRejectedResult => result.status === "rejected", + ); + for (const result of rejected) { + const detail = result.reason instanceof Error ? result.reason.message : String(result.reason); + assert( + detail.includes(expectedMessage), + `expected race rejection containing "${expectedMessage}", received "${detail}"`, + ); + } + calls.unexpectedErrors -= rejected.length; + calls.expectedErrors += rejected.length; + return rejected; +} + +function chunks(values: readonly T[], size: number) { + const result: T[][] = []; + for (let index = 0; index < values.length; index += size) { + result.push(values.slice(index, index + size)); + } + return result; +} + +function stableHash(value: unknown) { + return createHash("sha256").update(JSON.stringify(value)).digest("hex"); +} + +async function collectPage( + readPage: (cursor: string | null) => Promise<{ + page: T[]; + isDone: boolean; + continueCursor: string; + }>, +) { + const documents: T[] = []; + let cursor: string | null = null; + do { + const result = await readPage(cursor); + documents.push(...result.page); + cursor = result.isDone ? null : result.continueCursor; + } while (cursor); + return documents; +} + +async function main() { + const startedAt = performance.now(); + const config = JSON.parse(await readFile(LOCAL_CONFIG_PATH, "utf8")) as LocalConfig; + memory.backendPid = + readNumber(["lsof", "-t", `-iTCP:${config.ports.cloud}`, "-sTCP:LISTEN"]) || null; + assert(memory.backendPid, "local Convex backend is not listening"); + memory.backendRssStartKiB = readNumber(["ps", "-o", "rss=", "-p", String(memory.backendPid)]); + memory.backendRssPeakKiB = memory.backendRssStartKiB; + + const client = new ConvexHttpClient( + `http://127.0.0.1:${config.ports.cloud}`, + ) as unknown as AdminConvexClient; + client.setAdminAuth(config.adminKey); + + const query = (operation: () => Promise) => measured("query", operation); + const mutation = (operation: () => Promise) => measured("mutation", operation); + + const cronsSource = await readFile(resolve(process.cwd(), "convex/crons.ts"), "utf8"); + const schedulesPresent = + cronsSource.includes("skillsShCatalog") || cronsSource.includes("skills-sh"); + assert(!schedulesPresent, "skills.sh scheduler reference exists"); + const initial = await query(() => client.query(internal.skillsShCatalog.getStatusInternal, {})); + assert( + initial.control.mode === "off" && + !initial.control.discoveryEnabled && + !initial.control.writesEnabled && + !initial.control.scanPlanningEnabled && + !initial.control.scanAdmissionEnabled && + !initial.control.publicVisibilityEnabled, + "catalog controls must start fail-closed", + ); + assert(initial.runs.length === 0, "proof requires a fresh local deployment"); + assert(initial.entries.length === 0, "proof requires no catalog entries"); + const initialAttempts = await collectPage>((cursor) => + query(() => + client.query(internal.skillsShCatalog.listScanAttemptsPageInternal, { + paginationOpts: { cursor, numItems: 100 }, + }), + ), + ); + const initialRealAttempts = initialAttempts.filter((attempt) => attempt.dispatchKind === "real"); + assert(initialAttempts.length === 0, "proof requires no catalog scan attempts"); + + const nativeBefore = { + skills: await collectPage>((cursor) => + query(() => + client.query(internal.skillsShCatalog.listNativeSkillsIsolationPageInternal, { + paginationOpts: { cursor, numItems: 100 }, + }), + ), + ), + scanJobs: await collectPage>((cursor) => + query(() => + client.query(internal.skillsShCatalog.listNativeScanJobsIsolationPageInternal, { + paginationOpts: { cursor, numItems: 100 }, + }), + ), + ), + }; + const nativeBeforeHash = stableHash(nativeBefore); + + await mutation(() => + client.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + actor: "claw-556-local-proof", + reason: "20,000-row discovery overload proof", + confirm: "enable-skills-sh-fixture-control", + mode: "fixture", + discoveryEnabled: true, + writesEnabled: false, + scanPlanningEnabled: true, + scanAdmissionEnabled: false, + maxEntriesPerRun: 20_000, + maxEntriesPerBatch: 100, + maxWritesPerBatch: 2, + maxPlannedScans: 20_000, + maxScanAdmissionsPerBatch: 0, + maxScanAdmissionsPerRun: 0, + maxScanAdmissionsPerDay: 0, + maxCatalogQueued: 0, + maxCatalogInFlight: 0, + maxNativeQueued: 0, + maxNativeInFlight: 0, + realScanAllowlist: [], + }), + ); + const dryStart = await mutation(() => + client.mutation(internal.skillsShCatalog.startFixtureRunInternal, { + fixtureId: "synthetic-20000-v1", + actor: "claw-556-local-proof", + reason: "20,000-row discovery must not enqueue", + dryRun: true, + }), + ); + let dryRun = await query(() => + client.query(internal.skillsShCatalog.getRunInternal, { + runId: dryStart.runId, + }), + ); + while (dryRun?.status === "running") { + await mutation(() => + client.mutation(internal.skillsShCatalog.processFixtureBatchInternal, { + runId: dryStart.runId, + }), + ); + dryRun = await query(() => + client.query(internal.skillsShCatalog.getRunInternal, { + runId: dryStart.runId, + }), + ); + } + assert(dryRun?.status === "completed", "20,000-row dry run did not finish"); + assert( + dryRun.counts.observed === 20_000 && + dryRun.counts.scansPlanned === 20_000 && + dryRun.counts.wouldInsert === 20_000, + "20,000-row dry run did not observe and plan the complete fixture", + ); + const entriesAfterDryRun = await collectPage>((cursor) => + query(() => + client.query(internal.skillsShCatalog.listEntriesPageInternal, { + paginationOpts: { cursor, numItems: 100 }, + }), + ), + ); + const attemptsAfterDryRun = await collectPage>((cursor) => + query(() => + client.query(internal.skillsShCatalog.listRunScanAttemptsPageInternal, { + runId: dryStart.runId, + paginationOpts: { cursor, numItems: 100 }, + }), + ), + ); + assert(entriesAfterDryRun.length === 0, "dry run persisted catalog entries"); + assert(attemptsAfterDryRun.length === 0, "dry run enqueued scan attempts"); + + const mainControl = { + actor: "claw-556-local-proof", + reason: "bounded frozen 500 proof", + confirm: "enable-skills-sh-fixture-control", + mode: "fixture" as const, + discoveryEnabled: true, + writesEnabled: true, + scanPlanningEnabled: true, + scanAdmissionEnabled: true, + maxEntriesPerRun: 500, + maxEntriesPerBatch: 125, + maxWritesPerBatch: 100, + maxPlannedScans: 500, + maxScanAdmissionsPerBatch: 49, + maxScanAdmissionsPerRun: 500, + maxScanAdmissionsPerDay: 500, + maxCatalogQueued: 50, + maxCatalogInFlight: 10, + maxNativeQueued: 0, + maxNativeInFlight: 0, + realScanAllowlist: [] as string[], + }; + await mutation(() => + client.mutation(internal.skillsShCatalog.configureFixtureControlInternal, mainControl), + ); + const firstStart = await mutation(() => + client.mutation(internal.skillsShCatalog.startFixtureRunInternal, { + fixtureId: "skills-sh-500-2026-07-21", + actor: "claw-556-local-proof", + reason: "first frozen 500 application", + }), + ); + const firstBatch = await mutation(() => + client.mutation(internal.skillsShCatalog.processFixtureBatchInternal, { + runId: firstStart.runId, + }), + ); + await mutation(() => + client.mutation(internal.skillsShCatalog.setFixtureRunPausedInternal, { + runId: firstStart.runId, + paused: true, + }), + ); + await expectedFailure( + "mutation", + () => + client.mutation(internal.skillsShCatalog.processFixtureBatchInternal, { + runId: firstStart.runId, + }), + "run is paused", + ); + const pausedReadback = await query(() => + client.query(internal.skillsShCatalog.getRunInternal, { + runId: firstStart.runId, + }), + ); + assert( + pausedReadback?.cursor === firstBatch.cursor && + pausedReadback.counts.observed === firstBatch.counts.observed, + "pause replayed or advanced a completed batch", + ); + const pauseCheckpoint = { + cursor: pausedReadback.cursor, + observed: pausedReadback.counts.observed, + }; + await mutation(() => + client.mutation(internal.skillsShCatalog.setFixtureRunPausedInternal, { + runId: firstStart.runId, + paused: false, + }), + ); + let firstRun: RunReadback = pausedReadback; + while (firstRun?.status === "paused" || firstRun?.status === "running") { + await mutation(() => + client.mutation(internal.skillsShCatalog.processFixtureBatchInternal, { + runId: firstStart.runId, + }), + ); + firstRun = await query(() => + client.query(internal.skillsShCatalog.getRunInternal, { + runId: firstStart.runId, + }), + ); + } + assert(firstRun?.status === "completed", "frozen 500 run did not finish"); + + const frozenEntries = ( + await collectPage>((cursor) => + query(() => + client.query(internal.skillsShCatalog.listEntriesPageInternal, { + paginationOpts: { cursor, numItems: 100 }, + }), + ), + ) + ).filter((entry) => entry.sourceKind === "frozen-snapshot"); + assert(frozenEntries.length === 500, "frozen readback did not persist 500 rows"); + const changedExternalId = frozenSnapshot.rows[0]!.externalId; + const initialAdmissionIds = [ + changedExternalId, + ...frozenEntries + .map((entry) => entry.externalId) + .filter((externalId) => externalId !== changedExternalId), + ].slice(0, 496); + for (const externalIds of chunks(initialAdmissionIds, 49)) { + await mutation(() => + client.mutation(internal.skillsShCatalog.admitFixtureScansInternal, { + runId: firstStart.runId, + externalIds, + dispatchKind: "deterministic", + }), + ); + await mutation(() => + client.mutation(internal.skillsShCatalog.completeDeterministicScansInternal, { + runId: firstStart.runId, + limit: externalIds.length, + }), + ); + } + firstRun = await query(() => + client.query(internal.skillsShCatalog.getRunInternal, { + runId: firstStart.runId, + }), + ); + assert( + firstRun?.counts.scansAdmitted === 496 && firstRun.counts.scansCompleted === 496, + "initial deterministic scans did not reconcile", + ); + const realAdmissionCandidate = frozenEntries.find( + (entry) => !initialAdmissionIds.includes(entry.externalId), + ); + assert(realAdmissionCandidate, "real admission gate needs an unadmitted planned entry"); + const realAdmissionOutsideStagingLiveError = await expectedFailure( + "mutation", + () => + client.mutation(internal.skillsShCatalog.admitFixtureScansInternal, { + runId: firstStart.runId, + externalIds: [realAdmissionCandidate.externalId], + dispatchKind: "real", + }), + "requires staging-live controls", + ); + + const repeatedStart = await mutation(() => + client.mutation(internal.skillsShCatalog.startFixtureRunInternal, { + fixtureId: "skills-sh-500-2026-07-21", + actor: "claw-556-local-proof", + reason: "identical frozen rerun", + }), + ); + let repeatedRun = await query(() => + client.query(internal.skillsShCatalog.getRunInternal, { + runId: repeatedStart.runId, + }), + ); + while (repeatedRun?.status === "running") { + await mutation(() => + client.mutation(internal.skillsShCatalog.processFixtureBatchInternal, { + runId: repeatedStart.runId, + }), + ); + repeatedRun = await query(() => + client.query(internal.skillsShCatalog.getRunInternal, { + runId: repeatedStart.runId, + }), + ); + } + assert( + repeatedRun?.counts.unchanged === 500 && repeatedRun.counts.scansPlanned === 0, + "identical rerun was not idempotent", + ); + + const configureSynthetic = async (maxEntriesPerRun: number, maxScanAdmissionsPerDay: number) => + mutation(() => + client.mutation(internal.skillsShCatalog.configureFixtureControlInternal, { + ...mainControl, + reason: "atomic scan-admission proof", + maxEntriesPerRun, + maxEntriesPerBatch: maxEntriesPerRun, + maxPlannedScans: maxEntriesPerRun, + maxScanAdmissionsPerBatch: 1, + maxScanAdmissionsPerRun: 1, + maxScanAdmissionsPerDay, + maxCatalogQueued: 1, + maxCatalogInFlight: 1, + }), + ); + const runSynthetic = async (maxEntriesPerRun: number) => { + const started = await mutation(() => + client.mutation(internal.skillsShCatalog.startFixtureRunInternal, { + fixtureId: "synthetic-20000-v1", + actor: "claw-556-local-proof", + reason: `synthetic ${maxEntriesPerRun}-row admission race`, + }), + ); + let run = await query(() => + client.query(internal.skillsShCatalog.getRunInternal, { + runId: started.runId, + }), + ); + while (run?.status === "running") { + await mutation(() => + client.mutation(internal.skillsShCatalog.processFixtureBatchInternal, { + runId: started.runId, + }), + ); + run = await query(() => + client.query(internal.skillsShCatalog.getRunInternal, { + runId: started.runId, + }), + ); + } + return started.runId; + }; + + await configureSynthetic(2, 500); + const sameRunId = await runSynthetic(2); + const sameRunEntries = ( + await collectPage>((cursor) => + query(() => + client.query(internal.skillsShCatalog.listEntriesPageInternal, { + paginationOpts: { cursor, numItems: 100 }, + }), + ), + ) + ).filter((entry) => entry.owner === "synthetic-owner"); + const sameRunRace = await Promise.allSettled( + sameRunEntries.slice(0, 2).map((entry) => + measured("mutation", () => + client.mutation(internal.skillsShCatalog.admitFixtureScansInternal, { + runId: sameRunId, + externalIds: [entry.externalId], + dispatchKind: "deterministic", + }), + ), + ), + ); + const sameRunFulfilled = sameRunRace.filter((result) => result.status === "fulfilled"); + const sameRunRejected = reclassifyExpectedRaceFailures( + sameRunRace, + "run scan-admission budget exceeded", + ); + assert( + sameRunFulfilled.length === 1 && sameRunRejected.length === 1, + "same-run concurrent admission exceeded its cap", + ); + const sameRunAttempts = await collectPage>((cursor) => + query(() => + client.query(internal.skillsShCatalog.listRunScanAttemptsPageInternal, { + runId: sameRunId, + paginationOpts: { cursor, numItems: 100 }, + }), + ), + ); + assert(sameRunAttempts.length === 1, "same-run race persisted more than one attempt"); + const activeAtQueuedThreshold = ( + await collectPage>((cursor) => + query(() => + client.query(internal.skillsShCatalog.listScanAttemptsPageInternal, { + paginationOpts: { cursor, numItems: 100 }, + }), + ), + ) + ).filter((attempt) => attempt.status === "queued" || attempt.status === "running"); + assert( + activeAtQueuedThreshold.filter((attempt) => attempt.status === "queued").length === 1 && + activeAtQueuedThreshold.filter((attempt) => attempt.status === "running").length === 0, + "queued threshold readback did not show exactly one queued catalog attempt", + ); + + await configureSynthetic(3, 500); + const queueHealthRunId = await runSynthetic(3); + const queueHealthEntries = ( + await collectPage>((cursor) => + query(() => + client.query(internal.skillsShCatalog.listEntriesPageInternal, { + paginationOpts: { cursor, numItems: 100 }, + }), + ), + ) + ).filter((entry) => entry.owner === "synthetic-owner" && entry.scanStatus === "planned"); + assert(queueHealthEntries.length >= 1, "queue-health proof needs a planned row"); + await expectedFailure( + "mutation", + () => + client.mutation(internal.skillsShCatalog.admitFixtureScansInternal, { + runId: queueHealthRunId, + externalIds: [queueHealthEntries[0]!.externalId], + dispatchKind: "deterministic", + }), + "queued-scan budget exceeded", + ); + await mutation(() => + client.mutation(internal.skillsShCatalog.markScanAttemptRunningInternal, { + attemptId: sameRunAttempts[0]!._id, + }), + ); + const queueHealthAdmission = await mutation(() => + client.mutation(internal.skillsShCatalog.admitFixtureScansInternal, { + runId: queueHealthRunId, + externalIds: [queueHealthEntries[0]!.externalId], + dispatchKind: "deterministic", + }), + ); + const queueHealthAttempts = await collectPage>((cursor) => + query(() => + client.query(internal.skillsShCatalog.listRunScanAttemptsPageInternal, { + runId: queueHealthRunId, + paginationOpts: { cursor, numItems: 100 }, + }), + ), + ); + assert(queueHealthAttempts.length === 1, "queue-health proof did not persist one queued attempt"); + const activeAtInFlightThreshold = ( + await collectPage>((cursor) => + query(() => + client.query(internal.skillsShCatalog.listScanAttemptsPageInternal, { + paginationOpts: { cursor, numItems: 100 }, + }), + ), + ) + ).filter((attempt) => attempt.status === "queued" || attempt.status === "running"); + assert( + activeAtInFlightThreshold.filter((attempt) => attempt.status === "queued").length === 1 && + activeAtInFlightThreshold.filter((attempt) => attempt.status === "running").length === 1, + "in-flight threshold readback did not show one queued and one running catalog attempt", + ); + await expectedFailure( + "mutation", + () => + client.mutation(internal.skillsShCatalog.markScanAttemptRunningInternal, { + attemptId: queueHealthAttempts[0]!._id, + }), + "scan start is blocked by queue health", + ); + const canceledRunning = await mutation(() => + client.mutation(internal.skillsShCatalog.cancelCatalogRunInternal, { + runId: sameRunId, + limit: 10, + }), + ); + const canceledQueued = await mutation(() => + client.mutation(internal.skillsShCatalog.cancelCatalogRunInternal, { + runId: queueHealthRunId, + limit: 10, + }), + ); + assert( + canceledRunning.canceled === 1 && canceledQueued.canceled === 1, + "catalog cancellation did not cancel both active attempts", + ); + const canceledAttemptReadback = ( + await Promise.all( + [sameRunId, queueHealthRunId].map((runId) => + collectPage>((cursor) => + query(() => + client.query(internal.skillsShCatalog.listRunScanAttemptsPageInternal, { + runId, + paginationOpts: { cursor, numItems: 100 }, + }), + ), + ), + ), + ) + ).flat(); + assert( + canceledAttemptReadback.length === 2 && + canceledAttemptReadback.every((attempt) => attempt.status === "canceled"), + "catalog cancellation left an active attempt", + ); + const canceledRunAdmissionError = await expectedFailure( + "mutation", + () => + client.mutation(internal.skillsShCatalog.admitFixtureScansInternal, { + runId: queueHealthRunId, + externalIds: [queueHealthEntries[0]!.externalId], + dispatchKind: "deterministic", + }), + "Cannot admit scans for canceled run", + ); + + await configureSynthetic(4, 499); + const dailyRunA = await runSynthetic(4); + await configureSynthetic(5, 499); + const dailyRunB = await runSynthetic(5); + const syntheticEntries = ( + await collectPage>((cursor) => + query(() => + client.query(internal.skillsShCatalog.listEntriesPageInternal, { + paginationOpts: { cursor, numItems: 100 }, + }), + ), + ) + ).filter((entry) => entry.owner === "synthetic-owner"); + const dailyCandidates = syntheticEntries.filter((entry) => entry.scanStatus === "planned"); + assert(dailyCandidates.length >= 2, "daily race needs two planned rows"); + const dailyRace = await Promise.allSettled([ + measured("mutation", () => + client.mutation(internal.skillsShCatalog.admitFixtureScansInternal, { + runId: dailyRunA, + externalIds: [dailyCandidates[0]!.externalId], + dispatchKind: "deterministic", + }), + ), + measured("mutation", () => + client.mutation(internal.skillsShCatalog.admitFixtureScansInternal, { + runId: dailyRunB, + externalIds: [dailyCandidates[1]!.externalId], + dispatchKind: "deterministic", + }), + ), + ]); + const dailyFulfilled = dailyRace.filter((result) => result.status === "fulfilled"); + const dailyRejected = reclassifyExpectedRaceFailures( + dailyRace, + "daily scan-admission budget exceeded", + ); + assert( + dailyFulfilled.length === 1 && dailyRejected.length === 1, + "cross-run daily admission exceeded its cap", + ); + const dailyWinnerRunId = dailyRace[0]?.status === "fulfilled" ? dailyRunA : dailyRunB; + await mutation(() => + client.mutation(internal.skillsShCatalog.completeDeterministicScansInternal, { + runId: dailyWinnerRunId, + limit: 1, + }), + ); + + await mutation(() => + client.mutation(internal.skillsShCatalog.configureFixtureControlInternal, mainControl), + ); + const changedStart = await mutation(() => + client.mutation(internal.skillsShCatalog.startFixtureRunInternal, { + fixtureId: "skills-sh-500-2026-07-21-v2", + actor: "claw-556-local-proof", + reason: "changed frozen rerun", + }), + ); + let changedRun = await query(() => + client.query(internal.skillsShCatalog.getRunInternal, { + runId: changedStart.runId, + }), + ); + while (changedRun?.status === "running") { + await mutation(() => + client.mutation(internal.skillsShCatalog.processFixtureBatchInternal, { + runId: changedStart.runId, + }), + ); + changedRun = await query(() => + client.query(internal.skillsShCatalog.getRunInternal, { + runId: changedStart.runId, + }), + ); + } + assert( + changedRun?.counts.updated === 2 && changedRun.counts.scansPlanned === 1, + "changed rerun did not produce exactly two updates and one rescan plan", + ); + await mutation(() => + client.mutation(internal.skillsShCatalog.admitFixtureScansInternal, { + runId: changedStart.runId, + externalIds: [changedExternalId], + dispatchKind: "deterministic", + }), + ); + await mutation(() => + client.mutation(internal.skillsShCatalog.completeDeterministicScansInternal, { + runId: changedStart.runId, + limit: 1, + }), + ); + changedRun = await query(() => + client.query(internal.skillsShCatalog.getRunInternal, { + runId: changedStart.runId, + }), + ); + assert( + changedRun?.counts.scansAdmitted === 1 && changedRun.counts.scansCompleted === 1, + "changed rerun scan did not reconcile", + ); + + const finalFrozenEntries = ( + await collectPage>((cursor) => + query(() => + client.query(internal.skillsShCatalog.listEntriesPageInternal, { + paginationOpts: { cursor, numItems: 100 }, + }), + ), + ) + ).filter((entry) => entry.sourceKind === "frozen-snapshot"); + const firstAttempts = await collectPage>((cursor) => + query(() => + client.query(internal.skillsShCatalog.listRunScanAttemptsPageInternal, { + runId: firstStart.runId, + paginationOpts: { cursor, numItems: 100 }, + }), + ), + ); + const repeatedAttempts = await collectPage>((cursor) => + query(() => + client.query(internal.skillsShCatalog.listRunScanAttemptsPageInternal, { + runId: repeatedStart.runId, + paginationOpts: { cursor, numItems: 100 }, + }), + ), + ); + const changedAttempts = await collectPage>((cursor) => + query(() => + client.query(internal.skillsShCatalog.listRunScanAttemptsPageInternal, { + runId: changedStart.runId, + paginationOpts: { cursor, numItems: 100 }, + }), + ), + ); + const allAttempts = await collectPage>((cursor) => + query(() => + client.query(internal.skillsShCatalog.listScanAttemptsPageInternal, { + paginationOpts: { cursor, numItems: 100 }, + }), + ), + ); + assert(finalFrozenEntries.length === 500, "final frozen entry count drifted"); + assert(firstAttempts.length === 496, "first run attempt count drifted"); + assert(repeatedAttempts.length === 0, "identical rerun created attempts"); + assert(changedAttempts.length === 1, "changed rerun did not create one attempt"); + assert( + finalFrozenEntries.every((entry) => !entry.publicVisible), + "catalog entries became publicly visible", + ); + const deterministicOnlyInLocalProof = allAttempts.every( + (attempt) => attempt.dispatchKind === "deterministic", + ); + assert(deterministicOnlyInLocalProof, "local proof created a real scan attempt"); + + await mutation(() => + client.mutation(internal.skillsShCatalog.disableCatalogInternal, { + actor: "claw-556-local-proof", + reason: "visibility and control rollback proof", + confirm: "disable-skills-sh-catalog", + }), + ); + const disabled = await query(() => client.query(internal.skillsShCatalog.getStatusInternal, {})); + assert( + disabled.control.mode === "off" && + !disabled.control.discoveryEnabled && + !disabled.control.writesEnabled && + !disabled.control.scanAdmissionEnabled && + !disabled.control.publicVisibilityEnabled, + "catalog kill switches did not fail closed", + ); + const killSwitchAdmissionError = await expectedFailure( + "mutation", + () => + client.mutation(internal.skillsShCatalog.admitFixtureScansInternal, { + runId: changedStart.runId, + externalIds: [changedExternalId], + dispatchKind: "deterministic", + }), + "catalog controls are disabled", + ); + + const nativeAfter = { + skills: await collectPage>((cursor) => + query(() => + client.query(internal.skillsShCatalog.listNativeSkillsIsolationPageInternal, { + paginationOpts: { cursor, numItems: 100 }, + }), + ), + ), + scanJobs: await collectPage>((cursor) => + query(() => + client.query(internal.skillsShCatalog.listNativeScanJobsIsolationPageInternal, { + paginationOpts: { cursor, numItems: 100 }, + }), + ), + ), + }; + const nativeAfterHash = stableHash(nativeAfter); + assert(nativeAfterHash === nativeBeforeHash, "native skills or native scan jobs changed"); + + memory.backendRssEndKiB = readNumber(["ps", "-o", "rss=", "-p", String(memory.backendPid)]); + memory.backendRssPeakKiB = Math.max(memory.backendRssPeakKiB, memory.backendRssEndKiB); + memory.driverRssEndBytes = process.memoryUsage().rss; + sampleMemory(); + + const proof = { + verdict: "pass", + generatedAt: new Date().toISOString(), + deployment: { + kind: "disposable-local-convex", + name: config.deploymentName, + cloudUrl: `http://127.0.0.1:${config.ports.cloud}`, + permanentTestDeployed: false, + productionDeployed: false, + }, + runtime: { + elapsedMs: Math.round(performance.now() - startedAt), + memory, + clientCalls: calls, + sourceFetchesDuringProof: 0, + frozenSnapshotCaptureFetches: firstRun?.snapshotCaptureFetches ?? 0, + }, + defaultState: { + controls: initial.control, + schedulesPresent, + catalogScanAttempts: initialAttempts.length, + realScanAttempts: initialRealAttempts.length, + }, + discovery20000: { + observed: dryRun.counts.observed, + plannedScans: dryRun.counts.scansPlanned, + wouldInsert: dryRun.counts.wouldInsert, + persistedInserts: dryRun.counts.inserted, + authoritativePersistedEntries: entriesAfterDryRun.length, + authoritativeScanAttempts: attemptsAfterDryRun.length, + authoritativeNativeScanJobs: nativeBefore.scanJobs.length, + batches: dryRun.budgetConsumed.batchesProcessed, + operationEstimates: dryRun.operations, + }, + frozen500: { + capture: { + snapshotId: firstRun.snapshotId, + capturedAt: firstRun.sourceCapturedAt, + sourceFetches: firstRun.snapshotCaptureFetches, + }, + configuredBudgets: firstRun.budgets, + firstRun: { + expectedActions: { + wouldInsert: firstRun.counts.wouldInsert, + wouldUpdate: firstRun.counts.wouldUpdate, + scansPlanned: firstRun.counts.scansPlanned, + }, + persistedActions: { + inserted: firstRun.counts.inserted, + updated: firstRun.counts.updated, + authoritativeEntries: frozenEntries.length, + authoritativeAttempts: firstAttempts.length, + completedDeterministicAttempts: firstRun.counts.scansCompleted, + }, + counts: firstRun.counts, + batches: firstRun.budgetConsumed, + operationEstimates: firstRun.operations, + }, + pauseResume: { + pausedCursor: pauseCheckpoint.cursor, + pausedObserved: pauseCheckpoint.observed, + resumedFinalCursor: firstRun.cursor, + resumedFinalObserved: firstRun.counts.observed, + replayedCompletedBatch: false, + }, + identicalRerun: { + counts: repeatedRun.counts, + authoritativeAttempts: repeatedAttempts.length, + operationEstimates: repeatedRun.operations, + }, + changedRerun: { + counts: changedRun.counts, + authoritativeAttempts: changedAttempts.length, + exactSourceHashRescan: changedAttempts[0]?.sourceContentHash ?? null, + artifactHash: changedAttempts[0]?.artifactContentHash ?? null, + hashSemantics: + "sourceContentHash is the upstream observation; deterministic fixture completion is not an independent ClawScan artifact hash", + operationEstimates: changedRun.operations, + }, + finalAuthoritativeEntries: finalFrozenEntries.length, + allEntriesDark: finalFrozenEntries.every((entry) => !entry.publicVisible), + }, + concurrency: { + sameRun: { + fulfilled: sameRunFulfilled.length, + rejected: sameRunRejected.length, + cap: 1, + }, + queueHealth: { + queuedAtThresholdRejected: true, + inFlightAtThresholdRejected: true, + maxCatalogQueued: 1, + maxCatalogInFlight: 1, + maxNativeQueued: 0, + maxNativeInFlight: 0, + queuedThresholdObserved: { + nativeQueued: queueHealthAdmission.queueHealth.nativeQueued, + nativeInFlight: queueHealthAdmission.queueHealth.nativeInFlight, + catalogQueued: activeAtQueuedThreshold.filter((attempt) => attempt.status === "queued") + .length, + catalogInFlight: activeAtQueuedThreshold.filter((attempt) => attempt.status === "running") + .length, + }, + inFlightThresholdObserved: { + nativeQueued: queueHealthAdmission.queueHealth.nativeQueued, + nativeInFlight: queueHealthAdmission.queueHealth.nativeInFlight, + catalogQueued: activeAtInFlightThreshold.filter((attempt) => attempt.status === "queued") + .length, + catalogInFlight: activeAtInFlightThreshold.filter( + (attempt) => attempt.status === "running", + ).length, + }, + }, + crossRunDaily: { + fulfilled: dailyFulfilled.length, + rejected: dailyRejected.length, + cap: 499, + attemptsBeforeRace: 498, + }, + finalCatalogAttemptsToday: allAttempts.length, + }, + admissionContainment: { + deterministicOnlyInLocalProof, + realDispatchOutsideStagingLiveRejected: true, + realDispatchRejection: realAdmissionOutsideStagingLiveError, + }, + lifecycleContainment: { + canceledRunningAttempts: canceledRunning.canceled, + canceledQueuedAttempts: canceledQueued.canceled, + canceledAttemptReadback: canceledAttemptReadback.map((attempt) => ({ + dispatchKind: attempt.dispatchKind, + status: attempt.status, + })), + canceledRunAdmissionRejected: true, + canceledRunAdmissionRejection: canceledRunAdmissionError, + killSwitchAdmissionRejected: true, + killSwitchAdmissionRejection: killSwitchAdmissionError, + }, + rollback: { + controls: disabled.control, + schedulesPresent, + nativeBefore: { + skills: nativeBefore.skills.length, + scanJobs: nativeBefore.scanJobs.length, + sha256: nativeBeforeHash, + }, + nativeAfter: { + skills: nativeAfter.skills.length, + scanJobs: nativeAfter.scanJobs.length, + sha256: nativeAfterHash, + }, + nativeStateUnchanged: nativeBeforeHash === nativeAfterHash, + }, + limits: { + operationCountsAreEstimates: true, + realTestQueueIntegration: + "not deployed; real attempts require an allowlisted Test control and an external artifact fetch before completion", + statusPreviewLimit: disabled.limits, + authoritativeReadback: + "all entry, attempt, native skill, and native scan-job counts above use paginated internal queries", + }, + }; + + await mkdir(dirname(OUTPUT_PATH), { recursive: true }); + await writeFile(OUTPUT_PATH, `${JSON.stringify(proof, null, 2)}\n`); + console.log(JSON.stringify(proof, null, 2)); +} + +await main(); diff --git a/scripts/skills-sh-catalog/run-test-gate.ts b/scripts/skills-sh-catalog/run-test-gate.ts new file mode 100644 index 00000000..a19029a9 --- /dev/null +++ b/scripts/skills-sh-catalog/run-test-gate.ts @@ -0,0 +1,41 @@ +#!/usr/bin/env bun + +function requireEnv(name: string) { + const value = process.env[name]?.trim(); + if (!value) throw new Error(`${name} is required`); + return value; +} + +const targetUrl = requireEnv("CLAWHUB_TEST_CATALOG_GATE_URL"); +const operatorAuthorization = requireEnv("CLAWHUB_TEST_OPERATOR_TOKEN"); +const allowlist = (process.env.CLAWHUB_TEST_CATALOG_ALLOWLIST ?? "") + .split(",") + .map((externalId) => externalId.trim().toLowerCase()) + .filter(Boolean); +if (allowlist.length > 10) throw new Error("CLAWHUB_TEST_CATALOG_ALLOWLIST cannot exceed 10"); + +async function callGate(body: Record) { + const response = await fetch(targetUrl, { + method: "POST", + headers: { + Authorization: `Bearer ${operatorAuthorization}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }); + return { response, text: await response.text() }; +} + +const request = { + allowlist, + reason: "CLAW-556 bounded permanent Test proof", +}; +const execution = await callGate(request); +if (!execution.response.ok) { + throw new Error( + `Permanent Test catalog gate returned HTTP ${execution.response.status}: ${execution.text}`, + ); +} +console.log(JSON.stringify(JSON.parse(execution.text) as Record)); + +export {}; diff --git a/server/routes/ops/skills-sh/catalog-test.post.ts b/server/routes/ops/skills-sh/catalog-test.post.ts new file mode 100644 index 00000000..7372d02c --- /dev/null +++ b/server/routes/ops/skills-sh/catalog-test.post.ts @@ -0,0 +1,248 @@ +import { getVercelOidcToken } from "@vercel/oidc"; +import { defineEventHandler, getHeader, readBody } from "h3"; +import { + captureSkillsShCatalogTestSnapshot, + getSkillsShCatalogTestSourcePolicy, + type SkillsShCatalogGitHubOwnerProof, +} from "../../../skillsShCatalogSource"; + +const TEST_CONVEX_SITE_URL = "https://academic-chihuahua-392.convex.site"; +const OPERATOR_PATH = "/api/v1/operator/skills-sh/catalog-test"; +const MAX_BATCH_SIZE = 50; + +type CatalogTestRequest = { + allowlist?: string[]; + reason?: string; +}; + +function parseCatalogTestRequest(value: unknown): CatalogTestRequest | null { + if (typeof value !== "object" || value === null || Array.isArray(value)) return null; + const body = value as Record; + if ( + (body.allowlist !== undefined && + (!Array.isArray(body.allowlist) || + !body.allowlist.every((externalId) => typeof externalId === "string"))) || + (body.reason !== undefined && typeof body.reason !== "string") + ) { + return null; + } + return { + ...(body.allowlist !== undefined ? { allowlist: body.allowlist as string[] } : {}), + ...(body.reason !== undefined ? { reason: body.reason as string } : {}), + }; +} + +function jsonResponse(payload: unknown, status = 200) { + return new Response(JSON.stringify(payload), { + status, + headers: { + "Cache-Control": "no-store", + "Content-Type": "application/json; charset=utf-8", + }, + }); +} + +async function callConvexOperator( + authorization: string, + init: { method: "GET" } | { method: "POST"; body: Record }, +) { + const response = await fetch(`${TEST_CONVEX_SITE_URL}${OPERATOR_PATH}`, { + method: init.method, + headers: { + Accept: "application/json", + Authorization: authorization, + ...(init.method === "POST" ? { "Content-Type": "application/json" } : {}), + }, + ...(init.method === "POST" ? { body: JSON.stringify(init.body) } : {}), + }); + const text = await response.text(); + if (!response.ok) { + throw new Error(`Convex Test operator returned HTTP ${response.status}: ${text.slice(0, 300)}`); + } + return JSON.parse(text) as Record; +} + +async function executeSnapshotRun( + authorization: string, + snapshot: { + snapshotId: string; + capturedAt: string; + rows: Array>; + metrics: { skillsShFetches: number }; + }, + reason: string, + batchSize: number, +) { + const start = await callConvexOperator(authorization, { + method: "POST", + body: { + operation: "start", + reason, + snapshotId: snapshot.snapshotId, + sourceCapturedAt: snapshot.capturedAt, + snapshotCaptureFetches: snapshot.metrics.skillsShFetches, + fixtureLength: snapshot.rows.length, + }, + }); + const runId = start.runId; + if (typeof runId !== "string") throw new Error("Convex Test operator did not return a run id"); + + let run: Record | null = null; + for (let cursor = 0; cursor < snapshot.rows.length; cursor += batchSize) { + run = await callConvexOperator(authorization, { + method: "POST", + body: { + operation: "batch", + runId, + cursor, + rows: snapshot.rows.slice(cursor, cursor + batchSize), + }, + }); + } + if (run?.status !== "completed" || run.cursor !== snapshot.rows.length) { + throw new Error(`Convex Test snapshot run did not complete all ${snapshot.rows.length} rows`); + } + return { runId, run }; +} + +function batchSizeFromControl(control: Record) { + const maxEntriesPerBatch = control.maxEntriesPerBatch; + const maxWritesPerBatch = control.maxWritesPerBatch; + if ( + typeof maxEntriesPerBatch !== "number" || + !Number.isInteger(maxEntriesPerBatch) || + maxEntriesPerBatch < 1 || + typeof maxWritesPerBatch !== "number" || + !Number.isInteger(maxWritesPerBatch) || + maxWritesPerBatch < 2 + ) { + throw new Error("Convex Test controls do not provide usable batch budgets"); + } + return Math.min(MAX_BATCH_SIZE, maxEntriesPerBatch, Math.floor(maxWritesPerBatch / 2)); +} + +export default defineEventHandler(async (event) => { + const policy = getSkillsShCatalogTestSourcePolicy(process.env); + if (!policy.allowed) return jsonResponse({ error: "not_found" }, 404); + const authorization = getHeader(event, "authorization")?.trim() ?? ""; + if (!authorization.toLowerCase().startsWith("bearer ")) { + return jsonResponse({ error: "operator_authorization_required" }, 401); + } + let rawBody: unknown; + try { + rawBody = await readBody(event); + } catch { + return jsonResponse({ error: "invalid_request_body" }, 400); + } + const body = parseCatalogTestRequest(rawBody); + if (!body) return jsonResponse({ error: "invalid_request_body" }, 400); + const allowlist = Array.from( + new Set((body.allowlist ?? []).map((externalId) => externalId.trim().toLowerCase())), + ).filter(Boolean); + if (allowlist.length > policy.maxRealScanAdmissions) { + return jsonResponse({ error: "allowlist_exceeds_test_ceiling" }, 400); + } + const memoryStart = process.memoryUsage(); + const startedAt = Date.now(); + try { + const staging = await callConvexOperator(authorization, { method: "GET" }); + const control = staging.control; + if (!control || typeof control !== "object") { + throw new Error("Convex Test operator did not return catalog controls"); + } + const batchSize = batchSizeFromControl(control as Record); + const snapshot = await captureSkillsShCatalogTestSnapshot({ + env: process.env, + async getOidcToken() { + return await getVercelOidcToken(); + }, + readConvexControl: async () => + control as { + mode: "off" | "fixture" | "staging-live"; + discoveryEnabled: boolean; + writesEnabled: boolean; + scanPlanningEnabled: boolean; + maxEntriesPerRun: number; + publicVisibilityEnabled: boolean; + }, + admitExternalIds: allowlist, + resolveGitHubOwners: async (owners) => + (await callConvexOperator(authorization, { + method: "POST", + body: { + operation: "resolve-owners", + owners, + }, + })) as SkillsShCatalogGitHubOwnerProof, + }); + const reason = body.reason?.trim() || "bounded permanent Test skills.sh proof"; + const firstRun = await executeSnapshotRun(authorization, snapshot, reason, batchSize); + const identicalRerun = await executeSnapshotRun( + authorization, + snapshot, + `${reason} identical rerun`, + batchSize, + ); + const admission = + allowlist.length > 0 + ? await callConvexOperator(authorization, { + method: "POST", + body: { + operation: "admit", + runId: firstRun.runId, + externalIds: allowlist, + artifacts: snapshot.artifacts, + }, + }) + : { requested: 0, admitted: 0, skipped: 0 }; + if ( + admission.requested !== allowlist.length || + admission.admitted !== allowlist.length || + admission.skipped !== 0 + ) { + throw new Error("Convex Test did not admit the complete allowlist"); + } + const memoryEnd = process.memoryUsage(); + return jsonResponse({ + ok: true, + source: { + project: "openclaw-foundation/clawhub", + vercelSourceSha: process.env.VERCEL_GIT_COMMIT_SHA ?? null, + vercelTargetEnvironment: process.env.VERCEL_TARGET_ENV ?? null, + verifiedIdentity: snapshot.verifiedIdentity, + snapshotId: snapshot.snapshotId, + capturedAt: snapshot.capturedAt, + selection: snapshot.selection, + fetches: snapshot.metrics, + }, + convex: { + deploymentName: staging.deploymentName, + buildSha: staging.buildSha, + firstRun, + identicalRerun, + admission, + }, + runtime: { + elapsedMs: Date.now() - startedAt, + rssStartBytes: memoryStart.rss, + rssEndBytes: memoryEnd.rss, + heapUsedStartBytes: memoryStart.heapUsed, + heapUsedEndBytes: memoryEnd.heapUsed, + }, + controls: { + publicVisibilityEnabled: false, + schedulesEnabled: false, + maxRealScanAdmissions: policy.maxRealScanAdmissions, + batchSize, + }, + }); + } catch (error) { + return jsonResponse( + { + error: "skills_sh_catalog_test_failed", + message: error instanceof Error ? error.message : "Unknown Test gate failure", + }, + 502, + ); + } +}); diff --git a/server/skillsShCatalogSource.test.ts b/server/skillsShCatalogSource.test.ts new file mode 100644 index 00000000..4ad324d2 --- /dev/null +++ b/server/skillsShCatalogSource.test.ts @@ -0,0 +1,538 @@ +/* @vitest-environment node */ + +import { describe, expect, it, vi } from "vitest"; +import { + captureSkillsShCatalogTestSnapshot, + fetchSkillsShCatalogDetail, + fetchSkillsShCatalogPage, + fetchSkillsShCatalogTestPage, + getSkillsShCatalogTestSourcePolicy, + SkillsShCatalogOwnerProofRequiredError, + validateSkillsShCatalogGitHubOwnerProof, +} from "./skillsShCatalogSource"; + +describe("skills.sh Vercel source boundary", () => { + it("uses only the injected Vercel OIDC token for source authentication", async () => { + const fetchImpl = vi.fn(async () => { + return new Response( + JSON.stringify({ + data: [], + pagination: { page: 0, perPage: 500, total: 0, hasMore: false }, + }), + ); + }); + + await fetchSkillsShCatalogPage( + { page: 0, perPage: 500 }, + { + env: { VERCEL_OIDC_TOKEN: "short-lived-vercel-oidc" }, + fetchImpl, + }, + ); + + expect(fetchImpl).toHaveBeenCalledWith("https://skills.sh/api/v1/skills?page=0&per_page=500", { + headers: { + Accept: "application/json", + Authorization: "Bearer short-lived-vercel-oidc", + }, + }); + }); + + it("accepts a request-bound OIDC token without requiring an environment copy", async () => { + const fetchImpl = vi.fn(async () => { + return new Response( + JSON.stringify({ + data: [], + pagination: { page: 0, perPage: 500, total: 0, hasMore: false }, + }), + ); + }); + + await fetchSkillsShCatalogPage( + { page: 0, perPage: 500 }, + { + env: {}, + oidcToken: "request-bound-oidc", + fetchImpl, + }, + ); + + expect(fetchImpl).toHaveBeenCalledWith(expect.any(String), { + headers: expect.objectContaining({ + Authorization: "Bearer request-bound-oidc", + }), + }); + }); + + it("fails closed without OIDC and above the 500-row boundary", async () => { + await expect( + fetchSkillsShCatalogPage({ page: 0, perPage: 500 }, { env: {}, fetchImpl: vi.fn() }), + ).rejects.toThrow("requires VERCEL_OIDC_TOKEN"); + await expect( + fetchSkillsShCatalogPage( + { page: 0, perPage: 501 }, + { env: { VERCEL_OIDC_TOKEN: "token" }, fetchImpl: vi.fn() }, + ), + ).rejects.toThrow("perPage must be an integer between 1 and 500"); + }); + + it("preserves repository-qualified detail ids", async () => { + const fetchImpl = vi.fn(async () => { + return new Response( + JSON.stringify({ + id: "anthropics/claude-code/frontend-design", + source: "anthropics/claude-code", + slug: "frontend-design", + installs: 1, + hash: "hash", + files: [], + }), + ); + }); + + await fetchSkillsShCatalogDetail("anthropics/claude-code/frontend-design", { + env: { VERCEL_OIDC_TOKEN: "token" }, + fetchImpl, + }); + + expect(fetchImpl).toHaveBeenCalledWith( + "https://skills.sh/api/v1/skills/anthropics/claude-code/frontend-design", + expect.any(Object), + ); + }); + + it("requires the Test build, Preview runtime, baked backend, and explicit enable", () => { + expect( + getSkillsShCatalogTestSourcePolicy({ + VERCEL_ENV: "preview", + CLAWHUB_SKILLS_SH_TEST_LIVE_FETCH_ENABLED: "1", + }), + ).toMatchObject({ allowed: false }); + + expect( + getSkillsShCatalogTestSourcePolicy({ + VERCEL_ENV: "preview", + VERCEL_TARGET_ENV: "test", + VITE_CLAWHUB_DEPLOY_ENV: "test", + VITE_CONVEX_URL: "https://academic-chihuahua-392.convex.cloud", + CLAWHUB_SKILLS_SH_TEST_LIVE_FETCH_ENABLED: "1", + }), + ).toEqual({ + allowed: true, + environment: "test", + maxDiscoveryRows: 500, + maxRealScanAdmissions: 10, + }); + }); + + it("requires exact authenticated immutable owner coverage for the selected live set", () => { + expect( + Array.from( + validateSkillsShCatalogGitHubOwnerProof(["anthropics", "nvidia"], { + authentication: "clawhub-github-authenticated", + provenance: "live-github", + fetches: 2, + reused: 0, + owners: [ + { owner: "anthropics", login: "anthropics", id: 76_263_028 }, + { owner: "nvidia", login: "nvidia", id: 1_728_152 }, + ], + }), + ), + ).toEqual([ + ["anthropics", 76_263_028], + ["nvidia", 1_728_152], + ]); + + expect(() => + validateSkillsShCatalogGitHubOwnerProof(["anthropics", "nvidia"], { + authentication: "clawhub-github-authenticated", + provenance: "live-github", + fetches: 1, + reused: 0, + owners: [{ owner: "nvidia", login: "nvidia", id: 1_728_152 }], + }), + ).toThrow("lacks complete authenticated GitHub owner proof"); + expect(() => + validateSkillsShCatalogGitHubOwnerProof(["nvidia"], { + authentication: "clawhub-github-authenticated", + provenance: "stored-authenticated-staging-live", + fetches: 0, + reused: 1, + owners: [{ owner: "nvidia", login: "renamed-owner", id: 1_728_152 }], + }), + ).toThrow("invalid authenticated GitHub owner proof"); + }); + + it("selects 500 hash-qualified rows before requiring exact live owner proofs", async () => { + const listRows = [ + { + id: "anthropics/skills/frontend-design", + installUrl: "https://github.com/anthropics/skills", + installs: 100, + name: "Frontend Design", + slug: "frontend-design", + source: "anthropics/skills", + sourceType: "github", + url: "https://skills.sh/anthropics/skills/frontend-design", + }, + { + id: "anthropics/claude-code/frontend-design", + installUrl: "https://github.com/anthropics/claude-code", + installs: 99, + name: "Frontend Design", + slug: "frontend-design", + source: "anthropics/claude-code", + sourceType: "github", + url: "https://skills.sh/anthropics/claude-code/frontend-design", + }, + ...Array.from({ length: 498 }, (_, index) => ({ + id: `owner/repo-${index}/skill-${index}`, + installUrl: `https://github.com/owner/repo-${index}`, + installs: index, + name: `Skill ${index}`, + slug: `skill-${index}`, + source: `owner/repo-${index}`, + sourceType: "github", + url: `https://skills.sh/owner/repo-${index}/skill-${index}`, + })), + ]; + const nvidiaRows = Array.from({ length: 10 }, (_, index) => ({ + id: `nvidia/skills/nvidia-skill-${index}`, + installUrl: "https://github.com/nvidia/skills", + installs: 1_000 - index, + name: `NVIDIA Skill ${index}`, + slug: `nvidia-skill-${index}`, + source: "nvidia/skills", + sourceType: "github", + url: `https://skills.sh/nvidia/skills/nvidia-skill-${index}`, + })); + const detailUrls: string[] = []; + const fetchImpl = vi.fn(async (urlInput: string | URL | Request) => { + const url = String(urlInput); + if (url.includes("/skills?")) { + return new Response( + JSON.stringify({ + data: listRows, + pagination: { + page: 0, + perPage: 500, + total: 500, + hasMore: false, + }, + }), + ); + } + if (url.includes("/skills/search?")) { + return new Response(JSON.stringify({ data: nvidiaRows })); + } + detailUrls.push(url); + const id = decodeURIComponent(url.split("/api/v1/skills/")[1] ?? ""); + if (id === "owner/repo-0/skill-0") { + return new Response( + JSON.stringify({ + id, + source: "owner/repo-0", + slug: "skill-0", + installs: 1, + hash: null, + files: null, + }), + ); + } + if (id === "owner/repo-1/skill-1") { + return new Response( + JSON.stringify({ + id, + source: "owner/repo-1", + slug: "skill-1", + installs: 1, + hash: "b".repeat(64), + files: null, + }), + ); + } + if (id === "owner/repo-2/skill-2") { + return new Response( + JSON.stringify({ + id, + source: "owner/repo-2", + slug: "skill-2", + installs: 1, + hash: "c".repeat(64), + files: [{ name: "SKILL.md" }], + }), + ); + } + return new Response( + JSON.stringify({ + id, + source: id.split("/").slice(0, 2).join("/"), + slug: id.split("/").at(-1), + installs: 1, + hash: "a".repeat(64), + files: [{ name: "SKILL.md", content: `# ${id}` }], + }), + ); + }); + const readOidc = async () => "oidc"; + const verifyOidc = async () => ({ + payload: { + owner_id: "team_pLdjXbfy0XvPRiNmAygTjTSH", + project_id: "prj_UVAJPNPYrBwTEkPJwkpEySsge8Mc", + environment: "test", + sub: "owner:project:test", + aud: "https://vercel.com", + iss: "https://oidc.vercel.com", + }, + }); + const options = { + env: { + VERCEL_ENV: "preview", + VITE_CLAWHUB_DEPLOY_ENV: "test", + VITE_CONVEX_URL: "https://academic-chihuahua-392.convex.cloud", + CLAWHUB_SKILLS_SH_TEST_LIVE_FETCH_ENABLED: "1", + }, + fetchImpl: fetchImpl as typeof fetch, + getOidcToken: readOidc, + verifyOidcToken: verifyOidc, + readConvexControl: async () => ({ + mode: "staging-live" as const, + discoveryEnabled: true, + writesEnabled: true, + scanPlanningEnabled: true, + maxEntriesPerRun: 500, + publicVisibilityEnabled: false, + }), + admitExternalIds: ["nvidia/skills/nvidia-skill-0"], + }; + + await expect( + captureSkillsShCatalogTestSnapshot({ + ...options, + admitExternalIds: ["owner/repo-1/skill-1"], + }), + ).rejects.toThrow("live admission lacks artifact files"); + detailUrls.length = 0; + + await expect( + captureSkillsShCatalogTestSnapshot({ + ...options, + admitExternalIds: ["owner/repo-2/skill-2"], + }), + ).rejects.toThrow("live admission has incomplete artifact files"); + detailUrls.length = 0; + + const preflight = await captureSkillsShCatalogTestSnapshot(options).catch((error) => error); + expect(preflight).toBeInstanceOf(SkillsShCatalogOwnerProofRequiredError); + expect(preflight).toMatchObject({ + owners: ["anthropics", "nvidia", "owner"], + sourcePreflight: { + skillsShFetches: 506, + listFetches: 1, + searchFetches: 1, + detailFetches: 504, + selection: { + rows: 500, + nvidiaRows: 10, + skippedIncompleteDetails: 1, + }, + }, + }); + expect(detailUrls).toHaveLength(504); + + detailUrls.length = 0; + const snapshot = await captureSkillsShCatalogTestSnapshot({ + ...options, + githubOwnerProof: { + authentication: "clawhub-github-authenticated", + provenance: "stored-authenticated-staging-live", + fetches: 0, + reused: 3, + owners: [ + { owner: "anthropics", login: "anthropics", id: 76_263_028 }, + { owner: "nvidia", login: "nvidia", id: 1_728_152 }, + { owner: "owner", login: "owner", id: 123 }, + ], + }, + }); + expect(snapshot.rows).toHaveLength(500); + expect(snapshot.selection).toMatchObject({ + rows: 500, + nvidiaRows: 10, + requiredCollisionIds: [ + "anthropics/skills/frontend-design", + "anthropics/claude-code/frontend-design", + ], + skippedIncompleteDetails: 1, + }); + expect(snapshot.rows.some((row) => row.externalId === "owner/repo-0/skill-0")).toBe(false); + expect(snapshot.artifacts).toHaveLength(1); + expect(snapshot.metrics).toMatchObject({ + skillsShFetches: 506, + listFetches: 1, + searchFetches: 1, + detailFetches: 504, + githubOwnerFetches: 0, + githubOwnerIdsReused: 3, + githubOwnerProofProvenance: "stored-authenticated-staging-live", + skippedIncompleteDetails: 1, + }); + expect(detailUrls).toHaveLength(504); + }); + + it("rejects an ordinary Preview even when spoofable Test strings are present", async () => { + await expect( + fetchSkillsShCatalogTestPage({ + env: { + VERCEL_ENV: "preview", + VERCEL_TARGET_ENV: "test", + VITE_CLAWHUB_DEPLOY_ENV: "test", + VITE_CONVEX_URL: "https://academic-chihuahua-392.convex.cloud", + CLAWHUB_SKILLS_SH_TEST_LIVE_FETCH_ENABLED: "1", + }, + getOidcToken: async () => "ordinary-preview-token", + verifyOidcToken: async () => { + throw new Error("unexpected Vercel project"); + }, + readConvexControl: async () => ({ + mode: "staging-live", + discoveryEnabled: true, + writesEnabled: true, + scanPlanningEnabled: true, + maxEntriesPerRun: 500, + publicVisibilityEnabled: false, + }), + }), + ).rejects.toThrow("unexpected Vercel project"); + }); + + it("rejects a verified ClawHub Preview token without the custom Test environment claim", async () => { + await expect( + fetchSkillsShCatalogTestPage({ + env: { + VERCEL_ENV: "preview", + VERCEL_TARGET_ENV: "test", + VITE_CLAWHUB_DEPLOY_ENV: "test", + VITE_CONVEX_URL: "https://academic-chihuahua-392.convex.cloud", + CLAWHUB_SKILLS_SH_TEST_LIVE_FETCH_ENABLED: "1", + }, + getOidcToken: async () => "ordinary-preview-token", + verifyOidcToken: async () => ({ + payload: { + owner_id: "team_pLdjXbfy0XvPRiNmAygTjTSH", + project_id: "prj_UVAJPNPYrBwTEkPJwkpEySsge8Mc", + environment: "preview", + sub: "owner:project:preview", + aud: "https://vercel.com", + iss: "https://oidc.vercel.com", + }, + }), + readConvexControl: async () => ({ + mode: "staging-live", + discoveryEnabled: true, + writesEnabled: true, + scanPlanningEnabled: true, + maxEntriesPerRun: 500, + publicVisibilityEnabled: false, + }), + }), + ).rejects.toThrow("verified ClawHub Vercel identity"); + }); + + it("fetches through the verified request token only when the dark Convex control allows it", async () => { + const rows = Array.from({ length: 500 }, (_, index) => ({ + id: `owner/repo/skill-${index}`, + installUrl: null, + installs: index, + name: `Skill ${index}`, + slug: `skill-${index}`, + source: "owner/repo", + sourceType: "github", + url: `https://skills.sh/owner/repo/skill-${index}`, + })); + const fetchImpl = vi.fn(async () => { + return new Response( + JSON.stringify({ + data: rows, + pagination: { + page: 0, + perPage: 500, + total: 500, + hasMore: false, + }, + }), + ); + }); + const env = { + VERCEL_ENV: "preview", + VITE_CLAWHUB_DEPLOY_ENV: "test", + VITE_CONVEX_URL: "https://academic-chihuahua-392.convex.cloud", + CLAWHUB_SKILLS_SH_TEST_LIVE_FETCH_ENABLED: "1", + }; + const getOidcToken = vi.fn(async () => "request-token"); + const verifyOidcToken = vi.fn(async () => ({ + payload: { + owner_id: "team_pLdjXbfy0XvPRiNmAygTjTSH", + project_id: "prj_UVAJPNPYrBwTEkPJwkpEySsge8Mc", + environment: "test", + sub: "owner:project:test", + aud: "https://vercel.com", + iss: "https://oidc.vercel.com", + }, + })); + + const result = await fetchSkillsShCatalogTestPage({ + env, + fetchImpl, + getOidcToken, + verifyOidcToken, + readConvexControl: async () => ({ + mode: "staging-live", + discoveryEnabled: true, + writesEnabled: true, + scanPlanningEnabled: true, + maxEntriesPerRun: 500, + publicVisibilityEnabled: false, + }), + }); + + expect(result.page.data).toHaveLength(500); + expect(getOidcToken).toHaveBeenCalledOnce(); + expect(verifyOidcToken).toHaveBeenCalledWith("request-token", { + projectId: "prj_UVAJPNPYrBwTEkPJwkpEySsge8Mc", + ownerId: "team_pLdjXbfy0XvPRiNmAygTjTSH", + environment: "test", + }); + expect(result.controls).toEqual({ + maxDiscoveryRows: 500, + maxRealScanAdmissions: 10, + publicVisibilityEnabled: false, + }); + expect(fetchImpl).toHaveBeenCalledWith( + "https://skills.sh/api/v1/skills?page=0&per_page=500", + expect.objectContaining({ + headers: expect.objectContaining({ + Authorization: "Bearer request-token", + }), + }), + ); + + await expect( + fetchSkillsShCatalogTestPage({ + env, + fetchImpl, + getOidcToken: async () => "request-token", + verifyOidcToken, + readConvexControl: async () => ({ + mode: "fixture", + discoveryEnabled: true, + writesEnabled: true, + scanPlanningEnabled: true, + maxEntriesPerRun: 500, + publicVisibilityEnabled: false, + }), + }), + ).rejects.toThrow("dark Convex staging control"); + }); +}); diff --git a/server/skillsShCatalogSource.ts b/server/skillsShCatalogSource.ts new file mode 100644 index 00000000..b2ad062c --- /dev/null +++ b/server/skillsShCatalogSource.ts @@ -0,0 +1,678 @@ +import { createHash } from "node:crypto"; +import { getVercelOidcToken, verifyVercelOidcToken, type VercelOidcPayload } from "@vercel/oidc"; + +const SKILLS_SH_API_BASE = "https://skills.sh/api/v1"; +const MAX_SOURCE_PAGE_SIZE = 500; +const MAX_TEST_SCAN_ADMISSIONS = 10; +const DETAIL_CONCURRENCY = 8; +const CLAWHUB_VERCEL_OWNER_ID = "team_pLdjXbfy0XvPRiNmAygTjTSH"; +const CLAWHUB_VERCEL_PROJECT_ID = "prj_UVAJPNPYrBwTEkPJwkpEySsge8Mc"; +const CLAWHUB_TEST_CONVEX_URL = "https://academic-chihuahua-392.convex.cloud"; + +export type SkillsShCatalogSourceEnv = { + CLAWHUB_SKILLS_SH_TEST_LIVE_FETCH_ENABLED?: string; + VERCEL_ENV?: string; + VERCEL_OIDC_TOKEN?: string; + VERCEL_TARGET_ENV?: string; + VITE_CLAWHUB_DEPLOY_ENV?: string; + VITE_CONVEX_URL?: string; +}; + +export type SkillsShCatalogListRow = { + id: string; + installUrl: string | null; + installs: number; + name: string; + slug: string; + source: string; + sourceType: string; + url: string; +}; + +export type SkillsShCatalogDetail = { + id: string; + source: string; + slug: string; + installs: number; + hash: string | null; + files: Array<{ + name?: unknown; + content?: unknown; + }> | null; +}; + +type SkillsShCatalogPage = { + data: SkillsShCatalogListRow[]; + pagination: { + page: number; + perPage: number; + total: number; + hasMore: boolean; + }; +}; + +type SkillsShCatalogSearch = { + data: SkillsShCatalogListRow[]; +}; + +type HashQualifiedSkillsShCatalogDetail = SkillsShCatalogDetail & { + hash: string; +}; + +function assertIntegerInRange(name: string, value: number, min: number, max: number) { + if (!Number.isInteger(value) || value < min || value > max) { + throw new Error(`${name} must be an integer between ${min} and ${max}`); + } +} + +function requireOidcToken(env: SkillsShCatalogSourceEnv, requestOidcToken?: string) { + const token = requestOidcToken?.trim() || env.VERCEL_OIDC_TOKEN?.trim(); + if (!token) { + throw new Error("skills.sh catalog source requires VERCEL_OIDC_TOKEN"); + } + return token; +} + +async function fetchSkillsShJson( + path: string, + options: { + env?: SkillsShCatalogSourceEnv; + fetchImpl?: typeof fetch; + oidcToken?: string; + } = {}, +): Promise { + const env = options.env ?? process.env; + const fetchImpl = options.fetchImpl ?? fetch; + const response = await fetchImpl(`${SKILLS_SH_API_BASE}${path}`, { + headers: { + Accept: "application/json", + Authorization: `Bearer ${requireOidcToken(env, options.oidcToken)}`, + }, + }); + if (!response.ok) { + throw new Error(`skills.sh catalog source returned HTTP ${response.status}`); + } + return (await response.json()) as T; +} + +export async function fetchSkillsShCatalogPage( + args: { + page: number; + perPage: number; + }, + options: { + env?: SkillsShCatalogSourceEnv; + fetchImpl?: typeof fetch; + oidcToken?: string; + } = {}, +) { + assertIntegerInRange("page", args.page, 0, 100_000); + assertIntegerInRange("perPage", args.perPage, 1, MAX_SOURCE_PAGE_SIZE); + return await fetchSkillsShJson( + `/skills?page=${args.page}&per_page=${args.perPage}`, + options, + ); +} + +export async function searchSkillsShCatalog( + args: { + query: string; + owner?: string; + limit: number; + }, + options: { + env?: SkillsShCatalogSourceEnv; + fetchImpl?: typeof fetch; + oidcToken?: string; + } = {}, +) { + assertIntegerInRange("limit", args.limit, 1, MAX_SOURCE_PAGE_SIZE); + const params = new URLSearchParams({ + q: args.query, + limit: String(args.limit), + }); + if (args.owner) params.set("owner", args.owner); + return await fetchSkillsShJson( + `/skills/search?${params.toString()}`, + options, + ); +} + +export async function fetchSkillsShCatalogDetail( + id: string, + options: { + env?: SkillsShCatalogSourceEnv; + fetchImpl?: typeof fetch; + oidcToken?: string; + } = {}, +) { + const normalizedId = id + .split("/") + .map((part) => encodeURIComponent(part)) + .join("/"); + if (normalizedId.split("/").length !== 3) { + throw new Error("skills.sh catalog detail id must be owner/repo/skill"); + } + return await fetchSkillsShJson(`/skills/${normalizedId}`, options); +} + +export function getSkillsShCatalogTestSourcePolicy(env: SkillsShCatalogSourceEnv = process.env) { + if (env.VITE_CLAWHUB_DEPLOY_ENV !== "test") { + return { + allowed: false as const, + environment: env.VITE_CLAWHUB_DEPLOY_ENV?.trim() || "unknown", + reason: "skills.sh live Test discovery requires the Test build marker", + }; + } + if (env.VERCEL_ENV !== "preview") { + return { + allowed: false as const, + environment: env.VERCEL_ENV?.trim() || "unknown", + reason: "skills.sh live Test discovery requires the Vercel Preview runtime", + }; + } + if (env.VITE_CONVEX_URL !== CLAWHUB_TEST_CONVEX_URL) { + return { + allowed: false as const, + environment: "test", + reason: "skills.sh live Test discovery requires the baked Test Convex backend", + }; + } + if (env.CLAWHUB_SKILLS_SH_TEST_LIVE_FETCH_ENABLED !== "1") { + return { + allowed: false as const, + environment: "test", + reason: "skills.sh live Test discovery is disabled", + }; + } + return { + allowed: true as const, + environment: "test", + maxDiscoveryRows: MAX_SOURCE_PAGE_SIZE, + maxRealScanAdmissions: MAX_TEST_SCAN_ADMISSIONS, + }; +} + +type VerifyVercelOidc = ( + token: string, + options: { + projectId: string; + ownerId: string; + environment: string; + }, +) => Promise<{ payload: VercelOidcPayload }>; + +type SkillsShCatalogTestControl = { + mode: "off" | "fixture" | "staging-live"; + discoveryEnabled: boolean; + writesEnabled: boolean; + scanPlanningEnabled: boolean; + maxEntriesPerRun: number; + publicVisibilityEnabled: boolean; +}; + +export type SkillsShCatalogGitHubOwnerProof = { + authentication: "clawhub-github-authenticated"; + provenance: + | "live-github" + | "stored-authenticated-staging-live" + | "stored-authenticated-staging-live+live-github"; + fetches: number; + reused: number; + owners: Array<{ owner: string; id: number; login: string }>; +}; + +export class SkillsShCatalogOwnerProofRequiredError extends Error { + constructor( + readonly owners: string[], + readonly sourcePreflight: { + skillsShFetches: number; + listFetches: number; + searchFetches: number; + detailFetches: number; + selection: { + rows: number; + nvidiaRows: number; + requiredCollisionIds: readonly string[]; + skippedIncompleteDetails: number; + }; + }, + ) { + super("skills.sh live Test source requires authenticated GitHub owner proofs"); + this.name = "SkillsShCatalogOwnerProofRequiredError"; + } +} + +export function validateSkillsShCatalogGitHubOwnerProof( + selectedOwners: readonly string[], + proof: SkillsShCatalogGitHubOwnerProof, +) { + const expectedProvenance = + proof.reused === 0 + ? "live-github" + : proof.fetches === 0 + ? "stored-authenticated-staging-live" + : "stored-authenticated-staging-live+live-github"; + if ( + proof.authentication !== "clawhub-github-authenticated" || + !Number.isInteger(proof.fetches) || + proof.fetches < 0 || + !Number.isInteger(proof.reused) || + proof.reused < 0 || + proof.fetches + proof.reused !== selectedOwners.length || + proof.provenance !== expectedProvenance || + proof.owners.length !== selectedOwners.length + ) { + throw new Error("skills.sh live Test source lacks complete authenticated GitHub owner proof"); + } + const selected = new Set(selectedOwners); + const ids = new Map(); + for (const resolvedOwner of proof.owners) { + const owner = resolvedOwner.owner.trim().toLowerCase(); + const login = resolvedOwner.login.trim().toLowerCase(); + if ( + !selected.has(owner) || + login !== owner || + ids.has(owner) || + !Number.isSafeInteger(resolvedOwner.id) || + resolvedOwner.id <= 0 + ) { + throw new Error( + "skills.sh live Test source returned invalid authenticated GitHub owner proof", + ); + } + ids.set(owner, resolvedOwner.id); + } + for (const owner of selectedOwners) { + if (!ids.has(owner)) { + throw new Error( + `skills.sh live Test source lacks an authenticated immutable owner id for ${owner}`, + ); + } + } + return ids; +} + +async function authorizeSkillsShCatalogTestRequest( + options: { + env?: SkillsShCatalogSourceEnv; + getOidcToken?: () => Promise; + verifyOidcToken?: VerifyVercelOidc; + } = {}, +) { + const env = options.env ?? process.env; + const policy = getSkillsShCatalogTestSourcePolicy(env); + if (!policy.allowed) throw new Error(policy.reason); + + const getOidcToken = options.getOidcToken ?? getVercelOidcToken; + const verifyOidcToken = options.verifyOidcToken ?? verifyVercelOidcToken; + const token = await getOidcToken(); + const verified = await verifyOidcToken(token, { + projectId: CLAWHUB_VERCEL_PROJECT_ID, + ownerId: CLAWHUB_VERCEL_OWNER_ID, + environment: "test", + }); + if ( + verified.payload.project_id !== CLAWHUB_VERCEL_PROJECT_ID || + verified.payload.owner_id !== CLAWHUB_VERCEL_OWNER_ID || + verified.payload.environment !== "test" + ) { + throw new Error("skills.sh live Test discovery requires verified ClawHub Vercel identity"); + } + return { + ...policy, + oidcToken: token, + verifiedIdentity: { + ownerId: verified.payload.owner_id, + projectId: verified.payload.project_id, + environment: verified.payload.environment, + }, + }; +} + +export async function fetchSkillsShCatalogTestPage(options: { + env?: SkillsShCatalogSourceEnv; + fetchImpl?: typeof fetch; + getOidcToken?: () => Promise; + verifyOidcToken?: VerifyVercelOidc; + readConvexControl: () => Promise; +}) { + const authorization = await authorizeSkillsShCatalogTestRequest(options); + const control = await options.readConvexControl(); + if ( + control.mode !== "staging-live" || + !control.discoveryEnabled || + !control.writesEnabled || + !control.scanPlanningEnabled || + control.maxEntriesPerRun < 1 || + control.maxEntriesPerRun > authorization.maxDiscoveryRows || + control.publicVisibilityEnabled + ) { + throw new Error("skills.sh live Test discovery requires the dark Convex staging control"); + } + const page = await fetchSkillsShCatalogPage( + { page: 0, perPage: authorization.maxDiscoveryRows }, + { + env: options.env, + fetchImpl: options.fetchImpl, + oidcToken: authorization.oidcToken, + }, + ); + if (page.data.length > control.maxEntriesPerRun) { + throw new Error("skills.sh live Test discovery exceeded the Convex run budget"); + } + return { + page, + verifiedIdentity: authorization.verifiedIdentity, + controls: { + maxDiscoveryRows: control.maxEntriesPerRun, + maxRealScanAdmissions: authorization.maxRealScanAdmissions, + publicVisibilityEnabled: false, + }, + }; +} + +const REQUIRED_COLLISION_IDS = [ + "anthropics/skills/frontend-design", + "anthropics/claude-code/frontend-design", +] as const; + +function isGitHubCatalogRow(row: SkillsShCatalogListRow) { + const source = row.source.trim().toLowerCase(); + const slug = row.slug.trim().toLowerCase(); + return ( + row.sourceType === "github" && + source.split("/").length === 2 && + /^[a-z0-9][a-z0-9-]*$/.test(slug) && + row.id.trim().toLowerCase() === `${source}/${slug}` + ); +} + +function normalizeListRow(row: SkillsShCatalogListRow) { + const [owner = "", repo = ""] = row.source.split("/"); + const slug = row.slug.trim().toLowerCase(); + return { + ...row, + owner: owner.trim().toLowerCase(), + repo: repo.trim().toLowerCase(), + slug, + externalId: `${owner.trim().toLowerCase()}/${repo.trim().toLowerCase()}/${slug}`, + }; +} + +function sha256Hex(bytes: Uint8Array | string) { + return createHash("sha256").update(bytes).digest("hex"); +} + +type CompleteArtifactFile = { name: string; content: string }; + +function hasCompleteArtifactFiles( + files: SkillsShCatalogDetail["files"], +): files is CompleteArtifactFile[] { + return ( + Array.isArray(files) && + files.length > 0 && + files.every( + (file) => + typeof file.name === "string" && + file.name.trim().length > 0 && + typeof file.content === "string", + ) + ); +} + +function buildArtifact(detail: SkillsShCatalogDetail) { + if (!hasCompleteArtifactFiles(detail.files)) { + throw new Error(`skills.sh live admission has incomplete artifact files: ${detail.id}`); + } + const files = detail.files + .map((file) => { + const bytes = Buffer.from(file.content, "utf8"); + return { + path: file.name, + size: bytes.byteLength, + sha256: sha256Hex(bytes), + contentType: file.name.toLowerCase().endsWith(".md") + ? "text/markdown; charset=utf-8" + : "text/plain; charset=utf-8", + contentBase64: bytes.toString("base64"), + }; + }) + .sort((left, right) => left.path.localeCompare(right.path)); + const manifest = files.map((file) => `${file.path}\0${file.sha256}\n`).join(""); + return { + artifactContentHash: sha256Hex(manifest), + files, + }; +} + +function createSkillsShFetchOptions( + env: SkillsShCatalogSourceEnv | undefined, + fetchImpl: typeof fetch | undefined, + value: string, +) { + return { + env, + fetchImpl, + oidcToken: value, + }; +} + +async function selectSkillsShCatalogTestRows( + fetchOptions: { + env?: SkillsShCatalogSourceEnv; + fetchImpl?: typeof fetch; + oidcToken?: string; + }, + requiredArtifactIds: ReadonlySet, +) { + const candidates = new Map>(); + let listFetches = 0; + for (let page = 0; candidates.size < MAX_SOURCE_PAGE_SIZE + 100; page += 1) { + const response = await fetchSkillsShCatalogPage( + { page, perPage: MAX_SOURCE_PAGE_SIZE }, + fetchOptions, + ); + listFetches += 1; + for (const row of response.data) { + if (!isGitHubCatalogRow(row)) continue; + const normalized = normalizeListRow(row); + candidates.set(normalized.externalId, normalized); + } + if (!response.pagination.hasMore) break; + } + const nvidia = await searchSkillsShCatalog( + { query: "nvidia", owner: "nvidia", limit: 200 }, + fetchOptions, + ); + const nvidiaRows = nvidia.data + .filter(isGitHubCatalogRow) + .map(normalizeListRow) + .filter((row) => row.owner === "nvidia") + .slice(0, 10); + const requiredIds = new Set([ + ...REQUIRED_COLLISION_IDS, + ...nvidiaRows.map((row) => row.externalId), + ...requiredArtifactIds, + ]); + for (const row of nvidiaRows) candidates.set(row.externalId, row); + for (const id of requiredIds) { + if (!candidates.has(id)) throw new Error(`Required skills.sh live row is missing: ${id}`); + } + const candidateRows = [ + ...Array.from(requiredIds, (id) => candidates.get(id)!), + ...Array.from(candidates.values()).filter((row) => !requiredIds.has(row.externalId)), + ]; + const selected: Array<{ + row: ReturnType; + detail: HashQualifiedSkillsShCatalogDetail; + }> = []; + let detailFetches = 0; + let skippedIncompleteDetails = 0; + for ( + let offset = 0; + offset < candidateRows.length && selected.length < MAX_SOURCE_PAGE_SIZE; + offset += DETAIL_CONCURRENCY + ) { + const batch = candidateRows.slice(offset, offset + DETAIL_CONCURRENCY); + const details = await Promise.all( + batch.map(async (row) => await fetchSkillsShCatalogDetail(row.externalId, fetchOptions)), + ); + detailFetches += details.length; + for (let index = 0; index < batch.length; index += 1) { + const row = batch[index]!; + const detail = details[index]!; + const hasExactHash = + detail.id.trim().toLowerCase() === row.externalId && + typeof detail.hash === "string" && + /^[a-f0-9]{64}$/i.test(detail.hash); + if (!hasExactHash) { + if (requiredIds.has(row.externalId) || requiredArtifactIds.has(row.externalId)) { + throw new Error( + `Required skills.sh live row lacks an exact detail hash: ${row.externalId}`, + ); + } + skippedIncompleteDetails += 1; + continue; + } + if ( + requiredArtifactIds.has(row.externalId) && + (!Array.isArray(detail.files) || detail.files.length < 1) + ) { + throw new Error(`skills.sh live admission lacks artifact files: ${row.externalId}`); + } + if (requiredArtifactIds.has(row.externalId) && !hasCompleteArtifactFiles(detail.files)) { + throw new Error( + `skills.sh live admission has incomplete artifact files: ${row.externalId}`, + ); + } + selected.push({ row, detail: detail as HashQualifiedSkillsShCatalogDetail }); + if (selected.length === MAX_SOURCE_PAGE_SIZE) break; + } + } + if (selected.length !== MAX_SOURCE_PAGE_SIZE) { + throw new Error(`Expected ${MAX_SOURCE_PAGE_SIZE} hash-qualified live rows`); + } + return { + selected, + selectedOwners: Array.from(new Set(selected.map(({ row }) => row.owner))).sort(), + listFetches, + detailFetches, + skippedIncompleteDetails, + selection: { + rows: selected.length, + nvidiaRows: selected.filter(({ row }) => row.owner === "nvidia").length, + requiredCollisionIds: REQUIRED_COLLISION_IDS, + skippedIncompleteDetails, + }, + }; +} + +export async function captureSkillsShCatalogTestSnapshot(options: { + env?: SkillsShCatalogSourceEnv; + fetchImpl?: typeof fetch; + getOidcToken?: () => Promise; + verifyOidcToken?: VerifyVercelOidc; + readConvexControl: () => Promise; + admitExternalIds?: string[]; + githubOwnerProof?: SkillsShCatalogGitHubOwnerProof; + resolveGitHubOwners?: (owners: string[]) => Promise; +}) { + const startedAt = Date.now(); + const authorization = await authorizeSkillsShCatalogTestRequest(options); + const control = await options.readConvexControl(); + if ( + control.mode !== "staging-live" || + !control.discoveryEnabled || + !control.writesEnabled || + !control.scanPlanningEnabled || + control.maxEntriesPerRun !== MAX_SOURCE_PAGE_SIZE || + control.publicVisibilityEnabled + ) { + throw new Error("skills.sh live Test capture requires the exact dark 500-row control"); + } + const fetchOptions = createSkillsShFetchOptions( + options.env, + options.fetchImpl, + authorization.oidcToken, + ); + const admitted = new Set( + (options.admitExternalIds ?? []).map((externalId) => externalId.trim().toLowerCase()), + ); + if (admitted.size > authorization.maxRealScanAdmissions) { + throw new Error( + `skills.sh live Test admission cannot exceed ${authorization.maxRealScanAdmissions}`, + ); + } + const selection = await selectSkillsShCatalogTestRows(fetchOptions, admitted); + const sourcePreflight = { + skillsShFetches: selection.listFetches + 1 + selection.detailFetches, + listFetches: selection.listFetches, + searchFetches: 1, + detailFetches: selection.detailFetches, + selection: selection.selection, + }; + const githubOwnerProof = + options.githubOwnerProof ?? + (options.resolveGitHubOwners + ? await options.resolveGitHubOwners(selection.selectedOwners) + : null); + if (!githubOwnerProof) { + throw new SkillsShCatalogOwnerProofRequiredError(selection.selectedOwners, sourcePreflight); + } + const githubOwnerIds = validateSkillsShCatalogGitHubOwnerProof( + selection.selectedOwners, + githubOwnerProof, + ); + const selected = selection.selected; + const rows = selected.map(({ row, detail }) => { + const githubOwnerId = githubOwnerIds.get(row.owner)!; + return { + externalId: row.externalId, + githubOwnerId, + owner: row.owner, + repo: row.repo, + slug: row.slug, + displayName: row.name.trim() || row.slug, + sourceUrl: row.url, + githubRepoUrl: row.installUrl ?? `https://github.com/${row.owner}/${row.repo}`, + sourceContentHash: detail.hash.toLowerCase(), + installs: row.installs, + }; + }); + const artifacts = selected + .filter(({ row }) => admitted.has(row.externalId)) + .map(({ row, detail }) => ({ + externalId: row.externalId, + ...buildArtifact(detail), + })); + if (artifacts.length !== admitted.size) { + throw new Error("skills.sh live Test admission artifact is not present in the selected 500"); + } + return { + snapshotId: `skills-sh-test-live-500:${sha256Hex( + rows.map((row) => `${row.externalId}:${row.sourceContentHash}\n`).join(""), + ).slice(0, 16)}`, + capturedAt: new Date().toISOString(), + rows, + artifacts, + verifiedIdentity: authorization.verifiedIdentity, + selection: { + rows: rows.length, + nvidiaRows: rows.filter((row) => row.owner === "nvidia").length, + requiredCollisionIds: REQUIRED_COLLISION_IDS, + skippedIncompleteDetails: selection.skippedIncompleteDetails, + }, + metrics: { + runtimeMs: Date.now() - startedAt, + skillsShFetches: selection.listFetches + 1 + selection.detailFetches, + listFetches: selection.listFetches, + searchFetches: 1, + detailFetches: selection.detailFetches, + githubOwnerFetches: githubOwnerProof.fetches, + githubOwnerIdsReused: githubOwnerProof.reused, + githubOwnerProofProvenance: githubOwnerProof.provenance, + skippedIncompleteDetails: selection.skippedIncompleteDetails, + }, + }; +} diff --git a/server/skillsShCatalogTestRoute.test.ts b/server/skillsShCatalogTestRoute.test.ts new file mode 100644 index 00000000..17373952 --- /dev/null +++ b/server/skillsShCatalogTestRoute.test.ts @@ -0,0 +1,379 @@ +/* @vitest-environment node */ + +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +const getHeaderMock = vi.fn(); +const getVercelOidcTokenMock = vi.fn(); +const readBodyMock = vi.fn(); +const captureSnapshotMock = vi.fn(); +const sourcePolicyMock = vi.fn(); + +vi.mock("h3", () => ({ + defineEventHandler: (handler: unknown) => handler, + getHeader: (...args: unknown[]) => getHeaderMock(...args), + readBody: (...args: unknown[]) => readBodyMock(...args), +})); + +vi.mock("@vercel/oidc", () => ({ + getVercelOidcToken: (...args: unknown[]) => getVercelOidcTokenMock(...args), +})); + +vi.mock("./skillsShCatalogSource", () => ({ + captureSkillsShCatalogTestSnapshot: (...args: unknown[]) => captureSnapshotMock(...args), + getSkillsShCatalogTestSourcePolicy: (...args: unknown[]) => sourcePolicyMock(...args), +})); + +describe("skills.sh permanent Test operator route", () => { + beforeEach(() => { + getHeaderMock.mockReset(); + getVercelOidcTokenMock.mockReset(); + readBodyMock.mockReset(); + captureSnapshotMock.mockReset(); + sourcePolicyMock.mockReset(); + sourcePolicyMock.mockReturnValue({ + allowed: true, + environment: "test", + maxDiscoveryRows: 500, + maxRealScanAdmissions: 10, + }); + getHeaderMock.mockImplementation((_event: unknown, name: string) => { + if (name === "authorization") return "Bearer operator-token"; + return undefined; + }); + getVercelOidcTokenMock.mockResolvedValue("request-oidc-token"); + readBodyMock.mockResolvedValue({ + allowlist: ["nvidia/skills/aiq-deploy"], + reason: "bounded Test proof", + }); + captureSnapshotMock.mockImplementation(async (options) => { + expect(await options.getOidcToken()).toBe("request-oidc-token"); + expect(options.admitExternalIds).toEqual(["nvidia/skills/aiq-deploy"]); + expect(await options.resolveGitHubOwners(["anthropics", "nvidia"])).toEqual({ + authentication: "clawhub-github-authenticated", + provenance: "stored-authenticated-staging-live", + fetches: 0, + reused: 2, + owners: [ + { owner: "anthropics", login: "anthropics", id: 76_263_028 }, + { owner: "nvidia", login: "nvidia", id: 1_728_152 }, + ], + }); + return { + snapshotId: "skills-sh-test-live-500:abc", + capturedAt: "2026-07-21T00:00:00.000Z", + rows: Array.from({ length: 500 }, (_, index) => ({ + externalId: `owner/repo/skill-${index}`, + })), + artifacts: [ + { + externalId: "nvidia/skills/aiq-deploy", + artifactContentHash: "a".repeat(64), + files: [], + }, + ], + verifiedIdentity: { + ownerId: "team", + projectId: "project", + environment: "test", + }, + selection: { + rows: 500, + nvidiaRows: 10, + requiredCollisionIds: [], + }, + metrics: { + runtimeMs: 100, + skillsShFetches: 502, + listFetches: 1, + searchFetches: 1, + detailFetches: 500, + githubOwnerFetches: 0, + githubOwnerIdsReused: 2, + githubOwnerProofProvenance: "stored-authenticated-staging-live", + }, + }; + }); + vi.stubGlobal( + "fetch", + vi.fn(async (_url: string, init: RequestInit) => { + if (init.method === "GET") { + return new Response( + JSON.stringify({ + deploymentName: "academic-chihuahua-392", + buildSha: "source-sha", + control: { + mode: "staging-live", + discoveryEnabled: true, + writesEnabled: true, + scanPlanningEnabled: true, + maxEntriesPerRun: 500, + maxEntriesPerBatch: 100, + maxWritesPerBatch: 100, + publicVisibilityEnabled: false, + }, + }), + ); + } + const body = JSON.parse(String(init.body)) as { operation: string; cursor?: number }; + if (body.operation === "start") { + return new Response(JSON.stringify({ runId: "skillsShCatalogRuns:test" })); + } + if (body.operation === "resolve-owners") { + return new Response( + JSON.stringify({ + authentication: "clawhub-github-authenticated", + provenance: "stored-authenticated-staging-live", + fetches: 0, + reused: 2, + owners: [ + { owner: "anthropics", login: "anthropics", id: 76_263_028 }, + { owner: "nvidia", login: "nvidia", id: 1_728_152 }, + ], + }), + ); + } + if (body.operation === "batch") { + return new Response( + JSON.stringify({ + status: body.cursor === 450 ? "completed" : "running", + cursor: (body.cursor ?? 0) + 50, + }), + ); + } + if (body.operation === "admit") { + return new Response(JSON.stringify({ requested: 1, admitted: 1, skipped: 0 })); + } + return new Response(JSON.stringify({ error: "unsupported_operation" }), { status: 400 }); + }), + ); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it("resolves owners through Convex and never exposes the request OIDC token", async () => { + const handler = (await import("./routes/ops/skills-sh/catalog-test.post")).default; + const response = (await handler({} as never)) as Response; + expect(response.status).toBe(200); + const body = await response.json(); + expect(body).toMatchObject({ + ok: true, + source: { + selection: { rows: 500 }, + fetches: { + skillsShFetches: 502, + githubOwnerFetches: 0, + githubOwnerIdsReused: 2, + githubOwnerProofProvenance: "stored-authenticated-staging-live", + }, + }, + convex: { + firstRun: { runId: "skillsShCatalogRuns:test" }, + identicalRerun: { runId: "skillsShCatalogRuns:test" }, + admission: { admitted: 1 }, + }, + controls: { publicVisibilityEnabled: false, schedulesEnabled: false }, + }); + expect(JSON.stringify(body)).not.toContain("request-oidc-token"); + + const fetchMock = vi.mocked(fetch); + expect(fetchMock).toHaveBeenCalledTimes(25); + for (const [, init] of fetchMock.mock.calls) { + expect(init?.headers).toMatchObject({ Authorization: "Bearer operator-token" }); + expect(JSON.stringify(init)).not.toContain("request-oidc-token"); + } + expect( + fetchMock.mock.calls.some(([, init]) => { + const body = init?.body ? JSON.parse(String(init.body)) : null; + return body?.operation === "admit"; + }), + ).toBe(true); + }); + + it("is a hard 404 outside the exact Test runtime", async () => { + sourcePolicyMock.mockReturnValue({ + allowed: false, + environment: "production", + reason: "disabled", + }); + const handler = (await import("./routes/ops/skills-sh/catalog-test.post")).default; + const response = (await handler({} as never)) as Response; + expect(response.status).toBe(404); + expect(captureSnapshotMock).not.toHaveBeenCalled(); + expect(fetch).not.toHaveBeenCalled(); + }); + + it.each([ + { body: [], label: "array body" }, + { body: { allowlist: {} }, label: "non-array allowlist" }, + { body: { allowlist: [null] }, label: "non-string allowlist member" }, + { body: { reason: 42 }, label: "non-string reason" }, + ])("rejects a malformed $label before calling any backend", async ({ body }) => { + readBodyMock.mockResolvedValue(body); + const handler = (await import("./routes/ops/skills-sh/catalog-test.post")).default; + const response = (await handler({} as never)) as Response; + + expect(response.status).toBe(400); + expect(await response.json()).toEqual({ error: "invalid_request_body" }); + expect(captureSnapshotMock).not.toHaveBeenCalled(); + expect(fetch).not.toHaveBeenCalled(); + }); + + it("sizes persistence batches from the active Convex row and write budgets", async () => { + readBodyMock.mockResolvedValue({ + allowlist: ["nvidia/skills/aiq-deploy"], + reason: "bounded Test proof", + }); + const batchSizes: number[] = []; + vi.stubGlobal( + "fetch", + vi.fn(async (_url: string, init: RequestInit) => { + if (init.method === "GET") { + return new Response( + JSON.stringify({ + deploymentName: "academic-chihuahua-392", + buildSha: "source-sha", + control: { + mode: "staging-live", + discoveryEnabled: true, + writesEnabled: true, + scanPlanningEnabled: true, + maxEntriesPerRun: 500, + maxEntriesPerBatch: 20, + maxWritesPerBatch: 30, + publicVisibilityEnabled: false, + }, + }), + ); + } + const body = JSON.parse(String(init.body)) as { + operation: string; + cursor?: number; + rows?: unknown[]; + }; + if (body.operation === "start") { + return new Response(JSON.stringify({ runId: "skillsShCatalogRuns:test" })); + } + if (body.operation === "resolve-owners") { + return new Response( + JSON.stringify({ + authentication: "clawhub-github-authenticated", + provenance: "stored-authenticated-staging-live", + fetches: 0, + reused: 2, + owners: [ + { owner: "anthropics", login: "anthropics", id: 76_263_028 }, + { owner: "nvidia", login: "nvidia", id: 1_728_152 }, + ], + }), + ); + } + if (body.operation === "batch") { + batchSizes.push(body.rows?.length ?? 0); + return new Response( + JSON.stringify({ + status: + (body.cursor ?? 0) + (body.rows?.length ?? 0) >= 500 ? "completed" : "running", + cursor: (body.cursor ?? 0) + (body.rows?.length ?? 0), + }), + ); + } + if (body.operation === "admit") { + return new Response(JSON.stringify({ requested: 1, admitted: 1, skipped: 0 })); + } + return new Response(JSON.stringify({ error: "unsupported_operation" }), { status: 400 }); + }), + ); + + const handler = (await import("./routes/ops/skills-sh/catalog-test.post")).default; + const response = (await handler({} as never)) as Response; + + expect(response.status).toBe(200); + expect(await response.json()).toMatchObject({ + controls: { batchSize: 15 }, + }); + expect(batchSizes).toHaveLength(68); + expect(Math.max(...batchSizes)).toBe(15); + expect(batchSizes.reduce((sum, size) => sum + size, 0)).toBe(1_000); + }); + + it.each([ + { status: "running", cursor: 500 }, + { status: "completed", cursor: 499 }, + ])("fails closed when a snapshot run ends at $status with cursor $cursor", async (terminal) => { + const baseFetch = vi.mocked(fetch); + vi.stubGlobal( + "fetch", + vi.fn(async (url: string, init: RequestInit) => { + const body = init.body ? (JSON.parse(String(init.body)) as { operation?: string }) : null; + if (body?.operation === "batch") { + return new Response(JSON.stringify(terminal)); + } + return await baseFetch(url, init); + }), + ); + + const handler = (await import("./routes/ops/skills-sh/catalog-test.post")).default; + const response = (await handler({} as never)) as Response; + + expect(response.status).toBe(502); + expect(await response.json()).toMatchObject({ + error: "skills_sh_catalog_test_failed", + message: expect.stringContaining("did not complete all 500 rows"), + }); + }); + + it("fails closed when any allowlisted real scan is skipped", async () => { + const baseFetch = vi.mocked(fetch); + vi.stubGlobal( + "fetch", + vi.fn(async (url: string, init: RequestInit) => { + const body = init.body ? (JSON.parse(String(init.body)) as { operation?: string }) : null; + if (body?.operation === "admit") { + return new Response(JSON.stringify({ requested: 1, admitted: 0, skipped: 1 })); + } + return await baseFetch(url, init); + }), + ); + + const handler = (await import("./routes/ops/skills-sh/catalog-test.post")).default; + const response = (await handler({} as never)) as Response; + + expect(response.status).toBe(502); + expect(await response.json()).toMatchObject({ + error: "skills_sh_catalog_test_failed", + message: expect.stringContaining("did not admit the complete allowlist"), + }); + }); + + it("uses the normalized deduplicated allowlist for capture, admission, and accounting", async () => { + readBodyMock.mockResolvedValue({ + allowlist: [ + " NVIDIA/SKILLS/AIQ-DEPLOY ", + "", + "nvidia/skills/aiq-deploy", + " nvidia/skills/aiq-deploy ", + ], + }); + const handler = (await import("./routes/ops/skills-sh/catalog-test.post")).default; + const response = (await handler({} as never)) as Response; + + expect(response.status).toBe(200); + expect(captureSnapshotMock).toHaveBeenCalledWith( + expect.objectContaining({ + admitExternalIds: ["nvidia/skills/aiq-deploy"], + }), + ); + const admitCall = vi.mocked(fetch).mock.calls.find(([, init]) => { + const body = init?.body ? JSON.parse(String(init.body)) : null; + return body?.operation === "admit"; + }); + expect(JSON.parse(String(admitCall?.[1]?.body))).toMatchObject({ + externalIds: ["nvidia/skills/aiq-deploy"], + }); + expect(await response.json()).toMatchObject({ + convex: { admission: { requested: 1, admitted: 1, skipped: 0 } }, + }); + }); +});