@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,91 @@
|
|
|
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 { registerStoreRoutes } from "./store.js";
|
|
6
|
+
|
|
7
|
+
function createMockStoreClient(): QdrantClient {
|
|
8
|
+
return {
|
|
9
|
+
getCollection: async () => {
|
|
10
|
+
throw new Error("not found");
|
|
11
|
+
},
|
|
12
|
+
createCollection: async () => {},
|
|
13
|
+
upsert: async () => {},
|
|
14
|
+
} as unknown as QdrantClient;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe("store API", () => {
|
|
18
|
+
it("returns 400 when content is empty", async () => {
|
|
19
|
+
const app = Fastify();
|
|
20
|
+
registerStoreRoutes(app, { qdrantClient: createMockStoreClient() });
|
|
21
|
+
const res = await app.inject({
|
|
22
|
+
method: "POST",
|
|
23
|
+
url: "/store",
|
|
24
|
+
payload: { content: " " },
|
|
25
|
+
});
|
|
26
|
+
assert.strictEqual(res.statusCode, 400);
|
|
27
|
+
const body = JSON.parse(res.body);
|
|
28
|
+
assert.match(body.message, /content is required/);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("returns 400 when startLine is not a positive integer", async () => {
|
|
32
|
+
const app = Fastify();
|
|
33
|
+
registerStoreRoutes(app, { qdrantClient: createMockStoreClient() });
|
|
34
|
+
const res = await app.inject({
|
|
35
|
+
method: "POST",
|
|
36
|
+
url: "/store",
|
|
37
|
+
payload: { content: "note", startLine: 0 },
|
|
38
|
+
});
|
|
39
|
+
assert.strictEqual(res.statusCode, 400);
|
|
40
|
+
assert.match(JSON.parse(res.body).message, /startLine/);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("returns 400 when endLine is not a positive integer", async () => {
|
|
44
|
+
const app = Fastify();
|
|
45
|
+
registerStoreRoutes(app, { qdrantClient: createMockStoreClient() });
|
|
46
|
+
const res = await app.inject({
|
|
47
|
+
method: "POST",
|
|
48
|
+
url: "/store",
|
|
49
|
+
payload: { content: "note", endLine: -1 },
|
|
50
|
+
});
|
|
51
|
+
assert.strictEqual(res.statusCode, 400);
|
|
52
|
+
assert.match(JSON.parse(res.body).message, /endLine/);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("returns 200 and stores with anchor fields and scope when valid", async () => {
|
|
56
|
+
const app = Fastify();
|
|
57
|
+
registerStoreRoutes(app, { qdrantClient: createMockStoreClient() });
|
|
58
|
+
const res = await app.inject({
|
|
59
|
+
method: "POST",
|
|
60
|
+
url: "/store",
|
|
61
|
+
payload: {
|
|
62
|
+
content: "Summary of auth flow",
|
|
63
|
+
title: "Session summary: auth",
|
|
64
|
+
path: "src/auth.ts",
|
|
65
|
+
startLine: 10,
|
|
66
|
+
endLine: 20,
|
|
67
|
+
symbol: "login",
|
|
68
|
+
scope: "branch:main",
|
|
69
|
+
supersedesId: "old-id-123",
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
assert.strictEqual(res.statusCode, 200);
|
|
73
|
+
const body = JSON.parse(res.body);
|
|
74
|
+
assert.strictEqual(typeof body.id, "string");
|
|
75
|
+
assert.match(body.id, /^agent_note:/);
|
|
76
|
+
assert.strictEqual(body.message, "Stored.");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("returns 200 for minimal valid body", async () => {
|
|
80
|
+
const app = Fastify();
|
|
81
|
+
registerStoreRoutes(app, { qdrantClient: createMockStoreClient() });
|
|
82
|
+
const res = await app.inject({
|
|
83
|
+
method: "POST",
|
|
84
|
+
url: "/store",
|
|
85
|
+
payload: { content: "Minimal note" },
|
|
86
|
+
});
|
|
87
|
+
assert.strictEqual(res.statusCode, 200);
|
|
88
|
+
const body = JSON.parse(res.body);
|
|
89
|
+
assert.match(body.id, /^agent_note:/);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import type { FastifyInstance } from "fastify";
|
|
3
|
+
import type { QdrantClient } from "@qdrant/js-client-rest";
|
|
3
4
|
import { createQdrantClient } from "../qdrant/client.js";
|
|
4
5
|
import { ensureCollection, upsertPoints } from "../qdrant/client.js";
|
|
5
6
|
import { embedText } from "../embedding/stub.js";
|
|
@@ -8,6 +9,11 @@ export interface StoreBody {
|
|
|
8
9
|
content: string;
|
|
9
10
|
title?: string;
|
|
10
11
|
path?: string;
|
|
12
|
+
startLine?: number;
|
|
13
|
+
endLine?: number;
|
|
14
|
+
symbol?: string;
|
|
15
|
+
scope?: string;
|
|
16
|
+
supersedesId?: string;
|
|
11
17
|
}
|
|
12
18
|
|
|
13
19
|
const KIND_AGENT_NOTE = "agent_note";
|
|
@@ -16,18 +22,18 @@ function buildTextToEmbed(body: StoreBody): string {
|
|
|
16
22
|
const parts: string[] = [];
|
|
17
23
|
if (body.path) parts.push(`File: ${body.path}`);
|
|
18
24
|
if (body.title) parts.push(`Title: ${body.title}`);
|
|
25
|
+
if (body.symbol) parts.push(`Symbol: ${body.symbol}`);
|
|
19
26
|
parts.push(body.content);
|
|
20
27
|
return parts.join("\n\n");
|
|
21
28
|
}
|
|
22
29
|
|
|
23
30
|
export function registerStoreRoutes(
|
|
24
31
|
app: FastifyInstance,
|
|
25
|
-
options: { qdrantHost?: string; qdrantPort?: number; collection?: string }
|
|
32
|
+
options: { qdrantHost?: string; qdrantPort?: number; collection?: string; qdrantClient?: QdrantClient }
|
|
26
33
|
): void {
|
|
27
|
-
const client =
|
|
28
|
-
options.
|
|
29
|
-
options.qdrantPort ?? 6333
|
|
30
|
-
);
|
|
34
|
+
const client =
|
|
35
|
+
options.qdrantClient ??
|
|
36
|
+
createQdrantClient(options.qdrantHost ?? "127.0.0.1", options.qdrantPort ?? 6333);
|
|
31
37
|
const collection = options.collection ?? "project_memory";
|
|
32
38
|
|
|
33
39
|
app.post<{ Body: StoreBody }>(
|
|
@@ -41,16 +47,44 @@ export function registerStoreRoutes(
|
|
|
41
47
|
content: { type: "string" },
|
|
42
48
|
title: { type: ["string", "null"] },
|
|
43
49
|
path: { type: ["string", "null"] },
|
|
50
|
+
startLine: { type: ["number", "null"] },
|
|
51
|
+
endLine: { type: ["number", "null"] },
|
|
52
|
+
symbol: { type: ["string", "null"] },
|
|
53
|
+
scope: { type: ["string", "null"] },
|
|
54
|
+
supersedesId: { type: ["string", "null"] },
|
|
44
55
|
},
|
|
45
56
|
},
|
|
46
57
|
},
|
|
47
58
|
},
|
|
48
59
|
async (request, reply) => {
|
|
49
60
|
const raw = request.body;
|
|
50
|
-
const
|
|
61
|
+
const startLine = raw.startLine != null ? Number(raw.startLine) : undefined;
|
|
62
|
+
const endLine = raw.endLine != null ? Number(raw.endLine) : undefined;
|
|
63
|
+
if (startLine != null && (!Number.isInteger(startLine) || startLine < 1)) {
|
|
64
|
+
await reply.status(400).send({
|
|
65
|
+
statusCode: 400,
|
|
66
|
+
error: "Bad Request",
|
|
67
|
+
message: "startLine must be a positive integer when provided",
|
|
68
|
+
});
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (endLine != null && (!Number.isInteger(endLine) || endLine < 1)) {
|
|
72
|
+
await reply.status(400).send({
|
|
73
|
+
statusCode: 400,
|
|
74
|
+
error: "Bad Request",
|
|
75
|
+
message: "endLine must be a positive integer when provided",
|
|
76
|
+
});
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const body: StoreBody = {
|
|
51
80
|
content: raw.content ?? "",
|
|
52
81
|
title: raw.title ?? undefined,
|
|
53
82
|
path: raw.path ?? undefined,
|
|
83
|
+
startLine,
|
|
84
|
+
endLine,
|
|
85
|
+
symbol: raw.symbol ?? undefined,
|
|
86
|
+
scope: raw.scope ?? undefined,
|
|
87
|
+
supersedesId: raw.supersedesId ?? undefined,
|
|
54
88
|
};
|
|
55
89
|
const content = body.content.trim();
|
|
56
90
|
if (!content) {
|
|
@@ -65,18 +99,32 @@ export function registerStoreRoutes(
|
|
|
65
99
|
const textToEmbed = buildTextToEmbed({ ...body, content });
|
|
66
100
|
const vector = embedText(textToEmbed);
|
|
67
101
|
const id = randomUUID();
|
|
102
|
+
const storedAt = new Date().toISOString();
|
|
103
|
+
const source =
|
|
104
|
+
body.title?.trim().toLowerCase().startsWith("session summary")
|
|
105
|
+
? "session-summary"
|
|
106
|
+
: "store";
|
|
107
|
+
|
|
108
|
+
const payload = {
|
|
109
|
+
kind: KIND_AGENT_NOTE,
|
|
110
|
+
path: body.path,
|
|
111
|
+
title: body.title,
|
|
112
|
+
text: content,
|
|
113
|
+
storedAt,
|
|
114
|
+
source,
|
|
115
|
+
...(body.startLine != null && { startLine: body.startLine }),
|
|
116
|
+
...(body.endLine != null && { endLine: body.endLine }),
|
|
117
|
+
...(body.symbol != null && { symbol: body.symbol }),
|
|
118
|
+
...(body.scope != null && { scope: body.scope }),
|
|
119
|
+
...(body.supersedesId != null && { supersedesId: body.supersedesId }),
|
|
120
|
+
};
|
|
68
121
|
|
|
69
122
|
await ensureCollection(client, collection);
|
|
70
123
|
await upsertPoints(client, collection, [
|
|
71
124
|
{
|
|
72
125
|
id,
|
|
73
126
|
vector,
|
|
74
|
-
payload
|
|
75
|
-
kind: KIND_AGENT_NOTE,
|
|
76
|
-
path: body.path,
|
|
77
|
-
title: body.title,
|
|
78
|
-
text: content,
|
|
79
|
-
},
|
|
127
|
+
payload,
|
|
80
128
|
},
|
|
81
129
|
]);
|
|
82
130
|
|
|
@@ -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
|
|
|
9
10
|
const PORT = Number(process.env.MEM_SERVER_PORT) || 31415;
|
|
10
11
|
const CWD = process.env.MEM_CWD || process.cwd();
|
|
@@ -20,6 +21,7 @@ async function main() {
|
|
|
20
21
|
registerSystemRoutes(app, { qdrantHost: QDRANT_HOST, qdrantPort: QDRANT_PORT, collection: COLLECTION });
|
|
21
22
|
registerRefreshRoutes(app);
|
|
22
23
|
registerStoreRoutes(app, { qdrantHost: QDRANT_HOST, qdrantPort: QDRANT_PORT, collection: COLLECTION });
|
|
24
|
+
registerPointsRoutes(app, { qdrantHost: QDRANT_HOST, qdrantPort: QDRANT_PORT, collection: COLLECTION });
|
|
23
25
|
|
|
24
26
|
await app.listen({ port: PORT, host: "127.0.0.1" });
|
|
25
27
|
console.log(`Memory server at http://127.0.0.1:${PORT}`);
|
|
@@ -127,3 +127,18 @@ async function doSearch(
|
|
|
127
127
|
payload: (r.payload ?? {}) as PointPayload,
|
|
128
128
|
}));
|
|
129
129
|
}
|
|
130
|
+
|
|
131
|
+
export async function retrievePointsById(
|
|
132
|
+
client: QdrantClient,
|
|
133
|
+
collectionName: string,
|
|
134
|
+
ids: string[]
|
|
135
|
+
): Promise<{ id: string; payload: PointPayload }[]> {
|
|
136
|
+
if (ids.length === 0) return [];
|
|
137
|
+
const retrieve = (client as { retrieve?: (c: string, o: { ids: string[] }) => Promise<{ id: unknown; payload?: Record<string, unknown> }[]> }).retrieve;
|
|
138
|
+
if (typeof retrieve !== "function") return [];
|
|
139
|
+
const result = await retrieve.call(client, collectionName, { ids, with_payload: true });
|
|
140
|
+
return (result ?? []).map((r) => ({
|
|
141
|
+
id: (r.id as string) ?? String(r.id),
|
|
142
|
+
payload: (r.payload ?? {}) as PointPayload,
|
|
143
|
+
}));
|
|
144
|
+
}
|