@sdsrs/code-graph 0.149.0 → 0.151.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/README.md +8 -2
- package/claude-plugin/.claude-plugin/plugin.json +1 -1
- package/claude-plugin/scripts/cache-paths.js +12 -0
- package/claude-plugin/scripts/doctor.js +81 -2
- package/claude-plugin/scripts/find-binary.js +25 -1
- package/claude-plugin/scripts/launcher-install.js +30 -0
- package/claude-plugin/scripts/lifecycle.js +36 -3
- package/claude-plugin/templates/code-graph-snapshot.yml +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -427,6 +427,7 @@ skill does is also runnable by hand.
|
|
|
427
427
|
- **Rust has no `inherits` edges** — the language has no class inheritance, so `impl Trait for Type` is recorded as `implements` and an `inherits`-filtered query returns empty for Rust.
|
|
428
428
|
- **Kotlin/Swift interface conformance** is recorded as `inherits` (both use a single `: Type` grammar for base classes and protocols/interfaces), so `implements`-filtered queries return empty for these two languages.
|
|
429
429
|
- **Cross-file dead-code detection** may false-positive a type whose only cross-file reference sits beyond the 4096-byte stored-content cap per node (documented accepted limitation, v0.97.1).
|
|
430
|
+
- **Rust: borrowing a binding named `raw`** (`&raw`, `&raw[..]`, `&raw.field`) is misread by the pinned `tree-sitter-rust` 0.23 grammar as the start of the `&raw const` / `&raw mut` pointer operator, so the enclosing expression can be dropped from the index. `&mut raw`, `&self.raw` and the genuine `&raw const x` operator all parse correctly. The index reports the damage rather than hiding it. A run prints `Syntax errors in <file> — symbols may be incomplete` per file plus an `N file(s) parsed with syntax errors` total, and the verdict is stored with the index, so it survives the run: `health-check` names the files (`Parse: N file(s) indexed over a damaged parse`, plus `files_with_parse_errors` / `parse_error_files` under `--json`), `doctor` shows a `Parse` row, and the MCP `get_index_status` carries the same two fields. An incremental run over unrelated files does not clear it — only re-parsing the offending file, or deleting it, does. Workaround: rename the binding. Measured on this repo by indexing it fresh at v0.150.0: **9 of 163** tracked Rust files trip the warning, and the run's total says 10 — a Markdown file trips the same counter for an unrelated reason. The stored set names the same ten, which is the cheapest way to check this table has not drifted again: it is a reading of one tree at one commit, not a constant, and an earlier revision of this bullet carried 8/161/9 long after they stopped being true. Recovery is usually partial rather than total: across a fixture covering every trigger shape, the surrounding functions and their call edges survived.
|
|
430
431
|
|
|
431
432
|
## Team-shared graph snapshot
|
|
432
433
|
|
|
@@ -446,8 +447,13 @@ npx -y -p @sdsrs/code-graph code-graph-mcp snapshot inspect ./code-graph-snapsho
|
|
|
446
447
|
```
|
|
447
448
|
|
|
448
449
|
After setup, the auto-fetch is **opt-in per consumer**: an untrusted repo could
|
|
449
|
-
otherwise seed a misleading graph, so an unconfigured clone
|
|
450
|
-
|
|
450
|
+
otherwise seed a misleading graph, so an unconfigured clone silently skips the
|
|
451
|
+
install — the explanation is logged at `debug` level (`RUST_LOG=debug`) rather
|
|
452
|
+
than warned, because deciding whether this repo even publishes a snapshot would
|
|
453
|
+
require the network call the gate is declining to make, and an unconditional
|
|
454
|
+
warning would fire on every repository. A committed `[snapshot] url` override is
|
|
455
|
+
louder: that one does warn, because the file is already asking for something.
|
|
456
|
+
Enable the auto-fetch with one of the trust signals below. These live in
|
|
451
457
|
the *environment* (never in `.code-graph.toml`) so a committed/PR-injected config
|
|
452
458
|
file cannot set them.
|
|
453
459
|
|
|
@@ -32,6 +32,17 @@ const MANIFEST_FILE = path.join(CACHE_DIR, 'install-manifest.json');
|
|
|
32
32
|
/** Inter-process install lock (see install-lock.js). */
|
|
33
33
|
const INSTALL_LOCK_FILE = path.join(CACHE_DIR, 'install.lock');
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Where `find-binary.js` memoizes the resolved binary.
|
|
37
|
+
*
|
|
38
|
+
* It lived in find-binary.js, built from its own `os.homedir()` join — the JS-05
|
|
39
|
+
* shape this file's header describes, and the reason the teardown-gate audit
|
|
40
|
+
* missed it: the module that re-created the whole cache directory on the first
|
|
41
|
+
* cold resolution after an uninstall was not reachable from here, so an
|
|
42
|
+
* enumeration of cache writers done from this file came up one short.
|
|
43
|
+
*/
|
|
44
|
+
const BINARY_PATH_FILE = path.join(CACHE_DIR, 'binary-path');
|
|
45
|
+
|
|
35
46
|
/**
|
|
36
47
|
* Teardown tombstone — a SIBLING of CACHE_DIR, deliberately not a file inside
|
|
37
48
|
* it.
|
|
@@ -120,6 +131,7 @@ module.exports = {
|
|
|
120
131
|
UPDATE_STATE_FILE,
|
|
121
132
|
MANIFEST_FILE,
|
|
122
133
|
INSTALL_LOCK_FILE,
|
|
134
|
+
BINARY_PATH_FILE,
|
|
123
135
|
UNINSTALL_TOMBSTONE_FILE,
|
|
124
136
|
UNINSTALL_TOMBSTONE_TTL_MS,
|
|
125
137
|
writeUninstallTombstone,
|
|
@@ -10,7 +10,7 @@ const {
|
|
|
10
10
|
settingsPath, surveyHookCoverage,
|
|
11
11
|
installedGlobalPkgs, GLOBAL_INSTALL_MARKER, SHELL_PKG,
|
|
12
12
|
} = require('./lifecycle');
|
|
13
|
-
const { UPDATE_STATE_FILE } = require('./cache-paths');
|
|
13
|
+
const { UPDATE_STATE_FILE, uninstallTombstoneActive } = require('./cache-paths');
|
|
14
14
|
const { findBinary, clearCache: clearBinaryCache } = require('./find-binary');
|
|
15
15
|
const { hidden } = require('./proc-opts');
|
|
16
16
|
const { MAX_UPDATE_ATTEMPTS, GLOBAL_PKG_HEAL_MAX_ATTEMPTS, isBinaryHealExhausted, readState } = require('./auto-update');
|
|
@@ -177,6 +177,40 @@ function classifyHealthReport(hc) {
|
|
|
177
177
|
// degradation (model missing/not loaded) instead of false-greening it.
|
|
178
178
|
rows.push(classifyEmbeddings(hc));
|
|
179
179
|
rows.push(classifyIntegrity(hc));
|
|
180
|
+
// Files indexed over a tree-sitter error recovery. They look exactly like
|
|
181
|
+
// clean ones at query time — the symbols that survived are real, the ones that
|
|
182
|
+
// did not are simply absent — so a thin result reads as "the code is thin".
|
|
183
|
+
// Additive and absent on a clean index, which is why this is `!== undefined`
|
|
184
|
+
// rather than a truthiness test that would also swallow a reported 0.
|
|
185
|
+
//
|
|
186
|
+
// Today's producer cannot send 0 — `health.rs` emits both fields only inside
|
|
187
|
+
// `if !parse_error_files.is_empty()` — so the distinction is a contract for
|
|
188
|
+
// the next producer, not an observed shape. Said plainly because the comment
|
|
189
|
+
// used to imply 0 arrives in practice, and because the test that pins it had
|
|
190
|
+
// to be added after a mutation to truthiness survived.
|
|
191
|
+
//
|
|
192
|
+
// No `fixId`, and `advisory: true`. The repair is editing the source, which
|
|
193
|
+
// is the user's to do — and on this very repository the cause is a pinned
|
|
194
|
+
// tree-sitter grammar bug the README documents as unfixed, so for 8 of 161
|
|
195
|
+
// Rust files there is no user action at all.
|
|
196
|
+
//
|
|
197
|
+
// The advisory flag is the load-bearing half. `runDoctor` counts
|
|
198
|
+
// `issues.filter(r => !r.advisory)` and `runDoctorCli` exits 1 when that is
|
|
199
|
+
// non-empty, so a plain warn with no fixId pins the exit code at 1 forever on
|
|
200
|
+
// any repo holding one bad file — including under `--check-only`, and
|
|
201
|
+
// including this one. Measured: same HOME, same project, add one file with
|
|
202
|
+
// unbalanced delimiters and reindex → exit 0 → 1; remove it and reindex →
|
|
203
|
+
// back to 0. The `Embeddings` row above carries the same flag for the same
|
|
204
|
+
// reason.
|
|
205
|
+
if (hc.files_with_parse_errors !== undefined) {
|
|
206
|
+
const named = Array.isArray(hc.parse_error_files) ? hc.parse_error_files : [];
|
|
207
|
+
const more = hc.files_with_parse_errors - named.length;
|
|
208
|
+
rows.push({
|
|
209
|
+
name: 'Parse', status: 'warn', advisory: true,
|
|
210
|
+
detail: `${hc.files_with_parse_errors} file(s) indexed over a damaged parse — `
|
|
211
|
+
+ `symbols may be missing: ${named.join(', ')}${more > 0 ? `, and ${more} more` : ''}`,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
180
214
|
return rows;
|
|
181
215
|
}
|
|
182
216
|
|
|
@@ -426,6 +460,21 @@ function runDiagnostics({ checkOnly = false } = {}) {
|
|
|
426
460
|
: healthCheck();
|
|
427
461
|
if (hookResult.healthy) {
|
|
428
462
|
results.push({ name: 'Hooks', status: 'ok', detail: 'all paths valid' });
|
|
463
|
+
} else if (hookResult.skippedForTeardown) {
|
|
464
|
+
// healthCheck() declined the repair half because a teardown tombstone is
|
|
465
|
+
// standing. Advisory, and with no fixId, for the same reason
|
|
466
|
+
// `teardownRepairGuard` gives on the two arms below: these broken paths are
|
|
467
|
+
// the uninstall, not damage. Without `advisory` this would exit 1 for the
|
|
468
|
+
// life of the tombstone.
|
|
469
|
+
results.push({
|
|
470
|
+
name: 'Hooks',
|
|
471
|
+
status: 'warn',
|
|
472
|
+
advisory: true,
|
|
473
|
+
detail:
|
|
474
|
+
`${hookResult.issues.length} stale path(s) — NOT repaired: a teardown ran moments ago, ` +
|
|
475
|
+
`so these are the uninstalled state. If that was not intended: ` +
|
|
476
|
+
`node "${path.join(__dirname, 'lifecycle.js')}" install`,
|
|
477
|
+
});
|
|
429
478
|
} else if (hookResult.repaired && hookResult.rebuiltFrom) {
|
|
430
479
|
// The repair WORKED, but it worked by replacing an unusable settings.json
|
|
431
480
|
// with a freshly built one — the user's model / env / permissions / own
|
|
@@ -662,6 +711,34 @@ function formatReport(results, { checkOnly = false } = {}) {
|
|
|
662
711
|
* Returns true (and prints redirection) when this copy must NOT write config.
|
|
663
712
|
* `relic` is injectable for tests.
|
|
664
713
|
*/
|
|
714
|
+
/**
|
|
715
|
+
* Second gate on the same two settings-writing arms: is this the state a
|
|
716
|
+
* teardown just produced, rather than damage?
|
|
717
|
+
*
|
|
718
|
+
* `doctor` repairs by default and is the natural way to check an uninstall
|
|
719
|
+
* worked. Reproduced: a clean teardown, then one `doctor` with no flags, and
|
|
720
|
+
* all six hook entries were back in settings.json and the teardown tombstone
|
|
721
|
+
* was gone — re-opening the 41 MB auto-update race the tombstone closes, since
|
|
722
|
+
* `install({ clearTombstone: true })` erases the one marker the three gates in
|
|
723
|
+
* auto-update.js read.
|
|
724
|
+
*
|
|
725
|
+
* `relicRepairGuard` cannot cover this: it asks whether THIS copy is the active
|
|
726
|
+
* install, and returns false the moment `activeInstallPath()` is null — exactly
|
|
727
|
+
* the post-uninstall state.
|
|
728
|
+
*
|
|
729
|
+
* Bounded by the tombstone's own TTL, and deliberately: past the window a
|
|
730
|
+
* torn-down install is indistinguishable from one that was installed but never
|
|
731
|
+
* set up, which IS a repair case. `torndown` is injectable for tests.
|
|
732
|
+
*/
|
|
733
|
+
function teardownRepairGuard({ log = console.log, torndown = undefined } = {}) {
|
|
734
|
+
const isTorndown = torndown !== undefined ? torndown : uninstallTombstoneActive();
|
|
735
|
+
if (!isTorndown) return false;
|
|
736
|
+
log(' ⚠ A teardown ran moments ago — skipping settings repair.');
|
|
737
|
+
log(' Missing hooks ARE the uninstalled state here, not damage to repair.');
|
|
738
|
+
log(` If that was not intended: node "${path.join(__dirname, 'lifecycle.js')}" install`);
|
|
739
|
+
return true;
|
|
740
|
+
}
|
|
741
|
+
|
|
665
742
|
function relicRepairGuard({ log = console.log, relic = undefined } = {}) {
|
|
666
743
|
const { isStaleRelicContext, activeInstallPath } = require('./lifecycle');
|
|
667
744
|
const isRelic = relic !== undefined ? relic : isStaleRelicContext();
|
|
@@ -1133,6 +1210,7 @@ function runRepairs(results, {
|
|
|
1133
1210
|
case 'hooks-invalid': {
|
|
1134
1211
|
console.log('\n Repairing hooks...');
|
|
1135
1212
|
if (relicRepairGuard()) break;
|
|
1213
|
+
if (teardownRepairGuard()) break;
|
|
1136
1214
|
const { install, scanForBrokenPaths } = require('./lifecycle');
|
|
1137
1215
|
const installResult = install({ clearTombstone: true });
|
|
1138
1216
|
// Diagnosis already ran install()+re-scan and the paths were STILL
|
|
@@ -1172,6 +1250,7 @@ function runRepairs(results, {
|
|
|
1172
1250
|
case 'missing-hooks-in-settings': {
|
|
1173
1251
|
console.log('\n Registering code-graph hooks in settings.json...');
|
|
1174
1252
|
if (relicRepairGuard()) break;
|
|
1253
|
+
if (teardownRepairGuard()) break;
|
|
1175
1254
|
const { install } = require('./lifecycle');
|
|
1176
1255
|
const r = install({ clearTombstone: true });
|
|
1177
1256
|
if (r.hooksRegistered) {
|
|
@@ -1289,7 +1368,7 @@ function runDoctor(opts = {}) {
|
|
|
1289
1368
|
return { results, issueCount: issues.length, unresolved };
|
|
1290
1369
|
}
|
|
1291
1370
|
|
|
1292
|
-
module.exports = { runDiagnostics, formatReport, runRepairs, runDoctor, runDoctorCli, parseDoctorArgs, unresolvedCount, surveyHookCoverage, relicRepairGuard, classifyEmbeddings, classifyIntegrity, classifyHealthReport, parseHealthPayload, integrityResolved, healthRows, detectEmbedModel, devBuildCommand, binaryVersionResolved, updateIncompleteResolved, binaryBrokenResolved, autoUpdateNoOpReason, autoUpdateLastError, silentFailureReason };
|
|
1371
|
+
module.exports = { runDiagnostics, formatReport, runRepairs, runDoctor, runDoctorCli, parseDoctorArgs, unresolvedCount, surveyHookCoverage, relicRepairGuard, teardownRepairGuard, classifyEmbeddings, classifyIntegrity, classifyHealthReport, parseHealthPayload, integrityResolved, healthRows, detectEmbedModel, devBuildCommand, binaryVersionResolved, updateIncompleteResolved, binaryBrokenResolved, autoUpdateNoOpReason, autoUpdateLastError, silentFailureReason };
|
|
1293
1372
|
|
|
1294
1373
|
// Shared by BOTH doctor entry points: `node doctor.js …` and `node lifecycle.js
|
|
1295
1374
|
// doctor …`. It exists as one function because the first version of this guard
|
|
@@ -7,10 +7,18 @@ const os = require('os');
|
|
|
7
7
|
const { readBinaryVersion, compareVersions } = require('./version-utils');
|
|
8
8
|
const { npmInvocation } = require('./npm-exec');
|
|
9
9
|
const { hidden } = require('./proc-opts');
|
|
10
|
+
// The teardown predicate and this module's own cache path. cache-paths.js is
|
|
11
|
+
// deliberately tiny and builtin-only for exactly this kind of hook-path
|
|
12
|
+
// consumer, so the import costs nothing measurable.
|
|
13
|
+
const { uninstallTombstoneActive, BINARY_PATH_FILE } = require('./cache-paths');
|
|
10
14
|
|
|
11
15
|
const PLATFORM = os.platform();
|
|
12
16
|
const ARCH = os.arch();
|
|
13
|
-
|
|
17
|
+
// Still exported under this name — several modules and tests import
|
|
18
|
+
// `CACHE_FILE` from here — but no longer spelled here. `cache-paths.js` owns
|
|
19
|
+
// every `~/.cache/code-graph` file name, so an audit of who writes into that
|
|
20
|
+
// directory can be done from one file instead of by grep.
|
|
21
|
+
const CACHE_FILE = BINARY_PATH_FILE;
|
|
14
22
|
const BINARY_NAME = PLATFORM === 'win32' ? 'code-graph-mcp.exe' : 'code-graph-mcp';
|
|
15
23
|
// PATH lookup bound (NEW-07). 2 s matches the sibling `npm root -g` probe; the
|
|
16
24
|
// floor is what a budget-exhausted hook still gives it, because a `which` that
|
|
@@ -318,6 +326,22 @@ function readCacheEntry() {
|
|
|
318
326
|
}
|
|
319
327
|
|
|
320
328
|
function writeCacheEntry(binPath) {
|
|
329
|
+
// A teardown is in flight, and `path.dirname(CACHE_FILE)` IS the directory it
|
|
330
|
+
// just reclaimed — so the mkdir below re-creates it to hold a resolution
|
|
331
|
+
// nobody will read. auto-update.js guards its three writers the same way;
|
|
332
|
+
// this one was missed because it reaches CACHE_DIR through its own
|
|
333
|
+
// `os.homedir()` join instead of cache-paths.js, and it is the most-called of
|
|
334
|
+
// the four (every cold binary resolution on every hook). Measured: one
|
|
335
|
+
// findBinary() after a complete uninstall brought `~/.cache/code-graph/` back.
|
|
336
|
+
//
|
|
337
|
+
// Skipping costs a re-probe on the next call, not a wrong answer: the caller
|
|
338
|
+
// already has the resolved path, and this only memoizes it. Measured, so the
|
|
339
|
+
// trade is on the record rather than asserted: `findBinary()` runs ~5 ms warm
|
|
340
|
+
// (4-5 ms, n=5) against ~84 ms with the memo suppressed (71-93 ms, n=5) on a
|
|
341
|
+
// box where the binary resolves through a global npm install — roughly +79 ms
|
|
342
|
+
// per cold hook process, bounded by the tombstone's 5-minute TTL (pre-ship
|
|
343
|
+
// review).
|
|
344
|
+
if (uninstallTombstoneActive()) return;
|
|
321
345
|
try {
|
|
322
346
|
fs.mkdirSync(path.dirname(CACHE_FILE), { recursive: true });
|
|
323
347
|
fs.writeFileSync(
|
|
@@ -109,6 +109,36 @@ function installBinaryInBackground({
|
|
|
109
109
|
lockPath = null,
|
|
110
110
|
}) {
|
|
111
111
|
let lock = null;
|
|
112
|
+
// DELIBERATELY outside the teardown tombstone, unlike every other writer that
|
|
113
|
+
// can re-create CACHE_DIR (auto-update.js gates its three; find-binary.js's
|
|
114
|
+
// cold-cache memo is gated too). The reason, corrected twice by pre-ship
|
|
115
|
+
// review — the first version of this comment got both the cost and the
|
|
116
|
+
// benefit wrong:
|
|
117
|
+
//
|
|
118
|
+
// COST, measured rather than assumed. `acquireLock` mkdirs the lock file's
|
|
119
|
+
// parent, which IS CACHE_DIR, so a teardown landing here does get the
|
|
120
|
+
// directory back. What lands in it is `install.lock` — **47 bytes**, measured
|
|
121
|
+
// with a tombstone standing in a sandbox HOME. Not the ~41 MB an earlier
|
|
122
|
+
// version of this comment claimed: the npm arm below installs into the npm
|
|
123
|
+
// global prefix, not CACHE_DIR.
|
|
124
|
+
//
|
|
125
|
+
// BENEFIT, and it is narrower than it looks. The tombstone cannot tell a
|
|
126
|
+
// teardown from a REINSTALL: `/plugin install` reaches `install()` through
|
|
127
|
+
// session-init, which by design does not pass `clearTombstone` (a record any
|
|
128
|
+
// concurrent session may erase protects nobody — see lifecycle.js's
|
|
129
|
+
// install()). So right after a legitimate reinstall the tombstone still
|
|
130
|
+
// stands and the cache is empty, and this path is what puts a binary back.
|
|
131
|
+
// But only its FIRST arm is: the GitHub fallback below spawns auto-update
|
|
132
|
+
// with --install-missing, and `downloadBinary` opens with its own
|
|
133
|
+
// `tombstoneActive()` check, so on a machine where `npm install -g` fails —
|
|
134
|
+
// offline registry, npm absent, EACCES on the global prefix, precisely why
|
|
135
|
+
// that fallback exists — the reinstall already gets nothing for the rest of
|
|
136
|
+
// the TTL. Gating here would extend that to the npm arm as well, which is the
|
|
137
|
+
// arm that currently works.
|
|
138
|
+
//
|
|
139
|
+
// So: 47 bytes of residue against the one reinstall path that survives the
|
|
140
|
+
// window. Not a crash either way — the missing-binary path already answers
|
|
141
|
+
// the handshake with `serveEmptyMcpStub`.
|
|
112
142
|
if (lockPath) {
|
|
113
143
|
lock = acquireLock(lockPath);
|
|
114
144
|
if (!lock) {
|
|
@@ -22,7 +22,7 @@ const MARKETPLACE_NAME = 'code-graph-mcp';
|
|
|
22
22
|
// `MANIFEST_FILE` is module-private and always was, so nothing importable moved.
|
|
23
23
|
const {
|
|
24
24
|
CACHE_DIR, MANIFEST_FILE, INSTALL_LOCK_FILE,
|
|
25
|
-
writeUninstallTombstone, clearUninstallTombstone,
|
|
25
|
+
writeUninstallTombstone, clearUninstallTombstone, uninstallTombstoneActive,
|
|
26
26
|
} = require('./cache-paths');
|
|
27
27
|
// Bound for the `ps` fallback in getActiveCmdlines (pre-ship review 2026-09-06).
|
|
28
28
|
// 2 s matches the other hook-path probes; the floor is what a budget-exhausted
|
|
@@ -1340,7 +1340,15 @@ function install({ reclaimStatusline = false, clearTombstone = false } = {}) {
|
|
|
1340
1340
|
// So only the two genuinely user-initiated entry points pass true: the
|
|
1341
1341
|
// `lifecycle.js install` CLI and doctor's repair pass. Everything automatic —
|
|
1342
1342
|
// session-init's install reporting, healthCheck's repair — leaves it standing
|
|
1343
|
-
// and lets the TTL expire it.
|
|
1343
|
+
// and lets the TTL expire it.
|
|
1344
|
+
//
|
|
1345
|
+
// Doctor's half is now vestigial, and saying so beats leaving a reader to
|
|
1346
|
+
// work it out: `teardownRepairGuard` returns before both of doctor's
|
|
1347
|
+
// `install({ clearTombstone: true })` sites, so doctor can only ever clear a
|
|
1348
|
+
// tombstone that has already expired — at which point there is nothing to
|
|
1349
|
+
// clear. Kept rather than removed because a concurrent teardown can still
|
|
1350
|
+
// write one between the guard check and the install, and because the flag is
|
|
1351
|
+
// the honest statement of intent for that call site. The cost is that a reinstall inside the window
|
|
1344
1352
|
// may skip one update check, which the spec already accepts; the install
|
|
1345
1353
|
// itself supplies the binary.
|
|
1346
1354
|
if (clearTombstone) clearUninstallTombstone();
|
|
@@ -1959,13 +1967,38 @@ function scanForBrokenPaths() {
|
|
|
1959
1967
|
return issues;
|
|
1960
1968
|
}
|
|
1961
1969
|
|
|
1962
|
-
function healthCheck() {
|
|
1970
|
+
function healthCheck({ torndown = uninstallTombstoneActive } = {}) {
|
|
1963
1971
|
const issues = scanForBrokenPaths();
|
|
1964
1972
|
|
|
1965
1973
|
if (issues.length === 0) {
|
|
1966
1974
|
return { healthy: true, issues, repaired: false };
|
|
1967
1975
|
}
|
|
1968
1976
|
|
|
1977
|
+
// A teardown just ran: these broken paths ARE the uninstall, and the
|
|
1978
|
+
// auto-repair below is an install().
|
|
1979
|
+
//
|
|
1980
|
+
// This is the second door to the same room. `doctor`'s two settings-writing
|
|
1981
|
+
// repair arms are guarded, but `runDiagnostics` reaches HERE first on the
|
|
1982
|
+
// default (repairing) path, and this call site sits in front of neither
|
|
1983
|
+
// guard. Reproduced: sandbox HOME, plugin installed, all six hook commands
|
|
1984
|
+
// repointed at a nonexistent dir, fresh tombstone standing, one `node
|
|
1985
|
+
// doctor.js` — `Hooks ✅ 6 issue(s) auto-repaired`, settings.json back from
|
|
1986
|
+
// six broken paths to zero, and doctor's own guard never printed. Guarding
|
|
1987
|
+
// the repair arms while leaving the diagnosis free to install is a fix that
|
|
1988
|
+
// only looks like one (pre-ship review, reviewer-reproduced).
|
|
1989
|
+
//
|
|
1990
|
+
// Placed in healthCheck rather than at doctor's call site so `lifecycle.js
|
|
1991
|
+
// health` is covered by the same gate: both are "scan and auto-repair"
|
|
1992
|
+
// entry points, and one of the two remembering is how this reappears.
|
|
1993
|
+
//
|
|
1994
|
+
// Honest return: `healthy: false` with `repaired: false` is exactly what the
|
|
1995
|
+
// caller should render — the paths really are broken, and nothing fixed
|
|
1996
|
+
// them. `skippedForTeardown` lets a caller say WHY instead of implying the
|
|
1997
|
+
// repair was attempted and failed.
|
|
1998
|
+
if (torndown()) {
|
|
1999
|
+
return { healthy: false, issues, repaired: false, skippedForTeardown: true };
|
|
2000
|
+
}
|
|
2001
|
+
|
|
1969
2002
|
// Attempt auto-repair, then re-scan to confirm the issues actually went
|
|
1970
2003
|
// away. install() may legitimately fail to resolve a problem (binary path
|
|
1971
2004
|
// permanently gone, registry corrupted, etc.) and the previous code lied
|
|
@@ -35,7 +35,7 @@ jobs:
|
|
|
35
35
|
node-version: '20'
|
|
36
36
|
- name: Build snapshot
|
|
37
37
|
run: |
|
|
38
|
-
npx -y -p @sdsrs/code-graph@0.
|
|
38
|
+
npx -y -p @sdsrs/code-graph@0.151.0 code-graph-mcp snapshot create --out snapshot.db
|
|
39
39
|
zstd -9 snapshot.db -o snapshot.db.zst
|
|
40
40
|
mv snapshot.db.zst "code-graph-snapshot-${GITHUB_SHA:0:7}.db.zst"
|
|
41
41
|
- name: Upload to release
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdsrs/code-graph",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.151.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.151.0",
|
|
39
|
+
"@sdsrs/code-graph-linux-arm64": "0.151.0",
|
|
40
|
+
"@sdsrs/code-graph-darwin-x64": "0.151.0",
|
|
41
|
+
"@sdsrs/code-graph-darwin-arm64": "0.151.0",
|
|
42
|
+
"@sdsrs/code-graph-win32-x64": "0.151.0"
|
|
43
43
|
}
|
|
44
44
|
}
|