@sdsrs/code-graph 0.169.0 → 0.170.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 +3 -1
- package/claude-plugin/.claude-plugin/plugin.json +1 -1
- package/claude-plugin/scripts/cg-answer.js +62 -1
- package/claude-plugin/scripts/post-grep-inject.js +17 -6
- package/claude-plugin/scripts/recommendation-log.js +14 -2
- package/claude-plugin/scripts/statusline-composite.js +22 -1
- package/claude-plugin/scripts/statusline.js +67 -1
- package/claude-plugin/templates/code-graph-snapshot.yml +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -201,7 +201,7 @@ What you get:
|
|
|
201
201
|
- **Skills** — `explore` (structure-first navigation before reading files) and `index` (health-check / re-index / full rebuild); see [Plugin Skills](#plugin-skills)
|
|
202
202
|
- **Code Explorer Agent** — Deep code understanding expert via `code-explorer`
|
|
203
203
|
- **Auto-indexing Hook** — Incremental index on every file edit (PostToolUse)
|
|
204
|
-
- **StatusLine** — Real-time health display (nodes, files, watch status) — compatible with other plugins' StatusLine via composite multiplexer
|
|
204
|
+
- **StatusLine** — Real-time health display (nodes, files, watch status) and what code-graph did in this session (`5 answers · 2 calls`: answers its grep, edit, read, subagent and Stop hooks delivered to Claude, code-graph queries Claude ran) — compatible with other plugins' StatusLine via composite multiplexer
|
|
205
205
|
- **Auto-update** — Checks for a new version at session start (throttled to at most one check every 2 minutes). Between forced checks the re-check interval is 30 minutes after an "up to date" answer and 6 hours while an update is already pending. Updates install silently.
|
|
206
206
|
|
|
207
207
|
#### Manual Update
|
|
@@ -552,6 +552,7 @@ defaults are what you get by doing nothing.
|
|
|
552
552
|
| `CODE_GRAPH_VERBOSE_HOOKS=1` | The opposite: opt into the noisy form. |
|
|
553
553
|
| `CODE_GRAPH_NO_BLOCK_GREP=1` | Never turn a `grep` hint into a block — prefix a single command with it to get past one. |
|
|
554
554
|
| `CODE_GRAPH_NO_INJECT=1` | No post-tool AST context injection. |
|
|
555
|
+
| `CODE_GRAPH_STATUSLINE_COUNTS=0` | Drop the session counts (`5 answers · 2 calls`) from the statusline. |
|
|
555
556
|
| `CODE_GRAPH_HOOK_INDEX=on\|off` | Force the incremental-index hook on or off instead of letting it decide. |
|
|
556
557
|
| `CODE_GRAPH_MODEL_DIR=<dir>` | Load the embedding model from here (air-gapped installs). |
|
|
557
558
|
| `CODE_GRAPH_DISABLE_MODEL_DOWNLOAD=1` | Never fetch the model; fail instead. |
|
|
@@ -609,6 +610,7 @@ plugins' entries. An installer that reads exit codes must not treat it as
|
|
|
609
610
|
| Variable | Set by | Purpose |
|
|
610
611
|
|---|---|---|
|
|
611
612
|
| `CODE_GRAPH_STATUSLINE_CWD` | statusline-composite | Forwards Claude Code's authoritative cwd to each statusline provider. |
|
|
613
|
+
| `CODE_GRAPH_STATUSLINE_SESSION` | statusline-composite | Forwards the stdin `session_id` (UUID-shaped only), so the code-graph segment counts this session's answers and calls. |
|
|
612
614
|
| `CODE_GRAPH_STATUSLINE_DEBUG` | test suite | Makes statusline-composite print why it dropped a provider instead of swallowing the error. Off by default so a broken third-party provider stays silent in a real statusline. |
|
|
613
615
|
| `CODE_GRAPH_INSTALL_LOCK_HELD` | launcher | Tells a child that the parent already holds the install lock, so it does not deadlock. |
|
|
614
616
|
| `CODE_GRAPH_AUTO_UPDATE_SILENT` | session-init | Runs the update check without console output. |
|
|
@@ -559,8 +559,69 @@ function runCallgraphAnswer(opts = {}) {
|
|
|
559
559
|
}
|
|
560
560
|
}
|
|
561
561
|
|
|
562
|
+
// A definition label longer than this is cut (a C++ or Go qualified name can
|
|
563
|
+
// keep text from its declaration).
|
|
564
|
+
const DEFINITION_LABEL_MAX = 160;
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Where exactly `symbol` is defined in `file`: `show <symbol> --file <file>
|
|
568
|
+
* --json`, kept only when ONE row in that file carries the name (`X`, a
|
|
569
|
+
* qualified `Cls.X` or `mod::X`). The outside-path grep inject names this in
|
|
570
|
+
* place of a command to run (tasks/specs/outside-path-definition-answer.md):
|
|
571
|
+
* the model's next step after that pointer was a read of the definition, not
|
|
572
|
+
* a code-graph call (0 of 45 such pointers was followed by a code-graph call
|
|
573
|
+
* within 10 tool calls; 23 by a Read of the defining file or a Bash/Grep call
|
|
574
|
+
* naming it).
|
|
575
|
+
*
|
|
576
|
+
* `label` is `<type> <name>` on one line, whitespace collapsed, at most
|
|
577
|
+
* DEFINITION_LABEL_MAX characters (`…` when cut). No signature: each
|
|
578
|
+
* extractor writes its own shape, and the lines are what the next read needs.
|
|
579
|
+
* @returns {{status: 'hits', start: number, end: number, label: string}
|
|
580
|
+
* | {status: 'several'} | {status: 'no-hits'}
|
|
581
|
+
* | {status: 'no-binary'} | {status: 'unavailable', reason?: 'budget'}}
|
|
582
|
+
*/
|
|
583
|
+
function runDefinitionAnswer(opts = {}) {
|
|
584
|
+
const { cwd, symbol, file, timeoutMs = DEFAULT_TIMEOUT_MS } = opts;
|
|
585
|
+
try {
|
|
586
|
+
if (typeof symbol !== 'string' || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(symbol)
|
|
587
|
+
|| typeof file !== 'string' || !file) {
|
|
588
|
+
return { status: 'unavailable' };
|
|
589
|
+
}
|
|
590
|
+
const binary = resolveAnswerBinary(opts);
|
|
591
|
+
if (!binary) return { status: 'no-binary' };
|
|
592
|
+
// --compact: the rows without `code_content` (a whole class body otherwise).
|
|
593
|
+
const res = runCg(binary, ['show', symbol, '--file', file, '--json', '--compact'], { cwd, timeoutMs });
|
|
594
|
+
const verdict = classifyRun(res, { exitOneIsNoHits: true });
|
|
595
|
+
if (verdict !== 'ok') {
|
|
596
|
+
return { status: verdict, ...(res.budgetExhausted ? { reason: 'budget' } : {}) };
|
|
597
|
+
}
|
|
598
|
+
let rows;
|
|
599
|
+
try {
|
|
600
|
+
rows = JSON.parse(res.stdout || '');
|
|
601
|
+
} catch {
|
|
602
|
+
return { status: 'unavailable' };
|
|
603
|
+
}
|
|
604
|
+
if (!Array.isArray(rows)) return { status: 'unavailable' };
|
|
605
|
+
const named = rows.filter((r) => r && r.file_path === file && typeof r.name === 'string'
|
|
606
|
+
&& (r.name === symbol || r.name.endsWith(`.${symbol}`) || r.name.endsWith(`::${symbol}`)));
|
|
607
|
+
if (named.length === 0) return { status: 'no-hits' };
|
|
608
|
+
if (named.length > 1) return { status: 'several' };
|
|
609
|
+
const r = named[0];
|
|
610
|
+
if (!Number.isInteger(r.start_line) || !Number.isInteger(r.end_line)) return { status: 'unavailable' };
|
|
611
|
+
let label = `${r.type || 'symbol'} ${r.name}`
|
|
612
|
+
// U+0085 and U+001C-1E too: JS `\s` lacks them, and a reader splitting
|
|
613
|
+
// lines (Python's splitlines) breaks on them.
|
|
614
|
+
.replace(/[\s\u0085\u001c-\u001e]+/g, ' ').trim();
|
|
615
|
+
const chars = [...label];
|
|
616
|
+
if (chars.length > DEFINITION_LABEL_MAX) label = chars.slice(0, DEFINITION_LABEL_MAX - 1).join('') + '…';
|
|
617
|
+
return { status: 'hits', start: r.start_line, end: r.end_line, label };
|
|
618
|
+
} catch {
|
|
619
|
+
return { status: 'unavailable' };
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
562
623
|
module.exports = {
|
|
563
|
-
runGrepAnswer, runShowAnswer, runOverviewAnswer, runCallgraphAnswer,
|
|
624
|
+
runGrepAnswer, runShowAnswer, runOverviewAnswer, runCallgraphAnswer, runDefinitionAnswer,
|
|
564
625
|
truncateAtLine, sanitizeSearchPath,
|
|
565
626
|
// The one argv builder + its renderer, so the deny copy and the child process
|
|
566
627
|
// cannot describe different commands.
|
|
@@ -36,7 +36,9 @@ const { recordRecommendation } = require('./recommendation-log');
|
|
|
36
36
|
// `sanitizeSearchPath` is deliberately NOT imported any more: this hook passes
|
|
37
37
|
// the RAW path so buildGrepArgs can split a glob into scope + `-g` instead of
|
|
38
38
|
// widening it away. Leaving the require behind would read as "still used".
|
|
39
|
-
const {
|
|
39
|
+
const {
|
|
40
|
+
runGrepAnswer, runShowAnswer, runCallgraphAnswer, runDefinitionAnswer, shellQuoteArg,
|
|
41
|
+
} = require('./cg-answer');
|
|
40
42
|
const { emitPostToolContext } = require('./hook-emit');
|
|
41
43
|
const {
|
|
42
44
|
splitTopLevelSegments,
|
|
@@ -214,7 +216,10 @@ const CALLGRAPH_HEADER =
|
|
|
214
216
|
// is looking; a same-named symbol's 4 KB call tree from elsewhere is not about
|
|
215
217
|
// that place (a grep in tests/cli_e2e.rs got `indexed_project`'s tree from
|
|
216
218
|
// src/indexer/resync.rs). Its location was the useful part, so that is what it
|
|
217
|
-
// gets: one
|
|
219
|
+
// gets: one line. It used to end in the `callgraph` command for the tree; no
|
|
220
|
+
// session ran it (0 of 45, 0.164.0 to 2026-10-16) and the next step was a read
|
|
221
|
+
// of the definition, so the line carries the definition's lines, type and name
|
|
222
|
+
// instead when `show` names exactly one (tasks/specs/outside-path-definition-answer.md).
|
|
218
223
|
|
|
219
224
|
/** The file on a `callgraph` answer's root line (`name (path)`), or null. */
|
|
220
225
|
function definitionFile(text) {
|
|
@@ -236,9 +241,14 @@ function searchCovers(searchPath, file) {
|
|
|
236
241
|
return file === p || file.startsWith(p + '/');
|
|
237
242
|
}
|
|
238
243
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
244
|
+
/** @param {object} [def] a runDefinitionAnswer result; only `hits` adds lines, type and name. */
|
|
245
|
+
function buildPointerText(symbol, file, searchPath, def) {
|
|
246
|
+
const where = `${symbol} is defined in ${shellQuoteArg(file)}`;
|
|
247
|
+
const outside = `outside the path your grep searched (${shellQuoteArg(searchPath)})`;
|
|
248
|
+
if (def && def.status === 'hits') {
|
|
249
|
+
return `[code-graph] ${where}:${def.start}-${def.end}, ${outside}: ${def.label}`;
|
|
250
|
+
}
|
|
251
|
+
return `[code-graph] ${where}, ${outside}.`;
|
|
242
252
|
}
|
|
243
253
|
|
|
244
254
|
function buildInjectText(answer, mode) {
|
|
@@ -520,7 +530,8 @@ function runMain() {
|
|
|
520
530
|
...(outside ? { scope: 'outside' } : {}),
|
|
521
531
|
});
|
|
522
532
|
const text = outside
|
|
523
|
-
? buildPointerText(answeredSymbol, rootFile, searchPath
|
|
533
|
+
? buildPointerText(answeredSymbol, rootFile, searchPath,
|
|
534
|
+
runDefinitionAnswer({ cwd: root, symbol: answeredSymbol, file: rootFile }))
|
|
524
535
|
: buildInjectText(answer, answeredMode);
|
|
525
536
|
process.stdout.write(emitPostToolContext(text) + '\n');
|
|
526
537
|
}
|
|
@@ -158,6 +158,16 @@ function rotateIfNeeded(file) {
|
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
// The first 8 hex digits, lowercased, of a UUID-shaped Claude Code session id
|
|
162
|
+
// (CLAUDE_CODE_SESSION_ID, which Claude Code sets for every hook and tool
|
|
163
|
+
// child), else null. Rust `cli::session_tag` is the twin for the CLI's `use`
|
|
164
|
+
// rows, so one session's rows from both writers share a tag; the statusline
|
|
165
|
+
// counts this session's answers and calls by it.
|
|
166
|
+
const SESSION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
167
|
+
function sessionTag(raw) {
|
|
168
|
+
return typeof raw === 'string' && SESSION_ID.test(raw) ? raw.slice(0, 8).toLowerCase() : null;
|
|
169
|
+
}
|
|
170
|
+
|
|
161
171
|
/**
|
|
162
172
|
* Append one recommendation event to <cwd>/.code-graph/recommendations.jsonl.
|
|
163
173
|
* @param {string} cwd project root (the hook's process.cwd())
|
|
@@ -187,7 +197,9 @@ function recordRecommendation(cwd, event = {}) {
|
|
|
187
197
|
const file = path.join(dir, REC_FILE);
|
|
188
198
|
if (!isOwnedPath(file, 'file')) return false;
|
|
189
199
|
rotateIfNeeded(file); // rotate-before-append so the file never exceeds ~max + one line
|
|
190
|
-
const
|
|
200
|
+
const { session: _ignored, ...fields } = event;
|
|
201
|
+
const session = sessionTag(process.env.CLAUDE_CODE_SESSION_ID);
|
|
202
|
+
const line = JSON.stringify({ ts: new Date().toISOString(), ...fields, ...(session ? { session } : {}) }) + '\n';
|
|
191
203
|
const fd = openOwned(
|
|
192
204
|
file,
|
|
193
205
|
fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_APPEND
|
|
@@ -204,4 +216,4 @@ function recordRecommendation(cwd, event = {}) {
|
|
|
204
216
|
}
|
|
205
217
|
}
|
|
206
218
|
|
|
207
|
-
module.exports = { recordRecommendation, REC_FILE, isOwnedPath, openOwned };
|
|
219
|
+
module.exports = { recordRecommendation, REC_FILE, isOwnedPath, openOwned, sessionTag };
|
|
@@ -127,7 +127,16 @@ function runProvider(command, needsStdin, stdin, id) {
|
|
|
127
127
|
// to `_previous`/third-party providers, which ignore the unknown var. The
|
|
128
128
|
// CODE_GRAPH_ prefix (not CLAUDE_) keeps it out of Claude Code's own namespace.
|
|
129
129
|
const cwd = cwdFromStdin(stdin);
|
|
130
|
-
|
|
130
|
+
// Likewise the session id: the code-graph segment counts this session's
|
|
131
|
+
// answers and calls by it. Only a UUID-shaped id is forwarded.
|
|
132
|
+
const session = sessionFromStdin(stdin);
|
|
133
|
+
const env = (cwd || session)
|
|
134
|
+
? {
|
|
135
|
+
...process.env,
|
|
136
|
+
...(cwd ? { CODE_GRAPH_STATUSLINE_CWD: cwd } : {}),
|
|
137
|
+
...(session ? { CODE_GRAPH_STATUSLINE_SESSION: session } : {}),
|
|
138
|
+
}
|
|
139
|
+
: process.env;
|
|
131
140
|
|
|
132
141
|
const out = execFileSync(argv[0], argv.slice(1), hidden({
|
|
133
142
|
timeout: 3000,
|
|
@@ -217,6 +226,18 @@ function cwdFromStdin(stdin) {
|
|
|
217
226
|
} catch { return null; }
|
|
218
227
|
}
|
|
219
228
|
|
|
229
|
+
// Claude Code's session id from the stdin JSON context, when UUID-shaped
|
|
230
|
+
// (the only shape both session-tag rules accept); null otherwise.
|
|
231
|
+
const SESSION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
232
|
+
function sessionFromStdin(stdin) {
|
|
233
|
+
if (!stdin) return null;
|
|
234
|
+
try {
|
|
235
|
+
const v = JSON.parse(stdin);
|
|
236
|
+
const id = v && v.session_id;
|
|
237
|
+
return typeof id === 'string' && SESSION_ID.test(id) ? id : null;
|
|
238
|
+
} catch { return null; }
|
|
239
|
+
}
|
|
240
|
+
|
|
220
241
|
/// Shell constructs a direct `execFileSync` cannot honour at all: pipelines,
|
|
221
242
|
/// redirection, sequencing, command substitution, backgrounding.
|
|
222
243
|
///
|
|
@@ -214,4 +214,70 @@ try {
|
|
|
214
214
|
errText = [(e && e.stderr) || '', (e && e.stdout) || ''].map(String).join('\n');
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
|
|
217
|
+
// This session's code-graph activity (tasks/specs/statusline-session-counts.md):
|
|
218
|
+
// answers a hook delivered to the model, and code-graph commands the model ran
|
|
219
|
+
// itself. Without it the user cannot see code-graph working at all — every
|
|
220
|
+
// answer reaches the model silently. Rows are matched by the `session` tag
|
|
221
|
+
// both recorders stamp; the id comes from the composite's stdin payload.
|
|
222
|
+
// Kinds that delivered code-graph content (an unanswered hint or deny carried
|
|
223
|
+
// only advice; observe/bypass/follow-up rows delivered nothing).
|
|
224
|
+
const ANSWER_KINDS = [
|
|
225
|
+
['grep', 'deny', true], ['grep', 'inject', true], ['edit', 'hint', true], ['read', 'hint', true],
|
|
226
|
+
['subagent', 'subagent_context', null], ['stop', 'stop_check', null],
|
|
227
|
+
];
|
|
228
|
+
// Read no more than the rotation's keep size: the log is ~1 MB at most, and a
|
|
229
|
+
// render runs on every frame.
|
|
230
|
+
const COUNTS_TAIL_BYTES = 524288;
|
|
231
|
+
|
|
232
|
+
function sessionCounts(dir, tag) {
|
|
233
|
+
const { isOwnedPath, openOwned, REC_FILE } = require('./recommendation-log');
|
|
234
|
+
const what = 'statusline';
|
|
235
|
+
let dirStat;
|
|
236
|
+
try { dirStat = fs.lstatSync(dir); } catch { return null; }
|
|
237
|
+
if (!isOwnedPath(dir, 'dir', dirStat, what)) return null;
|
|
238
|
+
const file = path.join(dir, REC_FILE);
|
|
239
|
+
if (!isOwnedPath(file, 'file', undefined, what)) return null;
|
|
240
|
+
let fd;
|
|
241
|
+
try {
|
|
242
|
+
// O_NONBLOCK: a FIFO planted in the repo must not hold the render.
|
|
243
|
+
fd = openOwned(file, fs.constants.O_RDONLY | (fs.constants.O_NONBLOCK || 0), what);
|
|
244
|
+
} catch { return null; }
|
|
245
|
+
if (fd === null) return null;
|
|
246
|
+
let text;
|
|
247
|
+
try {
|
|
248
|
+
const size = fs.fstatSync(fd).size;
|
|
249
|
+
const len = Math.min(size, COUNTS_TAIL_BYTES);
|
|
250
|
+
const buf = Buffer.alloc(len);
|
|
251
|
+
fs.readSync(fd, buf, 0, len, size - len);
|
|
252
|
+
text = buf.toString('utf8');
|
|
253
|
+
} catch { return null; } finally { fs.closeSync(fd); }
|
|
254
|
+
let answers = 0;
|
|
255
|
+
let calls = 0;
|
|
256
|
+
for (const line of text.split('\n')) {
|
|
257
|
+
// Cheap prefilter on the tag alone; the parsed field decides.
|
|
258
|
+
if (!line.includes(tag)) continue;
|
|
259
|
+
let r;
|
|
260
|
+
try { r = JSON.parse(line); } catch { continue; }
|
|
261
|
+
if (!r || r.session !== tag) continue;
|
|
262
|
+
if (r.hook === 'cli' && r.action === 'use') calls++;
|
|
263
|
+
else if (ANSWER_KINDS.some(([h, a, ans]) => r.hook === h && r.action === a && (ans === null || r.answered === ans))) answers++;
|
|
264
|
+
}
|
|
265
|
+
return { answers, calls };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function renderSessionCounts() {
|
|
269
|
+
if (process.env.CODE_GRAPH_STATUSLINE_COUNTS === '0') return '';
|
|
270
|
+
try {
|
|
271
|
+
const { sessionTag } = require('./recommendation-log');
|
|
272
|
+
const tag = sessionTag(process.env.CODE_GRAPH_STATUSLINE_SESSION);
|
|
273
|
+
if (!tag) return '';
|
|
274
|
+
const c = sessionCounts(codeGraphDir, tag);
|
|
275
|
+
if (!c) return '';
|
|
276
|
+
const parts = [];
|
|
277
|
+
if (c.answers) parts.push(`${c.answers} answer${c.answers === 1 ? '' : 's'}`);
|
|
278
|
+
if (c.calls) parts.push(`${c.calls} call${c.calls === 1 ? '' : 's'}`);
|
|
279
|
+
return parts.length ? ` | ${parts.join(' · ')}` : '';
|
|
280
|
+
} catch { return ''; }
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
process.stdout.write(report ? renderHealth(report) + renderSessionCounts() : statusUnavailable(errText));
|
|
@@ -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.170.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.170.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.170.0",
|
|
39
|
+
"@sdsrs/code-graph-linux-arm64": "0.170.0",
|
|
40
|
+
"@sdsrs/code-graph-darwin-x64": "0.170.0",
|
|
41
|
+
"@sdsrs/code-graph-darwin-arm64": "0.170.0",
|
|
42
|
+
"@sdsrs/code-graph-win32-x64": "0.170.0"
|
|
43
43
|
}
|
|
44
44
|
}
|