@sdsrs/code-graph 0.138.0 → 0.139.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.
@@ -4,7 +4,7 @@
4
4
  "author": {
5
5
  "name": "sdsrs"
6
6
  },
7
- "version": "0.138.0",
7
+ "version": "0.139.0",
8
8
  "keywords": [
9
9
  "code-graph",
10
10
  "ast",
@@ -35,7 +35,7 @@ function commandExists(cmd) {
35
35
 
36
36
  // ── Configuration ──────────────────────────────────────────
37
37
  const GITHUB_REPO = 'sdsrss/code-graph-mcp';
38
- const STATE_FILE = path.join(CACHE_DIR, 'update-state.json');
38
+ const { UPDATE_STATE_FILE: STATE_FILE, MANIFEST_FILE, INSTALL_LOCK_FILE } = require('./cache-paths');
39
39
  const BINARY_CACHE_DIR = path.join(CACHE_DIR, 'bin');
40
40
  const CHECK_INTERVAL_MS = 6 * 60 * 60 * 1000; // 6h — steady-state re-check
41
41
  const UP_TO_DATE_RECHECK_MS = 30 * 60 * 1000; // 30min — re-verify an "up to date" result (release-race guard)
@@ -956,7 +956,7 @@ async function downloadAndInstall(latest, {
956
956
  const manifest = readManifest();
957
957
  manifest.version = latest.version;
958
958
  manifest.updatedAt = new Date().toISOString();
959
- writeJsonAtomic(path.join(CACHE_DIR, 'install-manifest.json'), manifest);
959
+ writeJsonAtomic(MANIFEST_FILE, manifest);
960
960
  } catch { /* not fatal */ }
961
961
  }
962
962
 
@@ -1272,7 +1272,7 @@ async function checkForUpdate({ installMissing = false, force = false, requestJs
1272
1272
  // shouldHealGlobalsOnThrottle). Cheap local check first; only the actually-
1273
1273
  // stale case takes the slow, lock-guarded npm path.
1274
1274
  if (shouldHealGlobalsOnThrottle(state)) {
1275
- installLock = acquireLock(path.join(CACHE_DIR, 'install.lock'));
1275
+ installLock = acquireLock(INSTALL_LOCK_FILE);
1276
1276
  if (installLock) {
1277
1277
  const globalHeal = await selfHealGlobalPkgs({ version: state.latestVersion }, state);
1278
1278
  saveState({ ...readState(), ...globalHeal });
@@ -1317,7 +1317,7 @@ async function checkForUpdate({ installMissing = false, force = false, requestJs
1317
1317
  // it marks that with CODE_GRAPH_INSTALL_LOCK_HELD so we don't deadlock
1318
1318
  // against our own parent.
1319
1319
  if (process.env.CODE_GRAPH_INSTALL_LOCK_HELD !== '1') {
1320
- installLock = acquireLock(path.join(CACHE_DIR, 'install.lock'));
1320
+ installLock = acquireLock(INSTALL_LOCK_FILE);
1321
1321
  // Reason, not null — same misreport as the fetch failure above: the
1322
1322
  // holder is mid-update, so "Up to date (v<old>)" is exactly wrong.
1323
1323
  if (!installLock) return { noop: true, reason: 'install-lock-held', from: installedVersion };
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+ // Single home for the `~/.cache/code-graph` file names.
3
+ //
4
+ // JS-05 (audit 2026-09-05): `update-state.json` was spelled in five modules,
5
+ // `install.lock` in two, `install-manifest.json` in three — and two of those
6
+ // rebuilt the whole path out of `os.homedir()` rather than joining `CACHE_DIR`,
7
+ // so the modules that would break first if the cache layout moved were also the
8
+ // two that never load the module where it was defined. A rename had to be found
9
+ // by grep. `tests/hardening.rs::cache_file_names_have_exactly_one_spelling`
10
+ // keeps this the only file that may spell them.
11
+ //
12
+ // Deliberately tiny and dependency-free beyond node builtins, for ONE consumer:
13
+ // `user-prompt-context.js` runs on the UserPromptSubmit hook path and does not
14
+ // load `lifecycle.js`, so taking a path string from there would cost it the
15
+ // whole module. Measured at 30.1 ms as shipped versus 34.5 ms if it also loaded
16
+ // `lifecycle.js` (+14%).
17
+ //
18
+ // `statusline.js` was the other hardcoded spelling and is NOT a reason for this
19
+ // file — it already does `require('./lifecycle')`, so its copy of the three path
20
+ // segments bought nothing. An earlier version of this comment claimed both
21
+ // modules needed the split (pre-ship review 2026-09-07).
22
+ const os = require('os');
23
+ const path = require('path');
24
+
25
+ const CACHE_DIR = path.join(os.homedir(), '.cache', 'code-graph');
26
+
27
+ /** Auto-update bookkeeping: available version, attempt counters, last error. */
28
+ const UPDATE_STATE_FILE = path.join(CACHE_DIR, 'update-state.json');
29
+ /** What the last install wrote, and where. Absent = never installed. */
30
+ const MANIFEST_FILE = path.join(CACHE_DIR, 'install-manifest.json');
31
+ /** Inter-process install lock (see install-lock.js). */
32
+ const INSTALL_LOCK_FILE = path.join(CACHE_DIR, 'install.lock');
33
+
34
+ module.exports = {
35
+ CACHE_DIR,
36
+ UPDATE_STATE_FILE,
37
+ MANIFEST_FILE,
38
+ INSTALL_LOCK_FILE,
39
+ };
@@ -6,10 +6,11 @@ const path = require('path');
6
6
  const os = require('os');
7
7
  const { readBinaryVersion, isDevMode, getNewestMtime } = require('./version-utils');
8
8
  const {
9
- getPluginVersion, readJson, readJsonResult, healthCheck, scanForBrokenPaths, CACHE_DIR,
9
+ getPluginVersion, readJson, readJsonResult, healthCheck, scanForBrokenPaths,
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
14
  const { findBinary, clearCache: clearBinaryCache } = require('./find-binary');
14
15
  const { hidden } = require('./proc-opts');
15
16
  const { MAX_UPDATE_ATTEMPTS, GLOBAL_PKG_HEAL_MAX_ATTEMPTS, isBinaryHealExhausted, readState } = require('./auto-update');
@@ -589,7 +590,7 @@ function runDiagnostics({ checkOnly = false } = {}) {
589
590
  // this line used to hardcode 3 while the comment named the constant, so
590
591
  // raising the cap in auto-update.js would have left this diagnosis
591
592
  // reporting the old one (audit 2026-08-29 JS-07).
592
- const state = readJson(path.join(CACHE_DIR, 'update-state.json')) || {};
593
+ const state = readJson(UPDATE_STATE_FILE) || {};
593
594
  const healGaveUp = (state.globalPkgHealAttempts || 0) >= GLOBAL_PKG_HEAL_MAX_ATTEMPTS;
594
595
  results.push({
595
596
  name: 'Global npm packages',
@@ -858,7 +859,7 @@ function probeHealth({ find = findBinary } = {}) {
858
859
  }
859
860
 
860
861
  function readUpdateState() {
861
- try { return readJson(path.join(CACHE_DIR, 'update-state.json')); } catch { return null; }
862
+ try { return readJson(UPDATE_STATE_FILE); } catch { return null; }
862
863
  }
863
864
 
864
865
  /**
@@ -12,7 +12,15 @@ const OLD_PLUGIN_IDS = [
12
12
  'code-graph@sdsrss-code-graph', // v2 legacy ID (pre-rename)
13
13
  ];
14
14
  const MARKETPLACE_NAME = 'code-graph-mcp';
15
- const CACHE_DIR = path.join(os.homedir(), '.cache', 'code-graph');
15
+ // Imported, not defined here: five other modules needed these three names and
16
+ // one of them — `user-prompt-context.js`, on the UserPromptSubmit hook path —
17
+ // cannot afford to load this file for them (see cache-paths.js, JS-05).
18
+ //
19
+ // The export surface below is unchanged. Measured rather than asserted, after
20
+ // the first version of this comment claimed `MANIFEST_FILE` was exported from
21
+ // here: `CACHE_DIR` and `INSTALL_LOCK_FILE` are exported and still are;
22
+ // `MANIFEST_FILE` is module-private and always was, so nothing importable moved.
23
+ const { CACHE_DIR, MANIFEST_FILE, INSTALL_LOCK_FILE } = require('./cache-paths');
16
24
  // Bound for the `ps` fallback in getActiveCmdlines (pre-ship review 2026-09-06).
17
25
  // 2 s matches the other hook-path probes; the floor is what a budget-exhausted
18
26
  // hook still gives it, since an empty list degrades to recency-only.
@@ -22,14 +30,12 @@ const PS_PROBE_MIN_MS = 250;
22
30
  // plugins when hooks run in shared process context (e.g. claude-mem-lite sets it
23
31
  // to its own marketplace path, polluting all subsequent settings.json hook processes).
24
32
  const PLUGIN_ROOT = path.resolve(__dirname, '..');
25
- const MANIFEST_FILE = path.join(CACHE_DIR, 'install-manifest.json');
26
33
  const REGISTRY_FILE = path.join(CACHE_DIR, 'statusline-registry.json');
27
34
  // Written by the launcher's background install when ITS `npm install -g` step
28
35
  // introduced the global shell + platform packages. Uninstall only removes
29
36
  // global packages it can prove the plugin installed (marker present) or when
30
37
  // the user passes --purge-global — a deliberate user install is never yanked.
31
38
  const GLOBAL_INSTALL_MARKER = path.join(CACHE_DIR, 'global-install-marker.json');
32
- const INSTALL_LOCK_FILE = path.join(CACHE_DIR, 'install.lock');
33
39
  const SHELL_PKG = '@sdsrs/code-graph';
34
40
 
35
41
  // Lazy resolvers — Claude Code's config dir can be overridden by CLAUDE_CONFIG_DIR
@@ -8,6 +8,7 @@ const {
8
8
  cleanupDisabledStatusline, isPluginInactive, isPluginUninstalled, removeCacheResidue,
9
9
  readJson, CACHE_DIR, settingsPath, isStaleRelicContext, hookCmdScript,
10
10
  } = require('./lifecycle');
11
+ const { UPDATE_STATE_FILE } = require('./cache-paths');
11
12
  const { readBinaryVersion, isDevMode, getNewestMtime } = require('./version-utils');
12
13
  const { maybeAutoAdopt, isAdopted, unadopt } = require('./adopt');
13
14
  const { isNonProjectCwd } = require('./project-detect');
@@ -604,7 +605,7 @@ function consistencyCheck(binary) {
604
605
 
605
606
  // Check 3: Auto-update incomplete
606
607
  try {
607
- const statePath = path.join(CACHE_DIR, 'update-state.json');
608
+ const statePath = UPDATE_STATE_FILE;
608
609
  const state = readJson(statePath);
609
610
  if (state && state.updateAvailable && state.binaryUpdated === false) {
610
611
  issues.push({
@@ -10,11 +10,11 @@ if (require.main === module) require('./hook-fail-open').installHookFailOpen('st
10
10
 
11
11
  const { execFileSync } = require('child_process');
12
12
  const fs = require('fs');
13
- const os = require('os');
14
13
  const path = require('path');
15
14
  const { findBinary } = require('./find-binary');
16
15
  const { resolveProjectRoot } = require('./project-root');
17
16
  const lifecycle = require('./lifecycle');
17
+ const { UPDATE_STATE_FILE } = require('./cache-paths');
18
18
  const { hidden } = require('./proc-opts');
19
19
  const cleanupDisabledStatusline = lifecycle.cleanupDisabledStatusline || (() => ({ cleaned: false }));
20
20
 
@@ -29,8 +29,7 @@ const cleanupDisabledStatusline = lifecycle.cleanupDisabledStatusline || (() =>
29
29
  const STUCK_UPDATE_ATTEMPTS = 5;
30
30
  function readUpdateState() {
31
31
  try {
32
- return JSON.parse(fs.readFileSync(
33
- path.join(os.homedir(), '.cache', 'code-graph', 'update-state.json'), 'utf8'));
32
+ return JSON.parse(fs.readFileSync(UPDATE_STATE_FILE, 'utf8'));
34
33
  } catch { return null; /* no state file or unreadable */ }
35
34
  }
36
35
  function updatePending(st = readUpdateState()) {
@@ -14,7 +14,6 @@ if (require.main === module) require('./hook-fail-open').installHookFailOpen('Us
14
14
  const { execFileSync } = require('child_process');
15
15
  const fs = require('fs');
16
16
  const path = require('path');
17
- const os = require('os');
18
17
  // Hook cooldown flags + the restart notice go under cgTmpDir() (a code-graph-mcp/
19
18
  // subdir of os.tmpdir()), NOT bare os.tmpdir(): Claude Code overrides $TMPDIR to
20
19
  // ~/.claude/tmp/, so bare-tmp flags interleave with transcript captures —
@@ -23,8 +22,11 @@ const os = require('os');
23
22
  const { cgTmpDir, cwdHash } = require('./tmp-dir');
24
23
  const { hidden } = require('./proc-opts');
25
24
 
26
- // Mid-session install detection: hook fires but no manifest yet.
27
- const MANIFEST_PATH = path.join(os.homedir(), '.cache', 'code-graph', 'install-manifest.json');
25
+ // Mid-session install detection: hook fires but no manifest yet. The path comes
26
+ // from `cache-paths.js` — deliberately NOT from `lifecycle.js`, which this hook
27
+ // must not load (startup budget); it used to be a third hand-spelled copy of the
28
+ // same three path segments (JS-05).
29
+ const { MANIFEST_FILE: MANIFEST_PATH } = require('./cache-paths');
28
30
 
29
31
  // --- Per-type rate limiting (replaces single global cooldown) ---
30
32
  const COOLDOWNS = {
@@ -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.138.0 code-graph-mcp snapshot create --out snapshot.db
38
+ npx -y -p @sdsrs/code-graph@0.139.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.138.0",
3
+ "version": "0.139.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.138.0",
39
- "@sdsrs/code-graph-linux-arm64": "0.138.0",
40
- "@sdsrs/code-graph-darwin-x64": "0.138.0",
41
- "@sdsrs/code-graph-darwin-arm64": "0.138.0",
42
- "@sdsrs/code-graph-win32-x64": "0.138.0"
38
+ "@sdsrs/code-graph-linux-x64": "0.139.0",
39
+ "@sdsrs/code-graph-linux-arm64": "0.139.0",
40
+ "@sdsrs/code-graph-darwin-x64": "0.139.0",
41
+ "@sdsrs/code-graph-darwin-arm64": "0.139.0",
42
+ "@sdsrs/code-graph-win32-x64": "0.139.0"
43
43
  }
44
44
  }