@sdsrs/code-graph 0.92.0 → 0.93.1
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.
|
@@ -570,6 +570,16 @@ function buildNoHitsFyi(pattern) {
|
|
|
570
570
|
return `[code-graph] FYI: \`code-graph-mcp grep "${pattern}"\` found no matches — raw grep proceeding. (Regex-metachar patterns: \`code-graph-mcp grep -F\` searches literally.)`;
|
|
571
571
|
}
|
|
572
572
|
|
|
573
|
+
// v0.92 — cg was allowed to answer but the binary ran-and-failed ('unavailable')
|
|
574
|
+
// or could not be found ('no-binary'). Like buildNoHitsFyi this is a breadcrumb
|
|
575
|
+
// only (PreToolUse exit-0 stdout → debug log, never the model); the operative
|
|
576
|
+
// effect at the call site is the ALLOW (no deny emitted) so the raw grep runs
|
|
577
|
+
// intact instead of a static deny that would hand the model nothing.
|
|
578
|
+
function buildUnavailableFyi(pattern, status) {
|
|
579
|
+
const why = status === 'no-binary' ? 'binary not found' : 'ran but failed';
|
|
580
|
+
return `[code-graph] FYI: \`code-graph-mcp grep "${pattern}"\` unavailable (${why}) — raw grep proceeding.`;
|
|
581
|
+
}
|
|
582
|
+
|
|
573
583
|
// --- Main execution (only when run directly) ---
|
|
574
584
|
|
|
575
585
|
// Kill switch: matches user-prompt-context.js convention. =1 forces silence
|
|
@@ -687,9 +697,24 @@ function runMain() {
|
|
|
687
697
|
}
|
|
688
698
|
}
|
|
689
699
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
700
|
+
// v0.92 — cg was allowed to answer but couldn't deliver hits: 'no-hits'
|
|
701
|
+
// (regex-dialect miss ≠ proof of absence), 'unavailable' (binary ran but
|
|
702
|
+
// failed/timed out), or 'no-binary' (binary missing). In every case a static
|
|
703
|
+
// deny hands the model NOTHING — pure friction that teaches the
|
|
704
|
+
// CODE_GRAPH_NO_BLOCK_GREP bypass (ubuntu-sec 2026-07 dogfood: the only 2
|
|
705
|
+
// non-converting denies were `unavailable`, one a `def render` compound cmd
|
|
706
|
+
// that then half-ran — grep blocked, `; python3 …` tail dropped, no result).
|
|
707
|
+
// ALLOW the raw grep so the command runs intact; record the fallthrough
|
|
708
|
+
// reason so the funnel still tells no-hits / unavailable / no-binary apart.
|
|
709
|
+
// Exception: CODE_GRAPH_NO_ANSWER_IN_DENY=1 means the user opted into the
|
|
710
|
+
// static deny (the answer never ran → status stays the default 'unavailable')
|
|
711
|
+
// — that path falls through to the v0.46 static deny below.
|
|
712
|
+
if (answer.status !== 'hits' && !isAnswerDisabled()) {
|
|
713
|
+
recordRecommendation(root, { hook: 'grep', action: 'hint', fallthrough: answer.status });
|
|
714
|
+
process.stdout.write(
|
|
715
|
+
(answer.status === 'no-hits'
|
|
716
|
+
? buildNoHitsFyi(pattern)
|
|
717
|
+
: buildUnavailableFyi(pattern, answer.status)) + '\n');
|
|
693
718
|
return;
|
|
694
719
|
}
|
|
695
720
|
|
|
@@ -769,6 +794,7 @@ module.exports = {
|
|
|
769
794
|
buildBlockReason,
|
|
770
795
|
buildBlockReasonWithAnswer,
|
|
771
796
|
buildNoHitsFyi,
|
|
797
|
+
buildUnavailableFyi, // v0.92 — allow-on-unavailable breadcrumb
|
|
772
798
|
commandHash,
|
|
773
799
|
isOnCooldown,
|
|
774
800
|
markCooldown,
|
|
@@ -1127,24 +1127,25 @@ test('e2e: stub reports no matches → grep allowed with FYI + records fallthrou
|
|
|
1127
1127
|
}
|
|
1128
1128
|
});
|
|
1129
1129
|
|
|
1130
|
-
test('e2e: stub
|
|
1130
|
+
test('e2e: stub ran-and-failed → grep ALLOWED (no static deny) + records fallthrough:unavailable', () => {
|
|
1131
|
+
// v0.92 — a binary that ran but failed (exit 3) can't answer, so a static deny
|
|
1132
|
+
// would hand the model nothing (pure friction that teaches the bypass). ALLOW
|
|
1133
|
+
// the raw grep instead; the funnel still distinguishes this from no-hits /
|
|
1134
|
+
// no-binary via the `fallthrough` field on the recorded hint event.
|
|
1131
1135
|
const uniq = `StubBoom${Date.now()}`;
|
|
1132
1136
|
const fixture = e2eFixture(`process.exit(3);`);
|
|
1133
1137
|
const cmd = `grep -rn "${uniq}" src/`;
|
|
1134
1138
|
try {
|
|
1135
1139
|
const res = runHook(cmd, fixture);
|
|
1136
1140
|
assert.equal(res.status, 0);
|
|
1137
|
-
|
|
1138
|
-
assert.
|
|
1139
|
-
|
|
1140
|
-
assert.match(out.hookSpecificOutput.permissionDecisionReason, /denied by code-graph hook/);
|
|
1141
|
+
// No deny JSON — plain FYI text means the grep proceeds.
|
|
1142
|
+
assert.throws(() => JSON.parse(res.stdout));
|
|
1143
|
+
assert.match(res.stdout, /unavailable \(ran but failed\)/);
|
|
1141
1144
|
const rec = JSON.parse(fsE2e.readFileSync(
|
|
1142
1145
|
pathE2e.join(fixture.dir, '.code-graph', 'recommendations.jsonl'), 'utf8').trim());
|
|
1143
|
-
assert.equal(rec.action, '
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
// funnel must NOT confuse this with a missing-binary ('no-binary') deny.
|
|
1147
|
-
assert.equal(rec.reason, 'unavailable');
|
|
1146
|
+
assert.equal(rec.action, 'hint');
|
|
1147
|
+
// Runtime-fail must stay distinguishable from a missing-binary fallthrough.
|
|
1148
|
+
assert.equal(rec.fallthrough, 'unavailable');
|
|
1148
1149
|
} finally {
|
|
1149
1150
|
cleanupFixture(fixture, cmd);
|
|
1150
1151
|
}
|
|
@@ -1222,7 +1223,12 @@ test('e2e: compound `grep …; sed` → deny answers grep AND flags the unanswer
|
|
|
1222
1223
|
}
|
|
1223
1224
|
});
|
|
1224
1225
|
|
|
1225
|
-
test('e2e: compound cmd + answer failure →
|
|
1226
|
+
test('e2e: compound cmd + answer failure → grep ALLOWED, whole command runs intact (no half-run tail drop)', () => {
|
|
1227
|
+
// v0.92 — the marquee fix: when cg can't answer, a static deny used to block
|
|
1228
|
+
// the grep AND drop the `&& cargo test` tail, leaving the model with nothing +
|
|
1229
|
+
// a re-issue chore (ubuntu-sec: the `grep "def render" …; python3 …` case).
|
|
1230
|
+
// Now the whole compound command is ALLOWED to run intact — no deny, no tail
|
|
1231
|
+
// note, no half-run. Recorded as a hint/fallthrough so the funnel still sees it.
|
|
1226
1232
|
const uniq = `StubTailBoom${Date.now()}`;
|
|
1227
1233
|
const fixture = e2eFixture(`process.exit(3);`);
|
|
1228
1234
|
const cmd = `grep -n "${uniq}" src/foo.rs && cargo test -q`;
|
|
@@ -1231,16 +1237,14 @@ test('e2e: compound cmd + answer failure → STATIC deny still flags tail + reco
|
|
|
1231
1237
|
fsE2e.writeFileSync(pathE2e.join(fixture.dir, 'src', 'foo.rs'), 'fn x() {}\n');
|
|
1232
1238
|
const res = runHook(cmd, fixture);
|
|
1233
1239
|
assert.equal(res.status, 0);
|
|
1234
|
-
|
|
1235
|
-
assert.
|
|
1236
|
-
|
|
1237
|
-
assert.match(reason, /denied by code-graph hook/); // static fallback path
|
|
1238
|
-
assert.match(reason, /did NOT run/);
|
|
1239
|
-
assert.match(reason, /cargo test -q/);
|
|
1240
|
+
// No deny JSON — the whole compound command proceeds, tail included.
|
|
1241
|
+
assert.throws(() => JSON.parse(res.stdout));
|
|
1242
|
+
assert.match(res.stdout, /unavailable \(ran but failed\)/);
|
|
1240
1243
|
const rec = JSON.parse(fsE2e.readFileSync(
|
|
1241
1244
|
pathE2e.join(fixture.dir, '.code-graph', 'recommendations.jsonl'), 'utf8').trim());
|
|
1242
|
-
assert.equal(rec.
|
|
1243
|
-
assert.equal(rec.
|
|
1245
|
+
assert.equal(rec.action, 'hint');
|
|
1246
|
+
assert.equal(rec.fallthrough, 'unavailable');
|
|
1247
|
+
assert.equal(rec.tail, undefined); // nothing dropped → no tail flag
|
|
1244
1248
|
} finally {
|
|
1245
1249
|
cleanupFixture(fixture, cmd);
|
|
1246
1250
|
}
|
|
@@ -1264,13 +1268,14 @@ test('e2e: simple (non-compound) denied grep → no tail field in the deny recor
|
|
|
1264
1268
|
}
|
|
1265
1269
|
});
|
|
1266
1270
|
|
|
1267
|
-
test('e2e: missing binary → static deny records
|
|
1268
|
-
//
|
|
1269
|
-
//
|
|
1270
|
-
//
|
|
1271
|
-
//
|
|
1272
|
-
//
|
|
1273
|
-
//
|
|
1271
|
+
test('e2e: missing binary → grep ALLOWED (no static deny) records fallthrough:no-binary (distinct from no-hits & unavailable)', () => {
|
|
1272
|
+
// v0.92 — when the binary can't be found the hook can't answer AND denying
|
|
1273
|
+
// would block the user's only search tool, so it ALLOWS the raw grep. The
|
|
1274
|
+
// `fallthrough` field still keeps a missing-binary case distinguishable in the
|
|
1275
|
+
// funnel from no-hits / runtime-unavailable. We can't make findBinary() return
|
|
1276
|
+
// null in-repo (dev target/release is always there), so run the child with a
|
|
1277
|
+
// `--require` shim that forces it null — and DON'T set _CG_ANSWER_BINARY (it
|
|
1278
|
+
// would short-circuit before findBinary()).
|
|
1274
1279
|
const uniq = `StubGone${Date.now()}`;
|
|
1275
1280
|
const fixture = e2eFixture(`process.stdout.write('unused\\n');`);
|
|
1276
1281
|
const shim = pathE2e.join(fixture.dir, 'no-binary-shim.js');
|
|
@@ -1302,16 +1307,14 @@ Module.prototype.require = function (id) {
|
|
|
1302
1307
|
},
|
|
1303
1308
|
});
|
|
1304
1309
|
assert.equal(res.status, 0);
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
assert.
|
|
1308
|
-
assert.match(out.hookSpecificOutput.permissionDecisionReason, /denied by code-graph hook/);
|
|
1310
|
+
// No deny JSON — the raw grep proceeds; FYI names the missing-binary cause.
|
|
1311
|
+
assert.throws(() => JSON.parse(res.stdout));
|
|
1312
|
+
assert.match(res.stdout, /unavailable \(binary not found\)/);
|
|
1309
1313
|
const rec = JSON.parse(fsE2e.readFileSync(
|
|
1310
1314
|
pathE2e.join(fixture.dir, '.code-graph', 'recommendations.jsonl'), 'utf8').trim());
|
|
1311
|
-
assert.equal(rec.action, '
|
|
1312
|
-
assert.equal(rec.
|
|
1313
|
-
|
|
1314
|
-
'a missing-binary deny must be distinguishable from an unavailable (runtime-fail) deny');
|
|
1315
|
+
assert.equal(rec.action, 'hint');
|
|
1316
|
+
assert.equal(rec.fallthrough, 'no-binary',
|
|
1317
|
+
'a missing-binary fallthrough must be distinguishable from an unavailable (runtime-fail) one');
|
|
1315
1318
|
} finally {
|
|
1316
1319
|
cleanupFixture(fixture, cmd);
|
|
1317
1320
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdsrs/code-graph",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.93.1",
|
|
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.93.1",
|
|
39
|
+
"@sdsrs/code-graph-linux-arm64": "0.93.1",
|
|
40
|
+
"@sdsrs/code-graph-darwin-x64": "0.93.1",
|
|
41
|
+
"@sdsrs/code-graph-darwin-arm64": "0.93.1",
|
|
42
|
+
"@sdsrs/code-graph-win32-x64": "0.93.1"
|
|
43
43
|
}
|
|
44
44
|
}
|