@folterung/project-memory 0.1.0
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/.memignore.example +11 -0
- package/README.md +48 -0
- package/docker-compose.yml +17 -0
- package/package.json +36 -0
- package/packages/cli/bin/mem.js +6 -0
- package/packages/cli/coverage/lcov-report/base.css +224 -0
- package/packages/cli/coverage/lcov-report/block-navigation.js +87 -0
- package/packages/cli/coverage/lcov-report/chunking/chunker.ts.html +538 -0
- package/packages/cli/coverage/lcov-report/chunking/hash.ts.html +148 -0
- package/packages/cli/coverage/lcov-report/chunking/index.html +146 -0
- package/packages/cli/coverage/lcov-report/chunking/types.ts.html +214 -0
- package/packages/cli/coverage/lcov-report/config/index.html +131 -0
- package/packages/cli/coverage/lcov-report/config/load.ts.html +184 -0
- package/packages/cli/coverage/lcov-report/config/types.ts.html +232 -0
- package/packages/cli/coverage/lcov-report/embedding/index.html +116 -0
- package/packages/cli/coverage/lcov-report/embedding/stub.ts.html +181 -0
- package/packages/cli/coverage/lcov-report/favicon.png +0 -0
- package/packages/cli/coverage/lcov-report/index.html +161 -0
- package/packages/cli/coverage/lcov-report/prettify.css +1 -0
- package/packages/cli/coverage/lcov-report/prettify.js +2 -0
- package/packages/cli/coverage/lcov-report/scope/allowlist.ts.html +199 -0
- package/packages/cli/coverage/lcov-report/scope/ignore.ts.html +343 -0
- package/packages/cli/coverage/lcov-report/scope/index.html +131 -0
- package/packages/cli/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/packages/cli/coverage/lcov-report/sorter.js +210 -0
- package/packages/cli/coverage/lcov.info +669 -0
- package/packages/cli/coverage/tmp/coverage-25917-1770055893226-0.json +1 -0
- package/packages/cli/coverage/tmp/coverage-25918-1770055893272-0.json +1 -0
- package/packages/cli/coverage/tmp/coverage-25919-1770055893273-0.json +1 -0
- package/packages/cli/coverage/tmp/coverage-25920-1770055893271-0.json +1 -0
- package/packages/cli/coverage/tmp/coverage-25921-1770055893279-0.json +1 -0
- package/packages/cli/coverage/tmp/coverage-25922-1770055893272-0.json +1 -0
- package/packages/cli/coverage/tmp/coverage-25923-1770055893275-0.json +1 -0
- package/packages/cli/coverage/tmp/coverage-25924-1770055893294-0.json +1 -0
- package/packages/cli/coverage/tmp/coverage-25925-1770055893290-0.json +1 -0
- package/packages/cli/dist/chunking/chunker.d.ts +2 -0
- package/packages/cli/dist/chunking/chunker.js +142 -0
- package/packages/cli/dist/chunking/chunker.js.map +1 -0
- package/packages/cli/dist/chunking/chunker.test.d.ts +1 -0
- package/packages/cli/dist/chunking/chunker.test.js +50 -0
- package/packages/cli/dist/chunking/chunker.test.js.map +1 -0
- package/packages/cli/dist/chunking/hash.d.ts +3 -0
- package/packages/cli/dist/chunking/hash.js +17 -0
- package/packages/cli/dist/chunking/hash.js.map +1 -0
- package/packages/cli/dist/chunking/hash.test.d.ts +1 -0
- package/packages/cli/dist/chunking/hash.test.js +36 -0
- package/packages/cli/dist/chunking/hash.test.js.map +1 -0
- package/packages/cli/dist/chunking/types.d.ts +19 -0
- package/packages/cli/dist/chunking/types.js +24 -0
- package/packages/cli/dist/chunking/types.js.map +1 -0
- package/packages/cli/dist/chunking/types.test.d.ts +1 -0
- package/packages/cli/dist/chunking/types.test.js +25 -0
- package/packages/cli/dist/chunking/types.test.js.map +1 -0
- package/packages/cli/dist/cli/index.d.ts +1 -0
- package/packages/cli/dist/cli/index.js +67 -0
- package/packages/cli/dist/cli/index.js.map +1 -0
- package/packages/cli/dist/commands/deep-index.d.ts +1 -0
- package/packages/cli/dist/commands/deep-index.js +17 -0
- package/packages/cli/dist/commands/deep-index.js.map +1 -0
- package/packages/cli/dist/commands/doctor.d.ts +1 -0
- package/packages/cli/dist/commands/doctor.js +27 -0
- package/packages/cli/dist/commands/doctor.js.map +1 -0
- package/packages/cli/dist/commands/down.d.ts +1 -0
- package/packages/cli/dist/commands/down.js +13 -0
- package/packages/cli/dist/commands/down.js.map +1 -0
- package/packages/cli/dist/commands/explain.d.ts +1 -0
- package/packages/cli/dist/commands/explain.js +23 -0
- package/packages/cli/dist/commands/explain.js.map +1 -0
- package/packages/cli/dist/commands/init.d.ts +1 -0
- package/packages/cli/dist/commands/init.js +35 -0
- package/packages/cli/dist/commands/init.js.map +1 -0
- package/packages/cli/dist/commands/query.d.ts +1 -0
- package/packages/cli/dist/commands/query.js +44 -0
- package/packages/cli/dist/commands/query.js.map +1 -0
- package/packages/cli/dist/commands/reset.d.ts +3 -0
- package/packages/cli/dist/commands/reset.js +28 -0
- package/packages/cli/dist/commands/reset.js.map +1 -0
- package/packages/cli/dist/commands/scaffold.d.ts +1 -0
- package/packages/cli/dist/commands/scaffold.js +52 -0
- package/packages/cli/dist/commands/scaffold.js.map +1 -0
- package/packages/cli/dist/commands/update.d.ts +5 -0
- package/packages/cli/dist/commands/update.js +23 -0
- package/packages/cli/dist/commands/update.js.map +1 -0
- package/packages/cli/dist/commands/watch.d.ts +1 -0
- package/packages/cli/dist/commands/watch.js +46 -0
- package/packages/cli/dist/commands/watch.js.map +1 -0
- package/packages/cli/dist/config/defaults.d.ts +2 -0
- package/packages/cli/dist/config/defaults.js +47 -0
- package/packages/cli/dist/config/defaults.js.map +1 -0
- package/packages/cli/dist/config/load.d.ts +4 -0
- package/packages/cli/dist/config/load.js +29 -0
- package/packages/cli/dist/config/load.js.map +1 -0
- package/packages/cli/dist/config/load.test.d.ts +1 -0
- package/packages/cli/dist/config/load.test.js +51 -0
- package/packages/cli/dist/config/load.test.js.map +1 -0
- package/packages/cli/dist/config/types.d.ts +32 -0
- package/packages/cli/dist/config/types.js +24 -0
- package/packages/cli/dist/config/types.js.map +1 -0
- package/packages/cli/dist/config/types.test.d.ts +1 -0
- package/packages/cli/dist/config/types.test.js +31 -0
- package/packages/cli/dist/config/types.test.js.map +1 -0
- package/packages/cli/dist/docker/compose.d.ts +5 -0
- package/packages/cli/dist/docker/compose.js +75 -0
- package/packages/cli/dist/docker/compose.js.map +1 -0
- package/packages/cli/dist/embedding/stub.d.ts +6 -0
- package/packages/cli/dist/embedding/stub.js +29 -0
- package/packages/cli/dist/embedding/stub.js.map +1 -0
- package/packages/cli/dist/embedding/stub.test.d.ts +1 -0
- package/packages/cli/dist/embedding/stub.test.js +31 -0
- package/packages/cli/dist/embedding/stub.test.js.map +1 -0
- package/packages/cli/dist/phase1/pipeline.d.ts +1 -0
- package/packages/cli/dist/phase1/pipeline.js +145 -0
- package/packages/cli/dist/phase1/pipeline.js.map +1 -0
- package/packages/cli/dist/phase2/deep-index.d.ts +1 -0
- package/packages/cli/dist/phase2/deep-index.js +105 -0
- package/packages/cli/dist/phase2/deep-index.js.map +1 -0
- package/packages/cli/dist/qdrant/upsert.d.ts +14 -0
- package/packages/cli/dist/qdrant/upsert.js +30 -0
- package/packages/cli/dist/qdrant/upsert.js.map +1 -0
- package/packages/cli/dist/scope/allowlist.d.ts +11 -0
- package/packages/cli/dist/scope/allowlist.js +36 -0
- package/packages/cli/dist/scope/allowlist.js.map +1 -0
- package/packages/cli/dist/scope/allowlist.test.d.ts +1 -0
- package/packages/cli/dist/scope/allowlist.test.js +23 -0
- package/packages/cli/dist/scope/allowlist.test.js.map +1 -0
- package/packages/cli/dist/scope/ignore.d.ts +2 -0
- package/packages/cli/dist/scope/ignore.js +80 -0
- package/packages/cli/dist/scope/ignore.js.map +1 -0
- package/packages/cli/dist/scope/ignore.test.d.ts +1 -0
- package/packages/cli/dist/scope/ignore.test.js +71 -0
- package/packages/cli/dist/scope/ignore.test.js.map +1 -0
- package/packages/cli/package.json +46 -0
- package/packages/cli/src/chunking/chunker.test.ts +54 -0
- package/packages/cli/src/chunking/chunker.ts +151 -0
- package/packages/cli/src/chunking/hash.test.ts +41 -0
- package/packages/cli/src/chunking/hash.ts +21 -0
- package/packages/cli/src/chunking/types.test.ts +28 -0
- package/packages/cli/src/chunking/types.ts +43 -0
- package/packages/cli/src/cli/index.ts +79 -0
- package/packages/cli/src/commands/deep-index.ts +16 -0
- package/packages/cli/src/commands/doctor.ts +32 -0
- package/packages/cli/src/commands/down.ts +12 -0
- package/packages/cli/src/commands/explain.ts +22 -0
- package/packages/cli/src/commands/init.ts +37 -0
- package/packages/cli/src/commands/query.ts +49 -0
- package/packages/cli/src/commands/reset.ts +30 -0
- package/packages/cli/src/commands/scaffold.ts +55 -0
- package/packages/cli/src/commands/update.ts +22 -0
- package/packages/cli/src/commands/watch.ts +47 -0
- package/packages/cli/src/config/defaults.ts +49 -0
- package/packages/cli/src/config/load.test.ts +55 -0
- package/packages/cli/src/config/load.ts +33 -0
- package/packages/cli/src/config/types.test.ts +43 -0
- package/packages/cli/src/config/types.ts +49 -0
- package/packages/cli/src/docker/compose.ts +75 -0
- package/packages/cli/src/embedding/stub.test.ts +35 -0
- package/packages/cli/src/embedding/stub.ts +32 -0
- package/packages/cli/src/phase1/pipeline.ts +164 -0
- package/packages/cli/src/phase2/deep-index.ts +120 -0
- package/packages/cli/src/qdrant/upsert.ts +45 -0
- package/packages/cli/src/scope/allowlist.test.ts +25 -0
- package/packages/cli/src/scope/allowlist.ts +38 -0
- package/packages/cli/src/scope/ignore.test.ts +71 -0
- package/packages/cli/src/scope/ignore.ts +86 -0
- package/packages/cli/tsconfig.json +16 -0
- package/packages/server/coverage/lcov-report/base.css +224 -0
- package/packages/server/coverage/lcov-report/block-navigation.js +87 -0
- package/packages/server/coverage/lcov-report/favicon.png +0 -0
- package/packages/server/coverage/lcov-report/index.html +116 -0
- package/packages/server/coverage/lcov-report/prettify.css +1 -0
- package/packages/server/coverage/lcov-report/prettify.js +2 -0
- package/packages/server/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/packages/server/coverage/lcov-report/sorter.js +210 -0
- package/packages/server/coverage/lcov-report/stub.ts.html +184 -0
- package/packages/server/coverage/lcov.info +57 -0
- package/packages/server/coverage/tmp/coverage-26012-1770055894042-0.json +1 -0
- package/packages/server/coverage/tmp/coverage-26013-1770055894077-0.json +1 -0
- package/packages/server/dist/api/explain.d.ts +6 -0
- package/packages/server/dist/api/explain.js +16 -0
- package/packages/server/dist/api/explain.js.map +1 -0
- package/packages/server/dist/api/health.d.ts +7 -0
- package/packages/server/dist/api/health.js +43 -0
- package/packages/server/dist/api/health.js.map +1 -0
- package/packages/server/dist/api/refresh.d.ts +2 -0
- package/packages/server/dist/api/refresh.js +8 -0
- package/packages/server/dist/api/refresh.js.map +1 -0
- package/packages/server/dist/api/search.d.ts +28 -0
- package/packages/server/dist/api/search.js +36 -0
- package/packages/server/dist/api/search.js.map +1 -0
- package/packages/server/dist/api/system.d.ts +6 -0
- package/packages/server/dist/api/system.js +17 -0
- package/packages/server/dist/api/system.js.map +1 -0
- package/packages/server/dist/embedding/stub.d.ts +7 -0
- package/packages/server/dist/embedding/stub.js +30 -0
- package/packages/server/dist/embedding/stub.js.map +1 -0
- package/packages/server/dist/embedding/stub.test.d.ts +1 -0
- package/packages/server/dist/embedding/stub.test.js +25 -0
- package/packages/server/dist/embedding/stub.test.js.map +1 -0
- package/packages/server/dist/index.d.ts +1 -0
- package/packages/server/dist/index.js +26 -0
- package/packages/server/dist/index.js.map +1 -0
- package/packages/server/dist/qdrant/client.d.ts +24 -0
- package/packages/server/dist/qdrant/client.js +55 -0
- package/packages/server/dist/qdrant/client.js.map +1 -0
- package/packages/server/package.json +37 -0
- package/packages/server/src/api/explain.ts +24 -0
- package/packages/server/src/api/health.ts +50 -0
- package/packages/server/src/api/refresh.ts +9 -0
- package/packages/server/src/api/search.ts +57 -0
- package/packages/server/src/api/system.ts +25 -0
- package/packages/server/src/embedding/stub.test.ts +28 -0
- package/packages/server/src/embedding/stub.ts +33 -0
- package/packages/server/src/index.ts +29 -0
- package/packages/server/src/qdrant/client.ts +92 -0
- package/packages/server/tsconfig.json +16 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@project-memory/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Project Memory CLI — init, scaffold, deep-index, search",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mem": "bin/mem.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsc --watch",
|
|
13
|
+
"test": "npm run build && c8 node --test dist/",
|
|
14
|
+
"test:no-coverage": "npm run build && node --test dist/"
|
|
15
|
+
},
|
|
16
|
+
"c8": {
|
|
17
|
+
"exclude": ["**/*.test.js", "**/cli/index.js", "**/bin/**"],
|
|
18
|
+
"include": ["dist/**/*.js"],
|
|
19
|
+
"check-coverage": true,
|
|
20
|
+
"lines": 85,
|
|
21
|
+
"functions": 85,
|
|
22
|
+
"branches": 85,
|
|
23
|
+
"statements": 85,
|
|
24
|
+
"reporter": ["text", "lcov"],
|
|
25
|
+
"reports-dir": "coverage"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"commander": "^12.0.0",
|
|
29
|
+
"fast-glob": "^3.3.2",
|
|
30
|
+
"minimatch": "^9.0.3",
|
|
31
|
+
"js-yaml": "^4.1.0",
|
|
32
|
+
"chalk": "^5.3.0",
|
|
33
|
+
"dotenv": "^16.3.1",
|
|
34
|
+
"@qdrant/js-client-rest": "^1.9.0",
|
|
35
|
+
"chokidar": "^3.5.3"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/js-yaml": "^4.0.9",
|
|
39
|
+
"@types/node": "^20.10.0",
|
|
40
|
+
"c8": "^9.1.0",
|
|
41
|
+
"typescript": "^5.3.0"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=18"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { strict as assert } from "node:assert";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import { chunkFile } from "./chunker.js";
|
|
4
|
+
import { getSchemaVersion } from "./hash.js";
|
|
5
|
+
|
|
6
|
+
describe("chunker", () => {
|
|
7
|
+
it("chunkFile returns chunks with required metadata", () => {
|
|
8
|
+
const chunks = chunkFile("src/foo.ts", "const x = 1;\nconst y = 2;\n");
|
|
9
|
+
assert.ok(Array.isArray(chunks));
|
|
10
|
+
assert.ok(chunks.length >= 1);
|
|
11
|
+
for (const c of chunks) {
|
|
12
|
+
assert.strictEqual(typeof c.id, "string");
|
|
13
|
+
assert.strictEqual(typeof c.text, "string");
|
|
14
|
+
assert.strictEqual(c.metadata.path, "src/foo.ts");
|
|
15
|
+
assert.strictEqual(c.metadata.language, "typescript");
|
|
16
|
+
assert.strictEqual(c.metadata.kind, "code");
|
|
17
|
+
assert.strictEqual(c.metadata.schema_version, getSchemaVersion());
|
|
18
|
+
assert.ok(c.metadata.hash.length > 0);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("chunkFile for config returns single chunk", () => {
|
|
23
|
+
const chunks = chunkFile("config.json", '{"a":1}');
|
|
24
|
+
assert.strictEqual(chunks.length, 1);
|
|
25
|
+
assert.strictEqual(chunks[0].metadata.kind, "config");
|
|
26
|
+
assert.strictEqual(chunks[0].text, '{"a":1}');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("chunkFile for markdown chunks by heading", () => {
|
|
30
|
+
const content = "# Title\n\nPara 1.\n\n## Section\n\nPara 2.\n";
|
|
31
|
+
const chunks = chunkFile("doc.md", content);
|
|
32
|
+
assert.ok(chunks.length >= 1);
|
|
33
|
+
const hasDoc = chunks.some((c) => c.metadata.kind === "doc");
|
|
34
|
+
assert.ok(hasDoc);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("chunkFile is deterministic for same input", () => {
|
|
38
|
+
const input = "same content\n";
|
|
39
|
+
const path = "src/same.ts";
|
|
40
|
+
const a = chunkFile(path, input);
|
|
41
|
+
const b = chunkFile(path, input);
|
|
42
|
+
assert.strictEqual(a.length, b.length);
|
|
43
|
+
for (let i = 0; i < a.length; i++) {
|
|
44
|
+
assert.strictEqual(a[i].id, b[i].id);
|
|
45
|
+
assert.strictEqual(a[i].metadata.hash, b[i].metadata.hash);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("chunkFile infers test kind for .test. path", () => {
|
|
50
|
+
const chunks = chunkFile("src/foo.test.ts", "describe('x', () => {});");
|
|
51
|
+
assert.ok(chunks.length >= 1);
|
|
52
|
+
assert.strictEqual(chunks[0].metadata.kind, "test");
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { hashFileContent, chunkIdFromParts, getSchemaVersion } from "./hash.js";
|
|
3
|
+
import type { Chunk, ChunkKind } from "./types.js";
|
|
4
|
+
import { getModuleFromPath, getLanguageFromPath } from "./types.js";
|
|
5
|
+
|
|
6
|
+
const SOFT_MAX_LINES = 400;
|
|
7
|
+
const MIN_LINES = 50;
|
|
8
|
+
|
|
9
|
+
function inferKind(path: string, language: string): ChunkKind {
|
|
10
|
+
const lower = path.toLowerCase();
|
|
11
|
+
if (lower.includes(".test.") || lower.includes(".spec.") || lower.includes("__tests__")) return "test";
|
|
12
|
+
if (lower.endsWith(".md") || lower.endsWith(".mdx")) return "doc";
|
|
13
|
+
if (lower.endsWith(".json") || lower.endsWith(".yaml") || lower.endsWith(".yml")) return "config";
|
|
14
|
+
if (lower.includes("schema")) return "schema";
|
|
15
|
+
return "code";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function chunkByLineWindows(
|
|
19
|
+
path: string,
|
|
20
|
+
content: string,
|
|
21
|
+
fileHash: string,
|
|
22
|
+
kind: ChunkKind
|
|
23
|
+
): Chunk[] {
|
|
24
|
+
const language = getLanguageFromPath(path);
|
|
25
|
+
const module = getModuleFromPath(path);
|
|
26
|
+
const lines = content.split(/\r?\n/);
|
|
27
|
+
const chunks: Chunk[] = [];
|
|
28
|
+
let start = 0;
|
|
29
|
+
let index = 0;
|
|
30
|
+
while (start < lines.length) {
|
|
31
|
+
const windowSize = Math.min(SOFT_MAX_LINES, lines.length - start);
|
|
32
|
+
const end = start + windowSize;
|
|
33
|
+
const windowLines = lines.slice(start, end);
|
|
34
|
+
const text = windowLines.join("\n");
|
|
35
|
+
const partHash = createHash("sha256").update(text, "utf8").digest("hex").slice(0, 16);
|
|
36
|
+
const part = `L${start}-${end}-${partHash}`;
|
|
37
|
+
const id = chunkIdFromParts(path, part, fileHash);
|
|
38
|
+
chunks.push({
|
|
39
|
+
id,
|
|
40
|
+
text,
|
|
41
|
+
metadata: {
|
|
42
|
+
path,
|
|
43
|
+
language,
|
|
44
|
+
module,
|
|
45
|
+
kind,
|
|
46
|
+
hash: fileHash,
|
|
47
|
+
schema_version: getSchemaVersion(),
|
|
48
|
+
startLine: start + 1,
|
|
49
|
+
endLine: end,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
start = end;
|
|
53
|
+
index++;
|
|
54
|
+
}
|
|
55
|
+
return chunks;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function chunkMarkdownByHeadings(path: string, content: string, fileHash: string): Chunk[] {
|
|
59
|
+
const language = "markdown";
|
|
60
|
+
const module = getModuleFromPath(path);
|
|
61
|
+
const lines = content.split(/\r?\n/);
|
|
62
|
+
const chunks: Chunk[] = [];
|
|
63
|
+
let currentStart = 0;
|
|
64
|
+
let currentLines: string[] = [];
|
|
65
|
+
let headingLevel = 0;
|
|
66
|
+
|
|
67
|
+
function flush(symbol?: string, endLine?: number) {
|
|
68
|
+
if (currentLines.length === 0) return;
|
|
69
|
+
const text = currentLines.join("\n");
|
|
70
|
+
const partHash = createHash("sha256").update(text, "utf8").digest("hex").slice(0, 16);
|
|
71
|
+
const part = symbol ?? `H${currentStart}-${partHash}`;
|
|
72
|
+
const id = chunkIdFromParts(path, part, fileHash);
|
|
73
|
+
chunks.push({
|
|
74
|
+
id,
|
|
75
|
+
text,
|
|
76
|
+
metadata: {
|
|
77
|
+
path,
|
|
78
|
+
language,
|
|
79
|
+
symbol: symbol ?? undefined,
|
|
80
|
+
module,
|
|
81
|
+
kind: "doc",
|
|
82
|
+
hash: fileHash,
|
|
83
|
+
schema_version: getSchemaVersion(),
|
|
84
|
+
startLine: currentStart + 1,
|
|
85
|
+
endLine: endLine ?? currentStart + currentLines.length,
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
for (let i = 0; i < lines.length; i++) {
|
|
91
|
+
const line = lines[i];
|
|
92
|
+
const match = line.match(/^(#{1,6})\s+(.+)$/);
|
|
93
|
+
if (match) {
|
|
94
|
+
const level = match[1].length;
|
|
95
|
+
const title = match[2].trim();
|
|
96
|
+
if (currentLines.length > 0) {
|
|
97
|
+
flush(headingLevel > 0 ? title : undefined, i);
|
|
98
|
+
}
|
|
99
|
+
currentStart = i;
|
|
100
|
+
currentLines = [line];
|
|
101
|
+
headingLevel = level;
|
|
102
|
+
} else {
|
|
103
|
+
currentLines.push(line);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (currentLines.length > 0) {
|
|
107
|
+
flush(headingLevel > 0 ? undefined : "body", lines.length);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (chunks.length === 0 && content.trim()) {
|
|
111
|
+
return chunkByLineWindows(path, content, fileHash, "doc");
|
|
112
|
+
}
|
|
113
|
+
return chunks;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function chunkConfigAsSingle(path: string, content: string, fileHash: string): Chunk[] {
|
|
117
|
+
const language = getLanguageFromPath(path);
|
|
118
|
+
const module = getModuleFromPath(path);
|
|
119
|
+
const part = "config";
|
|
120
|
+
const id = chunkIdFromParts(path, part, fileHash);
|
|
121
|
+
return [
|
|
122
|
+
{
|
|
123
|
+
id,
|
|
124
|
+
text: content,
|
|
125
|
+
metadata: {
|
|
126
|
+
path,
|
|
127
|
+
language,
|
|
128
|
+
module,
|
|
129
|
+
kind: "config",
|
|
130
|
+
hash: fileHash,
|
|
131
|
+
schema_version: getSchemaVersion(),
|
|
132
|
+
startLine: 1,
|
|
133
|
+
endLine: content.split(/\r?\n/).length,
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function chunkFile(path: string, content: string): Chunk[] {
|
|
140
|
+
const fileHash = hashFileContent(content);
|
|
141
|
+
const language = getLanguageFromPath(path);
|
|
142
|
+
const kind = inferKind(path, language);
|
|
143
|
+
|
|
144
|
+
if (kind === "doc" && (path.endsWith(".md") || path.endsWith(".mdx"))) {
|
|
145
|
+
return chunkMarkdownByHeadings(path, content, fileHash);
|
|
146
|
+
}
|
|
147
|
+
if (kind === "config") {
|
|
148
|
+
return chunkConfigAsSingle(path, content, fileHash);
|
|
149
|
+
}
|
|
150
|
+
return chunkByLineWindows(path, content, fileHash, kind);
|
|
151
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { strict as assert } from "node:assert";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import { hashFileContent, chunkIdFromParts, getSchemaVersion } from "./hash.js";
|
|
4
|
+
|
|
5
|
+
describe("chunking hash", () => {
|
|
6
|
+
it("hashFileContent is deterministic", () => {
|
|
7
|
+
const content = "hello\nworld\n";
|
|
8
|
+
const a = hashFileContent(content);
|
|
9
|
+
const b = hashFileContent(content);
|
|
10
|
+
assert.strictEqual(a, b);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("hashFileContent normalizes CRLF to LF", () => {
|
|
14
|
+
const crlf = "a\r\nb\r\n";
|
|
15
|
+
const lf = "a\nb\n";
|
|
16
|
+
assert.strictEqual(hashFileContent(crlf), hashFileContent(lf));
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("hashFileContent differs for different content", () => {
|
|
20
|
+
const h1 = hashFileContent("foo");
|
|
21
|
+
const h2 = hashFileContent("bar");
|
|
22
|
+
assert.notStrictEqual(h1, h2);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("chunkIdFromParts is deterministic", () => {
|
|
26
|
+
const id1 = chunkIdFromParts("src/a.ts", "L0-10-abc", "filehash123");
|
|
27
|
+
const id2 = chunkIdFromParts("src/a.ts", "L0-10-abc", "filehash123");
|
|
28
|
+
assert.strictEqual(id1, id2);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("chunkIdFromParts returns 32-char hex", () => {
|
|
32
|
+
const id = chunkIdFromParts("p", "part", "hash");
|
|
33
|
+
assert.match(id, /^[a-f0-9]{32}$/);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("getSchemaVersion returns number", () => {
|
|
37
|
+
const v = getSchemaVersion();
|
|
38
|
+
assert.strictEqual(typeof v, "number");
|
|
39
|
+
assert.ok(v >= 1);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
const SCHEMA_VERSION = 1;
|
|
4
|
+
|
|
5
|
+
function normalizeContent(content: string): string {
|
|
6
|
+
return content.replace(/\r\n/g, "\n").trimEnd() + "\n";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function hashFileContent(content: string): string {
|
|
10
|
+
const normalized = normalizeContent(content);
|
|
11
|
+
return createHash("sha256").update(normalized, "utf8").digest("hex");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getSchemaVersion(): number {
|
|
15
|
+
return SCHEMA_VERSION;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function chunkIdFromParts(path: string, part: string, contentHash: string): string {
|
|
19
|
+
const combined = `${path}:${part}:${contentHash}`;
|
|
20
|
+
return createHash("sha256").update(combined, "utf8").digest("hex").slice(0, 32);
|
|
21
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { strict as assert } from "node:assert";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import { getModuleFromPath, getLanguageFromPath } from "./types.js";
|
|
4
|
+
|
|
5
|
+
describe("chunking types", () => {
|
|
6
|
+
it("getModuleFromPath returns dir for nested path", () => {
|
|
7
|
+
assert.strictEqual(getModuleFromPath("src/foo/bar.ts"), "src/foo");
|
|
8
|
+
assert.strictEqual(getModuleFromPath("a/b/c/d.js"), "a/b/c");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("getModuleFromPath returns path for single segment", () => {
|
|
12
|
+
assert.strictEqual(getModuleFromPath("index.ts"), "index.ts");
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("getLanguageFromPath maps extensions", () => {
|
|
16
|
+
assert.strictEqual(getLanguageFromPath("file.ts"), "typescript");
|
|
17
|
+
assert.strictEqual(getLanguageFromPath("file.tsx"), "typescript");
|
|
18
|
+
assert.strictEqual(getLanguageFromPath("file.js"), "javascript");
|
|
19
|
+
assert.strictEqual(getLanguageFromPath("file.json"), "json");
|
|
20
|
+
assert.strictEqual(getLanguageFromPath("file.yaml"), "yaml");
|
|
21
|
+
assert.strictEqual(getLanguageFromPath("file.yml"), "yaml");
|
|
22
|
+
assert.strictEqual(getLanguageFromPath("file.md"), "markdown");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("getLanguageFromPath returns ext for unknown", () => {
|
|
26
|
+
assert.strictEqual(getLanguageFromPath("file.xyz"), "xyz");
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export type ChunkKind = "code" | "doc" | "config" | "schema" | "test" | "system_map" | "module_summary" | "entrypoint";
|
|
2
|
+
|
|
3
|
+
export interface ChunkMetadata {
|
|
4
|
+
path: string;
|
|
5
|
+
language: string;
|
|
6
|
+
symbol?: string;
|
|
7
|
+
module: string;
|
|
8
|
+
kind: ChunkKind;
|
|
9
|
+
hash: string;
|
|
10
|
+
schema_version: number;
|
|
11
|
+
startLine?: number;
|
|
12
|
+
endLine?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface Chunk {
|
|
16
|
+
id: string;
|
|
17
|
+
text: string;
|
|
18
|
+
metadata: ChunkMetadata;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function getModuleFromPath(path: string): string {
|
|
22
|
+
const parts = path.replace(/\\/g, "/").split("/");
|
|
23
|
+
if (parts.length <= 1) return path;
|
|
24
|
+
return parts.slice(0, -1).join("/");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getLanguageFromPath(path: string): string {
|
|
28
|
+
const ext = path.replace(/^.*\./, "").toLowerCase();
|
|
29
|
+
const map: Record<string, string> = {
|
|
30
|
+
ts: "typescript",
|
|
31
|
+
tsx: "typescript",
|
|
32
|
+
js: "javascript",
|
|
33
|
+
jsx: "javascript",
|
|
34
|
+
mjs: "javascript",
|
|
35
|
+
cjs: "javascript",
|
|
36
|
+
json: "json",
|
|
37
|
+
yaml: "yaml",
|
|
38
|
+
yml: "yaml",
|
|
39
|
+
md: "markdown",
|
|
40
|
+
mdx: "markdown",
|
|
41
|
+
};
|
|
42
|
+
return map[ext] ?? ext;
|
|
43
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { program } from "commander";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { cwd } from "node:process";
|
|
4
|
+
import { runInit } from "../commands/init.js";
|
|
5
|
+
import { runScaffold } from "../commands/scaffold.js";
|
|
6
|
+
import { runDown } from "../commands/down.js";
|
|
7
|
+
import { runDoctor } from "../commands/doctor.js";
|
|
8
|
+
import { runReset } from "../commands/reset.js";
|
|
9
|
+
import { runDeepIndexCommand } from "../commands/deep-index.js";
|
|
10
|
+
import { runUpdate } from "../commands/update.js";
|
|
11
|
+
import { runWatch } from "../commands/watch.js";
|
|
12
|
+
import { runQuery } from "../commands/query.js";
|
|
13
|
+
import { runExplain } from "../commands/explain.js";
|
|
14
|
+
|
|
15
|
+
const root = cwd();
|
|
16
|
+
|
|
17
|
+
program
|
|
18
|
+
.name("mem")
|
|
19
|
+
.description("Project Memory — local-first project understanding for Cursor")
|
|
20
|
+
.version("0.1.0");
|
|
21
|
+
|
|
22
|
+
program
|
|
23
|
+
.command("init")
|
|
24
|
+
.description("Create .mem/config.yml and .memignore")
|
|
25
|
+
.action(() => runInit(root));
|
|
26
|
+
|
|
27
|
+
program
|
|
28
|
+
.command("scaffold")
|
|
29
|
+
.description("Phase 1: start Qdrant, apply scope+ignore, build index, start server")
|
|
30
|
+
.option("--include <globs...>", "Override indexing.include")
|
|
31
|
+
.option("--exclude <globs...>", "Override indexing.exclude")
|
|
32
|
+
.option("--write-config", "Persist include/exclude to .mem/config.yml")
|
|
33
|
+
.action(async (opts) => runScaffold(root));
|
|
34
|
+
|
|
35
|
+
program
|
|
36
|
+
.command("down")
|
|
37
|
+
.description("Stop Docker Compose containers")
|
|
38
|
+
.action(() => runDown(root));
|
|
39
|
+
|
|
40
|
+
program
|
|
41
|
+
.command("doctor")
|
|
42
|
+
.description("Check Docker, Qdrant, config, scope/ignore")
|
|
43
|
+
.action(async () => runDoctor(root));
|
|
44
|
+
|
|
45
|
+
program
|
|
46
|
+
.command("reset")
|
|
47
|
+
.description("Wipe index state and deep-index progress")
|
|
48
|
+
.option("-v, --volumes", "Hint: use docker compose down -v to wipe volumes")
|
|
49
|
+
.action((opts) => runReset(root, { volumes: opts.volumes }));
|
|
50
|
+
|
|
51
|
+
program
|
|
52
|
+
.command("deep-index")
|
|
53
|
+
.description("Phase 2: embed remaining chunks (resumable)")
|
|
54
|
+
.action(() => runDeepIndexCommand(root));
|
|
55
|
+
|
|
56
|
+
program
|
|
57
|
+
.command("update")
|
|
58
|
+
.description("Incremental update: re-run Phase 1")
|
|
59
|
+
.action(() => runUpdate(root));
|
|
60
|
+
|
|
61
|
+
program
|
|
62
|
+
.command("watch")
|
|
63
|
+
.description("Watch filesystem and run update on change")
|
|
64
|
+
.action(() => runWatch(root));
|
|
65
|
+
|
|
66
|
+
program
|
|
67
|
+
.command("query <text>")
|
|
68
|
+
.description("Call server /search with text")
|
|
69
|
+
.action((text: string) => runQuery(root, text ?? ""));
|
|
70
|
+
|
|
71
|
+
program
|
|
72
|
+
.command("explain <symbolOrPath>")
|
|
73
|
+
.description("Call server /explain for symbol or path")
|
|
74
|
+
.action((symbolOrPath: string) => runExplain(root, symbolOrPath ?? ""));
|
|
75
|
+
|
|
76
|
+
export async function run(): Promise<void> {
|
|
77
|
+
await program.parseAsync();
|
|
78
|
+
if (!process.argv.slice(2).length) program.outputHelp();
|
|
79
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { configExists } from "../config/load.js";
|
|
3
|
+
import { runDeepIndex } from "../phase2/deep-index.js";
|
|
4
|
+
|
|
5
|
+
export async function runDeepIndexCommand(cwd: string): Promise<void> {
|
|
6
|
+
if (!configExists(cwd)) {
|
|
7
|
+
console.error(chalk.red("No config found. Run"), chalk.cyan("mem init"), chalk.red("then"), chalk.cyan("mem scaffold"), chalk.red("first."));
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
await runDeepIndex(cwd);
|
|
12
|
+
} catch (err) {
|
|
13
|
+
console.error(chalk.red("Deep index failed:"), (err as Error).message);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { configExists, loadConfig } from "../config/load.js";
|
|
3
|
+
import { checkDockerAvailable, checkQdrantHealthy } from "../docker/compose.js";
|
|
4
|
+
|
|
5
|
+
export async function runDoctor(cwd: string): Promise<void> {
|
|
6
|
+
console.log(chalk.bold("Project Memory Doctor\n"));
|
|
7
|
+
|
|
8
|
+
const dockerOk = checkDockerAvailable(cwd);
|
|
9
|
+
console.log(dockerOk ? chalk.green("✓") : chalk.red("✗"), "Docker Compose available");
|
|
10
|
+
if (!dockerOk) {
|
|
11
|
+
console.log(chalk.dim(" Install Docker and Docker Compose."));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const qdrantOk = await checkQdrantHealthy();
|
|
15
|
+
console.log(qdrantOk ? chalk.green("✓") : chalk.yellow("○"), "Qdrant healthy (localhost:6333)");
|
|
16
|
+
if (!qdrantOk) {
|
|
17
|
+
console.log(chalk.dim(" Run mem scaffold to start Qdrant."));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const hasConfig = configExists(cwd);
|
|
21
|
+
console.log(hasConfig ? chalk.green("✓") : chalk.yellow("○"), "Config present (.mem/config.yml)");
|
|
22
|
+
if (!hasConfig) {
|
|
23
|
+
console.log(chalk.dim(" Run mem init first."));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (hasConfig) {
|
|
27
|
+
const config = loadConfig(cwd);
|
|
28
|
+
console.log(chalk.green("✓"), "Scope:", config.indexing?.include?.length ? config.indexing.include.join(", ") : "entire repo");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
console.log();
|
|
32
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { dockerComposeDown } from "../docker/compose.js";
|
|
3
|
+
|
|
4
|
+
export function runDown(cwd: string): void {
|
|
5
|
+
try {
|
|
6
|
+
dockerComposeDown(cwd);
|
|
7
|
+
console.log(chalk.green("Containers stopped."));
|
|
8
|
+
} catch (err) {
|
|
9
|
+
console.error(chalk.red("Failed to stop containers:"), (err as Error).message);
|
|
10
|
+
throw err;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_SERVER_URL = "http://127.0.0.1:31415";
|
|
4
|
+
|
|
5
|
+
export async function runExplain(_cwd: string, symbolOrPath: string): Promise<void> {
|
|
6
|
+
const baseUrl = process.env.MEM_SERVER_URL ?? DEFAULT_SERVER_URL;
|
|
7
|
+
try {
|
|
8
|
+
const res = await fetch(`${baseUrl}/explain/${encodeURIComponent(symbolOrPath)}`);
|
|
9
|
+
if (!res.ok) {
|
|
10
|
+
console.error(chalk.red("Explain failed:"), res.status);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
const data = (await res.json()) as { symbol: string; snippet: string; path?: string };
|
|
14
|
+
console.log(chalk.bold(data.symbol));
|
|
15
|
+
if (data.path) console.log(chalk.dim("Path:"), data.path);
|
|
16
|
+
console.log("\n", data.snippet);
|
|
17
|
+
} catch (e) {
|
|
18
|
+
console.error(chalk.red("Request failed:"), (e as Error).message);
|
|
19
|
+
console.log(chalk.dim("Ensure memory server is running (mem scaffold)."));
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { getDefaultConfigYaml, getDefaultMemignore } from "../config/defaults.js";
|
|
5
|
+
import { CONFIG_DIR, CONFIG_FILE, MEMIGNORE_FILE } from "../config/types.js";
|
|
6
|
+
|
|
7
|
+
export function runInit(cwd: string): void {
|
|
8
|
+
const configPath = join(cwd, CONFIG_FILE);
|
|
9
|
+
const memignorePath = join(cwd, MEMIGNORE_FILE);
|
|
10
|
+
const memDir = join(cwd, CONFIG_DIR);
|
|
11
|
+
const stateDir = join(memDir, "state");
|
|
12
|
+
|
|
13
|
+
if (!existsSync(memDir)) {
|
|
14
|
+
mkdirSync(memDir, { recursive: true });
|
|
15
|
+
console.log(chalk.green("Created"), CONFIG_DIR + "/");
|
|
16
|
+
}
|
|
17
|
+
if (!existsSync(stateDir)) {
|
|
18
|
+
mkdirSync(stateDir, { recursive: true });
|
|
19
|
+
console.log(chalk.green("Created"), CONFIG_DIR + "/state/");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!existsSync(configPath)) {
|
|
23
|
+
writeFileSync(configPath, getDefaultConfigYaml(), "utf8");
|
|
24
|
+
console.log(chalk.green("Created"), CONFIG_FILE);
|
|
25
|
+
} else {
|
|
26
|
+
console.log(chalk.dim("Config already exists:"), CONFIG_FILE);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!existsSync(memignorePath)) {
|
|
30
|
+
writeFileSync(memignorePath, getDefaultMemignore(), "utf8");
|
|
31
|
+
console.log(chalk.green("Created"), MEMIGNORE_FILE);
|
|
32
|
+
} else {
|
|
33
|
+
console.log(chalk.dim("Ignore file already exists:"), MEMIGNORE_FILE);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
console.log(chalk.green("\nProject Memory initialized. Run"), chalk.cyan("mem scaffold"), chalk.green("to build the index."));
|
|
37
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_SERVER_URL = "http://127.0.0.1:31415";
|
|
4
|
+
|
|
5
|
+
async function search(query: string, limit: number, baseUrl: string): Promise<void> {
|
|
6
|
+
const res = await fetch(`${baseUrl}/search`, {
|
|
7
|
+
method: "POST",
|
|
8
|
+
headers: { "Content-Type": "application/json" },
|
|
9
|
+
body: JSON.stringify({ query, limit }),
|
|
10
|
+
});
|
|
11
|
+
if (!res.ok) {
|
|
12
|
+
console.error(chalk.red("Search failed:"), res.status, await res.text());
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
const data = (await res.json()) as {
|
|
16
|
+
systemMaps?: Array<{ text: string; path?: string }>;
|
|
17
|
+
moduleSummaries?: Array<{ text: string; path?: string }>;
|
|
18
|
+
chunks?: Array<{ text: string; path?: string; score?: number }>;
|
|
19
|
+
};
|
|
20
|
+
if (data.systemMaps?.length) {
|
|
21
|
+
console.log(chalk.bold("System maps"));
|
|
22
|
+
for (const sm of data.systemMaps.slice(0, 3)) {
|
|
23
|
+
console.log(chalk.dim(sm.path ?? ""), "\n", sm.text.slice(0, 200) + (sm.text.length > 200 ? "..." : ""), "\n");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (data.moduleSummaries?.length) {
|
|
27
|
+
console.log(chalk.bold("Module summaries"));
|
|
28
|
+
for (const ms of data.moduleSummaries.slice(0, 5)) {
|
|
29
|
+
console.log(chalk.dim(ms.path ?? ""), "\n", ms.text.slice(0, 150) + (ms.text.length > 150 ? "..." : ""), "\n");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (data.chunks?.length) {
|
|
33
|
+
console.log(chalk.bold("Chunks"));
|
|
34
|
+
for (const c of data.chunks.slice(0, 5)) {
|
|
35
|
+
console.log(chalk.dim(c.path ?? ""), c.score != null ? chalk.dim(`(${c.score})`) : "", "\n", c.text.slice(0, 200) + (c.text.length > 200 ? "..." : ""), "\n");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function runQuery(_cwd: string, text: string): Promise<void> {
|
|
41
|
+
const baseUrl = process.env.MEM_SERVER_URL ?? DEFAULT_SERVER_URL;
|
|
42
|
+
try {
|
|
43
|
+
await search(text, 15, baseUrl);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
console.error(chalk.red("Request failed:"), (e as Error).message);
|
|
46
|
+
console.log(chalk.dim("Ensure memory server is running (mem scaffold)."));
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { rmSync, existsSync, readdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { DEEP_INDEX_STATE_FILE } from "../config/types.js";
|
|
5
|
+
|
|
6
|
+
export function runReset(cwd: string, options: { volumes?: boolean } = {}): void {
|
|
7
|
+
const stateDir = join(cwd, ".mem", "state");
|
|
8
|
+
const deepIndexPath = join(cwd, DEEP_INDEX_STATE_FILE);
|
|
9
|
+
|
|
10
|
+
if (existsSync(deepIndexPath)) {
|
|
11
|
+
rmSync(deepIndexPath, { force: true });
|
|
12
|
+
console.log(chalk.green("Removed"), DEEP_INDEX_STATE_FILE);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (existsSync(stateDir)) {
|
|
16
|
+
try {
|
|
17
|
+
const files = readdirSync(stateDir);
|
|
18
|
+
for (const f of files) {
|
|
19
|
+
rmSync(join(stateDir, f), { force: true });
|
|
20
|
+
}
|
|
21
|
+
} catch {
|
|
22
|
+
// ignore
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
console.log(chalk.green("Reset complete. Index state and deep-index progress cleared."));
|
|
27
|
+
if (options.volumes) {
|
|
28
|
+
console.log(chalk.dim("To wipe Qdrant volume, run: docker compose down -v"));
|
|
29
|
+
}
|
|
30
|
+
}
|