From b1181e455d990ce4533e748f5005153868c48bb0 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Thu, 2 Jul 2026 21:38:33 -0700 Subject: [PATCH 01/23] added code complexity report --- ts/.gitignore | 3 + ts/package.json | 3 +- ts/pnpm-lock.yaml | 587 +++++++++++++++- ts/tools/package.json | 5 +- ts/tools/scripts/code/complexityReport.ts | 771 ++++++++++++++++++++++ 5 files changed, 1358 insertions(+), 11 deletions(-) create mode 100644 ts/tools/scripts/code/complexityReport.ts diff --git a/ts/.gitignore b/ts/.gitignore index f5813553e3..8c3ab39a4c 100644 --- a/ts/.gitignore +++ b/ts/.gitignore @@ -23,6 +23,9 @@ examples/schemaStudio/output/ # Contains actual account names, endpoints, and subdomain names — never commit. tools/scripts/pools.inventory.json +# Generated complexity report output (tools/scripts/code/complexityReport.ts). +tools/scripts/code/complexity-report/ + # Per-developer YAML config overrides loaded by @typeagent/config. # Holds local endpoints, deployment names, and (in Phase 1) secrets. # Never commit. diff --git a/ts/package.json b/ts/package.json index 5fc7276c72..afdf8104a6 100644 --- a/ts/package.json +++ b/ts/package.json @@ -25,6 +25,7 @@ "clean": "fluid-build . -t clean", "cli": "pnpm -C packages/cli run start", "cli:dev": "pnpm -C packages/cli run start:dev", + "code-complexity": "npx tsx tools/scripts/code/complexityReport.ts", "copilot": "node packages/copilot-plugin/scripts/launch.mjs", "copilot:dev": "node packages/copilot-plugin/scripts/launch-dev.mjs", "devtunnel:setup": "node tools/scripts/setup-devtunnel.mjs", @@ -107,7 +108,7 @@ "exifreader", "keytar", "koffi", - "protobufjs", + "onnxruntime-node", "puppeteer", "sharp" ], diff --git a/ts/pnpm-lock.yaml b/ts/pnpm-lock.yaml index 9b051dc504..a7860d6e54 100644 --- a/ts/pnpm-lock.yaml +++ b/ts/pnpm-lock.yaml @@ -935,7 +935,7 @@ importers: version: 12.0.2(webpack@5.105.0) jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.9.3)(ts-node@10.9.2(@types/node@25.9.3)(typescript@5.4.5)) + version: 29.7.0(@types/node@22.19.19)(ts-node@10.9.2(@types/node@22.19.19)(typescript@5.4.5)) prettier: specifier: ^3.5.3 version: 3.5.3 @@ -2695,7 +2695,7 @@ importers: version: 5.4.5 vite: specifier: ^6.4.3 - version: 6.4.3(@types/node@25.9.3)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.3) + version: 6.4.3(@types/node@25.9.3)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.9.0) packages/agents/montage: dependencies: @@ -3596,7 +3596,7 @@ importers: version: 16.5.0 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@22.19.19)(ts-node@10.9.2(@types/node@22.19.19)(typescript@5.4.5)) + version: 29.7.0(@types/node@25.9.3)(ts-node@10.9.2(@types/node@25.9.3)(typescript@5.4.5)) rimraf: specifier: ^6.0.1 version: 6.0.1 @@ -5617,7 +5617,7 @@ importers: version: 26.8.1(dmg-builder@26.8.1) electron-vite: specifier: ^4.0.1 - version: 4.0.1(vite@6.4.3(@types/node@25.9.3)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.0.1(vite@6.4.3(@types/node@25.9.3)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.9.0)) jest: specifier: ^29.7.0 version: 29.7.0(@types/node@25.9.3)(ts-node@10.9.2(@types/node@25.9.3)(typescript@5.4.5)) @@ -5638,7 +5638,7 @@ importers: version: 5.4.5 vite: specifier: ^6.4.3 - version: 6.4.3(@types/node@25.9.3)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.3) + version: 6.4.3(@types/node@25.9.3)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.9.0) packages/studio-service: dependencies: @@ -6290,6 +6290,15 @@ importers: dotenv: specifier: ^16.5.0 version: 16.5.0 + eslint: + specifier: ^10.6.0 + version: 10.6.0(jiti@2.5.1) + eslint-plugin-sonarjs: + specifier: ^4.1.0 + version: 4.1.0(eslint@10.6.0(jiti@2.5.1)) + typescript-eslint: + specifier: ^8.62.1 + version: 8.62.1(eslint@10.6.0(jiti@2.5.1))(typescript@5.4.5) tools/docsAutogen: dependencies: @@ -7820,6 +7829,36 @@ packages: cpu: [x64] os: [win32] + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.23.5': + resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/config-helpers@0.6.0': + resolution: {integrity: sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/core@1.2.1': + resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/object-schema@3.0.5': + resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/plugin-kit@0.7.2': + resolution: {integrity: sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@esm-bundle/chai@4.3.4-fix.0': resolution: {integrity: sha512-26SKdM4uvDWlY8/OOOxSB1AqQWeBosCX3wRYUZO7enTAj03CtVxIiCimYVG2WpULcyV51qapK4qTovwkUr5Mlw==} @@ -7934,6 +7973,26 @@ packages: '@huggingface/transformers@3.8.1': resolution: {integrity: sha512-tsTk4zVjImqdqjS8/AOZg2yNLd1z9S5v+7oUPpXaasDRwEDhB+xnglK1k5cad26lL5/ZIaeREgWWy0bs9y9pPA==} + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} @@ -9730,6 +9789,9 @@ packages: '@types/eslint@9.6.1': resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -10063,6 +10125,65 @@ packages: '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + '@typescript-eslint/eslint-plugin@8.62.1': + resolution: {integrity: sha512-4EQM77WgVNxj7OkL/5b/D/xZsw00G577+UriYTC7JF5opcF3T2AuoeY7ueLaZgSVjSgCS6yOAJB5bRGLPSJUzA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.62.1 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.62.1': + resolution: {integrity: sha512-sPhE4iHuJDSvoAiec+Ro8JyXw8f0ql13HFR82P99nCm9GwTEKG0KYLvDe6REk8BCXuit6vJAv/Yxg5ABaNS2rA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.62.1': + resolution: {integrity: sha512-yQ3RgY5RkSBpsNS1Bx/JQEcA24FOSdfGktoyprAr5u18390UQdtVcfnEv4nIrIshNnavlVyZBKxQwT1fIAE6cg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.62.1': + resolution: {integrity: sha512-r4d249KbQ1SFdpeStvob8Ih6aPPIzfqllPVOtvhve6ZcpuVcYo5/7zUWckKpHE7StASX4kTKZTLf0WQm/wPkcg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.62.1': + resolution: {integrity: sha512-xadytJqX9vJVQ2fdQjkcIVigwaOJNWkpjdLt6cEQ+xPnrI1fkp+/jZE/I97k9KUjqtpd25i0HeyZf3T6dutv2g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.62.1': + resolution: {integrity: sha512-aXM5xlqXiTxPibXB93cLAURfT3rlizf7uMXISCXy66Isr/9hISJx3yDsKl0L7lKa51b8JpFuNKby0/O0pEm9jg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.62.1': + resolution: {integrity: sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.62.1': + resolution: {integrity: sha512-xMcW9oP9u7fAMXYs9A65CVmtLQe2r//oXINHfi8HV+oiqhih17sbLdhXr4540YWlgpDKQdY854OL5ZrdCiQsAA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.62.1': + resolution: {integrity: sha512-sHtbPfuKNZCG+ih8SyjjucqRntSVmp8XgL5u6o9mAhiSn8ds5o/M/XdM0abweme2Tln3szOstOrZ9OXitvPh0g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.62.1': + resolution: {integrity: sha512-4g3BLxfdTMy8iZG0MaBkadnlRrCJ74cQiFbyEVMrkwIoqdyaXXQM22cotDvrl4x28wgIZ9rEJRoM+mmhSJpJ1g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typespec/ts-http-runtime@0.2.2': resolution: {integrity: sha512-Gz/Sm64+Sq/vklJu1tt9t+4R2lvnud8NbTD/ZfpZtMiUX7YeVpCA8j6NSW8ptwcoLL+NmYANwqP8DV0q/bwl2w==} engines: {node: '>=18.0.0'} @@ -10341,6 +10462,11 @@ packages: peerDependencies: acorn: ^8.14.0 + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + acorn-walk@8.3.0: resolution: {integrity: sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==} engines: {node: '>=0.4.0'} @@ -10350,6 +10476,11 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.17.0: + resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} + engines: {node: '>=0.4.0'} + hasBin: true + agent-base@5.1.1: resolution: {integrity: sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==} engines: {node: '>= 6.0.0'} @@ -10817,6 +10948,10 @@ packages: builder-util@26.8.1: resolution: {integrity: sha512-pm1lTYbGyc90DHgCDO7eo8Rl4EqKLciayNbZqGziqnH9jrlKe8ZANGdityLZU+pJh16dfzjAx2xQq9McuIPEtw==} + builtin-modules@3.3.0: + resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} + engines: {node: '>=6'} + bundle-name@4.1.0: resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} engines: {node: '>=18'} @@ -11638,6 +11773,9 @@ packages: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} @@ -12077,10 +12215,41 @@ packages: engines: {node: '>=6.0'} hasBin: true + eslint-plugin-sonarjs@4.1.0: + resolution: {integrity: sha512-rh+FlVz0yfd2RNIb6WqSkuGh0addX/Qi5scwQ5FphXDFrM6fZKcxP1+attJ78yUKcyYfiu6MTaISPpAFPzqRJw==} + peerDependencies: + eslint: ^8.0.0 || ^9.0.0 || ^10.0.0 + eslint-scope@5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} engines: {node: '>=8.0.0'} + eslint-scope@9.1.2: + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@10.6.0: + resolution: {integrity: sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + esprima@1.2.5: resolution: {integrity: sha512-S9VbPDU0adFErpDai3qDkjq8+G05ONtKzcyNrPKg/ZKa+tf879nX2KexNU95b31UoTJjRLInNBHHHjFPoCd7lQ==} engines: {node: '>=0.4.0'} @@ -12091,6 +12260,10 @@ packages: engines: {node: '>=4'} hasBin: true + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} @@ -12215,6 +12388,9 @@ packages: fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-levenshtein@3.0.0: resolution: {integrity: sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==} @@ -12258,6 +12434,10 @@ packages: picomatch: optional: true + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + file-size@1.0.0: resolution: {integrity: sha512-tLIdonWTpABkU6Axg2yGChYdrOsy4V8xcm0IcyAP8fSsu6jiXLm5pgs083e4sq5fzNRZuAYolUbZyYmPvCKfwQ==} @@ -12302,6 +12482,10 @@ packages: resolution: {integrity: sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==} engines: {node: '>=18'} + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + flat@5.0.2: resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} hasBin: true @@ -12312,6 +12496,9 @@ packages: flatbuffers@25.9.23: resolution: {integrity: sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ==} + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + follow-redirects@1.16.0: resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} engines: {node: '>=4.0'} @@ -12414,6 +12601,9 @@ packages: resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} engines: {node: '>= 0.4'} + functional-red-black-tree@1.0.1: + resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==} + functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} @@ -12540,6 +12730,10 @@ packages: resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} engines: {node: '>=10.0'} + globals@17.7.0: + resolution: {integrity: sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==} + engines: {node: '>=18'} + globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} @@ -12870,6 +13064,10 @@ packages: resolution: {integrity: sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==} engines: {node: '>= 4'} + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + image-size@0.5.5: resolution: {integrity: sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==} engines: {node: '>=0.10.0'} @@ -13535,6 +13733,9 @@ packages: json-schema-typed@8.0.2: resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json-stringify-safe@5.0.1: resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} @@ -13562,6 +13763,10 @@ packages: resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} engines: {node: '>=12', npm: '>=6'} + jsx-ast-utils-x@0.1.0: + resolution: {integrity: sha512-eQQBjBnsVtGacsG9uJNB8qOr3yA8rga4wAaGG1qRcBzSIvfhERLrWxMAM1hp5fcS6Abo8M4+bUBTekYR0qTPQw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + jszip@3.10.1: resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} @@ -13675,6 +13880,10 @@ packages: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + lib0@0.2.108: resolution: {integrity: sha512-+3eK/B0SqYoZiQu9fNk4VEc6EX8cb0Li96tPGKgugzoGj/OdRdREtuTLvUW+mtinoB2mFiJjSqOJBIaMkAGhxQ==} engines: {node: '>=16'} @@ -13794,6 +14003,9 @@ packages: lodash.memoize@4.1.2: resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.once@4.1.1: resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} @@ -14172,6 +14384,10 @@ packages: resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} engines: {node: 18 || 20 || >=22} + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.5: resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} @@ -14559,6 +14775,10 @@ packages: zod: optional: true + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + ora@5.4.1: resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} engines: {node: '>=10'} @@ -14881,6 +15101,10 @@ packages: deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available. hasBin: true + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + prettier@3.5.3: resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==} engines: {node: '>=14'} @@ -15227,6 +15451,10 @@ packages: resolution: {integrity: sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==} engines: {node: '>= 10.13.0'} + refa@0.12.1: + resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + reflect-metadata@0.2.2: resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} @@ -15237,6 +15465,10 @@ packages: regenerator-runtime@0.14.0: resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==} + regexp-ast-analysis@0.7.1: + resolution: {integrity: sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + regexp.escape@2.0.1: resolution: {integrity: sha512-JItRb4rmyTzmERBkAf6J87LjDPy/RscIwmaJQ3gsFlAzrmZbZU8LwBw5IydFZXW9hqpgbPlGbMhtpqtuAhMgtg==} engines: {node: '>= 0.4'} @@ -15454,6 +15686,10 @@ packages: resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} engines: {node: '>= 10.13.0'} + scslre@0.3.0: + resolution: {integrity: sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==} + engines: {node: ^14.0.0 || >=16.0.0} + secretlint@9.3.2: resolution: {integrity: sha512-IuFrtWMGeVFSWpuhn1T6JC0mgfwD9rbFNZG1aWtpkBKOUCbcKZ+RoJcJjdJ0DXv8oa9vRg/+DZUl6Q6omZdPQQ==} engines: {node: ^14.13.1 || >=16.0.0} @@ -16161,6 +16397,12 @@ packages: ts-algebra@2.0.0: resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==} + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + ts-dedent@2.2.0: resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} engines: {node: '>=6.10'} @@ -16270,6 +16512,10 @@ packages: resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + type-detect@4.0.8: resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} engines: {node: '>=4'} @@ -16336,6 +16582,13 @@ packages: typed-rest-client@1.8.11: resolution: {integrity: sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==} + typescript-eslint@8.62.1: + resolution: {integrity: sha512-vymnnM5g0AKQDSAyfP12nMIBvgwgA42syg74kkuZ4x1VuTzwQKwc5h9rGxeShCjny5o+zWAb6OEoz7XLgrIkIw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + typescript@5.4.5: resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} engines: {node: '>=14.17'} @@ -16837,6 +17090,10 @@ packages: winreg@1.2.5: resolution: {integrity: sha512-uf7tHf+tw0B1y+x+mKTLHkykBgK2KMs3g+KlzmyMbLvICSHQyB/xOFjTT8qZ3oeTFyU7Bbj4FzXitGG6jvKhYw==} + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} @@ -17002,6 +17259,11 @@ packages: engines: {node: '>= 14.6'} hasBin: true + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} + hasBin: true + yargs-parser@20.2.9: resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} engines: {node: '>=10'} @@ -19087,6 +19349,36 @@ snapshots: '@esbuild/win32-x64@0.28.1': optional: true + '@eslint-community/eslint-utils@4.9.1(eslint@10.6.0(jiti@2.5.1))': + dependencies: + eslint: 10.6.0(jiti@2.5.1) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.23.5': + dependencies: + '@eslint/object-schema': 3.0.5 + debug: 4.4.3(supports-color@8.1.1) + minimatch: 10.2.4 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.6.0': + dependencies: + '@eslint/core': 1.2.1 + + '@eslint/core@1.2.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/object-schema@3.0.5': {} + + '@eslint/plugin-kit@0.7.2': + dependencies: + '@eslint/core': 1.2.1 + levn: 0.4.1 + '@esm-bundle/chai@4.3.4-fix.0': dependencies: '@types/chai': 4.3.20 @@ -19220,6 +19512,22 @@ snapshots: onnxruntime-web: 1.22.0-dev.20250409-89f8206ba4 sharp: 0.34.5 + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + '@iconify/types@2.0.0': {} '@iconify/utils@3.1.3': @@ -21738,6 +22046,8 @@ snapshots: '@types/estree': 1.0.8 '@types/json-schema': 7.0.15 + '@types/esrecurse@4.3.1': {} + '@types/estree@1.0.8': {} '@types/express-serve-static-core@4.17.41': @@ -22117,6 +22427,97 @@ snapshots: '@types/node': 22.19.21 optional: true + '@typescript-eslint/eslint-plugin@8.62.1(@typescript-eslint/parser@8.62.1(eslint@10.6.0(jiti@2.5.1))(typescript@5.4.5))(eslint@10.6.0(jiti@2.5.1))(typescript@5.4.5)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.62.1(eslint@10.6.0(jiti@2.5.1))(typescript@5.4.5) + '@typescript-eslint/scope-manager': 8.62.1 + '@typescript-eslint/type-utils': 8.62.1(eslint@10.6.0(jiti@2.5.1))(typescript@5.4.5) + '@typescript-eslint/utils': 8.62.1(eslint@10.6.0(jiti@2.5.1))(typescript@5.4.5) + '@typescript-eslint/visitor-keys': 8.62.1 + eslint: 10.6.0(jiti@2.5.1) + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@5.4.5) + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.62.1(eslint@10.6.0(jiti@2.5.1))(typescript@5.4.5)': + dependencies: + '@typescript-eslint/scope-manager': 8.62.1 + '@typescript-eslint/types': 8.62.1 + '@typescript-eslint/typescript-estree': 8.62.1(typescript@5.4.5) + '@typescript-eslint/visitor-keys': 8.62.1 + debug: 4.4.3(supports-color@8.1.1) + eslint: 10.6.0(jiti@2.5.1) + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.62.1(typescript@5.4.5)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.62.1(typescript@5.4.5) + '@typescript-eslint/types': 8.62.1 + debug: 4.4.3(supports-color@8.1.1) + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.62.1': + dependencies: + '@typescript-eslint/types': 8.62.1 + '@typescript-eslint/visitor-keys': 8.62.1 + + '@typescript-eslint/tsconfig-utils@8.62.1(typescript@5.4.5)': + dependencies: + typescript: 5.4.5 + + '@typescript-eslint/type-utils@8.62.1(eslint@10.6.0(jiti@2.5.1))(typescript@5.4.5)': + dependencies: + '@typescript-eslint/types': 8.62.1 + '@typescript-eslint/typescript-estree': 8.62.1(typescript@5.4.5) + '@typescript-eslint/utils': 8.62.1(eslint@10.6.0(jiti@2.5.1))(typescript@5.4.5) + debug: 4.4.3(supports-color@8.1.1) + eslint: 10.6.0(jiti@2.5.1) + ts-api-utils: 2.5.0(typescript@5.4.5) + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.62.1': {} + + '@typescript-eslint/typescript-estree@8.62.1(typescript@5.4.5)': + dependencies: + '@typescript-eslint/project-service': 8.62.1(typescript@5.4.5) + '@typescript-eslint/tsconfig-utils': 8.62.1(typescript@5.4.5) + '@typescript-eslint/types': 8.62.1 + '@typescript-eslint/visitor-keys': 8.62.1 + debug: 4.4.3(supports-color@8.1.1) + minimatch: 10.2.4 + semver: 7.8.4 + tinyglobby: 0.2.16 + ts-api-utils: 2.5.0(typescript@5.4.5) + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.62.1(eslint@10.6.0(jiti@2.5.1))(typescript@5.4.5)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.6.0(jiti@2.5.1)) + '@typescript-eslint/scope-manager': 8.62.1 + '@typescript-eslint/types': 8.62.1 + '@typescript-eslint/typescript-estree': 8.62.1(typescript@5.4.5) + eslint: 10.6.0(jiti@2.5.1) + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.62.1': + dependencies: + '@typescript-eslint/types': 8.62.1 + eslint-visitor-keys: 5.0.1 + '@typespec/ts-http-runtime@0.2.2': dependencies: http-proxy-agent: 7.0.2 @@ -22609,10 +23010,16 @@ snapshots: dependencies: acorn: 8.15.0 + acorn-jsx@5.3.2(acorn@8.17.0): + dependencies: + acorn: 8.17.0 + acorn-walk@8.3.0: {} acorn@8.15.0: {} + acorn@8.17.0: {} + agent-base@5.1.1: {} agent-base@6.0.2: @@ -23206,6 +23613,8 @@ snapshots: transitivePeerDependencies: - supports-color + builtin-modules@3.3.0: {} + bundle-name@4.1.0: dependencies: run-applescript: 7.0.0 @@ -24134,6 +24543,8 @@ snapshots: deep-extend@0.6.0: {} + deep-is@0.1.4: {} + deepmerge@4.3.1: {} default-browser-id@5.0.0: {} @@ -24408,7 +24819,7 @@ snapshots: transitivePeerDependencies: - supports-color - electron-vite@4.0.1(vite@6.4.3(@types/node@25.9.3)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.3)): + electron-vite@4.0.1(vite@6.4.3(@types/node@25.9.3)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.9.0)): dependencies: '@babel/core': 7.29.7 '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.7) @@ -24416,7 +24827,7 @@ snapshots: esbuild: 0.25.12 magic-string: 0.30.17 picocolors: 1.1.1 - vite: 6.4.3(@types/node@25.9.3)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.3) + vite: 6.4.3(@types/node@25.9.3)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.9.0) transitivePeerDependencies: - supports-color @@ -24732,15 +25143,90 @@ snapshots: optionalDependencies: source-map: 0.6.1 + eslint-plugin-sonarjs@4.1.0(eslint@10.6.0(jiti@2.5.1)): + dependencies: + '@eslint-community/regexpp': 4.12.2 + builtin-modules: 3.3.0 + bytes: 3.1.2 + eslint: 10.6.0(jiti@2.5.1) + functional-red-black-tree: 1.0.1 + globals: 17.7.0 + jsx-ast-utils-x: 0.1.0 + lodash.merge: 4.6.2 + minimatch: 10.2.5 + scslre: 0.3.0 + semver: 7.8.4 + ts-api-utils: 2.5.0(typescript@5.4.5) + typescript: 5.4.5 + yaml: 2.9.0 + eslint-scope@5.1.1: dependencies: esrecurse: 4.3.0 estraverse: 4.3.0 + eslint-scope@9.1.2: + dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.8 + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@10.6.0(jiti@2.5.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.6.0(jiti@2.5.1)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.23.5 + '@eslint/config-helpers': 0.6.0 + '@eslint/core': 1.2.1 + '@eslint/plugin-kit': 0.7.2 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.15.0 + cross-spawn: 7.0.6 + debug: 4.4.3(supports-color@8.1.1) + escape-string-regexp: 4.0.0 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + minimatch: 10.2.4 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.5.1 + transitivePeerDependencies: + - supports-color + + espree@11.2.0: + dependencies: + acorn: 8.17.0 + acorn-jsx: 5.3.2(acorn@8.17.0) + eslint-visitor-keys: 5.0.1 + esprima@1.2.5: {} esprima@4.0.1: {} + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + esrecurse@4.3.0: dependencies: estraverse: 5.3.0 @@ -24968,6 +25454,8 @@ snapshots: fast-json-stable-stringify@2.1.0: {} + fast-levenshtein@2.0.6: {} + fast-levenshtein@3.0.0: dependencies: fastest-levenshtein: 1.0.16 @@ -25015,6 +25503,10 @@ snapshots: optionalDependencies: picomatch: 4.0.4 + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + file-size@1.0.0: {} file-uri-to-path@1.0.0: {} @@ -25078,12 +25570,19 @@ snapshots: path-exists: 5.0.0 unicorn-magic: 0.1.0 + flat-cache@4.0.1: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + flat@5.0.2: {} flatbuffers@24.3.25: {} flatbuffers@25.9.23: {} + flatted@3.4.2: {} + follow-redirects@1.16.0(debug@4.4.1): optionalDependencies: debug: 4.4.1 @@ -25186,6 +25685,8 @@ snapshots: hasown: 2.0.4 is-callable: 1.2.7 + functional-red-black-tree@1.0.1: {} + functions-have-names@1.2.3: {} gaxios@6.7.1(encoding@0.1.13): @@ -25343,6 +25844,8 @@ snapshots: semver: 7.8.0 serialize-error: 7.0.1 + globals@17.7.0: {} + globalthis@1.0.4: dependencies: define-properties: 1.2.1 @@ -25778,6 +26281,8 @@ snapshots: ignore@7.0.4: {} + ignore@7.0.5: {} + image-size@0.5.5: optional: true @@ -26801,6 +27306,8 @@ snapshots: json-schema-typed@8.0.2: {} + json-stable-stringify-without-jsonify@1.0.1: {} + json-stringify-safe@5.0.1: {} json5@2.2.3: {} @@ -26842,6 +27349,8 @@ snapshots: ms: 2.1.3 semver: 7.8.0 + jsx-ast-utils-x@0.1.0: {} + jszip@3.10.1: dependencies: lie: 3.3.0 @@ -26999,6 +27508,11 @@ snapshots: leven@3.1.0: {} + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + lib0@0.2.108: dependencies: isomorphic.js: 0.2.5 @@ -27115,6 +27629,8 @@ snapshots: lodash.memoize@4.1.2: {} + lodash.merge@4.6.2: {} + lodash.once@4.1.1: {} lodash.throttle@4.1.1: {} @@ -27706,6 +28222,10 @@ snapshots: dependencies: brace-expansion: 5.0.6 + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.6 + minimatch@3.1.5: dependencies: brace-expansion: 1.1.15 @@ -28091,6 +28611,15 @@ snapshots: ws: 8.21.0 zod: 3.25.76 + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + ora@5.4.1: dependencies: bl: 4.1.0 @@ -28427,6 +28956,8 @@ snapshots: tar-fs: 2.1.4 tunnel-agent: 0.6.0 + prelude-ls@1.2.1: {} + prettier@3.5.3: {} pretty-error@4.0.0: @@ -28868,6 +29399,10 @@ snapshots: dependencies: resolve: 1.22.8 + refa@0.12.1: + dependencies: + '@eslint-community/regexpp': 4.12.2 + reflect-metadata@0.2.2: {} reflect.getprototypeof@1.0.10: @@ -28883,6 +29418,11 @@ snapshots: regenerator-runtime@0.14.0: {} + regexp-ast-analysis@0.7.1: + dependencies: + '@eslint-community/regexpp': 4.12.2 + refa: 0.12.1 + regexp.escape@2.0.1: dependencies: call-bind: 1.0.8 @@ -29182,6 +29722,12 @@ snapshots: ajv-formats: 2.1.1(ajv@8.20.0) ajv-keywords: 5.1.0(ajv@8.20.0) + scslre@0.3.0: + dependencies: + '@eslint-community/regexpp': 4.12.2 + refa: 0.12.1 + regexp-ast-analysis: 0.7.1 + secretlint@9.3.2: dependencies: '@secretlint/config-creator': 9.3.2 @@ -30081,6 +30627,10 @@ snapshots: ts-algebra@2.0.0: {} + ts-api-utils@2.5.0(typescript@5.4.5): + dependencies: + typescript: 5.4.5 + ts-dedent@2.2.0: {} ts-deepmerge@7.0.2: {} @@ -30247,6 +30797,10 @@ snapshots: tunnel@0.0.6: {} + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + type-detect@4.0.8: {} type-fest@0.13.1: {} @@ -30326,6 +30880,17 @@ snapshots: tunnel: 0.0.6 underscore: 1.13.8 + typescript-eslint@8.62.1(eslint@10.6.0(jiti@2.5.1))(typescript@5.4.5): + dependencies: + '@typescript-eslint/eslint-plugin': 8.62.1(@typescript-eslint/parser@8.62.1(eslint@10.6.0(jiti@2.5.1))(typescript@5.4.5))(eslint@10.6.0(jiti@2.5.1))(typescript@5.4.5) + '@typescript-eslint/parser': 8.62.1(eslint@10.6.0(jiti@2.5.1))(typescript@5.4.5) + '@typescript-eslint/typescript-estree': 8.62.1(typescript@5.4.5) + '@typescript-eslint/utils': 8.62.1(eslint@10.6.0(jiti@2.5.1))(typescript@5.4.5) + eslint: 10.6.0(jiti@2.5.1) + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + typescript@5.4.5: {} typical@4.0.0: {} @@ -30539,7 +31104,7 @@ snapshots: tsx: 4.21.0 yaml: 2.8.3 - vite@6.4.3(@types/node@25.9.3)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.3): + vite@6.4.3(@types/node@25.9.3)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.9.0): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.4) @@ -30554,7 +31119,7 @@ snapshots: less: 4.3.0 terser: 5.39.2 tsx: 4.21.0 - yaml: 2.8.3 + yaml: 2.9.0 vscode-jsonrpc@8.2.0: {} @@ -30893,6 +31458,8 @@ snapshots: winreg@1.2.5: {} + word-wrap@1.2.5: {} + wordwrap@1.0.0: {} wordwrapjs@5.1.0: {} @@ -31013,6 +31580,8 @@ snapshots: yaml@2.8.3: {} + yaml@2.9.0: {} + yargs-parser@20.2.9: {} yargs-parser@21.1.1: {} diff --git a/ts/tools/package.json b/ts/tools/package.json index 20a1ecd141..217e351cc2 100644 --- a/ts/tools/package.json +++ b/ts/tools/package.json @@ -27,6 +27,9 @@ "validate-npm-package-name": "^6.0.0" }, "devDependencies": { - "dotenv": "^16.5.0" + "dotenv": "^16.5.0", + "eslint": "^10.6.0", + "eslint-plugin-sonarjs": "^4.1.0", + "typescript-eslint": "^8.62.1" } } diff --git a/ts/tools/scripts/code/complexityReport.ts b/ts/tools/scripts/code/complexityReport.ts new file mode 100644 index 0000000000..16d9622d84 --- /dev/null +++ b/ts/tools/scripts/code/complexityReport.ts @@ -0,0 +1,771 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * Cyclomatic + cognitive complexity report for the TypeAgent ts/ tree. + * + * The analysis engine is ESLint — the de-facto standard linter for JS/TS — so + * the numbers come from well-known, widely-used implementations rather than a + * bespoke metric: + * - Cyclomatic complexity: ESLint core `complexity` rule. This is the McCabe + * metric, the same one Visual Studio's Code Metrics reports. + * - Cognitive complexity: `eslint-plugin-sonarjs` `cognitive-complexity` + * rule (SonarSource's metric). Better than cyclomatic at surfacing code + * that is hard for a human to follow. Optional — if the plugin cannot be + * loaded the report still produces cyclomatic numbers. + * + * The repo does not need an ESLint configuration. This script builds a + * throwaway flat config in memory and runs it purely to harvest metrics, so it + * never interferes with the rest of the monorepo. + * + * Outputs (written to --out-dir, default tools/scripts/code/complexity-report): + * - functions.csv : every analyzed function, ranked by cyclomatic complexity + * - report.json : structured metrics (functions, per-file rollups, totals) + * - report.html : a self-contained, sortable report (open in a browser) + * plus a console summary (distribution + the worst offenders). + * + * Usage: + * npx tsx tools/scripts/code/complexityReport.ts [options] + * npm run code-complexity -- [options] + * + * Options: + * --include-tests Include test files (*.spec.*, *.test.*, test dirs). + * Excluded by default. + * --cyclomatic Cyclomatic "budget"; functions above it are counted as + * over budget (default 10, the classic McCabe limit). + * --cognitive Cognitive "budget" (default 15, SonarSource's default). + * --top Number of worst offenders to print / embed (default 25). + * --root Directory to scan (default: the ts/ root). + * --out-dir Output directory (default tools/scripts/code/complexity-report). + * --help Show this help. + */ + +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { ESLint, Linter } from "eslint"; +import tseslint from "typescript-eslint"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// --------------------------------------------------------------------------- +// Configuration +// --------------------------------------------------------------------------- + +const SOURCE_GLOB = "**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}"; + +// Generated / build-output directories that are never source. node_modules and +// .git are ignored by ESLint's flat config by default. +const IGNORE_DIRS = [ + "**/dist/**", + "**/build/**", + "**/out/**", + "**/coverage/**", + "**/bin/**", + "**/obj/**", + "**/.turbo/**", + "**/.next/**", + "**/bundle/**", +]; + +// Generated single-file artifacts (declarations, bundles, minified output). +const IGNORE_FILES = ["**/*.d.ts", "**/*.min.js", "**/*.bundle.js"]; + +// Test files and directories, excluded unless --include-tests is passed. +const TEST_GLOBS = [ + "**/test/**", + "**/tests/**", + "**/__tests__/**", + "**/*.spec.*", + "**/*.test.*", +]; + +// ESLint renders the metric into the message text; these pull the number back +// out. Core `complexity`: "Function 'foo' has a complexity of 12. Maximum ..." +const CYCLOMATIC_RE = /^(.*?) has a complexity of (\d+)\./; +// sonarjs: "Refactor this function to reduce its Cognitive Complexity from 21 .." +const COGNITIVE_RE = /Cognitive Complexity from (\d+)/; + +// Distribution buckets for the cyclomatic histogram. hi is inclusive. +const BUCKETS: { label: string; lo: number; hi: number; color: string }[] = [ + { label: "1-5 (low)", lo: 1, hi: 5, color: "#66bb6a" }, + { label: "6-10 (moderate)", lo: 6, hi: 10, color: "#9ccc65" }, + { label: "11-15 (high)", lo: 11, hi: 15, color: "#ffca28" }, + { label: "16-20 (very high)", lo: 16, hi: 20, color: "#ffa726" }, + { label: "21-30 (severe)", lo: 21, hi: 30, color: "#ef5350" }, + { label: "31+ (extreme)", lo: 31, hi: Infinity, color: "#e53935" }, +]; + +// --------------------------------------------------------------------------- +// Argument parsing +// --------------------------------------------------------------------------- + +interface Options { + root: string; + outDir: string; + includeTests: boolean; + cyclomaticBudget: number; + cognitiveBudget: number; + top: number; + help: boolean; +} + +function parseIntArg(arg: string, next: string | undefined): number { + if (next === undefined || !/^\d+$/.test(next)) { + throw new Error(`${arg} requires a non-negative integer value`); + } + return parseInt(next, 10); +} + +function parseArgs(argv: string[]): Options { + const opts: Options = { + root: path.resolve(__dirname, "..", "..", ".."), + outDir: path.join(__dirname, "complexity-report"), + includeTests: false, + cyclomaticBudget: 10, + cognitiveBudget: 15, + top: 25, + help: false, + }; + + // Normalize "--key=value" into ["--key", "value"]. + const tokens: string[] = []; + for (const raw of argv) { + const m = /^(--[\w-]+)=(.*)$/.exec(raw); + if (m) { + tokens.push(m[1], m[2]); + } else { + tokens.push(raw); + } + } + + for (let i = 0; i < tokens.length; i++) { + const arg = tokens[i]; + const next = tokens[i + 1]; + switch (arg) { + case "--help": + case "-h": + opts.help = true; + break; + case "--include-tests": + opts.includeTests = true; + break; + case "--cyclomatic": + opts.cyclomaticBudget = parseIntArg(arg, next); + i++; + break; + case "--cognitive": + opts.cognitiveBudget = parseIntArg(arg, next); + i++; + break; + case "--top": + opts.top = parseIntArg(arg, next); + i++; + break; + case "--root": + if (next === undefined) { + throw new Error(`${arg} requires a path`); + } + opts.root = path.resolve(next); + i++; + break; + case "--out-dir": + case "--outDir": + if (next === undefined) { + throw new Error(`${arg} requires a path`); + } + opts.outDir = path.resolve(next); + i++; + break; + default: + console.warn(`Ignoring unrecognized argument: ${arg}`); + break; + } + } + + if (opts.top <= 0) { + throw new Error("--top must be greater than 0"); + } + + return opts; +} + +const HELP = `Cyclomatic + cognitive complexity report for the TypeAgent ts/ tree. + +Usage: + npx tsx tools/scripts/code/complexityReport.ts [options] + npm run code-complexity -- [options] + +Options: + --include-tests Include test files (excluded by default). + --cyclomatic Cyclomatic budget; functions above it count as over + budget (default 10, the classic McCabe limit). + --cognitive Cognitive budget (default 15, SonarSource's default). + --top Number of worst offenders to print / embed (default 25). + --root Directory to scan (default: the ts/ root). + --out-dir Output directory (default: tools/scripts/code/complexity-report). + --help Show this help.`; + +// --------------------------------------------------------------------------- +// Analysis +// --------------------------------------------------------------------------- + +interface FuncRecord { + file: string; // repo-relative, forward-slash separated + line: number; + name: string; + cyclomatic: number; + cognitive: number; +} + +interface FileRollup { + file: string; + functions: number; + totalCyclomatic: number; + maxCyclomatic: number; + maxCognitive: number; +} + +interface AnalysisResult { + functions: FuncRecord[]; + filesAnalyzed: number; + parseErrorFiles: number; + cognitiveEnabled: boolean; + elapsedMs: number; +} + +/** Load the optional sonarjs plugin, returning undefined if unavailable. */ +async function loadSonar(): Promise { + try { + const mod = (await import("eslint-plugin-sonarjs")) as { + default?: unknown; + }; + return mod.default ?? mod; + } catch { + return undefined; + } +} + +async function analyze(opts: Options): Promise { + const started = Date.now(); + const sonar = await loadSonar(); + + const ignores = [...IGNORE_DIRS, ...IGNORE_FILES]; + if (!opts.includeTests) { + ignores.push(...TEST_GLOBS); + } + + // The core `complexity` rule reports when complexity > max, and the minimum + // complexity of any function is 1. max:0 therefore flags every function so + // we capture the whole distribution, not just the ones over some limit. + const rules: Linter.RulesRecord = { + complexity: ["warn", { max: 0 }], + }; + const plugins: Record = {}; + if (sonar) { + plugins.sonarjs = sonar as ESLint.Plugin; + rules["sonarjs/cognitive-complexity"] = ["warn", 0]; + } + + const eslint = new ESLint({ + cwd: opts.root, + errorOnUnmatchedPattern: false, + // Ignore any eslint config in the repo; use only what we define here. + overrideConfigFile: true, + overrideConfig: [ + { ignores }, + { + files: [SOURCE_GLOB], + languageOptions: { + parser: tseslint.parser as unknown as Linter.Parser, + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + ecmaFeatures: { jsx: true }, + }, + }, + plugins, + rules, + }, + ], + }); + + const results = await eslint.lintFiles([SOURCE_GLOB]); + + const functions: FuncRecord[] = []; + let parseErrorFiles = 0; + + for (const res of results) { + const rel = path + .relative(opts.root, res.filePath) + .split(path.sep) + .join("/"); + + // Collect cognitive complexity keyed by the function's start line so we + // can attach it to the matching cyclomatic record below. + const cognitiveByLine = new Map(); + let hadFatal = false; + for (const msg of res.messages) { + if (msg.fatal) { + hadFatal = true; + continue; + } + if (msg.ruleId === "sonarjs/cognitive-complexity") { + const m = COGNITIVE_RE.exec(msg.message); + if (m) { + cognitiveByLine.set(msg.line, parseInt(m[1], 10)); + } + } + } + if (hadFatal) { + parseErrorFiles++; + } + + for (const msg of res.messages) { + if (msg.ruleId !== "complexity") { + continue; + } + const m = CYCLOMATIC_RE.exec(msg.message); + if (!m) { + continue; + } + functions.push({ + file: rel, + line: msg.line, + name: m[1], + cyclomatic: parseInt(m[2], 10), + cognitive: cognitiveByLine.get(msg.line) ?? 0, + }); + } + } + + return { + functions, + filesAnalyzed: results.length, + parseErrorFiles, + cognitiveEnabled: sonar !== undefined, + elapsedMs: Date.now() - started, + }; +} + +// --------------------------------------------------------------------------- +// Rollups + formatting helpers +// --------------------------------------------------------------------------- + +function rollupByFile(functions: FuncRecord[]): FileRollup[] { + const map = new Map(); + for (const f of functions) { + let r = map.get(f.file); + if (!r) { + r = { + file: f.file, + functions: 0, + totalCyclomatic: 0, + maxCyclomatic: 0, + maxCognitive: 0, + }; + map.set(f.file, r); + } + r.functions++; + r.totalCyclomatic += f.cyclomatic; + r.maxCyclomatic = Math.max(r.maxCyclomatic, f.cyclomatic); + r.maxCognitive = Math.max(r.maxCognitive, f.cognitive); + } + return [...map.values()]; +} + +function distribution(functions: FuncRecord[]): number[] { + const counts = new Array(BUCKETS.length).fill(0); + for (const f of functions) { + const idx = BUCKETS.findIndex( + (b) => f.cyclomatic >= b.lo && f.cyclomatic <= b.hi, + ); + if (idx >= 0) { + counts[idx]++; + } + } + return counts; +} + +function csvEscape(value: string | number): string { + const s = String(value); + if (/[",\r\n]/.test(s)) { + return `"${s.replace(/"/g, '""')}"`; + } + return s; +} + +function htmlEscape(s: string): string { + return s.replace(/&/g, "&").replace(//g, ">"); +} + +/** Format an integer with thousands separators for human-readable output. */ +function fmt(n: number): string { + return n.toLocaleString("en-US"); +} + +function formatDate(d: Date): string { + const p = (n: number) => String(n).padStart(2, "0"); + return ( + `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ` + + `${p(d.getHours())}:${p(d.getMinutes())}` + ); +} + +// --------------------------------------------------------------------------- +// Console summary +// --------------------------------------------------------------------------- + +function printSummary( + opts: Options, + analysis: AnalysisResult, + byCyclomatic: FuncRecord[], + byCognitive: FuncRecord[], + counts: number[], +): void { + const { functions } = analysis; + const overCyclomatic = functions.filter( + (f) => f.cyclomatic > opts.cyclomaticBudget, + ).length; + const overCognitive = functions.filter( + (f) => f.cognitive > opts.cognitiveBudget, + ).length; + + console.log(""); + console.log( + `Analyzed ${fmt(functions.length)} functions in ` + + `${fmt(analysis.filesAnalyzed)} files ` + + `(${(analysis.elapsedMs / 1000).toFixed(1)}s).`, + ); + if (!analysis.cognitiveEnabled) { + console.log( + " Cognitive complexity unavailable (eslint-plugin-sonarjs not loaded).", + ); + } + if (analysis.parseErrorFiles > 0) { + console.log( + ` ${fmt(analysis.parseErrorFiles)} file(s) could not be parsed and were skipped.`, + ); + } + + console.log(""); + console.log("Cyclomatic complexity distribution:"); + const maxCount = Math.max(...counts, 1); + const barWidth = 32; + BUCKETS.forEach((b, i) => { + const c = counts[i]; + const bar = "#".repeat(Math.round((c / maxCount) * barWidth)); + console.log(` ${b.label.padEnd(18)} ${fmt(c).padStart(8)} ${bar}`); + }); + + console.log(""); + console.log( + `Over budget: ${fmt(overCyclomatic)} function(s) > cyclomatic ${opts.cyclomaticBudget}` + + (analysis.cognitiveEnabled + ? `, ${fmt(overCognitive)} > cognitive ${opts.cognitiveBudget}` + : ""), + ); + + const printTable = (title: string, rows: FuncRecord[]): void => { + console.log(""); + console.log(title); + console.log(" CC Cog Location"); + for (const f of rows) { + console.log( + ` ${String(f.cyclomatic).padStart(3)} ${String( + f.cognitive, + ).padStart(4)} ${f.file}:${f.line} ${f.name}`, + ); + } + }; + + printTable( + `Top ${opts.top} functions by cyclomatic complexity:`, + byCyclomatic.slice(0, opts.top), + ); + if (analysis.cognitiveEnabled) { + printTable( + `Top ${opts.top} functions by cognitive complexity:`, + byCognitive.slice(0, opts.top), + ); + } +} + +// --------------------------------------------------------------------------- +// File outputs +// --------------------------------------------------------------------------- + +function writeCsv(filePath: string, functions: FuncRecord[]): void { + const sorted = [...functions].sort((a, b) => b.cyclomatic - a.cyclomatic); + const lines = ["File,Line,Function,Cyclomatic,Cognitive"]; + for (const f of sorted) { + lines.push( + [ + csvEscape(f.file), + csvEscape(f.line), + csvEscape(f.name), + csvEscape(f.cyclomatic), + csvEscape(f.cognitive), + ].join(","), + ); + } + fs.writeFileSync(filePath, lines.join("\n") + "\n", "utf8"); +} + +function writeJson( + filePath: string, + opts: Options, + analysis: AnalysisResult, + files: FileRollup[], + counts: number[], +): void { + const { functions } = analysis; + const payload = { + generatedAt: new Date().toISOString(), + root: opts.root, + includeTests: opts.includeTests, + cognitiveEnabled: analysis.cognitiveEnabled, + thresholds: { + cyclomatic: opts.cyclomaticBudget, + cognitive: opts.cognitiveBudget, + }, + totals: { + functions: functions.length, + filesAnalyzed: analysis.filesAnalyzed, + parseErrorFiles: analysis.parseErrorFiles, + overCyclomatic: functions.filter( + (f) => f.cyclomatic > opts.cyclomaticBudget, + ).length, + overCognitive: functions.filter( + (f) => f.cognitive > opts.cognitiveBudget, + ).length, + }, + distribution: BUCKETS.map((b, i) => ({ + label: b.label, + lo: b.lo, + hi: b.hi === Infinity ? null : b.hi, + count: counts[i], + })), + files: [...files].sort((a, b) => b.totalCyclomatic - a.totalCyclomatic), + functions: [...functions].sort((a, b) => b.cyclomatic - a.cyclomatic), + }; + fs.writeFileSync(filePath, JSON.stringify(payload, null, 2) + "\n", "utf8"); +} + +function distributionBarsHtml(counts: number[]): string { + const max = Math.max(...counts, 1); + return BUCKETS.map((b, i) => { + const c = counts[i]; + const pct = ((c / max) * 100).toFixed(1); + return `
+
${htmlEscape(b.label)}
+
+
${fmt(c)}
+
`; + }).join("\n"); +} + +function funcRowsHtml(functions: FuncRecord[], budgetCc: number): string { + return functions + .map((f) => { + const over = f.cyclomatic > budgetCc ? " over" : ""; + return ` + ${fmt(f.cyclomatic)} + ${fmt(f.cognitive)} + ${htmlEscape(f.file)}:${f.line} + ${htmlEscape(f.name)} + `; + }) + .join("\n"); +} + +function fileRowsHtml(files: FileRollup[]): string { + return files + .map((f) => { + return ` + ${fmt(f.totalCyclomatic)} + ${fmt(f.maxCyclomatic)} + ${fmt(f.functions)} + ${htmlEscape(f.file)} + `; + }) + .join("\n"); +} + +function buildHtml( + opts: Options, + analysis: AnalysisResult, + byCyclomatic: FuncRecord[], + files: FileRollup[], + counts: number[], +): string { + const { functions } = analysis; + const overCyclomatic = functions.filter( + (f) => f.cyclomatic > opts.cyclomaticBudget, + ).length; + const topFns = byCyclomatic.slice(0, Math.max(opts.top, 100)); + const topFiles = [...files] + .sort((a, b) => b.totalCyclomatic - a.totalCyclomatic) + .slice(0, 50); + + const genDate = formatDate(new Date()); + const testNote = opts.includeTests ? "includes tests" : "excludes tests"; + const cogNote = analysis.cognitiveEnabled + ? "cyclomatic + cognitive" + : "cyclomatic only (sonarjs not loaded)"; + + return ` + + + + +TypeAgent ts/ — Complexity Report + + + +

TypeAgent ts/ — Complexity Report

+
${cogNote} · generated ${genDate} · ${testNote} · budgets: cyclomatic ${opts.cyclomaticBudget}, cognitive ${opts.cognitiveBudget}
+ +
+
${fmt(functions.length)}
functions
+
${fmt(analysis.filesAnalyzed)}
files
+
${fmt(overCyclomatic)}
over cyclomatic ${opts.cyclomaticBudget}
+
+ +

Cyclomatic complexity distribution

+
+${distributionBarsHtml(counts)} +
+ +

Worst offenders (top ${topFns.length} by cyclomatic complexity)

+ + + + + + + + +${funcRowsHtml(topFns, opts.cyclomaticBudget)} + +
CyclomaticCognitiveLocationFunction
+ +

Heaviest files (top ${topFiles.length} by total cyclomatic complexity)

+ + + + + + + + +${fileRowsHtml(topFiles)} + +
Total CCMax CCFunctionsFile
+ + + + +`; +} + +// --------------------------------------------------------------------------- +// Main +// --------------------------------------------------------------------------- + +async function main(): Promise { + const opts = parseArgs(process.argv.slice(2)); + if (opts.help) { + console.log(HELP); + return; + } + + console.log(`Scanning ${opts.root} for complexity metrics...`); + const analysis = await analyze(opts); + + if (analysis.functions.length === 0) { + console.log("No functions found. Nothing to report."); + return; + } + + const byCyclomatic = [...analysis.functions].sort( + (a, b) => b.cyclomatic - a.cyclomatic || b.cognitive - a.cognitive, + ); + const byCognitive = [...analysis.functions].sort( + (a, b) => b.cognitive - a.cognitive || b.cyclomatic - a.cyclomatic, + ); + const files = rollupByFile(analysis.functions); + const counts = distribution(analysis.functions); + + printSummary(opts, analysis, byCyclomatic, byCognitive, counts); + + fs.mkdirSync(opts.outDir, { recursive: true }); + const csvPath = path.join(opts.outDir, "functions.csv"); + const jsonPath = path.join(opts.outDir, "report.json"); + const htmlPath = path.join(opts.outDir, "report.html"); + writeCsv(csvPath, analysis.functions); + writeJson(jsonPath, opts, analysis, files, counts); + fs.writeFileSync( + htmlPath, + buildHtml(opts, analysis, byCyclomatic, files, counts), + "utf8", + ); + + console.log(""); + console.log("Wrote:"); + console.log(` ${csvPath}`); + console.log(` ${jsonPath}`); + console.log(` ${htmlPath}`); +} + +main().catch((err) => { + console.error(err instanceof Error ? err.message : err); + process.exit(1); +}); From 8939028b4c485f47cd30bb3a3c9b1432d96cf48a Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Thu, 2 Jul 2026 21:42:34 -0700 Subject: [PATCH 02/23] link to files --- ts/tools/scripts/code/complexityReport.ts | 28 ++++++++++++++++++----- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/ts/tools/scripts/code/complexityReport.ts b/ts/tools/scripts/code/complexityReport.ts index 16d9622d84..83b6b083eb 100644 --- a/ts/tools/scripts/code/complexityReport.ts +++ b/ts/tools/scripts/code/complexityReport.ts @@ -405,6 +405,16 @@ function fmt(n: number): string { return n.toLocaleString("en-US"); } +/** + * Build an escaped vscode:// deep link that opens the file (optionally at a + * given line) in VS Code when the path is clicked in a browser. + */ +function vscodeLink(root: string, relFile: string, line?: number): string { + const abs = path.resolve(root, relFile).split(path.sep).join("/"); + const target = line !== undefined ? `${abs}:${line}` : abs; + return htmlEscape(encodeURI(`vscode://file/${target}`)); +} + function formatDate(d: Date): string { const p = (n: number) => String(n).padStart(2, "0"); return ( @@ -566,28 +576,32 @@ function distributionBarsHtml(counts: number[]): string { }).join("\n"); } -function funcRowsHtml(functions: FuncRecord[], budgetCc: number): string { +function funcRowsHtml( + root: string, + functions: FuncRecord[], + budgetCc: number, +): string { return functions .map((f) => { const over = f.cyclomatic > budgetCc ? " over" : ""; return ` ${fmt(f.cyclomatic)} ${fmt(f.cognitive)} - ${htmlEscape(f.file)}:${f.line} + ${htmlEscape(f.file)}:${f.line} ${htmlEscape(f.name)} `; }) .join("\n"); } -function fileRowsHtml(files: FileRollup[]): string { +function fileRowsHtml(root: string, files: FileRollup[]): string { return files .map((f) => { return ` ${fmt(f.totalCyclomatic)} ${fmt(f.maxCyclomatic)} ${fmt(f.functions)} - ${htmlEscape(f.file)} + ${htmlEscape(f.file)} `; }) .join("\n"); @@ -648,6 +662,8 @@ function buildHtml( td.num { text-align: right; font-variant-numeric: tabular-nums; color: #ffcc80; } td.num.over { color: #ef5350; font-weight: 700; } td.path { font-family: 'Cascadia Code', Consolas, monospace; font-size: 12px; color: #b0bec5; } + td.path a { color: inherit; text-decoration: none; } + td.path a:hover { color: #4fc3f7; text-decoration: underline; } @@ -674,7 +690,7 @@ ${distributionBarsHtml(counts)} Function -${funcRowsHtml(topFns, opts.cyclomaticBudget)} +${funcRowsHtml(opts.root, topFns, opts.cyclomaticBudget)} @@ -687,7 +703,7 @@ ${funcRowsHtml(topFns, opts.cyclomaticBudget)} File -${fileRowsHtml(topFiles)} +${fileRowsHtml(opts.root, topFiles)} From a81d4926d939dad8f2f670ef0f3b0dbecf03552e Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Thu, 2 Jul 2026 22:46:07 -0700 Subject: [PATCH 03/23] complexity report now has a ratcheting cap on existing and new files. And added to build-ts to gate builds. --- .github/workflows/build-ts.yml | 14 + ts/tools/scripts/code/complexityReport.ts | 429 ++++++++++++++++++++-- 2 files changed, 407 insertions(+), 36 deletions(-) diff --git a/.github/workflows/build-ts.yml b/.github/workflows/build-ts.yml index 014de520b0..a90df10b97 100644 --- a/.github/workflows/build-ts.yml +++ b/.github/workflows/build-ts.yml @@ -88,6 +88,20 @@ jobs: else npm run lint fi + # Complexity gate (PRs only): the files this PR changes may not add + # functions over the cyclomatic/cognitive budget versus the base branch + # (a stateless ratchet — the base branch is the baseline, so the count + # can only trend down), and any brand-new file is additionally held to a + # hard cap. Tune the thresholds down as hotspots get refactored. + - name: Complexity ratchet + if: ${{ github.event_name == 'pull_request' && steps.filter.outputs.ts != 'false' }} + working-directory: ts + shell: bash + run: | + git fetch --no-tags origin "${{ github.base_ref }}" + npm run code-complexity -- --ratchet --base "origin/${{ github.base_ref }}" \ + --cyclomatic 25 --cognitive 30 \ + --new-file-cyclomatic 25 --new-file-cognitive 30 - name: Restore better-sqlite3 for Node.js if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts != 'false' }} working-directory: ts diff --git a/ts/tools/scripts/code/complexityReport.ts b/ts/tools/scripts/code/complexityReport.ts index 83b6b083eb..faea351d89 100644 --- a/ts/tools/scripts/code/complexityReport.ts +++ b/ts/tools/scripts/code/complexityReport.ts @@ -37,12 +37,22 @@ * --top Number of worst offenders to print / embed (default 25). * --root Directory to scan (default: the ts/ root). * --out-dir Output directory (default tools/scripts/code/complexity-report). + * --ratchet CI gate: exit non-zero if the files changed since + * --base contain more over-budget functions than they did + * at the merge base. The base branch is the baseline (no + * baseline file), so the count only ratchets down. + * --base Base git ref for --ratchet (default origin/main). + * --new-file-cyclomatic With --ratchet, fail if any function in a newly + * added file exceeds cyclomatic (0 = off, default). + * --new-file-cognitive Same, for cognitive complexity. * --help Show this help. */ import fs from "node:fs"; +import os from "node:os"; import path from "node:path"; import { fileURLToPath } from "node:url"; +import { execFileSync } from "node:child_process"; import { ESLint, Linter } from "eslint"; import tseslint from "typescript-eslint"; @@ -108,6 +118,10 @@ interface Options { cyclomaticBudget: number; cognitiveBudget: number; top: number; + ratchet: boolean; + base: string; + newCyclomaticCap: number; + newCognitiveCap: number; help: boolean; } @@ -126,6 +140,10 @@ function parseArgs(argv: string[]): Options { cyclomaticBudget: 10, cognitiveBudget: 15, top: 25, + ratchet: false, + base: "origin/main", + newCyclomaticCap: 0, + newCognitiveCap: 0, help: false, }; @@ -163,6 +181,24 @@ function parseArgs(argv: string[]): Options { opts.top = parseIntArg(arg, next); i++; break; + case "--ratchet": + opts.ratchet = true; + break; + case "--base": + if (next === undefined) { + throw new Error(`${arg} requires a git ref`); + } + opts.base = next; + i++; + break; + case "--new-file-cyclomatic": + opts.newCyclomaticCap = parseIntArg(arg, next); + i++; + break; + case "--new-file-cognitive": + opts.newCognitiveCap = parseIntArg(arg, next); + i++; + break; case "--root": if (next === undefined) { throw new Error(`${arg} requires a path`); @@ -205,6 +241,13 @@ Options: --top Number of worst offenders to print / embed (default 25). --root Directory to scan (default: the ts/ root). --out-dir Output directory (default: tools/scripts/code/complexity-report). + --ratchet CI gate: fail if changed files add complexity vs --base. + --base Base git ref for --ratchet (default origin/main). + --new-file-cyclomatic + With --ratchet, fail if any function in a NEW file exceeds + cyclomatic (0 = disabled, the default). + --new-file-cognitive + Same, for cognitive complexity. --help Show this help.`; // --------------------------------------------------------------------------- @@ -247,15 +290,12 @@ async function loadSonar(): Promise { } } -async function analyze(opts: Options): Promise { - const started = Date.now(); - const sonar = await loadSonar(); - - const ignores = [...IGNORE_DIRS, ...IGNORE_FILES]; - if (!opts.includeTests) { - ignores.push(...TEST_GLOBS); - } - +/** Build the throwaway flat config used purely to harvest metrics. */ +function buildComplexityConfig( + sonar: unknown, + useIgnores: boolean, + includeTests: boolean, +): Linter.Config[] { // The core `complexity` rule reports when complexity > max, and the minimum // complexity of any function is 1. max:0 therefore flags every function so // we capture the whole distribution, not just the ones over some limit. @@ -268,39 +308,40 @@ async function analyze(opts: Options): Promise { rules["sonarjs/cognitive-complexity"] = ["warn", 0]; } - const eslint = new ESLint({ - cwd: opts.root, - errorOnUnmatchedPattern: false, - // Ignore any eslint config in the repo; use only what we define here. - overrideConfigFile: true, - overrideConfig: [ - { ignores }, - { - files: [SOURCE_GLOB], - languageOptions: { - parser: tseslint.parser as unknown as Linter.Parser, - parserOptions: { - ecmaVersion: "latest", - sourceType: "module", - ecmaFeatures: { jsx: true }, - }, - }, - plugins, - rules, + const config: Linter.Config[] = []; + if (useIgnores) { + const ignores = [...IGNORE_DIRS, ...IGNORE_FILES]; + if (!includeTests) { + ignores.push(...TEST_GLOBS); + } + config.push({ ignores } as Linter.Config); + } + config.push({ + files: [SOURCE_GLOB], + languageOptions: { + parser: tseslint.parser as unknown as Linter.Parser, + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + ecmaFeatures: { jsx: true }, }, - ], + }, + plugins, + rules, }); + return config; +} - const results = await eslint.lintFiles([SOURCE_GLOB]); - +/** Turn ESLint results into FuncRecords, keyed relative to `cwd`. */ +function parseEslintResults( + results: ESLint.LintResult[], + cwd: string, +): { functions: FuncRecord[]; parseErrorFiles: number } { const functions: FuncRecord[] = []; let parseErrorFiles = 0; for (const res of results) { - const rel = path - .relative(opts.root, res.filePath) - .split(path.sep) - .join("/"); + const rel = path.relative(cwd, res.filePath).split(path.sep).join("/"); // Collect cognitive complexity keyed by the function's start line so we // can attach it to the matching cyclomatic record below. @@ -340,9 +381,48 @@ async function analyze(opts: Options): Promise { } } + return { functions, parseErrorFiles }; +} + +interface LintOutput { + functions: FuncRecord[]; + parseErrorFiles: number; + filesAnalyzed: number; +} + +/** Run ESLint over `patterns` (globs or explicit paths) relative to `cwd`. */ +async function lintToFunctions( + cwd: string, + patterns: string[], + sonar: unknown, + useIgnores: boolean, + includeTests: boolean, +): Promise { + const eslint = new ESLint({ + cwd, + errorOnUnmatchedPattern: false, + // Ignore any eslint config in the repo; use only what we define here. + overrideConfigFile: true, + overrideConfig: buildComplexityConfig(sonar, useIgnores, includeTests), + }); + const results = await eslint.lintFiles(patterns); + const { functions, parseErrorFiles } = parseEslintResults(results, cwd); + return { functions, parseErrorFiles, filesAnalyzed: results.length }; +} + +async function analyze(opts: Options): Promise { + const started = Date.now(); + const sonar = await loadSonar(); + const { functions, parseErrorFiles, filesAnalyzed } = await lintToFunctions( + opts.root, + [SOURCE_GLOB], + sonar, + true, + opts.includeTests, + ); return { functions, - filesAnalyzed: results.length, + filesAnalyzed, parseErrorFiles, cognitiveEnabled: sonar !== undefined, elapsedMs: Date.now() - started, @@ -732,6 +812,278 @@ function sortTable(id, col, numeric) { `; } +// --------------------------------------------------------------------------- +// Ratchet (CI gate) +// --------------------------------------------------------------------------- + +// Path predicates mirroring the ESLint ignores above, for filtering the raw +// file list that `git diff` returns. +const SOURCE_EXT_RE = /\.[cm]?[jt]sx?$/; +const IGNORE_PATH_RE = + /(^|\/)(node_modules|dist|build|out|coverage|bin|obj|\.turbo|\.next|bundle)\//; +const GENERATED_FILE_RE = /(\.d\.ts|\.min\.js|\.bundle\.js)$/; +const TEST_PATH_RE = + /(^|\/)(test|tests|__tests__)\/|\.(spec|test)\.[cm]?[jt]sx?$/; + +const EMPTY_LINT: LintOutput = { + functions: [], + parseErrorFiles: 0, + filesAnalyzed: 0, +}; + +function isReportableSource(relPath: string, includeTests: boolean): boolean { + if (!SOURCE_EXT_RE.test(relPath)) { + return false; + } + if (IGNORE_PATH_RE.test(relPath) || GENERATED_FILE_RE.test(relPath)) { + return false; + } + if (!includeTests && TEST_PATH_RE.test(relPath)) { + return false; + } + return true; +} + +function git(args: string[], cwd: string): string { + return execFileSync("git", args, { + cwd, + encoding: "utf8", + maxBuffer: 128 * 1024 * 1024, + }); +} + +interface DiffEntry { + head: string; // path in HEAD + base: string | null; // path at the merge base, or null if newly added +} + +/** Parse `git diff --name-status -M` into HEAD/base path pairs. */ +function parseNameStatus(raw: string): DiffEntry[] { + const entries: DiffEntry[] = []; + for (const line of raw.split(/\r?\n/)) { + if (!line) { + continue; + } + const parts = line.split("\t"); + const status = parts[0]; + if (status.startsWith("R") || status.startsWith("C")) { + entries.push({ base: parts[1], head: parts[2] }); // rename/copy + } else if (status === "A") { + entries.push({ base: null, head: parts[1] }); // added + } else if (status === "D") { + continue; // deleted in HEAD — nothing to lint + } else { + entries.push({ base: parts[1], head: parts[1] }); // modified, etc. + } + } + return entries; +} + +function countOver( + functions: FuncRecord[], + opts: Options, +): { overCyclomatic: number; overCognitive: number } { + return { + overCyclomatic: functions.filter( + (f) => f.cyclomatic > opts.cyclomaticBudget, + ).length, + overCognitive: functions.filter( + (f) => f.cognitive > opts.cognitiveBudget, + ).length, + }; +} + +/** + * Compare the files changed since --base against their content at the merge + * base. Fails (exit 1) if the changed files contain more over-budget functions + * than they did before, so complexity in touched code can only ratchet down. + * There is no committed baseline: the base branch itself is the baseline. + * Returns the desired process exit code. + */ +async function runRatchet(opts: Options): Promise { + let repoRoot: string; + let mergeBase: string; + try { + repoRoot = git(["rev-parse", "--show-toplevel"], opts.root).trim(); + mergeBase = git(["merge-base", opts.base, "HEAD"], opts.root).trim(); + } catch { + console.error( + `Ratchet: could not resolve base ref "${opts.base}" via git. ` + + "Pass --base (e.g. origin/main) and ensure it is fetched.", + ); + return 2; + } + + const entries = parseNameStatus( + git(["diff", "--name-status", "-M", mergeBase, "HEAD"], opts.root), + ).filter((e) => { + if (!isReportableSource(e.head, opts.includeTests)) { + return false; + } + const relToRoot = path.relative( + opts.root, + path.resolve(repoRoot, e.head), + ); + return !relToRoot.startsWith("..") && !path.isAbsolute(relToRoot); + }); + + if (entries.length === 0) { + console.log("Ratchet: no changed source files to check. OK."); + return 0; + } + + const sonar = await loadSonar(); + + // HEAD side: the changed files as they are in the working tree. + const headPaths = entries + .map((e) => path.resolve(repoRoot, e.head)) + .filter((p) => fs.existsSync(p)); + const head = headPaths.length + ? await lintToFunctions( + repoRoot, + headPaths, + sonar, + false, + opts.includeTests, + ) + : EMPTY_LINT; + + // BASE side: the same files' content at the merge base, materialized into a + // temp dir so we compare like-for-like without any committed baseline. + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "complexity-base-")); + let base: LintOutput = EMPTY_LINT; + try { + const basePaths: string[] = []; + for (const e of entries) { + if (!e.base || !isReportableSource(e.base, opts.includeTests)) { + continue; + } + let content: string; + try { + content = git(["show", `${mergeBase}:${e.base}`], repoRoot); + } catch { + continue; // not present at the base + } + const dest = path.join(tmp, e.base); + fs.mkdirSync(path.dirname(dest), { recursive: true }); + fs.writeFileSync(dest, content, "utf8"); + basePaths.push(dest); + } + if (basePaths.length) { + base = await lintToFunctions( + tmp, + basePaths, + sonar, + false, + opts.includeTests, + ); + } + } finally { + fs.rmSync(tmp, { recursive: true, force: true }); + } + + const headOver = countOver(head.functions, opts); + const baseOver = countOver(base.functions, opts); + const dCyc = headOver.overCyclomatic - baseOver.overCyclomatic; + const dCog = headOver.overCognitive - baseOver.overCognitive; + const sign = (n: number) => (n >= 0 ? `+${n}` : `${n}`); + + console.log(""); + console.log( + `Ratchet vs ${opts.base} (merge-base ${mergeBase.slice(0, 9)}): ` + + `${entries.length} changed source file(s).`, + ); + console.log( + ` Over cyclomatic ${opts.cyclomaticBudget}: ` + + `${fmt(baseOver.overCyclomatic)} -> ${fmt(headOver.overCyclomatic)} ` + + `(${sign(dCyc)})`, + ); + if (sonar) { + console.log( + ` Over cognitive ${opts.cognitiveBudget}: ` + + `${fmt(baseOver.overCognitive)} -> ${fmt(headOver.overCognitive)} ` + + `(${sign(dCog)})`, + ); + } + + // Absolute cap on brand-new files: they have no baseline to ratchet + // against, so hold them to a hard ceiling instead of just "no worse". + const newFiles = new Set( + entries.filter((e) => e.base === null).map((e) => e.head), + ); + const capEnabled = + newFiles.size > 0 && + (opts.newCyclomaticCap > 0 || opts.newCognitiveCap > 0); + const capViolations = capEnabled + ? head.functions + .filter( + (f) => + newFiles.has(f.file) && + ((opts.newCyclomaticCap > 0 && + f.cyclomatic > opts.newCyclomaticCap) || + (opts.newCognitiveCap > 0 && + f.cognitive > opts.newCognitiveCap)), + ) + .sort((a, b) => b.cyclomatic - a.cyclomatic) + : []; + + if (capEnabled) { + const parts: string[] = []; + if (opts.newCyclomaticCap > 0) { + parts.push(`cyclomatic ${opts.newCyclomaticCap}`); + } + if (opts.newCognitiveCap > 0) { + parts.push(`cognitive ${opts.newCognitiveCap}`); + } + console.log( + ` New-file cap (${parts.join(", ")}): ${newFiles.size} new file(s), ` + + `${capViolations.length} function(s) over.`, + ); + } + + const printTable = (title: string, rows: FuncRecord[]): void => { + console.log(""); + console.log(title); + console.log(" CC Cog Location"); + for (const f of rows.slice(0, opts.top)) { + console.log( + ` ${String(f.cyclomatic).padStart(3)} ${String( + f.cognitive, + ).padStart(4)} ${f.file}:${f.line} ${f.name}`, + ); + } + }; + + const regressed = dCyc > 0 || dCog > 0; + + if (regressed) { + const offenders = head.functions + .filter( + (f) => + f.cyclomatic > opts.cyclomaticBudget || + f.cognitive > opts.cognitiveBudget, + ) + .sort((a, b) => b.cyclomatic - a.cyclomatic); + printTable( + "Ratchet FAILED — reduce complexity in the changed files:", + offenders, + ); + } + + if (capViolations.length > 0) { + printTable( + "New-file cap FAILED — new files must be simpler than the cap:", + capViolations, + ); + } + + if (!regressed && capViolations.length === 0) { + console.log("Ratchet: OK — changed files did not add complexity."); + return 0; + } + return 1; +} + // --------------------------------------------------------------------------- // Main // --------------------------------------------------------------------------- @@ -743,6 +1095,11 @@ async function main(): Promise { return; } + if (opts.ratchet) { + process.exitCode = await runRatchet(opts); + return; + } + console.log(`Scanning ${opts.root} for complexity metrics...`); const analysis = await analyze(opts); From 8210a539f4ed41e7a8b1cffadc6ffaa36d2c63fa Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Thu, 2 Jul 2026 23:06:24 -0700 Subject: [PATCH 04/23] added more code reporting tools --- ts/.gitignore | 6 + ts/package.json | 8 +- ts/pnpm-lock.yaml | 395 +++++++++- ts/tools/scripts/code/consistencyReport.ts | 739 +++++++++++++++++++ ts/tools/scripts/code/duplicationReport.ts | 798 +++++++++++++++++++++ 5 files changed, 1932 insertions(+), 14 deletions(-) create mode 100644 ts/tools/scripts/code/consistencyReport.ts create mode 100644 ts/tools/scripts/code/duplicationReport.ts diff --git a/ts/.gitignore b/ts/.gitignore index 8c3ab39a4c..56c92868f8 100644 --- a/ts/.gitignore +++ b/ts/.gitignore @@ -26,6 +26,12 @@ tools/scripts/pools.inventory.json # Generated complexity report output (tools/scripts/code/complexityReport.ts). tools/scripts/code/complexity-report/ +# Generated duplication report output (tools/scripts/code/duplicationReport.ts). +tools/scripts/code/duplication-report/ + +# Generated consistency report output (tools/scripts/code/consistencyReport.ts). +tools/scripts/code/consistency-report/ + # Per-developer YAML config overrides loaded by @typeagent/config. # Holds local endpoints, deployment names, and (in Phase 1) secrets. # Never commit. diff --git a/ts/package.json b/ts/package.json index afdf8104a6..a52c7e3059 100644 --- a/ts/package.json +++ b/ts/package.json @@ -26,6 +26,8 @@ "cli": "pnpm -C packages/cli run start", "cli:dev": "pnpm -C packages/cli run start:dev", "code-complexity": "npx tsx tools/scripts/code/complexityReport.ts", + "code-consistency": "npx tsx tools/scripts/code/consistencyReport.ts", + "code-duplication": "npx tsx tools/scripts/code/duplicationReport.ts", "copilot": "node packages/copilot-plugin/scripts/launch.mjs", "copilot:dev": "node packages/copilot-plugin/scripts/launch-dev.mjs", "devtunnel:setup": "node tools/scripts/setup-devtunnel.mjs", @@ -74,10 +76,14 @@ "devDependencies": { "@fluidframework/build-tools": "^0.57.0", "@types/node": "^22.0.0", + "eslint": "^10.6.0", + "eslint-plugin-sonarjs": "^4.1.0", + "jscpd": "^4.2.5", "markdown-link-check": "^3.14.2", "prettier": "^3.5.3", "shx": "^0.4.0", - "tsx": "^4.21.0" + "tsx": "^4.21.0", + "typescript-eslint": "^8.62.1" }, "packageManager": "pnpm@10.34.4+sha512.8768be55200ae3f2226b6527fcca2687e14bc4e5f12d7721a0f25da3df47915177058648db4177baf348120fa0ba2752d8d8d93f6beaf1fe64ae18da8de961af", "engines": { diff --git a/ts/pnpm-lock.yaml b/ts/pnpm-lock.yaml index a7860d6e54..16b32d1573 100644 --- a/ts/pnpm-lock.yaml +++ b/ts/pnpm-lock.yaml @@ -27,6 +27,15 @@ importers: '@types/node': specifier: ^22.0.0 version: 22.15.18 + eslint: + specifier: ^10.6.0 + version: 10.6.0(jiti@2.5.1) + eslint-plugin-sonarjs: + specifier: ^4.1.0 + version: 4.1.0(eslint@10.6.0(jiti@2.5.1)) + jscpd: + specifier: ^4.2.5 + version: 4.2.5 markdown-link-check: specifier: ^3.14.2 version: 3.14.2 @@ -39,6 +48,9 @@ importers: tsx: specifier: ^4.21.0 version: 4.21.0 + typescript-eslint: + specifier: ^8.62.1 + version: 8.62.1(eslint@10.6.0(jiti@2.5.1))(typescript@5.4.5) examples/agentExamples/echo: dependencies: @@ -8509,6 +8521,21 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@jscpd/badge-reporter@4.2.5': + resolution: {integrity: sha512-ktXrjPeRaRyUDktxTroSA2/w5sshXpQplWkUuq/e6XqEpKBSbGEnwZLIaegSijOrMwIcCXPQ9k4feXIz5eVJNA==} + + '@jscpd/core@4.2.5': + resolution: {integrity: sha512-Esf2deHxaoNEjePwf2jqP6Urzj+BAOsJVPFLbnnSsV+q7rLNMcn0UEEoKBXIOOt4qMkrkhl9DfwpMyPPOr6GkQ==} + + '@jscpd/finder@4.2.5': + resolution: {integrity: sha512-Rw0dtwp/EeLANbujOubuQeJIuXXXkAlT+f5geZhwkB9TxEYP0hqNrdOJUK/TDBKQjRGrOizEtdNy+S4UlbdzOQ==} + + '@jscpd/html-reporter@4.2.5': + resolution: {integrity: sha512-zMMIKbvi43dMgeNeHXlHQy1ovf+KJrzNlUubaBvCAVatqP23ksW8d3fmsevIQG9mMMTH0D1xOz+SxUn1FREOPg==} + + '@jscpd/tokenizer@4.2.5': + resolution: {integrity: sha512-UM8Wx/jwahmflqQExlcKMQTYOAy58N/fn7Pv6NYrkD3EZm/FTk7gW97wkXy5aDE1Ts9oBUpT9tLY2rz7ogCHAQ==} + '@jsonjoy.com/base64@1.1.2': resolution: {integrity: sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==} engines: {node: '>=10.0'} @@ -10471,6 +10498,11 @@ packages: resolution: {integrity: sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==} engines: {node: '>=0.4.0'} + acorn@7.4.1: + resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} + engines: {node: '>=0.4.0'} + hasBin: true + acorn@8.15.0: resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} engines: {node: '>=0.4.0'} @@ -10668,6 +10700,9 @@ packages: resolution: {integrity: sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==} engines: {node: '>=8'} + asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + asn1@0.2.6: resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} @@ -10675,6 +10710,9 @@ packages: resolution: {integrity: sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==} engines: {node: '>=12.0.0'} + assert-never@1.4.0: + resolution: {integrity: sha512-5oJg84os6NMQNl27T9LnZkvvqzvAnHu03ShCnoj6bsJwS7L8AO4lf+C/XjK/nvzEqQB744moC6V128RucQd1jA==} + assert-plus@1.0.0: resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} engines: {node: '>=0.8'} @@ -10758,6 +10796,13 @@ packages: peerDependencies: '@babel/core': ^7.0.0 + babel-walk@3.0.0-canary-5: + resolution: {integrity: sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==} + engines: {node: '>= 10.0.0'} + + badgen@3.3.2: + resolution: {integrity: sha512-fbQwK9norfdzbdsoPwbLIAmgBXDGEme3jeIyqPAH7o6vp9lmuLHS7uXULvOiQ6XnMLkYNG4gDjILf74hgtTAug==} + bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} @@ -10844,6 +10889,10 @@ packages: bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + blamer@1.0.7: + resolution: {integrity: sha512-GbBStl/EVlSWkiJQBZps3H1iARBrC7vt++Jb/TTmCNu/jZ04VW7tSN1nScbFXBUy1AN+jzeL7Zep9sbQxLhXKA==} + engines: {node: '>=8.9'} + body-parser@1.20.3: resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -11061,6 +11110,9 @@ packages: character-entities@2.0.2: resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + character-parser@2.2.0: + resolution: {integrity: sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==} + chardet@2.1.0: resolution: {integrity: sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA==} @@ -11254,6 +11306,10 @@ packages: colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + colors@1.4.0: + resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==} + engines: {node: '>=0.1.90'} + combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -11278,6 +11334,10 @@ packages: resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==} engines: {node: '>=20'} + commander@15.0.0: + resolution: {integrity: sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==} + engines: {node: '>=22.12.0'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -11329,6 +11389,9 @@ packages: resolution: {integrity: sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==} engines: {node: '>=0.8'} + constantinople@4.0.1: + resolution: {integrity: sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==} + content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} @@ -11929,6 +11992,9 @@ packages: resolution: {integrity: sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==} engines: {node: '>=6'} + doctypes@1.1.0: + resolution: {integrity: sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==} + dom-converter@0.2.0: resolution: {integrity: sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==} @@ -12297,6 +12363,9 @@ packages: eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} @@ -12313,6 +12382,10 @@ packages: resolution: {integrity: sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==} engines: {node: '>=6'} + execa@4.1.0: + resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==} + engines: {node: '>=10'} + execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -13021,6 +13094,10 @@ packages: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} + human-signals@1.1.1: + resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} + engines: {node: '>=8.12.0'} + human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -13236,6 +13313,9 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} hasBin: true + is-expression@4.0.0: + resolution: {integrity: sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==} + is-extendable@0.1.1: resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} engines: {node: '>=0.10.0'} @@ -13344,6 +13424,9 @@ packages: is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-promise@2.2.2: + resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} + is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} @@ -13662,6 +13745,9 @@ packages: jose@6.1.3: resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} + js-stringify@1.0.2: + resolution: {integrity: sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -13680,6 +13766,13 @@ packages: jsbi@2.0.5: resolution: {integrity: sha512-TzO/62Hxeb26QMb4IGlI/5X+QLr9Uqp1FPkwp2+KOICW+Q+vSuFj61c8pkT6wAns4WcK56X7CmSHhJeDGWOqxQ==} + jscpd-sarif-reporter@4.2.5: + resolution: {integrity: sha512-O8LcM9grAS5yO5x1Q0yegYaYcUX//IEBEyvzGFSYCeo1YzHbMnAI6EK7oTrwD+7Csjvfg9m8B8G7OOxzcSlr9w==} + + jscpd@4.2.5: + resolution: {integrity: sha512-KDpApYw1ChGelfHb7MwYTEx694OnW52pv3McAasidUV4ILcGDQMiVJzB+vI8ox+ZPVfOSvdXQCk8uRa9B0LXnw==} + hasBin: true + jsdom@20.0.3: resolution: {integrity: sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==} engines: {node: '>=14'} @@ -13763,6 +13856,9 @@ packages: resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} engines: {node: '>=12', npm: '>=6'} + jstransformer@1.0.0: + resolution: {integrity: sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==} + jsx-ast-utils-x@0.1.0: resolution: {integrity: sha512-eQQBjBnsVtGacsG9uJNB8qOr3yA8rga4wAaGG1qRcBzSIvfhERLrWxMAM1hp5fcS6Abo8M4+bUBTekYR0qTPQw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -14112,6 +14208,9 @@ packages: markdown-link-extractor@4.0.3: resolution: {integrity: sha512-aEltJiQ4/oC0h6Jbw/uuATGSHZPkcH8DIunNH1A0e+GSFkvZ6BbBkdvBTVfIV8r6HapCU3yTd0eFdi3ZeM1eAQ==} + markdown-table@2.0.0: + resolution: {integrity: sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==} + markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} @@ -14633,6 +14732,10 @@ packages: resolution: {integrity: sha512-Pzr3rol8fvhG/oJjIq2NTVB0vmdNNlz22FENhhPojYRZ4/ee08CfK4YuKmuL54V9MLhI1kpzxfOJ/63LzmZzDg==} engines: {node: '>=14'} + node-sarif-builder@4.1.0: + resolution: {integrity: sha512-IWqZF6u0EI/07HTBm+zZ+MgXgWl09dnSJRGaDCPBSlOqilDcx6pj3Mpb3HvPN8V2Gr+ISw7ZrMsL7STWs1F++w==} + engines: {node: '>=20'} + nodemailer@8.0.4: resolution: {integrity: sha512-k+jf6N8PfQJ0Fe8ZhJlgqU5qJU44Lpvp2yvidH3vp1lPnVQMgi4yEEMPXg5eJS1gFIJTVq1NHBk7Ia9ARdSBdQ==} engines: {node: '>=6.0.0'} @@ -15143,6 +15246,9 @@ packages: resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} engines: {node: '>=10'} + promise@7.3.1: + resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==} + prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} @@ -15230,6 +15336,42 @@ packages: psl@1.15.0: resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} + pug-attrs@3.0.0: + resolution: {integrity: sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==} + + pug-code-gen@3.0.4: + resolution: {integrity: sha512-6okWYIKdasTyXICyEtvobmTZAVX57JkzgzIi4iRJlin8kmhG+Xry2dsus+Mun/nGCn6F2U49haHI5mkELXB14g==} + + pug-error@2.1.0: + resolution: {integrity: sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==} + + pug-filters@4.0.0: + resolution: {integrity: sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==} + + pug-lexer@5.0.1: + resolution: {integrity: sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==} + + pug-linker@4.0.0: + resolution: {integrity: sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==} + + pug-load@3.0.0: + resolution: {integrity: sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==} + + pug-parser@6.0.0: + resolution: {integrity: sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==} + + pug-runtime@3.0.1: + resolution: {integrity: sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==} + + pug-strip-comments@2.0.0: + resolution: {integrity: sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==} + + pug-walk@2.0.0: + resolution: {integrity: sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==} + + pug@3.0.4: + resolution: {integrity: sha512-kFfq5mMzrS7+wrl5pLJzZEzemx34OQ0w4SARfhy/3yxTlhbstsudDwJzhf1hP02yHzbjoVMSXUj/Sz6RNfMyXg==} + pump@3.0.2: resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} @@ -15502,6 +15644,10 @@ packages: renderkid@3.0.0: resolution: {integrity: sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==} + repeat-string@1.6.1: + resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} + engines: {node: '>=0.10'} + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -15984,6 +16130,9 @@ packages: resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} engines: {node: '>= 8'} + spark-md5@3.0.2: + resolution: {integrity: sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==} + sparse-bitfield@3.0.3: resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==} @@ -16351,6 +16500,9 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + token-stream@1.0.0: + resolution: {integrity: sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==} + tough-cookie@4.1.4: resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} engines: {node: '>=6'} @@ -16857,6 +17009,10 @@ packages: yaml: optional: true + void-elements@3.1.0: + resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} + engines: {node: '>=0.10.0'} + vscode-jsonrpc@8.2.0: resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} engines: {node: '>=14.0.0'} @@ -17090,6 +17246,10 @@ packages: winreg@1.2.5: resolution: {integrity: sha512-uf7tHf+tw0B1y+x+mKTLHkykBgK2KMs3g+KlzmyMbLvICSHQyB/xOFjTT8qZ3oeTFyU7Bbj4FzXitGG6jvKhYw==} + with@7.0.2: + resolution: {integrity: sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==} + engines: {node: '>= 10.0.0'} + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -18399,7 +18559,7 @@ snapshots: '@babel/generator@7.28.3': dependencies: '@babel/parser': 7.29.7 - '@babel/types': 7.28.4 + '@babel/types': 7.29.7 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 @@ -19003,7 +19163,7 @@ snapshots: node-gyp: 12.3.0 ora: 5.4.1 read-binary-file-arch: 1.0.6 - semver: 7.8.0 + semver: 7.8.4 tar: 7.5.16 yargs: 17.7.2 transitivePeerDependencies: @@ -20121,6 +20281,40 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@jscpd/badge-reporter@4.2.5': + dependencies: + badgen: 3.3.2 + colors: 1.4.0 + fs-extra: 11.3.4 + + '@jscpd/core@4.2.5': + dependencies: + eventemitter3: 5.0.4 + + '@jscpd/finder@4.2.5': + dependencies: + '@jscpd/core': 4.2.5 + '@jscpd/tokenizer': 4.2.5 + blamer: 1.0.7 + bytes: 3.1.2 + cli-table3: 0.6.5 + colors: 1.4.0 + fast-glob: 3.3.3 + fs-extra: 11.3.4 + markdown-table: 2.0.0 + pug: 3.0.4 + + '@jscpd/html-reporter@4.2.5': + dependencies: + colors: 1.4.0 + fs-extra: 11.3.4 + pug: 3.0.4 + + '@jscpd/tokenizer@4.2.5': + dependencies: + '@jscpd/core': 4.2.5 + spark-md5: 3.0.2 + '@jsonjoy.com/base64@1.1.2(tslib@2.8.1)': dependencies: tslib: 2.8.1 @@ -22642,7 +22836,7 @@ snapshots: '@vue/compiler-core@3.5.16': dependencies: - '@babel/parser': 7.28.4 + '@babel/parser': 7.29.7 '@vue/shared': 3.5.16 entities: 4.5.0 estree-walker: 2.0.2 @@ -23016,6 +23210,8 @@ snapshots: acorn-walk@8.3.0: {} + acorn@7.4.1: {} + acorn@8.15.0: {} acorn@8.17.0: {} @@ -23172,7 +23368,7 @@ snapshots: js-yaml: 4.3.0 json5: 2.2.3 lazy-val: 1.0.5 - minimatch: 10.2.4 + minimatch: 10.2.5 plist: 3.1.0 proper-lockfile: 4.1.2 resedit: 1.7.2 @@ -23264,6 +23460,8 @@ snapshots: arrify@2.0.1: {} + asap@2.0.6: {} + asn1@0.2.6: dependencies: safer-buffer: 2.1.2 @@ -23274,6 +23472,8 @@ snapshots: pvutils: 1.1.5 tslib: 2.8.1 + assert-never@1.4.0: {} + assert-plus@1.0.0: optional: true @@ -23373,6 +23573,12 @@ snapshots: babel-plugin-jest-hoist: 29.6.3 babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.7) + babel-walk@3.0.0-canary-5: + dependencies: + '@babel/types': 7.29.7 + + badgen@3.3.2: {} + bail@2.0.2: {} balanced-match@1.0.2: {} @@ -23445,6 +23651,11 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 + blamer@1.0.7: + dependencies: + execa: 4.1.0 + which: 2.0.2 + body-parser@1.20.3: dependencies: bytes: 3.1.2 @@ -23721,6 +23932,10 @@ snapshots: character-entities@2.0.2: {} + character-parser@2.2.0: + dependencies: + is-regex: 1.2.1 + chardet@2.1.0: {} cheerio-select@2.1.0: @@ -23951,6 +24166,8 @@ snapshots: colorette@2.0.20: {} + colors@1.4.0: {} + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 @@ -23975,6 +24192,8 @@ snapshots: commander@14.0.2: {} + commander@15.0.0: {} + commander@2.20.3: {} commander@5.1.0: {} @@ -24032,6 +24251,11 @@ snapshots: connect-history-api-fallback@2.0.0: {} + constantinople@4.0.1: + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + content-disposition@0.5.4: dependencies: safe-buffer: 5.2.1 @@ -24679,6 +24903,8 @@ snapshots: dependencies: '@leichtgewicht/ip-codec': 2.0.5 + doctypes@1.1.0: {} + dom-converter@0.2.0: dependencies: utila: 0.4.0 @@ -25255,6 +25481,8 @@ snapshots: eventemitter3@4.0.7: {} + eventemitter3@5.0.4: {} + events@3.3.0: {} eventsource-parser@3.0.6: {} @@ -25273,6 +25501,18 @@ snapshots: signal-exit: 3.0.7 strip-eof: 1.0.0 + execa@4.1.0: + dependencies: + cross-spawn: 7.0.6 + get-stream: 5.2.0 + human-signals: 1.1.1 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + execa@5.1.1: dependencies: cross-spawn: 7.0.6 @@ -25841,7 +26081,7 @@ snapshots: es6-error: 4.1.1 matcher: 3.0.0 roarr: 2.15.4 - semver: 7.8.0 + semver: 7.8.4 serialize-error: 7.0.1 globals@17.7.0: {} @@ -26247,6 +26487,8 @@ snapshots: transitivePeerDependencies: - supports-color + human-signals@1.1.1: {} + human-signals@2.1.0: {} humanize-ms@1.2.1: @@ -26444,6 +26686,11 @@ snapshots: is-docker@3.0.0: {} + is-expression@4.0.0: + dependencies: + acorn: 7.4.1 + object-assign: 4.1.1 + is-extendable@0.1.1: {} is-extglob@2.1.1: {} @@ -26519,6 +26766,8 @@ snapshots: is-potential-custom-element-name@1.0.1: {} + is-promise@2.2.2: {} + is-promise@4.0.0: {} is-regex@1.2.1: @@ -26630,7 +26879,7 @@ snapshots: '@babel/parser': 7.29.7 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.0 - semver: 7.8.0 + semver: 7.8.4 transitivePeerDependencies: - supports-color @@ -27114,7 +27363,7 @@ snapshots: jest-util: 29.7.0 natural-compare: 1.4.0 pretty-format: 29.7.0 - semver: 7.8.0 + semver: 7.8.4 transitivePeerDependencies: - supports-color @@ -27204,6 +27453,8 @@ snapshots: jose@6.1.3: {} + js-stringify@1.0.2: {} + js-tokens@4.0.0: {} js-yaml@3.15.0: @@ -27221,6 +27472,24 @@ snapshots: jsbi@2.0.5: {} + jscpd-sarif-reporter@4.2.5: + dependencies: + colors: 1.4.0 + fs-extra: 11.3.4 + node-sarif-builder: 4.1.0 + + jscpd@4.2.5: + dependencies: + '@jscpd/badge-reporter': 4.2.5 + '@jscpd/core': 4.2.5 + '@jscpd/finder': 4.2.5 + '@jscpd/html-reporter': 4.2.5 + '@jscpd/tokenizer': 4.2.5 + colors: 1.4.0 + commander: 15.0.0 + fs-extra: 11.3.4 + jscpd-sarif-reporter: 4.2.5 + jsdom@20.0.3: dependencies: abab: 2.0.6 @@ -27347,7 +27616,12 @@ snapshots: lodash.isstring: 4.0.1 lodash.once: 4.1.1 ms: 2.1.3 - semver: 7.8.0 + semver: 7.8.4 + + jstransformer@1.0.0: + dependencies: + is-promise: 2.2.2 + promise: 7.3.1 jsx-ast-utils-x@0.1.0: {} @@ -27715,7 +27989,7 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.8.0 + semver: 7.8.4 make-error@1.3.6: {} @@ -27755,6 +28029,10 @@ snapshots: html-link-extractor: 1.0.5 marked: 17.0.6 + markdown-table@2.0.0: + dependencies: + repeat-string: 1.6.1 + markdown-table@3.0.4: {} marked-terminal@7.3.0(marked@15.0.12): @@ -28398,7 +28676,7 @@ snapshots: node-abi@3.77.0: dependencies: - semver: 7.8.0 + semver: 7.8.4 node-abi@4.24.0: dependencies: @@ -28467,6 +28745,11 @@ snapshots: '@types/sarif': 2.1.7 fs-extra: 10.1.0 + node-sarif-builder@4.1.0: + dependencies: + '@types/sarif': 2.1.7 + fs-extra: 11.3.4 + nodemailer@8.0.4: {} noms@0.0.0: @@ -28988,6 +29271,10 @@ snapshots: err-code: 2.0.3 retry: 0.12.0 + promise@7.3.1: + dependencies: + asap: 2.0.6 + prompts@2.4.2: dependencies: kleur: 3.0.3 @@ -29127,6 +29414,73 @@ snapshots: dependencies: punycode: 2.3.1 + pug-attrs@3.0.0: + dependencies: + constantinople: 4.0.1 + js-stringify: 1.0.2 + pug-runtime: 3.0.1 + + pug-code-gen@3.0.4: + dependencies: + constantinople: 4.0.1 + doctypes: 1.1.0 + js-stringify: 1.0.2 + pug-attrs: 3.0.0 + pug-error: 2.1.0 + pug-runtime: 3.0.1 + void-elements: 3.1.0 + with: 7.0.2 + + pug-error@2.1.0: {} + + pug-filters@4.0.0: + dependencies: + constantinople: 4.0.1 + jstransformer: 1.0.0 + pug-error: 2.1.0 + pug-walk: 2.0.0 + resolve: 1.22.8 + + pug-lexer@5.0.1: + dependencies: + character-parser: 2.2.0 + is-expression: 4.0.0 + pug-error: 2.1.0 + + pug-linker@4.0.0: + dependencies: + pug-error: 2.1.0 + pug-walk: 2.0.0 + + pug-load@3.0.0: + dependencies: + object-assign: 4.1.1 + pug-walk: 2.0.0 + + pug-parser@6.0.0: + dependencies: + pug-error: 2.1.0 + token-stream: 1.0.0 + + pug-runtime@3.0.1: {} + + pug-strip-comments@2.0.0: + dependencies: + pug-error: 2.1.0 + + pug-walk@2.0.0: {} + + pug@3.0.4: + dependencies: + pug-code-gen: 3.0.4 + pug-filters: 4.0.0 + pug-lexer: 5.0.1 + pug-linker: 4.0.0 + pug-load: 3.0.0 + pug-parser: 6.0.0 + pug-runtime: 3.0.1 + pug-strip-comments: 2.0.0 + pump@3.0.2: dependencies: end-of-stream: 1.4.4 @@ -29501,6 +29855,8 @@ snapshots: lodash: 4.18.1 strip-ansi: 6.0.1 + repeat-string@1.6.1: {} + require-directory@2.1.1: {} require-from-string@2.0.2: {} @@ -30073,7 +30429,7 @@ snapshots: simple-update-notifier@2.0.0: dependencies: - semver: 7.8.0 + semver: 7.8.4 sisteransi@1.0.5: {} @@ -30166,6 +30522,8 @@ snapshots: source-map@0.7.4: {} + spark-md5@3.0.2: {} + sparse-bitfield@3.0.3: dependencies: memory-pager: 1.5.0 @@ -30415,7 +30773,7 @@ snapshots: tar-stream@2.2.0: dependencies: bl: 4.1.0 - end-of-stream: 1.4.4 + end-of-stream: 1.4.5 fs-constants: 1.0.0 inherits: 2.0.4 readable-stream: 3.6.2 @@ -30472,7 +30830,7 @@ snapshots: terser@5.27.0: dependencies: '@jridgewell/source-map': 0.3.5 - acorn: 8.15.0 + acorn: 8.17.0 commander: 2.20.3 source-map-support: 0.5.21 @@ -30584,6 +30942,8 @@ snapshots: toidentifier@1.0.1: {} + token-stream@1.0.0: {} + tough-cookie@4.1.4: dependencies: psl: 1.15.0 @@ -31121,6 +31481,8 @@ snapshots: tsx: 4.21.0 yaml: 2.9.0 + void-elements@3.1.0: {} + vscode-jsonrpc@8.2.0: {} vscode-jsonrpc@8.2.1: {} @@ -31458,6 +31820,13 @@ snapshots: winreg@1.2.5: {} + with@7.0.2: + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + assert-never: 1.4.0 + babel-walk: 3.0.0-canary-5 + word-wrap@1.2.5: {} wordwrap@1.0.0: {} diff --git a/ts/tools/scripts/code/consistencyReport.ts b/ts/tools/scripts/code/consistencyReport.ts new file mode 100644 index 0000000000..8bce6b72c6 --- /dev/null +++ b/ts/tools/scripts/code/consistencyReport.ts @@ -0,0 +1,739 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * Pattern-consistency report for the TypeAgent ts/ tree. + * + * Where complexityReport.ts and duplicationReport.ts wrap standard engines + * (ESLint, jscpd) to produce metrics, this report performs cross-file + * *structural* analysis that those per-file engines cannot: it surfaces places + * that stray from established repo patterns and candidates for consolidation. + * Its findings are heuristic — they are candidates to review, not hard errors. + * + * Three checks, each targeting a known TypeAgent convention: + * + * 1. Duplicate exports across packages. The same exported function/const/ + * class name defined in N different packages usually means a utility that + * should live in one shared package (e.g. delay, ensureDir, withTimeout). + * Complements jscpd, which finds copy-paste but misses re-implementations + * that drifted apart. Bare lifecycle names (instantiate, activate, ...) + * are ignored. + * + * 2. Direct process.env access in packages/. The convention is to read + * configuration through @typeagent/config (loadConfigSync) and the + * aiclient runtime config, not process.env scattered through the code. + * The canonical readers (packages/config, aiclient runtimeConfig) are + * excluded. + * + * 3. Agent layout conformance. Each agent under packages/agents should ship a + * Manifest.json and a handler that exports instantiate(). Agents + * missing either are flagged. + * + * Outputs (written to --out-dir, default tools/scripts/code/consistency-report): + * - duplicate-exports.csv : every cross-package duplicate export + * - report.json : structured results for all three checks + * - report.html : a self-contained, sortable report + * plus a console summary. + * + * Usage: + * npx tsx tools/scripts/code/consistencyReport.ts [options] + * npm run code-consistency -- [options] + * + * Options: + * --include-tests Include test files (excluded by default). + * --min-packages Report an export only if it appears in at least this + * many packages (default 3). + * --top Number of rows to print / embed (default 30). + * --root Directory to scan (default: the ts/ root). + * --out-dir Output directory (default consistency-report). + * --help Show this help. + */ + +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// --------------------------------------------------------------------------- +// Configuration +// --------------------------------------------------------------------------- + +const SCAN_SUBDIRS = ["packages", "examples", "extensions", "tools"]; + +// process.env check is scoped to production packages only. +const ENV_SCAN_PREFIX = "packages/"; + +const CODE_EXTS = new Set([ + ".ts", + ".mts", + ".cts", + ".tsx", + ".js", + ".mjs", + ".cjs", + ".jsx", +]); + +const IGNORE_DIR_NAMES = new Set([ + "node_modules", + "dist", + "build", + "out", + "coverage", + "bin", + "obj", + ".turbo", + ".next", + "bundle", + ".git", + ".jscpd", + "complexity-report", + "duplication-report", + "consistency-report", +]); + +// Bare export names that are conventions/lifecycle, not shareable utilities. +const EXPORT_DENYLIST = new Set([ + "instantiate", + "activate", + "deactivate", + "run", + "main", + "default", + "handlers", + "getCommands", + "register", + "start", + "stop", +]); + +// Canonical config readers that are allowed to touch process.env directly. +const ENV_ALLOWED = (rel: string): boolean => + rel.startsWith("packages/config/") || + rel === "packages/aiclient/src/runtimeConfig.ts"; + +// --------------------------------------------------------------------------- +// Argument parsing +// --------------------------------------------------------------------------- + +interface Options { + root: string; + outDir: string; + includeTests: boolean; + minPackages: number; + top: number; + help: boolean; +} + +function parseIntArg(arg: string, next: string | undefined): number { + if (next === undefined || !/^\d+$/.test(next)) { + throw new Error(`${arg} requires a non-negative integer value`); + } + return parseInt(next, 10); +} + +function parseArgs(argv: string[]): Options { + const opts: Options = { + root: path.resolve(__dirname, "..", "..", ".."), + outDir: path.join(__dirname, "consistency-report"), + includeTests: false, + minPackages: 3, + top: 30, + help: false, + }; + + const tokens: string[] = []; + for (const raw of argv) { + const m = /^(--[\w-]+)=(.*)$/.exec(raw); + if (m) { + tokens.push(m[1], m[2]); + } else { + tokens.push(raw); + } + } + + for (let i = 0; i < tokens.length; i++) { + const arg = tokens[i]; + const next = tokens[i + 1]; + switch (arg) { + case "--help": + case "-h": + opts.help = true; + break; + case "--include-tests": + opts.includeTests = true; + break; + case "--min-packages": + opts.minPackages = parseIntArg(arg, next); + i++; + break; + case "--top": + opts.top = parseIntArg(arg, next); + i++; + break; + case "--root": + if (next === undefined) { + throw new Error(`${arg} requires a path`); + } + opts.root = path.resolve(next); + i++; + break; + case "--out-dir": + case "--outDir": + if (next === undefined) { + throw new Error(`${arg} requires a path`); + } + opts.outDir = path.resolve(next); + i++; + break; + default: + console.warn(`Ignoring unrecognized argument: ${arg}`); + break; + } + } + + if (opts.top <= 0) { + throw new Error("--top must be greater than 0"); + } + if (opts.minPackages < 2) { + throw new Error("--min-packages must be at least 2"); + } + + return opts; +} + +const HELP = `Pattern-consistency report for the TypeAgent ts/ tree. + +Usage: + npx tsx tools/scripts/code/consistencyReport.ts [options] + npm run code-consistency -- [options] + +Options: + --include-tests Include test files (excluded by default). + --min-packages Report an export only if it appears in at least this many + packages (default 3). + --top Number of rows to print / embed (default 30). + --root Directory to scan (default: the ts/ root). + --out-dir Output directory (default: tools/scripts/code/consistency-report). + --help Show this help.`; + +// --------------------------------------------------------------------------- +// File walking +// --------------------------------------------------------------------------- + +function* walk(dir: string): Generator { + let entries: fs.Dirent[]; + try { + entries = fs.readdirSync(dir, { withFileTypes: true }); + } catch { + return; + } + for (const e of entries) { + const full = path.join(dir, e.name); + if (e.isDirectory()) { + if (!IGNORE_DIR_NAMES.has(e.name)) { + yield* walk(full); + } + } else if (e.isFile()) { + yield full; + } + } +} + +function isCodeFile(name: string): boolean { + if (/\.d\.(ts|mts|cts)$/.test(name)) { + return false; + } + return CODE_EXTS.has(path.extname(name)); +} + +function isTestFile(rel: string): boolean { + return ( + /\.(spec|test)\./.test(rel) || + /(^|\/)(test|tests|__tests__)\//.test(rel) + ); +} + +/** Package key: everything before the first `/src/`, else leading segments. */ +function packageKeyOf(rel: string): string { + const f = rel.replace(/\\/g, "/"); + const i = f.indexOf("/src/"); + if (i >= 0) { + return f.slice(0, i); + } + const parts = f.split("/"); + if (parts[0] === "packages" && parts[1] === "agents") { + return parts.slice(0, 3).join("/"); + } + if ( + parts[0] === "packages" || + parts[0] === "examples" || + parts[0] === "extensions" + ) { + return parts.slice(0, 2).join("/"); + } + return parts.slice(0, Math.min(2, parts.length)).join("/"); +} + +// --------------------------------------------------------------------------- +// Analysis +// --------------------------------------------------------------------------- + +const EXPORT_RES = [ + /^export\s+(?:async\s+)?function\s+([A-Za-z_$][\w$]*)/gm, + /^export\s+(?:const|let|var)\s+(?!enum\b)([A-Za-z_$][\w$]*)/gm, + /^export\s+(?:abstract\s+)?class\s+([A-Za-z_$][\w$]*)/gm, +]; + +interface DuplicateExport { + name: string; + packageCount: number; + packages: string[]; + files: string[]; +} + +interface EnvFile { + file: string; + pkg: string; + refs: number; +} + +interface EnvPackage { + pkg: string; + refs: number; + files: number; +} + +interface AgentStatus { + agent: string; + hasManifest: boolean; + hasInstantiate: boolean; +} + +interface AnalysisResult { + filesScanned: number; + duplicateExports: DuplicateExport[]; + envPackages: EnvPackage[]; + envFiles: EnvFile[]; + envTotalRefs: number; + agents: AgentStatus[]; + agentIssues: AgentStatus[]; + elapsedMs: number; +} + +function analyze(opts: Options): AnalysisResult { + const started = Date.now(); + + const files: string[] = []; + for (const sub of SCAN_SUBDIRS) { + const abs = path.join(opts.root, sub); + if (!fs.existsSync(abs)) { + continue; + } + for (const full of walk(abs)) { + if (!isCodeFile(full)) { + continue; + } + const rel = path + .relative(opts.root, full) + .split(path.sep) + .join("/"); + if (!opts.includeTests && isTestFile(rel)) { + continue; + } + files.push(rel); + } + } + + // name -> packageKey -> set(files) + const exportMap = new Map>>(); + const envByFile: EnvFile[] = []; + + for (const rel of files) { + let content: string; + try { + content = fs.readFileSync(path.join(opts.root, rel), "utf8"); + } catch { + continue; + } + const pkg = packageKeyOf(rel); + + // Check 1: exported symbol names. + for (const re of EXPORT_RES) { + re.lastIndex = 0; + let m: RegExpExecArray | null; + while ((m = re.exec(content)) !== null) { + const name = m[1]; + if (EXPORT_DENYLIST.has(name)) { + continue; + } + let byPkg = exportMap.get(name); + if (!byPkg) { + byPkg = new Map(); + exportMap.set(name, byPkg); + } + let set = byPkg.get(pkg); + if (!set) { + set = new Set(); + byPkg.set(pkg, set); + } + set.add(rel); + } + } + + // Check 2: direct process.env access (production packages only). + if (rel.startsWith(ENV_SCAN_PREFIX) && !ENV_ALLOWED(rel)) { + const matches = content.match(/process\.env\b/g); + if (matches && matches.length > 0) { + envByFile.push({ file: rel, pkg, refs: matches.length }); + } + } + } + + // Duplicate exports: names in >= minPackages different packages. + const duplicateExports: DuplicateExport[] = []; + for (const [name, byPkg] of exportMap) { + if (byPkg.size >= opts.minPackages) { + const packages = [...byPkg.keys()].sort(); + const fileList: string[] = []; + for (const set of byPkg.values()) { + fileList.push(...set); + } + duplicateExports.push({ + name, + packageCount: byPkg.size, + packages, + files: fileList.sort(), + }); + } + } + duplicateExports.sort( + (a, b) => + b.packageCount - a.packageCount || a.name.localeCompare(b.name), + ); + + // process.env rollups. + const envPkgMap = new Map(); + let envTotalRefs = 0; + for (const e of envByFile) { + envTotalRefs += e.refs; + const r = envPkgMap.get(e.pkg) ?? { pkg: e.pkg, refs: 0, files: 0 }; + r.refs += e.refs; + r.files += 1; + envPkgMap.set(e.pkg, r); + } + const envPackages = [...envPkgMap.values()].sort((a, b) => b.refs - a.refs); + const envFiles = envByFile.sort((a, b) => b.refs - a.refs); + + // Check 3: agent conformance. + const agents = analyzeAgents(opts.root); + const agentIssues = agents.filter( + (a) => !a.hasManifest || !a.hasInstantiate, + ); + + return { + filesScanned: files.length, + duplicateExports, + envPackages, + envFiles, + envTotalRefs, + agents, + agentIssues, + elapsedMs: Date.now() - started, + }; +} + +function analyzeAgents(root: string): AgentStatus[] { + const agentsDir = path.join(root, "packages", "agents"); + if (!fs.existsSync(agentsDir)) { + return []; + } + const result: AgentStatus[] = []; + for (const e of fs.readdirSync(agentsDir, { withFileTypes: true })) { + if (!e.isDirectory() || IGNORE_DIR_NAMES.has(e.name)) { + continue; + } + const dir = path.join(agentsDir, e.name); + if (!fs.existsSync(path.join(dir, "package.json"))) { + continue; + } + let hasManifest = false; + let hasInstantiate = false; + for (const f of walk(dir)) { + const base = path.basename(f); + if (/manifest\.json$/i.test(base)) { + hasManifest = true; + } + if (isCodeFile(f) && !hasInstantiate) { + try { + if (/\binstantiate\b/.test(fs.readFileSync(f, "utf8"))) { + hasInstantiate = true; + } + } catch { + /* ignore */ + } + } + if (hasManifest && hasInstantiate) { + break; + } + } + result.push({ + agent: `packages/agents/${e.name}`, + hasManifest, + hasInstantiate, + }); + } + return result.sort((a, b) => a.agent.localeCompare(b.agent)); +} + +// --------------------------------------------------------------------------- +// Formatting helpers +// --------------------------------------------------------------------------- + +function csvEscape(value: string | number | boolean): string { + const s = String(value); + if (/[",\r\n]/.test(s)) { + return `"${s.replace(/"/g, '""')}"`; + } + return s; +} + +function htmlEscape(s: string): string { + return s.replace(/&/g, "&").replace(//g, ">"); +} + +function num(n: number): string { + return n.toLocaleString("en-US"); +} + +// --------------------------------------------------------------------------- +// Report writers +// --------------------------------------------------------------------------- + +function writeCsv(outDir: string, dups: DuplicateExport[]): string { + const header = ["Export", "PackageCount", "Packages"].join(","); + const rows = dups.map((d) => + [d.name, d.packageCount, d.packages.join(" | ")] + .map(csvEscape) + .join(","), + ); + const file = path.join(outDir, "duplicate-exports.csv"); + fs.writeFileSync(file, [header, ...rows].join("\n") + "\n", "utf8"); + return file; +} + +function writeJson( + outDir: string, + opts: Options, + result: AnalysisResult, +): string { + const payload = { + generatedAt: new Date().toISOString(), + root: opts.root, + includeTests: opts.includeTests, + minPackages: opts.minPackages, + filesScanned: result.filesScanned, + duplicateExports: result.duplicateExports, + processEnv: { + totalRefs: result.envTotalRefs, + totalFiles: result.envFiles.length, + perPackage: result.envPackages, + files: result.envFiles, + }, + agents: { + total: result.agents.length, + conforming: result.agents.length - result.agentIssues.length, + issues: result.agentIssues, + all: result.agents, + }, + }; + const file = path.join(outDir, "report.json"); + fs.writeFileSync(file, JSON.stringify(payload, null, 2) + "\n", "utf8"); + return file; +} + +function writeHtml( + outDir: string, + opts: Options, + result: AnalysisResult, +): string { + const dupRows = result.duplicateExports + .slice(0, Math.max(opts.top, 100)) + .map( + (d) => + `${htmlEscape(d.name)}${d.packageCount} + ${d.packages.map(htmlEscape).join("
")}`, + ) + .join("\n"); + + const envRows = result.envPackages + .slice(0, Math.max(opts.top, 60)) + .map( + (p) => + `${htmlEscape(p.pkg)}${num(p.refs)}${num(p.files)}`, + ) + .join("\n"); + + const agentRows = result.agents + .map((a) => { + const ok = a.hasManifest && a.hasInstantiate; + return `${htmlEscape(a.agent)} + ${a.hasManifest ? "yes" : "MISSING"} + ${a.hasInstantiate ? "yes" : "MISSING"}`; + }) + .join("\n"); + + return ` + +TypeAgent consistency report + +

TypeAgent consistency report

+
generated ${htmlEscape(new Date().toISOString())} · + ${num(result.filesScanned)} files · min-packages ${opts.minPackages} · + tests ${opts.includeTests ? "included" : "excluded"} · heuristic — review candidates
+ +
+
${num(result.duplicateExports.length)}
cross-package duplicate exports
+
${num(result.envTotalRefs)}
direct process.env refs
+
${num(result.envFiles.length)}
files using process.env
+
${num(result.agentIssues.length)}
non-conforming agents
+
+ +

Duplicate exports across packages

+ +${dupRows || ''}
ExportPackagesWhere
None
+ +

Direct process.env usage (packages/, excl. config)

+ +${envRows || ''}
PackageRefsFiles
None
+ +

Agent layout conformance

+ +${agentRows}
AgentManifestinstantiate()
+ + +`; +} + +// --------------------------------------------------------------------------- +// Console summary +// --------------------------------------------------------------------------- + +function printSummary(opts: Options, result: AnalysisResult): void { + console.log(""); + console.log("Consistency report (heuristic — review candidates)"); + console.log( + `Scanned ${num(result.filesScanned)} files | elapsed ${(result.elapsedMs / 1000).toFixed(1)}s`, + ); + + console.log(""); + console.log( + `1. Duplicate exports across >=${opts.minPackages} packages: ${num(result.duplicateExports.length)}`, + ); + for (const d of result.duplicateExports.slice(0, opts.top)) { + console.log( + ` ${String(d.packageCount).padStart(2)} pkgs ${d.name.padEnd(26)} ${d.packages.join(", ")}`, + ); + } + + console.log(""); + console.log( + `2. Direct process.env in packages/ (excl. config): ${num(result.envTotalRefs)} refs in ${num(result.envFiles.length)} files`, + ); + for (const p of result.envPackages.slice(0, opts.top)) { + console.log( + ` ${String(p.refs).padStart(4)} refs / ${String(p.files).padStart(2)} files ${p.pkg}`, + ); + } + + console.log(""); + console.log( + `3. Agent conformance: ${result.agents.length - result.agentIssues.length}/${result.agents.length} conform`, + ); + if (result.agentIssues.length === 0) { + console.log(" All agents ship a Manifest + instantiate()."); + } else { + for (const a of result.agentIssues) { + const miss = [ + a.hasManifest ? null : "Manifest", + a.hasInstantiate ? null : "instantiate()", + ] + .filter(Boolean) + .join(" + "); + console.log(` ${a.agent} — missing ${miss}`); + } + } +} + +// --------------------------------------------------------------------------- +// Main +// --------------------------------------------------------------------------- + +function main(): void { + let opts: Options; + try { + opts = parseArgs(process.argv.slice(2)); + } catch (e) { + console.error((e as Error).message); + process.exitCode = 2; + return; + } + + if (opts.help) { + console.log(HELP); + return; + } + + fs.mkdirSync(opts.outDir, { recursive: true }); + const result = analyze(opts); + + const csv = writeCsv(opts.outDir, result.duplicateExports); + const json = writeJson(opts.outDir, opts, result); + const html = writeHtml(opts.outDir, opts, result); + fs.writeFileSync(path.join(opts.outDir, "report.html"), html, "utf8"); + + printSummary(opts, result); + console.log(""); + console.log("Reports written to:"); + console.log(` ${path.relative(opts.root, csv).split(path.sep).join("/")}`); + console.log( + ` ${path.relative(opts.root, json).split(path.sep).join("/")}`, + ); + console.log( + ` ${path.relative(opts.root, path.join(opts.outDir, "report.html")).split(path.sep).join("/")}`, + ); +} + +main(); diff --git a/ts/tools/scripts/code/duplicationReport.ts b/ts/tools/scripts/code/duplicationReport.ts new file mode 100644 index 0000000000..db94c682a4 --- /dev/null +++ b/ts/tools/scripts/code/duplicationReport.ts @@ -0,0 +1,798 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * Copy/paste (code duplication) report for the TypeAgent ts/ tree. + * + * The analysis engine is jscpd — the de-facto-standard copy/paste detector for + * source code — so the numbers come from a well-known, widely-used token-based + * implementation (Rabin-Karp fingerprinting) rather than a bespoke heuristic. + * This mirrors the sibling complexityReport.ts, which harvests its metrics from + * ESLint. + * + * jscpd@4 ships a programmatic API, but its ESM entry pulls a dependency + * (`colors/safe`) that breaks under Node's native ESM loader. We therefore load + * its CommonJS build via `createRequire`, which resolves that dependency + * correctly while keeping this script itself an ES module. + * + * On top of the raw jscpd output this report adds two things that matter for a + * maintainability pass: + * - Cross-package clones: clone pairs whose two sides live in different + * packages. These are the real consolidation targets (shared helpers), + * as opposed to within-file/within-package repetition. + * - Per-package rollups: which packages carry the most duplicated lines. + * + * Outputs (written to --out-dir, default tools/scripts/code/duplication-report): + * - clones.csv : every detected clone pair, ranked by duplicated lines + * - report.json : structured metrics (totals, per-format, per-package, clones) + * - report.html : a self-contained, sortable report (open in a browser) + * plus a console summary (totals + the worst offenders + cross-package hotspots). + * + * Usage: + * npx tsx tools/scripts/code/duplicationReport.ts [options] + * npm run code-duplication -- [options] + * + * Options: + * --include-tests Include test files (*.spec.*, *.test.*, test dirs). + * Excluded by default. + * --min-tokens Minimum token length of a clone (default 50, jscpd's + * default). Lower = more, smaller clones. + * --min-lines Minimum line length of a clone (default 5). + * --top Number of worst offenders to print / embed (default 25). + * --root Directory to scan (default: the ts/ root). + * --out-dir Output directory (default tools/scripts/code/duplication-report). + * --help Show this help. + */ + +import { createRequire } from "node:module"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const require = createRequire(import.meta.url); + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// --------------------------------------------------------------------------- +// Configuration +// --------------------------------------------------------------------------- + +// Source subtrees scanned by default (relative to --root). Restricting to these +// keeps jscpd away from node_modules / build output entirely. +const SCAN_SUBDIRS = ["packages", "examples", "extensions", "tools"]; + +// jscpd formats to consider, plus the extension map so .mts/.cts/.mjs/.cjs are +// treated as TypeScript/JavaScript (the repo uses .mts heavily for agents). +const CODE_FORMATS = "typescript,tsx,javascript"; +const FORMATS_EXTS = "typescript:ts,tsx,mts,cts;javascript:js,jsx,mjs,cjs"; + +// Generated / build-output / vendored directories that are never source. +const IGNORE_DIRS = [ + "**/node_modules/**", + "**/dist/**", + "**/build/**", + "**/out/**", + "**/coverage/**", + "**/bin/**", + "**/obj/**", + "**/.turbo/**", + "**/.next/**", + "**/bundle/**", + "**/complexity-report/**", + "**/duplication-report/**", +]; + +// Generated single-file artifacts (declarations, bundles, minified, sourcemaps). +const IGNORE_FILES = [ + "**/*.d.ts", + "**/*.d.mts", + "**/*.min.js", + "**/*.bundle.js", + "**/*.map", +]; + +// Test files and directories, excluded unless --include-tests is passed. +const TEST_GLOBS = [ + "**/test/**", + "**/tests/**", + "**/__tests__/**", + "**/*.spec.*", + "**/*.test.*", +]; + +// Distribution buckets for the clone-size histogram (by duplicated lines). +const BUCKETS: { label: string; lo: number; hi: number; color: string }[] = [ + { label: "5-9 lines", lo: 5, hi: 9, color: "#9ccc65" }, + { label: "10-19 lines", lo: 10, hi: 19, color: "#ffca28" }, + { label: "20-49 lines", lo: 20, hi: 49, color: "#ffa726" }, + { label: "50-99 lines", lo: 50, hi: 99, color: "#ef5350" }, + { label: "100+ lines", lo: 100, hi: Infinity, color: "#e53935" }, +]; + +// --------------------------------------------------------------------------- +// jscpd report shape (only the fields we consume) +// --------------------------------------------------------------------------- + +interface JscpdFileRef { + name: string; + start: number; + end: number; +} + +interface JscpdDuplicate { + format: string; + lines: number; + tokens: number; + firstFile: JscpdFileRef; + secondFile: JscpdFileRef; +} + +interface JscpdTotals { + lines: number; + tokens: number; + sources: number; + clones: number; + duplicatedLines: number; + duplicatedTokens: number; + percentage: number; + percentageTokens: number; +} + +interface JscpdReport { + statistics: { detectionDate?: string; total: JscpdTotals }; + duplicates: JscpdDuplicate[]; +} + +type JscpdFn = ( + argv: string[], + exitCallback?: (code: number) => {}, +) => Promise; + +// --------------------------------------------------------------------------- +// Argument parsing +// --------------------------------------------------------------------------- + +interface Options { + root: string; + outDir: string; + includeTests: boolean; + minTokens: number; + minLines: number; + top: number; + help: boolean; +} + +function parseIntArg(arg: string, next: string | undefined): number { + if (next === undefined || !/^\d+$/.test(next)) { + throw new Error(`${arg} requires a non-negative integer value`); + } + return parseInt(next, 10); +} + +function parseArgs(argv: string[]): Options { + const opts: Options = { + root: path.resolve(__dirname, "..", "..", ".."), + outDir: path.join(__dirname, "duplication-report"), + includeTests: false, + minTokens: 50, + minLines: 5, + top: 25, + help: false, + }; + + // Normalize "--key=value" into ["--key", "value"]. + const tokens: string[] = []; + for (const raw of argv) { + const m = /^(--[\w-]+)=(.*)$/.exec(raw); + if (m) { + tokens.push(m[1], m[2]); + } else { + tokens.push(raw); + } + } + + for (let i = 0; i < tokens.length; i++) { + const arg = tokens[i]; + const next = tokens[i + 1]; + switch (arg) { + case "--help": + case "-h": + opts.help = true; + break; + case "--include-tests": + opts.includeTests = true; + break; + case "--min-tokens": + opts.minTokens = parseIntArg(arg, next); + i++; + break; + case "--min-lines": + opts.minLines = parseIntArg(arg, next); + i++; + break; + case "--top": + opts.top = parseIntArg(arg, next); + i++; + break; + case "--root": + if (next === undefined) { + throw new Error(`${arg} requires a path`); + } + opts.root = path.resolve(next); + i++; + break; + case "--out-dir": + case "--outDir": + if (next === undefined) { + throw new Error(`${arg} requires a path`); + } + opts.outDir = path.resolve(next); + i++; + break; + default: + console.warn(`Ignoring unrecognized argument: ${arg}`); + break; + } + } + + if (opts.top <= 0) { + throw new Error("--top must be greater than 0"); + } + + return opts; +} + +const HELP = `Copy/paste (code duplication) report for the TypeAgent ts/ tree. + +Usage: + npx tsx tools/scripts/code/duplicationReport.ts [options] + npm run code-duplication -- [options] + +Options: + --include-tests Include test files (excluded by default). + --min-tokens Minimum token length of a clone (default 50). + --min-lines Minimum line length of a clone (default 5). + --top Number of worst offenders to print / embed (default 25). + --root Directory to scan (default: the ts/ root). + --out-dir Output directory (default: tools/scripts/code/duplication-report). + --help Show this help.`; + +// --------------------------------------------------------------------------- +// Analysis +// --------------------------------------------------------------------------- + +interface CloneRecord { + format: string; + lines: number; + tokens: number; + crossPackage: boolean; + packageA: string; + fileA: string; + startA: number; + endA: number; + packageB: string; + fileB: string; + startB: number; + endB: number; +} + +interface PackageRollup { + pkg: string; + clones: number; + duplicatedLines: number; + crossPackageClones: number; +} + +interface AnalysisResult { + clones: CloneRecord[]; + totals: JscpdTotals; + perFormat: { format: string; clones: number; duplicatedLines: number }[]; + perPackage: PackageRollup[]; + crossPackagePairs: { + a: string; + b: string; + clones: number; + lines: number; + }[]; + elapsedMs: number; +} + +/** + * Derive a package key for a repo-relative file path. Everything before the + * first `/src/` segment identifies the package (handles nested packages such as + * `packages/dispatcher/dispatcher`). Falls back to the leading path segments. + */ +function packageKeyOf(file: string): string { + const f = file.replace(/\\/g, "/"); + const i = f.indexOf("/src/"); + if (i >= 0) { + return f.slice(0, i); + } + const parts = f.split("/"); + if (parts[0] === "packages" && parts[1] === "agents") { + return parts.slice(0, 3).join("/"); + } + if ( + parts[0] === "packages" || + parts[0] === "examples" || + parts[0] === "extensions" + ) { + return parts.slice(0, 2).join("/"); + } + return parts.slice(0, Math.min(2, parts.length)).join("/"); +} + +async function analyze(opts: Options): Promise { + const started = Date.now(); + + // Scan relative to the root so jscpd emits root-relative paths. + const originalCwd = process.cwd(); + process.chdir(opts.root); + + const rawDir = path.join(opts.outDir, ".jscpd"); + fs.rmSync(rawDir, { recursive: true, force: true }); + fs.mkdirSync(rawDir, { recursive: true }); + + const scanTargets = SCAN_SUBDIRS.filter((d) => fs.existsSync(d)); + if (scanTargets.length === 0) { + throw new Error( + `No source subdirectories (${SCAN_SUBDIRS.join(", ")}) found under ${opts.root}`, + ); + } + + const ignores = [...IGNORE_DIRS, ...IGNORE_FILES]; + if (!opts.includeTests) { + ignores.push(...TEST_GLOBS); + } + + const argv = [ + process.execPath, + "jscpd", + ...scanTargets, + "--min-tokens", + String(opts.minTokens), + "--min-lines", + String(opts.minLines), + "--format", + CODE_FORMATS, + "--formats-exts", + FORMATS_EXTS, + "--reporters", + "json", + "--mode", + "mild", + "--output", + rawDir, + "--ignore", + ignores.join(","), + "--gitignore", + "--silent", + ]; + + const { jscpd } = require("jscpd") as { jscpd: JscpdFn }; + // The exit callback intercepts jscpd's process.exit (it exits non-zero when + // a duplication threshold is crossed); we only want the report. + await jscpd(argv, (() => ({})) as (code: number) => {}); + + process.chdir(originalCwd); + + const reportPath = path.join(rawDir, "jscpd-report.json"); + if (!fs.existsSync(reportPath)) { + throw new Error(`jscpd did not produce a report at ${reportPath}`); + } + const report = JSON.parse( + fs.readFileSync(reportPath, "utf8"), + ) as JscpdReport; + + const norm = (p: string) => p.replace(/\\/g, "/"); + const clones: CloneRecord[] = (report.duplicates ?? []).map((d) => { + const fileA = norm(d.firstFile.name); + const fileB = norm(d.secondFile.name); + const packageA = packageKeyOf(fileA); + const packageB = packageKeyOf(fileB); + return { + format: d.format, + lines: d.lines, + tokens: d.tokens, + crossPackage: packageA !== packageB, + packageA, + fileA, + startA: d.firstFile.start, + endA: d.firstFile.end, + packageB, + fileB, + startB: d.secondFile.start, + endB: d.secondFile.end, + }; + }); + clones.sort((a, b) => b.lines - a.lines); + + // Per-format rollup. + const fmtMap = new Map< + string, + { clones: number; duplicatedLines: number } + >(); + for (const c of clones) { + const r = fmtMap.get(c.format) ?? { clones: 0, duplicatedLines: 0 }; + r.clones++; + r.duplicatedLines += c.lines; + fmtMap.set(c.format, r); + } + const perFormat = [...fmtMap.entries()] + .map(([format, v]) => ({ format, ...v })) + .sort((a, b) => b.duplicatedLines - a.duplicatedLines); + + // Per-package rollup (a clone contributes to both of its packages). + const pkgMap = new Map(); + const bump = (pkg: string, lines: number, cross: boolean) => { + const r = + pkgMap.get(pkg) ?? + ({ + pkg, + clones: 0, + duplicatedLines: 0, + crossPackageClones: 0, + } as PackageRollup); + r.clones++; + r.duplicatedLines += lines; + if (cross) { + r.crossPackageClones++; + } + pkgMap.set(pkg, r); + }; + for (const c of clones) { + bump(c.packageA, c.lines, c.crossPackage); + if (c.packageB !== c.packageA) { + bump(c.packageB, c.lines, c.crossPackage); + } + } + const perPackage = [...pkgMap.values()].sort( + (a, b) => b.duplicatedLines - a.duplicatedLines, + ); + + // Cross-package pair rollup (the consolidation targets). + const pairMap = new Map< + string, + { a: string; b: string; clones: number; lines: number } + >(); + for (const c of clones) { + if (!c.crossPackage) { + continue; + } + const [a, b] = [c.packageA, c.packageB].sort(); + const key = `${a}\u0000${b}`; + const r = pairMap.get(key) ?? { a, b, clones: 0, lines: 0 }; + r.clones++; + r.lines += c.lines; + pairMap.set(key, r); + } + const crossPackagePairs = [...pairMap.values()].sort( + (x, y) => y.lines - x.lines, + ); + + return { + clones, + totals: report.statistics.total, + perFormat, + perPackage, + crossPackagePairs, + elapsedMs: Date.now() - started, + }; +} + +// --------------------------------------------------------------------------- +// Formatting helpers +// --------------------------------------------------------------------------- + +function distribution(clones: CloneRecord[]): number[] { + const counts = new Array(BUCKETS.length).fill(0); + for (const c of clones) { + const idx = BUCKETS.findIndex( + (b) => c.lines >= b.lo && c.lines <= b.hi, + ); + if (idx >= 0) { + counts[idx]++; + } + } + return counts; +} + +function csvEscape(value: string | number | boolean): string { + const s = String(value); + if (/[",\r\n]/.test(s)) { + return `"${s.replace(/"/g, '""')}"`; + } + return s; +} + +function htmlEscape(s: string): string { + return s.replace(/&/g, "&").replace(//g, ">"); +} + +function num(n: number): string { + return n.toLocaleString("en-US"); +} + +// --------------------------------------------------------------------------- +// Report writers +// --------------------------------------------------------------------------- + +function writeCsv(outDir: string, clones: CloneRecord[]): string { + const header = [ + "DuplicatedLines", + "Tokens", + "Format", + "CrossPackage", + "PackageA", + "FileA", + "StartA", + "EndA", + "PackageB", + "FileB", + "StartB", + "EndB", + ].join(","); + const rows = clones.map((c) => + [ + c.lines, + c.tokens, + c.format, + c.crossPackage, + c.packageA, + c.fileA, + c.startA, + c.endA, + c.packageB, + c.fileB, + c.startB, + c.endB, + ] + .map(csvEscape) + .join(","), + ); + const file = path.join(outDir, "clones.csv"); + fs.writeFileSync(file, [header, ...rows].join("\n") + "\n", "utf8"); + return file; +} + +function writeJson( + outDir: string, + opts: Options, + result: AnalysisResult, +): string { + const payload = { + generatedAt: new Date().toISOString(), + root: opts.root, + includeTests: opts.includeTests, + thresholds: { minTokens: opts.minTokens, minLines: opts.minLines }, + totals: result.totals, + crossPackageClones: result.clones.filter((c) => c.crossPackage).length, + distribution: BUCKETS.map((b, i) => ({ + label: b.label, + lo: b.lo, + hi: b.hi === Infinity ? null : b.hi, + count: distribution(result.clones)[i], + })), + perFormat: result.perFormat, + perPackage: result.perPackage, + crossPackagePairs: result.crossPackagePairs, + clones: result.clones, + }; + const file = path.join(outDir, "report.json"); + fs.writeFileSync(file, JSON.stringify(payload, null, 2) + "\n", "utf8"); + return file; +} + +function writeHtml( + outDir: string, + opts: Options, + result: AnalysisResult, +): string { + const t = result.totals; + const dist = distribution(result.clones); + const maxDist = Math.max(1, ...dist); + const crossCount = result.clones.filter((c) => c.crossPackage).length; + + const distRows = BUCKETS.map((b, i) => { + const w = Math.round((dist[i] / maxDist) * 100); + return `${htmlEscape(b.label)}${num(dist[i])} + `; + }).join("\n"); + + const pkgRows = result.perPackage + .slice(0, 40) + .map( + (p) => + `${htmlEscape(p.pkg)}${num(p.duplicatedLines)} + ${num(p.clones)}${num(p.crossPackageClones)}`, + ) + .join("\n"); + + const pairRows = result.crossPackagePairs + .slice(0, opts.top) + .map( + (p) => + `${num(p.lines)}${num(p.clones)} + ${htmlEscape(p.a)}${htmlEscape(p.b)}`, + ) + .join("\n"); + + const cloneRows = result.clones + .slice(0, Math.max(opts.top, 100)) + .map( + (c) => + ` + ${num(c.lines)}${num(c.tokens)} + ${c.crossPackage ? "yes" : ""} + ${htmlEscape(c.fileA)}:${c.startA}-${c.endA} + ${htmlEscape(c.fileB)}:${c.startB}-${c.endB}`, + ) + .join("\n"); + + return ` + +TypeAgent duplication report + +

TypeAgent duplication report

+
engine: jscpd · generated ${htmlEscape(new Date().toISOString())} · + min-tokens ${opts.minTokens}, min-lines ${opts.minLines} · + tests ${opts.includeTests ? "included" : "excluded"}
+ +
+
${num(t.clones)}
clone pairs
+
${num(t.duplicatedLines)}
duplicated lines (${t.percentage}%)
+
${num(crossCount)}
cross-package clones
+
${num(t.sources)}
files scanned
+
${num(t.lines)}
lines scanned
+
+ +

Clone size distribution

+ +${distRows}
BucketClones 
+ +

Cross-package hotspots (consolidation targets)

+ +${pairRows || ''}
Dup linesClonesPackage APackage B
None
+ +

Packages by duplicated lines

+ +${pkgRows}
PackageDup linesClonesCross-pkg
+ +

Largest clones

+ +${cloneRows}
LinesTokensCrossLocation ALocation B
+ + +`; +} + +// --------------------------------------------------------------------------- +// Console summary +// --------------------------------------------------------------------------- + +function printSummary(opts: Options, result: AnalysisResult): void { + const t = result.totals; + const dist = distribution(result.clones); + const crossCount = result.clones.filter((c) => c.crossPackage).length; + + console.log(""); + console.log("Duplication report (engine: jscpd)"); + console.log( + `Scanned ${num(t.sources)} files, ${num(t.lines)} lines | ` + + `elapsed ${(result.elapsedMs / 1000).toFixed(1)}s | ` + + `min-tokens ${opts.minTokens}, min-lines ${opts.minLines}`, + ); + console.log(""); + console.log( + `Clones: ${num(t.clones)} | Duplicated: ${num(t.duplicatedLines)} lines ` + + `(${t.percentage}%), ${num(t.duplicatedTokens)} tokens (${t.percentageTokens}%)`, + ); + console.log(`Cross-package clones: ${num(crossCount)}`); + + console.log(""); + console.log("Clone size distribution:"); + BUCKETS.forEach((b, i) => { + console.log(` ${b.label.padEnd(14)} ${num(dist[i])}`); + }); + + const topClones = result.clones.slice(0, opts.top); + if (topClones.length) { + console.log(""); + console.log(`Largest clones (top ${topClones.length}):`); + for (const c of topClones) { + const tag = c.crossPackage ? " [cross-pkg]" : ""; + console.log( + ` ${String(c.lines).padStart(4)}L ${c.fileA}:${c.startA}-${c.endA}` + + ` <-> ${c.fileB}:${c.startB}-${c.endB}${tag}`, + ); + } + } + + if (result.crossPackagePairs.length) { + console.log(""); + console.log("Cross-package hotspots (consolidation targets):"); + for (const p of result.crossPackagePairs.slice(0, opts.top)) { + console.log( + ` ${String(p.lines).padStart(4)}L / ${String(p.clones).padStart(2)} clones ` + + `${p.a} <-> ${p.b}`, + ); + } + } +} + +// --------------------------------------------------------------------------- +// Main +// --------------------------------------------------------------------------- + +async function main(): Promise { + let opts: Options; + try { + opts = parseArgs(process.argv.slice(2)); + } catch (e) { + console.error((e as Error).message); + process.exitCode = 2; + return; + } + + if (opts.help) { + console.log(HELP); + return; + } + + fs.mkdirSync(opts.outDir, { recursive: true }); + const result = await analyze(opts); + + const csv = writeCsv(opts.outDir, result.clones); + const json = writeJson(opts.outDir, opts, result); + const html = writeHtml(opts.outDir, opts, result); + fs.writeFileSync(path.join(opts.outDir, "report.html"), html, "utf8"); + + printSummary(opts, result); + console.log(""); + console.log("Reports written to:"); + console.log(` ${path.relative(opts.root, csv).split(path.sep).join("/")}`); + console.log( + ` ${path.relative(opts.root, json).split(path.sep).join("/")}`, + ); + console.log( + ` ${path.relative(opts.root, path.join(opts.outDir, "report.html")).split(path.sep).join("/")}`, + ); +} + +main().catch((e) => { + console.error(e); + process.exitCode = 1; +}); From 7f17f02a28e64bb4131102f230a1e7050c31c22c Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Fri, 3 Jul 2026 13:45:52 -0700 Subject: [PATCH 05/23] added more code quality tooling. --- ts/.gitignore | 12 + ts/package.json | 6 + ts/pnpm-lock.yaml | 1343 +++++++++++++++++-- ts/tools/scripts/code/circularDepsReport.ts | 654 +++++++++ ts/tools/scripts/code/deadCodeReport.ts | 528 ++++++++ ts/tools/scripts/code/debtMarkersReport.ts | 678 ++++++++++ ts/tools/scripts/code/knip.jsonc | 14 + ts/tools/scripts/code/lintReport.ts | 878 ++++++++++++ 8 files changed, 4003 insertions(+), 110 deletions(-) create mode 100644 ts/tools/scripts/code/circularDepsReport.ts create mode 100644 ts/tools/scripts/code/deadCodeReport.ts create mode 100644 ts/tools/scripts/code/debtMarkersReport.ts create mode 100644 ts/tools/scripts/code/knip.jsonc create mode 100644 ts/tools/scripts/code/lintReport.ts diff --git a/ts/.gitignore b/ts/.gitignore index 56c92868f8..292c219962 100644 --- a/ts/.gitignore +++ b/ts/.gitignore @@ -32,6 +32,18 @@ tools/scripts/code/duplication-report/ # Generated consistency report output (tools/scripts/code/consistencyReport.ts). tools/scripts/code/consistency-report/ +# Generated lint report output (tools/scripts/code/lintReport.ts). +tools/scripts/code/lint-report/ + +# Generated circular-dependency report output (tools/scripts/code/circularDepsReport.ts). +tools/scripts/code/circular-report/ + +# Generated dead-code report output (tools/scripts/code/deadCodeReport.ts). +tools/scripts/code/deadcode-report/ + +# Generated debt-markers report output (tools/scripts/code/debtMarkersReport.ts). +tools/scripts/code/debt-report/ + # Per-developer YAML config overrides loaded by @typeagent/config. # Holds local endpoints, deployment names, and (in Phase 1) secrets. # Never commit. diff --git a/ts/package.json b/ts/package.json index a52c7e3059..e67f8bd1a5 100644 --- a/ts/package.json +++ b/ts/package.json @@ -25,9 +25,13 @@ "clean": "fluid-build . -t clean", "cli": "pnpm -C packages/cli run start", "cli:dev": "pnpm -C packages/cli run start:dev", + "code-circular": "npx tsx tools/scripts/code/circularDepsReport.ts", "code-complexity": "npx tsx tools/scripts/code/complexityReport.ts", "code-consistency": "npx tsx tools/scripts/code/consistencyReport.ts", + "code-deadcode": "npx tsx tools/scripts/code/deadCodeReport.ts", + "code-debt": "npx tsx tools/scripts/code/debtMarkersReport.ts", "code-duplication": "npx tsx tools/scripts/code/duplicationReport.ts", + "code-lint": "npx tsx tools/scripts/code/lintReport.ts", "copilot": "node packages/copilot-plugin/scripts/launch.mjs", "copilot:dev": "node packages/copilot-plugin/scripts/launch-dev.mjs", "devtunnel:setup": "node tools/scripts/setup-devtunnel.mjs", @@ -79,6 +83,8 @@ "eslint": "^10.6.0", "eslint-plugin-sonarjs": "^4.1.0", "jscpd": "^4.2.5", + "knip": "^6.24.0", + "madge": "^8.0.0", "markdown-link-check": "^3.14.2", "prettier": "^3.5.3", "shx": "^0.4.0", diff --git a/ts/pnpm-lock.yaml b/ts/pnpm-lock.yaml index 16b32d1573..9ea89b9954 100644 --- a/ts/pnpm-lock.yaml +++ b/ts/pnpm-lock.yaml @@ -29,13 +29,19 @@ importers: version: 22.15.18 eslint: specifier: ^10.6.0 - version: 10.6.0(jiti@2.5.1) + version: 10.6.0(jiti@2.7.0) eslint-plugin-sonarjs: specifier: ^4.1.0 - version: 4.1.0(eslint@10.6.0(jiti@2.5.1)) + version: 4.1.0(eslint@10.6.0(jiti@2.7.0)) jscpd: specifier: ^4.2.5 version: 4.2.5 + knip: + specifier: ^6.24.0 + version: 6.24.0 + madge: + specifier: ^8.0.0 + version: 8.0.0(typescript@5.9.3) markdown-link-check: specifier: ^3.14.2 version: 3.14.2 @@ -50,7 +56,7 @@ importers: version: 4.21.0 typescript-eslint: specifier: ^8.62.1 - version: 8.62.1(eslint@10.6.0(jiti@2.5.1))(typescript@5.4.5) + version: 8.62.1(eslint@10.6.0(jiti@2.7.0))(typescript@5.9.3) examples/agentExamples/echo: dependencies: @@ -2098,7 +2104,7 @@ importers: version: 9.5.2(typescript@5.4.5)(webpack@5.105.0(esbuild@0.28.1)) vite: specifier: ^6.4.3 - version: 6.4.3(@types/node@22.15.18)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.3) + version: 6.4.3(@types/node@22.15.18)(jiti@2.7.0)(less@4.3.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.3) packages/agents/calendar: dependencies: @@ -2707,7 +2713,7 @@ importers: version: 5.4.5 vite: specifier: ^6.4.3 - version: 6.4.3(@types/node@25.9.3)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.9.0) + version: 6.4.3(@types/node@25.9.3)(jiti@2.7.0)(less@4.3.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.9.0) packages/agents/montage: dependencies: @@ -5629,7 +5635,7 @@ importers: version: 26.8.1(dmg-builder@26.8.1) electron-vite: specifier: ^4.0.1 - version: 4.0.1(vite@6.4.3(@types/node@25.9.3)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.9.0)) + version: 4.0.1(vite@6.4.3(@types/node@25.9.3)(jiti@2.7.0)(less@4.3.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.9.0)) jest: specifier: ^29.7.0 version: 29.7.0(@types/node@25.9.3)(ts-node@10.9.2(@types/node@25.9.3)(typescript@5.4.5)) @@ -5650,7 +5656,7 @@ importers: version: 5.4.5 vite: specifier: ^6.4.3 - version: 6.4.3(@types/node@25.9.3)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.9.0) + version: 6.4.3(@types/node@25.9.3)(jiti@2.7.0)(less@4.3.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.9.0) packages/studio-service: dependencies: @@ -6304,13 +6310,13 @@ importers: version: 16.5.0 eslint: specifier: ^10.6.0 - version: 10.6.0(jiti@2.5.1) + version: 10.6.0(jiti@2.7.0) eslint-plugin-sonarjs: specifier: ^4.1.0 - version: 4.1.0(eslint@10.6.0(jiti@2.5.1)) + version: 4.1.0(eslint@10.6.0(jiti@2.7.0)) typescript-eslint: specifier: ^8.62.1 - version: 8.62.1(eslint@10.6.0(jiti@2.5.1))(typescript@5.4.5) + version: 8.62.1(eslint@10.6.0(jiti@2.7.0))(typescript@5.9.3) tools/docsAutogen: dependencies: @@ -7158,6 +7164,10 @@ packages: resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} engines: {node: '>=20.19.0'} + '@dependents/detective-less@5.0.3': + resolution: {integrity: sha512-v6oD9Ukp+N7V4n6p5I/+mM5fIohSfkrDSGlFm5w/pYmchvbk+sMIHsLxrFJ5Lnujewj1BzWL0K84d88lwZAMQA==} + engines: {node: '>=18'} + '@develar/schema-utils@2.6.5': resolution: {integrity: sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==} engines: {node: '>= 8.9.0'} @@ -7166,6 +7176,10 @@ packages: resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} engines: {node: '>=10.0.0'} + '@discoveryjs/json-ext@1.1.0': + resolution: {integrity: sha512-Xc3VhU02wqZ1HvHRJUwL09HkZSTvidqY5Ya0NXBSYOxAp+Ln9dcJr9fySI+CkONzP3PekQo9WdzCv0PGER/mOA==} + engines: {node: '>=14.17.0'} + '@elastic/elasticsearch@9.3.4': resolution: {integrity: sha512-Mp14fPEYx+WTfZdcvAaZ9WkLYGHQCbwMx6EP5VCucYdhv4cn/g2sbnMT5HzK+gX3XEpBnnkEK/+WysCKzxuo3A==} engines: {node: '>=18'} @@ -7224,12 +7238,24 @@ packages: engines: {node: '>=14.14'} hasBin: true + '@emnapi/core@1.11.0': + resolution: {integrity: sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==} + + '@emnapi/core@1.11.1': + resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} + + '@emnapi/runtime@1.11.0': + resolution: {integrity: sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==} + '@emnapi/runtime@1.11.1': resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} '@emnapi/runtime@1.4.0': resolution: {integrity: sha512-64WYIf4UYcdLnbKn/umDlNjQDSS8AgZrI/R9+x5ilkUVFxXcA1Ebl+gQLc/6mERA4407Xof0R7wEyEuj091CVw==} + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + '@esbuild-plugins/node-globals-polyfill@0.2.3': resolution: {integrity: sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==} peerDependencies: @@ -8940,6 +8966,12 @@ packages: resolution: {integrity: sha512-ypTJ/DXzsJbTU3o7qXFlWmZGgEbh42JWQl7v5/i+DJz/HURELcSnq9ler9e1ukqma70JzmCQcIseiE/Xs6sczw==} engines: {node: '>= 10'} + '@napi-rs/wasm-runtime@1.1.6': + resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + '@noble/hashes@1.4.0': resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} engines: {node: '>= 16'} @@ -9036,6 +9068,239 @@ packages: resolution: {integrity: sha512-aKcOkyrorBGlajjRdVoJWHTxfxO1vCNHLJVlSDaRHDIdjU+pX8IYQPvPDkYiujKLbRnWU+1TBwEt0QRgSm4SGA==} engines: {node: '>=14'} + '@oxc-parser/binding-android-arm-eabi@0.137.0': + resolution: {integrity: sha512-KDs+0VPdEmasOkpuJHW9V5WCF+cvYdMQv2Jd+aJXt+cxIx12NToRQRbXaRwUEDsZw+/jMk81Ve8ZFbjUkJTOwA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxc-parser/binding-android-arm64@0.137.0': + resolution: {integrity: sha512-WhALNzfy3x/RfC6bsqX+csavuUY0yHHE7XfgPE5M542uhoBZUUoGTPG+nkMbGoG4+gcfss5s7urMyn5QBHu0sw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxc-parser/binding-darwin-arm64@0.137.0': + resolution: {integrity: sha512-bFPr5hgmNMOMoyPTGtdsK4Ug21RovIPojRMgDDhSp1LtCnc/DkLwGONKjgRjszg677RlGnkYSviQ8hHaUPOVYA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxc-parser/binding-darwin-x64@0.137.0': + resolution: {integrity: sha512-CL5dMm1asqXIDZHg14FLxj3Mc36w8PI7xCWh1uA4is6z8g2XrIILoTcQYOxDbwzuk34RDPX5IAGUxZr6LA9KAg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxc-parser/binding-freebsd-x64@0.137.0': + resolution: {integrity: sha512-79h8rYGnSlKPGWo7mHr2ixO6ea7aW8B0CT965SZ8SLbNnCOH5aOYBTeVXUY6eMvEaiLyWr8Skuiugr5pDYgLGw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxc-parser/binding-linux-arm-gnueabihf@0.137.0': + resolution: {integrity: sha512-ASgmlSimhGyr0lksgVIo6hibz1obnDq4qJbiMX/AzltfgPnanRrzG1Q+23g8ljOHOjv6dsznkUuCYL3gg0sY1Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm-musleabihf@0.137.0': + resolution: {integrity: sha512-AU2J9aa22Sx32wRGnDjybOU9TQXXQUud5sdUi+ZB0XxwM8aToWLweV+yA0wlQm0yIUVqljquqoHCYEq9II8gJQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm64-gnu@0.137.0': + resolution: {integrity: sha512-GdEtiG89yMr7XkUGxifgodXEEm2f+xW2f9CpDjlgAnBOwhTmrpQMvhOGobLVKUyzf/qHBXW16smk5zbF3nZU6w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-arm64-musl@0.137.0': + resolution: {integrity: sha512-EGJ+Bs8iXx8KBH8DQ5BLoEm5lnHaYjlh4/8j8vFhrr/6z4tqONy5BZDzLpKmmNWlN6Hlc5r8YOuBVHqZ9vRFEQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-linux-ppc64-gnu@0.137.0': + resolution: {integrity: sha512-vzFUQENy/fnbSe5DZWovq6tIBc1uhuMztanSW6rz1e9WdQE4gHwYuD7ZII6JnrJifd1R3RSoqiZbgRFlVL2tYQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-gnu@0.137.0': + resolution: {integrity: sha512-SfVI14HBQs9gtLcUD5hTt5hsNbdrqSUNg9S8muN+LhVQ5nf1WwH3hAoK6B9NKgdYgWAQSXFXGiiBedQ4r/BKuw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-musl@0.137.0': + resolution: {integrity: sha512-e7Ppy4FCIFNQxT/ikSeIWFoQ0l+N9vgtRBtLcyZXeolTzApyVoPqEXsYPrcdM/9i0Bwk8knvYd37vaEMxHyi6g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-linux-s390x-gnu@0.137.0': + resolution: {integrity: sha512-Bho5qFwdhqsIFR7gipYEUlqvi3SRrY8sugxXig380MIaakBB1PyU9+7dBiBVScfImTNWhijUxdBwqrprGdq5WA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-x64-gnu@0.137.0': + resolution: {integrity: sha512-36mGWtg7PyFzjJwGDkH6/F4o2nIDEoKXLPr/X/lwqklkomQwJJt1I5GJVmGhovUEmgPK5WAeAZMqlFCehwiy9Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-x64-musl@0.137.0': + resolution: {integrity: sha512-/Jqx6+N7A44n2BdvUr7pXhVr2vFjs6WGH3unZRczwrfiH0H1zY0QwKQMG/dtRiTlKGDKGukznPT8lx84/oEsZg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-openharmony-arm64@0.137.0': + resolution: {integrity: sha512-9Uj0qHNNl+OgT1UTGwF7ixIXU6T1u2SbMidmgPy/h1h/fl2gRS6YpAxxY1gwHofcWjoTwkoMFd8xs5Vuj6GOFA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxc-parser/binding-wasm32-wasi@0.137.0': + resolution: {integrity: sha512-gW2vfkytNGgMVADiuzdvOfw0mWG9za20F/1fCJsif5aBMAvWJTSbpIXbIe0XkOe0VENk+PadpQ7cZgUy2sUJcA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@oxc-parser/binding-win32-arm64-msvc@0.137.0': + resolution: {integrity: sha512-x+pFANF0yL5uK/6T7lu6SlR5qid6sp//eZXKLq5iNsIE+EQg6EaS8/wsW7E91nXXjpnPhSoMOHXShSVhGRdn8w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxc-parser/binding-win32-ia32-msvc@0.137.0': + resolution: {integrity: sha512-sQUqym80PFi6McRsIqfJrSu2JrSClEZIXXD+/FjAFoULEKzOPsldIdFBG96xdX8aVMzCNQ9792FPx3MfkEIrFA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxc-parser/binding-win32-x64-msvc@0.137.0': + resolution: {integrity: sha512-2AsevxlvNN4WKxpEn3RtqD5zbqMaXF+T7JXblsP4gVuY+vC9dXS4ED/PwfRCliFqoeisYS3Iro4DHzxr0TEvVA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxc-project/types@0.137.0': + resolution: {integrity: sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==} + + '@oxc-resolver/binding-android-arm-eabi@11.21.3': + resolution: {integrity: sha512-eNU11A2WNizh04v3uyaJCootrHIaS0B9aHYXvAvVnPNk4xYSjMUjHnhQ6dewPN2MRYDskV85d1N0Aw0WNWhcyg==} + cpu: [arm] + os: [android] + + '@oxc-resolver/binding-android-arm64@11.21.3': + resolution: {integrity: sha512-8Q+ZjTLvn2dIcWsrmhdrEihm7q+ag/k+mkry7Z+t0QbbHaVxXQfvH9AewyVMh/WrpEKhQ3DDgx9fYbqeCpeOEw==} + cpu: [arm64] + os: [android] + + '@oxc-resolver/binding-darwin-arm64@11.21.3': + resolution: {integrity: sha512-wkh0qKZGHXVUDxFw3oA1TXnU2BDYY/r775oJflGeIr8uDPPoN2pk8gijQIzYRT6hoql/lg3+Tx/SaTn9e2/aGg==} + cpu: [arm64] + os: [darwin] + + '@oxc-resolver/binding-darwin-x64@11.21.3': + resolution: {integrity: sha512-HbNc23FAQYbuyDV2vBWMez4u4mrsm5RAkniGZAWqr6lYZ3N4beeqIb776jzwRl8qL2zRhHVXpUj97X0QgogVzg==} + cpu: [x64] + os: [darwin] + + '@oxc-resolver/binding-freebsd-x64@11.21.3': + resolution: {integrity: sha512-K6xNsTUPEUdfrn0+kbMq5nOUB5w1C5pavPQngt4TM2FpN91lP0PBe2srSpamb4d69O7h86oAi/qWX/kZNRSjkw==} + cpu: [x64] + os: [freebsd] + + '@oxc-resolver/binding-linux-arm-gnueabihf@11.21.3': + resolution: {integrity: sha512-VcFmOpcpWX1zoEy8M58tR2M9YxM+Z9RuQhqAx5q0CTmrruaP7Gveejg75hzd/5sg5nk9G3aLALEa3hE2FsmmTQ==} + cpu: [arm] + os: [linux] + + '@oxc-resolver/binding-linux-arm-musleabihf@11.21.3': + resolution: {integrity: sha512-quVoxFLBy43hWaQbbDtQNRwAX5vX76mv7n64icAtQcJ3eNgVeblqmkupF/hAneNthdqSlnd1sTjb3aQSaDPaCQ==} + cpu: [arm] + os: [linux] + + '@oxc-resolver/binding-linux-arm64-gnu@11.21.3': + resolution: {integrity: sha512-X0AqNZgcD07Q4V3RDK18/vYOj/HQT/FnmEFGYS2jTWqY7JO13ryE3TEs3eAIgUJhBnNkpEaiXqz3VK8M7qQhWQ==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-arm64-musl@11.21.3': + resolution: {integrity: sha512-YkaQnaKYdbuaXvRt5Qd0GpbihzVnyfR6z1SpYfIUC6RTu4NF7lDKPjVkYb+jRI2gedVO2rVpN35Y6akG6ud4Lw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-linux-ppc64-gnu@11.21.3': + resolution: {integrity: sha512-gB9HwhrPiFqUzDeEq+y/CgAijz1YdI6BnXz5GaH2Pa9cWdutchlkGFAiAuGb/PjVQpiK6NFKzFuztxrweoit7A==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-riscv64-gnu@11.21.3': + resolution: {integrity: sha512-zjDWBlYk8QGv0H8dsPUWqkfjYIIjG2TvspGkzXL0eImbgxtZorA/klKeHyolevoT3Kvbi+1iMr9Lhrh7jf54Og==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-riscv64-musl@11.21.3': + resolution: {integrity: sha512-4UfsQvacV388y1zpXL7C1x1FNYaV52JtuNRiuzrfQA2z1z6ElVrsidkGsrvQ5EgeSq1Pj7kaKqrgGkvFuxJ/tw==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-linux-s390x-gnu@11.21.3': + resolution: {integrity: sha512-b5uH+HKH0MP5mNBYaK75SKsJbw52URqrx2LavYdq6wb0l3ExAG5niYRP9DWUNHdKilpaBVM2bXk9HNWrH3ew7Q==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-x64-gnu@11.21.3': + resolution: {integrity: sha512-PjYlmilBpNRh2ntXNYAK3Am5w/nPfEpnU/96iNx7CI8EzAn12J4JRiec63wHJTH31nLoCNxBg/829pN+3CfG3Q==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-x64-musl@11.21.3': + resolution: {integrity: sha512-QTBAb7JuHlZ7JUEyM8UiQi2f7m/L4swBhP2TNpYIDc9Wp/wRw1G/8sl6i13aIzQAXH7LKIm294LeOHd0lQR8zA==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-openharmony-arm64@11.21.3': + resolution: {integrity: sha512-4j1DFwjwv36ec9kds0jU/ucQ5Ha4ERO/H95BxR5JFf0kqUUAJ1kwII7XhTc1vZrkdJkvLGC9Q2MbpObpum8RBg==} + cpu: [arm64] + os: [openharmony] + + '@oxc-resolver/binding-wasm32-wasi@11.21.3': + resolution: {integrity: sha512-i8oluoel5kru/j1WNrjmQSiA3GQ7wvIYVR1IwIoZtKogAhya2iub+ZKIeSIkcJOrnzQ18Tzl/F+kL3fYOxZLvA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@oxc-resolver/binding-win32-arm64-msvc@11.21.3': + resolution: {integrity: sha512-M/8dw8dD6aOs+NlPJax401CZB9I7Aut84isQLgALGGwke4Afvw+/7yYhZb94yXf6t2sPLhQLmSmtSV+2FhsOWg==} + cpu: [arm64] + os: [win32] + + '@oxc-resolver/binding-win32-x64-msvc@11.21.3': + resolution: {integrity: sha512-H7BCt/VnS9hnmMp42eGhZ99izSCRvlnWwy/N71K1/J8QoExwY4262Z8QiEkMDtduRJrztayDxETTckmUuAVL9Q==} + cpu: [x64] + os: [win32] + '@peculiar/asn1-cms@2.8.0': resolution: {integrity: sha512-NgekZOrSJFSBFLFoLfwePguAWAx7z1+f2TEsWFUMyiqqfntZ4+S/S5hzqME3q4pCA0iOsFKdwiQ35dwY24eVqA==} @@ -9606,6 +9871,22 @@ packages: '@tootallnate/quickjs-emscripten@0.23.0': resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} + '@ts-graphviz/adapter@2.0.6': + resolution: {integrity: sha512-kJ10lIMSWMJkLkkCG5gt927SnGZcBuG0s0HHswGzcHTgvtUe7yk5/3zTEr0bafzsodsOq5Gi6FhQeV775nC35Q==} + engines: {node: '>=18'} + + '@ts-graphviz/ast@2.0.7': + resolution: {integrity: sha512-e6+2qtNV99UT6DJSoLbHfkzfyqY84aIuoV8Xlb9+hZAjgpum8iVHprGeAMQ4rF6sKUAxrmY8rfF/vgAwoPc3gw==} + engines: {node: '>=18'} + + '@ts-graphviz/common@2.1.5': + resolution: {integrity: sha512-S6/9+T6x8j6cr/gNhp+U2olwo1n0jKj/682QVqsh7yXWV6ednHYqxFw0ZsY3LyzT0N8jaZ6jQY9YD99le3cmvg==} + engines: {node: '>=18'} + + '@ts-graphviz/core@2.0.7': + resolution: {integrity: sha512-w071DSzP94YfN6XiWhOxnLpYT3uqtxJBDYdh6Jdjzt+Ce6DNspJsPQgpC7rbts/B8tEkq0LHoYuIF/O5Jh5rPg==} + engines: {node: '>=18'} + '@tsconfig/node10@1.0.9': resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} @@ -9618,6 +9899,9 @@ packages: '@tsconfig/node16@1.0.4': resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + '@types/accepts@1.3.7': resolution: {integrity: sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==} @@ -10290,15 +10574,27 @@ packages: '@vue/compiler-core@3.5.16': resolution: {integrity: sha512-AOQS2eaQOaaZQoL1u+2rCJIKDruNXVBZSiUD3chnUrsoX5ZTQMaCvXlWNIfxBJuU15r1o7+mpo5223KVtIhAgQ==} + '@vue/compiler-core@3.5.39': + resolution: {integrity: sha512-16KBTEXAJCpDr0mwlw+AZyhu8iyC7R3S2vBwsI7QnWJU6X3WKc9VKeNEZpiMdZ569qWhz9574L3vV55qRL0Vtw==} + '@vue/compiler-dom@3.5.16': resolution: {integrity: sha512-SSJIhBr/teipXiXjmWOVWLnxjNGo65Oj/8wTEQz0nqwQeP75jWZ0n4sF24Zxoht1cuJoWopwj0J0exYwCJ0dCQ==} + '@vue/compiler-dom@3.5.39': + resolution: {integrity: sha512-oQPigALqYbNxTNPvNgSOe+czwVExfbVF02lz8jP0S3AXJiu3jxYDygNUiqSep4ezzW8XgnubqH63My2A7JR/vg==} + '@vue/compiler-sfc@3.5.16': resolution: {integrity: sha512-rQR6VSFNpiinDy/DVUE0vHoIDUF++6p910cgcZoaAUm3POxgNOOdS/xgoll3rNdKYTYPnnbARDCZOyZ+QSe6Pw==} + '@vue/compiler-sfc@3.5.39': + resolution: {integrity: sha512-d0ki86iOyN8LoZPBmk5SJWNwHP19CnDDCfuo//+2WJa2g5Ke0Jay983PIBIcSSzldC68I8DrD5GrHV3OSDfodg==} + '@vue/compiler-ssr@3.5.16': resolution: {integrity: sha512-d2V7kfxbdsjrDSGlJE7my1ZzCXViEcqN6w14DOsDrUCHEA6vbnVCpRFfrc4ryCP/lCKzX2eS1YtnLE/BuC9f/A==} + '@vue/compiler-ssr@3.5.39': + resolution: {integrity: sha512-Ce7/wvwMHai74bdszfXExdazFigYnlF9zgCmEQUcM1j0fOymlouZ7XilTYNo8oUjhlnjYOZbGrcYKuqjz89Ucw==} + '@vue/reactivity@3.5.16': resolution: {integrity: sha512-FG5Q5ee/kxhIm1p2bykPpPwqiUBV3kFySsHEQha5BJvjXdZTUfmya7wP7zC39dFuZAcf/PD5S4Lni55vGLMhvA==} @@ -10316,6 +10612,9 @@ packages: '@vue/shared@3.5.16': resolution: {integrity: sha512-c/0fWy3Jw6Z8L9FmTyYfkpM5zklnqqa9+a6dz3DvONRKW2NEbh46BP0FHuLFSWi2TnQEtp91Z6zOWNrU6QiyPg==} + '@vue/shared@3.5.39': + resolution: {integrity: sha512-l1rrBtBfTnmxvtsvdQDXltUUy8S1Y+ZaqdfUzmAnJkTd8Z8rv5v/ytW+TKiqEOWyHPoqtPlNFSs0lhRmYVSHVA==} + '@web/browser-logs@0.4.1': resolution: {integrity: sha512-ypmMG+72ERm+LvP+loj9A64MTXvWMXHUOu773cPO4L1SV/VWg6xA9Pv7vkvkXQX+ItJtCJt+KQ+U6ui2HhSFUw==} engines: {node: '>=18.0.0'} @@ -10640,6 +10939,9 @@ packages: dmg-builder: 26.8.1 electron-builder-squirrel-windows: 26.8.1 + app-module-path@2.2.0: + resolution: {integrity: sha512-gkco+qxENJV+8vFcDiiFhuoSvRXb2a/QPqpSoWhVz829VNJfOTnELbBmPmNKFxf3xdNnw4DWCkzkDaavcX/1YQ==} + archiver-utils@2.1.0: resolution: {integrity: sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==} engines: {node: '>= 6'} @@ -10721,6 +11023,10 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + ast-module-types@6.0.2: + resolution: {integrity: sha512-6KuK/7nZ/2Qh7sGuVEiwxjCxzTY2Pdb5mTo5z1e6/J8BA0tvjR7G8vQJKrQMTqwmnA3UPEyKIFX4YUS1DO1Hvw==} + engines: {node: '>=18'} + ast-types@0.13.4: resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} engines: {node: '>=4'} @@ -11357,6 +11663,9 @@ packages: resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} engines: {node: ^12.20.0 || >=14} + commondir@1.0.1: + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + compare-version@0.1.2: resolution: {integrity: sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==} engines: {node: '>=0.10.0'} @@ -11912,6 +12221,11 @@ packages: resolution: {integrity: sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==} engines: {node: '>= 0.6.0'} + dependency-tree@11.5.0: + resolution: {integrity: sha512-K9zBwKDZrot3RkxizugpVSdImxULAg4Ycp3+ydy2r561k96oiiw6nfsOR15fwNDQ5BF2UXe+2JFM/H5Xz4MGQg==} + engines: {node: '>=18'} + hasBin: true + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -11947,6 +12261,49 @@ packages: detect-node@2.1.0: resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} + detective-amd@6.1.0: + resolution: {integrity: sha512-fmI6LGMvotqd49QaA3ZYw+q0aGp2yXmMjzIuY6fH9j9YFIXY/73yDhMwhX9cPbhWd+AH06NH1Di/LKOuCH0Ubg==} + engines: {node: '>=18'} + hasBin: true + + detective-cjs@6.1.1: + resolution: {integrity: sha512-pSh7mkCKEtLlmANqLu3KDFS3NV8Hx41jy/JF1/gAWOgU+Uo5QTkeI1tWNP4dWGo4L0E9j18Ez9EPsTleautKqA==} + engines: {node: '>=18'} + + detective-es6@5.0.2: + resolution: {integrity: sha512-+qHHGYhjupiVs4rnIpI9nZ5B130A4AmE35ZX1w33hb46vcZ7T3jfDbvmPw0FhWtMHn5BS5HHu7ZtnZ53bMcXZA==} + engines: {node: '>=18'} + + detective-postcss@8.0.4: + resolution: {integrity: sha512-DZ7M/hWPZyr17ZUdoQ+TVXaPj70mYr4XXrAE+GeJbca44haCvZgb191L/jLJmFYewhxRJuBd4lUtNSu986TXag==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4.47 + + detective-sass@6.0.2: + resolution: {integrity: sha512-i3xpXHDKS0qI2aFW4asQ7fqlPK00ndOVZELvQapFJCaF0VxYmsNWtd0AmvXbTLMk7bfO5VdIeorhY9KfmHVoVA==} + engines: {node: '>=18'} + + detective-scss@5.0.2: + resolution: {integrity: sha512-9JOEMZ8pDh3ShXmftq7hoQqqJsClaGgxo1hghfCeFlmKf5TC/Twtwb0PAaK8dXwpg9Z0uCmEYSrCxO+kel2eEg==} + engines: {node: '>=18'} + + detective-stylus@5.0.1: + resolution: {integrity: sha512-Dgn0bUqdGbE3oZJ+WCKf8Dmu7VWLcmRJGc6RCzBgG31DLIyai9WAoEhYRgIHpt/BCRMrnXLbGWGPQuBUrnF0TA==} + engines: {node: '>=18'} + + detective-typescript@14.1.2: + resolution: {integrity: sha512-bIeEn0eVi/JRsE1YizBR2ilnMlWRAIBJJ6kXCKNFxEEWhUcEY3R6I3KYIAy48ieURbD1hcb3Ebvl8AqeoPMSzg==} + engines: {node: '>=18'} + peerDependencies: + typescript: ^5.4.4 || ^6.0.2 + + detective-vue2@2.3.0: + resolution: {integrity: sha512-3gwbZPqVTm9sL9XdZsgEJ7x4x99O853VVZHapQAiEkGuMJMpFPjHDrecSgfqnS5JW3FJfYXesLZGvUOibjn49g==} + engines: {node: '>=18'} + peerDependencies: + typescript: ^5.4.4 || ^6.0.2 + devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} @@ -12156,6 +12513,10 @@ packages: resolution: {integrity: sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==} engines: {node: '>=10.13.0'} + enhanced-resolve@5.24.1: + resolution: {integrity: sha512-7DdUaTjmNwMcH2gLr1qycesKII3BK4RLy/mdAb7x10Lq7bR4aNKHt1BR1ZALSv0rPM/hF5wYF0PhGop/rJm8vw==} + engines: {node: '>=10.13.0'} + entities@2.2.0: resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} @@ -12167,6 +12528,10 @@ packages: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} @@ -12495,6 +12860,9 @@ packages: fb-watchman@2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + fd-package-json@2.0.0: + resolution: {integrity: sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==} + fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} @@ -12520,6 +12888,11 @@ packages: filelist@1.0.4: resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} + filing-cabinet@5.5.1: + resolution: {integrity: sha512-PzLBTChlVPn6LnNxF0KWs+XqPziVh3Sfmz/3TXOymHxu6a9yhrDcQn7YwgpcRM6mqhR2WHVGPR8RU4fmcF1IVA==} + engines: {node: '>=18'} + hasBin: true + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -12608,6 +12981,11 @@ packages: resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} engines: {node: '>= 6'} + formatly@0.3.0: + resolution: {integrity: sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w==} + engines: {node: '>=18.3.0'} + hasBin: true + formdata-node@4.4.1: resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} engines: {node: '>= 12.20'} @@ -12696,6 +13074,10 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + get-amd-module-type@6.0.2: + resolution: {integrity: sha512-7zShVYAYtMnj9S65CfN+hvpBCByfuB1OY8xID01nZEzXTZbx4YyysAfi+nMl95JSR6odt4q8TCj2W63KAoyVLQ==} + engines: {node: '>=18'} + get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} @@ -12713,6 +13095,9 @@ packages: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} + get-own-enumerable-property-symbols@3.0.2: + resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} + get-package-type@0.1.0: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} @@ -12831,6 +13216,11 @@ packages: resolution: {integrity: sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==} engines: {node: '>=18'} + gonzales-pe@4.3.0: + resolution: {integrity: sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==} + engines: {node: '>=0.6.0'} + hasBin: true + google-auth-library@9.15.1: resolution: {integrity: sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==} engines: {node: '>=14'} @@ -13295,6 +13685,10 @@ packages: is-core-module@2.13.1: resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + engines: {node: '>= 0.4'} + is-data-view@1.0.2: resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} engines: {node: '>= 0.4'} @@ -13397,6 +13791,10 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-obj@1.0.1: + resolution: {integrity: sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==} + engines: {node: '>=0.10.0'} + is-path-inside@3.0.3: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} @@ -13434,6 +13832,10 @@ packages: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} + is-regexp@1.0.0: + resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==} + engines: {node: '>=0.10.0'} + is-relative-url@4.1.0: resolution: {integrity: sha512-vhIXKasjAuxS7n+sdv7pJQykEAgS+YU8VBQOENXwo/VZpOHDgBBsIbHo7zFKaWBjYWF4qxERdhbPRRtFAeJKfg==} engines: {node: '>=14.16'} @@ -13478,6 +13880,10 @@ packages: resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} engines: {node: '>=18'} + is-url-superb@4.0.0: + resolution: {integrity: sha512-GI+WjezhPPcbM+tqE9LnmsY5qqjwHzTvjJ36wxYX5ujNXefSUJ/T17r5bqDV8yLhcgB59KTPNOc9O9cmHTPWsA==} + engines: {node: '>=10'} + is-weakmap@2.0.2: resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} engines: {node: '>= 0.4'} @@ -13736,6 +14142,10 @@ packages: resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==} hasBin: true + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + jju@1.4.0: resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} @@ -13915,6 +14325,11 @@ packages: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} + knip@6.24.0: + resolution: {integrity: sha512-PokLlgeEjLh1rAsB7ts+52wZ37HBr1nDhE6NNONwEaXdeZGCJOkP7ZlIAI2Gtu8xohquzTWy75bc/1diI9shQw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + koa-compose@4.1.0: resolution: {integrity: sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==} @@ -14171,9 +14586,22 @@ packages: resolution: {integrity: sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==} engines: {node: '>=16.14'} + madge@8.0.0: + resolution: {integrity: sha512-9sSsi3TBPhmkTCIpVQF0SPiChj1L7Rq9kU2KDG1o6v2XH9cCw086MopjVCD+vuoL5v8S77DTbVopTO8OUiQpIw==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + typescript: ^5.4.4 + peerDependenciesMeta: + typescript: + optional: true + magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + mailparser@3.9.6: resolution: {integrity: sha512-EJYTDWMrOS1kddK1mTsRkrx2Ngh2nYsg54SRMWVVWGVEGbHH4tod8tqqU9hIRPgGQVboSjFubDn9cboSitbM3Q==} @@ -14558,6 +14986,16 @@ packages: engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true + module-definition@6.0.2: + resolution: {integrity: sha512-SvAU3lB0+Yjbq55yHY3wkRZBOh+fhU1SnIF3IFbTewv6mtAh7yUT8ACHAJ2mGIJ7tCes2QuCL/cl6m0JSZ/ArA==} + engines: {node: '>=18'} + hasBin: true + + module-lookup-amd@9.1.3: + resolution: {integrity: sha512-Jc3XmOaR9FdfMJSK8+vyLgsCkzm8z2L0NS6vrlRWi12DjS7MY7TMNE7E1yj8yXx837xtMDbKSSgcdXnFlJ2YLg==} + engines: {node: '>=18'} + hasBin: true + mongodb-connection-string-url@3.0.0: resolution: {integrity: sha512-t1Vf+m1I5hC2M5RJx/7AtxgABy1cZmIPQRMXw+gEIPn/cZNF3Oiy+l0UIypUwVB5trcWHq3crg2g3uAR9aAwsQ==} @@ -14620,6 +15058,11 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanoid@3.3.15: + resolution: {integrity: sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + nanoid@5.1.5: resolution: {integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==} engines: {node: ^18 || >=20} @@ -14736,6 +15179,10 @@ packages: resolution: {integrity: sha512-IWqZF6u0EI/07HTBm+zZ+MgXgWl09dnSJRGaDCPBSlOqilDcx6pj3Mpb3HvPN8V2Gr+ISw7ZrMsL7STWs1F++w==} engines: {node: '>=20'} + node-source-walk@7.0.2: + resolution: {integrity: sha512-71kFFjYaSshDTA8/a2HiTYPLdASWjLJxUyJxGE+ffxU+KhxSBtM9kiLUX+R2yooFdSFKMFpi4n3PFtDy6qXv8A==} + engines: {node: '>=18'} + nodemailer@8.0.4: resolution: {integrity: sha512-k+jf6N8PfQJ0Fe8ZhJlgqU5qJU44Lpvp2yvidH3vp1lPnVQMgi4yEEMPXg5eJS1gFIJTVq1NHBk7Ia9ARdSBdQ==} engines: {node: '>=6.0.0'} @@ -14901,6 +15348,13 @@ packages: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} + oxc-parser@0.137.0: + resolution: {integrity: sha512-yFImD+WLElJpLKy8llG1qe4DCmMsL18peRp8XP1JKfig/gISbJkglnpDtX2aTmAn10kZF7164HbN2H8QPsXxGg==} + engines: {node: ^20.19.0 || >=22.12.0} + + oxc-resolver@11.21.3: + resolution: {integrity: sha512-2Mx3fKQz7+xgrBONjsxOgCGtMHOn38/HxMzW1I5efwXB5a4lRN0Vp40gYUJFBWJslcrvwoofTrqoTnLbwTd3pA==} + p-cancelable@2.1.1: resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} engines: {node: '>=8'} @@ -14988,6 +15442,10 @@ packages: resolution: {integrity: sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==} engines: {node: '>=16'} + parse-ms@2.1.0: + resolution: {integrity: sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==} + engines: {node: '>=6'} + parse-node-version@1.0.1: resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==} engines: {node: '>= 0.10'} @@ -15189,10 +15647,20 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} + postcss-values-parser@6.0.2: + resolution: {integrity: sha512-YLJpK0N1brcNJrs9WatuJFtHaV9q5aAOj+S4DI5S7jgHlRfm0PIbDCAFRYMQD5SHq7Fy6xsDhyutgS0QOAs0qw==} + engines: {node: '>=10'} + peerDependencies: + postcss: ^8.2.9 + postcss@8.5.14: resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} engines: {node: ^10 || ^12 || >=14} + postcss@8.5.16: + resolution: {integrity: sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==} + engines: {node: ^10 || ^12 || >=14} + postject@1.0.0-alpha.6: resolution: {integrity: sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==} engines: {node: '>=14.0.0'} @@ -15204,6 +15672,11 @@ packages: deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available. hasBin: true + precinct@12.3.2: + resolution: {integrity: sha512-JbJevI1K80z8e/WIyDt/4vUN/4qcfBSKKqOjJA4mosPPPb7zODKRJQV7YN7apVWN3k58nZYm/vEsLgEGYmnxwg==} + engines: {node: '>=18'} + hasBin: true + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -15220,6 +15693,10 @@ packages: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + pretty-ms@7.0.1: + resolution: {integrity: sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==} + engines: {node: '>=10'} + priorityqueuejs@2.0.0: resolution: {integrity: sha512-19BMarhgpq3x4ccvVi8k2QpJZcymo/iFUcrhPd4V96kYGovOdTsWwy7fxChYi4QY+m2EnGBWSX9Buakz+tWNQQ==} @@ -15509,6 +15986,9 @@ packages: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} + quote-unquote@1.0.0: + resolution: {integrity: sha512-twwRO/ilhlG/FIgYeKGFqyHhoEhqgnKVkcmqMKi2r524gz3ZbDTcyFt38E9xjJI2vT+KbRNHVbnJ/e0I25Azwg==} + range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -15656,6 +16136,15 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + requirejs-config-file@4.0.0: + resolution: {integrity: sha512-jnIre8cbWOyvr8a5F2KuqBnY+SDA4NXr/hzEZJG79Mxm2WiFQz2dzhC8ibtPJS7zkmBEl1mxSwp5HhC1W4qpxw==} + engines: {node: '>=10.13.0'} + + requirejs@2.3.8: + resolution: {integrity: sha512-7/cTSLOdYkNBNJcDMWf+luFvMriVm7eYxp4BcFCsAX0wF421Vyce5SXP17c+Jd5otXKGNehIonFlyQXSowL6Mw==} + engines: {node: '>=0.4.0'} + hasBin: true + requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} @@ -15670,6 +16159,10 @@ packages: resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} engines: {node: '>=8'} + resolve-dependency-path@4.0.1: + resolution: {integrity: sha512-YQftIIC4vzO9UMhO/sCgXukNyiwVRCVaxiWskCBy7Zpqkplm8kTAISZ8O1MoKW1ca6xzgLUBjZTcDgypXvXxiQ==} + engines: {node: '>=18'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -15692,6 +16185,11 @@ packages: resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} engines: {node: '>=10'} + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} + engines: {node: '>= 0.4'} + hasBin: true + resolve@1.22.8: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true @@ -15814,6 +16312,11 @@ packages: sanitize-filename@1.6.3: resolution: {integrity: sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==} + sass-lookup@6.1.2: + resolution: {integrity: sha512-GjmndmKQBtlPil79RK72L7yc5kDXZPCQeH97bP8R8DcxtXQJO6vECExb3WP/m6+cxaV9h4ZxrSRvCkPG2v/VSw==} + engines: {node: '>=18'} + hasBin: true + sax@1.3.0: resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==} @@ -16090,6 +16593,10 @@ packages: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + smol-toml@1.7.0: + resolution: {integrity: sha512-aqVvWoyO21L23mb+drl4RmMXbf6N7FdHjAhTRA9ZBL7apWBgfWC16KjrASI+1p9GAroljyMHj6fK67i0UiTNvQ==} + engines: {node: '>= 18'} + sockjs@0.3.24: resolution: {integrity: sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==} @@ -16201,6 +16708,9 @@ packages: stream-combiner@0.0.4: resolution: {integrity: sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==} + stream-to-array@2.3.0: + resolution: {integrity: sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==} + streamx@2.22.0: resolution: {integrity: sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==} @@ -16244,6 +16754,10 @@ packages: string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + stringify-object@3.3.0: + resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==} + engines: {node: '>=4'} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -16256,6 +16770,10 @@ packages: resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} engines: {node: '>=12'} + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + strip-bom@4.0.0: resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} engines: {node: '>=8'} @@ -16276,6 +16794,10 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strip-json-comments@5.0.3: + resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} + engines: {node: '>=14.16'} + strnum@2.2.3: resolution: {integrity: sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==} @@ -16288,6 +16810,11 @@ packages: stylis@4.4.0: resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==} + stylus-lookup@6.1.2: + resolution: {integrity: sha512-O+Q/SJ8s1X2aMLh4213fQ9X/bND9M3dhSsyTRe+O1OXPcewGLiYmAtKCrnP7FDvDBaXB2ZHPkCt3zi4cJXBlCQ==} + engines: {node: '>=18'} + hasBin: true + sumchecker@3.0.1: resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==} engines: {node: '>= 8.0'} @@ -16339,6 +16866,10 @@ packages: resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + engines: {node: '>=6'} + tar-fs@2.1.4: resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} @@ -16459,6 +16990,10 @@ packages: resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + tlds@1.261.0: resolution: {integrity: sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA==} hasBin: true @@ -16563,6 +17098,10 @@ packages: resolution: {integrity: sha512-akcpDTPuez4xzULo5NwuoKwYRtjQJ9eoNfBACiBMaXwNAx7B1PKfe5wqUFJuW5uKzQ68YjDFwPaWHDG1KnFGsA==} engines: {node: '>=14.13.1'} + ts-graphviz@2.1.6: + resolution: {integrity: sha512-XyLVuhBVvdJTJr2FJJV2L1pc4MwSjMhcunRVgDE9k4wbb2ee7ORYnPewxMWUav12vxyfUM686MSGsqnVRIInuw==} + engines: {node: '>=18'} + ts-jest@29.3.3: resolution: {integrity: sha512-y6jLm19SL4GroiBmHwFK4dSHUfDNmOrJbRfp6QmDIlI9p5tT5Q8ItccB4pTIslCIqOZuQnBwpTR0bQ5eUMYwkw==} engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} @@ -16635,6 +17174,10 @@ packages: '@swc/wasm': optional: true + tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} + tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} @@ -16746,6 +17289,11 @@ packages: engines: {node: '>=14.17'} hasBin: true + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + typical@4.0.0: resolution: {integrity: sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==} engines: {node: '>=8'} @@ -16762,6 +17310,10 @@ packages: engines: {node: '>=0.8.0'} hasBin: true + unbash@4.0.2: + resolution: {integrity: sha512-8gwNZ29+0/3zmXw7ToIHZtg6wK37xnniRUdBt7B27xZxaxfgR5tGMaGHT0t0dLtBV9fXE7zurh0s6Z1DHVjfWg==} + engines: {node: '>=14'} + unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} @@ -17057,6 +17609,14 @@ packages: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} + walk-up-path@4.0.0: + resolution: {integrity: sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==} + engines: {node: 20 || >=22} + + walkdir@0.4.1: + resolution: {integrity: sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==} + engines: {node: '>=6.0.0'} + walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} @@ -19056,6 +19616,11 @@ snapshots: '@csstools/css-tokenizer@4.0.0': {} + '@dependents/detective-less@5.0.3': + dependencies: + gonzales-pe: 4.3.0 + node-source-walk: 7.0.2 + '@develar/schema-utils@2.6.5': dependencies: ajv: 6.15.0 @@ -19063,6 +19628,8 @@ snapshots: '@discoveryjs/json-ext@0.5.7': {} + '@discoveryjs/json-ext@1.1.0': {} + '@elastic/elasticsearch@9.3.4': dependencies: '@elastic/transport': 9.3.5 @@ -19192,6 +19759,23 @@ snapshots: - supports-color optional: true + '@emnapi/core@1.11.0': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + + '@emnapi/core@1.11.1': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.11.0': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.11.1': dependencies: tslib: 2.8.1 @@ -19202,6 +19786,11 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/wasi-threads@1.2.2': + dependencies: + tslib: 2.8.1 + optional: true + '@esbuild-plugins/node-globals-polyfill@0.2.3(esbuild@0.28.1)': dependencies: esbuild: 0.28.1 @@ -19509,9 +20098,9 @@ snapshots: '@esbuild/win32-x64@0.28.1': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@10.6.0(jiti@2.5.1))': + '@eslint-community/eslint-utils@4.9.1(eslint@10.6.0(jiti@2.7.0))': dependencies: - eslint: 10.6.0(jiti@2.5.1) + eslint: 10.6.0(jiti@2.7.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -21075,6 +21664,20 @@ snapshots: '@napi-rs/canvas-win32-x64-msvc': 0.1.72 optional: true + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0)': + dependencies: + '@emnapi/core': 1.11.0 + '@emnapi/runtime': 1.11.0 + '@tybys/wasm-util': 0.10.3 + optional: true + + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@tybys/wasm-util': 0.10.3 + optional: true + '@noble/hashes@1.4.0': {} '@nodable/entities@2.1.0': {} @@ -21184,66 +21787,193 @@ snapshots: - react-devtools-core - utf-8-validate - '@oozcitak/dom@2.0.2': - dependencies: - '@oozcitak/infra': 2.0.2 - '@oozcitak/url': 3.0.0 - '@oozcitak/util': 10.0.0 + '@oozcitak/dom@2.0.2': + dependencies: + '@oozcitak/infra': 2.0.2 + '@oozcitak/url': 3.0.0 + '@oozcitak/util': 10.0.0 + + '@oozcitak/infra@2.0.2': + dependencies: + '@oozcitak/util': 10.0.0 + + '@oozcitak/url@3.0.0': + dependencies: + '@oozcitak/infra': 2.0.2 + '@oozcitak/util': 10.0.0 + + '@oozcitak/util@10.0.0': {} + + '@open-wc/dedupe-mixin@2.0.1': {} + + '@open-wc/scoped-elements@3.0.6': + dependencies: + '@open-wc/dedupe-mixin': 2.0.1 + lit: 3.3.2 + + '@open-wc/semantic-dom-diff@0.20.1': + dependencies: + '@types/chai': 4.3.20 + '@web/test-runner-commands': 0.9.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@open-wc/testing-helpers@3.0.1': + dependencies: + '@open-wc/scoped-elements': 3.0.6 + lit: 3.3.2 + lit-html: 3.3.2 + + '@open-wc/testing@4.0.0': + dependencies: + '@esm-bundle/chai': 4.3.4-fix.0 + '@open-wc/semantic-dom-diff': 0.20.1 + '@open-wc/testing-helpers': 3.0.1 + '@types/chai-dom': 1.11.3 + '@types/sinon-chai': 3.2.12 + chai-a11y-axe: 1.5.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@opentelemetry/api@1.9.0': {} + + '@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.34.0 + + '@opentelemetry/semantic-conventions@1.34.0': {} + + '@oxc-parser/binding-android-arm-eabi@0.137.0': + optional: true + + '@oxc-parser/binding-android-arm64@0.137.0': + optional: true + + '@oxc-parser/binding-darwin-arm64@0.137.0': + optional: true + + '@oxc-parser/binding-darwin-x64@0.137.0': + optional: true + + '@oxc-parser/binding-freebsd-x64@0.137.0': + optional: true + + '@oxc-parser/binding-linux-arm-gnueabihf@0.137.0': + optional: true + + '@oxc-parser/binding-linux-arm-musleabihf@0.137.0': + optional: true + + '@oxc-parser/binding-linux-arm64-gnu@0.137.0': + optional: true + + '@oxc-parser/binding-linux-arm64-musl@0.137.0': + optional: true + + '@oxc-parser/binding-linux-ppc64-gnu@0.137.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-gnu@0.137.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-musl@0.137.0': + optional: true + + '@oxc-parser/binding-linux-s390x-gnu@0.137.0': + optional: true + + '@oxc-parser/binding-linux-x64-gnu@0.137.0': + optional: true + + '@oxc-parser/binding-linux-x64-musl@0.137.0': + optional: true + + '@oxc-parser/binding-openharmony-arm64@0.137.0': + optional: true + + '@oxc-parser/binding-wasm32-wasi@0.137.0': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) + optional: true + + '@oxc-parser/binding-win32-arm64-msvc@0.137.0': + optional: true + + '@oxc-parser/binding-win32-ia32-msvc@0.137.0': + optional: true + + '@oxc-parser/binding-win32-x64-msvc@0.137.0': + optional: true + + '@oxc-project/types@0.137.0': {} + + '@oxc-resolver/binding-android-arm-eabi@11.21.3': + optional: true + + '@oxc-resolver/binding-android-arm64@11.21.3': + optional: true + + '@oxc-resolver/binding-darwin-arm64@11.21.3': + optional: true + + '@oxc-resolver/binding-darwin-x64@11.21.3': + optional: true - '@oozcitak/infra@2.0.2': - dependencies: - '@oozcitak/util': 10.0.0 + '@oxc-resolver/binding-freebsd-x64@11.21.3': + optional: true - '@oozcitak/url@3.0.0': - dependencies: - '@oozcitak/infra': 2.0.2 - '@oozcitak/util': 10.0.0 + '@oxc-resolver/binding-linux-arm-gnueabihf@11.21.3': + optional: true - '@oozcitak/util@10.0.0': {} + '@oxc-resolver/binding-linux-arm-musleabihf@11.21.3': + optional: true - '@open-wc/dedupe-mixin@2.0.1': {} + '@oxc-resolver/binding-linux-arm64-gnu@11.21.3': + optional: true - '@open-wc/scoped-elements@3.0.6': - dependencies: - '@open-wc/dedupe-mixin': 2.0.1 - lit: 3.3.2 + '@oxc-resolver/binding-linux-arm64-musl@11.21.3': + optional: true - '@open-wc/semantic-dom-diff@0.20.1': - dependencies: - '@types/chai': 4.3.20 - '@web/test-runner-commands': 0.9.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate + '@oxc-resolver/binding-linux-ppc64-gnu@11.21.3': + optional: true - '@open-wc/testing-helpers@3.0.1': - dependencies: - '@open-wc/scoped-elements': 3.0.6 - lit: 3.3.2 - lit-html: 3.3.2 + '@oxc-resolver/binding-linux-riscv64-gnu@11.21.3': + optional: true - '@open-wc/testing@4.0.0': - dependencies: - '@esm-bundle/chai': 4.3.4-fix.0 - '@open-wc/semantic-dom-diff': 0.20.1 - '@open-wc/testing-helpers': 3.0.1 - '@types/chai-dom': 1.11.3 - '@types/sinon-chai': 3.2.12 - chai-a11y-axe: 1.5.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate + '@oxc-resolver/binding-linux-riscv64-musl@11.21.3': + optional: true - '@opentelemetry/api@1.9.0': {} + '@oxc-resolver/binding-linux-s390x-gnu@11.21.3': + optional: true - '@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.0)': + '@oxc-resolver/binding-linux-x64-gnu@11.21.3': + optional: true + + '@oxc-resolver/binding-linux-x64-musl@11.21.3': + optional: true + + '@oxc-resolver/binding-openharmony-arm64@11.21.3': + optional: true + + '@oxc-resolver/binding-wasm32-wasi@11.21.3': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/semantic-conventions': 1.34.0 + '@emnapi/core': 1.11.0 + '@emnapi/runtime': 1.11.0 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0) + optional: true - '@opentelemetry/semantic-conventions@1.34.0': {} + '@oxc-resolver/binding-win32-arm64-msvc@11.21.3': + optional: true + + '@oxc-resolver/binding-win32-x64-msvc@11.21.3': + optional: true '@peculiar/asn1-cms@2.8.0': dependencies: @@ -21977,6 +22707,21 @@ snapshots: '@tootallnate/quickjs-emscripten@0.23.0': {} + '@ts-graphviz/adapter@2.0.6': + dependencies: + '@ts-graphviz/common': 2.1.5 + + '@ts-graphviz/ast@2.0.7': + dependencies: + '@ts-graphviz/common': 2.1.5 + + '@ts-graphviz/common@2.1.5': {} + + '@ts-graphviz/core@2.0.7': + dependencies: + '@ts-graphviz/ast': 2.0.7 + '@ts-graphviz/common': 2.1.5 + '@tsconfig/node10@1.0.9': {} '@tsconfig/node12@1.0.11': {} @@ -21985,6 +22730,11 @@ snapshots: '@tsconfig/node16@1.0.4': {} + '@tybys/wasm-util@0.10.3': + dependencies: + tslib: 2.8.1 + optional: true + '@types/accepts@1.3.7': dependencies: '@types/node': 22.19.21 @@ -22621,40 +23371,40 @@ snapshots: '@types/node': 22.19.21 optional: true - '@typescript-eslint/eslint-plugin@8.62.1(@typescript-eslint/parser@8.62.1(eslint@10.6.0(jiti@2.5.1))(typescript@5.4.5))(eslint@10.6.0(jiti@2.5.1))(typescript@5.4.5)': + '@typescript-eslint/eslint-plugin@8.62.1(@typescript-eslint/parser@8.62.1(eslint@10.6.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.6.0(jiti@2.7.0))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.62.1(eslint@10.6.0(jiti@2.5.1))(typescript@5.4.5) + '@typescript-eslint/parser': 8.62.1(eslint@10.6.0(jiti@2.7.0))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.62.1 - '@typescript-eslint/type-utils': 8.62.1(eslint@10.6.0(jiti@2.5.1))(typescript@5.4.5) - '@typescript-eslint/utils': 8.62.1(eslint@10.6.0(jiti@2.5.1))(typescript@5.4.5) + '@typescript-eslint/type-utils': 8.62.1(eslint@10.6.0(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/utils': 8.62.1(eslint@10.6.0(jiti@2.7.0))(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.62.1 - eslint: 10.6.0(jiti@2.5.1) + eslint: 10.6.0(jiti@2.7.0) ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 2.5.0(typescript@5.4.5) - typescript: 5.4.5 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.62.1(eslint@10.6.0(jiti@2.5.1))(typescript@5.4.5)': + '@typescript-eslint/parser@8.62.1(eslint@10.6.0(jiti@2.7.0))(typescript@5.9.3)': dependencies: '@typescript-eslint/scope-manager': 8.62.1 '@typescript-eslint/types': 8.62.1 - '@typescript-eslint/typescript-estree': 8.62.1(typescript@5.4.5) + '@typescript-eslint/typescript-estree': 8.62.1(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.62.1 debug: 4.4.3(supports-color@8.1.1) - eslint: 10.6.0(jiti@2.5.1) - typescript: 5.4.5 + eslint: 10.6.0(jiti@2.7.0) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.62.1(typescript@5.4.5)': + '@typescript-eslint/project-service@8.62.1(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.62.1(typescript@5.4.5) + '@typescript-eslint/tsconfig-utils': 8.62.1(typescript@5.9.3) '@typescript-eslint/types': 8.62.1 debug: 4.4.3(supports-color@8.1.1) - typescript: 5.4.5 + typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -22663,47 +23413,47 @@ snapshots: '@typescript-eslint/types': 8.62.1 '@typescript-eslint/visitor-keys': 8.62.1 - '@typescript-eslint/tsconfig-utils@8.62.1(typescript@5.4.5)': + '@typescript-eslint/tsconfig-utils@8.62.1(typescript@5.9.3)': dependencies: - typescript: 5.4.5 + typescript: 5.9.3 - '@typescript-eslint/type-utils@8.62.1(eslint@10.6.0(jiti@2.5.1))(typescript@5.4.5)': + '@typescript-eslint/type-utils@8.62.1(eslint@10.6.0(jiti@2.7.0))(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 8.62.1 - '@typescript-eslint/typescript-estree': 8.62.1(typescript@5.4.5) - '@typescript-eslint/utils': 8.62.1(eslint@10.6.0(jiti@2.5.1))(typescript@5.4.5) + '@typescript-eslint/typescript-estree': 8.62.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.62.1(eslint@10.6.0(jiti@2.7.0))(typescript@5.9.3) debug: 4.4.3(supports-color@8.1.1) - eslint: 10.6.0(jiti@2.5.1) - ts-api-utils: 2.5.0(typescript@5.4.5) - typescript: 5.4.5 + eslint: 10.6.0(jiti@2.7.0) + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color '@typescript-eslint/types@8.62.1': {} - '@typescript-eslint/typescript-estree@8.62.1(typescript@5.4.5)': + '@typescript-eslint/typescript-estree@8.62.1(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.62.1(typescript@5.4.5) - '@typescript-eslint/tsconfig-utils': 8.62.1(typescript@5.4.5) + '@typescript-eslint/project-service': 8.62.1(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.62.1(typescript@5.9.3) '@typescript-eslint/types': 8.62.1 '@typescript-eslint/visitor-keys': 8.62.1 debug: 4.4.3(supports-color@8.1.1) minimatch: 10.2.4 semver: 7.8.4 tinyglobby: 0.2.16 - ts-api-utils: 2.5.0(typescript@5.4.5) - typescript: 5.4.5 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.62.1(eslint@10.6.0(jiti@2.5.1))(typescript@5.4.5)': + '@typescript-eslint/utils@8.62.1(eslint@10.6.0(jiti@2.7.0))(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.6.0(jiti@2.5.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.6.0(jiti@2.7.0)) '@typescript-eslint/scope-manager': 8.62.1 '@typescript-eslint/types': 8.62.1 - '@typescript-eslint/typescript-estree': 8.62.1(typescript@5.4.5) - eslint: 10.6.0(jiti@2.5.1) - typescript: 5.4.5 + '@typescript-eslint/typescript-estree': 8.62.1(typescript@5.9.3) + eslint: 10.6.0(jiti@2.7.0) + typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -22842,11 +23592,24 @@ snapshots: estree-walker: 2.0.2 source-map-js: 1.2.1 + '@vue/compiler-core@3.5.39': + dependencies: + '@babel/parser': 7.29.7 + '@vue/shared': 3.5.39 + entities: 7.0.1 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + '@vue/compiler-dom@3.5.16': dependencies: '@vue/compiler-core': 3.5.16 '@vue/shared': 3.5.16 + '@vue/compiler-dom@3.5.39': + dependencies: + '@vue/compiler-core': 3.5.39 + '@vue/shared': 3.5.39 + '@vue/compiler-sfc@3.5.16': dependencies: '@babel/parser': 7.28.4 @@ -22859,11 +23622,28 @@ snapshots: postcss: 8.5.14 source-map-js: 1.2.1 + '@vue/compiler-sfc@3.5.39': + dependencies: + '@babel/parser': 7.29.7 + '@vue/compiler-core': 3.5.39 + '@vue/compiler-dom': 3.5.39 + '@vue/compiler-ssr': 3.5.39 + '@vue/shared': 3.5.39 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.16 + source-map-js: 1.2.1 + '@vue/compiler-ssr@3.5.16': dependencies: '@vue/compiler-dom': 3.5.16 '@vue/shared': 3.5.16 + '@vue/compiler-ssr@3.5.39': + dependencies: + '@vue/compiler-dom': 3.5.39 + '@vue/shared': 3.5.39 + '@vue/reactivity@3.5.16': dependencies: '@vue/shared': 3.5.16 @@ -22888,6 +23668,8 @@ snapshots: '@vue/shared@3.5.16': {} + '@vue/shared@3.5.39': {} + '@web/browser-logs@0.4.1': dependencies: errorstacks: 2.4.1 @@ -23380,6 +24162,8 @@ snapshots: transitivePeerDependencies: - supports-color + app-module-path@2.2.0: {} + archiver-utils@2.1.0: dependencies: glob: 7.2.3 @@ -23479,6 +24263,8 @@ snapshots: assertion-error@2.0.1: {} + ast-module-types@6.0.2: {} + ast-types@0.13.4: dependencies: tslib: 2.8.1 @@ -24204,6 +24990,8 @@ snapshots: commander@9.5.0: {} + commondir@1.0.1: {} + compare-version@0.1.2: {} compress-commons@2.1.1: @@ -24831,6 +25619,16 @@ snapshots: dependency-graph@0.11.0: {} + dependency-tree@11.5.0: + dependencies: + '@discoveryjs/json-ext': 1.1.0 + commander: 12.1.0 + filing-cabinet: 5.5.1 + precinct: 12.3.2 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + dequal@2.0.3: {} destroy@1.2.0: {} @@ -24849,6 +25647,62 @@ snapshots: detect-node@2.1.0: {} + detective-amd@6.1.0: + dependencies: + ast-module-types: 6.0.2 + escodegen: 2.1.0 + get-amd-module-type: 6.0.2 + node-source-walk: 7.0.2 + + detective-cjs@6.1.1: + dependencies: + ast-module-types: 6.0.2 + node-source-walk: 7.0.2 + + detective-es6@5.0.2: + dependencies: + node-source-walk: 7.0.2 + + detective-postcss@8.0.4(postcss@8.5.14): + dependencies: + is-url-superb: 4.0.0 + postcss: 8.5.14 + postcss-values-parser: 6.0.2(postcss@8.5.14) + + detective-sass@6.0.2: + dependencies: + gonzales-pe: 4.3.0 + node-source-walk: 7.0.2 + + detective-scss@5.0.2: + dependencies: + gonzales-pe: 4.3.0 + node-source-walk: 7.0.2 + + detective-stylus@5.0.1: {} + + detective-typescript@14.1.2(typescript@5.9.3): + dependencies: + '@typescript-eslint/typescript-estree': 8.62.1(typescript@5.9.3) + ast-module-types: 6.0.2 + node-source-walk: 7.0.2 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + detective-vue2@2.3.0(typescript@5.9.3): + dependencies: + '@dependents/detective-less': 5.0.3 + '@vue/compiler-sfc': 3.5.39 + detective-es6: 5.0.2 + detective-sass: 6.0.2 + detective-scss: 5.0.2 + detective-stylus: 5.0.1 + detective-typescript: 14.1.2(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + devlop@1.1.0: dependencies: dequal: 2.0.3 @@ -25045,7 +25899,7 @@ snapshots: transitivePeerDependencies: - supports-color - electron-vite@4.0.1(vite@6.4.3(@types/node@25.9.3)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.9.0)): + electron-vite@4.0.1(vite@6.4.3(@types/node@25.9.3)(jiti@2.7.0)(less@4.3.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.9.0)): dependencies: '@babel/core': 7.29.7 '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.7) @@ -25053,7 +25907,7 @@ snapshots: esbuild: 0.25.12 magic-string: 0.30.17 picocolors: 1.1.1 - vite: 6.4.3(@types/node@25.9.3)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.9.0) + vite: 6.4.3(@types/node@25.9.3)(jiti@2.7.0)(less@4.3.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.9.0) transitivePeerDependencies: - supports-color @@ -25121,12 +25975,19 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.0 + enhanced-resolve@5.24.1: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.3 + entities@2.2.0: {} entities@4.5.0: {} entities@6.0.1: {} + entities@7.0.1: {} + env-paths@2.2.1: {} envinfo@7.11.0: {} @@ -25369,12 +26230,12 @@ snapshots: optionalDependencies: source-map: 0.6.1 - eslint-plugin-sonarjs@4.1.0(eslint@10.6.0(jiti@2.5.1)): + eslint-plugin-sonarjs@4.1.0(eslint@10.6.0(jiti@2.7.0)): dependencies: '@eslint-community/regexpp': 4.12.2 builtin-modules: 3.3.0 bytes: 3.1.2 - eslint: 10.6.0(jiti@2.5.1) + eslint: 10.6.0(jiti@2.7.0) functional-red-black-tree: 1.0.1 globals: 17.7.0 jsx-ast-utils-x: 0.1.0 @@ -25402,9 +26263,9 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.6.0(jiti@2.5.1): + eslint@10.6.0(jiti@2.7.0): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.6.0(jiti@2.5.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.6.0(jiti@2.7.0)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.23.5 '@eslint/config-helpers': 0.6.0 @@ -25435,7 +26296,7 @@ snapshots: natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: - jiti: 2.5.1 + jiti: 2.7.0 transitivePeerDependencies: - supports-color @@ -25735,6 +26596,10 @@ snapshots: dependencies: bser: 2.1.1 + fd-package-json@2.0.0: + dependencies: + walk-up-path: 4.0.0 + fd-slicer@1.1.0: dependencies: pend: 1.2.0 @@ -25755,6 +26620,20 @@ snapshots: dependencies: minimatch: 5.1.9 + filing-cabinet@5.5.1: + dependencies: + app-module-path: 2.2.0 + commander: 12.1.0 + enhanced-resolve: 5.24.1 + module-definition: 6.0.2 + module-lookup-amd: 9.1.3 + resolve: 1.22.12 + resolve-dependency-path: 4.0.1 + sass-lookup: 6.1.2 + stylus-lookup: 6.1.2 + tsconfig-paths: 4.2.0 + typescript: 5.9.3 + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -25854,6 +26733,10 @@ snapshots: hasown: 2.0.4 mime-types: 2.1.35 + formatly@0.3.0: + dependencies: + fd-package-json: 2.0.0 + formdata-node@4.4.1: dependencies: node-domexception: 1.0.0 @@ -25953,6 +26836,11 @@ snapshots: gensync@1.0.0-beta.2: {} + get-amd-module-type@6.0.2: + dependencies: + ast-module-types: 6.0.2 + node-source-walk: 7.0.2 + get-caller-file@2.0.5: {} get-east-asian-width@1.3.0: {} @@ -25972,6 +26860,8 @@ snapshots: hasown: 2.0.4 math-intrinsics: 1.1.0 + get-own-enumerable-property-symbols@3.0.2: {} + get-package-type@0.1.0: {} get-proto@1.0.1: @@ -26140,6 +27030,10 @@ snapshots: slash: 5.1.0 unicorn-magic: 0.3.0 + gonzales-pe@4.3.0: + dependencies: + minimist: 1.2.8 + google-auth-library@9.15.1(encoding@0.1.13): dependencies: base64-js: 1.5.1 @@ -26671,6 +27565,10 @@ snapshots: dependencies: hasown: 2.0.4 + is-core-module@2.16.2: + dependencies: + hasown: 2.0.4 + is-data-view@1.0.2: dependencies: call-bound: 1.0.4 @@ -26750,6 +27648,8 @@ snapshots: is-number@7.0.0: {} + is-obj@1.0.1: {} + is-path-inside@3.0.3: {} is-plain-obj@2.1.0: {} @@ -26777,6 +27677,8 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.4 + is-regexp@1.0.0: {} + is-relative-url@4.1.0: dependencies: is-absolute-url: 4.0.1 @@ -26812,6 +27714,8 @@ snapshots: is-unicode-supported@2.1.0: {} + is-url-superb@4.0.0: {} + is-weakmap@2.0.2: {} is-weakref@1.1.1: @@ -27447,6 +28351,8 @@ snapshots: jiti@2.5.1: {} + jiti@2.7.0: {} + jju@1.4.0: {} jose@5.10.0: {} @@ -27689,6 +28595,22 @@ snapshots: kleur@3.0.3: {} + knip@6.24.0: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + formatly: 0.3.0 + get-tsconfig: 4.14.0 + jiti: 2.7.0 + oxc-parser: 0.137.0 + oxc-resolver: 11.21.3 + picomatch: 4.0.4 + smol-toml: 1.7.0 + strip-json-comments: 5.0.3 + tinyglobby: 0.2.17 + unbash: 4.0.2 + yaml: 2.9.0 + zod: 4.3.6 + koa-compose@4.1.0: {} koa-convert@2.0.0: @@ -27964,10 +28886,33 @@ snapshots: lru-cache@8.0.5: {} + madge@8.0.0(typescript@5.9.3): + dependencies: + chalk: 4.1.2 + commander: 7.2.0 + commondir: 1.0.1 + debug: 4.4.3(supports-color@8.1.1) + dependency-tree: 11.5.0 + ora: 5.4.1 + pluralize: 8.0.0 + pretty-ms: 7.0.1 + rc: 1.2.8 + stream-to-array: 2.3.0 + ts-graphviz: 2.1.6 + walkdir: 0.4.1 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + magic-string@0.30.17: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + mailparser@3.9.6: dependencies: '@zone-eu/mailsplit': 5.4.8 @@ -28600,6 +29545,17 @@ snapshots: yargs-parser: 21.1.1 yargs-unparser: 2.0.0 + module-definition@6.0.2: + dependencies: + ast-module-types: 6.0.2 + node-source-walk: 7.0.2 + + module-lookup-amd@9.1.3: + dependencies: + commander: 12.1.0 + requirejs: 2.3.8 + requirejs-config-file: 4.0.0 + mongodb-connection-string-url@3.0.0: dependencies: '@types/whatwg-url': 11.0.4 @@ -28644,6 +29600,8 @@ snapshots: nanoid@3.3.11: {} + nanoid@3.3.15: {} + nanoid@5.1.5: {} napi-build-utils@2.0.0: {} @@ -28750,6 +29708,10 @@ snapshots: '@types/sarif': 2.1.7 fs-extra: 11.3.4 + node-source-walk@7.0.2: + dependencies: + '@babel/parser': 7.29.7 + nodemailer@8.0.4: {} noms@0.0.0: @@ -28937,6 +29899,53 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 + oxc-parser@0.137.0: + dependencies: + '@oxc-project/types': 0.137.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.137.0 + '@oxc-parser/binding-android-arm64': 0.137.0 + '@oxc-parser/binding-darwin-arm64': 0.137.0 + '@oxc-parser/binding-darwin-x64': 0.137.0 + '@oxc-parser/binding-freebsd-x64': 0.137.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.137.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.137.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.137.0 + '@oxc-parser/binding-linux-arm64-musl': 0.137.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.137.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.137.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.137.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.137.0 + '@oxc-parser/binding-linux-x64-gnu': 0.137.0 + '@oxc-parser/binding-linux-x64-musl': 0.137.0 + '@oxc-parser/binding-openharmony-arm64': 0.137.0 + '@oxc-parser/binding-wasm32-wasi': 0.137.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.137.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.137.0 + '@oxc-parser/binding-win32-x64-msvc': 0.137.0 + + oxc-resolver@11.21.3: + optionalDependencies: + '@oxc-resolver/binding-android-arm-eabi': 11.21.3 + '@oxc-resolver/binding-android-arm64': 11.21.3 + '@oxc-resolver/binding-darwin-arm64': 11.21.3 + '@oxc-resolver/binding-darwin-x64': 11.21.3 + '@oxc-resolver/binding-freebsd-x64': 11.21.3 + '@oxc-resolver/binding-linux-arm-gnueabihf': 11.21.3 + '@oxc-resolver/binding-linux-arm-musleabihf': 11.21.3 + '@oxc-resolver/binding-linux-arm64-gnu': 11.21.3 + '@oxc-resolver/binding-linux-arm64-musl': 11.21.3 + '@oxc-resolver/binding-linux-ppc64-gnu': 11.21.3 + '@oxc-resolver/binding-linux-riscv64-gnu': 11.21.3 + '@oxc-resolver/binding-linux-riscv64-musl': 11.21.3 + '@oxc-resolver/binding-linux-s390x-gnu': 11.21.3 + '@oxc-resolver/binding-linux-x64-gnu': 11.21.3 + '@oxc-resolver/binding-linux-x64-musl': 11.21.3 + '@oxc-resolver/binding-openharmony-arm64': 11.21.3 + '@oxc-resolver/binding-wasm32-wasi': 11.21.3 + '@oxc-resolver/binding-win32-arm64-msvc': 11.21.3 + '@oxc-resolver/binding-win32-x64-msvc': 11.21.3 + p-cancelable@2.1.1: {} p-event@4.2.0: @@ -29037,6 +30046,8 @@ snapshots: lines-and-columns: 2.0.4 type-fest: 3.13.1 + parse-ms@2.1.0: {} + parse-node-version@1.0.1: {} parse-semver@1.1.1: @@ -29214,12 +30225,25 @@ snapshots: possible-typed-array-names@1.1.0: {} + postcss-values-parser@6.0.2(postcss@8.5.14): + dependencies: + color-name: 1.1.4 + is-url-superb: 4.0.0 + postcss: 8.5.14 + quote-unquote: 1.0.0 + postcss@8.5.14: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 source-map-js: 1.2.1 + postcss@8.5.16: + dependencies: + nanoid: 3.3.15 + picocolors: 1.1.1 + source-map-js: 1.2.1 + postject@1.0.0-alpha.6: dependencies: commander: 9.5.0 @@ -29239,6 +30263,26 @@ snapshots: tar-fs: 2.1.4 tunnel-agent: 0.6.0 + precinct@12.3.2: + dependencies: + '@dependents/detective-less': 5.0.3 + commander: 12.1.0 + detective-amd: 6.1.0 + detective-cjs: 6.1.1 + detective-es6: 5.0.2 + detective-postcss: 8.0.4(postcss@8.5.14) + detective-sass: 6.0.2 + detective-scss: 5.0.2 + detective-stylus: 5.0.1 + detective-typescript: 14.1.2(typescript@5.9.3) + detective-vue2: 2.3.0(typescript@5.9.3) + module-definition: 6.0.2 + node-source-walk: 7.0.2 + postcss: 8.5.14 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + prelude-ls@1.2.1: {} prettier@3.5.3: {} @@ -29254,6 +30298,10 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.2.0 + pretty-ms@7.0.1: + dependencies: + parse-ms: 2.1.0 + priorityqueuejs@2.0.0: {} prismjs@1.30.0: {} @@ -29634,6 +30682,8 @@ snapshots: quick-lru@5.1.1: {} + quote-unquote@1.0.0: {} + range-parser@1.2.1: {} raw-body@2.5.2: @@ -29861,6 +30911,13 @@ snapshots: require-from-string@2.0.2: {} + requirejs-config-file@4.0.0: + dependencies: + esprima: 4.0.1 + stringify-object: 3.3.0 + + requirejs@2.3.8: {} + requires-port@1.0.0: {} resedit@1.7.2: @@ -29873,6 +30930,8 @@ snapshots: dependencies: resolve-from: 5.0.0 + resolve-dependency-path@4.0.1: {} + resolve-from@4.0.0: {} resolve-from@5.0.0: {} @@ -29890,6 +30949,13 @@ snapshots: resolve.exports@2.0.3: {} + resolve@1.22.12: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.2 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + resolve@1.22.8: dependencies: is-core-module: 2.13.1 @@ -30054,6 +31120,11 @@ snapshots: dependencies: truncate-utf8-bytes: 1.0.2 + sass-lookup@6.1.2: + dependencies: + commander: 12.1.0 + enhanced-resolve: 5.24.1 + sax@1.3.0: {} saxes@6.0.0: @@ -30466,6 +31537,8 @@ snapshots: smart-buffer@4.2.0: {} + smol-toml@1.7.0: {} + sockjs@0.3.24: dependencies: faye-websocket: 0.11.4 @@ -30603,6 +31676,10 @@ snapshots: dependencies: duplexer: 0.1.2 + stream-to-array@2.3.0: + dependencies: + any-promise: 1.3.0 + streamx@2.22.0: dependencies: fast-fifo: 1.3.2 @@ -30670,6 +31747,12 @@ snapshots: dependencies: safe-buffer: 5.2.1 + stringify-object@3.3.0: + dependencies: + get-own-enumerable-property-symbols: 3.0.2 + is-obj: 1.0.1 + is-regexp: 1.0.0 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -30682,6 +31765,8 @@ snapshots: dependencies: ansi-regex: 6.2.2 + strip-bom@3.0.0: {} + strip-bom@4.0.0: {} strip-eof@1.0.0: {} @@ -30692,6 +31777,8 @@ snapshots: strip-json-comments@3.1.1: {} + strip-json-comments@5.0.3: {} + strnum@2.2.3: {} structured-source@4.0.0: @@ -30702,6 +31789,10 @@ snapshots: stylis@4.4.0: {} + stylus-lookup@6.1.2: + dependencies: + commander: 12.1.0 + sumchecker@3.0.1: dependencies: debug: 4.4.3(supports-color@8.1.1) @@ -30753,6 +31844,8 @@ snapshots: tapable@2.3.0: {} + tapable@2.3.3: {} + tar-fs@2.1.4: dependencies: chownr: 1.1.4 @@ -30908,6 +32001,11 @@ snapshots: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + tlds@1.261.0: {} tldts-core@5.7.112: {} @@ -30991,10 +32089,21 @@ snapshots: dependencies: typescript: 5.4.5 + ts-api-utils@2.5.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + ts-dedent@2.2.0: {} ts-deepmerge@7.0.2: {} + ts-graphviz@2.1.6: + dependencies: + '@ts-graphviz/adapter': 2.0.6 + '@ts-graphviz/ast': 2.0.7 + '@ts-graphviz/common': 2.1.5 + '@ts-graphviz/core': 2.0.7 + ts-jest@29.3.3(@babel/core@7.29.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.7))(esbuild@0.28.1)(jest@29.7.0(@types/node@22.15.18)(ts-node@10.9.2(@types/node@22.15.18)(typescript@5.4.5)))(typescript@5.4.5): dependencies: bs-logger: 0.2.6 @@ -31132,6 +32241,12 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 + tsconfig-paths@4.2.0: + dependencies: + json5: 2.2.3 + minimist: 1.2.8 + strip-bom: 3.0.0 + tslib@1.14.1: {} tslib@2.6.2: {} @@ -31240,19 +32355,21 @@ snapshots: tunnel: 0.0.6 underscore: 1.13.8 - typescript-eslint@8.62.1(eslint@10.6.0(jiti@2.5.1))(typescript@5.4.5): + typescript-eslint@8.62.1(eslint@10.6.0(jiti@2.7.0))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.62.1(@typescript-eslint/parser@8.62.1(eslint@10.6.0(jiti@2.5.1))(typescript@5.4.5))(eslint@10.6.0(jiti@2.5.1))(typescript@5.4.5) - '@typescript-eslint/parser': 8.62.1(eslint@10.6.0(jiti@2.5.1))(typescript@5.4.5) - '@typescript-eslint/typescript-estree': 8.62.1(typescript@5.4.5) - '@typescript-eslint/utils': 8.62.1(eslint@10.6.0(jiti@2.5.1))(typescript@5.4.5) - eslint: 10.6.0(jiti@2.5.1) - typescript: 5.4.5 + '@typescript-eslint/eslint-plugin': 8.62.1(@typescript-eslint/parser@8.62.1(eslint@10.6.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.6.0(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/parser': 8.62.1(eslint@10.6.0(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.62.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.62.1(eslint@10.6.0(jiti@2.7.0))(typescript@5.9.3) + eslint: 10.6.0(jiti@2.7.0) + typescript: 5.9.3 transitivePeerDependencies: - supports-color typescript@5.4.5: {} + typescript@5.9.3: {} + typical@4.0.0: {} typical@7.3.0: {} @@ -31262,6 +32379,8 @@ snapshots: uglify-js@3.19.3: optional: true + unbash@4.0.2: {} + unbox-primitive@1.1.0: dependencies: call-bound: 1.0.4 @@ -31447,7 +32566,7 @@ snapshots: less: 4.3.0 terser: 5.39.2 - vite@6.4.3(@types/node@22.15.18)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.3): + vite@6.4.3(@types/node@22.15.18)(jiti@2.7.0)(less@4.3.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.8.3): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.4) @@ -31458,13 +32577,13 @@ snapshots: optionalDependencies: '@types/node': 22.15.18 fsevents: 2.3.3 - jiti: 2.5.1 + jiti: 2.7.0 less: 4.3.0 terser: 5.39.2 tsx: 4.21.0 yaml: 2.8.3 - vite@6.4.3(@types/node@25.9.3)(jiti@2.5.1)(less@4.3.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.9.0): + vite@6.4.3(@types/node@25.9.3)(jiti@2.7.0)(less@4.3.0)(terser@5.39.2)(tsx@4.21.0)(yaml@2.9.0): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.4) @@ -31475,7 +32594,7 @@ snapshots: optionalDependencies: '@types/node': 25.9.3 fsevents: 2.3.3 - jiti: 2.5.1 + jiti: 2.7.0 less: 4.3.0 terser: 5.39.2 tsx: 4.21.0 @@ -31526,6 +32645,10 @@ snapshots: dependencies: xml-name-validator: 5.0.0 + walk-up-path@4.0.0: {} + + walkdir@0.4.1: {} + walker@1.0.8: dependencies: makeerror: 1.0.12 diff --git a/ts/tools/scripts/code/circularDepsReport.ts b/ts/tools/scripts/code/circularDepsReport.ts new file mode 100644 index 0000000000..4e350e5a7d --- /dev/null +++ b/ts/tools/scripts/code/circularDepsReport.ts @@ -0,0 +1,654 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * Circular-dependency report + ratchet gate for the TypeAgent ts/ tree. + * + * The engine is madge — the de-facto-standard dependency-graph tool — driven + * through its Node API (loaded via createRequire because the npx shim cannot + * resolve local bins in this environment). Type-only imports are excluded + * (skipTypeImports), so the cycles reported are real runtime import cycles, the + * ones that cause initialization-order bugs. + * + * Two modes: + * - Report (default): scan the source subtrees and write CSV/JSON/HTML + a + * console summary (cycle count, size distribution, per-package rollup, + * which cycles cross package boundaries). + * - Ratchet (--ratchet --base ): a stateless CI gate. It builds the + * cycle set for HEAD and for the merge base (checked out into a throwaway + * git worktree) and fails if HEAD introduces any cycle that is not already + * present at the base. The base branch is the baseline, so cycles can only + * trend down. Unlike the per-file lint/complexity ratchets, cycles are a + * whole-graph property, hence the worktree. + * + * Outputs (written to --out-dir, default tools/scripts/code/circular-report): + * - cycles.csv : every cycle, ranked by length + * - report.json : structured metrics + * - report.html : a self-contained, sortable report + * + * Usage: + * npx tsx tools/scripts/code/circularDepsReport.ts [options] + * npm run code-circular -- [options] + * + * Options: + * --include-tests Include test files (excluded by default). + * --top Number of cycles to print / embed (default 25). + * --root Directory to scan (default: the ts/ root). + * --out-dir Output directory (default tools/scripts/code/circular-report). + * --ratchet CI gate: fail if changed code introduces new cycles vs --base. + * --base Base git ref for --ratchet (default origin/main). + * --help Show this help. + */ + +import { createRequire } from "node:module"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { execFileSync } from "node:child_process"; +import { fileURLToPath } from "node:url"; + +const require = createRequire(import.meta.url); + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// --------------------------------------------------------------------------- +// Configuration +// --------------------------------------------------------------------------- + +const SCAN_SUBDIRS = ["packages", "examples", "extensions", "tools"]; + +const FILE_EXTENSIONS = ["ts", "mts", "cts", "tsx"]; + +// madge excludeRegExp entries (matched against file paths). +const BASE_EXCLUDES = [ + "/dist/", + "/node_modules/", + "\\.d\\.ts$", + "/bin/", + "/build/", + "/out/", +]; +const TEST_EXCLUDES = [ + "/test/", + "/tests/", + "/__tests__/", + "\\.spec\\.", + "\\.test\\.", +]; + +// Cycle-length distribution buckets. +const BUCKETS: { label: string; lo: number; hi: number }[] = [ + { label: "2 files", lo: 2, hi: 2 }, + { label: "3 files", lo: 3, hi: 3 }, + { label: "4-5 files", lo: 4, hi: 5 }, + { label: "6+ files", lo: 6, hi: Infinity }, +]; + +// --------------------------------------------------------------------------- +// madge Node API +// --------------------------------------------------------------------------- + +interface MadgeResult { + circular(): string[][]; + obj(): Record; +} +type MadgeFn = ( + pathOrPaths: string | string[], + opts?: Record, +) => Promise; + +function madgeOptions(includeTests: boolean): Record { + return { + fileExtensions: FILE_EXTENSIONS, + excludeRegExp: includeTests + ? BASE_EXCLUDES + : [...BASE_EXCLUDES, ...TEST_EXCLUDES], + detectiveOptions: { + ts: { skipTypeImports: true }, + tsx: { skipTypeImports: true }, + }, + }; +} + +const norm = (p: string) => p.replace(/\\/g, "/"); + +/** Run madge from `cwd` over the existing SCAN_SUBDIRS; return cycles + file count. */ +async function runMadge( + cwd: string, + includeTests: boolean, +): Promise<{ cycles: string[][]; files: number }> { + const madge = require("madge") as MadgeFn; + const prev = process.cwd(); + process.chdir(cwd); + try { + const targets = SCAN_SUBDIRS.filter((d) => fs.existsSync(d)); + if (targets.length === 0) { + return { cycles: [], files: 0 }; + } + const res = await madge(targets, madgeOptions(includeTests)); + const cycles = res.circular().map((c) => c.map(norm)); + return { cycles, files: Object.keys(res.obj()).length }; + } finally { + process.chdir(prev); + } +} + +/** Rotate a cycle to start at its lexicographically smallest node (rotation-invariant key). */ +function canonicalKey(cycle: string[]): string { + if (cycle.length === 0) { + return ""; + } + let minIdx = 0; + for (let i = 1; i < cycle.length; i++) { + if (cycle[i] < cycle[minIdx]) { + minIdx = i; + } + } + return [...cycle.slice(minIdx), ...cycle.slice(0, minIdx)].join(" > "); +} + +// --------------------------------------------------------------------------- +// Argument parsing +// --------------------------------------------------------------------------- + +interface Options { + root: string; + outDir: string; + includeTests: boolean; + top: number; + ratchet: boolean; + base: string; + help: boolean; +} + +function parseArgs(argv: string[]): Options { + const opts: Options = { + root: path.resolve(__dirname, "..", "..", ".."), + outDir: path.join(__dirname, "circular-report"), + includeTests: false, + top: 25, + ratchet: false, + base: "origin/main", + help: false, + }; + + const tokens: string[] = []; + for (const raw of argv) { + const m = /^(--[\w-]+)=(.*)$/.exec(raw); + if (m) { + tokens.push(m[1], m[2]); + } else { + tokens.push(raw); + } + } + + for (let i = 0; i < tokens.length; i++) { + const arg = tokens[i]; + const next = tokens[i + 1]; + switch (arg) { + case "--help": + case "-h": + opts.help = true; + break; + case "--include-tests": + opts.includeTests = true; + break; + case "--top": + if (next === undefined || !/^\d+$/.test(next)) { + throw new Error("--top requires a positive integer"); + } + opts.top = parseInt(next, 10); + i++; + break; + case "--ratchet": + opts.ratchet = true; + break; + case "--base": + if (next === undefined) { + throw new Error("--base requires a git ref"); + } + opts.base = next; + i++; + break; + case "--root": + if (next === undefined) { + throw new Error("--root requires a path"); + } + opts.root = path.resolve(next); + i++; + break; + case "--out-dir": + case "--outDir": + if (next === undefined) { + throw new Error("--out-dir requires a path"); + } + opts.outDir = path.resolve(next); + i++; + break; + default: + console.warn(`Ignoring unrecognized argument: ${arg}`); + break; + } + } + + if (opts.top <= 0) { + throw new Error("--top must be greater than 0"); + } + return opts; +} + +const HELP = `Circular-dependency report + ratchet gate for the TypeAgent ts/ tree. + +Usage: + npx tsx tools/scripts/code/circularDepsReport.ts [options] + npm run code-circular -- [options] + +Options: + --include-tests Include test files (excluded by default). + --top Number of cycles to print / embed (default 25). + --root Directory to scan (default: the ts/ root). + --out-dir Output directory (default: tools/scripts/code/circular-report). + --ratchet CI gate: fail if changed code introduces new cycles vs --base. + --base Base git ref for --ratchet (default origin/main). + --help Show this help.`; + +// --------------------------------------------------------------------------- +// Rollups +// --------------------------------------------------------------------------- + +function packageKeyOf(rel: string): string { + const f = rel.replace(/\\/g, "/"); + const i = f.indexOf("/src/"); + if (i >= 0) { + return f.slice(0, i); + } + const parts = f.split("/"); + if (parts[0] === "packages" && parts[1] === "agents") { + return parts.slice(0, 3).join("/"); + } + if ( + parts[0] === "packages" || + parts[0] === "examples" || + parts[0] === "extensions" + ) { + return parts.slice(0, 2).join("/"); + } + return parts.slice(0, Math.min(2, parts.length)).join("/"); +} + +interface CycleRecord { + length: number; + crossPackage: boolean; + packages: string[]; + nodes: string[]; +} + +interface AnalysisResult { + cycles: CycleRecord[]; + files: number; + perPackage: { pkg: string; cycles: number }[]; + crossPackageCount: number; + elapsedMs: number; +} + +function toRecords(cycles: string[][]): CycleRecord[] { + return cycles + .map((nodes) => { + const packages = [...new Set(nodes.map(packageKeyOf))].sort(); + return { + length: nodes.length, + crossPackage: packages.length > 1, + packages, + nodes, + }; + }) + .sort((a, b) => b.length - a.length); +} + +// --------------------------------------------------------------------------- +// Formatting helpers +// --------------------------------------------------------------------------- + +function csvEscape(value: string | number | boolean): string { + const s = String(value); + if (/[",\r\n]/.test(s)) { + return `"${s.replace(/"/g, '""')}"`; + } + return s; +} + +function htmlEscape(s: string): string { + return s.replace(/&/g, "&").replace(//g, ">"); +} + +function num(n: number): string { + return n.toLocaleString("en-US"); +} + +function distribution(cycles: CycleRecord[]): number[] { + const counts = new Array(BUCKETS.length).fill(0); + for (const c of cycles) { + const idx = BUCKETS.findIndex( + (b) => c.length >= b.lo && c.length <= b.hi, + ); + if (idx >= 0) { + counts[idx]++; + } + } + return counts; +} + +// --------------------------------------------------------------------------- +// Report writers +// --------------------------------------------------------------------------- + +function writeCsv(outDir: string, cycles: CycleRecord[]): string { + const header = ["Length", "CrossPackage", "Packages", "Cycle"].join(","); + const rows = cycles.map((c) => + [c.length, c.crossPackage, c.packages.join(" | "), c.nodes.join(" > ")] + .map(csvEscape) + .join(","), + ); + const file = path.join(outDir, "cycles.csv"); + fs.writeFileSync(file, [header, ...rows].join("\n") + "\n", "utf8"); + return file; +} + +function writeJson(outDir: string, opts: Options, r: AnalysisResult): string { + const payload = { + generatedAt: new Date().toISOString(), + root: opts.root, + includeTests: opts.includeTests, + totals: { + cycles: r.cycles.length, + files: r.files, + crossPackageCycles: r.crossPackageCount, + }, + distribution: BUCKETS.map((b, i) => ({ + label: b.label, + count: distribution(r.cycles)[i], + })), + perPackage: r.perPackage, + cycles: r.cycles, + }; + const file = path.join(outDir, "report.json"); + fs.writeFileSync(file, JSON.stringify(payload, null, 2) + "\n", "utf8"); + return file; +} + +function writeHtml(outDir: string, opts: Options, r: AnalysisResult): string { + const dist = distribution(r.cycles); + const maxDist = Math.max(1, ...dist); + const distRows = BUCKETS.map((b, i) => { + const w = Math.round((dist[i] / maxDist) * 100); + return `${htmlEscape(b.label)}${num(dist[i])} + `; + }).join("\n"); + + const pkgRows = r.perPackage + .slice(0, 60) + .map( + (p) => + `${htmlEscape(p.pkg)}${num(p.cycles)}`, + ) + .join("\n"); + + const cycleRows = r.cycles + .slice(0, Math.max(opts.top, 100)) + .map( + (c) => + `${c.length} + ${c.crossPackage ? "yes" : ""}${htmlEscape(c.nodes.join(" → "))}`, + ) + .join("\n"); + + const html = ` + +TypeAgent circular-dependency report + +

TypeAgent circular-dependency report

+
engine: madge (runtime imports only) · generated ${htmlEscape(new Date().toISOString())} · + tests ${opts.includeTests ? "included" : "excluded"}
+ +
+
${num(r.cycles.length)}
cycles
+
${num(r.crossPackageCount)}
cross-package cycles
+
${num(r.files)}
files in graph
+
+ +

Cycle size distribution

+ +${distRows}
BucketCycles 
+ +

Packages by cycle count

+ +${pkgRows || ''}
PackageCycles
None
+ +

Cycles

+ +${cycleRows || ''}
LengthCrossCycle
None found
+ + +`; + const file = path.join(outDir, "report.html"); + fs.writeFileSync(file, html, "utf8"); + return file; +} + +// --------------------------------------------------------------------------- +// Report mode +// --------------------------------------------------------------------------- + +async function runReport(opts: Options): Promise { + const started = Date.now(); + const { cycles, files } = await runMadge(opts.root, opts.includeTests); + const records = toRecords(cycles); + + const pkgMap = new Map(); + for (const c of records) { + for (const p of c.packages) { + pkgMap.set(p, (pkgMap.get(p) ?? 0) + 1); + } + } + const perPackage = [...pkgMap.entries()] + .map(([pkg, n]) => ({ pkg, cycles: n })) + .sort((a, b) => b.cycles - a.cycles || a.pkg.localeCompare(b.pkg)); + + const result: AnalysisResult = { + cycles: records, + files, + perPackage, + crossPackageCount: records.filter((c) => c.crossPackage).length, + elapsedMs: Date.now() - started, + }; + + fs.mkdirSync(opts.outDir, { recursive: true }); + const csv = writeCsv(opts.outDir, records); + const json = writeJson(opts.outDir, opts, result); + const html = writeHtml(opts.outDir, opts, result); + + const dist = distribution(records); + console.log(""); + console.log("Circular-dependency report (engine: madge, runtime imports)"); + console.log( + `Graph: ${num(files)} files | elapsed ${(result.elapsedMs / 1000).toFixed(1)}s`, + ); + console.log(""); + console.log( + `Cycles: ${num(records.length)} | cross-package: ${num(result.crossPackageCount)}`, + ); + console.log(""); + console.log("Size distribution:"); + BUCKETS.forEach((b, i) => + console.log(` ${b.label.padEnd(10)} ${num(dist[i])}`), + ); + console.log(""); + console.log( + `Top ${Math.min(opts.top, perPackage.length)} packages by cycles:`, + ); + for (const p of perPackage.slice(0, opts.top)) { + console.log(` ${String(p.cycles).padStart(4)} ${p.pkg}`); + } + console.log(""); + console.log(`Largest cycles (top ${Math.min(opts.top, records.length)}):`); + for (const c of records.slice(0, opts.top)) { + console.log( + ` ${String(c.length).padStart(2)} ${c.nodes.join(" > ")}${c.crossPackage ? " [cross-pkg]" : ""}`, + ); + } + console.log(""); + console.log("Reports written to:"); + for (const f of [csv, json, html]) { + console.log( + ` ${path.relative(opts.root, f).split(path.sep).join("/")}`, + ); + } + return 0; +} + +// --------------------------------------------------------------------------- +// Ratchet mode (CI gate) +// --------------------------------------------------------------------------- + +function git(args: string[], cwd: string): string { + return execFileSync("git", args, { + cwd, + encoding: "utf8", + maxBuffer: 128 * 1024 * 1024, + }); +} + +async function runRatchet(opts: Options): Promise { + let repoRoot: string; + let mergeBase: string; + try { + repoRoot = git(["rev-parse", "--show-toplevel"], opts.root).trim(); + mergeBase = git(["merge-base", opts.base, "HEAD"], opts.root).trim(); + } catch { + console.error( + `Ratchet: could not resolve base ref "${opts.base}" via git. ` + + "Pass --base (e.g. origin/main) and ensure it is fetched.", + ); + return 2; + } + + // HEAD cycles (current working tree). + const head = await runMadge(opts.root, opts.includeTests); + const headKeys = new Map(); + for (const c of head.cycles) { + headKeys.set(canonicalKey(c), c); + } + + // BASE cycles: check the merge base out into a throwaway worktree. + // Use a path git creates itself (worktree add refuses a pre-existing dir). + const worktree = path.join( + os.tmpdir(), + `circular-base-${process.pid}-${Date.now()}`, + ); + const baseKeys = new Set(); + try { + git(["worktree", "add", "--detach", worktree, mergeBase], repoRoot); + // opts.root is the ts/ dir relative to the repo root; mirror it in the worktree. + const rootRel = path.relative(repoRoot, opts.root); + const baseRoot = path.join(worktree, rootRel); + const base = await runMadge(baseRoot, opts.includeTests); + for (const c of base.cycles) { + baseKeys.add(canonicalKey(c)); + } + } catch (e) { + console.error(`Ratchet: failed to analyze base worktree: ${e}`); + return 2; + } finally { + try { + git(["worktree", "remove", "--force", worktree], repoRoot); + } catch { + fs.rmSync(worktree, { recursive: true, force: true }); + } + } + + const newCycles: string[][] = []; + for (const [key, cycle] of headKeys) { + if (!baseKeys.has(key)) { + newCycles.push(cycle); + } + } + + console.log( + `Ratchet: cycles base ${baseKeys.size} -> head ${headKeys.size}`, + ); + + if (newCycles.length > 0) { + console.error( + `\nRatchet FAILED: ${newCycles.length} new circular dependency(ies) introduced vs the base:`, + ); + for (const c of newCycles.slice(0, 50)) { + console.error(` ${c.join(" > ")}`); + } + console.error( + "\nBreak the cycle (e.g. extract the shared piece into a third module) before merging.", + ); + return 1; + } + console.log("Ratchet OK: no new circular dependencies."); + return 0; +} + +// --------------------------------------------------------------------------- +// Main +// --------------------------------------------------------------------------- + +async function main(): Promise { + let opts: Options; + try { + opts = parseArgs(process.argv.slice(2)); + } catch (e) { + console.error((e as Error).message); + process.exitCode = 2; + return; + } + + if (opts.help) { + console.log(HELP); + return; + } + + const code = opts.ratchet ? await runRatchet(opts) : await runReport(opts); + process.exitCode = code; +} + +main().catch((e) => { + console.error(e); + process.exitCode = 1; +}); diff --git a/ts/tools/scripts/code/deadCodeReport.ts b/ts/tools/scripts/code/deadCodeReport.ts new file mode 100644 index 0000000000..6b1eee5cb2 --- /dev/null +++ b/ts/tools/scripts/code/deadCodeReport.ts @@ -0,0 +1,528 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * Dead-code report for the TypeAgent ts/ tree. + * + * The engine is knip — the standard unused-files / exports / dependencies + * detector for JS/TS monorepos. This wrapper runs knip's JSON reporter (via + * `pnpm exec`, because the npx shim cannot resolve local bins in this + * environment), then rolls the results up per category and per package and + * writes CSV/JSON/HTML + a console summary. + * + * Important: knip's accuracy depends entirely on its configuration. Out of the + * box it cannot know which files are entry points (agent action handlers loaded + * via manifest, CLI/webpack entries, benchmark harnesses), so its raw numbers + * heavily overcount "unused" files/exports. Tune tools/scripts/code/knip.jsonc + * (declare entry points, ignore generated output) and the numbers become real. + * For that reason this tool is report-only — there is no ratchet gate until the + * configuration produces a trustworthy baseline. + * + * Outputs (written to --out-dir, default tools/scripts/code/deadcode-report): + * - deadcode.csv : every finding (file, category, item) + * - report.json : structured metrics (per-category, per-package) + * - report.html : a self-contained, sortable report + * + * Usage: + * npx tsx tools/scripts/code/deadCodeReport.ts [options] + * npm run code-deadcode -- [options] + * + * Options: + * --top Number of rows to print / embed (default 25). + * --root Directory knip runs in (default: the ts/ root). + * --config knip config (default tools/scripts/code/knip.jsonc). + * --out-dir Output directory (default tools/scripts/code/deadcode-report). + * --help Show this help. + */ + +import fs from "node:fs"; +import path from "node:path"; +import { execSync } from "node:child_process"; +import { fileURLToPath } from "node:url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// --------------------------------------------------------------------------- +// Categories +// --------------------------------------------------------------------------- + +// Categories that represent dead code within our source. +const DEAD_CODE_CATS = [ + "files", + "exports", + "types", + "enumMembers", + "namespaceMembers", + "duplicates", +]; + +// Categories that represent dependency / manifest hygiene. +const DEPENDENCY_CATS = [ + "dependencies", + "devDependencies", + "optionalPeerDependencies", + "unlisted", + "binaries", + "unresolved", +]; + +const ALL_CATS = [...DEAD_CODE_CATS, ...DEPENDENCY_CATS]; + +// --------------------------------------------------------------------------- +// Argument parsing +// --------------------------------------------------------------------------- + +interface Options { + root: string; + outDir: string; + config: string; + top: number; + help: boolean; +} + +function parseArgs(argv: string[]): Options { + const root = path.resolve(__dirname, "..", "..", ".."); + const opts: Options = { + root, + outDir: path.join(__dirname, "deadcode-report"), + config: path.join(__dirname, "knip.jsonc"), + top: 25, + help: false, + }; + + const tokens: string[] = []; + for (const raw of argv) { + const m = /^(--[\w-]+)=(.*)$/.exec(raw); + if (m) { + tokens.push(m[1], m[2]); + } else { + tokens.push(raw); + } + } + + for (let i = 0; i < tokens.length; i++) { + const arg = tokens[i]; + const next = tokens[i + 1]; + switch (arg) { + case "--help": + case "-h": + opts.help = true; + break; + case "--top": + if (next === undefined || !/^\d+$/.test(next)) { + throw new Error("--top requires a positive integer"); + } + opts.top = parseInt(next, 10); + i++; + break; + case "--root": + if (next === undefined) { + throw new Error("--root requires a path"); + } + opts.root = path.resolve(next); + i++; + break; + case "--config": + if (next === undefined) { + throw new Error("--config requires a path"); + } + opts.config = path.resolve(next); + i++; + break; + case "--out-dir": + case "--outDir": + if (next === undefined) { + throw new Error("--out-dir requires a path"); + } + opts.outDir = path.resolve(next); + i++; + break; + default: + console.warn(`Ignoring unrecognized argument: ${arg}`); + break; + } + } + + if (opts.top <= 0) { + throw new Error("--top must be greater than 0"); + } + return opts; +} + +const HELP = `Dead-code report for the TypeAgent ts/ tree (engine: knip). + +Usage: + npx tsx tools/scripts/code/deadCodeReport.ts [options] + npm run code-deadcode -- [options] + +Options: + --top Number of rows to print / embed (default 25). + --root Directory knip runs in (default: the ts/ root). + --config knip config (default: tools/scripts/code/knip.jsonc). + --out-dir Output directory (default: tools/scripts/code/deadcode-report). + --help Show this help.`; + +// --------------------------------------------------------------------------- +// knip invocation + parsing +// --------------------------------------------------------------------------- + +interface KnipIssue { + file: string; + [category: string]: unknown; +} + +function runKnip(opts: Options): KnipIssue[] { + const configArg = + opts.config && fs.existsSync(opts.config) + ? ` --config "${opts.config}"` + : ""; + const cmd = `pnpm exec knip --reporter json --no-progress${configArg}`; + let stdout: string; + try { + stdout = execSync(cmd, { + cwd: opts.root, + encoding: "utf8", + maxBuffer: 512 * 1024 * 1024, + stdio: ["ignore", "pipe", "pipe"], + }); + } catch (e) { + // knip exits non-zero when it finds issues; the JSON is still on stdout. + const err = e as { stdout?: Buffer | string; message?: string }; + stdout = err.stdout ? err.stdout.toString() : ""; + if (!stdout) { + throw new Error(`knip failed: ${err.message ?? e}`); + } + } + const data = JSON.parse(stdout) as { issues?: KnipIssue[] }; + return data.issues ?? []; +} + +/** Extract the human-readable item names for a category value. */ +function itemsOf(cat: string, value: unknown, file: string): string[] { + if (cat === "files") { + const flagged = Array.isArray(value) + ? value.length > 0 + : value === true; + return flagged ? [file] : []; + } + if (value && typeof value === "object" && !Array.isArray(value)) { + // enumMembers: { EnumName: [member, ...] } + const out: string[] = []; + for (const [k, arr] of Object.entries( + value as Record, + )) { + const members = Array.isArray(arr) ? arr : []; + for (const m of members) { + out.push( + `${k}.${typeof m === "string" ? m : ((m as { name?: string })?.name ?? String(m))}`, + ); + } + } + return out; + } + if (Array.isArray(value)) { + return value.map((x) => { + if (typeof x === "string") { + return x; + } + if (Array.isArray(x)) { + return x + .map((y) => (y as { name?: string })?.name ?? String(y)) + .join(" + "); + } + return (x as { name?: string })?.name ?? String(x); + }); + } + return []; +} + +// --------------------------------------------------------------------------- +// Rollups +// --------------------------------------------------------------------------- + +function packageKeyOf(rel: string): string { + const f = rel.replace(/\\/g, "/"); + const i = f.indexOf("/src/"); + if (i >= 0) { + return f.slice(0, i); + } + const parts = f.split("/"); + if (parts[0] === "packages" && parts[1] === "agents") { + return parts.slice(0, 3).join("/"); + } + if ( + parts[0] === "packages" || + parts[0] === "examples" || + parts[0] === "extensions" + ) { + return parts.slice(0, 2).join("/"); + } + return parts.slice(0, Math.min(2, parts.length)).join("/"); +} + +interface Finding { + file: string; + pkg: string; + category: string; + item: string; + deadCode: boolean; +} + +interface AnalysisResult { + findings: Finding[]; + perCategory: { name: string; count: number; deadCode: boolean }[]; + perPackage: { pkg: string; deadCode: number; total: number }[]; + deadCodeTotal: number; + dependencyTotal: number; + elapsedMs: number; +} + +function analyze(issues: KnipIssue[], startedAt: number): AnalysisResult { + const findings: Finding[] = []; + for (const issue of issues) { + const file = (issue.file ?? "").replace(/\\/g, "/"); + const pkg = packageKeyOf(file); + for (const cat of ALL_CATS) { + const items = itemsOf(cat, issue[cat], file); + const deadCode = DEAD_CODE_CATS.includes(cat); + for (const item of items) { + findings.push({ file, pkg, category: cat, item, deadCode }); + } + } + } + + const catMap = new Map(); + for (const f of findings) { + catMap.set(f.category, (catMap.get(f.category) ?? 0) + 1); + } + const perCategory = ALL_CATS.map((name) => ({ + name, + count: catMap.get(name) ?? 0, + deadCode: DEAD_CODE_CATS.includes(name), + })).sort((a, b) => b.count - a.count); + + const pkgMap = new Map(); + for (const f of findings) { + const r = pkgMap.get(f.pkg) ?? { deadCode: 0, total: 0 }; + r.total++; + if (f.deadCode) { + r.deadCode++; + } + pkgMap.set(f.pkg, r); + } + const perPackage = [...pkgMap.entries()] + .map(([pkg, v]) => ({ pkg, ...v })) + .sort((a, b) => b.deadCode - a.deadCode || b.total - a.total); + + return { + findings, + perCategory, + perPackage, + deadCodeTotal: findings.filter((f) => f.deadCode).length, + dependencyTotal: findings.filter((f) => !f.deadCode).length, + elapsedMs: Date.now() - startedAt, + }; +} + +// --------------------------------------------------------------------------- +// Formatting helpers +// --------------------------------------------------------------------------- + +function csvEscape(value: string | number | boolean): string { + const s = String(value); + if (/[",\r\n]/.test(s)) { + return `"${s.replace(/"/g, '""')}"`; + } + return s; +} + +function htmlEscape(s: string): string { + return s.replace(/&/g, "&").replace(//g, ">"); +} + +function num(n: number): string { + return n.toLocaleString("en-US"); +} + +// --------------------------------------------------------------------------- +// Report writers +// --------------------------------------------------------------------------- + +function writeCsv(outDir: string, findings: Finding[]): string { + const header = ["Category", "DeadCode", "Package", "File", "Item"].join( + ",", + ); + const rows = findings.map((f) => + [f.category, f.deadCode, f.pkg, f.file, f.item] + .map(csvEscape) + .join(","), + ); + const file = path.join(outDir, "deadcode.csv"); + fs.writeFileSync(file, [header, ...rows].join("\n") + "\n", "utf8"); + return file; +} + +function writeJson(outDir: string, opts: Options, r: AnalysisResult): string { + const payload = { + generatedAt: new Date().toISOString(), + root: opts.root, + config: fs.existsSync(opts.config) ? opts.config : null, + totals: { + deadCode: r.deadCodeTotal, + dependency: r.dependencyTotal, + findings: r.findings.length, + }, + perCategory: r.perCategory, + perPackage: r.perPackage, + findings: r.findings, + }; + const file = path.join(outDir, "report.json"); + fs.writeFileSync(file, JSON.stringify(payload, null, 2) + "\n", "utf8"); + return file; +} + +function writeHtml(outDir: string, opts: Options, r: AnalysisResult): string { + const catRows = r.perCategory + .map( + (c) => + `${htmlEscape(c.name)}${c.deadCode ? "dead code" : "dependency"} + ${num(c.count)}`, + ) + .join("\n"); + + const pkgRows = r.perPackage + .slice(0, Math.max(opts.top, 60)) + .map( + (p) => + `${htmlEscape(p.pkg)}${num(p.deadCode)}${num(p.total)}`, + ) + .join("\n"); + + const html = ` + +TypeAgent dead-code report + +

TypeAgent dead-code report

+
engine: knip · generated ${htmlEscape(new Date().toISOString())} · + config ${fs.existsSync(opts.config) ? htmlEscape(path.basename(opts.config)) : "(none — raw)"}
+
Numbers depend on knip's entry-point configuration. Untuned, "unused" + files/exports are heavily overcounted. Treat these as candidates to review, not deletions.
+ +
+
${num(r.deadCodeTotal)}
dead-code findings
+
${num(r.dependencyTotal)}
dependency findings
+
+ +

By category

+ +${catRows}
CategoryKindCount
+ +

Packages by dead-code findings

+ +${pkgRows || ''}
PackageDead codeTotal
None
+ + +`; + const file = path.join(outDir, "report.html"); + fs.writeFileSync(file, html, "utf8"); + return file; +} + +// --------------------------------------------------------------------------- +// Main +// --------------------------------------------------------------------------- + +function main(): void { + let opts: Options; + try { + opts = parseArgs(process.argv.slice(2)); + } catch (e) { + console.error((e as Error).message); + process.exitCode = 2; + return; + } + + if (opts.help) { + console.log(HELP); + return; + } + + const started = Date.now(); + console.log("Running knip (this analyzes the whole workspace)..."); + const issues = runKnip(opts); + const result = analyze(issues, started); + + fs.mkdirSync(opts.outDir, { recursive: true }); + const csv = writeCsv(opts.outDir, result.findings); + const json = writeJson(opts.outDir, opts, result); + const html = writeHtml(opts.outDir, opts, result); + + console.log(""); + console.log("Dead-code report (engine: knip)"); + console.log( + `Config: ${fs.existsSync(opts.config) ? path.relative(opts.root, opts.config).split(path.sep).join("/") : "(none — RAW, heavily overcounted)"} | ` + + `elapsed ${(result.elapsedMs / 1000).toFixed(1)}s`, + ); + console.log(""); + console.log( + `Dead-code findings: ${num(result.deadCodeTotal)} | dependency findings: ${num(result.dependencyTotal)}`, + ); + console.log(""); + console.log("By category:"); + for (const c of result.perCategory) { + console.log( + ` ${String(c.count).padStart(6)} ${c.name.padEnd(26)} ${c.deadCode ? "(dead code)" : "(dependency)"}`, + ); + } + console.log(""); + console.log(`Top ${opts.top} packages by dead-code findings:`); + for (const p of result.perPackage.slice(0, opts.top)) { + if (p.deadCode === 0) { + break; + } + console.log(` ${String(p.deadCode).padStart(5)} ${p.pkg}`); + } + console.log(""); + console.log( + "NOTE: tune tools/scripts/code/knip.jsonc (entry points) before trusting these numbers.", + ); + console.log("Reports written to:"); + for (const f of [csv, json, html]) { + console.log( + ` ${path.relative(opts.root, f).split(path.sep).join("/")}`, + ); + } +} + +main(); diff --git a/ts/tools/scripts/code/debtMarkersReport.ts b/ts/tools/scripts/code/debtMarkersReport.ts new file mode 100644 index 0000000000..d62ca5ec73 --- /dev/null +++ b/ts/tools/scripts/code/debtMarkersReport.ts @@ -0,0 +1,678 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * Debt-markers report + hard gate for the TypeAgent ts/ tree. + * + * A lightweight, dependency-free scan (line-level regex, so treat hits as + * candidates) for debt markers that the heavier engines do not cover: + * - TODO / FIXME / HACK / XXX comments + * - @deprecated annotations (APIs kept alive past their use-by date) + * - Skipped tests: .skip( / xit( / xdescribe( + * - Focused tests: .only( / fit( / fdescribe( (should never be committed) + * + * Unlike lint and circular deps (large problems -> ratchet), skipped/focused + * tests are a small, fixable problem, so this ships a *hard gate* rather than a + * ratchet: + * - Gate (--gate --base ): fail if any changed file contains a focused + * test, or introduces a new skipped test versus the base. TODO/@deprecated + * are reported but not gated (tracked, not blocked). + * + * Outputs (written to --out-dir, default tools/scripts/code/debt-report): + * - markers.csv : every marker (file, line, type, text) + * - report.json : structured metrics (per-type, per-package) + * - report.html : a self-contained, sortable report + * + * Usage: + * npx tsx tools/scripts/code/debtMarkersReport.ts [options] + * npm run code-debt -- [options] + * + * Options: + * --top Number of rows to print / embed (default 25). + * --root Directory to scan (default: the ts/ root). + * --out-dir Output directory (default tools/scripts/code/debt-report). + * --gate CI gate: fail on focused tests / new skipped tests vs --base. + * --base Base git ref for --gate (default origin/main). + * --help Show this help. + */ + +import fs from "node:fs"; +import path from "node:path"; +import { execFileSync } from "node:child_process"; +import { fileURLToPath } from "node:url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// --------------------------------------------------------------------------- +// Configuration +// --------------------------------------------------------------------------- + +const SCAN_SUBDIRS = ["packages", "examples", "extensions", "tools"]; + +const CODE_EXTS = new Set([ + ".ts", + ".mts", + ".cts", + ".tsx", + ".js", + ".mjs", + ".cjs", + ".jsx", +]); + +const IGNORE_DIR_NAMES = new Set([ + "node_modules", + "dist", + "build", + "out", + "coverage", + "bin", + "obj", + ".turbo", + ".next", + "bundle", + ".git", + ".jscpd", + "complexity-report", + "duplication-report", + "consistency-report", + "lint-report", + "circular-report", + "deadcode-report", + "debt-report", +]); + +// Marker matchers. Each returns the count of matches on a line. +const MARKERS: { + type: string; + re: RegExp; + gate: "focused" | "skip" | "none"; +}[] = [ + { type: "TODO", re: /\bTODO\b/g, gate: "none" }, + { type: "FIXME", re: /\bFIXME\b/g, gate: "none" }, + { type: "HACK", re: /\bHACK\b/g, gate: "none" }, + { type: "XXX", re: /\bXXX\b/g, gate: "none" }, + { type: "@deprecated", re: /@deprecated\b/g, gate: "none" }, + { + type: "skipped-test", + re: /\b(?:it|test|describe)\.skip\s*\(|\bxit\s*\(|\bxdescribe\s*\(/g, + gate: "skip", + }, + { + type: "focused-test", + re: /\b(?:it|test|describe)\.only\s*\(|\bfit\s*\(|\bfdescribe\s*\(/g, + gate: "focused", + }, +]; + +// Focused/skipped test markers are only meaningful in test files. +const FOCUSED_RE = + /\b(?:it|test|describe)\.only\s*\(|\bfit\s*\(|\bfdescribe\s*\(/g; +const SKIP_RE = + /\b(?:it|test|describe)\.skip\s*\(|\bxit\s*\(|\bxdescribe\s*\(/g; + +// --------------------------------------------------------------------------- +// Argument parsing +// --------------------------------------------------------------------------- + +interface Options { + root: string; + outDir: string; + top: number; + gate: boolean; + base: string; + help: boolean; +} + +function parseArgs(argv: string[]): Options { + const opts: Options = { + root: path.resolve(__dirname, "..", "..", ".."), + outDir: path.join(__dirname, "debt-report"), + top: 25, + gate: false, + base: "origin/main", + help: false, + }; + + const tokens: string[] = []; + for (const raw of argv) { + const m = /^(--[\w-]+)=(.*)$/.exec(raw); + if (m) { + tokens.push(m[1], m[2]); + } else { + tokens.push(raw); + } + } + + for (let i = 0; i < tokens.length; i++) { + const arg = tokens[i]; + const next = tokens[i + 1]; + switch (arg) { + case "--help": + case "-h": + opts.help = true; + break; + case "--top": + if (next === undefined || !/^\d+$/.test(next)) { + throw new Error("--top requires a positive integer"); + } + opts.top = parseInt(next, 10); + i++; + break; + case "--gate": + opts.gate = true; + break; + case "--base": + if (next === undefined) { + throw new Error("--base requires a git ref"); + } + opts.base = next; + i++; + break; + case "--root": + if (next === undefined) { + throw new Error("--root requires a path"); + } + opts.root = path.resolve(next); + i++; + break; + case "--out-dir": + case "--outDir": + if (next === undefined) { + throw new Error("--out-dir requires a path"); + } + opts.outDir = path.resolve(next); + i++; + break; + default: + console.warn(`Ignoring unrecognized argument: ${arg}`); + break; + } + } + + if (opts.top <= 0) { + throw new Error("--top must be greater than 0"); + } + return opts; +} + +const HELP = `Debt-markers report + hard gate for the TypeAgent ts/ tree. + +Usage: + npx tsx tools/scripts/code/debtMarkersReport.ts [options] + npm run code-debt -- [options] + +Options: + --top Number of rows to print / embed (default 25). + --root Directory to scan (default: the ts/ root). + --out-dir Output directory (default: tools/scripts/code/debt-report). + --gate CI gate: fail on focused tests / new skipped tests vs --base. + --base Base git ref for --gate (default origin/main). + --help Show this help.`; + +// --------------------------------------------------------------------------- +// Scanning +// --------------------------------------------------------------------------- + +function* walk(dir: string): Generator { + let entries: fs.Dirent[]; + try { + entries = fs.readdirSync(dir, { withFileTypes: true }); + } catch { + return; + } + for (const e of entries) { + const full = path.join(dir, e.name); + if (e.isDirectory()) { + if (!IGNORE_DIR_NAMES.has(e.name)) { + yield* walk(full); + } + } else if (e.isFile()) { + yield full; + } + } +} + +function isCodeFile(name: string): boolean { + if (/\.d\.(ts|mts|cts)$/.test(name)) { + return false; + } + return CODE_EXTS.has(path.extname(name)); +} + +function isTestFile(rel: string): boolean { + return ( + /\.(spec|test)\./.test(rel) || + /(^|\/)(test|tests|__tests__)\//.test(rel) + ); +} + +function packageKeyOf(rel: string): string { + const f = rel.replace(/\\/g, "/"); + const i = f.indexOf("/src/"); + if (i >= 0) { + return f.slice(0, i); + } + const parts = f.split("/"); + if (parts[0] === "packages" && parts[1] === "agents") { + return parts.slice(0, 3).join("/"); + } + if ( + parts[0] === "packages" || + parts[0] === "examples" || + parts[0] === "extensions" + ) { + return parts.slice(0, 2).join("/"); + } + return parts.slice(0, Math.min(2, parts.length)).join("/"); +} + +interface Marker { + file: string; + line: number; + type: string; + text: string; +} + +function scanContent(rel: string, content: string, isTest: boolean): Marker[] { + const markers: Marker[] = []; + const lines = content.split(/\r?\n/); + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + for (const m of MARKERS) { + // Skipped/focused test markers only count in test files. + if ((m.gate === "skip" || m.gate === "focused") && !isTest) { + continue; + } + m.re.lastIndex = 0; + const hits = line.match(m.re); + if (hits) { + for (let h = 0; h < hits.length; h++) { + markers.push({ + file: rel, + line: i + 1, + type: m.type, + text: line.trim().slice(0, 200), + }); + } + } + } + } + return markers; +} + +function countMatches(content: string, re: RegExp): number { + let n = 0; + for (const line of content.split(/\r?\n/)) { + re.lastIndex = 0; + const hits = line.match(re); + if (hits) { + n += hits.length; + } + } + return n; +} + +// --------------------------------------------------------------------------- +// Formatting helpers +// --------------------------------------------------------------------------- + +function csvEscape(value: string | number): string { + const s = String(value); + if (/[",\r\n]/.test(s)) { + return `"${s.replace(/"/g, '""')}"`; + } + return s; +} + +function htmlEscape(s: string): string { + return s.replace(/&/g, "&").replace(//g, ">"); +} + +function num(n: number): string { + return n.toLocaleString("en-US"); +} + +// --------------------------------------------------------------------------- +// Report mode +// --------------------------------------------------------------------------- + +function collect(root: string): Marker[] { + const markers: Marker[] = []; + for (const sub of SCAN_SUBDIRS) { + const abs = path.join(root, sub); + if (!fs.existsSync(abs)) { + continue; + } + for (const full of walk(abs)) { + if (!isCodeFile(full)) { + continue; + } + const rel = path.relative(root, full).split(path.sep).join("/"); + let content: string; + try { + content = fs.readFileSync(full, "utf8"); + } catch { + continue; + } + markers.push(...scanContent(rel, content, isTestFile(rel))); + } + } + return markers; +} + +function runReport(opts: Options): number { + const started = Date.now(); + const markers = collect(opts.root); + + const byType = new Map(); + for (const m of MARKERS) { + byType.set(m.type, 0); + } + for (const mk of markers) { + byType.set(mk.type, (byType.get(mk.type) ?? 0) + 1); + } + + const byPkg = new Map(); + for (const mk of markers) { + const p = packageKeyOf(mk.file); + byPkg.set(p, (byPkg.get(p) ?? 0) + 1); + } + const perPackage = [...byPkg.entries()] + .map(([pkg, count]) => ({ pkg, count })) + .sort((a, b) => b.count - a.count || a.pkg.localeCompare(b.pkg)); + + const byFile = new Map(); + for (const mk of markers) { + byFile.set(mk.file, (byFile.get(mk.file) ?? 0) + 1); + } + const perFile = [...byFile.entries()] + .map(([file, count]) => ({ file, count })) + .sort((a, b) => b.count - a.count); + + fs.mkdirSync(opts.outDir, { recursive: true }); + + // CSV + const header = ["Type", "Package", "File", "Line", "Text"].join(","); + const rows = markers.map((mk) => + [mk.type, packageKeyOf(mk.file), mk.file, mk.line, mk.text] + .map(csvEscape) + .join(","), + ); + const csv = path.join(opts.outDir, "markers.csv"); + fs.writeFileSync(csv, [header, ...rows].join("\n") + "\n", "utf8"); + + // JSON + const payload = { + generatedAt: new Date().toISOString(), + root: opts.root, + totals: { + markers: markers.length, + byType: Object.fromEntries(byType), + }, + perPackage, + perFile, + }; + const json = path.join(opts.outDir, "report.json"); + fs.writeFileSync(json, JSON.stringify(payload, null, 2) + "\n", "utf8"); + + // HTML + const typeRows = [...byType.entries()] + .sort((a, b) => b[1] - a[1]) + .map( + ([t, c]) => + `${htmlEscape(t)}${num(c)}`, + ) + .join("\n"); + const pkgRows = perPackage + .slice(0, Math.max(opts.top, 60)) + .map( + (p) => + `${htmlEscape(p.pkg)}${num(p.count)}`, + ) + .join("\n"); + const html = ` + +TypeAgent debt-markers report + +

TypeAgent debt-markers report

+
generated ${htmlEscape(new Date().toISOString())} · ${num(markers.length)} markers · line-level scan
+
+
${num(byType.get("skipped-test") ?? 0)}
skipped tests
+
${num(byType.get("focused-test") ?? 0)}
focused tests
+
${num(byType.get("@deprecated") ?? 0)}
@deprecated
+
${num((byType.get("TODO") ?? 0) + (byType.get("FIXME") ?? 0) + (byType.get("HACK") ?? 0) + (byType.get("XXX") ?? 0))}
TODO/FIXME/HACK/XXX
+
+

By type

+ +${typeRows}
TypeCount
+

By package

+ +${pkgRows}
PackageCount
+ +`; + fs.writeFileSync(path.join(opts.outDir, "report.html"), html, "utf8"); + + console.log(""); + console.log("Debt-markers report (line-level scan)"); + console.log( + `Markers: ${num(markers.length)} | elapsed ${((Date.now() - started) / 1000).toFixed(1)}s`, + ); + console.log(""); + console.log("By type:"); + for (const [t, c] of [...byType.entries()].sort((a, b) => b[1] - a[1])) { + console.log(` ${String(c).padStart(6)} ${t}`); + } + console.log(""); + console.log(`Top ${opts.top} packages:`); + for (const p of perPackage.slice(0, opts.top)) { + console.log(` ${String(p.count).padStart(6)} ${p.pkg}`); + } + console.log(""); + console.log("Reports written to:"); + for (const f of ["markers.csv", "report.json", "report.html"]) { + console.log( + ` ${path.relative(opts.root, path.join(opts.outDir, f)).split(path.sep).join("/")}`, + ); + } + return 0; +} + +// --------------------------------------------------------------------------- +// Gate mode (CI hard gate) +// --------------------------------------------------------------------------- + +const SOURCE_EXT_RE = /\.[cm]?[jt]sx?$/; +const IGNORE_PATH_RE = + /(^|\/)(node_modules|dist|build|out|coverage|bin|obj|\.turbo|\.next|bundle)\//; +const GENERATED_FILE_RE = /(\.d\.ts|\.min\.js|\.bundle\.js)$/; + +function git(args: string[], cwd: string): string { + return execFileSync("git", args, { + cwd, + encoding: "utf8", + maxBuffer: 128 * 1024 * 1024, + }); +} + +interface DiffEntry { + head: string; + base: string | null; +} + +function parseNameStatus(raw: string): DiffEntry[] { + const entries: DiffEntry[] = []; + for (const line of raw.split(/\r?\n/)) { + if (!line) { + continue; + } + const parts = line.split("\t"); + const status = parts[0]; + if (status.startsWith("R") || status.startsWith("C")) { + entries.push({ base: parts[1], head: parts[2] }); + } else if (status === "A") { + entries.push({ base: null, head: parts[1] }); + } else if (status === "D") { + continue; + } else { + entries.push({ base: parts[1], head: parts[1] }); + } + } + return entries; +} + +function isSource(rel: string): boolean { + return ( + SOURCE_EXT_RE.test(rel) && + !IGNORE_PATH_RE.test(rel) && + !GENERATED_FILE_RE.test(rel) + ); +} + +function runGate(opts: Options): number { + let repoRoot: string; + let mergeBase: string; + try { + repoRoot = git(["rev-parse", "--show-toplevel"], opts.root).trim(); + mergeBase = git(["merge-base", opts.base, "HEAD"], opts.root).trim(); + } catch { + console.error( + `Gate: could not resolve base ref "${opts.base}" via git. ` + + "Pass --base (e.g. origin/main) and ensure it is fetched.", + ); + return 2; + } + + const entries = parseNameStatus( + git(["diff", "--name-status", "-M", mergeBase, "HEAD"], opts.root), + ).filter((e) => { + if (!isSource(e.head)) { + return false; + } + const relToRoot = path.relative( + opts.root, + path.resolve(repoRoot, e.head), + ); + return !relToRoot.startsWith("..") && !path.isAbsolute(relToRoot); + }); + + if (entries.length === 0) { + console.log("Gate: no changed source files to check. OK."); + return 0; + } + + const focusedHits: string[] = []; + let headSkips = 0; + let baseSkips = 0; + + for (const e of entries) { + if (!isTestFile(e.head)) { + continue; // focused/skipped tests only matter in test files + } + const headAbs = path.resolve(repoRoot, e.head); + if (fs.existsSync(headAbs)) { + const content = fs.readFileSync(headAbs, "utf8"); + const lines = content.split(/\r?\n/); + for (let i = 0; i < lines.length; i++) { + FOCUSED_RE.lastIndex = 0; + if (FOCUSED_RE.test(lines[i])) { + focusedHits.push( + ` ${e.head}:${i + 1} ${lines[i].trim()}`, + ); + } + } + headSkips += countMatches(content, SKIP_RE); + } + if (e.base && isTestFile(e.base)) { + try { + const baseContent = git( + ["show", `${mergeBase}:${e.base}`], + repoRoot, + ); + baseSkips += countMatches(baseContent, SKIP_RE); + } catch { + /* file did not exist at base */ + } + } + } + + console.log( + `Gate: ${entries.length} changed source file(s) | skipped tests base ${baseSkips} -> head ${headSkips}`, + ); + + let failed = false; + if (focusedHits.length > 0) { + failed = true; + console.error( + `\nGate FAILED: focused test(s) must not be committed (.only/fit/fdescribe):`, + ); + focusedHits.slice(0, 50).forEach((h) => console.error(h)); + } + if (headSkips > baseSkips) { + failed = true; + console.error( + `\nGate FAILED: changed files add ${headSkips - baseSkips} skipped test(s) ` + + "(.skip/xit/xdescribe). Un-skip or delete them.", + ); + } + + if (failed) { + return 1; + } + console.log("Gate OK: no focused tests and no new skipped tests."); + return 0; +} + +// --------------------------------------------------------------------------- +// Main +// --------------------------------------------------------------------------- + +function main(): void { + let opts: Options; + try { + opts = parseArgs(process.argv.slice(2)); + } catch (e) { + console.error((e as Error).message); + process.exitCode = 2; + return; + } + + if (opts.help) { + console.log(HELP); + return; + } + + process.exitCode = opts.gate ? runGate(opts) : runReport(opts); +} + +main(); diff --git a/ts/tools/scripts/code/knip.jsonc b/ts/tools/scripts/code/knip.jsonc new file mode 100644 index 0000000000..f8d389aa55 --- /dev/null +++ b/ts/tools/scripts/code/knip.jsonc @@ -0,0 +1,14 @@ +{ + // Starter knip config for the dead-code report (tools/scripts/code/deadCodeReport.ts). + // + // knip auto-detects the pnpm workspaces and infers each package's entry + // points from package.json (main / bin / exports). The repo-wide ignores + // below just drop generated output. The real accuracy work — and the reason + // the raw counts overcount "unused" code — is declaring the entry points + // knip cannot infer: agent action handlers loaded at runtime via manifest, + // webpack/CLI entries, and benchmark/test harnesses. Add `entry` patterns + // (per workspace via the `workspaces` key) as hotspots are triaged. + "$schema": "https://unpkg.com/knip@6/schema.jsonc", + "ignore": ["**/dist/**", "**/*.d.ts", "**/bundle/**", "**/*.min.js"], + "ignoreBinaries": ["fluid-build", "shx", "rimraf"], +} diff --git a/ts/tools/scripts/code/lintReport.ts b/ts/tools/scripts/code/lintReport.ts new file mode 100644 index 0000000000..5b3f31bc14 --- /dev/null +++ b/ts/tools/scripts/code/lintReport.ts @@ -0,0 +1,878 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * Lint report + ratchet gate for the TypeAgent ts/ tree. + * + * The engine is ESLint (with typescript-eslint), the de-facto standard, run + * against a throwaway in-memory flat config so the repo needs no committed + * ESLint configuration — the same approach as the sibling complexityReport.ts. + * It harvests a curated, high-signal rule set rather than enforcing a full + * style guide: + * + * Syntactic (always on — fast, no type information required): + * - @typescript-eslint/no-explicit-any (erodes type safety) + * - no-console (allow warn/error) (stray console.log; use `debug`) + * - @typescript-eslint/no-unused-vars (dead code) + * - no-debugger, no-var, prefer-const + * + * Type-aware (opt-in via --type-aware — slower, needs the TS project): + * - @typescript-eslint/no-floating-promises (unawaited promises: the + * single highest-value bug + * class in an async agent system) + * - @typescript-eslint/no-misused-promises + * - @typescript-eslint/no-deprecated (use of @deprecated APIs) + * + * Two modes: + * - Report (default): scan the tree and write CSV/JSON/HTML + a console + * summary (per-rule, per-package, worst files). + * - Ratchet (--ratchet --base ): a stateless CI gate. It lints only the + * files changed since the merge base, on both their HEAD content and their + * content at the base, and fails if the change introduces more violations + * than it removes. The base branch is the baseline (no committed baseline + * file), so violations in touched code can only trend down. Ratchet uses + * the syntactic rules only, so HEAD and the base (materialized to a temp + * dir, where the TS project is unavailable) are compared like-for-like. + * + * Outputs (written to --out-dir, default tools/scripts/code/lint-report): + * - violations.csv : every violation, ranked + * - report.json : structured metrics (per-rule, per-package, per-file) + * - report.html : a self-contained, sortable report + * + * Usage: + * npx tsx tools/scripts/code/lintReport.ts [options] + * npm run code-lint -- [options] + * + * Options: + * --include-tests Include test files (excluded by default). + * --type-aware Also run the type-aware rules (slower; needs the TS + * project). Report mode only. + * --top Number of worst offenders to print / embed (default 25). + * --root Directory to scan (default: the ts/ root). + * --out-dir Output directory (default tools/scripts/code/lint-report). + * --ratchet CI gate: fail if changed files add violations vs --base. + * --base Base git ref for --ratchet (default origin/main). + * --new-file-max With --ratchet, also fail if any NEW file has more than + * violations (-1 = disabled, the default). + * --help Show this help. + */ + +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { execFileSync } from "node:child_process"; +import { fileURLToPath } from "node:url"; +import { ESLint, Linter } from "eslint"; +import tseslint from "typescript-eslint"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// --------------------------------------------------------------------------- +// Configuration +// --------------------------------------------------------------------------- + +const SOURCE_GLOB = "**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}"; + +// node_modules and .git are ignored by ESLint's flat config by default. +const IGNORE_DIRS = [ + "**/dist/**", + "**/build/**", + "**/out/**", + "**/coverage/**", + "**/bin/**", + "**/obj/**", + "**/.turbo/**", + "**/.next/**", + "**/bundle/**", +]; + +const IGNORE_FILES = ["**/*.d.ts", "**/*.min.js", "**/*.bundle.js"]; + +const TEST_GLOBS = [ + "**/test/**", + "**/tests/**", + "**/__tests__/**", + "**/*.spec.*", + "**/*.test.*", +]; + +const SYNTACTIC_RULES: Linter.RulesRecord = { + "no-console": ["warn", { allow: ["warn", "error"] }], + "no-debugger": "warn", + "no-var": "warn", + "prefer-const": "warn", + "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-unused-vars": [ + "warn", + { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + caughtErrors: "none", + }, + ], +}; + +const TYPE_AWARE_RULES: Linter.RulesRecord = { + "@typescript-eslint/no-floating-promises": "warn", + "@typescript-eslint/no-misused-promises": "warn", + "@typescript-eslint/no-deprecated": "warn", +}; + +// --------------------------------------------------------------------------- +// Argument parsing +// --------------------------------------------------------------------------- + +interface Options { + root: string; + outDir: string; + includeTests: boolean; + typeAware: boolean; + top: number; + ratchet: boolean; + base: string; + newFileMax: number; + help: boolean; +} + +function parseIntArg(arg: string, next: string | undefined): number { + if (next === undefined || !/^-?\d+$/.test(next)) { + throw new Error(`${arg} requires an integer value`); + } + return parseInt(next, 10); +} + +function parseArgs(argv: string[]): Options { + const opts: Options = { + root: path.resolve(__dirname, "..", "..", ".."), + outDir: path.join(__dirname, "lint-report"), + includeTests: false, + typeAware: false, + top: 25, + ratchet: false, + base: "origin/main", + newFileMax: -1, + help: false, + }; + + const tokens: string[] = []; + for (const raw of argv) { + const m = /^(--[\w-]+)=(.*)$/.exec(raw); + if (m) { + tokens.push(m[1], m[2]); + } else { + tokens.push(raw); + } + } + + for (let i = 0; i < tokens.length; i++) { + const arg = tokens[i]; + const next = tokens[i + 1]; + switch (arg) { + case "--help": + case "-h": + opts.help = true; + break; + case "--include-tests": + opts.includeTests = true; + break; + case "--type-aware": + opts.typeAware = true; + break; + case "--top": + opts.top = parseIntArg(arg, next); + i++; + break; + case "--ratchet": + opts.ratchet = true; + break; + case "--base": + if (next === undefined) { + throw new Error(`${arg} requires a git ref`); + } + opts.base = next; + i++; + break; + case "--new-file-max": + opts.newFileMax = parseIntArg(arg, next); + i++; + break; + case "--root": + if (next === undefined) { + throw new Error(`${arg} requires a path`); + } + opts.root = path.resolve(next); + i++; + break; + case "--out-dir": + case "--outDir": + if (next === undefined) { + throw new Error(`${arg} requires a path`); + } + opts.outDir = path.resolve(next); + i++; + break; + default: + console.warn(`Ignoring unrecognized argument: ${arg}`); + break; + } + } + + if (opts.top <= 0) { + throw new Error("--top must be greater than 0"); + } + + return opts; +} + +const HELP = `Lint report + ratchet gate for the TypeAgent ts/ tree. + +Usage: + npx tsx tools/scripts/code/lintReport.ts [options] + npm run code-lint -- [options] + +Options: + --include-tests Include test files (excluded by default). + --type-aware Also run type-aware rules (no-floating-promises, + no-misused-promises, no-deprecated). Slower; report only. + --top Number of worst offenders to print / embed (default 25). + --root Directory to scan (default: the ts/ root). + --out-dir Output directory (default: tools/scripts/code/lint-report). + --ratchet CI gate: fail if changed files add violations vs --base. + --base Base git ref for --ratchet (default origin/main). + --new-file-max With --ratchet, fail if any NEW file exceeds + violations (-1 = disabled, the default). + --help Show this help.`; + +// --------------------------------------------------------------------------- +// ESLint plumbing +// --------------------------------------------------------------------------- + +interface Violation { + file: string; // repo-relative, forward-slash separated + line: number; + column: number; + rule: string; + severity: number; // 1 = warn, 2 = error + message: string; +} + +interface LintOutput { + violations: Violation[]; + parseErrorFiles: number; + filesAnalyzed: number; +} + +function buildConfig( + typeAware: boolean, + useIgnores: boolean, + includeTests: boolean, + tsconfigRootDir: string, +): Linter.Config[] { + const rules: Linter.RulesRecord = { + ...SYNTACTIC_RULES, + ...(typeAware ? TYPE_AWARE_RULES : {}), + }; + + const config: Linter.Config[] = []; + if (useIgnores) { + const ignores = [...IGNORE_DIRS, ...IGNORE_FILES]; + if (!includeTests) { + ignores.push(...TEST_GLOBS); + } + config.push({ ignores } as Linter.Config); + } + + const parserOptions: Linter.ParserOptions = typeAware + ? { + projectService: true, + tsconfigRootDir, + ecmaFeatures: { jsx: true }, + } + : { + ecmaVersion: "latest", + sourceType: "module", + ecmaFeatures: { jsx: true }, + }; + + config.push({ + files: [SOURCE_GLOB], + languageOptions: { + parser: tseslint.parser as unknown as Linter.Parser, + parserOptions, + }, + plugins: { + "@typescript-eslint": tseslint.plugin as unknown as ESLint.Plugin, + }, + rules, + }); + return config; +} + +function parseResults( + results: ESLint.LintResult[], + cwd: string, +): { violations: Violation[]; parseErrorFiles: number } { + const violations: Violation[] = []; + let parseErrorFiles = 0; + for (const res of results) { + const rel = path.relative(cwd, res.filePath).split(path.sep).join("/"); + let hadFatal = false; + for (const msg of res.messages) { + if (msg.fatal) { + hadFatal = true; + continue; + } + violations.push({ + file: rel, + line: msg.line ?? 0, + column: msg.column ?? 0, + rule: msg.ruleId ?? "(unknown)", + severity: msg.severity, + message: msg.message, + }); + } + if (hadFatal) { + parseErrorFiles++; + } + } + return { violations, parseErrorFiles }; +} + +async function lint( + cwd: string, + patterns: string[], + opts: { typeAware: boolean; useIgnores: boolean; includeTests: boolean }, + tsconfigRootDir: string, +): Promise { + const eslint = new ESLint({ + cwd, + errorOnUnmatchedPattern: false, + overrideConfigFile: true, + overrideConfig: buildConfig( + opts.typeAware, + opts.useIgnores, + opts.includeTests, + tsconfigRootDir, + ), + }); + const results = await eslint.lintFiles(patterns); + const { violations, parseErrorFiles } = parseResults(results, cwd); + return { violations, parseErrorFiles, filesAnalyzed: results.length }; +} + +// --------------------------------------------------------------------------- +// Rollups +// --------------------------------------------------------------------------- + +function packageKeyOf(rel: string): string { + const f = rel.replace(/\\/g, "/"); + const i = f.indexOf("/src/"); + if (i >= 0) { + return f.slice(0, i); + } + const parts = f.split("/"); + if (parts[0] === "packages" && parts[1] === "agents") { + return parts.slice(0, 3).join("/"); + } + if ( + parts[0] === "packages" || + parts[0] === "examples" || + parts[0] === "extensions" + ) { + return parts.slice(0, 2).join("/"); + } + return parts.slice(0, Math.min(2, parts.length)).join("/"); +} + +function countBy(items: T[], key: (t: T) => string): Map { + const m = new Map(); + for (const it of items) { + const k = key(it); + m.set(k, (m.get(k) ?? 0) + 1); + } + return m; +} + +function sortedEntries( + m: Map, +): { name: string; count: number }[] { + return [...m.entries()] + .map(([name, count]) => ({ name, count })) + .sort((a, b) => b.count - a.count || a.name.localeCompare(b.name)); +} + +// --------------------------------------------------------------------------- +// Formatting helpers +// --------------------------------------------------------------------------- + +function csvEscape(value: string | number): string { + const s = String(value); + if (/[",\r\n]/.test(s)) { + return `"${s.replace(/"/g, '""')}"`; + } + return s; +} + +function htmlEscape(s: string): string { + return s.replace(/&/g, "&").replace(//g, ">"); +} + +function num(n: number): string { + return n.toLocaleString("en-US"); +} + +// --------------------------------------------------------------------------- +// Report writers +// --------------------------------------------------------------------------- + +function writeCsv(outDir: string, violations: Violation[]): string { + const header = [ + "File", + "Line", + "Column", + "Rule", + "Severity", + "Message", + ].join(","); + const rows = violations.map((v) => + [ + v.file, + v.line, + v.column, + v.rule, + v.severity === 2 ? "error" : "warn", + v.message, + ] + .map(csvEscape) + .join(","), + ); + const file = path.join(outDir, "violations.csv"); + fs.writeFileSync(file, [header, ...rows].join("\n") + "\n", "utf8"); + return file; +} + +function writeJson( + outDir: string, + opts: Options, + out: LintOutput, + perRule: { name: string; count: number }[], + perPackage: { name: string; count: number }[], + perFile: { name: string; count: number }[], +): string { + const payload = { + generatedAt: new Date().toISOString(), + root: opts.root, + includeTests: opts.includeTests, + typeAware: opts.typeAware, + totals: { + violations: out.violations.length, + filesAnalyzed: out.filesAnalyzed, + filesWithViolations: perFile.length, + parseErrorFiles: out.parseErrorFiles, + }, + perRule, + perPackage, + perFile, + }; + const file = path.join(outDir, "report.json"); + fs.writeFileSync(file, JSON.stringify(payload, null, 2) + "\n", "utf8"); + return file; +} + +function writeHtml( + outDir: string, + opts: Options, + out: LintOutput, + perRule: { name: string; count: number }[], + perPackage: { name: string; count: number }[], + perFile: { name: string; count: number }[], +): string { + const row2 = (a: string, b: number) => + `${htmlEscape(a)}${num(b)}`; + + const ruleRows = perRule.map((r) => row2(r.name, r.count)).join("\n"); + const pkgRows = perPackage + .slice(0, 60) + .map((r) => row2(r.name, r.count)) + .join("\n"); + const fileRows = perFile + .slice(0, Math.max(opts.top, 100)) + .map((r) => row2(r.name, r.count)) + .join("\n"); + + const html = ` + +TypeAgent lint report + +

TypeAgent lint report

+
engine: ESLint + typescript-eslint · generated ${htmlEscape(new Date().toISOString())} · + ${opts.typeAware ? "syntactic + type-aware" : "syntactic"} · + tests ${opts.includeTests ? "included" : "excluded"}
+ +
+
${num(out.violations.length)}
violations
+
${num(perFile.length)}
files with violations
+
${num(out.filesAnalyzed)}
files analyzed
+
${num(perRule.length)}
rules triggered
+
+ +

Violations by rule

+ +${ruleRows || ''}
RuleCount
None
+ +

Violations by package

+ +${pkgRows || ''}
PackageCount
None
+ +

Worst files

+ +${fileRows || ''}
FileCount
None
+ + +`; + const file = path.join(outDir, "report.html"); + fs.writeFileSync(file, html, "utf8"); + return file; +} + +// --------------------------------------------------------------------------- +// Report mode +// --------------------------------------------------------------------------- + +async function runReport(opts: Options): Promise { + const started = Date.now(); + if (opts.typeAware) { + console.log( + "Type-aware rules enabled — this uses the TS project service and is slower.", + ); + } + const out = await lint( + opts.root, + [SOURCE_GLOB], + { + typeAware: opts.typeAware, + useIgnores: true, + includeTests: opts.includeTests, + }, + opts.root, + ); + + const perRule = sortedEntries(countBy(out.violations, (v) => v.rule)); + const perPackage = sortedEntries( + countBy(out.violations, (v) => packageKeyOf(v.file)), + ); + const perFile = sortedEntries(countBy(out.violations, (v) => v.file)); + + fs.mkdirSync(opts.outDir, { recursive: true }); + const csv = writeCsv(opts.outDir, out.violations); + const json = writeJson( + opts.outDir, + opts, + out, + perRule, + perPackage, + perFile, + ); + const html = writeHtml( + opts.outDir, + opts, + out, + perRule, + perPackage, + perFile, + ); + + const elapsed = ((Date.now() - started) / 1000).toFixed(1); + console.log(""); + console.log("Lint report (engine: ESLint + typescript-eslint)"); + console.log( + `Analyzed ${num(out.filesAnalyzed)} files | elapsed ${elapsed}s | ` + + `${opts.typeAware ? "syntactic + type-aware" : "syntactic"}`, + ); + console.log(""); + console.log( + `Violations: ${num(out.violations.length)} in ${num(perFile.length)} files` + + (out.parseErrorFiles + ? ` | parse errors in ${num(out.parseErrorFiles)} files` + : ""), + ); + console.log(""); + console.log("By rule:"); + for (const r of perRule) { + console.log(` ${String(r.count).padStart(6)} ${r.name}`); + } + console.log(""); + console.log(`Top ${opts.top} packages:`); + for (const p of perPackage.slice(0, opts.top)) { + console.log(` ${String(p.count).padStart(6)} ${p.name}`); + } + console.log(""); + console.log("Reports written to:"); + for (const f of [csv, json, html]) { + console.log( + ` ${path.relative(opts.root, f).split(path.sep).join("/")}`, + ); + } + return 0; +} + +// --------------------------------------------------------------------------- +// Ratchet mode (CI gate) +// --------------------------------------------------------------------------- + +const SOURCE_EXT_RE = /\.[cm]?[jt]sx?$/; +const IGNORE_PATH_RE = + /(^|\/)(node_modules|dist|build|out|coverage|bin|obj|\.turbo|\.next|bundle)\//; +const GENERATED_FILE_RE = /(\.d\.ts|\.min\.js|\.bundle\.js)$/; +const TEST_PATH_RE = + /(^|\/)(test|tests|__tests__)\/|\.(spec|test)\.[cm]?[jt]sx?$/; + +function isReportableSource(relPath: string, includeTests: boolean): boolean { + if (!SOURCE_EXT_RE.test(relPath)) { + return false; + } + if (IGNORE_PATH_RE.test(relPath) || GENERATED_FILE_RE.test(relPath)) { + return false; + } + if (!includeTests && TEST_PATH_RE.test(relPath)) { + return false; + } + return true; +} + +function git(args: string[], cwd: string): string { + return execFileSync("git", args, { + cwd, + encoding: "utf8", + maxBuffer: 128 * 1024 * 1024, + }); +} + +interface DiffEntry { + head: string; + base: string | null; +} + +function parseNameStatus(raw: string): DiffEntry[] { + const entries: DiffEntry[] = []; + for (const line of raw.split(/\r?\n/)) { + if (!line) { + continue; + } + const parts = line.split("\t"); + const status = parts[0]; + if (status.startsWith("R") || status.startsWith("C")) { + entries.push({ base: parts[1], head: parts[2] }); + } else if (status === "A") { + entries.push({ base: null, head: parts[1] }); + } else if (status === "D") { + continue; + } else { + entries.push({ base: parts[1], head: parts[1] }); + } + } + return entries; +} + +async function runRatchet(opts: Options): Promise { + let repoRoot: string; + let mergeBase: string; + try { + repoRoot = git(["rev-parse", "--show-toplevel"], opts.root).trim(); + mergeBase = git(["merge-base", opts.base, "HEAD"], opts.root).trim(); + } catch { + console.error( + `Ratchet: could not resolve base ref "${opts.base}" via git. ` + + "Pass --base (e.g. origin/main) and ensure it is fetched.", + ); + return 2; + } + + const entries = parseNameStatus( + git(["diff", "--name-status", "-M", mergeBase, "HEAD"], opts.root), + ).filter((e) => { + if (!isReportableSource(e.head, opts.includeTests)) { + return false; + } + const relToRoot = path.relative( + opts.root, + path.resolve(repoRoot, e.head), + ); + return !relToRoot.startsWith("..") && !path.isAbsolute(relToRoot); + }); + + if (entries.length === 0) { + console.log("Ratchet: no changed source files to check. OK."); + return 0; + } + + const lintOpts = { + typeAware: false, + useIgnores: false, + includeTests: opts.includeTests, + }; + + // HEAD side. + const headPaths = entries + .map((e) => path.resolve(repoRoot, e.head)) + .filter((p) => fs.existsSync(p)); + const head = headPaths.length + ? await lint(repoRoot, headPaths, lintOpts, opts.root) + : { violations: [], parseErrorFiles: 0, filesAnalyzed: 0 }; + + // BASE side: materialize each changed file's base content into a temp dir. + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "lint-base-")); + let base: LintOutput = { + violations: [], + parseErrorFiles: 0, + filesAnalyzed: 0, + }; + try { + const basePaths: string[] = []; + for (const e of entries) { + if (!e.base || !isReportableSource(e.base, opts.includeTests)) { + continue; + } + let content: string; + try { + content = git(["show", `${mergeBase}:${e.base}`], repoRoot); + } catch { + continue; + } + const dest = path.join(tmp, e.base); + fs.mkdirSync(path.dirname(dest), { recursive: true }); + fs.writeFileSync(dest, content, "utf8"); + basePaths.push(dest); + } + if (basePaths.length) { + base = await lint(tmp, basePaths, lintOpts, opts.root); + } + } finally { + fs.rmSync(tmp, { recursive: true, force: true }); + } + + // Compare per-rule and totals. + const headTotal = head.violations.length; + const baseTotal = base.violations.length; + const headByRule = countBy(head.violations, (v) => v.rule); + const baseByRule = countBy(base.violations, (v) => v.rule); + + console.log( + `Ratchet: ${entries.length} changed source file(s) | ` + + `violations base ${baseTotal} -> head ${headTotal}`, + ); + + const worsened: string[] = []; + for (const rule of new Set([...headByRule.keys(), ...baseByRule.keys()])) { + const h = headByRule.get(rule) ?? 0; + const b = baseByRule.get(rule) ?? 0; + if (h > b) { + worsened.push(` ${rule}: ${b} -> ${h} (+${h - b})`); + } + } + + // New-file cap. + const newFileFailures: string[] = []; + if (opts.newFileMax >= 0) { + const headByFile = countBy(head.violations, (v) => + path + .relative(repoRoot, path.resolve(repoRoot, v.file)) + .split(path.sep) + .join("/"), + ); + for (const e of entries) { + if (e.base !== null) { + continue; // not a new file + } + const rel = e.head.replace(/\\/g, "/"); + const count = headByFile.get(rel) ?? 0; + if (count > opts.newFileMax) { + newFileFailures.push(` ${rel}: ${count} > ${opts.newFileMax}`); + } + } + } + + let failed = false; + if (headTotal > baseTotal) { + failed = true; + console.error( + `\nRatchet FAILED: changed files add ${headTotal - baseTotal} ` + + "violation(s) versus the base. Rules that worsened:", + ); + worsened.forEach((w) => console.error(w)); + } + if (newFileFailures.length) { + failed = true; + console.error( + `\nRatchet FAILED: new file(s) exceed --new-file-max ${opts.newFileMax}:`, + ); + newFileFailures.forEach((w) => console.error(w)); + } + + if (failed) { + console.error( + "\nRun `npm run code-lint` and fix the flagged lines in the files this PR changes.", + ); + return 1; + } + console.log("Ratchet OK: changed files do not add lint violations."); + return 0; +} + +// --------------------------------------------------------------------------- +// Main +// --------------------------------------------------------------------------- + +async function main(): Promise { + let opts: Options; + try { + opts = parseArgs(process.argv.slice(2)); + } catch (e) { + console.error((e as Error).message); + process.exitCode = 2; + return; + } + + if (opts.help) { + console.log(HELP); + return; + } + + const code = opts.ratchet ? await runRatchet(opts) : await runReport(opts); + process.exitCode = code; +} + +main().catch((e) => { + console.error(e); + process.exitCode = 1; +}); From e0223f766e61c08e033e3a788404a710361f6733 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Fri, 3 Jul 2026 13:51:49 -0700 Subject: [PATCH 06/23] added code quality build checks (ratchet-down) --- .github/workflows/build-ts.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/.github/workflows/build-ts.yml b/.github/workflows/build-ts.yml index a90df10b97..3728366800 100644 --- a/.github/workflows/build-ts.yml +++ b/.github/workflows/build-ts.yml @@ -102,6 +102,38 @@ jobs: npm run code-complexity -- --ratchet --base "origin/${{ github.base_ref }}" \ --cyclomatic 25 --cognitive 30 \ --new-file-cyclomatic 25 --new-file-cognitive 30 + # Lint ratchet (PRs only): the files this PR changes may not add ESLint + # violations (no-explicit-any, no-console, no-unused-vars, ...) versus the + # base branch. Syntactic rules only, so it is fast; the count can only + # trend down. Run `npm run code-lint` locally to see the full report. + - name: Lint ratchet + if: ${{ github.event_name == 'pull_request' && steps.filter.outputs.ts != 'false' }} + working-directory: ts + shell: bash + run: | + git fetch --no-tags origin "${{ github.base_ref }}" + npm run code-lint -- --ratchet --base "origin/${{ github.base_ref }}" + # Circular-dependency ratchet (PRs only): fail if the change introduces a + # runtime import cycle not already present at the base. Builds the cycle + # set for HEAD and for the merge base (via a throwaway git worktree), so + # this step is heavier than the others (madge runs twice). + - name: Circular dependency ratchet + if: ${{ github.event_name == 'pull_request' && steps.filter.outputs.ts != 'false' }} + working-directory: ts + shell: bash + run: | + git fetch --no-tags origin "${{ github.base_ref }}" + npm run code-circular -- --ratchet --base "origin/${{ github.base_ref }}" + # Test-debt gate (PRs only): zero tolerance for focused tests + # (.only/fit/fdescribe) and no newly skipped tests (.skip/xit/xdescribe) + # in changed files. A small, fixable problem -> a hard gate, not a ratchet. + - name: Test debt gate + if: ${{ github.event_name == 'pull_request' && steps.filter.outputs.ts != 'false' }} + working-directory: ts + shell: bash + run: | + git fetch --no-tags origin "${{ github.base_ref }}" + npm run code-debt -- --gate --base "origin/${{ github.base_ref }}" - name: Restore better-sqlite3 for Node.js if: ${{ github.event_name != 'pull_request' || steps.filter.outputs.ts != 'false' }} working-directory: ts From 4abd2591bb79ae7ac465b91d54e30e2d50067e8d Mon Sep 17 00:00:00 2001 From: robgruen Date: Fri, 3 Jul 2026 13:57:05 -0700 Subject: [PATCH 07/23] Potential fix for pull request finding 'CodeQL / Incomplete HTML attribute sanitization' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- ts/tools/scripts/code/complexityReport.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ts/tools/scripts/code/complexityReport.ts b/ts/tools/scripts/code/complexityReport.ts index faea351d89..352fb889c1 100644 --- a/ts/tools/scripts/code/complexityReport.ts +++ b/ts/tools/scripts/code/complexityReport.ts @@ -477,7 +477,12 @@ function csvEscape(value: string | number): string { } function htmlEscape(s: string): string { - return s.replace(/&/g, "&").replace(//g, ">"); + return s + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); } /** Format an integer with thousands separators for human-readable output. */ From 589c30db1422a278da1e4a83fdff66d5894b21d4 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Fri, 3 Jul 2026 14:10:04 -0700 Subject: [PATCH 08/23] refactored --- ts/tools/scripts/code/consistencyReport.ts | 169 ++++++++++++--------- 1 file changed, 99 insertions(+), 70 deletions(-) diff --git a/ts/tools/scripts/code/consistencyReport.ts b/ts/tools/scripts/code/consistencyReport.ts index 8bce6b72c6..e8ee5e969e 100644 --- a/ts/tools/scripts/code/consistencyReport.ts +++ b/ts/tools/scripts/code/consistencyReport.ts @@ -323,12 +323,10 @@ interface AnalysisResult { elapsedMs: number; } -function analyze(opts: Options): AnalysisResult { - const started = Date.now(); - +function collectFiles(root: string, includeTests: boolean): string[] { const files: string[] = []; for (const sub of SCAN_SUBDIRS) { - const abs = path.join(opts.root, sub); + const abs = path.join(root, sub); if (!fs.existsSync(abs)) { continue; } @@ -336,85 +334,89 @@ function analyze(opts: Options): AnalysisResult { if (!isCodeFile(full)) { continue; } - const rel = path - .relative(opts.root, full) - .split(path.sep) - .join("/"); - if (!opts.includeTests && isTestFile(rel)) { - continue; + const rel = path.relative(root, full).split(path.sep).join("/"); + if (includeTests || !isTestFile(rel)) { + files.push(rel); } - files.push(rel); } } + return files; +} - // name -> packageKey -> set(files) - const exportMap = new Map>>(); - const envByFile: EnvFile[] = []; - - for (const rel of files) { - let content: string; - try { - content = fs.readFileSync(path.join(opts.root, rel), "utf8"); - } catch { - continue; - } - const pkg = packageKeyOf(rel); - - // Check 1: exported symbol names. - for (const re of EXPORT_RES) { - re.lastIndex = 0; - let m: RegExpExecArray | null; - while ((m = re.exec(content)) !== null) { - const name = m[1]; - if (EXPORT_DENYLIST.has(name)) { - continue; - } - let byPkg = exportMap.get(name); - if (!byPkg) { - byPkg = new Map(); - exportMap.set(name, byPkg); - } - let set = byPkg.get(pkg); - if (!set) { - set = new Set(); - byPkg.set(pkg, set); - } - set.add(rel); +/** Record every top-level exported name in `content` under exportMap[name][pkg]. */ +function scanExports( + content: string, + pkg: string, + rel: string, + exportMap: Map>>, +): void { + for (const re of EXPORT_RES) { + re.lastIndex = 0; + let m: RegExpExecArray | null; + while ((m = re.exec(content)) !== null) { + const name = m[1]; + if (EXPORT_DENYLIST.has(name)) { + continue; } - } - - // Check 2: direct process.env access (production packages only). - if (rel.startsWith(ENV_SCAN_PREFIX) && !ENV_ALLOWED(rel)) { - const matches = content.match(/process\.env\b/g); - if (matches && matches.length > 0) { - envByFile.push({ file: rel, pkg, refs: matches.length }); + let byPkg = exportMap.get(name); + if (!byPkg) { + byPkg = new Map(); + exportMap.set(name, byPkg); } + const set = byPkg.get(pkg) ?? new Set(); + set.add(rel); + byPkg.set(pkg, set); } } +} - // Duplicate exports: names in >= minPackages different packages. - const duplicateExports: DuplicateExport[] = []; +/** Direct process.env access in a production package (excluding the config readers). */ +function scanEnv( + content: string, + rel: string, + pkg: string, +): EnvFile | undefined { + if (!rel.startsWith(ENV_SCAN_PREFIX) || ENV_ALLOWED(rel)) { + return undefined; + } + const matches = content.match(/process\.env\b/g); + return matches && matches.length > 0 + ? { file: rel, pkg, refs: matches.length } + : undefined; +} + +/** Exported names defined in at least `minPackages` different packages. */ +function buildDuplicateExports( + exportMap: Map>>, + minPackages: number, +): DuplicateExport[] { + const dups: DuplicateExport[] = []; for (const [name, byPkg] of exportMap) { - if (byPkg.size >= opts.minPackages) { - const packages = [...byPkg.keys()].sort(); - const fileList: string[] = []; - for (const set of byPkg.values()) { - fileList.push(...set); - } - duplicateExports.push({ - name, - packageCount: byPkg.size, - packages, - files: fileList.sort(), - }); + if (byPkg.size < minPackages) { + continue; + } + const fileList: string[] = []; + for (const set of byPkg.values()) { + fileList.push(...set); } + dups.push({ + name, + packageCount: byPkg.size, + packages: [...byPkg.keys()].sort(), + files: fileList.sort(), + }); } - duplicateExports.sort( + return dups.sort( (a, b) => b.packageCount - a.packageCount || a.name.localeCompare(b.name), ); +} - // process.env rollups. +function buildEnvRollups(envByFile: EnvFile[]): { + envPackages: EnvPackage[]; + envFiles: EnvFile[]; + envTotalRefs: number; +} { const envPkgMap = new Map(); let envTotalRefs = 0; for (const e of envByFile) { @@ -424,10 +426,37 @@ function analyze(opts: Options): AnalysisResult { r.files += 1; envPkgMap.set(e.pkg, r); } - const envPackages = [...envPkgMap.values()].sort((a, b) => b.refs - a.refs); - const envFiles = envByFile.sort((a, b) => b.refs - a.refs); + return { + envPackages: [...envPkgMap.values()].sort((a, b) => b.refs - a.refs), + envFiles: envByFile.sort((a, b) => b.refs - a.refs), + envTotalRefs, + }; +} + +function analyze(opts: Options): AnalysisResult { + const started = Date.now(); + const files = collectFiles(opts.root, opts.includeTests); + + // name -> packageKey -> set(files) + const exportMap = new Map>>(); + const envByFile: EnvFile[] = []; + for (const rel of files) { + let content: string; + try { + content = fs.readFileSync(path.join(opts.root, rel), "utf8"); + } catch { + continue; + } + const pkg = packageKeyOf(rel); + scanExports(content, pkg, rel, exportMap); + const env = scanEnv(content, rel, pkg); + if (env) { + envByFile.push(env); + } + } - // Check 3: agent conformance. + const duplicateExports = buildDuplicateExports(exportMap, opts.minPackages); + const { envPackages, envFiles, envTotalRefs } = buildEnvRollups(envByFile); const agents = analyzeAgents(opts.root); const agentIssues = agents.filter( (a) => !a.hasManifest || !a.hasInstantiate, From db2e89da707913e35235fea55d93942fabaa5122 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Fri, 3 Jul 2026 16:26:26 -0700 Subject: [PATCH 09/23] excluded tool folder --- ts/tools/scripts/code/lintReport.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/ts/tools/scripts/code/lintReport.ts b/ts/tools/scripts/code/lintReport.ts index 5b3f31bc14..204fef4ca0 100644 --- a/ts/tools/scripts/code/lintReport.ts +++ b/ts/tools/scripts/code/lintReport.ts @@ -263,6 +263,14 @@ interface LintOutput { filesAnalyzed: number; } +// CLI scripts and build/analysis tooling under tools/scripts write to stdout via +// console by design, so `no-console` does not apply to them. The leading **/ +// keeps this matching whether ESLint's cwd is ts/ (report mode) or the repo root +// (ratchet mode). +const CONSOLE_ALLOWED_GLOBS = [ + "**/tools/scripts/**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}", +]; + function buildConfig( typeAware: boolean, useIgnores: boolean, @@ -306,6 +314,11 @@ function buildConfig( }, rules, }); + // CLI/build scripts legitimately use console for their output. + config.push({ + files: CONSOLE_ALLOWED_GLOBS, + rules: { "no-console": "off" }, + } as Linter.Config); return config; } From 2e104715a039fcdeea86e71c35ae58352682015c Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Fri, 3 Jul 2026 21:02:29 -0700 Subject: [PATCH 10/23] added --fix --- ts/tools/scripts/code/lintReport.ts | 111 +++++++++++++++++++++++++++- 1 file changed, 109 insertions(+), 2 deletions(-) diff --git a/ts/tools/scripts/code/lintReport.ts b/ts/tools/scripts/code/lintReport.ts index 204fef4ca0..895adbd99c 100644 --- a/ts/tools/scripts/code/lintReport.ts +++ b/ts/tools/scripts/code/lintReport.ts @@ -85,6 +85,7 @@ const IGNORE_DIRS = [ "**/.turbo/**", "**/.next/**", "**/bundle/**", + "**/webview-dist/**", ]; const IGNORE_FILES = ["**/*.d.ts", "**/*.min.js", "**/*.bundle.js"]; @@ -101,7 +102,7 @@ const SYNTACTIC_RULES: Linter.RulesRecord = { "no-console": ["warn", { allow: ["warn", "error"] }], "no-debugger": "warn", "no-var": "warn", - "prefer-const": "warn", + "prefer-const": ["warn", { ignoreReadBeforeAssign: true }], "@typescript-eslint/no-explicit-any": "warn", "@typescript-eslint/no-unused-vars": [ "warn", @@ -119,6 +120,18 @@ const TYPE_AWARE_RULES: Linter.RulesRecord = { "@typescript-eslint/no-deprecated": "warn", }; +// Auto-fixable rules applied by --fix. Kept deliberately narrow: both are +// mechanical, semantics-preserving fixes, so a bulk fix is safe to land. +const FIX_RULES: Linter.RulesRecord = { + "no-var": "warn", + "prefer-const": ["warn", { ignoreReadBeforeAssign: true }], +}; + +// Rules that must stay at zero in changed files (trivially auto-fixable, so +// there is never a reason to introduce one). The ratchet fails if a changed +// file contains any of these, independent of the net count. +const ZERO_TOLERANCE_RULES = ["no-var", "prefer-const"]; + // --------------------------------------------------------------------------- // Argument parsing // --------------------------------------------------------------------------- @@ -132,6 +145,7 @@ interface Options { ratchet: boolean; base: string; newFileMax: number; + fix: boolean; help: boolean; } @@ -152,6 +166,7 @@ function parseArgs(argv: string[]): Options { ratchet: false, base: "origin/main", newFileMax: -1, + fix: false, help: false, }; @@ -179,6 +194,9 @@ function parseArgs(argv: string[]): Options { case "--type-aware": opts.typeAware = true; break; + case "--fix": + opts.fix = true; + break; case "--top": opts.top = parseIntArg(arg, next); i++; @@ -235,6 +253,8 @@ Options: --include-tests Include test files (excluded by default). --type-aware Also run type-aware rules (no-floating-promises, no-misused-promises, no-deprecated). Slower; report only. + --fix Apply the auto-fixable rules (no-var, prefer-const) to the + tree in place. Review + rebuild before committing. --top Number of worst offenders to print / embed (default 25). --root Directory to scan (default: the ts/ root). --out-dir Output directory (default: tools/scripts/code/lint-report). @@ -665,7 +685,7 @@ async function runReport(opts: Options): Promise { const SOURCE_EXT_RE = /\.[cm]?[jt]sx?$/; const IGNORE_PATH_RE = - /(^|\/)(node_modules|dist|build|out|coverage|bin|obj|\.turbo|\.next|bundle)\//; + /(^|\/)(node_modules|dist|build|out|coverage|bin|obj|\.turbo|\.next|bundle|webview-dist)\//; const GENERATED_FILE_RE = /(\.d\.ts|\.min\.js|\.bundle\.js)$/; const TEST_PATH_RE = /(^|\/)(test|tests|__tests__)\/|\.(spec|test)\.[cm]?[jt]sx?$/; @@ -852,6 +872,22 @@ async function runRatchet(opts: Options): Promise { newFileFailures.forEach((w) => console.error(w)); } + // Zero-tolerance rules: must not appear in changed files at all. + const zeroToleranceFailures = ZERO_TOLERANCE_RULES.map((rule) => ({ + rule, + h: headByRule.get(rule) ?? 0, + })) + .filter((x) => x.h > 0) + .map((x) => ` ${x.rule}: ${x.h}`); + if (zeroToleranceFailures.length) { + failed = true; + console.error( + "\nRatchet FAILED: zero-tolerance rules present in changed files " + + "(auto-fixable — run `npm run code-lint -- --fix`):", + ); + zeroToleranceFailures.forEach((w) => console.error(w)); + } + if (failed) { console.error( "\nRun `npm run code-lint` and fix the flagged lines in the files this PR changes.", @@ -862,6 +898,72 @@ async function runRatchet(opts: Options): Promise { return 0; } +// --------------------------------------------------------------------------- +// Fix mode (apply the mechanical, auto-fixable rules in place) +// --------------------------------------------------------------------------- + +function buildFixConfig( + useIgnores: boolean, + includeTests: boolean, +): Linter.Config[] { + const config: Linter.Config[] = []; + if (useIgnores) { + const ignores = [...IGNORE_DIRS, ...IGNORE_FILES]; + if (!includeTests) { + ignores.push(...TEST_GLOBS); + } + config.push({ ignores } as Linter.Config); + } + config.push({ + files: [SOURCE_GLOB], + languageOptions: { + parser: tseslint.parser as unknown as Linter.Parser, + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + ecmaFeatures: { jsx: true }, + }, + }, + rules: FIX_RULES, + }); + return config; +} + +async function runFix(opts: Options): Promise { + const started = Date.now(); + const eslint = new ESLint({ + cwd: opts.root, + errorOnUnmatchedPattern: false, + overrideConfigFile: true, + overrideConfig: buildFixConfig(true, opts.includeTests), + fix: true, + }); + const results = await eslint.lintFiles([SOURCE_GLOB]); + await ESLint.outputFixes(results); + + let fixedFiles = 0; + let unfixable = 0; + for (const r of results) { + if (r.output !== undefined) { + fixedFiles++; + } + unfixable += r.messages.length; + } + + console.log(""); + console.log(`Lint auto-fix (rules: ${Object.keys(FIX_RULES).join(", ")})`); + console.log( + `Rewrote ${num(fixedFiles)} file(s) in ${((Date.now() - started) / 1000).toFixed(1)}s.`, + ); + if (unfixable > 0) { + console.log( + `${num(unfixable)} occurrence(s) could not be auto-fixed; run \`npm run code-lint\` to see them.`, + ); + } + console.log("Review the diff, run prettier, and rebuild before committing."); + return 0; +} + // --------------------------------------------------------------------------- // Main // --------------------------------------------------------------------------- @@ -881,6 +983,11 @@ async function main(): Promise { return; } + if (opts.fix) { + process.exitCode = await runFix(opts); + return; + } + const code = opts.ratchet ? await runRatchet(opts) : await runReport(opts); process.exitCode = code; } From 1f59cc7f4e8f7f18e78f850f7c4576f7b098d407 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 6 Jul 2026 09:39:04 -0700 Subject: [PATCH 11/23] enforced const/var/let usage --- .../measure/src/measureActionHandler.ts | 8 +-- ts/examples/chat/src/codeChat/codeChat.ts | 5 +- ts/examples/chat/src/codeChat/codeMemory.ts | 6 +- ts/examples/chat/src/codeChat/common.ts | 2 +- ts/examples/chat/src/common.ts | 2 +- .../chat/src/knowledgeProc/emailMemory.ts | 18 ++--- .../chat/src/knowledgeProc/imageMemory.ts | 6 +- .../chat/src/knowledgeProc/importer.ts | 4 +- .../knowledgeProc/knowledgeProcessorMemory.ts | 44 ++++++------ .../knowledgeProc/knowledgeProcessorWriter.ts | 4 +- .../chat/src/knowledgeProc/podcastMemory.ts | 26 +++---- .../chat/src/memory/knowproAzSearch.ts | 16 ++--- ts/examples/chat/src/memory/knowproCommon.ts | 6 +- .../chat/src/memory/knowproDataFrame.ts | 8 +-- ts/examples/chat/src/memory/knowproDoc.ts | 8 +-- ts/examples/chat/src/memory/knowproEmail.ts | 4 +- ts/examples/chat/src/memory/knowproImage.ts | 4 +- .../chat/src/memory/knowproKnowledge.ts | 6 +- ts/examples/chat/src/memory/knowproMemory.ts | 16 ++--- ts/examples/chat/src/memory/knowproPodcast.ts | 9 ++- ts/examples/chat/src/memory/knowproPrinter.ts | 12 ++-- ts/examples/chat/src/memory/knowproTest.ts | 10 +-- ts/examples/docuProc/src/pdfChunker.ts | 4 +- ts/examples/docuProc/src/pdfFileDocumenter.ts | 4 +- ts/examples/docuProc/src/pdfImporter.ts | 2 +- .../docuProc/src/pdfQNAInteractiveApp.ts | 6 +- ts/examples/docuProc/src/srag/importPdf.ts | 18 ++--- ts/examples/docuProc/src/srag/kpPrinter.ts | 8 ++- .../docuProc/src/srag/pdfKnowproMem.ts | 14 ++-- ts/examples/mcpMemory/src/main.ts | 2 +- ts/examples/mcpMemory/src/mcp.ts | 2 +- ts/examples/mcpMemory/src/memoryServer.ts | 4 +- .../src/elastic/simplifiedTextIndex.ts | 6 +- .../src/elastic/storageClient.ts | 4 +- .../src/sqlite/keyValueTable.ts | 2 +- .../memoryProviders/src/sqlite/textTable.ts | 24 +++---- .../microdataExtract/src/commands/filter.ts | 2 +- .../microdataExtract/src/commands/merge.ts | 2 +- .../microdataExtract/src/commands/scrape.ts | 2 +- ts/examples/playground/src/main.ts | 16 ++--- ts/examples/schemaStudio/src/main.ts | 2 +- .../schemaStudio/src/schemaCommands.ts | 10 +-- ts/examples/schemaStudio/src/urlCommands.ts | 4 +- ts/examples/searchActionTest/src/main.ts | 2 +- .../vscodeSchemaGen/src/embedActions.ts | 6 +- ts/examples/vscodeSchemaGen/src/genStats.ts | 8 +-- ts/examples/vscodeSchemaGen/src/schemaGen.ts | 31 ++++----- .../vscodeSchemaGen/src/vscodeSchemaGenApp.ts | 4 +- ts/examples/websiteAliases/src/batchWorker.ts | 8 +-- .../src/searchBackedPhraseGeneration.ts | 4 +- .../websiteAliases/src/topN_BatchWorker.ts | 8 +-- ts/examples/websiteAliases/src/topNsites.ts | 2 +- ts/examples/whisperClient/src/index.ts | 2 +- .../src/generation/grammarGenerator.ts | 2 +- .../generation/schemaToGrammarGenerator.ts | 2 +- .../actionGrammar/src/nfaInterpreter.ts | 2 +- ts/packages/actionSchema/src/schemaParser.ts | 44 ++++++------ ts/packages/agentRpc/src/client.ts | 4 +- .../src/schemaToGrammarGenerator.ts | 2 +- .../graphUtils/src/calendarClient.ts | 16 ++--- .../graphUtils/src/calendarDataIndex.ts | 2 +- .../agentUtils/graphUtils/src/graphClient.ts | 6 +- .../agentUtils/graphUtils/src/mailClient.ts | 4 +- .../src/androidMobileActionHandler.ts | 12 ++-- .../src/agent/browserActionHandler.mts | 2 +- .../src/agent/discovery/translator.mts | 16 ++--- .../knowledge/actions/analyticsActions.mts | 2 +- .../progress/extractionProgressManager.mts | 2 +- .../browser/src/agent/searchWebMemories.mts | 2 +- .../browser/src/agent/websiteMemory.mts | 6 +- .../src/common/crossContextHtmlReducer.ts | 8 +-- .../src/extension/contentScript/domUtils.ts | 6 +- .../contentScript/elementInteraction.ts | 12 ++-- .../extension/contentScript/eventHandlers.ts | 4 +- .../contentScript/recording/capture.ts | 2 +- .../src/extension/serviceWorker/capture.ts | 2 +- .../serviceWorker/contentDownloader.ts | 4 +- .../extension/shared/unifiedHtmlProcessor.ts | 2 +- .../browser/src/extension/sites/paleobiodb.ts | 12 ++-- .../src/extension/uiEventsDispatcher.ts | 2 +- .../extension/views/entityGraphVisualizer.ts | 4 +- .../extension/views/topicGraphVisualizer.ts | 4 +- .../calendar/src/calendarActionHandlerV1.ts | 59 ++++++++-------- .../calendar/src/calendarActionHandlerV2.ts | 67 +++++++++---------- .../calendar/src/calendarActionHandlerV3.ts | 2 +- .../calendar/src/calendarQueryHelper.ts | 2 +- .../agents/chat/src/chatResponseHandler.ts | 2 +- ts/packages/agents/desktop/src/connector.ts | 4 +- .../agents/email/src/emailActionHandler.ts | 12 ++-- .../github-cli/src/github-cliActionHandler.ts | 2 +- .../greeting/src/greetingCommandHandler.ts | 14 ++-- .../agents/image/src/imageActionHandler.ts | 2 +- .../agents/list/src/listActionHandler.ts | 2 +- .../src/agent/markdownActionHandler.ts | 5 +- .../agents/markdown/src/agent/translator.ts | 4 +- .../markdown/src/view/site/slash-commands.ts | 2 +- .../montage/src/agent/montageActionHandler.ts | 2 +- ts/packages/agents/montage/src/route/route.ts | 2 +- .../src/discovery/discoveryHandler.ts | 2 +- .../agents/photo/src/photoActionHandler.ts | 2 +- ts/packages/agents/player/src/client.ts | 5 +- ts/packages/agents/player/src/playback.ts | 2 +- ts/packages/agents/player/src/search.ts | 2 +- ts/packages/agents/player/src/userData.ts | 2 +- .../src/agent/localPlayerHandlers.ts | 2 +- .../agents/powershell/src/actionHandler.mts | 2 +- .../settings/src/settingsCommandHandler.ts | 2 +- .../agents/taskflow/src/actionHandler.mts | 2 +- .../agents/timer/src/timerActionHandler.ts | 3 +- .../agents/video/src/videoActionHandler.ts | 4 +- ts/packages/aiclient/src/bing.ts | 4 +- ts/packages/aiclient/src/openai.ts | 6 +- ts/packages/aiclient/src/restClient.ts | 2 +- .../storageProviders/awsStorageProvider.ts | 2 +- .../storageProviders/azureStorageProvider.ts | 2 +- ts/packages/api/src/typeAgentServer.ts | 2 +- ts/packages/api/src/webDispatcher.ts | 2 +- ts/packages/api/src/webServer.ts | 8 +-- ts/packages/api/src/webSocketServer.ts | 2 +- .../azure-ai-foundry/src/urlResolver.ts | 10 +-- .../cache/src/explanation/v5/explanationV5.ts | 2 +- ts/packages/chat-ui/src/partialCompletion.ts | 1 - ts/packages/chat-ui/src/setContent.ts | 2 +- ts/packages/chat-ui/src/templateEditor.ts | 2 +- ts/packages/cli/src/commands/run/request.ts | 2 +- ts/packages/cli/src/debugInterceptor.ts | 2 +- ts/packages/cli/src/enhancedConsole.ts | 6 +- ts/packages/coda/src/extension.ts | 15 +++-- .../coda/src/handleEditorCodeActions.ts | 4 +- ts/packages/coda/src/handleVSCodeActions.ts | 12 ++-- .../coda/src/handleWorkBenchActions.ts | 2 +- ts/packages/codeProcessor/src/code.ts | 6 +- ts/packages/codeProcessor/src/codeReviewer.ts | 2 +- .../commandExecutor/src/commandServer.ts | 1 - .../demo/driver/tools/azure-tts.mts | 2 +- ts/packages/copilot-plugin/src/mcp/server.ts | 1 - .../defaultAgentProvider/src/utils/config.ts | 4 +- .../dispatcher/src/command/parameters.ts | 2 +- .../dispatcher/src/context/chatHistory.ts | 2 +- .../src/context/chatHistoryPrompt.ts | 2 +- .../dispatcher/src/context/memoryPrinter.ts | 6 +- .../system/handlers/randomCommandHandler.ts | 10 +-- .../system/handlers/tokenCommandHandler.ts | 2 +- .../dispatcher/src/helpers/completion/trie.ts | 8 +-- .../dispatcher/src/search/internet.ts | 6 +- .../dispatcher/src/search/search.ts | 4 +- .../src/translation/multipleActionSchema.ts | 2 +- .../src/validation/collisionDetector.mts | 4 +- .../interactiveApp/src/InteractiveIo.ts | 10 +-- .../interactiveApp/src/interactiveApp.ts | 22 +++--- ts/packages/interactiveApp/src/terminalUI.ts | 2 +- ts/packages/knowPro/src/answerGenerator.ts | 18 ++--- ts/packages/knowPro/src/collections.ts | 14 ++-- ts/packages/knowPro/src/common.ts | 12 ++-- ts/packages/knowPro/src/compileLib.ts | 2 +- ts/packages/knowPro/src/conversationIndex.ts | 6 +- .../knowPro/src/dataFrame/dataFrame.ts | 12 ++-- .../src/dataFrame/dataFrameConversation.ts | 4 +- .../src/dataFrame/dataFrameLangSearch.ts | 4 +- .../knowPro/src/dataFrame/dataFrameQuery.ts | 6 +- ts/packages/knowPro/src/fuzzyIndex.ts | 7 +- ts/packages/knowPro/src/knowledge.ts | 8 +-- ts/packages/knowPro/src/knowledgeLib.ts | 4 +- ts/packages/knowPro/src/knowledgeMerge.ts | 20 +++--- ts/packages/knowPro/src/knowledgeMergeEx.ts | 14 ++-- ts/packages/knowPro/src/message.ts | 2 +- ts/packages/knowPro/src/messageIndex.ts | 4 +- ts/packages/knowPro/src/query.ts | 12 ++-- ts/packages/knowPro/src/relatedTermsIndex.ts | 4 +- ts/packages/knowPro/src/search.ts | 12 ++-- ts/packages/knowPro/src/searchLang.ts | 20 +++--- ts/packages/knowPro/src/searchLib.ts | 5 +- .../knowPro/src/searchQueryTranslator.ts | 4 +- ts/packages/knowPro/src/secondaryIndexes.ts | 4 +- ts/packages/knowPro/src/serialization.ts | 8 +-- ts/packages/knowPro/src/storage.ts | 6 +- ts/packages/knowPro/src/textLocationIndex.ts | 2 +- ts/packages/knowProTest/src/answerTest.ts | 6 +- ts/packages/knowProTest/src/common.ts | 4 +- .../knowProTest/src/knowproCommands.ts | 6 +- ts/packages/knowProTest/src/logging.ts | 2 +- ts/packages/knowProTest/src/models.ts | 2 +- ts/packages/knowProTest/src/searchTest.ts | 10 +-- .../src/conversation/answerGenerator.ts | 2 +- .../src/conversation/conversation.ts | 4 +- .../src/conversation/conversationManager.ts | 6 +- .../src/conversation/entities.ts | 6 +- .../src/conversation/hierarchicalTopics.ts | 2 +- .../src/conversation/interactive.ts | 2 +- .../src/conversation/knowledge.ts | 6 +- .../src/conversation/knowledgeResponseLoss.ts | 14 ++-- .../src/conversation/searchProcessor.ts | 2 +- .../src/conversation/testData.ts | 8 +-- .../src/conversation/topics.ts | 14 ++-- .../src/conversation/transcript.ts | 12 ++-- .../knowledgeProcessor/src/images/image.ts | 2 +- .../knowledgeProcessor/src/modelCache.ts | 6 +- .../knowledgeProcessor/src/setOperations.ts | 24 ++++--- .../knowledgeProcessor/src/temporal.ts | 2 +- .../knowledgeProcessor/src/textChunker.ts | 4 +- .../knowledgeProcessor/src/textIndex.ts | 12 ++-- .../src/route/visualizationNotifier.ts | 16 ++--- .../src/site/visualizations/tangledTree.ts | 28 ++++---- ts/packages/kp/src/keywordExtractor.ts | 2 +- .../conversation/src/conversationMemory.ts | 6 +- .../memory/conversation/src/docImport.ts | 4 +- .../memory/conversation/src/emailImport.ts | 2 +- .../memory/conversation/src/emailMessage.ts | 2 +- ts/packages/memory/conversation/src/memory.ts | 4 +- .../memory/conversation/src/podcastImport.ts | 2 +- .../memory/conversation/src/termParser.ts | 2 +- .../memory/conversation/src/transcript.ts | 4 +- ts/packages/memory/image/src/importImages.ts | 2 +- .../memory/storage/src/azSearch/azQuery.ts | 6 +- .../storage/src/azSearch/azSearchIndex.ts | 2 +- .../src/azSearch/azSemanticRefIndex.ts | 12 ++-- ts/packages/memory/storage/src/fileSystem.ts | 5 +- .../storage/src/sqlite/sqliteCollection.ts | 2 +- .../storage/src/sqlite/sqliteDataFrame.ts | 24 +++---- .../memory/website/src/importWebsites.ts | 2 +- ts/packages/memory/website/src/websiteMeta.ts | 2 +- ts/packages/schemaAuthor/src/schema.ts | 2 +- ts/packages/shell/src/main/chatServer.ts | 2 +- ts/packages/shell/src/main/demo.ts | 6 +- ts/packages/shell/src/main/index.ts | 2 +- ts/packages/shell/src/main/instance.ts | 3 +- ts/packages/shell/src/preload/webView.ts | 10 +-- .../src/renderer/src/localWhisperClient.ts | 2 +- .../shell/src/renderer/src/webSocketAPI.ts | 6 +- ts/packages/studio-service/src/main.ts | 1 - ts/packages/telemetry/src/stopWatch.ts | 4 +- ts/packages/testLib/src/parse.ts | 4 +- ts/packages/textPro/src/html.ts | 10 +-- ts/packages/textPro/src/markdown.ts | 4 +- .../src/runtime/studioRuntimeCore.ts | 2 +- ts/packages/typeagent-studio/src/extension.ts | 2 +- .../src/studioServiceLauncher.ts | 2 - ts/packages/typeagent/src/arrayAsync.ts | 8 +-- ts/packages/typeagent/src/lib/array.ts | 4 +- ts/packages/typeagent/src/lib/linkedList.ts | 4 +- ts/packages/typeagent/src/lib/strings.ts | 2 +- ts/packages/typeagent/src/objStream.ts | 12 ++-- ts/packages/typeagent/src/prompt.ts | 6 +- .../typeagent/src/storage/embeddingFS.ts | 4 +- .../typeagent/src/storage/objectFolder.ts | 6 +- .../typeagent/src/storage/objectPage.ts | 2 +- .../typeagent/src/storage/workQueue.ts | 10 +-- ts/packages/typeagent/src/textProcessing.ts | 18 ++--- .../typeagent/src/vector/embeddings.ts | 2 +- .../typeagent/src/vector/semanticList.ts | 4 +- .../typeagent/src/vector/semanticMap.ts | 2 +- .../utils/commonUtils/src/base64Browser.ts | 2 +- .../utils/typechatUtils/src/datetimeHelper.ts | 14 ++-- .../src/incrementalJsonParser.ts | 4 +- .../utils/typechatUtils/src/jsonTranslator.ts | 2 +- .../utils/typechatUtils/src/location.ts | 6 +- ts/packages/vscode-chat/src/extension.ts | 2 +- .../vscode-shell/src/agentServerBridge.ts | 2 +- ts/tools/docsAutogen/src/paths.ts | 4 +- ts/tools/scripts/code/lintReport.ts | 4 +- ts/tools/scripts/elevate.js | 2 +- ts/tools/scripts/generate-selfhost-config.mjs | 2 +- ts/tools/scripts/lib/pimClient.mjs | 4 +- 263 files changed, 870 insertions(+), 851 deletions(-) diff --git a/ts/examples/agentExamples/measure/src/measureActionHandler.ts b/ts/examples/agentExamples/measure/src/measureActionHandler.ts index 8f9df872c3..4b88fff52e 100644 --- a/ts/examples/agentExamples/measure/src/measureActionHandler.ts +++ b/ts/examples/agentExamples/measure/src/measureActionHandler.ts @@ -154,8 +154,8 @@ export function createMeasurementAgent(): AppAgent { } function measurementsToCsv(measures: Measurement[]) { - let csvHeader = "Type, When, Value, Units\n"; - let rows = measures.map((m) => measurementToCsv(m)).join("\n"); + const csvHeader = "Type, When, Value, Units\n"; + const rows = measures.map((m) => measurementToCsv(m)).join("\n"); return csvHeader + rows; } @@ -211,8 +211,8 @@ async function createMeasurementTable( function get(filter: MeasurementFilter): Measurement[] { const sql = filterToSql(filter); const stmt = db.prepare(sql); - let rows = stmt.all(); - let measurements: Measurement[] = []; + const rows = stmt.all(); + const measurements: Measurement[] = []; for (const row of rows) { const mRow: MeasurementRow = row as MeasurementRow; measurements.push({ diff --git a/ts/examples/chat/src/codeChat/codeChat.ts b/ts/examples/chat/src/codeChat/codeChat.ts index 7128c65fce..854ac8e855 100644 --- a/ts/examples/chat/src/codeChat/codeChat.ts +++ b/ts/examples/chat/src/codeChat/codeChat.ts @@ -315,7 +315,10 @@ export async function runCodeChat(): Promise { let name = tsCode.getStatementName(statement); if (name) { name = `${moduleName}.${name}`; - let code = tsCode.getTextOfStatement(sourceFile, statement); + const code = tsCode.getTextOfStatement( + sourceFile, + statement, + ); const docs = await codeIndex.put( { code, language: "typescript" }, name, diff --git a/ts/examples/chat/src/codeChat/codeMemory.ts b/ts/examples/chat/src/codeChat/codeMemory.ts index 65ffd84960..3304c9983d 100644 --- a/ts/examples/chat/src/codeChat/codeMemory.ts +++ b/ts/examples/chat/src/codeChat/codeMemory.ts @@ -134,7 +134,7 @@ export async function runCodeMemoryCommands(): Promise { const concurrency = namedArgs.concurrency; printer.writeLine(`${functions.length} function/s found.`); for (let i = 0; i < functions.length; i += concurrency) { - let slice = functions.slice(i, i + concurrency); + const slice = functions.slice(i, i + concurrency); if (slice.length === 0) { break; } @@ -219,7 +219,7 @@ export async function runCodeMemoryCommands(): Promise { } const concurrency = namedArgs.concurrency; for (let i = 0; i < statements.length; i += concurrency) { - let slice = statements.slice(i, i + concurrency); + const slice = statements.slice(i, i + concurrency); if (slice.length === 0) { break; } @@ -361,7 +361,7 @@ export async function runCodeMemoryCommands(): Promise { statement: ts.Statement, fullPath: string, ): Promise { - let name = tsCode.getStatementName(statement); + const name = tsCode.getStatementName(statement); if (!name) { return undefined; } diff --git a/ts/examples/chat/src/codeChat/common.ts b/ts/examples/chat/src/codeChat/common.ts index 632d201443..2e5c23abb0 100644 --- a/ts/examples/chat/src/codeChat/common.ts +++ b/ts/examples/chat/src/codeChat/common.ts @@ -39,7 +39,7 @@ export async function loadTypescriptCode( const sourceText = await readAllLines(sourcePath); // Load lines of code const sourceCode = await tsCode.loadSourceFile(sourcePath); moduleDir = getModuleDirPath(sourceFile, moduleDir); - let modules = moduleDir + const modules = moduleDir ? await tsCode.loadImports(sourceCode, moduleDir) : undefined; diff --git a/ts/examples/chat/src/common.ts b/ts/examples/chat/src/common.ts index 51a592334f..90e90ada3c 100644 --- a/ts/examples/chat/src/common.ts +++ b/ts/examples/chat/src/common.ts @@ -345,7 +345,7 @@ export async function findConversationThread( predicate: (threadDef: conversation.ConversationThread) => boolean, ): Promise { const threadIndex = await cm.conversation.getThreadIndex(); - let allThreads: NameValue[] = + const allThreads: NameValue[] = await asyncArray.toArray(threadIndex.entries()); for (const threadEntry of allThreads) { if (predicate(threadEntry.value)) { diff --git a/ts/examples/chat/src/knowledgeProc/emailMemory.ts b/ts/examples/chat/src/knowledgeProc/emailMemory.ts index e94400d962..0cf5a1f7dd 100644 --- a/ts/examples/chat/src/knowledgeProc/emailMemory.ts +++ b/ts/examples/chat/src/knowledgeProc/emailMemory.ts @@ -137,9 +137,9 @@ export function createEmailCommands( commands.importEmail.metadata = importEmailDef(); async function importEmail(args: string[], io: InteractiveIo) { const namedArgs = parseNamedArguments(args, importEmailDef()); - let sourcePath: string = namedArgs.sourcePath; - let isDir = isDirectoryPath(sourcePath); - let isJson = sourcePath.endsWith("json"); + const sourcePath: string = namedArgs.sourcePath; + const isDir = isDirectoryPath(sourcePath); + const isJson = sourcePath.endsWith("json"); if (isDir) { await indexEmails(namedArgs, sourcePath); } else if (isJson) { @@ -171,7 +171,7 @@ export function createEmailCommands( ): Promise { const meta = emailConvertMsgDef(); const namedArgs = parseNamedArguments(args, meta); - let sourcePath: string = namedArgs.sourcePath; + const sourcePath: string = namedArgs.sourcePath; context.printer.writeInColor(chalk.cyan, "Converting message files"); const result = await convertMsgFiles(sourcePath, io); if (!result.success) { @@ -280,13 +280,13 @@ export function createEmailCommands( commands.emailActionItems.metadata = emailActionItemsDef(); async function emailActionItems(args: string[]): Promise { const namedArgs = parseNamedArguments(args, emailActionItemsDef()); - let tenses: string[] = []; + const tenses: string[] = []; if (namedArgs.period) { tenses.push(namedArgs.period); } else { tenses.push("past", "present", "future"); } - for (let tense of tenses) { + for (const tense of tenses) { const actionItems = await emailActionItemsFromConversation( context.emailMemory, namedArgs.name, @@ -406,7 +406,7 @@ export function createEmailCommands( commands.emailExportToKp.metadata = emailExportToKpDef(); async function emailExportKp(args: string[]) { const namedArgs = parseNamedArguments(args, emailExportToKpDef()); - let emailIndexPath = memoryNameToIndexPath( + const emailIndexPath = memoryNameToIndexPath( "/data/testChat/knowpro/email", namedArgs.name, ); @@ -475,7 +475,7 @@ export function createEmailCommands( async (filePath, index, total) => { context.printer.writeProgress(index + 1, total); - let email = await knowLib.email.loadEmailFile(filePath); + const email = await knowLib.email.loadEmailFile(filePath); const emailLength = email!.body.length; context.printer.writeLine( `${email!.sourcePath}\n${emailLength} chars`, @@ -546,7 +546,7 @@ export function createEmailCommands( rootPath: string, fileNames: string[], ): Promise { - let timestampedNames: dateTime.Timestamped[] = []; + const timestampedNames: dateTime.Timestamped[] = []; for (const fileName of fileNames) { const filePath = path.join(rootPath, fileName); const email = await knowLib.email.loadEmailFile(filePath); diff --git a/ts/examples/chat/src/knowledgeProc/imageMemory.ts b/ts/examples/chat/src/knowledgeProc/imageMemory.ts index f71376ebaa..fa67dfdf20 100644 --- a/ts/examples/chat/src/knowledgeProc/imageMemory.ts +++ b/ts/examples/chat/src/knowledgeProc/imageMemory.ts @@ -122,8 +122,8 @@ export function createImageCommands( async function importImage(args: string[], io: InteractiveIo) { const namedArgs = parseNamedArguments(args, importImageDef()); - let sourcePath: string = namedArgs.sourcePath; - let isDir = isDirectoryPath(sourcePath); + const sourcePath: string = namedArgs.sourcePath; + const isDir = isDirectoryPath(sourcePath); if (!fs.existsSync(sourcePath)) { console.log( @@ -169,7 +169,7 @@ export function createImageCommands( async function buildImageCountHistogram(args: string[], io: InteractiveIo) { const namedArgs = parseNamedArguments(args, importImageDef()); - let sourcePath: string = namedArgs.sourcePath; + const sourcePath: string = namedArgs.sourcePath; knowLib.image.buildImageCountHistogram(sourcePath); } diff --git a/ts/examples/chat/src/knowledgeProc/importer.ts b/ts/examples/chat/src/knowledgeProc/importer.ts index d72a49c2e9..820f7e0496 100644 --- a/ts/examples/chat/src/knowledgeProc/importer.ts +++ b/ts/examples/chat/src/knowledgeProc/importer.ts @@ -23,7 +23,7 @@ export function* timestampBlocks( minMsOffset, maxMsOffset, ); - for (let value of blocks) { + for (const value of blocks) { const timestamp = timestampGenerator.next().value; yield { timestamp, @@ -67,7 +67,7 @@ export async function runImportQueue( let attempts = 1; const timing = new StopWatch(); const maxAttempts = 2; - let stats = await loadIndexingStats(statsFilePath, clean); + const stats = await loadIndexingStats(statsFilePath, clean); let grandTotal = stats.itemStats.length; while (attempts <= maxAttempts) { const successCount = await queue.drain( diff --git a/ts/examples/chat/src/knowledgeProc/knowledgeProcessorMemory.ts b/ts/examples/chat/src/knowledgeProc/knowledgeProcessorMemory.ts index b1daabc143..3ad52c2dcf 100644 --- a/ts/examples/chat/src/knowledgeProc/knowledgeProcessorMemory.ts +++ b/ts/examples/chat/src/knowledgeProc/knowledgeProcessorMemory.ts @@ -297,7 +297,7 @@ export async function loadConversation( // This creates both (knowledge-processor) and know-pro commands export async function runKnowledgeProcessorCommands(): Promise { - let context = await createKnowledgeProcessorContext(captureTokenStats); + const context = await createKnowledgeProcessorContext(captureTokenStats); let showTokenStats = false; let printer = context.printer; const commands: Record = { @@ -403,13 +403,13 @@ export async function runKnowledgeProcessorCommands(): Promise { // Split full transcript text into paragraphs const blocks = knowLib.conversation.splitTranscriptIntoBlocks(chatText); const lengthMinutes = namedArgs.lengthMinutes ?? 60; - let lengthMs = lengthMinutes * 60 * 60; + const lengthMs = lengthMinutes * 60 * 60; const baseLineMs = lengthMs / blocks.length; // Average, these many minutes per block const chatDate = new Date(2023, 4, 1, 9); if (!addToCurrent) { await context.conversation.messages.clear(); } - for (let tBlock of timestampBlocks( + for (const tBlock of timestampBlocks( blocks, chatDate, baseLineMs, @@ -463,7 +463,7 @@ export async function runKnowledgeProcessorCommands(): Promise { ); const baseLineMs = 1000 * 60 * 10; // 10 minutes await context.conversation.messages.clear(); - for (let tBlock of timestampBlocks( + for (const tBlock of timestampBlocks( blocks, new Date(1900, 0), baseLineMs, @@ -629,8 +629,7 @@ export async function runKnowledgeProcessorCommands(): Promise { if (record.speaker) { msg = `${record.speaker}: ${msg}`; } - let knowledge: knowLib.conversation.KnowledgeResponse | undefined; - knowledge = await extractor.extract(msg).catch((err) => { + const knowledge = await extractor.extract(msg).catch((err) => { printer.writeError(`Error extracting knowledge: ${err}`); return undefined; }); @@ -750,10 +749,9 @@ export async function runKnowledgeProcessorCommands(): Promise { let aveSimpleLoss = 0.0; clock.start(); for (const record of inData) { - let msg = record.message; + const msg = record.message; const refKnowledge = record.knowledge; - let knowledge: knowLib.conversation.KnowledgeResponse | undefined; - knowledge = await extractor.extract(msg).catch((err) => { + const knowledge = await extractor.extract(msg).catch((err) => { printer.writeError(`Error extracting knowledge: ${err}`); return undefined; }); @@ -832,7 +830,7 @@ export async function runKnowledgeProcessorCommands(): Promise { }, ); - let [messages, msgCount] = await getMessagesAndCount( + const [messages, msgCount] = await getMessagesAndCount( context.conversationManager, namedArgs.maxTurns, ); @@ -881,11 +879,11 @@ export async function runKnowledgeProcessorCommands(): Promise { const cm = context.conversationManager; await cm.clear(false); - let [messages, msgCount] = await getMessagesAndCount( + const [messages, msgCount] = await getMessagesAndCount( cm, namedArgs.maxTurns, ); - let messageIndex = await context.conversation.getMessageIndex(); + const messageIndex = await context.conversation.getMessageIndex(); cm.topicMerger.settings.mergeWindowSize = namedArgs.mergeWindow; let count = 0; @@ -1019,7 +1017,7 @@ export async function runKnowledgeProcessorCommands(): Promise { commands.entities.metadata = entitiesDef(); async function entities(args: string[], io: InteractiveIo) { const namedArgs = parseNamedArguments(args, entitiesDef()); - let query = namedArgs.name ?? namedArgs.type ?? namedArgs.facet; + const query = namedArgs.name ?? namedArgs.type ?? namedArgs.facet; if (query) { const isMultipart = namedArgs.facet || (namedArgs.name && namedArgs.type); @@ -1281,7 +1279,7 @@ export async function runKnowledgeProcessorCommands(): Promise { commands.rag.metadata = ragDef(); async function rag(args: string[]) { const namedArgs = parseNamedArguments(args, ragDef()); - let prevStats = beginCountingTokens(); + const prevStats = beginCountingTokens(); try { const options: SearchOptions = { maxMatches: namedArgs.maxMessages, @@ -1413,12 +1411,12 @@ export async function runKnowledgeProcessorCommands(): Promise { commands.copyConversation.metadata = copyConversationDef(); async function copyConversation(args: string[]) { const namedArgs = parseNamedArguments(args, copyConversationDef()); - let srcPath = namedArgs.srcPath; - let srcName = getFileName(srcPath); - let srcDir = path.dirname(srcPath); - let destPath = namedArgs.destPath; - let destName = getFileName(destPath); - let destDir = path.dirname(destPath); + const srcPath = namedArgs.srcPath; + const srcName = getFileName(srcPath); + const srcDir = path.dirname(srcPath); + const destPath = namedArgs.destPath; + const destName = getFileName(destPath); + const destDir = path.dirname(destPath); if (namedArgs.clean) { await removeDir(destPath); await ensureDir(destPath); @@ -1450,7 +1448,7 @@ export async function runKnowledgeProcessorCommands(): Promise { const messageInfo = messages[i]!; const messageId = messageInfo.name; const messageText = messageInfo.value.value; - let newMessage: knowLib.conversation.ConversationMessage = { + const newMessage: knowLib.conversation.ConversationMessage = { text: messageText, }; if (namedArgs.timestamps) { @@ -1483,7 +1481,7 @@ export async function runKnowledgeProcessorCommands(): Promise { ): Promise { const maxMatches = namedArgs.maxMatches; const minScore = namedArgs.minScore; - let query = namedArgs.query.trim(); + const query = namedArgs.query.trim(); if (!query || query.length === 0) { return undefined; } @@ -1523,7 +1521,7 @@ export async function runKnowledgeProcessorCommands(): Promise { } //searcher.answers.settings.chunking.enable = true; - let prevStats = beginCountingTokens(); + const prevStats = beginCountingTokens(); const clock = new StopWatch(); clock.start(); try { diff --git a/ts/examples/chat/src/knowledgeProc/knowledgeProcessorWriter.ts b/ts/examples/chat/src/knowledgeProc/knowledgeProcessorWriter.ts index 8b0a73f025..ad211b8e00 100644 --- a/ts/examples/chat/src/knowledgeProc/knowledgeProcessorWriter.ts +++ b/ts/examples/chat/src/knowledgeProc/knowledgeProcessorWriter.ts @@ -63,7 +63,7 @@ export class KnowledgeProcessorWriter extends MemoryConsoleWriter { total?: number | undefined, ): void { label = label ? label + " " : ""; - let text = + const text = total !== undefined && total > 0 ? `[${label}(${batch.startAt + 1} to ${batch.startAt + batch.value.length}) / ${total}]` : `[${label}${batch.startAt + 1} to ${batch.startAt + batch.value.length}]`; @@ -341,7 +341,7 @@ export class KnowledgeProcessorWriter extends MemoryConsoleWriter { fallback: boolean = false, ) { if (response.answer) { - let answer = response.answer; + const answer = response.answer; this.writeInColor(chalk.green, answer); } else if (response.whyNoAnswer) { const answer = fallback diff --git a/ts/examples/chat/src/knowledgeProc/podcastMemory.ts b/ts/examples/chat/src/knowledgeProc/podcastMemory.ts index a060903bb9..f5c1b2801b 100644 --- a/ts/examples/chat/src/knowledgeProc/podcastMemory.ts +++ b/ts/examples/chat/src/knowledgeProc/podcastMemory.ts @@ -149,7 +149,7 @@ export function createPodcastCommands( commands.importPodcast.metadata = importPodcastDef(); async function importPodcast(args: string[]): Promise { const namedArgs = parseNamedArguments(args, importPodcastDef()); - let sourcePath: string = namedArgs.sourcePath; + const sourcePath: string = namedArgs.sourcePath; if (!isFilePath(sourcePath)) { context.printer.writeError(`${sourcePath} is not a file`); return; @@ -205,7 +205,7 @@ export function createPodcastCommands( range.startDate, range.stopDate, ); - let threadRange: kp.TextRange = { + const threadRange: kp.TextRange = { start: { messageOrdinal: podcastMessages.length, }, @@ -277,7 +277,7 @@ export function createPodcastCommands( commands.podcastExport.metadata = podcastExportDef(); async function podcastExport(args: string[]) { const namedArgs = parseNamedArguments(args, podcastExportDef()); - let destDirPath = + const destDirPath = namedArgs.destDirPath ?? path.join(context.storePath, "conversations/export"); @@ -285,7 +285,7 @@ export function createPodcastCommands( await removeDir(destDirPath); } await ensureDir(destDirPath); - let name = namedArgs.name ?? context.podcastMemory.conversationName; + const name = namedArgs.name ?? context.podcastMemory.conversationName; const destCm = await conversation.createConversationManager( {}, name, @@ -314,7 +314,7 @@ export function createPodcastCommands( context.podcastMemory, messageIds, )) { - let newMessage: conversation.ConversationMessage = + const newMessage: conversation.ConversationMessage = conversationMessageFromEmailText(message.value.value); newMessage.timestamp = message.timestamp; newMessage.knowledge = knowledge; @@ -454,7 +454,7 @@ export function createPodcastCommands( // Same as @entities but for the podcast index. async function podcastEntities(args: string[]): Promise { const namedArgs = parseNamedArguments(args, podcastEntitiesDef()); - let query = namedArgs.name ?? namedArgs.type ?? namedArgs.facet; + const query = namedArgs.name ?? namedArgs.type ?? namedArgs.facet; if (query) { const isMultipart = namedArgs.facet || (namedArgs.name && namedArgs.type); @@ -486,7 +486,7 @@ export function createPodcastCommands( const entityArray = await asyncArray.toArray(index.entities()); const entities = [...conversation.toCompositeEntities(entityArray)]; entities.sort((x, y) => x.name.localeCompare(y.name)); - let printer = context.printer; + const printer = context.printer; printer.writeCompositeEntities(entities); } @@ -523,7 +523,7 @@ export function createPodcastCommands( ): Promise { const maxMatches = namedArgs.maxMatches; const minScore = namedArgs.minScore; - let query = namedArgs.query.trim(); + const query = namedArgs.query.trim(); if (!query || query.length === 0) { return undefined; } @@ -683,8 +683,8 @@ export function createPodcastCommands( commands.podcastIndex.metadata = podcastIndexDef(); async function podcastIndex(args: string[] | NamedArgs) { const namedArgs = parseNamedArguments(args, podcastIndexDef()); - let sourcePath: string = namedArgs.sourcePath; - let isDir = isDirectoryPath(sourcePath); + const sourcePath: string = namedArgs.sourcePath; + const isDir = isDirectoryPath(sourcePath); if (isDir) { await indexTurns( sourcePath, @@ -983,8 +983,8 @@ export function createPodcastCommands( function podcastMessageFromEmailText(text: string) { let messageText = ""; let speaker: string | undefined; - let lines = knowLib.splitIntoLines(text); - for (let line of lines) { + const lines = knowLib.splitIntoLines(text); + for (const line of lines) { if (line.startsWith("From: ")) { speaker = line.replace("From: ", ""); } else if (line.startsWith(`"From: `)) { @@ -1005,7 +1005,7 @@ function conversationMessageFromEmailText( ): conversation.ConversationMessage { let sender: string | undefined; let recipients: string[] | undefined; - let lines = knowLib.splitIntoLines(text); + const lines = knowLib.splitIntoLines(text); for (let line of lines) { if (line.startsWith("From: ")) { sender = line.replace("From: ", ""); diff --git a/ts/examples/chat/src/memory/knowproAzSearch.ts b/ts/examples/chat/src/memory/knowproAzSearch.ts index 0e6253a166..75d5a4da75 100644 --- a/ts/examples/chat/src/memory/knowproAzSearch.ts +++ b/ts/examples/chat/src/memory/knowproAzSearch.ts @@ -64,12 +64,12 @@ export async function createKnowproAzureCommands( commands.azSearch.metadata = azSearchDef(); async function azSearch(args: string[]) { const commandDef = azSearchDef(); - let [termArgs, namedArgs] = parseFreeAndNamedArguments( + const [termArgs, namedArgs] = parseFreeAndNamedArguments( args, commandDef, ); const semanticRefIndex = getSemanticRefIndex(); - let query: string = namedArgs.query; + const query: string = namedArgs.query; let queryTerms: string | kp.SearchTermGroup; if (query) { // User provided a raw lucene query.. but with single quotes @@ -111,12 +111,12 @@ export async function createKnowproAzureCommands( commands.azScope.metadata = azScopeDef(); async function azScope(args: string[]) { const commandDef = azScopeDef(); - let [termArgs, namedArgs] = parseFreeAndNamedArguments( + const [termArgs, namedArgs] = parseFreeAndNamedArguments( args, commandDef, ); const semanticRefIndex = getSemanticRefIndex(); - let queryTerms: kp.SearchTermGroup = createSearchGroup( + const queryTerms: kp.SearchTermGroup = createSearchGroup( termArgs, namedArgs, commandDef, @@ -202,7 +202,7 @@ export async function createKnowproAzureCommands( async function azTerms(args: string[]) { const namedArgs = parseNamedArguments(args, azTermsDef()); const termIndex = getTermsIndex(); - let term: string = namedArgs.term; + const term: string = namedArgs.term; const embedding = await generateEmbedding( kpContext.similarityModel, term, @@ -242,7 +242,7 @@ export async function createKnowproAzureCommands( } const namedArgs = parseNamedArguments(args, ingestTermEmbeddingsDef()); const batchSize: number = namedArgs.batchSize; - let termData = relatedTermsIndex.serialize(); + const termData = relatedTermsIndex.serialize(); const progress = new ProgressBar( context.printer, Math.ceil(termData.textItems.length / batchSize), @@ -251,7 +251,7 @@ export async function createKnowproAzureCommands( termData.textItems, batchSize, )) { - let termBatch: ms.azSearch.TermDoc[] = []; + const termBatch: ms.azSearch.TermDoc[] = []; for (let i = 0; i < textBatch.value.length; ++i) { termBatch.push({ termId: (i + textBatch.startAt).toString(), @@ -325,7 +325,7 @@ export async function createKnowproAzureCommands( for (const result of indexingResults) { switch (result.statusCode) { default: - let errorMessage = + const errorMessage = result.errorMessage ?? result.statusCode.toString(); context.printer.writeError( `FAILED: ordinal: ${result.key} [${errorMessage}]`, diff --git a/ts/examples/chat/src/memory/knowproCommon.ts b/ts/examples/chat/src/memory/knowproCommon.ts index 57a5107e58..bad7a1a8c9 100644 --- a/ts/examples/chat/src/memory/knowproCommon.ts +++ b/ts/examples/chat/src/memory/knowproCommon.ts @@ -235,7 +235,7 @@ export function whenFilterFromNamedArgs( conversation: kp.IConversation, namedArgs: NamedArgs, ): kp.WhenFilter { - let filter: kp.WhenFilter = { + const filter: kp.WhenFilter = { knowledgeType: namedArgs.ktype, }; const dateRange = kp.getTimeRangeForConversation(conversation!); @@ -274,8 +274,8 @@ export function whenFilterFromNamedArgs( export function dateRangeFromNamedArgs( namedArgs: NamedArgs, ): kp.DateRange | undefined { - let startDate = argToDate(namedArgs.startDate); - let endDate = argToDate(namedArgs.endDate); + const startDate = argToDate(namedArgs.startDate); + const endDate = argToDate(namedArgs.endDate); if (startDate) { return { start: startDate, diff --git a/ts/examples/chat/src/memory/knowproDataFrame.ts b/ts/examples/chat/src/memory/knowproDataFrame.ts index 16667f4355..25942e9ff9 100644 --- a/ts/examples/chat/src/memory/knowproDataFrame.ts +++ b/ts/examples/chat/src/memory/knowproDataFrame.ts @@ -37,7 +37,7 @@ export async function createKnowproDataFrameCommands( const basePath = "/data/testChat/knowpro/restaurants"; const filePath = "/data/testChat/knowpro/restaurants/all/split_011.json"; - let query = "Punjabi restaurant with Rating 3.0 in Eisenhüttenstadt"; + const query = "Punjabi restaurant with Rating 3.0 in Eisenhüttenstadt"; let db: RestaurantDb | undefined; let restaurantIndex: RestaurantIndex | undefined; @@ -63,7 +63,7 @@ export async function createKnowproDataFrameCommands( // // Load some restaurants into a collection // - let numRestaurants = namedArgs.count ?? 16; + const numRestaurants = namedArgs.count ?? 16; const restaurantData: Restaurant[] = await loadThings(dataFramePath); @@ -220,7 +220,7 @@ export async function createKnowproDataFrameCommands( } function ensureIndex(newDb: boolean) { - let filePath = "/data/testChat/knowpro/restaurants/restaurants.db"; + const filePath = "/data/testChat/knowpro/restaurants/restaurants.db"; if (newDb) { db?.close(); db = new RestaurantDb(filePath, newDb); @@ -734,7 +734,7 @@ export class RestaurantIndex function parseRestaurantFacets( restaurant: Restaurant, ): RestaurantFacets | undefined { - let facets: RestaurantFacets = { rating: 3.0 }; + const facets: RestaurantFacets = { rating: 3.0 }; if (restaurant.address) { parseAddressFacets(restaurant.address, facets); } diff --git a/ts/examples/chat/src/memory/knowproDoc.ts b/ts/examples/chat/src/memory/knowproDoc.ts index 2347215a8a..3d50640f62 100644 --- a/ts/examples/chat/src/memory/knowproDoc.ts +++ b/ts/examples/chat/src/memory/knowproDoc.ts @@ -109,7 +109,7 @@ export async function createKnowproDocMemoryCommands( commands.kpDocLoad.metadata = docLoadDef(); async function docLoad(args: string[]): Promise { const namedArgs = parseNamedArguments(args, docLoadDef()); - let memoryFilePath = sourcePathToMemoryIndexPath(namedArgs.filePath); + const memoryFilePath = sourcePathToMemoryIndexPath(namedArgs.filePath); if (!memoryFilePath) { context.printer.writeError("No filepath or name provided"); return; @@ -154,7 +154,7 @@ export async function createKnowproDocMemoryCommands( return; } let html = ""; - let srcUrl = toUrl(filePath); + const srcUrl = toUrl(filePath); if (srcUrl !== undefined) { const htmlResult = await getHtml(srcUrl.href); if (!htmlResult.success) { @@ -169,7 +169,7 @@ export async function createKnowproDocMemoryCommands( } else { html = await readAllText(filePath); } - let markdown = tp.htmlToMarkdown(html, namedArgs.rootTag); + const markdown = tp.htmlToMarkdown(html, namedArgs.rootTag); context.printer.writeLine(markdown); const destPath = namedArgs.destPath @@ -200,7 +200,7 @@ export async function createKnowproDocMemoryCommands( ); } const maxMessages = context.docMemory.messages.length; - let progress = new ProgressBar(context.printer, maxMessages); + const progress = new ProgressBar(context.printer, maxMessages); const eventHandler = createIndexingEventHandler( context.printer, progress, diff --git a/ts/examples/chat/src/memory/knowproEmail.ts b/ts/examples/chat/src/memory/knowproEmail.ts index 01cb9ce807..14c696578d 100644 --- a/ts/examples/chat/src/memory/knowproEmail.ts +++ b/ts/examples/chat/src/memory/knowproEmail.ts @@ -84,7 +84,7 @@ export async function createKnowproEmailCommands( return; } context.printer.writeLine(`Adding ${emailsToAdd.length} messages`); - let progress = new ProgressBar(context.printer, 1); + const progress = new ProgressBar(context.printer, 1); const eventHandler = createIndexingEventHandler( context.printer, progress, @@ -140,7 +140,7 @@ export async function createKnowproEmailCommands( `OrdinalStartAt: ${ordinalStartAt + 1} / ${countToIndex}`, ); - let progress = new ProgressBar(context.printer, countToIndex); + const progress = new ProgressBar(context.printer, countToIndex); const eventHandler = createIndexingEventHandler( context.printer, progress, diff --git a/ts/examples/chat/src/memory/knowproImage.ts b/ts/examples/chat/src/memory/knowproImage.ts index 9b2b4322ea..3d01895fb9 100644 --- a/ts/examples/chat/src/memory/knowproImage.ts +++ b/ts/examples/chat/src/memory/knowproImage.ts @@ -91,7 +91,7 @@ export async function createKnowproImageCommands( return; } - let progress = new ProgressBar(context.printer, 165); + const progress = new ProgressBar(context.printer, 165); context.images = await im.importImages( namedArgs.filePath, namedArgs.cachePath, @@ -214,7 +214,7 @@ export async function createKnowproImageCommands( context.printer.writeLine(); context.printer.writeLine("Building index"); const maxMessages = namedArgs.maxMessages ?? messageCount; - let progress = new ProgressBar(context.printer, maxMessages); + const progress = new ProgressBar(context.printer, maxMessages); const indexResult = await context.images?.buildIndex( createIndexingEventHandler(context.printer, progress, maxMessages), ); diff --git a/ts/examples/chat/src/memory/knowproKnowledge.ts b/ts/examples/chat/src/memory/knowproKnowledge.ts index c9a7385528..26cc269974 100644 --- a/ts/examples/chat/src/memory/knowproKnowledge.ts +++ b/ts/examples/chat/src/memory/knowproKnowledge.ts @@ -127,7 +127,7 @@ export async function createKnowproKnowledgeCommands( progressBar.advance(); context.printer.writeLine(); - let text = textChunks[i]; + const text = textChunks[i]; context.printer.writeLineInColor( chalk.cyan, `${text.length} chars`, @@ -233,7 +233,7 @@ export async function createKnowproKnowledgeCommands( text: string, verify?: (k: kpLib.KnowledgeResponse) => boolean, ): Promise<[kpLib.KnowledgeResponse | undefined, number]> { - let maxAttempts = 2; + const maxAttempts = 2; let attempts = 1; let knowledge: kpLib.KnowledgeResponse | undefined; while (true) { @@ -362,7 +362,7 @@ export class KnowledgeCompiler { } for (const entity of entities) { - let entityGroup = kp.createOrTermGroup(); + const entityGroup = kp.createOrTermGroup(); this.compileEntity(entity, entityGroup); if (entityGroup.terms.length > 0) { termGroup.terms.push(entityGroup); diff --git a/ts/examples/chat/src/memory/knowproMemory.ts b/ts/examples/chat/src/memory/knowproMemory.ts index fe55388269..87dcdef30c 100644 --- a/ts/examples/chat/src/memory/knowproMemory.ts +++ b/ts/examples/chat/src/memory/knowproMemory.ts @@ -209,7 +209,7 @@ export async function createKnowproCommands( if (queryTerms && queryTerms.length > 0) { args = queryTerms; } - let [termArgs, namedArgs] = parseFreeAndNamedArguments( + const [termArgs, namedArgs] = parseFreeAndNamedArguments( args, commandDef, ); @@ -403,7 +403,7 @@ export async function createKnowproCommands( args = queryTerms; } delete commandDef.args!.query; - let [termArgs, namedArgs] = parseFreeAndNamedArguments( + const [termArgs, namedArgs] = parseFreeAndNamedArguments( args, commandDef, ); @@ -556,7 +556,7 @@ export async function createKnowproCommands( conversation.semanticRefs, (sr) => sr.knowledgeType === "entity", ); - let concreteEntities = entityRefs.map( + const concreteEntities = entityRefs.map( (e) => e.knowledge as knowLib.ConcreteEntity, ); context.printer.writeEntities(concreteEntities); @@ -613,7 +613,7 @@ export async function createKnowproCommands( conversation.semanticRefs, (sr) => sr.knowledgeType === "tag", ); - let tags = tagRefs.map((t) => t.knowledge as kp.Tag); + const tags = tagRefs.map((t) => t.knowledge as kp.Tag); if (tags.length > 0) { context.printer.writeHeading("Tags"); context.printer.writeTags(tags); @@ -709,7 +709,7 @@ export async function createKnowproCommands( } } context.printer.writeHeading("Message Abstract"); - let topicMatches = semanticRefsInMessage.get("topic"); + const topicMatches = semanticRefsInMessage.get("topic"); if (topicMatches && topicMatches.length > 0) { const topics = kp.getDistinctTopicMatches( semanticRefs, @@ -723,7 +723,7 @@ export async function createKnowproCommands( context.printer.writeLine(); } - let entityMatches = semanticRefsInMessage.get("entity"); + const entityMatches = semanticRefsInMessage.get("entity"); if (entityMatches && entityMatches.length > 0) { const entities = kp.getDistinctEntityMatches( semanticRefs, @@ -804,7 +804,7 @@ export async function createKnowproCommands( ); return; } - let searchTerm: kp.SearchTerm = { term: { text: namedArgs.term } }; + const searchTerm: kp.SearchTerm = { term: { text: namedArgs.term } }; searchTerm.relatedTerms = await relatedTerms.lookupTerm( searchTerm.term.text, ); @@ -860,7 +860,7 @@ export async function createKnowproCommands( function createAnswerOptions( namedArgs: NamedArgs, ): kp.AnswerContextOptions { - let topK = namedArgs.knowledgeTopK; + const topK = namedArgs.knowledgeTopK; if (topK === undefined) { return {}; } diff --git a/ts/examples/chat/src/memory/knowproPodcast.ts b/ts/examples/chat/src/memory/knowproPodcast.ts index 64e55f642e..9bb1f28af0 100644 --- a/ts/examples/chat/src/memory/knowproPodcast.ts +++ b/ts/examples/chat/src/memory/knowproPodcast.ts @@ -177,7 +177,7 @@ export async function createKnowproPodcastCommands( // Build index context.printer.writeLine(); const maxMessages = namedArgs.maxMessages ?? messageCount; - let originalMessages = context.podcast.messages; + const originalMessages = context.podcast.messages; try { if (maxMessages < messageCount) { context.podcast.messages = @@ -186,7 +186,7 @@ export async function createKnowproPodcastCommands( ); } context.printer.writeLine(`Building Index`); - let progress = new ProgressBar(context.printer, maxMessages); + const progress = new ProgressBar(context.printer, maxMessages); const eventHandler = createIndexingEventHandler( context.printer, progress, @@ -235,7 +235,10 @@ export async function createKnowproPodcastCommands( ...context.podcast!.settings.messageTextIndexSettings, }; settings.embeddingIndexSettings.batchSize = namedArgs.batchSize; - let progress = new ProgressBar(context.printer, namedArgs.maxMessages); + const progress = new ProgressBar( + context.printer, + namedArgs.maxMessages, + ); podcast.secondaryIndexes.messageIndex = new kp.MessageTextIndex( settings, ); diff --git a/ts/examples/chat/src/memory/knowproPrinter.ts b/ts/examples/chat/src/memory/knowproPrinter.ts index e04bb8ee71..225da10898 100644 --- a/ts/examples/chat/src/memory/knowproPrinter.ts +++ b/ts/examples/chat/src/memory/knowproPrinter.ts @@ -103,7 +103,7 @@ export class KnowProPrinter extends MemoryConsoleWriter { `Displaying ${matchesToDisplay.length} matches of total ${messageIndexMatches.length}`, ); for (let i = 0; i < matchesToDisplay.length; ++i) { - let pos = this.sortAsc ? matchesToDisplay.length - (i + 1) : i; + const pos = this.sortAsc ? matchesToDisplay.length - (i + 1) : i; this.writeScoredMessage( pos, matchesToDisplay.length, @@ -203,7 +203,7 @@ export class KnowProPrinter extends MemoryConsoleWriter { public writeTags(tags: kp.Tag[] | undefined) { if (tags && tags.length > 0) { - let tagStrings = kp.mergeTags(tags); + const tagStrings = kp.mergeTags(tags); tagStrings.sort(); this.writeList(tagStrings, { type: "ol" }); } @@ -265,7 +265,7 @@ export class KnowProPrinter extends MemoryConsoleWriter { `Displaying ${matchesToDisplay.length} matches of total ${semanticRefMatches.length}`, ); for (let i = 0; i < matchesToDisplay.length; ++i) { - let pos = this.sortAsc ? matchesToDisplay.length - (i + 1) : i; + const pos = this.sortAsc ? matchesToDisplay.length - (i + 1) : i; this.writeScoredRef( pos, matchesToDisplay.length, @@ -510,7 +510,9 @@ export class KnowProPrinter extends MemoryConsoleWriter { } if (distinctKnowledge) { for (let i = 0; i < distinctKnowledge.length; ++i) { - let pos = this.sortAsc ? distinctKnowledge.length - (i + 1) : i; + const pos = this.sortAsc + ? distinctKnowledge.length - (i + 1) + : i; const knowledge = distinctKnowledge[pos]; this.writeInColor( chalk.green, @@ -700,7 +702,7 @@ export class KnowProPrinter extends MemoryConsoleWriter { function getPodcastParticipants(podcast: cm.Podcast) { const participants = new Set(); - for (let message of podcast.messages) { + for (const message of podcast.messages) { const meta = message.metadata; if (meta.speaker) { participants.add(meta.speaker); diff --git a/ts/examples/chat/src/memory/knowproTest.ts b/ts/examples/chat/src/memory/knowproTest.ts index f985b89ce4..9e1f3130e9 100644 --- a/ts/examples/chat/src/memory/knowproTest.ts +++ b/ts/examples/chat/src/memory/knowproTest.ts @@ -85,12 +85,12 @@ export async function createKnowproTestCommands( commands.kpTestMdParse.metadata = testMdParseDef(); async function testMdParse(args: string[]) { const namedArgs = parseNamedArguments(args, testMdParseDef()); - let filePath = namedArgs.filePath; + const filePath = namedArgs.filePath; if (!filePath) { return; } - let markdown = await readAllText(filePath); - let mdDom = tp.markdownTokenize(markdown); + const markdown = await readAllText(filePath); + const mdDom = tp.markdownTokenize(markdown); //context.printer.writeJsonInColor(chalk.gray, mdDom); const chunkSize = namedArgs.chunkSize; const chunkSizeBuffer = chunkSize + chunkSize * 0.25; @@ -148,7 +148,7 @@ export async function createKnowproTestCommands( if (!filePath) { return; } - let html = await readAllText(filePath); + const html = await readAllText(filePath); let docParts = cm.docPartsFromHtml(html, false, namedArgs.rootTag); for (const part of docParts) { context.printer.writeLine("----------------"); @@ -568,7 +568,7 @@ export async function createKnowproTestCommands( context.printer.writeError(results.message); return; } - let errorResults = results.data.filter( + const errorResults = results.data.filter( (r) => r.error !== undefined && r.error.length > 0, ); if (errorResults.length > 0) { diff --git a/ts/examples/docuProc/src/pdfChunker.ts b/ts/examples/docuProc/src/pdfChunker.ts index 00fa1a51dc..bd736f75c6 100644 --- a/ts/examples/docuProc/src/pdfChunker.ts +++ b/ts/examples/docuProc/src/pdfChunker.ts @@ -63,7 +63,7 @@ export async function loadPdfChunksFromJson( rootDir: string, filenames: string[], ): Promise<(ChunkedFile | ErrorItem)[]> { - let results: (ChunkedFile | ErrorItem)[] = []; + const results: (ChunkedFile | ErrorItem)[] = []; try { const chunkedDocsDir = path.join(rootDir, "chunked-docs"); for (const filename of filenames) { @@ -117,7 +117,7 @@ export async function chunkifyPdfFiles( const absChunkerPath = resolve(chunkerPath); const absFilenames = filenames.map((f) => path.join(__dirname, f)); const outputDir = path.join(rootDir, "chunked-docs"); - let { stdout, stderr } = await execFilePromise( + const { stdout, stderr } = await execFilePromise( "python3", [ "-X", diff --git a/ts/examples/docuProc/src/pdfFileDocumenter.ts b/ts/examples/docuProc/src/pdfFileDocumenter.ts index a77668288b..77e43d6080 100644 --- a/ts/examples/docuProc/src/pdfFileDocumenter.ts +++ b/ts/examples/docuProc/src/pdfFileDocumenter.ts @@ -101,7 +101,7 @@ export function createPdfDocumenter(model: ChatModel): PdfFileDocumenter { // Process each page let pageCount = 0; for (const pageid in pageChunksMap) { - let content: MultimodalPromptContent[] = []; + const content: MultimodalPromptContent[] = []; pageCount++; const { pageRootChunk, pageChunks } = pageChunksMap[pageid]; @@ -205,7 +205,7 @@ The document information should be extracted from the chunk. Every chunk should For each chunk, the summary should contain most five sentences that covers the main points. Also document the root chunk which is for page. The root chunk doesn't have any content and no parentId.`; - let promptSections: PromptSection[] = [ + const promptSections: PromptSection[] = [ { role: "user", content: content }, ]; diff --git a/ts/examples/docuProc/src/pdfImporter.ts b/ts/examples/docuProc/src/pdfImporter.ts index c20df05f1c..731af38a8a 100644 --- a/ts/examples/docuProc/src/pdfImporter.ts +++ b/ts/examples/docuProc/src/pdfImporter.ts @@ -88,7 +88,7 @@ async function importPdfFiles( maxPagesToProcess: number = -1, ): Promise { // Canonicalize filenames. - let filenames = files.map((file) => + const filenames = files.map((file) => fs.existsSync(file) ? fs.realpathSync(file) : file, ); diff --git a/ts/examples/docuProc/src/pdfQNAInteractiveApp.ts b/ts/examples/docuProc/src/pdfQNAInteractiveApp.ts index 21a841091d..44e5c99d23 100644 --- a/ts/examples/docuProc/src/pdfQNAInteractiveApp.ts +++ b/ts/examples/docuProc/src/pdfQNAInteractiveApp.ts @@ -96,8 +96,8 @@ export async function interactiveAppLoop( const model = openai.createJsonChatModel("GPT_4", ["PdfDownloader"]); const pdfDownloader = createPDFDownloadTranslator(model); - let context = await createKnowProContext(captureTokenStats); - let showTokenStats = false; + const context = await createKnowProContext(captureTokenStats); + const showTokenStats = false; let printer = context.printer; await createKnowproCommands(context, handlers); @@ -668,7 +668,7 @@ export async function purgeNormalizedFile( verbose: boolean, ): Promise { // Step 1: Find chunks to remove. - let toDelete: Set = new Set(); + const toDelete: Set = new Set(); for await (const chunk of chunkyIndex.chunkFolder.allObjects()) { if (chunk.fileName === fileName) { toDelete.add(chunk.id); diff --git a/ts/examples/docuProc/src/srag/importPdf.ts b/ts/examples/docuProc/src/srag/importPdf.ts index e1b8e56095..627da34148 100644 --- a/ts/examples/docuProc/src/srag/importPdf.ts +++ b/ts/examples/docuProc/src/srag/importPdf.ts @@ -72,7 +72,7 @@ export async function chunkifyPdfFiles( fs.mkdirSync(CHUNKED_DOCS_DIR, { recursive: true }); } - let { stdout, stderr } = await execFilePromise( + const { stdout, stderr } = await execFilePromise( "python3", [ "-X", @@ -117,7 +117,7 @@ export async function loadPdfChunksFromJson( rootDir: string, filenames: string[], ): Promise<(ChunkedFile | ErrorItem)[]> { - let results: (ChunkedFile | ErrorItem)[] = []; + const results: (ChunkedFile | ErrorItem)[] = []; try { for (const filename of filenames) { const paperId = getPaperIdFromFilename(filename); @@ -202,7 +202,7 @@ export async function importPdf( results = await chunkifyPdfFiles(outputDir, [pdfFilePath]); t1 = Date.now(); } else { - let pdfFileName = getPaperIdFromFilename(pdfFilePath); + const pdfFileName = getPaperIdFromFilename(pdfFilePath); const chunkedJsonFile = path.join( CHUNKED_DOCS_DIR, pdfFileName, @@ -212,7 +212,7 @@ export async function importPdf( t1 = Date.now(); } - let numLines = 0; + const numLines = 0; let numBlobs = 0; let numChunks = 0; let numErrors = 0; @@ -295,7 +295,7 @@ export function processPdfChunks( chunks: Chunk[], ): PdfChunkMessage[] { const fileName = path.basename(pdfFileName); - let chunkMessages: PdfChunkMessage[] = []; + const chunkMessages: PdfChunkMessage[] = []; const pageChunksMap: Record< string, { pageRootChunk: Chunk; pageChunks: Chunk[] } @@ -339,7 +339,7 @@ export function processPdfChunks( : blob.paraHeader; } - let chunkMessageMeta: PdfChunkMessageMeta = + const chunkMessageMeta: PdfChunkMessageMeta = new PdfChunkMessageMeta( fileName, chunk.pageid, @@ -347,7 +347,7 @@ export function processPdfChunks( sectionName, catMetaEntry, ); - let chunkMessage: PdfChunkMessage = new PdfChunkMessage( + const chunkMessage: PdfChunkMessage = new PdfChunkMessage( [], chunkMessageMeta, ); @@ -371,7 +371,7 @@ export async function indexPdfChunks( chunkResults: (ChunkedFile | ErrorItem)[] = [], ): Promise { const pdfCatalog = await loadCatalogWithMeta(); - let pdfChunkMessages: PdfChunkMessage[] = []; + const pdfChunkMessages: PdfChunkMessage[] = []; if (pdfCatalog !== undefined) { const chunkedFiles = chunkResults.filter( @@ -384,7 +384,7 @@ export async function indexPdfChunks( assert(fs.existsSync(pdfFile), `File not found: ${pdfFile}`); const paperId = getPaperIdFromFilename(pdfFile); - let catEntry = pdfCatalog[paperId]; + const catEntry = pdfCatalog[paperId]; if (catEntry !== undefined) { const messages = processPdfChunks( pdfFilePath, diff --git a/ts/examples/docuProc/src/srag/kpPrinter.ts b/ts/examples/docuProc/src/srag/kpPrinter.ts index ecc5a7928a..cef7093ec7 100644 --- a/ts/examples/docuProc/src/srag/kpPrinter.ts +++ b/ts/examples/docuProc/src/srag/kpPrinter.ts @@ -295,7 +295,9 @@ export class KPPrinter extends AppPrinter { } if (distinctKnowledge) { for (let i = 0; i < distinctKnowledge.length; ++i) { - let pos = this.sortAsc ? distinctKnowledge.length - (i + 1) : i; + const pos = this.sortAsc + ? distinctKnowledge.length - (i + 1) + : i; const knowledge = distinctKnowledge[pos]; this.writeInColor( chalk.green, @@ -431,7 +433,7 @@ export class KPPrinter extends AppPrinter { `Displaying ${matchesToDisplay.length} matches of total ${messageIndexMatches.length}`, ); for (let i = 0; i < matchesToDisplay.length; ++i) { - let pos = this.sortAsc ? matchesToDisplay.length - (i + 1) : i; + const pos = this.sortAsc ? matchesToDisplay.length - (i + 1) : i; this.writeScoredMessage( pos, matchesToDisplay.length, @@ -505,7 +507,7 @@ export class KPPrinter extends AppPrinter { `Displaying ${matchesToDisplay.length} matches of total ${semanticRefMatches.length}`, ); for (let i = 0; i < matchesToDisplay.length; ++i) { - let pos = this.sortAsc ? matchesToDisplay.length - (i + 1) : i; + const pos = this.sortAsc ? matchesToDisplay.length - (i + 1) : i; this.writeScoredRef( pos, matchesToDisplay.length, diff --git a/ts/examples/docuProc/src/srag/pdfKnowproMem.ts b/ts/examples/docuProc/src/srag/pdfKnowproMem.ts index 0e9c82804e..b70c79995e 100644 --- a/ts/examples/docuProc/src/srag/pdfKnowproMem.ts +++ b/ts/examples/docuProc/src/srag/pdfKnowproMem.ts @@ -360,7 +360,7 @@ export async function createKnowproCommands( context.printer.writeLine("Building index"); const maxMessages = namedArgs.maxMessages ?? messageCount; - let progress = new ProgressBar(context.printer, maxMessages); + const progress = new ProgressBar(context.printer, maxMessages); const indexResult = await context.pdfIndex?.buildIndex( createIndexingEventHandler(context.printer, progress, maxMessages), ); @@ -417,7 +417,7 @@ export async function createKnowproCommands( return; } const commandDef = searchTermsDef(); - let [termArgs, namedArgs] = parseFreeAndNamedArguments( + const [termArgs, namedArgs] = parseFreeAndNamedArguments( args, commandDef, ); @@ -827,7 +827,7 @@ export async function createKnowproCommands( } } context.printer.writeHeading("Message Abstract"); - let topicMatches = semanticRefsInMessage.get("topic"); + const topicMatches = semanticRefsInMessage.get("topic"); if (topicMatches && topicMatches.length > 0) { const topics = kp.getDistinctTopicMatches( semanticRefs, @@ -841,7 +841,7 @@ export async function createKnowproCommands( context.printer.writeLine(); } - let entityMatches = semanticRefsInMessage.get("entity"); + const entityMatches = semanticRefsInMessage.get("entity"); if (entityMatches && entityMatches.length > 0) { const entities = kp.getDistinctEntityMatches( semanticRefs, @@ -939,7 +939,7 @@ export async function createKnowproCommands( function createAnswerOptions( namedArgs: NamedArgs, ): kp.AnswerContextOptions { - let topK = namedArgs.knowledgeTopK; + const topK = namedArgs.knowledgeTopK; if (topK === undefined) { return {}; } @@ -1084,7 +1084,7 @@ export async function createKnowproCommands( }*/ function createSearchOptions(namedArgs: NamedArgs): kp.SearchOptions { - let options = kp.createSearchOptions(); + const options = kp.createSearchOptions(); options.exactMatch = namedArgs.exact; options.maxKnowledgeMatches = namedArgs.knowledgeTopK; options.maxMessageMatches = namedArgs.messageTopK; @@ -1104,7 +1104,7 @@ export async function createKnowproCommands( namedArgs: NamedArgs, commandDef: CommandMetadata, ): kp.WhenFilter { - let filter: kp.WhenFilter = { + const filter: kp.WhenFilter = { knowledgeType: namedArgs.ktype, }; const conv: kp.IConversation | undefined = context.conversation; diff --git a/ts/examples/mcpMemory/src/main.ts b/ts/examples/mcpMemory/src/main.ts index cfb067a939..757a24e02c 100644 --- a/ts/examples/mcpMemory/src/main.ts +++ b/ts/examples/mcpMemory/src/main.ts @@ -107,7 +107,7 @@ async function addMcpCommands( return; } -let commandHandlers: Record = {}; +const commandHandlers: Record = {}; addStandardHandlers(commandHandlers); function onStart(io: InteractiveIo): void {} diff --git a/ts/examples/mcpMemory/src/mcp.ts b/ts/examples/mcpMemory/src/mcp.ts index c2e7553221..fde3263a9f 100644 --- a/ts/examples/mcpMemory/src/mcp.ts +++ b/ts/examples/mcpMemory/src/mcp.ts @@ -101,7 +101,7 @@ export function addPingTool(server: McpServer) { "ping", { inputSchema: pingSchema() }, async (pingRequest: PingRequest) => { - let response = pingRequest.message + const response = pingRequest.message ? "PONG: " + pingRequest.message : "pong"; return toolResult(response); diff --git a/ts/examples/mcpMemory/src/memoryServer.ts b/ts/examples/mcpMemory/src/memoryServer.ts index 8b29eeabd5..2712263d21 100644 --- a/ts/examples/mcpMemory/src/memoryServer.ts +++ b/ts/examples/mcpMemory/src/memoryServer.ts @@ -72,7 +72,7 @@ export class MemoryServer { } public async remember(request: RememberRequest): Promise { - let memory = await this.getMemory(request.memoryName); + const memory = await this.getMemory(request.memoryName); if (!memory) { return toolResult(`Memory ${request.memoryName} does not exist`); } @@ -89,7 +89,7 @@ export class MemoryServer { } public async recall(request: RecallRequest): Promise { - let memory = await this.getMemory(request.memoryName); + const memory = await this.getMemory(request.memoryName); if (!memory) { return toolResult(`Memory ${request.memoryName} does not exist`); } diff --git a/ts/examples/memoryProviders/src/elastic/simplifiedTextIndex.ts b/ts/examples/memoryProviders/src/elastic/simplifiedTextIndex.ts index 6c3d42e14a..6dea24314e 100644 --- a/ts/examples/memoryProviders/src/elastic/simplifiedTextIndex.ts +++ b/ts/examples/memoryProviders/src/elastic/simplifiedTextIndex.ts @@ -252,7 +252,7 @@ export async function createTextIndex< async function put(text: string, postings?: TSourceId[]): Promise { const textId = generateTextId(text); const embedding = await generateEmbedding(embeddingModel, text); - let convertedEmbedding: number[] = []; + const convertedEmbedding: number[] = []; embedding.forEach((value) => { convertedEmbedding.push(value); }); @@ -331,7 +331,7 @@ export async function createTextIndex< maxMatches: number, ): Promise { const queryEmbedding = await generateEmbedding(embeddingModel, value); - let convertedQuery: number[] = []; + const convertedQuery: number[] = []; queryEmbedding.forEach((value) => { convertedQuery.push(value); }); @@ -375,7 +375,7 @@ export async function createTextIndex< texts: string[], k: number, ): Promise { - let results: TSourceId[][] = []; + const results: TSourceId[][] = []; texts.forEach(async (text) => { results.push(await getNearest(text, k)); }); diff --git a/ts/examples/memoryProviders/src/elastic/storageClient.ts b/ts/examples/memoryProviders/src/elastic/storageClient.ts index f80ad6f5d7..65cd85a622 100644 --- a/ts/examples/memoryProviders/src/elastic/storageClient.ts +++ b/ts/examples/memoryProviders/src/elastic/storageClient.ts @@ -19,13 +19,13 @@ import { createTextIndex } from "./simplifiedTextIndex.js"; export async function createStorageIndex( createNew: boolean, ): Promise { - let uri = process.env.ELASTIC_URI; + const uri = process.env.ELASTIC_URI; if (!uri) { throw new Error("ELASTIC_URI environment variable not set"); } - let elasticClient = await createElasicClient(uri, createNew); + const elasticClient = await createElasicClient(uri, createNew); return { createObjectFolder: _createObjectFolder, diff --git a/ts/examples/memoryProviders/src/sqlite/keyValueTable.ts b/ts/examples/memoryProviders/src/sqlite/keyValueTable.ts index 25de18f326..02263f3021 100644 --- a/ts/examples/memoryProviders/src/sqlite/keyValueTable.ts +++ b/ts/examples/memoryProviders/src/sqlite/keyValueTable.ts @@ -153,7 +153,7 @@ export function createKeyValueTable< ids: TKeyId[], concurrency?: number, ): Promise { - let matches: TValueId[][] = []; + const matches: TValueId[][] = []; let valueIds: TValueId[] = []; for (const id of ids) { const rows = sql_get.iterate(id); diff --git a/ts/examples/memoryProviders/src/sqlite/textTable.ts b/ts/examples/memoryProviders/src/sqlite/textTable.ts index 4b1af5f093..564bbca162 100644 --- a/ts/examples/memoryProviders/src/sqlite/textTable.ts +++ b/ts/examples/memoryProviders/src/sqlite/textTable.ts @@ -130,7 +130,7 @@ export function createStringTable( const inClause = sql_makeInClause(values); const sql = `SELECT stringId from ${tableName} WHERE value IN (${inClause})`; const stmt = db.prepare(sql); - let rows = stmt.all(); + const rows = stmt.all(); for (const row of rows) { yield (row as StringTableRow).stringId; } @@ -147,7 +147,7 @@ export function createStringTable( const stmt = db.prepare( `SELECT value from ${tableName} WHERE stringId IN (${ids})`, ); - let rows = stmt.iterate(); + const rows = stmt.iterate(); for (const row of rows) { yield (row as StringTableRow).value; } @@ -236,7 +236,7 @@ export async function createTextIndex< WHERE value = ?`, ); - let [vectorStore, semanticIndex] = + const [vectorStore, semanticIndex] = settings.semanticIndex !== undefined && settings.semanticIndex ? createVectorIndex() : [undefined, undefined]; @@ -353,7 +353,7 @@ export async function createTextIndex< } async function put(text: string, postings?: TSourceId[]): Promise { - let assignedId = textTable.add(text); + const assignedId = textTable.add(text); if (postings && postings.length > 0) { postingsTable.putSync(postings, assignedId.id); } @@ -418,7 +418,7 @@ export async function createTextIndex< minScore?: number, ): Promise { maxMatches ??= 1; - let matchedTextIds = await getExactAndNearestTextIds( + const matchedTextIds = await getExactAndNearestTextIds( value, maxMatches, minScore, @@ -484,7 +484,7 @@ export async function createTextIndex< scoreBoost?: number, aliases?: knowLib.TextMatcher, ): Promise { - let matchedTextIds = await asyncArray.mapAsync( + const matchedTextIds = await asyncArray.mapAsync( values, settings.concurrency, (value) => @@ -570,7 +570,7 @@ export async function createTextIndex< if (!semanticIndex) { return []; } - let matches = await nearestNeighborsTextIds( + const matches = await nearestNeighborsTextIds( value, maxMatches, minScore, @@ -593,13 +593,13 @@ export async function createTextIndex< if (!semanticIndex) { return []; } - let matches = await semanticIndex.nearestNeighbors( + const matches = await semanticIndex.nearestNeighbors( value, maxMatches, minScore, ); // Also do an exact match - let textId = textTable.getId(value); + const textId = textTable.getId(value); if (textId) { // Remove prior match const pos = matches.findIndex((m) => m.item === textId); @@ -644,7 +644,7 @@ export async function createTextIndex< if (!existingPostings || existingPostings.length === 0) { return; } - let updatedPostings = collections.removeItemFromArray( + const updatedPostings = collections.removeItemFromArray( existingPostings, postings, ); @@ -673,7 +673,7 @@ export async function createTextIndex< // Check exact match first let matchedIds: TextId[] | undefined; - let exactId = textTable.getId(value); + const exactId = textTable.getId(value); if (exactId) { matchedIds = [exactId]; } @@ -710,7 +710,7 @@ export async function createTextIndex< maxMatches ??= 1; // Check exact match first let matchedIds: ScoredItem[] | undefined; - let exactId = textTable.getId(value); + const exactId = textTable.getId(value); if (exactId) { matchedIds = [ { diff --git a/ts/examples/microdataExtract/src/commands/filter.ts b/ts/examples/microdataExtract/src/commands/filter.ts index fd48ddd287..1feade6149 100644 --- a/ts/examples/microdataExtract/src/commands/filter.ts +++ b/ts/examples/microdataExtract/src/commands/filter.ts @@ -55,7 +55,7 @@ export default class Filter extends Command { return restaurants .map((restaurant) => this.normalizeRestaurant(restaurant)) .filter((restaurant: FilterRestaurant) => { - let allowedHosts = ["tripadvisor.com", "www.tripadvisor.com"]; + const allowedHosts = ["tripadvisor.com", "www.tripadvisor.com"]; const sourceIncludesTripAdvisor = typeof restaurant.source === "string" && diff --git a/ts/examples/microdataExtract/src/commands/merge.ts b/ts/examples/microdataExtract/src/commands/merge.ts index 4903f6aa4b..90976c1b59 100644 --- a/ts/examples/microdataExtract/src/commands/merge.ts +++ b/ts/examples/microdataExtract/src/commands/merge.ts @@ -41,7 +41,7 @@ export default class Merge extends Command { return urls.map((rawUrl) => { try { const url = new URL(rawUrl); - let allowedHosts = ["tripadvisor.com", "www.tripadvisor.com"]; + const allowedHosts = ["tripadvisor.com", "www.tripadvisor.com"]; if (allowedHosts.includes(url.hostname)) { return `https://www.tripadvisor.com${url.pathname}${url.search}`; diff --git a/ts/examples/microdataExtract/src/commands/scrape.ts b/ts/examples/microdataExtract/src/commands/scrape.ts index 119fc60f82..c41f47a7d2 100644 --- a/ts/examples/microdataExtract/src/commands/scrape.ts +++ b/ts/examples/microdataExtract/src/commands/scrape.ts @@ -244,7 +244,7 @@ export default class Scrape extends Command { outputFile: string, ): Promise { const page = await browser.newPage(); - let allRestaurantData: any[] = []; + const allRestaurantData: any[] = []; let currentPage = 1; let nextPageUrl: string | null = baseUrl; diff --git a/ts/examples/playground/src/main.ts b/ts/examples/playground/src/main.ts index 0ad39a7e41..c4c21f0149 100644 --- a/ts/examples/playground/src/main.ts +++ b/ts/examples/playground/src/main.ts @@ -54,16 +54,16 @@ const ChatSchema = `export type ChatResponse = { async function runPlayground(): Promise { // Bot instructions - let instructions = "You are an AI assistant who helps the user"; + const instructions = "You are an AI assistant who helps the user"; // // Create Model // - let [apiSettings, chatModel, completionSettings] = createModel(false); + const [apiSettings, chatModel, completionSettings] = createModel(false); // // Create Chat History // - let maxContextLength = 8196; // characters - let maxWindowLength = 30; + const maxContextLength = 8196; // characters + const maxWindowLength = 30; let chatHistory: PromptSection[] = []; // // Create TypeChat... @@ -84,7 +84,7 @@ async function runPlayground(): Promise { // // Set up playground application // - let playgroundCommands: Record = { + const playgroundCommands: Record = { clearHistory, history, saveHistory, @@ -94,7 +94,7 @@ async function runPlayground(): Promise { streamMode, help, }; - let interactiveApp: InteractiveAppSettings = { + const interactiveApp: InteractiveAppSettings = { onStart: onStart, inputHandler: nextChatTurn, commandHandler: (cmdLine, io) => @@ -272,7 +272,7 @@ async function runPlayground(): Promise { // Create default model apiSettings = openai.apiSettingsFromEnv(); } - let completionSettings: CompletionSettings = { + const completionSettings: CompletionSettings = { temperature: 0.8, max_tokens: 1000, // Max response tokens response_format: { type: "json_object" }, // createChatModel will remove it if the model doesn't support it @@ -362,7 +362,7 @@ async function runPlayground(): Promise { } function printWelcome(io: InteractiveIo): void { - let modelName = apiSettings.modelName ?? apiSettings.endpoint; + const modelName = apiSettings.modelName ?? apiSettings.endpoint; io.writer.writeLine(`💬 🛝\nWelcome to Interactive Chat Playground.\n`); io.writer.writeLine(`Model: ${modelName}`); io.writer.writeLine( diff --git a/ts/examples/schemaStudio/src/main.ts b/ts/examples/schemaStudio/src/main.ts index bf44d0a877..5f7640b7bb 100644 --- a/ts/examples/schemaStudio/src/main.ts +++ b/ts/examples/schemaStudio/src/main.ts @@ -241,7 +241,7 @@ async function runStudio(): Promise { ); } // Facets are any remaining options - let list = await generateVariationsRecursive( + const list = await generateVariationsRecursive( studio.model, phrase, variationOptions, diff --git a/ts/examples/schemaStudio/src/schemaCommands.ts b/ts/examples/schemaStudio/src/schemaCommands.ts index 8fd073a7b1..376afd0d8a 100644 --- a/ts/examples/schemaStudio/src/schemaCommands.ts +++ b/ts/examples/schemaStudio/src/schemaCommands.ts @@ -149,12 +149,12 @@ async function getTypeChatResponse( id: string, ): Promise> { // Create Model instance - let chatModel = createModel(); + const chatModel = createModel(); // Create Chat History - let maxContextLength = 8196; - let maxWindowLength = 30; - let chatHistory: PromptSection[] = []; + const maxContextLength = 8196; + const maxWindowLength = 30; + const chatHistory: PromptSection[] = []; // create TypeChat object const chat = createTypeChat( @@ -184,7 +184,7 @@ function createModel(): ChatModelWithStreaming { // Create default model apiSettings = openai.apiSettingsFromEnv(); } - let completionSettings: CompletionSettings = { + const completionSettings: CompletionSettings = { temperature: 1.0, // Max response tokens max_tokens: 1000, diff --git a/ts/examples/schemaStudio/src/urlCommands.ts b/ts/examples/schemaStudio/src/urlCommands.ts index af78859a88..223e92a0c1 100644 --- a/ts/examples/schemaStudio/src/urlCommands.ts +++ b/ts/examples/schemaStudio/src/urlCommands.ts @@ -352,8 +352,8 @@ export function createURLValidateCommands( } // Start checking each URL - let passCount = 0; - let failCount = 0; + const passCount = 0; + const failCount = 0; for (const url of urls) { const temp = url.split("\t"); diff --git a/ts/examples/searchActionTest/src/main.ts b/ts/examples/searchActionTest/src/main.ts index 52aa13fbda..76b25e1593 100644 --- a/ts/examples/searchActionTest/src/main.ts +++ b/ts/examples/searchActionTest/src/main.ts @@ -453,7 +453,7 @@ function createSyntheticLabel(syntheticResult: CalResult): string { export async function runTest(): Promise { // Create Model // - let [_apiSettings, chatModel] = createModel(false); + const [_apiSettings, chatModel] = createModel(false); const chatTranslator = createChatTranslator( chatModel, diff --git a/ts/examples/vscodeSchemaGen/src/embedActions.ts b/ts/examples/vscodeSchemaGen/src/embedActions.ts index 2a5dd66c01..39c8482d92 100644 --- a/ts/examples/vscodeSchemaGen/src/embedActions.ts +++ b/ts/examples/vscodeSchemaGen/src/embedActions.ts @@ -23,9 +23,7 @@ export interface VscodeActionsIndex { export function createVscodeActionsIndex() { let vscodeActionEmbeddings: Record = {}; - let embeddingModel: TextEmbeddingModel; - - embeddingModel = openai.createEmbeddingModel(); + const embeddingModel: TextEmbeddingModel = openai.createEmbeddingModel(); return { addOrUpdate, remove, @@ -38,7 +36,7 @@ export function createVscodeActionsIndex() { actionData: any, ): Promise { const actionString: string = `${actionData.typeName} ${actionData.actionName} ${actionData.comments.join(" ")}`; - let embedding = await generateEmbedding( + const embedding = await generateEmbedding( embeddingModel, JSON.stringify(actionString, null, 2), ); diff --git a/ts/examples/vscodeSchemaGen/src/genStats.ts b/ts/examples/vscodeSchemaGen/src/genStats.ts index e7034fdc95..6a73499490 100644 --- a/ts/examples/vscodeSchemaGen/src/genStats.ts +++ b/ts/examples/vscodeSchemaGen/src/genStats.ts @@ -224,7 +224,7 @@ export function printDetailedMarkdownTable( ) .join(", "); - let res: string = `| ${chalk.cyan(request.padEnd(34))} | ${chalk.yellow( + const res: string = `| ${chalk.cyan(request.padEnd(34))} | ${chalk.yellow( actualActionName.padEnd(24), )} | ${chalk.green(rank.toFixed(2))} | ${chalk.magenta(meanScore.toFixed(2))} | ${chalk.magenta( medianScore.toFixed(2), @@ -290,13 +290,13 @@ export function loadCommentsActionSchema( schema.loadSchema(filePath); const typeNames = schema.actionTypeNames(); - let actionSchemaComments: Record = {}; + const actionSchemaComments: Record = {}; for (const type of typeNames) { const node = schema.openActionNode(type); if (node !== undefined) { - let actionName = getActionName(node); + const actionName = getActionName(node); if (actionName !== "") { - let comments = node.leadingComments?.join(" ") ?? ""; + const comments = node.leadingComments?.join(" ") ?? ""; actionSchemaComments[actionName] = comments; } } diff --git a/ts/examples/vscodeSchemaGen/src/schemaGen.ts b/ts/examples/vscodeSchemaGen/src/schemaGen.ts index 8d7faca918..1366f90dd9 100644 --- a/ts/examples/vscodeSchemaGen/src/schemaGen.ts +++ b/ts/examples/vscodeSchemaGen/src/schemaGen.ts @@ -190,19 +190,19 @@ export async function genEmbeddingDataFromActionSchema( } const embeddingModel = openai.createEmbeddingModel(); - let aggrData: any = []; + const aggrData: any = []; let processedNodeCount = 0; for (const schemaStr of schemaDefinitions) { - let actionSchemaData: any = parseTypeComponents(schemaStr); + const actionSchemaData: any = parseTypeComponents(schemaStr); const actionString: string = `${actionSchemaData.typeName} ${actionSchemaData.actionName} ${actionSchemaData.comments.join(" ")}`; - let actionEmbedding: Float32Array = + const actionEmbedding: Float32Array = await generateEmbeddingWithRetry( embeddingModel, JSON.stringify(actionString), ); - let typeSchema: TypeSchema = { + const typeSchema: TypeSchema = { typeName: actionSchemaData.typeName, schemaText: schemaStr, }; @@ -218,10 +218,11 @@ export async function genEmbeddingDataFromActionSchema( actionRequests = dedupeList(actionRequests); actionRequests.sort(); - let actionReqEmbeddings = await generateEmbeddingForActionsRequests( - embeddingModel, - actionRequests, - ); + const actionReqEmbeddings = + await generateEmbeddingForActionsRequests( + embeddingModel, + actionRequests, + ); aggrData.push({ ...actionSchemaData, @@ -305,8 +306,8 @@ export async function processVscodeCommandsJsonFile( const schemaDefinitions: string[] = []; let processedNodeCount = 0; let schemaCount = 0; - let aggrData: any = []; - let actionTypeNames: Set = new Set(); + const aggrData: any = []; + const actionTypeNames: Set = new Set(); for (const node of jsonData) { try { @@ -337,17 +338,17 @@ export async function processVscodeCommandsJsonFile( schemaDefinitions.push(schemaStr); schemaCount++; - let actionSchemaData: any = parseTypeComponents(schemaStr); + const actionSchemaData: any = parseTypeComponents(schemaStr); actionTypeNames.add(actionSchemaData.typeName); const actionString: string = `${actionSchemaData.typeName} ${actionSchemaData.actionName} ${actionSchemaData.comments.join(" ")}`; - let actionEmbedding: Float32Array = + const actionEmbedding: Float32Array = await generateEmbeddingWithRetry( embeddingModel, JSON.stringify(actionString), ); - let typeSchema: TypeSchema = { + const typeSchema: TypeSchema = { typeName: actionSchemaData.typeName, schemaText: schemaStr, }; @@ -363,7 +364,7 @@ export async function processVscodeCommandsJsonFile( actionRequests = dedupeList(actionRequests); actionRequests.sort(); - let actionReqEmbeddings = + const actionReqEmbeddings = await generateEmbeddingForActionsRequests( embeddingModel, actionRequests, @@ -405,7 +406,7 @@ export async function processVscodeCommandsJsonFile( ); // print action type names separates by "|" - let actionTypeNamesStr = Array.from(actionTypeNames).join("|"); + const actionTypeNamesStr = Array.from(actionTypeNames).join("|"); console.log("------------------------------------------------"); console.log(`Action Type Names: ${actionTypeNamesStr}\n`); console.log(`Total action type names: ${actionTypeNames.size}\n`); diff --git a/ts/examples/vscodeSchemaGen/src/vscodeSchemaGenApp.ts b/ts/examples/vscodeSchemaGen/src/vscodeSchemaGenApp.ts index 87fd9209c6..1f43e361aa 100644 --- a/ts/examples/vscodeSchemaGen/src/vscodeSchemaGenApp.ts +++ b/ts/examples/vscodeSchemaGen/src/vscodeSchemaGenApp.ts @@ -98,7 +98,7 @@ export async function createVSCodeSchemaGenApp(): Promise { } else if (args.includes("-schemagen")) { console.log("VSCODE Action Schema generation ..."); - let maxNodestoProcess = parseArg( + const maxNodestoProcess = parseArg( "maxNodesToProcess", "number", args, @@ -184,7 +184,7 @@ export async function createVSCodeSchemaGenApp(): Promise { actionreqEmbeddingsFile, ); console.log("statGenFilePath: ", statGenFilePath); - let zerorankStatsFile = path.join( + const zerorankStatsFile = path.join( path.dirname(statGenFilePath), "zero_rank_stats.csv", ); diff --git a/ts/examples/websiteAliases/src/batchWorker.ts b/ts/examples/websiteAliases/src/batchWorker.ts index a7a3a488d8..bd7e26688f 100644 --- a/ts/examples/websiteAliases/src/batchWorker.ts +++ b/ts/examples/websiteAliases/src/batchWorker.ts @@ -57,11 +57,11 @@ async function getTypeChatResponse( pageMarkdown: string, ): Promise> { // Create Model instance - let chatModel = createModel(false); + const chatModel = createModel(false); // Create Chat History - let maxContextLength = 8196; - let maxWindowLength = 30; + const maxContextLength = 8196; + const maxWindowLength = 30; // create TypeChat object const chat = createTypeChat( @@ -109,7 +109,7 @@ function createModel(fastModel: boolean = true): ChatModelWithStreaming { } } - let completionSettings: CompletionSettings = { + const completionSettings: CompletionSettings = { temperature: 1.0, // Max response tokens max_tokens: 1000, diff --git a/ts/examples/websiteAliases/src/searchBackedPhraseGeneration.ts b/ts/examples/websiteAliases/src/searchBackedPhraseGeneration.ts index 861232efa5..61d61df55f 100644 --- a/ts/examples/websiteAliases/src/searchBackedPhraseGeneration.ts +++ b/ts/examples/websiteAliases/src/searchBackedPhraseGeneration.ts @@ -246,7 +246,7 @@ export class searchResultsPhraseGenerator { let avgUrlsPerDomain: number = 0; let count: number = 0; //let minD: string = ""; - let domainsWithMaxUrlCount: string[] = []; + const domainsWithMaxUrlCount: string[] = []; for (const [domain, stats] of Object.entries(this.cache.domains)) { const dd: urlResolverCache.domainData = @@ -278,7 +278,7 @@ export class searchResultsPhraseGenerator { let totalPhraseCount = 0; let minPhraseCount = Number.MAX_SAFE_INTEGER; let maxPhraseCount = Number.MIN_SAFE_INTEGER; - let urlsWithMaxPhraseCount: string[] = []; + const urlsWithMaxPhraseCount: string[] = []; for (const [url, info] of Object.entries(this.cache.urls)) { const ii: urlResolverCache.urlData = diff --git a/ts/examples/websiteAliases/src/topN_BatchWorker.ts b/ts/examples/websiteAliases/src/topN_BatchWorker.ts index fd35d346e1..5bd0867589 100644 --- a/ts/examples/websiteAliases/src/topN_BatchWorker.ts +++ b/ts/examples/websiteAliases/src/topN_BatchWorker.ts @@ -65,11 +65,11 @@ async function getTypeChatResponse( pageMarkdown: string, ): Promise> { // Create Model instance - let chatModel = createModel(false); + const chatModel = createModel(false); // Create Chat History - let maxContextLength = 8196; - let maxWindowLength = 30; + const maxContextLength = 8196; + const maxWindowLength = 30; // create TypeChat object const chat = createTypeChat( @@ -117,7 +117,7 @@ function createModel(fastModel: boolean = true): ChatModelWithStreaming { } } - let completionSettings: CompletionSettings = { + const completionSettings: CompletionSettings = { temperature: 1.0, // Max response tokens max_tokens: 1000, diff --git a/ts/examples/websiteAliases/src/topNsites.ts b/ts/examples/websiteAliases/src/topNsites.ts index 8fcd34f700..a7189fa0c6 100644 --- a/ts/examples/websiteAliases/src/topNsites.ts +++ b/ts/examples/websiteAliases/src/topNsites.ts @@ -404,7 +404,7 @@ export class topNDomainsExtractor { let avgPhraseCount: number = 0; let maxCollisions: number = 0; - let mCollision: string[] = []; + const mCollision: string[] = []; let lastCollision: string[] = []; let lastCollisionPhrase: string = ""; for (const [phrase, sites] of Object.entries(this.processed.phrases)) { diff --git a/ts/examples/whisperClient/src/index.ts b/ts/examples/whisperClient/src/index.ts index c569854c84..d08ffd4499 100644 --- a/ts/examples/whisperClient/src/index.ts +++ b/ts/examples/whisperClient/src/index.ts @@ -78,7 +78,7 @@ class WhisperRecognizer { body: formData, }); - let responseBody = await response.json(); + const responseBody = await response.json(); this.awaitingResult = false; return responseBody.transcription; } diff --git a/ts/packages/actionGrammar/src/generation/grammarGenerator.ts b/ts/packages/actionGrammar/src/generation/grammarGenerator.ts index cca3c66b27..ebda160ce3 100644 --- a/ts/packages/actionGrammar/src/generation/grammarGenerator.ts +++ b/ts/packages/actionGrammar/src/generation/grammarGenerator.ts @@ -484,7 +484,7 @@ Generate a corrected rule now.`; // Extract JSON from response - find matching braces let jsonText = ""; let braceCount = 0; - let startIndex = jsonStart; + const startIndex = jsonStart; for (let i = jsonStart; i < text.length; i++) { if (text[i] === "{") { diff --git a/ts/packages/actionGrammar/src/generation/schemaToGrammarGenerator.ts b/ts/packages/actionGrammar/src/generation/schemaToGrammarGenerator.ts index 334beda828..263896017f 100644 --- a/ts/packages/actionGrammar/src/generation/schemaToGrammarGenerator.ts +++ b/ts/packages/actionGrammar/src/generation/schemaToGrammarGenerator.ts @@ -308,7 +308,7 @@ export class SchemaToGrammarGenerator { // Step 3: Generate complete grammar console.log(`\nStep 3: Generating complete grammar...`); - let grammarText = await this.generateCompleteGrammar( + const grammarText = await this.generateCompleteGrammar( schemaInfo, examplesByAction, config, diff --git a/ts/packages/actionGrammar/src/nfaInterpreter.ts b/ts/packages/actionGrammar/src/nfaInterpreter.ts index a05858d8ed..69baee7e97 100644 --- a/ts/packages/actionGrammar/src/nfaInterpreter.ts +++ b/ts/packages/actionGrammar/src/nfaInterpreter.ts @@ -714,7 +714,7 @@ function tryTransition( case "wildcard": { // Match token and write to slot (variables compile to slot indices) let slotValue: SlotValue = token; - let skipCount = 0; + const skipCount = 0; // Check type constraints and validate/convert token if (trans.typeName) { diff --git a/ts/packages/actionSchema/src/schemaParser.ts b/ts/packages/actionSchema/src/schemaParser.ts index 963b41b257..4ab5da22e9 100644 --- a/ts/packages/actionSchema/src/schemaParser.ts +++ b/ts/packages/actionSchema/src/schemaParser.ts @@ -63,12 +63,12 @@ export class SchemaParser { constructor() {} public loadSchema(fileName: string): void { - let options: ts.CompilerOptions = { + const options: ts.CompilerOptions = { target: ts.ScriptTarget.ES5, module: ts.ModuleKind.CommonJS, }; - let program = ts.createProgram([fileName], options); + const program = ts.createProgram([fileName], options); this.checker = program.getTypeChecker(); for (const sourceFile of program.getSourceFiles()) { @@ -152,7 +152,7 @@ export class SchemaParser { symNode: SymbolNode | undefined, ): SymbolNode | undefined { if (ts.isTypeLiteralNode(node)) { - let typeLiteralNode = new SymbolNode( + const typeLiteralNode = new SymbolNode( nodeName, node.getText(), NodeType.Object, @@ -161,7 +161,7 @@ export class SchemaParser { ); typeLiteralNode.symbol.optional = this.isNodeOptional(node); symNode?.children.push(typeLiteralNode); - let typeLiteralMembers = node.members; + const typeLiteralMembers = node.members; typeLiteralMembers.forEach((typeLiteralMember) => { this.parseTypeElement( typeLiteralMember.name?.getText(), @@ -203,13 +203,13 @@ export class SchemaParser { symNode?.children.push(typeReferenceNode); if (node.pos >= 0) { - let typeArguments = node.getChildAt(0); + const typeArguments = node.getChildAt(0); const identifierType = this.checker?.getTypeAtLocation(typeArguments); const typeSymbols = identifierType?.symbol; if (typeSymbols) { - let typeRefMembers = typeSymbols.members; + const typeRefMembers = typeSymbols.members; typeRefMembers?.forEach((typeRefMember: ts.Symbol) => { this.parseTypeSymbol( typeRefMember.escapedName.toString(), @@ -225,13 +225,13 @@ export class SchemaParser { ts.isTypeReferenceNode(node) && ts.isIdentifier(node.typeName) ) { - let typeArguments = nodeDecl.getChildAt(0); + const typeArguments = nodeDecl.getChildAt(0); const identifierType = this.checker?.getTypeAtLocation(typeArguments); const typeSymbols = identifierType?.symbol; if (typeSymbols) { - let typeRefMembers = typeSymbols.members; + const typeRefMembers = typeSymbols.members; typeRefMembers?.forEach((typeRefMember: ts.Symbol) => { this.parseTypeSymbol( typeRefMember.escapedName.toString(), @@ -246,7 +246,7 @@ export class SchemaParser { return typeReferenceNode; } else if (ts.isArrayTypeNode(node)) { if (ts.isTypeLiteralNode(node.elementType)) { - let arrayNode = new SymbolNode( + const arrayNode = new SymbolNode( nodeName, node.getText(), NodeType.ObjectArray, @@ -254,7 +254,7 @@ export class SchemaParser { symNode, ); arrayNode.symbol.optional = this.isNodeOptional(node); - let typeLiteralMembers = node.elementType.members; + const typeLiteralMembers = node.elementType.members; typeLiteralMembers.forEach((typeLiteralMember) => { this.parseTypeElement( typeLiteralMember.name?.getText(), @@ -266,7 +266,7 @@ export class SchemaParser { symNode?.children.push(arrayNode); return arrayNode; } else { - let arrayNode = new SymbolNode( + const arrayNode = new SymbolNode( nodeName, node.pos >= 0 ? node.getText() @@ -288,10 +288,10 @@ export class SchemaParser { } } else if (ts.isUnionTypeNode(node)) { // TBD: Only handling union of strings for now. - let values: string = node.types + const values: string = node.types .map((type) => type.getText()) .join(" | "); - let unionNode = new SymbolNode( + const unionNode = new SymbolNode( nodeName, values, NodeType.Union, @@ -304,7 +304,7 @@ export class SchemaParser { return unionNode; } else if (ts.isPropertySignature(node)) { debug(`${indentation}Property: ${nodeName}`); - let literalNode = new SymbolNode( + const literalNode = new SymbolNode( nodeName, node.type?.getText() ?? "", NodeType.Property, @@ -318,8 +318,8 @@ export class SchemaParser { return literalNode; } else if (ts.isLiteralTypeNode(node)) { debug(`${indentation}Literal: ${nodeName}`); - let literalValue = node.literal; - let literalNode = new SymbolNode( + const literalValue = node.literal; + const literalNode = new SymbolNode( nodeName, literalValue.getText(), NodeType.Literal, @@ -453,12 +453,12 @@ export class SchemaParser { if (ts.isTypeAliasDeclaration(node) && node.name) { debug(`Type alias declaration:${node.name.text}`); - let symbol = this.checker?.getSymbolAtLocation(node.name); + const symbol = this.checker?.getSymbolAtLocation(node.name); if (symbol) { debug(` Symbol:${symbol.name}`); } - let type = this.checker?.getTypeAtLocation(node); + const type = this.checker?.getTypeAtLocation(node); if (type) { debug(` Type:${this.checker?.typeToString(type)}`); const symNode = this.parseTypeNode( @@ -477,21 +477,21 @@ export class SchemaParser { } } - let unionTypes = node + const unionTypes = node .getChildren() .filter((child) => ts.isUnionTypeNode(child)); unionTypes.forEach((unionType) => { debug(` UnionType:${unionType.getText()}`); }); - let interfaces = node + const interfaces = node .getChildren() .filter((child) => ts.isInterfaceDeclaration(child)); interfaces.forEach((interfaceNode) => { debug(` Interface:${interfaceNode.getText()}`); }); - let typeReferences = node + const typeReferences = node .getChildren() .filter((child) => ts.isTypeReferenceNode(child)); typeReferences.forEach((typeReference) => { @@ -515,7 +515,7 @@ export class SchemaParser { this.nodeMap[node.name.text] = symNode; } - let members = node.members; + const members = node.members; members.forEach((member) => { this.parserInterfaceMembers(member, symNode); }); diff --git a/ts/packages/agentRpc/src/client.ts b/ts/packages/agentRpc/src/client.ts index f524da3798..0467fa62f5 100644 --- a/ts/packages/agentRpc/src/client.ts +++ b/ts/packages/agentRpc/src/client.ts @@ -83,8 +83,8 @@ type ObjectMap = { }; function createObjectMap(): ObjectMap { let nextObjectId = 0; - let objectIdMap = new Map(); - let objectMap = new Map(); + const objectIdMap = new Map(); + const objectMap = new Map(); function getId(obj: T) { let objectId = objectIdMap.get(obj); diff --git a/ts/packages/agentSdkWrapper/src/schemaToGrammarGenerator.ts b/ts/packages/agentSdkWrapper/src/schemaToGrammarGenerator.ts index 4534c64b06..a36190f83a 100644 --- a/ts/packages/agentSdkWrapper/src/schemaToGrammarGenerator.ts +++ b/ts/packages/agentSdkWrapper/src/schemaToGrammarGenerator.ts @@ -249,7 +249,7 @@ export class SchemaToGrammarGenerator { // Step 3: Generate complete grammar console.log(`\nStep 3: Generating complete grammar...`); - let grammarText = await this.generateCompleteGrammar( + const grammarText = await this.generateCompleteGrammar( schemaInfo, examplesByAction, config, diff --git a/ts/packages/agents/agentUtils/graphUtils/src/calendarClient.ts b/ts/packages/agents/agentUtils/graphUtils/src/calendarClient.ts index b070790b93..306a15555f 100644 --- a/ts/packages/agents/agentUtils/graphUtils/src/calendarClient.ts +++ b/ts/packages/agents/agentUtils/graphUtils/src/calendarClient.ts @@ -54,7 +54,7 @@ export class CalendarClient extends GraphClient { if (fSync) { for (const [eventid] of this.calendarDataMap) { - let found = events.find( + const found = events.find( (event: any) => event.id === eventid, ); if (!found) { @@ -93,7 +93,7 @@ export class CalendarClient extends GraphClient { }) .select("id,subject,bodyPreview,attendees"); - let responseData = await response?.get(); + const responseData = await response?.get(); allEvents = allEvents.concat(responseData.value || []); nextPageLink = responseData["@odata.nextLink"]; @@ -341,14 +341,14 @@ export class CalendarClient extends GraphClient { } public async findEventsFromEmbeddings(subject: string): Promise { - let matchingEvents = []; + const matchingEvents = []; if (this.useEmbeddings) { - let searchResult: any = await this.calendarDataIndex.search( + const searchResult: any = await this.calendarDataIndex.search( subject, 1, ); if (searchResult) { - let event = this.calendarDataMap.get( + const event = this.calendarDataMap.get( searchResult[0].item.value, ); @@ -401,7 +401,7 @@ export class CalendarClient extends GraphClient { ); } - let responseData = await response?.get(); + const responseData = await response?.get(); await this.generateEmbedding(responseData.value, false); allEvents = allEvents.concat(responseData.value || []); nextPageLink = responseData["@odata.nextLink"]; @@ -421,7 +421,7 @@ export class CalendarClient extends GraphClient { subject.toLocaleLowerCase(), ); } else { - let searchResult: any = + const searchResult: any = await this.calendarDataIndex.search(subject, 1); if (searchResult) { matchingEvent = allEvents.find( @@ -613,7 +613,7 @@ export class CalendarClient extends GraphClient { "id,subject,bodyPreview,start,end,attendees", ); - let responseData = await response?.get(); + const responseData = await response?.get(); //await this.generateEmbedding(responseData.value, false); allEvents = allEvents.concat(responseData.value || []); diff --git a/ts/packages/agents/agentUtils/graphUtils/src/calendarDataIndex.ts b/ts/packages/agents/agentUtils/graphUtils/src/calendarDataIndex.ts index c169318451..e47c3f0aee 100644 --- a/ts/packages/agents/agentUtils/graphUtils/src/calendarDataIndex.ts +++ b/ts/packages/agents/agentUtils/graphUtils/src/calendarDataIndex.ts @@ -47,7 +47,7 @@ export function createCalendarDataIndex() { if (embeddingModel === undefined) { return; } - let embedding = await generateEmbedding( + const embedding = await generateEmbedding( embeddingModel, String(eventInfo.eventData), ); diff --git a/ts/packages/agents/agentUtils/graphUtils/src/graphClient.ts b/ts/packages/agents/agentUtils/graphUtils/src/graphClient.ts index e090b74d81..d1a0407b00 100644 --- a/ts/packages/agents/agentUtils/graphUtils/src/graphClient.ts +++ b/ts/packages/agents/agentUtils/graphUtils/src/graphClient.ts @@ -126,7 +126,7 @@ function writeFileSafety(filePath: string, content: string) { } async function withLockFile(file: string, fn: () => Promise): Promise { - let release = await lockfile.lock(file); + const release = await lockfile.lock(file); try { return await fn(); } finally { @@ -718,7 +718,7 @@ export class GraphClient extends EventEmitter { public async getEmailAddressesOfUsernamesLocal( usernames: string[], ): Promise { - let emailAddresses: string[] = []; + const emailAddresses: string[] = []; try { if (this._userEmailAddresses.size === 0) { await this.loadUserEmailAddresses(); @@ -744,7 +744,7 @@ export class GraphClient extends EventEmitter { usernames: string[], ): Promise { const client = await this.ensureClient(); - let emailAddresses: string[] = []; + const emailAddresses: string[] = []; try { for (const username of usernames) { const response = await client diff --git a/ts/packages/agents/agentUtils/graphUtils/src/mailClient.ts b/ts/packages/agents/agentUtils/graphUtils/src/mailClient.ts index 3e27d4faed..0d70220f7e 100644 --- a/ts/packages/agents/agentUtils/graphUtils/src/mailClient.ts +++ b/ts/packages/agents/agentUtils/graphUtils/src/mailClient.ts @@ -198,7 +198,7 @@ export class MailClient extends GraphClient { const client = await this.ensureClient(); try { if (sender && sender.length > 0) { - let msgs = await client + const msgs = await client .api("/me/messages") .filter(`from/emailAddress/address eq '${sender}'`) .select(["from", "id", "receivedDateTime", "subject"]) @@ -207,7 +207,7 @@ export class MailClient extends GraphClient { if (msgs && msgs.value && msgs.value.length > 0) { // we take the latest message - let msg = msgs.value[msgs.value.length - 1]; + const msg = msgs.value[msgs.value.length - 1]; return msg.id; } } diff --git a/ts/packages/agents/androidMobile/src/androidMobileActionHandler.ts b/ts/packages/agents/androidMobile/src/androidMobileActionHandler.ts index b05ab0a4e7..4cd3831dc4 100644 --- a/ts/packages/agents/androidMobile/src/androidMobileActionHandler.ts +++ b/ts/packages/agents/androidMobile/src/androidMobileActionHandler.ts @@ -36,7 +36,7 @@ async function executeAction( action: TypeAgentAction, context: ActionContext, ) { - let result = await handlePhotoAction(action, context); + const result = await handlePhotoAction(action, context); return result; } @@ -63,7 +63,7 @@ async function handlePhotoAction( let result: ActionResult | undefined = undefined; switch (action.actionName) { case "sendSMS": { - let smsAction = action as SendSMSAction; + const smsAction = action as SendSMSAction; result = createActionResult( `Sending SMS to ${smsAction.parameters.phoneNumber} message '${smsAction.parameters.message}'`, ); @@ -71,7 +71,7 @@ async function handlePhotoAction( break; } case "callPhoneNumber": { - let callAction = action as CallPhoneNumberAction; + const callAction = action as CallPhoneNumberAction; result = createActionResult( `Calling ${callAction.parameters.phoneNumber}`, ); @@ -82,13 +82,13 @@ async function handlePhotoAction( break; } case "setAlarm": { - let alarmAction = action as SetAlarmAction; + const alarmAction = action as SetAlarmAction; result = createActionResult("Setting Alarm"); context.actionIO.takeAction("set-alarm", alarmAction.parameters); break; } case "searchNearby": { - let nearbySearchAction = action as SearchNearbyAction; + const nearbySearchAction = action as SearchNearbyAction; result = createActionResult("Local search"); context.actionIO.takeAction( "search-nearby", @@ -97,7 +97,7 @@ async function handlePhotoAction( break; } case "automateUI": { - let automateAction = action as AutomatePhoneUIAction; + const automateAction = action as AutomatePhoneUIAction; result = createActionResult("Automating phone UI"); context.actionIO.takeAction( "automate-phone-ui", diff --git a/ts/packages/agents/browser/src/agent/browserActionHandler.mts b/ts/packages/agents/browser/src/agent/browserActionHandler.mts index db58fde5d5..a58f68f2fe 100644 --- a/ts/packages/agents/browser/src/agent/browserActionHandler.mts +++ b/ts/packages/agents/browser/src/agent/browserActionHandler.mts @@ -2224,7 +2224,7 @@ async function executeBrowserActionImpl( const browserCtrl = context.sessionContext.agentContext.browserControl; if (browserCtrl) { try { - let schemaName = "browser"; + const schemaName = "browser"; if (action.schemaName === "browser.actionDiscovery") { const discoveryResult = await handleSchemaDiscoveryAction( action, diff --git a/ts/packages/agents/browser/src/agent/discovery/translator.mts b/ts/packages/agents/browser/src/agent/discovery/translator.mts index ea6a8a8857..bd32362251 100644 --- a/ts/packages/agents/browser/src/agent/discovery/translator.mts +++ b/ts/packages/agents/browser/src/agent/discovery/translator.mts @@ -42,7 +42,7 @@ export enum CommercePageType { } function getPrefixPromptSection() { - let prefixSection = []; + const prefixSection = []; prefixSection.push({ type: "text", text: "You are a virtual assistant that can help users to complete requests by interacting with the UI of a webpage.", @@ -51,7 +51,7 @@ function getPrefixPromptSection() { } function getSuffixPromptSection() { - let suffixSection = []; + const suffixSection = []; suffixSection.push({ type: "text", text: ` @@ -62,7 +62,7 @@ The following is the COMPLETE JSON response object with 2 spaces of indentation } function getHtmlPromptSection(fragments: HtmlFragments[] | undefined) { - let htmlSection = []; + const htmlSection = []; if (fragments) { const contentFragments = fragments.map((a) => a.content); htmlSection.push({ @@ -82,7 +82,7 @@ function getScreenshotPromptSection( screenshots: string[] | undefined, fragments: HtmlFragments[] | undefined, ) { - let screenshotSection = []; + const screenshotSection = []; if ( screenshots !== undefined && Array.isArray(screenshots) && @@ -194,7 +194,7 @@ export class SchemaDiscoveryAgent { const prefixSection = getPrefixPromptSection(); const suffixSection = getSuffixPromptSection(); - let requestSection = []; + const requestSection = []; if (userRequest) { requestSection.push({ type: "text", @@ -415,7 +415,7 @@ export class SchemaDiscoveryAgent { const htmlSection = getHtmlPromptSection(fragments); const prefixSection = getPrefixPromptSection(); const suffixSection = getSuffixPromptSection(); - let requestSection = []; + const requestSection = []; if (userRequest) { requestSection.push({ type: "text", @@ -477,7 +477,7 @@ export class SchemaDiscoveryAgent { const htmlSection = getHtmlPromptSection(fragments); const prefixSection = getPrefixPromptSection(); const suffixSection = getSuffixPromptSection(); - let requestSection = []; + const requestSection = []; requestSection.push({ type: "text", text: ` @@ -570,7 +570,7 @@ export class SchemaDiscoveryAgent { const htmlSection = getHtmlPromptSection(fragments); const prefixSection = getPrefixPromptSection(); const suffixSection = getSuffixPromptSection(); - let requestSection = []; + const requestSection = []; requestSection.push({ type: "text", text: ` diff --git a/ts/packages/agents/browser/src/agent/knowledge/actions/analyticsActions.mts b/ts/packages/agents/browser/src/agent/knowledge/actions/analyticsActions.mts index 6721e4ec58..8ca5cd4463 100644 --- a/ts/packages/agents/browser/src/agent/knowledge/actions/analyticsActions.mts +++ b/ts/packages/agents/browser/src/agent/knowledge/actions/analyticsActions.mts @@ -647,7 +647,7 @@ export async function getTopDomains( // Count sites by domain const domainCounts: { [domain: string]: number } = {}; - let totalCount = websites.length; + const totalCount = websites.length; for (const site of websites) { const metadata = site.metadata as any; diff --git a/ts/packages/agents/browser/src/agent/knowledge/progress/extractionProgressManager.mts b/ts/packages/agents/browser/src/agent/knowledge/progress/extractionProgressManager.mts index d594d78610..93f75a36ab 100644 --- a/ts/packages/agents/browser/src/agent/knowledge/progress/extractionProgressManager.mts +++ b/ts/packages/agents/browser/src/agent/knowledge/progress/extractionProgressManager.mts @@ -79,7 +79,7 @@ export async function handleKnowledgeExtractionProgress( debug(`Knowledge Extraction 2 Progress [${extractionId}]:`, progress); // Get active extraction tracking (it should already exist) - let activeExtraction: ActiveKnowledgeExtraction | undefined = + const activeExtraction: ActiveKnowledgeExtraction | undefined = getActiveKnowledgeExtraction(extractionId); if (!activeExtraction) { console.warn( diff --git a/ts/packages/agents/browser/src/agent/searchWebMemories.mts b/ts/packages/agents/browser/src/agent/searchWebMemories.mts index f5c78affd4..21c5d0d2e2 100644 --- a/ts/packages/agents/browser/src/agent/searchWebMemories.mts +++ b/ts/packages/agents/browser/src/agent/searchWebMemories.mts @@ -191,7 +191,7 @@ export async function searchWebMemories( context: SessionContext, ): Promise { const startTime = Date.now(); - let timing = { + const timing = { parsing: 0, search: 0, processing: 0, diff --git a/ts/packages/agents/browser/src/agent/websiteMemory.mts b/ts/packages/agents/browser/src/agent/websiteMemory.mts index 7cdba1f2b7..c004e245df 100644 --- a/ts/packages/agents/browser/src/agent/websiteMemory.mts +++ b/ts/packages/agents/browser/src/agent/websiteMemory.mts @@ -878,7 +878,7 @@ export async function importHtmlFolderFromSession( const extractionMode = options.mode || "basic"; // Initialize import options for folder processing - let importOptions: any = {}; + const importOptions: any = {}; // For AI-enabled modes, validate AI availability before starting import if (extractionMode !== "basic") { @@ -1368,8 +1368,8 @@ export async function getWebsiteStats( const { groupBy = "domain", limit = 10 } = action.parameters || {}; const websites = websiteCollection.messages.getAll(); - let stats: { [key: string]: number } = {}; - let totalCount = websites.length; + const stats: { [key: string]: number } = {}; + const totalCount = websites.length; for (const site of websites) { const metadata = site.metadata as website.WebsiteDocPartMeta; diff --git a/ts/packages/agents/browser/src/common/crossContextHtmlReducer.ts b/ts/packages/agents/browser/src/common/crossContextHtmlReducer.ts index 6190dad1b7..08af465b61 100644 --- a/ts/packages/agents/browser/src/common/crossContextHtmlReducer.ts +++ b/ts/packages/agents/browser/src/common/crossContextHtmlReducer.ts @@ -149,7 +149,7 @@ export class CrossContextHtmlReducer { */ reduce(html: string): string { // Parse document using cross-context parser - let domPurify = + const domPurify = this.dependencies.DOMPurify || (typeof DOMPurify !== "undefined" ? DOMPurify : undefined); if (!domPurify) { @@ -170,11 +170,11 @@ export class CrossContextHtmlReducer { ); } while (preprocessed !== previous); - let safeHtml = domPurify.sanitize(preprocessed, { + const safeHtml = domPurify.sanitize(preprocessed, { RETURN_TRUSTED_TYPE: false, }); - let doc = this.parseDocument(safeHtml); + const doc = this.parseDocument(safeHtml); if (!doc) { console.warn("Failed to parse HTML, returning original"); return html; @@ -332,7 +332,7 @@ export class CrossContextHtmlReducer { private removeDataAttributes(doc: Document): void { const elements = doc.querySelectorAll("*"); elements.forEach((element) => { - let dataAttributes = Array.from(element.attributes).filter((a) => + const dataAttributes = Array.from(element.attributes).filter((a) => a.name.startsWith("data-"), ); dataAttributes.forEach((attr) => { diff --git a/ts/packages/agents/browser/src/extension/contentScript/domUtils.ts b/ts/packages/agents/browser/src/extension/contentScript/domUtils.ts index cc81485f18..b3485061e6 100644 --- a/ts/packages/agents/browser/src/extension/contentScript/domUtils.ts +++ b/ts/packages/agents/browser/src/extension/contentScript/domUtils.ts @@ -240,7 +240,7 @@ export function getCSSSelector(element: HTMLElement): string { return `#${element.id}`; } - let path = []; + const path = []; while (element && element.nodeType === Node.ELEMENT_NODE) { let selector = element.tagName.toLowerCase(); @@ -330,7 +330,7 @@ export function setIdsOnAllElements( useTimestampIds?: boolean, ): void { const allElements = Array.from(document.getElementsByTagName("*")); - let idPrefix = `id_${daysIntoYear()}_${frameId}_`; + const idPrefix = `id_${daysIntoYear()}_${frameId}_`; const skipIdsFor = [ "BR", @@ -369,7 +369,7 @@ export function setIdsOnAllElements( ]; for (let i = 0; i < allElements.length; i++) { - let element = allElements[i]; + const element = allElements[i]; if ( !element.hasAttribute("id") && diff --git a/ts/packages/agents/browser/src/extension/contentScript/elementInteraction.ts b/ts/packages/agents/browser/src/extension/contentScript/elementInteraction.ts index 80d3469fef..7e7444e0f6 100644 --- a/ts/packages/agents/browser/src/extension/contentScript/elementInteraction.ts +++ b/ts/packages/agents/browser/src/extension/contentScript/elementInteraction.ts @@ -10,12 +10,12 @@ import { getFullSelector, isVisible } from "./domUtils"; */ export function getInteractiveElementsBoundingBoxes(): ElementBoundingBoxes { const allElements = Array.from(document.getElementsByTagName("*")); - let textInputBounds: any[] = []; - let clickBounds: any[] = []; - let scrollBounds: any[] = []; - let tableRowBounds: any[] = []; - let tableColBounds: any[] = []; - let tableCellBounds: any[] = []; + const textInputBounds: any[] = []; + const clickBounds: any[] = []; + const scrollBounds: any[] = []; + const tableRowBounds: any[] = []; + const tableColBounds: any[] = []; + const tableCellBounds: any[] = []; let index = 0; let rowIndex = 0; let colIndex = 0; diff --git a/ts/packages/agents/browser/src/extension/contentScript/eventHandlers.ts b/ts/packages/agents/browser/src/extension/contentScript/eventHandlers.ts index 0df51b670b..6ad51bccd5 100644 --- a/ts/packages/agents/browser/src/extension/contentScript/eventHandlers.ts +++ b/ts/packages/agents/browser/src/extension/contentScript/eventHandlers.ts @@ -225,7 +225,7 @@ async function handleCrosswordChanged(url: string): Promise { function setupPageEventListeners(): void { // Listen for custom events from PaleoDb document.addEventListener("fromPaleoDbAutomation", function (e: any) { - var message = e.detail; + const message = e.detail; console.log("received from PaleoDb:", message); }); @@ -233,7 +233,7 @@ function setupPageEventListeners(): void { document.addEventListener( "fromUIEventsDispatcher", async function (e: any) { - var message = e.detail; + const message = e.detail; console.log("received from UI Events:", message); }, ); diff --git a/ts/packages/agents/browser/src/extension/contentScript/recording/capture.ts b/ts/packages/agents/browser/src/extension/contentScript/recording/capture.ts index e6d92b66d4..e4977d31dc 100644 --- a/ts/packages/agents/browser/src/extension/contentScript/recording/capture.ts +++ b/ts/packages/agents/browser/src/extension/contentScript/recording/capture.ts @@ -67,7 +67,7 @@ export async function captureAnnotatedScreenshot( ctx.fillStyle = "red"; ctx.font = "bold 14px Arial"; - var textWidth = ctx.measureText( + const textWidth = ctx.measureText( action.cssSelector ?? "", ).width; diff --git a/ts/packages/agents/browser/src/extension/serviceWorker/capture.ts b/ts/packages/agents/browser/src/extension/serviceWorker/capture.ts index 0e7e773753..6be4d8e535 100644 --- a/ts/packages/agents/browser/src/extension/serviceWorker/capture.ts +++ b/ts/packages/agents/browser/src/extension/serviceWorker/capture.ts @@ -33,7 +33,7 @@ export async function getTabHTMLFragments( const frames = await chrome.webNavigation.getAllFrames({ tabId: targetTab.id!, }); - let htmlFragments: HTMLFragment[] = []; + const htmlFragments: HTMLFragment[] = []; if (frames) { for (let i = 0; i < frames?.length; i++) { if (frames[i].url == "about:blank") { diff --git a/ts/packages/agents/browser/src/extension/serviceWorker/contentDownloader.ts b/ts/packages/agents/browser/src/extension/serviceWorker/contentDownloader.ts index c2c956446c..b23605d216 100644 --- a/ts/packages/agents/browser/src/extension/serviceWorker/contentDownloader.ts +++ b/ts/packages/agents/browser/src/extension/serviceWorker/contentDownloader.ts @@ -26,7 +26,7 @@ export class BrowserContentDownloader implements ContentDownloadAdapter { * Sanitize and clamp the timeout value to a safe range */ private sanitizeTimeout(timeout: any): number { - let t = Number(timeout); + const t = Number(timeout); if (!Number.isFinite(t) || t < this.minTimeout) { return this.defaultTimeout; } @@ -313,7 +313,7 @@ export class BrowserContentDownloader implements ContentDownloadAdapter { html: string, options: ProcessingOptions = {}, ): Promise<{ html: string; text: string }> { - let processedHtml = html; + const processedHtml = html; let textContent = ""; if (options.extractText) { diff --git a/ts/packages/agents/browser/src/extension/shared/unifiedHtmlProcessor.ts b/ts/packages/agents/browser/src/extension/shared/unifiedHtmlProcessor.ts index 2bfc95c9ed..eefd5dbd02 100644 --- a/ts/packages/agents/browser/src/extension/shared/unifiedHtmlProcessor.ts +++ b/ts/packages/agents/browser/src/extension/shared/unifiedHtmlProcessor.ts @@ -52,7 +52,7 @@ export class UnifiedHtmlProcessor { const startTime = Date.now(); const originalSize = html.length; let processedHtml = html; - let elementsRemoved = 0; + const elementsRemoved = 0; try { // Apply Readability filter if requested diff --git a/ts/packages/agents/browser/src/extension/sites/paleobiodb.ts b/ts/packages/agents/browser/src/extension/sites/paleobiodb.ts index c08194e453..e95f07faf6 100644 --- a/ts/packages/agents/browser/src/extension/sites/paleobiodb.ts +++ b/ts/packages/agents/browser/src/extension/sites/paleobiodb.ts @@ -8,10 +8,10 @@ import { } from "@typeagent/agent-sdk"; import { PaleoBioDbActions } from "./paleobiodbSchema.mjs"; -declare var taxaBrowser: any; -declare var timeScale: any; -declare var navMap: any; -declare var map: any; +declare let taxaBrowser: any; +declare let timeScale: any; +declare let navMap: any; +declare let map: any; function enablePaleoBioDbFilter(time: string, taxa: string) { if (time) { @@ -100,7 +100,7 @@ function setMapLocation(latitude: number, longitude: number) { } function getPrevalentTaxons() { - let taxonNames: string[] = []; + const taxonNames: string[] = []; const taxonList = document.querySelectorAll( "#graphics > div.col-sm-12.prevalence-row > div > div:nth-child(1) > p", ); @@ -241,7 +241,7 @@ export function createPaleoBioDbAgent(): AppAgent { } document.addEventListener("toPaleoDbAutomation", function (e: any) { - var message = e.detail; + const message = e.detail; console.log("received", message); const actionName = message.actionName; switch (actionName) { diff --git a/ts/packages/agents/browser/src/extension/uiEventsDispatcher.ts b/ts/packages/agents/browser/src/extension/uiEventsDispatcher.ts index 0ee96335db..05f12e8e89 100644 --- a/ts/packages/agents/browser/src/extension/uiEventsDispatcher.ts +++ b/ts/packages/agents/browser/src/extension/uiEventsDispatcher.ts @@ -296,7 +296,7 @@ window.addEventListener("message", async (event: any) => { const { requestId, payload } = data; try { - var message = payload; + const message = payload; console.log("received", message); const actionName = message.actionName ?? message.fullActionName.split(".").at(-1); diff --git a/ts/packages/agents/browser/src/extension/views/entityGraphVisualizer.ts b/ts/packages/agents/browser/src/extension/views/entityGraphVisualizer.ts index 9a8a2fc97c..e3459056a0 100644 --- a/ts/packages/agents/browser/src/extension/views/entityGraphVisualizer.ts +++ b/ts/packages/agents/browser/src/extension/views/entityGraphVisualizer.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. // Entity Graph Visualizer - Cytoscape.js integration for entity visualization -declare var cytoscape: any; +declare let cytoscape: any; interface EntityData { name: string; @@ -90,7 +90,7 @@ export class EntityGraphVisualizer { const nodeCount = this.cy ? this.cy.nodes().length : 0; // Configure WebGL settings based on graph size - let webglConfig = { + const webglConfig = { name: "canvas", webgl: true, webglTexSize: 2048, diff --git a/ts/packages/agents/browser/src/extension/views/topicGraphVisualizer.ts b/ts/packages/agents/browser/src/extension/views/topicGraphVisualizer.ts index 2f0c42e1cb..65d63f673c 100644 --- a/ts/packages/agents/browser/src/extension/views/topicGraphVisualizer.ts +++ b/ts/packages/agents/browser/src/extension/views/topicGraphVisualizer.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. // Topic Graph Visualizer - Cytoscape.js integration for global importance and neighborhood topic visualization -declare var cytoscape: any; +declare let cytoscape: any; interface TopicData { id: string; @@ -71,7 +71,7 @@ export class TopicGraphVisualizer { const nodeCount = this.cy ? this.cy.nodes().length : 0; // Configure WebGL settings based on graph size - let webglConfig = { + const webglConfig = { name: "canvas", webgl: true, webglTexSize: 2048, diff --git a/ts/packages/agents/calendar/src/calendarActionHandlerV1.ts b/ts/packages/agents/calendar/src/calendarActionHandlerV1.ts index ad981e4666..c87963011f 100644 --- a/ts/packages/agents/calendar/src/calendarActionHandlerV1.ts +++ b/ts/packages/agents/calendar/src/calendarActionHandlerV1.ts @@ -138,7 +138,7 @@ function deleteLocalGraphEventId( calendarContext: CalendarActionContext, ) { if (calendarContext.graphEventIds !== undefined) { - let index = calendarContext.graphEventIds.findIndex( + const index = calendarContext.graphEventIds.findIndex( (graphEventRefId) => graphEventRefId.localEventId === localEventId, ); if (index !== -1) { @@ -154,7 +154,7 @@ function getGraphEventId( ) { let graphEventId = undefined; if (localEventId !== undefined && calendarContext !== undefined) { - let graphEventRefIds = calendarContext.graphEventIds?.find( + const graphEventRefIds = calendarContext.graphEventIds?.find( (graphEventRefId) => graphEventRefId.localEventId === localEventId, ); if (graphEventRefIds !== undefined) { @@ -170,7 +170,7 @@ function getLocalEventId( ) { let localEventId = undefined; if (graphEventId !== undefined && calendarContext !== undefined) { - let graphEventRefIds = calendarContext.graphEventIds?.find( + const graphEventRefIds = calendarContext.graphEventIds?.find( (graphEventRefId) => graphEventRefId.graphEventId === graphEventId, ); if (graphEventRefIds !== undefined) { @@ -200,7 +200,7 @@ async function executeCalendarAction( action: AppAction, context: ActionContext, ) { - let result = await handleCalendarAction( + const result = await handleCalendarAction( action as CalendarAction, context.sessionContext.agentContext, ); @@ -261,12 +261,10 @@ async function addParticipantsToMeeting( dateInfo: any, calendarClient: CalendarClient, ): Promise { - let emailAddrsInMeeting: string[] | undefined = await getParticipantsToAdd( - participantsInMeeting, - calendarClient, - ); + const emailAddrsInMeeting: string[] | undefined = + await getParticipantsToAdd(participantsInMeeting, calendarClient); - let emailAddrsToAdd: string[] | undefined = await getParticipantsToAdd( + const emailAddrsToAdd: string[] | undefined = await getParticipantsToAdd( participantsToAdd, calendarClient, ); @@ -292,7 +290,7 @@ function updateCalendarEntity( ) { const localEventId = getLocalEventId(eventid, calendarContext); if (localEventId !== undefined) { - let calendarEvent = calendarContext.mapGraphEntity?.get(localEventId); + const calendarEvent = calendarContext.mapGraphEntity?.get(localEventId); if (calendarEvent !== undefined) { calendarEvent.participants?.push(...emailAddrsToAdd); calendarEvent.lastModifiedDateTime = new Date().toISOString(); @@ -411,7 +409,7 @@ export async function handleCalendarAction( ); } - let eventid = await client.createCalendarEvent( + const eventid = await client.createCalendarEvent( actionEvent.description, actionEvent.description ?? "", response.startDate, @@ -439,7 +437,7 @@ export async function handleCalendarAction( ); debug(displayText); - let result = + const result = createActionResultFromHtmlDisplay(displayText); if (result && localId) { @@ -507,9 +505,9 @@ export async function handleCalendarAction( actionEvent.timeRange || actionEvent.dayRange) ) { - let findQuery = getTimeRangeBasedQuery(actionEvent); + const findQuery = getTimeRangeBasedQuery(actionEvent); if (findQuery !== undefined) { - let results: any = + const results: any = await client.findCalendarEventsByDateRange(findQuery); return populateMeetingDetailsFromEvent( actionEvent.description!, @@ -541,13 +539,13 @@ export async function handleCalendarAction( actionEvent.participants && actionEvent.participants.length > 0 ) { - let findQuery = getTimeRangeBasedQuery(actionEvent); + const findQuery = getTimeRangeBasedQuery(actionEvent); if ( actionEvent?.participants?.length > 0 && findQuery !== undefined ) { debug(findQuery); - let results: any = + const results: any = await client.findCalendarEventsByDateRange(findQuery); if (Array.isArray(results)) { @@ -594,7 +592,7 @@ export async function handleCalendarAction( case "addParticipants": debug(chalk.green("Handling ADD_PARTICIPANTS action ...")); actionEvent = action.parameters.eventReference; - let participantsToAdd = action.parameters.participants; + const participantsToAdd = action.parameters.participants; if ( actionEvent != undefined && actionEvent.description != undefined @@ -610,7 +608,7 @@ export async function handleCalendarAction( false, ); } else { - let { startDateTime, endDateTime } = + const { startDateTime, endDateTime } = getNWeeksDateRangeISO(2); dateInfo = { @@ -619,7 +617,7 @@ export async function handleCalendarAction( }; } - let eventId = await addParticipantsToMeeting( + const eventId = await addParticipantsToMeeting( actionEvent.participants, participantsToAdd, actionEvent.description ?? "** Generated Event **", @@ -649,7 +647,8 @@ export async function handleCalendarAction( ); debug(displayText); - let result = createActionResultFromHtmlDisplay(displayText); + const result = + createActionResultFromHtmlDisplay(displayText); if (result && localId) { result.entities = [ { @@ -684,17 +683,17 @@ export async function handleCalendarAction( lastLocalEventId, ); if (meeting) { - let participantsToAdd = + const participantsToAdd = action.parameters.participants; - let participantsInMeeting = meeting.participants; + const participantsInMeeting = meeting.participants; - let emailAddrsInMeeting: string[] | undefined = + const emailAddrsInMeeting: string[] | undefined = await getParticipantsToAdd( participantsInMeeting, client, ); - let emailAddrsToAdd: string[] | undefined = + const emailAddrsToAdd: string[] | undefined = await getParticipantsToAdd( participantsToAdd, client, @@ -705,7 +704,7 @@ export async function handleCalendarAction( emailAddrsToAdd && emailAddrsToAdd.length > 0 ) { - let eventId = + const eventId = await client.addParticipantsToExistingMeeting( lastGraphEventId, emailAddrsInMeeting, @@ -734,7 +733,7 @@ export async function handleCalendarAction( ); debug(displayText); - let result = + const result = createActionResultFromHtmlDisplay( displayText, ); @@ -751,7 +750,7 @@ export async function handleCalendarAction( // Could happen because the calendar event was deleted // and clear the entity from the context - let err = eventId as ErrorResponse; + const err = eventId as ErrorResponse; if (err.code === "ErrorItemNotFound") { deleteLocalGraphEventId( lastLocalEventId, @@ -786,16 +785,16 @@ async function populateMeetingDetailsFromEvent( if (events instanceof Array) { if (events && events.length > 0) { const displayText = findEventsDisplayHtml(events); - let result = createActionResultFromHtmlDisplay(displayText); + const result = createActionResultFromHtmlDisplay(displayText); return result; } else { const displayText = `You have a meeting free day 😊`; - let result = createActionResultFromTextDisplay(displayText); + const result = createActionResultFromTextDisplay(displayText); return result; } } else { const displayText = findEventsDisplayHtml(events); - let result = createActionResultFromHtmlDisplay(displayText); + const result = createActionResultFromHtmlDisplay(displayText); return result; } } diff --git a/ts/packages/agents/calendar/src/calendarActionHandlerV2.ts b/ts/packages/agents/calendar/src/calendarActionHandlerV2.ts index a2a11f2060..3a1da49c8e 100644 --- a/ts/packages/agents/calendar/src/calendarActionHandlerV2.ts +++ b/ts/packages/agents/calendar/src/calendarActionHandlerV2.ts @@ -138,7 +138,7 @@ function deleteLocalGraphEventId( calendarContext: CalendarActionContext, ) { if (calendarContext.graphEventIds !== undefined) { - let index = calendarContext.graphEventIds.findIndex( + const index = calendarContext.graphEventIds.findIndex( (graphEventRefId) => graphEventRefId.localEventId === localEventId, ); if (index !== -1) { @@ -154,7 +154,7 @@ function getGraphEventId( ) { let graphEventId = undefined; if (localEventId !== undefined && calendarContext !== undefined) { - let graphEventRefIds = calendarContext.graphEventIds?.find( + const graphEventRefIds = calendarContext.graphEventIds?.find( (graphEventRefId) => graphEventRefId.localEventId === localEventId, ); if (graphEventRefIds !== undefined) { @@ -184,7 +184,7 @@ async function executeCalendarAction( action: AppAction, context: ActionContext, ) { - let result = await handleCalendarAction( + const result = await handleCalendarAction( action as CalendarAction, context.sessionContext.agentContext, ); @@ -251,12 +251,10 @@ async function addParticipantsToMeeting( dateInfo: any, calendarClient: CalendarClient, ): Promise { - let emailAddrsInMeeting: string[] | undefined = await getParticipantsToAdd( - participantsInMeeting, - calendarClient, - ); + const emailAddrsInMeeting: string[] | undefined = + await getParticipantsToAdd(participantsInMeeting, calendarClient); - let emailAddrsToAdd: string[] | undefined = await getParticipantsToAdd( + const emailAddrsToAdd: string[] | undefined = await getParticipantsToAdd( participantsToAdd, calendarClient, ); @@ -281,7 +279,7 @@ function getLocalEventId( ) { let localEventId = undefined; if (graphEventId !== undefined && calendarContext !== undefined) { - let graphEventRefIds = calendarContext.graphEventIds?.find( + const graphEventRefIds = calendarContext.graphEventIds?.find( (graphEventRefId) => graphEventRefId.graphEventId === graphEventId, ); if (graphEventRefIds !== undefined) { @@ -298,7 +296,7 @@ function updateCalendarEntity( ) { const localEventId = getLocalEventId(eventid, calendarContext); if (localEventId !== undefined) { - let calendarEvent = calendarContext.mapGraphEntity?.get(localEventId); + const calendarEvent = calendarContext.mapGraphEntity?.get(localEventId); if (calendarEvent !== undefined) { calendarEvent.participants?.push(...emailAddrsToAdd); calendarEvent.lastModifiedDateTime = new Date().toISOString(); @@ -360,12 +358,12 @@ export async function handleCalendarAction( actionEvent.timeRange.startDateTime !== undefined && actionEvent.timeRange.startDateTime?.day !== undefined ) { - let startDateTime = parseCalendarDateTime( + const startDateTime = parseCalendarDateTime( actionEvent.timeRange.startDateTime, true, ); if (startDateTime !== undefined) { - let endDateTimeRes = calcEndDateTime( + const endDateTimeRes = calcEndDateTime( startDateTime, actionEvent.timeRange.duration ?? "1h", ); @@ -373,7 +371,7 @@ export async function handleCalendarAction( endDateTimeRes.success && endDateTimeRes.parsedDateTime !== undefined ) { - let endDateTime = endDateTimeRes.parsedDateTime; + const endDateTime = endDateTimeRes.parsedDateTime; let participantsToAdd: string[] | undefined = []; if ( @@ -388,7 +386,7 @@ export async function handleCalendarAction( actionEvent.description = actionEvent.description ?? "Meeting"; - let eventid = await client.createCalendarEvent( + const eventid = await client.createCalendarEvent( actionEvent.description ?? "Meeting", actionEvent.description ?? "", startDateTime, @@ -418,7 +416,7 @@ export async function handleCalendarAction( ); debug(displayText); - let result = + const result = createActionResultFromHtmlDisplay( displayText, ); @@ -494,12 +492,12 @@ export async function handleCalendarAction( actionEvent.timeRange?.startDateTime && actionEvent.timeRange?.endDateTime ) { - let findQuery = getQueryParamsFromTimeRange( + const findQuery = getQueryParamsFromTimeRange( actionEvent.timeRange?.startDateTime, actionEvent.timeRange?.endDateTime, ); if (findQuery !== undefined) { - let results: any = + const results: any = await client.findCalendarEventsByDateRange(findQuery); return populateMeetingDetailsFromEvent( actionEvent.description!, @@ -518,7 +516,7 @@ export async function handleCalendarAction( actionEvent.timeRange?.startDateTime && actionEvent.timeRange?.endDateTime ) { - let findQuery = getQueryParamsFromTimeRange( + const findQuery = getQueryParamsFromTimeRange( actionEvent.timeRange?.startDateTime, actionEvent.timeRange?.endDateTime, ); @@ -527,7 +525,7 @@ export async function handleCalendarAction( findQuery !== undefined ) { debug(findQuery); - let results: any = + const results: any = await client.findCalendarEventsByDateRange(findQuery); if (Array.isArray(results)) { @@ -592,7 +590,7 @@ export async function handleCalendarAction( case "addParticipants": debug(chalk.green("Handling ADD_PARTICIPANTS action ...")); actionEvent = action.parameters.eventReference; - let participantsToAdd = action.parameters.participants; + const participantsToAdd = action.parameters.participants; if ( actionEvent != undefined && actionEvent.description != undefined @@ -615,7 +613,7 @@ export async function handleCalendarAction( "1h", ); } else { - let { startDateTime, endDateTime } = + const { startDateTime, endDateTime } = getNWeeksDateRangeISO(2); dateInfo = { startDate: startDateTime, @@ -624,7 +622,7 @@ export async function handleCalendarAction( } } } else { - let { startDateTime, endDateTime } = + const { startDateTime, endDateTime } = getNWeeksDateRangeISO(2); dateInfo = { @@ -633,7 +631,7 @@ export async function handleCalendarAction( }; } - let eventId = await addParticipantsToMeeting( + const eventId = await addParticipantsToMeeting( actionEvent.participants, participantsToAdd, actionEvent.description ?? "** Generated Event **", @@ -663,7 +661,8 @@ export async function handleCalendarAction( ); debug(displayText); - let result = createActionResultFromHtmlDisplay(displayText); + const result = + createActionResultFromHtmlDisplay(displayText); if (result && localId) { result.entities = [ { @@ -698,17 +697,17 @@ export async function handleCalendarAction( lastLocalEventId, ); if (meeting) { - let participantsToAdd = + const participantsToAdd = action.parameters.participants; - let participantsInMeeting = meeting.participants; + const participantsInMeeting = meeting.participants; - let emailAddrsInMeeting: string[] | undefined = + const emailAddrsInMeeting: string[] | undefined = await getParticipantsToAdd( participantsInMeeting, client, ); - let emailAddrsToAdd: string[] | undefined = + const emailAddrsToAdd: string[] | undefined = await getParticipantsToAdd( participantsToAdd, client, @@ -719,7 +718,7 @@ export async function handleCalendarAction( emailAddrsToAdd && emailAddrsToAdd.length > 0 ) { - let eventId = + const eventId = await client.addParticipantsToExistingMeeting( lastGraphEventId, emailAddrsInMeeting, @@ -748,7 +747,7 @@ export async function handleCalendarAction( ); debug(displayText); - let result = + const result = createActionResultFromHtmlDisplay( displayText, ); @@ -765,7 +764,7 @@ export async function handleCalendarAction( // Could happen because the calendar event was deleted // and clear the entity from the context - let err = eventId as ErrorResponse; + const err = eventId as ErrorResponse; if (err.code === "ErrorItemNotFound") { deleteLocalGraphEventId( lastLocalEventId, @@ -801,16 +800,16 @@ async function populateMeetingDetailsFromEvent( if (events instanceof Array) { if (events && events.length > 0) { const displayText = findEventsDisplayHtml(events); - let result = createActionResultFromMarkdownDisplay(displayText); + const result = createActionResultFromMarkdownDisplay(displayText); return result; } else { const displayText = `You have a meeting free day 😊`; - let result = createActionResultFromTextDisplay(displayText); + const result = createActionResultFromTextDisplay(displayText); return result; } } else { const displayText = findEventsDisplayHtml(events); - let result = createActionResultFromMarkdownDisplay(displayText); + const result = createActionResultFromMarkdownDisplay(displayText); return result; } } diff --git a/ts/packages/agents/calendar/src/calendarActionHandlerV3.ts b/ts/packages/agents/calendar/src/calendarActionHandlerV3.ts index fff9192d16..73b587281d 100644 --- a/ts/packages/agents/calendar/src/calendarActionHandlerV3.ts +++ b/ts/packages/agents/calendar/src/calendarActionHandlerV3.ts @@ -622,7 +622,7 @@ export class CalendarActionHandlerV3 implements AppAgent { const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; // Parse the natural language date (default to today if not provided) - let eventDate = date ? this.parseNaturalDate(date) : new Date(); + const eventDate = date ? this.parseNaturalDate(date) : new Date(); if (!eventDate) { return createActionResultFromError( `Could not parse date: ${date}`, diff --git a/ts/packages/agents/calendar/src/calendarQueryHelper.ts b/ts/packages/agents/calendar/src/calendarQueryHelper.ts index 3a194ec059..97a5b73437 100644 --- a/ts/packages/agents/calendar/src/calendarQueryHelper.ts +++ b/ts/packages/agents/calendar/src/calendarQueryHelper.ts @@ -26,7 +26,7 @@ export function generateEventReferenceCriteria( ); } if (eventReference.dayRange) { - let nlpCriteria = generateNaturalLanguageCriteria( + const nlpCriteria = generateNaturalLanguageCriteria( eventReference.dayRange, ); if (nlpCriteria) criteria.push(nlpCriteria); diff --git a/ts/packages/agents/chat/src/chatResponseHandler.ts b/ts/packages/agents/chat/src/chatResponseHandler.ts index a70d4b946b..010bb0f13c 100644 --- a/ts/packages/agents/chat/src/chatResponseHandler.ts +++ b/ts/packages/agents/chat/src/chatResponseHandler.ts @@ -38,7 +38,7 @@ async function rehydrateImages(context: ActionContext, files: string[]) { name = files[i].substring(files[i].lastIndexOf("\\") + 1); } - let a = await context.sessionContext.sessionStorage?.read( + const a = await context.sessionContext.sessionStorage?.read( `\\..\\user_files\\${name}`, "base64", ); diff --git a/ts/packages/agents/desktop/src/connector.ts b/ts/packages/agents/desktop/src/connector.ts index e956c4bf47..1a6c064433 100644 --- a/ts/packages/agents/desktop/src/connector.ts +++ b/ts/packages/agents/desktop/src/connector.ts @@ -453,13 +453,13 @@ async function mapInputToAppNameFromIndex( backupProgramNameTable?: string[], ): Promise { try { - let matchedNames = await programNameIndex.search(input, 1); + const matchedNames = await programNameIndex.search(input, 1); if (matchedNames && matchedNames.length > 0) { return matchedNames[0].item.value; } } catch (e: any) { if (backupProgramNameTable !== undefined) { - let stringMatches = searchTable(input, backupProgramNameTable, 1); + const stringMatches = searchTable(input, backupProgramNameTable, 1); if (stringMatches && stringMatches.length > 0) { return stringMatches[0]; diff --git a/ts/packages/agents/email/src/emailActionHandler.ts b/ts/packages/agents/email/src/emailActionHandler.ts index 7e874ae8ac..1405138d22 100644 --- a/ts/packages/agents/email/src/emailActionHandler.ts +++ b/ts/packages/agents/email/src/emailActionHandler.ts @@ -557,7 +557,7 @@ async function executeEmailAction( completion_tokens: 0, total_tokens: 0, }; - let result = await handleEmailAction(action, context, tokenUsage); + const result = await handleEmailAction(action, context, tokenUsage); if (result) { // If handler already built an ActionResultSuccess, return it directly const actionResult = @@ -609,7 +609,7 @@ async function handleEmailAction( let genContent: string = ""; if (action.parameters.genContent.generateBody) { - let query = action.parameters.genContent.bodySearchQuery; + const query = action.parameters.genContent.bodySearchQuery; if (query) { const chatModel = openai.createChatModel( "GPT_35_TURBO", @@ -629,7 +629,7 @@ async function handleEmailAction( tokenUsage.total_tokens += usage.total_tokens ?? 0; } }; - let result = await generateNotes( + const result = await generateNotes( query, 4096, chatModel, @@ -711,7 +711,7 @@ async function handleForwardOrReplyAction( emailProvider: IEmailProvider, ) { const rawRef = action.parameters.messageRef as MessageReference | string; - let msgRef: MessageReference = + const msgRef: MessageReference = typeof rawRef === "string" ? { content: rawRef } : rawRef; if (msgRef) { // use the message reference to find the email to reply to @@ -733,7 +733,7 @@ async function handleForwardOrReplyAction( searchQuery.startDateTime = msgRef.receivedDateTime.startTime; if (msgRef.receivedDateTime?.endTime) searchQuery.endDateTime = msgRef.receivedDateTime.endTime; - let msg_id = await emailProvider.findEmail(searchQuery); + const msg_id = await emailProvider.findEmail(searchQuery); if (msg_id) { let cc_addrs: string[] | undefined = []; @@ -1153,7 +1153,7 @@ async function handleFindEmailAction( ); if (answer && answer.chunksUsed > 0) { // Post-process: link email subjects in the answer HTML - let answerHtml = linkEmailSubjects(answer.answer, messages); + const answerHtml = linkEmailSubjects(answer.answer, messages); // Count linked titles already in the answer body const inlineLinks = (answerHtml.match(/ { - let context: GreetingAgentContext = { + const context: GreetingAgentContext = { user: { givenName: undefined, surName: undefined, @@ -183,7 +183,7 @@ export class GreetingCommandHandler implements CommandHandler { if (response.success) { context.actionIO.appendDiagnosticData(response.data); - let action: GreetingAction = response.data as GreetingAction; + const action: GreetingAction = response.data as GreetingAction; let result: ActionResultSuccess | undefined = undefined; switch (action.actionName) { case "personalizedGreetingAction": @@ -235,7 +235,7 @@ export class GreetingCommandHandler implements CommandHandler { apiSettings = openai.apiSettingsFromEnv(); } } - let completionSettings: CompletionSettings = { + const completionSettings: CompletionSettings = { temperature: 1.0, // Max response tokens max_tokens: 1000, @@ -261,7 +261,7 @@ export class GreetingCommandHandler implements CommandHandler { }, ): Promise> { // Create Model instance - let chatModel = this.createModel(true); + const chatModel = this.createModel(true); // Capture per-call token usage reported by the model so the agent can // attribute it to the request. TypeChat's translate() doesn't expose a @@ -279,9 +279,9 @@ export class GreetingCommandHandler implements CommandHandler { } // Create Chat History - let maxContextLength = 8196; - let maxWindowLength = 30; - let chatHistory: PromptSection[] = []; + const maxContextLength = 8196; + const maxWindowLength = 30; + const chatHistory: PromptSection[] = []; // create TypeChat object const chat = createTypeChat( diff --git a/ts/packages/agents/image/src/imageActionHandler.ts b/ts/packages/agents/image/src/imageActionHandler.ts index 218bb23b1a..a8c51d9279 100644 --- a/ts/packages/agents/image/src/imageActionHandler.ts +++ b/ts/packages/agents/image/src/imageActionHandler.ts @@ -48,7 +48,7 @@ async function executePhotoAction( completion_tokens: 0, total_tokens: 0, }; - let result = await handlePhotoAction( + const result = await handlePhotoAction( action as ImageAction, context, tokenUsage, diff --git a/ts/packages/agents/list/src/listActionHandler.ts b/ts/packages/agents/list/src/listActionHandler.ts index 777bc7a861..028e73b6cf 100644 --- a/ts/packages/agents/list/src/listActionHandler.ts +++ b/ts/packages/agents/list/src/listActionHandler.ts @@ -33,7 +33,7 @@ async function executeListAction( action: TypeAgentAction, context: ActionContext, ) { - let result = await handleListAction( + const result = await handleListAction( action, context.sessionContext.agentContext, ); diff --git a/ts/packages/agents/markdown/src/agent/markdownActionHandler.ts b/ts/packages/agents/markdown/src/agent/markdownActionHandler.ts index bf588a88f3..4db1503955 100644 --- a/ts/packages/agents/markdown/src/agent/markdownActionHandler.ts +++ b/ts/packages/agents/markdown/src/agent/markdownActionHandler.ts @@ -36,7 +36,10 @@ async function executeMarkdownAction( action: AppAction, context: ActionContext, ) { - let result = await handleMarkdownAction(action as MarkdownAction, context); + const result = await handleMarkdownAction( + action as MarkdownAction, + context, + ); return result; } diff --git a/ts/packages/agents/markdown/src/agent/translator.ts b/ts/packages/agents/markdown/src/agent/translator.ts index aff1c1a451..ce2b0043cb 100644 --- a/ts/packages/agents/markdown/src/agent/translator.ts +++ b/ts/packages/agents/markdown/src/agent/translator.ts @@ -98,7 +98,7 @@ export class MarkdownAgent { cursorPosition?: number, context?: any, // Already deserialized from JSON string ) { - let contentPrompt = []; + const contentPrompt = []; if (currentMarkdown) { contentPrompt.push({ type: "text", @@ -112,7 +112,7 @@ export class MarkdownAgent { } // Add cursor position context if available - let positionPrompt = []; + const positionPrompt = []; if (typeof cursorPosition === "number" && cursorPosition >= 0) { positionPrompt.push({ type: "text", diff --git a/ts/packages/agents/markdown/src/view/site/slash-commands.ts b/ts/packages/agents/markdown/src/view/site/slash-commands.ts index f2c6f43644..4afcb99b78 100644 --- a/ts/packages/agents/markdown/src/view/site/slash-commands.ts +++ b/ts/packages/agents/markdown/src/view/site/slash-commands.ts @@ -289,7 +289,7 @@ export const slashCommandPreview = $prose(() => { }, ]; - let decorations: Decoration[] = []; + const decorations: Decoration[] = []; for (const cmd of commandPatterns) { const match = lineText.match(cmd.pattern); diff --git a/ts/packages/agents/montage/src/agent/montageActionHandler.ts b/ts/packages/agents/montage/src/agent/montageActionHandler.ts index 3001a47bd9..4f1860c6e6 100644 --- a/ts/packages/agents/montage/src/agent/montageActionHandler.ts +++ b/ts/packages/agents/montage/src/agent/montageActionHandler.ts @@ -918,7 +918,7 @@ async function findRequestedImages( } function filterToSearchTerm(filters: string[]): kp.SearchTerm[] { - let terms: kp.SearchTerm[] = []; + const terms: kp.SearchTerm[] = []; filters.forEach((value) => terms.push({ term: { text: value } })); return terms; diff --git a/ts/packages/agents/montage/src/route/route.ts b/ts/packages/agents/montage/src/route/route.ts index f6565dbed2..d2a4e060d3 100644 --- a/ts/packages/agents/montage/src/route/route.ts +++ b/ts/packages/agents/montage/src/route/route.ts @@ -203,7 +203,7 @@ app.post("/montageUpdated", (req: Request, res: Response) => { }); let clients: any[] = []; -let messageQueue: any[] = []; +const messageQueue: any[] = []; //let filePath: string; app.get("/events", (req, res) => { diff --git a/ts/packages/agents/onboarding/src/discovery/discoveryHandler.ts b/ts/packages/agents/onboarding/src/discovery/discoveryHandler.ts index 185722619e..83fe498813 100644 --- a/ts/packages/agents/onboarding/src/discovery/discoveryHandler.ts +++ b/ts/packages/agents/onboarding/src/discovery/discoveryHandler.ts @@ -681,7 +681,7 @@ async function handleCrawlCliHelp( } const source = `cli:${command}`; - let actions: DiscoveredAction[] = result.data.actions.map((a) => { + const actions: DiscoveredAction[] = result.data.actions.map((a) => { const action: DiscoveredAction = { name: a.name, description: a.description, diff --git a/ts/packages/agents/photo/src/photoActionHandler.ts b/ts/packages/agents/photo/src/photoActionHandler.ts index 7909927aec..2535e2c286 100644 --- a/ts/packages/agents/photo/src/photoActionHandler.ts +++ b/ts/packages/agents/photo/src/photoActionHandler.ts @@ -29,7 +29,7 @@ async function executePhotoAction( action: TypeAgentAction, context: ActionContext, ) { - let result = await handlePhotoAction(action, context); + const result = await handlePhotoAction(action, context); return result; } diff --git a/ts/packages/agents/player/src/client.ts b/ts/packages/agents/player/src/client.ts index 15627e0a5e..785cd86875 100644 --- a/ts/packages/agents/player/src/client.ts +++ b/ts/packages/agents/player/src/client.ts @@ -888,7 +888,10 @@ async function playPlaylistAction( playlist.id, ); const collection = new PlaylistTrackCollection(playlist, tracks); - let actionResult = await playTrackCollection(collection, clientContext); + const actionResult = await playTrackCollection( + collection, + clientContext, + ); // add the playlist as an entity actionResult.entities.push({ diff --git a/ts/packages/agents/player/src/playback.ts b/ts/packages/agents/player/src/playback.ts index 82a6f90291..21443c0670 100644 --- a/ts/packages/agents/player/src/playback.ts +++ b/ts/packages/agents/player/src/playback.ts @@ -29,7 +29,7 @@ export function chalkStatus(status: SpotifyApi.CurrentPlaybackResponse) { if (status.progress_ms) { timePart = `${msToElapsedMinSec(status.progress_ms)}/${timePart}`; } - let symbol = status.is_playing ? playSymbol : pauseSymbol; + const symbol = status.is_playing ? playSymbol : pauseSymbol; console.log( `${symbol} ${timePart} ${chalk.cyanBright(status.item.name)}`, ); diff --git a/ts/packages/agents/player/src/search.ts b/ts/packages/agents/player/src/search.ts index bcfab93b6a..41f03cd0c5 100644 --- a/ts/packages/agents/player/src/search.ts +++ b/ts/packages/agents/player/src/search.ts @@ -352,7 +352,7 @@ async function searchAlbumsWithTrackArtists( albumEntity: Entity | undefined, ) { // Try again without artist, as the artist on the album might not match the one in the tracks. - let albums = await searchAlbumSorted( + const albums = await searchAlbumSorted( albumName, undefined, context, diff --git a/ts/packages/agents/player/src/userData.ts b/ts/packages/agents/player/src/userData.ts index 94baa20ead..f4f72a261b 100644 --- a/ts/packages/agents/player/src/userData.ts +++ b/ts/packages/agents/player/src/userData.ts @@ -114,7 +114,7 @@ export function mergeUserDataKind( ) { let added = 0; for (const newItem of newItems) { - let info = existing.get(newItem.id); + const info = existing.get(newItem.id); if (!info) { existing.set(newItem.id, newItem); added++; diff --git a/ts/packages/agents/playerLocal/src/agent/localPlayerHandlers.ts b/ts/packages/agents/playerLocal/src/agent/localPlayerHandlers.ts index d58f2e2871..f569ff775a 100644 --- a/ts/packages/agents/playerLocal/src/agent/localPlayerHandlers.ts +++ b/ts/packages/agents/playerLocal/src/agent/localPlayerHandlers.ts @@ -413,7 +413,7 @@ function handleSearchFiles(service: LocalPlayerService, query: string) { .map((f, i) => `
  • ${i + 1}. ${f.name}
  • `) .join(""); - let html = `

    🔍 Search Results for "${query}" (${files.length} found)

      ${fileListHtml}
    `; + const html = `

    🔍 Search Results for "${query}" (${files.length} found)

      ${fileListHtml}
    `; return createActionResultFromHtmlDisplay(html); } diff --git a/ts/packages/agents/powershell/src/actionHandler.mts b/ts/packages/agents/powershell/src/actionHandler.mts index 89d95a7f14..b85422b23e 100644 --- a/ts/packages/agents/powershell/src/actionHandler.mts +++ b/ts/packages/agents/powershell/src/actionHandler.mts @@ -1040,7 +1040,7 @@ const POWERSHELL_BUILTIN_ACTIONS = new Set([ ]); export function instantiate(): AppAgent { - let agentContext: PowerShellAgentContext = {}; + const agentContext: PowerShellAgentContext = {}; return { async initializeAgentContext() { diff --git a/ts/packages/agents/settings/src/settingsCommandHandler.ts b/ts/packages/agents/settings/src/settingsCommandHandler.ts index e850289acf..49eb6e91af 100644 --- a/ts/packages/agents/settings/src/settingsCommandHandler.ts +++ b/ts/packages/agents/settings/src/settingsCommandHandler.ts @@ -30,7 +30,7 @@ async function executeSettingsAction( action: TypeAgentAction, context: ActionContext, ) { - let result = await handleSettingsAction(action, context); + const result = await handleSettingsAction(action, context); return result; } diff --git a/ts/packages/agents/taskflow/src/actionHandler.mts b/ts/packages/agents/taskflow/src/actionHandler.mts index 34a331df67..63957bce49 100644 --- a/ts/packages/agents/taskflow/src/actionHandler.mts +++ b/ts/packages/agents/taskflow/src/actionHandler.mts @@ -571,7 +571,7 @@ const TASKFLOW_BUILTIN_ACTIONS = new Set([ ]); export function instantiate(): AppAgent { - let agentContext: TaskFlowAgentContext = {}; + const agentContext: TaskFlowAgentContext = {}; return { async initializeAgentContext() { diff --git a/ts/packages/agents/timer/src/timerActionHandler.ts b/ts/packages/agents/timer/src/timerActionHandler.ts index 293c9fae88..9b4a328cda 100644 --- a/ts/packages/agents/timer/src/timerActionHandler.ts +++ b/ts/packages/agents/timer/src/timerActionHandler.ts @@ -71,7 +71,6 @@ function persistAsync(storage: Storage | undefined, ctx: TimerContext): void { reminders: Array.from(ctx.reminders.values()), }; storage.write(STORAGE_FILE, JSON.stringify(state)).catch((e) => { - // eslint-disable-next-line no-console console.error("timer-agent: failed to persist reminders", e); }); } @@ -188,7 +187,7 @@ function fireDueReminders(context: SessionContext) { thread.complete(); } catch (e) { // Don't let one bad reminder kill the loop. - // eslint-disable-next-line no-console + console.error( `timer-agent: failed to fire reminder ${reminder.id}`, e, diff --git a/ts/packages/agents/video/src/videoActionHandler.ts b/ts/packages/agents/video/src/videoActionHandler.ts index e61cd2a2a8..3fa34da3fa 100644 --- a/ts/packages/agents/video/src/videoActionHandler.ts +++ b/ts/packages/agents/video/src/videoActionHandler.ts @@ -47,7 +47,7 @@ async function executeVideoAction( completion_tokens: 0, total_tokens: 0, }; - let result = await handleVideoAction(action as VideoAction, context); + const result = await handleVideoAction(action as VideoAction, context); if (result !== undefined && result.error === undefined) { result.tokenUsage = tokenUsage; } @@ -116,7 +116,7 @@ async function getInPaintItems( return await Promise.all( relatedFiles.map(async (file) => { - let a = await context!.sessionContext.sessionStorage?.read( + const a = await context!.sessionContext.sessionStorage?.read( `\\..\\user_files\\${file}`, "base64", ); diff --git a/ts/packages/aiclient/src/bing.ts b/ts/packages/aiclient/src/bing.ts index 6c81129518..ecf26f19af 100644 --- a/ts/packages/aiclient/src/bing.ts +++ b/ts/packages/aiclient/src/bing.ts @@ -243,7 +243,7 @@ export async function searchWeb( site?: string, ): Promise { deprecated(); - let options = count ? { count } : undefined; + const options = count ? { count } : undefined; if (site) { query += ` site:${encodeURIComponent(site)}`; } @@ -285,7 +285,7 @@ export function buildQuery(queries: string[], operator: "AND" | "OR"): string { return queries[0]; } let query = ""; - let operatorText = ` ${operator} `; + const operatorText = ` ${operator} `; for (let i = 0; i < queries.length; ++i) { if (i > 0) { query += operatorText; diff --git a/ts/packages/aiclient/src/openai.ts b/ts/packages/aiclient/src/openai.ts index febc92dcf6..42f28d1350 100644 --- a/ts/packages/aiclient/src/openai.ts +++ b/ts/packages/aiclient/src/openai.ts @@ -753,7 +753,7 @@ function createAzureOpenAIChatModel( // image_url content with streaming token usage reporting is currently broken // TODO: remove after API endpoint correctly handles this case let historyIncludesImages: boolean = false; - let isImageProptContent = (c: MultimodalPromptContent) => + const isImageProptContent = (c: MultimodalPromptContent) => (c as ImagePromptContent).type == "image_url"; messages.map((ps) => { if (Array.isArray(ps.content)) { @@ -878,7 +878,7 @@ function createAzureOpenAIChatModel( } function verifyFilterResults(filterResult: FilterResult) { - let filters: string[] = new Array(); + const filters: string[] = new Array(); if (filterResult) { if (filterResult.hate?.filtered) { filters.push("hate"); @@ -900,7 +900,7 @@ function verifyFilterResults(filterResult: FilterResult) { } if (filters.length > 0) { - let msg = `A content filter has been triggered by one or more messages. The triggered filters are: ${filters.join(", ")}`; + const msg = `A content filter has been triggered by one or more messages. The triggered filters are: ${filters.join(", ")}`; throw new Error(`${msg}`); } } diff --git a/ts/packages/aiclient/src/restClient.ts b/ts/packages/aiclient/src/restClient.ts index f2e93d07b2..fef8eeda9e 100644 --- a/ts/packages/aiclient/src/restClient.ts +++ b/ts/packages/aiclient/src/restClient.ts @@ -395,7 +395,7 @@ export function getRetryAfterMs( // console.log(`Retry-After: ${pauseHeader}`); pauseHeader = pauseHeader.trim(); if (pauseHeader) { - let seconds = parseInt(pauseHeader); + const seconds = parseInt(pauseHeader); let pauseMs: number; if (isNaN(seconds)) { const retryDate = new Date(pauseHeader); diff --git a/ts/packages/api/src/storageProviders/awsStorageProvider.ts b/ts/packages/api/src/storageProviders/awsStorageProvider.ts index fec13999b2..951c502133 100644 --- a/ts/packages/api/src/storageProviders/awsStorageProvider.ts +++ b/ts/packages/api/src/storageProviders/awsStorageProvider.ts @@ -43,7 +43,7 @@ export class AWSStorageProvider implements TypeAgentStorageProvider { async listRemoteFiles(prefix?: string): Promise { const results: string[] = []; - let continueToken: string | undefined = undefined; + const continueToken: string | undefined = undefined; do { const command = new ListObjectsV2Command({ diff --git a/ts/packages/api/src/storageProviders/azureStorageProvider.ts b/ts/packages/api/src/storageProviders/azureStorageProvider.ts index 8a55cabd2a..816e33a8ee 100644 --- a/ts/packages/api/src/storageProviders/azureStorageProvider.ts +++ b/ts/packages/api/src/storageProviders/azureStorageProvider.ts @@ -121,7 +121,7 @@ export class AzureStorageProvider implements TypeAgentStorageProvider { const containerClient: ContainerClient = this.blobServiceClient.getContainerClient(this.containerName!!); - let blobName = fileName.replace(getUserDataDir(), ""); + const blobName = fileName.replace(getUserDataDir(), ""); const blockBlobClient: BlockBlobClient = containerClient.getBlockBlobClient(blobName!!); diff --git a/ts/packages/api/src/typeAgentServer.ts b/ts/packages/api/src/typeAgentServer.ts index 707085a706..3d320738c3 100644 --- a/ts/packages/api/src/typeAgentServer.ts +++ b/ts/packages/api/src/typeAgentServer.ts @@ -164,7 +164,7 @@ export class TypeAgentServer { for (const blob of response.segment.blobItems) { const blobClient = containerClient.getBlobClient(blob.name); const filePath = path.join(getUserDataDir(), blob.name); - let dir = path.dirname(filePath); + const dir = path.dirname(filePath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); diff --git a/ts/packages/api/src/webDispatcher.ts b/ts/packages/api/src/webDispatcher.ts index 82d674dce8..2aeb6c19c1 100644 --- a/ts/packages/api/src/webDispatcher.ts +++ b/ts/packages/api/src/webDispatcher.ts @@ -87,7 +87,7 @@ export async function createWebDispatcher(): Promise { // TODO: expose executeAction so we can call that directly instead of running it through a command // TODO: bubble back any action results along with the command result - var paramStr = + const paramStr = action.parameters && Object.keys(action.parameters).length ? `--parameters '${JSON.stringify(action.parameters).replaceAll("'", "\\'")}'` : ""; diff --git a/ts/packages/api/src/webServer.ts b/ts/packages/api/src/webServer.ts index 92fa881094..916b2adc86 100644 --- a/ts/packages/api/src/webServer.ts +++ b/ts/packages/api/src/webServer.ts @@ -60,7 +60,7 @@ export class TypeAgentAPIWebServer { serve(config: TypeAgentAPIServerConfig, request: any, response: any) { // serve up the requested file if we have it - let root: string = path.resolve(config.wwwroot); + const root: string = path.resolve(config.wwwroot); let requestedFile: string = request.url == "/" || request.url === undefined ? "index.html" @@ -72,7 +72,7 @@ export class TypeAgentAPIWebServer { } // process POST requests - let url = new URL(request.url, "http://localhost"); + const url = new URL(request.url, "http://localhost"); if ( url.pathname == "/action/" && (request.method === "PUT" || request.method === "GET") @@ -85,13 +85,13 @@ export class TypeAgentAPIWebServer { } try { - var action: any = JSON.parse(data?.toString() ?? ""); + const action: any = JSON.parse(data?.toString() ?? ""); console.log( "Received action request: ", JSON.stringify(action, null, 2), ); - var actionResult = this.actionHandler(action); + const actionResult = this.actionHandler(action); response.writeHead(200, { "Content-Type": "application/json" }); response.end(JSON.stringify(actionResult)); diff --git a/ts/packages/api/src/webSocketServer.ts b/ts/packages/api/src/webSocketServer.ts index 4d41ae12e8..b01c102213 100644 --- a/ts/packages/api/src/webSocketServer.ts +++ b/ts/packages/api/src/webSocketServer.ts @@ -34,7 +34,7 @@ export class TypeAgentAPIWebSocketServer { const params = new URLSearchParams(req.url.split("?")[1]); const clientId = params.get("clientId"); if (clientId) { - for (var client of this.server.clients) { + for (const client of this.server.clients) { if ((client as any).clientId) { this.server.clients.delete(client); } diff --git a/ts/packages/azure-ai-foundry/src/urlResolver.ts b/ts/packages/azure-ai-foundry/src/urlResolver.ts index 3270ff258e..6d6f53333e 100644 --- a/ts/packages/azure-ai-foundry/src/urlResolver.ts +++ b/ts/packages/azure-ai-foundry/src/urlResolver.ts @@ -245,7 +245,7 @@ export async function resolveURLWithWikipedia( limit: numberOfResults.toString(), }); - let retVal: string[] = []; + const retVal: string[] = []; await fetch(`${url}?${parameters}`, { method: "GET", headers: headers }) .then((response) => response.json()) .then(async (data: any) => { @@ -336,11 +336,11 @@ async function getTypeChatResponse( config: wikipedia.WikipediaApiSettings, ): Promise> { // Create Model instance - let chatModel = createModel(true); + const chatModel = createModel(true); // Create Chat History - let maxContextLength = 8196; - let maxWindowLength = 30; + const maxContextLength = 8196; + const maxWindowLength = 30; // create TypeChat object const chat = createTypeChat( @@ -384,7 +384,7 @@ function createModel(fastModel: boolean = true): ChatModelWithStreaming { } } - let completionSettings: CompletionSettings = { + const completionSettings: CompletionSettings = { temperature: 1.0, // Max response tokens max_tokens: 1000, diff --git a/ts/packages/cache/src/explanation/v5/explanationV5.ts b/ts/packages/cache/src/explanation/v5/explanationV5.ts index 13990636f3..3985023a03 100644 --- a/ts/packages/cache/src/explanation/v5/explanationV5.ts +++ b/ts/packages/cache/src/explanation/v5/explanationV5.ts @@ -507,7 +507,7 @@ function isValueCopiedFromSubPhrase( phrase: SubPhrase, paramInfo: PropertyValue, ) { - let ltext = normalizeParamString(phrase.text); + const ltext = normalizeParamString(phrase.text); const lval = normalizeParamString(paramInfo.propertyValue.toString()); // Try direct match first diff --git a/ts/packages/chat-ui/src/partialCompletion.ts b/ts/packages/chat-ui/src/partialCompletion.ts index ef08f042fa..e3e0984603 100644 --- a/ts/packages/chat-ui/src/partialCompletion.ts +++ b/ts/packages/chat-ui/src/partialCompletion.ts @@ -20,7 +20,6 @@ function pcTrace(...args: unknown[]): void { (globalThis as { __partialCompletionTrace?: boolean }) .__partialCompletionTrace === true ) { - // eslint-disable-next-line no-console console.debug("[partialCompletion]", ...args); } } diff --git a/ts/packages/chat-ui/src/setContent.ts b/ts/packages/chat-ui/src/setContent.ts index 8d64576371..c1d7317fcb 100644 --- a/ts/packages/chat-ui/src/setContent.ts +++ b/ts/packages/chat-ui/src/setContent.ts @@ -273,7 +273,7 @@ export function setContent( // use the same stylesheets as the main page let css: string = ""; const links = document.head.getElementsByTagName("link"); - let promises: Promise[] = []; + const promises: Promise[] = []; for (let i = 0; i < links.length; i++) { if (links[i].rel.toLowerCase() == "stylesheet") { if (links[i].href.startsWith("./")) { diff --git a/ts/packages/chat-ui/src/templateEditor.ts b/ts/packages/chat-ui/src/templateEditor.ts index 4cdaedafea..abf43a8c48 100644 --- a/ts/packages/chat-ui/src/templateEditor.ts +++ b/ts/packages/chat-ui/src/templateEditor.ts @@ -1090,7 +1090,7 @@ abstract class FieldArrayBase extends FieldGroup { const value = this.getArray(); if (value) { const editingField = this.data.setEditing(undefined); - let isEditing = editingField?.isAncestor(field); + const isEditing = editingField?.isAncestor(field); value.splice(index, 1); this.createChildFields(); diff --git a/ts/packages/cli/src/commands/run/request.ts b/ts/packages/cli/src/commands/run/request.ts index 41f13663ed..b6084bcd87 100644 --- a/ts/packages/cli/src/commands/run/request.ts +++ b/ts/packages/cli/src/commands/run/request.ts @@ -113,7 +113,7 @@ export default class RequestCommand extends Command { throw Error(`The file '${fileName}' does not exist.`); } - let retVal: string[] = new Array(); + const retVal: string[] = new Array(); retVal.push(Buffer.from(readFileSync(fileName)).toString("base64")); return retVal; diff --git a/ts/packages/cli/src/debugInterceptor.ts b/ts/packages/cli/src/debugInterceptor.ts index 508586d91e..0de842b4ec 100644 --- a/ts/packages/cli/src/debugInterceptor.ts +++ b/ts/packages/cli/src/debugInterceptor.ts @@ -9,7 +9,7 @@ const DEBUG_GUTTER_LAST = chalk.dim(" … "); const FRAMED_GUTTER = chalk.dim(" │ "); const GUTTER_WIDTH = 4; // " │ " visible chars const CONT_INDENT = 6; // " │ " visible chars -// eslint-disable-next-line no-control-regex + const ANSI_RE = /\x1b\[[0-9;]*m/g; // Namespace color palette for framed panel display diff --git a/ts/packages/cli/src/enhancedConsole.ts b/ts/packages/cli/src/enhancedConsole.ts index ff09df10f5..2dd4689ef5 100644 --- a/ts/packages/cli/src/enhancedConsole.ts +++ b/ts/packages/cli/src/enhancedConsole.ts @@ -2287,11 +2287,7 @@ function renderStartupBanner(): void { i < LOGO_LINES.length ? LOGO_LINES[i] : " ".repeat(LOGO_WIDTH); const coloredLogo = chalk.cyan(logo); const content = i < contentLines.length ? contentLines[i] : ""; - const contentVisible = content.replace( - // eslint-disable-next-line no-control-regex - /\x1b\[[0-9;]*m/g, - "", - ); + const contentVisible = content.replace(/\x1b\[[0-9;]*m/g, ""); const padding = Math.max( 0, innerWidth - LOGO_WIDTH - contentVisible.length, diff --git a/ts/packages/coda/src/extension.ts b/ts/packages/coda/src/extension.ts index 73e0c3ee8f..9b654c9e14 100644 --- a/ts/packages/coda/src/extension.ts +++ b/ts/packages/coda/src/extension.ts @@ -21,15 +21,18 @@ export function activate(context: vscode.ExtensionContext) { // The command has been defined in the package.json file // Now provide the implemeninitializeWStation of the command with registerCommand // The commandId parameter must match the command field in package.json - let disposable = vscode.commands.registerCommand("coda-shell.start", () => { - // The code you place here will be executed every time your command is executed - // Display a message box to the user - vscode.window.showInformationMessage("Hello World from Coda!"); - }); + const disposable = vscode.commands.registerCommand( + "coda-shell.start", + () => { + // The code you place here will be executed every time your command is executed + // Display a message box to the user + vscode.window.showInformationMessage("Hello World from Coda!"); + }, + ); context.subscriptions.push(disposable); // make a command to list to console all of the commands available - let listCommands = vscode.commands.registerCommand( + const listCommands = vscode.commands.registerCommand( "coda-shell.listCommands", () => { vscode.commands.getCommands().then((commands) => { diff --git a/ts/packages/coda/src/handleEditorCodeActions.ts b/ts/packages/coda/src/handleEditorCodeActions.ts index 2b15b4b249..e5316e9110 100644 --- a/ts/packages/coda/src/handleEditorCodeActions.ts +++ b/ts/packages/coda/src/handleEditorCodeActions.ts @@ -573,7 +573,7 @@ export async function handleCreateCodeBlockAction( }; } - let insertPos = resolvePosition(editor, position); + const insertPos = resolvePosition(editor, position); if (!insertPos) { return { handled: false, @@ -895,7 +895,7 @@ export async function handleUpsertLinesAction( } const pos = resolvePosition(editor, position); - let targetLine = pos.line; + const targetLine = pos.line; await editor.edit((editBuilder) => { if (operation === "insert") { diff --git a/ts/packages/coda/src/handleVSCodeActions.ts b/ts/packages/coda/src/handleVSCodeActions.ts index e74a96bbb4..7e75e213b4 100644 --- a/ts/packages/coda/src/handleVSCodeActions.ts +++ b/ts/packages/coda/src/handleVSCodeActions.ts @@ -10,7 +10,7 @@ import { handleExtensionActions } from "./handleExtensionActions"; import { handleEditorCodeActions } from "./handleEditorCodeActions"; async function execChangeEditorColumns(actionData: any): Promise { - let actionResult: ActionResult = { + const actionResult: ActionResult = { handled: true, message: "Ok", }; @@ -51,7 +51,7 @@ async function execChangeEditorColumns(actionData: any): Promise { export async function handleDisplayKBActions( action: any, ): Promise { - let actionResult: ActionResult = { + const actionResult: ActionResult = { handled: true, message: "Ok", }; @@ -162,7 +162,7 @@ export async function handleDisplayKBActions( export async function handleGeneralKBActions( action: any, ): Promise { - let actionResult: ActionResult = { + const actionResult: ActionResult = { handled: true, message: "Ok", }; @@ -179,7 +179,7 @@ export async function handleGeneralKBActions( case "gotoFileOrLineOrSymbol": { const editor = vscode.window.activeTextEditor; if (action.parameters.goto === "file") { - let fileName = action.parameters.ref; + const fileName = action.parameters.ref; if (!fileName) { vscode.commands.executeCommand( "workbench.action.quickOpen", @@ -263,7 +263,7 @@ export async function handleBaseEditorActions( message: "Ok", }; - let actionData = action.parameters; + const actionData = action.parameters; const actionName = action.actionName ?? action.fullActionName.split(".").at(-1); @@ -341,7 +341,7 @@ export async function handleBaseEditorActions( } case "changeColorScheme": { - let currentTheme = await vscode.workspace + const currentTheme = await vscode.workspace .getConfiguration() .get("workbench.colorTheme"); if (currentTheme && currentTheme === actionData.theme) { diff --git a/ts/packages/coda/src/handleWorkBenchActions.ts b/ts/packages/coda/src/handleWorkBenchActions.ts index 9b120f4d9b..16159427d4 100644 --- a/ts/packages/coda/src/handleWorkBenchActions.ts +++ b/ts/packages/coda/src/handleWorkBenchActions.ts @@ -12,7 +12,7 @@ import * as fs from "fs/promises"; import { aliasManager } from "./commandAliasMgr"; async function handleOpenFileAction(action: any): Promise { - let actionResult: ActionResult = { + const actionResult: ActionResult = { handled: true, message: "Ok", }; diff --git a/ts/packages/codeProcessor/src/code.ts b/ts/packages/codeProcessor/src/code.ts index f2f81779a9..7f51ddd0c2 100644 --- a/ts/packages/codeProcessor/src/code.ts +++ b/ts/packages/codeProcessor/src/code.ts @@ -41,7 +41,7 @@ export async function loadCodeWithLineNumbers( filePath: string, basePath?: string, ): Promise { - let codeText: string = await readAllText(filePath, basePath); + const codeText: string = await readAllText(filePath, basePath); return annotateCodeWithLineNumbers(codeText); } @@ -101,7 +101,7 @@ export function createCodeSection( language: string = "typescript", ): PromptSection { code = annotateCodeWithLineNumbers(code); - let content = `${language} code prefixed with line numbers:\n"""\n${code}\n"""\n`; + const content = `${language} code prefixed with line numbers:\n"""\n${code}\n"""\n`; return { role: MessageSourceRole.user, content, @@ -114,7 +114,7 @@ export type Api = { }; export function createApiSection(api: Api): PromptSection { - let content = api.callConditions + const content = api.callConditions ? `Call this Api when the following conditions are met:\n${api.callConditions}\n\n${api.apiSignatures}` : api.apiSignatures; return { diff --git a/ts/packages/codeProcessor/src/codeReviewer.ts b/ts/packages/codeProcessor/src/codeReviewer.ts index b24ab36a6e..5bc11589ed 100644 --- a/ts/packages/codeProcessor/src/codeReviewer.ts +++ b/ts/packages/codeProcessor/src/codeReviewer.ts @@ -148,7 +148,7 @@ export function createCodeReviewer( ): Promise { const annotatedCode = codeSectionFromBlock(code); facets ??= "accurate, active voice, crisp, succinct"; - let request = + const request = "Understand the included code and document it where necessary, especially complicated loops. Also explain parameters as needed using JSDoc syntax." + `The docs must be: ${facets}`; return getData(await docTranslator.translate(request, [annotatedCode])); diff --git a/ts/packages/commandExecutor/src/commandServer.ts b/ts/packages/commandExecutor/src/commandServer.ts index b568b889b1..dea7b72861 100644 --- a/ts/packages/commandExecutor/src/commandServer.ts +++ b/ts/packages/commandExecutor/src/commandServer.ts @@ -104,7 +104,6 @@ function toolResult(result: string): CallToolResult { } function stripAnsi(text: string): string { - // eslint-disable-next-line no-control-regex return text.replace(/\x1b\[[0-9;]*m/g, ""); } diff --git a/ts/packages/copilot-plugin/demo/driver/tools/azure-tts.mts b/ts/packages/copilot-plugin/demo/driver/tools/azure-tts.mts index f3aacabe46..c446a1d71b 100644 --- a/ts/packages/copilot-plugin/demo/driver/tools/azure-tts.mts +++ b/ts/packages/copilot-plugin/demo/driver/tools/azure-tts.mts @@ -104,7 +104,7 @@ function parseArgs(argv: string[]): Args { 'Usage: node azure-tts.mjs --text "..." [--voice NAME] [--style NAME] [--rate +0%] [--region R] [--resource RID] [--out FILE] [--no-play]', ); process.exit(0); - // eslint-disable-next-line no-fallthrough + default: throw new Error(`Unknown argument: ${a}`); } diff --git a/ts/packages/copilot-plugin/src/mcp/server.ts b/ts/packages/copilot-plugin/src/mcp/server.ts index 9f6a380995..695ca82bc1 100644 --- a/ts/packages/copilot-plugin/src/mcp/server.ts +++ b/ts/packages/copilot-plugin/src/mcp/server.ts @@ -31,7 +31,6 @@ import { extractMessageText } from "../shared/message-formatter.js"; // ── Helpers ────────────────────────────────────────────────────────────────── function stripAnsi(text: string): string { - // eslint-disable-next-line no-control-regex return text.replace(/\x1b\[[0-9;]*m/g, ""); } diff --git a/ts/packages/defaultAgentProvider/src/utils/config.ts b/ts/packages/defaultAgentProvider/src/utils/config.ts index dfa29d987f..d6425a115b 100644 --- a/ts/packages/defaultAgentProvider/src/utils/config.ts +++ b/ts/packages/defaultAgentProvider/src/utils/config.ts @@ -37,7 +37,9 @@ export type ProviderConfig = AppAgentConfig & { let providerConfig: ProviderConfig | undefined; export function getProviderConfig(configName?: string): ProviderConfig { if (providerConfig === undefined) { - var fileName = configName ? `config.${configName}.json` : "config.json"; + const fileName = configName + ? `config.${configName}.json` + : "config.json"; providerConfig = JSON.parse( fs.readFileSync(getPackageFilePath(`./data/${fileName}`), "utf8"), ) as ProviderConfig; diff --git a/ts/packages/dispatcher/dispatcher/src/command/parameters.ts b/ts/packages/dispatcher/dispatcher/src/command/parameters.ts index 266cc3f817..8b96acab90 100644 --- a/ts/packages/dispatcher/dispatcher/src/command/parameters.ts +++ b/ts/packages/dispatcher/dispatcher/src/command/parameters.ts @@ -375,7 +375,7 @@ export function parseParams( } } } - let nextArgs: string[] = []; + const nextArgs: string[] = []; if (argDefs !== undefined) { // Detect missing arguments if (argDefIndex !== argDefs.length) { diff --git a/ts/packages/dispatcher/dispatcher/src/context/chatHistory.ts b/ts/packages/dispatcher/dispatcher/src/context/chatHistory.ts index 92e13fb251..7afeff186c 100644 --- a/ts/packages/dispatcher/dispatcher/src/context/chatHistory.ts +++ b/ts/packages/dispatcher/dispatcher/src/context/chatHistory.ts @@ -268,7 +268,7 @@ export function createChatHistory(init: boolean): ChatHistory { // LLM like to correct/change casing. Normalize for look up. const normalizedName = normalizeParamString(entity.name); const uniqueIndex = `${normalizedName}.${entity.type}`; - let existing = uniqueEntities.get(uniqueIndex); + const existing = uniqueEntities.get(uniqueIndex); const promptEntity: PromptEntity = { ...entity, sourceAppAgentName: appAgentName, diff --git a/ts/packages/dispatcher/dispatcher/src/context/chatHistoryPrompt.ts b/ts/packages/dispatcher/dispatcher/src/context/chatHistoryPrompt.ts index a4994a78fb..101ad0f56c 100644 --- a/ts/packages/dispatcher/dispatcher/src/context/chatHistoryPrompt.ts +++ b/ts/packages/dispatcher/dispatcher/src/context/chatHistoryPrompt.ts @@ -32,7 +32,7 @@ export function serializeEntityForPrompt( if (duplicate) { // Last-write-wins on duplicate facet names. Surface via debug so we can tell // whether the flat shape is silently losing data in practice. - // eslint-disable-next-line no-console + console.warn( `serializeEntityForPrompt: duplicate facet name in entity '${entity.name}' — last-write-wins`, ); diff --git a/ts/packages/dispatcher/dispatcher/src/context/memoryPrinter.ts b/ts/packages/dispatcher/dispatcher/src/context/memoryPrinter.ts index 76607381b5..6bb908e8a8 100644 --- a/ts/packages/dispatcher/dispatcher/src/context/memoryPrinter.ts +++ b/ts/packages/dispatcher/dispatcher/src/context/memoryPrinter.ts @@ -229,7 +229,7 @@ function writeDistinctKnowledge( out.writeLine(`Distinct and sorted in ascending order (lowest first)`); } for (let i = 0; i < distinctKnowledge.length; ++i) { - let pos = sortAsc ? distinctKnowledge.length - (i + 1) : i; + const pos = sortAsc ? distinctKnowledge.length - (i + 1) : i; const knowledge = distinctKnowledge[pos]; out.writeInColor( chalk.green, @@ -303,7 +303,7 @@ function writeScoredSemanticRefs( `Displaying ${matchesToDisplay.length} matches of total ${semanticRefMatches.length}`, ); for (let i = 0; i < matchesToDisplay.length; ++i) { - let pos = sortAsc ? matchesToDisplay.length - (i + 1) : i; + const pos = sortAsc ? matchesToDisplay.length - (i + 1) : i; writeScoredRef( out, pos, @@ -388,7 +388,7 @@ function writeScoredMessages( `Displaying ${matchesToDisplay.length} matches of total ${messageIndexMatches.length}`, ); for (let i = 0; i < matchesToDisplay.length; ++i) { - let pos = sortAsc ? matchesToDisplay.length - (i + 1) : i; + const pos = sortAsc ? matchesToDisplay.length - (i + 1) : i; writeScoredMessage( out, pos, diff --git a/ts/packages/dispatcher/dispatcher/src/context/system/handlers/randomCommandHandler.ts b/ts/packages/dispatcher/dispatcher/src/context/system/handlers/randomCommandHandler.ts index 3e12935d1f..ea2fc1b7ad 100644 --- a/ts/packages/dispatcher/dispatcher/src/context/system/handlers/randomCommandHandler.ts +++ b/ts/packages/dispatcher/dispatcher/src/context/system/handlers/randomCommandHandler.ts @@ -99,13 +99,13 @@ class RandomOnlineCommandHandler implements CommandHandlerNoParams { // // Create Model // - let chatModel = this.createModel(); + const chatModel = this.createModel(); // // Create Chat History // - let maxContextLength = 8196; // characters - let maxWindowLength = 30; - let chatHistory: PromptSection[] = []; + const maxContextLength = 8196; // characters + const maxWindowLength = 30; + const chatHistory: PromptSection[] = []; const chat = createTypeChat( chatModel, @@ -163,7 +163,7 @@ class RandomOnlineCommandHandler implements CommandHandlerNoParams { // Create default model apiSettings = openai.apiSettingsFromEnv(); } - let completionSettings: CompletionSettings = { + const completionSettings: CompletionSettings = { temperature: 1.0, max_tokens: 1000, // Max response tokens response_format: { type: "json_object" }, // createChatModel will remove it if the model doesn't support it diff --git a/ts/packages/dispatcher/dispatcher/src/context/system/handlers/tokenCommandHandler.ts b/ts/packages/dispatcher/dispatcher/src/context/system/handlers/tokenCommandHandler.ts index 777b8c2a98..7dfa9c3b98 100644 --- a/ts/packages/dispatcher/dispatcher/src/context/system/handlers/tokenCommandHandler.ts +++ b/ts/packages/dispatcher/dispatcher/src/context/system/handlers/tokenCommandHandler.ts @@ -37,7 +37,7 @@ class TokenDetailsCommandHandler implements CommandHandlerNoParams { public readonly description = "Gets detailed LLM usage statistics."; public async run(context: ActionContext) { - let retValue: string[] = []; + const retValue: string[] = []; for (const [t, tokens] of TokenCounter.getInstance().counters) { retValue.push( `#${t}\n------------------\nPrompt: ${tokens.total.prompt_tokens}\nCompletion: ${tokens.total.completion_tokens}\nTotal: ${tokens.total.total_tokens}\n`, diff --git a/ts/packages/dispatcher/dispatcher/src/helpers/completion/trie.ts b/ts/packages/dispatcher/dispatcher/src/helpers/completion/trie.ts index 9518be7cfe..1088a6f580 100644 --- a/ts/packages/dispatcher/dispatcher/src/helpers/completion/trie.ts +++ b/ts/packages/dispatcher/dispatcher/src/helpers/completion/trie.ts @@ -59,7 +59,7 @@ export class TST { if (key.length === 0) { throw new Error("key must have length >= 1"); } - let x = this.getRadix(this.root, key, 0); + const x = this.getRadix(this.root, key, 0); if (x === undefined) { return undefined; } @@ -77,7 +77,7 @@ export class TST { if (key.length === 0) { throw new Error("key must have length >= 1"); } - let c = key.charAt(d); + const c = key.charAt(d); if (c < x.c) { return this.getRadix(x.left, key, d); } else if (c > x.c) { @@ -108,7 +108,7 @@ export class TST { d: number, isNewKey: boolean, ): TSTNode { - let c = key.charAt(d); + const c = key.charAt(d); if (x === undefined) { x = new TSTNode(c); } @@ -154,7 +154,7 @@ export class TST { x.count--; x.data = undefined; } else { - let c = key.charAt(d); + const c = key.charAt(d); if (c < x.c) { x.left = this.deleteRadix(x.left, key, d); } else if (c > x.c) { diff --git a/ts/packages/dispatcher/dispatcher/src/search/internet.ts b/ts/packages/dispatcher/dispatcher/src/search/internet.ts index a3232d4961..16eda913c8 100644 --- a/ts/packages/dispatcher/dispatcher/src/search/internet.ts +++ b/ts/packages/dispatcher/dispatcher/src/search/internet.ts @@ -158,12 +158,12 @@ export async function getLookupSettings( rewriteFocus = lookupConfig.rewriteFocus; } fastModelName ??= "GPT_35_TURBO"; - let fastModel = + const fastModel = openai.createLocalChatModel(fastModelName, undefined, [ "chatResponseHandler", ]) ?? openai.createJsonChatModel(fastModelName, ["chatResponseHandler"]); - let generalModel = openai.createJsonChatModel(undefined, [ + const generalModel = openai.createJsonChatModel(undefined, [ "chatResponseHandler", ]); rewriteFocus ??= @@ -194,7 +194,7 @@ async function createActionResultWithMessage( settings: LookupSettings, ): Promise { let historyText = ""; - let linkEntities: Entity[] = []; + const linkEntities: Entity[] = []; let refCount = 0; for (const answer of answers) { historyText += `${answer.text}\n`; diff --git a/ts/packages/dispatcher/dispatcher/src/search/search.ts b/ts/packages/dispatcher/dispatcher/src/search/search.ts index 28c6de4f49..7211223ee9 100644 --- a/ts/packages/dispatcher/dispatcher/src/search/search.ts +++ b/ts/packages/dispatcher/dispatcher/src/search/search.ts @@ -31,7 +31,7 @@ async function getAnswerFromConversationManager( if (conversationManager === undefined) { throw new Error("Conversation manager is undefined!"); } - let searchResponse = await conversationManager.getSearchResponse( + const searchResponse = await conversationManager.getSearchResponse( question, conversationLookupFilters, ); @@ -178,7 +178,7 @@ async function rehydrateImages( name = files[i]!.substring(files[i]!.lastIndexOf("\\") + 1); } - let a = await context.sessionContext.sessionStorage?.read( + const a = await context.sessionContext.sessionStorage?.read( `\\..\\user_files\\${name}`, "base64", ); diff --git a/ts/packages/dispatcher/dispatcher/src/translation/multipleActionSchema.ts b/ts/packages/dispatcher/dispatcher/src/translation/multipleActionSchema.ts index 4571551d81..0dbe0501e7 100644 --- a/ts/packages/dispatcher/dispatcher/src/translation/multipleActionSchema.ts +++ b/ts/packages/dispatcher/dispatcher/src/translation/multipleActionSchema.ts @@ -82,7 +82,7 @@ export function createMultipleActionSchema( const actionRequestEntryType = sc.obj(actionRequestEntryFields); - let requestEntryType = pending + const requestEntryType = pending ? sc.union( actionRequestEntryType, sc.obj({ diff --git a/ts/packages/dispatcher/dispatcher/src/validation/collisionDetector.mts b/ts/packages/dispatcher/dispatcher/src/validation/collisionDetector.mts index 71c5ee72e4..b1ed1ee01f 100644 --- a/ts/packages/dispatcher/dispatcher/src/validation/collisionDetector.mts +++ b/ts/packages/dispatcher/dispatcher/src/validation/collisionDetector.mts @@ -108,7 +108,7 @@ export class CollisionDetector { private patternToExamples(pattern: string): string[] { const examples: string[] = []; - let example = pattern.replace(/\$\([^)]+\)/g, (match) => { + const example = pattern.replace(/\$\([^)]+\)/g, (match) => { const parts = match.match(/\$\((\w+):(\w+)\)/); if (parts) { const [, paramName, paramType] = parts; @@ -119,7 +119,7 @@ export class CollisionDetector { examples.push(example); - let exampleWithArticle = pattern.replace(/\$\([^)]+\)/g, (match) => { + const exampleWithArticle = pattern.replace(/\$\([^)]+\)/g, (match) => { const parts = match.match(/\$\((\w+):(\w+)\)/); if (parts) { const [, paramName, paramType] = parts; diff --git a/ts/packages/interactiveApp/src/InteractiveIo.ts b/ts/packages/interactiveApp/src/InteractiveIo.ts index 6778d68b9c..9e21479cbf 100644 --- a/ts/packages/interactiveApp/src/InteractiveIo.ts +++ b/ts/packages/interactiveApp/src/InteractiveIo.ts @@ -217,10 +217,10 @@ export class ConsoleWriter { if (sort) { keys.sort(); } - let maxLength = this.getMaxLength(keys); + const maxLength = this.getMaxLength(keys); for (const key of keys) { - let value = record[key]; - let strValues = stringifyValue ? stringifyValue(value) : value; + const value = record[key]; + const strValues = stringifyValue ? stringifyValue(value) : value; if (Array.isArray(strValues) && strValues.length > 0) { this.writeNameValue( key, @@ -284,7 +284,7 @@ export async function askYesNo( io: InteractiveIo, question: string, ): Promise { - let answer = await io.readline.question(`${question} (y/n):`); + const answer = await io.readline.question(`${question} (y/n):`); return answer.trim().toLowerCase() === "y"; } @@ -305,7 +305,7 @@ export class ProgressBar { next = this.total; } this.count = next; - let progressText = `[${this.count} / ${this.total}]`; + const progressText = `[${this.count} / ${this.total}]`; this.writer.writeInline(progressText, this._lastText); this._lastText = progressText; } diff --git a/ts/packages/interactiveApp/src/interactiveApp.ts b/ts/packages/interactiveApp/src/interactiveApp.ts index 0ec777c03d..a96e92a0ec 100644 --- a/ts/packages/interactiveApp/src/interactiveApp.ts +++ b/ts/packages/interactiveApp/src/interactiveApp.ts @@ -400,7 +400,7 @@ export function makeArg( type: ArgType, defaultValue?: any | undefined, ): ArgDef { - let arg: ArgDef = { + const arg: ArgDef = { type, }; if (description) { @@ -480,7 +480,7 @@ export function createNamedArgs(): NamedArgs { type: ArgType, required?: boolean, ): any | undefined { - let value = namedArgs[key]; + const value = namedArgs[key]; if (value === undefined) { if (required) { throw Error( @@ -494,8 +494,8 @@ export function createNamedArgs(): NamedArgs { function bind(argDefs: Record, required: boolean): void { for (const key in argDefs) { - let def = argDefs[key]; - let type = def.type ?? "string"; + const def = argDefs[key]; + const type = def.type ?? "string"; let arg = value(key, type, required); // This will do type conversions if (arg === undefined) { if (def.defaultValue !== undefined) { @@ -570,14 +570,14 @@ export function parseNamedArguments( return args; } const rawArgs = typeof args === "string" ? parseCommandLine(args) : args; - let namedArgs = createNamedArgs(); + const namedArgs = createNamedArgs(); if (!rawArgs) { return namedArgs; } // First, collect all name, value pairs on the command line let name: string | undefined; for (const rawArg of rawArgs) { - var value = rawArg.trim(); + const value = rawArg.trim(); // // Names of arguments have a prefix // @@ -657,7 +657,7 @@ export function namedArgsToArgs( namedArgs: NamedArgs, keyPrefix: string = "--", ): string[] { - let args: string[] = []; + const args: string[] = []; for (const key in namedArgs) { const value = namedArgs[key]; if ( @@ -731,7 +731,7 @@ export async function dispatchCommand( caseSensitive: boolean = false, helpFlags?: string[], ): Promise { - let args = parseCommandLine(cmdLine); + const args = parseCommandLine(cmdLine); if (args) { let commandName = getArg(args, 0); if (commandName === undefined) { @@ -853,7 +853,7 @@ export function searchCommands( handlers: Record, io: InteractiveIo, ): boolean { - let name = getArg(args, 0); + const name = getArg(args, 0); if (!name) { return false; } @@ -1085,7 +1085,7 @@ function displayArgs( args, true, (v: ArgDef) => { - let text = v.description; + const text = v.description; if (v.defaultValue !== undefined) { const defText = `(default): ${v.defaultValue}`; return text ? [text, defText] : defText; @@ -1171,7 +1171,7 @@ function filterCommandsByName( name = name.slice(1); } let matchCount = 0; - let matches: Record = {}; + const matches: Record = {}; for (const key in handlers) { const handlerName = key.toLowerCase(); if ( diff --git a/ts/packages/interactiveApp/src/terminalUI.ts b/ts/packages/interactiveApp/src/terminalUI.ts index 269ecfb9db..8b2aeda2fc 100644 --- a/ts/packages/interactiveApp/src/terminalUI.ts +++ b/ts/packages/interactiveApp/src/terminalUI.ts @@ -1127,7 +1127,7 @@ export class CompletionMenu { // Hide cursor during render this.stream.write(ANSI.hideCursor); - let lines: string[] = []; + const lines: string[] = []; // Header if (this.header) { diff --git a/ts/packages/knowPro/src/answerGenerator.ts b/ts/packages/knowPro/src/answerGenerator.ts index 3bc6692b02..2de233c766 100644 --- a/ts/packages/knowPro/src/answerGenerator.ts +++ b/ts/packages/knowPro/src/answerGenerator.ts @@ -280,7 +280,7 @@ export async function generateAnswerInChunks( return runSingleChunk(chunks[0]); } - let chunkAnswers: answerSchema.AnswerResponse[] = []; + const chunkAnswers: answerSchema.AnswerResponse[] = []; const knowledgeChunks = getKnowledgeChunks(chunks); let hasKnowledgeAnswer = false; if (knowledgeChunks.length > 0) { @@ -425,7 +425,7 @@ export class AnswerGenerator implements IAnswerGenerator { this.settings.maxCharsInBudget, ); } - let prompt: string[] = []; + const prompt: string[] = []; const questionPrompt = createQuestionPrompt(question); prompt.push(questionPrompt); prompt.push( @@ -450,7 +450,7 @@ export class AnswerGenerator implements IAnswerGenerator { partialAnswers: (answerSchema.AnswerResponse | undefined)[], ): Promise> { if (partialAnswers.length === 1) { - let response = partialAnswers[0]; + const response = partialAnswers[0]; if (response) { return success(response); } @@ -516,7 +516,7 @@ export function answerContextFromSearchResult( searchResult: ConversationSearchResult, options?: AnswerContextOptions, ): contextSchema.AnswerContext { - let context: contextSchema.AnswerContext = {}; + const context: contextSchema.AnswerContext = {}; for (const knowledgeType of searchResult.knowledgeMatches.keys()) { switch (knowledgeType) { default: @@ -557,14 +557,14 @@ export function getRelevantTopicsForAnswer( searchResult.semanticRefMatches, "topic", ); - let mergedTopics = mergeScoredTopics(scoredEntities, true); + const mergedTopics = mergeScoredTopics(scoredEntities, true); let candidateTopics: Iterable> = mergedTopics.values(); if (topK !== undefined && topK > 0 && mergedTopics.size > topK) { candidateTopics = getTopK(candidateTopics, topK); } const relevantTopics: contextSchema.RelevantKnowledge[] = []; for (const scoredValue of candidateTopics) { - let mergedTopic = scoredValue.item; + const mergedTopic = scoredValue.item; const relevantTopic = createRelevantKnowledge( conversation, mergedTopic.topic, @@ -593,7 +593,7 @@ export function getRelevantEntitiesForAnswer( } const relevantEntities: contextSchema.RelevantKnowledge[] = []; for (const scoredValue of candidateEntities) { - let mergedEntity = scoredValue.item; + const mergedEntity = scoredValue.item; const relevantEntity = createRelevantKnowledge( conversation, mergedToConcreteEntity(mergedEntity), @@ -684,7 +684,7 @@ function createRelevantKnowledge( knowledge: Knowledge, sourceMessageOrdinals?: Iterable, ): contextSchema.RelevantKnowledge { - let relevantKnowledge: contextSchema.RelevantKnowledge = { + const relevantKnowledge: contextSchema.RelevantKnowledge = { knowledge, }; if (sourceMessageOrdinals) { @@ -708,7 +708,7 @@ function createRelevantKnowledge( } function createQuestionPrompt(question: string): string { - let prompt: string[] = [ + const prompt: string[] = [ "The following is a user question:", "===", question, diff --git a/ts/packages/knowPro/src/collections.ts b/ts/packages/knowPro/src/collections.ts index a08b212064..949f796b1d 100644 --- a/ts/packages/knowPro/src/collections.ts +++ b/ts/packages/knowPro/src/collections.ts @@ -542,7 +542,7 @@ export class MessageAccumulator extends MatchAccumulator { this.addExact(range.start.messageOrdinal, score); if (range.end) { let ordinal = range.start.messageOrdinal + 1; - let endOrdinal = range.end.messageOrdinal; + const endOrdinal = range.end.messageOrdinal; for (; ordinal < endOrdinal; ++ordinal) { this.addExact(ordinal, score); } @@ -598,7 +598,7 @@ export class MessageAccumulator extends MatchAccumulator { public static fromScoredOrdinals( ordinals: ScoredMessageOrdinal[] | undefined, ): MessageAccumulator { - let accumulator = new MessageAccumulator(); + const accumulator = new MessageAccumulator(); if (ordinals && ordinals.length > 0) { accumulator.addScoredMatches(ordinals); } @@ -610,11 +610,11 @@ export function intersectScoredMessageOrdinals( x: ScoredMessageOrdinal[] | undefined, y: ScoredMessageOrdinal[] | undefined, ) { - let xSet = + const xSet = x !== undefined && x.length > 0 ? MessageAccumulator.fromScoredOrdinals(x) : undefined; - let ySet = + const ySet = y !== undefined && y?.length > 0 ? MessageAccumulator.fromScoredOrdinals(y) : undefined; @@ -665,7 +665,7 @@ export class TextRangeCollection implements Iterable { // Future: merge ranges // Is this text range already in this collection? - let pos = collections.binarySearch( + const pos = collections.binarySearch( this.ranges, textRange, compareTextRange, @@ -883,7 +883,7 @@ function* union( ): IterableIterator { const x: Iterator = Array.isArray(xArray) ? xArray.values() : xArray; const y: Iterator = Array.isArray(yArray) ? yArray.values() : yArray; - let unionSet = new Set(); + const unionSet = new Set(); let xVal = x.next(); while (!xVal.done) { unionSet.add(xVal.value); @@ -924,7 +924,7 @@ export function setIntersect( if (set === undefined) { set = new Set(values); } else { - let intersect = new Set(); + const intersect = new Set(); for (const value of values) { if (set.has(value)) { intersect.add(value); diff --git a/ts/packages/knowPro/src/common.ts b/ts/packages/knowPro/src/common.ts index b1955f3121..8a51e208ec 100644 --- a/ts/packages/knowPro/src/common.ts +++ b/ts/packages/knowPro/src/common.ts @@ -42,7 +42,7 @@ export type Scored = { */ export function compareTextLocation(x: TextLocation, y: TextLocation): number { - let cmp = x.messageOrdinal - y.messageOrdinal; + const cmp = x.messageOrdinal - y.messageOrdinal; if (cmp !== 0) { return cmp; } @@ -67,13 +67,13 @@ export function isInTextRange( ): boolean { // outer start must be <= inner start // inner end must be < outerEnd (which is exclusive) - let cmpStart = compareTextLocation(outerRange.start, innerRange.start); + const cmpStart = compareTextLocation(outerRange.start, innerRange.start); if (outerRange.end === undefined && innerRange.end === undefined) { // Since both ends are undefined, we have an point location, not a range. // Points must be equal return cmpStart == 0; } - let cmpEnd = compareTextLocation( + const cmpEnd = compareTextLocation( // innerRange.end must be < outerRange end innerRange.end ?? innerRange.start, outerRange.end ?? outerRange.start, @@ -97,8 +97,8 @@ export function compareDates(x: Date, y: Date): number { export function isInDateRange(outerRange: DateRange, date: Date): boolean { // outer start must be <= date // date must be <= outer end - let cmpStart = compareDates(outerRange.start, date); - let cmpEnd = + const cmpStart = compareDates(outerRange.start, date); + const cmpEnd = outerRange.end !== undefined ? compareDates(date, outerRange.end) : -1; return cmpStart <= 0 && cmpEnd <= 0; } @@ -113,7 +113,7 @@ export function isPromptSection(value: any): value is PromptSection { } export function flattenResultsArray(results: Result[]): Result { - let data: T[] = []; + const data: T[] = []; for (const result of results) { if (!result.success) { return error(result.message); diff --git a/ts/packages/knowPro/src/compileLib.ts b/ts/packages/knowPro/src/compileLib.ts index 2869ea04cc..08811e1088 100644 --- a/ts/packages/knowPro/src/compileLib.ts +++ b/ts/packages/knowPro/src/compileLib.ts @@ -149,7 +149,7 @@ function validateSearchTerm(term: SearchTerm): string | undefined { } if (term.relatedTerms !== undefined && term.relatedTerms.length > 0) { for (let i = 0; i < term.relatedTerms.length; ++i) { - let relatedTerm = term.relatedTerms[i]; + const relatedTerm = term.relatedTerms[i]; if (!validateTerm(relatedTerm)) { let error = `Invalid related term for:\n${JSON.stringify(term.term)}`; if (i > 0) { diff --git a/ts/packages/knowPro/src/conversationIndex.ts b/ts/packages/knowPro/src/conversationIndex.ts index 3d26bd909d..160d571bea 100644 --- a/ts/packages/knowPro/src/conversationIndex.ts +++ b/ts/packages/knowPro/src/conversationIndex.ts @@ -348,7 +348,7 @@ async function addBatchToSemanticRefIndex( beginIndexing(conversation); const messages = conversation.messages; - let indexingResult: TextIndexingResult = {}; + const indexingResult: TextIndexingResult = {}; const textBatch = batch.map((tl) => { const text = messages.get(tl.messageOrdinal).textChunks[ @@ -572,7 +572,7 @@ export async function addToConversationIndex( ); } if (!indexingResult.semanticRefs?.error && conversation.semanticRefIndex) { - let relatedTerms = [...termsAdded.values()]; + const relatedTerms = [...termsAdded.values()]; termsAdded.clear(); indexingResult.secondaryIndexResults = await addToSecondaryIndexes( conversation, @@ -674,7 +674,7 @@ export function addMessageKnowledgeToSemanticRefIndex( ) { const msg = messages.get(messageOrdinal); const chunkOrdinal = 0; - let knowledge = msg.getKnowledge(); + const knowledge = msg.getKnowledge(); if (knowledge !== undefined) { addKnowledgeToSemanticRefIndex( conversation, diff --git a/ts/packages/knowPro/src/dataFrame/dataFrame.ts b/ts/packages/knowPro/src/dataFrame/dataFrame.ts index 9ed3eae162..720c08692e 100644 --- a/ts/packages/knowPro/src/dataFrame/dataFrame.ts +++ b/ts/packages/knowPro/src/dataFrame/dataFrame.ts @@ -114,7 +114,11 @@ export class DataFrame implements IDataFrame { columnValue: DataFrameValue, compareOp: ComparisonOp, ): DataFrameRow[] | undefined { - let ordinals = this.findRowOrdinals(columnName, columnValue, compareOp); + const ordinals = this.findRowOrdinals( + columnName, + columnValue, + compareOp, + ); const rows = this.getRows(ordinals); return rows.length > 0 ? rows : undefined; } @@ -122,7 +126,7 @@ export class DataFrame implements IDataFrame { public findRows( searchTerms: DataFrameTermGroup, ): DataFrameRow[] | undefined { - let ordinalSet = this.searchBoolean(searchTerms); + const ordinalSet = this.searchBoolean(searchTerms); if (ordinalSet === undefined || ordinalSet.size === 0) { return undefined; } @@ -132,7 +136,7 @@ export class DataFrame implements IDataFrame { public findSources( searchTerms: DataFrameTermGroup, ): RowSourceRef[] | undefined { - let ordinalSet = this.searchBoolean(searchTerms); + const ordinalSet = this.searchBoolean(searchTerms); if (ordinalSet === undefined || ordinalSet.size === 0) { return undefined; } @@ -234,7 +238,7 @@ export class DataFrame implements IDataFrame { private searchOrMax( searchTerms: DataFrameTermGroup, ): Set | undefined { - let matches: MatchAccumulator = new MatchAccumulator(); + const matches: MatchAccumulator = new MatchAccumulator(); for (const term of searchTerms.terms) { for (const ordinal of this.findRowOrdinals( term.columnName, diff --git a/ts/packages/knowPro/src/dataFrame/dataFrameConversation.ts b/ts/packages/knowPro/src/dataFrame/dataFrameConversation.ts index 00d46c73fb..1c42a7235f 100644 --- a/ts/packages/knowPro/src/dataFrame/dataFrameConversation.ts +++ b/ts/packages/knowPro/src/dataFrame/dataFrameConversation.ts @@ -42,13 +42,13 @@ export async function searchConversationWithJoin( rawQuery, ); // Also match any messages with matching data frame columns - let dataFrameMatches = searchDataFrames( + const dataFrameMatches = searchDataFrames( dfConversation.dataFrames, searchTermGroup, options, ); - let joinedMatches = intersectScoredMessageOrdinals( + const joinedMatches = intersectScoredMessageOrdinals( conversationMatches?.messageMatches, dataFrameMatches, ); diff --git a/ts/packages/knowPro/src/dataFrame/dataFrameLangSearch.ts b/ts/packages/knowPro/src/dataFrame/dataFrameLangSearch.ts index 5376f47023..dd6a848e06 100644 --- a/ts/packages/knowPro/src/dataFrame/dataFrameLangSearch.ts +++ b/ts/packages/knowPro/src/dataFrame/dataFrameLangSearch.ts @@ -140,8 +140,8 @@ function facetTermsToSearchTerms( function typeDefForDataFrames(dataFrames: DataFrameCollection): string { let text = "export type KnownFacet = "; - let types: string[] = []; - for (let [dfName, df] of dataFrames) { + const types: string[] = []; + for (const [dfName, df] of dataFrames) { for (const colName of df.columns.keys()) { types.push(`"${dfName}.${colName}"`); } diff --git a/ts/packages/knowPro/src/dataFrame/dataFrameQuery.ts b/ts/packages/knowPro/src/dataFrame/dataFrameQuery.ts index c399dda160..2ee486e315 100644 --- a/ts/packages/knowPro/src/dataFrame/dataFrameQuery.ts +++ b/ts/packages/knowPro/src/dataFrame/dataFrameQuery.ts @@ -88,7 +88,7 @@ export class DataFrameCompiler { return undefined; } this.validateAndPrepareGroups(dfTermGroups); - let termExpressions: MatchDataFrameTermsExpr[] = []; + const termExpressions: MatchDataFrameTermsExpr[] = []; for (const dfTermGroup of dfTermGroups) { termExpressions.push(new MatchDataFrameTermsExpr(dfTermGroup)); } @@ -159,7 +159,7 @@ export function getDataFrameTermGroups( continue; } if (qualifiedColumn !== undefined && columnValue) { - let dataFrame = qualifiedColumn[0]; + const dataFrame = qualifiedColumn[0]; let dfGroup = dfTermGroups.get(dataFrame.name); if (dfGroup === undefined) { dfGroup = { @@ -183,7 +183,7 @@ export function resolveDataFrameColumn( dataFrames: DataFrameCollection, qualifiedColumnName: string, ): [IDataFrame, string] | undefined { - let [dfName, columnName] = getDataFrameAndColumnName(qualifiedColumnName); + const [dfName, columnName] = getDataFrameAndColumnName(qualifiedColumnName); if (dfName) { const dataFrame = dataFrames.get(dfName); if (dataFrame !== undefined) { diff --git a/ts/packages/knowPro/src/fuzzyIndex.ts b/ts/packages/knowPro/src/fuzzyIndex.ts index 7c9e8f4852..98adcd9ba8 100644 --- a/ts/packages/knowPro/src/fuzzyIndex.ts +++ b/ts/packages/knowPro/src/fuzzyIndex.ts @@ -189,9 +189,8 @@ export async function generateTextEmbeddingsForIndex( texts: string | string[], ): Promise> { try { - let embeddings: NormalizedEmbedding[]; const textsToEmbed = Array.isArray(texts) ? texts : [texts]; - embeddings = await generateTextEmbeddingsWithRetry( + const embeddings = await generateTextEmbeddingsWithRetry( embeddingModel, textsToEmbed, ); @@ -206,7 +205,7 @@ export async function addTextToEmbeddingIndex( embeddingModel: TextEmbeddingModel, textToIndex: string[], ): Promise { - let result: ListIndexingResult = { numberCompleted: 0 }; + const result: ListIndexingResult = { numberCompleted: 0 }; const embeddingResult = await generateTextEmbeddingsForIndex( embeddingModel, textToIndex, @@ -227,7 +226,7 @@ export async function addTextBatchToEmbeddingIndex( batchSize: number, eventHandler?: IndexingEventHandlers, ): Promise { - let result: ListIndexingResult = { numberCompleted: 0 }; + const result: ListIndexingResult = { numberCompleted: 0 }; for (const batch of getIndexingBatches(textToIndex, batchSize)) { if ( eventHandler?.onEmbeddingsCreated && diff --git a/ts/packages/knowPro/src/knowledge.ts b/ts/packages/knowPro/src/knowledge.ts index 89bbc4467d..fd06fb41e2 100644 --- a/ts/packages/knowPro/src/knowledge.ts +++ b/ts/packages/knowPro/src/knowledge.ts @@ -62,16 +62,16 @@ export function extractKnowledgeFromTextBatch( } export function mergeTopics(topics: string[]): string[] { - let mergedTopics = new Set(); - for (let topic of topics) { + const mergedTopics = new Set(); + for (const topic of topics) { mergedTopics.add(topic); } return [...mergedTopics.values()]; } export function mergeTags(tags: Tag[]): string[] { - let mergedText = new Set(); - for (let tag of tags) { + const mergedText = new Set(); + for (const tag of tags) { mergedText.add(tag.text); } return [...mergedText.values()]; diff --git a/ts/packages/knowPro/src/knowledgeLib.ts b/ts/packages/knowPro/src/knowledgeLib.ts index bd2bdee447..1c43cc8e2b 100644 --- a/ts/packages/knowPro/src/knowledgeLib.ts +++ b/ts/packages/knowPro/src/knowledgeLib.ts @@ -36,7 +36,7 @@ export function getTopKnowledge( toKnowledge: (item: T) => Knowledge, topK?: number, ) { - let topKValues = + const topKValues = topK !== undefined && topK > 0 ? getTopK(values, topK) : values; const mergedKnowledge: ScoredKnowledge[] = []; @@ -55,7 +55,7 @@ export function* getScoredSemanticRefsFromOrdinals( semanticRefMatches: ScoredSemanticRefOrdinal[], knowledgeType: KnowledgeType, ): IterableIterator> { - for (let semanticRefMatch of semanticRefMatches) { + for (const semanticRefMatch of semanticRefMatches) { const semanticRef = semanticRefs.get( semanticRefMatch.semanticRefOrdinal, ); diff --git a/ts/packages/knowPro/src/knowledgeMerge.ts b/ts/packages/knowPro/src/knowledgeMerge.ts index f0f32ff427..decc65f1ad 100644 --- a/ts/packages/knowPro/src/knowledgeMerge.ts +++ b/ts/packages/knowPro/src/knowledgeMerge.ts @@ -34,7 +34,7 @@ export function getDistinctSemanticRefTopics( semanticRefMatches, "topic", ); - let mergedTopics = mergeScoredTopics(scoredTopics, false); + const mergedTopics = mergeScoredTopics(scoredTopics, false); const mergedKnowledge = getTopKnowledge( mergedTopics.values(), "topic", @@ -54,7 +54,7 @@ export function getDistinctSemanticRefEntities( semanticRefMatches, "entity", ); - let mergedEntities = mergeScoredConcreteEntities(scoredEntities, false); + const mergedEntities = mergeScoredConcreteEntities(scoredEntities, false); const mergedKnowledge: ScoredKnowledge[] = getTopKnowledge( mergedEntities.values(), "entity", @@ -87,8 +87,8 @@ export function mergeScoredTopics( scoredTopics: Iterable>, mergeOrdinals: boolean, ): Map> { - let mergedTopics = new Map>(); - for (let scoredTopic of scoredTopics) { + const mergedTopics = new Map>(); + for (const scoredTopic of scoredTopics) { const topic = scoredTopic.item.knowledge as Topic; let existing = mergedTopics.get(topic.text); if (existing) { @@ -113,8 +113,8 @@ export function mergeScoredConcreteEntities( scoredEntities: Iterable>, mergeOrdinals: boolean, ): Map> { - let mergedEntities = new Map>(); - for (let scoredEntity of scoredEntities) { + const mergedEntities = new Map>(); + for (const scoredEntity of scoredEntities) { const mergedEntity = concreteToMergedEntity( scoredEntity.item.knowledge as kpLib.ConcreteEntity, ); @@ -144,7 +144,7 @@ export function mergeScoredConcreteEntities( export function mergeConcreteEntities( entities: kpLib.ConcreteEntity[], ): kpLib.ConcreteEntity[] { - let mergedEntities = concreteToMergedEntities(entities); + const mergedEntities = concreteToMergedEntities(entities); const mergedConcreteEntities: kpLib.ConcreteEntity[] = []; for (const mergedEntity of mergedEntities.values()) { @@ -161,8 +161,8 @@ function mergeMessageOrdinals(mergedEntity: MergedKnowledge, sr: SemanticRef) { export function concreteToMergedEntities( entities: kpLib.ConcreteEntity[], ): Map { - let mergedEntities = new Map(); - for (let entity of entities) { + const mergedEntities = new Map(); + for (const entity of entities) { const mergedEntity = concreteToMergedEntity(entity); const existing = mergedEntities.get(mergedEntity.name); if (existing) { @@ -175,7 +175,7 @@ export function concreteToMergedEntities( } function concreteToMergedEntity(entity: kpLib.ConcreteEntity): MergedEntity { - let type = [...entity.type]; + const type = [...entity.type]; collections.lowerAndSort(type); return { name: entity.name.toLowerCase(), diff --git a/ts/packages/knowPro/src/knowledgeMergeEx.ts b/ts/packages/knowPro/src/knowledgeMergeEx.ts index 2dfadbef5b..18a6d3dc84 100644 --- a/ts/packages/knowPro/src/knowledgeMergeEx.ts +++ b/ts/packages/knowPro/src/knowledgeMergeEx.ts @@ -34,7 +34,7 @@ export function getDistinctSemanticRefTopics( semanticRefMatches, "topic", ); - let mergedTopics = mergeScoredTopics(scoredTopics, false); + const mergedTopics = mergeScoredTopics(scoredTopics, false); const mergedKnowledge = getTopKnowledge( mergedTopics.values(), "topic", @@ -54,7 +54,7 @@ export function getDistinctSemanticRefEntities( semanticRefMatches, "entity", ); - let mergedEntities = mergeScoredConcreteEntities(scoredEntities, false); + const mergedEntities = mergeScoredConcreteEntities(scoredEntities, false); const mergedKnowledge: ScoredKnowledge[] = getTopKnowledge( mergedEntities.values(), "entity", @@ -85,8 +85,8 @@ export function mergeScoredTopics( scoredTopics: Iterable>, mergeOrdinals: boolean, ): Map> { - let mergedTopics = new Map>(); - for (let scoredTopic of scoredTopics) { + const mergedTopics = new Map>(); + for (const scoredTopic of scoredTopics) { const topic = scoredTopic.item.knowledge as Topic; let existing = mergedTopics.get(topic.text); if (existing) { @@ -117,8 +117,8 @@ export function mergeScoredConcreteEntities( mergeOrdinals: boolean, options?: EntityMergeOptions, ): Map> { - let mergedEntities = new Map>(); - for (let scoredEntity of scoredEntities) { + const mergedEntities = new Map>(); + for (const scoredEntity of scoredEntities) { const concreteEntity = scoredEntity.item .knowledge as kpLib.ConcreteEntity; @@ -153,7 +153,7 @@ export function mergeConcreteEntities( entities: kpLib.ConcreteEntity[], options?: EntityMergeOptions, ): kpLib.ConcreteEntity[] { - let mergedEntities = concreteToMergedEntities(entities, options); + const mergedEntities = concreteToMergedEntities(entities, options); const mergedConcreteEntities: kpLib.ConcreteEntity[] = []; for (const mergedEntity of mergedEntities.values()) { diff --git a/ts/packages/knowPro/src/message.ts b/ts/packages/knowPro/src/message.ts index fade1ffb54..c43abf164e 100644 --- a/ts/packages/knowPro/src/message.ts +++ b/ts/packages/knowPro/src/message.ts @@ -130,7 +130,7 @@ export function textRangesFromMessageOrdinalsMerged( } // Sort ordinals in ascending order messageOrdinals.sort((x, y) => x - y); - let ranges: TextRange[] = []; + const ranges: TextRange[] = []; let rangeStartOrdinal: MessageOrdinal | undefined = messageOrdinals[0]; let rangeEndOrdinal = rangeStartOrdinal; for (let i = 1; i < messageOrdinals.length; ++i) { diff --git a/ts/packages/knowPro/src/messageIndex.ts b/ts/packages/knowPro/src/messageIndex.ts index 6effa9e433..a823873ca2 100644 --- a/ts/packages/knowPro/src/messageIndex.ts +++ b/ts/packages/knowPro/src/messageIndex.ts @@ -80,7 +80,7 @@ export class MessageTextIndex implements IMessageTextEmbeddingIndex { // Collect everything so we can batch efficiently let i = 0; for (const message of messages) { - let messageOrdinal = baseMessageOrdinal + i; + const messageOrdinal = baseMessageOrdinal + i; for ( let chunkOrdinal = 0; chunkOrdinal < message.textChunks.length; @@ -217,7 +217,7 @@ export async function addToMessageIndex( eventHandler?: IndexingEventHandlers, batchSize: number = 8, ): Promise { - let result: ListIndexingResult = { + const result: ListIndexingResult = { numberCompleted: 0, }; if (conversation.secondaryIndexes) { diff --git a/ts/packages/knowPro/src/query.ts b/ts/packages/knowPro/src/query.ts index 9a05b1d28d..5f67626e4f 100644 --- a/ts/packages/knowPro/src/query.ts +++ b/ts/packages/knowPro/src/query.ts @@ -363,7 +363,7 @@ export function lookupTermFiltered( ): ScoredSemanticRefOrdinal[] | undefined { const scoredRefs = semanticRefIndex.lookupTerm(term.text); if (scoredRefs && scoredRefs.length > 0) { - let filtered = scoredRefs.filter((sr) => { + const filtered = scoredRefs.filter((sr) => { const semanticRef = semanticRefs.get(sr.semanticRefOrdinal); const result = filter(semanticRef, sr); return result; @@ -1160,7 +1160,7 @@ export class GetScopeExpr extends QueryOpExpr { } public eval(context: QueryEvalContext): TextRangesInScope { - let rangesInScope = new TextRangesInScope(); + const rangesInScope = new TextRangesInScope(); for (const selector of this.rangeSelectors) { const range = selector.eval(context); if (range) { @@ -1421,7 +1421,7 @@ export class RankMessagesBySimilarityExpr extends QueryOpExpr 0 ) { - let uniqueRelatedForSearchTerm: Term[] = []; + const uniqueRelatedForSearchTerm: Term[] = []; for (const candidateRelatedTerm of searchTerm.relatedTerms) { if (allSearchTerms.has(candidateRelatedTerm)) { // This related term is already a search term @@ -358,7 +358,7 @@ export class TermEmbeddingIndex maxMatches?: number, minScore?: number, ): Promise { - let matches = await this.embeddingIndex.getIndexesOfNearest( + const matches = await this.embeddingIndex.getIndexesOfNearest( text, maxMatches, minScore, diff --git a/ts/packages/knowPro/src/search.ts b/ts/packages/knowPro/src/search.ts index 260920b7c5..99ec4b1d38 100644 --- a/ts/packages/knowPro/src/search.ts +++ b/ts/packages/knowPro/src/search.ts @@ -161,7 +161,7 @@ export async function searchConversationKnowledge( if (!q.isConversationSearchable(conversation)) { return undefined; } - let error = validateSearchTermGroup(searchTermGroup); + const error = validateSearchTermGroup(searchTermGroup); if (error !== undefined) { throw new Error(error); } @@ -420,7 +420,7 @@ class QueryCompiler { filter?: WhenFilter, options?: SearchOptions, ) { - let query = await this.compileQuery(terms, filter, options); + const query = await this.compileQuery(terms, filter, options); const exactMatch = options?.exactMatch ?? false; if (!exactMatch) { @@ -501,7 +501,7 @@ class QueryCompiler { options?: SearchOptions, ) { // Select is a combination of ordinary search terms and property search terms - let [searchTermUsed, selectExpr] = this.compileSearchGroupTerms( + const [searchTermUsed, selectExpr] = this.compileSearchGroupTerms( termGroup, scopeExpr, ); @@ -592,7 +592,7 @@ class QueryCompiler { compiledTerms[0].terms.push(term); } } - let boolExpr = createOp( + const boolExpr = createOp( termExpressions, searchGroup.booleanOp, scopeExpr, @@ -651,7 +651,7 @@ class QueryCompiler { ); } else if (termGroup) { // Treat any actions as inherently scope selecting. - let actionTermsGroup = + const actionTermsGroup = this.getActionTermsFromSearchGroup(termGroup); if (actionTermsGroup !== undefined) { scopeSelectors ??= []; @@ -729,7 +729,7 @@ class QueryCompiler { } private compileWhere(filter: WhenFilter): q.IQuerySemanticRefPredicate[] { - let predicates: q.IQuerySemanticRefPredicate[] = []; + const predicates: q.IQuerySemanticRefPredicate[] = []; if (filter.knowledgeType) { predicates.push(new q.KnowledgeTypePredicate(filter.knowledgeType)); } diff --git a/ts/packages/knowPro/src/searchLang.ts b/ts/packages/knowPro/src/searchLang.ts index a8f977f360..73a49afad7 100644 --- a/ts/packages/knowPro/src/searchLang.ts +++ b/ts/packages/knowPro/src/searchLang.ts @@ -96,7 +96,7 @@ export async function searchConversationWithLanguage( ); debugContext.usedSimilarityFallback.fill(false); } - let fallbackQueryExpr = compileFallbackQuery( + const fallbackQueryExpr = compileFallbackQuery( langQueryResult.data.query, options.compileOptions, langSearchFilter, @@ -427,8 +427,8 @@ class SearchQueryCompiler { public compileSearchFilter( filter: querySchema.SearchFilter, ): SearchSelectExpr { - let searchTermGroup = this.compileTermGroup(filter); - let when = this.compileWhen(filter); + const searchTermGroup = this.compileTermGroup(filter); + const when = this.compileWhen(filter); return { searchTermGroup, when, @@ -645,7 +645,11 @@ class SearchQueryCompiler { const dedupe = this.dedupe; this.dedupe = false; - let termGroup = this.compileActionTerm(actionTerm, true, includeVerbs); + const termGroup = this.compileActionTerm( + actionTerm, + true, + includeVerbs, + ); if ( includeAdditionalEntities && isEntityTermArray(actionTerm.additionalEntities) @@ -963,7 +967,7 @@ class SearchQueryCompiler { public compileSearchFilter2( filter: querySchema2.SearchFilter, ): SearchSelectExpr { - let searchTermGroup = this.compileTermGroup(filter); + const searchTermGroup = this.compileTermGroup(filter); let when = this.compileWhen(filter); if (filter.scopeSubQuery !== undefined) { when = this.compileScopeFilter(filter.scopeSubQuery, when); @@ -987,7 +991,7 @@ class SearchQueryCompiler { filter.entitySearchTerms.length > 0 ) { // Compile entity terms to search structured tags - let termGroup = this.compileEntityTermsForScope( + const termGroup = this.compileEntityTermsForScope( filter.entitySearchTerms, ); when.tagMatchingTerms = termGroup; @@ -1012,7 +1016,7 @@ class SearchQueryCompiler { private compileEntityTermsForScope( entityTerms: querySchema.EntityTerm[], ): SearchTermGroup { - let termGroup = createAndTermGroup(); + const termGroup = createAndTermGroup(); const dedupe = this.dedupe; this.dedupe = false; for (const term of entityTerms) { @@ -1132,7 +1136,7 @@ export async function searchConversationWithLanguage2( ); debugContext.usedSimilarityFallback.fill(false); } - let fallbackQueryExpr = compileFallbackQuery( + const fallbackQueryExpr = compileFallbackQuery( langQueryResult.data.query, options.compileOptions, langSearchFilter, diff --git a/ts/packages/knowPro/src/searchLib.ts b/ts/packages/knowPro/src/searchLib.ts index df4a2e1762..c4a954a4e4 100644 --- a/ts/packages/knowPro/src/searchLib.ts +++ b/ts/packages/knowPro/src/searchLib.ts @@ -52,7 +52,6 @@ export function createPropertySearchTerm( exactMatchValue: boolean = false, ): PropertySearchTerm { let propertyName: KnowledgePropertyName | SearchTerm; - let propertyValue: SearchTerm; // Check if this is one of our well known predefined values switch (name) { default: @@ -69,7 +68,7 @@ export function createPropertySearchTerm( propertyName = name; break; } - propertyValue = createSearchTerm(value); + const propertyValue: SearchTerm = createSearchTerm(value); if (exactMatchValue) { // No related terms should be matched for this term propertyValue.relatedTerms = []; @@ -270,7 +269,7 @@ export function isSearchGroupTerm( } function splitTermValues(term: string, splitChar: string): string[] { - let allTermStrings = kpLib.split(term, splitChar, { + const allTermStrings = kpLib.split(term, splitChar, { trim: true, removeEmpty: true, }); diff --git a/ts/packages/knowPro/src/searchQueryTranslator.ts b/ts/packages/knowPro/src/searchQueryTranslator.ts index 16ebe26ba3..8fc7035d06 100644 --- a/ts/packages/knowPro/src/searchQueryTranslator.ts +++ b/ts/packages/knowPro/src/searchQueryTranslator.ts @@ -102,7 +102,7 @@ export async function searchQueryFromLanguage( promptPreamble?: PromptSection[], ): Promise> { const timeRange = getTimeRangePromptSectionForConversation(conversation); - let queryContext: PromptSection[] = + const queryContext: PromptSection[] = promptPreamble && promptPreamble.length > 0 ? [...promptPreamble, ...timeRange] : timeRange; @@ -128,7 +128,7 @@ export async function searchQueryFromLanguage2( return error("Scoped queries not supported"); } const timeRange = getTimeRangePromptSectionForConversation(conversation); - let queryContext: PromptSection[] = + const queryContext: PromptSection[] = promptPreamble && promptPreamble.length > 0 ? [...promptPreamble, ...timeRange] : timeRange; diff --git a/ts/packages/knowPro/src/secondaryIndexes.ts b/ts/packages/knowPro/src/secondaryIndexes.ts index 24d0fc0024..b955735a68 100644 --- a/ts/packages/knowPro/src/secondaryIndexes.ts +++ b/ts/packages/knowPro/src/secondaryIndexes.ts @@ -43,7 +43,7 @@ export async function buildSecondaryIndexes( conversation.secondaryIndexes ??= new ConversationSecondaryIndexes( conversationSettings, ); - let result: SecondaryIndexingResults = buildTransientSecondaryIndexes( + const result: SecondaryIndexingResults = buildTransientSecondaryIndexes( conversation, conversationSettings, ); @@ -74,7 +74,7 @@ export async function addToSecondaryIndexes( conversation.secondaryIndexes ??= new ConversationSecondaryIndexes( conversationSettings, ); - let result: SecondaryIndexingResults = addToTransientSecondaryIndexes( + const result: SecondaryIndexingResults = addToTransientSecondaryIndexes( conversation, conversationSettings, messageOrdinalStartAt, diff --git a/ts/packages/knowPro/src/serialization.ts b/ts/packages/knowPro/src/serialization.ts index c191534f09..d6c2dc2bdc 100644 --- a/ts/packages/knowPro/src/serialization.ts +++ b/ts/packages/knowPro/src/serialization.ts @@ -62,7 +62,7 @@ export async function readConversationDataFromFile( if (!jsonData) { return undefined; } - let fileData: ConversationFileData = { + const fileData: ConversationFileData = { jsonData: jsonData, binaryData: {}, }; @@ -103,7 +103,7 @@ export async function readConversationDataFromBuffer( return undefined; } let embeddings: Float32Array[] | undefined; - let fileData: ConversationFileData = { + const fileData: ConversationFileData = { jsonData: JSON.parse(jsonData), binaryData: {}, }; @@ -245,7 +245,7 @@ function toConversationFileData( conversationData: IConversationDataWithIndexes, modelMeta?: EmbeddingModelMetadata, ): ConversationFileData { - let fileData: ConversationFileData = { + const fileData: ConversationFileData = { jsonData: { ...conversationData, fileHeader: createFileHeader(), @@ -301,7 +301,7 @@ function fromConversationFileData( fileData: ConversationFileData, ): IConversationDataWithIndexes { // TODO: Remove this temporary backward compat. All future files should have proper headers - let embeddingFileHeader = fileData.jsonData.embeddingFileHeader ?? { + const embeddingFileHeader = fileData.jsonData.embeddingFileHeader ?? { relatedCount: fileData.jsonData.relatedTermsIndexData?.textEmbeddingData ?.textItems.length, diff --git a/ts/packages/knowPro/src/storage.ts b/ts/packages/knowPro/src/storage.ts index a0aaf0ef93..3e63f5d896 100644 --- a/ts/packages/knowPro/src/storage.ts +++ b/ts/packages/knowPro/src/storage.ts @@ -104,7 +104,7 @@ export function* getBatchesFromCollection( ): IterableIterator> { let startAt = startAtOrdinal; while (true) { - let batch = collection.getSlice(startAt, startAt + batchSize); + const batch = collection.getSlice(startAt, startAt + batchSize); if (batch.length === 0) { break; } @@ -117,7 +117,7 @@ export function mapCollection( collection: IReadonlyCollection, callback: (item: T, index: number) => T, ) { - let results: T[] = []; + const results: T[] = []; const length = collection.length; for (let i = 0; i < length; ++i) { results.push(callback(collection.get(i), i)); @@ -129,7 +129,7 @@ export function filterCollection( collection: IReadonlyCollection, predicate: (item: T, index: number) => boolean, ) { - let results: T[] = []; + const results: T[] = []; const length = collection.length; for (let i = 0; i < length; ++i) { const item = collection.get(i); diff --git a/ts/packages/knowPro/src/textLocationIndex.ts b/ts/packages/knowPro/src/textLocationIndex.ts index 707d7e4976..2f8520d40a 100644 --- a/ts/packages/knowPro/src/textLocationIndex.ts +++ b/ts/packages/knowPro/src/textLocationIndex.ts @@ -248,7 +248,7 @@ function createMessageIndexingEventHandler( } export function normalizeTextRange(range: TextRange): TextRange { - let range_n: TextRange = { ...range }; + const range_n: TextRange = { ...range }; if (range_n.start.chunkOrdinal === undefined) { range_n.start.chunkOrdinal = 0; } diff --git a/ts/packages/knowProTest/src/answerTest.ts b/ts/packages/knowProTest/src/answerTest.ts index 2f008aedea..6d830a1718 100644 --- a/ts/packages/knowProTest/src/answerTest.ts +++ b/ts/packages/knowProTest/src/answerTest.ts @@ -73,8 +73,8 @@ export async function verifyQuestionAnswerBatch( cb?: BatchCallback>>, stopOnError: boolean = false, ): Promise[]>> { - let results: SimilarityComparison[] = []; - let questionAnswers = await readJsonFile(batchFilePath); + const results: SimilarityComparison[] = []; + const questionAnswers = await readJsonFile(batchFilePath); if (questionAnswers === undefined || questionAnswers.length === 0) { return error(`${batchFilePath} does not contain QuestionAnswer[]`); } @@ -159,7 +159,7 @@ async function getQuestionAnswer( } function flattenAnswers(answerResponses: kp.AnswerResponse[]) { - let answers: string[] = []; + const answers: string[] = []; for (const answerResponse of answerResponses) { const answer = answerResponse.type === "Answered" diff --git a/ts/packages/knowProTest/src/common.ts b/ts/packages/knowProTest/src/common.ts index cac64f8298..44f72c73c4 100644 --- a/ts/packages/knowProTest/src/common.ts +++ b/ts/packages/knowProTest/src/common.ts @@ -209,7 +209,7 @@ export async function compareObjectFuzzy( threshold = 0.9, ): Promise { // First do a straight compare - let error = compareObject(x, y, label); + const error = compareObject(x, y, label); if (error === undefined) { return undefined; } @@ -320,7 +320,7 @@ export function compareArray( return `${label}: length mismatch`; } for (let i = 0; i < x.length; ++i) { - let error = comparer(x[i], y[i]); + const error = comparer(x[i], y[i]); if (error !== undefined) { return error; } diff --git a/ts/packages/knowProTest/src/knowproCommands.ts b/ts/packages/knowProTest/src/knowproCommands.ts index 785c2bfda3..8509df206f 100644 --- a/ts/packages/knowProTest/src/knowproCommands.ts +++ b/ts/packages/knowProTest/src/knowproCommands.ts @@ -298,7 +298,7 @@ async function getAnswerFromSearchResult( context.answerGenerator.settings.fastStop = request.fastStop; } try { - let question = searchResult.rawSearchQuery ?? request.query; + const question = searchResult.rawSearchQuery ?? request.query; // // Generate an answer from search results // @@ -349,7 +349,7 @@ function createLangFilter( } export function createSearchOptions(request: SearchRequest): kp.SearchOptions { - let options = kp.createSearchOptions(); + const options = kp.createSearchOptions(); options.exactMatch = request.exact; options.maxMessageMatches = request.messageTopK; options.maxCharsInBudget = request.charBudget; @@ -359,7 +359,7 @@ export function createSearchOptions(request: SearchRequest): kp.SearchOptions { function createAnswerOptions( namedArgs: GetAnswerRequest, ): kp.AnswerContextOptions { - let topK = namedArgs.knowledgeTopK; + const topK = namedArgs.knowledgeTopK; if (topK === undefined) { return {}; } diff --git a/ts/packages/knowProTest/src/logging.ts b/ts/packages/knowProTest/src/logging.ts index e55b3638f9..6e9b195249 100644 --- a/ts/packages/knowProTest/src/logging.ts +++ b/ts/packages/knowProTest/src/logging.ts @@ -19,7 +19,7 @@ export class KnowproLog { const timestamp = new Date(); const dirPath = this.ensureCommandLogDir(timestamp, commandName); const fileName = `${dateTime.timestampString(timestamp)}.json`; - let filePath = path.join(dirPath, fileName); + const filePath = path.join(dirPath, fileName); writeObjectToUniqueFile(filePath, obj); } catch (ex) { console.log(`Error while writing log file:\n{ex}`); diff --git a/ts/packages/knowProTest/src/models.ts b/ts/packages/knowProTest/src/models.ts index 4b2cfab0ac..77cf339117 100644 --- a/ts/packages/knowProTest/src/models.ts +++ b/ts/packages/knowProTest/src/models.ts @@ -54,7 +54,7 @@ export function createGpt41Models(): { } export function create35Model(): LanguageModel | undefined { - let modelName = "GPT_35_TURBO"; + const modelName = "GPT_35_TURBO"; if (hasApiSettings(openai.EnvVars.AZURE_OPENAI_API_KEY, modelName)) { return { model: createKnowledgeModel(modelName), modelName }; } diff --git a/ts/packages/knowProTest/src/searchTest.ts b/ts/packages/knowProTest/src/searchTest.ts index eb595fd77c..7d3cd54481 100644 --- a/ts/packages/knowProTest/src/searchTest.ts +++ b/ts/packages/knowProTest/src/searchTest.ts @@ -123,7 +123,7 @@ export async function verifyLangSearchResultsBatch( if (response.success) { const actual = response.data; const error = compareLangSearchResults(actual, expected); - let comparisonResult: Comparison = { + const comparisonResult: Comparison = { actual, expected, error, @@ -237,7 +237,7 @@ function compareLangSearchResults( lr1: LangSearchResults, lr2: LangSearchResults, ): string | undefined { - let error = compareSearchQuery(lr1.searchQueryExpr, lr2.searchQueryExpr); + const error = compareSearchQuery(lr1.searchQueryExpr, lr2.searchQueryExpr); if (error !== undefined && error.length > 0) { return error; } @@ -421,7 +421,7 @@ function compareEntityTerm( return `${label}.name: ${x.name} !== ${y.name}`; } - let error = compareTypes(x.type, y.type, label); + const error = compareTypes(x.type, y.type, label); if (error !== undefined) { return error; } @@ -437,7 +437,7 @@ function compareEntityTerm( yType: string[] | undefined, label: string, ): string | undefined { - let error = compareStringArray(xType, yType, label + ".type"); + const error = compareStringArray(xType, yType, label + ".type"); if (error === undefined) { return undefined; } @@ -558,7 +558,7 @@ export async function compareSearchQueryTranslations( return resultScope; } - let cmp: ScopeQueryComparison = { + const cmp: ScopeQueryComparison = { actual: resultScope.data, expected: result.data, query, diff --git a/ts/packages/knowledgeProcessor/src/conversation/answerGenerator.ts b/ts/packages/knowledgeProcessor/src/conversation/answerGenerator.ts index d325978bc5..1c84d8b2e3 100644 --- a/ts/packages/knowledgeProcessor/src/conversation/answerGenerator.ts +++ b/ts/packages/knowledgeProcessor/src/conversation/answerGenerator.ts @@ -219,7 +219,7 @@ export function createAnswerGenerator( if (trim && contextContent.length > getMaxContextLength()) { contextContent = trimContext(contextContent, getMaxContextLength()); } - let preamble: PromptSection[] = []; + const preamble: PromptSection[] = []; if (settings.contextProvider) { preamble.push( ...(await settings.contextProvider.getSections(question)), diff --git a/ts/packages/knowledgeProcessor/src/conversation/conversation.ts b/ts/packages/knowledgeProcessor/src/conversation/conversation.ts index f035b51376..addabee68b 100644 --- a/ts/packages/knowledgeProcessor/src/conversation/conversation.ts +++ b/ts/packages/knowledgeProcessor/src/conversation/conversation.ts @@ -708,8 +708,8 @@ export async function createConversation( getActionIndex(), ]); const results = createSearchResults(); - for (let filter of filters) { - let topLevelTopicMatches = + for (const filter of filters) { + const topLevelTopicMatches = options.topic && options.topic.useHighLevel && options.topicLevel === 1 diff --git a/ts/packages/knowledgeProcessor/src/conversation/conversationManager.ts b/ts/packages/knowledgeProcessor/src/conversation/conversationManager.ts index f150870f16..2797bad79f 100644 --- a/ts/packages/knowledgeProcessor/src/conversation/conversationManager.ts +++ b/ts/packages/knowledgeProcessor/src/conversation/conversationManager.ts @@ -218,7 +218,7 @@ export async function createConversationManager( createKnowledgeExtractorSettings(), ); - let topicMerger = await createMerger(); + const topicMerger = await createMerger(); const searchProcessor = createSearchProcessor( conversation, @@ -500,7 +500,7 @@ export async function addMessageToConversation( const messageIndex = await conversation.getMessageIndex(); await messageIndex.put(messageBlock.value, messageBlock.blockId); - let extractedKnowledge = await extractKnowledgeFromMessage( + const extractedKnowledge = await extractKnowledgeFromMessage( knowledgeExtractor, message, messageBlock, @@ -671,7 +671,7 @@ function getMessageHeaderAndText( if (typeof message.text === "string") { return message.header + "\n\n" + message.text; } - let textBlock: TextBlock = { + const textBlock: TextBlock = { type: message.text.type, value: message.header + "\n\n" + message.text.value, sourceIds: message.text.sourceIds, diff --git a/ts/packages/knowledgeProcessor/src/conversation/entities.ts b/ts/packages/knowledgeProcessor/src/conversation/entities.ts index e5c0765a68..38467b6b73 100644 --- a/ts/packages/knowledgeProcessor/src/conversation/entities.ts +++ b/ts/packages/knowledgeProcessor/src/conversation/entities.ts @@ -451,7 +451,7 @@ export async function createEntityIndexOnStorage( ), ]); entityIdHitTable.roundScores(2); - let entityIdHits = entityIdHitTable + const entityIdHits = entityIdHitTable .getTopK(determineTopK(options)) .sort(); @@ -590,7 +590,7 @@ export function mergeCompositeEntities( entities: Iterable, ): Map> { const merged = new Map>(); - for (let entity of entities) { + for (const entity of entities) { const existing = merged.get(entity.name); if (existing) { if (appendCompositeEntity(existing.value, entity)) { @@ -688,7 +688,7 @@ export function entityFromRecord( type: string, record: Record, ): ConcreteEntity { - let entity: ConcreteEntity = { + const entity: ConcreteEntity = { name: `${ns}:${name}`, type: [`${ns}:${type}`], }; diff --git a/ts/packages/knowledgeProcessor/src/conversation/hierarchicalTopics.ts b/ts/packages/knowledgeProcessor/src/conversation/hierarchicalTopics.ts index c56ba51d25..f2cd72f84a 100644 --- a/ts/packages/knowledgeProcessor/src/conversation/hierarchicalTopics.ts +++ b/ts/packages/knowledgeProcessor/src/conversation/hierarchicalTopics.ts @@ -56,7 +56,7 @@ export function createHierarchicalTopicExtractor( } // Step 2: Use existing TopicExtractor to find higher-level themes - let aggregatedTopics: string[] = []; + const aggregatedTopics: string[] = []; if (topicExtractor) { const mergeResult = await topicExtractor.mergeTopics( allTopics, diff --git a/ts/packages/knowledgeProcessor/src/conversation/interactive.ts b/ts/packages/knowledgeProcessor/src/conversation/interactive.ts index d2bf916ca5..ed360b9e98 100644 --- a/ts/packages/knowledgeProcessor/src/conversation/interactive.ts +++ b/ts/packages/knowledgeProcessor/src/conversation/interactive.ts @@ -36,7 +36,7 @@ export async function interactivelyProcessUserInput( ) => Promise, maxAttempts: number = 3, ): Promise | undefined> { - let previousUserInputs: PromptSection[] = []; + const previousUserInputs: PromptSection[] = []; let userInput: string | undefined = initialUserInput; let attempt = 1; while (true) { diff --git a/ts/packages/knowledgeProcessor/src/conversation/knowledge.ts b/ts/packages/knowledgeProcessor/src/conversation/knowledge.ts index 580e73dce9..78de0bbe4e 100644 --- a/ts/packages/knowledgeProcessor/src/conversation/knowledge.ts +++ b/ts/packages/knowledgeProcessor/src/conversation/knowledge.ts @@ -251,7 +251,7 @@ export async function extractKnowledgeFromBlock( if (message.value.length === 0) { return undefined; } - let knowledge = await extractor.extract(messageText); + const knowledge = await extractor.extract(messageText); if (!knowledge) { return undefined; } @@ -317,8 +317,8 @@ export function mergeKnowledge( mergeEntities(y.entities, merged); } - let topics = y ? collections.concatArrays(x.topics, y.topics) : x.topics; - let actions = y + const topics = y ? collections.concatArrays(x.topics, y.topics) : x.topics; + const actions = y ? collections.concatArrays(x.actions, y.actions) : x.actions; return { diff --git a/ts/packages/knowledgeProcessor/src/conversation/knowledgeResponseLoss.ts b/ts/packages/knowledgeProcessor/src/conversation/knowledgeResponseLoss.ts index 9220a882d4..121a1a8f56 100644 --- a/ts/packages/knowledgeProcessor/src/conversation/knowledgeResponseLoss.ts +++ b/ts/packages/knowledgeProcessor/src/conversation/knowledgeResponseLoss.ts @@ -225,14 +225,14 @@ async function facetLoss( model: TextEmbeddingModel, ) { let loss = 0; - let potentialNameLoss = 2; - let potentialValueLoss = 1; - let potentialLoss = potentialNameLoss + potentialValueLoss; + const potentialNameLoss = 2; + const potentialValueLoss = 1; + const potentialLoss = potentialNameLoss + potentialValueLoss; if (!refFacets || refFacets.length === 0) { console.log("no ref facets"); return 0; } - let potentialLossTotal = refFacets.length * potentialLoss; + const potentialLossTotal = refFacets.length * potentialLoss; const genMap = await createSemanticMap(model); genMap.setMultiple(genFacets.map((facet) => [facet.name, facet])); for (const facet of refFacets) { @@ -457,17 +457,17 @@ class NormalizedKnowledgeResponse { const potentialTopicLoss = 1; const potentialLossTotal = potentialEntityLoss + potentialActionLoss + potentialTopicLoss; - let genEntityLoss = await entityLoss( + const genEntityLoss = await entityLoss( this.entities, normGenResponse.entities, model, ); - let actionLoss = await actionsLoss( + const actionLoss = await actionsLoss( this.response.actions, normGenResponse.response.actions, model, ); - let topicLoss = await stringListLoss( + const topicLoss = await stringListLoss( this.response.topics, normGenResponse.response.topics, model, diff --git a/ts/packages/knowledgeProcessor/src/conversation/searchProcessor.ts b/ts/packages/knowledgeProcessor/src/conversation/searchProcessor.ts index cbdf241623..7ab87e5e62 100644 --- a/ts/packages/knowledgeProcessor/src/conversation/searchProcessor.ts +++ b/ts/packages/knowledgeProcessor/src/conversation/searchProcessor.ts @@ -152,7 +152,7 @@ export function createSearchProcessor( if (!actionResult.success) { return undefined; } - let action = actionResult.data; + const action = actionResult.data; if (options.progress) { options.progress(action); } diff --git a/ts/packages/knowledgeProcessor/src/conversation/testData.ts b/ts/packages/knowledgeProcessor/src/conversation/testData.ts index 5c110b9bf4..b1a6c3edcb 100644 --- a/ts/packages/knowledgeProcessor/src/conversation/testData.ts +++ b/ts/packages/knowledgeProcessor/src/conversation/testData.ts @@ -39,8 +39,8 @@ export async function searchBatchFile( `${getFileName(filePath)}_result.json`, ); } - let lines = await readAllLines(filePath, undefined, true, true); - let results = await searchBatch(cm, lines, concurrency, progress); + const lines = await readAllLines(filePath, undefined, true, true); + const results = await searchBatch(cm, lines, concurrency, progress); await writeJsonFile(destPath, results); } @@ -50,7 +50,7 @@ export async function searchBatch( concurrency: number, progress?: BatchProgress, ): Promise { - let results = await asyncArray.mapAsync( + const results = await asyncArray.mapAsync( queries, concurrency, async (query, index) => { @@ -94,7 +94,7 @@ export async function compareQueryBatch( concurrency: number, progress?: BatchProgress, ): Promise { - let comparisons = await asyncArray.mapAsync( + const comparisons = await asyncArray.mapAsync( baseLine, concurrency, async (item, index) => { diff --git a/ts/packages/knowledgeProcessor/src/conversation/topics.ts b/ts/packages/knowledgeProcessor/src/conversation/topics.ts index fe42dde34e..e2d3a5472e 100644 --- a/ts/packages/knowledgeProcessor/src/conversation/topics.ts +++ b/ts/packages/knowledgeProcessor/src/conversation/topics.ts @@ -272,7 +272,9 @@ export async function createTopicMerger( ): Promise> { const topicExtractor = createTopicExtractor(model); let childSize: number = await childIndex.sequence.size(); - let recentTopics = createRecentItemsWindow(settings.mergeWindowSize); + const recentTopics = createRecentItemsWindow( + settings.mergeWindowSize, + ); return { settings, next, @@ -328,7 +330,7 @@ export async function createTopicMerger( if (topics.length === 0) { return undefined; } - let topicsResponse = await topicExtractor.mergeTopics( + const topicsResponse = await topicExtractor.mergeTopics( topics, settings.trackRecent ? recentTopics.getUnique() : undefined, ); @@ -595,7 +597,11 @@ export async function createTopicIndexOnStorage< } const topicIds: TopicId[] = []; for (let i = 0; i < topics.length; ++i) { - let id = await add(topics[i], sourceName, ids ? ids[i] : undefined); + const id = await add( + topics[i], + sourceName, + ids ? ids[i] : undefined, + ); topicIds.push(id); } @@ -654,7 +660,7 @@ export async function createTopicIndexOnStorage< rawTerms?: string[] | undefined, possibleIds?: TopicId[] | undefined, ): Promise> { - let results = createSearchResults(); + const results = createSearchResults(); if (filter.timeRange) { results.temporalSequence = await sequence.getEntriesInRange( toStartDate(filter.timeRange.startDate), diff --git a/ts/packages/knowledgeProcessor/src/conversation/transcript.ts b/ts/packages/knowledgeProcessor/src/conversation/transcript.ts index 3c5bb239e3..f763ee9d40 100644 --- a/ts/packages/knowledgeProcessor/src/conversation/transcript.ts +++ b/ts/packages/knowledgeProcessor/src/conversation/transcript.ts @@ -199,7 +199,7 @@ export function splitTranscriptIntoTurns(transcript: string): TranscriptTurn[] { const match = regex.exec(line); if (match && match.groups) { let speaker = match.groups["speaker"]; - let speech = match.groups["speech"]; + const speech = match.groups["speech"]; if (turn) { if (speaker) { turns.push(turn); @@ -291,7 +291,7 @@ export function timestampTranscriptTurns( 0, ); const ticksPerChar = ticksLength / textLength; - for (let turn of turns) { + for (const turn of turns) { turn.timestamp = new Date(startTicks).toISOString(); // Now, we will 'elapse' time .. proportional to length of the text // This assumes that each speaker speaks equally fast... @@ -335,7 +335,7 @@ function assignTurnListeners( for (const turn of turns) { const speaker = getSpeaker(turn); if (speaker) { - let listeners: string[] = []; + const listeners: string[] = []; for (const p of participants) { if (p !== speaker) { listeners.push(p); @@ -383,7 +383,7 @@ export function createTranscriptOverview( metadata: TranscriptMetadata, turns: TranscriptTurn[], ): string { - let participantSet = getTranscriptParticipants(turns); + const participantSet = getTranscriptParticipants(turns); let overview = metadata.name; if (metadata.description) { overview += "\n"; @@ -400,9 +400,9 @@ export function createTranscriptOverview( export function getTranscriptParticipants( turns: TranscriptTurn[], ): Set { - let participantSet = new Set(); + const participantSet = new Set(); for (const turn of turns) { - let speaker = getSpeaker(turn); + const speaker = getSpeaker(turn); if (speaker) { participantSet.add(speaker); } diff --git a/ts/packages/knowledgeProcessor/src/images/image.ts b/ts/packages/knowledgeProcessor/src/images/image.ts index d1ddadefb0..ad38f8cfe5 100644 --- a/ts/packages/knowledgeProcessor/src/images/image.ts +++ b/ts/packages/knowledgeProcessor/src/images/image.ts @@ -639,7 +639,7 @@ export async function loadImageWithKnowledge( // if the image exceeds the supplied limit we'll resize the image to fit under that threshold const fileSize: number = fs.statSync(fileName, {}).size; - let buffer: Buffer = fs.readFileSync(fileName); + const buffer: Buffer = fs.readFileSync(fileName); let smaller_buffer: Buffer | undefined = undefined; if (maxSize != -1 && fileSize > maxSize) { try { diff --git a/ts/packages/knowledgeProcessor/src/modelCache.ts b/ts/packages/knowledgeProcessor/src/modelCache.ts index 0164612135..a8c4ccc098 100644 --- a/ts/packages/knowledgeProcessor/src/modelCache.ts +++ b/ts/packages/knowledgeProcessor/src/modelCache.ts @@ -47,7 +47,7 @@ export function createTextEmbeddingModelWithCache( return modelWithCache; async function generateEmbedding(input: string): Promise> { - let embedding = cache.getEmbedding(input); + const embedding = cache.getEmbedding(input); if (embedding) { return success(embedding); } @@ -65,8 +65,8 @@ export function createTextEmbeddingModelWithCache( let inputBatch: string[] | undefined; // First, grab any embeddings we already have for (let i = 0; i < inputs.length; ++i) { - let input = inputs[i]; - let embedding = cache.getEmbedding(input); + const input = inputs[i]; + const embedding = cache.getEmbedding(input); if (embedding === undefined) { // This one needs embeddings inputBatch ??= []; diff --git a/ts/packages/knowledgeProcessor/src/setOperations.ts b/ts/packages/knowledgeProcessor/src/setOperations.ts index b14d75f315..e3670d3f88 100644 --- a/ts/packages/knowledgeProcessor/src/setOperations.ts +++ b/ts/packages/knowledgeProcessor/src/setOperations.ts @@ -128,7 +128,7 @@ export function* union( ): IterableIterator { const x: Iterator = Array.isArray(xArray) ? xArray.values() : xArray; const y: Iterator = Array.isArray(yArray) ? yArray.values() : yArray; - let unionSet = new Set(); + const unionSet = new Set(); let xVal = x.next(); while (!xVal.done) { unionSet.add(xVal.value); @@ -346,8 +346,8 @@ export function intersectSets( ): Set | undefined { if (x && y) { // xValue is smaller than yValues in size - let xValues = x.size < y.size ? x : y; - let yValues = x.size < y.size ? y : x; + const xValues = x.size < y.size ? x : y; + const yValues = x.size < y.size ? y : x; let result: Set | undefined; for (const val of xValues) { if (yValues.has(val)) { @@ -365,9 +365,9 @@ export function intersectSets( export function unionSets(x?: Set, y?: Set): Set | undefined { if (x && y) { // xValue is smaller than yValues in size - let xValues = x.size < y.size ? x : y; - let yValues = x.size < y.size ? y : x; - let result = new Set(yValues); + const xValues = x.size < y.size ? x : y; + const yValues = x.size < y.size ? y : x; + const result = new Set(yValues); addToSet(result, xValues); return result; } else if (x) { @@ -381,7 +381,7 @@ export function intersectUnionSets( y?: Set, ): Set | undefined { // We can to do this more optimally... - let combined = createHitTable(); + const combined = createHitTable(); if (x) { combined.addMultiple(x.values()); } @@ -580,7 +580,9 @@ export function createHitTable( return []; } // Descending order - let valuesByScore = [...map.values()].sort((x, y) => y.score - x.score); + const valuesByScore = [...map.values()].sort( + (x, y) => y.score - x.score, + ); return valuesByScore; } @@ -593,8 +595,8 @@ export function createHitTable( if (map.size === 0) { return []; } - let maxScore = mathLib.max(map.values(), (v) => v.score)!.score; - let top: T[] = []; + const maxScore = mathLib.max(map.values(), (v) => v.score)!.score; + const top: T[] = []; for (const value of map.values()) { if (value.score === maxScore) { top.push(value.item); @@ -648,7 +650,7 @@ export function createHitTable( } function roundScores(decimalPlace: number): void { - let roundUnit = Math.pow(10, decimalPlace); + const roundUnit = Math.pow(10, decimalPlace); if (roundUnit > 0) { for (const scoredItem of map.values()) { scoredItem.score = diff --git a/ts/packages/knowledgeProcessor/src/temporal.ts b/ts/packages/knowledgeProcessor/src/temporal.ts index 4c7aa4dc04..c45b3ba005 100644 --- a/ts/packages/knowledgeProcessor/src/temporal.ts +++ b/ts/packages/knowledgeProcessor/src/temporal.ts @@ -363,7 +363,7 @@ export function createRecentItemsWindow( } function getContext(maxContextLength: number): string[] { - let sections: string[] = []; + const sections: string[] = []; let totalLength = 0; // Get the range of sections that could be pushed on, NEWEST first for (const item of entries.itemsReverse()) { diff --git a/ts/packages/knowledgeProcessor/src/textChunker.ts b/ts/packages/knowledgeProcessor/src/textChunker.ts index 82ad85d3ca..52375d1b6c 100644 --- a/ts/packages/knowledgeProcessor/src/textChunker.ts +++ b/ts/packages/knowledgeProcessor/src/textChunker.ts @@ -170,7 +170,7 @@ function* blocksToChunks( autoTruncate: boolean = true, ): IterableIterator { const separator = " "; - let blockQueue: TextBlock[] = []; + const blockQueue: TextBlock[] = []; blockQueue.push(...text); let i = 0; @@ -216,7 +216,7 @@ export function* joinTextBlocks( ): IterableIterator { separator ??= " "; let chunk = emptyTextChunk(); - for (let chunkPart of chunks) { + for (const chunkPart of chunks) { if ( chunk.value.length + (chunkPart.value.length + separator.length) > maxCharsPerChunk diff --git a/ts/packages/knowledgeProcessor/src/textIndex.ts b/ts/packages/knowledgeProcessor/src/textIndex.ts index dea625b4cb..7aed8a7863 100644 --- a/ts/packages/knowledgeProcessor/src/textIndex.ts +++ b/ts/packages/knowledgeProcessor/src/textIndex.ts @@ -335,7 +335,7 @@ export async function createTextIndex( text: string, postings?: TSourceId[], ): Promise { - let textId = await entriesFolder.put(text); + const textId = await entriesFolder.put(text); const tasks = []; if (postings && postings.length > 0) { tasks.push(postingFolder.put(postings, textId)); @@ -369,7 +369,7 @@ export async function createTextIndex( if (!existingPostings || existingPostings.length === 0) { return; } - let updatedPostings = collections.removeItemFromArray( + const updatedPostings = collections.removeItemFromArray( existingPostings, postings, ); @@ -416,7 +416,7 @@ export async function createTextIndex( ): Promise { maxMatches ??= 1; // Check exact match first - let postingsExact = await get(value); + const postingsExact = await get(value); let postingsAlias: TSourceId[] | undefined; if (aliases) { // If no exact match, see if matched any (optional) aliases. @@ -513,7 +513,7 @@ export async function createTextIndex( maxMatches ??= 1; // Check exact match first let matchedIds: TextId[] = []; - let exactMatchId = textToId(value); + const exactMatchId = textToId(value); if (exactMatchId) { matchedIds.push(exactMatchId); } @@ -573,13 +573,13 @@ export async function createTextIndex( if (!semanticIndex) { return []; } - let matches = await semanticIndex.nearestNeighbors( + const matches = await semanticIndex.nearestNeighbors( value, maxMatches, minScore, ); // Also do an exact match - let textId: TextId | undefined = textToId(value); + const textId: TextId | undefined = textToId(value); if (textId) { // Remove prior match const pos = matches.findIndex((m) => m.item === textId); diff --git a/ts/packages/knowledgeVisualizer/src/route/visualizationNotifier.ts b/ts/packages/knowledgeVisualizer/src/route/visualizationNotifier.ts index d75ebc2639..110362f082 100644 --- a/ts/packages/knowledgeVisualizer/src/route/visualizationNotifier.ts +++ b/ts/packages/knowledgeVisualizer/src/route/visualizationNotifier.ts @@ -195,7 +195,7 @@ export class VisualizationNotifier { }; public async enumerateLists(): Promise { - let retValue: TypeAgentList = new TypeAgentList( + const retValue: TypeAgentList = new TypeAgentList( "sessions", new Array(), ); @@ -208,7 +208,7 @@ export class VisualizationNotifier { return; // skip sessions starting with '.' } - let newList = new TypeAgentList(n, new Array()); + const newList = new TypeAgentList(n, new Array()); (retValue.items as TypeAgentList[]).push(newList); // get the lists for all sessions @@ -235,7 +235,7 @@ export class VisualizationNotifier { } public async enumerateKnowledge(): Promise { - let retValue: KnowledgeGraph[][] = new Array(); + const retValue: KnowledgeGraph[][] = new Array(); // create levels for (let i = 0; i < 6; i++) { @@ -373,7 +373,7 @@ export class VisualizationNotifier { public async enumerateKnowledgeForHierarchy(): Promise< KnowledgeHierarchy[] > { - let retValue: KnowledgeHierarchy[] = new Array(); + const retValue: KnowledgeHierarchy[] = new Array(); retValue.push({ name: "knowledge.entity", imports: [] }); retValue.push({ name: "knowledge.action", imports: [] }); @@ -410,7 +410,7 @@ export class VisualizationNotifier { if (kk.entities?.length > 0) { kk.entities.map((e) => { - let newE: KnowledgeHierarchy = { + const newE: KnowledgeHierarchy = { name: `knowledge.entities.${e.value.name.replace(".", ",")}`, imports: [ "knowledge.entity", @@ -478,7 +478,7 @@ export class VisualizationNotifier { a.value.params?.map((p) => { if (typeof p === "string") { - let valueImports = [ + const valueImports = [ "knowledge.param", `knowledge.messages.${f}`, ]; @@ -511,7 +511,7 @@ export class VisualizationNotifier { } // the original message that has the aforementioned EATs - let hh: KnowledgeHierarchy = { + const hh: KnowledgeHierarchy = { name: `knowledge.messages.${f}`, imports: new Array("knowledge.message"), }; @@ -522,7 +522,7 @@ export class VisualizationNotifier { } public async enumerateKnowledgeForWordCloud(): Promise { - let retValue: string[] = new Array(); + const retValue: string[] = new Array(); const sessions: string[] = await getInstanceSessionNames(); const lastSession: string = sessions[sessions.length - 1]; diff --git a/ts/packages/knowledgeVisualizer/src/site/visualizations/tangledTree.ts b/ts/packages/knowledgeVisualizer/src/site/visualizations/tangledTree.ts index 4ae6eb0392..649e5d4b4e 100644 --- a/ts/packages/knowledgeVisualizer/src/site/visualizations/tangledTree.ts +++ b/ts/packages/knowledgeVisualizer/src/site/visualizations/tangledTree.ts @@ -24,8 +24,8 @@ export class TangledTree { // precompute level depth levels.forEach((l, i) => l.forEach((n) => (n.level = i))); - var nodes = levels.reduce((a, x) => a.concat(x), []); - var nodes_index = {}; + const nodes = levels.reduce((a, x) => a.concat(x), []); + const nodes_index = {}; nodes.forEach((d) => (nodes_index[d.id] = d)); // objectification @@ -37,13 +37,13 @@ export class TangledTree { // precompute bundles levels.forEach((l, i) => { - var index = {}; + const index = {}; l.forEach((n) => { if (n.parents.length == 0) { return; } - var id = n.parents + const id = n.parents .map((d) => d.id) .sort() .join("-X-"); @@ -63,14 +63,14 @@ export class TangledTree { l.bundles.forEach((b, i) => (b.i = i)); }); - let links = new Array(); + const links = new Array(); nodes.forEach((d) => { d.parents.forEach((p) => links.push({ source: d, bundle: d.bundle, target: p }), ); }); - var bundles = levels.reduce((a, x) => a.concat(x.bundles), []); + const bundles = levels.reduce((a, x) => a.concat(x.bundles), []); // reverse pointer from parent to bundles bundles.forEach((b) => @@ -127,8 +127,8 @@ export class TangledTree { (n) => (n.height = (Math.max(1, n.bundles.length) - 1) * metro_d), ); - var x_offset = padding; - var y_offset = padding; + let x_offset = padding; + let y_offset = padding; levels.forEach((l) => { x_offset += l.bundles.length * bundle_width; y_offset += level_y_padding; @@ -140,7 +140,7 @@ export class TangledTree { }); }); - var i = 0; + let i = 0; levels.forEach((l) => { l.bundles.forEach((b) => { b.x = @@ -166,7 +166,7 @@ export class TangledTree { }); // compress vertical space - var y_negative_offset = 0; + let y_negative_offset = 0; levels.forEach((l) => { y_negative_offset += -min_family_height + @@ -191,7 +191,7 @@ export class TangledTree { l.c2 = c; }); - var layout = { + const layout = { width: max(nodes, (n) => n.x) + node_width + 2 * padding, height: max(nodes, (n) => n.y) + node_height / 2 + 2 * padding, node_height, @@ -227,7 +227,7 @@ export class TangledTree { ${tangleLayout.bundles.map((b, i) => { - let d = b.links + const d = b.links .map( (l) => ` M${l.xt} ${l.yt} @@ -269,9 +269,9 @@ export class TangledTree { public update(data: any): void { console.log(data); - let options = { c: 0, bigc: 0, color: (_, i) => this.color(i) }; + const options = { c: 0, bigc: 0, color: (_, i) => this.color(i) }; try { - let div = document.createElement("div"); + const div = document.createElement("div"); if (data.length > 0) { div.innerHTML = this.renderChart(data, options); diff --git a/ts/packages/kp/src/keywordExtractor.ts b/ts/packages/kp/src/keywordExtractor.ts index 4026cfc779..e3365c0711 100644 --- a/ts/packages/kp/src/keywordExtractor.ts +++ b/ts/packages/kp/src/keywordExtractor.ts @@ -292,7 +292,7 @@ function tokenize(text: string): string[] { // Preserve email addresses and URLs as tokens (bounded quantifiers per email spec limits) const emailPattern = /[\w.+-]{1,254}@[\w.-]{1,253}\.\w{2,63}/g; const emails: string[] = []; - let stripped = text.replace(emailPattern, (match) => { + const stripped = text.replace(emailPattern, (match) => { emails.push(match); return " __EMAIL__ "; }); diff --git a/ts/packages/memory/conversation/src/conversationMemory.ts b/ts/packages/memory/conversation/src/conversationMemory.ts index 04cff62de2..04e37d4ce6 100644 --- a/ts/packages/memory/conversation/src/conversationMemory.ts +++ b/ts/packages/memory/conversation/src/conversationMemory.ts @@ -181,8 +181,8 @@ export class ConversationMemory } // Now, add the message to memory and index it - let messageOrdinalStartAt = this.messages.length; - let semanticRefOrdinalStartAt = this.semanticRefs.length; + const messageOrdinalStartAt = this.messages.length; + const semanticRefOrdinalStartAt = this.semanticRefs.length; this.messages.append(message); try { @@ -463,7 +463,7 @@ function assignMessageRecipients( ) { for (const msg of msgs) { if (msg.metadata.sender) { - let recipients: string[] = []; + const recipients: string[] = []; for (const p of participants) { if (p !== msg.metadata.sender) { recipients.push(p); diff --git a/ts/packages/memory/conversation/src/docImport.ts b/ts/packages/memory/conversation/src/docImport.ts index 8d2560435a..c56f4b215c 100644 --- a/ts/packages/memory/conversation/src/docImport.ts +++ b/ts/packages/memory/conversation/src/docImport.ts @@ -59,7 +59,7 @@ export async function importDocMemoryFromTextFile( type = "md"; break; } - let memory = await importDocMemoryFromText( + const memory = await importDocMemoryFromText( docText, type, maxCharsPerChunk, @@ -232,7 +232,7 @@ export function docPartsFromMarkdown( const parts: DocPart[] = []; for (let i = 0; i < textBlocks.length; ++i) { const kBlock = knowledgeBlocks[i]; - let textBlock = textBlocks[i]; + const textBlock = textBlocks[i]; if (textBlock.length === 0) { // Empty text block continue; diff --git a/ts/packages/memory/conversation/src/emailImport.ts b/ts/packages/memory/conversation/src/emailImport.ts index 1e21b13cab..3cf7bde3b0 100644 --- a/ts/packages/memory/conversation/src/emailImport.ts +++ b/ts/packages/memory/conversation/src/emailImport.ts @@ -67,7 +67,7 @@ export function getLastResponseInEmailThread(emailText: string): string { let firstDelimiterAt = -1; for (const delimiter of delimiters) { - let index = emailText.indexOf(delimiter); + const index = emailText.indexOf(delimiter); if ( index >= 0 && (firstDelimiterAt == -1 || index < firstDelimiterAt) diff --git a/ts/packages/memory/conversation/src/emailMessage.ts b/ts/packages/memory/conversation/src/emailMessage.ts index 5029e21988..aaad0ed5e1 100644 --- a/ts/packages/memory/conversation/src/emailMessage.ts +++ b/ts/packages/memory/conversation/src/emailMessage.ts @@ -108,7 +108,7 @@ export async function loadEmailMessagesFromDir( dirPath: string, ): Promise { const filePaths = ms.getFilePathsInDir(dirPath); - let emails: EmailMessage[] = []; + const emails: EmailMessage[] = []; for (const filePath of filePaths) { const email = await loadEmailMessageFromFile(filePath); if (email) { diff --git a/ts/packages/memory/conversation/src/memory.ts b/ts/packages/memory/conversation/src/memory.ts index 9cd0c80e3d..0af5caed61 100644 --- a/ts/packages/memory/conversation/src/memory.ts +++ b/ts/packages/memory/conversation/src/memory.ts @@ -92,7 +92,7 @@ export function addSynonymsAsAliases( synonyms: TermSynonyms[], ): void { for (const ts of synonyms) { - let relatedTerm: kp.Term = { text: ts.term.toLowerCase() }; + const relatedTerm: kp.Term = { text: ts.term.toLowerCase() }; for (const synonym of ts.relatedTerms) { aliases.addRelatedTerm(synonym.toLowerCase(), relatedTerm); } @@ -196,7 +196,7 @@ export class Message } getKnowledge(): kpLib.KnowledgeResponse | undefined { - let metaKnowledge = this.metadata.getKnowledge(); + const metaKnowledge = this.metadata.getKnowledge(); if (!metaKnowledge) { return this.knowledge; } diff --git a/ts/packages/memory/conversation/src/podcastImport.ts b/ts/packages/memory/conversation/src/podcastImport.ts index 32eab32cfe..b71c78bbe6 100644 --- a/ts/packages/memory/conversation/src/podcastImport.ts +++ b/ts/packages/memory/conversation/src/podcastImport.ts @@ -111,7 +111,7 @@ function assignMessageListeners( ) { for (const msg of msgs) { if (msg.metadata.speaker) { - let listeners: string[] = []; + const listeners: string[] = []; for (const p of participants) { if (p !== msg.metadata.speaker) { listeners.push(p); diff --git a/ts/packages/memory/conversation/src/termParser.ts b/ts/packages/memory/conversation/src/termParser.ts index f69fd5c549..0b78cce9d3 100644 --- a/ts/packages/memory/conversation/src/termParser.ts +++ b/ts/packages/memory/conversation/src/termParser.ts @@ -54,7 +54,7 @@ export class SearchTermParser { } public removeNoise(terms: string[]): string[] { - let cleanTerms = terms.filter( + const cleanTerms = terms.filter( (t) => this.isNumeric(t) || (t.length >= this.minTermLength && !this.noiseTerms.has(t)), diff --git a/ts/packages/memory/conversation/src/transcript.ts b/ts/packages/memory/conversation/src/transcript.ts index 63254107e6..fa09dc73ae 100644 --- a/ts/packages/memory/conversation/src/transcript.ts +++ b/ts/packages/memory/conversation/src/transcript.ts @@ -37,7 +37,7 @@ export function parseTranscript( const match = turnParserRegex.exec(line); if (match && match.groups) { let speaker = match.groups["speaker"]; - let speech = match.groups["speech"]; + const speech = match.groups["speech"]; if (curMsg) { if (speaker) { messages.push(curMsg); @@ -98,7 +98,7 @@ export function timestampMessages( if (ticksLength <= 0) { throw new Error(`${startDate} is not < ${endDate}`); } - let messageLengths = messages.map((m) => messageLength(m)); + const messageLengths = messages.map((m) => messageLength(m)); const textLength: number = messageLengths.reduce( (total: number, l) => total + l, 0, diff --git a/ts/packages/memory/image/src/importImages.ts b/ts/packages/memory/image/src/importImages.ts index 3a79563273..9b0fe16fe3 100644 --- a/ts/packages/memory/image/src/importImages.ts +++ b/ts/packages/memory/image/src/importImages.ts @@ -30,7 +30,7 @@ export async function importImages( recursive: boolean = true, callback?: (text: string, count: number, max: number) => void, ): Promise { - let isDir = isDirectoryPath(imagePath); + const isDir = isDirectoryPath(imagePath); if (!fs.existsSync(imagePath)) { throw Error( diff --git a/ts/packages/memory/storage/src/azSearch/azQuery.ts b/ts/packages/memory/storage/src/azSearch/azQuery.ts index b03534f01b..839cc2ba69 100644 --- a/ts/packages/memory/storage/src/azSearch/azQuery.ts +++ b/ts/packages/memory/storage/src/azSearch/azQuery.ts @@ -57,7 +57,7 @@ export class AzSearchQueryCompiler { } public compileWhenFilter(filter: kp.WhenFilter): string | undefined { - let filterExpressions: string[] = []; + const filterExpressions: string[] = []; if (filter.knowledgeType) { filterExpressions.push( filterCompareExpr( @@ -111,14 +111,14 @@ export class AzSearchQueryCompiler { } private compileSearchTerm(searchTerm: kp.SearchTerm): string { - let searchExpr = queryPhraseExpr(searchTerm.term); + const searchExpr = queryPhraseExpr(searchTerm.term); if ( searchTerm.relatedTerms === undefined || searchTerm.relatedTerms.length === 0 ) { return searchExpr; } - let searchExprGroup: string[] = [searchExpr]; + const searchExprGroup: string[] = [searchExpr]; for (const relatedTerm of searchTerm.relatedTerms) { searchExprGroup.push(queryPhraseExpr(relatedTerm)); } diff --git a/ts/packages/memory/storage/src/azSearch/azSearchIndex.ts b/ts/packages/memory/storage/src/azSearch/azSearchIndex.ts index 826d264a4f..3d2045da5b 100644 --- a/ts/packages/memory/storage/src/azSearch/azSearchIndex.ts +++ b/ts/packages/memory/storage/src/azSearch/azSearchIndex.ts @@ -36,7 +36,7 @@ export class AzSearchIndex { searchOptions, ); // Search returns a paging iterator. Collect all results - let results: azSearch.SearchResult[] = []; + const results: azSearch.SearchResult[] = []; for await (const result of searchResults.results) { results.push(result); } diff --git a/ts/packages/memory/storage/src/azSearch/azSemanticRefIndex.ts b/ts/packages/memory/storage/src/azSearch/azSemanticRefIndex.ts index d1b806db38..d92fdf5406 100644 --- a/ts/packages/memory/storage/src/azSearch/azSemanticRefIndex.ts +++ b/ts/packages/memory/storage/src/azSearch/azSemanticRefIndex.ts @@ -63,11 +63,11 @@ export class AzSemanticRefIndex extends AzSearchIndex { matches: azSearch.SearchResult[], ] > { - let searchQuery = + const searchQuery = typeof query === "string" ? query : this.queryCompiler.compileSearchTermGroup(query); - let filter = when + const filter = when ? this.queryCompiler.compileWhenFilter(when) : undefined; //orderby: "search.score() desc" @@ -77,7 +77,7 @@ export class AzSemanticRefIndex extends AzSearchIndex { if (filter) { searchOptions.filter = filter; } - let results = await this.getSearchResults(searchQuery, searchOptions); + const results = await this.getSearchResults(searchQuery, searchOptions); return [{ searchQuery, filter }, results]; } @@ -89,7 +89,7 @@ export class AzSemanticRefIndex extends AzSearchIndex { matches: azSearch.SearchResult[], ] > { - let searchQuery = when.scopeDefiningTerms + const searchQuery = when.scopeDefiningTerms ? this.queryCompiler.compileSearchTermGroup(when.scopeDefiningTerms) : ""; const filter = this.queryCompiler.compileWhenFilter(when); @@ -104,7 +104,7 @@ export class AzSemanticRefIndex extends AzSearchIndex { if (filter) { searchOptions.filter = filter; } - let results = await this.getSearchResults(searchQuery, searchOptions); + const results = await this.getSearchResults(searchQuery, searchOptions); return [{ searchQuery, filter }, results]; } @@ -159,7 +159,7 @@ export function semanticRefToHeader( timestamp?: string, ): SemanticRefHeader { const range = kp.normalizeTextRange(sr.range); - let header: SemanticRefHeader = { + const header: SemanticRefHeader = { kType: sr.knowledgeType, semanticRefOrdinal: semanticRefOrdinalToKey(sr.semanticRefOrdinal), start: range.start, diff --git a/ts/packages/memory/storage/src/fileSystem.ts b/ts/packages/memory/storage/src/fileSystem.ts index 8f0c53a07c..7a39bfdf2a 100644 --- a/ts/packages/memory/storage/src/fileSystem.ts +++ b/ts/packages/memory/storage/src/fileSystem.ts @@ -48,8 +48,7 @@ export function readJsonFile( validator?: ((obj: any) => T) | undefined, ): T | undefined { try { - let json; - json = fs.readFileSync(filePath, { + const json = fs.readFileSync(filePath, { encoding: "utf-8", }); if (json.length > 0) { @@ -79,7 +78,7 @@ export function readAllLines( filePath: string, trim: boolean = true, ): string[] | undefined { - let fileText = readAllText(filePath); + const fileText = readAllText(filePath); if (!fileText) { return undefined; } diff --git a/ts/packages/memory/storage/src/sqlite/sqliteCollection.ts b/ts/packages/memory/storage/src/sqlite/sqliteCollection.ts index a1dc8cbca5..3f3248657e 100644 --- a/ts/packages/memory/storage/src/sqlite/sqliteCollection.ts +++ b/ts/packages/memory/storage/src/sqlite/sqliteCollection.ts @@ -65,7 +65,7 @@ export class SqliteCollection } public getMultiple(ordinals: TOrdinal[]): T[] { - let actualOrdinals: number[] = new Array(ordinals.length); + const actualOrdinals: number[] = new Array(ordinals.length); for (let i = 0; i < ordinals.length; ++i) { actualOrdinals[i] = ordinals[i] + 1; } diff --git a/ts/packages/memory/storage/src/sqlite/sqliteDataFrame.ts b/ts/packages/memory/storage/src/sqlite/sqliteDataFrame.ts index c0f4028678..904ab700b0 100644 --- a/ts/packages/memory/storage/src/sqlite/sqliteDataFrame.ts +++ b/ts/packages/memory/storage/src/sqlite/sqliteDataFrame.ts @@ -49,7 +49,7 @@ export class SqliteDataFrame implements kp.dataFrame.IDataFrame { op: kp.ComparisonOp, ): kp.dataFrame.DataFrameRow[] | undefined { const stmt = this.sqlGet(columnName, op); - let rows: kp.dataFrame.DataFrameRow[] = []; + const rows: kp.dataFrame.DataFrameRow[] = []; for (const row of stmt.iterate(columnValue)) { rows.push(this.toDataFrameRow(row)); } @@ -79,7 +79,7 @@ export class SqliteDataFrame implements kp.dataFrame.IDataFrame { } public *[Symbol.iterator](): Iterator { - for (let row of this.sql_getAll.iterate()) { + for (const row of this.sql_getAll.iterate()) { const value = this.toDataFrameRow(row); if (value !== undefined) { yield value; @@ -110,7 +110,7 @@ export class SqliteDataFrame implements kp.dataFrame.IDataFrame { } private getAddValues(row: kp.dataFrame.DataFrameRow) { - let values: any[] = []; + const values: any[] = []; values.push(this.serializeSourceRef(row.sourceRef)); for (const colName of this.recordColumnNames) { values.push(row.record[colName]); @@ -128,7 +128,7 @@ export class SqliteDataFrame implements kp.dataFrame.IDataFrame { } private sqlGet(columnName: string, op: kp.ComparisonOp) { - let sql = `SELECT * from ${this.name} WHERE ${columnName} ${comparisonOpToSql(op)} ?`; + const sql = `SELECT * from ${this.name} WHERE ${columnName} ${comparisonOpToSql(op)} ?`; return this.db.prepare(sql); } @@ -143,12 +143,12 @@ export class SqliteDataFrame implements kp.dataFrame.IDataFrame { } private ensureDb() { - let schemaSql = dataFrameToSchemaSql(this.name, this.columns); + const schemaSql = dataFrameToSchemaSql(this.name, this.columns); if (!schemaSql) { throw new Error(`No schema for Sqlite data frame ${this.name}`); } this.db.exec(schemaSql); - let indexSql = dataFrameToIndexSql(this.name, this.columns); + const indexSql = dataFrameToIndexSql(this.name, this.columns); if (indexSql) { this.db.exec(indexSql); } @@ -163,8 +163,8 @@ export function dataFrameTermGroupToSql( group: kp.dataFrame.DataFrameTermGroup, colDefs: kp.dataFrame.DataFrameColumns, ): string { - let clauses: string[] = []; - for (let searchTerm of group.terms) { + const clauses: string[] = []; + for (const searchTerm of group.terms) { const colDef = colDefs.get(searchTerm.columnName); if (!colDef) { throw new Error( @@ -231,13 +231,13 @@ export function dataFrameToSchemaSql( if (colDefs.size === 0) { return ""; } - let columns: string[] = []; + const columns: string[] = []; columns.push("rowId INTEGER PRIMARY KEY AUTOINCREMENT"); columns.push("sourceRef TEXT NOT NULL"); for (const [columnName, columnDef] of colDefs) { columns.push(columnDefToSchemaSql(columnName, columnDef)); } - let schemaSql = `CREATE TABLE IF NOT EXISTS ${dfName} (\n${columns.join(", \n")}\n)`; + const schemaSql = `CREATE TABLE IF NOT EXISTS ${dfName} (\n${columns.join(", \n")}\n)`; return schemaSql; } @@ -248,7 +248,7 @@ export function dataFrameToIndexSql( if (colDefs.size === 0) { return ""; } - let indexes: string[] = []; + const indexes: string[] = []; for (const [columnName, columnDef] of colDefs) { if (columnDef.index) { indexes.push(columnDefToIndexSql(dfName, columnName, columnDef)); @@ -279,6 +279,6 @@ function columnDefToIndexSql( columnName: string, columnDef: kp.dataFrame.DataFrameColumnDef, ) { - let sql = `CREATE INDEX IF NOT EXISTS idx_${columnName} ON ${dfName} (${columnName});`; + const sql = `CREATE INDEX IF NOT EXISTS idx_${columnName} ON ${dfName} (${columnName});`; return sql; } diff --git a/ts/packages/memory/website/src/importWebsites.ts b/ts/packages/memory/website/src/importWebsites.ts index c63f665e74..66156ddb4a 100644 --- a/ts/packages/memory/website/src/importWebsites.ts +++ b/ts/packages/memory/website/src/importWebsites.ts @@ -180,7 +180,7 @@ export async function importChromeBookmarks( }, ]; - let processedRef = { count: 0 }; + const processedRef = { count: 0 }; const totalEstimate = 1000; // Rough estimate, will be updated for (const { folder, folderPath } of rootFolders) { diff --git a/ts/packages/memory/website/src/websiteMeta.ts b/ts/packages/memory/website/src/websiteMeta.ts index 6c77ed3afe..a61e24a550 100644 --- a/ts/packages/memory/website/src/websiteMeta.ts +++ b/ts/packages/memory/website/src/websiteMeta.ts @@ -344,7 +344,7 @@ export class Website implements kp.IMessage { } public getKnowledge(): kpLib.KnowledgeResponse | undefined { - let metaKnowledge = this.metadata.getKnowledge(); + const metaKnowledge = this.metadata.getKnowledge(); if (!metaKnowledge) { return this.knowledge; } diff --git a/ts/packages/schemaAuthor/src/schema.ts b/ts/packages/schemaAuthor/src/schema.ts index 87494f92cb..093f60a703 100644 --- a/ts/packages/schemaAuthor/src/schema.ts +++ b/ts/packages/schemaAuthor/src/schema.ts @@ -22,7 +22,7 @@ export function loadActionSchema( let schemaText = getTypeSchema(schema, typeName, node.symbol.valueType); const refTypes = getReferencedTypes(node); if (refTypes) { - let refTypesSchema = getSchemaForReferencedTypes(schema, refTypes); + const refTypesSchema = getSchemaForReferencedTypes(schema, refTypes); schemaText = appendBlock(schemaText, refTypesSchema); } return { diff --git a/ts/packages/shell/src/main/chatServer.ts b/ts/packages/shell/src/main/chatServer.ts index 21364b8fa6..702f380a35 100644 --- a/ts/packages/shell/src/main/chatServer.ts +++ b/ts/packages/shell/src/main/chatServer.ts @@ -78,7 +78,7 @@ export class ChatServer { debug(`Received request: ${request.url}`); // serve up the requested file if we have it - let root: string = path.resolve("out/renderer"); + const root: string = path.resolve("out/renderer"); let requestedFile: string = request.url == "/" || request.url === undefined ? "index.html" diff --git a/ts/packages/shell/src/main/demo.ts b/ts/packages/shell/src/main/demo.ts index 7f49be551a..4504925013 100644 --- a/ts/packages/shell/src/main/demo.ts +++ b/ts/packages/shell/src/main/demo.ts @@ -11,7 +11,7 @@ async function openDemoFile(window: BrowserWindow) { const result = await dialog.showOpenDialog(window, options); if (result && !result.canceled) { - let paths = result.filePaths; + const paths = result.filePaths; if (paths && paths.length > 0) { const content = readFileSync(paths[0], "utf-8").toString(); console.log(content); @@ -161,7 +161,7 @@ export async function runDemo( try { const lines = data.split(/\r?\n/); - for (let line of lines) { + for (const line of lines) { if (getDemoState() === "aborted") break; if (line.startsWith("@pauseForInput")) { setDemoState(chatView, "paused"); @@ -169,7 +169,7 @@ export async function runDemo( if (getDemoState() === "aborted") break; setDemoState(chatView, "running"); } else if (line && !line.startsWith("#")) { - var manualInput = awaitKeyboardInput && !line.startsWith("@"); + const manualInput = awaitKeyboardInput && !line.startsWith("@"); if (manualInput) setDemoState(chatView, "paused"); await sendChatInputText(line, chatView); await getActionCompleteEvent(manualInput); diff --git a/ts/packages/shell/src/main/index.ts b/ts/packages/shell/src/main/index.ts index db63268960..86524659d1 100644 --- a/ts/packages/shell/src/main/index.ts +++ b/ts/packages/shell/src/main/index.ts @@ -253,7 +253,7 @@ async function initialize() { }); if (result && !result.canceled) { - let paths = result.filePaths; + const paths = result.filePaths; if (paths && paths.length > 0) { const content = readFileSync(paths[0], "base64"); shellWindow.chatView.webContents.send( diff --git a/ts/packages/shell/src/main/instance.ts b/ts/packages/shell/src/main/instance.ts index f9ea37da12..1947b02cb6 100644 --- a/ts/packages/shell/src/main/instance.ts +++ b/ts/packages/shell/src/main/instance.ts @@ -295,7 +295,6 @@ async function initializeDispatcher( // links instead of the app quitting; a manual retry clears it. let reconnectStopped = false; let lastReconnectError: string | undefined; - let onConnectionLost: (() => void) | undefined; const broadcastReconnect = ( status: ConnectionStatus | undefined, @@ -444,7 +443,7 @@ async function initializeDispatcher( } }; - onConnectionLost = () => { + const onConnectionLost = () => { if (quitting) return; debugShellInit( "Dispatcher connection lost; will attempt to reconnect.", diff --git a/ts/packages/shell/src/preload/webView.ts b/ts/packages/shell/src/preload/webView.ts index 7d0e83d2d1..50cb400122 100644 --- a/ts/packages/shell/src/preload/webView.ts +++ b/ts/packages/shell/src/preload/webView.ts @@ -189,8 +189,8 @@ export async function getTabHTMLFragments( extractText && (typeof compressionModeOrFullSize !== "object" || compressionModeOrFullSize !== CompressionMode.KnowledgeExtraction); - let htmlFragments: any[] = []; - let htmlPromises: Promise[] = []; + const htmlFragments: any[] = []; + const htmlPromises: Promise[] = []; htmlPromises.push( sendScriptAction( @@ -287,7 +287,7 @@ export async function getTabHTMLFragments( export async function sendScriptActionToAllFrames(body: any, timeout?: number) { const frames = [window.top, ...Array.from(window.frames)]; - let htmlPromises: Promise[] = []; + const htmlPromises: Promise[] = []; frames.forEach((frame, index) => { htmlPromises.push( sendScriptAction(body, timeout, frame, index.toString()), @@ -299,7 +299,7 @@ export async function sendScriptActionToAllFrames(body: any, timeout?: number) { async function runBrowserAction(action: any) { let responseObject: any; - let confirmationMessage = "OK"; + const confirmationMessage = "OK"; const actionName = action.actionName ?? action.fullActionName.split(".").at(-1); switch (actionName) { @@ -337,7 +337,7 @@ async function runBrowserAction(action: any) { } async function runSiteAction(schemaName: string, action: any) { - let confirmationMessage = "OK"; + const confirmationMessage = "OK"; switch (schemaName) { case "browser.crossword": { sendScriptAction({ diff --git a/ts/packages/shell/src/renderer/src/localWhisperClient.ts b/ts/packages/shell/src/renderer/src/localWhisperClient.ts index d1bbc21ef8..29c1884f6f 100644 --- a/ts/packages/shell/src/renderer/src/localWhisperClient.ts +++ b/ts/packages/shell/src/renderer/src/localWhisperClient.ts @@ -85,7 +85,7 @@ export class WhisperRecognizer { body: formData, }); - let responseBody = await response.json(); + const responseBody = await response.json(); this.awaitingResult = false; return responseBody.transcription; } diff --git a/ts/packages/shell/src/renderer/src/webSocketAPI.ts b/ts/packages/shell/src/renderer/src/webSocketAPI.ts index 02b5ca8d6a..8de825ec31 100644 --- a/ts/packages/shell/src/renderer/src/webSocketAPI.ts +++ b/ts/packages/shell/src/renderer/src/webSocketAPI.ts @@ -137,9 +137,9 @@ const { } = createDispatcherRpcClient(dispatcherChannel.channel); export async function createWebSocket(autoReconnect: boolean = true) { - let url = window.location; - let protocol = url.protocol.toLowerCase() == "https:" ? "wss" : "ws"; - let port = url.hostname.toLowerCase() == "localhost" ? ":3000" : ""; + const url = window.location; + const protocol = url.protocol.toLowerCase() == "https:" ? "wss" : "ws"; + const port = url.hostname.toLowerCase() == "localhost" ? ":3000" : ""; const endpoint = `${protocol}://${url.hostname}${port}`; diff --git a/ts/packages/studio-service/src/main.ts b/ts/packages/studio-service/src/main.ts index 7764c33336..4941dba73b 100644 --- a/ts/packages/studio-service/src/main.ts +++ b/ts/packages/studio-service/src/main.ts @@ -71,7 +71,6 @@ async function main(): Promise { } main().catch((err) => { - // eslint-disable-next-line no-console console.error(err); process.exit(1); }); diff --git a/ts/packages/telemetry/src/stopWatch.ts b/ts/packages/telemetry/src/stopWatch.ts index ddaf2a7b9c..f73f6518f6 100644 --- a/ts/packages/telemetry/src/stopWatch.ts +++ b/ts/packages/telemetry/src/stopWatch.ts @@ -67,8 +67,8 @@ export class StopWatch { public log(label: string, inSeconds: boolean = true): void { import("chalk").then((chalk) => { - let elapsed = `[${this.elapsedString(inSeconds)}]`; - let text = `${chalk.default.gray(label)} ${chalk.default.green(elapsed)}`; + const elapsed = `[${this.elapsedString(inSeconds)}]`; + const text = `${chalk.default.gray(label)} ${chalk.default.green(elapsed)}`; console.log(text); }); } diff --git a/ts/packages/testLib/src/parse.ts b/ts/packages/testLib/src/parse.ts index 376d5cc7df..015eb811cb 100644 --- a/ts/packages/testLib/src/parse.ts +++ b/ts/packages/testLib/src/parse.ts @@ -14,9 +14,9 @@ export type CommandArgs = { */ export function parseCommandArgs(line: string): CommandArgs { const namedArgsStartAt = line.indexOf("--"); - let argPart = + const argPart = namedArgsStartAt >= 0 ? line.slice(0, namedArgsStartAt) : line; - let namedArgPart = + const namedArgPart = namedArgsStartAt >= 0 ? line.slice(namedArgsStartAt) : undefined; const commandArgs: CommandArgs = {}; diff --git a/ts/packages/textPro/src/html.ts b/ts/packages/textPro/src/html.ts index 23d4714de5..56877e254a 100644 --- a/ts/packages/textPro/src/html.ts +++ b/ts/packages/textPro/src/html.ts @@ -489,7 +489,7 @@ export class HtmlToMdConvertor { } break; case "text": - let text = this.getInnerText(child); + const text = this.getInnerText(child); if (text && text.length > 0) { this.append(text); } @@ -586,7 +586,7 @@ export class HtmlToMdConvertor { this.appendInnerMarkup(child); break; case "text": - let text = this.getInnerText(child); + const text = this.getInnerText(child); if (text && text.length > 0) { this.append(text); } @@ -672,7 +672,7 @@ export class HtmlToMdConvertor { } private append(text: string): void { - let markdownText = escapeMarkdownText(text); + const markdownText = escapeMarkdownText(text); this.curBlock += markdownText; } @@ -709,7 +709,7 @@ export class HtmlToMdConvertor { } private appendInnerList(element: cheerio.Element): void { - let list = this.$(element).find("li"); + const list = this.$(element).find("li"); list.each((i, li) => { if (i > 0) { this.appendMarkup(", "); @@ -729,7 +729,7 @@ export class HtmlToMdConvertor { } private convertTable(element: cheerio.Element): void { - let tbody = this.$(element).find("tbody"); + const tbody = this.$(element).find("tbody"); if (tbody !== undefined) { element = tbody[0]; } diff --git a/ts/packages/textPro/src/markdown.ts b/ts/packages/textPro/src/markdown.ts index 9066d08f48..413f833705 100644 --- a/ts/packages/textPro/src/markdown.ts +++ b/ts/packages/textPro/src/markdown.ts @@ -51,7 +51,7 @@ export function markdownToTextBlocks( ): string[] { const markdownTokens = typeof markdown === "string" ? markdownTokenize(markdown) : markdown; - let textBlocks: string[] = []; + const textBlocks: string[] = []; let curTextBlock = ""; let prevBlockName: string = ""; let prevTokenName: string = ""; @@ -631,7 +631,7 @@ function splitList( let curListStartAt = 0; let curListLength = 0; for (let i = 0; i < listItems.length; ++i) { - let itemLength = listItems[i].raw.length; + const itemLength = listItems[i].raw.length; if (curListLength + itemLength > maxCharsPerChunk) { lists.push( listItemsToList(list, listItems.slice(curListStartAt, i)), diff --git a/ts/packages/typeagent-core/src/runtime/studioRuntimeCore.ts b/ts/packages/typeagent-core/src/runtime/studioRuntimeCore.ts index e0529f34d6..bb3a9bbd83 100644 --- a/ts/packages/typeagent-core/src/runtime/studioRuntimeCore.ts +++ b/ts/packages/typeagent-core/src/runtime/studioRuntimeCore.ts @@ -1216,7 +1216,7 @@ export function createStudioRuntimeCore( // that no longer resolves) shouldn't block the // rest. Log to the extension host console; the // surviving sandboxes still come back. - // eslint-disable-next-line no-console + console.warn( `[typeagent-studio] Failed to restore sandbox '${entry.id}':`, err, diff --git a/ts/packages/typeagent-studio/src/extension.ts b/ts/packages/typeagent-studio/src/extension.ts index e13a3c3d82..4398cfa0ac 100644 --- a/ts/packages/typeagent-studio/src/extension.ts +++ b/ts/packages/typeagent-studio/src/extension.ts @@ -629,7 +629,7 @@ export function activate(context: vscode.ExtensionContext): void { treeDataProvider: collisions, }); - let currentCollisionsSource: CollisionsSource = collisionsChannelSource; + const currentCollisionsSource: CollisionsSource = collisionsChannelSource; // Shared scan flow used by both the manual command and the auto-scan // Auto-scan debounce state, declared before the shared scan helper so an diff --git a/ts/packages/typeagent-studio/src/studioServiceLauncher.ts b/ts/packages/typeagent-studio/src/studioServiceLauncher.ts index 52f6d777f5..ad72dc6e96 100644 --- a/ts/packages/typeagent-studio/src/studioServiceLauncher.ts +++ b/ts/packages/typeagent-studio/src/studioServiceLauncher.ts @@ -97,14 +97,12 @@ function spawnService(repoRoot: string): Promise { env: { ...process.env, ELECTRON_RUN_AS_NODE: "1" }, }); } catch (err) { - // eslint-disable-next-line no-console console.warn("[typeagent-studio] failed to spawn service:", err); settle(undefined); return; } child.unref(); child.on("error", (err) => { - // eslint-disable-next-line no-console console.warn("[typeagent-studio] service spawn error:", err); settle(undefined); }); diff --git a/ts/packages/typeagent/src/arrayAsync.ts b/ts/packages/typeagent/src/arrayAsync.ts index 95ddafdbd5..917c617d77 100644 --- a/ts/packages/typeagent/src/arrayAsync.ts +++ b/ts/packages/typeagent/src/arrayAsync.ts @@ -63,8 +63,8 @@ export async function mapAsync( if (progress) { let stop = false; for (let s = 0; s < sliceResults.length; ++s) { - let index = s + i; - let r = progress(array[index], index, results[index]); + const index = s + i; + const r = progress(array[index], index, results[index]); if (shouldStop(r)) { stop = true; } @@ -125,8 +125,8 @@ export async function forEachAsync( if (progress) { let stop = false; for (let s = 0; s < slice.length; ++s) { - let index = s + i; - let r = progress(array[index], index); + const index = s + i; + const r = progress(array[index], index); if (shouldStop(r)) { stop = true; } diff --git a/ts/packages/typeagent/src/lib/array.ts b/ts/packages/typeagent/src/lib/array.ts index d604aa6722..5abfdc9d9b 100644 --- a/ts/packages/typeagent/src/lib/array.ts +++ b/ts/packages/typeagent/src/lib/array.ts @@ -136,7 +136,7 @@ export function getInRange( compareFn: (x: any, y: any) => number, ): any[] { - let startIndex = binarySearchFirst( + const startIndex = binarySearchFirst( values, startAt, compareFn, @@ -192,7 +192,7 @@ export function removeItemFromArray(array: T[], items: T | T[]): T[] { } export function mapAndFilter(array: T[], callbackfn: (value: T, index: number, array: T[]) => R | undefined, predicate?: (value: R, index: number, array: T[]) => boolean): R[] { - let results: R[] = []; + const results: R[] = []; for (let i = 0; i < array.length; ++i) { const result = callbackfn(array[i], i, array); if (!result || (predicate && !predicate(result, i, array))) { diff --git a/ts/packages/typeagent/src/lib/linkedList.ts b/ts/packages/typeagent/src/lib/linkedList.ts index 5afd83c6bf..b482d4a3e5 100644 --- a/ts/packages/typeagent/src/lib/linkedList.ts +++ b/ts/packages/typeagent/src/lib/linkedList.ts @@ -25,7 +25,7 @@ export function detachListNode(node: ListNode): void { export function appendListNode(node: ListNode, appendNode: ListNode): void { assert(isSingleton(node)); - var next = node.next; + const next = node.next; if (next) { next.prev = appendNode; appendNode.next = next; @@ -46,7 +46,7 @@ export function isTail(node: ListNode): boolean { export function prependListNode(node: ListNode, prependNode: ListNode): void { assert(isSingleton(prependNode)); - var prev = node.prev; + const prev = node.prev; if (prev) { prev.next = prependNode; prependNode.prev = prev; diff --git a/ts/packages/typeagent/src/lib/strings.ts b/ts/packages/typeagent/src/lib/strings.ts index be0b2e9b3c..b11ed2e58d 100644 --- a/ts/packages/typeagent/src/lib/strings.ts +++ b/ts/packages/typeagent/src/lib/strings.ts @@ -120,7 +120,7 @@ export function* getStringChunks( } export function stringsToSet(values: string, separator: string= ","): Set { - let keys = values.split(separator); + const keys = values.split(separator); const set = new Set(); for (let key of keys) { key = key.trim(); diff --git a/ts/packages/typeagent/src/objStream.ts b/ts/packages/typeagent/src/objStream.ts index 3b9a95b0c1..a774de3344 100644 --- a/ts/packages/typeagent/src/objStream.ts +++ b/ts/packages/typeagent/src/objStream.ts @@ -136,7 +136,7 @@ export async function* readLines( * @returns array of objects */ export async function readAllObjects(input: Readable | Path): Promise { - let items: T[] = []; + const items: T[] = []; for await (const obj of readObjects(input)) { items.push(obj); } @@ -152,7 +152,7 @@ async function writeObjectsImpl( ): Promise { const writer = await createWriteStream(filePath, mode); try { - for (let obj of objects) { + for (const obj of objects) { await writeObject(writer, obj); } } finally { @@ -369,8 +369,8 @@ export async function writeJsonFiles( await ensureDir(destFolderPath); const padLength = objects.length.toString().length; for (let i = 0; i < objects.length; ++i) { - let fileId = (i + 1).toString().padStart(padLength, "0"); - let turnFilePath = path.join( + const fileId = (i + 1).toString().padStart(padLength, "0"); + const turnFilePath = path.join( destFolderPath, `${baseFileName}_${fileId}.json`, ); @@ -554,7 +554,7 @@ export function toJsonLines(objects: any[]): string { } let json = ""; - for (let obj of objects) { + for (const obj of objects) { json += toJsonLine(obj); } return json; @@ -566,7 +566,7 @@ export function toJsonLines(objects: any[]): string { * @returns */ export function fromJsonLines(lines: string): T[] { - let objects: T[] = []; + const objects: T[] = []; if (lines.length > 0) { for (let json in lines.split(/\r?\n/)) { json = json.trim(); diff --git a/ts/packages/typeagent/src/prompt.ts b/ts/packages/typeagent/src/prompt.ts index bd3e77a899..95aa51b556 100644 --- a/ts/packages/typeagent/src/prompt.ts +++ b/ts/packages/typeagent/src/prompt.ts @@ -166,7 +166,7 @@ export function createPromptBuilder( return pushText(sections); } if (Array.isArray(sections)) { - for (let section of sections) { + for (const section of sections) { if (!push(section)) { return false; } @@ -201,13 +201,13 @@ export function createPromptBuilder( sections: PromptSection[] | IterableIterator, ): boolean { if (Array.isArray(sections)) { - for (let section of sections) { + for (const section of sections) { if (!pushSection(section)) { return false; } } } else { - for (let section of sections) { + for (const section of sections) { if (!pushSection(section)) { return false; } diff --git a/ts/packages/typeagent/src/storage/embeddingFS.ts b/ts/packages/typeagent/src/storage/embeddingFS.ts index 6bd4783ac4..6f92d79774 100644 --- a/ts/packages/typeagent/src/storage/embeddingFS.ts +++ b/ts/packages/typeagent/src/storage/embeddingFS.ts @@ -140,12 +140,12 @@ export async function createEmbeddingFolder( embeddings: Embedding[]; }> { const names: string[] = await folder.allNames(); - let loadedEmbeddings = await asyncArray.mapAsync( + const loadedEmbeddings = await asyncArray.mapAsync( names, concurrency!, (name) => folder.get(name), ); - let embeddings = loadedEmbeddings.filter( + const embeddings = loadedEmbeddings.filter( (e) => e !== undefined, ) as Embedding[]; return { names, embeddings }; diff --git a/ts/packages/typeagent/src/storage/objectFolder.ts b/ts/packages/typeagent/src/storage/objectFolder.ts index 6e19634c11..376b983abc 100644 --- a/ts/packages/typeagent/src/storage/objectFolder.ts +++ b/ts/packages/typeagent/src/storage/objectFolder.ts @@ -165,7 +165,7 @@ export async function createObjectFolder( async function* all(): AsyncIterableIterator> { const names = await fileNames.get(); - for (let name of names) { + for (const name of names) { const value = await get(name); if (value) { yield { name: name, value: value }; @@ -193,7 +193,7 @@ export async function createObjectFolder( // Could not rmdir. Manually delete each object const names = await fileNames.get(); - for (let name of names) { + for (const name of names) { await remove(name); } } @@ -316,7 +316,7 @@ export function generateMonotonicName( ): { name: string | undefined; lastCounter: number } { let counter = counterStartAt; let name: string | undefined; - let maxCounter = 10 ^ maxDigits; + const maxCounter = 10 ^ maxDigits; for (; counter < maxCounter; ++counter) { name = baseName + intString(counter, maxDigits); diff --git a/ts/packages/typeagent/src/storage/objectPage.ts b/ts/packages/typeagent/src/storage/objectPage.ts index d9e0c44987..6b53247993 100644 --- a/ts/packages/typeagent/src/storage/objectPage.ts +++ b/ts/packages/typeagent/src/storage/objectPage.ts @@ -38,7 +38,7 @@ export async function createObjectPage( ): Promise> { const pageSettings = settings ?? {}; const fileSystem = fSys ?? fsDefault(); - let data: T[] = + const data: T[] = (await readObjectFromFile( filePath, pageSettings.deserializer, diff --git a/ts/packages/typeagent/src/storage/workQueue.ts b/ts/packages/typeagent/src/storage/workQueue.ts index 370561b87c..3c0ad7b505 100644 --- a/ts/packages/typeagent/src/storage/workQueue.ts +++ b/ts/packages/typeagent/src/storage/workQueue.ts @@ -45,15 +45,15 @@ export async function createWorkQueueFolder( workItemFilter?: (queuePath: string, workItems: Path[]) => Promise, ): Promise { queueFolderName ??= "queue"; - let queuePath = await ensureDir(path.join(rootPath, queueFolderName)); + const queuePath = await ensureDir(path.join(rootPath, queueFolderName)); queueFolderName ??= ""; - let completedPath = await ensureDir( + const completedPath = await ensureDir( path.join(rootPath, queueFolderName + "_completed"), ); - let skippedPath = await ensureDir( + const skippedPath = await ensureDir( path.join(rootPath, queueFolderName + "_skipped"), ); - let errorPath = await ensureDir( + const errorPath = await ensureDir( path.join(rootPath, queueFolderName + "_error"), ); const namedGenerator = createFileNameGenerator( @@ -115,7 +115,7 @@ export async function createWorkQueueFolder( const total = fileNames.length; let startAt = 0; let successCount = 0; - for (let slice of slices(fileNames, concurrency)) { + for (const slice of slices(fileNames, concurrency)) { startAt = slice.startAt; await asyncArray.forEachAsync( slice.value, diff --git a/ts/packages/typeagent/src/textProcessing.ts b/ts/packages/typeagent/src/textProcessing.ts index a9d007a0cb..636ab88be7 100644 --- a/ts/packages/typeagent/src/textProcessing.ts +++ b/ts/packages/typeagent/src/textProcessing.ts @@ -163,7 +163,7 @@ export function* buildChunks( separator: string | undefined = " ", ): IterableIterator { let chunk = ""; - for (let str of strings) { + for (const str of strings) { if (chunk.length + str.length > maxCharsPerChunk) { yield chunk; chunk = ""; @@ -193,7 +193,7 @@ export function getTextChunks( } let subStrings = splitIntoSentences(text); if (subStrings.some((s) => s.length > maxCharsPerChunk)) { - let phrases: string[] = []; + const phrases: string[] = []; for (const sentence of subStrings) { if (sentence.length > maxCharsPerChunk) { for (let i = 0; i < sentence.length; i += maxCharsPerChunk) { @@ -226,7 +226,7 @@ export async function getCompletionOnLargeText( instruction: string | ((chunk: string) => string), progress?: Progress, ): Promise> { - let completions: string[] = []; + const completions: string[] = []; for (const chunk of getTextChunks(text, maxCharsPerChunk)) { const result = await complete(chunk); if (!result.success) { @@ -455,7 +455,7 @@ export function generateAnswer( // // First, split text into chunks at sentence boundaries // - let chunks = getTextChunks(text, maxCharsPerChunk); + const chunks = getTextChunks(text, maxCharsPerChunk); for (let i = 0; i < chunks.length; i += concurrency) { // // Then run concurrency number of chunks simultaneously @@ -641,7 +641,7 @@ export async function generateAnswerFromWebPages( return answer; function toChunkResponse(answer: AnswerResponse): ChunkChatResponse { - let response: ChunkChatResponse = { + const response: ChunkChatResponse = { generatedText: answer.answer ?? "", entities: [], urls: [], @@ -912,9 +912,9 @@ export async function lookupAnswersOnWebPages( context?: PromptSection[], progress?: ProcessProgress, ): Promise { - let partialAnswer: WebLookupAnswer = emptyWebLookupAnswer(); + const partialAnswer: WebLookupAnswer = emptyWebLookupAnswer(); for (const webPageUrl of webPageUrls) { - let lookup: WebLookup = { query, webPageUrl }; + const lookup: WebLookup = { query, webPageUrl }; const answer = await lookupAnswerOnWebPage( model, lookup, @@ -1408,8 +1408,8 @@ export async function generateVariationsRecursive( // Breadth first recursion const uniqueVariations = new Set(); - let pendingPhrases: string[] = []; - let newPhrases: string[] = []; + const pendingPhrases: string[] = []; + const newPhrases: string[] = []; let currentDepth = 0; let phrase; newPhrases.push(seedPhrase); diff --git a/ts/packages/typeagent/src/vector/embeddings.ts b/ts/packages/typeagent/src/vector/embeddings.ts index 87c98b60a4..ae887af33a 100644 --- a/ts/packages/typeagent/src/vector/embeddings.ts +++ b/ts/packages/typeagent/src/vector/embeddings.ts @@ -57,7 +57,7 @@ export function indexOfNearest( other: Embedding, type: SimilarityType, ): ScoredItem { - let best: ScoredItem = { score: Number.MIN_VALUE, item: -1 }; + const best: ScoredItem = { score: Number.MIN_VALUE, item: -1 }; if (type === SimilarityType.Dot) { for (let i = 0; i < list.length; ++i) { const score: number = vector.dotProduct(list[i], other); diff --git a/ts/packages/typeagent/src/vector/semanticList.ts b/ts/packages/typeagent/src/vector/semanticList.ts index e8bde581cc..e7b1639b5a 100644 --- a/ts/packages/typeagent/src/vector/semanticList.ts +++ b/ts/packages/typeagent/src/vector/semanticList.ts @@ -68,7 +68,7 @@ export function createSemanticList( existingValues?: EmbeddedValue[], stringify?: (value: T) => string, ): SemanticList { - let values = existingValues ?? []; + const values = existingValues ?? []; return { values, push, @@ -194,7 +194,7 @@ export function createSemanticList( other: NormalizedEmbedding, type: SimilarityType, ): ScoredItem { - let best: ScoredItem = { score: Number.MIN_VALUE, item: -1 }; + const best: ScoredItem = { score: Number.MIN_VALUE, item: -1 }; for (let i = 0; i < list.length; ++i) { const score: number = similarity(list[i].embedding, other, type); if (score > best.score) { diff --git a/ts/packages/typeagent/src/vector/semanticMap.ts b/ts/packages/typeagent/src/vector/semanticMap.ts index e75668cd36..6d9d13782d 100644 --- a/ts/packages/typeagent/src/vector/semanticMap.ts +++ b/ts/packages/typeagent/src/vector/semanticMap.ts @@ -125,7 +125,7 @@ export async function createSemanticMap( ): Promise { let newItems: string[] | undefined; for (const item of items) { - let [text, value] = item; + const [text, value] = item; if (semanticIndex !== undefined && !map.has(text)) { newItems ??= []; newItems.push(text); diff --git a/ts/packages/utils/commonUtils/src/base64Browser.ts b/ts/packages/utils/commonUtils/src/base64Browser.ts index 426069d991..4be4692e9f 100644 --- a/ts/packages/utils/commonUtils/src/base64Browser.ts +++ b/ts/packages/utils/commonUtils/src/base64Browser.ts @@ -4,7 +4,7 @@ export function uint8ArrayToBase64(bytes: Uint8Array): string { let binary = ""; const len = bytes.byteLength; - for (var i = 0; i < len; i++) { + for (let i = 0; i < len; i++) { binary += String.fromCharCode(bytes[i]); } return btoa(binary); diff --git a/ts/packages/utils/typechatUtils/src/datetimeHelper.ts b/ts/packages/utils/typechatUtils/src/datetimeHelper.ts index a57e50f019..687a1d3367 100644 --- a/ts/packages/utils/typechatUtils/src/datetimeHelper.ts +++ b/ts/packages/utils/typechatUtils/src/datetimeHelper.ts @@ -151,7 +151,7 @@ export function getDateRelativeToDayV3(relativeDate: string): Date | undefined { } const [, ordinalStr, dayStr, , direction, monthStr] = match; - let ordinal = ordinalStr ? parseInt(ordinalStr, 10) : 1; + const ordinal = ordinalStr ? parseInt(ordinalStr, 10) : 1; let targetMonthIndex: number; if (direction && direction.toLowerCase() === "next") { @@ -392,10 +392,10 @@ export async function getNormalizedDateRange( } | undefined > { - let currentDate = getShortDate(new Date(inputDate)); + const currentDate = getShortDate(new Date(inputDate)); if (currentDate !== undefined) { if (startTime != undefined) { - let startDateTime = combineDateTime( + const startDateTime = combineDateTime( currentDate, parseTimeString(formatTime(startTime)), ); @@ -410,7 +410,7 @@ export async function getNormalizedDateRange( fLocaleTime, ); } else { - let endDateTime = combineDateTime( + const endDateTime = combineDateTime( currentDate, parseTimeString(formatTime(endTime)), ); @@ -440,9 +440,9 @@ export async function getNormalizedDateTimes( if (curDay === undefined) return undefined; - let currentDate = getShortDate(curDay); + const currentDate = getShortDate(curDay); if (startTime != undefined) { - let startDateTime = combineDateTime( + const startDateTime = combineDateTime( currentDate, parseTimeString(formatTime(startTime)), ); @@ -457,7 +457,7 @@ export async function getNormalizedDateTimes( fLocaleTime, ); } else { - let endDateTime = combineDateTime( + const endDateTime = combineDateTime( currentDate, parseTimeString(formatTime(endTime)), ); diff --git a/ts/packages/utils/typechatUtils/src/incrementalJsonParser.ts b/ts/packages/utils/typechatUtils/src/incrementalJsonParser.ts index f0483b7927..1fb2c36a68 100644 --- a/ts/packages/utils/typechatUtils/src/incrementalJsonParser.ts +++ b/ts/packages/utils/typechatUtils/src/incrementalJsonParser.ts @@ -69,10 +69,10 @@ export function createIncrementalJsonParser( let expectedKeywordValue: boolean | null = null; // Nested object/arrays property names - let props: (string | number)[] = []; + const props: (string | number)[] = []; // For full mode - let nested: any[] = []; + const nested: any[] = []; let currentNested: any = undefined; function parseLiteralKeyword(c: string) { diff --git a/ts/packages/utils/typechatUtils/src/jsonTranslator.ts b/ts/packages/utils/typechatUtils/src/jsonTranslator.ts index aea179b82c..398d472306 100644 --- a/ts/packages/utils/typechatUtils/src/jsonTranslator.ts +++ b/ts/packages/utils/typechatUtils/src/jsonTranslator.ts @@ -234,7 +234,7 @@ async function attachAttachments( attachments: CachedImageWithDetails[] | undefined, promptPreamble?: string | PromptSection[], ) { - let pp: PromptSection[] = promptPreamble as PromptSection[]; + const pp: PromptSection[] = promptPreamble as PromptSection[]; if (attachments && attachments.length > 0 && pp) { for (let i = 0; i < attachments.length; i++) { diff --git a/ts/packages/utils/typechatUtils/src/location.ts b/ts/packages/utils/typechatUtils/src/location.ts index 0f62cf597a..e46fbc43fe 100644 --- a/ts/packages/utils/typechatUtils/src/location.ts +++ b/ts/packages/utils/typechatUtils/src/location.ts @@ -138,7 +138,7 @@ export async function findNearbyPointsOfInterest( // get the result const response = await fetch(nearby, options); - let responseBody = await response.json(); + const responseBody = await response.json(); // summarize results // TODO: update any once @azure-rest/maps-search incorporates V1 return types @@ -189,7 +189,7 @@ export async function reverseGeocode( return []; } - let reverseGeocode = `${getEnvSetting(env, openai.EnvVars.AZURE_MAPS_ENDPOINT)}reverseGeocode?api-version=2023-06-01&coordinates=${position.longitude},${position.latitude}`; + const reverseGeocode = `${getEnvSetting(env, openai.EnvVars.AZURE_MAPS_ENDPOINT)}reverseGeocode?api-version=2023-06-01&coordinates=${position.longitude},${position.latitude}`; const options: RequestInit = { method: "GET", @@ -201,7 +201,7 @@ export async function reverseGeocode( // get the result const response = await fetch(reverseGeocode, options); - let responseBody = await response.json(); + const responseBody = await response.json(); // summarize results // TODO: update any once @azure-rest/maps-search incorporates V1 return types diff --git a/ts/packages/vscode-chat/src/extension.ts b/ts/packages/vscode-chat/src/extension.ts index 95cf1b0107..b8ad1088c4 100644 --- a/ts/packages/vscode-chat/src/extension.ts +++ b/ts/packages/vscode-chat/src/extension.ts @@ -358,7 +358,7 @@ export async function activate( if (isUntitledConversation(conversationId)) { return; } - let state = conversationLabelState.get(conversationId); + const state = conversationLabelState.get(conversationId); if (state === undefined) { upsertConversationState(conversationId, item.label, item.label); return; diff --git a/ts/packages/vscode-shell/src/agentServerBridge.ts b/ts/packages/vscode-shell/src/agentServerBridge.ts index fa6cef92d7..93d00929ad 100644 --- a/ts/packages/vscode-shell/src/agentServerBridge.ts +++ b/ts/packages/vscode-shell/src/agentServerBridge.ts @@ -381,7 +381,7 @@ export class AgentServerBridge { // Join the session with our ClientIO implementation const clientIO = this.createClientIO(); - let joinOpts: any = { + const joinOpts: any = { clientType: "extension", // filter: false so multiple tabs sharing the same session all // receive setDisplay/appendDisplay broadcasts. Per-connection diff --git a/ts/tools/docsAutogen/src/paths.ts b/ts/tools/docsAutogen/src/paths.ts index fea4c5462b..a839700978 100644 --- a/ts/tools/docsAutogen/src/paths.ts +++ b/ts/tools/docsAutogen/src/paths.ts @@ -12,7 +12,7 @@ import path from "node:path"; */ export function findRepoRoot(start: string): string { let cur = path.resolve(start); - // eslint-disable-next-line no-constant-condition + while (true) { if (fs.existsSync(path.join(cur, ".git"))) { return cur; @@ -33,7 +33,7 @@ export function findRepoRoot(start: string): string { */ export function findMonorepoRoot(start: string): string { let cur = path.resolve(start); - // eslint-disable-next-line no-constant-condition + while (true) { if (fs.existsSync(path.join(cur, "pnpm-workspace.yaml"))) { return cur; diff --git a/ts/tools/scripts/code/lintReport.ts b/ts/tools/scripts/code/lintReport.ts index 895adbd99c..68cd310277 100644 --- a/ts/tools/scripts/code/lintReport.ts +++ b/ts/tools/scripts/code/lintReport.ts @@ -960,7 +960,9 @@ async function runFix(opts: Options): Promise { `${num(unfixable)} occurrence(s) could not be auto-fixed; run \`npm run code-lint\` to see them.`, ); } - console.log("Review the diff, run prettier, and rebuild before committing."); + console.log( + "Review the diff, run prettier, and rebuild before committing.", + ); return 0; } diff --git a/ts/tools/scripts/elevate.js b/ts/tools/scripts/elevate.js index 917561cd16..d5d807cc2c 100644 --- a/ts/tools/scripts/elevate.js +++ b/ts/tools/scripts/elevate.js @@ -12,7 +12,7 @@ async function elevate(startDateTime) { for (element of config) { console.log(`>> Elevating ${element.roleName}`); - var v = await client.elevate({ + const v = await client.elevate({ requestType: element.properties.RequestType, roleName: element.roleName, expirationType: element.properties.ScheduleInfo.Expiration.Type, diff --git a/ts/tools/scripts/generate-selfhost-config.mjs b/ts/tools/scripts/generate-selfhost-config.mjs index faeacb7f7c..4c0e13591f 100644 --- a/ts/tools/scripts/generate-selfhost-config.mjs +++ b/ts/tools/scripts/generate-selfhost-config.mjs @@ -234,7 +234,7 @@ function main() { return 1; } - let embedding = (arg("--embedding") ?? "local").toLowerCase(); + const embedding = (arg("--embedding") ?? "local").toLowerCase(); const validEmbedding = ["local", "ollama", "openai", "none"]; if (!validEmbedding.includes(embedding)) { console.error( diff --git a/ts/tools/scripts/lib/pimClient.mjs b/ts/tools/scripts/lib/pimClient.mjs index 242fdca0d2..9b7311772e 100644 --- a/ts/tools/scripts/lib/pimClient.mjs +++ b/ts/tools/scripts/lib/pimClient.mjs @@ -84,9 +84,9 @@ class AzPIMClient { const options = { filter, }; - var role; + let role; - for await (let item of client.roleEligibilitySchedules.listForScope( + for await (const item of client.roleEligibilitySchedules.listForScope( scope, options, )) { From 16de2f006d16fe928ea03ac464f8386d4a20948b Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Mon, 6 Jul 2026 09:45:55 -0700 Subject: [PATCH 12/23] documented progress --- ts/tools/scripts/code/README.md | 131 ++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 ts/tools/scripts/code/README.md diff --git a/ts/tools/scripts/code/README.md b/ts/tools/scripts/code/README.md new file mode 100644 index 0000000000..9df48ce8f0 --- /dev/null +++ b/ts/tools/scripts/code/README.md @@ -0,0 +1,131 @@ +# Code-quality analysis suite + +A set of self-contained analysis tools for the TypeAgent `ts/` tree, plus the +maintainability roadmap they drive. Each tool wraps a **standard engine** +(ESLint, jscpd, madge, knip) rather than a bespoke metric, runs against a +throwaway in-memory config so the repo needs no committed linter config, and +emits a CSV + JSON + self-contained HTML report **and** a console summary. + +Most tools also have a **CI mode** — a stateless _ratchet_ (the base branch is +the baseline, so the metric can only trend down) or a zero-tolerance _gate_ — +wired into [`.github/workflows/build-ts.yml`](../../../../.github/workflows/build-ts.yml) +so existing debt never blocks a PR but new debt does. + +> Report output folders (`*-report/`) are generated and git-ignored. + +## The tools + +Run any of these from `ts/`. All accept `--help`, `--root `, +`--out-dir `, `--top `. + +| Command | Engine | Measures | Baseline (2026-07) | CI gate | +| -------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------- | +| `npm run code-complexity` | ESLint + sonarjs | cyclomatic + cognitive complexity per function | budgets 25/30 in CI | **ratchet** (+ new-file caps) | +| `npm run code-lint` | ESLint + typescript-eslint | `no-explicit-any`, `no-console`, `no-unused-vars`, `no-var`, `prefer-const` (+ opt-in type-aware) | 6,396 (any 3,874 · console 2,098 · unused 421 · **var/const 0**) | **ratchet** (+ zero-tolerance for `no-var`/`prefer-const`) | +| `npm run code-duplication` | jscpd | copy/paste clones, cross-package + per-package | 744 clones / 13,984 lines (3.67%) | — | +| `npm run code-circular` | madge | runtime import cycles | 232 cycles (dispatcher 115) | **ratchet** (no new cycles) | +| `npm run code-consistency` | custom | cross-package duplicate exports, direct `process.env`, agent-layout conformance | 16 dup exports · 263 env refs · agents 33/34 | — | +| `npm run code-deadcode` | knip | unused files / exports / deps | ~1,790 raw (needs config) | — (report-only) | +| `npm run code-debt` | regex scan | TODO/FIXME/HACK/XXX, `@deprecated`, skipped/focused tests | TODO 208 · `@deprecated` 41 · skipped 14 · focused 0 | **gate** (no focused / no new skips) | + +### Modes + +- **Report (default):** scans the tree, writes `-report/{*.csv,report.json,report.html}` + a console summary. +- **Ratchet / gate (`--ratchet` / `--gate` `--base `):** lints only the + files changed vs the merge base and fails if the change adds debt. Used in CI + with `--base "origin/${{ github.base_ref }}"`. + +### Tool-specific flags + +- **code-lint:** `--fix` (apply `no-var`/`prefer-const` autofixes in place), + `--type-aware` (adds `no-floating-promises`, `no-misused-promises`, + `no-deprecated` — slower, report only), `--new-file-max `. +- **code-complexity:** `--cyclomatic `, `--cognitive `, + `--new-file-cyclomatic `, `--new-file-cognitive `. +- **code-deadcode:** `--config` (defaults to [`knip.jsonc`](./knip.jsonc)). + +### Notes for maintainers + +- `npx` cannot resolve local bins in some dev environments; the tools invoke + engines via `pnpm exec` (knip) or `createRequire` (jscpd, madge) accordingly. +- `jscpd@4` (not 5, which is a Rust CLI with no Node API) is required for the + programmatic API used by the duplication tool. +- Each tool is intentionally self-contained (its own `packageKeyOf`, arg parser, + report writers) so it can be copied/run in isolation. + +## CI gates + +PR-only steps in `build-ts.yml`, after the build, each fetching the base ref: + +1. **Complexity ratchet** — changed files may not add functions over the + cyclomatic/cognitive budget; new files held to a hard cap. +2. **Lint ratchet** — changed files may not add violations; `no-var` / + `prefer-const` are zero-tolerance (must stay at 0). +3. **Circular dependency ratchet** — the change may not introduce a new import + cycle (compares HEAD vs the merge base via a throwaway git worktree; heavier, + ~80s). +4. **Test debt gate** — zero focused tests (`.only`/`fit`/`fdescribe`), no newly + skipped tests (`.skip`/`xit`/`xdescribe`) in changed files. + +Dead-code is **not** gated yet — knip's numbers are inflated until +[`knip.jsonc`](./knip.jsonc) declares the entry points it can't infer. + +## Maintainability roadmap + +### Done + +- Built the 7-tool analysis suite above. +- Wired 4 CI gates (complexity, lint, circular, debt) into `build-ts.yml`. +- **Quick win #1 — `prefer-const`/`no-var` → 0** (262 files auto-fixed via + `code-lint --fix` + 7 hand-fixed; now gated at zero). Verified: full build + passes, and every unit/live test failure was proven pre-existing (the changes + are semantics-preserving `let`/`var`→`const`). + +### Next — quick wins (low risk) + +- **14 skipped tests** — un-skip or delete; the debt gate already blocks new ones. +- **WebSocket helpers** — `createWebSocket` + `keepWebSocketAlive` are + reimplemented in 4 packages; point `browser`/`coda`/`shell` at + `utils/webSocketUtils` and delete the forks. + +### Next — consolidations (medium) + +- **Async helpers → `packages/typeagent`** — unify `delay`/`sleep`/`pause`, + export the private `withTimeout`, consolidate retry. +- **FS helpers → shared module** — `ensureDir` (4 pkgs) + JSON read/write. +- **De-fork `actionGrammar` ↔ `agentSdkWrapper`** — ~1,115 duplicated lines + (grammar-gen near-forks). +- **Extract `powershell` ↔ `taskflow` benchmark harness** — ~1,069 lines / 45 + clones → one shared package. +- **`process.env` → `@typeagent/config`** — 263 refs across 115 files; then add + a lint rule so it can't regress. + +### Ongoing burn-downs (ratchets already hold the line) + +- **`any`** — 3,874 occurrences. +- **`console.log` → `debug` tracing** — 2,098 in non-CLI packages. +- **Untangle `dispatcher/internal.ts` barrel** — source of ~115 of the 232 cycles. +- **God-file decomposition** — `chatPanel.ts` (4,790), `collisionCorpusHandlers.ts` + (5,230), `grammarMatcher.ts` (3,744)… +- **Remove `@deprecated` APIs** — 41. + +### Tooling follow-ups + +- Tune [`knip.jsonc`](./knip.jsonc) entry points → trustworthy dead-code number + → then add a `code-deadcode` gate. +- Optionally restrict the heavier circular ratchet to one CI matrix cell + (`&& matrix.os == 'ubuntu-latest' && matrix.version == 22`) to save compute. +- Optionally add a `code-duplication` ratchet. + +## Known pre-existing issues (surfaced by the test runs, unrelated to the above) + +- **Missing local test fixtures** — e.g. + `packages/memory/conversation/test/data/claudeSession.jsonl` (+ `copilotSession.jsonl`, + `claudeSessions/`). Causes `ENOENT` unit + live failures locally. +- **Stale local build/link** — `knowledgeProcessor` (`Cannot find module 'aiclient'`), + `shell` (`Cannot find module 'action-grammar'`, missing `PartialCompletionSession` + export). A clean `pnpm install && pnpm run build` likely clears these. +- **`workflow/cli`** unit tests time out at the default 5s (need higher timeouts). +- **`defaultAgentProvider` translate live tests** drift — the model returns + newer/plausible actions (e.g. `createPlaylist`) the fixtures don't allow; the + expectations need refreshing. From c90fedd20634c22b95054e62821688aee225f863 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Jul 2026 17:17:06 +0000 Subject: [PATCH 13/23] Fix Windows lint failure by batching prettier-changed arguments --- ts/tools/scripts/prettier-changed.mjs | 67 +++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 10 deletions(-) diff --git a/ts/tools/scripts/prettier-changed.mjs b/ts/tools/scripts/prettier-changed.mjs index 4e8d692d0d..39601989b7 100644 --- a/ts/tools/scripts/prettier-changed.mjs +++ b/ts/tools/scripts/prettier-changed.mjs @@ -46,6 +46,9 @@ const SUPPORTED_EXTENSIONS = new Set([ ".yaml", ]); +// Keep each prettier invocation comfortably below Windows shell length limits. +const MAX_PRETTIER_COMMAND_CHARS = 7000; + function parseArgs(argv) { let write = false; let base = process.env.PRETTIER_BASE ?? "origin/main"; @@ -151,6 +154,35 @@ function collectChangedFiles(repoRoot, base) { return new Set([...committed, ...unstaged, ...staged, ...untracked]); } +function splitIntoBatches(files, staticArgs) { + const batches = []; + const staticLength = staticArgs.join(" ").length; + + let current = []; + let currentLength = staticLength; + + for (const file of files) { + const addedLength = 1 + file.length; + if ( + current.length > 0 && + currentLength + addedLength > MAX_PRETTIER_COMMAND_CHARS + ) { + batches.push(current); + current = [file]; + currentLength = staticLength + file.length; + } else { + current.push(file); + currentLength += addedLength; + } + } + + if (current.length > 0) { + batches.push(current); + } + + return batches; +} + function main() { const { write, base } = parseArgs(process.argv.slice(2)); const repoRoot = getRepoRoot(); @@ -183,20 +215,35 @@ function main() { "prettier", write ? "--write" : "--check", "--ignore-unknown", - ...tsRelative, ]; - const result = spawnSync("pnpm", ["exec", ...prettierArgs], { - cwd: tsRoot, - stdio: "inherit", - shell: process.platform === "win32", - }); + const pnpmExecutable = process.platform === "win32" ? "pnpm.cmd" : "pnpm"; + const batches = splitIntoBatches(tsRelative, [ + pnpmExecutable, + "exec", + ...prettierArgs, + ]); - if (result.error) { - console.error(`error: failed to run prettier: ${result.error.message}`); - process.exit(1); + for (const batch of batches) { + const result = spawnSync( + pnpmExecutable, + ["exec", ...prettierArgs, ...batch], + { + cwd: tsRoot, + stdio: "inherit", + }, + ); + + if (result.error) { + console.error( + `error: failed to run prettier: ${result.error.message}`, + ); + process.exit(1); + } + if ((result.status ?? 0) !== 0) { + process.exit(result.status ?? 1); + } } - process.exit(result.status ?? 0); } main(); From 5efe5a95a8bff646b2bcf7382799f1c04d408dea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 6 Jul 2026 18:29:26 +0000 Subject: [PATCH 14/23] fix: add shell:true on Windows for pnpm.cmd spawning (Node 24 CVE-2024-27980 compat) --- ts/tools/scripts/prettier-changed.mjs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ts/tools/scripts/prettier-changed.mjs b/ts/tools/scripts/prettier-changed.mjs index 39601989b7..0e4895dc01 100644 --- a/ts/tools/scripts/prettier-changed.mjs +++ b/ts/tools/scripts/prettier-changed.mjs @@ -231,6 +231,9 @@ function main() { { cwd: tsRoot, stdio: "inherit", + // Node 24 (CVE-2024-27980) disallows spawning .cmd/.bat files + // without shell:true on Windows. + shell: process.platform === "win32", }, ); From da3489df21bb36c78b083c8e62871c34499926a2 Mon Sep 17 00:00:00 2001 From: robgruen Date: Tue, 7 Jul 2026 09:46:16 -0700 Subject: [PATCH 15/23] removed stale todo and enabled test --- ts/packages/actionGrammar/test/dynamicGrammarLoader.spec.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ts/packages/actionGrammar/test/dynamicGrammarLoader.spec.ts b/ts/packages/actionGrammar/test/dynamicGrammarLoader.spec.ts index 532a174fc8..681e405565 100644 --- a/ts/packages/actionGrammar/test/dynamicGrammarLoader.spec.ts +++ b/ts/packages/actionGrammar/test/dynamicGrammarLoader.spec.ts @@ -420,8 +420,7 @@ describe("Dynamic Grammar Loader", () => { ); }); - // TODO: Re-enable after grammar imports and type declarations for converters are complete - it.skip("should load rules with CalendarDate symbol", () => { + it("should load rules with CalendarDate symbol", () => { const loader = new DynamicGrammarLoader(); const generatedRule = `@ import { CalendarDate } from "types.ts" From 3db0be5aec6f9e15e5ce7102719d988ac63818ab Mon Sep 17 00:00:00 2001 From: robgruen Date: Tue, 7 Jul 2026 10:05:37 -0700 Subject: [PATCH 16/23] updated test skipping logic test --- ts/tools/scripts/code/debtMarkersReport.ts | 41 ++++++++++++++++------ 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/ts/tools/scripts/code/debtMarkersReport.ts b/ts/tools/scripts/code/debtMarkersReport.ts index d62ca5ec73..7dc1396bb9 100644 --- a/ts/tools/scripts/code/debtMarkersReport.ts +++ b/ts/tools/scripts/code/debtMarkersReport.ts @@ -8,8 +8,9 @@ * candidates) for debt markers that the heavier engines do not cover: * - TODO / FIXME / HACK / XXX comments * - @deprecated annotations (APIs kept alive past their use-by date) - * - Skipped tests: .skip( / xit( / xdescribe( - * - Focused tests: .only( / fit( / fdescribe( (should never be committed) + * - Skipped tests: .skip( / .skip.each( / xit( / xdescribe( (empty-body + * "() => {}" stubs are conditional/placeholder skips, not disabled tests) + * - Focused tests: .only( / .only.each( / fit( / fdescribe( (never committed) * * Unlike lint and circular deps (large problems -> ratchet), skipped/focused * tests are a small, fixable problem, so this ships a *hard gate* rather than a @@ -96,21 +97,29 @@ const MARKERS: { { type: "@deprecated", re: /@deprecated\b/g, gate: "none" }, { type: "skipped-test", - re: /\b(?:it|test|describe)\.skip\s*\(|\bxit\s*\(|\bxdescribe\s*\(/g, + re: /\b(?:it|test|describe)\.skip(?:\.each)?\s*[(`]|\bxit\s*\(|\bxdescribe\s*\(/g, gate: "skip", }, { type: "focused-test", - re: /\b(?:it|test|describe)\.only\s*\(|\bfit\s*\(|\bfdescribe\s*\(/g, + re: /\b(?:it|test|describe)\.only(?:\.each)?\s*[(`]|\bfit\s*\(|\bfdescribe\s*\(/g, gate: "focused", }, ]; // Focused/skipped test markers are only meaningful in test files. const FOCUSED_RE = - /\b(?:it|test|describe)\.only\s*\(|\bfit\s*\(|\bfdescribe\s*\(/g; + /\b(?:it|test|describe)\.only(?:\.each)?\s*[(`]|\bfit\s*\(|\bfdescribe\s*\(/g; const SKIP_RE = - /\b(?:it|test|describe)\.skip\s*\(|\bxit\s*\(|\bxdescribe\s*\(/g; + /\b(?:it|test|describe)\.skip(?:\.each)?\s*[(`]|\bxit\s*\(|\bxdescribe\s*\(/g; + +// A skip whose callback is an empty arrow body `() => {}` on the same line is a +// *conditional* or *placeholder* skip -- the key-gated `testIf`/`describeIf` +// helpers, or data-driven `if (fixture === undefined) it.skip(name, () => {})` +// placeholders -- not a genuinely disabled test. A real disabled test keeps its +// body, so its `.skip(` opener is not an empty stub. Excluding the empty stubs +// makes the count reflect tests that are actually turned off. +const EMPTY_SKIP_STUB_RE = /=>\s*\{\s*\}/; // --------------------------------------------------------------------------- // Argument parsing @@ -285,6 +294,10 @@ function scanContent(rel: string, content: string, isTest: boolean): Marker[] { if ((m.gate === "skip" || m.gate === "focused") && !isTest) { continue; } + // Conditional/placeholder skips (empty `() => {}` bodies) aren't debt. + if (m.gate === "skip" && EMPTY_SKIP_STUB_RE.test(line)) { + continue; + } m.re.lastIndex = 0; const hits = line.match(m.re); if (hits) { @@ -302,11 +315,17 @@ function scanContent(rel: string, content: string, isTest: boolean): Marker[] { return markers; } -function countMatches(content: string, re: RegExp): number { +// Count genuinely disabled skipped tests in a file, excluding conditional / +// placeholder stubs (empty `() => {}` bodies) so the gate only trips on real +// newly-disabled tests, not on key-gated or data-driven runtime skips. +function countSkips(content: string): number { let n = 0; for (const line of content.split(/\r?\n/)) { - re.lastIndex = 0; - const hits = line.match(re); + if (EMPTY_SKIP_STUB_RE.test(line)) { + continue; + } + SKIP_RE.lastIndex = 0; + const hits = line.match(SKIP_RE); if (hits) { n += hits.length; } @@ -611,7 +630,7 @@ function runGate(opts: Options): number { ); } } - headSkips += countMatches(content, SKIP_RE); + headSkips += countSkips(content); } if (e.base && isTestFile(e.base)) { try { @@ -619,7 +638,7 @@ function runGate(opts: Options): number { ["show", `${mergeBase}:${e.base}`], repoRoot, ); - baseSkips += countMatches(baseContent, SKIP_RE); + baseSkips += countSkips(baseContent); } catch { /* file did not exist at base */ } From ecb8b7805b9d2319d3766eaf8691b9359d9f2105 Mon Sep 17 00:00:00 2001 From: robgruen Date: Tue, 7 Jul 2026 11:00:37 -0700 Subject: [PATCH 17/23] fixed stale test --- .../actionGrammar/test/dynamicGrammarLoader.spec.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/ts/packages/actionGrammar/test/dynamicGrammarLoader.spec.ts b/ts/packages/actionGrammar/test/dynamicGrammarLoader.spec.ts index 681e405565..cd08f0fb7c 100644 --- a/ts/packages/actionGrammar/test/dynamicGrammarLoader.spec.ts +++ b/ts/packages/actionGrammar/test/dynamicGrammarLoader.spec.ts @@ -423,8 +423,7 @@ describe("Dynamic Grammar Loader", () => { it("should load rules with CalendarDate symbol", () => { const loader = new DynamicGrammarLoader(); - const generatedRule = `@ import { CalendarDate } from "types.ts" - = ; + const generatedRule = ` = ; = schedule $(event:string) on $(date:CalendarDate) -> { actionName: "scheduleEvent", parameters: { @@ -445,9 +444,12 @@ describe("Dynamic Grammar Loader", () => { ]); expect(matchResult.matched).toBe(true); expect(matchResult.actionValue?.parameters?.event).toBe("meeting"); - expect(matchResult.actionValue?.parameters?.date).toBeInstanceOf( - Date, - ); + // CalendarDate preserves the user's original text (resolved to a + // Date lazily via CalendarDateValue.asDate()), so the slot value + // carries { text } rather than a raw Date. + expect(matchResult.actionValue?.parameters?.date).toMatchObject({ + text: "tomorrow", + }); }); }); }); From ef63a0e94b83a48bd8d127f6244143800521e713 Mon Sep 17 00:00:00 2001 From: robgruen Date: Tue, 7 Jul 2026 15:45:53 -0700 Subject: [PATCH 18/23] updated readme --- ts/tools/scripts/code/README.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/ts/tools/scripts/code/README.md b/ts/tools/scripts/code/README.md index 9df48ce8f0..6939c624b0 100644 --- a/ts/tools/scripts/code/README.md +++ b/ts/tools/scripts/code/README.md @@ -26,7 +26,7 @@ Run any of these from `ts/`. All accept `--help`, `--root `, | `npm run code-circular` | madge | runtime import cycles | 232 cycles (dispatcher 115) | **ratchet** (no new cycles) | | `npm run code-consistency` | custom | cross-package duplicate exports, direct `process.env`, agent-layout conformance | 16 dup exports · 263 env refs · agents 33/34 | — | | `npm run code-deadcode` | knip | unused files / exports / deps | ~1,790 raw (needs config) | — (report-only) | -| `npm run code-debt` | regex scan | TODO/FIXME/HACK/XXX, `@deprecated`, skipped/focused tests | TODO 208 · `@deprecated` 41 · skipped 14 · focused 0 | **gate** (no focused / no new skips) | +| `npm run code-debt` | regex scan | TODO/FIXME/HACK/XXX, `@deprecated`, skipped/focused tests | TODO 207 · `@deprecated` 41 · skipped 3 · focused 0 | **gate** (no focused / no new skips) | ### Modes @@ -64,8 +64,8 @@ PR-only steps in `build-ts.yml`, after the build, each fetching the base ref: 3. **Circular dependency ratchet** — the change may not introduce a new import cycle (compares HEAD vs the merge base via a throwaway git worktree; heavier, ~80s). -4. **Test debt gate** — zero focused tests (`.only`/`fit`/`fdescribe`), no newly - skipped tests (`.skip`/`xit`/`xdescribe`) in changed files. +4. **Test debt gate** — zero focused tests (`.only`/`.only.each`/`fit`/`fdescribe`), + no newly skipped tests (`.skip`/`.skip.each`/`xit`/`xdescribe`) in changed files. Dead-code is **not** gated yet — knip's numbers are inflated until [`knip.jsonc`](./knip.jsonc) declares the entry points it can't infer. @@ -80,10 +80,18 @@ Dead-code is **not** gated yet — knip's numbers are inflated until `code-lint --fix` + 7 hand-fixed; now gated at zero). Verified: full build passes, and every unit/live test failure was proven pre-existing (the changes are semantics-preserving `let`/`var`→`const`). +- **Quick win #2 — skipped-test detection + triage.** Taught `code-debt` to see + `.skip.each`/`.only.each` and to ignore conditional/placeholder stubs (empty + `() => {}` bodies from the `testIf`/`describeIf` key-gates and data-driven + loops), so the count reflects genuinely disabled tests: **14 → 3**. Re-enabled + the `CalendarDate` grammar test (the converter it waited on now exists). ### Next — quick wins (low risk) -- **14 skipped tests** — un-skip or delete; the debt gate already blocks new ones. +- **3 remaining disabled tests** — un-skip or delete: `api/test/api.spec.ts` + (`describe.skip`; its `expect(response.ok)` calls are also no-op assertions), + plus the `.skip.each` data suites `actionGrammar/test/nfaDfaParity.spec.ts` + and `actionSchema/test/regen.spec.ts`. The debt gate already blocks new ones. - **WebSocket helpers** — `createWebSocket` + `keepWebSocketAlive` are reimplemented in 4 packages; point `browser`/`coda`/`shell` at `utils/webSocketUtils` and delete the forks. From 9e53f453e8669968b55f9b66afd84a5cc3628d7a Mon Sep 17 00:00:00 2001 From: robgruen Date: Tue, 7 Jul 2026 16:15:36 -0700 Subject: [PATCH 19/23] Fixed bad test. --- ts/packages/api/test/api.spec.ts | 22 --------- ts/packages/api/test/api.test.ts | 78 ++++++++++++++++++++++++++++++++ ts/tools/scripts/code/README.md | 17 ++++--- 3 files changed, 88 insertions(+), 29 deletions(-) delete mode 100644 ts/packages/api/test/api.spec.ts create mode 100644 ts/packages/api/test/api.test.ts diff --git a/ts/packages/api/test/api.spec.ts b/ts/packages/api/test/api.spec.ts deleted file mode 100644 index 8d897dd4aa..0000000000 --- a/ts/packages/api/test/api.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import { TypeAgentServer } from "../src/typeAgentServer.js"; - -describe.skip("api web/ws server", () => { - it("verify web server respnses", async () => { - const typeAgentServer: TypeAgentServer = new TypeAgentServer(); - await typeAgentServer.start(); - - let response = await fetch("http://localhost:3000/"); - expect(response.ok); - - response = await fetch("http://localhost:3000/index.html"); - expect(response.ok); - - response = await fetch("http://localhost:3000/sdfadfs.asdfsdf"); - expect(response.status == 404); - - typeAgentServer.stop(); - }); -}); diff --git a/ts/packages/api/test/api.test.ts b/ts/packages/api/test/api.test.ts new file mode 100644 index 0000000000..9dba44b5cb --- /dev/null +++ b/ts/packages/api/test/api.test.ts @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { TypeAgentAPIWebServer } from "../src/webServer.js"; +import type { TypeAgentAPIServerConfig } from "../src/webServer.js"; +import type { AddressInfo } from "node:net"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +// Exercises the real static-file HTTP server (TypeAgentAPIWebServer) against a +// throwaway web root on an ephemeral port. It intentionally does not spin up the +// full TypeAgentServer (which brings up the LLM-backed dispatcher and needs API +// keys + data/config.json); this covers the web-serving behavior the old +// (skipped, no-op) api.spec.ts was meant to check. + +describe("api web server", () => { + let wwwroot: string; + let server: TypeAgentAPIWebServer; + let baseUrl: string; + + beforeAll(async () => { + // realpathSync so the canonical path matches what the server resolves + // requests against (avoids Windows short/long-name mismatches that would + // otherwise trip the server's under-web-root check). + wwwroot = fs.realpathSync( + fs.mkdtempSync(path.join(os.tmpdir(), "api-webserver-")), + ); + fs.writeFileSync( + path.join(wwwroot, "index.html"), + "TypeAgent

    ok

    ", + ); + + const config: TypeAgentAPIServerConfig = { + wwwroot, + port: 0, // unused: we bind an ephemeral port directly below + broadcast: false, + blobBackupEnabled: false, + }; + + // A no-op action handler is sufficient; these checks don't post actions. + server = new TypeAgentAPIWebServer(config, () => undefined); + + // Bind an ephemeral loopback port instead of calling start(), which + // hardcodes 3000 (and 3443) and would conflict / bind all interfaces. + await new Promise((resolve, reject) => { + server.server.once("error", reject); + server.server.listen(0, "127.0.0.1", resolve); + }); + const { port } = server.server.address() as AddressInfo; + baseUrl = `http://127.0.0.1:${port}`; + }); + + afterAll(async () => { + await new Promise((resolve) => + server.server.close(() => resolve()), + ); + fs.rmSync(wwwroot, { recursive: true, force: true }); + }); + + it("serves index.html at the web root", async () => { + const response = await fetch(`${baseUrl}/`); + expect(response.status).toBe(200); + expect(response.headers.get("content-type")).toContain("text/html"); + expect(await response.text()).toContain("

    ok

    "); + }); + + it("serves a file requested by name", async () => { + const response = await fetch(`${baseUrl}/index.html`); + expect(response.status).toBe(200); + expect(await response.text()).toContain("

    ok

    "); + }); + + it("returns 404 for a missing file", async () => { + const response = await fetch(`${baseUrl}/does-not-exist.asdfsdf`); + expect(response.status).toBe(404); + }); +}); diff --git a/ts/tools/scripts/code/README.md b/ts/tools/scripts/code/README.md index 6939c624b0..6b7ad1ee62 100644 --- a/ts/tools/scripts/code/README.md +++ b/ts/tools/scripts/code/README.md @@ -26,7 +26,7 @@ Run any of these from `ts/`. All accept `--help`, `--root `, | `npm run code-circular` | madge | runtime import cycles | 232 cycles (dispatcher 115) | **ratchet** (no new cycles) | | `npm run code-consistency` | custom | cross-package duplicate exports, direct `process.env`, agent-layout conformance | 16 dup exports · 263 env refs · agents 33/34 | — | | `npm run code-deadcode` | knip | unused files / exports / deps | ~1,790 raw (needs config) | — (report-only) | -| `npm run code-debt` | regex scan | TODO/FIXME/HACK/XXX, `@deprecated`, skipped/focused tests | TODO 207 · `@deprecated` 41 · skipped 3 · focused 0 | **gate** (no focused / no new skips) | +| `npm run code-debt` | regex scan | TODO/FIXME/HACK/XXX, `@deprecated`, skipped/focused tests | TODO 207 · `@deprecated` 41 · skipped 2 · focused 0 | **gate** (no focused / no new skips) | ### Modes @@ -83,15 +83,18 @@ Dead-code is **not** gated yet — knip's numbers are inflated until - **Quick win #2 — skipped-test detection + triage.** Taught `code-debt` to see `.skip.each`/`.only.each` and to ignore conditional/placeholder stubs (empty `() => {}` bodies from the `testIf`/`describeIf` key-gates and data-driven - loops), so the count reflects genuinely disabled tests: **14 → 3**. Re-enabled - the `CalendarDate` grammar test (the converter it waited on now exists). + loops), so the count reflects genuinely disabled tests. Re-enabled the + `CalendarDate` grammar test (the converter it waited on now exists) and + replaced the dead, no-op `api/test/api.spec.ts` with a real `api.test.ts` + web-server smoke test: **14 → 2** (both remaining are intentional). ### Next — quick wins (low risk) -- **3 remaining disabled tests** — un-skip or delete: `api/test/api.spec.ts` - (`describe.skip`; its `expect(response.ok)` calls are also no-op assertions), - plus the `.skip.each` data suites `actionGrammar/test/nfaDfaParity.spec.ts` - and `actionSchema/test/regen.spec.ts`. The debt gate already blocks new ones. +- **Skipped tests — down to 2, both intentional** (no action needed): the + documented `.skip.each` suites `actionGrammar/test/nfaDfaParity.spec.ts` + (DFA-AST ruleRef-binding gap) and `actionSchema/test/regen.spec.ts` + (exact-regen, explicitly not a goal). Un-skip when those land; the debt gate + already blocks new ones. - **WebSocket helpers** — `createWebSocket` + `keepWebSocketAlive` are reimplemented in 4 packages; point `browser`/`coda`/`shell` at `utils/webSocketUtils` and delete the forks. From 55c9ffd7be6c8d32774df09d17c6ad6c048800fa Mon Sep 17 00:00:00 2001 From: robgruen Date: Tue, 7 Jul 2026 16:22:30 -0700 Subject: [PATCH 20/23] updated test run --- ts/packages/api/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ts/packages/api/package.json b/ts/packages/api/package.json index cdc24f8d6a..297d2933ff 100644 --- a/ts/packages/api/package.json +++ b/ts/packages/api/package.json @@ -21,7 +21,8 @@ "prettier:fix": "prettier --write . --ignore-path ../../.prettierignore", "start": "node ./dist/index.js", "test": "npm run test:local", - "test:local": "pnpm run jest-esm --testPathPattern=\".*[.]spec[.]js\"", + "test:live": "pnpm run jest-esm --testPathPattern=\".*[.]test[.]js\"", + "test:local": "pnpm run jest-esm --testPathPattern=\".*[.]spec[.]js\" --passWithNoTests", "test:local:debug": "node --inspect-brk --no-warnings --experimental-vm-modules ./node_modules/jest/bin/jest.js --testPathPattern=\".*\\.spec\\.js\"", "tsc": "tsc -b" }, From 571c1c825f221766eddc0b2e8538804c72060144 Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Tue, 7 Jul 2026 19:54:53 -0700 Subject: [PATCH 21/23] updated to output lines and locations of offenders. Added exception list to each report/gate/check. pnpm lint now runs these checks in addition to prettier --- ts/package.json | 4 +- ts/tools/scripts/code/README.md | 17 ++ ts/tools/scripts/code/circularDepsReport.ts | 78 +++++- ts/tools/scripts/code/complexityReport.ts | 2 +- ts/tools/scripts/code/debtMarkersReport.ts | 86 ++++++- ts/tools/scripts/code/lintReport.ts | 261 +++++++++++++++++++- 6 files changed, 430 insertions(+), 18 deletions(-) diff --git a/ts/package.json b/ts/package.json index e67f8bd1a5..e726f4bc96 100644 --- a/ts/package.json +++ b/ts/package.json @@ -48,8 +48,8 @@ "knowledgeVisualizer": "pnpm -C packages/knowledgeVisualizer exec npm run start", "kv": "pnpm -C packages/knowledgeVisualizer exec npm run start", "line-histogram": "npx tsx tools/scripts/lineHistogram.ts", - "lint": "fluid-build . -t prettier", - "lint:fix": "pnpm run prettier:fix", + "lint": "fluid-build . -t prettier && pnpm run code-lint -- --ratchet", + "lint:fix": "pnpm run code-lint -- --fix --changed && pnpm run prettier:fix", "montage": "pnpm -C packages/montage exec npm run start", "postinstall:better-sqlite3-node-copy": "node ./tools/scripts/copy-better-sqlite3-node.js", "postinstall:better-sqlite3-node-restore": "node ./tools/scripts/restore-better-sqlite3-node.js", diff --git a/ts/tools/scripts/code/README.md b/ts/tools/scripts/code/README.md index 9df48ce8f0..9ddbda24d3 100644 --- a/ts/tools/scripts/code/README.md +++ b/ts/tools/scripts/code/README.md @@ -38,12 +38,29 @@ Run any of these from `ts/`. All accept `--help`, `--root `, ### Tool-specific flags - **code-lint:** `--fix` (apply `no-var`/`prefer-const` autofixes in place), + `--changed` (with `--fix`, only rewrite files changed vs `--base`), `--type-aware` (adds `no-floating-promises`, `no-misused-promises`, `no-deprecated` — slower, report only), `--new-file-max `. - **code-complexity:** `--cyclomatic `, `--cognitive `, `--new-file-cyclomatic `, `--new-file-cognitive `. - **code-deadcode:** `--config` (defaults to [`knip.jsonc`](./knip.jsonc)). +### Baseline exceptions + +The ratchet/gate tools accept `--exceptions-file ` — an optional JSON file +that grandfathers a specific set of known offenders so they don't trip the gate +(useful when a file move that git rename detection misses makes pre-existing +debt look new). Each file's `--ratchet`/`--gate` step honors it; the report +modes ignore it. Shape is `{ "exceptions": [ ... ] }` (a bare array also works): + +- **code-lint** / **code-complexity** / **code-debt:** entries are + `{ "file": "packages/foo/src/bar.ts", "line": 42 }` (paths are normalized, so + a leading `ts/` is optional). Matches by `file:line`. +- **code-circular:** entries are `{ "cycle": ["packages/a/src/x.ts", "packages/b/src/y.ts"] }` + or `{ "key": "packages/a/src/x.ts > packages/b/src/y.ts" }`. Matched + rotation-invariantly against detected cycles. + + ### Notes for maintainers - `npx` cannot resolve local bins in some dev environments; the tools invoke diff --git a/ts/tools/scripts/code/circularDepsReport.ts b/ts/tools/scripts/code/circularDepsReport.ts index 4e350e5a7d..e072a83daa 100644 --- a/ts/tools/scripts/code/circularDepsReport.ts +++ b/ts/tools/scripts/code/circularDepsReport.ts @@ -37,6 +37,7 @@ * --out-dir Output directory (default tools/scripts/code/circular-report). * --ratchet CI gate: fail if changed code introduces new cycles vs --base. * --base Base git ref for --ratchet (default origin/main). + * --exceptions-file Baseline exceptions (known cycles) ignored by --ratchet. * --help Show this help. */ @@ -148,6 +149,59 @@ function canonicalKey(cycle: string[]): string { return [...cycle.slice(minIdx), ...cycle.slice(0, minIdx)].join(" > "); } +// Baseline exceptions: a JSON file of known cycles the ratchet should ignore. +// Each entry is either a "cycle" (array of module paths) or a "key" string +// ("a > b > c"); both are canonicalized the same way as detected cycles, so a +// grandfathered cycle matches regardless of rotation. Lets a pre-existing cycle +// that a refactor merely relocates pass without weakening the gate. +function normalizeCycleNode(node: string): string { + return node + .trim() + .replace(/\\/g, "/") + .replace(/^\.\//, "") + .replace(/^ts\//, ""); +} + +function loadCycleExceptionSet( + exceptionsFile: string | undefined, +): Set { + if (!exceptionsFile) { + return new Set(); + } + const filePath = path.isAbsolute(exceptionsFile) + ? exceptionsFile + : path.resolve(process.cwd(), exceptionsFile); + if (!fs.existsSync(filePath)) { + throw new Error(`Exceptions file not found: ${filePath}`); + } + let raw: unknown; + try { + raw = JSON.parse(fs.readFileSync(filePath, "utf8")); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + throw new Error( + `Invalid JSON in exceptions file ${filePath}: ${message}`, + ); + } + const entries = Array.isArray(raw) + ? raw + : ((raw as { exceptions?: Array<{ cycle?: string[]; key?: string }> }) + .exceptions ?? []); + const out = new Set(); + for (const entry of entries) { + let nodes: string[] = []; + if (Array.isArray(entry?.cycle)) { + nodes = entry.cycle.map(normalizeCycleNode).filter(Boolean); + } else if (typeof entry?.key === "string") { + nodes = entry.key.split(">").map(normalizeCycleNode).filter(Boolean); + } + if (nodes.length > 0) { + out.add(canonicalKey(nodes)); + } + } + return out; +} + // --------------------------------------------------------------------------- // Argument parsing // --------------------------------------------------------------------------- @@ -159,6 +213,7 @@ interface Options { top: number; ratchet: boolean; base: string; + exceptionsFile?: string; help: boolean; } @@ -170,6 +225,7 @@ function parseArgs(argv: string[]): Options { top: 25, ratchet: false, base: "origin/main", + exceptionsFile: undefined, help: false, }; @@ -211,6 +267,14 @@ function parseArgs(argv: string[]): Options { opts.base = next; i++; break; + case "--exceptions-file": + case "--exceptionsFile": + if (next === undefined) { + throw new Error(`${arg} requires a path`); + } + opts.exceptionsFile = next; + i++; + break; case "--root": if (next === undefined) { throw new Error("--root requires a path"); @@ -251,6 +315,10 @@ Options: --out-dir Output directory (default: tools/scripts/code/circular-report). --ratchet CI gate: fail if changed code introduces new cycles vs --base. --base Base git ref for --ratchet (default origin/main). + --exceptions-file + Optional JSON baseline-exception file. Cycles listed in it + (as a "cycle" node array or a "key" string) are ignored by + the --ratchet gate. --help Show this help.`; // --------------------------------------------------------------------------- @@ -551,6 +619,7 @@ function git(args: string[], cwd: string): string { } async function runRatchet(opts: Options): Promise { + const exceptions = loadCycleExceptionSet(opts.exceptionsFile); let repoRoot: string; let mergeBase: string; try { @@ -600,7 +669,7 @@ async function runRatchet(opts: Options): Promise { const newCycles: string[][] = []; for (const [key, cycle] of headKeys) { - if (!baseKeys.has(key)) { + if (!baseKeys.has(key) && !exceptions.has(key)) { newCycles.push(cycle); } } @@ -608,12 +677,17 @@ async function runRatchet(opts: Options): Promise { console.log( `Ratchet: cycles base ${baseKeys.size} -> head ${headKeys.size}`, ); + if (exceptions.size > 0) { + console.log( + ` Baseline exceptions ignored: ${exceptions.size} cycle(s).`, + ); + } if (newCycles.length > 0) { console.error( `\nRatchet FAILED: ${newCycles.length} new circular dependency(ies) introduced vs the base:`, ); - for (const c of newCycles.slice(0, 50)) { + for (const c of newCycles) { console.error(` ${c.join(" > ")}`); } console.error( diff --git a/ts/tools/scripts/code/complexityReport.ts b/ts/tools/scripts/code/complexityReport.ts index 352fb889c1..7ce3ec17b0 100644 --- a/ts/tools/scripts/code/complexityReport.ts +++ b/ts/tools/scripts/code/complexityReport.ts @@ -1050,7 +1050,7 @@ async function runRatchet(opts: Options): Promise { console.log(""); console.log(title); console.log(" CC Cog Location"); - for (const f of rows.slice(0, opts.top)) { + for (const f of rows) { console.log( ` ${String(f.cyclomatic).padStart(3)} ${String( f.cognitive, diff --git a/ts/tools/scripts/code/debtMarkersReport.ts b/ts/tools/scripts/code/debtMarkersReport.ts index d62ca5ec73..e1d0ed09b8 100644 --- a/ts/tools/scripts/code/debtMarkersReport.ts +++ b/ts/tools/scripts/code/debtMarkersReport.ts @@ -33,6 +33,7 @@ * --out-dir Output directory (default tools/scripts/code/debt-report). * --gate CI gate: fail on focused tests / new skipped tests vs --base. * --base Base git ref for --gate (default origin/main). + * --exceptions-file Baseline exceptions (file:line) ignored by --gate. * --help Show this help. */ @@ -122,6 +123,7 @@ interface Options { top: number; gate: boolean; base: string; + exceptionsFile?: string; help: boolean; } @@ -132,6 +134,7 @@ function parseArgs(argv: string[]): Options { top: 25, gate: false, base: "origin/main", + exceptionsFile: undefined, help: false, }; @@ -170,6 +173,14 @@ function parseArgs(argv: string[]): Options { opts.base = next; i++; break; + case "--exceptions-file": + case "--exceptionsFile": + if (next === undefined) { + throw new Error(`${arg} requires a path`); + } + opts.exceptionsFile = next; + i++; + break; case "--root": if (next === undefined) { throw new Error("--root requires a path"); @@ -209,6 +220,9 @@ Options: --out-dir Output directory (default: tools/scripts/code/debt-report). --gate CI gate: fail on focused tests / new skipped tests vs --base. --base Base git ref for --gate (default origin/main). + --exceptions-file + Optional JSON baseline-exception file. Focused/skipped tests + listed in it (by file:line) are ignored by the --gate check. --help Show this help.`; // --------------------------------------------------------------------------- @@ -559,7 +573,56 @@ function isSource(rel: string): boolean { ); } +// Baseline exceptions: a JSON file of { file, line } entries (or +// { exceptions: [...] }) whose file:line focused/skipped tests the gate ignores. +// Lets a deliberately retained marker be grandfathered without weakening the +// gate for everything else. +function normalizeExceptionPath(file: string): string { + const normalized = file.replaceAll("\\", "/").replace(/^\.\//, ""); + return normalized.replace(/^ts\//, ""); +} + +function exceptionKey(file: string, line: number): string { + return `${normalizeExceptionPath(file)}:${line}`; +} + +function loadExceptionSet(exceptionsFile: string | undefined): Set { + if (!exceptionsFile) { + return new Set(); + } + const filePath = path.isAbsolute(exceptionsFile) + ? exceptionsFile + : path.resolve(process.cwd(), exceptionsFile); + if (!fs.existsSync(filePath)) { + throw new Error(`Exceptions file not found: ${filePath}`); + } + let raw: unknown; + try { + raw = JSON.parse(fs.readFileSync(filePath, "utf8")); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + throw new Error( + `Invalid JSON in exceptions file ${filePath}: ${message}`, + ); + } + const entries = Array.isArray(raw) + ? raw + : ((raw as { exceptions?: Array<{ file?: string; line?: number }> }) + .exceptions ?? []); + const out = new Set(); + for (const entry of entries) { + const file = normalizeExceptionPath(entry?.file ?? ""); + const line = entry?.line; + if (!file || typeof line !== "number" || line <= 0) { + continue; + } + out.add(`${file}:${line}`); + } + return out; +} + function runGate(opts: Options): number { + const exceptions = loadExceptionSet(opts.exceptionsFile); let repoRoot: string; let mergeBase: string; try { @@ -592,6 +655,7 @@ function runGate(opts: Options): number { } const focusedHits: string[] = []; + const skipHits: string[] = []; let headSkips = 0; let baseSkips = 0; @@ -604,14 +668,23 @@ function runGate(opts: Options): number { const content = fs.readFileSync(headAbs, "utf8"); const lines = content.split(/\r?\n/); for (let i = 0; i < lines.length; i++) { + // A baseline exception (file:line) grandfathers whatever + // focused/skipped marker sits on that head line. + if (exceptions.has(exceptionKey(e.head, i + 1))) { + continue; + } FOCUSED_RE.lastIndex = 0; if (FOCUSED_RE.test(lines[i])) { focusedHits.push( ` ${e.head}:${i + 1} ${lines[i].trim()}`, ); } + const skipsOnLine = countMatches(lines[i], SKIP_RE); + if (skipsOnLine > 0) { + headSkips += skipsOnLine; + skipHits.push(` ${e.head}:${i + 1} ${lines[i].trim()}`); + } } - headSkips += countMatches(content, SKIP_RE); } if (e.base && isTestFile(e.base)) { try { @@ -629,6 +702,11 @@ function runGate(opts: Options): number { console.log( `Gate: ${entries.length} changed source file(s) | skipped tests base ${baseSkips} -> head ${headSkips}`, ); + if (exceptions.size > 0) { + console.log( + ` Baseline exceptions ignored: ${exceptions.size} (file:line).`, + ); + } let failed = false; if (focusedHits.length > 0) { @@ -636,7 +714,7 @@ function runGate(opts: Options): number { console.error( `\nGate FAILED: focused test(s) must not be committed (.only/fit/fdescribe):`, ); - focusedHits.slice(0, 50).forEach((h) => console.error(h)); + focusedHits.forEach((h) => console.error(h)); } if (headSkips > baseSkips) { failed = true; @@ -644,6 +722,10 @@ function runGate(opts: Options): number { `\nGate FAILED: changed files add ${headSkips - baseSkips} skipped test(s) ` + "(.skip/xit/xdescribe). Un-skip or delete them.", ); + if (skipHits.length > 0) { + console.error("Skipped tests in the changed files:"); + skipHits.forEach((h) => console.error(h)); + } } if (failed) { diff --git a/ts/tools/scripts/code/lintReport.ts b/ts/tools/scripts/code/lintReport.ts index 68cd310277..ba1a953f6d 100644 --- a/ts/tools/scripts/code/lintReport.ts +++ b/ts/tools/scripts/code/lintReport.ts @@ -33,6 +33,8 @@ * file), so violations in touched code can only trend down. Ratchet uses * the syntactic rules only, so HEAD and the base (materialized to a temp * dir, where the TS project is unavailable) are compared like-for-like. + * On failure it prints where the new violations are — the file:line of the + * regressed rules in each changed file. * * Outputs (written to --out-dir, default tools/scripts/code/lint-report): * - violations.csv : every violation, ranked @@ -54,6 +56,9 @@ * --base Base git ref for --ratchet (default origin/main). * --new-file-max With --ratchet, also fail if any NEW file has more than * violations (-1 = disabled, the default). + * --fix Apply the auto-fixable rules (no-var, prefer-const). + * --changed With --fix, only fix files changed since --base. + * --exceptions-file Baseline exceptions (file:line) ignored by --ratchet. * --help Show this help. */ @@ -146,6 +151,8 @@ interface Options { base: string; newFileMax: number; fix: boolean; + changed: boolean; + exceptionsFile?: string; help: boolean; } @@ -167,6 +174,8 @@ function parseArgs(argv: string[]): Options { base: "origin/main", newFileMax: -1, fix: false, + changed: false, + exceptionsFile: undefined, help: false, }; @@ -197,6 +206,17 @@ function parseArgs(argv: string[]): Options { case "--fix": opts.fix = true; break; + case "--changed": + opts.changed = true; + break; + case "--exceptions-file": + case "--exceptionsFile": + if (next === undefined) { + throw new Error(`${arg} requires a path`); + } + opts.exceptionsFile = next; + i++; + break; case "--top": opts.top = parseIntArg(arg, next); i++; @@ -253,8 +273,13 @@ Options: --include-tests Include test files (excluded by default). --type-aware Also run type-aware rules (no-floating-promises, no-misused-promises, no-deprecated). Slower; report only. - --fix Apply the auto-fixable rules (no-var, prefer-const) to the - tree in place. Review + rebuild before committing. + --fix Apply the auto-fixable rules (no-var, prefer-const) in + place. Review + rebuild before committing. + --changed With --fix, only fix files changed since --base (instead + of the whole tree). Keeps a PR self-contained. + --exceptions-file + Optional JSON baseline-exception file. Violations listed + in it (by file:line) are ignored by the --ratchet gate. --top Number of worst offenders to print / embed (default 25). --root Directory to scan (default: the ts/ root). --out-dir Output directory (default: tools/scripts/code/lint-report). @@ -737,7 +762,15 @@ function parseNameStatus(raw: string): DiffEntry[] { return entries; } -async function runRatchet(opts: Options): Promise { +interface ChangedSources { + repoRoot: string; + mergeBase: string; + entries: DiffEntry[]; +} + +// Resolve the merge base against --base and collect the changed source files +// (respecting ignores/tests). Returns null if the base ref cannot be resolved. +function collectChangedSources(opts: Options): ChangedSources | null { let repoRoot: string; let mergeBase: string; try { @@ -745,10 +778,10 @@ async function runRatchet(opts: Options): Promise { mergeBase = git(["merge-base", opts.base, "HEAD"], opts.root).trim(); } catch { console.error( - `Ratchet: could not resolve base ref "${opts.base}" via git. ` + + `Could not resolve base ref "${opts.base}" via git. ` + "Pass --base (e.g. origin/main) and ensure it is fetched.", ); - return 2; + return null; } const entries = parseNameStatus( @@ -764,11 +797,170 @@ async function runRatchet(opts: Options): Promise { return !relToRoot.startsWith("..") && !path.isAbsolute(relToRoot); }); + return { repoRoot, mergeBase, entries }; +} + +// A rule that got worse in a specific changed file: the HEAD content has more +// occurrences than the base did. These are the locations a PR introduced. +interface RegressionGroup { + file: string; + rule: string; + delta: number; + locations: Violation[]; +} + +// Compute per-file, per-rule regressions: HEAD occurrences minus the base +// occurrences (mapped through renames). Only positive deltas are regressions. +function locateRegressions( + entries: DiffEntry[], + head: LintOutput, + base: LintOutput, +): RegressionGroup[] { + const baseToHead = new Map(); + for (const e of entries) { + if (e.base) { + baseToHead.set( + e.base.replace(/\\/g, "/"), + e.head.replace(/\\/g, "/"), + ); + } + } + + const SEP = "\u0000"; + const headByFileRule = new Map(); + for (const v of head.violations) { + const key = `${v.file}${SEP}${v.rule}`; + const list = headByFileRule.get(key); + if (list) { + list.push(v); + } else { + headByFileRule.set(key, [v]); + } + } + + const baseCountByFileRule = new Map(); + for (const v of base.violations) { + const headFile = baseToHead.get(v.file) ?? v.file; + const key = `${headFile}${SEP}${v.rule}`; + baseCountByFileRule.set(key, (baseCountByFileRule.get(key) ?? 0) + 1); + } + + const regressions: RegressionGroup[] = []; + for (const [key, locations] of headByFileRule) { + const delta = locations.length - (baseCountByFileRule.get(key) ?? 0); + if (delta > 0) { + const sep = key.indexOf(SEP); + regressions.push({ + file: key.slice(0, sep), + rule: key.slice(sep + 1), + delta, + locations: [...locations].sort( + (a, b) => a.line - b.line || a.column - b.column, + ), + }); + } + } + regressions.sort( + (a, b) => + b.delta - a.delta || + a.file.localeCompare(b.file) || + a.rule.localeCompare(b.rule), + ); + return regressions; +} + +// Print, per changed file, the rules that regressed and the HEAD locations to +// look at. This answers "where is the regression?" without a committed baseline. +// Everything is printed: the set is bounded by the files this PR changed, so a +// developer can fix them all in one pass rather than re-running to find more. +function printRegressionLocations(regressions: RegressionGroup[]): void { + if (regressions.length === 0) { + return; + } + const byFile = new Map(); + for (const g of regressions) { + const arr = byFile.get(g.file); + if (arr) { + arr.push(g); + } else { + byFile.set(g.file, [g]); + } + } + + console.error("\nWhere the new violations are (changed files):"); + for (const [file, groups] of byFile) { + console.error(` ${file}`); + for (const g of groups) { + console.error(` ${g.rule} (+${g.delta})`); + for (const v of g.locations) { + console.error(` ${v.line}:${v.column} ${v.message}`); + } + } + } +} + +// Baseline exceptions: a JSON file of { file, line } entries (or +// { exceptions: [...] }) whose file:line violations the ratchet ignores. Lets a +// known pre-existing violation — e.g. one surfaced by a file move that git +// rename detection misses — be grandfathered without weakening the gate. +function normalizeExceptionPath(file: string): string { + const normalized = file.replaceAll("\\", "/").replace(/^\.\//, ""); + return normalized.replace(/^ts\//, ""); +} + +function exceptionKey(file: string, line: number): string { + return `${normalizeExceptionPath(file)}:${line}`; +} + +function loadExceptionSet(exceptionsFile: string | undefined): Set { + if (!exceptionsFile) { + return new Set(); + } + const filePath = path.isAbsolute(exceptionsFile) + ? exceptionsFile + : path.resolve(process.cwd(), exceptionsFile); + if (!fs.existsSync(filePath)) { + throw new Error(`Exceptions file not found: ${filePath}`); + } + let raw: unknown; + try { + raw = JSON.parse(fs.readFileSync(filePath, "utf8")); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + throw new Error( + `Invalid JSON in exceptions file ${filePath}: ${message}`, + ); + } + const entries = Array.isArray(raw) + ? raw + : ((raw as { exceptions?: Array<{ file?: string; line?: number }> }) + .exceptions ?? []); + const out = new Set(); + for (const entry of entries) { + const file = normalizeExceptionPath(entry?.file ?? ""); + const line = entry?.line; + if (!file || typeof line !== "number" || line <= 0) { + continue; + } + out.add(`${file}:${line}`); + } + return out; +} + +async function runRatchet(opts: Options): Promise { + const changed = collectChangedSources(opts); + if (!changed) { + return 2; + } + const { repoRoot, mergeBase, entries } = changed; + if (entries.length === 0) { console.log("Ratchet: no changed source files to check. OK."); return 0; } + const exceptions = loadExceptionSet(opts.exceptionsFile); + const lintOpts = { typeAware: false, useIgnores: false, @@ -814,6 +1006,18 @@ async function runRatchet(opts: Options): Promise { fs.rmSync(tmp, { recursive: true, force: true }); } + // Baseline exceptions: drop known violations (by file:line) from both sides + // so they neither count toward the totals nor trip the new-file / zero- + // tolerance checks. Applied before any rollup below. + if (exceptions.size > 0) { + head.violations = head.violations.filter( + (v) => !exceptions.has(exceptionKey(v.file, v.line)), + ); + base.violations = base.violations.filter( + (v) => !exceptions.has(exceptionKey(v.file, v.line)), + ); + } + // Compare per-rule and totals. const headTotal = head.violations.length; const baseTotal = base.violations.length; @@ -824,6 +1028,11 @@ async function runRatchet(opts: Options): Promise { `Ratchet: ${entries.length} changed source file(s) | ` + `violations base ${baseTotal} -> head ${headTotal}`, ); + if (exceptions.size > 0) { + console.log( + ` Baseline exceptions ignored: ${exceptions.size} (file:line).`, + ); + } const worsened: string[] = []; for (const rule of new Set([...headByRule.keys(), ...baseByRule.keys()])) { @@ -855,6 +1064,8 @@ async function runRatchet(opts: Options): Promise { } } + const regressions = locateRegressions(entries, head, base); + let failed = false; if (headTotal > baseTotal) { failed = true; @@ -883,14 +1094,20 @@ async function runRatchet(opts: Options): Promise { failed = true; console.error( "\nRatchet FAILED: zero-tolerance rules present in changed files " + - "(auto-fixable — run `npm run code-lint -- --fix`):", + "(auto-fixable — run `npm run code-lint -- --fix --changed`):", ); zeroToleranceFailures.forEach((w) => console.error(w)); } if (failed) { + printRegressionLocations(regressions); console.error( - "\nRun `npm run code-lint` and fix the flagged lines in the files this PR changes.", + "\nNext steps:\n" + + " - Fix the locations listed above in the files this PR changes.\n" + + " - Mechanical rules (no-var, prefer-const) can be auto-fixed:\n" + + " npm run code-lint -- --fix --changed\n" + + " - Re-check with:\n" + + ` npm run code-lint -- --ratchet --base ${opts.base}`, ); return 1; } @@ -931,14 +1148,34 @@ function buildFixConfig( async function runFix(opts: Options): Promise { const started = Date.now(); + + let cwd = opts.root; + let patterns: string[] = [SOURCE_GLOB]; + let useIgnores = true; + if (opts.changed) { + const changed = collectChangedSources(opts); + if (!changed) { + return 2; + } + cwd = changed.repoRoot; + patterns = changed.entries + .map((e) => path.resolve(changed.repoRoot, e.head)) + .filter((p) => fs.existsSync(p)); + useIgnores = false; + if (patterns.length === 0) { + console.log("No changed source files to fix."); + return 0; + } + } + const eslint = new ESLint({ - cwd: opts.root, + cwd, errorOnUnmatchedPattern: false, overrideConfigFile: true, - overrideConfig: buildFixConfig(true, opts.includeTests), + overrideConfig: buildFixConfig(useIgnores, opts.includeTests), fix: true, }); - const results = await eslint.lintFiles([SOURCE_GLOB]); + const results = await eslint.lintFiles(patterns); await ESLint.outputFixes(results); let fixedFiles = 0; @@ -951,7 +1188,9 @@ async function runFix(opts: Options): Promise { } console.log(""); - console.log(`Lint auto-fix (rules: ${Object.keys(FIX_RULES).join(", ")})`); + console.log( + `Lint auto-fix (${opts.changed ? "changed files" : "whole tree"}; rules: ${Object.keys(FIX_RULES).join(", ")})`, + ); console.log( `Rewrote ${num(fixedFiles)} file(s) in ${((Date.now() - started) / 1000).toFixed(1)}s.`, ); From 3104b0831995a092aa02b2a7405430a19c4e835a Mon Sep 17 00:00:00 2001 From: typeagent-bot Date: Wed, 8 Jul 2026 03:02:14 +0000 Subject: [PATCH 22/23] style: apply prettier formatting and policy fixes --- ts/tools/scripts/code/README.md | 1 - ts/tools/scripts/code/circularDepsReport.ts | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ts/tools/scripts/code/README.md b/ts/tools/scripts/code/README.md index 556715ea47..d678f97fee 100644 --- a/ts/tools/scripts/code/README.md +++ b/ts/tools/scripts/code/README.md @@ -60,7 +60,6 @@ modes ignore it. Shape is `{ "exceptions": [ ... ] }` (a bare array also works): or `{ "key": "packages/a/src/x.ts > packages/b/src/y.ts" }`. Matched rotation-invariantly against detected cycles. - ### Notes for maintainers - `npx` cannot resolve local bins in some dev environments; the tools invoke diff --git a/ts/tools/scripts/code/circularDepsReport.ts b/ts/tools/scripts/code/circularDepsReport.ts index e072a83daa..bd123d98b7 100644 --- a/ts/tools/scripts/code/circularDepsReport.ts +++ b/ts/tools/scripts/code/circularDepsReport.ts @@ -193,7 +193,10 @@ function loadCycleExceptionSet( if (Array.isArray(entry?.cycle)) { nodes = entry.cycle.map(normalizeCycleNode).filter(Boolean); } else if (typeof entry?.key === "string") { - nodes = entry.key.split(">").map(normalizeCycleNode).filter(Boolean); + nodes = entry.key + .split(">") + .map(normalizeCycleNode) + .filter(Boolean); } if (nodes.length > 0) { out.add(canonicalKey(nodes)); From c83b42c84025039c6d33f7d12592f12c1268c87f Mon Sep 17 00:00:00 2001 From: Robert Gruen Date: Tue, 7 Jul 2026 20:18:23 -0700 Subject: [PATCH 23/23] fixed complexity --- ts/tools/scripts/code/debtMarkersReport.ts | 98 ++++++++++++++-------- 1 file changed, 63 insertions(+), 35 deletions(-) diff --git a/ts/tools/scripts/code/debtMarkersReport.ts b/ts/tools/scripts/code/debtMarkersReport.ts index c3121936a0..11d0755227 100644 --- a/ts/tools/scripts/code/debtMarkersReport.ts +++ b/ts/tools/scripts/code/debtMarkersReport.ts @@ -640,6 +640,64 @@ function loadExceptionSet(exceptionsFile: string | undefined): Set { return out; } +interface HeadScan { + focusedHits: string[]; + skipHits: string[]; + skips: number; +} + +// Scan a changed test file's HEAD content for focused/skipped test markers, +// honoring baseline exceptions and the empty-stub exclusion (matching +// countSkips). Returns the focused/skip locations and the real skip count. +function scanHeadTestFile( + relPath: string, + absPath: string, + exceptions: Set, +): HeadScan { + const focusedHits: string[] = []; + const skipHits: string[] = []; + let skips = 0; + const lines = fs.readFileSync(absPath, "utf8").split(/\r?\n/); + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + // A baseline exception (file:line) grandfathers whatever + // focused/skipped marker sits on that head line. + if (exceptions.has(exceptionKey(relPath, i + 1))) { + continue; + } + FOCUSED_RE.lastIndex = 0; + if (FOCUSED_RE.test(line)) { + focusedHits.push(` ${relPath}:${i + 1} ${line.trim()}`); + } + // Ignore empty stub bodies (`() => {}`) so the gate only trips on + // genuinely disabled tests, not key-gated/data-driven runtime skips. + if (EMPTY_SKIP_STUB_RE.test(line)) { + continue; + } + SKIP_RE.lastIndex = 0; + const skipsOnLine = line.match(SKIP_RE)?.length ?? 0; + if (skipsOnLine > 0) { + skips += skipsOnLine; + skipHits.push(` ${relPath}:${i + 1} ${line.trim()}`); + } + } + return { focusedHits, skipHits, skips }; +} + +// Count real skipped tests in a changed file's content at the merge base. +// Returns 0 if the file did not exist there. +function countBaseSkips( + repoRoot: string, + mergeBase: string, + basePath: string, +): number { + try { + return countSkips(git(["show", `${mergeBase}:${basePath}`], repoRoot)); + } catch { + return 0; // file did not exist at base + } +} + function runGate(opts: Options): number { const exceptions = loadExceptionSet(opts.exceptionsFile); let repoRoot: string; @@ -684,43 +742,13 @@ function runGate(opts: Options): number { } const headAbs = path.resolve(repoRoot, e.head); if (fs.existsSync(headAbs)) { - const content = fs.readFileSync(headAbs, "utf8"); - const lines = content.split(/\r?\n/); - for (let i = 0; i < lines.length; i++) { - // A baseline exception (file:line) grandfathers whatever - // focused/skipped marker sits on that head line. - if (exceptions.has(exceptionKey(e.head, i + 1))) { - continue; - } - FOCUSED_RE.lastIndex = 0; - if (FOCUSED_RE.test(lines[i])) { - focusedHits.push( - ` ${e.head}:${i + 1} ${lines[i].trim()}`, - ); - } - // Match countSkips(): ignore empty stub bodies (`() => {}`) so - // the gate only trips on genuinely disabled tests. - if (EMPTY_SKIP_STUB_RE.test(lines[i])) { - continue; - } - SKIP_RE.lastIndex = 0; - const skipsOnLine = lines[i].match(SKIP_RE)?.length ?? 0; - if (skipsOnLine > 0) { - headSkips += skipsOnLine; - skipHits.push(` ${e.head}:${i + 1} ${lines[i].trim()}`); - } - } + const scan = scanHeadTestFile(e.head, headAbs, exceptions); + focusedHits.push(...scan.focusedHits); + skipHits.push(...scan.skipHits); + headSkips += scan.skips; } if (e.base && isTestFile(e.base)) { - try { - const baseContent = git( - ["show", `${mergeBase}:${e.base}`], - repoRoot, - ); - baseSkips += countSkips(baseContent); - } catch { - /* file did not exist at base */ - } + baseSkips += countBaseSkips(repoRoot, mergeBase, e.base); } }