@folterung/project-memory 0.1.19 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -8
- package/package.json +4 -2
- package/packages/cli/coverage/lcov-report/chunking/chunker.ts.html +81 -81
- package/packages/cli/coverage/lcov-report/chunking/hash.ts.html +12 -12
- package/packages/cli/coverage/lcov-report/chunking/index.html +1 -1
- package/packages/cli/coverage/lcov-report/chunking/types.ts.html +23 -23
- package/packages/cli/coverage/lcov-report/config/index.html +1 -1
- package/packages/cli/coverage/lcov-report/config/load.ts.html +1 -1
- package/packages/cli/coverage/lcov-report/config/types.ts.html +1 -1
- package/packages/cli/coverage/lcov-report/embedding/index.html +1 -1
- package/packages/cli/coverage/lcov-report/embedding/stub.ts.html +1 -1
- package/packages/cli/coverage/lcov-report/index.html +18 -18
- package/packages/cli/coverage/lcov-report/qdrant/index.html +1 -1
- package/packages/cli/coverage/lcov-report/qdrant/upsert.ts.html +1 -1
- package/packages/cli/coverage/lcov-report/scope/allowlist.ts.html +51 -51
- package/packages/cli/coverage/lcov-report/scope/ignore.ts.html +1 -1
- package/packages/cli/coverage/lcov-report/scope/index.html +19 -19
- package/packages/cli/coverage/lcov.info +221 -213
- package/packages/cli/coverage/tmp/{coverage-7460-1770137208481-0.json → coverage-24604-1770174821315-0.json} +1 -1
- package/packages/cli/coverage/tmp/coverage-24605-1770174821362-0.json +1 -0
- package/packages/cli/coverage/tmp/{coverage-7462-1770137208524-0.json → coverage-24606-1770174821361-0.json} +1 -1
- package/packages/cli/coverage/tmp/{coverage-7463-1770137208524-0.json → coverage-24607-1770174821359-0.json} +1 -1
- package/packages/cli/coverage/tmp/{coverage-7464-1770137208531-0.json → coverage-24608-1770174821367-0.json} +1 -1
- package/packages/cli/coverage/tmp/{coverage-7465-1770137208526-0.json → coverage-24609-1770174821359-0.json} +1 -1
- package/packages/cli/coverage/tmp/{coverage-7466-1770137208528-0.json → coverage-24610-1770174821360-0.json} +1 -1
- package/packages/cli/coverage/tmp/{coverage-7467-1770137208579-0.json → coverage-24611-1770174821416-0.json} +1 -1
- package/packages/cli/coverage/tmp/coverage-24612-1770174821392-0.json +1 -0
- package/packages/cli/coverage/tmp/{coverage-7469-1770137208543-0.json → coverage-24613-1770174821380-0.json} +1 -1
- package/packages/cli/dist/chunking/chunker.test.js +19 -0
- package/packages/cli/dist/chunking/chunker.test.js.map +1 -1
- package/packages/cli/dist/cli/index.js +1 -1
- package/packages/cli/dist/cli/index.js.map +1 -1
- package/packages/cli/dist/phase1/pipeline.js +17 -7
- package/packages/cli/dist/phase1/pipeline.js.map +1 -1
- package/packages/cli/dist/phase2/deep-index.js +18 -6
- package/packages/cli/dist/phase2/deep-index.js.map +1 -1
- package/packages/cli/dist/scope/allowlist.test.js +14 -1
- package/packages/cli/dist/scope/allowlist.test.js.map +1 -1
- package/packages/cli/src/chunking/chunker.test.ts +21 -0
- package/packages/cli/src/cli/index.ts +1 -1
- package/packages/cli/src/phase1/pipeline.ts +15 -8
- package/packages/cli/src/phase2/deep-index.ts +15 -6
- package/packages/cli/src/scope/allowlist.test.ts +16 -1
- package/packages/mcp-server/dist/index.js +174 -16
- package/packages/mcp-server/dist/index.js.map +1 -1
- package/packages/mcp-server/dist/system-instructions.md +2 -0
- package/packages/mcp-server/src/index.ts +204 -16
- package/packages/mcp-server/system-instructions.md +2 -0
- package/packages/server/coverage/lcov-report/api/index.html +146 -0
- package/packages/server/coverage/lcov-report/api/points.ts.html +361 -0
- package/packages/server/coverage/lcov-report/api/search.ts.html +436 -0
- package/packages/server/coverage/lcov-report/api/store.ts.html +487 -0
- package/packages/server/coverage/lcov-report/embedding/index.html +116 -0
- package/packages/server/coverage/lcov-report/embedding/stub.ts.html +184 -0
- package/packages/server/coverage/lcov-report/index.html +40 -10
- package/packages/server/coverage/lcov-report/qdrant/client.ts.html +517 -0
- package/packages/server/coverage/lcov-report/qdrant/index.html +116 -0
- package/packages/server/coverage/lcov-report/stub.ts.html +1 -1
- package/packages/server/coverage/lcov.info +674 -27
- package/packages/server/coverage/tmp/{coverage-7549-1770137209315-0.json → coverage-24692-1770174822211-0.json} +1 -1
- package/packages/server/coverage/tmp/coverage-24693-1770174822463-0.json +1 -0
- package/packages/server/coverage/tmp/coverage-24694-1770174822454-0.json +1 -0
- package/packages/server/coverage/tmp/coverage-24695-1770174822463-0.json +1 -0
- package/packages/server/coverage/tmp/{coverage-7550-1770137209349-0.json → coverage-24696-1770174822253-0.json} +1 -1
- package/packages/server/dist/api/points.d.ts +24 -0
- package/packages/server/dist/api/points.js +60 -0
- package/packages/server/dist/api/points.js.map +1 -0
- package/packages/server/dist/api/points.test.d.ts +1 -0
- package/packages/server/dist/api/points.test.js +105 -0
- package/packages/server/dist/api/points.test.js.map +1 -0
- package/packages/server/dist/api/search.d.ts +16 -0
- package/packages/server/dist/api/search.js +26 -8
- package/packages/server/dist/api/search.js.map +1 -1
- package/packages/server/dist/api/search.test.d.ts +1 -0
- package/packages/server/dist/api/search.test.js +102 -0
- package/packages/server/dist/api/search.test.js.map +1 -0
- package/packages/server/dist/api/store.d.ts +7 -0
- package/packages/server/dist/api/store.js +50 -7
- package/packages/server/dist/api/store.js.map +1 -1
- package/packages/server/dist/api/store.test.d.ts +1 -0
- package/packages/server/dist/api/store.test.js +85 -0
- package/packages/server/dist/api/store.test.js.map +1 -0
- package/packages/server/dist/index.js +2 -0
- package/packages/server/dist/index.js.map +1 -1
- package/packages/server/dist/qdrant/client.d.ts +4 -0
- package/packages/server/dist/qdrant/client.js +12 -0
- package/packages/server/dist/qdrant/client.js.map +1 -1
- package/packages/server/package.json +1 -1
- package/packages/server/src/api/points.test.ts +113 -0
- package/packages/server/src/api/points.ts +92 -0
- package/packages/server/src/api/search.test.ts +110 -0
- package/packages/server/src/api/search.ts +59 -13
- package/packages/server/src/api/store.test.ts +91 -0
- package/packages/server/src/api/store.ts +60 -12
- package/packages/server/src/index.ts +2 -0
- package/packages/server/src/qdrant/client.ts +15 -0
- package/packages/cli/coverage/tmp/coverage-7461-1770137208528-0.json +0 -1
- package/packages/cli/coverage/tmp/coverage-7468-1770137208547-0.json +0 -1
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { strict as assert } from "node:assert";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import Fastify from "fastify";
|
|
4
|
+
import { registerStoreRoutes } from "./store.js";
|
|
5
|
+
function createMockStoreClient() {
|
|
6
|
+
return {
|
|
7
|
+
getCollection: async () => {
|
|
8
|
+
throw new Error("not found");
|
|
9
|
+
},
|
|
10
|
+
createCollection: async () => { },
|
|
11
|
+
upsert: async () => { },
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
describe("store API", () => {
|
|
15
|
+
it("returns 400 when content is empty", async () => {
|
|
16
|
+
const app = Fastify();
|
|
17
|
+
registerStoreRoutes(app, { qdrantClient: createMockStoreClient() });
|
|
18
|
+
const res = await app.inject({
|
|
19
|
+
method: "POST",
|
|
20
|
+
url: "/store",
|
|
21
|
+
payload: { content: " " },
|
|
22
|
+
});
|
|
23
|
+
assert.strictEqual(res.statusCode, 400);
|
|
24
|
+
const body = JSON.parse(res.body);
|
|
25
|
+
assert.match(body.message, /content is required/);
|
|
26
|
+
});
|
|
27
|
+
it("returns 400 when startLine is not a positive integer", async () => {
|
|
28
|
+
const app = Fastify();
|
|
29
|
+
registerStoreRoutes(app, { qdrantClient: createMockStoreClient() });
|
|
30
|
+
const res = await app.inject({
|
|
31
|
+
method: "POST",
|
|
32
|
+
url: "/store",
|
|
33
|
+
payload: { content: "note", startLine: 0 },
|
|
34
|
+
});
|
|
35
|
+
assert.strictEqual(res.statusCode, 400);
|
|
36
|
+
assert.match(JSON.parse(res.body).message, /startLine/);
|
|
37
|
+
});
|
|
38
|
+
it("returns 400 when endLine is not a positive integer", async () => {
|
|
39
|
+
const app = Fastify();
|
|
40
|
+
registerStoreRoutes(app, { qdrantClient: createMockStoreClient() });
|
|
41
|
+
const res = await app.inject({
|
|
42
|
+
method: "POST",
|
|
43
|
+
url: "/store",
|
|
44
|
+
payload: { content: "note", endLine: -1 },
|
|
45
|
+
});
|
|
46
|
+
assert.strictEqual(res.statusCode, 400);
|
|
47
|
+
assert.match(JSON.parse(res.body).message, /endLine/);
|
|
48
|
+
});
|
|
49
|
+
it("returns 200 and stores with anchor fields and scope when valid", async () => {
|
|
50
|
+
const app = Fastify();
|
|
51
|
+
registerStoreRoutes(app, { qdrantClient: createMockStoreClient() });
|
|
52
|
+
const res = await app.inject({
|
|
53
|
+
method: "POST",
|
|
54
|
+
url: "/store",
|
|
55
|
+
payload: {
|
|
56
|
+
content: "Summary of auth flow",
|
|
57
|
+
title: "Session summary: auth",
|
|
58
|
+
path: "src/auth.ts",
|
|
59
|
+
startLine: 10,
|
|
60
|
+
endLine: 20,
|
|
61
|
+
symbol: "login",
|
|
62
|
+
scope: "branch:main",
|
|
63
|
+
supersedesId: "old-id-123",
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
assert.strictEqual(res.statusCode, 200);
|
|
67
|
+
const body = JSON.parse(res.body);
|
|
68
|
+
assert.strictEqual(typeof body.id, "string");
|
|
69
|
+
assert.match(body.id, /^agent_note:/);
|
|
70
|
+
assert.strictEqual(body.message, "Stored.");
|
|
71
|
+
});
|
|
72
|
+
it("returns 200 for minimal valid body", async () => {
|
|
73
|
+
const app = Fastify();
|
|
74
|
+
registerStoreRoutes(app, { qdrantClient: createMockStoreClient() });
|
|
75
|
+
const res = await app.inject({
|
|
76
|
+
method: "POST",
|
|
77
|
+
url: "/store",
|
|
78
|
+
payload: { content: "Minimal note" },
|
|
79
|
+
});
|
|
80
|
+
assert.strictEqual(res.statusCode, 200);
|
|
81
|
+
const body = JSON.parse(res.body);
|
|
82
|
+
assert.match(body.id, /^agent_note:/);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
//# sourceMappingURL=store.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.test.js","sourceRoot":"","sources":["../../src/api/store.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,MAAM,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,OAAO,MAAM,SAAS,CAAC;AAE9B,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEjD,SAAS,qBAAqB;IAC5B,OAAO;QACL,aAAa,EAAE,KAAK,IAAI,EAAE;YACxB,MAAM,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC;QAC/B,CAAC;QACD,gBAAgB,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC;QAChC,MAAM,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC;KACI,CAAC;AAC/B,CAAC;AAED,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;IACzB,EAAE,CAAC,mCAAmC,EAAE,KAAK,IAAI,EAAE;QACjD,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;QACtB,mBAAmB,CAAC,GAAG,EAAE,EAAE,YAAY,EAAE,qBAAqB,EAAE,EAAE,CAAC,CAAC;QACpE,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC;YAC3B,MAAM,EAAE,MAAM;YACd,GAAG,EAAE,QAAQ;YACb,OAAO,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE;SAC5B,CAAC,CAAC;QACH,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;QACxC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,qBAAqB,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;QACpE,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;QACtB,mBAAmB,CAAC,GAAG,EAAE,EAAE,YAAY,EAAE,qBAAqB,EAAE,EAAE,CAAC,CAAC;QACpE,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC;YAC3B,MAAM,EAAE,MAAM;YACd,GAAG,EAAE,QAAQ;YACb,OAAO,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,EAAE;SAC3C,CAAC,CAAC;QACH,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;QACxC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;QAClE,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;QACtB,mBAAmB,CAAC,GAAG,EAAE,EAAE,YAAY,EAAE,qBAAqB,EAAE,EAAE,CAAC,CAAC;QACpE,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC;YAC3B,MAAM,EAAE,MAAM;YACd,GAAG,EAAE,QAAQ;YACb,OAAO,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE;SAC1C,CAAC,CAAC;QACH,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;QACxC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gEAAgE,EAAE,KAAK,IAAI,EAAE;QAC9E,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;QACtB,mBAAmB,CAAC,GAAG,EAAE,EAAE,YAAY,EAAE,qBAAqB,EAAE,EAAE,CAAC,CAAC;QACpE,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC;YAC3B,MAAM,EAAE,MAAM;YACd,GAAG,EAAE,QAAQ;YACb,OAAO,EAAE;gBACP,OAAO,EAAE,sBAAsB;gBAC/B,KAAK,EAAE,uBAAuB;gBAC9B,IAAI,EAAE,aAAa;gBACnB,SAAS,EAAE,EAAE;gBACb,OAAO,EAAE,EAAE;gBACX,MAAM,EAAE,OAAO;gBACf,KAAK,EAAE,aAAa;gBACpB,YAAY,EAAE,YAAY;aAC3B;SACF,CAAC,CAAC;QACH,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;QACxC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClC,MAAM,CAAC,WAAW,CAAC,OAAO,IAAI,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;QAC7C,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC;QACtC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;QAClD,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;QACtB,mBAAmB,CAAC,GAAG,EAAE,EAAE,YAAY,EAAE,qBAAqB,EAAE,EAAE,CAAC,CAAC;QACpE,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC;YAC3B,MAAM,EAAE,MAAM;YACd,GAAG,EAAE,QAAQ;YACb,OAAO,EAAE,EAAE,OAAO,EAAE,cAAc,EAAE;SACrC,CAAC,CAAC;QACH,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;QACxC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -5,6 +5,7 @@ import { registerExplainRoutes } from "./api/explain.js";
|
|
|
5
5
|
import { registerSystemRoutes } from "./api/system.js";
|
|
6
6
|
import { registerRefreshRoutes } from "./api/refresh.js";
|
|
7
7
|
import { registerStoreRoutes } from "./api/store.js";
|
|
8
|
+
import { registerPointsRoutes } from "./api/points.js";
|
|
8
9
|
const PORT = Number(process.env.MEM_SERVER_PORT) || 31415;
|
|
9
10
|
const CWD = process.env.MEM_CWD || process.cwd();
|
|
10
11
|
const QDRANT_HOST = process.env.MEM_QDRANT_HOST || "127.0.0.1";
|
|
@@ -18,6 +19,7 @@ async function main() {
|
|
|
18
19
|
registerSystemRoutes(app, { qdrantHost: QDRANT_HOST, qdrantPort: QDRANT_PORT, collection: COLLECTION });
|
|
19
20
|
registerRefreshRoutes(app);
|
|
20
21
|
registerStoreRoutes(app, { qdrantHost: QDRANT_HOST, qdrantPort: QDRANT_PORT, collection: COLLECTION });
|
|
22
|
+
registerPointsRoutes(app, { qdrantHost: QDRANT_HOST, qdrantPort: QDRANT_PORT, collection: COLLECTION });
|
|
21
23
|
await app.listen({ port: PORT, host: "127.0.0.1" });
|
|
22
24
|
console.log(`Memory server at http://127.0.0.1:${PORT}`);
|
|
23
25
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEvD,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,KAAK,CAAC;AAC1D,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;AACjD,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,WAAW,CAAC;AAC/D,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,IAAI,CAAC;AAChE,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,gBAAgB,CAAC;AAElE,KAAK,UAAU,IAAI;IACjB,MAAM,GAAG,GAAG,OAAO,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IACtC,oBAAoB,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC;IAClH,oBAAoB,CAAC,GAAG,EAAE,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC;IACxG,qBAAqB,CAAC,GAAG,EAAE,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC;IACzG,oBAAoB,CAAC,GAAG,EAAE,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC;IACxG,qBAAqB,CAAC,GAAG,CAAC,CAAC;IAC3B,mBAAmB,CAAC,GAAG,EAAE,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC;IACvG,oBAAoB,CAAC,GAAG,EAAE,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC;IAExG,MAAM,GAAG,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;IACpD,OAAO,CAAC,GAAG,CAAC,qCAAqC,IAAI,EAAE,CAAC,CAAC;AAC3D,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -22,3 +22,7 @@ export declare function searchPointsKinds(client: QdrantClient, collectionName:
|
|
|
22
22
|
score: number;
|
|
23
23
|
payload: PointPayload;
|
|
24
24
|
}[]>;
|
|
25
|
+
export declare function retrievePointsById(client: QdrantClient, collectionName: string, ids: string[]): Promise<{
|
|
26
|
+
id: string;
|
|
27
|
+
payload: PointPayload;
|
|
28
|
+
}[]>;
|
|
@@ -85,4 +85,16 @@ async function doSearch(client, collectionName, vector, limit, filter) {
|
|
|
85
85
|
payload: (r.payload ?? {}),
|
|
86
86
|
}));
|
|
87
87
|
}
|
|
88
|
+
export async function retrievePointsById(client, collectionName, ids) {
|
|
89
|
+
if (ids.length === 0)
|
|
90
|
+
return [];
|
|
91
|
+
const retrieve = client.retrieve;
|
|
92
|
+
if (typeof retrieve !== "function")
|
|
93
|
+
return [];
|
|
94
|
+
const result = await retrieve.call(client, collectionName, { ids, with_payload: true });
|
|
95
|
+
return (result ?? []).map((r) => ({
|
|
96
|
+
id: r.id ?? String(r.id),
|
|
97
|
+
payload: (r.payload ?? {}),
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
88
100
|
//# sourceMappingURL=client.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/qdrant/client.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/qdrant/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { strict as assert } from "node:assert";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import Fastify from "fastify";
|
|
4
|
+
import type { QdrantClient } from "@qdrant/js-client-rest";
|
|
5
|
+
import { registerPointsRoutes } from "./points.js";
|
|
6
|
+
|
|
7
|
+
function createMockPointsClient(): QdrantClient {
|
|
8
|
+
return {
|
|
9
|
+
retrieve: async (_collection: string, opts: { ids: string[] }) =>
|
|
10
|
+
opts.ids.map((id) => ({
|
|
11
|
+
id,
|
|
12
|
+
payload: {
|
|
13
|
+
kind: "chunk",
|
|
14
|
+
text: `Full text for ${id}`,
|
|
15
|
+
path: "src/example.ts",
|
|
16
|
+
startLine: 1,
|
|
17
|
+
endLine: 10,
|
|
18
|
+
symbol: "example",
|
|
19
|
+
indexedAt: "2026-02-03T12:00:00.000Z",
|
|
20
|
+
source: "chunk",
|
|
21
|
+
},
|
|
22
|
+
})) as never,
|
|
23
|
+
} as unknown as QdrantClient;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe("points batch API", () => {
|
|
27
|
+
it("returns 400 when ids is empty", async () => {
|
|
28
|
+
const app = Fastify();
|
|
29
|
+
registerPointsRoutes(app, { qdrantClient: createMockPointsClient() });
|
|
30
|
+
const res = await app.inject({
|
|
31
|
+
method: "POST",
|
|
32
|
+
url: "/points/batch",
|
|
33
|
+
payload: { ids: [] },
|
|
34
|
+
});
|
|
35
|
+
assert.strictEqual(res.statusCode, 400);
|
|
36
|
+
assert.match(JSON.parse(res.body).message, /non-empty/);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("returns 400 when ids is not an array", async () => {
|
|
40
|
+
const app = Fastify();
|
|
41
|
+
registerPointsRoutes(app, { qdrantClient: createMockPointsClient() });
|
|
42
|
+
const res = await app.inject({
|
|
43
|
+
method: "POST",
|
|
44
|
+
url: "/points/batch",
|
|
45
|
+
payload: {},
|
|
46
|
+
});
|
|
47
|
+
assert.strictEqual(res.statusCode, 400);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("returns 400 when ids exceeds max", async () => {
|
|
51
|
+
const app = Fastify();
|
|
52
|
+
registerPointsRoutes(app, { qdrantClient: createMockPointsClient() });
|
|
53
|
+
const ids = Array.from({ length: 25 }, (_, i) => `id-${i}`);
|
|
54
|
+
const res = await app.inject({
|
|
55
|
+
method: "POST",
|
|
56
|
+
url: "/points/batch",
|
|
57
|
+
payload: { ids },
|
|
58
|
+
});
|
|
59
|
+
assert.strictEqual(res.statusCode, 400);
|
|
60
|
+
assert.match(JSON.parse(res.body).message, /at most 20/);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("returns 200 with items array and full metadata", async () => {
|
|
64
|
+
const app = Fastify();
|
|
65
|
+
registerPointsRoutes(app, { qdrantClient: createMockPointsClient() });
|
|
66
|
+
const res = await app.inject({
|
|
67
|
+
method: "POST",
|
|
68
|
+
url: "/points/batch",
|
|
69
|
+
payload: { ids: ["point-1", "point-2"] },
|
|
70
|
+
});
|
|
71
|
+
assert.strictEqual(res.statusCode, 200);
|
|
72
|
+
const body = JSON.parse(res.body);
|
|
73
|
+
assert.ok(Array.isArray(body.items));
|
|
74
|
+
assert.strictEqual(body.items.length, 2);
|
|
75
|
+
const item = body.items[0];
|
|
76
|
+
assert.strictEqual(item.id, "point-1");
|
|
77
|
+
assert.strictEqual(item.text, "Full text for point-1");
|
|
78
|
+
assert.strictEqual(item.path, "src/example.ts");
|
|
79
|
+
assert.strictEqual(item.startLine, 1);
|
|
80
|
+
assert.strictEqual(item.endLine, 10);
|
|
81
|
+
assert.strictEqual(item.symbol, "example");
|
|
82
|
+
assert.strictEqual(item.kind, "chunk");
|
|
83
|
+
assert.strictEqual(item.indexedAt, "2026-02-03T12:00:00.000Z");
|
|
84
|
+
assert.strictEqual(item.source, "chunk");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("deduplicates ids", async () => {
|
|
88
|
+
const app = Fastify();
|
|
89
|
+
registerPointsRoutes(app, { qdrantClient: createMockPointsClient() });
|
|
90
|
+
const res = await app.inject({
|
|
91
|
+
method: "POST",
|
|
92
|
+
url: "/points/batch",
|
|
93
|
+
payload: { ids: ["a", "a", "b"] },
|
|
94
|
+
});
|
|
95
|
+
assert.strictEqual(res.statusCode, 200);
|
|
96
|
+
const body = JSON.parse(res.body);
|
|
97
|
+
assert.strictEqual(body.items.length, 2);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("returns empty items when client returns no points", async () => {
|
|
101
|
+
const emptyMock = { retrieve: async () => [] } as unknown as QdrantClient;
|
|
102
|
+
const app = Fastify();
|
|
103
|
+
registerPointsRoutes(app, { qdrantClient: emptyMock });
|
|
104
|
+
const res = await app.inject({
|
|
105
|
+
method: "POST",
|
|
106
|
+
url: "/points/batch",
|
|
107
|
+
payload: { ids: ["missing-id"] },
|
|
108
|
+
});
|
|
109
|
+
assert.strictEqual(res.statusCode, 200);
|
|
110
|
+
const body = JSON.parse(res.body);
|
|
111
|
+
assert.strictEqual(body.items.length, 0);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { FastifyInstance } from "fastify";
|
|
2
|
+
import type { QdrantClient } from "@qdrant/js-client-rest";
|
|
3
|
+
import { createQdrantClient } from "../qdrant/client.js";
|
|
4
|
+
import { retrievePointsById } from "../qdrant/client.js";
|
|
5
|
+
import type { PointPayload } from "../qdrant/client.js";
|
|
6
|
+
|
|
7
|
+
const BATCH_MAX_IDS = 20;
|
|
8
|
+
|
|
9
|
+
export interface PointsBatchBody {
|
|
10
|
+
ids: string[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface PointItem {
|
|
14
|
+
id: string;
|
|
15
|
+
text: string;
|
|
16
|
+
path?: string;
|
|
17
|
+
startLine?: number;
|
|
18
|
+
endLine?: number;
|
|
19
|
+
symbol?: string;
|
|
20
|
+
kind?: string;
|
|
21
|
+
indexedAt?: string;
|
|
22
|
+
storedAt?: string;
|
|
23
|
+
source?: string;
|
|
24
|
+
supersededById?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function payloadToItem(id: string, payload: PointPayload): PointItem {
|
|
28
|
+
return {
|
|
29
|
+
id,
|
|
30
|
+
text: payload.text ?? "",
|
|
31
|
+
path: payload.path,
|
|
32
|
+
startLine: payload.startLine as number | undefined,
|
|
33
|
+
endLine: payload.endLine as number | undefined,
|
|
34
|
+
symbol: payload.symbol as string | undefined,
|
|
35
|
+
kind: payload.kind as string | undefined,
|
|
36
|
+
indexedAt: payload.indexedAt as string | undefined,
|
|
37
|
+
storedAt: payload.storedAt as string | undefined,
|
|
38
|
+
source: payload.source as string | undefined,
|
|
39
|
+
supersededById: payload.supersededById as string | undefined,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function registerPointsRoutes(
|
|
44
|
+
app: FastifyInstance,
|
|
45
|
+
options: { qdrantHost?: string; qdrantPort?: number; collection?: string; qdrantClient?: QdrantClient }
|
|
46
|
+
): void {
|
|
47
|
+
const client =
|
|
48
|
+
options.qdrantClient ??
|
|
49
|
+
createQdrantClient(options.qdrantHost ?? "127.0.0.1", options.qdrantPort ?? 6333);
|
|
50
|
+
const collection = options.collection ?? "project_memory";
|
|
51
|
+
|
|
52
|
+
app.post<{ Body: PointsBatchBody }>(
|
|
53
|
+
"/points/batch",
|
|
54
|
+
{
|
|
55
|
+
schema: {
|
|
56
|
+
body: {
|
|
57
|
+
type: "object",
|
|
58
|
+
required: ["ids"],
|
|
59
|
+
properties: {
|
|
60
|
+
ids: {
|
|
61
|
+
type: "array",
|
|
62
|
+
items: { type: "string" },
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
async (request, reply) => {
|
|
69
|
+
const { ids } = request.body ?? {};
|
|
70
|
+
if (!Array.isArray(ids) || ids.length === 0) {
|
|
71
|
+
await reply.status(400).send({
|
|
72
|
+
statusCode: 400,
|
|
73
|
+
error: "Bad Request",
|
|
74
|
+
message: "ids must be a non-empty array (max " + BATCH_MAX_IDS + " items)",
|
|
75
|
+
});
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (ids.length > BATCH_MAX_IDS) {
|
|
79
|
+
await reply.status(400).send({
|
|
80
|
+
statusCode: 400,
|
|
81
|
+
error: "Bad Request",
|
|
82
|
+
message: "ids must contain at most " + BATCH_MAX_IDS + " items",
|
|
83
|
+
});
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const uniqueIds = [...new Set(ids)] as string[];
|
|
87
|
+
const points = await retrievePointsById(client, collection, uniqueIds);
|
|
88
|
+
const items: PointItem[] = points.map((p) => payloadToItem(p.id, p.payload));
|
|
89
|
+
await reply.send({ items });
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { strict as assert } from "node:assert";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import Fastify from "fastify";
|
|
4
|
+
import type { QdrantClient } from "@qdrant/js-client-rest";
|
|
5
|
+
import { registerSearchRoutes } from "./search.js";
|
|
6
|
+
|
|
7
|
+
function createMockSearchClient(): QdrantClient {
|
|
8
|
+
const chunkPayload = {
|
|
9
|
+
kind: "chunk",
|
|
10
|
+
text: "some code content here that could be long",
|
|
11
|
+
path: "src/foo.ts",
|
|
12
|
+
startLine: 42,
|
|
13
|
+
endLine: 80,
|
|
14
|
+
symbol: "handleSubmit",
|
|
15
|
+
indexedAt: "2026-02-03T12:00:00.000Z",
|
|
16
|
+
source: "chunk",
|
|
17
|
+
};
|
|
18
|
+
const searchResult = [
|
|
19
|
+
{
|
|
20
|
+
id: "chunk-id-1",
|
|
21
|
+
score: 0.9,
|
|
22
|
+
payload: chunkPayload,
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
return {
|
|
26
|
+
getCollection: async () =>
|
|
27
|
+
({ config: { params: { vectors: { default: {} } } } }) as never,
|
|
28
|
+
search: async () => searchResult as never,
|
|
29
|
+
} as unknown as QdrantClient;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe("search API", () => {
|
|
33
|
+
it("returns 200 with systemMaps, moduleSummaries, chunks", async () => {
|
|
34
|
+
const app = Fastify();
|
|
35
|
+
registerSearchRoutes(app, { qdrantClient: createMockSearchClient() });
|
|
36
|
+
const res = await app.inject({
|
|
37
|
+
method: "POST",
|
|
38
|
+
url: "/search",
|
|
39
|
+
payload: { query: "auth flow", limit: 20 },
|
|
40
|
+
});
|
|
41
|
+
assert.strictEqual(res.statusCode, 200);
|
|
42
|
+
const body = JSON.parse(res.body);
|
|
43
|
+
assert.ok(Array.isArray(body.systemMaps));
|
|
44
|
+
assert.ok(Array.isArray(body.moduleSummaries));
|
|
45
|
+
assert.ok(Array.isArray(body.chunks));
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("returns chunks with anchor and freshness fields when present in payload", async () => {
|
|
49
|
+
const app = Fastify();
|
|
50
|
+
registerSearchRoutes(app, { qdrantClient: createMockSearchClient() });
|
|
51
|
+
const res = await app.inject({
|
|
52
|
+
method: "POST",
|
|
53
|
+
url: "/search",
|
|
54
|
+
payload: { query: "test" },
|
|
55
|
+
});
|
|
56
|
+
assert.strictEqual(res.statusCode, 200);
|
|
57
|
+
const body = JSON.parse(res.body);
|
|
58
|
+
assert.ok(body.chunks.length >= 1);
|
|
59
|
+
const c = body.chunks[0];
|
|
60
|
+
assert.strictEqual(c.path, "src/foo.ts");
|
|
61
|
+
assert.strictEqual(c.startLine, 42);
|
|
62
|
+
assert.strictEqual(c.endLine, 80);
|
|
63
|
+
assert.strictEqual(c.symbol, "handleSubmit");
|
|
64
|
+
assert.strictEqual(c.kind, "chunk");
|
|
65
|
+
assert.strictEqual(c.indexedAt, "2026-02-03T12:00:00.000Z");
|
|
66
|
+
assert.strictEqual(c.source, "chunk");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("refsOnly truncates chunk text to 80 chars", async () => {
|
|
70
|
+
const longText = "x".repeat(100);
|
|
71
|
+
const app = Fastify();
|
|
72
|
+
const mockClient = {
|
|
73
|
+
getCollection: async () =>
|
|
74
|
+
({ config: { params: { vectors: { default: {} } } } }) as never,
|
|
75
|
+
search: async () =>
|
|
76
|
+
[
|
|
77
|
+
{
|
|
78
|
+
id: "id1",
|
|
79
|
+
score: 0.9,
|
|
80
|
+
payload: { kind: "chunk", text: longText },
|
|
81
|
+
},
|
|
82
|
+
] as never,
|
|
83
|
+
} as unknown as QdrantClient;
|
|
84
|
+
registerSearchRoutes(app, { qdrantClient: mockClient });
|
|
85
|
+
const res = await app.inject({
|
|
86
|
+
method: "POST",
|
|
87
|
+
url: "/search",
|
|
88
|
+
payload: { query: "test", refsOnly: true },
|
|
89
|
+
});
|
|
90
|
+
assert.strictEqual(res.statusCode, 200);
|
|
91
|
+
const body = JSON.parse(res.body);
|
|
92
|
+
assert.ok(body.chunks[0].text.length <= 81);
|
|
93
|
+
assert.match(body.chunks[0].text, /…$/);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("accepts optional kinds and intent", async () => {
|
|
97
|
+
const app = Fastify();
|
|
98
|
+
registerSearchRoutes(app, { qdrantClient: createMockSearchClient() });
|
|
99
|
+
const res = await app.inject({
|
|
100
|
+
method: "POST",
|
|
101
|
+
url: "/search",
|
|
102
|
+
payload: {
|
|
103
|
+
query: "session summary",
|
|
104
|
+
kinds: ["agent_note"],
|
|
105
|
+
intent: "planning",
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
assert.strictEqual(res.statusCode, 200);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { FastifyInstance } from "fastify";
|
|
2
|
+
import type { QdrantClient } from "@qdrant/js-client-rest";
|
|
2
3
|
import { createQdrantClient } from "../qdrant/client.js";
|
|
3
4
|
import { embedText } from "../embedding/stub.js";
|
|
4
5
|
import { searchPoints, searchPointsKinds } from "../qdrant/client.js";
|
|
@@ -6,22 +7,40 @@ import { searchPoints, searchPointsKinds } from "../qdrant/client.js";
|
|
|
6
7
|
export interface SearchBody {
|
|
7
8
|
query: string;
|
|
8
9
|
limit?: number;
|
|
10
|
+
refsOnly?: boolean;
|
|
11
|
+
intent?: "factual" | "planning" | "navigation";
|
|
12
|
+
kinds?: string[];
|
|
13
|
+
limitSystemMap?: number;
|
|
14
|
+
limitModuleSummary?: number;
|
|
15
|
+
limitChunks?: number;
|
|
9
16
|
}
|
|
10
17
|
|
|
11
18
|
export interface SearchResponse {
|
|
12
19
|
systemMaps: Array<{ id: string; text: string; path?: string }>;
|
|
13
20
|
moduleSummaries: Array<{ id: string; text: string; path?: string }>;
|
|
14
|
-
chunks: Array<{
|
|
21
|
+
chunks: Array<{
|
|
22
|
+
id: string;
|
|
23
|
+
text: string;
|
|
24
|
+
path?: string;
|
|
25
|
+
score?: number;
|
|
26
|
+
startLine?: number;
|
|
27
|
+
endLine?: number;
|
|
28
|
+
symbol?: string;
|
|
29
|
+
kind?: string;
|
|
30
|
+
indexedAt?: string;
|
|
31
|
+
storedAt?: string;
|
|
32
|
+
source?: string;
|
|
33
|
+
supersededById?: string;
|
|
34
|
+
}>;
|
|
15
35
|
}
|
|
16
36
|
|
|
17
37
|
export function registerSearchRoutes(
|
|
18
38
|
app: FastifyInstance,
|
|
19
|
-
options: { qdrantHost?: string; qdrantPort?: number; collection?: string }
|
|
39
|
+
options: { qdrantHost?: string; qdrantPort?: number; collection?: string; qdrantClient?: QdrantClient }
|
|
20
40
|
): void {
|
|
21
|
-
const client =
|
|
22
|
-
options.
|
|
23
|
-
options.qdrantPort ?? 6333
|
|
24
|
-
);
|
|
41
|
+
const client =
|
|
42
|
+
options.qdrantClient ??
|
|
43
|
+
createQdrantClient(options.qdrantHost ?? "127.0.0.1", options.qdrantPort ?? 6333);
|
|
25
44
|
const collection = options.collection ?? "project_memory";
|
|
26
45
|
|
|
27
46
|
app.post<{ Body: SearchBody }>(
|
|
@@ -34,36 +53,63 @@ export function registerSearchRoutes(
|
|
|
34
53
|
properties: {
|
|
35
54
|
query: { type: "string" },
|
|
36
55
|
limit: { type: "number" },
|
|
56
|
+
refsOnly: { type: "boolean" },
|
|
57
|
+
intent: { type: "string", enum: ["factual", "planning", "navigation"] },
|
|
58
|
+
kinds: { type: "array", items: { type: "string" } },
|
|
59
|
+
limitSystemMap: { type: "number" },
|
|
60
|
+
limitModuleSummary: { type: "number" },
|
|
61
|
+
limitChunks: { type: "number" },
|
|
37
62
|
},
|
|
38
63
|
},
|
|
39
64
|
},
|
|
40
65
|
},
|
|
41
66
|
async (request, reply) => {
|
|
42
|
-
const {
|
|
67
|
+
const {
|
|
68
|
+
query,
|
|
69
|
+
limit = 20,
|
|
70
|
+
refsOnly = false,
|
|
71
|
+
kinds,
|
|
72
|
+
limitSystemMap = 5,
|
|
73
|
+
limitModuleSummary = 10,
|
|
74
|
+
limitChunks,
|
|
75
|
+
} = request.body;
|
|
43
76
|
const vector = embedText(query ?? "");
|
|
77
|
+
const chunkKinds = kinds && kinds.length > 0 ? kinds : ["chunk", "entrypoint", "agent_note"];
|
|
78
|
+
const chunkLimit = limitChunks ?? limit;
|
|
44
79
|
|
|
45
80
|
const [systemMaps, moduleSummaries, chunks] = await Promise.all([
|
|
46
|
-
searchPoints(client, collection, vector,
|
|
47
|
-
searchPoints(client, collection, vector,
|
|
48
|
-
searchPointsKinds(client, collection, vector,
|
|
81
|
+
searchPoints(client, collection, vector, limitSystemMap, "system_map"),
|
|
82
|
+
searchPoints(client, collection, vector, limitModuleSummary, "module_summary"),
|
|
83
|
+
searchPointsKinds(client, collection, vector, chunkLimit, chunkKinds),
|
|
49
84
|
]);
|
|
50
85
|
|
|
86
|
+
const textForChunk = (raw: string, refsOnlyMode: boolean): string =>
|
|
87
|
+
refsOnlyMode && raw.length > 80 ? raw.slice(0, 80) + "…" : raw;
|
|
88
|
+
|
|
51
89
|
const response: SearchResponse = {
|
|
52
90
|
systemMaps: systemMaps.map((r) => ({
|
|
53
91
|
id: r.id,
|
|
54
|
-
text: r.payload.text ?? "",
|
|
92
|
+
text: textForChunk(r.payload.text ?? "", refsOnly),
|
|
55
93
|
path: r.payload.path,
|
|
56
94
|
})),
|
|
57
95
|
moduleSummaries: moduleSummaries.map((r) => ({
|
|
58
96
|
id: r.id,
|
|
59
|
-
text: r.payload.text ?? "",
|
|
97
|
+
text: textForChunk(r.payload.text ?? "", refsOnly),
|
|
60
98
|
path: r.payload.path,
|
|
61
99
|
})),
|
|
62
100
|
chunks: chunks.map((r) => ({
|
|
63
101
|
id: r.id,
|
|
64
|
-
text: r.payload.text ?? "",
|
|
102
|
+
text: textForChunk(r.payload.text ?? "", refsOnly),
|
|
65
103
|
path: r.payload.path,
|
|
66
104
|
score: r.score,
|
|
105
|
+
startLine: r.payload.startLine as number | undefined,
|
|
106
|
+
endLine: r.payload.endLine as number | undefined,
|
|
107
|
+
symbol: r.payload.symbol as string | undefined,
|
|
108
|
+
kind: r.payload.kind as string | undefined,
|
|
109
|
+
indexedAt: r.payload.indexedAt as string | undefined,
|
|
110
|
+
storedAt: r.payload.storedAt as string | undefined,
|
|
111
|
+
source: r.payload.source as string | undefined,
|
|
112
|
+
supersededById: r.payload.supersededById as string | undefined,
|
|
67
113
|
})),
|
|
68
114
|
};
|
|
69
115
|
await reply.send(response);
|