feat(TRUEREF-0020): add embedding profiles, default local embeddings, and version-scoped semantic retrieval
- Add embedding_profiles table with provider registry pattern - Install @xenova/transformers as runtime dependency - Update snippet_embeddings with composite PK (snippet_id, profile_id) - Seed default local profile using Xenova/all-MiniLM-L6-v2 - Add provider registry (local-transformers, openai-compatible) - Update EmbeddingService to persist and retrieve by profileId - Add version-scoped VectorSearch with optional versionId filtering - Add searchMode (auto|keyword|semantic|hybrid) to HybridSearchService - Update API /context route to load active profile, support searchMode/alpha params - Extend MCP query-docs tool with searchMode and alpha parameters - Update settings API to work with embedding_profiles table - Add comprehensive test coverage for profiles, registry, version scoping Status: 445/451 tests passing, core feature complete
This commit is contained in:
434
package-lock.json
generated
434
package-lock.json
generated
@@ -9,6 +9,7 @@
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.27.1",
|
||||
"@xenova/transformers": "^2.17.2",
|
||||
"better-sqlite3": "^12.6.2",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
@@ -1150,6 +1151,15 @@
|
||||
"hono": "^4"
|
||||
}
|
||||
},
|
||||
"node_modules/@huggingface/jinja": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@huggingface/jinja/-/jinja-0.2.2.tgz",
|
||||
"integrity": "sha512-/KPde26khDUIPkTGU82jdtTW9UAuvUTumCAbFs/7giR0SxsvZC4hru51PBvpijH6BVkHcROcvZM/lpy5h1jRRA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanfs/core": {
|
||||
"version": "0.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
||||
@@ -1337,6 +1347,70 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@protobufjs/aspromise": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
||||
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/base64": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
|
||||
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/codegen": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
|
||||
"integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/eventemitter": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
|
||||
"integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/fetch": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
|
||||
"integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@protobufjs/aspromise": "^1.1.1",
|
||||
"@protobufjs/inquire": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@protobufjs/float": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
|
||||
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/inquire": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
|
||||
"integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/path": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
|
||||
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/pool": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
|
||||
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/utf8": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
|
||||
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@rollup/plugin-commonjs": {
|
||||
"version": "29.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-29.0.2.tgz",
|
||||
@@ -2234,11 +2308,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/long": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
|
||||
"integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz",
|
||||
"integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.16.0"
|
||||
@@ -2712,6 +2791,20 @@
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@xenova/transformers": {
|
||||
"version": "2.17.2",
|
||||
"resolved": "https://registry.npmjs.org/@xenova/transformers/-/transformers-2.17.2.tgz",
|
||||
"integrity": "sha512-lZmHqzrVIkSvZdKZEx7IYY51TK0WDrC8eR0c5IMnBsO8di8are1zzw8BlLhyO2TklZKLN5UffNGs1IJwT6oOqQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@huggingface/jinja": "^0.2.2",
|
||||
"onnxruntime-web": "1.14.0",
|
||||
"sharp": "^0.32.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"onnxruntime-node": "1.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
|
||||
@@ -2858,6 +2951,20 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/b4a": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz",
|
||||
"integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==",
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"react-native-b4a": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-native-b4a": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
@@ -2865,6 +2972,97 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bare-events": {
|
||||
"version": "2.8.2",
|
||||
"resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz",
|
||||
"integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"bare-abort-controller": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bare-abort-controller": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/bare-fs": {
|
||||
"version": "4.5.6",
|
||||
"resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.6.tgz",
|
||||
"integrity": "sha512-1QovqDrR80Pmt5HPAsMsXTCFcDYr+NSUKW6nd6WO5v0JBmnItc/irNRzm2KOQ5oZ69P37y+AMujNyNtG+1Rggw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"bare-events": "^2.5.4",
|
||||
"bare-path": "^3.0.0",
|
||||
"bare-stream": "^2.6.4",
|
||||
"bare-url": "^2.2.2",
|
||||
"fast-fifo": "^1.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"bare": ">=1.16.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bare-buffer": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bare-buffer": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/bare-os": {
|
||||
"version": "3.8.0",
|
||||
"resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.8.0.tgz",
|
||||
"integrity": "sha512-Dc9/SlwfxkXIGYhvMQNUtKaXCaGkZYGcd1vuNUUADVqzu4/vQfvnMkYYOUnt2VwQ2AqKr/8qAVFRtwETljgeFg==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"bare": ">=1.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bare-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz",
|
||||
"integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"bare-os": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/bare-stream": {
|
||||
"version": "2.11.0",
|
||||
"resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.11.0.tgz",
|
||||
"integrity": "sha512-Y/+iQ49fL3rIn6w/AVxI/2+BRrpmzJvdWt5Jv8Za6Ngqc6V227c+pYjYYgLdpR3MwQ9ObVXD0ZrqoBztakM0rw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"streamx": "^2.25.0",
|
||||
"teex": "^1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bare-abort-controller": "*",
|
||||
"bare-buffer": "*",
|
||||
"bare-events": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bare-abort-controller": {
|
||||
"optional": true
|
||||
},
|
||||
"bare-buffer": {
|
||||
"optional": true
|
||||
},
|
||||
"bare-events": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/bare-url": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.0.tgz",
|
||||
"integrity": "sha512-NSTU5WN+fy/L0DDenfE8SXQna4voXuW0FHM7wH8i3/q9khUSchfPbPezO4zSFMnDGIf9YE+mt/RWhZgNRKRIXA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"bare-path": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
@@ -3093,11 +3291,23 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/color": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
|
||||
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1",
|
||||
"color-string": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
@@ -3110,9 +3320,18 @@
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/color-string": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
|
||||
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "^1.0.0",
|
||||
"simple-swizzle": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/commondir": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
||||
@@ -3859,6 +4078,15 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/events-universal": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
|
||||
"integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"bare-events": "^2.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eventsource": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
|
||||
@@ -3975,6 +4203,12 @@
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-fifo": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
|
||||
"integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-json-stable-stringify": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
||||
@@ -4094,6 +4328,12 @@
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/flatbuffers": {
|
||||
"version": "1.12.0",
|
||||
"resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-1.12.0.tgz",
|
||||
"integrity": "sha512-c7CZADjRcl6j0PlvFy0ZqXQ67qSEZfrVPynmnL+2zPc+NtMvrF8Y0QceMo7QqnSPc7+uWjUIAbvCQ5WIKlMVdQ==",
|
||||
"license": "SEE LICENSE IN LICENSE.txt"
|
||||
},
|
||||
"node_modules/flatted": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
|
||||
@@ -4250,6 +4490,12 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/guid-typescript": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/guid-typescript/-/guid-typescript-1.0.9.tgz",
|
||||
"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",
|
||||
@@ -4417,6 +4663,12 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/is-arrayish": {
|
||||
"version": "0.3.4",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
|
||||
"integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-core-module": {
|
||||
"version": "2.16.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||
@@ -4886,6 +5138,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/long": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
|
||||
"integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==",
|
||||
"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",
|
||||
@@ -5070,6 +5328,12 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz",
|
||||
"integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
@@ -5123,6 +5387,50 @@
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/onnx-proto": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/onnx-proto/-/onnx-proto-4.0.4.tgz",
|
||||
"integrity": "sha512-aldMOB3HRoo6q/phyB6QRQxSt895HNNw82BNyZ2CMh4bjeKv7g/c+VpAFtJuEMVfYLMbRx61hbuqnKceLeDcDA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"protobufjs": "^6.8.8"
|
||||
}
|
||||
},
|
||||
"node_modules/onnxruntime-common": {
|
||||
"version": "1.14.0",
|
||||
"resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.14.0.tgz",
|
||||
"integrity": "sha512-3LJpegM2iMNRX2wUmtYfeX/ytfOzNwAWKSq1HbRrKc9+uqG/FsEA0bbKZl1btQeZaXhC26l44NWpNUeXPII7Ew==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/onnxruntime-node": {
|
||||
"version": "1.14.0",
|
||||
"resolved": "https://registry.npmjs.org/onnxruntime-node/-/onnxruntime-node-1.14.0.tgz",
|
||||
"integrity": "sha512-5ba7TWomIV/9b6NH/1x/8QEeowsb+jBEvFzU6z0T4mNsFwdPqXeFUM7uxC6QeSRkEbWu3qEB0VMjrvzN/0S9+w==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32",
|
||||
"darwin",
|
||||
"linux"
|
||||
],
|
||||
"dependencies": {
|
||||
"onnxruntime-common": "~1.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/onnxruntime-web": {
|
||||
"version": "1.14.0",
|
||||
"resolved": "https://registry.npmjs.org/onnxruntime-web/-/onnxruntime-web-1.14.0.tgz",
|
||||
"integrity": "sha512-Kcqf43UMfW8mCydVGcX9OMXI2VN17c0p6XvR7IPSZzBf/6lteBzXHvcEVWDPmCKuGombl997HgLqj91F11DzXw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"flatbuffers": "^1.12.0",
|
||||
"guid-typescript": "^1.0.9",
|
||||
"long": "^4.0.0",
|
||||
"onnx-proto": "^4.0.4",
|
||||
"onnxruntime-common": "~1.14.0",
|
||||
"platform": "^1.3.6"
|
||||
}
|
||||
},
|
||||
"node_modules/optionator": {
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||
@@ -5267,6 +5575,12 @@
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"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/playwright": {
|
||||
"version": "1.58.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz",
|
||||
@@ -5593,6 +5907,32 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/protobufjs": {
|
||||
"version": "6.11.4",
|
||||
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz",
|
||||
"integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@protobufjs/aspromise": "^1.1.2",
|
||||
"@protobufjs/base64": "^1.1.2",
|
||||
"@protobufjs/codegen": "^2.0.4",
|
||||
"@protobufjs/eventemitter": "^1.1.0",
|
||||
"@protobufjs/fetch": "^1.1.0",
|
||||
"@protobufjs/float": "^1.0.2",
|
||||
"@protobufjs/inquire": "^1.1.0",
|
||||
"@protobufjs/path": "^1.1.2",
|
||||
"@protobufjs/pool": "^1.1.0",
|
||||
"@protobufjs/utf8": "^1.1.0",
|
||||
"@types/long": "^4.0.1",
|
||||
"@types/node": ">=13.7.0",
|
||||
"long": "^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"pbjs": "bin/pbjs",
|
||||
"pbts": "bin/pbts"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-addr": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||
@@ -5938,6 +6278,55 @@
|
||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/sharp": {
|
||||
"version": "0.32.6",
|
||||
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz",
|
||||
"integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"color": "^4.2.3",
|
||||
"detect-libc": "^2.0.2",
|
||||
"node-addon-api": "^6.1.0",
|
||||
"prebuild-install": "^7.1.1",
|
||||
"semver": "^7.5.4",
|
||||
"simple-get": "^4.0.1",
|
||||
"tar-fs": "^3.0.4",
|
||||
"tunnel-agent": "^0.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.15.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/sharp/node_modules/tar-fs": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz",
|
||||
"integrity": "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pump": "^3.0.0",
|
||||
"tar-stream": "^3.1.5"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"bare-fs": "^4.0.1",
|
||||
"bare-path": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sharp/node_modules/tar-stream": {
|
||||
"version": "3.1.8",
|
||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.8.tgz",
|
||||
"integrity": "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"b4a": "^1.6.4",
|
||||
"bare-fs": "^4.5.5",
|
||||
"fast-fifo": "^1.2.0",
|
||||
"streamx": "^2.15.0"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
@@ -6083,6 +6472,15 @@
|
||||
"simple-concat": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-swizzle": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
|
||||
"integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-arrayish": "^0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/sirv": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz",
|
||||
@@ -6152,6 +6550,17 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/streamx": {
|
||||
"version": "2.25.0",
|
||||
"resolved": "https://registry.npmjs.org/streamx/-/streamx-2.25.0.tgz",
|
||||
"integrity": "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"events-universal": "^1.0.0",
|
||||
"fast-fifo": "^1.3.2",
|
||||
"text-decoder": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
@@ -6343,6 +6752,24 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/teex": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz",
|
||||
"integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"streamx": "^2.12.5"
|
||||
}
|
||||
},
|
||||
"node_modules/text-decoder": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz",
|
||||
"integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"b4a": "^1.6.4"
|
||||
}
|
||||
},
|
||||
"node_modules/tinybench": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
|
||||
@@ -7020,7 +7447,6 @@
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
||||
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.27.1",
|
||||
"@xenova/transformers": "^2.17.2",
|
||||
"better-sqlite3": "^12.6.2",
|
||||
"zod": "^4.3.6"
|
||||
}
|
||||
|
||||
34
src/lib/server/db/migrations/0002_silky_stellaris.sql
Normal file
34
src/lib/server/db/migrations/0002_silky_stellaris.sql
Normal file
@@ -0,0 +1,34 @@
|
||||
CREATE TABLE `embedding_profiles` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`provider_kind` text NOT NULL,
|
||||
`title` text NOT NULL,
|
||||
`enabled` integer DEFAULT true NOT NULL,
|
||||
`is_default` integer DEFAULT false NOT NULL,
|
||||
`model` text NOT NULL,
|
||||
`dimensions` integer NOT NULL,
|
||||
`config` text NOT NULL,
|
||||
`created_at` integer NOT NULL,
|
||||
`updated_at` integer NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
INSERT INTO embedding_profiles (id, provider_kind, title, enabled, is_default, model, dimensions, config, created_at, updated_at)
|
||||
VALUES ('local-default', 'local-transformers', 'Local (Xenova/all-MiniLM-L6-v2)', 1, 1, 'Xenova/all-MiniLM-L6-v2', 384, '{}', unixepoch(), unixepoch())
|
||||
ON CONFLICT(id) DO NOTHING;
|
||||
--> statement-breakpoint
|
||||
PRAGMA foreign_keys=OFF;--> statement-breakpoint
|
||||
CREATE TABLE `__new_snippet_embeddings` (
|
||||
`snippet_id` text NOT NULL,
|
||||
`profile_id` text NOT NULL,
|
||||
`model` text NOT NULL,
|
||||
`dimensions` integer NOT NULL,
|
||||
`embedding` blob NOT NULL,
|
||||
`created_at` integer NOT NULL,
|
||||
PRIMARY KEY(`snippet_id`, `profile_id`),
|
||||
FOREIGN KEY (`snippet_id`) REFERENCES `snippets`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY (`profile_id`) REFERENCES `embedding_profiles`(`id`) ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
INSERT INTO `__new_snippet_embeddings`("snippet_id", "profile_id", "model", "dimensions", "embedding", "created_at") SELECT "snippet_id", 'local-default', "model", "dimensions", "embedding", "created_at" FROM `snippet_embeddings`;--> statement-breakpoint
|
||||
DROP TABLE `snippet_embeddings`;--> statement-breakpoint
|
||||
ALTER TABLE `__new_snippet_embeddings` RENAME TO `snippet_embeddings`;--> statement-breakpoint
|
||||
PRAGMA foreign_keys=ON;
|
||||
856
src/lib/server/db/migrations/meta/0002_snapshot.json
Normal file
856
src/lib/server/db/migrations/meta/0002_snapshot.json
Normal file
@@ -0,0 +1,856 @@
|
||||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "31531dab-a199-4fc5-a889-1884940039cd",
|
||||
"prevId": "60c9a1b5-449f-45fd-9b2d-1ab4cca78ab6",
|
||||
"tables": {
|
||||
"documents": {
|
||||
"name": "documents",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"repository_id": {
|
||||
"name": "repository_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"version_id": {
|
||||
"name": "version_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"file_path": {
|
||||
"name": "file_path",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"language": {
|
||||
"name": "language",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"token_count": {
|
||||
"name": "token_count",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"checksum": {
|
||||
"name": "checksum",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"indexed_at": {
|
||||
"name": "indexed_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"documents_repository_id_repositories_id_fk": {
|
||||
"name": "documents_repository_id_repositories_id_fk",
|
||||
"tableFrom": "documents",
|
||||
"tableTo": "repositories",
|
||||
"columnsFrom": [
|
||||
"repository_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"documents_version_id_repository_versions_id_fk": {
|
||||
"name": "documents_version_id_repository_versions_id_fk",
|
||||
"tableFrom": "documents",
|
||||
"tableTo": "repository_versions",
|
||||
"columnsFrom": [
|
||||
"version_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"embedding_profiles": {
|
||||
"name": "embedding_profiles",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"provider_kind": {
|
||||
"name": "provider_kind",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"enabled": {
|
||||
"name": "enabled",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": true
|
||||
},
|
||||
"is_default": {
|
||||
"name": "is_default",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": false
|
||||
},
|
||||
"model": {
|
||||
"name": "model",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"dimensions": {
|
||||
"name": "dimensions",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"config": {
|
||||
"name": "config",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"indexing_jobs": {
|
||||
"name": "indexing_jobs",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"repository_id": {
|
||||
"name": "repository_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"version_id": {
|
||||
"name": "version_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "'queued'"
|
||||
},
|
||||
"progress": {
|
||||
"name": "progress",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"total_files": {
|
||||
"name": "total_files",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"processed_files": {
|
||||
"name": "processed_files",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"error": {
|
||||
"name": "error",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"started_at": {
|
||||
"name": "started_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"completed_at": {
|
||||
"name": "completed_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"indexing_jobs_repository_id_repositories_id_fk": {
|
||||
"name": "indexing_jobs_repository_id_repositories_id_fk",
|
||||
"tableFrom": "indexing_jobs",
|
||||
"tableTo": "repositories",
|
||||
"columnsFrom": [
|
||||
"repository_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"repositories": {
|
||||
"name": "repositories",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"source": {
|
||||
"name": "source",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"source_url": {
|
||||
"name": "source_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"branch": {
|
||||
"name": "branch",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": "'main'"
|
||||
},
|
||||
"state": {
|
||||
"name": "state",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "'pending'"
|
||||
},
|
||||
"total_snippets": {
|
||||
"name": "total_snippets",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"total_tokens": {
|
||||
"name": "total_tokens",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"trust_score": {
|
||||
"name": "trust_score",
|
||||
"type": "real",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"benchmark_score": {
|
||||
"name": "benchmark_score",
|
||||
"type": "real",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"stars": {
|
||||
"name": "stars",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"github_token": {
|
||||
"name": "github_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"last_indexed_at": {
|
||||
"name": "last_indexed_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"repository_configs": {
|
||||
"name": "repository_configs",
|
||||
"columns": {
|
||||
"repository_id": {
|
||||
"name": "repository_id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"project_title": {
|
||||
"name": "project_title",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"folders": {
|
||||
"name": "folders",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"exclude_folders": {
|
||||
"name": "exclude_folders",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"exclude_files": {
|
||||
"name": "exclude_files",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"rules": {
|
||||
"name": "rules",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"previous_versions": {
|
||||
"name": "previous_versions",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"repository_configs_repository_id_repositories_id_fk": {
|
||||
"name": "repository_configs_repository_id_repositories_id_fk",
|
||||
"tableFrom": "repository_configs",
|
||||
"tableTo": "repositories",
|
||||
"columnsFrom": [
|
||||
"repository_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"repository_versions": {
|
||||
"name": "repository_versions",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"repository_id": {
|
||||
"name": "repository_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"tag": {
|
||||
"name": "tag",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"commit_hash": {
|
||||
"name": "commit_hash",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"state": {
|
||||
"name": "state",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "'pending'"
|
||||
},
|
||||
"total_snippets": {
|
||||
"name": "total_snippets",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"indexed_at": {
|
||||
"name": "indexed_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"repository_versions_repository_id_repositories_id_fk": {
|
||||
"name": "repository_versions_repository_id_repositories_id_fk",
|
||||
"tableFrom": "repository_versions",
|
||||
"tableTo": "repositories",
|
||||
"columnsFrom": [
|
||||
"repository_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"settings": {
|
||||
"name": "settings",
|
||||
"columns": {
|
||||
"key": {
|
||||
"name": "key",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"value": {
|
||||
"name": "value",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"snippet_embeddings": {
|
||||
"name": "snippet_embeddings",
|
||||
"columns": {
|
||||
"snippet_id": {
|
||||
"name": "snippet_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"profile_id": {
|
||||
"name": "profile_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"model": {
|
||||
"name": "model",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"dimensions": {
|
||||
"name": "dimensions",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"embedding": {
|
||||
"name": "embedding",
|
||||
"type": "blob",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"snippet_embeddings_snippet_id_snippets_id_fk": {
|
||||
"name": "snippet_embeddings_snippet_id_snippets_id_fk",
|
||||
"tableFrom": "snippet_embeddings",
|
||||
"tableTo": "snippets",
|
||||
"columnsFrom": [
|
||||
"snippet_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"snippet_embeddings_profile_id_embedding_profiles_id_fk": {
|
||||
"name": "snippet_embeddings_profile_id_embedding_profiles_id_fk",
|
||||
"tableFrom": "snippet_embeddings",
|
||||
"tableTo": "embedding_profiles",
|
||||
"columnsFrom": [
|
||||
"profile_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"snippet_embeddings_snippet_id_profile_id_pk": {
|
||||
"columns": [
|
||||
"snippet_id",
|
||||
"profile_id"
|
||||
],
|
||||
"name": "snippet_embeddings_snippet_id_profile_id_pk"
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"snippets": {
|
||||
"name": "snippets",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"document_id": {
|
||||
"name": "document_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"repository_id": {
|
||||
"name": "repository_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"version_id": {
|
||||
"name": "version_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"content": {
|
||||
"name": "content",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"language": {
|
||||
"name": "language",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"breadcrumb": {
|
||||
"name": "breadcrumb",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"token_count": {
|
||||
"name": "token_count",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"snippets_document_id_documents_id_fk": {
|
||||
"name": "snippets_document_id_documents_id_fk",
|
||||
"tableFrom": "snippets",
|
||||
"tableTo": "documents",
|
||||
"columnsFrom": [
|
||||
"document_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"snippets_repository_id_repositories_id_fk": {
|
||||
"name": "snippets_repository_id_repositories_id_fk",
|
||||
"tableFrom": "snippets",
|
||||
"tableTo": "repositories",
|
||||
"columnsFrom": [
|
||||
"repository_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"snippets_version_id_repository_versions_id_fk": {
|
||||
"name": "snippets_version_id_repository_versions_id_fk",
|
||||
"tableFrom": "snippets",
|
||||
"tableTo": "repository_versions",
|
||||
"columnsFrom": [
|
||||
"version_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
}
|
||||
},
|
||||
"views": {},
|
||||
"enums": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
},
|
||||
"internal": {
|
||||
"indexes": {}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,13 @@
|
||||
"when": 1774448049161,
|
||||
"tag": "0001_quick_nighthawk",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 2,
|
||||
"version": "6",
|
||||
"when": 1774461897742,
|
||||
"tag": "0002_silky_stellaris",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { blob, integer, real, sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
||||
import { blob, integer, primaryKey, real, sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// repositories
|
||||
@@ -86,18 +86,41 @@ export const snippets = sqliteTable('snippets', {
|
||||
createdAt: integer('created_at', { mode: 'timestamp' }).notNull()
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// embedding_profiles
|
||||
// ---------------------------------------------------------------------------
|
||||
export const embeddingProfiles = sqliteTable('embedding_profiles', {
|
||||
id: text('id').primaryKey(),
|
||||
providerKind: text('provider_kind').notNull(),
|
||||
title: text('title').notNull(),
|
||||
enabled: integer('enabled', { mode: 'boolean' }).notNull().default(true),
|
||||
isDefault: integer('is_default', { mode: 'boolean' }).notNull().default(false),
|
||||
model: text('model').notNull(),
|
||||
dimensions: integer('dimensions').notNull(),
|
||||
config: text('config', { mode: 'json' }).notNull().$type<Record<string, unknown>>(),
|
||||
createdAt: integer('created_at').notNull(),
|
||||
updatedAt: integer('updated_at').notNull()
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// snippet_embeddings
|
||||
// ---------------------------------------------------------------------------
|
||||
export const snippetEmbeddings = sqliteTable('snippet_embeddings', {
|
||||
snippetId: text('snippet_id')
|
||||
.primaryKey()
|
||||
.references(() => snippets.id, { onDelete: 'cascade' }),
|
||||
model: text('model').notNull(), // embedding model identifier
|
||||
dimensions: integer('dimensions').notNull(),
|
||||
embedding: blob('embedding').notNull(), // Float32Array as binary blob
|
||||
createdAt: integer('created_at', { mode: 'timestamp' }).notNull()
|
||||
});
|
||||
export const snippetEmbeddings = sqliteTable(
|
||||
'snippet_embeddings',
|
||||
{
|
||||
snippetId: text('snippet_id')
|
||||
.notNull()
|
||||
.references(() => snippets.id, { onDelete: 'cascade' }),
|
||||
profileId: text('profile_id')
|
||||
.notNull()
|
||||
.references(() => embeddingProfiles.id, { onDelete: 'cascade' }),
|
||||
model: text('model').notNull(), // embedding model identifier
|
||||
dimensions: integer('dimensions').notNull(),
|
||||
embedding: blob('embedding').notNull(), // Float32Array as binary blob
|
||||
createdAt: integer('created_at').notNull()
|
||||
},
|
||||
(table) => [primaryKey({ columns: [table.snippetId, table.profileId] })]
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// indexing_jobs
|
||||
@@ -165,6 +188,9 @@ export type NewDocument = typeof documents.$inferInsert;
|
||||
export type Snippet = typeof snippets.$inferSelect;
|
||||
export type NewSnippet = typeof snippets.$inferInsert;
|
||||
|
||||
export type EmbeddingProfile = typeof embeddingProfiles.$inferSelect;
|
||||
export type NewEmbeddingProfile = typeof embeddingProfiles.$inferInsert;
|
||||
|
||||
export type SnippetEmbedding = typeof snippetEmbeddings.$inferSelect;
|
||||
export type NewSnippetEmbedding = typeof snippetEmbeddings.$inferInsert;
|
||||
|
||||
|
||||
@@ -248,6 +248,99 @@ describe('OpenAIEmbeddingProvider', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Migration Tests — embedding_profiles table
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Migration — embedding_profiles', () => {
|
||||
it('creates the embedding_profiles table', () => {
|
||||
const { client } = createTestDb();
|
||||
const tables = client
|
||||
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='embedding_profiles'")
|
||||
.all();
|
||||
expect(tables).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('seeds the default local profile', () => {
|
||||
const { client } = createTestDb();
|
||||
const row = client
|
||||
.prepare("SELECT * FROM embedding_profiles WHERE id = 'local-default'")
|
||||
.get() as any;
|
||||
expect(row).toBeDefined();
|
||||
expect(row.is_default).toBe(1);
|
||||
expect(row.provider_kind).toBe('local-transformers');
|
||||
expect(row.model).toBe('Xenova/all-MiniLM-L6-v2');
|
||||
expect(row.dimensions).toBe(384);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Provider Registry Tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Provider Registry', () => {
|
||||
it('creates LocalEmbeddingProvider for local-transformers', () => {
|
||||
const { createProviderFromProfile } = require('./registry.js');
|
||||
const profile: schema.EmbeddingProfile = {
|
||||
id: 'test-local',
|
||||
providerKind: 'local-transformers',
|
||||
title: 'Test Local',
|
||||
enabled: true,
|
||||
isDefault: false,
|
||||
model: 'Xenova/all-MiniLM-L6-v2',
|
||||
dimensions: 384,
|
||||
config: {},
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now()
|
||||
};
|
||||
const provider = createProviderFromProfile(profile);
|
||||
expect(provider.name).toBe('local');
|
||||
expect(provider.model).toBe('Xenova/all-MiniLM-L6-v2');
|
||||
expect(provider.dimensions).toBe(384);
|
||||
});
|
||||
|
||||
it('creates OpenAIEmbeddingProvider for openai-compatible', () => {
|
||||
const { createProviderFromProfile } = require('./registry.js');
|
||||
const profile: schema.EmbeddingProfile = {
|
||||
id: 'test-openai',
|
||||
providerKind: 'openai-compatible',
|
||||
title: 'Test OpenAI',
|
||||
enabled: true,
|
||||
isDefault: false,
|
||||
model: 'text-embedding-3-small',
|
||||
dimensions: 1536,
|
||||
config: {
|
||||
baseUrl: 'https://api.openai.com/v1',
|
||||
apiKey: 'test-key',
|
||||
model: 'text-embedding-3-small'
|
||||
},
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now()
|
||||
};
|
||||
const provider = createProviderFromProfile(profile);
|
||||
expect(provider.name).toBe('openai');
|
||||
expect(provider.model).toBe('text-embedding-3-small');
|
||||
});
|
||||
|
||||
it('returns NoopEmbeddingProvider for unknown providerKind', () => {
|
||||
const { createProviderFromProfile } = require('./registry.js');
|
||||
const profile: schema.EmbeddingProfile = {
|
||||
id: 'test-unknown',
|
||||
providerKind: 'unknown-provider',
|
||||
title: 'Unknown',
|
||||
enabled: true,
|
||||
isDefault: false,
|
||||
model: 'unknown',
|
||||
dimensions: 0,
|
||||
config: {},
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now()
|
||||
};
|
||||
const provider = createProviderFromProfile(profile);
|
||||
expect(provider.name).toBe('noop');
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// EmbeddingService — storage logic
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -281,23 +374,36 @@ describe('EmbeddingService', () => {
|
||||
it('stores embeddings in snippet_embeddings table', async () => {
|
||||
const snippetId = seedSnippet(db, client);
|
||||
const provider = makeProvider(4);
|
||||
const service = new EmbeddingService(client, provider);
|
||||
const service = new EmbeddingService(client, provider, 'test-profile');
|
||||
|
||||
await service.embedSnippets([snippetId]);
|
||||
|
||||
const rows = client.prepare('SELECT * FROM snippet_embeddings WHERE snippet_id = ?').all(snippetId);
|
||||
const rows = client
|
||||
.prepare('SELECT * FROM snippet_embeddings WHERE snippet_id = ? AND profile_id = ?')
|
||||
.all(snippetId, 'test-profile');
|
||||
expect(rows).toHaveLength(1);
|
||||
|
||||
const row = rows[0] as { model: string; dimensions: number; embedding: Buffer };
|
||||
const row = rows[0] as { model: string; dimensions: number; embedding: Buffer; profile_id: string };
|
||||
expect(row.model).toBe('test-model');
|
||||
expect(row.dimensions).toBe(4);
|
||||
expect(row.profile_id).toBe('test-profile');
|
||||
expect(row.embedding).toBeInstanceOf(Buffer);
|
||||
});
|
||||
|
||||
it('stores embeddings as retrievable Float32Array blobs', async () => {
|
||||
const snippetId = seedSnippet(db, client);
|
||||
const provider = makeProvider(3);
|
||||
const service = new EmbeddingService(client, provider);
|
||||
const service = new EmbeddingService(client, provider, 'test-profile');
|
||||
|
||||
await service.embedSnippets([snippetId]);
|
||||
|
||||
const embedding = service.getEmbedding(snippetId, 'test-profile');
|
||||
expect(embedding).toBeInstanceOf(Float32Array);
|
||||
expect(embedding).toHaveLength(3);
|
||||
expect(embedding![0]).toBeCloseTo(0.0, 5);
|
||||
expect(embedding![1]).toBeCloseTo(0.1, 5);
|
||||
expect(embedding![2]).toBeCloseTo(0.2, 5);
|
||||
});
|
||||
|
||||
await service.embedSnippets([snippetId]);
|
||||
|
||||
|
||||
@@ -19,7 +19,8 @@ const TEXT_MAX_CHARS = 2048;
|
||||
export class EmbeddingService {
|
||||
constructor(
|
||||
private readonly db: Database.Database,
|
||||
private readonly provider: EmbeddingProvider
|
||||
private readonly provider: EmbeddingProvider,
|
||||
private readonly profileId: string = 'local-default'
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -54,9 +55,9 @@ export class EmbeddingService {
|
||||
.slice(0, TEXT_MAX_CHARS)
|
||||
);
|
||||
|
||||
const insert = this.db.prepare<[string, string, number, Buffer]>(`
|
||||
INSERT OR REPLACE INTO snippet_embeddings (snippet_id, model, dimensions, embedding, created_at)
|
||||
VALUES (?, ?, ?, ?, unixepoch())
|
||||
const insert = this.db.prepare<[string, string, string, number, Buffer]>(`
|
||||
INSERT OR REPLACE INTO snippet_embeddings (snippet_id, profile_id, model, dimensions, embedding, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, unixepoch())
|
||||
`);
|
||||
|
||||
for (let i = 0; i < snippets.length; i += BATCH_SIZE) {
|
||||
@@ -71,6 +72,7 @@ export class EmbeddingService {
|
||||
const embedding = embeddings[j];
|
||||
insert.run(
|
||||
snippet.id,
|
||||
this.profileId,
|
||||
embedding.model,
|
||||
embedding.dimensions,
|
||||
Buffer.from(embedding.values.buffer)
|
||||
@@ -85,14 +87,17 @@ export class EmbeddingService {
|
||||
|
||||
/**
|
||||
* Retrieve a stored embedding for a snippet as a Float32Array.
|
||||
* Returns null when no embedding has been stored for the given snippet.
|
||||
* Returns null when no embedding has been stored for the given snippet and profile.
|
||||
*
|
||||
* @param snippetId - Snippet UUID
|
||||
* @param profileId - Embedding profile ID (default: 'local-default')
|
||||
*/
|
||||
getEmbedding(snippetId: string): Float32Array | null {
|
||||
getEmbedding(snippetId: string, profileId: string = 'local-default'): Float32Array | null {
|
||||
const row = this.db
|
||||
.prepare<[string], { embedding: Buffer; dimensions: number }>(
|
||||
`SELECT embedding, dimensions FROM snippet_embeddings WHERE snippet_id = ?`
|
||||
.prepare<[string, string], { embedding: Buffer; dimensions: number }>(
|
||||
`SELECT embedding, dimensions FROM snippet_embeddings WHERE snippet_id = ? AND profile_id = ?`
|
||||
)
|
||||
.get(snippetId);
|
||||
.get(snippetId, profileId);
|
||||
|
||||
if (!row) return null;
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
/**
|
||||
* Factory — create an EmbeddingProvider from a persisted EmbeddingConfig.
|
||||
*
|
||||
* This module maintains backward compatibility with the old enum-style config
|
||||
* while the registry pattern is adopted. Settings endpoints transition to
|
||||
* using embedding_profiles table + registry.ts directly.
|
||||
*/
|
||||
|
||||
import type { EmbeddingProvider } from './provider.js';
|
||||
@@ -7,6 +11,9 @@ import { NoopEmbeddingProvider } from './provider.js';
|
||||
import { OpenAIEmbeddingProvider } from './openai.provider.js';
|
||||
import { LocalEmbeddingProvider } from './local.provider.js';
|
||||
|
||||
// Re-export registry functions for new callers
|
||||
export { createProviderFromProfile, getDefaultLocalProfile, getRegisteredProviderKinds } from './registry.js';
|
||||
|
||||
export interface EmbeddingConfig {
|
||||
provider: 'openai' | 'local' | 'none';
|
||||
openai?: {
|
||||
|
||||
64
src/lib/server/embeddings/registry.ts
Normal file
64
src/lib/server/embeddings/registry.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Provider Registry — map providerKind to EmbeddingProvider instances.
|
||||
*
|
||||
* Replaces the enum-style factory with a registry pattern that supports
|
||||
* arbitrary custom provider adapters without changing core types.
|
||||
*/
|
||||
|
||||
import type { EmbeddingProvider } from './provider.js';
|
||||
import { NoopEmbeddingProvider } from './provider.js';
|
||||
import { OpenAIEmbeddingProvider } from './openai.provider.js';
|
||||
import { LocalEmbeddingProvider } from './local.provider.js';
|
||||
import type { EmbeddingProfile } from '../db/schema.js';
|
||||
|
||||
export type ProviderFactory = (config: Record<string, unknown>) => EmbeddingProvider;
|
||||
|
||||
const PROVIDER_REGISTRY: Record<string, ProviderFactory> = {
|
||||
'local-transformers': (_config) => new LocalEmbeddingProvider(),
|
||||
'openai-compatible': (config) =>
|
||||
new OpenAIEmbeddingProvider({
|
||||
baseUrl: config.baseUrl as string,
|
||||
apiKey: config.apiKey as string,
|
||||
model: config.model as string,
|
||||
dimensions: config.dimensions as number | undefined,
|
||||
maxBatchSize: config.maxBatchSize as number | undefined
|
||||
})
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an EmbeddingProvider from a persisted EmbeddingProfile.
|
||||
*
|
||||
* Falls back to NoopEmbeddingProvider when the providerKind is not recognized.
|
||||
*/
|
||||
export function createProviderFromProfile(profile: EmbeddingProfile): EmbeddingProvider {
|
||||
const factory = PROVIDER_REGISTRY[profile.providerKind];
|
||||
if (!factory) return new NoopEmbeddingProvider();
|
||||
const config = (profile.config as Record<string, unknown>) ?? {};
|
||||
return factory(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return metadata for the default local profile.
|
||||
*
|
||||
* Used by migration seeds and runtime defaults.
|
||||
*/
|
||||
export function getDefaultLocalProfile(): Pick<
|
||||
EmbeddingProfile,
|
||||
'id' | 'providerKind' | 'model' | 'dimensions'
|
||||
> {
|
||||
return {
|
||||
id: 'local-default',
|
||||
providerKind: 'local-transformers',
|
||||
model: 'Xenova/all-MiniLM-L6-v2',
|
||||
dimensions: 384
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all registered providerKind values.
|
||||
*
|
||||
* Useful for settings UI validation and provider discovery.
|
||||
*/
|
||||
export function getRegisteredProviderKinds(): string[] {
|
||||
return Object.keys(PROVIDER_REGISTRY);
|
||||
}
|
||||
@@ -25,16 +25,18 @@ function createTestDb(): Database.Database {
|
||||
client.pragma('foreign_keys = ON');
|
||||
|
||||
const migrationsFolder = join(import.meta.dirname, '../db/migrations');
|
||||
const migrationSql = readFileSync(
|
||||
join(migrationsFolder, '0000_large_master_chief.sql'),
|
||||
'utf-8'
|
||||
);
|
||||
const statements = migrationSql
|
||||
.split('--> statement-breakpoint')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
for (const stmt of statements) {
|
||||
client.exec(stmt);
|
||||
|
||||
// Run all migrations in order
|
||||
const migrations = ['0000_large_master_chief.sql', '0001_quick_nighthawk.sql', '0002_silky_stellaris.sql'];
|
||||
for (const migrationFile of migrations) {
|
||||
const migrationSql = readFileSync(join(migrationsFolder, migrationFile), 'utf-8');
|
||||
const statements = migrationSql
|
||||
.split('--> statement-breakpoint')
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
for (const stmt of statements) {
|
||||
client.exec(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
const ftsSql = readFileSync(join(import.meta.dirname, '../db/fts.sql'), 'utf-8');
|
||||
@@ -104,16 +106,17 @@ function seedEmbedding(
|
||||
client: Database.Database,
|
||||
snippetId: string,
|
||||
values: number[],
|
||||
profileId = 'local-default',
|
||||
model = 'test-model'
|
||||
): void {
|
||||
const f32 = new Float32Array(values);
|
||||
client
|
||||
.prepare(
|
||||
`INSERT OR REPLACE INTO snippet_embeddings
|
||||
(snippet_id, model, dimensions, embedding, created_at)
|
||||
VALUES (?, ?, ?, ?, ?)`
|
||||
(snippet_id, profile_id, model, dimensions, embedding, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`
|
||||
)
|
||||
.run(snippetId, model, values.length, Buffer.from(f32.buffer), NOW_S);
|
||||
.run(snippetId, profileId, model, values.length, Buffer.from(f32.buffer), NOW_S);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -621,4 +624,203 @@ describe('HybridSearchService', () => {
|
||||
const results = await svc.search('default alpha hybrid', { repositoryId: repoId });
|
||||
expect(Array.isArray(results)).toBe(true);
|
||||
});
|
||||
|
||||
it('filters by versionId — excludes snippets from other versions', async () => {
|
||||
const client = createTestDb();
|
||||
const repoId = seedRepo(client);
|
||||
const docId = seedDocument(client, repoId);
|
||||
|
||||
// Create two versions
|
||||
client
|
||||
.prepare(
|
||||
`INSERT INTO repository_versions (id, repository_id, tag, state, total_snippets, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`
|
||||
)
|
||||
.run('/test/repo/v1.0', repoId, 'v1.0', 'indexed', 0, NOW_S);
|
||||
client
|
||||
.prepare(
|
||||
`INSERT INTO repository_versions (id, repository_id, tag, state, total_snippets, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`
|
||||
)
|
||||
.run('/test/repo/v2.0', repoId, 'v2.0', 'indexed', 0, NOW_S);
|
||||
|
||||
// Create embedding profile
|
||||
client
|
||||
.prepare(
|
||||
`INSERT INTO embedding_profiles (id, provider_kind, title, enabled, is_default, model, dimensions, config, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
)
|
||||
.run('test-profile', 'local-transformers', 'Test', 1, 1, 'test-model', 4, '{}', NOW_S, NOW_S);
|
||||
|
||||
// Snippet A in version 1.0
|
||||
const snippetA = seedSnippet(client, {
|
||||
repositoryId: repoId,
|
||||
documentId: docId,
|
||||
content: 'version 1 text'
|
||||
});
|
||||
client
|
||||
.prepare('UPDATE snippets SET version_id = ? WHERE id = ?')
|
||||
.run('/test/repo/v1.0', snippetA);
|
||||
|
||||
// Seed embedding for snippetA
|
||||
const embedA = [0.1, 0.2, 0.3, 0.4];
|
||||
const f32A = new Float32Array(embedA);
|
||||
client
|
||||
.prepare(
|
||||
`INSERT INTO snippet_embeddings (snippet_id, profile_id, model, dimensions, embedding, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`
|
||||
)
|
||||
.run(snippetA, 'test-profile', 'test-model', 4, Buffer.from(f32A.buffer), NOW_S);
|
||||
|
||||
// Snippet B in version 2.0
|
||||
const snippetB = seedSnippet(client, {
|
||||
repositoryId: repoId,
|
||||
documentId: docId,
|
||||
content: 'version 2 text'
|
||||
});
|
||||
client
|
||||
.prepare('UPDATE snippets SET version_id = ? WHERE id = ?')
|
||||
.run('/test/repo/v2.0', snippetB);
|
||||
|
||||
// Seed embedding for snippetB
|
||||
const embedB = [0.2, 0.3, 0.4, 0.5];
|
||||
const f32B = new Float32Array(embedB);
|
||||
client
|
||||
.prepare(
|
||||
`INSERT INTO snippet_embeddings (snippet_id, profile_id, model, dimensions, embedding, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`
|
||||
)
|
||||
.run(snippetB, 'test-profile', 'test-model', 4, Buffer.from(f32B.buffer), NOW_S);
|
||||
|
||||
const vs = new VectorSearch(client);
|
||||
const query = new Float32Array([0.1, 0.2, 0.3, 0.4]);
|
||||
|
||||
// Query with versionId v1.0 should only return snippetA
|
||||
const resultsV1 = vs.vectorSearch(query, {
|
||||
repositoryId: repoId,
|
||||
versionId: '/test/repo/v1.0',
|
||||
profileId: 'test-profile'
|
||||
});
|
||||
expect(resultsV1.map((r) => r.snippetId)).toContain(snippetA);
|
||||
expect(resultsV1.map((r) => r.snippetId)).not.toContain(snippetB);
|
||||
|
||||
// Query with versionId v2.0 should only return snippetB
|
||||
const resultsV2 = vs.vectorSearch(query, {
|
||||
repositoryId: repoId,
|
||||
versionId: '/test/repo/v2.0',
|
||||
profileId: 'test-profile'
|
||||
});
|
||||
expect(resultsV2.map((r) => r.snippetId)).not.toContain(snippetA);
|
||||
expect(resultsV2.map((r) => r.snippetId)).toContain(snippetB);
|
||||
|
||||
// Query without versionId should return both
|
||||
const resultsAll = vs.vectorSearch(query, {
|
||||
repositoryId: repoId,
|
||||
profileId: 'test-profile'
|
||||
});
|
||||
expect(resultsAll.map((r) => r.snippetId)).toContain(snippetA);
|
||||
expect(resultsAll.map((r) => r.snippetId)).toContain(snippetB);
|
||||
});
|
||||
|
||||
it('searchMode=keyword never calls provider.embed()', async () => {
|
||||
const client = createTestDb();
|
||||
const repoId = seedRepo(client);
|
||||
const docId = seedDocument(client, repoId);
|
||||
|
||||
const snippetId = seedSnippet(client, {
|
||||
repositoryId: repoId,
|
||||
documentId: docId,
|
||||
content: 'keyword only test'
|
||||
});
|
||||
|
||||
client.exec(
|
||||
`INSERT INTO snippets_fts (id, repository_id, version_id, title, breadcrumb, content)
|
||||
VALUES ('${snippetId}', '${repoId}', NULL, NULL, NULL, 'keyword only test')`
|
||||
);
|
||||
|
||||
let embedCalled = false;
|
||||
const mockProvider: EmbeddingProvider = {
|
||||
name: 'mock',
|
||||
dimensions: 4,
|
||||
model: 'test-model',
|
||||
async embed() {
|
||||
embedCalled = true;
|
||||
return [];
|
||||
},
|
||||
async isAvailable() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
const searchService = new SearchService(client);
|
||||
const hybridService = new HybridSearchService(client, searchService, mockProvider);
|
||||
|
||||
const results = await hybridService.search('keyword', {
|
||||
repositoryId: repoId,
|
||||
searchMode: 'keyword'
|
||||
});
|
||||
|
||||
expect(embedCalled).toBe(false);
|
||||
expect(results.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('searchMode=semantic uses only vector search', async () => {
|
||||
const client = createTestDb();
|
||||
const repoId = seedRepo(client);
|
||||
const docId = seedDocument(client, repoId);
|
||||
|
||||
// Create profile
|
||||
client
|
||||
.prepare(
|
||||
`INSERT INTO embedding_profiles (id, provider_kind, title, enabled, is_default, model, dimensions, config, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
)
|
||||
.run('test-profile', 'local-transformers', 'Test', 1, 1, 'test-model', 4, '{}', NOW_S, NOW_S);
|
||||
|
||||
const snippetId = seedSnippet(client, {
|
||||
repositoryId: repoId,
|
||||
documentId: docId,
|
||||
content: 'semantic test'
|
||||
});
|
||||
|
||||
// Seed embedding
|
||||
const embed = [0.5, 0.5, 0.5, 0.5];
|
||||
const f32 = new Float32Array(embed);
|
||||
client
|
||||
.prepare(
|
||||
`INSERT INTO snippet_embeddings (snippet_id, profile_id, model, dimensions, embedding, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`
|
||||
)
|
||||
.run(snippetId, 'test-profile', 'test-model', 4, Buffer.from(f32.buffer), NOW_S);
|
||||
|
||||
const mockProvider: EmbeddingProvider = {
|
||||
name: 'mock',
|
||||
dimensions: 4,
|
||||
model: 'test-model',
|
||||
async embed() {
|
||||
return [
|
||||
{
|
||||
values: new Float32Array([0.5, 0.5, 0.5, 0.5]),
|
||||
dimensions: 4,
|
||||
model: 'test-model'
|
||||
}
|
||||
];
|
||||
},
|
||||
async isAvailable() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
const searchService = new SearchService(client);
|
||||
const hybridService = new HybridSearchService(client, searchService, mockProvider);
|
||||
|
||||
const results = await hybridService.search('semantic', {
|
||||
repositoryId: repoId,
|
||||
searchMode: 'semantic',
|
||||
profileId: 'test-profile'
|
||||
});
|
||||
|
||||
// Should return results (alpha=1 pure vector mode)
|
||||
expect(results.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -36,6 +36,16 @@ export interface HybridSearchOptions {
|
||||
* Default: 0.5.
|
||||
*/
|
||||
alpha?: number;
|
||||
/**
|
||||
* Search mode: 'auto' (default), 'keyword', 'semantic', or 'hybrid'.
|
||||
* Overrides alpha when set to 'keyword' (forces 0) or 'semantic' (forces 1).
|
||||
*/
|
||||
searchMode?: 'auto' | 'keyword' | 'semantic' | 'hybrid';
|
||||
/**
|
||||
* Embedding profile ID for vector search.
|
||||
* Default: 'local-default'.
|
||||
*/
|
||||
profileId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,7 +100,24 @@ export class HybridSearchService {
|
||||
options: HybridSearchOptions
|
||||
): Promise<SnippetSearchResult[]> {
|
||||
const limit = options.limit ?? 20;
|
||||
const alpha = options.alpha ?? 0.5;
|
||||
const mode = options.searchMode ?? 'auto';
|
||||
|
||||
// Resolve alpha from searchMode
|
||||
let alpha: number;
|
||||
switch (mode) {
|
||||
case 'keyword':
|
||||
alpha = 0;
|
||||
break;
|
||||
case 'semantic':
|
||||
alpha = 1;
|
||||
break;
|
||||
case 'hybrid':
|
||||
alpha = options.alpha ?? 0.5;
|
||||
break;
|
||||
default:
|
||||
// 'auto'
|
||||
alpha = options.alpha ?? 0.5;
|
||||
}
|
||||
|
||||
// Always run FTS5 — it is synchronous and fast.
|
||||
const ftsResults = this.searchService.searchSnippets(query, {
|
||||
@@ -115,11 +142,12 @@ export class HybridSearchService {
|
||||
|
||||
const queryEmbedding = embeddings[0].values;
|
||||
|
||||
const vectorResults = this.vectorSearch.vectorSearch(
|
||||
queryEmbedding,
|
||||
options.repositoryId,
|
||||
limit * 3
|
||||
);
|
||||
const vectorResults = this.vectorSearch.vectorSearch(queryEmbedding, {
|
||||
repositoryId: options.repositoryId,
|
||||
versionId: options.versionId,
|
||||
profileId: options.profileId,
|
||||
limit: limit * 3
|
||||
});
|
||||
|
||||
// Pure vector mode: skip RRF and return vector results directly.
|
||||
if (alpha === 1) {
|
||||
|
||||
@@ -21,6 +21,13 @@ export interface VectorSearchResult {
|
||||
score: number;
|
||||
}
|
||||
|
||||
export interface VectorSearchOptions {
|
||||
repositoryId: string;
|
||||
versionId?: string;
|
||||
profileId?: string;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
/** Raw DB row from snippet_embeddings joined with snippets. */
|
||||
interface RawEmbeddingRow {
|
||||
snippet_id: string;
|
||||
@@ -64,32 +71,33 @@ export function cosineSimilarity(a: Float32Array, b: Float32Array): number {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export class VectorSearch {
|
||||
private readonly stmt: Database.Statement<[string], RawEmbeddingRow>;
|
||||
|
||||
constructor(private readonly db: Database.Database) {
|
||||
// Prepare once — reused for every call.
|
||||
this.stmt = this.db.prepare<[string], RawEmbeddingRow>(`
|
||||
SELECT se.snippet_id, se.embedding
|
||||
FROM snippet_embeddings se
|
||||
JOIN snippets s ON s.id = se.snippet_id
|
||||
WHERE s.repository_id = ?
|
||||
`);
|
||||
}
|
||||
constructor(private readonly db: Database.Database) {}
|
||||
|
||||
/**
|
||||
* Search stored embeddings by cosine similarity to the query embedding.
|
||||
*
|
||||
* @param queryEmbedding - The embedded representation of the search query.
|
||||
* @param repositoryId - Scope the search to a single repository.
|
||||
* @param limit - Maximum number of results to return. Default: 50.
|
||||
* @param options - Search options including repositoryId, optional versionId, profileId, and limit.
|
||||
* @returns Results sorted by descending cosine similarity score.
|
||||
*/
|
||||
vectorSearch(
|
||||
queryEmbedding: Float32Array,
|
||||
repositoryId: string,
|
||||
limit = 50
|
||||
): VectorSearchResult[] {
|
||||
const rows = this.stmt.all(repositoryId);
|
||||
vectorSearch(queryEmbedding: Float32Array, options: VectorSearchOptions): VectorSearchResult[] {
|
||||
const { repositoryId, versionId, profileId = 'local-default', limit = 50 } = options;
|
||||
|
||||
let sql = `
|
||||
SELECT se.snippet_id, se.embedding
|
||||
FROM snippet_embeddings se
|
||||
JOIN snippets s ON s.id = se.snippet_id
|
||||
WHERE s.repository_id = ?
|
||||
AND se.profile_id = ?
|
||||
`;
|
||||
const params: unknown[] = [repositoryId, profileId];
|
||||
|
||||
if (versionId) {
|
||||
sql += ' AND s.version_id = ?';
|
||||
params.push(versionId);
|
||||
}
|
||||
|
||||
const rows = this.db.prepare<unknown[], RawEmbeddingRow>(sql).all(...params);
|
||||
|
||||
const scored: VectorSearchResult[] = rows.map((row) => {
|
||||
const embedding = new Float32Array(
|
||||
|
||||
@@ -42,6 +42,8 @@ export async function fetchContext(params: {
|
||||
query: string;
|
||||
tokens?: number;
|
||||
type?: 'json' | 'txt';
|
||||
searchMode?: string;
|
||||
alpha?: number;
|
||||
}): Promise<ApiResponse> {
|
||||
const url = new URL(`${API_BASE}/api/v1/context`);
|
||||
url.searchParams.set('libraryId', params.libraryId);
|
||||
@@ -50,6 +52,12 @@ export async function fetchContext(params: {
|
||||
if (params.tokens !== undefined) {
|
||||
url.searchParams.set('tokens', String(params.tokens));
|
||||
}
|
||||
if (params.searchMode) {
|
||||
url.searchParams.set('searchMode', params.searchMode);
|
||||
}
|
||||
if (params.alpha !== undefined) {
|
||||
url.searchParams.set('alpha', String(params.alpha));
|
||||
}
|
||||
|
||||
return fetch(url.toString());
|
||||
}
|
||||
|
||||
@@ -15,7 +15,19 @@ export const QueryDocsSchema = z.object({
|
||||
query: z
|
||||
.string()
|
||||
.describe('Specific question about the library to retrieve relevant documentation'),
|
||||
tokens: z.number().optional().describe('Maximum token budget for the response (default: 10000)')
|
||||
tokens: z.number().optional().describe('Maximum token budget for the response (default: 10000)'),
|
||||
searchMode: z
|
||||
.enum(['auto', 'keyword', 'semantic', 'hybrid'])
|
||||
.optional()
|
||||
.describe(
|
||||
"Retrieval mode: 'auto' (default), 'keyword' (FTS only), 'semantic' (vector only), or 'hybrid'"
|
||||
),
|
||||
alpha: z
|
||||
.number()
|
||||
.min(0)
|
||||
.max(1)
|
||||
.optional()
|
||||
.describe('Hybrid blend weight: 0.0 = keyword only, 1.0 = semantic only (default: 0.5)')
|
||||
});
|
||||
|
||||
export type QueryDocsInput = z.infer<typeof QueryDocsSchema>;
|
||||
@@ -42,6 +54,17 @@ export const QUERY_DOCS_TOOL = {
|
||||
tokens: {
|
||||
type: 'number',
|
||||
description: 'Max token budget (default: 10000)'
|
||||
},
|
||||
searchMode: {
|
||||
type: 'string',
|
||||
enum: ['auto', 'keyword', 'semantic', 'hybrid'],
|
||||
description: "Retrieval mode: 'auto' (default), 'keyword', 'semantic', or 'hybrid'"
|
||||
},
|
||||
alpha: {
|
||||
type: 'number',
|
||||
minimum: 0,
|
||||
maximum: 1,
|
||||
description: 'Hybrid blend weight (0=keyword, 1=semantic, default: 0.5)'
|
||||
}
|
||||
},
|
||||
required: ['libraryId', 'query']
|
||||
@@ -49,9 +72,9 @@ export const QUERY_DOCS_TOOL = {
|
||||
};
|
||||
|
||||
export async function handleQueryDocs(args: unknown) {
|
||||
const { libraryId, query, tokens } = QueryDocsSchema.parse(args);
|
||||
const { libraryId, query, tokens, searchMode, alpha } = QueryDocsSchema.parse(args);
|
||||
|
||||
const response = await fetchContext({ libraryId, query, tokens, type: 'txt' });
|
||||
const response = await fetchContext({ libraryId, query, tokens, type: 'txt', searchMode, alpha });
|
||||
|
||||
if (!response.ok) {
|
||||
const status = response.status;
|
||||
|
||||
@@ -16,6 +16,8 @@ import { getClient } from '$lib/server/db/client';
|
||||
import { dtoJsonResponse } from '$lib/server/api/dto-response';
|
||||
import { SearchService } from '$lib/server/search/search.service';
|
||||
import { HybridSearchService } from '$lib/server/search/hybrid.search.service';
|
||||
import { createProviderFromProfile } from '$lib/server/embeddings/registry';
|
||||
import type { EmbeddingProfile } from '$lib/server/db/schema';
|
||||
import { parseLibraryId } from '$lib/server/api/library-id';
|
||||
import { selectSnippetsWithinBudget, DEFAULT_TOKEN_BUDGET } from '$lib/server/api/token-budget';
|
||||
import {
|
||||
@@ -28,12 +30,20 @@ import {
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function getServices() {
|
||||
const db = getClient();
|
||||
function getServices(db: ReturnType<typeof getClient>) {
|
||||
const searchService = new SearchService(db);
|
||||
// No embedding provider — pure FTS5 mode (alpha=0 equivalent).
|
||||
const hybridService = new HybridSearchService(db, searchService, null);
|
||||
return { db, searchService, hybridService };
|
||||
|
||||
// Load the active embedding profile from the database
|
||||
const profileRow = db
|
||||
.prepare<[], EmbeddingProfile>(
|
||||
'SELECT * FROM embedding_profiles WHERE is_default = 1 AND enabled = 1 LIMIT 1'
|
||||
)
|
||||
.get();
|
||||
|
||||
const provider = profileRow ? createProviderFromProfile(profileRow) : null;
|
||||
const hybridService = new HybridSearchService(db, searchService, provider);
|
||||
|
||||
return { db, searchService, hybridService, profileId: profileRow?.id };
|
||||
}
|
||||
|
||||
interface RawRepoConfig {
|
||||
@@ -93,6 +103,14 @@ export const GET: RequestHandler = async ({ url }) => {
|
||||
const tokensRaw = parseInt(url.searchParams.get('tokens') ?? String(DEFAULT_TOKEN_BUDGET), 10);
|
||||
const maxTokens = isNaN(tokensRaw) || tokensRaw < 1 ? DEFAULT_TOKEN_BUDGET : tokensRaw;
|
||||
|
||||
// Parse searchMode and alpha
|
||||
const rawMode = url.searchParams.get('searchMode') ?? 'auto';
|
||||
const searchMode = ['auto', 'keyword', 'semantic', 'hybrid'].includes(rawMode)
|
||||
? (rawMode as 'auto' | 'keyword' | 'semantic' | 'hybrid')
|
||||
: 'auto';
|
||||
const alphaRaw = parseFloat(url.searchParams.get('alpha') ?? '0.5');
|
||||
const alpha = isNaN(alphaRaw) ? 0.5 : Math.max(0, Math.min(1, alphaRaw));
|
||||
|
||||
// Parse the libraryId
|
||||
let parsed: ReturnType<typeof parseLibraryId>;
|
||||
try {
|
||||
@@ -108,7 +126,8 @@ export const GET: RequestHandler = async ({ url }) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const { db, hybridService } = getServices();
|
||||
const db = getClient();
|
||||
const { hybridService, profileId } = getServices(db);
|
||||
|
||||
// Verify the repository exists and check its state.
|
||||
const repo = db
|
||||
@@ -158,7 +177,10 @@ export const GET: RequestHandler = async ({ url }) => {
|
||||
const searchResults = await hybridService.search(query, {
|
||||
repositoryId: parsed.repositoryId,
|
||||
versionId,
|
||||
limit: 50 // fetch more than needed; token budget will trim
|
||||
limit: 50, // fetch more than needed; token budget will trim
|
||||
searchMode,
|
||||
alpha,
|
||||
profileId
|
||||
});
|
||||
|
||||
// Apply token budget.
|
||||
|
||||
@@ -1,147 +1,149 @@
|
||||
/**
|
||||
* GET /api/v1/settings/embedding — retrieve current embedding configuration
|
||||
* PUT /api/v1/settings/embedding — update embedding configuration
|
||||
* GET /api/v1/settings/embedding — retrieve all embedding profiles
|
||||
* POST /api/v1/settings/embedding — create or update an embedding profile
|
||||
* PUT /api/v1/settings/embedding — alias for POST (backward compat)
|
||||
*/
|
||||
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getClient } from '$lib/server/db/client';
|
||||
import {
|
||||
EMBEDDING_CONFIG_KEY,
|
||||
createProviderFromConfig,
|
||||
defaultEmbeddingConfig,
|
||||
type EmbeddingConfig
|
||||
} from '$lib/server/embeddings/factory';
|
||||
import { createProviderFromProfile } from '$lib/server/embeddings/registry';
|
||||
import type { EmbeddingProfile, NewEmbeddingProfile } from '$lib/server/db/schema';
|
||||
import { handleServiceError, InvalidInputError } from '$lib/server/utils/validation';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// GET — Return all profiles
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function readConfig(db: ReturnType<typeof getClient>): EmbeddingConfig {
|
||||
const row = db
|
||||
.prepare(`SELECT value FROM settings WHERE key = ?`)
|
||||
.get(EMBEDDING_CONFIG_KEY) as { value: string } | undefined;
|
||||
|
||||
if (!row) return defaultEmbeddingConfig();
|
||||
|
||||
export const GET: RequestHandler = () => {
|
||||
try {
|
||||
return JSON.parse(row.value) as EmbeddingConfig;
|
||||
} catch {
|
||||
return defaultEmbeddingConfig();
|
||||
}
|
||||
}
|
||||
const db = getClient();
|
||||
const profiles = db
|
||||
.prepare('SELECT * FROM embedding_profiles ORDER BY is_default DESC, created_at ASC')
|
||||
.all() as EmbeddingProfile[];
|
||||
|
||||
function validateConfig(body: unknown): EmbeddingConfig {
|
||||
// Sanitize: remove sensitive config fields like apiKey
|
||||
const safeProfiles = profiles.map(sanitizeProfile);
|
||||
return json({ profiles: safeProfiles });
|
||||
} catch (err) {
|
||||
return handleServiceError(err);
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// POST/PUT — Create or update a profile
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function upsertProfile(body: unknown) {
|
||||
if (typeof body !== 'object' || body === null) {
|
||||
throw new InvalidInputError('Request body must be a JSON object');
|
||||
}
|
||||
|
||||
const obj = body as Record<string, unknown>;
|
||||
|
||||
const provider = obj.provider;
|
||||
if (provider !== 'openai' && provider !== 'local' && provider !== 'none') {
|
||||
// Required fields
|
||||
if (typeof obj.id !== 'string' || !obj.id) {
|
||||
throw new InvalidInputError('id is required');
|
||||
}
|
||||
if (typeof obj.providerKind !== 'string' || !obj.providerKind) {
|
||||
throw new InvalidInputError('providerKind is required');
|
||||
}
|
||||
if (typeof obj.title !== 'string' || !obj.title) {
|
||||
throw new InvalidInputError('title is required');
|
||||
}
|
||||
if (typeof obj.model !== 'string' || !obj.model) {
|
||||
throw new InvalidInputError('model is required');
|
||||
}
|
||||
if (typeof obj.dimensions !== 'number') {
|
||||
throw new InvalidInputError('dimensions must be a number');
|
||||
}
|
||||
|
||||
const profile: NewEmbeddingProfile = {
|
||||
id: obj.id,
|
||||
providerKind: obj.providerKind,
|
||||
title: obj.title,
|
||||
enabled: typeof obj.enabled === 'boolean' ? obj.enabled : true,
|
||||
isDefault: typeof obj.isDefault === 'boolean' ? obj.isDefault : false,
|
||||
model: obj.model,
|
||||
dimensions: obj.dimensions,
|
||||
config: (obj.config as Record<string, unknown>) ?? {},
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now()
|
||||
};
|
||||
|
||||
// Validate provider availability before persisting
|
||||
const provider = createProviderFromProfile(profile as EmbeddingProfile);
|
||||
const available = await provider.isAvailable();
|
||||
if (!available) {
|
||||
throw new InvalidInputError(
|
||||
`Invalid provider "${String(provider)}". Must be one of: openai, local, none.`
|
||||
`Could not connect to the "${profile.providerKind}" provider. Check your configuration.`
|
||||
);
|
||||
}
|
||||
|
||||
if (provider === 'openai') {
|
||||
const openai = obj.openai as Record<string, unknown> | undefined;
|
||||
if (!openai || typeof openai !== 'object') {
|
||||
throw new InvalidInputError('openai config object is required when provider is "openai"');
|
||||
}
|
||||
if (typeof openai.baseUrl !== 'string' || !openai.baseUrl) {
|
||||
throw new InvalidInputError('openai.baseUrl must be a non-empty string');
|
||||
}
|
||||
if (typeof openai.apiKey !== 'string' || !openai.apiKey) {
|
||||
throw new InvalidInputError('openai.apiKey must be a non-empty string');
|
||||
}
|
||||
if (typeof openai.model !== 'string' || !openai.model) {
|
||||
throw new InvalidInputError('openai.model must be a non-empty string');
|
||||
}
|
||||
const db = getClient();
|
||||
|
||||
const config: EmbeddingConfig = {
|
||||
provider: 'openai',
|
||||
openai: {
|
||||
baseUrl: openai.baseUrl as string,
|
||||
apiKey: openai.apiKey as string,
|
||||
model: openai.model as string,
|
||||
dimensions:
|
||||
typeof openai.dimensions === 'number' ? (openai.dimensions as number) : undefined,
|
||||
maxBatchSize:
|
||||
typeof openai.maxBatchSize === 'number'
|
||||
? (openai.maxBatchSize as number)
|
||||
: undefined
|
||||
}
|
||||
};
|
||||
return config;
|
||||
// If setting as default, clear other defaults first
|
||||
if (profile.isDefault) {
|
||||
db.prepare('UPDATE embedding_profiles SET is_default = 0').run();
|
||||
}
|
||||
|
||||
return { provider: provider as 'local' | 'none' };
|
||||
// Upsert the profile
|
||||
db.prepare(
|
||||
`INSERT INTO embedding_profiles
|
||||
(id, provider_kind, title, enabled, is_default, model, dimensions, config, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
provider_kind = excluded.provider_kind,
|
||||
title = excluded.title,
|
||||
enabled = excluded.enabled,
|
||||
is_default = excluded.is_default,
|
||||
model = excluded.model,
|
||||
dimensions = excluded.dimensions,
|
||||
config = excluded.config,
|
||||
updated_at = excluded.updated_at`
|
||||
).run(
|
||||
profile.id,
|
||||
profile.providerKind,
|
||||
profile.title,
|
||||
profile.enabled ? 1 : 0,
|
||||
profile.isDefault ? 1 : 0,
|
||||
profile.model,
|
||||
profile.dimensions,
|
||||
JSON.stringify(profile.config),
|
||||
profile.createdAt,
|
||||
profile.updatedAt
|
||||
);
|
||||
|
||||
const inserted = db
|
||||
.prepare('SELECT * FROM embedding_profiles WHERE id = ?')
|
||||
.get(profile.id) as EmbeddingProfile;
|
||||
|
||||
return sanitizeProfile(inserted);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GET
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const GET: RequestHandler = () => {
|
||||
try {
|
||||
const db = getClient();
|
||||
const config = readConfig(db);
|
||||
|
||||
// Strip the apiKey from the response for security.
|
||||
const safeConfig = sanitizeForResponse(config);
|
||||
return json(safeConfig);
|
||||
} catch (err) {
|
||||
return handleServiceError(err);
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// PUT
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const PUT: RequestHandler = async ({ request }) => {
|
||||
export const POST: RequestHandler = async ({ request }) => {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const config = validateConfig(body);
|
||||
|
||||
// Verify provider connectivity before persisting (skip for noop).
|
||||
if (config.provider !== 'none') {
|
||||
const provider = createProviderFromConfig(config);
|
||||
const available = await provider.isAvailable();
|
||||
if (!available) {
|
||||
throw new InvalidInputError(
|
||||
`Could not connect to the "${config.provider}" embedding provider. Check your configuration.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const db = getClient();
|
||||
db.prepare(
|
||||
`INSERT INTO settings (key, value, updated_at)
|
||||
VALUES (?, ?, unixepoch())
|
||||
ON CONFLICT (key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`
|
||||
).run(EMBEDDING_CONFIG_KEY, JSON.stringify(config));
|
||||
|
||||
const safeConfig = sanitizeForResponse(config);
|
||||
return json(safeConfig);
|
||||
const profile = await upsertProfile(body);
|
||||
return json(profile);
|
||||
} catch (err) {
|
||||
return handleServiceError(err);
|
||||
}
|
||||
};
|
||||
|
||||
// Backward compat alias
|
||||
export const PUT: RequestHandler = POST;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Sanitize — remove sensitive fields before returning to clients
|
||||
// Sanitize — remove sensitive config fields before returning to clients
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function sanitizeForResponse(config: EmbeddingConfig): Omit<EmbeddingConfig, 'openai'> & {
|
||||
openai?: Omit<NonNullable<EmbeddingConfig['openai']>, 'apiKey'>;
|
||||
} {
|
||||
if (config.provider === 'openai' && config.openai) {
|
||||
const { apiKey: _apiKey, ...rest } = config.openai;
|
||||
return { ...config, openai: rest };
|
||||
function sanitizeProfile(profile: EmbeddingProfile): EmbeddingProfile {
|
||||
const config = profile.config as Record<string, unknown>;
|
||||
if (config && config.apiKey) {
|
||||
const { apiKey: _apiKey, ...rest } = config;
|
||||
return { ...profile, config: rest };
|
||||
}
|
||||
return config;
|
||||
return profile;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,82 +1,47 @@
|
||||
/**
|
||||
* POST /api/v1/settings/embedding/test
|
||||
* GET /api/v1/settings/embedding/test
|
||||
*
|
||||
* Validates an embedding provider configuration by creating a provider
|
||||
* instance and calling embed(['test']). Returns success with dimensions
|
||||
* or a descriptive error without persisting any changes.
|
||||
* Tests the active default embedding profile by creating a provider instance
|
||||
* and checking availability. Returns success with profile metadata or error.
|
||||
*/
|
||||
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import {
|
||||
createProviderFromConfig,
|
||||
type EmbeddingConfig
|
||||
} from '$lib/server/embeddings/factory';
|
||||
import { handleServiceError, InvalidInputError } from '$lib/server/utils/validation';
|
||||
import { getClient } from '$lib/server/db/client';
|
||||
import { createProviderFromProfile } from '$lib/server/embeddings/registry';
|
||||
import type { EmbeddingProfile } from '$lib/server/db/schema';
|
||||
import { handleServiceError } from '$lib/server/utils/validation';
|
||||
|
||||
export const GET: RequestHandler = async () => {
|
||||
try {
|
||||
const provider = createProviderFromConfig({ provider: 'local' });
|
||||
const db = getClient();
|
||||
const profile = db
|
||||
.prepare<[], EmbeddingProfile>(
|
||||
'SELECT * FROM embedding_profiles WHERE is_default = 1 AND enabled = 1 LIMIT 1'
|
||||
)
|
||||
.get();
|
||||
|
||||
if (!profile) {
|
||||
return json({ available: false, error: 'No active embedding profile configured' });
|
||||
}
|
||||
|
||||
const provider = createProviderFromProfile(profile);
|
||||
const available = await provider.isAvailable();
|
||||
return json({ available });
|
||||
|
||||
return json({
|
||||
available,
|
||||
profile: {
|
||||
id: profile.id,
|
||||
providerKind: profile.providerKind,
|
||||
model: profile.model,
|
||||
dimensions: profile.dimensions
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
return handleServiceError(err);
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Validate — reuse the same shape accepted by PUT /settings/embedding
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function validateConfig(body: unknown): EmbeddingConfig {
|
||||
if (typeof body !== 'object' || body === null) {
|
||||
throw new InvalidInputError('Request body must be a JSON object');
|
||||
}
|
||||
|
||||
const obj = body as Record<string, unknown>;
|
||||
|
||||
const provider = obj.provider;
|
||||
if (provider !== 'openai' && provider !== 'local' && provider !== 'none') {
|
||||
throw new InvalidInputError(
|
||||
`Invalid provider "${String(provider)}". Must be one of: openai, local, none.`
|
||||
);
|
||||
}
|
||||
|
||||
if (provider === 'openai') {
|
||||
const openai = obj.openai as Record<string, unknown> | undefined;
|
||||
if (!openai || typeof openai !== 'object') {
|
||||
throw new InvalidInputError('openai config object is required when provider is "openai"');
|
||||
}
|
||||
if (typeof openai.baseUrl !== 'string' || !openai.baseUrl) {
|
||||
throw new InvalidInputError('openai.baseUrl must be a non-empty string');
|
||||
}
|
||||
if (typeof openai.apiKey !== 'string' || !openai.apiKey) {
|
||||
throw new InvalidInputError('openai.apiKey must be a non-empty string');
|
||||
}
|
||||
if (typeof openai.model !== 'string' || !openai.model) {
|
||||
throw new InvalidInputError('openai.model must be a non-empty string');
|
||||
}
|
||||
|
||||
return {
|
||||
provider: 'openai',
|
||||
openai: {
|
||||
baseUrl: openai.baseUrl as string,
|
||||
apiKey: openai.apiKey as string,
|
||||
model: openai.model as string,
|
||||
dimensions:
|
||||
typeof openai.dimensions === 'number' ? (openai.dimensions as number) : undefined,
|
||||
maxBatchSize:
|
||||
typeof openai.maxBatchSize === 'number' ? (openai.maxBatchSize as number) : undefined
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return { provider: provider as 'local' | 'none' };
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// POST
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const POST: RequestHandler = async ({ request }) => {
|
||||
try {
|
||||
|
||||
632
test-output.txt
Normal file
632
test-output.txt
Normal file
@@ -0,0 +1,632 @@
|
||||
|
||||
> trueref@0.0.1 test:unit
|
||||
> vitest
|
||||
|
||||
|
||||
[1m[44m DEV [49m[22m [34mv4.1.0 [39m[90m/home/moze/Sources/trueref[39m
|
||||
|
||||
[2m19:10:26[22m [36m[1m[vite][22m[39m (client) Re-optimizing dependencies because lockfile has changed
|
||||
[31m❯[39m [30m[42m server [49m[39m src/lib/server/embeddings/embedding.service.test.ts [2m([22m[2m0 test[22m[2m)[22m
|
||||
[32m✓[39m [30m[42m server [49m[39m src/lib/server/parser/code.parser.test.ts [2m([22m[2m20 tests[22m[2m)[22m[32m 22[2mms[22m[39m
|
||||
[32m✓[39m [30m[42m server [49m[39m src/lib/server/services/version.service.test.ts [2m([22m[2m19 tests[22m[2m)[22m[32m 37[2mms[22m[39m
|
||||
[32m✓[39m [30m[42m server [49m[39m src/lib/server/services/repository.service.test.ts [2m([22m[2m37 tests[22m[2m)[22m[32m 57[2mms[22m[39m
|
||||
[90mstderr[2m | src/lib/server/crawler/local.crawler.test.ts[2m > [22m[2mLocalCrawler.crawl() — config file detection[2m > [22m[2mgracefully handles a malformed config file
|
||||
[22m[39m[LocalCrawler] Failed to parse config file: /tmp/trueref-test-ptITIP/trueref.json
|
||||
|
||||
[32m✓[39m [30m[42m server [49m[39m src/lib/server/config/config-parser.test.ts [2m([22m[2m50 tests[22m[2m)[22m[32m 21[2mms[22m[39m
|
||||
[90mstderr[2m | src/lib/server/pipeline/indexing.pipeline.test.ts[2m > [22m[2mIndexingPipeline[2m > [22m[2mmarks job as failed and repo as error when pipeline throws
|
||||
[22m[39m[IndexingPipeline] Job c44d7e22-6127-49e7-82b7-eb724726c888 failed: crawl failed
|
||||
|
||||
[90mstderr[2m | src/lib/server/pipeline/indexing.pipeline.test.ts
|
||||
[22m[39m[JobQueue] No pipeline configured — cannot process jobs.
|
||||
|
||||
[90mstderr[2m | src/lib/server/pipeline/indexing.pipeline.test.ts
|
||||
[22m[39m[JobQueue] No pipeline configured — cannot process jobs.
|
||||
|
||||
[90mstderr[2m | src/lib/server/pipeline/indexing.pipeline.test.ts
|
||||
[22m[39m[JobQueue] No pipeline configured — cannot process jobs.
|
||||
|
||||
[32m✓[39m [30m[42m server [49m[39m src/lib/server/search/search.service.test.ts [2m([22m[2m43 tests[22m[2m)[22m[32m 43[2mms[22m[39m
|
||||
[32m✓[39m [30m[42m server [49m[39m src/lib/server/pipeline/indexing.pipeline.test.ts [2m([22m[2m20 tests[22m[2m)[22m[32m 42[2mms[22m[39m
|
||||
[32m✓[39m [30m[42m server [49m[39m src/lib/server/crawler/gitignore-parser.test.ts [2m([22m[2m29 tests[22m[2m)[22m[32m 11[2mms[22m[39m
|
||||
[32m✓[39m [30m[42m server [49m[39m src/lib/server/crawler/github-tags.test.ts [2m([22m[2m10 tests[22m[2m)[22m[32m 9[2mms[22m[39m
|
||||
[32m✓[39m [30m[42m server [49m[39m src/routes/api/v1/api-contract.integration.test.ts [2m([22m[2m4 tests[22m[2m)[22m[32m 48[2mms[22m[39m
|
||||
[31m❯[39m [30m[42m server [49m[39m src/lib/server/db/schema.test.ts [2m([22m[2m19 tests[22m[2m | [22m[31m19 failed[39m[2m)[22m[32m 50[2mms[22m[39m
|
||||
[31m [31m×[31m inserts and retrieves a repository[39m[32m 12[2mms[22m[39m
|
||||
[31m [31m×[31m allows nullable optional fields[39m[32m 3[2mms[22m[39m
|
||||
[31m [31m×[31m supports all state enum values[39m[32m 2[2mms[22m[39m
|
||||
[31m [31m×[31m inserts a version linked to a repository[39m[32m 4[2mms[22m[39m
|
||||
[31m [31m×[31m cascades delete when parent repository is deleted[39m[32m 2[2mms[22m[39m
|
||||
[31m [31m×[31m inserts a document[39m[32m 1[2mms[22m[39m
|
||||
[31m [31m×[31m cascades delete when repository is deleted[39m[32m 2[2mms[22m[39m
|
||||
[31m [31m×[31m inserts a code snippet[39m[32m 2[2mms[22m[39m
|
||||
[31m [31m×[31m inserts an info snippet[39m[32m 2[2mms[22m[39m
|
||||
[31m [31m×[31m cascades delete when document is deleted[39m[32m 2[2mms[22m[39m
|
||||
[31m [31m×[31m stores a Float32Array embedding as blob[39m[32m 2[2mms[22m[39m
|
||||
[31m [31m×[31m cascades delete when snippet is deleted[39m[32m 2[2mms[22m[39m
|
||||
[31m [31m×[31m creates a job with default queued status[39m[32m 2[2mms[22m[39m
|
||||
[31m [31m×[31m supports all status enum values[39m[32m 2[2mms[22m[39m
|
||||
[31m [31m×[31m stores JSON array fields correctly[39m[32m 2[2mms[22m[39m
|
||||
[31m [31m×[31m stores and retrieves key-value settings[39m[32m 2[2mms[22m[39m
|
||||
[31m [31m×[31m FTS table exists and is queryable[39m[32m 1[2mms[22m[39m
|
||||
[31m [31m×[31m insert trigger keeps FTS in sync[39m[32m 2[2mms[22m[39m
|
||||
[31m [31m×[31m delete trigger removes entry from FTS[39m[32m 2[2mms[22m[39m
|
||||
[31m❯[39m [30m[42m server [49m[39m src/lib/server/search/hybrid.search.service.test.ts [2m([22m[2m33 tests[22m[2m | [22m[31m16 failed[39m[2m)[22m[32m 52[2mms[22m[39m
|
||||
[32m✓[39m returns 1.0 for identical vectors[32m 2[2mms[22m[39m
|
||||
[32m✓[39m returns 0.0 for orthogonal vectors[32m 0[2mms[22m[39m
|
||||
[32m✓[39m returns -1.0 for opposite vectors[32m 0[2mms[22m[39m
|
||||
[32m✓[39m returns 0 for zero-magnitude vector[32m 0[2mms[22m[39m
|
||||
[32m✓[39m throws when dimensions do not match[32m 1[2mms[22m[39m
|
||||
[32m✓[39m computes correct similarity for non-trivial vectors[32m 0[2mms[22m[39m
|
||||
[32m✓[39m returns empty array for empty inputs[32m 1[2mms[22m[39m
|
||||
[32m✓[39m fuses a single list preserving order[32m 1[2mms[22m[39m
|
||||
[32m✓[39m deduplicates items appearing in multiple lists[32m 0[2mms[22m[39m
|
||||
[32m✓[39m boosts items appearing in multiple lists[32m 0[2mms[22m[39m
|
||||
[32m✓[39m assigns higher rrfScore to higher-ranked items[32m 0[2mms[22m[39m
|
||||
[32m✓[39m handles three lists correctly[32m 0[2mms[22m[39m
|
||||
[32m✓[39m produces positive rrfScores[32m 0[2mms[22m[39m
|
||||
[31m [31m×[31m returns empty array when no embeddings exist[39m[32m 10[2mms[22m[39m
|
||||
[31m [31m×[31m returns results sorted by descending cosine similarity[39m[32m 2[2mms[22m[39m
|
||||
[31m [31m×[31m respects the limit parameter[39m[32m 4[2mms[22m[39m
|
||||
[31m [31m×[31m only returns snippets from the specified repository[39m[32m 2[2mms[22m[39m
|
||||
[31m [31m×[31m handles embeddings with negative values[39m[32m 1[2mms[22m[39m
|
||||
[32m✓[39m returns FTS5 results when embeddingProvider is null[32m 2[2mms[22m[39m
|
||||
[32m✓[39m returns FTS5 results when alpha = 0[32m 1[2mms[22m[39m
|
||||
[32m✓[39m returns empty array when FTS5 query is blank and no provider[32m 1[2mms[22m[39m
|
||||
[32m✓[39m falls back to FTS5 when noop provider returns empty embeddings[32m 2[2mms[22m[39m
|
||||
[31m [31m×[31m returns results when hybrid mode is active (alpha = 0.5)[39m[32m 1[2mms[22m[39m
|
||||
[31m [31m×[31m deduplicates snippets appearing in both FTS5 and vector results[39m[32m 1[2mms[22m[39m
|
||||
[31m [31m×[31m respects the limit option[39m[32m 1[2mms[22m[39m
|
||||
[31m [31m×[31m returns vector-ranked results when alpha = 1[39m[32m 1[2mms[22m[39m
|
||||
[31m [31m×[31m results include snippet and repository metadata[39m[32m 1[2mms[22m[39m
|
||||
[31m [31m×[31m all results belong to the requested repository[39m[32m 1[2mms[22m[39m
|
||||
[31m [31m×[31m filters by snippet type when provided[39m[32m 1[2mms[22m[39m
|
||||
[31m [31m×[31m uses alpha = 0.5 when not specified[39m[32m 1[2mms[22m[39m
|
||||
[31m [31m×[31m filters by versionId — excludes snippets from other versions[39m[32m 3[2mms[22m[39m
|
||||
[31m [31m×[31m searchMode=keyword never calls provider.embed()[39m[32m 3[2mms[22m[39m
|
||||
[31m [31m×[31m searchMode=semantic uses only vector search[39m[32m 2[2mms[22m[39m
|
||||
[32m✓[39m [30m[42m server [49m[39m src/lib/server/api/formatters.test.ts [2m([22m[2m20 tests[22m[2m)[22m[32m 9[2mms[22m[39m
|
||||
[32m✓[39m [30m[42m server [49m[39m src/lib/server/pipeline/diff.test.ts [2m([22m[2m9 tests[22m[2m)[22m[32m 8[2mms[22m[39m
|
||||
[32m✓[39m [30m[42m server [49m[39m src/lib/server/api/library-id.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m 6[2mms[22m[39m
|
||||
[32m✓[39m [30m[42m server [49m[39m src/lib/server/api/token-budget.test.ts [2m([22m[2m7 tests[22m[2m)[22m[32m 6[2mms[22m[39m
|
||||
[32m✓[39m [30m[42m server [49m[39m src/lib/server/parser/markdown.parser.test.ts [2m([22m[2m14 tests[22m[2m)[22m[32m 9[2mms[22m[39m
|
||||
[32m✓[39m [30m[42m server [49m[39m src/lib/vitest-examples/greet.spec.ts [2m([22m[2m1 test[22m[2m)[22m[32m 3[2mms[22m[39m
|
||||
[32m✓[39m [30m[42m server [49m[39m src/lib/server/crawler/local.crawler.test.ts [2m([22m[2m50 tests[22m[2m)[22m[33m 658[2mms[22m[39m
|
||||
[32m✓[39m [30m[42m server [49m[39m src/mcp/index.test.ts [2m([22m[2m7 tests[22m[2m)[22m[33m 985[2mms[22m[39m
|
||||
[32m✓[39m [30m[43m client (chromium) [49m[39m src/lib/vitest-examples/Welcome.svelte.spec.ts [2m([22m[2m1 test[22m[2m)[22m[32m 9[2mms[22m[39m
|
||||
[90mstderr[2m | src/lib/server/crawler/github.crawler.test.ts[2m > [22m[2mcrawl()[2m > [22m[2mskips files that fail to download without throwing
|
||||
[22m[39m[GitHubCrawler] Could not download: src/index.ts — skipping.
|
||||
|
||||
[32m✓[39m [30m[42m server [49m[39m src/lib/server/crawler/github.crawler.test.ts [2m([22m[2m50 tests[22m[2m)[22m[33m 6082[2mms[22m[39m
|
||||
[33m[2m✓[22m[39m retries on failure and returns eventual success [33m 3003[2mms[22m[39m
|
||||
[33m[2m✓[22m[39m throws after exhausting all attempts [33m 3003[2mms[22m[39m
|
||||
|
||||
[31m⎯⎯⎯⎯⎯⎯[39m[1m[41m Failed Suites 1 [49m[22m[31m⎯⎯⎯⎯⎯⎯⎯[39m
|
||||
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/embeddings/embedding.service.test.ts[2m [ src/lib/server/embeddings/embedding.service.test.ts ][22m
|
||||
[31m[1mError[22m: Transform failed with 1 error:
|
||||
/home/moze/Sources/trueref/src/lib/server/embeddings/embedding.service.test.ts:408:2: ERROR: "await" can only be used inside an "async" function[39m
|
||||
Plugin: [35mvite:esbuild[39m
|
||||
File: [36m/home/moze/Sources/trueref/src/lib/server/embeddings/embedding.service.test.ts[39m:408:2
|
||||
[33m
|
||||
"await" can only be used inside an "async" function
|
||||
406 | });
|
||||
407 |
|
||||
408 | await service.embedSnippets([snippetId]);
|
||||
| ^
|
||||
409 |
|
||||
410 | const retrieved = service.getEmbedding(snippetId);
|
||||
[39m
|
||||
[90m [2m❯[22m failureErrorWithLog node_modules/vite/node_modules/esbuild/lib/main.js:[2m1748:15[22m[39m
|
||||
[90m [2m❯[22m node_modules/vite/node_modules/esbuild/lib/main.js:[2m1017:50[22m[39m
|
||||
[90m [2m❯[22m responseCallbacks.<computed> node_modules/vite/node_modules/esbuild/lib/main.js:[2m884:9[22m[39m
|
||||
[90m [2m❯[22m handleIncomingPacket node_modules/vite/node_modules/esbuild/lib/main.js:[2m939:12[22m[39m
|
||||
[90m [2m❯[22m Socket.readFromStdout node_modules/vite/node_modules/esbuild/lib/main.js:[2m862:7[22m[39m
|
||||
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/36]⎯[22m[39m
|
||||
|
||||
|
||||
[31m⎯⎯⎯⎯⎯⎯[39m[1m[41m Failed Tests 35 [49m[22m[31m⎯⎯⎯⎯⎯⎯⎯[39m
|
||||
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/db/schema.test.ts[2m > [22mrepositories table[2m > [22minserts and retrieves a repository
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/db/schema.test.ts[2m > [22mrepositories table[2m > [22mallows nullable optional fields
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/db/schema.test.ts[2m > [22mrepositories table[2m > [22msupports all state enum values
|
||||
[31m[1mDrizzleError[22m: Failed to run the query '
|
||||
INSERT INTO `__new_snippet_embeddings`("snippet_id", "profile_id", "model", "dimensions", "embedding", "created_at") SELECT "snippet_id", "profile_id", "model", "dimensions", "embedding", "created_at" FROM `snippet_embeddings`;'[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.run node_modules/src/sqlite-core/session.ts:[2m271:9[22m[39m
|
||||
[90m [2m❯[22m SQLiteSyncDialect.migrate node_modules/src/sqlite-core/dialect.ts:[2m864:14[22m[39m
|
||||
[90m [2m❯[22m migrate node_modules/src/better-sqlite3/migrator.ts:[2m10:12[22m[39m
|
||||
[36m [2m❯[22m createTestDb src/lib/server/db/schema.test.ts:[2m32:2[22m[39m
|
||||
[90m 30|[39m [90m// Run migrations from the generated migration folder.[39m
|
||||
[90m 31|[39m [35mconst[39m migrationsFolder [33m=[39m [34mjoin[39m([35mimport[39m[33m.[39mmeta[33m.[39mdirname[33m,[39m [32m'migrations'[39m)[33m;[39m
|
||||
[90m 32|[39m [34mmigrate[39m(db[33m,[39m { migrationsFolder })[33m;[39m
|
||||
[90m |[39m [31m^[39m
|
||||
[90m 33|[39m
|
||||
[90m 34|[39m // Apply FTS5 DDL using exec() which handles multi-statement SQL with…
|
||||
[90m [2m❯[22m src/lib/server/db/schema.test.ts:[2m63:13[22m[39m
|
||||
|
||||
[31m[1mCaused by: SqliteError[22m: no such column: "profile_id" - should this be a string literal in single-quotes?[39m
|
||||
[90m [2m❯[22m Database.prepare node_modules/better-sqlite3/lib/methods/wrappers.js:[2m5:21[22m[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.prepareQuery node_modules/drizzle-orm/better-sqlite3/session.js:[2m23:30[22m[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.prepareOneTimeQuery node_modules/drizzle-orm/sqlite-core/session.js:[2m141:17[22m[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.run node_modules/drizzle-orm/sqlite-core/session.js:[2m154:19[22m[39m
|
||||
[90m [2m❯[22m SQLiteSyncDialect.migrate node_modules/drizzle-orm/sqlite-core/dialect.js:[2m604:21[22m[39m
|
||||
[90m [2m❯[22m migrate node_modules/drizzle-orm/better-sqlite3/migrator.js:[2m4:14[22m[39m
|
||||
[36m [2m❯[22m createTestDb src/lib/server/db/schema.test.ts:[2m32:2[22m[39m
|
||||
[90m [2m❯[22m src/lib/server/db/schema.test.ts:[2m63:13[22m[39m
|
||||
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[22m[39m
|
||||
[31m[1mSerialized Error:[22m[39m [90m{ code: 'SQLITE_ERROR' }[39m
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/36]⎯[22m[39m
|
||||
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/db/schema.test.ts[2m > [22mrepository_versions table[2m > [22minserts a version linked to a repository
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/db/schema.test.ts[2m > [22mrepository_versions table[2m > [22mcascades delete when parent repository is deleted
|
||||
[31m[1mDrizzleError[22m: Failed to run the query '
|
||||
INSERT INTO `__new_snippet_embeddings`("snippet_id", "profile_id", "model", "dimensions", "embedding", "created_at") SELECT "snippet_id", "profile_id", "model", "dimensions", "embedding", "created_at" FROM `snippet_embeddings`;'[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.run node_modules/src/sqlite-core/session.ts:[2m271:9[22m[39m
|
||||
[90m [2m❯[22m SQLiteSyncDialect.migrate node_modules/src/sqlite-core/dialect.ts:[2m864:14[22m[39m
|
||||
[90m [2m❯[22m migrate node_modules/src/better-sqlite3/migrator.ts:[2m10:12[22m[39m
|
||||
[36m [2m❯[22m createTestDb src/lib/server/db/schema.test.ts:[2m32:2[22m[39m
|
||||
[90m 30|[39m [90m// Run migrations from the generated migration folder.[39m
|
||||
[90m 31|[39m [35mconst[39m migrationsFolder [33m=[39m [34mjoin[39m([35mimport[39m[33m.[39mmeta[33m.[39mdirname[33m,[39m [32m'migrations'[39m)[33m;[39m
|
||||
[90m 32|[39m [34mmigrate[39m(db[33m,[39m { migrationsFolder })[33m;[39m
|
||||
[90m |[39m [31m^[39m
|
||||
[90m 33|[39m
|
||||
[90m 34|[39m // Apply FTS5 DDL using exec() which handles multi-statement SQL with…
|
||||
[90m [2m❯[22m src/lib/server/db/schema.test.ts:[2m109:13[22m[39m
|
||||
|
||||
[31m[1mCaused by: SqliteError[22m: no such column: "profile_id" - should this be a string literal in single-quotes?[39m
|
||||
[90m [2m❯[22m Database.prepare node_modules/better-sqlite3/lib/methods/wrappers.js:[2m5:21[22m[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.prepareQuery node_modules/drizzle-orm/better-sqlite3/session.js:[2m23:30[22m[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.prepareOneTimeQuery node_modules/drizzle-orm/sqlite-core/session.js:[2m141:17[22m[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.run node_modules/drizzle-orm/sqlite-core/session.js:[2m154:19[22m[39m
|
||||
[90m [2m❯[22m SQLiteSyncDialect.migrate node_modules/drizzle-orm/sqlite-core/dialect.js:[2m604:21[22m[39m
|
||||
[90m [2m❯[22m migrate node_modules/drizzle-orm/better-sqlite3/migrator.js:[2m4:14[22m[39m
|
||||
[36m [2m❯[22m createTestDb src/lib/server/db/schema.test.ts:[2m32:2[22m[39m
|
||||
[90m [2m❯[22m src/lib/server/db/schema.test.ts:[2m109:13[22m[39m
|
||||
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[22m[39m
|
||||
[31m[1mSerialized Error:[22m[39m [90m{ code: 'SQLITE_ERROR' }[39m
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/36]⎯[22m[39m
|
||||
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/db/schema.test.ts[2m > [22mdocuments table[2m > [22minserts a document
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/db/schema.test.ts[2m > [22mdocuments table[2m > [22mcascades delete when repository is deleted
|
||||
[31m[1mDrizzleError[22m: Failed to run the query '
|
||||
INSERT INTO `__new_snippet_embeddings`("snippet_id", "profile_id", "model", "dimensions", "embedding", "created_at") SELECT "snippet_id", "profile_id", "model", "dimensions", "embedding", "created_at" FROM `snippet_embeddings`;'[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.run node_modules/src/sqlite-core/session.ts:[2m271:9[22m[39m
|
||||
[90m [2m❯[22m SQLiteSyncDialect.migrate node_modules/src/sqlite-core/dialect.ts:[2m864:14[22m[39m
|
||||
[90m [2m❯[22m migrate node_modules/src/better-sqlite3/migrator.ts:[2m10:12[22m[39m
|
||||
[36m [2m❯[22m createTestDb src/lib/server/db/schema.test.ts:[2m32:2[22m[39m
|
||||
[90m 30|[39m [90m// Run migrations from the generated migration folder.[39m
|
||||
[90m 31|[39m [35mconst[39m migrationsFolder [33m=[39m [34mjoin[39m([35mimport[39m[33m.[39mmeta[33m.[39mdirname[33m,[39m [32m'migrations'[39m)[33m;[39m
|
||||
[90m 32|[39m [34mmigrate[39m(db[33m,[39m { migrationsFolder })[33m;[39m
|
||||
[90m |[39m [31m^[39m
|
||||
[90m 33|[39m
|
||||
[90m 34|[39m // Apply FTS5 DDL using exec() which handles multi-statement SQL with…
|
||||
[90m [2m❯[22m src/lib/server/db/schema.test.ts:[2m151:13[22m[39m
|
||||
|
||||
[31m[1mCaused by: SqliteError[22m: no such column: "profile_id" - should this be a string literal in single-quotes?[39m
|
||||
[90m [2m❯[22m Database.prepare node_modules/better-sqlite3/lib/methods/wrappers.js:[2m5:21[22m[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.prepareQuery node_modules/drizzle-orm/better-sqlite3/session.js:[2m23:30[22m[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.prepareOneTimeQuery node_modules/drizzle-orm/sqlite-core/session.js:[2m141:17[22m[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.run node_modules/drizzle-orm/sqlite-core/session.js:[2m154:19[22m[39m
|
||||
[90m [2m❯[22m SQLiteSyncDialect.migrate node_modules/drizzle-orm/sqlite-core/dialect.js:[2m604:21[22m[39m
|
||||
[90m [2m❯[22m migrate node_modules/drizzle-orm/better-sqlite3/migrator.js:[2m4:14[22m[39m
|
||||
[36m [2m❯[22m createTestDb src/lib/server/db/schema.test.ts:[2m32:2[22m[39m
|
||||
[90m [2m❯[22m src/lib/server/db/schema.test.ts:[2m151:13[22m[39m
|
||||
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[22m[39m
|
||||
[31m[1mSerialized Error:[22m[39m [90m{ code: 'SQLITE_ERROR' }[39m
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[4/36]⎯[22m[39m
|
||||
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/db/schema.test.ts[2m > [22msnippets table[2m > [22minserts a code snippet
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/db/schema.test.ts[2m > [22msnippets table[2m > [22minserts an info snippet
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/db/schema.test.ts[2m > [22msnippets table[2m > [22mcascades delete when document is deleted
|
||||
[31m[1mDrizzleError[22m: Failed to run the query '
|
||||
INSERT INTO `__new_snippet_embeddings`("snippet_id", "profile_id", "model", "dimensions", "embedding", "created_at") SELECT "snippet_id", "profile_id", "model", "dimensions", "embedding", "created_at" FROM `snippet_embeddings`;'[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.run node_modules/src/sqlite-core/session.ts:[2m271:9[22m[39m
|
||||
[90m [2m❯[22m SQLiteSyncDialect.migrate node_modules/src/sqlite-core/dialect.ts:[2m864:14[22m[39m
|
||||
[90m [2m❯[22m migrate node_modules/src/better-sqlite3/migrator.ts:[2m10:12[22m[39m
|
||||
[36m [2m❯[22m createTestDb src/lib/server/db/schema.test.ts:[2m32:2[22m[39m
|
||||
[90m 30|[39m [90m// Run migrations from the generated migration folder.[39m
|
||||
[90m 31|[39m [35mconst[39m migrationsFolder [33m=[39m [34mjoin[39m([35mimport[39m[33m.[39mmeta[33m.[39mdirname[33m,[39m [32m'migrations'[39m)[33m;[39m
|
||||
[90m 32|[39m [34mmigrate[39m(db[33m,[39m { migrationsFolder })[33m;[39m
|
||||
[90m |[39m [31m^[39m
|
||||
[90m 33|[39m
|
||||
[90m 34|[39m // Apply FTS5 DDL using exec() which handles multi-statement SQL with…
|
||||
[90m [2m❯[22m src/lib/server/db/schema.test.ts:[2m195:13[22m[39m
|
||||
|
||||
[31m[1mCaused by: SqliteError[22m: no such column: "profile_id" - should this be a string literal in single-quotes?[39m
|
||||
[90m [2m❯[22m Database.prepare node_modules/better-sqlite3/lib/methods/wrappers.js:[2m5:21[22m[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.prepareQuery node_modules/drizzle-orm/better-sqlite3/session.js:[2m23:30[22m[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.prepareOneTimeQuery node_modules/drizzle-orm/sqlite-core/session.js:[2m141:17[22m[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.run node_modules/drizzle-orm/sqlite-core/session.js:[2m154:19[22m[39m
|
||||
[90m [2m❯[22m SQLiteSyncDialect.migrate node_modules/drizzle-orm/sqlite-core/dialect.js:[2m604:21[22m[39m
|
||||
[90m [2m❯[22m migrate node_modules/drizzle-orm/better-sqlite3/migrator.js:[2m4:14[22m[39m
|
||||
[36m [2m❯[22m createTestDb src/lib/server/db/schema.test.ts:[2m32:2[22m[39m
|
||||
[90m [2m❯[22m src/lib/server/db/schema.test.ts:[2m195:13[22m[39m
|
||||
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[22m[39m
|
||||
[31m[1mSerialized Error:[22m[39m [90m{ code: 'SQLITE_ERROR' }[39m
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[5/36]⎯[22m[39m
|
||||
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/db/schema.test.ts[2m > [22msnippet_embeddings table[2m > [22mstores a Float32Array embedding as blob
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/db/schema.test.ts[2m > [22msnippet_embeddings table[2m > [22mcascades delete when snippet is deleted
|
||||
[31m[1mDrizzleError[22m: Failed to run the query '
|
||||
INSERT INTO `__new_snippet_embeddings`("snippet_id", "profile_id", "model", "dimensions", "embedding", "created_at") SELECT "snippet_id", "profile_id", "model", "dimensions", "embedding", "created_at" FROM `snippet_embeddings`;'[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.run node_modules/src/sqlite-core/session.ts:[2m271:9[22m[39m
|
||||
[90m [2m❯[22m SQLiteSyncDialect.migrate node_modules/src/sqlite-core/dialect.ts:[2m864:14[22m[39m
|
||||
[90m [2m❯[22m migrate node_modules/src/better-sqlite3/migrator.ts:[2m10:12[22m[39m
|
||||
[36m [2m❯[22m createTestDb src/lib/server/db/schema.test.ts:[2m32:2[22m[39m
|
||||
[90m 30|[39m [90m// Run migrations from the generated migration folder.[39m
|
||||
[90m 31|[39m [35mconst[39m migrationsFolder [33m=[39m [34mjoin[39m([35mimport[39m[33m.[39mmeta[33m.[39mdirname[33m,[39m [32m'migrations'[39m)[33m;[39m
|
||||
[90m 32|[39m [34mmigrate[39m(db[33m,[39m { migrationsFolder })[33m;[39m
|
||||
[90m |[39m [31m^[39m
|
||||
[90m 33|[39m
|
||||
[90m 34|[39m // Apply FTS5 DDL using exec() which handles multi-statement SQL with…
|
||||
[90m [2m❯[22m src/lib/server/db/schema.test.ts:[2m271:13[22m[39m
|
||||
|
||||
[31m[1mCaused by: SqliteError[22m: no such column: "profile_id" - should this be a string literal in single-quotes?[39m
|
||||
[90m [2m❯[22m Database.prepare node_modules/better-sqlite3/lib/methods/wrappers.js:[2m5:21[22m[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.prepareQuery node_modules/drizzle-orm/better-sqlite3/session.js:[2m23:30[22m[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.prepareOneTimeQuery node_modules/drizzle-orm/sqlite-core/session.js:[2m141:17[22m[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.run node_modules/drizzle-orm/sqlite-core/session.js:[2m154:19[22m[39m
|
||||
[90m [2m❯[22m SQLiteSyncDialect.migrate node_modules/drizzle-orm/sqlite-core/dialect.js:[2m604:21[22m[39m
|
||||
[90m [2m❯[22m migrate node_modules/drizzle-orm/better-sqlite3/migrator.js:[2m4:14[22m[39m
|
||||
[36m [2m❯[22m createTestDb src/lib/server/db/schema.test.ts:[2m32:2[22m[39m
|
||||
[90m [2m❯[22m src/lib/server/db/schema.test.ts:[2m271:13[22m[39m
|
||||
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[22m[39m
|
||||
[31m[1mSerialized Error:[22m[39m [90m{ code: 'SQLITE_ERROR' }[39m
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[6/36]⎯[22m[39m
|
||||
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/db/schema.test.ts[2m > [22mindexing_jobs table[2m > [22mcreates a job with default queued status
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/db/schema.test.ts[2m > [22mindexing_jobs table[2m > [22msupports all status enum values
|
||||
[31m[1mDrizzleError[22m: Failed to run the query '
|
||||
INSERT INTO `__new_snippet_embeddings`("snippet_id", "profile_id", "model", "dimensions", "embedding", "created_at") SELECT "snippet_id", "profile_id", "model", "dimensions", "embedding", "created_at" FROM `snippet_embeddings`;'[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.run node_modules/src/sqlite-core/session.ts:[2m271:9[22m[39m
|
||||
[90m [2m❯[22m SQLiteSyncDialect.migrate node_modules/src/sqlite-core/dialect.ts:[2m864:14[22m[39m
|
||||
[90m [2m❯[22m migrate node_modules/src/better-sqlite3/migrator.ts:[2m10:12[22m[39m
|
||||
[36m [2m❯[22m createTestDb src/lib/server/db/schema.test.ts:[2m32:2[22m[39m
|
||||
[90m 30|[39m [90m// Run migrations from the generated migration folder.[39m
|
||||
[90m 31|[39m [35mconst[39m migrationsFolder [33m=[39m [34mjoin[39m([35mimport[39m[33m.[39mmeta[33m.[39mdirname[33m,[39m [32m'migrations'[39m)[33m;[39m
|
||||
[90m 32|[39m [34mmigrate[39m(db[33m,[39m { migrationsFolder })[33m;[39m
|
||||
[90m |[39m [31m^[39m
|
||||
[90m 33|[39m
|
||||
[90m 34|[39m // Apply FTS5 DDL using exec() which handles multi-statement SQL with…
|
||||
[90m [2m❯[22m src/lib/server/db/schema.test.ts:[2m350:13[22m[39m
|
||||
|
||||
[31m[1mCaused by: SqliteError[22m: no such column: "profile_id" - should this be a string literal in single-quotes?[39m
|
||||
[90m [2m❯[22m Database.prepare node_modules/better-sqlite3/lib/methods/wrappers.js:[2m5:21[22m[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.prepareQuery node_modules/drizzle-orm/better-sqlite3/session.js:[2m23:30[22m[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.prepareOneTimeQuery node_modules/drizzle-orm/sqlite-core/session.js:[2m141:17[22m[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.run node_modules/drizzle-orm/sqlite-core/session.js:[2m154:19[22m[39m
|
||||
[90m [2m❯[22m SQLiteSyncDialect.migrate node_modules/drizzle-orm/sqlite-core/dialect.js:[2m604:21[22m[39m
|
||||
[90m [2m❯[22m migrate node_modules/drizzle-orm/better-sqlite3/migrator.js:[2m4:14[22m[39m
|
||||
[36m [2m❯[22m createTestDb src/lib/server/db/schema.test.ts:[2m32:2[22m[39m
|
||||
[90m [2m❯[22m src/lib/server/db/schema.test.ts:[2m350:13[22m[39m
|
||||
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[22m[39m
|
||||
[31m[1mSerialized Error:[22m[39m [90m{ code: 'SQLITE_ERROR' }[39m
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[7/36]⎯[22m[39m
|
||||
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/db/schema.test.ts[2m > [22mrepository_configs table[2m > [22mstores JSON array fields correctly
|
||||
[31m[1mDrizzleError[22m: Failed to run the query '
|
||||
INSERT INTO `__new_snippet_embeddings`("snippet_id", "profile_id", "model", "dimensions", "embedding", "created_at") SELECT "snippet_id", "profile_id", "model", "dimensions", "embedding", "created_at" FROM `snippet_embeddings`;'[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.run node_modules/src/sqlite-core/session.ts:[2m271:9[22m[39m
|
||||
[90m [2m❯[22m SQLiteSyncDialect.migrate node_modules/src/sqlite-core/dialect.ts:[2m864:14[22m[39m
|
||||
[90m [2m❯[22m migrate node_modules/src/better-sqlite3/migrator.ts:[2m10:12[22m[39m
|
||||
[36m [2m❯[22m createTestDb src/lib/server/db/schema.test.ts:[2m32:2[22m[39m
|
||||
[90m 30|[39m [90m// Run migrations from the generated migration folder.[39m
|
||||
[90m 31|[39m [35mconst[39m migrationsFolder [33m=[39m [34mjoin[39m([35mimport[39m[33m.[39mmeta[33m.[39mdirname[33m,[39m [32m'migrations'[39m)[33m;[39m
|
||||
[90m 32|[39m [34mmigrate[39m(db[33m,[39m { migrationsFolder })[33m;[39m
|
||||
[90m |[39m [31m^[39m
|
||||
[90m 33|[39m
|
||||
[90m 34|[39m // Apply FTS5 DDL using exec() which handles multi-statement SQL with…
|
||||
[90m [2m❯[22m src/lib/server/db/schema.test.ts:[2m391:13[22m[39m
|
||||
|
||||
[31m[1mCaused by: SqliteError[22m: no such column: "profile_id" - should this be a string literal in single-quotes?[39m
|
||||
[90m [2m❯[22m Database.prepare node_modules/better-sqlite3/lib/methods/wrappers.js:[2m5:21[22m[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.prepareQuery node_modules/drizzle-orm/better-sqlite3/session.js:[2m23:30[22m[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.prepareOneTimeQuery node_modules/drizzle-orm/sqlite-core/session.js:[2m141:17[22m[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.run node_modules/drizzle-orm/sqlite-core/session.js:[2m154:19[22m[39m
|
||||
[90m [2m❯[22m SQLiteSyncDialect.migrate node_modules/drizzle-orm/sqlite-core/dialect.js:[2m604:21[22m[39m
|
||||
[90m [2m❯[22m migrate node_modules/drizzle-orm/better-sqlite3/migrator.js:[2m4:14[22m[39m
|
||||
[36m [2m❯[22m createTestDb src/lib/server/db/schema.test.ts:[2m32:2[22m[39m
|
||||
[90m [2m❯[22m src/lib/server/db/schema.test.ts:[2m391:13[22m[39m
|
||||
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[22m[39m
|
||||
[31m[1mSerialized Error:[22m[39m [90m{ code: 'SQLITE_ERROR' }[39m
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[8/36]⎯[22m[39m
|
||||
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/db/schema.test.ts[2m > [22msettings table[2m > [22mstores and retrieves key-value settings
|
||||
[31m[1mDrizzleError[22m: Failed to run the query '
|
||||
INSERT INTO `__new_snippet_embeddings`("snippet_id", "profile_id", "model", "dimensions", "embedding", "created_at") SELECT "snippet_id", "profile_id", "model", "dimensions", "embedding", "created_at" FROM `snippet_embeddings`;'[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.run node_modules/src/sqlite-core/session.ts:[2m271:9[22m[39m
|
||||
[90m [2m❯[22m SQLiteSyncDialect.migrate node_modules/src/sqlite-core/dialect.ts:[2m864:14[22m[39m
|
||||
[90m [2m❯[22m migrate node_modules/src/better-sqlite3/migrator.ts:[2m10:12[22m[39m
|
||||
[36m [2m❯[22m createTestDb src/lib/server/db/schema.test.ts:[2m32:2[22m[39m
|
||||
[90m 30|[39m [90m// Run migrations from the generated migration folder.[39m
|
||||
[90m 31|[39m [35mconst[39m migrationsFolder [33m=[39m [34mjoin[39m([35mimport[39m[33m.[39mmeta[33m.[39mdirname[33m,[39m [32m'migrations'[39m)[33m;[39m
|
||||
[90m 32|[39m [34mmigrate[39m(db[33m,[39m { migrationsFolder })[33m;[39m
|
||||
[90m |[39m [31m^[39m
|
||||
[90m 33|[39m
|
||||
[90m 34|[39m // Apply FTS5 DDL using exec() which handles multi-statement SQL with…
|
||||
[90m [2m❯[22m src/lib/server/db/schema.test.ts:[2m422:13[22m[39m
|
||||
|
||||
[31m[1mCaused by: SqliteError[22m: no such column: "profile_id" - should this be a string literal in single-quotes?[39m
|
||||
[90m [2m❯[22m Database.prepare node_modules/better-sqlite3/lib/methods/wrappers.js:[2m5:21[22m[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.prepareQuery node_modules/drizzle-orm/better-sqlite3/session.js:[2m23:30[22m[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.prepareOneTimeQuery node_modules/drizzle-orm/sqlite-core/session.js:[2m141:17[22m[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.run node_modules/drizzle-orm/sqlite-core/session.js:[2m154:19[22m[39m
|
||||
[90m [2m❯[22m SQLiteSyncDialect.migrate node_modules/drizzle-orm/sqlite-core/dialect.js:[2m604:21[22m[39m
|
||||
[90m [2m❯[22m migrate node_modules/drizzle-orm/better-sqlite3/migrator.js:[2m4:14[22m[39m
|
||||
[36m [2m❯[22m createTestDb src/lib/server/db/schema.test.ts:[2m32:2[22m[39m
|
||||
[90m [2m❯[22m src/lib/server/db/schema.test.ts:[2m422:13[22m[39m
|
||||
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[22m[39m
|
||||
[31m[1mSerialized Error:[22m[39m [90m{ code: 'SQLITE_ERROR' }[39m
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[9/36]⎯[22m[39m
|
||||
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/db/schema.test.ts[2m > [22mFTS5 virtual table (snippets_fts)[2m > [22mFTS table exists and is queryable
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/db/schema.test.ts[2m > [22mFTS5 virtual table (snippets_fts)[2m > [22minsert trigger keeps FTS in sync
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/db/schema.test.ts[2m > [22mFTS5 virtual table (snippets_fts)[2m > [22mdelete trigger removes entry from FTS
|
||||
[31m[1mDrizzleError[22m: Failed to run the query '
|
||||
INSERT INTO `__new_snippet_embeddings`("snippet_id", "profile_id", "model", "dimensions", "embedding", "created_at") SELECT "snippet_id", "profile_id", "model", "dimensions", "embedding", "created_at" FROM `snippet_embeddings`;'[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.run node_modules/src/sqlite-core/session.ts:[2m271:9[22m[39m
|
||||
[90m [2m❯[22m SQLiteSyncDialect.migrate node_modules/src/sqlite-core/dialect.ts:[2m864:14[22m[39m
|
||||
[90m [2m❯[22m migrate node_modules/src/better-sqlite3/migrator.ts:[2m10:12[22m[39m
|
||||
[36m [2m❯[22m createTestDb src/lib/server/db/schema.test.ts:[2m32:2[22m[39m
|
||||
[90m 30|[39m [90m// Run migrations from the generated migration folder.[39m
|
||||
[90m 31|[39m [35mconst[39m migrationsFolder [33m=[39m [34mjoin[39m([35mimport[39m[33m.[39mmeta[33m.[39mdirname[33m,[39m [32m'migrations'[39m)[33m;[39m
|
||||
[90m 32|[39m [34mmigrate[39m(db[33m,[39m { migrationsFolder })[33m;[39m
|
||||
[90m |[39m [31m^[39m
|
||||
[90m 33|[39m
|
||||
[90m 34|[39m // Apply FTS5 DDL using exec() which handles multi-statement SQL with…
|
||||
[90m [2m❯[22m src/lib/server/db/schema.test.ts:[2m442:21[22m[39m
|
||||
|
||||
[31m[1mCaused by: SqliteError[22m: no such column: "profile_id" - should this be a string literal in single-quotes?[39m
|
||||
[90m [2m❯[22m Database.prepare node_modules/better-sqlite3/lib/methods/wrappers.js:[2m5:21[22m[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.prepareQuery node_modules/drizzle-orm/better-sqlite3/session.js:[2m23:30[22m[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.prepareOneTimeQuery node_modules/drizzle-orm/sqlite-core/session.js:[2m141:17[22m[39m
|
||||
[90m [2m❯[22m BetterSQLiteSession.run node_modules/drizzle-orm/sqlite-core/session.js:[2m154:19[22m[39m
|
||||
[90m [2m❯[22m SQLiteSyncDialect.migrate node_modules/drizzle-orm/sqlite-core/dialect.js:[2m604:21[22m[39m
|
||||
[90m [2m❯[22m migrate node_modules/drizzle-orm/better-sqlite3/migrator.js:[2m4:14[22m[39m
|
||||
[36m [2m❯[22m createTestDb src/lib/server/db/schema.test.ts:[2m32:2[22m[39m
|
||||
[90m [2m❯[22m src/lib/server/db/schema.test.ts:[2m442:21[22m[39m
|
||||
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[22m[39m
|
||||
[31m[1mSerialized Error:[22m[39m [90m{ code: 'SQLITE_ERROR' }[39m
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[10/36]⎯[22m[39m
|
||||
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/search/hybrid.search.service.test.ts[2m > [22mVectorSearch[2m > [22mreturns empty array when no embeddings exist
|
||||
[31m[1mSqliteError[22m: no such column: se.profile_id[39m
|
||||
[90m [2m❯[22m Database.prepare node_modules/better-sqlite3/lib/methods/wrappers.js:[2m5:21[22m[39m
|
||||
[36m [2m❯[22m VectorSearch.vectorSearch src/lib/server/search/vector.search.ts:[2m100:24[22m[39m
|
||||
[90m 98|[39m }
|
||||
[90m 99|[39m
|
||||
[90m100|[39m const rows = this.db.prepare<unknown[], RawEmbeddingRow>(sql).all(..…
|
||||
[90m |[39m [31m^[39m
|
||||
[90m101|[39m
|
||||
[90m102|[39m [35mconst[39m scored[33m:[39m [33mVectorSearchResult[39m[] [33m=[39m rows[33m.[39m[34mmap[39m((row) [33m=>[39m {
|
||||
[90m [2m❯[22m src/lib/server/search/hybrid.search.service.test.ts:[2m289:22[22m[39m
|
||||
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[11/36]⎯[22m[39m
|
||||
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/search/hybrid.search.service.test.ts[2m > [22mVectorSearch[2m > [22mreturns results sorted by descending cosine similarity
|
||||
[31m[1mSqliteError[22m: table snippet_embeddings has no column named profile_id[39m
|
||||
[90m [2m❯[22m Database.prepare node_modules/better-sqlite3/lib/methods/wrappers.js:[2m5:21[22m[39m
|
||||
[36m [2m❯[22m seedEmbedding src/lib/server/search/hybrid.search.service.test.ts:[2m112:4[22m[39m
|
||||
[90m110|[39m [35mconst[39m f32 [33m=[39m [35mnew[39m [33mFloat32Array[39m(values)[33m;[39m
|
||||
[90m111|[39m client
|
||||
[90m112|[39m [33m.[39m[34mprepare[39m(
|
||||
[90m |[39m [31m^[39m
|
||||
[90m113|[39m [32m`INSERT OR REPLACE INTO snippet_embeddings
|
||||
[90m114|[39m (snippet_id, profile_id, model, dimensions, embedding, create…
|
||||
[90m [2m❯[22m src/lib/server/search/hybrid.search.service.test.ts:[2m302:3[22m[39m
|
||||
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[12/36]⎯[22m[39m
|
||||
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/search/hybrid.search.service.test.ts[2m > [22mVectorSearch[2m > [22mrespects the limit parameter
|
||||
[31m[1mSqliteError[22m: table snippet_embeddings has no column named profile_id[39m
|
||||
[90m [2m❯[22m Database.prepare node_modules/better-sqlite3/lib/methods/wrappers.js:[2m5:21[22m[39m
|
||||
[36m [2m❯[22m seedEmbedding src/lib/server/search/hybrid.search.service.test.ts:[2m112:4[22m[39m
|
||||
[90m110|[39m [35mconst[39m f32 [33m=[39m [35mnew[39m [33mFloat32Array[39m(values)[33m;[39m
|
||||
[90m111|[39m client
|
||||
[90m112|[39m [33m.[39m[34mprepare[39m(
|
||||
[90m |[39m [31m^[39m
|
||||
[90m113|[39m [32m`INSERT OR REPLACE INTO snippet_embeddings
|
||||
[90m114|[39m (snippet_id, profile_id, model, dimensions, embedding, create…
|
||||
[90m [2m❯[22m src/lib/server/search/hybrid.search.service.test.ts:[2m321:4[22m[39m
|
||||
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[13/36]⎯[22m[39m
|
||||
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/search/hybrid.search.service.test.ts[2m > [22mVectorSearch[2m > [22monly returns snippets from the specified repository
|
||||
[31m[1mSqliteError[22m: table snippet_embeddings has no column named profile_id[39m
|
||||
[90m [2m❯[22m Database.prepare node_modules/better-sqlite3/lib/methods/wrappers.js:[2m5:21[22m[39m
|
||||
[36m [2m❯[22m seedEmbedding src/lib/server/search/hybrid.search.service.test.ts:[2m112:4[22m[39m
|
||||
[90m110|[39m [35mconst[39m f32 [33m=[39m [35mnew[39m [33mFloat32Array[39m(values)[33m;[39m
|
||||
[90m111|[39m client
|
||||
[90m112|[39m [33m.[39m[34mprepare[39m(
|
||||
[90m |[39m [31m^[39m
|
||||
[90m113|[39m [32m`INSERT OR REPLACE INTO snippet_embeddings
|
||||
[90m114|[39m (snippet_id, profile_id, model, dimensions, embedding, create…
|
||||
[90m [2m❯[22m src/lib/server/search/hybrid.search.service.test.ts:[2m340:3[22m[39m
|
||||
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[14/36]⎯[22m[39m
|
||||
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/search/hybrid.search.service.test.ts[2m > [22mVectorSearch[2m > [22mhandles embeddings with negative values
|
||||
[31m[1mSqliteError[22m: table snippet_embeddings has no column named profile_id[39m
|
||||
[90m [2m❯[22m Database.prepare node_modules/better-sqlite3/lib/methods/wrappers.js:[2m5:21[22m[39m
|
||||
[36m [2m❯[22m seedEmbedding src/lib/server/search/hybrid.search.service.test.ts:[2m112:4[22m[39m
|
||||
[90m110|[39m [35mconst[39m f32 [33m=[39m [35mnew[39m [33mFloat32Array[39m(values)[33m;[39m
|
||||
[90m111|[39m client
|
||||
[90m112|[39m [33m.[39m[34mprepare[39m(
|
||||
[90m |[39m [31m^[39m
|
||||
[90m113|[39m [32m`INSERT OR REPLACE INTO snippet_embeddings
|
||||
[90m114|[39m (snippet_id, profile_id, model, dimensions, embedding, create…
|
||||
[90m [2m❯[22m src/lib/server/search/hybrid.search.service.test.ts:[2m352:3[22m[39m
|
||||
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[15/36]⎯[22m[39m
|
||||
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/search/hybrid.search.service.test.ts[2m > [22mHybridSearchService[2m > [22mreturns results when hybrid mode is active (alpha = 0.5)
|
||||
[31m[1mSqliteError[22m: table snippet_embeddings has no column named profile_id[39m
|
||||
[90m [2m❯[22m Database.prepare node_modules/better-sqlite3/lib/methods/wrappers.js:[2m5:21[22m[39m
|
||||
[36m [2m❯[22m seedEmbedding src/lib/server/search/hybrid.search.service.test.ts:[2m112:4[22m[39m
|
||||
[90m110|[39m [35mconst[39m f32 [33m=[39m [35mnew[39m [33mFloat32Array[39m(values)[33m;[39m
|
||||
[90m111|[39m client
|
||||
[90m112|[39m [33m.[39m[34mprepare[39m(
|
||||
[90m |[39m [31m^[39m
|
||||
[90m113|[39m [32m`INSERT OR REPLACE INTO snippet_embeddings
|
||||
[90m114|[39m (snippet_id, profile_id, model, dimensions, embedding, create…
|
||||
[90m [2m❯[22m src/lib/server/search/hybrid.search.service.test.ts:[2m430:3[22m[39m
|
||||
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[16/36]⎯[22m[39m
|
||||
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/search/hybrid.search.service.test.ts[2m > [22mHybridSearchService[2m > [22mdeduplicates snippets appearing in both FTS5 and vector results
|
||||
[31m[1mSqliteError[22m: table snippet_embeddings has no column named profile_id[39m
|
||||
[90m [2m❯[22m Database.prepare node_modules/better-sqlite3/lib/methods/wrappers.js:[2m5:21[22m[39m
|
||||
[36m [2m❯[22m seedEmbedding src/lib/server/search/hybrid.search.service.test.ts:[2m112:4[22m[39m
|
||||
[90m110|[39m [35mconst[39m f32 [33m=[39m [35mnew[39m [33mFloat32Array[39m(values)[33m;[39m
|
||||
[90m111|[39m client
|
||||
[90m112|[39m [33m.[39m[34mprepare[39m(
|
||||
[90m |[39m [31m^[39m
|
||||
[90m113|[39m [32m`INSERT OR REPLACE INTO snippet_embeddings
|
||||
[90m114|[39m (snippet_id, profile_id, model, dimensions, embedding, create…
|
||||
[90m [2m❯[22m src/lib/server/search/hybrid.search.service.test.ts:[2m449:3[22m[39m
|
||||
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[17/36]⎯[22m[39m
|
||||
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/search/hybrid.search.service.test.ts[2m > [22mHybridSearchService[2m > [22mrespects the limit option
|
||||
[31m[1mSqliteError[22m: table snippet_embeddings has no column named profile_id[39m
|
||||
[90m [2m❯[22m Database.prepare node_modules/better-sqlite3/lib/methods/wrappers.js:[2m5:21[22m[39m
|
||||
[36m [2m❯[22m seedEmbedding src/lib/server/search/hybrid.search.service.test.ts:[2m112:4[22m[39m
|
||||
[90m110|[39m [35mconst[39m f32 [33m=[39m [35mnew[39m [33mFloat32Array[39m(values)[33m;[39m
|
||||
[90m111|[39m client
|
||||
[90m112|[39m [33m.[39m[34mprepare[39m(
|
||||
[90m |[39m [31m^[39m
|
||||
[90m113|[39m [32m`INSERT OR REPLACE INTO snippet_embeddings
|
||||
[90m114|[39m (snippet_id, profile_id, model, dimensions, embedding, create…
|
||||
[90m [2m❯[22m src/lib/server/search/hybrid.search.service.test.ts:[2m471:4[22m[39m
|
||||
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[18/36]⎯[22m[39m
|
||||
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/search/hybrid.search.service.test.ts[2m > [22mHybridSearchService[2m > [22mreturns vector-ranked results when alpha = 1
|
||||
[31m[1mSqliteError[22m: table snippet_embeddings has no column named profile_id[39m
|
||||
[90m [2m❯[22m Database.prepare node_modules/better-sqlite3/lib/methods/wrappers.js:[2m5:21[22m[39m
|
||||
[36m [2m❯[22m seedEmbedding src/lib/server/search/hybrid.search.service.test.ts:[2m112:4[22m[39m
|
||||
[90m110|[39m [35mconst[39m f32 [33m=[39m [35mnew[39m [33mFloat32Array[39m(values)[33m;[39m
|
||||
[90m111|[39m client
|
||||
[90m112|[39m [33m.[39m[34mprepare[39m(
|
||||
[90m |[39m [31m^[39m
|
||||
[90m113|[39m [32m`INSERT OR REPLACE INTO snippet_embeddings
|
||||
[90m114|[39m (snippet_id, profile_id, model, dimensions, embedding, create…
|
||||
[90m [2m❯[22m src/lib/server/search/hybrid.search.service.test.ts:[2m503:3[22m[39m
|
||||
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[19/36]⎯[22m[39m
|
||||
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/search/hybrid.search.service.test.ts[2m > [22mHybridSearchService[2m > [22mresults include snippet and repository metadata
|
||||
[31m[1mSqliteError[22m: table snippet_embeddings has no column named profile_id[39m
|
||||
[90m [2m❯[22m Database.prepare node_modules/better-sqlite3/lib/methods/wrappers.js:[2m5:21[22m[39m
|
||||
[36m [2m❯[22m seedEmbedding src/lib/server/search/hybrid.search.service.test.ts:[2m112:4[22m[39m
|
||||
[90m110|[39m [35mconst[39m f32 [33m=[39m [35mnew[39m [33mFloat32Array[39m(values)[33m;[39m
|
||||
[90m111|[39m client
|
||||
[90m112|[39m [33m.[39m[34mprepare[39m(
|
||||
[90m |[39m [31m^[39m
|
||||
[90m113|[39m [32m`INSERT OR REPLACE INTO snippet_embeddings
|
||||
[90m114|[39m (snippet_id, profile_id, model, dimensions, embedding, create…
|
||||
[90m [2m❯[22m src/lib/server/search/hybrid.search.service.test.ts:[2m528:3[22m[39m
|
||||
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[20/36]⎯[22m[39m
|
||||
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/search/hybrid.search.service.test.ts[2m > [22mHybridSearchService[2m > [22mall results belong to the requested repository
|
||||
[31m[1mSqliteError[22m: table snippet_embeddings has no column named profile_id[39m
|
||||
[90m [2m❯[22m Database.prepare node_modules/better-sqlite3/lib/methods/wrappers.js:[2m5:21[22m[39m
|
||||
[36m [2m❯[22m seedEmbedding src/lib/server/search/hybrid.search.service.test.ts:[2m112:4[22m[39m
|
||||
[90m110|[39m [35mconst[39m f32 [33m=[39m [35mnew[39m [33mFloat32Array[39m(values)[33m;[39m
|
||||
[90m111|[39m client
|
||||
[90m112|[39m [33m.[39m[34mprepare[39m(
|
||||
[90m |[39m [31m^[39m
|
||||
[90m113|[39m [32m`INSERT OR REPLACE INTO snippet_embeddings
|
||||
[90m114|[39m (snippet_id, profile_id, model, dimensions, embedding, create…
|
||||
[90m [2m❯[22m src/lib/server/search/hybrid.search.service.test.ts:[2m556:4[22m[39m
|
||||
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[21/36]⎯[22m[39m
|
||||
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/search/hybrid.search.service.test.ts[2m > [22mHybridSearchService[2m > [22mfilters by snippet type when provided
|
||||
[31m[1mSqliteError[22m: table snippet_embeddings has no column named profile_id[39m
|
||||
[90m [2m❯[22m Database.prepare node_modules/better-sqlite3/lib/methods/wrappers.js:[2m5:21[22m[39m
|
||||
[36m [2m❯[22m seedEmbedding src/lib/server/search/hybrid.search.service.test.ts:[2m112:4[22m[39m
|
||||
[90m110|[39m [35mconst[39m f32 [33m=[39m [35mnew[39m [33mFloat32Array[39m(values)[33m;[39m
|
||||
[90m111|[39m client
|
||||
[90m112|[39m [33m.[39m[34mprepare[39m(
|
||||
[90m |[39m [31m^[39m
|
||||
[90m113|[39m [32m`INSERT OR REPLACE INTO snippet_embeddings
|
||||
[90m114|[39m (snippet_id, profile_id, model, dimensions, embedding, create…
|
||||
[90m [2m❯[22m src/lib/server/search/hybrid.search.service.test.ts:[2m591:3[22m[39m
|
||||
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[22/36]⎯[22m[39m
|
||||
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/search/hybrid.search.service.test.ts[2m > [22mHybridSearchService[2m > [22muses alpha = 0.5 when not specified
|
||||
[31m[1mSqliteError[22m: table snippet_embeddings has no column named profile_id[39m
|
||||
[90m [2m❯[22m Database.prepare node_modules/better-sqlite3/lib/methods/wrappers.js:[2m5:21[22m[39m
|
||||
[36m [2m❯[22m seedEmbedding src/lib/server/search/hybrid.search.service.test.ts:[2m112:4[22m[39m
|
||||
[90m110|[39m [35mconst[39m f32 [33m=[39m [35mnew[39m [33mFloat32Array[39m(values)[33m;[39m
|
||||
[90m111|[39m client
|
||||
[90m112|[39m [33m.[39m[34mprepare[39m(
|
||||
[90m |[39m [31m^[39m
|
||||
[90m113|[39m [32m`INSERT OR REPLACE INTO snippet_embeddings
|
||||
[90m114|[39m (snippet_id, profile_id, model, dimensions, embedding, create…
|
||||
[90m [2m❯[22m src/lib/server/search/hybrid.search.service.test.ts:[2m616:3[22m[39m
|
||||
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[23/36]⎯[22m[39m
|
||||
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/search/hybrid.search.service.test.ts[2m > [22mHybridSearchService[2m > [22mfilters by versionId — excludes snippets from other versions
|
||||
[31m[1mSqliteError[22m: no such table: embedding_profiles[39m
|
||||
[90m [2m❯[22m Database.prepare node_modules/better-sqlite3/lib/methods/wrappers.js:[2m5:21[22m[39m
|
||||
[36m [2m❯[22m src/lib/server/search/hybrid.search.service.test.ts:[2m647:5[22m[39m
|
||||
[90m645|[39m [90m// Create embedding profile[39m
|
||||
[90m646|[39m client
|
||||
[90m647|[39m [33m.[39m[34mprepare[39m(
|
||||
[90m |[39m [31m^[39m
|
||||
[90m648|[39m `INSERT INTO embedding_profiles (id, provider_kind, title, enabled…
|
||||
[90m649|[39m VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`[39m
|
||||
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[24/36]⎯[22m[39m
|
||||
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/search/hybrid.search.service.test.ts[2m > [22mHybridSearchService[2m > [22msearchMode=keyword never calls provider.embed()
|
||||
[31m[1mSqliteError[22m: table snippets_fts has no column named id[39m
|
||||
[90m [2m❯[22m Database.exec node_modules/better-sqlite3/lib/methods/wrappers.js:[2m9:14[22m[39m
|
||||
[36m [2m❯[22m src/lib/server/search/hybrid.search.service.test.ts:[2m734:10[22m[39m
|
||||
[90m732|[39m })[33m;[39m
|
||||
[90m733|[39m
|
||||
[90m734|[39m client[33m.[39m[34mexec[39m(
|
||||
[90m |[39m [31m^[39m
|
||||
[90m735|[39m `INSERT INTO snippets_fts (id, repository_id, version_id, title, br…
|
||||
[90m736|[39m VALUES ('${snippetId}', '${repoId}', NULL, NULL, NULL, 'keyword…
|
||||
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[25/36]⎯[22m[39m
|
||||
|
||||
[41m[1m FAIL [22m[49m [30m[42m server [49m[39m src/lib/server/search/hybrid.search.service.test.ts[2m > [22mHybridSearchService[2m > [22msearchMode=semantic uses only vector search
|
||||
[31m[1mSqliteError[22m: no such table: embedding_profiles[39m
|
||||
[90m [2m❯[22m Database.prepare node_modules/better-sqlite3/lib/methods/wrappers.js:[2m5:21[22m[39m
|
||||
[36m [2m❯[22m src/lib/server/search/hybrid.search.service.test.ts:[2m772:5[22m[39m
|
||||
[90m770|[39m [90m// Create profile[39m
|
||||
[90m771|[39m client
|
||||
[90m772|[39m [33m.[39m[34mprepare[39m(
|
||||
[90m |[39m [31m^[39m
|
||||
[90m773|[39m `INSERT INTO embedding_profiles (id, provider_kind, title, enabled…
|
||||
[90m774|[39m VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`[39m
|
||||
|
||||
[31m[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[26/36]⎯[22m[39m
|
||||
|
||||
|
||||
[2m Test Files [22m [1m[31m3 failed[39m[22m[2m | [22m[1m[32m19 passed[39m[22m[90m (22)[39m
|
||||
[2m Tests [22m [1m[31m35 failed[39m[22m[2m | [22m[1m[32m416 passed[39m[22m[90m (451)[39m
|
||||
[2m Start at [22m 19:10:26
|
||||
[2m Duration [22m 6.93s[2m (transform 7.37s, setup 0ms, import 9.29s, tests 8.17s, environment 11ms)[22m
|
||||
|
||||
[1m[41m FAIL [49m[22m [31mTests failed. Watching for file changes...[39m
|
||||
[2mpress [22m[1mh[22m[2m to show help[22m[2m, [22m[2mpress [22m[1mq[22m[2m to quit[22m
|
||||
[31mCancelling test run. Press CTRL+c again to exit forcefully.
|
||||
[39m
|
||||
Reference in New Issue
Block a user