@sdsrs/code-graph 0.45.1 → 0.45.3
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.
|
@@ -197,6 +197,20 @@ function cachedBinaryNeedsUpdate(latest, { binaryPath = cachedBinaryPath(), read
|
|
|
197
197
|
return readVersion(binaryPath) !== latest.version;
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Throttle-bypass predicate: is a *present* cached binary stale relative to the
|
|
202
|
+
* last known latest release (`state.latestVersion`, set on the previous fetch —
|
|
203
|
+
* no network here)? Used so a present-but-stale binary skips the time-based
|
|
204
|
+
* throttle instead of staying pinned for up to a full check interval. Returns
|
|
205
|
+
* false when there is no prior latestVersion (first run fetches anyway) or the
|
|
206
|
+
* binary is missing (handled by the separate `binaryMissing` bypass).
|
|
207
|
+
*/
|
|
208
|
+
function cachedBinaryStaleVsState(state, { binaryPath = cachedBinaryPath(), readVersion = readBinaryVersion } = {}) {
|
|
209
|
+
if (!state || !state.latestVersion) return false;
|
|
210
|
+
if (!fs.existsSync(binaryPath)) return false;
|
|
211
|
+
return readVersion(binaryPath) !== state.latestVersion;
|
|
212
|
+
}
|
|
213
|
+
|
|
200
214
|
/**
|
|
201
215
|
* Download just the platform binary from a GitHub release into the cache.
|
|
202
216
|
* Used in two paths:
|
|
@@ -353,6 +367,19 @@ async function downloadAndInstall(latest) {
|
|
|
353
367
|
|
|
354
368
|
// ── Main Entry ─────────────────────────────────────────────
|
|
355
369
|
|
|
370
|
+
/**
|
|
371
|
+
* Self-heal the cached native binary when the plugin shell is already at latest
|
|
372
|
+
* but the binary lags (missing OR a different version). This is the orchestration
|
|
373
|
+
* glue that broke twice in the field (v0.45.1, v0.45.2): the decision predicate
|
|
374
|
+
* was correct, but nothing guaranteed checkForUpdate actually invoked the download
|
|
375
|
+
* on the shell-matches-latest path. Extracted + injectable so the wiring itself is
|
|
376
|
+
* regression-tested, not just the predicate. Returns true iff a download promoted.
|
|
377
|
+
*/
|
|
378
|
+
async function selfHealStaleBinary(latest, { needsUpdate = cachedBinaryNeedsUpdate, download = downloadBinary } = {}) {
|
|
379
|
+
if (!needsUpdate(latest)) return false;
|
|
380
|
+
return await download(latest);
|
|
381
|
+
}
|
|
382
|
+
|
|
356
383
|
async function checkForUpdate({ installMissing = false } = {}) {
|
|
357
384
|
try {
|
|
358
385
|
// Skip in dev mode — unless the launcher explicitly requested a missing-
|
|
@@ -365,11 +392,14 @@ async function checkForUpdate({ installMissing = false } = {}) {
|
|
|
365
392
|
// bypasses auto-update.js, so re-sync state.installedVersion every call.
|
|
366
393
|
const installedVersion = readManifest().version || '0.0.0';
|
|
367
394
|
|
|
368
|
-
// Time-based throttle.
|
|
369
|
-
// cannot start)
|
|
370
|
-
//
|
|
395
|
+
// Time-based throttle. Two conditions override it: a missing cache binary
|
|
396
|
+
// (launcher cannot start) and a present-but-stale binary (otherwise it stays
|
|
397
|
+
// pinned to the old version for up to a full check interval — the binary
|
|
398
|
+
// self-heal would never run inside the throttle window). Both bypass to the
|
|
399
|
+
// fetch + self-heal path below.
|
|
371
400
|
const binaryMissing = !fs.existsSync(cachedBinaryPath());
|
|
372
|
-
|
|
401
|
+
const binaryStale = cachedBinaryStaleVsState(state);
|
|
402
|
+
if (!binaryMissing && !binaryStale && !shouldCheck(state)) {
|
|
373
403
|
if (state.installedVersion !== installedVersion) {
|
|
374
404
|
saveState({ ...state, installedVersion });
|
|
375
405
|
}
|
|
@@ -414,14 +444,10 @@ async function checkForUpdate({ installMissing = false } = {}) {
|
|
|
414
444
|
}
|
|
415
445
|
|
|
416
446
|
// No plugin-shell update — but self-heal the native binary if it is missing
|
|
417
|
-
// OR stale. The shell version (manifest.version)
|
|
418
|
-
// cached binary lags
|
|
419
|
-
//
|
|
420
|
-
|
|
421
|
-
let selfHealedBinary = false;
|
|
422
|
-
if (cachedBinaryNeedsUpdate(latest)) {
|
|
423
|
-
selfHealedBinary = await downloadBinary(latest);
|
|
424
|
-
}
|
|
447
|
+
// OR stale (see selfHealStaleBinary). The shell version (manifest.version)
|
|
448
|
+
// can match latest while the cached binary lags — this is exactly the wild
|
|
449
|
+
// failure observed in the field (shell at v0.45, binary pinned at v0.16.6).
|
|
450
|
+
const selfHealedBinary = await selfHealStaleBinary(latest);
|
|
425
451
|
|
|
426
452
|
saveState({
|
|
427
453
|
...state,
|
|
@@ -446,7 +472,8 @@ module.exports = {
|
|
|
446
472
|
getExtractedPluginVersion, readBinaryVersion, promoteVerifiedBinary,
|
|
447
473
|
isSilentMode, isInstallMissingMode,
|
|
448
474
|
requestJson, parseLatestRelease, fetchLatestRelease,
|
|
449
|
-
downloadBinary, cachedBinaryPath, cachedBinaryNeedsUpdate,
|
|
475
|
+
downloadBinary, cachedBinaryPath, cachedBinaryNeedsUpdate, cachedBinaryStaleVsState,
|
|
476
|
+
selfHealStaleBinary,
|
|
450
477
|
};
|
|
451
478
|
|
|
452
479
|
// CLI: node auto-update.js [check|status] [--silent] [--install-missing]
|
|
@@ -14,7 +14,9 @@ const {
|
|
|
14
14
|
promoteVerifiedBinary,
|
|
15
15
|
cachedBinaryPath,
|
|
16
16
|
cachedBinaryNeedsUpdate,
|
|
17
|
+
cachedBinaryStaleVsState,
|
|
17
18
|
downloadBinary,
|
|
19
|
+
selfHealStaleBinary,
|
|
18
20
|
isInstallMissingMode,
|
|
19
21
|
isSilentMode,
|
|
20
22
|
} = require('./auto-update');
|
|
@@ -116,6 +118,61 @@ test('cachedBinaryNeedsUpdate is version-aware, not existence-only', (t) => {
|
|
|
116
118
|
assert.equal(cachedBinaryNeedsUpdate(null, { binaryPath }), false);
|
|
117
119
|
});
|
|
118
120
|
|
|
121
|
+
test('cachedBinaryStaleVsState bypasses throttle only for a present-but-stale binary', (t) => {
|
|
122
|
+
const dir = mkDir(t, 'code-graph-throttle-');
|
|
123
|
+
const binaryPath = path.join(dir, 'code-graph-mcp');
|
|
124
|
+
fs.writeFileSync(binaryPath, 'x'); // present
|
|
125
|
+
|
|
126
|
+
// no prior latestVersion → don't bypass (first run fetches anyway)
|
|
127
|
+
assert.equal(cachedBinaryStaleVsState({}, { binaryPath }), false);
|
|
128
|
+
assert.equal(cachedBinaryStaleVsState(null, { binaryPath }), false);
|
|
129
|
+
|
|
130
|
+
// present + stale vs last known latest → bypass throttle (the 6h-gap fix)
|
|
131
|
+
assert.equal(
|
|
132
|
+
cachedBinaryStaleVsState({ latestVersion: '0.45.1' }, { binaryPath, readVersion: () => '0.16.6' }),
|
|
133
|
+
true,
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
// present + current → stay throttled
|
|
137
|
+
assert.equal(
|
|
138
|
+
cachedBinaryStaleVsState({ latestVersion: '0.45.1' }, { binaryPath, readVersion: () => '0.45.1' }),
|
|
139
|
+
false,
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
// missing binary → false here (the separate binaryMissing bypass handles it)
|
|
143
|
+
fs.rmSync(binaryPath);
|
|
144
|
+
assert.equal(cachedBinaryStaleVsState({ latestVersion: '0.45.1' }, { binaryPath }), false);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('selfHealStaleBinary wires the stale-binary check to a download (the v0.45.x glue)', async () => {
|
|
148
|
+
const latest = { version: '0.45.2', binaryUrl: 'https://example/bin' };
|
|
149
|
+
|
|
150
|
+
// Field failure mode: shell already at latest, binary pinned stale → MUST download.
|
|
151
|
+
let downloaded = false;
|
|
152
|
+
const healed = await selfHealStaleBinary(latest, {
|
|
153
|
+
needsUpdate: () => true,
|
|
154
|
+
download: async () => { downloaded = true; return true; },
|
|
155
|
+
});
|
|
156
|
+
assert.equal(downloaded, true, 'stale binary must trigger a download');
|
|
157
|
+
assert.equal(healed, true);
|
|
158
|
+
|
|
159
|
+
// Binary current → no download, no-op.
|
|
160
|
+
let touched = false;
|
|
161
|
+
const noop = await selfHealStaleBinary(latest, {
|
|
162
|
+
needsUpdate: () => false,
|
|
163
|
+
download: async () => { touched = true; return true; },
|
|
164
|
+
});
|
|
165
|
+
assert.equal(touched, false, 'current binary must not download');
|
|
166
|
+
assert.equal(noop, false);
|
|
167
|
+
|
|
168
|
+
// Download fails (no curl / network) → returns false so the next session retries.
|
|
169
|
+
const failed = await selfHealStaleBinary(latest, {
|
|
170
|
+
needsUpdate: () => true,
|
|
171
|
+
download: async () => false,
|
|
172
|
+
});
|
|
173
|
+
assert.equal(failed, false);
|
|
174
|
+
});
|
|
175
|
+
|
|
119
176
|
test('parseLatestRelease selects the matching platform asset', () => {
|
|
120
177
|
const latest = parseLatestRelease({
|
|
121
178
|
tag_name: 'v1.2.3',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdsrs/code-graph",
|
|
3
|
-
"version": "0.45.
|
|
3
|
+
"version": "0.45.3",
|
|
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.45.
|
|
39
|
-
"@sdsrs/code-graph-linux-arm64": "0.45.
|
|
40
|
-
"@sdsrs/code-graph-darwin-x64": "0.45.
|
|
41
|
-
"@sdsrs/code-graph-darwin-arm64": "0.45.
|
|
42
|
-
"@sdsrs/code-graph-win32-x64": "0.45.
|
|
38
|
+
"@sdsrs/code-graph-linux-x64": "0.45.3",
|
|
39
|
+
"@sdsrs/code-graph-linux-arm64": "0.45.3",
|
|
40
|
+
"@sdsrs/code-graph-darwin-x64": "0.45.3",
|
|
41
|
+
"@sdsrs/code-graph-darwin-arm64": "0.45.3",
|
|
42
|
+
"@sdsrs/code-graph-win32-x64": "0.45.3"
|
|
43
43
|
}
|
|
44
44
|
}
|