@sdsrs/code-graph 0.58.0 → 0.60.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.
|
@@ -24,6 +24,23 @@ function readAdoptedBy(filePath) {
|
|
|
24
24
|
return m ? m[1] : null;
|
|
25
25
|
} catch { return null; }
|
|
26
26
|
}
|
|
27
|
+
// Atomic write (tmp in same dir → rename) so a crash mid-write can't leave a
|
|
28
|
+
// half-written MEMORY.md / detail file — the dir is shared with claude-mem-lite,
|
|
29
|
+
// which reads MEMORY.md on every keyword match. Mirrors lifecycle.js
|
|
30
|
+
// writeJsonAtomic / auto-update.js binary promote; accepts a string or Buffer.
|
|
31
|
+
function writeFileAtomic(filePath, data) {
|
|
32
|
+
const tmp = filePath + '.tmp.' + process.pid;
|
|
33
|
+
fs.writeFileSync(tmp, data);
|
|
34
|
+
try {
|
|
35
|
+
fs.renameSync(tmp, filePath);
|
|
36
|
+
} catch (e) {
|
|
37
|
+
// rename can fail (ENOSPC / EACCES / EROFS on the dir). Don't orphan the
|
|
38
|
+
// temp in the shared memory dir — mirror auto-update.js's binary promote,
|
|
39
|
+
// which cleans its tmp on failure. Best-effort unlink, then rethrow original.
|
|
40
|
+
try { fs.unlinkSync(tmp); } catch { /* already gone */ }
|
|
41
|
+
throw e;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
27
44
|
// One-liner per MEMORY.md spec ("each entry should be one line"). All routing
|
|
28
45
|
// triggers from prior multi-line block preserved verbatim — collapsing to single
|
|
29
46
|
// line is a structural fix, not a signal change. Decision table lives in the
|
|
@@ -354,7 +371,7 @@ function adopt({ cwd, home, templatePath } = {}) {
|
|
|
354
371
|
// ADOPTED_BY_RE strip below).
|
|
355
372
|
const tplBody = fs.readFileSync(tpl);
|
|
356
373
|
const marker = Buffer.from(`<!-- adopted-by: ${effectiveCwd} -->\n`);
|
|
357
|
-
|
|
374
|
+
writeFileAtomic(target, Buffer.concat([marker, tplBody]));
|
|
358
375
|
|
|
359
376
|
const indexPath = path.join(dir, 'MEMORY.md');
|
|
360
377
|
const index = fs.existsSync(indexPath) ? fs.readFileSync(indexPath, 'utf8') : '# Memory Index\n';
|
|
@@ -372,7 +389,7 @@ function adopt({ cwd, home, templatePath } = {}) {
|
|
|
372
389
|
const cleaned = stripSentinelBlock(index);
|
|
373
390
|
const healed = cleaned !== index;
|
|
374
391
|
const base = cleaned.endsWith('\n') ? cleaned : cleaned + '\n';
|
|
375
|
-
|
|
392
|
+
writeFileAtomic(indexPath, base + desiredBlock + '\n');
|
|
376
393
|
return { ok: true, target, indexPath, indexed: true, healed, collisionWith };
|
|
377
394
|
}
|
|
378
395
|
|
|
@@ -479,7 +496,7 @@ function unadopt({ cwd, home } = {}) {
|
|
|
479
496
|
const before = fs.readFileSync(indexPath, 'utf8');
|
|
480
497
|
const after = stripSentinelBlock(before);
|
|
481
498
|
if (after !== before) {
|
|
482
|
-
|
|
499
|
+
writeFileAtomic(indexPath, after);
|
|
483
500
|
indexPruned = true;
|
|
484
501
|
}
|
|
485
502
|
}
|
|
@@ -90,6 +90,39 @@ test('adopt preserves existing MEMORY.md content and appends', () => {
|
|
|
90
90
|
} finally { sb.cleanup(); }
|
|
91
91
|
});
|
|
92
92
|
|
|
93
|
+
test('adopt + unadopt write atomically — no .tmp residue in the memory dir', () => {
|
|
94
|
+
// The memory dir is shared with claude-mem-lite, which reads MEMORY.md on
|
|
95
|
+
// every keyword match; a non-atomic write crashing mid-flight would corrupt
|
|
96
|
+
// it. adopt/unadopt now go through writeFileAtomic (tmp + rename). Proof the
|
|
97
|
+
// rename completed cleanly across all three write sites (detail file + adopt
|
|
98
|
+
// index + unadopt prune): no leftover `*.tmp.<pid>` entries.
|
|
99
|
+
const sb = makeSandbox();
|
|
100
|
+
try {
|
|
101
|
+
adopt({ cwd: sb.cwd, home: sb.home });
|
|
102
|
+
unadopt({ cwd: sb.cwd, home: sb.home });
|
|
103
|
+
const residue = fs.readdirSync(sb.dir).filter((f) => f.includes('.tmp.'));
|
|
104
|
+
assert.deepStrictEqual(residue, [], `no atomic-write tmp residue; found: ${residue}`);
|
|
105
|
+
} finally { sb.cleanup(); }
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('writeFileAtomic cleans its temp file when rename fails (no orphaned .tmp)', () => {
|
|
109
|
+
// The success path leaves no residue (above). This pins the FAILURE path: if
|
|
110
|
+
// renameSync throws (ENOSPC/EACCES/EROFS on the shared memory dir) the temp must
|
|
111
|
+
// be unlinked, not orphaned. Force every rename to fail and assert no `.tmp.<pid>`
|
|
112
|
+
// survives the (failed) adopt.
|
|
113
|
+
const sb = makeSandbox();
|
|
114
|
+
const realRename = fs.renameSync;
|
|
115
|
+
try {
|
|
116
|
+
fs.renameSync = () => { const e = new Error('EROFS: simulated read-only fs'); e.code = 'EROFS'; throw e; };
|
|
117
|
+
try { adopt({ cwd: sb.cwd, home: sb.home }); } catch { /* expected — rename failed */ }
|
|
118
|
+
const residue = fs.readdirSync(sb.dir).filter((f) => f.includes('.tmp.'));
|
|
119
|
+
assert.deepStrictEqual(residue, [], `failed rename must not orphan a temp; found: ${residue}`);
|
|
120
|
+
} finally {
|
|
121
|
+
fs.renameSync = realRename;
|
|
122
|
+
sb.cleanup();
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
93
126
|
test('adopt refuses a non-project cwd even when the memory dir already exists (regression: /tmp adoption)', () => {
|
|
94
127
|
// Bug: the isProjectRoot guard was nested inside `if (!fs.existsSync(dir))`,
|
|
95
128
|
// so when Claude Code had already created ~/.claude/projects/<slug>/memory
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdsrs/code-graph",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.60.0",
|
|
4
4
|
"description": "MCP server that indexes codebases into an AST knowledge graph with semantic search, call graph traversal, and HTTP route tracing",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -35,10 +35,10 @@
|
|
|
35
35
|
"node": ">=16"
|
|
36
36
|
},
|
|
37
37
|
"optionalDependencies": {
|
|
38
|
-
"@sdsrs/code-graph-linux-x64": "0.
|
|
39
|
-
"@sdsrs/code-graph-linux-arm64": "0.
|
|
40
|
-
"@sdsrs/code-graph-darwin-x64": "0.
|
|
41
|
-
"@sdsrs/code-graph-darwin-arm64": "0.
|
|
42
|
-
"@sdsrs/code-graph-win32-x64": "0.
|
|
38
|
+
"@sdsrs/code-graph-linux-x64": "0.60.0",
|
|
39
|
+
"@sdsrs/code-graph-linux-arm64": "0.60.0",
|
|
40
|
+
"@sdsrs/code-graph-darwin-x64": "0.60.0",
|
|
41
|
+
"@sdsrs/code-graph-darwin-arm64": "0.60.0",
|
|
42
|
+
"@sdsrs/code-graph-win32-x64": "0.60.0"
|
|
43
43
|
}
|
|
44
44
|
}
|