@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,55 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { configExists } from "../config/load.js";
|
|
6
|
+
import { dockerComposeUp, waitForQdrantHealth } from "../docker/compose.js";
|
|
7
|
+
import { runPhase1 } from "../phase1/pipeline.js";
|
|
8
|
+
|
|
9
|
+
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
10
|
+
|
|
11
|
+
export async function runScaffold(cwd: string): Promise<void> {
|
|
12
|
+
if (!configExists(cwd)) {
|
|
13
|
+
console.error(chalk.red("No config found. Run"), chalk.cyan("mem init"), chalk.red("first."));
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
console.log(chalk.bold("Project Memory Scaffold (Phase 1)\n"));
|
|
18
|
+
|
|
19
|
+
console.log(chalk.dim("Starting Docker Compose..."));
|
|
20
|
+
try {
|
|
21
|
+
dockerComposeUp(cwd);
|
|
22
|
+
} catch (err) {
|
|
23
|
+
console.error(chalk.red("Docker Compose failed:"), (err as Error).message);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
console.log(chalk.dim("Waiting for Qdrant..."));
|
|
28
|
+
try {
|
|
29
|
+
await waitForQdrantHealth();
|
|
30
|
+
} catch (err) {
|
|
31
|
+
console.error(chalk.red("Qdrant health check failed:"), (err as Error).message);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
console.log(chalk.green("✓"), "Qdrant ready");
|
|
35
|
+
|
|
36
|
+
console.log(chalk.dim("Phase 1: file tree, symbol index, module summaries, system maps, embedding..."));
|
|
37
|
+
try {
|
|
38
|
+
await runPhase1(cwd);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
console.error(chalk.red("Phase 1 failed:"), (err as Error).message);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const serverPath = join(__dirname, "..", "..", "..", "server", "dist", "index.js");
|
|
45
|
+
const env = { ...process.env, MEM_CWD: cwd };
|
|
46
|
+
try {
|
|
47
|
+
spawn(process.execPath, [serverPath], { env, stdio: "ignore", detached: true }).unref();
|
|
48
|
+
} catch {
|
|
49
|
+
console.log(chalk.yellow("Could not start memory server. Run: cd packages/server && MEM_CWD=" + cwd + " npm start"));
|
|
50
|
+
}
|
|
51
|
+
console.log(chalk.green("✓"), "Memory server started (detached)");
|
|
52
|
+
|
|
53
|
+
console.log(chalk.green("\nScaffold Phase 1 complete. Cursor integration ready."));
|
|
54
|
+
console.log(chalk.dim("Run"), chalk.cyan("mem deep-index"), chalk.dim("to embed remaining chunks (Phase 2)."));
|
|
55
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { configExists } from "../config/load.js";
|
|
3
|
+
import { runPhase1 } from "../phase1/pipeline.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Incremental update: re-run Phase 1 (full scope+ignore, chunk, embed Phase 1 points).
|
|
7
|
+
* V1: full Phase 1 re-run; true incremental (changed files only) can be added later via manifest.
|
|
8
|
+
*/
|
|
9
|
+
export async function runUpdate(cwd: string): Promise<void> {
|
|
10
|
+
if (!configExists(cwd)) {
|
|
11
|
+
console.error(chalk.red("No config found. Run"), chalk.cyan("mem init"), chalk.red("first."));
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
console.log(chalk.bold("Project Memory Update\n"));
|
|
15
|
+
try {
|
|
16
|
+
await runPhase1(cwd);
|
|
17
|
+
console.log(chalk.green("Update complete."));
|
|
18
|
+
} catch (err) {
|
|
19
|
+
console.error(chalk.red("Update failed:"), (err as Error).message);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { configExists, loadConfig } from "../config/load.js";
|
|
3
|
+
import { listInScopeFiles } from "../scope/allowlist.js";
|
|
4
|
+
import { createIgnoreFilter } from "../scope/ignore.js";
|
|
5
|
+
import { toRelativePath } from "../scope/allowlist.js";
|
|
6
|
+
import { runUpdate } from "./update.js";
|
|
7
|
+
|
|
8
|
+
export async function runWatch(cwd: string): Promise<void> {
|
|
9
|
+
if (!configExists(cwd)) {
|
|
10
|
+
console.error(chalk.red("No config found. Run"), chalk.cyan("mem init"), chalk.red("first."));
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
const config = loadConfig(cwd);
|
|
14
|
+
const isIgnored = createIgnoreFilter(cwd, config.indexing?.exclude ?? []);
|
|
15
|
+
const inScopePaths = listInScopeFiles(cwd, config);
|
|
16
|
+
const eligiblePaths: string[] = [];
|
|
17
|
+
for (const abs of inScopePaths) {
|
|
18
|
+
const rel = toRelativePath(cwd, abs);
|
|
19
|
+
if (!isIgnored(rel)) eligiblePaths.push(abs);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const { default: chokidar } = await import("chokidar");
|
|
23
|
+
let debounceMs = 2000;
|
|
24
|
+
let timeout: ReturnType<typeof setTimeout> | null = null;
|
|
25
|
+
|
|
26
|
+
const watcher = chokidar.watch(eligiblePaths, {
|
|
27
|
+
ignoreInitial: true,
|
|
28
|
+
awaitWriteFinish: { stabilityThreshold: 500 },
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
watcher.on("change", () => {
|
|
32
|
+
if (timeout) clearTimeout(timeout);
|
|
33
|
+
timeout = setTimeout(async () => {
|
|
34
|
+
console.log(chalk.dim("Change detected, running update..."));
|
|
35
|
+
try {
|
|
36
|
+
await runUpdate(cwd);
|
|
37
|
+
} catch (e) {
|
|
38
|
+
console.error(chalk.red("Update failed:"), (e as Error).message);
|
|
39
|
+
}
|
|
40
|
+
timeout = null;
|
|
41
|
+
}, debounceMs);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
console.log(chalk.bold("Project Memory Watch"));
|
|
45
|
+
console.log(chalk.dim("Watching"), eligiblePaths.length, "files. Ctrl+C to stop.");
|
|
46
|
+
console.log(chalk.dim("Run mem update manually or wait for file changes."));
|
|
47
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { MemConfig } from "./types.js";
|
|
2
|
+
import { DEFAULT_CONFIG } from "./types.js";
|
|
3
|
+
|
|
4
|
+
export function getDefaultConfigYaml(): string {
|
|
5
|
+
const c: MemConfig = {
|
|
6
|
+
indexing: {
|
|
7
|
+
include: ["src/**", "docs/**"],
|
|
8
|
+
exclude: [],
|
|
9
|
+
},
|
|
10
|
+
phase1: DEFAULT_CONFIG.phase1,
|
|
11
|
+
qdrant: DEFAULT_CONFIG.qdrant,
|
|
12
|
+
chunk: DEFAULT_CONFIG.chunk,
|
|
13
|
+
embedding: DEFAULT_CONFIG.embedding,
|
|
14
|
+
};
|
|
15
|
+
return `# Project Memory config
|
|
16
|
+
# indexing.include: allowlist globs (only these paths are eligible). Omit to index repo root.
|
|
17
|
+
indexing:
|
|
18
|
+
include:
|
|
19
|
+
- "src/**"
|
|
20
|
+
- "docs/**"
|
|
21
|
+
exclude: []
|
|
22
|
+
|
|
23
|
+
phase1:
|
|
24
|
+
maxEntrypointFiles: ${c.phase1!.maxEntrypointFiles}
|
|
25
|
+
maxEntrypointChunks: ${c.phase1!.maxEntrypointChunks}
|
|
26
|
+
|
|
27
|
+
qdrant:
|
|
28
|
+
host: ${c.qdrant!.host}
|
|
29
|
+
port: ${c.qdrant!.port}
|
|
30
|
+
collection: ${c.qdrant!.collection}
|
|
31
|
+
|
|
32
|
+
chunk:
|
|
33
|
+
maxLines: ${c.chunk!.maxLines}
|
|
34
|
+
minLines: ${c.chunk!.minLines}
|
|
35
|
+
|
|
36
|
+
embedding:
|
|
37
|
+
provider: ${c.embedding!.provider}
|
|
38
|
+
`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function getDefaultMemignore(): string {
|
|
42
|
+
return `# Project Memory ignore rules (gitignore-style)
|
|
43
|
+
# Tests
|
|
44
|
+
**/*.test.*
|
|
45
|
+
**/*.spec.*
|
|
46
|
+
**/__tests__/**
|
|
47
|
+
**/tests/**
|
|
48
|
+
`;
|
|
49
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { strict as assert } from "node:assert";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import { mkdtempSync, writeFileSync, rmSync, mkdirSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { loadConfig, getConfigPath, configExists } from "./load.js";
|
|
7
|
+
|
|
8
|
+
describe("config load", () => {
|
|
9
|
+
it("loadConfig with no file returns merged defaults", () => {
|
|
10
|
+
const cwd = mkdtempSync(join(tmpdir(), "mem-config-"));
|
|
11
|
+
try {
|
|
12
|
+
const config = loadConfig(cwd);
|
|
13
|
+
assert.strictEqual(config.phase1?.maxEntrypointFiles, 50);
|
|
14
|
+
assert.strictEqual(config.qdrant?.port, 6333);
|
|
15
|
+
assert.strictEqual(config.embedding?.provider, "local");
|
|
16
|
+
} finally {
|
|
17
|
+
rmSync(cwd, { recursive: true, force: true });
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("getConfigPath returns .mem/config.yml under cwd", () => {
|
|
22
|
+
const cwd = "/some/repo";
|
|
23
|
+
assert.ok(getConfigPath(cwd).endsWith(".mem/config.yml"));
|
|
24
|
+
assert.ok(getConfigPath(cwd).includes("some"));
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("configExists returns false when file missing", () => {
|
|
28
|
+
const cwd = mkdtempSync(join(tmpdir(), "mem-config-"));
|
|
29
|
+
try {
|
|
30
|
+
assert.strictEqual(configExists(cwd), false);
|
|
31
|
+
} finally {
|
|
32
|
+
rmSync(cwd, { recursive: true, force: true });
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("loadConfig merges overrides when file exists", () => {
|
|
37
|
+
const cwd = mkdtempSync(join(tmpdir(), "mem-config-"));
|
|
38
|
+
const configPath = join(cwd, ".mem", "config.yml");
|
|
39
|
+
mkdirSync(join(cwd, ".mem"), { recursive: true });
|
|
40
|
+
writeFileSync(
|
|
41
|
+
configPath,
|
|
42
|
+
"phase1:\n maxEntrypointFiles: 10\nqdrant:\n port: 9999\n",
|
|
43
|
+
"utf8"
|
|
44
|
+
);
|
|
45
|
+
try {
|
|
46
|
+
const config = loadConfig(cwd);
|
|
47
|
+
assert.strictEqual(config.phase1?.maxEntrypointFiles, 10);
|
|
48
|
+
assert.strictEqual(config.qdrant?.port, 9999);
|
|
49
|
+
assert.strictEqual(config.embedding?.provider, "local");
|
|
50
|
+
assert.strictEqual(configExists(cwd), true);
|
|
51
|
+
} finally {
|
|
52
|
+
rmSync(cwd, { recursive: true, force: true });
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import yaml from "js-yaml";
|
|
4
|
+
import type { MemConfig } from "./types.js";
|
|
5
|
+
import { DEFAULT_CONFIG, CONFIG_FILE } from "./types.js";
|
|
6
|
+
|
|
7
|
+
function mergeConfig(loaded: MemConfig): MemConfig {
|
|
8
|
+
return {
|
|
9
|
+
indexing: loaded.indexing ?? DEFAULT_CONFIG.indexing,
|
|
10
|
+
phase1: { ...DEFAULT_CONFIG.phase1, ...loaded.phase1 },
|
|
11
|
+
qdrant: { ...DEFAULT_CONFIG.qdrant, ...loaded.qdrant },
|
|
12
|
+
chunk: { ...DEFAULT_CONFIG.chunk, ...loaded.chunk },
|
|
13
|
+
embedding: { ...DEFAULT_CONFIG.embedding, ...loaded.embedding },
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function loadConfig(cwd: string): MemConfig {
|
|
18
|
+
const path = join(cwd, CONFIG_FILE);
|
|
19
|
+
if (!existsSync(path)) {
|
|
20
|
+
return mergeConfig({});
|
|
21
|
+
}
|
|
22
|
+
const raw = readFileSync(path, "utf8");
|
|
23
|
+
const parsed = yaml.load(raw) as MemConfig | undefined;
|
|
24
|
+
return mergeConfig(parsed ?? {});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getConfigPath(cwd: string): string {
|
|
28
|
+
return join(cwd, CONFIG_FILE);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function configExists(cwd: string): boolean {
|
|
32
|
+
return existsSync(join(cwd, CONFIG_FILE));
|
|
33
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { strict as assert } from "node:assert";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_CONFIG,
|
|
5
|
+
CONFIG_DIR,
|
|
6
|
+
CONFIG_FILE,
|
|
7
|
+
MEMIGNORE_FILE,
|
|
8
|
+
DEEP_INDEX_STATE_FILE,
|
|
9
|
+
} from "./types.js";
|
|
10
|
+
|
|
11
|
+
describe("config types", () => {
|
|
12
|
+
it("DEFAULT_CONFIG has phase1 caps", () => {
|
|
13
|
+
assert.strictEqual(DEFAULT_CONFIG.phase1.maxEntrypointFiles, 50);
|
|
14
|
+
assert.strictEqual(DEFAULT_CONFIG.phase1.maxEntrypointChunks, 300);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("DEFAULT_CONFIG has qdrant defaults", () => {
|
|
18
|
+
assert.strictEqual(DEFAULT_CONFIG.qdrant.host, "127.0.0.1");
|
|
19
|
+
assert.strictEqual(DEFAULT_CONFIG.qdrant.port, 6333);
|
|
20
|
+
assert.strictEqual(DEFAULT_CONFIG.qdrant.collection, "project_memory");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("DEFAULT_CONFIG has chunk defaults", () => {
|
|
24
|
+
assert.strictEqual(DEFAULT_CONFIG.chunk.maxLines, 400);
|
|
25
|
+
assert.ok((DEFAULT_CONFIG.chunk.minLines ?? 0) >= 0);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("CONFIG_DIR is .mem", () => {
|
|
29
|
+
assert.strictEqual(CONFIG_DIR, ".mem");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("CONFIG_FILE is .mem/config.yml", () => {
|
|
33
|
+
assert.strictEqual(CONFIG_FILE, ".mem/config.yml");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("MEMIGNORE_FILE is .memignore", () => {
|
|
37
|
+
assert.strictEqual(MEMIGNORE_FILE, ".memignore");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("DEEP_INDEX_STATE_FILE is .mem/state/deep-index.json", () => {
|
|
41
|
+
assert.strictEqual(DEEP_INDEX_STATE_FILE, ".mem/state/deep-index.json");
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export interface MemConfig {
|
|
2
|
+
indexing?: {
|
|
3
|
+
include?: string[];
|
|
4
|
+
exclude?: string[];
|
|
5
|
+
};
|
|
6
|
+
phase1?: {
|
|
7
|
+
maxEntrypointFiles?: number;
|
|
8
|
+
maxEntrypointChunks?: number;
|
|
9
|
+
};
|
|
10
|
+
qdrant?: {
|
|
11
|
+
host?: string;
|
|
12
|
+
port?: number;
|
|
13
|
+
collection?: string;
|
|
14
|
+
};
|
|
15
|
+
chunk?: {
|
|
16
|
+
maxLines?: number;
|
|
17
|
+
minLines?: number;
|
|
18
|
+
};
|
|
19
|
+
embedding?: {
|
|
20
|
+
provider?: string;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const DEFAULT_CONFIG: Required<
|
|
25
|
+
Pick<MemConfig, "phase1" | "qdrant" | "chunk" | "embedding">
|
|
26
|
+
> & { indexing: { include?: string[]; exclude?: string[] } } = {
|
|
27
|
+
phase1: {
|
|
28
|
+
maxEntrypointFiles: 50,
|
|
29
|
+
maxEntrypointChunks: 300,
|
|
30
|
+
},
|
|
31
|
+
qdrant: {
|
|
32
|
+
host: "127.0.0.1",
|
|
33
|
+
port: 6333,
|
|
34
|
+
collection: "project_memory",
|
|
35
|
+
},
|
|
36
|
+
chunk: {
|
|
37
|
+
maxLines: 400,
|
|
38
|
+
minLines: 50,
|
|
39
|
+
},
|
|
40
|
+
embedding: {
|
|
41
|
+
provider: "local",
|
|
42
|
+
},
|
|
43
|
+
indexing: {},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const CONFIG_DIR = ".mem";
|
|
47
|
+
export const CONFIG_FILE = ".mem/config.yml";
|
|
48
|
+
export const MEMIGNORE_FILE = ".memignore";
|
|
49
|
+
export const DEEP_INDEX_STATE_FILE = ".mem/state/deep-index.json";
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { execSync, spawn } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
const QDRANT_HEALTH_URL = "http://127.0.0.1:6333/health";
|
|
6
|
+
const HEALTH_POLL_MS = 1000;
|
|
7
|
+
const HEALTH_TIMEOUT_MS = 60_000;
|
|
8
|
+
|
|
9
|
+
function getComposeDir(cwd: string): string {
|
|
10
|
+
const atRoot = join(cwd, "docker-compose.yml");
|
|
11
|
+
const atMem = join(cwd, ".mem", "docker-compose.yml");
|
|
12
|
+
if (existsSync(atRoot)) return cwd;
|
|
13
|
+
if (existsSync(atMem)) return join(cwd, ".mem");
|
|
14
|
+
return cwd;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function composeCmd(cwd: string): "docker compose" | "docker-compose" {
|
|
18
|
+
try {
|
|
19
|
+
execSync("docker compose version", { stdio: "pipe", cwd });
|
|
20
|
+
return "docker compose";
|
|
21
|
+
} catch {
|
|
22
|
+
try {
|
|
23
|
+
execSync("docker-compose --version", { stdio: "pipe", cwd });
|
|
24
|
+
return "docker-compose";
|
|
25
|
+
} catch {
|
|
26
|
+
throw new Error("Docker Compose not found. Install Docker and Docker Compose.");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function dockerComposeUp(cwd: string): void {
|
|
32
|
+
const dir = getComposeDir(cwd);
|
|
33
|
+
const cmd = composeCmd(dir);
|
|
34
|
+
execSync(`${cmd} up -d`, { stdio: "inherit", cwd: dir });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function dockerComposeDown(cwd: string): void {
|
|
38
|
+
const dir = getComposeDir(cwd);
|
|
39
|
+
const cmd = composeCmd(dir);
|
|
40
|
+
execSync(`${cmd} down`, { stdio: "inherit", cwd: dir });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function waitForQdrantHealth(timeoutMs: number = HEALTH_TIMEOUT_MS): Promise<void> {
|
|
44
|
+
const start = Date.now();
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
function poll() {
|
|
47
|
+
if (Date.now() - start > timeoutMs) {
|
|
48
|
+
reject(new Error("Qdrant health check timed out"));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
fetch(QDRANT_HEALTH_URL)
|
|
52
|
+
.then((r) => {
|
|
53
|
+
if (r.ok) resolve();
|
|
54
|
+
else setTimeout(poll, HEALTH_POLL_MS);
|
|
55
|
+
})
|
|
56
|
+
.catch(() => setTimeout(poll, HEALTH_POLL_MS));
|
|
57
|
+
}
|
|
58
|
+
poll();
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function checkDockerAvailable(cwd: string): boolean {
|
|
63
|
+
try {
|
|
64
|
+
composeCmd(getComposeDir(cwd));
|
|
65
|
+
return true;
|
|
66
|
+
} catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function checkQdrantHealthy(): Promise<boolean> {
|
|
72
|
+
return fetch(QDRANT_HEALTH_URL)
|
|
73
|
+
.then((r) => r.ok)
|
|
74
|
+
.catch(() => false);
|
|
75
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { strict as assert } from "node:assert";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import { embedText, embedBatch, getVectorSize } from "./stub.js";
|
|
4
|
+
|
|
5
|
+
describe("embedding stub", () => {
|
|
6
|
+
it("getVectorSize returns 384", () => {
|
|
7
|
+
assert.strictEqual(getVectorSize(), 384);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("embedText returns array of length 384", () => {
|
|
11
|
+
const vec = embedText("hello world");
|
|
12
|
+
assert.strictEqual(vec.length, 384);
|
|
13
|
+
assert.ok(vec.every((x) => typeof x === "number" && x >= 0 && x <= 1));
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("embedText is deterministic", () => {
|
|
17
|
+
const a = embedText("same text");
|
|
18
|
+
const b = embedText("same text");
|
|
19
|
+
assert.deepStrictEqual(a, b);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("embedText differs for different input", () => {
|
|
23
|
+
const a = embedText("foo");
|
|
24
|
+
const b = embedText("bar");
|
|
25
|
+
assert.notDeepStrictEqual(a, b);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("embedBatch returns one vector per text", () => {
|
|
29
|
+
const batch = embedBatch(["a", "b", "c"]);
|
|
30
|
+
assert.strictEqual(batch.length, 3);
|
|
31
|
+
assert.strictEqual(batch[0].length, 384);
|
|
32
|
+
assert.strictEqual(batch[1].length, 384);
|
|
33
|
+
assert.strictEqual(batch[2].length, 384);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const VECTOR_SIZE = 384;
|
|
2
|
+
|
|
3
|
+
function simpleHash(str: string): number {
|
|
4
|
+
let h = 0;
|
|
5
|
+
for (let i = 0; i < str.length; i++) {
|
|
6
|
+
h = (h * 31 + str.charCodeAt(i)) >>> 0;
|
|
7
|
+
}
|
|
8
|
+
return h;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Stub embedder: must match server's stub (same size and logic) for vector compatibility.
|
|
13
|
+
*/
|
|
14
|
+
export function embedText(text: string): number[] {
|
|
15
|
+
const vec: number[] = [];
|
|
16
|
+
const words = text.split(/\s+/).filter(Boolean);
|
|
17
|
+
for (let i = 0; i < VECTOR_SIZE; i++) {
|
|
18
|
+
const seed = words[i % words.length]
|
|
19
|
+
? simpleHash(words[i % words.length]) + i * 31
|
|
20
|
+
: i;
|
|
21
|
+
vec.push(Math.sin(seed) * 0.5 + 0.5);
|
|
22
|
+
}
|
|
23
|
+
return vec;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function embedBatch(texts: string[]): number[][] {
|
|
27
|
+
return texts.map(embedText);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function getVectorSize(): number {
|
|
31
|
+
return VECTOR_SIZE;
|
|
32
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { loadConfig } from "../config/load.js";
|
|
5
|
+
import { chunkFile } from "../chunking/chunker.js";
|
|
6
|
+
import type { Chunk } from "../chunking/types.js";
|
|
7
|
+
import { getModuleFromPath } from "../chunking/types.js";
|
|
8
|
+
import { toRelativePath } from "../scope/allowlist.js";
|
|
9
|
+
import { embedBatch } from "../embedding/stub.js";
|
|
10
|
+
import { createQdrantClient, ensureCollection, upsertPoints } from "../qdrant/upsert.js";
|
|
11
|
+
import { listInScopeFiles } from "../scope/allowlist.js";
|
|
12
|
+
import { createIgnoreFilter } from "../scope/ignore.js";
|
|
13
|
+
import { DEEP_INDEX_STATE_FILE } from "../config/types.js";
|
|
14
|
+
import { writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
15
|
+
|
|
16
|
+
const BATCH_SIZE = 16;
|
|
17
|
+
const PHASE1_KINDS = ["system_map", "module_summary", "entrypoint"] as const;
|
|
18
|
+
|
|
19
|
+
function selectEntrypoints(
|
|
20
|
+
chunks: Chunk[],
|
|
21
|
+
maxFiles: number,
|
|
22
|
+
maxChunks: number
|
|
23
|
+
): Chunk[] {
|
|
24
|
+
const byPath = new Map<string, Chunk[]>();
|
|
25
|
+
for (const c of chunks) {
|
|
26
|
+
const list = byPath.get(c.metadata.path) ?? [];
|
|
27
|
+
list.push(c);
|
|
28
|
+
byPath.set(c.metadata.path, list);
|
|
29
|
+
}
|
|
30
|
+
const paths = [...byPath.keys()].sort();
|
|
31
|
+
const entrypointPaths = paths.slice(0, maxFiles);
|
|
32
|
+
const result: Chunk[] = [];
|
|
33
|
+
let count = 0;
|
|
34
|
+
for (const p of entrypointPaths) {
|
|
35
|
+
const list = byPath.get(p) ?? [];
|
|
36
|
+
for (const c of list) {
|
|
37
|
+
if (count >= maxChunks) break;
|
|
38
|
+
result.push(c);
|
|
39
|
+
count++;
|
|
40
|
+
}
|
|
41
|
+
if (count >= maxChunks) break;
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function buildModuleSummaries(eligiblePaths: string[], cwd: string): { id: string; text: string; path: string; kind: string }[] {
|
|
47
|
+
const byDir = new Map<string, string[]>();
|
|
48
|
+
for (const abs of eligiblePaths) {
|
|
49
|
+
const rel = toRelativePath(cwd, abs);
|
|
50
|
+
const dir = getModuleFromPath(rel);
|
|
51
|
+
const list = byDir.get(dir) ?? [];
|
|
52
|
+
list.push(rel);
|
|
53
|
+
byDir.set(dir, list);
|
|
54
|
+
}
|
|
55
|
+
const out: { id: string; text: string; path: string; kind: string }[] = [];
|
|
56
|
+
for (const [dir, files] of byDir) {
|
|
57
|
+
const text = `Module: ${dir}\nFiles: ${files.slice(0, 50).join(", ")}${files.length > 50 ? ` (+${files.length - 50} more)` : ""}`;
|
|
58
|
+
const id = `module_summary:${dir.replace(/\//g, "_")}`;
|
|
59
|
+
out.push({ id, text, path: dir, kind: "module_summary" });
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function buildSystemMaps(cwd: string, eligiblePaths: string[]): { id: string; text: string; path: string; kind: string }[] {
|
|
65
|
+
const relPaths = eligiblePaths.map((p) => toRelativePath(cwd, p)).sort();
|
|
66
|
+
const overview = `System overview\nIndexed paths (${relPaths.length}):\n${relPaths.slice(0, 100).join("\n")}${relPaths.length > 100 ? `\n... and ${relPaths.length - 100} more` : ""}`;
|
|
67
|
+
return [
|
|
68
|
+
{ id: "system_map:overview", text: overview, path: "", kind: "system_map" },
|
|
69
|
+
];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function runPhase1(cwd: string): Promise<void> {
|
|
73
|
+
const config = loadConfig(cwd);
|
|
74
|
+
const isIgnored = createIgnoreFilter(cwd, config.indexing?.exclude ?? []);
|
|
75
|
+
const inScopePaths = listInScopeFiles(cwd, config);
|
|
76
|
+
const eligiblePaths: string[] = [];
|
|
77
|
+
for (const abs of inScopePaths) {
|
|
78
|
+
const rel = toRelativePath(cwd, abs);
|
|
79
|
+
if (!isIgnored(rel)) eligiblePaths.push(abs);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const allChunks: Chunk[] = [];
|
|
83
|
+
for (const abs of eligiblePaths) {
|
|
84
|
+
try {
|
|
85
|
+
const content = readFileSync(abs, "utf8");
|
|
86
|
+
const chunks = chunkFile(toRelativePath(cwd, abs), content);
|
|
87
|
+
allChunks.push(...chunks);
|
|
88
|
+
} catch {
|
|
89
|
+
// skip binary or unreadable
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const maxFiles = config.phase1?.maxEntrypointFiles ?? 50;
|
|
94
|
+
const maxChunks = config.phase1?.maxEntrypointChunks ?? 300;
|
|
95
|
+
const entrypointChunks = selectEntrypoints(allChunks, maxFiles, maxChunks);
|
|
96
|
+
|
|
97
|
+
const moduleSummaries = buildModuleSummaries(eligiblePaths, cwd);
|
|
98
|
+
const systemMaps = buildSystemMaps(cwd, eligiblePaths);
|
|
99
|
+
|
|
100
|
+
const host = config.qdrant?.host ?? "127.0.0.1";
|
|
101
|
+
const port = config.qdrant?.port ?? 6333;
|
|
102
|
+
const collection = config.qdrant?.collection ?? "project_memory";
|
|
103
|
+
const client = createQdrantClient(host, port);
|
|
104
|
+
|
|
105
|
+
await ensureCollection(client, collection);
|
|
106
|
+
|
|
107
|
+
const points: { id: string; vector: number[]; payload: { kind: string; path?: string; text: string } }[] = [];
|
|
108
|
+
|
|
109
|
+
for (const sm of systemMaps) {
|
|
110
|
+
points.push({
|
|
111
|
+
id: sm.id,
|
|
112
|
+
vector: embedBatch([sm.text])[0],
|
|
113
|
+
payload: { kind: sm.kind, path: sm.path || undefined, text: sm.text },
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
for (const ms of moduleSummaries) {
|
|
117
|
+
points.push({
|
|
118
|
+
id: ms.id,
|
|
119
|
+
vector: embedBatch([ms.text])[0],
|
|
120
|
+
payload: { kind: ms.kind, path: ms.path, text: ms.text },
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const entrypointTexts = entrypointChunks.map((c) => c.text);
|
|
125
|
+
const entrypointVectors = embedBatch(entrypointTexts);
|
|
126
|
+
for (let i = 0; i < entrypointChunks.length; i++) {
|
|
127
|
+
const c = entrypointChunks[i];
|
|
128
|
+
points.push({
|
|
129
|
+
id: c.id,
|
|
130
|
+
vector: entrypointVectors[i],
|
|
131
|
+
payload: {
|
|
132
|
+
kind: "entrypoint",
|
|
133
|
+
path: c.metadata.path,
|
|
134
|
+
text: c.text.slice(0, 8000),
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
for (let i = 0; i < points.length; i += BATCH_SIZE) {
|
|
140
|
+
const batch = points.slice(i, i + BATCH_SIZE);
|
|
141
|
+
await upsertPoints(client, collection, batch);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const stateDir = join(cwd, ".mem", "state");
|
|
145
|
+
if (!existsSync(stateDir)) mkdirSync(stateDir, { recursive: true });
|
|
146
|
+
const deepIndexStatePath = join(cwd, DEEP_INDEX_STATE_FILE);
|
|
147
|
+
const totalChunks = allChunks.length;
|
|
148
|
+
const phase1Embedded = points.length;
|
|
149
|
+
const embeddedIds = points.map((p) => p.id);
|
|
150
|
+
writeFileSync(
|
|
151
|
+
deepIndexStatePath,
|
|
152
|
+
JSON.stringify({
|
|
153
|
+
embedded: phase1Embedded,
|
|
154
|
+
total: totalChunks,
|
|
155
|
+
embeddedIds,
|
|
156
|
+
phase1Complete: true,
|
|
157
|
+
lastUpdated: new Date().toISOString(),
|
|
158
|
+
}),
|
|
159
|
+
"utf8"
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
console.log(chalk.green("✓"), "Phase 1: embedded", phase1Embedded, "points (system maps, module summaries, entrypoints)");
|
|
163
|
+
console.log(chalk.dim(" Total chunks (for deep-index):"), totalChunks);
|
|
164
|
+
}
|