From 409dcd25458e56fd6e03af75c5a451ce158f2495 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Wed, 14 Oct 2026 09:25:41 -0700 Subject: [PATCH 1/3] chore(gate): drive the dead-export guard with knip, drop ts-prune ts-prune has been unmaintained since 2022 and pulls ts-morph@13 plus a hand-rolled output parser that false-positived on satisfies/boolean/ Record narrowing and could not resolve path-alias fan-in. The guard unions knip's default scan with a production agent-workspace scan (knip.agents.json; --production ignores test-file consumers so the director surface stays enforced); parsing is fail-closed and config carries no spawn args, so the tsPruneArgs race is gone. Allowlist semantics are unchanged. Entries go 70 to 83: 42 superseded ts-prune artifacts removed; tax entries added for 22 consumer-less exports plus 4 unreachable files (owner-reason blocks, pending cleanup lanes) and the 29 director-surface names consumed only by workspace package tests. Deleting them belongs to the cleanup lanes. --- bun.lock | 224 +++++++++------- knip.agents.json | 39 +++ knip.json | 33 +++ package.json | 2 +- scripts/check-dead-exports.test.ts | 410 +++++++++++++++++++---------- scripts/check-dead-exports.ts | 390 +++++++++++++++++++-------- scripts/dead-export-allowlist.txt | 163 +++++++----- scripts/dead-export-guard.json | 1 - 8 files changed, 861 insertions(+), 401 deletions(-) create mode 100644 knip.agents.json create mode 100644 knip.json diff --git a/bun.lock b/bun.lock index 261a518a3..ba5cd6113 100644 --- a/bun.lock +++ b/bun.lock @@ -36,9 +36,9 @@ "devDependencies": { "@intx/inference-testing": "0.3.0", "@types/bun": "1.4.3", + "knip": "^6.41.0", "oxfmt": "^0.73.0", "oxlint": "^1.88.0", - "ts-prune": "0.10.3", "typescript": "5.9.3", "typescript-language-server": "^4.3.4", "ws": "^8.21.0", @@ -264,10 +264,6 @@ "@ark/util": ["@ark/util@0.56.7", "", {}, "sha512-z8pVnrPk/x9GC18yOp5wCe8rR2+Z6us4CTSg7dvxdsDLuA2xXYq+QETD1fO6f3fMuXe+PCjpvtCnhTW9RHLgEw=="], - "@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="], - - "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], - "@better-auth/core": ["@better-auth/core@1.7.6", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.41.1", "@standard-schema/spec": "^1.1.0", "zod": "^4.5.4" }, "peerDependencies": { "@better-auth/utils": "0.4.2", "@better-fetch/fetch": "1.3.2", "@opentelemetry/api": "^1.9.0", "better-call": "1.4.0", "jose": "^6.1.0", "kysely": "^0.28.5 || ^0.29.0", "nanostores": "^1.0.1" }, "optionalPeers": ["@opentelemetry/api"] }, "sha512-yBDDO0J4VCHT1qEZtzj6aMexX4etyLesitoxehcWHzPrzYqrFHwe/hgskye+imlGhQVOysuljMg8M8BA6GrtIg=="], "@better-auth/drizzle-adapter": ["@better-auth/drizzle-adapter@1.7.6", "", { "peerDependencies": { "@better-auth/core": "^1.7.6", "@better-auth/utils": "0.4.2", "drizzle-orm": "^0.45.2 || >=1.0.0-rc.1 <2.0.0" }, "optionalPeers": ["drizzle-orm"] }, "sha512-ENlhwC7kkTjquuiel3Efggik0hC4vtD7ppJ8FhsiHhRJA8K6RT4hbhzkfm6keNvjdnAeWwCejm7FmMO4GoSGpA=="], @@ -324,6 +320,12 @@ "@corbits/xai-provider": ["@corbits/xai-provider@0.1.2", "", { "dependencies": { "arktype": "^2.2.3" }, "peerDependencies": { "@corbits/oauth-core": "^0.2.0", "@corbits/openai-responses": "^0.2.0", "@intx/inference": "^0.4.0", "@intx/types": "^0.4.0" } }, "sha512-4gCSLehuE922UG5DeAQLMA3iaq5MzSEgKNuq2Lb4aJqEYsQ85t8rTmu0DXs+T6mHNq2cbCI9eCF4aXuyv420iA=="], + "@emnapi/core": ["@emnapi/core@1.11.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.11.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA=="], + + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], + "@gar/promise-retry": ["@gar/promise-retry@1.0.3", "", {}, "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA=="], "@hono/node-server": ["@hono/node-server@2.1.1", "", { "peerDependencies": { "hono": "^4" } }, "sha512-ELuehkj5VCBdgEw9zs+ivkKwyzzUCSQuE96YmiPvn1ECBoZCczbFXJLeEGMTYjphP6gydh4pHMqEYPVMYUVgQg=="], @@ -390,16 +392,12 @@ "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.30.0", "", { "dependencies": { "@hono/node-server": "^1.19.9 || ^2.0.5", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA=="], + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.2.5", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" } }, "sha512-HshSvXzmBxNzqejd3k/KemgQ3hdREUYFRRrgxOZ+gPljDIsO2SDZrVZMd4Z/HsxudQbE62OVyVFE8z9BCurfjA=="], + "@noble/ciphers": ["@noble/ciphers@2.4.0", "", {}, "sha512-AnjFn0Jv92laAkvMrghlFZq4qQCIN/4DxFV/eooqtC2YTjB7kBeLMS2T9KJX4Dn+ZVXLOwK0lSgqDtx9gvxtiw=="], "@noble/hashes": ["@noble/hashes@2.4.0", "", {}, "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA=="], - "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], - - "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], - - "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], - "@npmcli/agent": ["@npmcli/agent@4.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^11.2.1", "socks-proxy-agent": "^8.0.3" } }, "sha512-EUEuWAxnL07Sp5/iC/1X6Xj+XThUvnbei9zfRWZdEXa7lss9RTHMhAHBeg+MZ5To9s/gGaSI+UwZTPdYMvKSeg=="], "@npmcli/fs": ["@npmcli/fs@5.0.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og=="], @@ -426,6 +424,84 @@ "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.5.12", "", { "os": "win32", "cpu": "x64" }, "sha512-KTwtwpfd2zF9opVh3SyRJYDd1o3Xv4XL8OZb8Zi+CqWUel6Y2IDCiVivCv8fGJt3J7wOIXXtuZI9ZUkLyKJCiQ=="], + "@oxc-parser/binding-android-arm-eabi": ["@oxc-parser/binding-android-arm-eabi@0.153.0", "", { "os": "android", "cpu": "arm" }, "sha512-BcWu25dMwMjHpaDCEidmpAi7rGg1qZoihJeUUH9bD55pVZ0z4Wa5CQgqSTo0R41H81oozisQKW3WqvAN2m77IQ=="], + + "@oxc-parser/binding-android-arm64": ["@oxc-parser/binding-android-arm64@0.153.0", "", { "os": "android", "cpu": "arm64" }, "sha512-9l5rdp2DhRqVriBHsNpu5cEf+hizZ4B58rt93HzyQEBUgZiC6qe2oFpaAAnXGCxHGnQExI1s7S2G0QhFacUZ6w=="], + + "@oxc-parser/binding-darwin-arm64": ["@oxc-parser/binding-darwin-arm64@0.153.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-EcpsyRXHhENDpekpG4QNaSZj2F/BAz4Gh4o89zZwNss86R8KyRSQ6sXbAj74Xyeb4z3j/yBZZ6joQZhraIWyfw=="], + + "@oxc-parser/binding-darwin-x64": ["@oxc-parser/binding-darwin-x64@0.153.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-hlj80HUQy07/2lYp6rmxH+3u+H2ik3rjLR52CUdPApxmZCNxASDUf/+ykTzAHS/fehftmVMATKvPaWmuLHuasg=="], + + "@oxc-parser/binding-freebsd-x64": ["@oxc-parser/binding-freebsd-x64@0.153.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-fWRcv2pMSh1qtez46MOu8vuUJ5fiaQZPHSphbWulbL7SHBMKfRgZl2PzjXZYQJbq7NEXyXbgIuIuUm+/Rmkv8w=="], + + "@oxc-parser/binding-linux-arm-gnueabihf": ["@oxc-parser/binding-linux-arm-gnueabihf@0.153.0", "", { "os": "linux", "cpu": "arm" }, "sha512-paZeUK1GXShl/bOJQET1/8AQxayFeuN4SIWAkgTRxE7ZQwv9uYSB2n0648smygP22N7jcI3zxAm0Z+ALNiIKxg=="], + + "@oxc-parser/binding-linux-arm-musleabihf": ["@oxc-parser/binding-linux-arm-musleabihf@0.153.0", "", { "os": "linux", "cpu": "arm" }, "sha512-qFPp5NwMf4K1C0O+DCVGmcbOaYyYAq98Rvv9p4VSfb777iUwlJCqMUxf6QG9N5f5ls4TEX6LitOZHi+6GL6rYQ=="], + + "@oxc-parser/binding-linux-arm64-gnu": ["@oxc-parser/binding-linux-arm64-gnu@0.153.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-oufEhZCX7saeo27Logra8cvBR1jQ/yw2zUI2hHmWw3qA/XYqEW31FOvLQBv7DWN32NjVdlcaP11WBBZTJo4k9g=="], + + "@oxc-parser/binding-linux-arm64-musl": ["@oxc-parser/binding-linux-arm64-musl@0.153.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-zXYzuAqng85h6+LI3iRm0zeMLX1/K2z41ZAMWxVRgt0D10VvZUaV5q+TEB46KDhoKBpdexoTLC6QoB/EiP2oqQ=="], + + "@oxc-parser/binding-linux-ppc64-gnu": ["@oxc-parser/binding-linux-ppc64-gnu@0.153.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-J7+NLwLEzdkKM9cKyPHH/AjXXstWOs98KYgLrPPnO4N9MO8EZRBfPNnBcS3S67uMOBkRpUmCl2FhRnWdQ5CbBA=="], + + "@oxc-parser/binding-linux-riscv64-gnu": ["@oxc-parser/binding-linux-riscv64-gnu@0.153.0", "", { "os": "linux", "cpu": "none" }, "sha512-sD0K11GQTLWylln54nPMMTtgmBbQrR5It/vMpZpy/fogiw8O2AvIE+9LuWUXddrWk9zvFIHGRcSgFn4IHOfoYg=="], + + "@oxc-parser/binding-linux-riscv64-musl": ["@oxc-parser/binding-linux-riscv64-musl@0.153.0", "", { "os": "linux", "cpu": "none" }, "sha512-7L66uql2BhvSz51LwfExjOdn6et/ydTiQxl23VApdKzJhTc3TczA1Qt6VR/UcvlQrL2ZbytIJb447vh7p5wmUw=="], + + "@oxc-parser/binding-linux-s390x-gnu": ["@oxc-parser/binding-linux-s390x-gnu@0.153.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-hXV3T7mNyrPR19rbPYkglkQJtoM/UNo/xwbZeeqdAxEg8PXgb4LXXuxPpGgI4bzRRCWwLUxdDSS58xIyC2u1TA=="], + + "@oxc-parser/binding-linux-x64-gnu": ["@oxc-parser/binding-linux-x64-gnu@0.153.0", "", { "os": "linux", "cpu": "x64" }, "sha512-Da8Vqs1xdtoDYtEjmozD7wGgJeLBQkMbR0WUBECOSxodyX5DXy8ySlvJWbukyO2Q0+TM9EK+4LT6tbq0jCg4hQ=="], + + "@oxc-parser/binding-linux-x64-musl": ["@oxc-parser/binding-linux-x64-musl@0.153.0", "", { "os": "linux", "cpu": "x64" }, "sha512-zGAPxNTIIw6X5WyTRscIFE/oDjpdNtrrl32ing6NOXdGjzRucadnxBilexLdeYQ9YxixF93GujpXyQ0jw7ddSg=="], + + "@oxc-parser/binding-openharmony-arm64": ["@oxc-parser/binding-openharmony-arm64@0.153.0", "", { "os": "none", "cpu": "arm64" }, "sha512-GoHzpqP5XyPIp9DHokoEdCp+K4UPl/h1neFj1uXnUB3pJYyOQ7NOhFVOttNUgCGcj2eywAvu5cRivTOSq4S10g=="], + + "@oxc-parser/binding-win32-arm64-msvc": ["@oxc-parser/binding-win32-arm64-msvc@0.153.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-xGcy12tO9k7zFbeyQzEuilfWfQpyRq23/fLPfN3wjSWuWv58k46fQT1hsO1KFNykZuVGW9xSJE/z592TmAoSDA=="], + + "@oxc-parser/binding-win32-ia32-msvc": ["@oxc-parser/binding-win32-ia32-msvc@0.153.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-o1ykUjWncvXX6N5a4KebgLjtD8zRui+f1llMhSGoSC9oP2KOqapRpdwmZ3NcNG0axFouwVKmhGBjd+IhA8BksA=="], + + "@oxc-parser/binding-win32-x64-msvc": ["@oxc-parser/binding-win32-x64-msvc@0.153.0", "", { "os": "win32", "cpu": "x64" }, "sha512-gxQz7G96Up136fd+2zWrP+o6FYGs9NO4Dyv55IoeJFJUDwLFHcLYY11hhA5wQKs0HoM57Q8esQ3nEVhnCdsjAQ=="], + + "@oxc-project/types": ["@oxc-project/types@0.153.0", "", {}, "sha512-jSNrRIyvPVLL5E1z/tS5zLAprosGz/FuI1PWwEOz59j3E4TTTzSpqKb/RDGqJhhW0QHYISGXxq582wGx6T11Tw=="], + + "@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.24.2", "", { "os": "android", "cpu": "arm" }, "sha512-y09e0L0SRI2OA2tUIrjBgoV3eH5hvUKXNkJqXmNo5V2WxIjyC7I7aJfRLMEVpA8yi95f90gFDvO0VMgrDw+vwA=="], + + "@oxc-resolver/binding-android-arm64": ["@oxc-resolver/binding-android-arm64@11.24.2", "", { "os": "android", "cpu": "arm64" }, "sha512-cl4icWaZFnLdg8m6qtnh5rBMuGbxc/ptStFHLeCNwr+2cZjkjNwQu/jYRS0CHlnPecOJMpuS5M6/BH+0J/YkEg=="], + + "@oxc-resolver/binding-darwin-arm64": ["@oxc-resolver/binding-darwin-arm64@11.24.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-At29QEMF6HajbQvgY8K6OXnHD1x9rad74xBEfmCB6ZqCGsdq75aK7tOYcTbOanMy8qdIBrfL3SMr3p/lfSlb9w=="], + + "@oxc-resolver/binding-darwin-x64": ["@oxc-resolver/binding-darwin-x64@11.24.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-A5Kqr1EUj4oIL5CF4WRssq/o5P0Y11cwoFouMRmQ7YnC/A8V93nv1nb7aSU8HwcgmXropjLNkVTl4MN87cu28Q=="], + + "@oxc-resolver/binding-freebsd-x64": ["@oxc-resolver/binding-freebsd-x64@11.24.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-R5xkRBRRz7ceH/P5Jrc6G7FmdUdgpLYyESFAUDVTNQ9K0sGPxcp4ljiwEwEqsvNcQ4sYbMRrWcHHBCu7ksAJVw=="], + + "@oxc-resolver/binding-linux-arm-gnueabihf": ["@oxc-resolver/binding-linux-arm-gnueabihf@11.24.2", "", { "os": "linux", "cpu": "arm" }, "sha512-k/RuYL4L/R58IBn3wT5ma3Wh4k62bp1eYCFRWCmMsasUOqL+H6sW0VGFadEzKWXFFlz+2uIMoeMk9ySSZJHgbg=="], + + "@oxc-resolver/binding-linux-arm-musleabihf": ["@oxc-resolver/binding-linux-arm-musleabihf@11.24.2", "", { "os": "linux", "cpu": "arm" }, "sha512-bnHAak3ujYfH5pKk4NieFNbvYvernfoQDgwLddbZ3OtMYrem87/qjlA+u+aKG0oZcqSLGCful/6/CEA+aeAgaA=="], + + "@oxc-resolver/binding-linux-arm64-gnu": ["@oxc-resolver/binding-linux-arm64-gnu@11.24.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-vDT3KHgzYp47gmtNOqL2VNhCyl5Zv643eyxm//A68J8DeUGXrvD1pZFiaT4jSfe+RInfnn1R2yVHye4enx6RnA=="], + + "@oxc-resolver/binding-linux-arm64-musl": ["@oxc-resolver/binding-linux-arm64-musl@11.24.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-+kMlQvbzfyEYtu5FcjE4p+ttBLpKW4d/AsAsuE69BxV6V4twZJeIQZFfD8gh/wqglY0MkPSezWXQH0jBV13MUw=="], + + "@oxc-resolver/binding-linux-ppc64-gnu": ["@oxc-resolver/binding-linux-ppc64-gnu@11.24.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-shjfMhmZ3gq9fv/w7bi3PnZlgOPG+2QAOFf0BJF0EgBSIGZ6PMLN2zbGEblTUYB/NKVDRyYhE2ff3dJ1QqNPkA=="], + + "@oxc-resolver/binding-linux-riscv64-gnu": ["@oxc-resolver/binding-linux-riscv64-gnu@11.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-zGelwFR5oRo+b69k8Lrzun86DyUHzfKN6cnjbR9l7Z7NIRznOE/2ZvPa1IUKqAL2PzAXOdwkfVqNvO1H2RlpAw=="], + + "@oxc-resolver/binding-linux-riscv64-musl": ["@oxc-resolver/binding-linux-riscv64-musl@11.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-qxZ1SWCXJY0eyhAlP6Lmo9F2Nrtx7EkYj9oCgL8apDPCwXwCEDA2U697bbT81JIc2IrVjxO4KX6WU2N+oN9Z4w=="], + + "@oxc-resolver/binding-linux-s390x-gnu": ["@oxc-resolver/binding-linux-s390x-gnu@11.24.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-sGCecF3cx2DFlH4t/z7ApnOnXqN48p5p5mlHDEnHTAukQa2P+qMVE4CwyWE9W+q/m3QJ7kKfGrIjax31f44oFQ=="], + + "@oxc-resolver/binding-linux-x64-gnu": ["@oxc-resolver/binding-linux-x64-gnu@11.24.2", "", { "os": "linux", "cpu": "x64" }, "sha512-k/VlMMcSzMlahb3/fENM4rTlsJ0s3fFROA0KXPBmKggqmTSaE383sl8F3KCOXPLmVsYfW6hCitMhXCEtNeZxxg=="], + + "@oxc-resolver/binding-linux-x64-musl": ["@oxc-resolver/binding-linux-x64-musl@11.24.2", "", { "os": "linux", "cpu": "x64" }, "sha512-8hbnZyNi97b/8wapYaIF9+t9GmZKBW2vunaOc3h9HGJptH7b7XpvZqOTBSm/MpTjr7H497BlgOaSfLUdhmy2bw=="], + + "@oxc-resolver/binding-openharmony-arm64": ["@oxc-resolver/binding-openharmony-arm64@11.24.2", "", { "os": "none", "cpu": "arm64" }, "sha512-MvyGik3a6pVgZ0t/kWlbmFxFLmXQJwgLsY2eYFHLpy0wGwRbfzeIGgDwQ3kXqE30z+kSXennRkCrT7TUvkptNg=="], + + "@oxc-resolver/binding-wasm32-wasi": ["@oxc-resolver/binding-wasm32-wasi@11.24.2", "", { "dependencies": { "@emnapi/core": "1.11.2", "@emnapi/runtime": "1.11.2", "@napi-rs/wasm-runtime": "^1.1.6" }, "cpu": "none" }, "sha512-vHcssMPwO08RTvj/c0iOBz90attxyG3wQJ0dTcyEQK43LRpcdLWZlV5feBhv6Isn6ahbQIzHbCgfa81+RiML0Q=="], + + "@oxc-resolver/binding-win32-arm64-msvc": ["@oxc-resolver/binding-win32-arm64-msvc@11.24.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-uokJqro2iBqkFvJdKQLP7d8/BUmFwESQFVmIJUQKj1Xn1a/LysJoe1vmeECLF5b3jsV8CAL5sEMJXX6SdK9Nhg=="], + + "@oxc-resolver/binding-win32-x64-msvc": ["@oxc-resolver/binding-win32-x64-msvc@11.24.2", "", { "os": "win32", "cpu": "x64" }, "sha512-UqGPmo56KDfLlfXFAFIrNflHT8tFxWGEivWg3Zeyp4Uy2NlKN1FGPr6/BxcLGG3+kZ6Wp14g5Uj+n71boqZfiw=="], + "@oxfmt/binding-android-arm-eabi": ["@oxfmt/binding-android-arm-eabi@0.73.0", "", { "os": "android", "cpu": "arm" }, "sha512-dhunAdMbi/4o0Y66/+Wl6rpsNqpvE4i26Qw6bB77A97L2lJoktWg6m+X9QOon8iOEEqzD9rEZqFi5uZ4oIzhCA=="], "@oxfmt/binding-android-arm64": ["@oxfmt/binding-android-arm64@0.73.0", "", { "os": "android", "cpu": "arm64" }, "sha512-dBoypjC2/xeyKyIhN2waX0/XeoccH9k0lVVe+sGRc4X8HSJVRtqyZDoXdr7bGY9A75c51dX0z0dLDpGQKwCdCg=="], @@ -508,7 +584,7 @@ "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - "@ts-morph/common": ["@ts-morph/common@0.12.3", "", { "dependencies": { "fast-glob": "^3.2.7", "minimatch": "^3.0.4", "mkdirp": "^1.0.4", "path-browserify": "^1.0.1" } }, "sha512-4tUmeLyXJnJWvTFOKtcNJ1yh0a3SsTLi2MUoyj8iUNznFRN1ZquaNe7Oukqrnki2FzZkm0J9adCNLDZxUzvj+w=="], + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.4", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A=="], "@types/bun": ["@types/bun@1.4.3", "", { "dependencies": { "bun-types": "1.4.3" } }, "sha512-N67Pqy8JXbAY/j0tNraS3V5mubGvc6rrrNDmDCtZLlFzccbUJPDb0tvgUBC5q4m8ybkP93Jcn1QB+RYQ6PvBhw=="], @@ -516,8 +592,6 @@ "@types/node": ["@types/node@26.5.0", "", { "dependencies": { "undici-types": "~8.9.0" } }, "sha512-dVSGpriSoCgz8WnDNTuSSuSv1PC/ALXihO4ulRZt7Md8k9mlbdin3lGOcDE8SnWOgf513ByWlXd7BK4azmyg/A=="], - "@types/parse-json": ["@types/parse-json@4.0.2", "", {}, "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="], - "@types/semver": ["@types/semver@7.8.0", "", {}, "sha512-1mAINjtQCXXeLkJ9ehXkwOcBpqtLxiVtKhpUf83DdRNdQKV0iXZpaHYqRr7nj+wvxuJzoAmAwXI+sCNMv1CzLQ=="], "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], @@ -540,7 +614,7 @@ "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], - "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], @@ -550,9 +624,7 @@ "body-parser": ["body-parser@2.3.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^2.0.0", "debug": "^4.4.3", "http-errors": "^2.0.1", "iconv-lite": "^0.7.2", "on-finished": "^2.4.1", "qs": "^6.15.2", "raw-body": "^3.0.2", "type-is": "^2.1.0" } }, "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw=="], - "brace-expansion": ["brace-expansion@1.1.18", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw=="], - - "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + "brace-expansion": ["brace-expansion@5.0.9", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg=="], "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], @@ -570,18 +642,10 @@ "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], - "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], - "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], "clean-git-ref": ["clean-git-ref@2.0.1", "", {}, "sha512-bLSptAy2P0s6hU4PzuIMKmMJJSE6gLXGH1cntDu7bWJUksvuM+7ReOK61mozULErYvP6a15rnYl0zFDef+pyPw=="], - "code-block-writer": ["code-block-writer@11.0.3", "", {}, "sha512-NiujjUFB4SwScJq2bwbYUtXbZhBSlY6vYzm++3Q6oC+U+injTqfPYFK8wS9COOmb2lueqp0ZRB4nK1VYeHgNyw=="], - - "commander": ["commander@6.2.1", "", {}, "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA=="], - - "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], - "content-disposition": ["content-disposition@1.1.0", "", {}, "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g=="], "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], @@ -592,8 +656,6 @@ "cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="], - "cosmiconfig": ["cosmiconfig@7.1.0", "", { "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", "parse-json": "^5.0.0", "path-type": "^4.0.0", "yaml": "^1.10.0" } }, "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA=="], - "crc-32": ["crc-32@1.2.2", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="], "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], @@ -622,8 +684,6 @@ "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], - "error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="], - "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], @@ -648,20 +708,20 @@ "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], - "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], - "fast-text-encoding": ["fast-text-encoding@1.0.6", "", {}, "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w=="], "fast-uri": ["fast-uri@3.1.7", "", {}, "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg=="], - "fastq": ["fastq@1.20.3", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw=="], + "fd-package-json": ["fd-package-json@2.0.0", "", { "dependencies": { "walk-up-path": "^4.0.0" } }, "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ=="], - "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], "finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], + "formatly": ["formatly@0.11.4", "", { "dependencies": { "fd-package-json": "^2.0.0", "package-manager-detector": "^1.8.0" }, "bin": { "formatly": "bin/index.mjs" } }, "sha512-TeCC10lP8KCgoX+Fe5lryILIT0/Dy+nP0OSVnsuB5CwXoLuOdqxCYlPFF1inR8S/zTy1ec7TjhS2TeRKWzzImA=="], + "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], @@ -676,9 +736,9 @@ "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], - "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], + "get-tsconfig": ["get-tsconfig@4.14.3", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA=="], - "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], @@ -710,24 +770,14 @@ "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], - "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], "ip-address": ["ip-address@10.7.0", "", {}, "sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA=="], "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], - "is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], - "is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="], - "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], - - "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], - - "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], - "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], "is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="], @@ -740,29 +790,23 @@ "isomorphic-textencoder": ["isomorphic-textencoder@1.0.1", "", { "dependencies": { "fast-text-encoding": "^1.0.0" } }, "sha512-676hESgHullDdHDsj469hr+7t3i/neBKU9J7q1T4RHaWwLAsaQnywC0D1dIUId0YZ+JtVrShzuBk1soo0+GVcQ=="], - "jose": ["jose@6.2.12", "", {}, "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw=="], - - "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="], - "json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="], + "jose": ["jose@6.2.12", "", {}, "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw=="], "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], "json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="], - "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], - "jsonparse": ["jsonparse@1.3.1", "", {}, "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg=="], "just-debounce-it": ["just-debounce-it@1.1.0", "", {}, "sha512-87Nnc0qZKgBZuhFZjYVjSraic0x7zwjhaTMrCKlj0QYKH6lh0KbFzVnfu6LHan03NO7J8ygjeBeD0epejn5Zcg=="], "just-once": ["just-once@1.1.0", "", {}, "sha512-+rZVpl+6VyTilK7vB/svlMPil4pxqIJZkbnN7DKZTOzyXfun6ZiFeq2Pk4EtCEHZ0VU4EkdFzG8ZK5F3PErcDw=="], - "kysely": ["kysely@0.29.6", "", {}, "sha512-hHaB8C/rfzDDtr/t8YZwxAuPJTT0zHyaPoVzcXwDYhYNAgH/4sIfVhi/XLLIY+bL/FqaIJnjATDbi8ObSELmxg=="], - - "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], + "knip": ["knip@6.41.0", "", { "dependencies": { "formatly": "^0.11.4", "get-tsconfig": "4.14.3", "jiti": "^2.7.0", "oxc-parser": "^0.153.0", "oxc-resolver": "11.24.2", "picomatch": "^4.0.7", "smol-toml": "^1.9.0", "strip-json-comments": "5.0.3", "tinyglobby": "^0.2.17", "unbash": "^5.0.0", "yaml": "^2.9.1", "zod": "^4.6.5" }, "bin": { "knip": "bin/knip.js", "knip-bun": "bin/knip-bun.js" } }, "sha512-DrhQlIuV6xScd5Q+WTF1znd1w41QzxFGCtAHXCajsH/ndGMotHoNYbdXbuFtzW3mIOmniHtZ7zCtvnQkUmJtQQ=="], - "lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="], + "kysely": ["kysely@0.29.6", "", {}, "sha512-hHaB8C/rfzDDtr/t8YZwxAuPJTT0zHyaPoVzcXwDYhYNAgH/4sIfVhi/XLLIY+bL/FqaIJnjATDbi8ObSELmxg=="], "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], @@ -776,17 +820,13 @@ "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="], - "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], - - "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], - "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], - "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + "minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="], "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], @@ -806,8 +846,6 @@ "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="], - "mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "nanostores": ["nanostores@1.5.4", "", {}, "sha512-4UXY7OAJEK0Y6V3SudBU7cXjAzTDc189R78dDOh0Xbm19gpKYto6WizSg58LJf12lTsRxKSLGyE117OiLpM4DQ=="], @@ -834,33 +872,29 @@ "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], + "oxc-parser": ["oxc-parser@0.153.0", "", { "dependencies": { "@oxc-project/types": "^0.153.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.153.0", "@oxc-parser/binding-android-arm64": "0.153.0", "@oxc-parser/binding-darwin-arm64": "0.153.0", "@oxc-parser/binding-darwin-x64": "0.153.0", "@oxc-parser/binding-freebsd-x64": "0.153.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.153.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.153.0", "@oxc-parser/binding-linux-arm64-gnu": "0.153.0", "@oxc-parser/binding-linux-arm64-musl": "0.153.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.153.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.153.0", "@oxc-parser/binding-linux-riscv64-musl": "0.153.0", "@oxc-parser/binding-linux-s390x-gnu": "0.153.0", "@oxc-parser/binding-linux-x64-gnu": "0.153.0", "@oxc-parser/binding-linux-x64-musl": "0.153.0", "@oxc-parser/binding-openharmony-arm64": "0.153.0", "@oxc-parser/binding-win32-arm64-msvc": "0.153.0", "@oxc-parser/binding-win32-ia32-msvc": "0.153.0", "@oxc-parser/binding-win32-x64-msvc": "0.153.0" } }, "sha512-cEnpyyV1OJRXv++SFNw0z8i+2o/cr88rVLaOYJafELDoHM5yHvCAbwGRLvxuany6SNLcbblSMUyZaGzH+quSYg=="], + + "oxc-resolver": ["oxc-resolver@11.24.2", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.24.2", "@oxc-resolver/binding-android-arm64": "11.24.2", "@oxc-resolver/binding-darwin-arm64": "11.24.2", "@oxc-resolver/binding-darwin-x64": "11.24.2", "@oxc-resolver/binding-freebsd-x64": "11.24.2", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.24.2", "@oxc-resolver/binding-linux-arm-musleabihf": "11.24.2", "@oxc-resolver/binding-linux-arm64-gnu": "11.24.2", "@oxc-resolver/binding-linux-arm64-musl": "11.24.2", "@oxc-resolver/binding-linux-ppc64-gnu": "11.24.2", "@oxc-resolver/binding-linux-riscv64-gnu": "11.24.2", "@oxc-resolver/binding-linux-riscv64-musl": "11.24.2", "@oxc-resolver/binding-linux-s390x-gnu": "11.24.2", "@oxc-resolver/binding-linux-x64-gnu": "11.24.2", "@oxc-resolver/binding-linux-x64-musl": "11.24.2", "@oxc-resolver/binding-openharmony-arm64": "11.24.2", "@oxc-resolver/binding-wasm32-wasi": "11.24.2", "@oxc-resolver/binding-win32-arm64-msvc": "11.24.2", "@oxc-resolver/binding-win32-x64-msvc": "11.24.2" } }, "sha512-FY91FiDBj7ls5MsFS9jN3tjz2o0/zsdSsymlakySaBwVJZorHhkWyICLZMKxlu1R9vYo+sd3z1jwb4J8x7bNDw=="], + "oxfmt": ["oxfmt@0.73.0", "", { "dependencies": { "tinypool": "2.2.0" }, "optionalDependencies": { "@oxfmt/binding-android-arm-eabi": "0.73.0", "@oxfmt/binding-android-arm64": "0.73.0", "@oxfmt/binding-darwin-arm64": "0.73.0", "@oxfmt/binding-darwin-x64": "0.73.0", "@oxfmt/binding-freebsd-x64": "0.73.0", "@oxfmt/binding-linux-arm-gnueabihf": "0.73.0", "@oxfmt/binding-linux-arm-musleabihf": "0.73.0", "@oxfmt/binding-linux-arm64-gnu": "0.73.0", "@oxfmt/binding-linux-arm64-musl": "0.73.0", "@oxfmt/binding-linux-ppc64-gnu": "0.73.0", "@oxfmt/binding-linux-riscv64-gnu": "0.73.0", "@oxfmt/binding-linux-riscv64-musl": "0.73.0", "@oxfmt/binding-linux-s390x-gnu": "0.73.0", "@oxfmt/binding-linux-x64-gnu": "0.73.0", "@oxfmt/binding-linux-x64-musl": "0.73.0", "@oxfmt/binding-openharmony-arm64": "0.73.0", "@oxfmt/binding-win32-arm64-msvc": "0.73.0", "@oxfmt/binding-win32-ia32-msvc": "0.73.0", "@oxfmt/binding-win32-x64-msvc": "0.73.0" }, "peerDependencies": { "@astrojs/compiler-rs": "^0.4.0 || ^0.5.0", "svelte": "^5.0.0", "vite-plus": "*" }, "optionalPeers": ["@astrojs/compiler-rs", "svelte", "vite-plus"], "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-Ke4xbpuzqIBP4bkd48q815RhqWNJqOBzxVA9199M4qcF0y0LtpUv7UTg1dJ5ea6jw0RBUDZvX3qOUMNmmxfxbw=="], "oxlint": ["oxlint@1.88.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.88.0", "@oxlint/binding-android-arm64": "1.88.0", "@oxlint/binding-darwin-arm64": "1.88.0", "@oxlint/binding-darwin-x64": "1.88.0", "@oxlint/binding-freebsd-x64": "1.88.0", "@oxlint/binding-linux-arm-gnueabihf": "1.88.0", "@oxlint/binding-linux-arm-musleabihf": "1.88.0", "@oxlint/binding-linux-arm64-gnu": "1.88.0", "@oxlint/binding-linux-arm64-musl": "1.88.0", "@oxlint/binding-linux-ppc64-gnu": "1.88.0", "@oxlint/binding-linux-riscv64-gnu": "1.88.0", "@oxlint/binding-linux-riscv64-musl": "1.88.0", "@oxlint/binding-linux-s390x-gnu": "1.88.0", "@oxlint/binding-linux-x64-gnu": "1.88.0", "@oxlint/binding-linux-x64-musl": "1.88.0", "@oxlint/binding-openharmony-arm64": "1.88.0", "@oxlint/binding-win32-arm64-msvc": "1.88.0", "@oxlint/binding-win32-ia32-msvc": "1.88.0", "@oxlint/binding-win32-x64-msvc": "1.88.0" }, "peerDependencies": { "oxlint-tsgolint": ">=7.0.2003", "vite-plus": "*" }, "optionalPeers": ["oxlint-tsgolint", "vite-plus"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-uIcFgw8BmqvFRGzH5+r7d2zJMEexwNSrEmM6WQNnGh3VkO0XYTEFoyxyNE6tjKMUib9Cue4XJA2DoWruO5D6aw=="], "p-map": ["p-map@7.0.7", "", {}, "sha512-VaWRu2i4FJNRtiRWCuuQRgfQ1B7a6+gMSrO+3j0EQi/k0ULfS9kosRxGoiqwzIjZTDI02tGfk5mXXltLg6QtfQ=="], - "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], - - "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + "package-manager-detector": ["package-manager-detector@1.9.0", "", {}, "sha512-zAMfbta9nPA8AKkA4WdmNAT8R8s6Dk0/xwPsqNnRidMFcLo1YkLuPDTgs2f64XdzS8vbLNnoJ+uUIkgbz7oRHA=="], - "parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], + "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], - "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="], - "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@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="], - "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], - - "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - - "picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + "picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="], "pify": ["pify@4.0.1", "", {}, "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g=="], @@ -880,8 +914,6 @@ "quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="], - "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], - "range-parser": ["range-parser@1.3.0", "", {}, "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw=="], "raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="], @@ -890,16 +922,12 @@ "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], - "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], - - "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], "rou3": ["rou3@0.9.2", "", {}, "sha512-3SOzvaAg8rkHrXtRjpCvCvbyO5to9oOO27Z/XqHEYXfMRVSw/qMIVdmaOk9W2lcRLtR6dlqTjo9hDeJk70QBYQ=="], "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], - "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], - "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], @@ -936,6 +964,8 @@ "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], + "smol-toml": ["smol-toml@1.9.1", "", {}, "sha512-j7WyPdf0CJoutMzMoPLF/bVXHtKZIT9Rb4eGeEbWw8X2t5oirjPgpL+OpaENZrILYaXqYwnVH+qgUwVbl+pUJQ=="], + "socks": ["socks@2.8.10", "", { "dependencies": { "ip-address": "^10.1.1", "smart-buffer": "^4.2.0" } }, "sha512-e0VyvkVTwVYViNovRkZ9aodhxVlyoMn7eJhVUPxZ+eK9P/7CBkxvvsBOHqFPEH416726W8tLXXXjKwqgTErrCQ=="], "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="], @@ -950,21 +980,19 @@ "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + "strip-json-comments": ["strip-json-comments@5.0.3", "", {}, "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw=="], + "tar": ["tar@7.5.22", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA=="], + "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], + "tinypool": ["tinypool@2.2.0", "", {}, "sha512-jBrmx4lYmaC9k/mgPbylxs7kBUxHtD8256up+HjLaDFfXScKJQyil+SWXSvhAtT7XHo+yTVlpB81PGmHP8oLSQ=="], "to-buffer": ["to-buffer@1.2.2", "", { "dependencies": { "isarray": "^2.0.5", "safe-buffer": "^5.2.1", "typed-array-buffer": "^1.0.3" } }, "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw=="], - "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], - "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], - "true-myth": ["true-myth@4.1.1", "", {}, "sha512-rqy30BSpxPznbbTcAcci90oZ1YR4DqvKcNXNerG5gQBU2v4jk0cygheiul5J6ExIMrgDVuanv/MkGfqZbKrNNg=="], - - "ts-morph": ["ts-morph@13.0.3", "", { "dependencies": { "@ts-morph/common": "~0.12.3", "code-block-writer": "^11.0.0" } }, "sha512-pSOfUMx8Ld/WUreoSzvMFQG5i9uEiWIsBYjpU9+TTASOeUa89j5HykomeqVULm1oqWtBdleI3KEFRLrlA3zGIw=="], - - "ts-prune": ["ts-prune@0.10.3", "", { "dependencies": { "commander": "^6.2.1", "cosmiconfig": "^7.0.1", "json5": "^2.1.3", "lodash": "^4.17.21", "true-myth": "^4.1.0", "ts-morph": "^13.0.1" }, "bin": { "ts-prune": "lib/index.js" } }, "sha512-iS47YTbdIcvN8Nh/1BFyziyUqmjXz7GVzWu02RaZXqb+e/3Qe1B7IQ4860krOeCGUeJmterAlaM2FRH0Ue0hjw=="], + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "type-is": ["type-is@2.1.0", "", { "dependencies": { "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA=="], @@ -974,6 +1002,8 @@ "typescript-language-server": ["typescript-language-server@4.4.1", "", { "bin": { "typescript-language-server": "lib/cli.mjs" } }, "sha512-vEvDw+cjY75afMN2rhx0z8sQtFDAUHoBniksBmB6NUZlDt0rEIwQq01VMiqyXFm9uZwcED2475cmDeXxoDHleg=="], + "unbash": ["unbash@5.1.0", "", {}, "sha512-YVGyy4XqoZ2AVgHjVdL9cchMSurlXFHWviORa8RiU/Bnol4ooW5Lpyur/5XmbgDdyCKxymmgsEQsLGBMIJ/Agg=="], + "undici-types": ["undici-types@8.9.0", "", {}, "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg=="], "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], @@ -986,6 +1016,8 @@ "vscode-languageserver-types": ["vscode-languageserver-types@3.18.3", "", {}, "sha512-XIlzJ7Qp/jzSI1ds7/FwPAWrPeTZA7pAtlW4hdJ1J6xXWJL6dR9QYnDhJOdLzdKhUQ5Mm6mvUMw+3DcOQQasPw=="], + "walk-up-path": ["walk-up-path@4.0.0", "", {}, "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A=="], + "web-tree-sitter": ["web-tree-sitter@0.25.10", "", { "peerDependencies": { "@types/emscripten": "^1.40.0" }, "optionalPeers": ["@types/emscripten"] }, "sha512-Y09sF44/13XvgVKgO2cNDw5rGk6s26MgoZPXLESvMXeefBf7i6/73eFurre0IsTW6E14Y0ArIzhUMmjoc7xyzA=="], "which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="], @@ -998,12 +1030,14 @@ "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], - "yaml": ["yaml@1.10.3", "", {}, "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA=="], + "yaml": ["yaml@2.9.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw=="], - "zod": ["zod@4.5.4", "", {}, "sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA=="], + "zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="], "zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="], + "@better-auth/core/zod": ["zod@4.5.4", "", {}, "sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA=="], + "@intx/db/@intx/crypto": ["@intx/crypto@0.4.0", "", { "dependencies": { "@intx/types": "0.4.0" } }, "sha512-4/qWypC90GevbV9u1i/93tn46jsXhj8wE1Pn+awg8/ECpwhHnhPLT5Uy423Arr9eGmRmF6HiMyXcEgH1Z9/Rkw=="], "@intx/hub-api/@intx/crypto": ["@intx/crypto@0.4.0", "", { "dependencies": { "@intx/types": "0.4.0" } }, "sha512-4/qWypC90GevbV9u1i/93tn46jsXhj8wE1Pn+awg8/ECpwhHnhPLT5Uy423Arr9eGmRmF6HiMyXcEgH1Z9/Rkw=="], @@ -1020,8 +1054,12 @@ "@intx/workflow-deploy/@intx/workflow": ["@intx/workflow@0.4.0", "", { "dependencies": { "@intx/agent": "0.4.0", "@intx/inference": "0.4.0", "@intx/types": "0.4.0", "arktype": "^2.1.29" } }, "sha512-F5Is/lyKb4/WguAVYMDXwtI4ddUvqXbg34rcM6hPUOqupJHpbbD9+ZK/6TDfvbCiEgginbTVwMmNj6Fyg8vU+Q=="], + "@modelcontextprotocol/sdk/zod": ["zod@4.5.4", "", {}, "sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA=="], + "@npmcli/agent/lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], + "better-auth/zod": ["zod@4.5.4", "", {}, "sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA=="], + "better-call/@better-auth/utils": ["@better-auth/utils@0.5.0", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-BL8W4EfIZFwlu0r54m3v1ztjDhu6dDe/amLTm0xybmbZaNgYUqhD3SjpAsnq0q8YD6/ki4iwIgxJNLP/N3TxiA=="], "body-parser/content-type": ["content-type@2.1.0", "", {}, "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag=="], @@ -1032,8 +1070,6 @@ "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], - "glob/minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="], - "hono-openapi/@hono/standard-validator": ["@hono/standard-validator@0.4.0", "", { "peerDependencies": { "@standard-schema/spec": "^1.0.0", "hono": ">=4.11.2" } }, "sha512-MxOm1asDh9j7c1D0KiWwppSbFgAQxTRO4YEhjMrJn3lF1BIcXenPdgjSGKDRfQmZdaTaMA8slQLETgVOKyJxFw=="], "make-fetch-happen/proc-log": ["proc-log@6.1.0", "", {}, "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ=="], @@ -1062,8 +1098,6 @@ "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - "glob/minimatch/brace-expansion": ["brace-expansion@5.0.9", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg=="], - "minipass-flush/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], "minipass-pipeline/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], @@ -1072,8 +1106,6 @@ "npm-registry-fetch/npm-package-arg/validate-npm-package-name": ["validate-npm-package-name@7.0.2", "", {}, "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A=="], - "glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - "npm-registry-fetch/npm-package-arg/hosted-git-info/lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], } } diff --git a/knip.agents.json b/knip.agents.json new file mode 100644 index 000000000..9df9596b5 --- /dev/null +++ b/knip.agents.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://unpkg.com/knip@6/schema.json", + "entry": [ + "src/index.ts", + "src/tui/smoke.ts", + "scripts/*.ts", + "e2e/*.test.ts", + "evals/*.ts" + ], + "project": [ + "src/**/*.ts", + "testkit/**/*.ts", + "packages/**/*.ts", + "e2e/**/*.ts", + "evals/**/*.ts", + "scripts/**/*.ts", + "agents/**/*.ts", + "vendor/**/*.ts" + ], + "workspaces": { + "agents/*": { + "entry": ["src/index.ts!"], + "project": ["src/**/*.ts!"] + } + }, + "includeEntryExports": true, + "rules": { + "exports": "error", + "types": "error", + "files": "error", + "enumMembers": "error", + "namespaceMembers": "error", + "nsExports": "off", + "nsTypes": "off", + "duplicates": "off", + "cycles": "off" + }, + "ignoreExportsUsedInFile": true +} diff --git a/knip.json b/knip.json new file mode 100644 index 000000000..2ffb246d5 --- /dev/null +++ b/knip.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://unpkg.com/knip@6/schema.json", + "entry": [ + "src/index.ts", + "src/tui/smoke.ts", + "scripts/*.ts", + "e2e/*.test.ts", + "evals/*.ts" + ], + "project": [ + "src/**/*.ts", + "testkit/**/*.ts", + "packages/**/*.ts", + "e2e/**/*.ts", + "evals/**/*.ts", + "scripts/**/*.ts", + "agents/**/*.ts", + "vendor/**/*.ts" + ], + "includeEntryExports": true, + "rules": { + "exports": "error", + "types": "error", + "files": "error", + "enumMembers": "error", + "namespaceMembers": "error", + "nsExports": "off", + "nsTypes": "off", + "duplicates": "off", + "cycles": "off" + }, + "ignoreExportsUsedInFile": true +} diff --git a/package.json b/package.json index 2fe40ea15..be6838024 100644 --- a/package.json +++ b/package.json @@ -118,9 +118,9 @@ "devDependencies": { "@intx/inference-testing": "0.3.0", "@types/bun": "1.4.3", + "knip": "^6.41.0", "oxfmt": "^0.73.0", "oxlint": "^1.88.0", - "ts-prune": "0.10.3", "typescript": "5.9.3", "typescript-language-server": "^4.3.4", "ws": "^8.21.0" diff --git a/scripts/check-dead-exports.test.ts b/scripts/check-dead-exports.test.ts index 70211f157..db92b8049 100644 --- a/scripts/check-dead-exports.test.ts +++ b/scripts/check-dead-exports.test.ts @@ -10,22 +10,26 @@ import { join, relative } from "node:path"; import { describe, expect, test } from "bun:test"; import { + agentWorkspaceIds, countScannedFiles, evaluateGuard, isAllowlisted, isCoverageEnough, isGuardPassing, + knipAgentProductionSpawn, + agentsConfigPath, + knipDefaultSpawn, loadAllowlist, loadGuardConfig, parseAllowlistText, parseGuardConfig, parseInjectedScannedFiles, - parseTsPruneLine, - tsPruneSpawn, + parseKnipReport, validateAllowlistEntry, validateAllowlistOwnership, validateAllowlistText, } from "./check-dead-exports.js"; +import type { DeadExport } from "./check-dead-exports.js"; const repoRoot = join(import.meta.dir, ".."); @@ -68,11 +72,10 @@ describe("scoped exemptions", () => { const rules = parseAllowlistText( "src/auth/codex/usage.ts: fetchCodexUsage\n", ); - const outcome = evaluateGuard( - rules, - "src/auth/codex/usage.ts:114 - fetchCodexUsage\n" + - "src/auth/codex/usage.ts:200 - someNewDeadExport\n", - ); + const outcome = evaluateGuard(rules, [ + { file: "src/auth/codex/usage.ts", name: "fetchCodexUsage" }, + { file: "src/auth/codex/usage.ts", name: "someNewDeadExport" }, + ]); expect(outcome.dead).toBe(2); expect(outcome.violations).toEqual([ "src/auth/codex/usage.ts: someNewDeadExport", @@ -80,71 +83,236 @@ describe("scoped exemptions", () => { expect(outcome.unused).toEqual([]); }); - test("exports used only inside their own module do not count", () => { - const outcome = evaluateGuard( - [], - "src/inference-abort.ts:5 - InferenceAbortReason (used in module)\n", + test("the whole-unreachable-file finding matches a full-file allowlist entry", () => { + const rules = parseAllowlistText( + "evals/completion/tasks/x/fixture/data.ts\n", ); - expect(outcome.dead).toBe(0); + const outcome = evaluateGuard(rules, [ + { + file: "evals/completion/tasks/x/fixture/data.ts", + name: "evals/completion/tasks/x/fixture/data.ts", + }, + ]); expect(outcome.violations).toEqual([]); + expect(outcome.unused).toEqual([]); }); - test("parseTsPruneLine skips blank and unparseable lines", () => { - expect(parseTsPruneLine("")).toBeUndefined(); - expect(parseTsPruneLine("not a ts-prune line")).toBeUndefined(); - expect( - parseTsPruneLine("src/auth/xai/usage.ts:89 - fetchXaiUsage"), - ).toEqual({ file: "src/auth/xai/usage.ts", name: "fetchXaiUsage" }); + test("a vendor prefix covers every vendor finding regardless of count", () => { + const rules = parseAllowlistText("vendor/\n"); + const outcome = evaluateGuard(rules, [ + { file: "vendor/intx-types/src/index.ts", name: "ErrorResponse" }, + { file: "vendor/intx-mime/src/whatever.ts", name: "SomethingElse" }, + ]); + expect(outcome.violations).toEqual([]); + expect(outcome.unused).toEqual([]); + expect(isGuardPassing(outcome)).toBe(true); }); }); -// Stale entries (matching no current flag) fail the gate so the exemption -// dies with the code it covered; warn-only reporting let them linger. -describe("stale allowlist entries", () => { - test("an entry matching nothing is reported as unused and fails the gate", () => { - const rules = parseAllowlistText( - "src/auth/xai/usage.ts: fetchXaiUsage\n" + - "src/gone.ts: vanishedExport\n", - ); - const outcome = evaluateGuard( - rules, - "src/auth/xai/usage.ts:89 - fetchXaiUsage\n", - ); - expect(outcome.violations).toEqual([]); - expect(outcome.unused).toEqual(["src/gone.ts: vanishedExport"]); - expect(isGuardPassing(outcome)).toBe(false); +// knip's report parse: one place feeds the rule engine; a wrong shape must +// fail closed instead of silently narrowing the scan. +describe("knip report parsing", () => { + test("exports, types, and members fold into (file, name) findings", () => { + const report = JSON.stringify({ + issues: [ + { + file: "src/a.ts", + exports: [{ name: "alpha", line: 1, col: 14, pos: 13 }], + types: [{ name: "AlphaShape", line: 2, col: 13, pos: 40 }], + enumMembers: [], + namespaceMembers: [], + files: [], + }, + ], + }); + expect(parseKnipReport(report)).toEqual([ + { file: "src/a.ts", name: "alpha" }, + { file: "src/a.ts", name: "AlphaShape" }, + ]); + }); + + test("an unreachable file folds into one finding whose name is the path", () => { + const report = JSON.stringify({ + issues: [ + { + file: "evals/x/fixture/data.ts", + exports: [], + files: [{ name: "evals/x/fixture/data.ts" }], + }, + ], + }); + expect(parseKnipReport(report)).toEqual([ + { file: "evals/x/fixture/data.ts", name: "evals/x/fixture/data.ts" }, + ]); + }); + + test("issue classes outside the counted set are ignored", () => { + const report = JSON.stringify({ + issues: [ + { + file: "src/a.ts", + dependencies: [{ name: "lodash" }], + binaries: [{ name: "pgrep" }], + duplicates: [[{ name: "dup" }]], + exports: [{ name: "alpha" }], + }, + ], + }); + expect(parseKnipReport(report)).toEqual([ + { file: "src/a.ts", name: "alpha" }, + ]); + }); + + test("malformed reports fail closed", () => { + expect(() => parseKnipReport("")).toThrow(); + expect(() => parseKnipReport("[]")).toThrow(); + expect(() => parseKnipReport("{}")).toThrow(); + expect(() => parseKnipReport('{"issues": [42]}')).toThrow(); + expect(() => parseKnipReport('{"issues": [{}]}')).toThrow(); + expect(() => + parseKnipReport('{"issues": [{"file": "a.ts", "exports": [{}]}]}'), + ).toThrow(); + expect(() => + parseKnipReport('{"issues": [{"file": "a.ts", "files": [{}]}]}'), + ).toThrow(); }); +}); + +// The guard semantics over findings shapes. +describe("guard evaluation", () => { + const findings: readonly DeadExport[] = [ + { file: "vendor/intx-types/src/index.ts", name: "ErrorResponse" }, + { file: "src/auth/xai/usage.ts", name: "fetchXaiUsage" }, + ]; test("a fully fresh allowlist passes the gate", () => { const rules = parseAllowlistText( "vendor/\nsrc/auth/xai/usage.ts: fetchXaiUsage\n", ); - const outcome = evaluateGuard( - rules, - "vendor/intx-types/src/index.ts:3 - ErrorResponse\n" + - "src/auth/xai/usage.ts:89 - fetchXaiUsage\n", - ); + const outcome = evaluateGuard(rules, findings); expect(outcome.violations).toEqual([]); expect(outcome.unused).toEqual([]); expect(isGuardPassing(outcome)).toBe(true); }); - test("a prefix entry counts as used when any flag falls under it", () => { - const rules = parseAllowlistText("vendor/\n"); + test("violations fail the gate", () => { const outcome = evaluateGuard( - rules, - "vendor/intx-types/src/index.ts:3 - ErrorResponse\n", + [], + [{ file: "src/new.ts", name: "freshDeadExport" }], ); - expect(outcome.violations).toEqual([]); - expect(outcome.unused).toEqual([]); - expect(isGuardPassing(outcome)).toBe(true); + expect(outcome.violations).toEqual(["src/new.ts: freshDeadExport"]); + expect(isGuardPassing(outcome)).toBe(false); }); +}); - test("violations fail the gate", () => { - const outcome = evaluateGuard([], "src/new.ts:1 - freshDeadExport\n"); - expect(outcome.violations).toEqual(["src/new.ts: freshDeadExport"]); +// The knip semantics the guard depends on, run against the real repo: +// +// 1. In the default scan knip counts test-file imports as consumption, so an +// export whose only importer is a test must NOT be reported there. +// 2. The production agent-workspace scan ignores test-file consumers, so the +// six-name director surface's worker names surface again — including their +// genuinely consumed siblings (registry.ts imports only `director`, and +// src/agent/prompts.ts consumes dispatch's `systemPrompt`), which is the +// test that catches a knip change silently restoring the laundering. +// 3. The allowlist covers the union so the gate stays green with the worker +// names disclosed rather than silently absent. +describe("live knip scan semantics", () => { + const knipBin = join(repoRoot, "node_modules", ".bin", "knip"); + + test("default scan counts test-file consumers as alive (documented gap)", () => { + const spawn = knipDefaultSpawn(knipBin); + const ran = spawnSync(spawn.command, spawn.args, { + cwd: repoRoot, + encoding: "utf8", + }); + const findings = parseKnipReport(String(ran.stdout)); + const reported = (file: string, name: string): boolean => + findings.some((f) => f.file === file && f.name === name); + + // Exports used only inside their own module are not dead. + expect(reported("src/inference-abort.ts", "InferenceAbortReason")).toBe( + false, + ); + // The director fan-in is a live consumer. + expect(reported("agents/coder/src/index.ts", "director")).toBe(false); + // knip counts the package contract test's import as consumption. + expect(reported("agents/coder/src/index.ts", "tools")).toBe(false); + // The ts-prune parser false positives are gone. + expect(reported("src/agent/directors/identity.ts", "satisfies")).toBe( + false, + ); + }, 120_000); + + test("production agent scan removes test-file consumers from the surface", () => { + const rules = loadAllowlist(); + const spawn = knipAgentProductionSpawn(knipBin, agentWorkspaceIds(rules)); + const ran = spawnSync(spawn.command, spawn.args, { + cwd: repoRoot, + encoding: "utf8", + }); + const findings = parseKnipReport(String(ran.stdout)); + const reported = (file: string, name: string): boolean => + findings.some((f) => f.file === file && f.name === name); + + for (const config of [ + "agents/coder", + "agents/explorer", + "agents/reviewer", + "agents/planner", + "agents/artist", + "agents/prober", + "agents/warden", + "agents/designer", + "agents/shakespeare", + "agents/dispatch", + ]) { + const index = `${config}/src/index.ts`; + // The laundered worker names resurface. + expect(reported(index, "tools")).toBe(true); + expect(reported(index, "config")).toBe(true); + // dispatch's systemPrompt has a production consumer; the rest do not. + if (config !== "agents/dispatch") { + expect(reported(index, "systemPrompt")).toBe(true); + } + // The registry fan-in keeps its entry-file name alive. + expect(reported(index, "director")).toBe(false); + // The re-added allowlist entries match every surfaced finding. + expect(isAllowlisted(rules, index, "tools")).toBe(true); + expect(isAllowlisted(rules, index, "config")).toBe(true); + expect(isAllowlisted(rules, `${config}/src/agent.ts`, "agent")).toBe( + true, + ); + } + expect(reported("agents/dispatch/src/index.ts", "systemPrompt")).toBe( + false, + ); + }, 120_000); +}); + +// Stale entries (matching no current finding) fail the gate so the exemption +// dies with the code it covered; warn-only reporting let them linger. +describe("stale allowlist entries", () => { + test("an entry matching nothing is reported as unused and fails the gate", () => { + const rules = parseAllowlistText( + "src/auth/xai/usage.ts: fetchXaiUsage\nsrc/gone.ts: vanishedExport\n", + ); + const outcome = evaluateGuard(rules, [ + { file: "src/auth/xai/usage.ts", name: "fetchXaiUsage" }, + ]); + expect(outcome.violations).toEqual([]); + expect(outcome.unused).toEqual(["src/gone.ts: vanishedExport"]); expect(isGuardPassing(outcome)).toBe(false); }); + + test("a prefix entry counts as used when any finding falls under it", () => { + const rules = parseAllowlistText("vendor/\n"); + const outcome = evaluateGuard(rules, [ + { file: "vendor/intx-types/src/index.ts", name: "ErrorResponse" }, + ]); + expect(outcome.violations).toEqual([]); + expect(outcome.unused).toEqual([]); + expect(isGuardPassing(outcome)).toBe(true); + }); }); // Shapes the matcher would silently misinterpret must fail: a mistyped exact @@ -240,79 +408,75 @@ describe("allowlist ownership", () => { }); }); -// The scan invocation is pinned to dead-export-guard.json so it never depends -// on ts-prune's working-directory config discovery; the gate fails closed -// when the scanned file count drops below the checked-in floor. +// The scan invocation is pinned in code (reporter, flags, node runtime); the +// guard config cannot narrow it. The floor fails closed when the pinned +// tsconfig's program shrinks. describe("pinned scan invocation", () => { test("the checked-in config pins the project and a positive floor", () => { const config = loadGuardConfig(); expect(config.tsconfig).toBe("tsconfig.json"); - expect(config.tsPruneArgs).toEqual(["-p", "tsconfig.json"]); expect(config.minScannedFiles).toBeGreaterThan(0); expect(existsSync(join(repoRoot, config.tsconfig))).toBe(true); }); - test("ts-prune is launched with node, not as a Bun-executed bin", () => { - const spawn = tsPruneSpawn("/repo/node_modules/.bin/ts-prune", [ - "-p", - "tsconfig.json", - ]); + test("the default scan runs with the pinned flags", () => { + const spawn = knipDefaultSpawn("/repo/node_modules/.bin/knip"); expect(spawn.command).toBe("node"); expect(spawn.args).toEqual([ - "/repo/node_modules/.bin/ts-prune", - "-p", - "tsconfig.json", + "/repo/node_modules/.bin/knip", + "--reporter", + "json", + "--no-progress", + "--no-exit-code", ]); }); - test("parseGuardConfig rejects an unpinned or empty invocation", () => { - const valid = { - tsconfig: "tsconfig.json", - tsPruneArgs: ["-p", "tsconfig.json"], - minScannedFiles: 1130, - }; - expect(parseGuardConfig(valid)).toEqual(valid); - expect(() => parseGuardConfig({ ...valid, tsPruneArgs: [] })).toThrow(); - expect(() => - parseGuardConfig({ ...valid, tsPruneArgs: ["--ignore", "x"] }), - ).toThrow(); - expect(() => - parseGuardConfig({ - ...valid, - tsPruneArgs: ["-p", "tsconfig.other.json"], - }), - ).toThrow(); + test("the agent-workspace production scan is pinned per workspace", () => { + const spawn = knipAgentProductionSpawn("/repo/node_modules/.bin/knip", [ + "agents/coder", + "agents/dispatch", + ]); + expect(spawn.command).toBe("node"); + expect(spawn.args).toEqual([ + "/repo/node_modules/.bin/knip", + "-c", + agentsConfigPath(), + "--production", + "--reporter", + "json", + "--no-progress", + "--no-exit-code", + "-W", + "agents/coder", + "-W", + "agents/dispatch", + ]); + expect(() => knipAgentProductionSpawn("/repo/x", [])).toThrow(); }); - test("parseGuardConfig rejects extra narrowing flags on a pinned invocation", () => { - const valid = { - tsconfig: "tsconfig.json", - tsPruneArgs: ["-p", "tsconfig.json"], - minScannedFiles: 1130, - }; - expect(parseGuardConfig(valid)).toEqual(valid); - const narrowed = [ - ["-p", "tsconfig.json", "-i", "src/.*"], - ["-p", "tsconfig.json", "--ignore", "src/.*"], - ["-p", "tsconfig.json", "--error"], - ["--ignore", "src/.*", "-p", "tsconfig.json"], - ]; - for (const tsPruneArgs of narrowed) { - expect(() => parseGuardConfig({ ...valid, tsPruneArgs })).toThrow(); - } + test("agent workspace ids derive from the allowlist and fail closed", () => { + const rules = parseAllowlistText( + "agents/coder/src/index.ts: agent\n" + + "agents/coder/src/index.ts: agent\n" + + "agents/dispatch/src/index.ts: agent\n" + + "src/auth/codex/constants.ts: CODEX_REFRESH_SKEW_MS\n", + ); + // Parse dedupes nothing at rule level; the derivation dedupes ids. + expect(agentWorkspaceIds(rules)).toEqual([ + "agents/coder", + "agents/dispatch", + ]); + expect(() => agentWorkspaceIds(parseAllowlistText("vendor/\n"))).toThrow(); }); - test("parseGuardConfig rejects a missing floor", () => { - const valid = { - tsconfig: "tsconfig.json", - tsPruneArgs: ["-p", "tsconfig.json"], - minScannedFiles: 1130, - }; - for (const floor of [0, -5, 1.5, "1130", undefined]) { - expect(() => - parseGuardConfig({ ...valid, minScannedFiles: floor }), - ).toThrow(); - } + test("parseGuardConfig rejects a missing tsconfig or floor and extra keys", () => { + const valid = { tsconfig: "tsconfig.json", minScannedFiles: 1240 }; + expect(parseGuardConfig(valid)).toEqual(valid); + expect(() => parseGuardConfig({ ...valid, minScannedFiles: 0 })).toThrow(); + expect(() => parseGuardConfig({ ...valid, tsconfig: "" })).toThrow(); + expect(() => + parseGuardConfig({ ...valid, tsPruneArgs: ["-p", "x"] }), + ).toThrow(); expect(() => parseGuardConfig(null)).toThrow(); expect(() => parseGuardConfig([])).toThrow(); }); @@ -331,9 +495,9 @@ describe("pinned scan invocation", () => { describe("scan coverage floor", () => { test("counts below the floor fail, counts at or above pass", () => { - expect(isCoverageEnough(1129, 1130)).toBe(false); - expect(isCoverageEnough(1130, 1130)).toBe(true); - expect(isCoverageEnough(2000, 1130)).toBe(true); + expect(isCoverageEnough(1239, 1240)).toBe(false); + expect(isCoverageEnough(1240, 1240)).toBe(true); + expect(isCoverageEnough(2000, 1240)).toBe(true); }); test("the live program file count clears the checked-in floor", () => { @@ -350,33 +514,17 @@ describe("scan coverage floor", () => { }); // Unit tests above prove the rule engine flags a probe; this one proves the -// wired-up gate does. A temp probe export lands in the tsconfig-covered -// scripts/ tree, the real guard runs as a subprocess, and the run must exit -// nonzero naming the probe. The probe lives only for the test so the -// keep-alive check cannot itself become a dead export. A temp narrow -// tsconfig keeps the run to ts-prune startup instead of the ~5s full scan. +// wired-up gate does. A temp probe export lands in the knip-covered scripts/ +// tree, the real guard runs as a subprocess, and the run must exit nonzero +// naming the probe. The probe lives only for the test so the keep-alive check +// cannot itself become a dead export; the injected count keeps the floor +// subprocess out of the timing. describe("violation end to end", () => { test("a temp dead export fails the guard, which names it", () => { const probeFile = `dead-export-guard-probe-${process.pid}.ts`; const probeName = `deadExportGuardProbe${process.pid}`; const probePath = join(repoRoot, "scripts", probeFile); - const narrowTsconfigFile = `dead-export-guard-tsconfig-${process.pid}.json`; - const narrowTsconfigPath = join(repoRoot, "scripts", narrowTsconfigFile); writeFileSync(probePath, `export const ${probeName} = 1;\n`); - writeFileSync( - narrowTsconfigPath, - JSON.stringify({ - compilerOptions: { - noEmit: true, - target: "ESNext", - module: "ESNext", - moduleResolution: "bundler", - skipLibCheck: true, - }, - // Relative to the narrow tsconfig's own directory (scripts/). - files: [probeFile], - }), - ); try { const ran = spawnSync( process.execPath, @@ -385,11 +533,10 @@ describe("violation end to end", () => { cwd: repoRoot, encoding: "utf8", // Floor tests cover the file-count subprocess; inject the count so - // this run pays only ts-prune. + // this run pays only knip. env: { ...process.env, DEAD_EXPORT_GUARD_SCANNED_FILES: String(liveScannedFileCount()), - DEAD_EXPORT_GUARD_TSCONFIG: `scripts/${narrowTsconfigFile}`, }, }, ); @@ -399,9 +546,8 @@ describe("violation end to end", () => { ); } finally { rmSync(probePath, { force: true }); - rmSync(narrowTsconfigPath, { force: true }); } - }, 120_000); + }, 240_000); }); // A re-created barrel would silently resurrect a surface the purge removed. diff --git a/scripts/check-dead-exports.ts b/scripts/check-dead-exports.ts index 2c8195722..41ec3a789 100644 --- a/scripts/check-dead-exports.ts +++ b/scripts/check-dead-exports.ts @@ -3,36 +3,44 @@ import { existsSync, readFileSync, realpathSync } from "node:fs"; import { dirname, join, sep } from "node:path"; import { fileURLToPath } from "node:url"; -// Dead-export guard: runs ts-prune and fails when an export with no consumer -// falls outside scripts/dead-export-allowlist.txt. Exports used only inside -// their own module ("(used in module)") do not count. New dead exports must -// be deleted, not allowlisted — the allowlist covers entry points, cross-lane -// ownership, path-loaded plugin surfaces, and ts-prune parser false positives -// only. +// Dead-export guard: runs knip and fails when a consumer-less export or an +// unused file falls outside scripts/dead-export-allowlist.txt. knip replaces +// the retired ts-prune scanner (CL-10655); it resolves the module graph, so an +// export consumed through a re-export barrel or the closed director fan-in is +// not reported, and exports used only inside their own module are skipped +// (ignoreExportsUsedInFile in knip.json knits both semantics). knip.json pins +// the scan (entry points per tree plus the workspace packages); this guard +// owns only the allowlist and the fail-closed floor. New dead exports must be +// deleted, not allowlisted — the allowlist covers entry-point API surfaces and +// deferred-deletion lanes only. // // Hardening: stale entries fail instead of warning, every entry must pass // shape validation and sit under a reason comment (the gate enforces the -// reason; review enforces the owning lane), the scan is pinned to -// scripts/dead-export-guard.json, and the gate fails closed when the scanned -// file count drops below that config's floor. +// reason; review enforces the owning lane), the file-count floor is computed +// from the pinned tsconfig and the gate fails closed under it, and the knip +// invocation is pinned in code (reporter + flags) so the scan cannot be +// narrowed from the guard config. // Test injection: a subprocess test can pass the deterministic file count // instead of paying for the ~1.5s tsc --listFilesOnly run. Production never // sets it, so the gate always computes the count in normal runs. const scannedFilesEnv = "DEAD_EXPORT_GUARD_SCANNED_FILES"; -// Test injection (same pattern as scannedFilesEnv): a subprocess test can -// point the pinned scan at a smaller tsconfig instead of paying for the ~5s -// full-project ts-prune run. Production never sets it. -const tsconfigEnv = "DEAD_EXPORT_GUARD_TSCONFIG"; - const here = dirname(fileURLToPath(import.meta.url)); const repoRoot = dirname(here); const allowlistPath = join(here, "dead-export-allowlist.txt"); const guardConfigPath = join(here, "dead-export-guard.json"); -const tsPruneBin = join(repoRoot, "node_modules", ".bin", "ts-prune"); +const knipBin = join(repoRoot, "node_modules", ".bin", "knip"); const tscBin = join(repoRoot, "node_modules", "typescript", "bin", "tsc"); +// The integrated-monorepo knip config that drives the production agent scan; +// a separate file so the default scan keeps knip.json's ts-prune-parity +// project scope (the `workspaces` key switches knip into integrated mode, +// which also widens its default file detection beyond that scope). +export function agentsConfigPath(): string { + return join(repoRoot, "knip.agents.json"); +} + export type AllowRule = | { readonly kind: "prefix"; @@ -59,7 +67,6 @@ export interface GuardOutcome { export interface GuardConfig { readonly tsconfig: string; - readonly tsPruneArgs: readonly string[]; readonly minScannedFiles: number; } @@ -160,34 +167,24 @@ export function loadAllowlist(): AllowRule[] { return parseAllowlistText(readFileSync(allowlistPath, "utf8")); } -// The invocation must be exactly "-p ": narrowing flags cannot -// shrink the scan while the file count stays flat. The floor must be a -// positive integer the gate fails closed against. +// The counted floor must be a positive integer the gate fails closed against. +// Unknown keys reject: a config carrying a retired or misnamed scan knob must +// not silently narrow how the gate runs. export function parseGuardConfig(raw: unknown): GuardConfig { if (typeof raw !== "object" || raw === null || Array.isArray(raw)) { throw new Error("dead-export guard config must be a JSON object"); } const config = raw as Record; + const known = new Set(["tsconfig", "minScannedFiles"]); + for (const key of Object.keys(config)) { + if (!known.has(key)) { + throw new Error(`dead-export guard config has an unexpected key: ${key}`); + } + } const tsconfig = config["tsconfig"]; if (typeof tsconfig !== "string" || tsconfig === "") { throw new Error('dead-export guard config needs a "tsconfig" path string'); } - const tsPruneArgs = config["tsPruneArgs"]; - if ( - !Array.isArray(tsPruneArgs) || - tsPruneArgs.length === 0 || - tsPruneArgs.some((arg) => typeof arg !== "string") - ) { - throw new Error( - 'dead-export guard config needs a non-empty "tsPruneArgs" string array', - ); - } - const args = tsPruneArgs as string[]; - if (args.length !== 2 || args[0] !== "-p" || args[1] !== tsconfig) { - throw new Error( - 'dead-export guard config "tsPruneArgs" must be exactly ["-p", ""] with no extra flags', - ); - } const minScannedFiles = config["minScannedFiles"]; if ( typeof minScannedFiles !== "number" || @@ -198,7 +195,7 @@ export function parseGuardConfig(raw: unknown): GuardConfig { 'dead-export guard config needs a positive integer "minScannedFiles"', ); } - return { tsconfig, tsPruneArgs: [...args], minScannedFiles }; + return { tsconfig, minScannedFiles }; } export function loadGuardConfig(): GuardConfig { @@ -229,36 +226,200 @@ export function isAllowlisted( ); } -// Parses one ts-prune output line. Returns undefined for blank lines, -// unparseable lines, and exports used only inside their own module. -export function parseTsPruneLine(raw: string): DeadExport | undefined { - const match = raw.match(/^(.*?):\d+ - (\S+?)(\s+\(used in module\))?$/); - if (!match) return undefined; - if (match[3] !== undefined) return undefined; - const file = match[1]; - const name = match[2]; - if (file === undefined || name === undefined) return undefined; - return { file, name }; +// knip issue items are { name } objects (plus position fields the guard never +// reads); a `files` item carries the file path in `name`. +interface KnipReportItem { + readonly name: unknown; +} + +// The knip issue classes the guard counts. knip.json enables exports, types, +// enumMembers, namespaceMembers, and files and turns everything else off, so +// this list is the closed set the reporter can surface findings under. +const knipFindingClasses = [ + "exports", + "types", + "enumMembers", + "namespaceMembers", +] as const; +type KnipFindingClass = (typeof knipFindingClasses)[number]; + +function isFindingClass(value: string): value is KnipFindingClass { + return (knipFindingClasses as readonly string[]).includes(value); +} + +// Parses one knip --reporter json stdout blob into dead-export findings. +// Malformed shapes throw: a mis-parsed report must fail closed, not pass. +// `files` issues become one finding per file (the unreachable file is the +// maximal consumer-less export); everything else is (file, export name). +export function parseKnipReport(raw: string): DeadExport[] { + let parsed: unknown; + try { + parsed = JSON.parse(raw); + } catch (err) { + throw new Error(`knip report is not valid JSON: ${(err as Error).message}`); + } + if ( + typeof parsed !== "object" || + parsed === null || + !("issues" in parsed) || + !Array.isArray(parsed.issues) + ) { + throw new Error("knip report is missing its issues array"); + } + const findings: DeadExport[] = []; + for (const issue of parsed.issues) { + if (typeof issue !== "object" || issue === null) { + throw new Error("knip report issue is not an object"); + } + const entry = issue as Record; + if (typeof entry.file !== "string") { + throw new Error("knip report issue is missing its file path"); + } + const file = entry.file; + if (Array.isArray(entry.files) && entry.files.length > 0) { + for (const item of entry.files) { + const name = nameOf(item); + if (name === undefined) { + throw new Error("knip report files issue carries no file name"); + } + findings.push({ file, name }); + } + continue; + } + for (const [key, value] of Object.entries(entry)) { + if (!isFindingClass(key) || !Array.isArray(value)) continue; + for (const item of value) { + const name = nameOf(item); + if (name === undefined) { + throw new Error(`knip report ${key} issue carries no name`); + } + findings.push({ file, name }); + } + } + } + return findings; +} + +function nameOf(item: unknown): string | undefined { + if (typeof item !== "object" || item === null) return undefined; + const name = (item as KnipReportItem).name; + return typeof name === "string" ? name : undefined; +} + +// Passes only when nothing new died and no exemption is stale. Stale entries +// used to warn; now they fail so dead exemptions cannot linger. +export function isGuardPassing(outcome: GuardOutcome): boolean { + return outcome.violations.length === 0 && outcome.unused.length === 0; +} + +// knip's own binary shebang is node, but Bun spawns .bin targets with Bun — +// and knip's bun-mode path can behave differently across versions. The +// invocation is always launched with node and its flags are pinned here (the +// guard config carries no scan flags), so the report shape is stable and the +// scan cannot be narrowed side-channel. +export function knipSpawn( + knipBinPath: string, + args: readonly string[], +): { + readonly command: string; + readonly args: string[]; +} { + return { command: "node", args: [knipBinPath, ...args] }; +} + +// Both knip configs must exist: the default scan reads knip.json and the +// production agent scan reads the integrated-monorepo variant. A missing +// config silently narrows the scan, so the guard fails closed instead. +export function requireKnipConfigs(): void { + for (const configPath of [join(repoRoot, "knip.json"), agentsConfigPath()]) { + if (!existsSync(configPath)) { + throw new Error(`knip config not found: ${configPath}`); + } + } +} + +// The pinned base flags for every knip scan: JSON reporter, no progress +// output, and no exit-code clobbering (the guard owns the verdict; a knip +// process failure must surface as a guard failure, not as a finding count). +const knipBaseArgs = [ + "--reporter", + "json", + "--no-progress", + "--no-exit-code", +] as const; + +// The default scan covers every tree in knip.json's project scope. knip +// counts test-file imports as consumption, so an export whose only importers +// are tests is not reported here. +export function knipDefaultSpawn(knipBinPath: string): { + readonly command: string; + readonly args: string[]; +} { + return knipSpawn(knipBinPath, knipBaseArgs); +} + +// The agent-workspace production scan is the test-laundering counterweight: +// knip's `--production` mode does not count test-file consumers, and the +// `-W agents/*` workspace filter keeps the pass scoped to the agent packages' +// entry-point surfaces the guard protects (the AGENTS.md director-extract +// bar). It runs the integrated-monorepo config (knip.agents.json, whose +// `workspaces` key makes cross-workspace consumers visible in production +// mode), so the registry fan-in still clears `director` while the laundered +// worker names surface. Outside that surface the default scan owns +// enforcement at ts-prune parity; the union of both scans is the guard's +// finding set. +export function knipAgentProductionSpawn( + knipBinPath: string, + workspaces: readonly string[], +): { readonly command: string; readonly args: string[] } { + if (workspaces.length === 0) { + throw new Error("agent-workspace production scan needs a workspace list"); + } + return knipSpawn(knipBinPath, [ + "-c", + agentsConfigPath(), + "--production", + ...knipBaseArgs, + ...workspaces.flatMap((workspace) => ["-W", workspace]), + ]); +} + +// The agent workspaces the production scan must cover, derived from the +// allowlist's `agents//src/index.ts` entries so the scan stays pinned to +// the surface the allowlist governs. Fails closed when the surface names no +// workspace: a silent no-op scan cannot be trusted. +export function agentWorkspaceIds(rules: readonly AllowRule[]): string[] { + const ids = new Set(); + for (const rule of rules) { + if (rule.kind === "exact" && rule.file.startsWith("agents/")) { + const id = rule.file.slice("agents/".length).split("/")[0]; + if (id !== undefined && id !== "") ids.add(`agents/${id}`); + } + } + const sorted = [...ids].sort(); + if (sorted.length === 0) { + throw new Error( + "dead-export allowlist names no agents//src/index.ts surface: " + + "the agent-workspace production scan has nothing to cover", + ); + } + return sorted; } export function evaluateGuard( rules: AllowRule[], - tsPruneStdout: string, + findings: readonly DeadExport[], ): GuardOutcome { const used = new Set(); const violations: string[] = []; - let dead = 0; - for (const raw of tsPruneStdout.split("\n")) { - const parsed = parseTsPruneLine(raw); - if (parsed === undefined) continue; - dead += 1; + for (const finding of findings) { const index = rules.findIndex((rule) => rule.kind === "prefix" - ? parsed.file.startsWith(rule.prefix) - : rule.file === parsed.file && rule.name === parsed.name, + ? finding.file.startsWith(rule.prefix) + : rule.file === finding.file && rule.name === finding.name, ); if (index === -1) { - violations.push(`${parsed.file}: ${parsed.name}`); + violations.push(`${finding.file}: ${finding.name}`); } else { used.add(index); } @@ -266,23 +427,12 @@ export function evaluateGuard( const unused = rules .filter((_, index) => !used.has(index)) .map((r) => r.source); - return { dead, violations, unused }; -} - -// Passes only when nothing new died and no exemption is stale. Stale entries -// used to warn; now they fail so dead exemptions cannot linger. -export function isGuardPassing(outcome: GuardOutcome): boolean { - return outcome.violations.length === 0 && outcome.unused.length === 0; -} - -// ts-prune under-reports unused exports when the CLI runs on Bun (Linux CI -// then sees the Darwin/Node allowlist as stale). The bin shebang is `node`, -// but Bun spawns it with Bun anyway. Always launch the CLI with node. -export function tsPruneSpawn( - tsPruneBinPath: string, - tsPruneArgs: readonly string[], -): { readonly command: string; readonly args: string[] } { - return { command: "node", args: [tsPruneBinPath, ...tsPruneArgs] }; + // Deterministic output ordering does not depend on knip's report order. + return { + dead: findings.length, + violations: violations.sort(), + unused: unused.sort(), + }; } // Undefined means compute the count as usual; anything else must be a @@ -301,9 +451,10 @@ export function parseInjectedScannedFiles( } // Counts the TS files the pinned tsconfig pulls into its program via -// tsc --listFilesOnly — the same project ts-prune analyzes. A narrowed -// tsconfig shrinks this count, and the gate fails closed against the -// checked-in floor. +// tsc --listFilesOnly. A narrowed tsconfig shrinks this count, and the gate +// fails closed against the checked-in floor. knip's own scan scope is pinned +// by knip.json; this floor is the repo-wide sanity check on the project +// graph that gate tooling parses against. export function countScannedFiles( repoRootDir: string, tsconfigPath: string, @@ -337,6 +488,62 @@ function fail(message: string): never { process.exit(1); } +// The guard's finding set is the union of two knip reports: +// +// 1. the default scan over knip.json's project scope (ts-prune parity), and +// 2. the `--production` scan filtered to the agent workspaces, where knip +// does not count test-file consumers. +// +// Both run under the pinned pinned spawn contract; a failing or malformed +// report fails the gate rather than narrowing it silently. +export function runKnipScans(rules: readonly AllowRule[]): DeadExport[] { + requireKnipConfigs(); + const merge = (reports: readonly DeadExport[]): DeadExport[] => { + const seen = new Set(); + const merged: DeadExport[] = []; + for (const finding of reports) { + const key = `${finding.file}::${finding.name}`; + if (seen.has(key)) continue; + seen.add(key); + merged.push(finding); + } + return merged; + }; + const spawnAndParse = ( + spawn: { readonly command: string; readonly args: string[] }, + label: string, + ): DeadExport[] => { + const ran = spawnSync(spawn.command, spawn.args, { + cwd: repoRoot, + encoding: "utf8", + maxBuffer: 32 * 1024 * 1024, + }); + if (ran.error !== undefined) { + fail( + `${label} failed to start with ${spawn.command}: ${ran.error.message}`, + ); + } + if (ran.status !== 0) { + fail(`${label} failed:\n${ran.stderr || ran.stdout}`); + } + try { + return parseKnipReport(String(ran.stdout)); + } catch (err) { + fail(`${label} report unusable: ${(err as Error).message}`); + } + }; + const defaultFindings = spawnAndParse( + knipDefaultSpawn(knipBin), + "knip default scan", + ); + const agentWorkspaces = agentWorkspaceIds(rules); + const agentFindings = spawnAndParse( + knipAgentProductionSpawn(knipBin, agentWorkspaces), + "knip production agent scan", + ); + return merge([...defaultFindings, ...agentFindings]); +} + function main(): void { let config: GuardConfig; try { @@ -344,19 +551,6 @@ function main(): void { } catch (err) { fail(`dead-export guard: invalid guard config: ${(err as Error).message}`); } - const overrideTsconfig = process.env[tsconfigEnv]; - if (overrideTsconfig !== undefined) { - if (!existsSync(join(repoRoot, overrideTsconfig))) { - fail( - `dead-export guard: tsconfig override not found: ${overrideTsconfig}`, - ); - } - config = { - ...config, - tsconfig: overrideTsconfig, - tsPruneArgs: ["-p", overrideTsconfig], - }; - } const allowlistText = readFileSync(allowlistPath, "utf8"); const allowlistProblems = [ ...validateAllowlistText(allowlistText), @@ -369,20 +563,8 @@ function main(): void { ); } const rules = parseAllowlistText(allowlistText); - const prune = tsPruneSpawn(tsPruneBin, config.tsPruneArgs); - const pruned = spawnSync(prune.command, prune.args, { - cwd: repoRoot, - encoding: "utf8", - }); - if (pruned.error !== undefined) { - fail( - `ts-prune failed to start with ${prune.command}: ${pruned.error.message}`, - ); - } - if (pruned.status !== 0) { - fail(`ts-prune failed:\n${pruned.stderr || pruned.stdout}`); - } - const outcome = evaluateGuard(rules, String(pruned.stdout)); + const findings = runKnipScans(rules); + const outcome = evaluateGuard(rules, findings); let scannedFiles: number; const injectedCount = parseInjectedScannedFiles(process.env[scannedFilesEnv]); if (injectedCount !== undefined) { @@ -397,7 +579,7 @@ function main(): void { } } console.log( - `dead-export guard: ${outcome.dead} consumer-less exports, ${outcome.dead - outcome.violations.length} allowlisted, ${outcome.violations.length} violations, ${scannedFiles} scanned files (floor ${config.minScannedFiles})`, + `dead-export guard: ${outcome.dead} consumer-less exports or unreachable files (default scan + production agent-workspace scan), ${outcome.dead - outcome.violations.length} allowlisted, ${outcome.violations.length} violations, ${scannedFiles} scanned files (floor ${config.minScannedFiles})`, ); if (!isCoverageEnough(scannedFiles, config.minScannedFiles)) { fail( @@ -407,7 +589,7 @@ function main(): void { } if (outcome.unused.length > 0) { console.error( - "Stale allowlist entries matching no dead export (remove them):\n" + + "Stale allowlist entries matching no knip finding (remove them):\n" + outcome.unused.map((entry) => ` ${entry}`).join("\n"), ); } diff --git a/scripts/dead-export-allowlist.txt b/scripts/dead-export-allowlist.txt index 516625db5..c0fbde2f9 100644 --- a/scripts/dead-export-allowlist.txt +++ b/scripts/dead-export-allowlist.txt @@ -1,19 +1,25 @@ -# Dead-export allowlist for `bun run check:dead-exports` (CL-6797). +# Dead-export allowlist for `bun run check:dead-exports` (CL-6797, +# knip-driven since CL-10655). # # Format: `#` lines are reasons and attach to the entries below them. An entry # is either a path prefix ending in `/` (matches a directory subtree), a full -# file path (matches every export in that file), or `file: name` (matches one -# export). Anything else (a directory without its trailing `/`, a `file:` -# line that is not `path/to/file.ts: ExportName`) fails the gate. +# file path (matches every export in that file plus a whole-unreachable-file +# knip finding), or `file: name` (matches one export). Anything else (a +# directory without its trailing `/`, a `file:` line that is not +# `path/to/file.ts: ExportName`) fails the gate. +# +# The guard's finding set is the union of knip's default scan and its +# `--production` agent-workspace scan, so an entry matches a finding from +# either scan. # # Review convention: this repo has no CODEOWNERS, so every entry block should # name its owning lane in the reason comment above it, and changes to an # entry need that lane's review. The gate rejects entries with no reason -# comment above them (presence only), and stale entries (matching no current ts-prune flag) -# fail the gate: remove the entry with the code it covered. +# comment above them (presence only), and stale entries (matching no current +# knip finding) fail the gate: remove the entry with the code it covered. # -# Everything NOT listed here must have zero ts-prune flags: any export with no -# consumer gets deleted instead of allowlisted. +# Everything NOT listed here must have zero knip findings: any export with no +# consumer, or any file nothing imports, gets deleted instead of allowlisted. # Vendored upstream mirror (workspace vendor/intx-*). Its public surface is # owned upstream; pruning here would diverge the mirror and break sibling @@ -22,81 +28,95 @@ vendor/ # Deferred Codex/xAI usage-data cleanup owned by the CL-6815 lane: these # modules are fully dead deferred code with no live importers (not test-only -# helpers), and that lane decides their fate. Scoped to the exact ts-prune -# flags so a new dead export in these files fails the guard instead of -# hiding under a whole-file exemption. Remove each entry with the export it -# names when the owning cleanup lands. +# helpers), and that lane decides their fate. Scoped to the exact knip flags +# so a new dead export in these files fails the guard instead of hiding under +# a whole-file exemption. Remove each entry with the export it names when the +# owning cleanup lands. src/auth/codex/constants.ts: CODEX_REFRESH_SKEW_MS src/auth/codex/constants.ts: CODEX_HEADLESS_REFRESH_INTERVAL_MS -# ts-prune parser false positives: bare tokens on `as const satisfies ...` -# lines inside live exports. None of these is an export declaration. -src/provider/reasoning-effort.ts: satisfies -src/tui/prompt-input.ts: satisfies -src/agent/directors/identity.ts: satisfies -src/agent/directors/identity.ts: Record -packages/opencode-go/src/models.ts: satisfies -packages/opencode-go/src/models.ts: readonly - # Agent-package public API (CL-9800 extraction wave): each @corbits/code-agent-* -# workspace package's six-name entry-point surface (agent, defineAgent, director, -# tools, systemPrompt, config) is the package's public contract. The closed -# director fan-in (src/agent/directors/registry.ts) consumes `director`; the -# remaining names are public API by design (custom-agent authoring surface), and -# were previously laundered through the deleted src/agent/fleet.ts tests-only -# barrel. Honest public API — owned by the agent-template lane; remove with the -# code if the package is deleted. +# workspace package's entry-point surface (agent, defineAgent, director, tools, +# systemPrompt, config) is the package's public contract. DIRECTOR_REGISTRY +# (src/agent/directors/registry.ts) consumes the package's `director` name and +# nothing else (src/agent/prompts.ts consumes dispatch's `systemPrompt`), so +# those two names stay consumer-clear in the guard's union scans. agent and +# defineAgent surface in the default scan only, and the backing agent.ts +# module's `agent` surfaces in both; the default scan also counts +# tools/systemPrompt/config alive through each package's contract test +# (src/agent/directors//package.test.ts), so they resurface only under the +# production agent-workspace scan. That is a test-laundering disclosure under +# the AGENTS.md director-extract bar: these worker names ride the package +# contract test until a production consumer (the registry or a real importer) +# or deletion lands. Honest public API for the per-name reasons above, owned +# by the agent-template lane; remove with the code if the package is deleted, +# and remove each entry as its name gains a production consumer. agents/dispatch/src/index.ts: agent agents/dispatch/src/index.ts: defineAgent -agents/dispatch/src/config.ts: satisfies -agents/dispatch/src/config.ts: boolean +agents/dispatch/src/index.ts: tools +agents/dispatch/src/index.ts: config +agents/dispatch/src/agent.ts: agent agents/warden/src/index.ts: agent agents/warden/src/index.ts: defineAgent -agents/warden/src/config.ts: satisfies -agents/warden/src/config.ts: boolean +agents/warden/src/index.ts: tools +agents/warden/src/index.ts: systemPrompt +agents/warden/src/index.ts: config +agents/warden/src/agent.ts: agent agents/designer/src/index.ts: agent agents/designer/src/index.ts: defineAgent -agents/designer/src/config.ts: satisfies -agents/designer/src/config.ts: boolean +agents/designer/src/index.ts: tools +agents/designer/src/index.ts: systemPrompt +agents/designer/src/index.ts: config +agents/designer/src/agent.ts: agent agents/reviewer/src/index.ts: agent agents/reviewer/src/index.ts: defineAgent -agents/reviewer/src/index.ts: director agents/reviewer/src/index.ts: tools agents/reviewer/src/index.ts: systemPrompt agents/reviewer/src/index.ts: config -agents/reviewer/src/config.ts: satisfies -agents/reviewer/src/config.ts: boolean +agents/reviewer/src/agent.ts: agent agents/planner/src/index.ts: agent agents/planner/src/index.ts: defineAgent -agents/planner/src/index.ts: director agents/planner/src/index.ts: tools agents/planner/src/index.ts: systemPrompt agents/planner/src/index.ts: config -agents/planner/src/config.ts: satisfies -agents/planner/src/config.ts: boolean +agents/planner/src/agent.ts: agent agents/coder/src/index.ts: agent agents/coder/src/index.ts: defineAgent -agents/coder/src/index.ts: director agents/coder/src/index.ts: tools agents/coder/src/index.ts: systemPrompt agents/coder/src/index.ts: config -agents/coder/src/config.ts: satisfies -agents/coder/src/config.ts: boolean +agents/coder/src/agent.ts: agent agents/explorer/src/index.ts: agent agents/explorer/src/index.ts: defineAgent -agents/explorer/src/index.ts: director agents/explorer/src/index.ts: tools agents/explorer/src/index.ts: systemPrompt agents/explorer/src/index.ts: config -agents/explorer/src/config.ts: satisfies -agents/explorer/src/config.ts: boolean +agents/explorer/src/agent.ts: agent +agents/artist/src/index.ts: agent +agents/artist/src/index.ts: defineAgent +agents/artist/src/index.ts: tools +agents/artist/src/index.ts: systemPrompt +agents/artist/src/index.ts: config +agents/artist/src/agent.ts: agent +agents/shakespeare/src/index.ts: agent +agents/shakespeare/src/index.ts: defineAgent +agents/shakespeare/src/index.ts: tools +agents/shakespeare/src/index.ts: systemPrompt +agents/shakespeare/src/index.ts: config +agents/shakespeare/src/agent.ts: agent +agents/prober/src/index.ts: agent +agents/prober/src/index.ts: defineAgent +agents/prober/src/index.ts: tools +agents/prober/src/index.ts: systemPrompt +agents/prober/src/index.ts: config +agents/prober/src/agent.ts: agent # Versioned prompt-variance package public surface (CL-8269 lane, # agent-owned). Family rows data re-exported through the package entry point; # no in-repo barrel consumer exists yet for the entries below (src/ consumes # claudeRow, gptRow, grokRow, grokToolBudgetResidual, and museRow through the # barrel, and the package tests import the backing modules directly). Scoped -# to the exact ts-prune flags so a new dead export in this barrel fails the +# to the exact knip flags so a new dead export in this barrel fails the # guard instead of hiding under a whole-file exemption. Remove each entry # with the export it names if the public surface shrinks. packages/prompt-variance/src/index.ts: defaultRow @@ -105,23 +125,32 @@ packages/prompt-variance/src/index.ts: FAMILY_ROWS packages/prompt-variance/src/index.ts: PromptVarianceFamily packages/prompt-variance/src/index.ts: PromptVarianceRow -# Agent-package public API (CL-9800 extraction wave): each @corbits/code-agent-* -# workspace package's six-name entry-point surface (agent, defineAgent, director, -# tools, systemPrompt, config) is the package's public contract. The closed -# director fan-in (src/agent/directors/registry.ts) consumes `director`; the -# remaining names are public API by design (custom-agent authoring surface), and -# were previously laundered through the deleted src/agent/fleet.ts tests-only -# barrel. Honest public API — owned by the agent-template lane; remove with the -# code if the package is deleted. -agents/artist/src/index.ts: agent -agents/artist/src/index.ts: defineAgent -agents/artist/src/config.ts: satisfies -agents/artist/src/config.ts: boolean -agents/shakespeare/src/index.ts: agent -agents/shakespeare/src/index.ts: defineAgent -agents/shakespeare/src/config.ts: satisfies -agents/shakespeare/src/config.ts: boolean -agents/prober/src/index.ts: agent -agents/prober/src/index.ts: defineAgent -agents/prober/src/config.ts: satisfies -agents/prober/src/config.ts: boolean +# Exports only knip sees dead (CL-10655 migration): ts-prune lacked module +# resolution, so a re-export whose consumer lives elsewhere in the module +# graph, a name shadowed by an identically-named external symbol, or an +# in-module-only use all escaped it, and knip now reports them. They are real +# consumer-less surface pending deletion by the dead-code cleanup lane +# (owner: CL-10655 review picks the follow-up lane); scoped to the exact +# names so the gate still fails on any NEW dead export in these files. +src/auth/xai/constants.ts: XAI_REDIRECT_URI +src/auth/xai/constants.ts: XAI_REFRESH_SKEW_MS +src/auth/codex/login.ts: openInBrowser +src/branding.ts: PRODUCT_SHORT_NAME +src/config/index.ts: fetchBifrostModels +src/config/settings.ts: shellEnvFromSettings +src/perf/assert-spans.ts: spanDurationNs +src/perf/index.ts: isOpaqueId +src/subagent/run.ts: NestedDispatchDeps +src/subagent/run.ts: SubAgentSandboxDeps +src/plugins/loader.ts: ProjectTrustStore +src/tui/overlays.ts: PrimaryOverlayKind + +# Unreachable whole files only knip's `files` class catches (CL-10655 +# migration). The completion-eval fixture data files are loaded by path from +# evals/completion/tasks.json (the runner copies them into per-run workdirs), +# so no import edges exist and knip flags the file as a whole. Owned by the +# eval-harness lane; delete with the task entry if a fixture becomes unused. +evals/completion/tasks/decline-refactor/fixture/notes.ts +evals/completion/tasks/version-endpoint/fixture/service.ts +evals/completion/tasks/stall-read/fixture/data.ts +evals/completion/tasks/sum-fix/fixture/sum.ts diff --git a/scripts/dead-export-guard.json b/scripts/dead-export-guard.json index e29ca9e6c..cf4803e00 100644 --- a/scripts/dead-export-guard.json +++ b/scripts/dead-export-guard.json @@ -1,5 +1,4 @@ { "tsconfig": "tsconfig.json", - "tsPruneArgs": ["-p", "tsconfig.json"], "minScannedFiles": 1240 } From f15a34a2b071738950ebeb24ed315cd736868a27 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Thu, 15 Oct 2026 13:23:34 -0700 Subject: [PATCH 2/3] fix(gate): delete unconsumed exports instead of allowlisting them The allowlist now names only the agent-package contract. Exports with no production importer are removed, and vendor plus path-loaded eval fixtures stay outside the knip scan. --- .../tasks/decline-refactor/fixture/notes.ts | 1 - .../tasks/stall-read/fixture/data.ts | 1 - evals/completion/tasks/sum-fix/fixture/sum.ts | 1 - .../tasks/version-endpoint/fixture/service.ts | 1 - .../plugins/implement-feature/src/index.ts | 2 - knip.agents.json | 4 +- knip.json | 4 +- packages/prompt-variance/src/index.ts | 5 - scripts/check-dead-exports.test.ts | 33 +++--- scripts/check-dead-exports.ts | 32 +++--- scripts/dead-export-allowlist.txt | 107 +++--------------- src/auth/codex/constants.ts | 5 - src/auth/codex/login.ts | 3 - src/auth/xai/constants.ts | 4 +- src/branding.ts | 3 - src/config/bifrost.ts | 29 ----- src/config/index.ts | 3 - src/config/settings.ts | 9 -- src/perf/assert-spans.ts | 4 - src/perf/index.ts | 7 +- src/plugins/loader.ts | 3 +- src/provider/bifrost-adapter.ts | 3 +- src/subagent/run.ts | 7 +- src/tui/overlays.ts | 3 +- 24 files changed, 53 insertions(+), 221 deletions(-) delete mode 100644 src/config/bifrost.ts diff --git a/evals/completion/tasks/decline-refactor/fixture/notes.ts b/evals/completion/tasks/decline-refactor/fixture/notes.ts index 97740c7e6..f6d1c6671 100644 --- a/evals/completion/tasks/decline-refactor/fixture/notes.ts +++ b/evals/completion/tasks/decline-refactor/fixture/notes.ts @@ -1,6 +1,5 @@ export const notes: string[] = []; -// ts-prune-ignore-next export function addNote(note: string): void { notes.push(note); } diff --git a/evals/completion/tasks/stall-read/fixture/data.ts b/evals/completion/tasks/stall-read/fixture/data.ts index b3f77b0b2..92b227258 100644 --- a/evals/completion/tasks/stall-read/fixture/data.ts +++ b/evals/completion/tasks/stall-read/fixture/data.ts @@ -1,2 +1 @@ -// ts-prune-ignore-next export const data = { seed: 7 }; diff --git a/evals/completion/tasks/sum-fix/fixture/sum.ts b/evals/completion/tasks/sum-fix/fixture/sum.ts index 4f6cf1d10..dce2a7d38 100644 --- a/evals/completion/tasks/sum-fix/fixture/sum.ts +++ b/evals/completion/tasks/sum-fix/fixture/sum.ts @@ -1,4 +1,3 @@ -// ts-prune-ignore-next export function total(values: number[]): number { let sum = 0; for (const value of values) { diff --git a/evals/completion/tasks/version-endpoint/fixture/service.ts b/evals/completion/tasks/version-endpoint/fixture/service.ts index 6da89986f..601785b22 100644 --- a/evals/completion/tasks/version-endpoint/fixture/service.ts +++ b/evals/completion/tasks/version-endpoint/fixture/service.ts @@ -3,7 +3,6 @@ export interface Reply { body: unknown; } -// ts-prune-ignore-next export function handleRequest(method: string, path: string): Reply { if (method === "GET" && path === "/health") { return { status: 200, body: { ok: true } }; diff --git a/fixtures/plugins/implement-feature/src/index.ts b/fixtures/plugins/implement-feature/src/index.ts index 4deedf8f5..69777fe3f 100644 --- a/fixtures/plugins/implement-feature/src/index.ts +++ b/fixtures/plugins/implement-feature/src/index.ts @@ -2,12 +2,10 @@ import { implementFeature } from "./workflows/implement-feature.js"; import type { CommandPlugin } from "../../../../src/tui/commands/registry.js"; import type { WorkflowPlugin } from "../../../../src/workflows/definition.js"; -// ts-prune-ignore-next export const workflowPlugin: WorkflowPlugin = { workflows: [implementFeature], }; -// ts-prune-ignore-next export const commandPlugin: CommandPlugin = { commands: [ { diff --git a/knip.agents.json b/knip.agents.json index 9df9596b5..d07dcb614 100644 --- a/knip.agents.json +++ b/knip.agents.json @@ -14,9 +14,9 @@ "e2e/**/*.ts", "evals/**/*.ts", "scripts/**/*.ts", - "agents/**/*.ts", - "vendor/**/*.ts" + "agents/**/*.ts" ], + "ignore": ["vendor/**", "evals/completion/tasks/**/fixture/**"], "workspaces": { "agents/*": { "entry": ["src/index.ts!"], diff --git a/knip.json b/knip.json index 2ffb246d5..9e9c47c61 100644 --- a/knip.json +++ b/knip.json @@ -14,9 +14,9 @@ "e2e/**/*.ts", "evals/**/*.ts", "scripts/**/*.ts", - "agents/**/*.ts", - "vendor/**/*.ts" + "agents/**/*.ts" ], + "ignore": ["vendor/**", "evals/completion/tasks/**/fixture/**"], "includeEntryExports": true, "rules": { "exports": "error", diff --git a/packages/prompt-variance/src/index.ts b/packages/prompt-variance/src/index.ts index f13c5ce5b..6eb6c3d51 100644 --- a/packages/prompt-variance/src/index.ts +++ b/packages/prompt-variance/src/index.ts @@ -1,14 +1,9 @@ export { claudeRow, - defaultRow, - FAMILY_IDS, - FAMILY_ROWS, gptRow, grokRow, grokToolBudgetResidual, museRow, - type PromptVarianceFamily, - type PromptVarianceRow, } from "./rows.js"; // Astra tool-evasion residual: the gpt-6-astra leaf re-issues the same diff --git a/scripts/check-dead-exports.test.ts b/scripts/check-dead-exports.test.ts index db92b8049..ae2744ae5 100644 --- a/scripts/check-dead-exports.test.ts +++ b/scripts/check-dead-exports.test.ts @@ -47,24 +47,19 @@ function liveScannedFileCount(): number { // Scoped exemptions cover only the named flags; a sibling probe still fails. describe("scoped exemptions", () => { - test("the real allowlist covers the named flags but not a sibling probe", () => { + test("the real allowlist covers the named public exports but not a sibling", () => { const rules = loadAllowlist(); + expect(isAllowlisted(rules, "agents/coder/src/index.ts", "tools")).toBe( + true, + ); + expect(isAllowlisted(rules, "agents/coder/src/index.ts", "config")).toBe( + true, + ); + expect(isAllowlisted(rules, "agents/coder/src/index.ts", "director")).toBe( + false, + ); expect( - isAllowlisted( - rules, - "src/auth/codex/constants.ts", - "CODEX_REFRESH_SKEW_MS", - ), - ).toBe(true); - expect( - isAllowlisted( - rules, - "src/auth/codex/constants.ts", - "CODEX_HEADLESS_REFRESH_INTERVAL_MS", - ), - ).toBe(true); - expect( - isAllowlisted(rules, "src/auth/codex/usage.ts", "someNewDeadExport"), + isAllowlisted(rules, "agents/coder/src/index.ts", "someNewDeadExport"), ).toBe(false); }); @@ -237,7 +232,7 @@ describe("live knip scan semantics", () => { expect(reported("agents/coder/src/index.ts", "director")).toBe(false); // knip counts the package contract test's import as consumption. expect(reported("agents/coder/src/index.ts", "tools")).toBe(false); - // The ts-prune parser false positives are gone. + // A `satisfies` token inside a live export is not itself an export. expect(reported("src/agent/directors/identity.ts", "satisfies")).toBe( false, ); @@ -459,7 +454,7 @@ describe("pinned scan invocation", () => { "agents/coder/src/index.ts: agent\n" + "agents/coder/src/index.ts: agent\n" + "agents/dispatch/src/index.ts: agent\n" + - "src/auth/codex/constants.ts: CODEX_REFRESH_SKEW_MS\n", + "src/auth/codex/constants.ts: CODEX_BASE_URL\n", ); // Parse dedupes nothing at rule level; the derivation dedupes ids. expect(agentWorkspaceIds(rules)).toEqual([ @@ -475,7 +470,7 @@ describe("pinned scan invocation", () => { expect(() => parseGuardConfig({ ...valid, minScannedFiles: 0 })).toThrow(); expect(() => parseGuardConfig({ ...valid, tsconfig: "" })).toThrow(); expect(() => - parseGuardConfig({ ...valid, tsPruneArgs: ["-p", "x"] }), + parseGuardConfig({ ...valid, scanArgs: ["-p", "x"] }), ).toThrow(); expect(() => parseGuardConfig(null)).toThrow(); expect(() => parseGuardConfig([])).toThrow(); diff --git a/scripts/check-dead-exports.ts b/scripts/check-dead-exports.ts index 41ec3a789..35cb81245 100644 --- a/scripts/check-dead-exports.ts +++ b/scripts/check-dead-exports.ts @@ -4,15 +4,14 @@ import { dirname, join, sep } from "node:path"; import { fileURLToPath } from "node:url"; // Dead-export guard: runs knip and fails when a consumer-less export or an -// unused file falls outside scripts/dead-export-allowlist.txt. knip replaces -// the retired ts-prune scanner (CL-10655); it resolves the module graph, so an -// export consumed through a re-export barrel or the closed director fan-in is -// not reported, and exports used only inside their own module are skipped -// (ignoreExportsUsedInFile in knip.json knits both semantics). knip.json pins -// the scan (entry points per tree plus the workspace packages); this guard -// owns only the allowlist and the fail-closed floor. New dead exports must be -// deleted, not allowlisted — the allowlist covers entry-point API surfaces and -// deferred-deletion lanes only. +// unused file falls outside scripts/dead-export-allowlist.txt. knip resolves +// the module graph, so an export consumed through a re-export barrel or the +// closed director fan-in is not reported, and exports used only inside their +// own module are skipped (ignoreExportsUsedInFile in knip.json). knip.json +// pins the scan; this guard owns only the allowlist and the fail-closed floor. +// New dead exports must be deleted, not allowlisted — the allowlist names +// entry-point API surfaces only. vendor/ and path-loaded eval fixtures are +// knip ignore entries, not allowlist blankets. // // Hardening: stale entries fail instead of warning, every entry must pass // shape validation and sit under a reason comment (the gate enforces the @@ -33,10 +32,10 @@ const guardConfigPath = join(here, "dead-export-guard.json"); const knipBin = join(repoRoot, "node_modules", ".bin", "knip"); const tscBin = join(repoRoot, "node_modules", "typescript", "bin", "tsc"); -// The integrated-monorepo knip config that drives the production agent scan; -// a separate file so the default scan keeps knip.json's ts-prune-parity -// project scope (the `workspaces` key switches knip into integrated mode, -// which also widens its default file detection beyond that scope). +// The integrated-monorepo knip config that drives the production agent scan. +// A separate file so the default scan keeps knip.json's project scope (the +// `workspaces` key switches knip into integrated mode, which also widens its +// default file detection beyond that scope). export function agentsConfigPath(): string { return join(repoRoot, "knip.agents.json"); } @@ -366,8 +365,7 @@ export function knipDefaultSpawn(knipBinPath: string): { // `workspaces` key makes cross-workspace consumers visible in production // mode), so the registry fan-in still clears `director` while the laundered // worker names surface. Outside that surface the default scan owns -// enforcement at ts-prune parity; the union of both scans is the guard's -// finding set. +// enforcement; the union of both scans is the guard's finding set. export function knipAgentProductionSpawn( knipBinPath: string, workspaces: readonly string[], @@ -490,11 +488,11 @@ function fail(message: string): never { // The guard's finding set is the union of two knip reports: // -// 1. the default scan over knip.json's project scope (ts-prune parity), and +// 1. the default scan over knip.json's project scope, and // 2. the `--production` scan filtered to the agent workspaces, where knip // does not count test-file consumers. // -// Both run under the pinned pinned spawn contract; a failing or malformed +// Both run under the pinned spawn contract; a failing or malformed // report fails the gate rather than narrowing it silently. export function runKnipScans(rules: readonly AllowRule[]): DeadExport[] { requireKnipConfigs(); diff --git a/scripts/dead-export-allowlist.txt b/scripts/dead-export-allowlist.txt index c0fbde2f9..096ff04f9 100644 --- a/scripts/dead-export-allowlist.txt +++ b/scripts/dead-export-allowlist.txt @@ -1,56 +1,21 @@ -# Dead-export allowlist for `bun run check:dead-exports` (CL-6797, -# knip-driven since CL-10655). +# Dead-export allowlist for `bun run check:dead-exports`. # -# Format: `#` lines are reasons and attach to the entries below them. An entry -# is either a path prefix ending in `/` (matches a directory subtree), a full -# file path (matches every export in that file plus a whole-unreachable-file -# knip finding), or `file: name` (matches one export). Anything else (a -# directory without its trailing `/`, a `file:` line that is not -# `path/to/file.ts: ExportName`) fails the gate. +# An entry is `path/to/file.ts: ExportName`. `#` lines are the reason and +# attach to the entries below them until a blank line. A directory prefix or a +# bare file path still parses, but it blankets a surface, so this list does +# not use one. Anything else fails the gate. # # The guard's finding set is the union of knip's default scan and its -# `--production` agent-workspace scan, so an entry matches a finding from -# either scan. +# `--production` agent-workspace scan. Stale entries fail the gate. An export +# with no production importer is deleted, not added here. The upstream +# `vendor/` mirror and completion-eval fixtures (loaded by path from +# evals/completion/tasks.json, not imported) are knip `ignore` entries. # -# Review convention: this repo has no CODEOWNERS, so every entry block should -# name its owning lane in the reason comment above it, and changes to an -# entry need that lane's review. The gate rejects entries with no reason -# comment above them (presence only), and stale entries (matching no current -# knip finding) fail the gate: remove the entry with the code it covered. -# -# Everything NOT listed here must have zero knip findings: any export with no -# consumer, or any file nothing imports, gets deleted instead of allowlisted. - -# Vendored upstream mirror (workspace vendor/intx-*). Its public surface is -# owned upstream; pruning here would diverge the mirror and break sibling -# vendor workspaces on the next sync. Prune upstream, not here. -vendor/ - -# Deferred Codex/xAI usage-data cleanup owned by the CL-6815 lane: these -# modules are fully dead deferred code with no live importers (not test-only -# helpers), and that lane decides their fate. Scoped to the exact knip flags -# so a new dead export in these files fails the guard instead of hiding under -# a whole-file exemption. Remove each entry with the export it names when the -# owning cleanup lands. -src/auth/codex/constants.ts: CODEX_REFRESH_SKEW_MS -src/auth/codex/constants.ts: CODEX_HEADLESS_REFRESH_INTERVAL_MS - -# Agent-package public API (CL-9800 extraction wave): each @corbits/code-agent-* -# workspace package's entry-point surface (agent, defineAgent, director, tools, -# systemPrompt, config) is the package's public contract. DIRECTOR_REGISTRY -# (src/agent/directors/registry.ts) consumes the package's `director` name and -# nothing else (src/agent/prompts.ts consumes dispatch's `systemPrompt`), so -# those two names stay consumer-clear in the guard's union scans. agent and -# defineAgent surface in the default scan only, and the backing agent.ts -# module's `agent` surfaces in both; the default scan also counts -# tools/systemPrompt/config alive through each package's contract test -# (src/agent/directors//package.test.ts), so they resurface only under the -# production agent-workspace scan. That is a test-laundering disclosure under -# the AGENTS.md director-extract bar: these worker names ride the package -# contract test until a production consumer (the registry or a real importer) -# or deletion lands. Honest public API for the per-name reasons above, owned -# by the agent-template lane; remove with the code if the package is deleted, -# and remove each entry as its name gains a production consumer. +# Public contract of each private @corbits/code-agent-* package (agent-template +# lane): the custom-agent authoring surface. DIRECTOR_REGISTRY consumes +# `director`, and src/agent/prompts.ts consumes dispatch's `systemPrompt`, so +# those names are not listed. `agent` in `agent.ts` is the definition the +# package entry re-exports. agents/dispatch/src/index.ts: agent agents/dispatch/src/index.ts: defineAgent agents/dispatch/src/index.ts: tools @@ -110,47 +75,3 @@ agents/prober/src/index.ts: tools agents/prober/src/index.ts: systemPrompt agents/prober/src/index.ts: config agents/prober/src/agent.ts: agent - -# Versioned prompt-variance package public surface (CL-8269 lane, -# agent-owned). Family rows data re-exported through the package entry point; -# no in-repo barrel consumer exists yet for the entries below (src/ consumes -# claudeRow, gptRow, grokRow, grokToolBudgetResidual, and museRow through the -# barrel, and the package tests import the backing modules directly). Scoped -# to the exact knip flags so a new dead export in this barrel fails the -# guard instead of hiding under a whole-file exemption. Remove each entry -# with the export it names if the public surface shrinks. -packages/prompt-variance/src/index.ts: defaultRow -packages/prompt-variance/src/index.ts: FAMILY_IDS -packages/prompt-variance/src/index.ts: FAMILY_ROWS -packages/prompt-variance/src/index.ts: PromptVarianceFamily -packages/prompt-variance/src/index.ts: PromptVarianceRow - -# Exports only knip sees dead (CL-10655 migration): ts-prune lacked module -# resolution, so a re-export whose consumer lives elsewhere in the module -# graph, a name shadowed by an identically-named external symbol, or an -# in-module-only use all escaped it, and knip now reports them. They are real -# consumer-less surface pending deletion by the dead-code cleanup lane -# (owner: CL-10655 review picks the follow-up lane); scoped to the exact -# names so the gate still fails on any NEW dead export in these files. -src/auth/xai/constants.ts: XAI_REDIRECT_URI -src/auth/xai/constants.ts: XAI_REFRESH_SKEW_MS -src/auth/codex/login.ts: openInBrowser -src/branding.ts: PRODUCT_SHORT_NAME -src/config/index.ts: fetchBifrostModels -src/config/settings.ts: shellEnvFromSettings -src/perf/assert-spans.ts: spanDurationNs -src/perf/index.ts: isOpaqueId -src/subagent/run.ts: NestedDispatchDeps -src/subagent/run.ts: SubAgentSandboxDeps -src/plugins/loader.ts: ProjectTrustStore -src/tui/overlays.ts: PrimaryOverlayKind - -# Unreachable whole files only knip's `files` class catches (CL-10655 -# migration). The completion-eval fixture data files are loaded by path from -# evals/completion/tasks.json (the runner copies them into per-run workdirs), -# so no import edges exist and knip flags the file as a whole. Owned by the -# eval-harness lane; delete with the task entry if a fixture becomes unused. -evals/completion/tasks/decline-refactor/fixture/notes.ts -evals/completion/tasks/version-endpoint/fixture/service.ts -evals/completion/tasks/stall-read/fixture/data.ts -evals/completion/tasks/sum-fix/fixture/sum.ts diff --git a/src/auth/codex/constants.ts b/src/auth/codex/constants.ts index fac16c07d..b81fab440 100644 --- a/src/auth/codex/constants.ts +++ b/src/auth/codex/constants.ts @@ -4,7 +4,6 @@ import { codexOAuthConfig } from "@corbits/codex-provider"; export { CODEX_BASE_URL, CODEX_REDIRECT_URI, - CODEX_REFRESH_SKEW_MS, CODEX_RESPONSES_PATH, } from "@corbits/codex-provider"; @@ -38,7 +37,3 @@ export const CODEX_DEFAULT_MODELS = [ "gpt-5.6-luna", "gpt-5.4-mini", ] as const; - -// How often a headless run re-checks its Codex token and reseeds the source. -// Half the skew so the refresh window is never missed between ticks. -export const CODEX_HEADLESS_REFRESH_INTERVAL_MS = 30_000; diff --git a/src/auth/codex/login.ts b/src/auth/codex/login.ts index 2c3f3f03f..10d755125 100644 --- a/src/auth/codex/login.ts +++ b/src/auth/codex/login.ts @@ -1,6 +1,5 @@ import { buildAuthorizeUrl, - openInBrowser, startOAuthLogin, type OAuthLoginHandle, type StartOAuthLoginOptions, @@ -16,8 +15,6 @@ import { saveCodexProfile } from "../../config/oauth-stores.js"; import { startCodexCallbackServer } from "./callback-server.js"; import { withDefaultCodexExpiry } from "./store.js"; -export { openInBrowser }; - export type CodexLoginHandle = OAuthLoginHandle; export type StartCodexLoginOptions = StartOAuthLoginOptions & { home?: string; diff --git a/src/auth/xai/constants.ts b/src/auth/xai/constants.ts index cbdde7c59..e8ad3b3fd 100644 --- a/src/auth/xai/constants.ts +++ b/src/auth/xai/constants.ts @@ -2,12 +2,10 @@ import { callbackTargetFor } from "@corbits/oauth-core"; import { XAI_DEFAULT_MODELS as VENDOR_XAI_DEFAULT_MODELS, XAI_OAUTH_PROXY_BASE_URL as XAI_BASE_URL, - XAI_REDIRECT_URI, - XAI_REFRESH_SKEW_MS, xaiOAuthConfig, } from "@corbits/xai-provider"; -export { XAI_BASE_URL, XAI_REDIRECT_URI, XAI_REFRESH_SKEW_MS }; +export { XAI_BASE_URL }; const GROK_47 = "grok-4.7"; diff --git a/src/branding.ts b/src/branding.ts index f4626ca65..b8304ce4a 100644 --- a/src/branding.ts +++ b/src/branding.ts @@ -7,9 +7,6 @@ export const PRODUCT_NAME = "Corbits Code"; -// Short attribution form used in compact UI (status lines, footers). -export const PRODUCT_SHORT_NAME = "Corbits"; - // Canonical public URLs, each paired with the text shown in its place. The label // is spelled out rather than stripped from the URL so it can diverge from the // bare host later without a rendering helper having to special-case it. diff --git a/src/config/bifrost.ts b/src/config/bifrost.ts deleted file mode 100644 index 57546ce2c..000000000 --- a/src/config/bifrost.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { requestModelsEndpoint } from "../provider/models-endpoint.js"; - -/** - * Fetch the model list from a Bifrost gateway for the given virtual key. - * Sends both the x-bf-vk header and a conventional Bearer Authorization so - * either auth style works. - * - * Returns only the model id strings. Throws on HTTP or parse errors. - */ -export async function fetchBifrostModels( - baseURL: string, - apiKey: string, -): Promise { - const headers: Record = { - "x-bf-vk": apiKey, - }; - if (apiKey && apiKey.length > 0) { - headers.authorization = `Bearer ${apiKey}`; - } - const res = await requestModelsEndpoint({ baseURL, headers }); - if (!res.ok) { - const text = await res.text().catch(() => ""); - throw new Error(`Failed to list Bifrost models (${res.status}): ${text}`); - } - const json = (await res.json()) as { data?: { id?: unknown }[] }; - return (json.data ?? []) - .map((d) => (typeof d.id === "string" ? d.id : undefined)) - .filter((id): id is string => id !== undefined && id.length > 0); -} diff --git a/src/config/index.ts b/src/config/index.ts index bbe63150c..05844ae1b 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -44,10 +44,7 @@ import { xaiProfilesToCatalogEntries, xaiProvidersAsSettings, } from "./xai-providers.js"; -import { fetchBifrostModels } from "./bifrost.js"; import { customReasoningSettings } from "./providers.js"; - -export { fetchBifrostModels }; import { CODEX_BASE_URL } from "../auth/codex/constants.js"; import { XAI_BASE_URL } from "../auth/xai/constants.js"; import { diff --git a/src/config/settings.ts b/src/config/settings.ts index aef269b65..6c0ac56d2 100644 --- a/src/config/settings.ts +++ b/src/config/settings.ts @@ -433,15 +433,6 @@ export function toolWatchdogFromSettings(settings?: Settings | null): }; } -// Maps settings.env (per-project) to the extra env vars the shell-guard plugin -// merges into the run_shell spawn environment. Returns undefined when unset so -// callers inherit process.env unmodified. -export function shellEnvFromSettings( - local?: LocalSettings | null, -): Record | undefined { - return local?.env; -} - export interface PluginConfig { enabled?: boolean; // One-time consent for a tool plugin (kind "tool"): its tools add diff --git a/src/perf/assert-spans.ts b/src/perf/assert-spans.ts index f905955b5..463fe46ee 100644 --- a/src/perf/assert-spans.ts +++ b/src/perf/assert-spans.ts @@ -6,7 +6,6 @@ import type { PerfSpan, SpanName } from "./index.js"; import type { PhaseSummary, TurnSummary } from "./rollup.js"; -import { spanDurationNs } from "./rollup.js"; /** Verify at least one span with the given phase name exists. */ export function assertPhasePresent( @@ -112,6 +111,3 @@ export function assertPhaseSummary( } return phase; } - -/** Span duration helper re-export for eval scripts that only import assertions. */ -export { spanDurationNs }; diff --git a/src/perf/index.ts b/src/perf/index.ts index 171e57cdd..08d6dfdc6 100644 --- a/src/perf/index.ts +++ b/src/perf/index.ts @@ -9,12 +9,7 @@ import { isOpaqueId, sanitizeTags, type PerfTags } from "./sanitize.js"; import { clearActiveTurnId } from "./active-turn.js"; -export { - sanitizeTags, - isOpaqueId, - ALLOWED_TAG_KEYS, - type PerfTags, -} from "./sanitize.js"; +export { sanitizeTags, ALLOWED_TAG_KEYS, type PerfTags } from "./sanitize.js"; export { type FlushPerfToOtelOptions } from "./otel-sink.js"; diff --git a/src/plugins/loader.ts b/src/plugins/loader.ts index 90da0f8d7..cacc38143 100644 --- a/src/plugins/loader.ts +++ b/src/plugins/loader.ts @@ -29,7 +29,6 @@ import { import { originRequiresTrust, type PluginOrigin, - type ProjectTrustStore, } from "../trust/project-trust.js"; export type { PluginLoadDiagnostics } from "./diagnostics.js"; @@ -1120,4 +1119,4 @@ function looksLikeVersionDirId(id: string): boolean { } /** Re-export for callers that need the type without importing trust directly. */ -export type { PluginOrigin, ProjectTrustStore }; +export type { PluginOrigin }; diff --git a/src/provider/bifrost-adapter.ts b/src/provider/bifrost-adapter.ts index bb1b4ec79..7ab6edeef 100644 --- a/src/provider/bifrost-adapter.ts +++ b/src/provider/bifrost-adapter.ts @@ -10,8 +10,7 @@ import { createOpenAICompatibleAdapter } from "./openai-compatible-adapter.js"; // permissions and model list via an `x-bf-vk` header (raw key value) // alongside Authorization: Bearer. `bifrostVirtualKey: true` providers get // this adapter, the x-bf-vk sentinel, and provider: "bifrost" from -// buildInferenceSourceForRef. Model listing uses fetchBifrostModels, which -// calls the gateway's /models with the same headers. +// buildInferenceSourceForRef. export const BIFROST_PROVIDER = "bifrost"; diff --git a/src/subagent/run.ts b/src/subagent/run.ts index babbee396..55057aa4b 100644 --- a/src/subagent/run.ts +++ b/src/subagent/run.ts @@ -297,12 +297,7 @@ function sleepUnlessAborted( }); } -export type { - NestedDispatchDeps, - RunSubAgentParams, - SubAgentProvider, - SubAgentSandboxDeps, -} from "./types.js"; +export type { RunSubAgentParams, SubAgentProvider } from "./types.js"; // Source used when no profile tier resolves. Exported for tests: catalog // entry markers pick the adapter (Bifrost virtual keys, Codex/xAI OAuth diff --git a/src/tui/overlays.ts b/src/tui/overlays.ts index 1839ffb25..abbc64ef3 100644 --- a/src/tui/overlays.ts +++ b/src/tui/overlays.ts @@ -7,7 +7,6 @@ import type { AppShell, ItemDescription, OverlaySelection, - PrimaryOverlayKind, } from "./shell/internals.js"; import { closeReplaceableOverlay, @@ -15,7 +14,7 @@ import { reserveOverlayHost, } from "./shell/overlay-host.js"; import type { KeyEvent } from "@opentui/core"; -export type { OverlaySelection, PrimaryOverlayKind }; +export type { OverlaySelection }; export interface OpenPermissionsOpts { /** Choices to offer; there is no fallback list — callers supply their own. */ From 8ebbda88fb0723568a5d91a6af664a1c9614153c Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Thu, 15 Oct 2026 13:26:41 -0700 Subject: [PATCH 3/3] fix(gate): production-scan every agents workspace Discover agent packages from agents/ instead of the allowlist, so a new package whose entry is imported only by its own test fails the dead-export guard. --- scripts/check-dead-exports.test.ts | 98 +++++++++++++++++++++++++----- scripts/check-dead-exports.ts | 40 +++++++----- 2 files changed, 108 insertions(+), 30 deletions(-) diff --git a/scripts/check-dead-exports.test.ts b/scripts/check-dead-exports.test.ts index ae2744ae5..860d4dfc5 100644 --- a/scripts/check-dead-exports.test.ts +++ b/scripts/check-dead-exports.test.ts @@ -1,11 +1,14 @@ import { spawnSync } from "node:child_process"; import { existsSync, + mkdirSync, + mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync, } from "node:fs"; +import { tmpdir } from "node:os"; import { join, relative } from "node:path"; import { describe, expect, test } from "bun:test"; @@ -240,7 +243,10 @@ describe("live knip scan semantics", () => { test("production agent scan removes test-file consumers from the surface", () => { const rules = loadAllowlist(); - const spawn = knipAgentProductionSpawn(knipBin, agentWorkspaceIds(rules)); + const spawn = knipAgentProductionSpawn( + knipBin, + agentWorkspaceIds(join(repoRoot, "agents")), + ); const ran = spawnSync(spawn.command, spawn.args, { cwd: repoRoot, encoding: "utf8", @@ -449,19 +455,24 @@ describe("pinned scan invocation", () => { expect(() => knipAgentProductionSpawn("/repo/x", [])).toThrow(); }); - test("agent workspace ids derive from the allowlist and fail closed", () => { - const rules = parseAllowlistText( - "agents/coder/src/index.ts: agent\n" + - "agents/coder/src/index.ts: agent\n" + - "agents/dispatch/src/index.ts: agent\n" + - "src/auth/codex/constants.ts: CODEX_BASE_URL\n", - ); - // Parse dedupes nothing at rule level; the derivation dedupes ids. - expect(agentWorkspaceIds(rules)).toEqual([ - "agents/coder", - "agents/dispatch", - ]); - expect(() => agentWorkspaceIds(parseAllowlistText("vendor/\n"))).toThrow(); + test("agent workspace ids are the agents directory and fail closed", () => { + const agentsRoot = join(repoRoot, "agents"); + const fromDisk = readdirSync(agentsRoot, { withFileTypes: true }) + .filter( + (entry) => + entry.isDirectory() && + existsSync(join(agentsRoot, entry.name, "package.json")), + ) + .map((entry) => `agents/${entry.name}`) + .sort(); + expect(fromDisk.length).toBeGreaterThan(0); + expect(agentWorkspaceIds(agentsRoot)).toEqual(fromDisk); + const empty = mkdtempSync(join(tmpdir(), "dead-export-agents-")); + try { + expect(() => agentWorkspaceIds(empty)).toThrow(); + } finally { + rmSync(empty, { recursive: true, force: true }); + } }); test("parseGuardConfig rejects a missing tsconfig or floor and extra keys", () => { @@ -543,6 +554,65 @@ describe("violation end to end", () => { rmSync(probePath, { force: true }); } }, 240_000); + + test("a test-only export in an extra agent workspace fails the guard", () => { + const id = `dead-export-guard-agent-${process.pid}`; + const exportName = `deadExportGuardAgentProbe${process.pid}`; + const dir = join(repoRoot, "agents", id); + mkdirSync(join(dir, "src"), { recursive: true }); + writeFileSync( + join(dir, "package.json"), + JSON.stringify({ + name: `@corbits/code-agent-${id}`, + private: true, + type: "module", + }), + ); + writeFileSync( + join(dir, "src", "index.ts"), + `export const ${exportName} = 1;\n`, + ); + writeFileSync( + join(dir, "src", "index.test.ts"), + `import { ${exportName} } from "./index.js";\nvoid ${exportName};\n`, + ); + try { + const knipBin = join(repoRoot, "node_modules", ".bin", "knip"); + const defaultSpawn = knipDefaultSpawn(knipBin); + const defaultRun = spawnSync(defaultSpawn.command, defaultSpawn.args, { + cwd: repoRoot, + encoding: "utf8", + }); + expect(defaultRun.status).toBe(0); + const defaultFindings = parseKnipReport(String(defaultRun.stdout)); + expect( + defaultFindings.some( + (finding) => + finding.file === `agents/${id}/src/index.ts` && + finding.name === exportName, + ), + ).toBe(false); + + const ran = spawnSync( + process.execPath, + ["scripts/check-dead-exports.ts"], + { + cwd: repoRoot, + encoding: "utf8", + env: { + ...process.env, + DEAD_EXPORT_GUARD_SCANNED_FILES: String(liveScannedFileCount()), + }, + }, + ); + expect(ran.status).toBe(1); + expect(`${ran.stdout}\n${ran.stderr}`).toContain( + `agents/${id}/src/index.ts: ${exportName}`, + ); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }, 240_000); }); // A re-created barrel would silently resurrect a surface the purge removed. diff --git a/scripts/check-dead-exports.ts b/scripts/check-dead-exports.ts index 35cb81245..d5da6ef16 100644 --- a/scripts/check-dead-exports.ts +++ b/scripts/check-dead-exports.ts @@ -1,5 +1,5 @@ import { spawnSync } from "node:child_process"; -import { existsSync, readFileSync, realpathSync } from "node:fs"; +import { existsSync, readdirSync, readFileSync, realpathSync } from "node:fs"; import { dirname, join, sep } from "node:path"; import { fileURLToPath } from "node:url"; @@ -382,22 +382,30 @@ export function knipAgentProductionSpawn( ]); } -// The agent workspaces the production scan must cover, derived from the -// allowlist's `agents//src/index.ts` entries so the scan stays pinned to -// the surface the allowlist governs. Fails closed when the surface names no +// Every agents/ workspace, not the allowlist. A package whose entry exports +// are imported only by its own test is invisible to the default scan (knip +// counts test files there) and would skip this production scan if membership +// came from allowlist entries. Fails closed when the directory names no // workspace: a silent no-op scan cannot be trusted. -export function agentWorkspaceIds(rules: readonly AllowRule[]): string[] { - const ids = new Set(); - for (const rule of rules) { - if (rule.kind === "exact" && rule.file.startsWith("agents/")) { - const id = rule.file.slice("agents/".length).split("/")[0]; - if (id !== undefined && id !== "") ids.add(`agents/${id}`); - } +export function agentWorkspaceIds(agentsRoot: string): string[] { + let names: string[]; + try { + names = readdirSync(agentsRoot, { withFileTypes: true }).flatMap((entry) => + entry.isDirectory() && + !entry.name.startsWith(".") && + existsSync(join(agentsRoot, entry.name, "package.json")) + ? [entry.name] + : [], + ); + } catch (err) { + throw new Error( + `agent workspace root not readable (${agentsRoot}): ${(err as Error).message}`, + ); } - const sorted = [...ids].sort(); + const sorted = names.map((name) => `agents/${name}`).sort(); if (sorted.length === 0) { throw new Error( - "dead-export allowlist names no agents//src/index.ts surface: " + + `agents directory names no workspace (${agentsRoot}): ` + "the agent-workspace production scan has nothing to cover", ); } @@ -494,7 +502,7 @@ function fail(message: string): never { // // Both run under the pinned spawn contract; a failing or malformed // report fails the gate rather than narrowing it silently. -export function runKnipScans(rules: readonly AllowRule[]): DeadExport[] { +export function runKnipScans(): DeadExport[] { requireKnipConfigs(); const merge = (reports: readonly DeadExport[]): DeadExport[] => { const seen = new Set(); @@ -534,7 +542,7 @@ export function runKnipScans(rules: readonly AllowRule[]): DeadExport[] { knipDefaultSpawn(knipBin), "knip default scan", ); - const agentWorkspaces = agentWorkspaceIds(rules); + const agentWorkspaces = agentWorkspaceIds(join(repoRoot, "agents")); const agentFindings = spawnAndParse( knipAgentProductionSpawn(knipBin, agentWorkspaces), "knip production agent scan", @@ -561,7 +569,7 @@ function main(): void { ); } const rules = parseAllowlistText(allowlistText); - const findings = runKnipScans(rules); + const findings = runKnipScans(); const outcome = evaluateGuard(rules, findings); let scannedFiles: number; const injectedCount = parseInjectedScannedFiles(process.env[scannedFilesEnv]);