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'],
+ },
+});