@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.
Files changed (98) hide show
  1. package/README.md +13 -8
  2. package/package.json +4 -2
  3. package/packages/cli/coverage/lcov-report/chunking/chunker.ts.html +81 -81
  4. package/packages/cli/coverage/lcov-report/chunking/hash.ts.html +12 -12
  5. package/packages/cli/coverage/lcov-report/chunking/index.html +1 -1
  6. package/packages/cli/coverage/lcov-report/chunking/types.ts.html +23 -23
  7. package/packages/cli/coverage/lcov-report/config/index.html +1 -1
  8. package/packages/cli/coverage/lcov-report/config/load.ts.html +1 -1
  9. package/packages/cli/coverage/lcov-report/config/types.ts.html +1 -1
  10. package/packages/cli/coverage/lcov-report/embedding/index.html +1 -1
  11. package/packages/cli/coverage/lcov-report/embedding/stub.ts.html +1 -1
  12. package/packages/cli/coverage/lcov-report/index.html +18 -18
  13. package/packages/cli/coverage/lcov-report/qdrant/index.html +1 -1
  14. package/packages/cli/coverage/lcov-report/qdrant/upsert.ts.html +1 -1
  15. package/packages/cli/coverage/lcov-report/scope/allowlist.ts.html +51 -51
  16. package/packages/cli/coverage/lcov-report/scope/ignore.ts.html +1 -1
  17. package/packages/cli/coverage/lcov-report/scope/index.html +19 -19
  18. package/packages/cli/coverage/lcov.info +221 -213
  19. package/packages/cli/coverage/tmp/{coverage-7460-1770137208481-0.json → coverage-24604-1770174821315-0.json} +1 -1
  20. package/packages/cli/coverage/tmp/coverage-24605-1770174821362-0.json +1 -0
  21. package/packages/cli/coverage/tmp/{coverage-7462-1770137208524-0.json → coverage-24606-1770174821361-0.json} +1 -1
  22. package/packages/cli/coverage/tmp/{coverage-7463-1770137208524-0.json → coverage-24607-1770174821359-0.json} +1 -1
  23. package/packages/cli/coverage/tmp/{coverage-7464-1770137208531-0.json → coverage-24608-1770174821367-0.json} +1 -1
  24. package/packages/cli/coverage/tmp/{coverage-7465-1770137208526-0.json → coverage-24609-1770174821359-0.json} +1 -1
  25. package/packages/cli/coverage/tmp/{coverage-7466-1770137208528-0.json → coverage-24610-1770174821360-0.json} +1 -1
  26. package/packages/cli/coverage/tmp/{coverage-7467-1770137208579-0.json → coverage-24611-1770174821416-0.json} +1 -1
  27. package/packages/cli/coverage/tmp/coverage-24612-1770174821392-0.json +1 -0
  28. package/packages/cli/coverage/tmp/{coverage-7469-1770137208543-0.json → coverage-24613-1770174821380-0.json} +1 -1
  29. package/packages/cli/dist/chunking/chunker.test.js +19 -0
  30. package/packages/cli/dist/chunking/chunker.test.js.map +1 -1
  31. package/packages/cli/dist/cli/index.js +1 -1
  32. package/packages/cli/dist/cli/index.js.map +1 -1
  33. package/packages/cli/dist/phase1/pipeline.js +17 -7
  34. package/packages/cli/dist/phase1/pipeline.js.map +1 -1
  35. package/packages/cli/dist/phase2/deep-index.js +18 -6
  36. package/packages/cli/dist/phase2/deep-index.js.map +1 -1
  37. package/packages/cli/dist/scope/allowlist.test.js +14 -1
  38. package/packages/cli/dist/scope/allowlist.test.js.map +1 -1
  39. package/packages/cli/src/chunking/chunker.test.ts +21 -0
  40. package/packages/cli/src/cli/index.ts +1 -1
  41. package/packages/cli/src/phase1/pipeline.ts +15 -8
  42. package/packages/cli/src/phase2/deep-index.ts +15 -6
  43. package/packages/cli/src/scope/allowlist.test.ts +16 -1
  44. package/packages/mcp-server/dist/index.js +174 -16
  45. package/packages/mcp-server/dist/index.js.map +1 -1
  46. package/packages/mcp-server/dist/system-instructions.md +2 -0
  47. package/packages/mcp-server/src/index.ts +204 -16
  48. package/packages/mcp-server/system-instructions.md +2 -0
  49. package/packages/server/coverage/lcov-report/api/index.html +146 -0
  50. package/packages/server/coverage/lcov-report/api/points.ts.html +361 -0
  51. package/packages/server/coverage/lcov-report/api/search.ts.html +436 -0
  52. package/packages/server/coverage/lcov-report/api/store.ts.html +487 -0
  53. package/packages/server/coverage/lcov-report/embedding/index.html +116 -0
  54. package/packages/server/coverage/lcov-report/embedding/stub.ts.html +184 -0
  55. package/packages/server/coverage/lcov-report/index.html +40 -10
  56. package/packages/server/coverage/lcov-report/qdrant/client.ts.html +517 -0
  57. package/packages/server/coverage/lcov-report/qdrant/index.html +116 -0
  58. package/packages/server/coverage/lcov-report/stub.ts.html +1 -1
  59. package/packages/server/coverage/lcov.info +674 -27
  60. package/packages/server/coverage/tmp/{coverage-7549-1770137209315-0.json → coverage-24692-1770174822211-0.json} +1 -1
  61. package/packages/server/coverage/tmp/coverage-24693-1770174822463-0.json +1 -0
  62. package/packages/server/coverage/tmp/coverage-24694-1770174822454-0.json +1 -0
  63. package/packages/server/coverage/tmp/coverage-24695-1770174822463-0.json +1 -0
  64. package/packages/server/coverage/tmp/{coverage-7550-1770137209349-0.json → coverage-24696-1770174822253-0.json} +1 -1
  65. package/packages/server/dist/api/points.d.ts +24 -0
  66. package/packages/server/dist/api/points.js +60 -0
  67. package/packages/server/dist/api/points.js.map +1 -0
  68. package/packages/server/dist/api/points.test.d.ts +1 -0
  69. package/packages/server/dist/api/points.test.js +105 -0
  70. package/packages/server/dist/api/points.test.js.map +1 -0
  71. package/packages/server/dist/api/search.d.ts +16 -0
  72. package/packages/server/dist/api/search.js +26 -8
  73. package/packages/server/dist/api/search.js.map +1 -1
  74. package/packages/server/dist/api/search.test.d.ts +1 -0
  75. package/packages/server/dist/api/search.test.js +102 -0
  76. package/packages/server/dist/api/search.test.js.map +1 -0
  77. package/packages/server/dist/api/store.d.ts +7 -0
  78. package/packages/server/dist/api/store.js +50 -7
  79. package/packages/server/dist/api/store.js.map +1 -1
  80. package/packages/server/dist/api/store.test.d.ts +1 -0
  81. package/packages/server/dist/api/store.test.js +85 -0
  82. package/packages/server/dist/api/store.test.js.map +1 -0
  83. package/packages/server/dist/index.js +2 -0
  84. package/packages/server/dist/index.js.map +1 -1
  85. package/packages/server/dist/qdrant/client.d.ts +4 -0
  86. package/packages/server/dist/qdrant/client.js +12 -0
  87. package/packages/server/dist/qdrant/client.js.map +1 -1
  88. package/packages/server/package.json +1 -1
  89. package/packages/server/src/api/points.test.ts +113 -0
  90. package/packages/server/src/api/points.ts +92 -0
  91. package/packages/server/src/api/search.test.ts +110 -0
  92. package/packages/server/src/api/search.ts +59 -13
  93. package/packages/server/src/api/store.test.ts +91 -0
  94. package/packages/server/src/api/store.ts +60 -12
  95. package/packages/server/src/index.ts +2 -0
  96. package/packages/server/src/qdrant/client.ts +15 -0
  97. package/packages/cli/coverage/tmp/coverage-7461-1770137208528-0.json +0 -1
  98. 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":"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"}
@@ -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;AAErD,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;IAEvG,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"}
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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"}
@@ -17,7 +17,7 @@
17
17
  "check-coverage": true,
18
18
  "lines": 85,
19
19
  "functions": 85,
20
- "branches": 85,
20
+ "branches": 70,
21
21
  "statements": 85,
22
22
  "reporter": ["text", "lcov"],
23
23
  "reports-dir": "coverage"
@@ -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<{ id: string; text: string; path?: string; score?: number }>;
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 = createQdrantClient(
22
- options.qdrantHost ?? "127.0.0.1",
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 { query, limit = 20 } = request.body;
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, 5, "system_map"),
47
- searchPoints(client, collection, vector, 10, "module_summary"),
48
- searchPointsKinds(client, collection, vector, limit, ["chunk", "entrypoint", "agent_note"]),
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);