@sdsrs/code-graph 0.67.0 → 0.69.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/claude-plugin/.claude-plugin/plugin.json +1 -1
- package/claude-plugin/scripts/pre-grep-guide.js +15 -5
- package/claude-plugin/scripts/pre-grep-guide.test.js +36 -0
- package/claude-plugin/scripts/session-init.js +3 -0
- package/claude-plugin/scripts/session-init.test.js +12 -0
- package/claude-plugin/scripts/user-prompt-context.js +11 -1
- package/claude-plugin/scripts/user-prompt-context.test.js +28 -0
- package/package.json +6 -6
|
@@ -65,10 +65,20 @@ const SRC_PATH = new RegExp(`(?:^|\\s|["'])(${SRC_PREFIXES})/`);
|
|
|
65
65
|
const SRC_PATH_TOKEN = new RegExp(`^(?:\\./)?(${SRC_PREFIXES})/`);
|
|
66
66
|
const PIPE_INTO_GREP = /\|\s*(?:grep|rg|ag)\b/;
|
|
67
67
|
const CG_INVOKED = /\bcode-graph-mcp\b/;
|
|
68
|
-
//
|
|
69
|
-
// path
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
// File argument(s) that end in a config/lockfile/data extension. If, after removing
|
|
69
|
+
// ALL of them, no source-tree path remains, the grep is searching config/data not code.
|
|
70
|
+
// v0.69 floor-hardening: (a) extended the extension list (ini/conf/xml/log/csv) and
|
|
71
|
+
// (b) made the strip GLOBAL so multiple data files (`grep X src/a.json src/b.json`) all
|
|
72
|
+
// peel off — previously only the first did, leaving the 2nd's `src/`-prefixed path to
|
|
73
|
+
// false-match SRC_PATH and fire. cg has no structural answer for these, so a deny is
|
|
74
|
+
// friction-without-value that teaches CODE_GRAPH_NO_BLOCK_GREP bypass (2026-06-23 reach
|
|
75
|
+
// audit: the unreached ~75% of greps are genuinely non-foldable — keep precision).
|
|
76
|
+
const NON_SOURCE_EXTS =
|
|
77
|
+
'toml|md|json|yml|yaml|lock|txt|cfg|env|gitignore|properties|ini|conf|xml|log|csv';
|
|
78
|
+
const CONFIG_TARGET_ONLY = new RegExp(`(?:^|\\s)[^\\s|<>]*\\.(?:${NON_SOURCE_EXTS})(?:\\s|$)`, 'i');
|
|
79
|
+
// Global + trailing-lookahead variant for the strip: lookahead (not consume) so adjacent
|
|
80
|
+
// data-file tokens both match; global so every one is peeled before the SRC_PATH re-check.
|
|
81
|
+
const CONFIG_TARGET_STRIP = new RegExp(`(?:^|\\s)[^\\s|<>]*\\.(?:${NON_SOURCE_EXTS})(?=\\s|$)`, 'gi');
|
|
72
82
|
|
|
73
83
|
function shouldHint(cmd) {
|
|
74
84
|
if (!cmd || typeof cmd !== 'string') return false;
|
|
@@ -79,7 +89,7 @@ function shouldHint(cmd) {
|
|
|
79
89
|
if (!SRC_PATH.test(cmd)) return false; // not against indexed source tree
|
|
80
90
|
// If a config file appears AND no source path remains after stripping it, skip.
|
|
81
91
|
if (CONFIG_TARGET_ONLY.test(cmd)) {
|
|
82
|
-
const stripped = cmd.replace(
|
|
92
|
+
const stripped = cmd.replace(CONFIG_TARGET_STRIP, ' ');
|
|
83
93
|
if (!SRC_PATH.test(stripped)) return false;
|
|
84
94
|
}
|
|
85
95
|
return true;
|
|
@@ -108,6 +108,42 @@ test('shouldHint: grep on a markdown changelog', () => {
|
|
|
108
108
|
assert.equal(shouldHint('grep "v0.24" CHANGELOG.md'), false);
|
|
109
109
|
});
|
|
110
110
|
|
|
111
|
+
// ── Floor (v0.69 hardening): non-foldable greps must NEVER deny/hint ──
|
|
112
|
+
// cg has no structural answer for these → a deny is friction-without-value that teaches
|
|
113
|
+
// CODE_GRAPH_NO_BLOCK_GREP bypass. 2026-06-23 reach audit: foldability (~24%) ≈
|
|
114
|
+
// interception (24%), so the floor (precision) is the lever — not reach expansion.
|
|
115
|
+
|
|
116
|
+
test('floor: grep on an external / non-indexed dir (/tmp clone) never fires', () => {
|
|
117
|
+
assert.equal(shouldHint('grep -rn "FooBar" /tmp/openwolf-analysis'), false);
|
|
118
|
+
assert.equal(shouldBlock('grep -rn "FooBar" /tmp/openwolf-analysis'), false);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('floor: external path with an embedded src/ segment never fires', () => {
|
|
122
|
+
// SRC_PATH only matches a prefix at ^|\s|quote — `/tmp/clone/src/` is not a project path.
|
|
123
|
+
assert.equal(shouldHint('grep -rn "FooBar" /tmp/clone/src/'), false);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('floor: a non-source data file (.log) under src/ never fires', () => {
|
|
127
|
+
assert.equal(shouldHint('grep "ErrorHandler" src/fixtures/app.log'), false);
|
|
128
|
+
assert.equal(shouldBlock('grep "ErrorHandler" src/fixtures/app.log'), false);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('floor: ini/conf/xml/csv data files under src/ never fire', () => {
|
|
132
|
+
assert.equal(shouldHint('grep "FooBar" src/config.ini'), false);
|
|
133
|
+
assert.equal(shouldHint('grep "FooBar" src/app.conf'), false);
|
|
134
|
+
assert.equal(shouldHint('grep "FooBar" src/data.xml'), false);
|
|
135
|
+
assert.equal(shouldHint('grep "FooBar" src/rows.csv'), false);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test('floor: multiple config files under a src prefix all peel off → skip', () => {
|
|
139
|
+
// global strip (v0.69): pre-fix only the first .json peeled, the 2nd false-matched SRC_PATH.
|
|
140
|
+
assert.equal(shouldHint('grep "FooBar" src/a.json src/b.json'), false);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('floor: mixed target (data file + real source file) STILL fires (no foldable miss)', () => {
|
|
144
|
+
assert.equal(shouldHint('grep -rn "FooBar" src/app.log src/handler.rs'), true);
|
|
145
|
+
});
|
|
146
|
+
|
|
111
147
|
// ── Should NOT fire: not search tools ───────────────────────────────
|
|
112
148
|
|
|
113
149
|
test('shouldHint: ls src/', () => {
|
|
@@ -531,6 +531,9 @@ function injectProjectMap() {
|
|
|
531
531
|
timeout: 5000,
|
|
532
532
|
encoding: 'utf8',
|
|
533
533
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
534
|
+
// Hook-internal delivery, not a model conversion — keep record_cli_use from
|
|
535
|
+
// logging this `map` run as a phantom `use` (mirror injectRecentImpact's affected call).
|
|
536
|
+
env: { ...process.env, CODE_GRAPH_INTERNAL: '1' },
|
|
534
537
|
});
|
|
535
538
|
|
|
536
539
|
if (output && output.trim()) {
|
|
@@ -347,3 +347,15 @@ test('consistencyCheck returns version-mismatch when versions differ', (t) => {
|
|
|
347
347
|
assert.ok(versionIssue.msg.includes('0.0.1'));
|
|
348
348
|
});
|
|
349
349
|
|
|
350
|
+
test('injectProjectMap map call carries CODE_GRAPH_INTERNAL (delivery, not a model conversion)', () => {
|
|
351
|
+
// injectProjectMap runs `code-graph-mcp map --compact` to inject the project map.
|
|
352
|
+
// That run is a hook-internal delivery — it must carry the internal marker so
|
|
353
|
+
// record_cli_use (src/cli.rs) does not log it as a phantom model `use` event
|
|
354
|
+
// (the 2026-06-23 mem audit found this leak class; the sibling affected call was
|
|
355
|
+
// already guarded). Asserted at source level because injectProjectMap is not exported.
|
|
356
|
+
const src = fs.readFileSync(path.join(__dirname, 'session-init.js'), 'utf8');
|
|
357
|
+
const i = src.indexOf("['map', '--compact']");
|
|
358
|
+
assert.ok(i >= 0, 'map injection present');
|
|
359
|
+
assert.match(src.slice(i, i + 420), /CODE_GRAPH_INTERNAL:\s*'1'/);
|
|
360
|
+
});
|
|
361
|
+
|
|
@@ -374,6 +374,15 @@ function determineQueryType(intents, symbols, filePaths, isCoolingDownFn, messag
|
|
|
374
374
|
return null;
|
|
375
375
|
}
|
|
376
376
|
|
|
377
|
+
// Hook-internal CLI runs are PUSH deliveries, not model-initiated conversions.
|
|
378
|
+
// Tagging them CODE_GRAPH_INTERNAL=1 keeps record_cli_use (src/cli.rs) from
|
|
379
|
+
// logging them as `use` events — otherwise this hook's own injected callgraph/
|
|
380
|
+
// overview/search results read back as genuine consumer adoption (2026-06-23 mem
|
|
381
|
+
// audit: 100 phantom "model CLI calls" were this hook crediting its own deliveries).
|
|
382
|
+
function buildRunEnv(base = process.env) {
|
|
383
|
+
return { ...base, CODE_GRAPH_INTERNAL: '1' };
|
|
384
|
+
}
|
|
385
|
+
|
|
377
386
|
// --- Main execution (only when run directly) ---
|
|
378
387
|
// All exit-on-condition checks (manifest, computeQuietHooks, message length,
|
|
379
388
|
// db presence) live INSIDE this guard so `require()` from tests doesn't
|
|
@@ -454,6 +463,7 @@ function runMain() {
|
|
|
454
463
|
timeout: 3000,
|
|
455
464
|
encoding: 'utf8',
|
|
456
465
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
466
|
+
env: buildRunEnv(),
|
|
457
467
|
});
|
|
458
468
|
}
|
|
459
469
|
|
|
@@ -477,4 +487,4 @@ if (require.main === module) {
|
|
|
477
487
|
runMain();
|
|
478
488
|
}
|
|
479
489
|
|
|
480
|
-
module.exports = { shouldSkip, extractFilePaths, extractSymbols, detectIntents, scoreIntent, INTENT_PATTERNS, INTENT_THRESHOLD, determineQueryType, computeQuietHooks, STOP_WORDS, PLAIN_WORD_EXCLUDE, hasSymptom, SYMPTOM_PATTERNS };
|
|
490
|
+
module.exports = { shouldSkip, extractFilePaths, extractSymbols, detectIntents, scoreIntent, INTENT_PATTERNS, INTENT_THRESHOLD, determineQueryType, computeQuietHooks, STOP_WORDS, PLAIN_WORD_EXCLUDE, hasSymptom, SYMPTOM_PATTERNS, buildRunEnv };
|
|
@@ -14,6 +14,7 @@ const {
|
|
|
14
14
|
INTENT_THRESHOLD,
|
|
15
15
|
determineQueryType,
|
|
16
16
|
computeQuietHooks,
|
|
17
|
+
buildRunEnv,
|
|
17
18
|
} = require('./user-prompt-context');
|
|
18
19
|
|
|
19
20
|
// ── shouldSkip ──────────────────────────────────────────
|
|
@@ -713,3 +714,30 @@ test('integration: Why does this not work? → symptom-hint', () => {
|
|
|
713
714
|
const r = analyze('Why does this not work?');
|
|
714
715
|
assert.equal(r.query && r.query.type, 'symptom-hint');
|
|
715
716
|
});
|
|
717
|
+
|
|
718
|
+
// ── buildRunEnv: hook-internal delivery marker (anti phantom-conversion) ──
|
|
719
|
+
|
|
720
|
+
test('buildRunEnv: tags CODE_GRAPH_INTERNAL=1 so deliveries are not logged as model `use`', () => {
|
|
721
|
+
const env = buildRunEnv({ PATH: '/usr/bin', HOME: '/home/x' });
|
|
722
|
+
assert.equal(env.CODE_GRAPH_INTERNAL, '1');
|
|
723
|
+
// preserves the base env (binary still resolves on PATH, cwd inherited, etc.)
|
|
724
|
+
assert.equal(env.PATH, '/usr/bin');
|
|
725
|
+
assert.equal(env.HOME, '/home/x');
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
test('buildRunEnv: defaults to process.env when no base given', () => {
|
|
729
|
+
const env = buildRunEnv();
|
|
730
|
+
assert.equal(env.CODE_GRAPH_INTERNAL, '1');
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
test('run() wires buildRunEnv() into execFileSync (no phantom use-event leak)', () => {
|
|
734
|
+
// run() lives inside runMain() (the file top-level-executes on require), so assert
|
|
735
|
+
// the wiring at the source level: every code-graph-mcp invocation this hook makes
|
|
736
|
+
// must carry the internal marker, else its PUSH injections read back as model
|
|
737
|
+
// adoption (the 2026-06-23 mem audit: 100 phantom "model CLI calls"). Mirrors the
|
|
738
|
+
// cg-answer.js / pre-edit-guide.js internal-env guard.
|
|
739
|
+
const src = fs.readFileSync(path.join(__dirname, 'user-prompt-context.js'), 'utf8');
|
|
740
|
+
const i = src.indexOf('function run(');
|
|
741
|
+
assert.ok(i >= 0, 'run() helper present');
|
|
742
|
+
assert.match(src.slice(i, i + 320), /env:\s*buildRunEnv\(\)/);
|
|
743
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdsrs/code-graph",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.69.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.69.0",
|
|
39
|
+
"@sdsrs/code-graph-linux-arm64": "0.69.0",
|
|
40
|
+
"@sdsrs/code-graph-darwin-x64": "0.69.0",
|
|
41
|
+
"@sdsrs/code-graph-darwin-arm64": "0.69.0",
|
|
42
|
+
"@sdsrs/code-graph-win32-x64": "0.69.0"
|
|
43
43
|
}
|
|
44
44
|
}
|