@sdsrs/code-graph 0.150.0 → 0.152.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 CHANGED
@@ -427,7 +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, but only while indexing: a run prints `Syntax errors in <file> — symbols may be incomplete` per file plus an `N file(s) parsed with syntax errors` total. Nothing replays that afterwards — `health-check` and `doctor` both report a degraded index as healthy, so re-run the index to see the list. Workaround: rename the binding. Measured on this repo: 8 of 161 Rust files trip the warning (the total says 9 — a Markdown file trips the same counter for an unrelated reason), and one symbol is lost, `cmd_affected`. Recovery is usually partial rather than total: across a fixture covering every trigger shape, the surrounding functions and their call edges survived.
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.
431
431
 
432
432
  ## Team-shared graph snapshot
433
433
 
@@ -4,7 +4,7 @@
4
4
  "author": {
5
5
  "name": "sdsrs"
6
6
  },
7
- "version": "0.150.0",
7
+ "version": "0.152.0",
8
8
  "keywords": [
9
9
  "code-graph",
10
10
  "ast",
@@ -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
- const CACHE_FILE = path.join(os.homedir(), '.cache', 'code-graph', 'binary-path');
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. The cost is that a reinstall inside the window
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.150.0 code-graph-mcp snapshot create --out snapshot.db
38
+ npx -y -p @sdsrs/code-graph@0.152.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.150.0",
3
+ "version": "0.152.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.150.0",
39
- "@sdsrs/code-graph-linux-arm64": "0.150.0",
40
- "@sdsrs/code-graph-darwin-x64": "0.150.0",
41
- "@sdsrs/code-graph-darwin-arm64": "0.150.0",
42
- "@sdsrs/code-graph-win32-x64": "0.150.0"
38
+ "@sdsrs/code-graph-linux-x64": "0.152.0",
39
+ "@sdsrs/code-graph-linux-arm64": "0.152.0",
40
+ "@sdsrs/code-graph-darwin-x64": "0.152.0",
41
+ "@sdsrs/code-graph-darwin-arm64": "0.152.0",
42
+ "@sdsrs/code-graph-win32-x64": "0.152.0"
43
43
  }
44
44
  }