diff --git a/package-lock.json b/package-lock.json index 41b8966..2947e3e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,16 +21,101 @@ "devDependencies": { "@types/better-sqlite3": "^7.6.13", "@types/node": "^22.15.3", - "typescript": "^5.8.3" + "@vitest/coverage-v8": "^4.1.10", + "typescript": "^5.8.3", + "vitest": "^4.1.10" }, "engines": { "node": ">=22.0.0 <27.0.0" } }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -530,6 +615,63 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.138.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.138.0.tgz", + "integrity": "sha512-1a7ZKmrRTCoN1XMZ4L0PyyqrMnrNlLyPuOkdSX2MZg7IiIGRUyurNhAm73ptDOraoBcIordsIGKNPKUzy3ZmfA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -569,12 +711,6 @@ "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", "license": "BSD-3-Clause" }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz", - "integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==", - "license": "BSD-3-Clause" - }, "node_modules/@protobufjs/path": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", @@ -593,142 +729,650 @@ "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", "license": "BSD-3-Clause" }, - "node_modules/@types/better-sqlite3": { - "version": "7.6.13", - "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", - "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.4.tgz", + "integrity": "sha512-EZLpf/8y7GXkkra90ML47kzik/GMP3EMcE9bPyHmRfxLC6z9+aW5A8poCsoxjrT5GfEcNAAvWwUHjvP1pUQkfw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@types/node": "*" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@types/node": { - "version": "22.19.19", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", - "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.4.tgz", + "integrity": "sha512-aUi+HBvmYb7j8krl1+qJgkG8C17fO79gk3c+jPw4S8glRFc1DTija9S3EyaTSQUm5GJXYKDAsugBEhFHH2vYiQ==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/adm-zip": { - "version": "0.5.17", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.17.tgz", - "integrity": "sha512-+Ut8d9LLqwEvHHJl1+PIHqoyDxFgVN847JTVM3Izi3xHDWPE4UtzzXysMZQs64DMcrJfBeS/uoEP4AD3HQHnQQ==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.4.tgz", + "integrity": "sha512-F7hHC3gwY11+vByKPRWqwGbeXWVgKmL+pTGCinaEhdihzBV2aQ0fvZOch9cXYUOKuKKq429HeYXOqQLc7wFCEg==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=12.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.4.tgz", + "integrity": "sha512-sI5yw+7s92SK6odiEhD5lKCBlWcpjHS5qyqpVQbZAJ0fIzEUXrmbl3DH2ybR3PZogulNJF+COLtmA8hUfvkCCQ==", + "cpu": [ + "x64" ], - "license": "MIT" - }, - "node_modules/better-sqlite3": { - "version": "12.11.1", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.11.1.tgz", - "integrity": "sha512-dq9AtApgg5PGFtBzPFSBl3HZQjHok5gaQCM6zh2Yk0aSmDCs1CbnVI8/HgASQkNKsWFpseIO9beg5xxpYhbIfA==", - "hasInstallScript": true, + "dev": true, "license": "MIT", - "dependencies": { - "bindings": "^1.5.0", - "prebuild-install": "^7.1.1" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": "20.x || 22.x || 23.x || 24.x || 25.x || 26.x" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.4.tgz", + "integrity": "sha512-mCi0OKgEieFircrtVYmQAFGszRtMnZ6fpZAXrxanXAu7lqZcsK1E1RAaZNG0uKAnxox3B1f4EyQNnoyMfN1vAA==", + "cpu": [ + "arm" + ], + "dev": true, "license": "MIT", - "dependencies": { - "file-uri-to-path": "1.0.0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.4.tgz", + "integrity": "sha512-B9Ial3Kv5sh0SHnB1g/QWcUQCEvCF6QKGAl4zXypYj65mVI+B4AhFBwPtSN7pDrJeIx8Z7zdy4ntx+wQABom7w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/boolean": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", - "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "license": "MIT" - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.4.tgz", + "integrity": "sha512-lZVym0PuHE1KZ22gmFTC15lAkrg9iTszR617oYRB/iPY1A56ywoJzVKOJBKaot5RiikCObmur6pogpse3gRcng==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" ], "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/chownr": { + "node_modules/@rolldown/binding-linux-ppc64-gnu": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" - }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.4.tgz", + "integrity": "sha512-t2DNiLJWNTbnEHyUzTumldML6ET4/g16467LZoDDJ3tSxGvguL5/NyC2lCsNKuyRycg9XeDQF5SSv+TNOhQEXg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/deep-extend": { - "version": "0.6.0", + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.4.tgz", + "integrity": "sha512-0WIRnL1Uw4BvTZRLQt+PVgo6ZKTJadlC2btP+/EOXv2f/DWbY0rEgl+y834mIVwP1FkTlWVTrGGJXf12lru7EQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.4.tgz", + "integrity": "sha512-JWtGshGfX+oENAKonoNkqEJX+7hC8yfhi9GUyPX1VX4mdh1y5r+ZiJLR5XzAB0aoP6s/PcILsGjKq8O0mm24bw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.4.tgz", + "integrity": "sha512-rT6yQcxUuXs4CnbofqwHRRV0iem349rLMYpTjkgQGLjrY4ado/eDzwPZPTCgTOlF6Nkp8NEv70yLMTn6qkWxsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.4.tgz", + "integrity": "sha512-KXMGoboq5cyaCQjDA4GLuRiOwBQ0EyFnJoVViLeZ45/3rFItRODEr+NdsBcVpll40hhNArlm/speWGRvj08LzA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.4.tgz", + "integrity": "sha512-5K83rb36oJiY7BCyE9zLZtGcPV4g5wvq+xwdO0XPIwDVZI8cyB/AUjkNXGb92/rnmezEkjMOpgY61rtwjQtFwg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.4.tgz", + "integrity": "sha512-PnWBtw3TV5KOg69HQQDR0mnQuyCmSGR2pAB4DC1rPF808fgKeTUMj2EOEyKATpgiuxuR5APQmiDO7PDgEjTFSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.4.tgz", + "integrity": "sha512-M1lpniBePobTfsa7Ks9a199e1akxsXn+GYBUKsEzv3YFzOm1HJAMNwKI3qr0Zq+mxwx9gOZoTdP1yXRYsZUocQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/better-sqlite3": { + "version": "7.6.13", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", + "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", + "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.10.tgz", + "integrity": "sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.10", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.1.10", + "vitest": "4.1.10" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", + "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", + "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.10", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", + "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", + "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.10", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", + "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "@vitest/utils": "4.1.10", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", + "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", + "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/adm-zip": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.17.tgz", + "integrity": "sha512-+Ut8d9LLqwEvHHJl1+PIHqoyDxFgVN847JTVM3Izi3xHDWPE4UtzzXysMZQs64DMcrJfBeS/uoEP4AD3HQHnQQ==", + "license": "MIT", + "engines": { + "node": ">=12.0" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.4.tgz", + "integrity": "sha512-0bC0/4bTSrnwdhU3IsZDwEdojvuPrSg59OYZfKsLRtJZ0u8VBx9DebfqqG8bRdCC0I7vjgxmPi41P0lpkhJHtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/better-sqlite3": { + "version": "12.11.1", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.11.1.tgz", + "integrity": "sha512-dq9AtApgg5PGFtBzPFSBl3HZQjHok5gaQCM6zh2Yk0aSmDCs1CbnVI8/HgASQkNKsWFpseIO9beg5xxpYhbIfA==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + }, + "engines": { + "node": "20.x || 22.x || 23.x || 24.x || 25.x || 26.x" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT" + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "license": "MIT", @@ -812,6 +1456,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.0.tgz", + "integrity": "sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw==", + "dev": true, + "license": "MIT" + }, "node_modules/es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", @@ -830,6 +1481,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -839,6 +1500,34 @@ "node": ">=6" } }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -857,6 +1546,21 @@ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "license": "MIT" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -914,6 +1618,16 @@ "integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==", "license": "ISC" }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", @@ -923,46 +1637,372 @@ "es-define-property": "^1.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC" + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" ], - "license": "BSD-3-Clause" - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "license": "ISC" + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, "node_modules/long": { "version": "5.3.2", @@ -970,6 +2010,44 @@ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", "license": "Apache-2.0" }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.3.tgz", + "integrity": "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/matcher": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", @@ -1009,6 +2087,25 @@ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "license": "MIT" }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/napi-build-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", @@ -1036,6 +2133,20 @@ "node": ">= 0.4" } }, + "node_modules/obug": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz", + "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1088,12 +2199,68 @@ "integrity": "sha512-BOoomdHYmNRL5r4iQ4bMvsl2t0/hzVQ3OM3PHD0gxeXu1PmggqBv3puZicEUVOA3AtHHYmqZtjMj9FOfGrATTw==", "license": "MIT" }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/platform": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==", "license": "MIT" }, + "node_modules/postcss": { + "version": "8.5.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", + "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/prebuild-install": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", @@ -1122,9 +2289,9 @@ } }, "node_modules/protobufjs": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.2.tgz", - "integrity": "sha512-N9EiLovGEQOJSPF26Ij7qUGvahfEnq0eeYZ02aigIedkmz1qZSwjnP9SBITHJuF/6MYbIW4HDN8zdYjsjqJKXQ==", + "version": "7.6.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz", + "integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { @@ -1134,7 +2301,6 @@ "@protobufjs/eventemitter": "^1.1.1", "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", @@ -1201,6 +2367,40 @@ "node": ">=8.0" } }, + "node_modules/rolldown": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.4.tgz", + "integrity": "sha512-IjZYiLxZwpnhwhdBH2ugdTGVSdhCQUmLxLoqyjiL0JxYjyRst+5a0P3xfrTxJ5F638j4Mvvw5FAX5XE6eHpXbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.138.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.1.4", + "@rolldown/binding-darwin-arm64": "1.1.4", + "@rolldown/binding-darwin-x64": "1.1.4", + "@rolldown/binding-freebsd-x64": "1.1.4", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.4", + "@rolldown/binding-linux-arm64-gnu": "1.1.4", + "@rolldown/binding-linux-arm64-musl": "1.1.4", + "@rolldown/binding-linux-ppc64-gnu": "1.1.4", + "@rolldown/binding-linux-s390x-gnu": "1.1.4", + "@rolldown/binding-linux-x64-gnu": "1.1.4", + "@rolldown/binding-linux-x64-musl": "1.1.4", + "@rolldown/binding-openharmony-arm64": "1.1.4", + "@rolldown/binding-wasm32-wasi": "1.1.4", + "@rolldown/binding-win32-arm64-msvc": "1.1.4", + "@rolldown/binding-win32-x64-msvc": "1.1.4" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1298,6 +2498,13 @@ "@img/sharp-win32-x64": "0.34.5" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/simple-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", @@ -1343,12 +2550,36 @@ "simple-concat": "^1.0.0" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sprintf-js": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", "license": "BSD-3-Clause" }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -1367,6 +2598,19 @@ "node": ">=0.10.0" } }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/tar-fs": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", @@ -1395,6 +2639,50 @@ "node": ">=6" } }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tree-sitter-wasms": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/tree-sitter-wasms/-/tree-sitter-wasms-0.1.13.tgz", @@ -1461,12 +2749,197 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/vite": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.3.tgz", + "integrity": "sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.16", + "rolldown": "~1.1.3", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", + "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.10", + "@vitest/mocker": "4.1.10", + "@vitest/pretty-format": "4.1.10", + "@vitest/runner": "4.1.10", + "@vitest/snapshot": "4.1.10", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.10", + "@vitest/browser-preview": "4.1.10", + "@vitest/browser-webdriverio": "4.1.10", + "@vitest/coverage-istanbul": "4.1.10", + "@vitest/coverage-v8": "4.1.10", + "@vitest/ui": "4.1.10", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, "node_modules/web-tree-sitter": { "version": "0.24.7", "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.24.7.tgz", "integrity": "sha512-CdC/TqVFbXqR+C51v38hv6wOPatKEUGxa39scAeFSm98wIhZxAYonhRQPSMmfZ2w7JDI0zQDdzdmgtNk06/krQ==", "license": "MIT" }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index 8575f7c..03a4f06 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,12 @@ "smoke:openhands": "npm run build && node scripts/smoke-openhands-install.mjs", "smoke:copilot": "npm run build && node scripts/smoke-copilot-install.mjs", "smoke:opencode": "npm run build && node scripts/smoke-opencode-install.mjs", - "test": "npm run build && node scripts/check-transcript-bundle.js && node scripts/check-repo-context.js && node scripts/check-install-options.js && node scripts/check-graph-view.js && node scripts/check-proposal-validate.js && node scripts/check-bm25-tokenizer.js && node scripts/check-anchor-drift.js", - "test:transcript-bundle": "npm run build && node scripts/check-transcript-bundle.js", - "test:repo-context": "npm run build && node scripts/check-repo-context.js", + "test": "npm run build && vitest run", + "test:fast": "vitest run", + "test:watch": "vitest", + "test:coverage": "npm run build && vitest run --coverage", + "test:transcript-bundle": "npm run build && vitest run scripts/check-transcript-bundle.js", + "test:repo-context": "npm run build && vitest run scripts/check-repo-context.js", "eval:bootstrap-current": "npm run build && node dist/evals/cases/bootstrap-current-repo-at-8038fe8/run.js", "eval:coding-proposal-apply-atomicity": "npm run build && node dist/evals/cases/coding-proposal-apply-atomicity-at-d6ebf80/run.js", "eval:curated-doc-bootstrap-vercel-chat": "npm run build && node dist/evals/cases/curated-doc-bootstrap-vercel-chat-at-f3de128/run.js", @@ -65,6 +68,8 @@ "devDependencies": { "@types/better-sqlite3": "^7.6.13", "@types/node": "^22.15.3", - "typescript": "^5.8.3" + "@vitest/coverage-v8": "^4.1.10", + "typescript": "^5.8.3", + "vitest": "^4.1.10" } } diff --git a/scripts/check-anchor-drift.js b/scripts/check-anchor-drift.js index 09815db..a8c5957 100644 --- a/scripts/check-anchor-drift.js +++ b/scripts/check-anchor-drift.js @@ -1,42 +1,101 @@ -import assert from "node:assert/strict"; +import { describe, test, expect, beforeAll } from "vitest"; import { mkdtempSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; const root = new URL("..", import.meta.url); -const { CodeAnchorResolver } = await import(new URL("dist/libs/knowledge-graph/code-anchors/resolver.js", root)); -const { fingerprintClaimAnchors } = await import(new URL("dist/libs/knowledge-graph/code-anchors/fingerprint.js", root)); -const { auditClaimCodeAnchors } = await import(new URL("dist/libs/knowledge-graph/code-anchors/audit.js", root)); +let CodeAnchorResolver; +let fingerprintClaimAnchors; +let auditClaimCodeAnchors; -const repo = mkdtempSync(join(tmpdir(), "greplica-anchor-drift-test-")); -const file = join(repo, "mod.py"); -const anchor = { file: "mod.py", symbol: "foo" }; -const claim = { id: "claim.foo", kind: "fact", text: "foo returns 3", truth: "code_verified", intent: "intended", code_anchors: [anchor] }; +beforeAll(async () => { + const resolver = await import(new URL("dist/libs/knowledge-graph/code-anchors/resolver.js", root)); + const fingerprint = await import(new URL("dist/libs/knowledge-graph/code-anchors/fingerprint.js", root)); + const audit = await import(new URL("dist/libs/knowledge-graph/code-anchors/audit.js", root)); + CodeAnchorResolver = resolver.CodeAnchorResolver; + fingerprintClaimAnchors = fingerprint.fingerprintClaimAnchors; + auditClaimCodeAnchors = audit.auditClaimCodeAnchors; +}); -// Baseline fingerprint captured when the fact was "written". -writeFileSync(file, "def foo():\n # returns the threshold\n return 3\n"); -const baseline = new Map([["claim.foo", await fingerprintClaimAnchors(repo, [anchor], new CodeAnchorResolver())]]); +describe("anchor drift", () => { + test("unchanged code does not drift", async () => { + const repo = mkdtempSync(join(tmpdir(), "greplica-anchor-drift-test-")); + const file = join(repo, "mod.py"); + const anchor = { file: "mod.py", symbol: "foo" }; + const claim = { id: "claim.foo", kind: "fact", text: "foo returns 3", truth: "code_verified", intent: "intended", code_anchors: [anchor] }; -async function driftedIds(variant) { - writeFileSync(file, variant); - const result = await auditClaimCodeAnchors(repo, [claim], new CodeAnchorResolver(), baseline); - return result.drifted.map((issue) => issue.claim_id); -} + writeFileSync(file, "def foo():\n # returns the threshold\n return 3\n"); + const baseline = new Map([["claim.foo", await fingerprintClaimAnchors(repo, [anchor], new CodeAnchorResolver())]]); -// Unchanged code does not drift. -assert.deepEqual(await driftedIds("def foo():\n # returns the threshold\n return 3\n"), []); + async function driftedIds(variant) { + writeFileSync(file, variant); + const result = await auditClaimCodeAnchors(repo, [claim], new CodeAnchorResolver(), baseline); + return result.drifted.map((issue) => issue.claim_id); + } -// A real value change (3 -> 8) drifts. -assert.deepEqual(await driftedIds("def foo():\n # returns the threshold\n return 8\n"), ["claim.foo"]); + expect(await driftedIds("def foo():\n # returns the threshold\n return 3\n")).toEqual([]); + }); -// Comment-only edits do not drift. -assert.deepEqual(await driftedIds("def foo():\n # returns the configured threshold value\n return 3\n"), []); + test("a real value change drifts", async () => { + const repo = mkdtempSync(join(tmpdir(), "greplica-anchor-drift-test-")); + const file = join(repo, "mod.py"); + const anchor = { file: "mod.py", symbol: "foo" }; + const claim = { id: "claim.foo", kind: "fact", text: "foo returns 3", truth: "code_verified", intent: "intended", code_anchors: [anchor] }; -// Whitespace-only edits do not drift. -assert.deepEqual(await driftedIds("def foo():\n\n # returns the threshold\n return 3\n\n"), []); + writeFileSync(file, "def foo():\n # returns the threshold\n return 3\n"); + const baseline = new Map([["claim.foo", await fingerprintClaimAnchors(repo, [anchor], new CodeAnchorResolver())]]); -// A claim with no stored baseline is treated as unknown, never drifted. -const noBaseline = await auditClaimCodeAnchors(repo, [claim], new CodeAnchorResolver()); -assert.deepEqual(noBaseline.drifted, []); + async function driftedIds(variant) { + writeFileSync(file, variant); + const result = await auditClaimCodeAnchors(repo, [claim], new CodeAnchorResolver(), baseline); + return result.drifted.map((issue) => issue.claim_id); + } -console.log("check-anchor-drift: ok"); + expect(await driftedIds("def foo():\n # returns the threshold\n return 8\n")).toEqual(["claim.foo"]); + }); + + test("comment-only edits do not drift", async () => { + const repo = mkdtempSync(join(tmpdir(), "greplica-anchor-drift-test-")); + const file = join(repo, "mod.py"); + const anchor = { file: "mod.py", symbol: "foo" }; + const claim = { id: "claim.foo", kind: "fact", text: "foo returns 3", truth: "code_verified", intent: "intended", code_anchors: [anchor] }; + + writeFileSync(file, "def foo():\n # returns the threshold\n return 3\n"); + const baseline = new Map([["claim.foo", await fingerprintClaimAnchors(repo, [anchor], new CodeAnchorResolver())]]); + + async function driftedIds(variant) { + writeFileSync(file, variant); + const result = await auditClaimCodeAnchors(repo, [claim], new CodeAnchorResolver(), baseline); + return result.drifted.map((issue) => issue.claim_id); + } + + expect(await driftedIds("def foo():\n # returns the configured threshold value\n return 3\n")).toEqual([]); + }); + + test("whitespace-only edits do not drift", async () => { + const repo = mkdtempSync(join(tmpdir(), "greplica-anchor-drift-test-")); + const file = join(repo, "mod.py"); + const anchor = { file: "mod.py", symbol: "foo" }; + const claim = { id: "claim.foo", kind: "fact", text: "foo returns 3", truth: "code_verified", intent: "intended", code_anchors: [anchor] }; + + writeFileSync(file, "def foo():\n # returns the threshold\n return 3\n"); + const baseline = new Map([["claim.foo", await fingerprintClaimAnchors(repo, [anchor], new CodeAnchorResolver())]]); + + async function driftedIds(variant) { + writeFileSync(file, variant); + const result = await auditClaimCodeAnchors(repo, [claim], new CodeAnchorResolver(), baseline); + return result.drifted.map((issue) => issue.claim_id); + } + + expect(await driftedIds("def foo():\n\n # returns the threshold\n return 3\n\n")).toEqual([]); + }); + + test("a claim with no stored baseline is treated as unknown, never drifted", async () => { + const repo = mkdtempSync(join(tmpdir(), "greplica-anchor-drift-test-")); + const anchor = { file: "mod.py", symbol: "foo" }; + const claim = { id: "claim.foo", kind: "fact", text: "foo returns 3", truth: "code_verified", intent: "intended", code_anchors: [anchor] }; + + const noBaseline = await auditClaimCodeAnchors(repo, [claim], new CodeAnchorResolver()); + expect(noBaseline.drifted).toEqual([]); + }); +}); diff --git a/scripts/check-bm25-tokenizer.js b/scripts/check-bm25-tokenizer.js index f880b45..5456ce0 100644 --- a/scripts/check-bm25-tokenizer.js +++ b/scripts/check-bm25-tokenizer.js @@ -1,28 +1,54 @@ -import assert from "node:assert/strict"; +import { describe, test, expect, beforeAll } from "vitest"; const root = new URL("..", import.meta.url); -const { tokenize, scoreBm25 } = await import(new URL("dist/libs/knowledge-graph/graph-context/bm25.js", root)); -const { graphContextConfig } = await import(new URL("dist/libs/knowledge-graph/graph-context/config.js", root)); - -assert.ok(tokenize("handleUserAuth").includes("user"), "camelCase should emit sub-token user"); -assert.ok(tokenize("handleUserAuth").includes("auth"), "camelCase should emit sub-token auth"); -assert.ok(tokenize("handleUserAuth").includes("handle"), "camelCase should emit sub-token handle"); -assert.ok(tokenize("handleUserAuth").includes("handleuserauth"), "camelCase should keep full lowercased token"); - -assert.ok(tokenize("HandleUserAuth").includes("user"), "PascalCase should emit sub-token user"); -assert.ok(tokenize("handle_user_auth").includes("user"), "snake_case should emit sub-token user"); -assert.ok(tokenize("handle_user_auth").includes("handle_user_auth"), "snake_case should keep original token"); - -assert.ok(tokenize("graph-context").includes("graph"), "kebab-case should emit sub-token graph"); -assert.ok(tokenize("graph-context").includes("context"), "kebab-case should emit sub-token context"); - -assert.ok(tokenize("user2FA").includes("user"), "letter-number boundary should emit user"); -assert.ok(tokenize("user2FA").includes("fa"), "letter-number boundary should emit fa"); - -assert.ok(tokenize("tokens").includes("token"), "English stemming variants should still apply"); - -const documents = [{ key: "doc:auth", text: "The handleUserAuth function validates sessions." }]; -const ranked = scoreBm25("user auth validation", documents, graphContextConfig); -assert.equal(ranked[0]?.id, "doc:auth", "BM25 should match camelCase doc tokens to spaced query terms"); - -console.log("BM25 tokenizer checks passed."); +let tokenize; +let scoreBm25; +let graphContextConfig; + +beforeAll(async () => { + const bm25 = await import(new URL("dist/libs/knowledge-graph/graph-context/bm25.js", root)); + const config = await import(new URL("dist/libs/knowledge-graph/graph-context/config.js", root)); + tokenize = bm25.tokenize; + scoreBm25 = bm25.scoreBm25; + graphContextConfig = config.graphContextConfig; +}); + +describe("BM25 tokenizer", () => { + test("camelCase emits sub-tokens and keeps full lowercased token", () => { + const tokens = tokenize("handleUserAuth"); + expect(tokens).toContain("user"); + expect(tokens).toContain("auth"); + expect(tokens).toContain("handle"); + expect(tokens).toContain("handleuserauth"); + }); + + test("PascalCase emits sub-tokens", () => { + expect(tokenize("HandleUserAuth")).toContain("user"); + }); + + test("snake_case emits sub-tokens and keeps original token", () => { + const tokens = tokenize("handle_user_auth"); + expect(tokens).toContain("user"); + expect(tokens).toContain("handle_user_auth"); + }); + + test("kebab-case emits sub-tokens", () => { + expect(tokenize("graph-context")).toContain("graph"); + expect(tokenize("graph-context")).toContain("context"); + }); + + test("letter-number boundary emits sub-tokens", () => { + expect(tokenize("user2FA")).toContain("user"); + expect(tokenize("user2FA")).toContain("fa"); + }); + + test("English stemming variants still apply", () => { + expect(tokenize("tokens")).toContain("token"); + }); + + test("BM25 matches camelCase doc tokens to spaced query terms", () => { + const documents = [{ key: "doc:auth", text: "The handleUserAuth function validates sessions." }]; + const ranked = scoreBm25("user auth validation", documents, graphContextConfig); + expect(ranked[0]?.id).toBe("doc:auth"); + }); +}); diff --git a/scripts/check-graph-view.js b/scripts/check-graph-view.js index 11661ed..3649c27 100644 --- a/scripts/check-graph-view.js +++ b/scripts/check-graph-view.js @@ -1,48 +1,59 @@ -import assert from "node:assert/strict"; +import { describe, test, expect, beforeAll } from "vitest"; import { mkdtempSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; const root = new URL("..", import.meta.url); -const { openDatabase } = await import(new URL("dist/libs/storage/sqlite/db.js", root)); -const { SqliteRepository } = await import(new URL("dist/libs/storage/sqlite/repository.js", root)); -const { KnowledgeGraphService } = await import(new URL("dist/libs/knowledge-graph/service.js", root)); +let openDatabase; +let SqliteRepository; +let KnowledgeGraphService; -const tmp = mkdtempSync(join(tmpdir(), "greplica-graph-view-test-")); -const db = openDatabase(join(tmp, "graph.db")); +beforeAll(async () => { + const db = await import(new URL("dist/libs/storage/sqlite/db.js", root)); + const repo = await import(new URL("dist/libs/storage/sqlite/repository.js", root)); + const service = await import(new URL("dist/libs/knowledge-graph/service.js", root)); + openDatabase = db.openDatabase; + SqliteRepository = repo.SqliteRepository; + KnowledgeGraphService = service.KnowledgeGraphService; +}); -try { - const repository = new SqliteRepository(db); - const service = new KnowledgeGraphService(repository); - const repo = { - repo_root: join(tmp, "repo"), - repo_name: "graph-view-null-anchor", - default_branch: "main", - }; +describe("graph view", () => { + test("renders components without code anchors", () => { + const tmp = mkdtempSync(join(tmpdir(), "greplica-graph-view-test-")); + const db = openDatabase(join(tmp, "graph.db")); - const initialized = service.initRepo(repo); - const memoryCommit = repository.createMemoryCommit({ - scope_id: initialized.working_scope_id, - title: "Seed null component anchor", - }); + try { + const repository = new SqliteRepository(db); + const service = new KnowledgeGraphService(repository); + const repo = { + repo_root: join(tmp, "repo"), + repo_name: "graph-view-null-anchor", + default_branch: "main", + }; - repository.createProposalRecords(initialized.working_scope_id, memoryCommit.id, { - title: "Seed null component anchor", - creates: { - components: [ - { - id: "component.no_anchor", - name: "Component Without Anchor", - }, - ], - }, - }); + const initialized = service.initRepo(repo); + const memoryCommit = repository.createMemoryCommit({ + scope_id: initialized.working_scope_id, + title: "Seed null component anchor", + }); - const html = service.buildGraphView(repo); - assert.match(html, /Component Without Anchor/); - assert.match(html, /Greplica graph view/); -} finally { - db.close(); -} + repository.createProposalRecords(initialized.working_scope_id, memoryCommit.id, { + title: "Seed null component anchor", + creates: { + components: [ + { + id: "component.no_anchor", + name: "Component Without Anchor", + }, + ], + }, + }); -console.log("Graph view checks passed."); + const html = service.buildGraphView(repo); + expect(html).toMatch(/Component Without Anchor/); + expect(html).toMatch(/Greplica graph view/); + } finally { + db.close(); + } + }); +}); diff --git a/scripts/check-install-options.js b/scripts/check-install-options.js index 3de68aa..3e51dd0 100644 --- a/scripts/check-install-options.js +++ b/scripts/check-install-options.js @@ -1,4 +1,4 @@ -import assert from "node:assert/strict"; +import { describe, test, expect, beforeAll } from "vitest"; import { execFileSync, spawnSync } from "node:child_process"; import { existsSync, mkdirSync, mkdtempSync, readFileSync } from "node:fs"; import { tmpdir } from "node:os"; @@ -7,123 +7,27 @@ import { fileURLToPath } from "node:url"; const root = new URL("..", import.meta.url); const cliPath = fileURLToPath(new URL("dist/apps/cli/main.js", root)); -const { installCommandSuggestion, installPlatformUsage } = await import(new URL("dist/libs/install/paths.js", root)); -const { greplicaHookGuidance } = await import(new URL("dist/libs/hooks/guidance.js", root)); -const { shouldRunAutoMemoryUpdates } = await import(new URL("dist/libs/hooks/worker.js", root)); - -const tmp = mkdtempSync(join(tmpdir(), "greplica-install-options-test-")); - -const autoSave = installInTempRepo("auto-save", ["--hooks", "enabled", "--auto-memory", "enabled"]); -assert.match(autoSave.output, /Hooks: installed for UserPromptSubmit, Stop\./); -assert.match(autoSave.output, /Automatic memory updates: enabled\./); -assert.ok(existsSync(join(autoSave.codexHome, "hooks.json"))); -assert.equal(readConfig(autoSave.greplicaHome).session.autoMemoryUpdates, true); -assert.equal(shouldRunAutoMemoryUpdates(readConfig(autoSave.greplicaHome)), true); - -const guidanceOnly = installInTempRepo("guidance-only", ["--hooks", "enabled", "--auto-memory", "disabled"]); -assert.match(guidanceOnly.output, /Hooks: installed for UserPromptSubmit, Stop\./); -assert.match(guidanceOnly.output, /Automatic memory updates: disabled\./); -assert.ok(existsSync(join(guidanceOnly.codexHome, "hooks.json"))); -assert.equal(readConfig(guidanceOnly.greplicaHome).session.autoMemoryUpdates, false); -assert.equal(shouldRunAutoMemoryUpdates(readConfig(guidanceOnly.greplicaHome)), false); - -const hookOutput = execFileSync( - process.execPath, - [cliPath, "hook", "ingest", "--platform", "codex"], - { - cwd: guidanceOnly.repo, - encoding: "utf8", - input: JSON.stringify({ - hook_event_name: "UserPromptSubmit", - session_id: "guidance-only-session", - cwd: guidanceOnly.repo, - }), - env: guidanceOnly.env, - }, -); -assert.match(hookOutput, /Greplica hook guidance/); -assert.match(hookOutput, /greplica graph context/); - -const noHooks = installInTempRepo("no-hooks", ["--hooks", "disabled"]); -assert.match(noHooks.output, /Hooks: not installed\./); -assert.match(noHooks.output, /Automatic memory updates: disabled\./); -assert.match(noHooks.output, /To give future agents Greplica guidance without hooks/); -assert.ok(noHooks.output.includes(greplicaHookGuidance)); -assert.equal(existsSync(join(noHooks.codexHome, "hooks.json")), false); -assert.equal(readConfig(noHooks.greplicaHome).session.autoMemoryUpdates, false); - -const opencodeHooks = installInTempRepo("opencode-hooks", ["--hooks", "enabled", "--auto-memory", "enabled"], "opencode"); -assert.match(opencodeHooks.output, /Installed Greplica for OpenCode\./); -assert.match(opencodeHooks.output, /Hooks: installed for UserPromptSubmit, Stop\./); -assert.match(opencodeHooks.output, /Automatic memory updates: enabled\./); -assert.ok(existsSync(join(opencodeHooks.xdgConfigHome, "opencode", "hooks.json"))); -assert.equal(readConfig(opencodeHooks.greplicaHome).session.autoMemoryUpdates, true); - -const copilotHooks = installInTempRepo("copilot-hooks", ["--hooks", "enabled", "--auto-memory", "enabled"], "copilot"); -assert.match(copilotHooks.output, /Installed Greplica for GitHub Copilot CLI\./); -assert.match(copilotHooks.output, /Hooks: installed for SessionStart, Stop\./); -assert.match(copilotHooks.output, /Automatic memory updates: enabled\./); -assert.ok(existsSync(join(copilotHooks.copilotHome, "hooks", "greplica.json"))); -assert.equal(readConfig(copilotHooks.greplicaHome).session.autoMemoryUpdates, true); - -const invalid = spawnSync( - process.execPath, - [ - cliPath, - "install", - "--platform", - "codex", - "--embedding", - "local", - "--hooks", - "disabled", - "--auto-memory", - "enabled", - ], - { - cwd: noHooks.repo, - encoding: "utf8", - env: noHooks.env, - }, -); -assert.notEqual(invalid.status, 0); -assert.match(invalid.stderr, /--auto-memory enabled requires --hooks enabled/); - -const invalidValue = spawnSync( - process.execPath, - [cliPath, "install", "--platform", "codex", "--embedding", "local", "--hooks", "sometimes"], - { - cwd: noHooks.repo, - encoding: "utf8", - env: noHooks.env, - }, -); -assert.notEqual(invalidValue.status, 0); -assert.match(invalidValue.stderr, /expected enabled or disabled/); - -const notInstalledRepo = join(tmp, "not-installed", "repo"); -const notInstalledHome = join(tmp, "not-installed", "greplica-home"); -mkdirSync(notInstalledRepo, { recursive: true }); -execFileSync("git", ["init", "--quiet"], { cwd: notInstalledRepo, encoding: "utf8" }); -const notInstalled = spawnSync(process.execPath, [cliPath, "graph", "read"], { - cwd: notInstalledRepo, - encoding: "utf8", - env: { - ...process.env, - GREPLICA_HOME: notInstalledHome, - }, + +let installCommandSuggestion; +let installPlatformUsage; +let greplicaHookGuidance; +let shouldRunAutoMemoryUpdates; + +beforeAll(async () => { + const paths = await import(new URL("dist/libs/install/paths.js", root)); + const guidance = await import(new URL("dist/libs/hooks/guidance.js", root)); + const worker = await import(new URL("dist/libs/hooks/worker.js", root)); + installCommandSuggestion = paths.installCommandSuggestion; + installPlatformUsage = paths.installPlatformUsage; + greplicaHookGuidance = guidance.greplicaHookGuidance; + shouldRunAutoMemoryUpdates = worker.shouldRunAutoMemoryUpdates; }); -assert.notEqual(notInstalled.status, 0); -assert.match(notInstalled.stderr, /Greplica is not installed for this repo/); -assert.ok(notInstalled.stderr.includes(installCommandSuggestion), "not installed error should use shared install guidance"); -assert.ok(notInstalled.stderr.includes(installPlatformUsage), "not installed error should include every install platform"); -assert.match(notInstalled.stderr, /openhands/); -assert.match(notInstalled.stderr, /factory-droid/); -assert.match(notInstalled.stderr, /antigravity/); -console.log("Install option checks passed."); +function readConfig(greplicaHome) { + return JSON.parse(readFileSync(join(greplicaHome, "config.json"), "utf8")); +} -function installInTempRepo(name, flags, platform = "codex") { +function installInTempRepo(tmp, name, flags, platform = "codex") { const repo = join(tmp, name, "repo"); const greplicaHome = join(tmp, name, "greplica-home"); const codexHome = join(tmp, name, "codex-home"); @@ -143,20 +47,130 @@ function installInTempRepo(name, flags, platform = "codex") { const output = execFileSync( process.execPath, [cliPath, "install", "--platform", platform, "--embedding", "local", ...flags], - { - cwd: repo, - encoding: "utf8", - env, - }, + { cwd: repo, encoding: "utf8", env }, ); - execFileSync(process.execPath, [cliPath, "doctor"], { - cwd: repo, - encoding: "utf8", - env, - }); + execFileSync(process.execPath, [cliPath, "doctor"], { cwd: repo, encoding: "utf8", env }); return { repo, greplicaHome, codexHome, copilotHome, xdgConfigHome, output, env }; } -function readConfig(greplicaHome) { - return JSON.parse(readFileSync(join(greplicaHome, "config.json"), "utf8")); -} +describe("install options", () => { + test("auto-save mode installs hooks and enables auto memory updates", () => { + const tmp = mkdtempSync(join(tmpdir(), "greplica-install-options-test-")); + const autoSave = installInTempRepo(tmp, "auto-save", ["--hooks", "enabled", "--auto-memory", "enabled"]); + + expect(autoSave.output).toMatch(/Hooks: installed for UserPromptSubmit, Stop\./); + expect(autoSave.output).toMatch(/Automatic memory updates: enabled\./); + expect(existsSync(join(autoSave.codexHome, "hooks.json"))).toBe(true); + expect(readConfig(autoSave.greplicaHome).session.autoMemoryUpdates).toBe(true); + expect(shouldRunAutoMemoryUpdates(readConfig(autoSave.greplicaHome))).toBe(true); + }); + + test("guidance-only mode installs hooks and injects guidance but disables auto memory updates", () => { + const tmp = mkdtempSync(join(tmpdir(), "greplica-install-options-test-")); + const guidanceOnly = installInTempRepo(tmp, "guidance-only", ["--hooks", "enabled", "--auto-memory", "disabled"]); + + expect(guidanceOnly.output).toMatch(/Hooks: installed for UserPromptSubmit, Stop\./); + expect(guidanceOnly.output).toMatch(/Automatic memory updates: disabled\./); + expect(existsSync(join(guidanceOnly.codexHome, "hooks.json"))).toBe(true); + expect(readConfig(guidanceOnly.greplicaHome).session.autoMemoryUpdates).toBe(false); + expect(shouldRunAutoMemoryUpdates(readConfig(guidanceOnly.greplicaHome))).toBe(false); + + const hookOutput = execFileSync( + process.execPath, + [cliPath, "hook", "ingest", "--platform", "codex"], + { + cwd: guidanceOnly.repo, + encoding: "utf8", + input: JSON.stringify({ + hook_event_name: "UserPromptSubmit", + session_id: "guidance-only-session", + cwd: guidanceOnly.repo, + }), + env: guidanceOnly.env, + }, + ); + expect(hookOutput).toMatch(/Greplica hook guidance/); + expect(hookOutput).toMatch(/greplica graph context/); + }); + + test("no-hooks mode does not install hooks and disables auto memory updates", () => { + const tmp = mkdtempSync(join(tmpdir(), "greplica-install-options-test-")); + const noHooks = installInTempRepo(tmp, "no-hooks", ["--hooks", "disabled"]); + + expect(noHooks.output).toMatch(/Hooks: not installed\./); + expect(noHooks.output).toMatch(/Automatic memory updates: disabled\./); + expect(noHooks.output).toMatch(/To give future agents Greplica guidance without hooks/); + expect(noHooks.output).toContain(greplicaHookGuidance); + expect(existsSync(join(noHooks.codexHome, "hooks.json"))).toBe(false); + expect(readConfig(noHooks.greplicaHome).session.autoMemoryUpdates).toBe(false); + }); + + test("opencode installs hooks and enables auto memory updates", () => { + const tmp = mkdtempSync(join(tmpdir(), "greplica-install-options-test-")); + const opencodeHooks = installInTempRepo(tmp, "opencode-hooks", ["--hooks", "enabled", "--auto-memory", "enabled"], "opencode"); + + expect(opencodeHooks.output).toMatch(/Installed Greplica for OpenCode\./); + expect(opencodeHooks.output).toMatch(/Hooks: installed for UserPromptSubmit, Stop\./); + expect(opencodeHooks.output).toMatch(/Automatic memory updates: enabled\./); + expect(existsSync(join(opencodeHooks.xdgConfigHome, "opencode", "hooks.json"))).toBe(true); + expect(readConfig(opencodeHooks.greplicaHome).session.autoMemoryUpdates).toBe(true); + }); + + test("copilot installs hooks and enables auto memory updates", () => { + const tmp = mkdtempSync(join(tmpdir(), "greplica-install-options-test-")); + const copilotHooks = installInTempRepo(tmp, "copilot-hooks", ["--hooks", "enabled", "--auto-memory", "enabled"], "copilot"); + + expect(copilotHooks.output).toMatch(/Installed Greplica for GitHub Copilot CLI\./); + expect(copilotHooks.output).toMatch(/Hooks: installed for SessionStart, Stop\./); + expect(copilotHooks.output).toMatch(/Automatic memory updates: enabled\./); + expect(existsSync(join(copilotHooks.copilotHome, "hooks", "greplica.json"))).toBe(true); + expect(readConfig(copilotHooks.greplicaHome).session.autoMemoryUpdates).toBe(true); + }); + + test("rejects auto-memory enabled without hooks enabled", () => { + const tmp = mkdtempSync(join(tmpdir(), "greplica-install-options-test-")); + const noHooks = installInTempRepo(tmp, "invalid-combo", ["--hooks", "disabled"]); + + const invalid = spawnSync( + process.execPath, + [cliPath, "install", "--platform", "codex", "--embedding", "local", "--hooks", "disabled", "--auto-memory", "enabled"], + { cwd: noHooks.repo, encoding: "utf8", env: noHooks.env }, + ); + expect(invalid.status).not.toBe(0); + expect(invalid.stderr).toMatch(/--auto-memory enabled requires --hooks enabled/); + }); + + test("rejects invalid flag values", () => { + const tmp = mkdtempSync(join(tmpdir(), "greplica-install-options-test-")); + const noHooks = installInTempRepo(tmp, "invalid-flag", ["--hooks", "disabled"]); + + const invalidValue = spawnSync( + process.execPath, + [cliPath, "install", "--platform", "codex", "--embedding", "local", "--hooks", "sometimes"], + { cwd: noHooks.repo, encoding: "utf8", env: noHooks.env }, + ); + expect(invalidValue.status).not.toBe(0); + expect(invalidValue.stderr).toMatch(/expected enabled or disabled/); + }); + + test("graph read on uninstalled repo reports not installed with install guidance", () => { + const tmp = mkdtempSync(join(tmpdir(), "greplica-install-options-test-")); + const notInstalledRepo = join(tmp, "not-installed", "repo"); + const notInstalledHome = join(tmp, "not-installed", "greplica-home"); + mkdirSync(notInstalledRepo, { recursive: true }); + execFileSync("git", ["init", "--quiet"], { cwd: notInstalledRepo, encoding: "utf8" }); + + const notInstalled = spawnSync(process.execPath, [cliPath, "graph", "read"], { + cwd: notInstalledRepo, + encoding: "utf8", + env: { ...process.env, GREPLICA_HOME: notInstalledHome }, + }); + expect(notInstalled.status).not.toBe(0); + expect(notInstalled.stderr).toMatch(/Greplica is not installed for this repo/); + expect(notInstalled.stderr).toContain(installCommandSuggestion); + expect(notInstalled.stderr).toContain(installPlatformUsage); + expect(notInstalled.stderr).toMatch(/openhands/); + expect(notInstalled.stderr).toMatch(/factory-droid/); + expect(notInstalled.stderr).toMatch(/antigravity/); + }); +}); diff --git a/scripts/check-proposal-validate.js b/scripts/check-proposal-validate.js index a1f6b85..51d4efb 100644 --- a/scripts/check-proposal-validate.js +++ b/scripts/check-proposal-validate.js @@ -1,160 +1,153 @@ -import assert from "node:assert/strict"; +import { describe, test, expect, beforeAll } from "vitest"; import { mkdtempSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; const root = new URL("..", import.meta.url); -const { normalizeProposal } = await import(new URL("dist/libs/knowledge-graph/proposal.js", root)); -const { validateProposal } = await import(new URL("dist/libs/knowledge-graph/validate-proposal.js", root)); -const { KnowledgeGraphService } = await import(new URL("dist/libs/knowledge-graph/service.js", root)); -const { openDatabase } = await import(new URL("dist/libs/storage/sqlite/db.js", root)); -const { SqliteRepository } = await import(new URL("dist/libs/storage/sqlite/repository.js", root)); +let normalizeProposal; +let validateProposal; +let KnowledgeGraphService; +let openDatabase; +let SqliteRepository; + +beforeAll(async () => { + const proposal = await import(new URL("dist/libs/knowledge-graph/proposal.js", root)); + const validate = await import(new URL("dist/libs/knowledge-graph/validate-proposal.js", root)); + const service = await import(new URL("dist/libs/knowledge-graph/service.js", root)); + const db = await import(new URL("dist/libs/storage/sqlite/db.js", root)); + const repo = await import(new URL("dist/libs/storage/sqlite/repository.js", root)); + normalizeProposal = proposal.normalizeProposal; + validateProposal = validate.validateProposal; + KnowledgeGraphService = service.KnowledgeGraphService; + openDatabase = db.openDatabase; + SqliteRepository = repo.SqliteRepository; +}); const components = [ { id: "component.a", name: "A" }, { id: "component.b", name: "B" }, ]; -// Edge written with canonical field names but no types (issue #82) must not crash -// normalization, and must surface as a validation error. -const malformedEdge = { - title: "Malformed edge repro", - creates: { - components, - edges: [{ from_id: "component.a", to_id: "component.b", kind: "contains" }], - }, -}; - -let normalized; -assert.doesNotThrow(() => { - normalized = normalizeProposal(malformedEdge); -}, "normalizeProposal must not throw on a malformed edge"); - -const malformedResult = validateProposal(normalized); -assert.equal(malformedResult.valid, false, "malformed edge proposal must be invalid"); -assert.ok( - malformedResult.errors.some((error) => error.includes("from'/'to")), - `expected a from/to guidance error, got: ${JSON.stringify(malformedResult.errors)}`, -); - -// Edge missing kind must also be reported, not crash. -const missingKindEdge = { - title: "Missing kind repro", - creates: { - components, - edges: [{ from: "component.a", to: "component.b" }], - }, -}; - -let normalizedMissingKind; -assert.doesNotThrow(() => { - normalizedMissingKind = normalizeProposal(missingKindEdge); -}, "normalizeProposal must not throw on an edge without kind"); -assert.equal(validateProposal(normalizedMissingKind).valid, false, "edge without kind must be invalid"); - -// A well-formed compact edge must keep validating cleanly. -const compactEdge = { - title: "Compact edge", - creates: { - components, - edges: [{ kind: "contains", from: "component.a", to: "component.b" }], - }, -}; - -const compactResult = validateProposal(normalizeProposal(compactEdge)); -assert.equal(compactResult.valid, true, `compact edge must stay valid, got: ${JSON.stringify(compactResult.errors)}`); - -const tmp = mkdtempSync(join(tmpdir(), "greplica-proposal-validate-test-")); -const db = openDatabase(join(tmp, "graph.db")); - -try { - const repository = new SqliteRepository(db); - const service = new KnowledgeGraphService(repository); - const repoA = { - repo_root: join(tmp, "repo-a"), - repo_name: "repo-a", - default_branch: "main", - }; - const repoB = { - repo_root: join(tmp, "repo-b"), - repo_name: "repo-b", - default_branch: "main", - }; - const repoC = { - repo_root: join(tmp, "repo-c"), - repo_name: "repo-c", - default_branch: "main", - }; - const repoAProposal = { - title: "Seed CLI component", - creates: { - components: [{ id: "component.cli", name: "Repo A CLI" }], - }, - }; - const repoBProposal = { - title: "Seed CLI component", - creates: { - components: [{ id: "component.cli", name: "Repo B CLI" }], - }, - }; - - const initializedA = service.initRepo(repoA); - const initializedB = service.initRepo(repoB); - service.initRepo(repoC); - const memoryCommit = repository.createMemoryCommit({ - scope_id: initializedA.working_scope_id, - title: "Seed repo A", +describe("proposal validation", () => { + test("does not crash on edges using from_id/to_id and reports a clear error", () => { + const malformedEdge = { + title: "Malformed edge repro", + creates: { + components, + edges: [{ from_id: "component.a", to_id: "component.b", kind: "contains" }], + }, + }; + + let normalized; + expect(() => { + normalized = normalizeProposal(malformedEdge); + }).not.toThrow(); + + const malformedResult = validateProposal(normalized); + expect(malformedResult.valid).toBe(false); + expect(malformedResult.errors.some((error) => error.includes("from'/'to"))).toBe(true); }); - repository.createProposalRecords(initializedA.working_scope_id, memoryCommit.id, normalizeProposal(repoAProposal)); - - const repoBResult = await service.validateProposal(repoB, repoBProposal); - assert.equal( - repoBResult.valid, - true, - `repo B must be able to create its own component.cli, got: ${JSON.stringify(repoBResult.errors)}`, - ); - - const repoCCrossReference = await service.validateProposal(repoC, { - title: "Repo C cross-repo compact reference", - creates: { - claims: [ - { - id: "claim.about_cli", - kind: "fact", - text: "Repo C references a CLI component.", - truth: "unknown", - intent: "unknown", - about: "component.cli", - }, - ], - }, + + test("does not crash on edges missing kind and reports them invalid", () => { + const missingKindEdge = { + title: "Missing kind repro", + creates: { + components, + edges: [{ from: "component.a", to: "component.b" }], + }, + }; + + let normalizedMissingKind; + expect(() => { + normalizedMissingKind = normalizeProposal(missingKindEdge); + }).not.toThrow(); + + expect(validateProposal(normalizedMissingKind).valid).toBe(false); }); - assert.equal(repoCCrossReference.valid, false, "repo C must not resolve compact references through repo A"); - assert.ok( - repoCCrossReference.errors.some((error) => error.includes("component:component.cli")), - `expected repo C to report missing component.cli, got: ${JSON.stringify(repoCCrossReference.errors)}`, - ); - - const repoBMemoryCommit = repository.createMemoryCommit({ - scope_id: initializedB.working_scope_id, - title: "Seed repo B", + + test("accepts well-formed compact edges", () => { + const compactEdge = { + title: "Compact edge", + creates: { + components, + edges: [{ kind: "contains", from: "component.a", to: "component.b" }], + }, + }; + + const compactResult = validateProposal(normalizeProposal(compactEdge)); + expect(compactResult.valid).toBe(true); + }); +}); + +describe("repo-scoped graph objects", () => { + test("repos are distinct and cross-repo compact references are rejected", async () => { + const tmp = mkdtempSync(join(tmpdir(), "greplica-proposal-validate-test-")); + const db = openDatabase(join(tmp, "graph.db")); + + try { + const repository = new SqliteRepository(db); + const service = new KnowledgeGraphService(repository); + const repoA = { repo_root: join(tmp, "repo-a"), repo_name: "repo-a", default_branch: "main" }; + const repoB = { repo_root: join(tmp, "repo-b"), repo_name: "repo-b", default_branch: "main" }; + const repoC = { repo_root: join(tmp, "repo-c"), repo_name: "repo-c", default_branch: "main" }; + const repoAProposal = { + title: "Seed CLI component", + creates: { components: [{ id: "component.cli", name: "Repo A CLI" }] }, + }; + const repoBProposal = { + title: "Seed CLI component", + creates: { components: [{ id: "component.cli", name: "Repo B CLI" }] }, + }; + + const initializedA = service.initRepo(repoA); + const initializedB = service.initRepo(repoB); + service.initRepo(repoC); + const memoryCommit = repository.createMemoryCommit({ + scope_id: initializedA.working_scope_id, + title: "Seed repo A", + }); + repository.createProposalRecords(initializedA.working_scope_id, memoryCommit.id, normalizeProposal(repoAProposal)); + + const repoBResult = await service.validateProposal(repoB, repoBProposal); + expect(repoBResult.valid).toBe(true); + + const repoCCrossReference = await service.validateProposal(repoC, { + title: "Repo C cross-repo compact reference", + creates: { + claims: [ + { + id: "claim.about_cli", + kind: "fact", + text: "Repo C references a CLI component.", + truth: "unknown", + intent: "unknown", + about: "component.cli", + }, + ], + }, + }); + expect(repoCCrossReference.valid).toBe(false); + expect( + repoCCrossReference.errors.some((error) => error.includes("component:component.cli")), + ).toBe(true); + + const repoBMemoryCommit = repository.createMemoryCommit({ + scope_id: initializedB.working_scope_id, + title: "Seed repo B", + }); + repository.createProposalRecords(initializedB.working_scope_id, repoBMemoryCommit.id, normalizeProposal(repoBProposal)); + + const repoBDuplicateResult = await service.validateProposal(repoB, repoBProposal); + expect(repoBDuplicateResult.valid).toBe(false); + expect(repoBDuplicateResult.errors.includes("component:component.cli already exists.")).toBe(true); + + const repoAGraph = service.readGraph(repoA); + const repoBGraph = service.readGraph(repoB); + expect(repoAGraph.components.map((component) => component.name)).toEqual(["Repo A CLI"]); + expect(repoBGraph.components.map((component) => component.name)).toEqual(["Repo B CLI"]); + expect(initializedB.repo_id === initializedA.repo_id).toBe(false); + } finally { + db.close(); + } }); - repository.createProposalRecords(initializedB.working_scope_id, repoBMemoryCommit.id, normalizeProposal(repoBProposal)); - - const repoBDuplicateResult = await service.validateProposal(repoB, repoBProposal); - assert.equal(repoBDuplicateResult.valid, false, "repo B must still reject duplicate IDs inside the same repo"); - assert.ok( - repoBDuplicateResult.errors.includes("component:component.cli already exists."), - `expected repo B duplicate error, got: ${JSON.stringify(repoBDuplicateResult.errors)}`, - ); - - const repoAGraph = service.readGraph(repoA); - const repoBGraph = service.readGraph(repoB); - assert.deepEqual(repoAGraph.components.map((component) => component.name), ["Repo A CLI"]); - assert.deepEqual(repoBGraph.components.map((component) => component.name), ["Repo B CLI"]); - assert.equal(initializedB.repo_id === initializedA.repo_id, false, "test repos must be distinct"); -} finally { - db.close(); -} - -console.log("check-proposal-validate: ok"); +}); diff --git a/scripts/check-repo-context.js b/scripts/check-repo-context.js index 410a9eb..7216bfe 100644 --- a/scripts/check-repo-context.js +++ b/scripts/check-repo-context.js @@ -1,47 +1,16 @@ -import assert from "node:assert/strict"; +import { describe, test, expect, beforeAll } from "vitest"; import { execFileSync } from "node:child_process"; import { mkdtempSync, mkdirSync, realpathSync } from "node:fs"; import { tmpdir } from "node:os"; import { basename, join } from "node:path"; const root = new URL("..", import.meta.url); -const { detectRepoContext } = await import(new URL("dist/apps/cli/repo-context.js", root)); -const tmp = mkdtempSync(join(tmpdir(), "greplica-repo-context-test-")); - -const nonGitFolder = join(tmp, "plain-folder"); -mkdirSync(nonGitFolder); - -const fallbackContext = detectRepoContext(nonGitFolder); -assert.equal(fallbackContext.repo_root, realpathSync(nonGitFolder)); -assert.equal(fallbackContext.repo_name, basename(nonGitFolder)); -assert.equal(fallbackContext.default_branch, "main"); -assert.equal(fallbackContext.remote_url, undefined); - -const sshRepo = initRepo(join(tmp, "ssh-repo")); -git(sshRepo, "remote", "add", "origin", "git@github.com:Autoloops/greplica.git"); -const sshContext = detectRepoContext(sshRepo); -assert.equal(sshContext.repo_root, realpathSync(sshRepo)); -assert.equal(sshContext.remote_url, "git@github.com:Autoloops/greplica.git"); -assert.equal(sshContext.repo_name, "greplica"); - -const httpsRepo = initRepo(join(tmp, "https-repo")); -git(httpsRepo, "remote", "add", "origin", "https://github.com/Autoloops/greplica.git"); -const httpsContext = detectRepoContext(httpsRepo); -assert.equal(httpsContext.remote_url, "https://github.com/Autoloops/greplica.git"); -assert.equal(httpsContext.repo_name, "greplica"); - -const branchRepo = initRepo(join(tmp, "branch-repo")); -git(branchRepo, "remote", "add", "origin", "https://github.com/Autoloops/greplica.git"); -git(branchRepo, "symbolic-ref", "refs/remotes/origin/HEAD", "refs/remotes/origin/trunk"); -const branchContext = detectRepoContext(branchRepo); -assert.equal(branchContext.default_branch, "trunk"); - -const noRemoteHeadRepo = initRepo(join(tmp, "no-remote-head-repo")); -git(noRemoteHeadRepo, "remote", "add", "origin", "https://github.com/Autoloops/greplica.git"); -const noRemoteHeadContext = detectRepoContext(noRemoteHeadRepo); -assert.equal(noRemoteHeadContext.default_branch, "main"); - -console.log("Repo context checks passed."); +let detectRepoContext; + +beforeAll(async () => { + const repoContext = await import(new URL("dist/apps/cli/repo-context.js", root)); + detectRepoContext = repoContext.detectRepoContext; +}); function initRepo(path) { mkdirSync(path); @@ -56,3 +25,57 @@ function git(cwd, ...args) { stdio: ["ignore", "pipe", "pipe"], }).trim(); } + +describe("repo context", () => { + test("non-git folder falls back to folder name and main branch", () => { + const tmp = mkdtempSync(join(tmpdir(), "greplica-repo-context-test-")); + const nonGitFolder = join(tmp, "plain-folder"); + mkdirSync(nonGitFolder); + + const fallbackContext = detectRepoContext(nonGitFolder); + expect(fallbackContext.repo_root).toBe(realpathSync(nonGitFolder)); + expect(fallbackContext.repo_name).toBe(basename(nonGitFolder)); + expect(fallbackContext.default_branch).toBe("main"); + expect(fallbackContext.remote_url).toBeUndefined(); + }); + + test("SSH remote is parsed correctly", () => { + const tmp = mkdtempSync(join(tmpdir(), "greplica-repo-context-test-")); + const sshRepo = initRepo(join(tmp, "ssh-repo")); + git(sshRepo, "remote", "add", "origin", "git@github.com:Autoloops/greplica.git"); + + const sshContext = detectRepoContext(sshRepo); + expect(sshContext.repo_root).toBe(realpathSync(sshRepo)); + expect(sshContext.remote_url).toBe("git@github.com:Autoloops/greplica.git"); + expect(sshContext.repo_name).toBe("greplica"); + }); + + test("HTTPS remote is parsed correctly", () => { + const tmp = mkdtempSync(join(tmpdir(), "greplica-repo-context-test-")); + const httpsRepo = initRepo(join(tmp, "https-repo")); + git(httpsRepo, "remote", "add", "origin", "https://github.com/Autoloops/greplica.git"); + + const httpsContext = detectRepoContext(httpsRepo); + expect(httpsContext.remote_url).toBe("https://github.com/Autoloops/greplica.git"); + expect(httpsContext.repo_name).toBe("greplica"); + }); + + test("custom default branch is detected from remote HEAD", () => { + const tmp = mkdtempSync(join(tmpdir(), "greplica-repo-context-test-")); + const branchRepo = initRepo(join(tmp, "branch-repo")); + git(branchRepo, "remote", "add", "origin", "https://github.com/Autoloops/greplica.git"); + git(branchRepo, "symbolic-ref", "refs/remotes/origin/HEAD", "refs/remotes/origin/trunk"); + + const branchContext = detectRepoContext(branchRepo); + expect(branchContext.default_branch).toBe("trunk"); + }); + + test("defaults to main when remote HEAD is not set", () => { + const tmp = mkdtempSync(join(tmpdir(), "greplica-repo-context-test-")); + const noRemoteHeadRepo = initRepo(join(tmp, "no-remote-head-repo")); + git(noRemoteHeadRepo, "remote", "add", "origin", "https://github.com/Autoloops/greplica.git"); + + const noRemoteHeadContext = detectRepoContext(noRemoteHeadRepo); + expect(noRemoteHeadContext.default_branch).toBe("main"); + }); +}); diff --git a/scripts/check-transcript-bundle.js b/scripts/check-transcript-bundle.js index b16333f..08e8c4d 100644 --- a/scripts/check-transcript-bundle.js +++ b/scripts/check-transcript-bundle.js @@ -1,272 +1,230 @@ -import assert from "node:assert/strict"; +import { describe, test, expect } from "vitest"; import { execFileSync } from "node:child_process"; import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs"; import { join, dirname } from "node:path"; import { tmpdir } from "node:os"; import { fileURLToPath } from "node:url"; -const root = new URL("..", import.meta.url); -const cliPath = fileURLToPath(new URL("dist/apps/cli/main.js", root)); -const tmp = mkdtempSync(join(tmpdir(), "greplica-transcript-bundle-test-")); +const root = fileURLToPath(new URL("..", import.meta.url)); +const cliPath = join(root, "dist", "apps", "cli", "main.js"); -const codexOne = join(tmp, "codex-one.jsonl"); -const codexTwo = join(tmp, "codex-two.jsonl"); -const claudeOne = join(tmp, "claude-one.jsonl"); -const copilotOne = join(tmp, "copilot-one.jsonl"); -const codexOut = join(tmp, "codex-bundle.md"); -const claudeOut = join(tmp, "claude-bundle.md"); -const copilotOut = join(tmp, "copilot-bundle.md"); -const opencodeOut = join(tmp, "opencode-bundle.md"); +describe("transcript bundle", () => { + test("bundles codex transcripts and strips system instructions", () => { + const tmp = mkdtempSync(join(tmpdir(), "greplica-transcript-bundle-test-")); + const codexOne = join(tmp, "codex-one.jsonl"); + const codexTwo = join(tmp, "codex-two.jsonl"); + const codexOut = join(tmp, "codex-bundle.md"); -writeFileSync( - codexOne, - [ - JSON.stringify({ - type: "session_meta", - payload: { - id: "codex-session-one", - timestamp: "2026-06-25T00:00:00.000Z", - cwd: "/repo/example", - }, - }), - JSON.stringify({ - timestamp: "2026-06-25T00:01:00.000Z", - type: "event_msg", - payload: { - type: "user_message", - message: "Remember this durable Codex insight. do not keep this", - }, - }), - ].join("\n"), - "utf8", -); - -writeFileSync( - codexTwo, - [ - JSON.stringify({ - type: "session_meta", - payload: { - id: "codex-session-two", - cwd: "/repo/example", - }, - }), - JSON.stringify({ - timestamp: "2026-06-25T00:02:00.000Z", - type: "event_msg", - payload: { - type: "agent_message", - message: "A second Codex transcript fact. drop this", - }, - }), - ].join("\n"), - "utf8", -); + writeFileSync( + codexOne, + [ + JSON.stringify({ + type: "session_meta", + payload: { + id: "codex-session-one", + timestamp: "2026-06-25T00:00:00.000Z", + cwd: "/repo/example", + }, + }), + JSON.stringify({ + timestamp: "2026-06-25T00:01:00.000Z", + type: "event_msg", + payload: { + type: "user_message", + message: "Remember this durable Codex insight. do not keep this", + }, + }), + ].join("\n"), + "utf8", + ); -writeFileSync( - claudeOne, - [ - JSON.stringify({ - type: "user", - sessionId: "claude-session-one", - cwd: "/repo/example", - timestamp: "2026-06-25T00:03:00.000Z", - message: { - role: "user", - content: [ - { - type: "text", - text: "Remember this durable Claude insight.", + writeFileSync( + codexTwo, + [ + JSON.stringify({ + type: "session_meta", + payload: { + id: "codex-session-two", + cwd: "/repo/example", + }, + }), + JSON.stringify({ + timestamp: "2026-06-25T00:02:00.000Z", + type: "event_msg", + payload: { + type: "agent_message", + message: "A second Codex transcript fact. drop this", }, - ], - }, - }), - ].join("\n"), - "utf8", -); + }), + ].join("\n"), + "utf8", + ); -writeFileSync( - copilotOne, - [ - JSON.stringify({ - type: "session.start", - data: { - sessionId: "copilot-session-one", - copilotVersion: "1.0.66", - context: { + const output = execFileSync( + process.execPath, + [cliPath, "transcript", "bundle", "--platform", "codex", "--file", codexOne, "--file", codexTwo, "--out", codexOut], + { encoding: "utf8" }, + ); + const bundle = readFileSync(codexOut, "utf8"); + + expect(output).toMatch(/Wrote transcript bundle/); + expect(output).toMatch(/codex-session:codex-session-one/); + expect(output).toMatch(/codex-session:codex-session-two/); + expect(bundle).toMatch(/file_count: 2/); + expect(bundle).toMatch(/session_ref: codex-session:codex-session-one/); + expect(bundle).toMatch(/session_ref: codex-session:codex-session-two/); + expect(bundle).toMatch(/Remember this durable Codex insight/); + expect(bundle).toMatch(/A second Codex transcript fact/); + expect(bundle).not.toMatch(/do not keep this/); + expect(bundle).not.toMatch(/drop this/); + }); + + test("bundles claude transcript", () => { + const tmp = mkdtempSync(join(tmpdir(), "greplica-transcript-bundle-test-")); + const claudeOne = join(tmp, "claude-one.jsonl"); + const claudeOut = join(tmp, "claude-bundle.md"); + + writeFileSync( + claudeOne, + [ + JSON.stringify({ + type: "user", + sessionId: "claude-session-one", cwd: "/repo/example", - repository: "Autoloops/greplica", - branch: "copilot-test", - }, - }, - timestamp: "2026-06-25T00:03:30.000Z", - }), - JSON.stringify({ - type: "session.model_change", - data: { - newModel: "claude-haiku-4.5", - }, - timestamp: "2026-06-25T00:03:45.000Z", - }), - JSON.stringify({ - session_id: "copilot-session-one", - cwd: "/repo/example", - timestamp: "2026-06-25T00:04:00.000Z", - role: "user", - content: "Remember this durable Copilot insight. remove this", - }), - JSON.stringify({ - session_id: "copilot-session-one", - cwd: "/repo/example", - timestamp: "2026-06-25T00:05:00.000Z", - role: "assistant", - content: [{ type: "text", text: "A Copilot assistant fact." }], - }), - ].join("\n"), - "utf8", -); + timestamp: "2026-06-25T00:03:00.000Z", + message: { + role: "user", + content: [{ type: "text", text: "Remember this durable Claude insight." }], + }, + }), + ].join("\n"), + "utf8", + ); -const codexOutput = execFileSync( - process.execPath, - [ - cliPath, - "transcript", - "bundle", - "--platform", - "codex", - "--file", - codexOne, - "--file", - codexTwo, - "--out", - codexOut, - ], - { encoding: "utf8" }, -); -const codexBundle = readFileSync(codexOut, "utf8"); -assert.match(codexOutput, /Wrote transcript bundle/); -assert.match(codexOutput, /codex-session:codex-session-one/); -assert.match(codexOutput, /codex-session:codex-session-two/); -assert.match(codexBundle, /file_count: 2/); -assert.match(codexBundle, /session_ref: codex-session:codex-session-one/); -assert.match(codexBundle, /session_ref: codex-session:codex-session-two/); -assert.match(codexBundle, /Remember this durable Codex insight/); -assert.match(codexBundle, /A second Codex transcript fact/); -assert.doesNotMatch(codexBundle, /do not keep this/); -assert.doesNotMatch(codexBundle, /drop this/); + const output = execFileSync( + process.execPath, + [cliPath, "transcript", "bundle", "--platform", "claude", "--file", claudeOne, "--out", claudeOut], + { encoding: "utf8" }, + ); + const bundle = readFileSync(claudeOut, "utf8"); -const claudeOutput = execFileSync( - process.execPath, - [ - cliPath, - "transcript", - "bundle", - "--platform", - "claude", - "--file", - claudeOne, - "--out", - claudeOut, - ], - { encoding: "utf8" }, -); -const claudeBundle = readFileSync(claudeOut, "utf8"); -assert.match(claudeOutput, /claude-code-session:claude-session-one/); -assert.match(claudeBundle, /session_ref: claude-code-session:claude-session-one/); -assert.match(claudeBundle, /Remember this durable Claude insight/); + expect(output).toMatch(/claude-code-session:claude-session-one/); + expect(bundle).toMatch(/session_ref: claude-code-session:claude-session-one/); + expect(bundle).toMatch(/Remember this durable Claude insight/); + }); -const copilotOutput = execFileSync( - process.execPath, - [ - cliPath, - "transcript", - "bundle", - "--platform", - "copilot", - "--file", - copilotOne, - "--out", - copilotOut, - ], - { encoding: "utf8" }, -); -const copilotBundle = readFileSync(copilotOut, "utf8"); -assert.match(copilotOutput, /copilot-session:copilot-session-one/); -assert.match(copilotBundle, /session_ref: copilot-session:copilot-session-one/); -assert.match(copilotBundle, /repository: Autoloops\/greplica/); -assert.match(copilotBundle, /branch: copilot-test/); -assert.match(copilotBundle, /Remember this durable Copilot insight/); -assert.match(copilotBundle, /A Copilot assistant fact/); -assert.doesNotMatch(copilotBundle, /remove this/); + test("bundles copilot transcript and strips system instructions", () => { + const tmp = mkdtempSync(join(tmpdir(), "greplica-transcript-bundle-test-")); + const copilotOne = join(tmp, "copilot-one.jsonl"); + const copilotOut = join(tmp, "copilot-bundle.md"); -assert.throws( - () => - execFileSync( + writeFileSync( + copilotOne, + [ + JSON.stringify({ + type: "session.start", + data: { + sessionId: "copilot-session-one", + copilotVersion: "1.0.66", + context: { cwd: "/repo/example", repository: "Autoloops/greplica", branch: "copilot-test" }, + }, + timestamp: "2026-06-25T00:03:30.000Z", + }), + JSON.stringify({ + type: "session.model_change", + data: { newModel: "claude-haiku-4.5" }, + timestamp: "2026-06-25T00:03:45.000Z", + }), + JSON.stringify({ + session_id: "copilot-session-one", + cwd: "/repo/example", + timestamp: "2026-06-25T00:04:00.000Z", + role: "user", + content: "Remember this durable Copilot insight. remove this", + }), + JSON.stringify({ + session_id: "copilot-session-one", + cwd: "/repo/example", + timestamp: "2026-06-25T00:05:00.000Z", + role: "assistant", + content: [{ type: "text", text: "A Copilot assistant fact." }], + }), + ].join("\n"), + "utf8", + ); + + const output = execFileSync( process.execPath, - [cliPath, "transcript", "bundle", "--platform", "codex", "--file", join(tmp, "missing.jsonl"), "--out", join(tmp, "missing.md")], - { encoding: "utf8", stdio: "pipe" }, - ), - /Transcript file does not exist/, -); + [cliPath, "transcript", "bundle", "--platform", "copilot", "--file", copilotOne, "--out", copilotOut], + { encoding: "utf8" }, + ); + const bundle = readFileSync(copilotOut, "utf8"); + + expect(output).toMatch(/copilot-session:copilot-session-one/); + expect(bundle).toMatch(/session_ref: copilot-session:copilot-session-one/); + expect(bundle).toMatch(/repository: Autoloops\/greplica/); + expect(bundle).toMatch(/branch: copilot-test/); + expect(bundle).toMatch(/Remember this durable Copilot insight/); + expect(bundle).toMatch(/A Copilot assistant fact/); + expect(bundle).not.toMatch(/remove this/); + }); -const opencodeDataHome = join(tmp, "opencode-data"); -const opencodeSessionId = "opencode-session-one"; -const opencodeSessionFile = join(opencodeDataHome, "opencode", "storage", "session", `${opencodeSessionId}.json`); -mkdirSync(dirname(opencodeSessionFile), { recursive: true }); -mkdirSync(join(opencodeDataHome, "opencode", "storage", "message", opencodeSessionId), { recursive: true }); -writeFileSync( - opencodeSessionFile, - JSON.stringify({ - id: opencodeSessionId, - directory: "/repo/example", - }), - "utf8", -); -writeFileSync( - join(opencodeDataHome, "opencode", "storage", "message", opencodeSessionId, "msg-01.json"), - JSON.stringify({ - role: "user", - content: "Remember this durable OpenCode insight. remove this", - time: "2026-06-25T00:06:00.000Z", - }), - "utf8", -); -writeFileSync( - join(opencodeDataHome, "opencode", "storage", "message", opencodeSessionId, "msg-02.json"), - JSON.stringify({ - role: "assistant", - parts: [{ text: "An OpenCode assistant fact." }], - time: "2026-06-25T00:07:00.000Z", - }), - "utf8", -); + test("errors on missing transcript file", () => { + const tmp = mkdtempSync(join(tmpdir(), "greplica-transcript-bundle-test-")); + expect(() => + execFileSync( + process.execPath, + [cliPath, "transcript", "bundle", "--platform", "codex", "--file", join(tmp, "missing.jsonl"), "--out", join(tmp, "missing.md")], + { encoding: "utf8", stdio: "pipe" }, + ), + ).toThrow(/Transcript file does not exist/); + }); -const opencodeOutput = execFileSync( - process.execPath, - [ - cliPath, - "transcript", - "bundle", - "--platform", - "opencode", - "--file", - opencodeSessionFile, - "--out", - opencodeOut, - ], - { - encoding: "utf8", - env: { - ...process.env, - XDG_DATA_HOME: opencodeDataHome, - }, - }, -); -const opencodeBundle = readFileSync(opencodeOut, "utf8"); -assert.match(opencodeOutput, /opencode-session:opencode-session-one/); -assert.match(opencodeBundle, /session_ref: opencode-session:opencode-session-one/); -assert.match(opencodeBundle, /Remember this durable OpenCode insight/); -assert.match(opencodeBundle, /An OpenCode assistant fact/); -assert.doesNotMatch(opencodeBundle, /remove this/); + test("bundles opencode transcripts and strips system instructions", () => { + const tmp = mkdtempSync(join(tmpdir(), "greplica-transcript-bundle-test-")); + const opencodeDataHome = join(tmp, "opencode-data"); + const opencodeSessionId = "opencode-session-one"; + const opencodeSessionFile = join(opencodeDataHome, "opencode", "storage", "session", `${opencodeSessionId}.json`); + const opencodeOut = join(tmp, "opencode-bundle.md"); + + mkdirSync(dirname(opencodeSessionFile), { recursive: true }); + mkdirSync(join(opencodeDataHome, "opencode", "storage", "message", opencodeSessionId), { recursive: true }); + + writeFileSync( + opencodeSessionFile, + JSON.stringify({ id: opencodeSessionId, directory: "/repo/example" }), + "utf8", + ); + writeFileSync( + join(opencodeDataHome, "opencode", "storage", "message", opencodeSessionId, "msg-01.json"), + JSON.stringify({ + role: "user", + content: "Remember this durable OpenCode insight. remove this", + time: "2026-06-25T00:06:00.000Z", + }), + "utf8", + ); + writeFileSync( + join(opencodeDataHome, "opencode", "storage", "message", opencodeSessionId, "msg-02.json"), + JSON.stringify({ + role: "assistant", + parts: [{ text: "An OpenCode assistant fact." }], + time: "2026-06-25T00:07:00.000Z", + }), + "utf8", + ); + + const output = execFileSync( + process.execPath, + [cliPath, "transcript", "bundle", "--platform", "opencode", "--file", opencodeSessionFile, "--out", opencodeOut], + { encoding: "utf8", env: { ...process.env, XDG_DATA_HOME: opencodeDataHome } }, + ); + const bundle = readFileSync(opencodeOut, "utf8"); -console.log("Transcript bundle checks passed."); + expect(output).toMatch(/opencode-session:opencode-session-one/); + expect(bundle).toMatch(/session_ref: opencode-session:opencode-session-one/); + expect(bundle).toMatch(/Remember this durable OpenCode insight/); + expect(bundle).toMatch(/An OpenCode assistant fact/); + expect(bundle).not.toMatch(/remove this/); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index cb89211..c4e8387 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,5 +9,10 @@ "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, - "include": ["apps/**/*.ts", "libs/**/*.ts", "evals/**/*.ts", "scripts/**/*.ts"] + "include": [ + "apps/**/*.ts", + "libs/**/*.ts", + "evals/**/*.ts", + "scripts/**/*.ts" + ] } diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..8afb5ec --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: false, + environment: 'node', + include: ['scripts/check-*.js'], + exclude: ['node_modules', 'dist', 'evals', 'scripts/check-node-version.js'], + }, + resolve: { + conditions: ['node'], + }, +});