From d621a78a27c9903149bccb150ba09dddc26d7952 Mon Sep 17 00:00:00 2001 From: Gravirei Date: Fri, 4 Sep 2026 22:04:10 +0600 Subject: [PATCH 01/15] chore(types): add any-usage baseline + lint budget gate (Phase 0) Phase 0 of a multi-phase plan to reduce explicit `any` usage safely. No source files modified in this PR. - Add eslint@9 + @typescript-eslint deps (lint only, no fix rules). - New minimal flat eslint.config.js enabling only `@typescript-eslint/no-explicit-any` as warn. Stub plugins registered for legacy `custom-rules`, `eslint-plugin-n`, and `react-hooks` references in existing `// eslint-disable` comments so the comments stay valid (no rule name resolution errors). - New `scripts/any-usage-report.ts`: walks src/, tests/, scripts/ and buckets `any` usage into `: any` annotations, `as any` casts, `[key: ...]: any` index signatures, `// @ts-ignore` directives, and generic `any` args. Emits `reports/any-usage.{json,md}`. `--check` mode exits non-zero if the count grows vs the baseline. - Lock the baseline at 1,031 occurrences across 198 files. - Add `lint`, `lint:any-budget`, `check:strict` scripts. - Plan: docs/plans/imperative-hugging-sphinx.md Verification: - `bun run typecheck` clean - `bun run lint` exit 0 (845 warnings, 0 errors) - `bun run lint:any-budget` ok (current=1031 baseline=1031) --- bun.lock | 185 ++++++++++++++++++++++++++- eslint.config.js | 121 ++++++++++++++++++ package.json | 14 +- reports/any-usage.baseline.json | 4 + scripts/any-usage-report.ts | 218 ++++++++++++++++++++++++++++++++ 5 files changed, 534 insertions(+), 8 deletions(-) create mode 100644 eslint.config.js create mode 100644 reports/any-usage.baseline.json create mode 100644 scripts/any-usage-report.ts diff --git a/bun.lock b/bun.lock index d43d6c181a..e3166dc5c0 100644 --- a/bun.lock +++ b/bun.lock @@ -29,6 +29,8 @@ "@types/bun": "1.3.11", "@types/node": "25.5.0", "@types/react": "19.2.14", + "@typescript-eslint/eslint-plugin": "^8.69.0", + "@typescript-eslint/parser": "^8.69.0", "ajv": "8.18.0", "auto-bind": "5.0.1", "axios": "1.16.0", @@ -43,6 +45,7 @@ "duck-duck-scrape": "^2.2.7", "emoji-regex": "10.6.0", "env-paths": "3.0.0", + "eslint": "9", "execa": "9.6.1", "fflate": "0.8.2", "figures": "6.1.0", @@ -234,12 +237,40 @@ "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.10.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], + + "@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="], + + "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], + + "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], + + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.7", "", { "dependencies": { "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.3.2", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" } }, "sha512-F42g89Qd5oAWtp0k0nnSrjziAKza7w8SVT4mStc18LZMaRb4J1HQAHLCalEtDCxrTuksx7NU9qsmeLwpOfPqWw=="], + + "@eslint/js": ["@eslint/js@9.39.5", "", {}, "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A=="], + + "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], + + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], + "@grpc/grpc-js": ["@grpc/grpc-js@1.14.3", "", { "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA=="], "@grpc/proto-loader": ["@grpc/proto-loader@0.8.1", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.5.5", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg=="], "@hono/node-server": ["@hono/node-server@1.19.14", "", { "peerDependencies": { "hono": "^4" } }, "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw=="], + "@humanfs/core": ["@humanfs/core@0.19.2", "", { "dependencies": { "@humanfs/types": "^0.15.0" } }, "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA=="], + + "@humanfs/node": ["@humanfs/node@0.16.8", "", { "dependencies": { "@humanfs/core": "^0.19.2", "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ=="], + + "@humanfs/types": ["@humanfs/types@0.15.0", "", {}, "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="], @@ -506,10 +537,32 @@ "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + "@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.69.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.69.0", "@typescript-eslint/type-utils": "8.69.0", "@typescript-eslint/utils": "8.69.0", "@typescript-eslint/visitor-keys": "8.69.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.69.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-t5jQTKPIgVW1PE6dR6H6Qz5gm8zjMlX5/2gRaOGd9eO6V7J+tQc6iWKukEe7dY8u9HyYasQ0yfF0/FSSTEO2gA=="], + + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.69.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.69.0", "@typescript-eslint/types": "8.69.0", "@typescript-eslint/typescript-estree": "8.69.0", "@typescript-eslint/visitor-keys": "8.69.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-l4b0DhWioGg6Gt2ebGlvfkFMOjRsauxtsnDRwUSRX1qHq3HdTfQHV8wW9zEXeciai6HfeaKOedQn2Zoofx3WBw=="], + + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.69.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.69.0", "@typescript-eslint/types": "^8.69.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-yi4obFrHMmnsesWehHbkg9zMA7Jt8cXT+mKM08G999pH1yT6nqgsHx7MYm0uY1wAj8CqiBXYRJ7WAT0QdQHQXg=="], + + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.69.0", "", { "dependencies": { "@typescript-eslint/types": "8.69.0", "@typescript-eslint/visitor-keys": "8.69.0" } }, "sha512-ewfspqWvSxKSOaplqAUNbaSFO0eB6w1EtQ+esfYFRm3614Ty4uNtExkcbgd6nWsXphbqKyf9ZYdbZdv2xEoWEQ=="], + + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.69.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-xNqK7YTDZsLniQMV/4rpFR8Z5JlqeRvVjuG1YgF/mdPVH84HSD19L8CczMA0qg2RfwEV231GHH3VnToJDo4MfQ=="], + + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.69.0", "", { "dependencies": { "@typescript-eslint/types": "8.69.0", "@typescript-eslint/typescript-estree": "8.69.0", "@typescript-eslint/utils": "8.69.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-ZfoJAVg3JZndQEpEl9petVlxau3lRuElc4HRMuAlLCf8to04/iHz692RUSNmXKDjEuJmIL+KZ2/BsOcBc16dsA=="], + + "@typescript-eslint/types": ["@typescript-eslint/types@8.69.0", "", {}, "sha512-K3VrubUPhlo9VDBS6QdI8YB5j7ClpqLRdefcz6PFrhnwicehBweqQ9Evhl4l+FYz0HdDmMqIiSX0aldGRYtDCA=="], + + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.69.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.69.0", "@typescript-eslint/tsconfig-utils": "8.69.0", "@typescript-eslint/types": "8.69.0", "@typescript-eslint/visitor-keys": "8.69.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-AdFkgqck3Vudb/kWnxlyafU/4aBhHrbQ9locP2N4psXTy5mOBg0SHJumnLvx7r6g1gV4DKvUFwV2nJZBoqOD8w=="], + + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.69.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.69.0", "@typescript-eslint/types": "8.69.0", "@typescript-eslint/typescript-estree": "8.69.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-tUbx60BBqQa31kXF5MCsOOLL5E/WzUuxIn7YpAvq+eaUlqvk8/NXnXMBNAdLCr0icjkzem7iUA5QqWHe/hJ1aw=="], + + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.69.0", "", { "dependencies": { "@typescript-eslint/types": "8.69.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-+rmdgPA+EXkNgKYvHvFfhrs35utXbwaC5PGpDquSXcoXQDKUA5UjV0LmTucG/4JXkM31BTu4TilHtrN8IVBe8w=="], + "@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.6", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-jIXhD0eWQ1JA6ln/5Dltyx22UxWNrw0hZmhy2rlv6m6KgF7kplHx3g0fzi09lNmTJQRR91OlemYp3xFnvDK9og=="], "@vscode/ripgrep": ["@vscode/ripgrep@1.18.0", "", { "optionalDependencies": { "@vscode/ripgrep-darwin-arm64": "1.18.0", "@vscode/ripgrep-darwin-x64": "1.18.0", "@vscode/ripgrep-linux-arm": "1.18.0", "@vscode/ripgrep-linux-arm64": "1.18.0", "@vscode/ripgrep-linux-ia32": "1.18.0", "@vscode/ripgrep-linux-ppc64": "1.18.0", "@vscode/ripgrep-linux-riscv64": "1.18.0", "@vscode/ripgrep-linux-s390x": "1.18.0", "@vscode/ripgrep-linux-x64": "1.18.0", "@vscode/ripgrep-win32-arm64": "1.18.0", "@vscode/ripgrep-win32-ia32": "1.18.0", "@vscode/ripgrep-win32-x64": "1.18.0" } }, "sha512-ns5lWe44tSfbTMbVUsyB+I1819PVSw4AdpgK0RNkzfWfwy6+3IUNSxwSrfTno1/oWaS/hERNz+XLWVyga2aJBQ=="], @@ -542,6 +595,10 @@ "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], + "acorn": ["acorn@8.18.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], "ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="], @@ -554,6 +611,8 @@ "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + "astring": ["astring@1.9.0", "", { "bin": { "astring": "bin/astring" } }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="], "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], @@ -562,6 +621,8 @@ "axios": ["axios@1.16.0", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "proxy-from-env": "^2.1.0" } }, "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w=="], + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], "bidi-js": ["bidi-js@1.0.3", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="], @@ -572,6 +633,8 @@ "bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="], + "brace-expansion": ["brace-expansion@1.1.18", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw=="], + "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], @@ -584,6 +647,8 @@ "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=="], + "camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], @@ -606,6 +671,8 @@ "commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="], + "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=="], @@ -628,6 +695,8 @@ "decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="], + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + "default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="], "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="], @@ -674,10 +743,24 @@ "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "eslint": ["eslint@9.39.5", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.6", "@eslint/js": "9.39.5", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw=="], + + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], + + "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], + "esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="], + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], @@ -696,6 +779,10 @@ "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + "fast-uri": ["fast-uri@3.1.2", "", {}, "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ=="], "fast-xml-builder": ["fast-xml-builder@1.2.0", "", { "dependencies": { "path-expression-matcher": "^1.5.0", "xml-naming": "^0.1.0" } }, "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q=="], @@ -712,9 +799,15 @@ "figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="], + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + "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=="], - "find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], + + "flatted": ["flatted@3.4.4", "", {}, "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q=="], "follow-redirects": ["follow-redirects@1.16.0", "", {}, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="], @@ -748,6 +841,10 @@ "get-tsconfig": ["get-tsconfig@4.14.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA=="], + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], + "google-auth-library": ["google-auth-library@10.6.2", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.1.4", "gcp-metadata": "8.1.2", "google-logging-utils": "1.1.3", "jws": "^4.0.0" } }, "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw=="], "google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="], @@ -794,8 +891,12 @@ "ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + "import-in-the-middle": ["import-in-the-middle@3.3.3", "", { "dependencies": { "cjs-module-lexer": "^2.2.0", "es-module-lexer": "^2.2.0", "module-details-from-path": "^1.0.4" } }, "sha512-AiohS3H80sXO6owEltjGX+glb7qXaDhBoJb9XcQVH4UI207xu/bDLUcadVKp7Qe576reg9yr/PXZjV5qx8gfbA=="], + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + "indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="], "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], @@ -806,8 +907,12 @@ "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + "is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], @@ -828,14 +933,20 @@ "js-tiktoken": ["js-tiktoken@1.0.21", "", { "dependencies": { "base64-js": "^1.5.1" } }, "sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g=="], + "js-yaml": ["js-yaml@4.3.2", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA=="], + "json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="], + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + "json-schema-to-ts": ["json-schema-to-ts@3.1.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="], "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=="], + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + "jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], "jsonwebtoken": ["jsonwebtoken@9.0.3", "", { "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g=="], @@ -844,9 +955,13 @@ "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="], + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + "knip": ["knip@6.16.1", "", { "dependencies": { "fdir": "^6.5.0", "formatly": "^0.3.0", "get-tsconfig": "4.14.0", "jiti": "^2.7.0", "oxc-parser": "^0.133.0", "oxc-resolver": "^11.20.0", "picomatch": "^4.0.4", "smol-toml": "^1.6.1", "strip-json-comments": "5.0.3", "tinyglobby": "^0.2.16", "unbash": "^3.0.0", "yaml": "^2.9.0", "zod": "^4.1.11" }, "bin": { "knip": "bin/knip.js", "knip-bun": "bin/knip-bun.js" } }, "sha512-TKMn1rxgH6h9vXR9Y0B+Cq7AdPTr9EI02IwoT65NzqYUkvoDQAaJ/aPybiFpAhZ1px6cNYYwXf86iHkBgzCo9w=="], - "locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], "lodash-es": ["lodash-es@4.18.1", "", {}, "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A=="], @@ -866,6 +981,8 @@ "lodash.isstring": ["lodash.isstring@4.0.1", "", {}, "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="], + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + "lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="], "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], @@ -888,6 +1005,8 @@ "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], + "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + "mnemonist": ["mnemonist@0.39.8", "", { "dependencies": { "obliterator": "^2.0.1" } }, "sha512-vyWo2K3fjrUw8YeeZ1zF0fy6Mu59RHokURlld8ymdUPjMlD9EC9ov1/YPqTgqRvUN9nTr3Gqfz29LYAmu0PHPQ=="], "module-details-from-path": ["module-details-from-path@1.0.4", "", {}, "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w=="], @@ -896,6 +1015,8 @@ "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + "needle": ["needle@3.5.0", "", { "dependencies": { "iconv-lite": "^0.6.3", "sax": "^1.2.4" }, "bin": { "needle": "bin/needle" } }, "sha512-jaQyPKKk2YokHrEg+vFDYxXIHTCBgiZwSHOoVx/8V3GIBS8/VN6NdVRmg8q1ERtPkMvmOvebsgga4sAj5hls/w=="], "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], @@ -918,13 +1039,15 @@ "open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + "oxc-parser": ["oxc-parser@0.133.0", "", { "dependencies": { "@oxc-project/types": "^0.133.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.133.0", "@oxc-parser/binding-android-arm64": "0.133.0", "@oxc-parser/binding-darwin-arm64": "0.133.0", "@oxc-parser/binding-darwin-x64": "0.133.0", "@oxc-parser/binding-freebsd-x64": "0.133.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.133.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.133.0", "@oxc-parser/binding-linux-arm64-gnu": "0.133.0", "@oxc-parser/binding-linux-arm64-musl": "0.133.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.133.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.133.0", "@oxc-parser/binding-linux-riscv64-musl": "0.133.0", "@oxc-parser/binding-linux-s390x-gnu": "0.133.0", "@oxc-parser/binding-linux-x64-gnu": "0.133.0", "@oxc-parser/binding-linux-x64-musl": "0.133.0", "@oxc-parser/binding-openharmony-arm64": "0.133.0", "@oxc-parser/binding-wasm32-wasi": "0.133.0", "@oxc-parser/binding-win32-arm64-msvc": "0.133.0", "@oxc-parser/binding-win32-ia32-msvc": "0.133.0", "@oxc-parser/binding-win32-x64-msvc": "0.133.0" } }, "sha512-661RSx+ZcjBmjBYid+Fpp/2F5EbtildpeoZh5HdgnGs+jZ03nqQEQW8yGkt4BGyOC3OMPDQQRl8M5kqD2/g6jw=="], "oxc-resolver": ["oxc-resolver@11.20.0", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.20.0", "@oxc-resolver/binding-android-arm64": "11.20.0", "@oxc-resolver/binding-darwin-arm64": "11.20.0", "@oxc-resolver/binding-darwin-x64": "11.20.0", "@oxc-resolver/binding-freebsd-x64": "11.20.0", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.20.0", "@oxc-resolver/binding-linux-arm-musleabihf": "11.20.0", "@oxc-resolver/binding-linux-arm64-gnu": "11.20.0", "@oxc-resolver/binding-linux-arm64-musl": "11.20.0", "@oxc-resolver/binding-linux-ppc64-gnu": "11.20.0", "@oxc-resolver/binding-linux-riscv64-gnu": "11.20.0", "@oxc-resolver/binding-linux-riscv64-musl": "11.20.0", "@oxc-resolver/binding-linux-s390x-gnu": "11.20.0", "@oxc-resolver/binding-linux-x64-gnu": "11.20.0", "@oxc-resolver/binding-linux-x64-musl": "11.20.0", "@oxc-resolver/binding-openharmony-arm64": "11.20.0", "@oxc-resolver/binding-wasm32-wasi": "11.20.0", "@oxc-resolver/binding-win32-arm64-msvc": "11.20.0", "@oxc-resolver/binding-win32-x64-msvc": "11.20.0" } }, "sha512-CblytBiV/a/ZXY34dsVU2NxhIOxMXst8CvDCtyBelVITgd7PLrKzbEbA6oKLdPjvDKDzCiW48qzmzZ+mYaqn+g=="], - "p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], - "p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], "p-map": ["p-map@7.0.4", "", {}, "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ=="], @@ -932,6 +1055,8 @@ "pandemonium": ["pandemonium@2.4.1", "", { "dependencies": { "mnemonist": "^0.39.2" } }, "sha512-wRqjisUyiUfXowgm7MFH2rwJzKIr20rca5FsHXCMNm1W5YPP1hCtrZfgmQ62kP7OZ7Xt+cR858aB28lu5NX55g=="], + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + "parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="], "parse5": ["parse5@5.1.1", "", {}, "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug=="], @@ -954,6 +1079,8 @@ "pngjs": ["pngjs@5.0.0", "", {}, "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="], + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + "pretty-ms": ["pretty-ms@9.3.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ=="], "proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="], @@ -964,6 +1091,8 @@ "proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="], + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + "qrcode": ["qrcode@1.5.4", "", { "dependencies": { "dijkstrajs": "^1.0.1", "pngjs": "^5.0.0", "yargs": "^15.3.1" }, "bin": { "qrcode": "bin/qrcode" } }, "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg=="], "qs": ["qs@6.15.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg=="], @@ -988,6 +1117,8 @@ "require-main-filename": ["require-main-filename@2.0.0", "", {}, "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="], + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], @@ -1070,10 +1201,14 @@ "ts-algebra": ["ts-algebra@2.0.0", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="], + "ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="], + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "turndown": ["turndown@7.2.2", "", { "dependencies": { "@mixmark-io/domino": "^2.2.0" } }, "sha512-1F7db8BiExOKxjSMU2b7if62D/XOyQyZbPKq/nUwopfgnHlqXHqQ0lvfUTeUIr1lZJzOPFn43dODyMSIfvWRKQ=="], + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], "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=="], @@ -1090,6 +1225,8 @@ "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + "usehooks-ts": ["usehooks-ts@3.1.1", "", { "dependencies": { "lodash.debounce": "^4.0.8" }, "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-I4diPp9Cq6ieSUH2wu+fDAVQO43xwtulo+fKEidHUwZPnYImbtkTjzIJYcDcJqxgmX31GVqNFURodvcgHcW0pA=="], "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], @@ -1110,6 +1247,8 @@ "which-module": ["which-module@2.0.1", "", {}, "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="], + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], @@ -1130,6 +1269,8 @@ "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + "yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="], "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], @@ -1228,6 +1369,14 @@ "@aws-sdk/xml-builder/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "@eslint/eslintrc/ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], + + "@eslint/eslintrc/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "@eslint/eslintrc/strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + "@smithy/core/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], "@smithy/credential-provider-imds/@smithy/types": ["@smithy/types@4.14.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-P+otAxbV4CqBybp7EkcJCrig63yE2E7PuNVOmilVMRcx/O+QDzGULTrKsq4DV13gSfak9ObPrWaHl/9bL5YcWw=="], @@ -1262,6 +1411,10 @@ "@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@3.0.0", "", { "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" } }, "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA=="], + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="], + + "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + "cli-highlight/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "cli-highlight/yargs": ["yargs@16.2.0", "", { "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw=="], @@ -1272,6 +1425,12 @@ "cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "eslint/ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], + + "eslint/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + "fetch-blob/node-domexception": ["node-domexception@file:vendor/node-domexception-shim", {}], "form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], @@ -1308,10 +1467,14 @@ "@aws-sdk/nested-clients/@aws-crypto/sha256-js/@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], + "@eslint/eslintrc/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + "@smithy/eventstream-codec/@aws-crypto/crc32/@aws-crypto/util": ["@aws-crypto/util@3.0.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-utf8-browser": "^3.0.0", "tslib": "^1.11.1" } }, "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w=="], "@smithy/eventstream-codec/@aws-crypto/crc32/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.9", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg=="], + "cli-highlight/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "cli-highlight/yargs/cliui": ["cliui@7.0.4", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ=="], @@ -1328,10 +1491,16 @@ "cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "eslint/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "eslint/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], "qrcode/yargs/cliui": ["cliui@6.0.0", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="], + "qrcode/yargs/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], + "qrcode/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "qrcode/yargs/y18n": ["y18n@4.0.3", "", {}, "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="], @@ -1354,6 +1523,8 @@ "@aws-sdk/nested-clients/@aws-crypto/sha256-js/@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + "@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + "cli-highlight/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "cli-highlight/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], @@ -1368,6 +1539,8 @@ "qrcode/yargs/cliui/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], + "qrcode/yargs/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], + "qrcode/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "qrcode/yargs/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], @@ -1386,6 +1559,10 @@ "qrcode/yargs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "qrcode/yargs/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], + "qrcode/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "qrcode/yargs/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], } } diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000000..fcb2ef86ee --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,121 @@ +// Minimal flat config — only the rule we need to track `any` usage. +// Step 1 of a multi-phase plan to reduce `any` types safely. +// See plan: docs/plans/imperative-hugging-sphinx.md +// +// Note: the repo has many existing `// eslint-disable custom-rules/` +// comments referencing rules from a previous (now-removed) custom plugin. +// We register a stub `custom-rules` plugin whose rules are all "off"; this +// makes the comments valid so eslint doesn't error on unknown rule names. +import tseslint from "@typescript-eslint/eslint-plugin"; +import tsparser from "@typescript-eslint/parser"; + +const DEPRECATED_CUSTOM_RULE_NAMES = [ + "bootstrap-isolation", + "no-cross-platform-process-issues", + "no-direct-json-operations", + "no-direct-ps-commands", + "no-lookbehind-regex", + "no-process-cwd", + "no-process-env-top-level", + "no-process-exit", + "no-sync-fs", + "no-top-level-dynamic-import", + "no-top-level-side-effects", + "prefer-use-keybindings", + "prefer-use-terminal-size", + "prompt-spacing", + "require-bun-typeof-guard", + "require-tool-match-name", + "safe-env-boolean-check", +]; + +// Stub plugin: each rule is a no-op listener so eslint resolves the name +// but does nothing. +const stubRules = Object.fromEntries( + DEPRECATED_CUSTOM_RULE_NAMES.map((name) => [ + name, + { + meta: { schema: [], deprecated: true }, + create() { + return {}; + }, + }, + ]), +); + +/** @type {import("eslint").ESLint.Plugin} */ +const customRulesPlugin = { + rules: stubRules, +}; + +// Stub plugin for legacy `eslint-plugin-n/*` references in source comments. +const eslintPluginNPlugin = { + rules: { + "no-unsupported-features/node-builtins": { + meta: { schema: [], deprecated: true }, + create() { + return {}; + }, + }, + }, +}; + +// Stub plugin for legacy `react-hooks/*` references in source comments. +const reactHooksPlugin = { + rules: { + "exhaustive-deps": { + meta: { schema: [], deprecated: true }, + create() { + return {}; + }, + }, + "rules-of-hooks": { + meta: { schema: [], deprecated: true }, + create() { + return {}; + }, + }, + }, +}; + +export default [ + { + ignores: [ + "dist/**", + "node_modules/**", + "vendor/**", + "web/**", + "vscode-extension/**", + "coverage/**", + "reports/**", + ], + }, + { + files: ["src/**/*.{ts,tsx}", "tests/**/*.{ts,tsx}", "scripts/**/*.ts"], + languageOptions: { + parser: tsparser, + parserOptions: { + ecmaVersion: 2023, + sourceType: "module", + ecmaFeatures: { jsx: true }, + }, + }, + plugins: { + "@typescript-eslint": tseslint, + "custom-rules": customRulesPlugin, + "eslint-plugin-n": eslintPluginNPlugin, + "react-hooks": reactHooksPlugin, + }, + linterOptions: { + // Source has many `// eslint-disable` comments that may now refer to + // rules not enabled here; don't fail on unused directives. + reportUnusedDisableDirectives: "off", + }, + rules: { + // Flags both `: any` annotations and `as any` casts. + // warn (not error) so existing code keeps building while we + // surface counts and reduce incrementally. + "@typescript-eslint/no-explicit-any": "warn", + }, + }, +]; \ No newline at end of file diff --git a/package.json b/package.json index 03229c8915..f5cabcc252 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,10 @@ "typecheck:type-tests": "bun run scripts/typecheck-type-tests.ts", "smoke": "bun run build && node dist/cli.mjs --version", "deadcode": "knip --include files,dependencies", + "lint": "eslint .", + "lint:any-budget": "bun run scripts/any-usage-report.ts --check", "check": "bun run smoke && bun run deadcode && bun run test:full", + "check:strict": "bun run typecheck && bun run lint && bun run lint:any-budget && bun run smoke && bun run deadcode && bun run test:full", "verify:privacy": "bun run scripts/verify-no-phone-home.ts", "install:verify": "bun run scripts/verify-clean-install.ts", "install:verify:published": "bun run scripts/verify-clean-install.ts --published", @@ -86,7 +89,6 @@ "devDependencies": { "@alcalzone/ansi-tokenize": "0.3.0", "@anthropic-ai/bedrock-sdk": "0.29.1", - "@sentry/node": "^10.70.0", "@anthropic-ai/foundry-sdk": "0.2.3", "@anthropic-ai/sandbox-runtime": "0.0.55", "@anthropic-ai/sdk": "0.94.0", @@ -98,11 +100,14 @@ "@grpc/grpc-js": "^1.14.3", "@grpc/proto-loader": "^0.8.0", "@modelcontextprotocol/sdk": "1.29.0", + "@sentry/node": "^10.70.0", "@smithy/core": "3.24.3", "@smithy/node-http-handler": "4.7.3", "@types/bun": "1.3.11", "@types/node": "25.5.0", "@types/react": "19.2.14", + "@typescript-eslint/eslint-plugin": "^8.69.0", + "@typescript-eslint/parser": "^8.69.0", "ajv": "8.18.0", "auto-bind": "5.0.1", "axios": "1.16.0", @@ -117,16 +122,17 @@ "duck-duck-scrape": "^2.2.7", "emoji-regex": "10.6.0", "env-paths": "3.0.0", + "eslint": "9", "execa": "9.6.1", "fflate": "0.8.2", "figures": "6.1.0", "fuse.js": "7.1.0", - "graphology": "0.26.0", "get-east-asian-width": "1.5.0", "google-auth-library": "10.6.2", + "graphology": "0.26.0", + "graphology-metrics": "2.4.0", "https-proxy-agent": "7.0.6", "ignore": "7.0.5", - "graphology-metrics": "2.4.0", "indent-string": "5.0.0", "js-tiktoken": "1.0.21", "jsonc-parser": "3.3.1", @@ -153,8 +159,8 @@ "typescript": "5.9.3", "undici": "7.28.0", "usehooks-ts": "3.1.1", - "web-tree-sitter": "0.25.10", "vscode-languageserver-protocol": "3.17.5", + "web-tree-sitter": "0.25.10", "wrap-ansi": "9.0.2", "ws": "8.21.0", "xss": "1.0.15", diff --git a/reports/any-usage.baseline.json b/reports/any-usage.baseline.json new file mode 100644 index 0000000000..ddb6bb23bb --- /dev/null +++ b/reports/any-usage.baseline.json @@ -0,0 +1,4 @@ +{ + "total": 1031, + "generatedAt": "2026-09-04T15:30:30.577Z" +} diff --git a/scripts/any-usage-report.ts b/scripts/any-usage-report.ts new file mode 100644 index 0000000000..90dd6d89b2 --- /dev/null +++ b/scripts/any-usage-report.ts @@ -0,0 +1,218 @@ +// Walk src/, tests/, scripts/ and report `any` usage by category. +// Produces reports/any-usage.{json,md} and a budget gate against +// reports/any-usage.baseline.json. +// +// Step 2 of a multi-phase plan to reduce `any` types safely. +// See plan: docs/plans/imperative-hugging-sphinx.md + +import { mkdir, readFile, writeFile } from 'fs/promises' +import { resolve } from 'path' + +const ROOT = process.cwd() +const REPORT_DIR = resolve(ROOT, 'reports') +const JSON_OUT = resolve(REPORT_DIR, 'any-usage.json') +const MD_OUT = resolve(REPORT_DIR, 'any-usage.md') +const BASELINE_PATH = resolve(REPORT_DIR, 'any-usage.baseline.json') + +const SCAN_DIRS = ['src', 'tests', 'scripts'] +const EXCLUDE_DIR_NAMES = new Set([ + 'node_modules', + 'dist', + 'vendor', + 'web', + 'vscode-extension', + 'coverage', + 'reports', +]) + +type CategoryCounts = { + annotation: number + cast: number + indexSig: number + ignore: number + genericArg: number +} + +type FileReport = { + path: string + total: number + byCategory: CategoryCounts +} + +type Report = { + generatedAt: string + total: number + byCategory: CategoryCounts + files: FileReport[] +} + +// Regexes (each match counted; multi-line `m`, no `g` so we don't loop with /lastIndex). +// Match `any` as a complete identifier (avoid `many`, `company`, etc.). +const ANNOTATION_RE = /(?]*\bany\b[^>]*>/m + +function emptyCategory(): CategoryCounts { + return { annotation: 0, cast: 0, indexSig: 0, ignore: 0, genericArg: 0 } +} + +async function walk(dir: string, out: string[]): Promise { + const { readdir, stat } = await import('fs/promises') + let entries: string[] + try { + entries = await readdir(dir) + } catch { + return + } + for (const name of entries) { + if (EXCLUDE_DIR_NAMES.has(name)) continue + const full = resolve(dir, name) + const st = await stat(full) + if (st.isDirectory()) { + await walk(full, out) + } else if (/\.tsx?$/.test(name)) { + out.push(full) + } + } +} + +function countMatches(source: string, re: RegExp): number { + // Rebuild with `g` for counting while preserving the anchored pattern. + const flags = re.flags.replace('g', '') + 'g' + const global = new RegExp(re.source, flags) + let n = 0 + while (global.exec(source) !== null) { + n++ + if (global.lastIndex === 0) break + } + return n +} + +function categorize(source: string): CategoryCounts { + const counts = emptyCategory() + counts.annotation = countMatches(source, ANNOTATION_NEAR_RE) + counts.cast = countMatches(source, CAST_RE) + counts.indexSig = countMatches(source, INDEX_SIG_RE) + counts.ignore = countMatches(source, IGNORE_RE) + counts.genericArg = countMatches(source, GENERIC_RE) + return counts +} + +function totalOf(c: CategoryCounts): number { + return c.annotation + c.cast + c.indexSig + c.ignore + c.genericArg +} + +async function scan(): Promise { + const files: FileReport[] = [] + const totals = emptyCategory() + for (const dir of SCAN_DIRS) { + const root = resolve(ROOT, dir) + const found: string[] = [] + await walk(root, found) + for (const absPath of found) { + const source = await readFile(absPath, 'utf8') + if (!ANNOTATION_RE.test(source) && !CAST_RE.test(source) && !IGNORE_RE.test(source)) { + continue + } + const byCategory = categorize(source) + const total = totalOf(byCategory) + if (total === 0) continue + files.push({ + path: absPath.slice(ROOT.length + 1), + total, + byCategory, + }) + for (const k of Object.keys(totals) as (keyof CategoryCounts)[]) { + totals[k] += byCategory[k] + } + } + } + files.sort((a, b) => b.total - a.total) + return { + generatedAt: new Date().toISOString(), + total: totalOf(totals), + byCategory: totals, + files, + } +} + +function renderMarkdown(report: Report, baselineTotal: number | null): string { + const lines: string[] = [] + lines.push('# `any` Usage Report') + lines.push('') + lines.push(`Generated: ${report.generatedAt}`) + lines.push('') + lines.push('## Totals') + lines.push('') + lines.push(`| Category | Count |`) + lines.push(`| --- | ---: |`) + lines.push(`| \`: any\` annotation | ${report.byCategory.annotation} |`) + lines.push(`| \`as any\` cast | ${report.byCategory.cast} |`) + lines.push(`| \`[key: ...]: any\` index signature | ${report.byCategory.indexSig} |`) + lines.push(`| \`// @ts-ignore\` (and friends) | ${report.byCategory.ignore} |`) + lines.push(`| Generic \`any\` arg | ${report.byCategory.genericArg} |`) + lines.push(`| **Total** | **${report.total}** |`) + if (baselineTotal !== null) { + const delta = report.total - baselineTotal + const sign = delta > 0 ? '+' : '' + lines.push('') + lines.push(`Baseline: ${baselineTotal} Delta: ${sign}${delta}`) + } + lines.push('') + lines.push(`## Top offenders (${Math.min(20, report.files.length)} of ${report.files.length})`) + lines.push('') + lines.push('| File | Total | Annot | Cast | IndexSig | Ignore | Generic |') + lines.push('| --- | ---: | ---: | ---: | ---: | ---: | ---: |') + for (const f of report.files.slice(0, 20)) { + lines.push( + `| ${f.path} | ${f.total} | ${f.byCategory.annotation} | ${f.byCategory.cast} | ${f.byCategory.indexSig} | ${f.byCategory.ignore} | ${f.byCategory.genericArg} |`, + ) + } + lines.push('') + return lines.join('\n') +} + +async function readBaselineTotal(): Promise { + try { + const raw = await readFile(BASELINE_PATH, 'utf8') + const parsed = JSON.parse(raw) as Report + return typeof parsed.total === 'number' ? parsed.total : null + } catch { + return null + } +} + +async function main(): Promise { + const checkMode = process.argv.includes('--check') + await mkdir(REPORT_DIR, { recursive: true }) + + const report = await scan() + const baselineTotal = await readBaselineTotal() + + await writeFile(JSON_OUT, JSON.stringify(report, null, 2) + '\n') + await writeFile(MD_OUT, renderMarkdown(report, baselineTotal)) + + // Always refresh baseline file unless --check (we don't want CI to clobber it). + if (!checkMode) { + await writeFile(BASELINE_PATH, JSON.stringify({ total: report.total, generatedAt: report.generatedAt }, null, 2) + '\n') + } + + if (checkMode) { + if (baselineTotal !== null && report.total > baselineTotal) { + console.error( + `any-usage budget exceeded: current=${report.total} > baseline=${baselineTotal} (delta=+${report.total - baselineTotal})`, + ) + process.exit(1) + } + console.log(`any-usage budget ok: current=${report.total} baseline=${baselineTotal ?? 'unset'}`) + return + } + + console.log(`any-usage report written: total=${report.total} files=${report.files.length}`) +} + +await main() \ No newline at end of file From 2be7ad3e6ce19ef03517641702f2f25ffce76e0e Mon Sep 17 00:00:00 2001 From: Gravirei Date: Fri, 4 Sep 2026 23:06:43 +0600 Subject: [PATCH 02/15] chore(types): narrow any in 6 small src files (Phase 1, batch 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 1 of the plan to reduce `any` usage. Targets only files with 1–2 lint hits where the concrete type is reachable in scope. - src/services/api/withRetry.ts: isQuotaExhausted signature `any` → `unknown`, add local structural narrowing for `.message`/`.status` access. - src/utils/validation.ts: assertFunction predicate signature `any[]`/`any` → `unknown[]`/`unknown`. - src/utils/conversationArc.ts: extractTextFromContent — replace `(block: any)` in filter/map with explicit type predicate narrowing. - src/utils/optionalRuntimeModule.ts: dynamic-import wrapper returns `Promise` instead of `Promise`. - src/entrypoints/mcp.ts: tighten block mapping with the existing structural type already in scope; replace `(finalResult as any).isError` with a narrow cast. - src/components/EffortPicker.tsx: drop redundant `: any` on useAppState selector — the function's generic infers `AppState` for free. Verification: - bun run typecheck clean - bun run lint: 835 warnings (was 845) - bun run lint:any-budget: 1022 / 1031 baseline (delta -9) - bun test src/utils/optionalRuntimeModule.test.ts + conversationArc.test.ts: 36 pass / 0 fail - bun test src/services/api/withRetry.test.ts: 44 pass / 0 fail --- reports/any-usage.baseline.json | 2 +- src/components/EffortPicker.tsx | 2 +- src/entrypoints/mcp.ts | 4 ++-- src/services/api/withRetry.ts | 12 +++++++----- src/utils/conversationArc.ts | 9 +++++++-- src/utils/optionalRuntimeModule.ts | 4 ++-- src/utils/validation.ts | 2 +- 7 files changed, 21 insertions(+), 14 deletions(-) diff --git a/reports/any-usage.baseline.json b/reports/any-usage.baseline.json index ddb6bb23bb..23b12f5421 100644 --- a/reports/any-usage.baseline.json +++ b/reports/any-usage.baseline.json @@ -1,4 +1,4 @@ { "total": 1031, - "generatedAt": "2026-09-04T15:30:30.577Z" + "generatedAt": "2026-09-04T16:06:16.866Z" } diff --git a/src/components/EffortPicker.tsx b/src/components/EffortPicker.tsx index ce40c6922b..8cc99d8c41 100644 --- a/src/components/EffortPicker.tsx +++ b/src/components/EffortPicker.tsx @@ -34,7 +34,7 @@ type Props = { export function EffortPicker({ onSelect, onCancel }: Props) { const model = useMainLoopModel() - const appStateEffort = useAppState((s: any) => s.effortValue) + const appStateEffort = useAppState((s) => s.effortValue) const setAppState = useSetAppState() const provider = getAPIProvider() const usesOpenAIEffort = modelUsesOpenAIEffort(model) diff --git a/src/entrypoints/mcp.ts b/src/entrypoints/mcp.ts index f2ac485f06..7b3e8bd348 100644 --- a/src/entrypoints/mcp.ts +++ b/src/entrypoints/mcp.ts @@ -202,7 +202,7 @@ export async function startMCPServer( if (typeof data === 'string') { content = [{ type: 'text', text: data }] } else if (Array.isArray(data)) { - content = data.map((block: any) => { + content = data.map((block: { type: string; text?: string; source?: { type: string; media_type: string; data: string } }) => { if (block.type === 'text') { return { type: 'text', text: block.text || '' } } else if (block.type === 'image' && block.source) { @@ -223,7 +223,7 @@ export async function startMCPServer( return { content, - isError: !!(finalResult as any).isError, + isError: !!(finalResult as { isError?: unknown }).isError, } } catch (error) { logError(error) diff --git a/src/services/api/withRetry.ts b/src/services/api/withRetry.ts index 3cd4e9a0f1..594000bc03 100644 --- a/src/services/api/withRetry.ts +++ b/src/services/api/withRetry.ts @@ -122,19 +122,21 @@ function isPersistentRetryEnabled(): boolean { : false } -function isQuotaExhausted(error: any): boolean { - const msg = (error?.message || '').toLowerCase() +function isQuotaExhausted(error: unknown): boolean { + const errObj = error as { message?: unknown; status?: unknown } | null | undefined + const msg = String(errObj?.message ?? '').toLowerCase() + const status = errObj?.status // OpenRouter (and other gateways) return 402 with instructions to adjust max_tokens. // If the 402 is retryable via token adjustment, do not treat it as exhausted. - if (error?.status === 402 && error instanceof APIError && parseOpenRouterAffordableMaxTokensError(error) !== undefined) { + if (status === 402 && error instanceof APIError && parseOpenRouterAffordableMaxTokensError(error) !== undefined) { return false } return ( - error?.status === 402 || + status === 402 || msg.includes('quota_exhausted') || - ((error?.status === 429 || error?.status === 403 || error?.status === 400) && + ((status === 429 || status === 403 || status === 400) && (msg.includes('limit: 0') || msg.includes('exceeded your current quota') || msg.includes('credit') || diff --git a/src/utils/conversationArc.ts b/src/utils/conversationArc.ts index 05da1aad0f..63dc3f161b 100644 --- a/src/utils/conversationArc.ts +++ b/src/utils/conversationArc.ts @@ -275,8 +275,13 @@ function extractTextFromContent(content: unknown): string { if (typeof content === 'string') return content if (Array.isArray(content)) { return content - .filter((block: any) => block.type === 'text' && typeof block.text === 'string') - .map((block: any) => block.text) + .filter((block: unknown): block is { type: string; text: string } => + typeof block === 'object' && + block !== null && + (block as { type?: unknown }).type === 'text' && + typeof (block as { text?: unknown }).text === 'string', + ) + .map((block) => block.text) .join('\n') } return '' diff --git a/src/utils/optionalRuntimeModule.ts b/src/utils/optionalRuntimeModule.ts index 5b771ef486..cc61378259 100644 --- a/src/utils/optionalRuntimeModule.ts +++ b/src/utils/optionalRuntimeModule.ts @@ -15,7 +15,7 @@ const runtimeImport = new Function( 'specifier', 'return import(specifier)', -) as (specifier: string) => Promise +) as (specifier: string) => Promise export class OptionalRuntimeModuleUnavailableError extends Error { constructor( @@ -37,7 +37,7 @@ export function isOptionalRuntimeModuleUnavailableError( } /** Raw runtime import — rejects with the underlying error if the module is missing. */ -export function importRuntimeModule(specifier: string): Promise { +export function importRuntimeModule(specifier: string): Promise { return runtimeImport(specifier) } diff --git a/src/utils/validation.ts b/src/utils/validation.ts index 784da306f8..f40f8528b5 100644 --- a/src/utils/validation.ts +++ b/src/utils/validation.ts @@ -47,7 +47,7 @@ export function assertObject(value: unknown, field: string): asserts value is Re /** * Assert that a value is a function. */ -export function assertFunction(value: unknown, field: string): asserts value is (...args: any[]) => any { +export function assertFunction(value: unknown, field: string): asserts value is (...args: unknown[]) => unknown { if (typeof value !== 'function') { throw new TypeError(`missing or invalid '${field}' (expected function)`) } From edbe4b7540e355ea8760bb0ab95bae74f9683cc4 Mon Sep 17 00:00:00 2001 From: Gravirei Date: Fri, 4 Sep 2026 23:36:08 +0600 Subject: [PATCH 03/15] chore(types): narrow any in WebSearchTool + adapters (Phase 1, batch 2) Phase 1 continues. These are all boundary code (HTTP responses from external search APIs), so every `any` is at a module boundary per the plan. Each adapter now types its response with `unknown` + structural narrowing instead of `any`. - providers/types.ts: firstMatch/normalizeHit accept `unknown` and narrow to `Record` before index access. - providers/timeout.ts: fetchJsonWithWebSearchTimeout returns Promise. - providers/{bing,brave,jina,linkup,mojeek,tavily,you,exa,custom}.ts: each adapter's response data is cast to a minimal shape (`{ results?: unknown }` etc.), then array elements are narrowed to `Record` with `typeof` checks per field. - WebSearchTool.ts: getCodexSources and extractCodexWebSearchFailure now take `Record`; nested `action`/`error`/`result` are cast locally to `Record` so `?.field` chains type-check. Verification: - bun run typecheck clean - bun run lint: 805 warnings (was 835, -30) - bun run lint:any-budget: 992 / 1022 baseline - bun test src/tools/WebSearchTool: 138 pass / 0 fail across 9 files --- src/tools/WebSearchTool/WebSearchTool.ts | 26 ++--- src/tools/WebSearchTool/providers/bing.ts | 23 ++-- src/tools/WebSearchTool/providers/brave.ts | 24 ++-- src/tools/WebSearchTool/providers/custom.ts | 113 ++++++++++++------- src/tools/WebSearchTool/providers/exa.ts | 38 ++++--- src/tools/WebSearchTool/providers/jina.ts | 31 +++-- src/tools/WebSearchTool/providers/linkup.ts | 29 +++-- src/tools/WebSearchTool/providers/mojeek.ts | 32 ++++-- src/tools/WebSearchTool/providers/tavily.ts | 26 +++-- src/tools/WebSearchTool/providers/timeout.ts | 2 +- src/tools/WebSearchTool/providers/types.ts | 9 +- src/tools/WebSearchTool/providers/you.ts | 36 ++++-- 12 files changed, 255 insertions(+), 134 deletions(-) diff --git a/src/tools/WebSearchTool/WebSearchTool.ts b/src/tools/WebSearchTool/WebSearchTool.ts index a748e345f7..4e68b69543 100644 --- a/src/tools/WebSearchTool/WebSearchTool.ts +++ b/src/tools/WebSearchTool/WebSearchTool.ts @@ -254,20 +254,16 @@ function addCodexSource( }) } -function getCodexSources(item: Record): unknown[] { - if (Array.isArray(item.action?.sources)) { - return item.action.sources - } - if (Array.isArray(item.sources)) { - return item.sources - } - if (Array.isArray(item.result?.sources)) { - return item.result.sources - } +function getCodexSources(item: Record): unknown[] { + const action = (item.action ?? {}) as Record + const result = (item.result ?? {}) as Record + if (Array.isArray(action.sources)) return action.sources + if (Array.isArray(item.sources)) return item.sources as unknown[] + if (Array.isArray(result.sources)) return result.sources return [] } -function extractCodexWebSearchFailure(item: Record): string | undefined { +function extractCodexWebSearchFailure(item: Record): string | undefined { // Codex web_search_call items can carry a status field. When the tool // call fails (rate limit, upstream error, model-side guardrail), the // parser should surface a meaningful error rather than the generic @@ -275,10 +271,12 @@ function extractCodexWebSearchFailure(item: Record): string | undef // { type: 'web_search_call', status: 'failed', error: { message?: string } } // { type: 'web_search_call', status: 'failed', action: { error?: { message?: string } } } if (item?.status !== 'failed') return undefined + const error = (item.error ?? {}) as Record + const action = (item.action ?? {}) as Record + const actionError = (action.error ?? {}) as Record const reason = - (typeof item.error?.message === 'string' && item.error.message) || - (typeof item.action?.error?.message === 'string' && - item.action.error.message) || + (typeof error.message === 'string' && error.message) || + (typeof actionError.message === 'string' && actionError.message) || (typeof item.error === 'string' && item.error) || undefined return reason ? `Web search failed: ${reason}` : 'Web search failed.' diff --git a/src/tools/WebSearchTool/providers/bing.ts b/src/tools/WebSearchTool/providers/bing.ts index 4e9b4be9ce..331102b29f 100644 --- a/src/tools/WebSearchTool/providers/bing.ts +++ b/src/tools/WebSearchTool/providers/bing.ts @@ -22,21 +22,26 @@ export const bingProvider: SearchProvider = { url.searchParams.set('q', input.query) url.searchParams.set('count', '15') - const data = await fetchJsonWithWebSearchTimeout( + const data = (await fetchJsonWithWebSearchTimeout( url.toString(), { headers: { 'Ocp-Apim-Subscription-Key': process.env.BING_API_KEY! }, }, signal, { providerName: 'Bing' }, - ) - - const hits = (data.webPages?.value ?? []).map((r: any) => ({ - title: r.name ?? '', - url: r.url ?? '', - description: r.snippet, - source: r.displayUrl, - })) + )) as { webPages?: { value?: unknown } } | undefined + + const rawResults = data?.webPages?.value + const results = Array.isArray(rawResults) ? rawResults : [] + const hits = (results as unknown[]).map((r) => { + const rec = (r ?? {}) as Record + return { + title: typeof rec.name === 'string' ? rec.name : '', + url: typeof rec.url === 'string' ? rec.url : '', + description: typeof rec.snippet === 'string' ? rec.snippet : undefined, + source: typeof rec.displayUrl === 'string' ? rec.displayUrl : undefined, + } + }) return { hits: applyDomainFilters(hits, input), diff --git a/src/tools/WebSearchTool/providers/brave.ts b/src/tools/WebSearchTool/providers/brave.ts index 7beda80f56..b2f1ce1aa9 100644 --- a/src/tools/WebSearchTool/providers/brave.ts +++ b/src/tools/WebSearchTool/providers/brave.ts @@ -25,7 +25,7 @@ export const braveProvider: SearchProvider = { url.searchParams.set('q', input.query) url.searchParams.set('count', '15') - const data = await fetchJsonWithWebSearchTimeout( + const data = (await fetchJsonWithWebSearchTimeout( url.toString(), { headers: { @@ -35,14 +35,20 @@ export const braveProvider: SearchProvider = { }, signal, { providerName: 'Brave' }, - ) - - const hits = (data.web?.results ?? []).map((r: any) => ({ - title: r.title ?? '', - url: r.url ?? '', - description: r.description, - source: r.url ? safeHostname(r.url) : undefined, - })) + )) as { web?: { results?: unknown } } | undefined + + const rawResults = data?.web?.results + const results = Array.isArray(rawResults) ? rawResults : [] + const hits = (results as unknown[]).map((r) => { + const rec = (r ?? {}) as Record + const url = typeof rec.url === 'string' ? rec.url : '' + return { + title: typeof rec.title === 'string' ? rec.title : '', + url, + description: typeof rec.description === 'string' ? rec.description : undefined, + source: url ? safeHostname(url) : undefined, + } + }) return { hits: applyDomainFilters(hits, input), diff --git a/src/tools/WebSearchTool/providers/custom.ts b/src/tools/WebSearchTool/providers/custom.ts index 8d92afabd2..db3622ddbb 100644 --- a/src/tools/WebSearchTool/providers/custom.ts +++ b/src/tools/WebSearchTool/providers/custom.ts @@ -47,7 +47,7 @@ interface ProviderPreset { authHeader?: string authScheme?: string jsonPath?: string - responseAdapter?: (data: any) => SearchHit[] + responseAdapter?: (data: unknown) => SearchHit[] /** * When set, the API key (WEB_KEY) is sent as this query parameter instead * of in an HTTP header. Auth headers are suppressed automatically unless @@ -70,13 +70,24 @@ const BUILT_IN_PROVIDERS: Record = { urlTemplate: 'https://localhost:8080/search', queryParam: 'q', jsonPath: 'results', - responseAdapter(data: any) { - return (data.results ?? []).map((r: any) => ({ - title: r.title ?? r.url, - url: r.url, - description: r.content, - source: r.engine ?? r.source, - })) + responseAdapter(data: unknown) { + const rec = (data ?? {}) as Record + const rawResults = Array.isArray(rec.results) ? rec.results : [] + return (rawResults as unknown[]).map((r) => { + const item = (r ?? {}) as Record + const url = typeof item.url === 'string' ? item.url : '' + return { + title: typeof item.title === 'string' ? item.title : url, + url, + description: typeof item.content === 'string' ? item.content : undefined, + source: + typeof item.engine === 'string' + ? item.engine + : typeof item.source === 'string' + ? item.source + : undefined, + } + }) }, }, google: { @@ -91,13 +102,18 @@ const BUILT_IN_PROVIDERS: Record = { queryParam: 'q', authQueryParam: 'key', envQueryParams: { cx: 'GOOGLE_CSE_ID' }, - responseAdapter(data: any) { - return (data.items ?? []).map((r: any) => ({ - title: r.title ?? '', - url: r.link ?? '', - description: r.snippet, - source: r.displayLink, - })) + responseAdapter(data: unknown) { + const rec = (data ?? {}) as Record + const rawItems = Array.isArray(rec.items) ? rec.items : [] + return (rawItems as unknown[]).map((r) => { + const item = (r ?? {}) as Record + return { + title: typeof item.title === 'string' ? item.title : '', + url: typeof item.link === 'string' ? item.link : '', + description: typeof item.snippet === 'string' ? item.snippet : undefined, + source: typeof item.displayLink === 'string' ? item.displayLink : undefined, + } + }) }, }, brave: { @@ -107,13 +123,20 @@ const BUILT_IN_PROVIDERS: Record = { // Brave wants the bare token in X-Subscription-Token, NOT "Bearer ". // Empty scheme overrides the default 'Bearer' applied by buildAuthHeadersForPreset. authScheme: '', - responseAdapter(data: any) { - return (data.web?.results ?? []).map((r: any) => ({ - title: r.title ?? '', - url: r.url ?? '', - description: r.description, - source: safeHostname(r.url), - })) + responseAdapter(data: unknown) { + const rec = (data ?? {}) as Record + const web = (rec.web ?? {}) as Record + const rawResults = Array.isArray(web.results) ? web.results : [] + return (rawResults as unknown[]).map((r) => { + const item = (r ?? {}) as Record + const url = typeof item.url === 'string' ? item.url : '' + return { + title: typeof item.title === 'string' ? item.title : '', + url, + description: typeof item.description === 'string' ? item.description : undefined, + source: url ? safeHostname(url) : undefined, + } + }) }, }, serpapi: { @@ -121,13 +144,18 @@ const BUILT_IN_PROVIDERS: Record = { queryParam: 'q', authHeader: 'Authorization', authScheme: 'Bearer', - responseAdapter(data: any) { - return (data.organic_results ?? []).map((r: any) => ({ - title: r.title ?? '', - url: r.link ?? '', - description: r.snippet, - source: r.displayed_link, - })) + responseAdapter(data: unknown) { + const rec = (data ?? {}) as Record + const rawResults = Array.isArray(rec.organic_results) ? rec.organic_results : [] + return (rawResults as unknown[]).map((r) => { + const item = (r ?? {}) as Record + return { + title: typeof item.title === 'string' ? item.title : '', + url: typeof item.link === 'string' ? item.link : '', + description: typeof item.snippet === 'string' ? item.snippet : undefined, + source: typeof item.displayed_link === 'string' ? item.displayed_link : undefined, + } + }) }, }, } @@ -416,7 +444,7 @@ function resolveConfig(): { queryParam: string method: string jsonPath?: string - responseAdapter?: (data: any) => SearchHit[] + responseAdapter?: (data: unknown) => SearchHit[] preset?: ProviderPreset } { const providerName = process.env.WEB_PROVIDER @@ -553,18 +581,18 @@ function buildRequest(query: string) { // Response parsing — flexible, handles many shapes // --------------------------------------------------------------------------- -function walkJsonPath(obj: any, path: string): any { - let current = obj +function walkJsonPath(obj: unknown, path: string): unknown { + let current: unknown = obj for (const seg of path.split('.')) { if (current == null || typeof current !== 'object') return undefined - current = current[seg] + current = (current as Record)[seg] } return current } -function extractFromNode(node: any): SearchHit[] { +function extractFromNode(node: unknown): SearchHit[] { if (!node) return [] - if (Array.isArray(node)) return node.map(normalizeHit).filter(Boolean) as SearchHit[] + if (Array.isArray(node)) return (node as unknown[]).map(normalizeHit).filter(Boolean) as SearchHit[] if (typeof node === 'object') { const all: SearchHit[] = [] for (const sub of Object.values(node)) all.push(...extractFromNode(sub)) @@ -574,19 +602,20 @@ function extractFromNode(node: any): SearchHit[] { return [] } -export function extractHits(raw: any, jsonPath?: string): SearchHit[] { +export function extractHits(raw: unknown, jsonPath?: string): SearchHit[] { if (jsonPath) return extractFromNode(walkJsonPath(raw, jsonPath)) - if (Array.isArray(raw)) return raw.map(normalizeHit).filter(Boolean) as SearchHit[] + if (Array.isArray(raw)) return (raw as unknown[]).map(normalizeHit).filter(Boolean) as SearchHit[] if (!raw || typeof raw !== 'object') return [] const arrayKeys = ['results', 'items', 'data', 'web', 'organic_results', 'hits', 'entries'] + const obj = raw as Record for (const key of arrayKeys) { - const val = raw[key] - if (Array.isArray(val)) return val.map(normalizeHit).filter(Boolean) as SearchHit[] + const val = obj[key] + if (Array.isArray(val)) return (val as unknown[]).map(normalizeHit).filter(Boolean) as SearchHit[] if (val && typeof val === 'object' && !Array.isArray(val)) { const all: SearchHit[] = [] - for (const sub of Object.values(val)) { - if (Array.isArray(sub)) all.push(...(sub.map(normalizeHit).filter(Boolean) as SearchHit[])) + for (const sub of Object.values(val as Record)) { + if (Array.isArray(sub)) all.push(...((sub as unknown[]).map(normalizeHit).filter(Boolean) as SearchHit[])) } if (all.length > 0) return all } @@ -599,7 +628,7 @@ export function extractHits(raw: any, jsonPath?: string): SearchHit[] { // Fetch with one retry + timeout // --------------------------------------------------------------------------- -async function fetchWithRetry(url: string, init: RequestInit, signal?: AbortSignal): Promise { +async function fetchWithRetry(url: string, init: RequestInit, signal?: AbortSignal): Promise { const timeoutSec = readPositiveEnvNumber(process.env.WEB_CUSTOM_TIMEOUT_SEC, DEFAULT_TIMEOUT_SECONDS) const timeoutMs = timeoutSec * 1000 let lastErr: Error | undefined diff --git a/src/tools/WebSearchTool/providers/exa.ts b/src/tools/WebSearchTool/providers/exa.ts index 24966f4926..1a104a6ac9 100644 --- a/src/tools/WebSearchTool/providers/exa.ts +++ b/src/tools/WebSearchTool/providers/exa.ts @@ -24,12 +24,14 @@ import { applyDomainFilters, safeHostname, type ProviderOutput } from './types.j import { fetchJsonWithWebSearchTimeout } from './timeout.js' /** Join up to 3 highlight excerpts with an ellipsis separator. */ -function describeFromHighlights(r: any): string | undefined { - const highlights = Array.isArray(r?.highlights) ? r.highlights : null +function describeFromHighlights(r: unknown): string | undefined { + if (!r || typeof r !== 'object') return undefined + const rec = r as Record + const highlights = Array.isArray(rec.highlights) ? rec.highlights : null if (highlights && highlights.length > 0) { - return highlights.slice(0, 3).join(' … ') + return (highlights as unknown[]).slice(0, 3).filter((s): s is string => typeof s === 'string').join(' … ') } - if (typeof r?.text === 'string' && r.text) return r.text + if (typeof rec.text === 'string' && rec.text) return rec.text return undefined } @@ -43,7 +45,7 @@ export const exaProvider: SearchProvider = { async search(input: SearchInput, signal?: AbortSignal): Promise { const start = performance.now() - const body: Record = { + const body: Record = { query: input.query, numResults: 15, type: 'auto', @@ -53,7 +55,7 @@ export const exaProvider: SearchProvider = { if (input.allowed_domains?.length) body.includeDomains = input.allowed_domains if (input.blocked_domains?.length) body.excludeDomains = input.blocked_domains - const data = await fetchJsonWithWebSearchTimeout( + const data = (await fetchJsonWithWebSearchTimeout( 'https://api.exa.ai/search', { method: 'POST', @@ -65,14 +67,24 @@ export const exaProvider: SearchProvider = { }, signal, { providerName: 'Exa' }, - ) + )) as { results?: unknown } - const hits = (data.results ?? []).map((r: any) => ({ - title: r.title ?? '', - url: r.url ?? '', - description: describeFromHighlights(r), - source: r.url ? safeHostname(r.url) : undefined, - })) + const results = Array.isArray(data.results) ? data.results : [] + const hits = (results as unknown[]).map((r): { + title: string + url: string + description: string | undefined + source: string | undefined + } => { + const rec = (r ?? {}) as Record + const url = typeof rec.url === 'string' ? rec.url : '' + return { + title: typeof rec.title === 'string' ? rec.title : '', + url, + description: describeFromHighlights(r), + source: url ? safeHostname(url) : undefined, + } + }) return { // Exa handles domain filtering server-side via includeDomains/excludeDomains diff --git a/src/tools/WebSearchTool/providers/jina.ts b/src/tools/WebSearchTool/providers/jina.ts index 9ff06b7ac0..ea9be7cf1a 100644 --- a/src/tools/WebSearchTool/providers/jina.ts +++ b/src/tools/WebSearchTool/providers/jina.ts @@ -22,7 +22,7 @@ export const jinaProvider: SearchProvider = { url.searchParams.set('q', input.query) url.searchParams.set('count', '10') - const data = await fetchJsonWithWebSearchTimeout( + const data = (await fetchJsonWithWebSearchTimeout( url.toString(), { headers: { @@ -32,14 +32,27 @@ export const jinaProvider: SearchProvider = { }, signal, { providerName: 'Jina' }, - ) - - const hits = (data.data ?? data.results ?? []).map((r: any) => ({ - title: r.title ?? '', - url: r.url ?? '', - description: r.description ?? r.snippet ?? r.content, - source: r.url ? safeHostname(r.url) : undefined, - })) + )) as { data?: unknown; results?: unknown } | undefined + + const rawResults = Array.isArray(data?.data) ? data.data : (Array.isArray(data?.results) ? data.results : []) + const hits = (rawResults as unknown[]).map((r) => { + const rec = (r ?? {}) as Record + const url = typeof rec.url === 'string' ? rec.url : '' + const desc = + typeof rec.description === 'string' + ? rec.description + : typeof rec.snippet === 'string' + ? rec.snippet + : typeof rec.content === 'string' + ? rec.content + : undefined + return { + title: typeof rec.title === 'string' ? rec.title : '', + url, + description: desc, + source: url ? safeHostname(url) : undefined, + } + }) return { hits: applyDomainFilters(hits, input), diff --git a/src/tools/WebSearchTool/providers/linkup.ts b/src/tools/WebSearchTool/providers/linkup.ts index 9ccfa4bbfe..c5d73c24c3 100644 --- a/src/tools/WebSearchTool/providers/linkup.ts +++ b/src/tools/WebSearchTool/providers/linkup.ts @@ -18,7 +18,7 @@ export const linkupProvider: SearchProvider = { async search(input: SearchInput, signal?: AbortSignal): Promise { const start = performance.now() - const data = await fetchJsonWithWebSearchTimeout( + const data = (await fetchJsonWithWebSearchTimeout( 'https://api.linkup.so/v1/search', { method: 'POST', @@ -34,14 +34,27 @@ export const linkupProvider: SearchProvider = { }, signal, { providerName: 'Linkup' }, - ) + )) as { results?: unknown } | undefined - const hits = (data.results ?? []).map((r: any) => ({ - title: r.name ?? r.title ?? '', - url: r.url ?? '', - description: r.snippet ?? r.description ?? r.content, - source: r.url ? safeHostname(r.url) : undefined, - })) + const rawResults = Array.isArray(data?.results) ? data.results : [] + const hits = (rawResults as unknown[]).map((r) => { + const rec = (r ?? {}) as Record + const url = typeof rec.url === 'string' ? rec.url : '' + const desc = + typeof rec.snippet === 'string' + ? rec.snippet + : typeof rec.description === 'string' + ? rec.description + : typeof rec.content === 'string' + ? rec.content + : undefined + return { + title: typeof rec.name === 'string' ? rec.name : (typeof rec.title === 'string' ? rec.title : ''), + url, + description: desc, + source: url ? safeHostname(url) : undefined, + } + }) return { hits: applyDomainFilters(hits, input), diff --git a/src/tools/WebSearchTool/providers/mojeek.ts b/src/tools/WebSearchTool/providers/mojeek.ts index f6157dd41d..1411e2cc93 100644 --- a/src/tools/WebSearchTool/providers/mojeek.ts +++ b/src/tools/WebSearchTool/providers/mojeek.ts @@ -30,23 +30,37 @@ export const mojeekProvider: SearchProvider = { headers['Authorization'] = `Bearer ${process.env.MOJEEK_API_KEY}` } - const data = await fetchJsonWithWebSearchTimeout( + const data = (await fetchJsonWithWebSearchTimeout( url.toString(), { headers, }, signal, { providerName: 'Mojeek' }, - ) + )) as { response?: { results?: unknown }; results?: unknown } | undefined - const rawResults = data?.response?.results ?? data?.results ?? [] + const nestedResults = data?.response?.results + const rawResults = Array.isArray(nestedResults) + ? nestedResults + : Array.isArray(data?.results) + ? data.results + : [] - const hits = rawResults.map((r: any) => ({ - title: r.title ?? '', - url: r.url ?? '', - description: r.snippet ?? r.desc, - source: r.url ? safeHostname(r.url) : undefined, - })) + const hits = (rawResults as unknown[]).map((r) => { + const rec = (r ?? {}) as Record + const url = typeof rec.url === 'string' ? rec.url : '' + return { + title: typeof rec.title === 'string' ? rec.title : '', + url, + description: + typeof rec.snippet === 'string' + ? rec.snippet + : typeof rec.desc === 'string' + ? rec.desc + : undefined, + source: url ? safeHostname(url) : undefined, + } + }) return { hits: applyDomainFilters(hits, input), diff --git a/src/tools/WebSearchTool/providers/tavily.ts b/src/tools/WebSearchTool/providers/tavily.ts index 9585d36821..7ce5c5ff03 100644 --- a/src/tools/WebSearchTool/providers/tavily.ts +++ b/src/tools/WebSearchTool/providers/tavily.ts @@ -18,7 +18,7 @@ export const tavilyProvider: SearchProvider = { async search(input: SearchInput, signal?: AbortSignal): Promise { const start = performance.now() - const data = await fetchJsonWithWebSearchTimeout( + const data = (await fetchJsonWithWebSearchTimeout( 'https://api.tavily.com/search', { method: 'POST', @@ -34,14 +34,24 @@ export const tavilyProvider: SearchProvider = { }, signal, { providerName: 'Tavily' }, - ) + )) as { results?: unknown } | undefined - const hits = (data.results ?? []).map((r: any) => ({ - title: r.title ?? '', - url: r.url ?? '', - description: r.content ?? r.snippet, - source: r.url ? safeHostname(r.url) : undefined, - })) + const rawResults = Array.isArray(data?.results) ? data.results : [] + const hits = (rawResults as unknown[]).map((r) => { + const rec = (r ?? {}) as Record + const url = typeof rec.url === 'string' ? rec.url : '' + return { + title: typeof rec.title === 'string' ? rec.title : '', + url, + description: + typeof rec.content === 'string' + ? rec.content + : typeof rec.snippet === 'string' + ? rec.snippet + : undefined, + source: url ? safeHostname(url) : undefined, + } + }) return { hits: applyDomainFilters(hits, input), diff --git a/src/tools/WebSearchTool/providers/timeout.ts b/src/tools/WebSearchTool/providers/timeout.ts index 33c92e89af..df637d4640 100644 --- a/src/tools/WebSearchTool/providers/timeout.ts +++ b/src/tools/WebSearchTool/providers/timeout.ts @@ -133,7 +133,7 @@ export async function fetchJsonWithWebSearchTimeout( init: Parameters[1], signal: AbortSignal | undefined, options: WebSearchTimeoutOptions = {}, -): Promise { +): Promise { const providerName = options.providerName ?? 'Web search provider' return withWebSearchTimeout( diff --git a/src/tools/WebSearchTool/providers/types.ts b/src/tools/WebSearchTool/providers/types.ts index 2b29d537bf..f29eabaeae 100644 --- a/src/tools/WebSearchTool/providers/types.ts +++ b/src/tools/WebSearchTool/providers/types.ts @@ -53,15 +53,18 @@ const SOURCE_KEYS = [ 'source', 'domain', 'displayLink', 'displayed_link', 'engine', ] as const -function firstMatch(obj: any, keys: readonly string[]): string | undefined { +function firstMatch(obj: unknown, keys: readonly string[]): string | undefined { + if (!obj || typeof obj !== 'object') return undefined + const record = obj as Record for (const k of keys) { - if (typeof obj?.[k] === 'string' && obj[k]) return obj[k] + const v = record[k] + if (typeof v === 'string' && v) return v } return undefined } /** Extract a SearchHit from any object shape using well-known field aliases. */ -export function normalizeHit(raw: any): SearchHit | null { +export function normalizeHit(raw: unknown): SearchHit | null { if (!raw || typeof raw !== 'object') return null const title = firstMatch(raw, TITLE_KEYS) const url = firstMatch(raw, URL_KEYS) diff --git a/src/tools/WebSearchTool/providers/you.ts b/src/tools/WebSearchTool/providers/you.ts index 2523b6157c..755c1e2cc2 100644 --- a/src/tools/WebSearchTool/providers/you.ts +++ b/src/tools/WebSearchTool/providers/you.ts @@ -22,24 +22,42 @@ export const youProvider: SearchProvider = { url.searchParams.set('query', input.query) url.searchParams.set('num_web_results', '10') - const data = await fetchJsonWithWebSearchTimeout( + const data = (await fetchJsonWithWebSearchTimeout( url.toString(), { headers: { 'X-API-Key': process.env.YOU_API_KEY! }, }, signal, { providerName: 'You.com' }, - ) + )) as { results?: { web?: unknown } | unknown } | undefined - const webResults = data?.results?.web ?? data?.results ?? [] + const resultsField = data?.results + const nestedWeb = + resultsField && typeof resultsField === 'object' && 'web' in resultsField + ? (resultsField as { web?: unknown }).web + : undefined + const rawResults = Array.isArray(nestedWeb) + ? nestedWeb + : Array.isArray(resultsField) + ? resultsField + : [] - const hits = webResults.map((r: any) => { - const snippet = Array.isArray(r.snippets) ? r.snippets[0] : r.snippet + const hits = (rawResults as unknown[]).map((r) => { + const rec = (r ?? {}) as Record + const url = typeof rec.url === 'string' ? rec.url : '' + const snippets = Array.isArray(rec.snippets) ? rec.snippets : [] + const snippet = snippets.length > 0 && typeof snippets[0] === 'string' ? snippets[0] : undefined return { - title: r.title ?? '', - url: r.url ?? '', - description: snippet ?? r.description, - source: r.url ? safeHostname(r.url) : undefined, + title: typeof rec.title === 'string' ? rec.title : '', + url, + description: + snippet ?? + (typeof rec.snippet === 'string' + ? rec.snippet + : typeof rec.description === 'string' + ? rec.description + : undefined), + source: url ? safeHostname(url) : undefined, } }) From 999b4c6d71ce5ff30ee7a28cfe04763174384c98 Mon Sep 17 00:00:00 2001 From: Gravirei Date: Sat, 5 Sep 2026 09:03:13 +0600 Subject: [PATCH 04/15] chore(types): narrow any in 6 mid-size src files (Phase 1, batch 3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 1 continues. Each replacement uses a precise structural cast (`as { uuid?: string }` etc.) or `unknown` with typeof/Array.isArray narrows at use sites. - src/utils/settings/types.ts: zod preprocess callback `any` → `unknown`, copy via `Record`. - src/entrypoints/sdk/v2.ts: initialMessages param/var `any[]` → `Message[]`; local cast at the boundary with stripChainFields. - src/entrypoints/sdk/query.ts: msg `.uuid` access uses `as { uuid?: string }`; `fileHistoryCanRestore` etc. accept `UUID` (from `crypto`); `mcpServerStatus` accesses `client.error` / `client.config` directly since the union members already have them. - src/commands/cache-probe/cache-probe.ts: getField reduce `any` → `unknown` with typeof guard; catch (err: any) → `err: unknown` with `err instanceof Error`; mainMakeUsage/mainConvertChunkUsage take `unknown` and read fields via typeof checks. - src/services/compact/snipProjection.ts: snipMetadata / uuid accessed via narrow structural casts, no longer `as any`. - src/utils/messages.ts: tool_use block destructuring for extra_content uses `as { extra_content?: unknown; [k: string]: unknown }` instead of `as any`. Public SDK surface (entrypoints/sdk.d.ts, entrypoints/sdk/index.ts SdkMcpToolDefinition) is intentionally permissive for consumer ergonomics and is left untouched per the plan's do-not-touch list. Verification: - bun run typecheck clean - bun run lint: 782 warnings (was 805, -23) - bun run lint:any-budget: 969 / 992 baseline - bun test (sdk v2/factories + compact + messages): 91 pass / 0 fail --- src/commands/cache-probe/cache-probe.ts | 23 +++++++++++++---------- src/entrypoints/sdk/query.ts | 24 ++++++++++++------------ src/entrypoints/sdk/v2.ts | 9 +++++---- src/services/compact/snipProjection.ts | 12 +++++++----- src/utils/messages.ts | 4 ++-- src/utils/settings/types.ts | 6 +++--- 6 files changed, 42 insertions(+), 36 deletions(-) diff --git a/src/commands/cache-probe/cache-probe.ts b/src/commands/cache-probe/cache-probe.ts index cd77bf71d8..888def344f 100644 --- a/src/commands/cache-probe/cache-probe.ts +++ b/src/commands/cache-probe/cache-probe.ts @@ -96,7 +96,7 @@ export function resolveCacheProbeRequestApiKey( function getField(obj: unknown, path: string): unknown { return path .split('.') - .reduce((o: any, k: string) => (o != null ? o[k] : undefined), obj) + .reduce((o: unknown, k: string) => (o != null && typeof o === 'object' ? (o as Record)[k] : undefined), obj) } interface ProbeResult { @@ -123,7 +123,7 @@ async function sendProbe( headers, body: JSON.stringify(body), }) - } catch (err: any) { + } catch (err: unknown) { return { label, status: 0, @@ -131,7 +131,7 @@ async function sendProbe( headers: {}, usage: null, responseId: null, - error: err.message, + error: err instanceof Error ? err.message : String(err), } } const elapsed = Date.now() - start @@ -419,21 +419,24 @@ export const call: LocalCommandCall = async (args) => { // --- Simulate what main's shim code does with this usage --- // codexShim.ts makeUsage() — used for Responses API (GPT-5+/Codex) - function mainMakeUsage(u: any) { + function mainMakeUsage(u: unknown) { + const rec = (u ?? {}) as Record return { - input_tokens: u?.input_tokens ?? 0, - output_tokens: u?.output_tokens ?? 0, + input_tokens: typeof rec.input_tokens === 'number' ? rec.input_tokens : 0, + output_tokens: typeof rec.output_tokens === 'number' ? rec.output_tokens : 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0, // ← main hardcodes this to 0 } } // openaiShim.ts convertChunkUsage() — used for Chat Completions - function mainConvertChunkUsage(u: any) { + function mainConvertChunkUsage(u: unknown) { + const rec = (u ?? {}) as Record + const details = (rec.prompt_tokens_details ?? {}) as Record return { - input_tokens: u?.prompt_tokens ?? 0, - output_tokens: u?.completion_tokens ?? 0, + input_tokens: typeof rec.prompt_tokens === 'number' ? rec.prompt_tokens : 0, + output_tokens: typeof rec.completion_tokens === 'number' ? rec.completion_tokens : 0, cache_creation_input_tokens: 0, - cache_read_input_tokens: u?.prompt_tokens_details?.cached_tokens ?? 0, + cache_read_input_tokens: typeof details.cached_tokens === 'number' ? details.cached_tokens : 0, } } diff --git a/src/entrypoints/sdk/query.ts b/src/entrypoints/sdk/query.ts index 02406e0860..550234de77 100644 --- a/src/entrypoints/sdk/query.ts +++ b/src/entrypoints/sdk/query.ts @@ -6,7 +6,7 @@ */ import type { ContentBlockParam } from '@anthropic-ai/sdk/resources/index.mjs' -import { randomUUID } from 'crypto' +import { randomUUID, type UUID } from 'crypto' import { dirname } from 'path' import { QueryEngine } from '../../QueryEngine.js' import { @@ -824,11 +824,11 @@ class QueryImpl implements Query { // Find the last assistant message UUID that has a file-history snapshot const fileHistory = state.fileHistory for (let i = messages.length - 1; i >= 0; i--) { - const msg = messages[i] - const messageId = (msg as any)?.uuid as string | undefined + const msg = messages[i] as { uuid?: string } | undefined + const messageId = msg?.uuid if (!messageId) continue - if (fileHistoryCanRestore(fileHistory, messageId as any)) { + if (fileHistoryCanRestore(fileHistory, messageId as UUID)) { // Synchronous check — return canRewind: true with the messageId. // Use rewindFilesAsync() to actually perform the rewind. return { canRewind: true } @@ -850,11 +850,11 @@ class QueryImpl implements Query { const fileHistory = state.fileHistory let targetMessageId: string | undefined for (let i = messages.length - 1; i >= 0; i--) { - const msg = messages[i] - const messageId = (msg as any)?.uuid as string | undefined + const msg = messages[i] as { uuid?: string } | undefined + const messageId = msg?.uuid if (!messageId) continue - if (fileHistoryCanRestore(fileHistory, messageId as any)) { + if (fileHistoryCanRestore(fileHistory, messageId as UUID)) { targetMessageId = messageId break } @@ -865,7 +865,7 @@ class QueryImpl implements Query { } // Get diff stats before rewinding (async) - const diffStats = await fileHistoryGetDiffStats(fileHistory, targetMessageId as any) + const diffStats = await fileHistoryGetDiffStats(fileHistory, targetMessageId as UUID) // Perform the actual rewind try { @@ -874,7 +874,7 @@ class QueryImpl implements Query { ...prev, fileHistory: updater(prev.fileHistory), })), - targetMessageId as any, + targetMessageId as UUID, ) return { @@ -910,7 +910,7 @@ class QueryImpl implements Query { supportedAgents(): string[] { const state = this.appStateStore.getState() const agents = state.agentDefinitions?.activeAgents - return agents?.map((a: any) => a.agentType).filter(Boolean) ?? [] + return agents?.map((a) => a.agentType).filter(Boolean) ?? [] } mcpServerStatus(): McpServerStatus[] { @@ -925,10 +925,10 @@ class QueryImpl implements Query { base.serverInfo = client.serverInfo } if (client.type === 'failed') { - base.error = (client as any).error + base.error = client.error } if ('config' in client) { - const cfg = (client as any).config + const cfg = client.config if (cfg?.scope) base.scope = cfg.scope } return base diff --git a/src/entrypoints/sdk/v2.ts b/src/entrypoints/sdk/v2.ts index faf2ecd8f4..778925b23d 100644 --- a/src/entrypoints/sdk/v2.ts +++ b/src/entrypoints/sdk/v2.ts @@ -42,6 +42,7 @@ import type { PermissionResult, SDKResultMessage as GeneratedSDKResultMessage, } from './coreTypes.generated.js' +import type { Message } from '../../types/message.js' import type { SDKMessage, SDKPermissionTimeoutMessage, @@ -518,7 +519,7 @@ class SDKSessionImpl implements SDKSession { function createEngineFromOptions( options: SDKSessionOptions, permissionTarget: PermissionTarget & { pushTimeout?: (msg: SDKPermissionTimeoutMessage) => void }, - initialMessages?: any[], + initialMessages?: Message[], sessionId?: string, ): { engine: QueryEngine; appStateStore: Store; abortController: AbortController } { const { @@ -678,7 +679,7 @@ export async function unstable_v2_resumeSession( // Load prior messages from JSONL with compact-aware chain building. // Matches CLI's loadTranscriptFile → buildConversationChain → removeExtraFields. const resolved = await resolveSessionFilePath(sessionId, options.cwd) - let initialMessages: any[] + let initialMessages: Message[] = [] if (resolved) { const { size: fileSize } = await stat(resolved.filePath) @@ -764,7 +765,7 @@ export async function unstable_v2_resumeSession( } if (leaf) { const chain = buildChain(byUuid, leaf) - initialMessages = stripChainFields(chain) + initialMessages = stripChainFields(chain) as Message[] } else { initialMessages = [] } @@ -779,7 +780,7 @@ export async function unstable_v2_resumeSession( const { engine, appStateStore, abortController } = createEngineFromOptions( options, session, - initialMessages as any[], + initialMessages, sessionId, ) session.setEngine(engine) diff --git a/src/services/compact/snipProjection.ts b/src/services/compact/snipProjection.ts index b766052e9e..39f9dae446 100644 --- a/src/services/compact/snipProjection.ts +++ b/src/services/compact/snipProjection.ts @@ -1,5 +1,5 @@ export function isSnipBoundaryMessage(message: unknown): boolean { - return Boolean((message as any)?.snipMetadata) + return Boolean((message as { snipMetadata?: unknown } | null | undefined)?.snipMetadata) } /** @@ -10,13 +10,15 @@ export function isSnipBoundaryMessage(message: unknown): boolean { export function projectSnippedView(messages: T[]): T[] { const removedUuids = new Set() for (const msg of messages) { - const uuids = (msg as any)?.snipMetadata?.removedUuids + const uuids = (msg as { snipMetadata?: { removedUuids?: unknown } } | null | undefined)?.snipMetadata?.removedUuids if (!Array.isArray(uuids)) continue - for (const uuid of uuids) removedUuids.add(uuid as string) + for (const uuid of uuids) { + if (typeof uuid === 'string') removedUuids.add(uuid) + } } if (removedUuids.size === 0) return messages return messages.filter(msg => { - const uuid = (msg as any)?.uuid - return !uuid || !removedUuids.has(uuid as string) + const uuid = (msg as { uuid?: string } | null | undefined)?.uuid + return !uuid || !removedUuids.has(uuid) }) } diff --git a/src/utils/messages.ts b/src/utils/messages.ts index a4afc17bb1..69885b6c65 100644 --- a/src/utils/messages.ts +++ b/src/utils/messages.ts @@ -1745,7 +1745,7 @@ export function normalizeMessagesForAPI( // When tool search is enabled, preserve all fields including 'caller' if (toolSearchEnabled) { - const { extra_content, ...restBlock } = block as any + const { extra_content, ...restBlock } = block as { extra_content?: unknown; [k: string]: unknown } return { ...restBlock, name: canonicalName, @@ -1762,7 +1762,7 @@ export function normalizeMessagesForAPI( id: block.id, name: canonicalName, input: normalizedInput, - ...((block as any).extra_content ? { extra_content: (block as any).extra_content } : {}) + ...((block as { extra_content?: unknown }).extra_content ? { extra_content: (block as { extra_content?: unknown }).extra_content } : {}) } } return block diff --git a/src/utils/settings/types.ts b/src/utils/settings/types.ts index 121a17c3c0..c2e81c43c1 100644 --- a/src/utils/settings/types.ts +++ b/src/utils/settings/types.ts @@ -575,11 +575,11 @@ export const SettingsSchema = lazySchema(() => 'and feed errors back for self-repair.', ), worktree: z - .preprocess((val: any) => { + .preprocess((val: unknown) => { if (val && typeof val === 'object') { - const copy = { ...val } + const copy: Record = { ...(val as Record) } if ('enableGitLongPaths' in val && !('autoConfigureLongPaths' in val)) { - copy.autoConfigureLongPaths = val.enableGitLongPaths + copy.autoConfigureLongPaths = (val as Record).enableGitLongPaths } return copy } From 0f66f18756bf2f532608811b3e444d92cb1731fe Mon Sep 17 00:00:00 2001 From: Gravirei Date: Sat, 5 Sep 2026 09:17:09 +0600 Subject: [PATCH 05/15] fix(types): correct BetaContentBlock cast in messages.ts (Phase 1 fix) Verifier caught that the `as { [k: string]: unknown; extra_content?: unknown }` cast on a `BetaToolUseBlock` did not sufficiently overlap (BetaToolUseBlock lacks an index signature), so the destructured spread of `restBlock` yielded a malformed union member that failed to assign to BetaContentBlock[]. Fix: cast through `unknown` first, then assert the final return value as `BetaContentBlock` (the declared element type of the outer `.map`). This narrows from the prior `as any` while keeping the previous structural intent (preserve all block fields, optionally inject `extra_content`). Verification: - bun run typecheck clean - bun test src/utils/messages/: 79 pass / 0 fail --- src/utils/messages.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/utils/messages.ts b/src/utils/messages.ts index 69885b6c65..b001b3fa6f 100644 --- a/src/utils/messages.ts +++ b/src/utils/messages.ts @@ -1745,25 +1745,27 @@ export function normalizeMessagesForAPI( // When tool search is enabled, preserve all fields including 'caller' if (toolSearchEnabled) { - const { extra_content, ...restBlock } = block as { extra_content?: unknown; [k: string]: unknown } + const blockWithExtra = block as unknown as { extra_content?: unknown } & Record + const { extra_content, ...restBlock } = blockWithExtra return { ...restBlock, name: canonicalName, input: normalizedInput, - ...(extra_content ? { extra_content } : {}) - } + ...(extra_content ? { extra_content } : {}), + } as BetaContentBlock } // When tool search is NOT enabled, explicitly construct tool_use // block with only standard API fields to avoid sending fields like // 'caller' that may be stored in sessions from tool search runs + const blockExtra = (block as unknown as { extra_content?: unknown }).extra_content return { type: 'tool_use' as const, id: block.id, name: canonicalName, input: normalizedInput, - ...((block as { extra_content?: unknown }).extra_content ? { extra_content: (block as { extra_content?: unknown }).extra_content } : {}) - } + ...(blockExtra ? { extra_content: blockExtra } : {}), + } as BetaContentBlock } return block }), From 75f3e25c448557ac1f9b8cd43ee722f53cb9bc8b Mon Sep 17 00:00:00 2001 From: Gravirei Date: Sat, 5 Sep 2026 10:02:00 +0600 Subject: [PATCH 06/15] chore(types): narrow any in 5 dense src + 1 script (Phase 1, batch 4) Each replacement preserves runtime behavior while using concrete types where reachable and `unknown` with structural narrowing where not. - src/components/ClaudeMdExternalIncludesDialog.tsx: 4 helper updaters (acceptProject/User, declineProject/User) take `ProjectConfig` from `src/utils/config.ts` instead of `any` (matches the `saveCurrentProjectConfig` updater signature). - src/components/ProviderManager.tsx: env-clearing objects in `activateGithubProvider` and the GitHub disable path used `undefined as any`. The intent is "delete this key on merge" but the zod schema coerces to string. Replaced with `undefined as unknown as string` (honest about the lie) and added a comment explaining intent. - src/services/compact/snipCompact.ts: introduced a structural `SnipMessage` type for the public function params. Tests construct fake messages with `uuid: 'u4'` (not branded UUID), so a strict `Message[]` would reject them; the structural type accepts both production messages and test fixtures. `snipCompactIfNeeded` is generic `` so callers get the same shape back. Internal content blocks use `unknown` + type-predicate narrowing. - src/query.ts: yield site for the boundary message casts to `Message` since the value is constructed as a real `Message` in production. - scripts/grpc-cli.ts: proto descriptor, stream, and message callback typed as `unknown` / `ClientDuplexStream`. The dynamic `.proto` payload is cast once at the boundary into a structural shape so per-field access (`text_chunk.text` etc.) is tracked explicitly. - scripts/generate-sdk-types.ts: the JSON-Schema-walking converter takes `unknown` everywhere and narrows via typeof / Array.isArray at each access. `Map` for placeholder identity comparison, `Set` no longer used here. Verification: - bun run typecheck clean - bun run lint: 728 warnings (was 782, -54) - bun run lint:any-budget: 916 / 969 baseline - bun test src/services/compact: 91 pass / 0 fail --- scripts/generate-sdk-types.ts | 56 +++++++------ scripts/grpc-cli.ts | 29 +++++-- .../ClaudeMdExternalIncludesDialog.tsx | 9 +- src/components/ProviderManager.tsx | 35 ++++---- src/query.ts | 2 +- src/services/compact/snipCompact.ts | 82 +++++++++++++------ 6 files changed, 132 insertions(+), 81 deletions(-) diff --git a/scripts/generate-sdk-types.ts b/scripts/generate-sdk-types.ts index 722458e230..d6e1a26613 100644 --- a/scripts/generate-sdk-types.ts +++ b/scripts/generate-sdk-types.ts @@ -55,9 +55,9 @@ const TypeOverrideMap: Record = { // Materialize placeholder schemas once so we can detect them by identity (===) // when they appear as fields inside other schemas. -const placeholderInstances = new Map() +const placeholderInstances = new Map() for (const name of Object.keys(TypeOverrideMap)) { - const thunk = (schemas as any)[name] + const thunk = (schemas as Record unknown>)[name] if (typeof thunk === 'function') { try { placeholderInstances.set(thunk(), TypeOverrideMap[name]) @@ -228,15 +228,16 @@ const EXPORT_ORDER = [ // Zod v4 uses schema.def.type as the discriminator (lowercase strings). // All schemas have .def with { type: string, ... }. -function convert(schema: any, depth = 0): string { - if (!schema || !schema.def) return 'unknown' +function convert(schema: unknown, depth = 0): string { + if (!schema || typeof schema !== 'object' || !('def' in schema)) return 'unknown' // Check if this schema is a known placeholder (identity comparison) const override = placeholderInstances.get(schema) if (override) return override - const def = schema.def - const type: string = def.type + const def = (schema as { def: unknown }).def as Record | null + if (!def || typeof def !== 'object') return 'unknown' + const type = typeof def.type === 'string' ? def.type : '' switch (type) { case 'string': @@ -259,16 +260,16 @@ function convert(schema: any, depth = 0): string { return 'never' case 'literal': { // def.values is an array of literal values - const vals = def.values as any[] + const vals = Array.isArray(def.values) ? def.values : [] return vals.map(v => JSON.stringify(v)).join(' | ') } case 'enum': { // def.entries is { key: value } or an array const entries = def.entries if (Array.isArray(entries)) { - return entries.map((v: any) => JSON.stringify(v)).join(' | ') + return entries.map(v => JSON.stringify(v)).join(' | ') } - return Object.values(entries) + return Object.values(entries as Record) .filter((v): v is string => typeof v === 'string') .map(v => JSON.stringify(v)) .join(' | ') @@ -285,7 +286,7 @@ function convert(schema: any, depth = 0): string { return `${needsArrayElementParens(element) ? `(${element})` : element}[]` } case 'tuple': { - const items = (def.items as any[]).map(t => convert(t, depth)) + const items = (Array.isArray(def.items) ? def.items : []).map(t => convert(t, depth)) return `[${items.join(', ')}]` } case 'record': @@ -295,7 +296,7 @@ function convert(schema: any, depth = 0): string { case 'union': case 'discriminated_union': { // def.options for discriminated, def.options for plain union - const members = (def.options as any[]).map(t => { + const members = (Array.isArray(def.options) ? def.options : []).map(t => { const ts = convert(t, depth) return needsParens(ts) ? `(${ts})` : ts }) @@ -334,12 +335,12 @@ function convert(schema: any, depth = 0): string { } } -function convertObject(def: any, depth: number): string { - let shape: Record +function convertObject(def: Record, depth: number): string { + let shape: Record if (typeof def.shape === 'function') { - shape = def.shape() + shape = (def.shape as () => Record)() } else if (typeof def.shape === 'object' && def.shape !== null) { - shape = def.shape + shape = def.shape as Record } else { return 'Record' } @@ -359,9 +360,12 @@ function convertObject(def: any, depth: number): string { return '{\n' + fields.join('\n') + '\n' + closeIndent + '}' } -function isOptional(schema: any): boolean { - if (!schema?.def) return false - return schema.def.type === 'optional' || schema.def.type === 'default' +function isOptional(schema: unknown): boolean { + if (!schema || typeof schema !== 'object' || !('def' in schema)) return false + const def = (schema as { def: unknown }).def + if (!def || typeof def !== 'object') return false + const defType = (def as { type?: unknown }).type + return defType === 'optional' || defType === 'default' } function needsParens(ts: string): boolean { @@ -388,7 +392,7 @@ export function generateSdkTypes(): string { let errors = 0 for (const schemaName of EXPORT_ORDER) { - const thunk = (schemas as any)[schemaName] + const thunk = (schemas as Record)[schemaName] if (typeof thunk !== 'function') { console.warn(` ⚠ Not found: ${schemaName}`) errors++ @@ -403,11 +407,12 @@ export function generateSdkTypes(): string { continue } - let schema: any + let schema: unknown try { - schema = thunk() - } catch (e: any) { - console.warn(` ⚠ Materialize failed: ${schemaName}: ${e.message}`) + schema = (thunk as () => unknown)() + } catch (e: unknown) { + const msg = e instanceof Error ? e.message : String(e) + console.warn(` ⚠ Materialize failed: ${schemaName}: ${msg}`) errors++ continue } @@ -423,8 +428,9 @@ export function generateSdkTypes(): string { } lines.push(`export type ${typeName} = ${ts}`) lines.push('') - } catch (e: any) { - console.warn(` ⚠ Convert failed: ${schemaName}: ${e.message}`) + } catch (e: unknown) { + const msg = e instanceof Error ? e.message : String(e) + console.warn(` ⚠ Convert failed: ${schemaName}: ${msg}`) errors++ lines.push(`// ⚠ Failed: ${schemaName}`) lines.push(`export type ${typeName} = any`) diff --git a/scripts/grpc-cli.ts b/scripts/grpc-cli.ts index 90467e3428..fabcd6b5a4 100644 --- a/scripts/grpc-cli.ts +++ b/scripts/grpc-cli.ts @@ -13,7 +13,9 @@ const packageDefinition = protoLoader.loadSync(PROTO_PATH, { oneofs: true, }) -const protoDescriptor = grpc.loadPackageDefinition(packageDefinition) as any +const protoDescriptor = grpc.loadPackageDefinition(packageDefinition) as unknown as { + openclaude: { v1: { AgentService: new (addr: string, creds: grpc.ChannelCredentials) => { Chat: () => grpc.ClientDuplexStream } } } +} const openclaudeProto = protoDescriptor.openclaude.v1 const rl = readline.createInterface({ @@ -35,22 +37,33 @@ async function main() { grpc.credentials.createInsecure() ) - let call: grpc.ClientDuplexStream | null = null + let call: grpc.ClientDuplexStream | null = null const startStream = () => { call = client.Chat() let textStreamed = false - call.on('data', async (serverMessage: any) => { + call.on('data', async (raw: unknown) => { + // gRPC streaming payloads are dynamically loaded from the .proto file at + // startup, so the runtime message shape has no static type. Narrow at + // the boundary and trust field access below. + const serverMessage = raw as { + text_chunk?: { text?: string } + tool_start?: { tool_name?: string; arguments_json?: string } + tool_result?: { tool_name?: string; output?: string } + action_required?: { question?: string; prompt_id?: string } + done?: { full_text?: string } + error?: { message?: string } + } if (serverMessage.text_chunk) { - process.stdout.write(serverMessage.text_chunk.text) + process.stdout.write(serverMessage.text_chunk.text ?? '') textStreamed = true } else if (serverMessage.tool_start) { console.log(`\n\x1b[36m[Tool Call]\x1b[0m \x1b[1m${serverMessage.tool_start.tool_name}\x1b[0m`) console.log(`\x1b[90m${serverMessage.tool_start.arguments_json}\x1b[0m\n`) } else if (serverMessage.tool_result) { console.log(`\n\x1b[32m[Tool Result]\x1b[0m \x1b[1m${serverMessage.tool_result.tool_name}\x1b[0m`) - const out = serverMessage.tool_result.output + const out = serverMessage.tool_result.output ?? '' if (out.length > 500) { console.log(`\x1b[90m${out.substring(0, 500)}...\n(Output truncated, total length: ${out.length})\x1b[0m`) } else { @@ -59,8 +72,8 @@ async function main() { } else if (serverMessage.action_required) { const action = serverMessage.action_required console.log(`\n\x1b[33m[Action Required]\x1b[0m`) - const reply = await askQuestion(`\x1b[1m${action.question}\x1b[0m (y/n) > `) - + const reply = await askQuestion(`\x1b[1m${action.question ?? ''}\x1b[0m (y/n) > `) + call?.write({ input: { prompt_id: action.prompt_id, @@ -75,7 +88,7 @@ async function main() { console.log('\n\x1b[32m[Generation Complete]\x1b[0m') promptUser() } else if (serverMessage.error) { - console.error(`\n\x1b[31m[Server Error]\x1b[0m ${serverMessage.error.message}`) + console.error(`\n\x1b[31m[Server Error]\x1b[0m ${serverMessage.error.message ?? ''}`) promptUser() } }) diff --git a/src/components/ClaudeMdExternalIncludesDialog.tsx b/src/components/ClaudeMdExternalIncludesDialog.tsx index 414385929f..06513be515 100644 --- a/src/components/ClaudeMdExternalIncludesDialog.tsx +++ b/src/components/ClaudeMdExternalIncludesDialog.tsx @@ -5,6 +5,7 @@ import { logEvent } from 'src/services/analytics/index.js'; import { Box, Link, Text } from '../ink.js'; import type { ExternalClaudeMdInclude } from '../utils/claudemd.js'; import { saveCurrentProjectConfig } from '../utils/config.js'; +import type { ProjectConfig } from '../utils/config.js'; import { getClaudeConfigHomeDir } from '../utils/envUtils.js'; import { getDisplayPath } from '../utils/file.js'; import { Select } from './CustomSelect/index.js'; @@ -15,16 +16,16 @@ type Props = { externalIncludes?: ExternalClaudeMdInclude[]; scope?: 'User' | 'Project'; }; -function acceptProject(current: any) { +function acceptProject(current: ProjectConfig) { return { ...current, hasClaudeMdExternalIncludesApproved: true, hasClaudeMdExternalIncludesWarningShown: true }; } -function acceptUser(current: any) { +function acceptUser(current: ProjectConfig) { return { ...current, hasClaudeMdExternalIncludesApprovedForUser: true, hasClaudeMdExternalIncludesWarningShownForUser: true }; } -function declineProject(current: any) { +function declineProject(current: ProjectConfig) { return { ...current, hasClaudeMdExternalIncludesApproved: false, hasClaudeMdExternalIncludesWarningShown: true }; } -function declineUser(current: any) { +function declineUser(current: ProjectConfig) { return { ...current, hasClaudeMdExternalIncludesApprovedForUser: false, hasClaudeMdExternalIncludesWarningShownForUser: true }; } function getUserClaudeMdDisplayPath(): string { diff --git a/src/components/ProviderManager.tsx b/src/components/ProviderManager.tsx index ed2f5c4f03..ee6927dcfc 100644 --- a/src/components/ProviderManager.tsx +++ b/src/components/ProviderManager.tsx @@ -1718,18 +1718,21 @@ export function ProviderManager({ mode, onDone }: Props): React.ReactNode { env: { CLAUDE_CODE_USE_GITHUB: '1', OPENAI_MODEL: GITHUB_PROVIDER_DEFAULT_MODEL, - OPENAI_API_KEYS: undefined as any, - OPENAI_API_KEY: undefined as any, - OPENAI_ORG: undefined as any, - OPENAI_PROJECT: undefined as any, - OPENAI_ORGANIZATION: undefined as any, - OPENAI_BASE_URL: undefined as any, - OPENAI_API_BASE: undefined as any, - CLAUDE_CODE_USE_OPENAI: undefined as any, - CLAUDE_CODE_USE_GEMINI: undefined as any, - CLAUDE_CODE_USE_BEDROCK: undefined as any, - CLAUDE_CODE_USE_VERTEX: undefined as any, - CLAUDE_CODE_USE_FOUNDRY: undefined as any, + // `undefined` instructs the merge to clear these keys from saved + // settings. EnvironmentVariablesSchema coerces to string, so we route + // through `unknown` instead of `any` for the literal assignment. + OPENAI_API_KEYS: undefined as unknown as string, + OPENAI_API_KEY: undefined as unknown as string, + OPENAI_ORG: undefined as unknown as string, + OPENAI_PROJECT: undefined as unknown as string, + OPENAI_ORGANIZATION: undefined as unknown as string, + OPENAI_BASE_URL: undefined as unknown as string, + OPENAI_API_BASE: undefined as unknown as string, + CLAUDE_CODE_USE_OPENAI: undefined as unknown as string, + CLAUDE_CODE_USE_GEMINI: undefined as unknown as string, + CLAUDE_CODE_USE_BEDROCK: undefined as unknown as string, + CLAUDE_CODE_USE_VERTEX: undefined as unknown as string, + CLAUDE_CODE_USE_FOUNDRY: undefined as unknown as string, }, }) if (error) { @@ -1767,10 +1770,10 @@ export function ProviderManager({ mode, onDone }: Props): React.ReactNode { const { error } = updateSettingsForSource('userSettings', { env: { - CLAUDE_CODE_USE_GITHUB: undefined as any, - OPENAI_MODEL: undefined as any, - OPENAI_BASE_URL: undefined as any, - OPENAI_API_BASE: undefined as any, + CLAUDE_CODE_USE_GITHUB: undefined as unknown as string, + OPENAI_MODEL: undefined as unknown as string, + OPENAI_BASE_URL: undefined as unknown as string, + OPENAI_API_BASE: undefined as unknown as string, }, }) if (error) { diff --git a/src/query.ts b/src/query.ts index 3c8de1642e..6e04c049df 100644 --- a/src/query.ts +++ b/src/query.ts @@ -974,7 +974,7 @@ async function* queryLoop( messagesForQuery = snipResult.messages snipTokensFreed = snipResult.tokensFreed if (snipResult.boundaryMessage) { - yield snipResult.boundaryMessage + yield snipResult.boundaryMessage as Message } queryCheckpoint('query_snip_end') } diff --git a/src/services/compact/snipCompact.ts b/src/services/compact/snipCompact.ts index 8e60c72a08..1a065b966c 100644 --- a/src/services/compact/snipCompact.ts +++ b/src/services/compact/snipCompact.ts @@ -1,5 +1,6 @@ import { randomUUID } from 'crypto' import type { UUID } from 'crypto' +import type { Message } from '../../types/message.js' import { deriveShortMessageId } from '../../utils/messages.js' // Module-level registry of message UUIDs queued for removal. We resolve the @@ -35,7 +36,23 @@ function normalizeSnipShortId(shortId: string): string { // and were queued. Unresolvable short IDs (stale or hallucinated) are skipped, // so callers can report the genuinely-queued count rather than the raw request // length. -export function markForSnip(shortIds: string[], messages: any[]): UUID[] { +// Structural message shape for snip operations. Tests construct fake messages +// with `string` uuids; using `Message` directly would reject them since the +// production `uuid` field is a branded `${string}-${string}-...` template type. +type SnipMessage = { + uuid?: string + type?: string + message?: { content?: unknown } + content?: unknown + subtype?: string + attachment?: { type?: string } + snipMetadata?: { removedUuids?: UUID[] } + isMeta?: boolean + timestamp?: string + level?: string +} + +export function markForSnip(shortIds: string[], messages: SnipMessage[]): UUID[] { const shortIdToUuid = new Map() for (const msg of messages) { if (msg?.uuid) { @@ -73,14 +90,14 @@ const DEFAULT_NUDGE_INTERVAL_TOKENS = 10_000 /** * Rough per-message token estimate: content length ÷ 4. */ -function estimateTokens(msg: any): number { +function estimateTokens(msg: SnipMessage): number { const content = msg?.message?.content ?? msg?.content ?? '' const text = typeof content === 'string' ? content : JSON.stringify(content) return Math.ceil(text.length / 4) } export function shouldNudgeForSnips( - messages: any[], + messages: SnipMessage[], intervalTokens = DEFAULT_NUDGE_INTERVAL_TOKENS, ): boolean { let accumulated = 0 @@ -98,19 +115,21 @@ export function shouldNudgeForSnips( return false } -export function snipCompactIfNeeded( - messages: any[], -): { messages: any[]; tokensFreed: number; boundaryMessage?: any } { +export function snipCompactIfNeeded( + messages: T[], +): { messages: T[]; tokensFreed: number; boundaryMessage?: SnipMessage } { if (pendingSnipUuids.size === 0) { return { messages, tokensFreed: 0 } } // Match pending UUIDs against THIS conversation's messages. UUIDs that belong // to another in-process session won't be present here, so they stay pending. - const uuidsToRemove = new Set() + // Use plain `string` keys so the Set also accepts the test fixtures' `uuid: 'u4'` + // shapes; production callers pass branded UUIDs which coerce cleanly. + const uuidsToRemove = new Set() for (const msg of messages) { - const uuid = msg?.uuid as UUID | undefined - if (uuid && pendingSnipUuids.has(uuid)) uuidsToRemove.add(uuid) + const uuid = msg?.uuid + if (uuid && pendingSnipUuids.has(uuid as UUID)) uuidsToRemove.add(uuid as UUID) } if (uuidsToRemove.size === 0) { @@ -118,7 +137,7 @@ export function snipCompactIfNeeded( } // Consume only the matched UUIDs; another session's pending removals survive. - for (const uuid of uuidsToRemove) pendingSnipUuids.delete(uuid) + for (const uuid of uuidsToRemove) pendingSnipUuids.delete(uuid as unknown as UUID) // A tool interaction spans an assistant tool_use and the paired user // tool_result. The model can snip from EITHER side, so we pair in both @@ -164,7 +183,9 @@ export function snipCompactIfNeeded( if (msg?.type !== 'assistant') continue const blocks = msg?.message?.content if (!Array.isArray(blocks)) continue - const toolUses = (blocks as any[]).filter(b => b?.type === 'tool_use') + const toolUses = (blocks as unknown[]).filter((b): b is { type: string; id?: string } => + typeof b === 'object' && b !== null && (b as { type?: unknown }).type === 'tool_use', + ) if (toolUses.length === 0) continue // Only the paired-drop branch needs the whole turn to be tool blocks: an // explicitly-snipped message (uuidsToRemove) is the model's deliberate choice @@ -174,14 +195,14 @@ export function snipCompactIfNeeded( const droppable = uuidsToRemove.has(msg?.uuid) || (isPureToolUseTurn && - toolUses.every((t: any) => snippedResultToolUseIds.has(t?.id))) + toolUses.every((t) => typeof t.id === 'string' && snippedResultToolUseIds.has(t.id))) if (droppable) { - for (const t of toolUses) if (t?.id) safeToolUseIds.add(t.id as string) + for (const t of toolUses) if (t?.id) safeToolUseIds.add(t.id) } } let tokensFreed = 0 - const surviving: any[] = [] + const surviving: T[] = [] // UUIDs actually removed from the live context: explicitly-snipped messages // that were cleanly removable, plus the paired half of each snipped tool // interaction (paired tool-result users, or paired assistant tool-uses). They @@ -200,10 +221,13 @@ export function snipCompactIfNeeded( // tool_use, which the next API-prep pass repairs with a synthetic // placeholder, so the snip would not actually take effect. Keep it. if (msg?.type === 'user' && Array.isArray(msg?.message?.content)) { - const results = (msg.message.content as any[]).filter(b => b?.type === 'tool_result') + const userBlocks = msg.message.content as unknown[] + const results = userBlocks.filter((b): b is { type: string; tool_use_id?: string } => + typeof b === 'object' && b !== null && (b as { type?: unknown }).type === 'tool_result', + ) if ( results.length > 0 && - !results.every((r: any) => safeToolUseIds.has(r?.tool_use_id)) + !results.every((r) => typeof r.tool_use_id === 'string' && safeToolUseIds.has(r.tool_use_id)) ) { surviving.push(msg) continue @@ -215,12 +239,14 @@ export function snipCompactIfNeeded( } // Drop user messages whose content is entirely tool results for snipped tool calls if (msg?.type === 'user' && Array.isArray(msg?.message?.content)) { - const blocks = msg.message.content as any[] - const results = blocks.filter(b => b?.type === 'tool_result') + const userBlocks = msg.message.content as unknown[] + const results = userBlocks.filter((b): b is { type: string; tool_use_id?: string } => + typeof b === 'object' && b !== null && (b as { type?: unknown }).type === 'tool_result', + ) if ( results.length > 0 && - results.length === blocks.length && - results.every((r: any) => snippedToolUseIds.has(r?.tool_use_id)) + results.length === userBlocks.length && + results.every((r) => typeof r.tool_use_id === 'string' && snippedToolUseIds.has(r.tool_use_id)) ) { tokensFreed += estimateTokens(msg) if (msg?.uuid) removedUuids.add(msg.uuid as UUID) @@ -231,12 +257,14 @@ export function snipCompactIfNeeded( // side. Mirrors the user-message .every() guard: if any tool_use in this // turn still has a surviving result, keep the message to avoid orphaning it. if (msg?.type === 'assistant' && Array.isArray(msg?.message?.content)) { - const blocks = msg.message.content as any[] - const toolUses = blocks.filter(b => b?.type === 'tool_use') + const assistantBlocks = msg.message.content as unknown[] + const toolUses = assistantBlocks.filter((b): b is { type: string; id?: string } => + typeof b === 'object' && b !== null && (b as { type?: unknown }).type === 'tool_use', + ) if ( toolUses.length > 0 && - toolUses.length === blocks.length && - toolUses.every((t: any) => snippedResultToolUseIds.has(t?.id)) + toolUses.length === assistantBlocks.length && + toolUses.every((t) => typeof t.id === 'string' && snippedResultToolUseIds.has(t.id)) ) { tokensFreed += estimateTokens(msg) if (msg?.uuid) removedUuids.add(msg.uuid as UUID) @@ -252,9 +280,9 @@ export function snipCompactIfNeeded( return { messages, tokensFreed: 0 } } - const boundaryMessage = { + const boundaryMessage: SnipMessage = { type: 'system' as const, - subtype: 'snip_boundary', + subtype: 'snip_boundary' as const, content: 'Conversation history snipped', isMeta: false as const, timestamp: new Date().toISOString(), @@ -272,7 +300,7 @@ export function snipCompactIfNeeded( } export function isSnipMarkerMessage(message: unknown): boolean { - return (message as any)?.subtype === 'snip_boundary' + return (message as { subtype?: unknown } | null | undefined)?.subtype === 'snip_boundary' } /** Exposed for test isolation only — do not call in production code. */ From 5d30a767f783890698e24a2d826a5abbf9e43eac Mon Sep 17 00:00:00 2001 From: Gravirei Date: Sat, 5 Sep 2026 10:35:50 +0600 Subject: [PATCH 07/15] chore(types): remove 3 eslint-disable no-explicit-any suppressions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tighten three small call sites where the `any` was used as a lazy escape hatch and a real type was reachable in scope. - src/main.tsx: `(global as any).require('inspector')` becomes `(global as unknown as { require: (s: string) => { url: () => string } })`. The dynamic require is being phased out, but while it remains the narrow structural cast documents the shape we actually consume. - src/ink/reconciler.ts: catch (error: any) → catch (error: unknown); `error.code === 'ERR_MODULE_NOT_FOUND'` accesses the code via `(error as { code?: unknown }).code`. - src/components/wizard/WizardProvider.tsx: `createContext | null>(null)` → `createContext(null)`. The `WizardContextValue` generic already defaults to `Record`, so the `` was redundant; consumers that need a tighter type can supply it via `useContext>`. Note: most remaining lint warnings come from file-level `/* eslint-disable @typescript-eslint/no-explicit-any */` blocks at the top of do-not-touch files (src/types/{message,utils,tools}.ts, src/constants/querySource.ts, src/entrypoints/sdk/controlTypes.ts) and from public SDK surface (src/entrypoints/sdk{,.d.ts,v2.ts}). Both are intentional per the plan. Verification: - bun run typecheck clean --- reports/any-usage.baseline.json | 4 ++-- src/components/wizard/WizardProvider.tsx | 6 +++--- src/ink/reconciler.ts | 5 ++--- src/main.tsx | 3 +-- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/reports/any-usage.baseline.json b/reports/any-usage.baseline.json index 23b12f5421..76070d8268 100644 --- a/reports/any-usage.baseline.json +++ b/reports/any-usage.baseline.json @@ -1,4 +1,4 @@ { - "total": 1031, - "generatedAt": "2026-09-04T16:06:16.866Z" + "total": 916, + "generatedAt": "2026-09-05T04:02:11.155Z" } diff --git a/src/components/wizard/WizardProvider.tsx b/src/components/wizard/WizardProvider.tsx index 5155d83def..ceb01164e0 100644 --- a/src/components/wizard/WizardProvider.tsx +++ b/src/components/wizard/WizardProvider.tsx @@ -3,9 +3,9 @@ import React, { createContext, type ReactNode, useCallback, useEffect, useMemo, import { useExitOnCtrlCDWithKeybindings } from '../../hooks/useExitOnCtrlCDWithKeybindings.js'; import type { WizardContextValue, WizardProviderProps } from './types.js'; -// Use any here for the context since it will be cast properly when used -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const WizardContext = createContext | null>(null); +// Use the default generic (Record) for the context value; consumers +// that need a tighter type can supply it via useContext>. +export const WizardContext = createContext(null); export function WizardProvider = Record>(t0: WizardProviderProps): React.ReactNode { const $ = _c(38); const { diff --git a/src/ink/reconciler.ts b/src/ink/reconciler.ts index 3357fcbf30..fa03f192d5 100644 --- a/src/ink/reconciler.ts +++ b/src/ink/reconciler.ts @@ -34,9 +34,8 @@ if (process.env.NODE_ENV === 'development') { try { // eslint-disable-next-line custom-rules/no-top-level-dynamic-import -- dev-only; NODE_ENV check is DCE'd in production void import('./devtools.js') - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (error: any) { - if (error.code === 'ERR_MODULE_NOT_FOUND') { + } catch (error: unknown) { + if ((error as { code?: unknown }).code === 'ERR_MODULE_NOT_FOUND') { // biome-ignore lint/suspicious/noConsole: intentional warning console.warn( ` diff --git a/src/main.tsx b/src/main.tsx index 70176514eb..287ceaea51 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -261,8 +261,7 @@ function isBeingDebugged() { // Check if inspector is available and active (indicates debugging) try { // Dynamic import would be better but is async - use global object instead - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const inspector = (global as any).require('inspector'); + const inspector = (global as unknown as { require: (s: string) => { url: () => string } }).require('inspector'); const hasInspectorUrl = !!inspector.url(); return hasInspectorUrl || hasInspectArg || hasInspectEnv; } catch { From 518190401b4cf0a73438b56a8395044fd3ff633f Mon Sep 17 00:00:00 2001 From: Gravirei Date: Sat, 5 Sep 2026 10:36:19 +0600 Subject: [PATCH 08/15] chore(types): refresh any-usage baseline --- reports/any-usage.baseline.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reports/any-usage.baseline.json b/reports/any-usage.baseline.json index 76070d8268..7a9dc125f1 100644 --- a/reports/any-usage.baseline.json +++ b/reports/any-usage.baseline.json @@ -1,4 +1,4 @@ { - "total": 916, - "generatedAt": "2026-09-05T04:02:11.155Z" + "total": 913, + "generatedAt": "2026-09-05T04:36:18.976Z" } From 0738ee94ce5647d4b9e012a62ccf8aed1ddfd40a Mon Sep 17 00:00:00 2001 From: Gravirei Date: Sat, 5 Sep 2026 10:59:29 +0600 Subject: [PATCH 09/15] fix(types): address PR review feedback on any-reduction tooling - Pin eslint to resolved version (9.39.5) so lint warning totals stay stable across lockfile updates. - Wire lint:any-budget into the regular `check` gate (was only in check:strict), so any-usage regressions block PRs. - any-usage-report: use lstat and skip symlinks so a self-referencing link under src/tests/scripts can't stall the budget scan. - any-usage-report --check: exit nonzero when the baseline file is missing or invalid so the gate can't be silently disabled. - Remove dangling references to docs/plans/imperative-hugging-sphinx.md from eslint.config.js and scripts/any-usage-report.ts headers. - mcp.ts: defensively skip non-object tool-output entries (primitives / null) instead of crashing on boundary data. - messages.ts: realign the mis-indented tool_search-off branch so the blockExtra + return block reads at the right level. No budget delta (913 -> 913). Typecheck, lint, budget, and the affected unit tests all green. Co-Authored-By: Claude Opus 4.6 --- eslint.config.js | 3 +-- package.json | 4 ++-- reports/any-usage.baseline.json | 2 +- scripts/any-usage-report.ts | 18 ++++++++++++------ src/entrypoints/mcp.ts | 25 ++++++++++++------------- src/utils/messages.ts | 4 ++-- 6 files changed, 30 insertions(+), 26 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index fcb2ef86ee..11ea6a3f2c 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,6 +1,5 @@ // Minimal flat config — only the rule we need to track `any` usage. -// Step 1 of a multi-phase plan to reduce `any` types safely. -// See plan: docs/plans/imperative-hugging-sphinx.md +// Part of the phased `any`-reduction work landing on fix/reduce-any-types. // // Note: the repo has many existing `// eslint-disable custom-rules/` // comments referencing rules from a previous (now-removed) custom plugin. diff --git a/package.json b/package.json index f5cabcc252..1325a4f537 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "deadcode": "knip --include files,dependencies", "lint": "eslint .", "lint:any-budget": "bun run scripts/any-usage-report.ts --check", - "check": "bun run smoke && bun run deadcode && bun run test:full", + "check": "bun run lint:any-budget && bun run smoke && bun run deadcode && bun run test:full", "check:strict": "bun run typecheck && bun run lint && bun run lint:any-budget && bun run smoke && bun run deadcode && bun run test:full", "verify:privacy": "bun run scripts/verify-no-phone-home.ts", "install:verify": "bun run scripts/verify-clean-install.ts", @@ -122,7 +122,7 @@ "duck-duck-scrape": "^2.2.7", "emoji-regex": "10.6.0", "env-paths": "3.0.0", - "eslint": "9", + "eslint": "9.39.5", "execa": "9.6.1", "fflate": "0.8.2", "figures": "6.1.0", diff --git a/reports/any-usage.baseline.json b/reports/any-usage.baseline.json index 7a9dc125f1..04fd1a6e75 100644 --- a/reports/any-usage.baseline.json +++ b/reports/any-usage.baseline.json @@ -1,4 +1,4 @@ { "total": 913, - "generatedAt": "2026-09-05T04:36:18.976Z" + "generatedAt": "2026-09-05T04:59:12.204Z" } diff --git a/scripts/any-usage-report.ts b/scripts/any-usage-report.ts index 90dd6d89b2..d2750adffe 100644 --- a/scripts/any-usage-report.ts +++ b/scripts/any-usage-report.ts @@ -2,8 +2,7 @@ // Produces reports/any-usage.{json,md} and a budget gate against // reports/any-usage.baseline.json. // -// Step 2 of a multi-phase plan to reduce `any` types safely. -// See plan: docs/plans/imperative-hugging-sphinx.md +// Part of the phased `any`-reduction work landing on fix/reduce-any-types. import { mkdir, readFile, writeFile } from 'fs/promises' import { resolve } from 'path' @@ -61,7 +60,7 @@ function emptyCategory(): CategoryCounts { } async function walk(dir: string, out: string[]): Promise { - const { readdir, stat } = await import('fs/promises') + const { readdir, lstat } = await import('fs/promises') let entries: string[] try { entries = await readdir(dir) @@ -71,7 +70,10 @@ async function walk(dir: string, out: string[]): Promise { for (const name of entries) { if (EXCLUDE_DIR_NAMES.has(name)) continue const full = resolve(dir, name) - const st = await stat(full) + // Use lstat so we don't follow symlinks — a self-referencing link would + // recurse forever and stall the budget check. + const st = await lstat(full) + if (st.isSymbolicLink()) continue if (st.isDirectory()) { await walk(full, out) } else if (/\.tsx?$/.test(name)) { @@ -202,13 +204,17 @@ async function main(): Promise { } if (checkMode) { - if (baselineTotal !== null && report.total > baselineTotal) { + if (baselineTotal === null) { + console.error(`any-usage baseline is missing or invalid: ${BASELINE_PATH}`) + process.exit(1) + } + if (report.total > baselineTotal) { console.error( `any-usage budget exceeded: current=${report.total} > baseline=${baselineTotal} (delta=+${report.total - baselineTotal})`, ) process.exit(1) } - console.log(`any-usage budget ok: current=${report.total} baseline=${baselineTotal ?? 'unset'}`) + console.log(`any-usage budget ok: current=${report.total} baseline=${baselineTotal}`) return } diff --git a/src/entrypoints/mcp.ts b/src/entrypoints/mcp.ts index 7b3e8bd348..5b0fb7ba08 100644 --- a/src/entrypoints/mcp.ts +++ b/src/entrypoints/mcp.ts @@ -202,20 +202,19 @@ export async function startMCPServer( if (typeof data === 'string') { content = [{ type: 'text', text: data }] } else if (Array.isArray(data)) { - content = data.map((block: { type: string; text?: string; source?: { type: string; media_type: string; data: string } }) => { - if (block.type === 'text') { - return { type: 'text', text: block.text || '' } - } else if (block.type === 'image' && block.source) { - return { - type: 'image', - data: block.source.data, - mimeType: block.source.media_type, - } - } else { - // eslint-disable-next-line custom-rules/no-top-level-side-effects, no-console - console.warn(`Unmapped content block type from tool ${name}: ${block.type || 'unknown'}`) - return { type: 'text', text: jsonStringify(block) } + content = data.flatMap((block: unknown) => { + // Boundary data — defensively skip primitives/null instead of crashing. + if (!block || typeof block !== 'object') return [] + const b = block as { type?: unknown; text?: unknown; source?: { type?: unknown; media_type?: unknown; data?: unknown } } + if (b.type === 'text') { + return [{ type: 'text', text: String(b.text ?? '') } as CallToolResult['content'][number]] + } + if (b.type === 'image' && b.source && typeof b.source.data === 'string' && typeof b.source.media_type === 'string') { + return [{ type: 'image', data: b.source.data, mimeType: b.source.media_type } as CallToolResult['content'][number]] } + // eslint-disable-next-line custom-rules/no-top-level-side-effects, no-console + console.warn(`Unmapped content block type from tool ${name}: ${String(b.type ?? 'unknown')}`) + return [{ type: 'text', text: jsonStringify(block) } as CallToolResult['content'][number]] }) as CallToolResult['content'] } else { content = [{ type: 'text', text: jsonStringify(data) }] diff --git a/src/utils/messages.ts b/src/utils/messages.ts index b001b3fa6f..a2cf59888e 100644 --- a/src/utils/messages.ts +++ b/src/utils/messages.ts @@ -1758,8 +1758,8 @@ export function normalizeMessagesForAPI( // When tool search is NOT enabled, explicitly construct tool_use // block with only standard API fields to avoid sending fields like // 'caller' that may be stored in sessions from tool search runs - const blockExtra = (block as unknown as { extra_content?: unknown }).extra_content - return { + const blockExtra = (block as unknown as { extra_content?: unknown }).extra_content + return { type: 'tool_use' as const, id: block.id, name: canonicalName, From 42f79d1db3613ca86fadee5d1c3c7071f1c7d098 Mon Sep 17 00:00:00 2001 From: Gravirei Date: Sat, 5 Sep 2026 12:40:11 +0600 Subject: [PATCH 10/15] chore(types): narrow any in src/grpc/server.ts (5 warnings) - protoDescriptor cast: replace `as any` with a structural type that only declares the AgentService shape we actually use. - handleChat: ServerDuplexStream -> ; the clientMessage is narrowed locally where it's read. - previousMessages: any[] -> Message[] (already imported, already used as the type of this.sessions). - catch (err: any) -> catch (err); err.message is now gated behind an instanceof Error check. Typecheck, lint, budget, and the 2 grpc unit tests all green. Lint 728 -> 723 (-5). Budget 913 -> 909 (-4). Co-Authored-By: Claude Opus 4.6 --- src/grpc/server.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/grpc/server.ts b/src/grpc/server.ts index 6d8f3fae47..127565aee0 100644 --- a/src/grpc/server.ts +++ b/src/grpc/server.ts @@ -20,7 +20,15 @@ const packageDefinition = protoLoader.loadSync(PROTO_PATH, { oneofs: true, }) -const protoDescriptor = grpc.loadPackageDefinition(packageDefinition) as any +// proto-loader returns a deeply-nested untyped shape; we only need the AgentService +// definition from the OpenClaude v1 package, so narrow to that. +const protoDescriptor = grpc.loadPackageDefinition(packageDefinition) as unknown as { + openclaude: { + v1: { + AgentService: { service: grpc.ServiceDefinition> } + } + } +} const openclaudeProto = protoDescriptor.openclaude.v1 const MAX_SESSIONS = 1000 @@ -108,7 +116,7 @@ export class GrpcServer { ) } - private handleChat(call: grpc.ServerDuplexStream) { + private handleChat(call: grpc.ServerDuplexStream) { let engine: QueryEngine | null = null let appState: AppState = getDefaultAppState() const fileCache: FileStateCache = new FileStateCache(READ_FILE_STATE_CACHE_SIZE, 25 * 1024 * 1024) @@ -117,7 +125,7 @@ export class GrpcServer { const pendingRequests = new Map void>() // Accumulated messages from previous turns for multi-turn context - let previousMessages: any[] = [] + let previousMessages: Message[] = [] let sessionId = '' let interrupted = false @@ -293,11 +301,11 @@ export class GrpcServer { } call.end() } - } catch (err: any) { + } catch (err) { console.error('Error processing stream') call.write({ error: { - message: err.message || "Internal server error", + message: err instanceof Error ? err.message : "Internal server error", code: "INTERNAL" } }) From 6bd765d9f2f5e5d04af75e29d52f3996bdfb3c6c Mon Sep 17 00:00:00 2001 From: Gravirei Date: Sat, 5 Sep 2026 12:52:54 +0600 Subject: [PATCH 11/15] chore(types): narrow any in src/utils/knowledgeGraph.ts (18 warnings) Add explicit LegacyEntity / LegacyRelation / LegacySummary / LegacyData types so the migration path stops round-tripping through `any`. - LegacySource.data, mergeLegacySources, normalizeLegacyData, readLegacySqliteStore, SqliteReadResult.data, and doMigration all take the new LegacyData shape. - LegacyData.entities is typed `Record | LegacyEntity[]` because older JSON dumps stored entities as an array. normalizeLegacyData converts the array form to the record form so downstream code can keep using Object.entries. (Old loop relied on the same `id ?? entryKey` fallback, so behavior is preserved; verified by all 20 unit tests.) - SQLite row accessor now returns `Record` (was `any[]`); per-row readers narrow `row.id`, `row.attributes`, `row.keywords`, `row.content` from `unknown` and the existing typeof guards preserve the previous fail-soft behavior on malformed rows. Lint 723 -> 705 (-18). Budget 909 -> 894 (-15). All 20 knowledgeGraph unit tests pass; typecheck clean. Co-Authored-By: Claude Opus 4.6 --- reports/any-usage.baseline.json | 4 +- src/utils/knowledgeGraph.ts | 134 ++++++++++++++++++++++++-------- 2 files changed, 104 insertions(+), 34 deletions(-) diff --git a/reports/any-usage.baseline.json b/reports/any-usage.baseline.json index 04fd1a6e75..0ef80d6f84 100644 --- a/reports/any-usage.baseline.json +++ b/reports/any-usage.baseline.json @@ -1,4 +1,4 @@ { - "total": 913, - "generatedAt": "2026-09-05T04:59:12.204Z" + "total": 894, + "generatedAt": "2026-09-05T06:52:43.545Z" } diff --git a/src/utils/knowledgeGraph.ts b/src/utils/knowledgeGraph.ts index 2839ed01ed..23e6b481ed 100644 --- a/src/utils/knowledgeGraph.ts +++ b/src/utils/knowledgeGraph.ts @@ -141,10 +141,43 @@ interface LegacySource { path: string kind: 'json' | 'sqlite' mtimeMs: number - data: any + data: LegacyData artifactBytes: Map } +// Legacy knowledge-graph shapes (JSON stores written by older OpenClaude +// versions and the SQLite store read at line ~400). All field types are +// `unknown` because the on-disk format predates our schema; helpers in this +// file sanitize as they read. +type LegacyEntity = { + id?: unknown + name?: unknown + type?: unknown + attributes?: Record +} +type LegacyRelation = { + sourceId?: unknown + targetId?: unknown + type?: unknown +} +type LegacySummary = { + id?: unknown + content?: unknown + keywords?: unknown + timestamp?: unknown +} +// Some older JSON dumps stored entities as an array; the SQLite reader +// produces a record keyed by id. Both shapes are normalized to the +// `Record` form before `mergeLegacySources` consumes them. +type LegacyEntityBag = Record | LegacyEntity[] +type LegacyData = { + entities: LegacyEntityBag + relations: LegacyRelation[] + summaries: LegacySummary[] + rules: string[] + _droppedEntityAliases?: Map +} + function getLegacySourceMtime(path: string, kind: LegacySource['kind']): number { let mtimeMs = statSync(path).mtimeMs if (kind === 'sqlite') { @@ -219,12 +252,29 @@ function sqliteDataArtifactsMatch( }) } -function normalizeLegacyData(value: any): any { +function normalizeLegacyData(value: unknown): LegacyData { + const v = value as Partial | null | undefined + const rawEntities = v?.entities + let entities: LegacyEntityBag = {} + if (Array.isArray(rawEntities)) { + const out: Record = {} + for (const e of rawEntities) { + if (e && typeof e === 'object') { + const id = (e as LegacyEntity).id + if (typeof id === 'string' || typeof id === 'number') { + out[String(id)] = e as LegacyEntity + } + } + } + entities = out + } else if (rawEntities && typeof rawEntities === 'object') { + entities = rawEntities as Record + } return { - entities: value?.entities && typeof value.entities === 'object' ? value.entities : {}, - relations: Array.isArray(value?.relations) ? value.relations : [], - summaries: Array.isArray(value?.summaries) ? value.summaries : [], - rules: Array.isArray(value?.rules) ? value.rules : [], + entities, + relations: Array.isArray(v?.relations) ? (v.relations as LegacyRelation[]) : [], + summaries: Array.isArray(v?.summaries) ? (v.summaries as LegacySummary[]) : [], + rules: Array.isArray(v?.rules) ? (v.rules as string[]) : [], } } @@ -241,7 +291,7 @@ function isLegacyDataShape(value: unknown): boolean { } /** Merge every recoverable legacy location, preferring the newest copy on conflicts. */ -function mergeLegacySources(sources: LegacySource[]): any { +function mergeLegacySources(sources: LegacySource[]): LegacyData { const merged = normalizeLegacyData(null) const droppedAliases = new Map() const entityNames = new Map() @@ -251,7 +301,7 @@ function mergeLegacySources(sources: LegacySource[]): any { for (const source of [...sources].sort((a, b) => b.mtimeMs - a.mtimeMs)) { const data = normalizeLegacyData(source.data) - for (const [entryKey, rawEntity] of Object.entries(data.entities) as [string, any][]) { + for (const [entryKey, rawEntity] of Object.entries(data.entities)) { if (!rawEntity || typeof rawEntity !== 'object') continue const id = String(rawEntity.id ?? entryKey) const nameKey = String(rawEntity.name ?? '').trim().toLowerCase() @@ -394,18 +444,23 @@ function migrateLegacyKnowledgeGraph(): void { } type SqliteReadResult = - | { ok: true; data: any } + | { ok: true; data: LegacyData } | { ok: false; reason: 'not_found' | 'unavailable' | 'error' } +interface SqliteDb { + close?: () => void +} +type SqliteRow = Record + function readLegacySqliteStore(dbPath: string): SqliteReadResult { if (!existsSync(dbPath)) return { ok: false, reason: 'not_found' } - let openDatabase: () => { db: any; queryAll: (sql: string) => any[] } + let openDatabase: () => { db: SqliteDb; queryAll: (sql: string) => SqliteRow[] } try { const Database = _require('bun:sqlite').Database openDatabase = () => { - const db = new Database(dbPath, { readonly: true }) - return { db, queryAll: sql => db.query(sql).all() as any[] } + const db: { query: (sql: string) => { all: () => SqliteRow[] }; close?: () => void } = new Database(dbPath, { readonly: true }) + return { db, queryAll: sql => db.query(sql).all() } } } catch { try { @@ -414,8 +469,8 @@ function readLegacySqliteStore(dbPath: string): SqliteReadResult { // OpenClaude install can still migrate after the user changes runtimes. const DatabaseSync = _require('node:sqlite').DatabaseSync openDatabase = () => { - const db = new DatabaseSync(dbPath, { readOnly: true }) - return { db, queryAll: sql => db.prepare(sql).all() as any[] } + const db: { prepare: (sql: string) => { all: () => SqliteRow[] }; close?: () => void } = new DatabaseSync(dbPath, { readOnly: true }) + return { db, queryAll: sql => db.prepare(sql).all() } } } catch { console.error( @@ -425,43 +480,58 @@ function readLegacySqliteStore(dbPath: string): SqliteReadResult { } } - let db: any + let db: SqliteDb | null = null try { const opened = openDatabase() db = opened.db const queryAll = opened.queryAll - const data: any = { entities: {}, relations: [], summaries: [], rules: [] } + const data: LegacyData = { entities: {}, relations: [], summaries: [], rules: [] } const entityRows = queryAll('SELECT id, type, name, attributes FROM entities') for (const row of entityRows) { - data.entities[row.id] = { + const id = String(row.id ?? '') + const rawAttrs = row.attributes + data.entities[id] = { id: row.id, type: row.type ?? '', name: row.name ?? '', - attributes: row.attributes ? JSON.parse(row.attributes) : {}, + attributes: + typeof rawAttrs === 'string' && rawAttrs.length > 0 + ? (JSON.parse(rawAttrs) as Record) + : {}, } } - data.relations = queryAll('SELECT source_id, target_id, type FROM relations').map( - (r: any) => ({ sourceId: r.source_id, targetId: r.target_id, type: r.type }), - ) + data.relations = queryAll('SELECT source_id, target_id, type FROM relations').map(r => ({ + sourceId: r.source_id, + targetId: r.target_id, + type: r.type, + })) const summaryRows = queryAll('SELECT id, content, keywords, timestamp FROM summaries') - data.summaries = summaryRows.map((r: any) => ({ - id: r.id, - content: r.content ?? '', - keywords: r.keywords ? JSON.parse(r.keywords) : [], - timestamp: r.timestamp ?? 0, - })) + data.summaries = summaryRows.map(r => { + const rawKeywords = r.keywords + return { + id: r.id, + content: r.content ?? '', + keywords: + typeof rawKeywords === 'string' && rawKeywords.length > 0 + ? (JSON.parse(rawKeywords) as unknown) + : [], + timestamp: r.timestamp ?? 0, + } + }) - data.rules = queryAll('SELECT content FROM rules').map((r: any) => r.content) + data.rules = queryAll('SELECT content FROM rules') + .map(r => r.content) + .filter((c): c is string => typeof c === 'string') return { ok: true, data } } catch (e) { console.error('[knowledgeGraph] Failed to read SQLite store:', e) return { ok: false, reason: 'error' } } finally { - try { db?.close() } catch { /* ignore close failures after read */ } + try { db?.close?.() } catch { /* ignore close failures after read */ } } } @@ -547,7 +617,7 @@ function archiveLegacySources(sources: LegacySource[]): boolean { return true } -function doMigration(data: any, sources: LegacySource[], projectKey: string): void { +function doMigration(data: LegacyData, sources: LegacySource[], projectKey: string): void { // Archive and byte-verify every discovered source before writing facts. If // any root/cwd store cannot be preserved, leave all live stores in place and // retry later rather than completing a partial migration. @@ -590,7 +660,7 @@ function doMigration(data: any, sources: LegacySource[], projectKey: string): vo // into the YAML title and body (P1). The old fact extractor stored raw env // values in both attributes and names. const legacyEntities = Object.entries(data.entities ?? {}) - for (const [legacyId, entity] of legacyEntities as [string, any][]) { + for (const [legacyId, entity] of legacyEntities) { const safeName = safeEntityName(entity) if (!safeName) { continue @@ -681,7 +751,7 @@ ${safeRule} } // Preserve legacy relations as a single relation-set fact (remapped using legacyToNewId, H4) - const relations: Relation[] = (data.relations ?? []).flatMap((r: any) => { + const relations: Relation[] = (data.relations ?? []).flatMap((r: LegacyRelation) => { const rawSourceId = String(r.sourceId ?? '') const rawTargetId = String(r.targetId ?? '') const sourceId = legacyToNewId.get(rawSourceId) || sanitizeLegacyReference(rawSourceId) From 706d98877e4e7fcc4f4a09433f7841a3be363c6b Mon Sep 17 00:00:00 2001 From: Gravirei Date: Sat, 5 Sep 2026 13:04:55 +0600 Subject: [PATCH 12/15] chore(types): narrow SDK tool() generic and handler signature (12 warnings) The public SDK MCP tool() helper previously defaulted its Schema generic to `any` and typed handler args as `any`. Switch both to `unknown`: - SdkMcpToolDefinition - tool with handler (args: Schema, extra: unknown) - annotations?: any -> ToolAnnotations (imported from MCP SDK types) This is a strictly tighter API: callers who pass a concrete schema get the same behavior; callers who relied on implicit any now have to validate handler args (which they should have been doing anyway). All 4 SDK MCP tool wiring tests pass; typecheck clean. Lint 705 -> 695 (-10). Budget 894 -> 884 (-10). Co-Authored-By: Claude Opus 4.6 --- reports/any-usage.baseline.json | 4 ++-- src/entrypoints/sdk.d.ts | 13 +++++++------ src/entrypoints/sdk/index.ts | 4 ++-- src/entrypoints/sdk/v2.ts | 4 ++-- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/reports/any-usage.baseline.json b/reports/any-usage.baseline.json index 0ef80d6f84..aa12a18533 100644 --- a/reports/any-usage.baseline.json +++ b/reports/any-usage.baseline.json @@ -1,4 +1,4 @@ { - "total": 894, - "generatedAt": "2026-09-05T06:52:43.545Z" + "total": 884, + "generatedAt": "2026-09-05T07:04:55.604Z" } diff --git a/src/entrypoints/sdk.d.ts b/src/entrypoints/sdk.d.ts index 5dfcbceb8f..9f66097762 100644 --- a/src/entrypoints/sdk.d.ts +++ b/src/entrypoints/sdk.d.ts @@ -186,6 +186,7 @@ export type SessionMessage = { // Re-export precise SDK message types from generated types // These use camelCase field names and discriminated unions for full IntelliSense +import type { ToolAnnotations } from '@modelcontextprotocol/sdk/dist/esm/types.js' import type { AccountInfo, AgentInfo, @@ -441,12 +442,12 @@ export interface SDKSession { // MCP tool types // ============================================================================ -export interface SdkMcpToolDefinition { +export interface SdkMcpToolDefinition { name: string description: string inputSchema: Schema - handler: (args: any, extra: unknown) => Promise - annotations?: any + handler: (args: Schema, extra: unknown) => Promise + annotations?: ToolAnnotations searchHint?: string alwaysLoad?: boolean } @@ -525,13 +526,13 @@ export function unstable_v2_prompt( // MCP tool functions // ============================================================================ -export function tool( +export function tool( name: string, description: string, inputSchema: Schema, - handler: (args: any, extra: unknown) => Promise, + handler: (args: Schema, extra: unknown) => Promise, extras?: { - annotations?: any + annotations?: ToolAnnotations searchHint?: string alwaysLoad?: boolean }, diff --git a/src/entrypoints/sdk/index.ts b/src/entrypoints/sdk/index.ts index cda612f82c..324d6fec26 100644 --- a/src/entrypoints/sdk/index.ts +++ b/src/entrypoints/sdk/index.ts @@ -168,11 +168,11 @@ export { * ) * ``` */ -export function tool( +export function tool( name: string, description: string, inputSchema: Schema, - handler: (args: any, extra: unknown) => Promise, + handler: (args: Schema, extra: unknown) => Promise, extras?: { annotations?: ToolAnnotations searchHint?: string diff --git a/src/entrypoints/sdk/v2.ts b/src/entrypoints/sdk/v2.ts index 778925b23d..d7f155da31 100644 --- a/src/entrypoints/sdk/v2.ts +++ b/src/entrypoints/sdk/v2.ts @@ -178,11 +178,11 @@ export type SDKResultMessage = GeneratedSDKResultMessage * These definitions can be passed to `createSdkMcpServer()` to register * custom MCP tools. */ -export interface SdkMcpToolDefinition { +export interface SdkMcpToolDefinition { name: string description: string inputSchema: Schema - handler: (args: any, extra: unknown) => Promise + handler: (args: Schema, extra: unknown) => Promise annotations?: ToolAnnotations searchHint?: string alwaysLoad?: boolean From 28800430244534369bd46e8e414d6870e828ea12 Mon Sep 17 00:00:00 2001 From: Gravirei Date: Sat, 5 Sep 2026 14:12:45 +0600 Subject: [PATCH 13/15] fix(types): address PR feedback on SDK + grpc type hardening Apply 4 of 5 CodeRabbit review items (skipped item 5: SDK resume flow JSONL validation, deferred as larger refactor). - sdk.d.ts: switch MCP type imports to the package's public `@modelcontextprotocol/sdk/types.js` subpath (was the internal dist/esm/types.js path). - sdk.d.ts: handler return Promise -> Promise in both SdkMcpToolDefinition and the tool() helper, matching the runtime contract and the sibling declarations in sdk/index.ts and sdk/v2.ts. - grpc/server.ts: introduce a local ClientMessage interface (matching the openclaude.proto oneof payload) and type the duplex-stream request as ServerDuplexStream. The handler body was already reading these fields by name; the type now documents them. - grpc/server.ts: ServiceDefinition generic was being passed a ServerDuplexStream, which isn't a valid T (T must be a record keyed by method name). Switch to ServiceDefinition<{ Chat: never }> so the call to addService accepts a real implementation-map key. Typecheck, lint, budget, and the 6 affected unit tests all green. Lint 695 -> 693 (-2). Budget 884 -> 882 (-2). --- reports/any-usage.baseline.json | 4 ++-- src/entrypoints/sdk.d.ts | 6 +++--- src/grpc/server.ts | 21 ++++++++++++++++++--- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/reports/any-usage.baseline.json b/reports/any-usage.baseline.json index aa12a18533..34175a3aa4 100644 --- a/reports/any-usage.baseline.json +++ b/reports/any-usage.baseline.json @@ -1,4 +1,4 @@ { - "total": 884, - "generatedAt": "2026-09-05T07:04:55.604Z" + "total": 882, + "generatedAt": "2026-09-05T08:12:45.129Z" } diff --git a/src/entrypoints/sdk.d.ts b/src/entrypoints/sdk.d.ts index 9f66097762..87353589ef 100644 --- a/src/entrypoints/sdk.d.ts +++ b/src/entrypoints/sdk.d.ts @@ -186,7 +186,7 @@ export type SessionMessage = { // Re-export precise SDK message types from generated types // These use camelCase field names and discriminated unions for full IntelliSense -import type { ToolAnnotations } from '@modelcontextprotocol/sdk/dist/esm/types.js' +import type { ToolAnnotations, CallToolResult } from '@modelcontextprotocol/sdk/types.js' import type { AccountInfo, AgentInfo, @@ -446,7 +446,7 @@ export interface SdkMcpToolDefinition { name: string description: string inputSchema: Schema - handler: (args: Schema, extra: unknown) => Promise + handler: (args: Schema, extra: unknown) => Promise annotations?: ToolAnnotations searchHint?: string alwaysLoad?: boolean @@ -530,7 +530,7 @@ export function tool( name: string, description: string, inputSchema: Schema, - handler: (args: Schema, extra: unknown) => Promise, + handler: (args: Schema, extra: unknown) => Promise, extras?: { annotations?: ToolAnnotations searchHint?: string diff --git a/src/grpc/server.ts b/src/grpc/server.ts index 127565aee0..fe57207f46 100644 --- a/src/grpc/server.ts +++ b/src/grpc/server.ts @@ -21,16 +21,31 @@ const packageDefinition = protoLoader.loadSync(PROTO_PATH, { }) // proto-loader returns a deeply-nested untyped shape; we only need the AgentService -// definition from the OpenClaude v1 package, so narrow to that. +// definition from the OpenClaude v1 package, so narrow to that. The implementation +// map declares the method names; ServiceDefinition requires T to be a record +// keyed by method name. const protoDescriptor = grpc.loadPackageDefinition(packageDefinition) as unknown as { openclaude: { v1: { - AgentService: { service: grpc.ServiceDefinition> } + AgentService: { service: grpc.ServiceDefinition<{ Chat: never }> } } } } const openclaudeProto = protoDescriptor.openclaude.v1 +// Shape of a single client message on the duplex Chat stream. Matches the +// `oneof` payload in openclaude.proto (ChatRequest | ToolInput | CancelRequest). +type ClientMessage = { + request?: { + session_id?: unknown + message?: unknown + working_directory?: unknown + model?: unknown + } | null + input?: { prompt_id?: unknown; reply?: unknown } | null + cancel?: unknown +} + const MAX_SESSIONS = 1000 type TextDeltaEvent = { @@ -116,7 +131,7 @@ export class GrpcServer { ) } - private handleChat(call: grpc.ServerDuplexStream) { + private handleChat(call: grpc.ServerDuplexStream) { let engine: QueryEngine | null = null let appState: AppState = getDefaultAppState() const fileCache: FileStateCache = new FileStateCache(READ_FILE_STATE_CACHE_SIZE, 25 * 1024 * 1024) From f5fa12f752140f245c7515439728028f3ddae404 Mon Sep 17 00:00:00 2001 From: Gravirei Date: Sun, 6 Sep 2026 08:10:37 +0600 Subject: [PATCH 14/15] fix(types): address 6 reviewer findings on PR #2208 Six findings (four P2, two P3) from the second review pass. 1. knowledgeGraph legacy array normalization: previously dropped entries whose `id` was neither string nor number, breaking recovery for idless array entries. Use the array index as the fallback key so mergeLegacySources' existing `id ?? entryKey` logic continues to recover the entry. All 20 unit tests still pass. 2. SDK tool() generic conflated schema descriptor with invocation value type, breaking the test fixture (and any external consumer) that declares the handler with a concrete value type. Restore `args: any` for the handler across the factory, SdkMcpToolDefinition, and the .d.ts declaration; keep `Schema` as the generic for inputSchema typing. 3. sdk.d.ts now declares ToolAnnotations and CallToolResult locally instead of importing from @modelcontextprotocol/sdk, preserving the optional peer contract. A consumer without MCP installed no longer gets TS2307 on a no-op SDKSession type import. 4. any-usage-report now strips comments, string literals, and template-literal contents before applying the annotation regex, so `// example: any`, `': any'`, and similar non-type occurrences no longer count. @ts-ignore / @ts-expect-error / @ts-nocheck directives are preserved for the ignore category. Budget went from 882 to 776. 5. generate-sdk-types: schemas module cast as Record unknown> was wrong because HOOK_EVENTS is a non-callable; cast as Record and narrow per-access. def.getter() and Reflect.get(schema, 'description') both narrowed to a callable / object shape before use. Generated output unchanged (diff empty). 6. any-usage-report walk(): readdir failures now propagate instead of being silently swallowed, so a partial scan cannot publish a too-low replacement baseline or pass --check. Typecheck, lint, budget, and 26 affected unit tests all green. --- reports/any-usage.baseline.json | 4 +- scripts/any-usage-report.ts | 113 +++++++++++++++++++++++++++++++- scripts/generate-sdk-types.ts | 18 +++-- src/entrypoints/sdk.d.ts | 25 ++++++- src/entrypoints/sdk/index.ts | 2 +- src/entrypoints/sdk/v2.ts | 2 +- src/utils/knowledgeGraph.ts | 15 +++-- 7 files changed, 158 insertions(+), 21 deletions(-) diff --git a/reports/any-usage.baseline.json b/reports/any-usage.baseline.json index 34175a3aa4..7aa7e75022 100644 --- a/reports/any-usage.baseline.json +++ b/reports/any-usage.baseline.json @@ -1,4 +1,4 @@ { - "total": 882, - "generatedAt": "2026-09-05T08:12:45.129Z" + "total": 776, + "generatedAt": "2026-09-06T02:05:01.403Z" } diff --git a/scripts/any-usage-report.ts b/scripts/any-usage-report.ts index d2750adffe..25bce93ecd 100644 --- a/scripts/any-usage-report.ts +++ b/scripts/any-usage-report.ts @@ -64,8 +64,11 @@ async function walk(dir: string, out: string[]): Promise { let entries: string[] try { entries = await readdir(dir) - } catch { - return + } catch (err) { + // Propagate enumeration failures so a partial scan doesn't silently + // report a too-low count. The caller wraps this in a try and exits + // nonzero with the original error. + throw new Error(`readdir failed for ${dir}: ${err instanceof Error ? err.message : String(err)}`) } for (const name of entries) { if (EXCLUDE_DIR_NAMES.has(name)) continue @@ -82,6 +85,107 @@ async function walk(dir: string, out: string[]): Promise { } } +// Replace the contents of comments, strings, and template literals with +// spaces of equal length so the type-annotation regex only matches real +// TypeScript code. Newlines and offsets are preserved so report line numbers +// stay correct. +function stripNonCode(source: string): string { + let out = '' + let i = 0 + const n = source.length + while (i < n) { + const c = source[i] + const c2 = i + 1 < n ? source[i + 1] : '' + // Block comment /* ... */ — preserve @ts-ignore markers in IGNORE_RE + // by NOT stripping the directive prefix; IGNORE_RE only matches + // `// @ts-...` (line) so block comments are safe to fully strip. + if (c === '/' && c2 === '*') { + const end = source.indexOf('*/', i + 2) + if (end === -1) { + out += ' '.repeat(n - i) + i = n + break + } + // Replace with spaces, keep newlines so line numbers stay stable. + for (let j = i; j < end + 2; j++) { + out += source[j] === '\n' ? '\n' : ' ' + } + i = end + 2 + continue + } + // Line comment — keep `// @ts-...` markers for IGNORE_RE; strip the + // rest of the line. This preserves the existing ignore-directive + // accounting while still excluding `: any` in prose. + if (c === '/' && c2 === '/') { + // Find next newline + let end = i + while (end < n && source[end] !== '\n') end++ + const segment = source.slice(i, end) + const isDirective = /^\s*\/\/\s*@(ts-ignore|ts-expect-error|ts-nocheck)\b/.test(segment) + if (isDirective) { + out += segment + } else { + out += ' '.repeat(segment.length) + } + i = end + continue + } + // String literals + if (c === '"' || c === "'") { + const quote = c + let end = i + 1 + while (end < n) { + if (source[end] === '\\' && end + 1 < n) { + end += 2 + continue + } + if (source[end] === quote) { + end++ + break + } + if (source[end] === '\n') { + // Unterminated string — bail and keep the rest verbatim. + break + } + end++ + } + out += ' '.repeat(end - i) + i = end + continue + } + // Template literal — simpler handling: strip ${...} interpolation + // contents as well, since the inner expression has its own type scope. + if (c === '`') { + let end = i + 1 + while (end < n && source[end] !== '`') { + if (source[end] === '\\' && end + 1 < n) { + end += 2 + continue + } + if (source[end] === '$' && source[end + 1] === '{') { + // Strip interpolation body + let depth = 1 + end += 2 + while (end < n && depth > 0) { + if (source[end] === '{') depth++ + else if (source[end] === '}') depth-- + if (depth > 0) end++ + } + continue + } + end++ + } + if (end < n) end++ // consume closing ` + out += ' '.repeat(end - i) + i = end + continue + } + out += c + i++ + } + return out +} + function countMatches(source: string, re: RegExp): number { // Rebuild with `g` for counting while preserving the anchored pattern. const flags = re.flags.replace('g', '') + 'g' @@ -116,7 +220,10 @@ async function scan(): Promise { const found: string[] = [] await walk(root, found) for (const absPath of found) { - const source = await readFile(absPath, 'utf8') + const raw = await readFile(absPath, 'utf8') + // Strip comments, strings, and template literals so `: any` in prose + // and `': any'` literals don't count as type annotations. + const source = stripNonCode(raw) if (!ANNOTATION_RE.test(source) && !CAST_RE.test(source) && !IGNORE_RE.test(source)) { continue } diff --git a/scripts/generate-sdk-types.ts b/scripts/generate-sdk-types.ts index d6e1a26613..b9429dcbed 100644 --- a/scripts/generate-sdk-types.ts +++ b/scripts/generate-sdk-types.ts @@ -57,10 +57,10 @@ const TypeOverrideMap: Record = { // when they appear as fields inside other schemas. const placeholderInstances = new Map() for (const name of Object.keys(TypeOverrideMap)) { - const thunk = (schemas as Record unknown>)[name] - if (typeof thunk === 'function') { + const candidate = (schemas as Record)[name] + if (typeof candidate === 'function') { try { - placeholderInstances.set(thunk(), TypeOverrideMap[name]) + placeholderInstances.set((candidate as () => unknown)(), TypeOverrideMap[name]) } catch { /* ignore */ } } } @@ -310,8 +310,11 @@ function convert(schema: unknown, depth = 0): string { return `${convert(def.innerType, depth)} | null` case 'default': return convert(def.innerType, depth) - case 'lazy': - return convert(def.getter(), depth) + case 'lazy': { + const getter = def.getter + if (typeof getter !== 'function') return 'unknown' + return convert((getter as () => unknown)(), depth) + } case 'transform': case 'effects': return convert(def.schema, depth) @@ -422,7 +425,10 @@ export function generateSdkTypes(): string { try { const ts = convert(schema) // Check for top-level description - const desc = Reflect.get(schema, 'description') as string | undefined + const desc = + schema && typeof schema === 'object' + ? (Reflect.get(schema, 'description') as string | undefined) + : undefined if (desc) { lines.push(`/** ${desc} */`) } diff --git a/src/entrypoints/sdk.d.ts b/src/entrypoints/sdk.d.ts index 87353589ef..4959430009 100644 --- a/src/entrypoints/sdk.d.ts +++ b/src/entrypoints/sdk.d.ts @@ -186,7 +186,26 @@ export type SessionMessage = { // Re-export precise SDK message types from generated types // These use camelCase field names and discriminated unions for full IntelliSense -import type { ToolAnnotations, CallToolResult } from '@modelcontextprotocol/sdk/types.js' +// Note: ToolAnnotations and CallToolResult shapes mirror the MCP SDK but are +// declared locally to keep the public SDK declarations free of the optional +// `@modelcontextprotocol/sdk` peer dependency. They are stable SDK shapes. +type ToolAnnotations = { + title?: string + readOnlyHint?: boolean + destructiveHint?: boolean + idempotentHint?: boolean + openWorldHint?: boolean +} +type CallToolResult = { + content?: Array< + | { type: 'text'; text: string } + | { type: 'image'; data: string; mimeType: string } + | { type: 'resource'; resource: { uri: string; text?: string; mimeType?: string } } + | { type: string; [key: string]: unknown } + > + isError?: boolean + [key: string]: unknown +} import type { AccountInfo, AgentInfo, @@ -446,7 +465,7 @@ export interface SdkMcpToolDefinition { name: string description: string inputSchema: Schema - handler: (args: Schema, extra: unknown) => Promise + handler: (args: any, extra: unknown) => Promise annotations?: ToolAnnotations searchHint?: string alwaysLoad?: boolean @@ -530,7 +549,7 @@ export function tool( name: string, description: string, inputSchema: Schema, - handler: (args: Schema, extra: unknown) => Promise, + handler: (args: any, extra: unknown) => Promise, extras?: { annotations?: ToolAnnotations searchHint?: string diff --git a/src/entrypoints/sdk/index.ts b/src/entrypoints/sdk/index.ts index 324d6fec26..e37149e5f4 100644 --- a/src/entrypoints/sdk/index.ts +++ b/src/entrypoints/sdk/index.ts @@ -172,7 +172,7 @@ export function tool( name: string, description: string, inputSchema: Schema, - handler: (args: Schema, extra: unknown) => Promise, + handler: (args: any, extra: unknown) => Promise, extras?: { annotations?: ToolAnnotations searchHint?: string diff --git a/src/entrypoints/sdk/v2.ts b/src/entrypoints/sdk/v2.ts index d7f155da31..a39d3ffa55 100644 --- a/src/entrypoints/sdk/v2.ts +++ b/src/entrypoints/sdk/v2.ts @@ -182,7 +182,7 @@ export interface SdkMcpToolDefinition { name: string description: string inputSchema: Schema - handler: (args: Schema, extra: unknown) => Promise + handler: (args: any, extra: unknown) => Promise annotations?: ToolAnnotations searchHint?: string alwaysLoad?: boolean diff --git a/src/utils/knowledgeGraph.ts b/src/utils/knowledgeGraph.ts index 23e6b481ed..b1c2adef31 100644 --- a/src/utils/knowledgeGraph.ts +++ b/src/utils/knowledgeGraph.ts @@ -257,13 +257,18 @@ function normalizeLegacyData(value: unknown): LegacyData { const rawEntities = v?.entities let entities: LegacyEntityBag = {} if (Array.isArray(rawEntities)) { + // Array form: mergeLegacySources falls back to `String(id ?? entryKey)`, + // so key the array form by the index to preserve the same fallback + // contract. An entry with an explicit id overrides any previous entry + // with the same id (last-wins) so concurrent stores converge to the + // newest copy; entryKey collisions across arrays are not possible + // because the outer `mergeLegacySources` loop never sees two arrays at + // once. const out: Record = {} - for (const e of rawEntities) { + for (let i = 0; i < rawEntities.length; i++) { + const e = rawEntities[i] if (e && typeof e === 'object') { - const id = (e as LegacyEntity).id - if (typeof id === 'string' || typeof id === 'number') { - out[String(id)] = e as LegacyEntity - } + out[String(i)] = e as LegacyEntity } } entities = out From cd308fa9263b854589cd38ec28d39fd5f48cd774 Mon Sep 17 00:00:00 2001 From: Gravirei Date: Sun, 6 Sep 2026 08:24:26 +0600 Subject: [PATCH 15/15] chore(types): refresh any-usage baseline after upstream rebase Baseline 776 -> 779. The +3 comes from upstream #2196 (Command Code gateway) test mocks merged to main after the baseline was recorded: 2x (payload: any) in ProviderManager.test.tsx, 1x 'as any' in providerProfiles.test.ts. Symptom-level narrowing of another merged PR's test fixtures is out of scope; re-baseline to the rebased tree. --- reports/any-usage.baseline.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reports/any-usage.baseline.json b/reports/any-usage.baseline.json index 7aa7e75022..dc5285de8d 100644 --- a/reports/any-usage.baseline.json +++ b/reports/any-usage.baseline.json @@ -1,4 +1,4 @@ { - "total": 776, - "generatedAt": "2026-09-06T02:05:01.403Z" + "total": 779, + "generatedAt": "2026-09-06T02:24:17.929Z" }