depgraph-core 1.8.0 → 1.9.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.
- package/.vscode/depgraph-output.json +1787 -163
- package/README.md +6 -6
- package/depgraph-mcp.js +480 -73
- package/depgraph.js +323 -4
- package/package.json +2 -1
- package/scripts/benchmark.js +138 -0
package/depgraph.js
CHANGED
|
@@ -65,7 +65,8 @@ var require_constants = __commonJS({
|
|
|
65
65
|
".vue",
|
|
66
66
|
".svelte",
|
|
67
67
|
".dart",
|
|
68
|
-
".rs"
|
|
68
|
+
".rs",
|
|
69
|
+
".sql"
|
|
69
70
|
]);
|
|
70
71
|
exports2.MAX_FILE_SIZE = 3e5;
|
|
71
72
|
exports2.MAX_BFS_DEPTH = 10;
|
|
@@ -3466,6 +3467,322 @@ var require_rust = __commonJS({
|
|
|
3466
3467
|
}
|
|
3467
3468
|
});
|
|
3468
3469
|
|
|
3470
|
+
// dist/languages/sql/helpers.js
|
|
3471
|
+
var require_helpers = __commonJS({
|
|
3472
|
+
"dist/languages/sql/helpers.js"(exports2) {
|
|
3473
|
+
"use strict";
|
|
3474
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
3475
|
+
exports2.NON_TABLES = exports2.ROUTINE_RECOVERY_RX = exports2.QUAL_NAME = exports2.NAME_PART = void 0;
|
|
3476
|
+
exports2.lineOf = lineOf;
|
|
3477
|
+
exports2.normIdent = normIdent;
|
|
3478
|
+
exports2.maskSqlComments = maskSqlComments;
|
|
3479
|
+
exports2.collectCteNames = collectCteNames;
|
|
3480
|
+
exports2.collectTableRefs = collectTableRefs;
|
|
3481
|
+
exports2.collectFkRefs = collectFkRefs;
|
|
3482
|
+
exports2.NAME_PART = '(?:"(?:[^"\\n]|"")*"|`(?:[^`\\n]|``)*`|\\[(?:[^\\]\\n]|\\]\\])*\\]|[\\w$]+)';
|
|
3483
|
+
exports2.QUAL_NAME = `${exports2.NAME_PART}(?:\\s*\\.\\s*${exports2.NAME_PART})*`;
|
|
3484
|
+
exports2.ROUTINE_RECOVERY_RX = new RegExp(`\\bCREATE\\s+(?:OR\\s+(?:REPLACE|ALTER)\\s+)?(?:FUNCTION|PROC(?:EDURE)?)\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?(${exports2.QUAL_NAME})`, "gi");
|
|
3485
|
+
function lineOf(text, offset) {
|
|
3486
|
+
return text.slice(0, offset).split("\n").length;
|
|
3487
|
+
}
|
|
3488
|
+
function normIdent(name) {
|
|
3489
|
+
return name.split(".").map((p) => {
|
|
3490
|
+
const s = p.trim();
|
|
3491
|
+
if (s.length >= 2 && (s[0] === s[s.length - 1] && (s[0] === '"' || s[0] === "`") || s[0] === "[" && s[s.length - 1] === "]")) {
|
|
3492
|
+
return s.slice(1, -1).toLowerCase();
|
|
3493
|
+
}
|
|
3494
|
+
return s.toLowerCase();
|
|
3495
|
+
}).join(".");
|
|
3496
|
+
}
|
|
3497
|
+
function maskSqlComments(text) {
|
|
3498
|
+
const out = [];
|
|
3499
|
+
let i = 0;
|
|
3500
|
+
const n = text.length;
|
|
3501
|
+
function blank(upto) {
|
|
3502
|
+
for (const ch of text.slice(i, upto))
|
|
3503
|
+
out.push(ch === "\n" ? "\n" : " ");
|
|
3504
|
+
return upto;
|
|
3505
|
+
}
|
|
3506
|
+
while (i < n) {
|
|
3507
|
+
const c = text[i];
|
|
3508
|
+
if (c === "'") {
|
|
3509
|
+
let j = i + 1;
|
|
3510
|
+
while (j < n && text[j] !== "\n") {
|
|
3511
|
+
if (text[j] === "'") {
|
|
3512
|
+
if (j + 1 < n && text[j + 1] === "'") {
|
|
3513
|
+
j += 2;
|
|
3514
|
+
continue;
|
|
3515
|
+
}
|
|
3516
|
+
j++;
|
|
3517
|
+
break;
|
|
3518
|
+
}
|
|
3519
|
+
j++;
|
|
3520
|
+
}
|
|
3521
|
+
i = blank(j);
|
|
3522
|
+
} else if (c === '"' || c === "`" || c === "[") {
|
|
3523
|
+
const closer = c === "[" ? "]" : c;
|
|
3524
|
+
let j = i + 1;
|
|
3525
|
+
let closed = false;
|
|
3526
|
+
while (j < n && text[j] !== "\n") {
|
|
3527
|
+
if (text[j] === closer) {
|
|
3528
|
+
if (j + 1 < n && text[j + 1] === closer) {
|
|
3529
|
+
j += 2;
|
|
3530
|
+
continue;
|
|
3531
|
+
}
|
|
3532
|
+
j++;
|
|
3533
|
+
closed = true;
|
|
3534
|
+
break;
|
|
3535
|
+
}
|
|
3536
|
+
j++;
|
|
3537
|
+
}
|
|
3538
|
+
const span = text.slice(i, j);
|
|
3539
|
+
if (closed && !span.includes("--") && !span.includes("/*")) {
|
|
3540
|
+
out.push(span);
|
|
3541
|
+
i = j;
|
|
3542
|
+
} else {
|
|
3543
|
+
let eol = text.indexOf("\n", i);
|
|
3544
|
+
if (eol === -1)
|
|
3545
|
+
eol = n;
|
|
3546
|
+
i = blank(eol);
|
|
3547
|
+
}
|
|
3548
|
+
} else if (c === "-" && i + 1 < n && text[i + 1] === "-") {
|
|
3549
|
+
let j = i;
|
|
3550
|
+
while (j < n && text[j] !== "\n")
|
|
3551
|
+
j++;
|
|
3552
|
+
i = blank(j);
|
|
3553
|
+
} else if (c === "/" && i + 1 < n && text[i + 1] === "*") {
|
|
3554
|
+
let depth = 1;
|
|
3555
|
+
let j = i + 2;
|
|
3556
|
+
while (j < n && depth > 0) {
|
|
3557
|
+
if (text[j] === "/" && j + 1 < n && text[j + 1] === "*") {
|
|
3558
|
+
depth++;
|
|
3559
|
+
j += 2;
|
|
3560
|
+
} else if (text[j] === "*" && j + 1 < n && text[j + 1] === "/") {
|
|
3561
|
+
depth--;
|
|
3562
|
+
j += 2;
|
|
3563
|
+
} else
|
|
3564
|
+
j++;
|
|
3565
|
+
}
|
|
3566
|
+
i = blank(j);
|
|
3567
|
+
} else {
|
|
3568
|
+
out.push(c);
|
|
3569
|
+
i++;
|
|
3570
|
+
}
|
|
3571
|
+
}
|
|
3572
|
+
return out.join("");
|
|
3573
|
+
}
|
|
3574
|
+
var NON_TABLES = /* @__PURE__ */ new Set([
|
|
3575
|
+
"select",
|
|
3576
|
+
"where",
|
|
3577
|
+
"set",
|
|
3578
|
+
"dual",
|
|
3579
|
+
"null",
|
|
3580
|
+
"true",
|
|
3581
|
+
"false",
|
|
3582
|
+
"first",
|
|
3583
|
+
"skip",
|
|
3584
|
+
"rows",
|
|
3585
|
+
"next",
|
|
3586
|
+
"only",
|
|
3587
|
+
"lateral",
|
|
3588
|
+
"values",
|
|
3589
|
+
"inserted",
|
|
3590
|
+
"deleted",
|
|
3591
|
+
"new",
|
|
3592
|
+
"old"
|
|
3593
|
+
]);
|
|
3594
|
+
exports2.NON_TABLES = NON_TABLES;
|
|
3595
|
+
function collectCteNames(text) {
|
|
3596
|
+
const ctes = /* @__PURE__ */ new Set();
|
|
3597
|
+
const rx = /\bWITH\s+(?:RECURSIVE\s+)?([\w$]+)\s*(?:\([^()]*\))?\s+AS\s*\(/gi;
|
|
3598
|
+
for (const m of text.matchAll(rx))
|
|
3599
|
+
ctes.add(normIdent(m[1]));
|
|
3600
|
+
return ctes;
|
|
3601
|
+
}
|
|
3602
|
+
function collectTableRefs(masked, extraNonTables = /* @__PURE__ */ new Set()) {
|
|
3603
|
+
const skip = /* @__PURE__ */ new Set([...NON_TABLES, ...extraNonTables]);
|
|
3604
|
+
const refs = [];
|
|
3605
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3606
|
+
const rx = new RegExp(`\\b(?:FROM|JOIN|INTO|UPDATE)\\s+(${exports2.QUAL_NAME})`, "gi");
|
|
3607
|
+
for (const m of masked.matchAll(rx)) {
|
|
3608
|
+
const raw = m[1];
|
|
3609
|
+
const key = normIdent(raw);
|
|
3610
|
+
if (skip.has(key) || seen.has(key))
|
|
3611
|
+
continue;
|
|
3612
|
+
seen.add(key);
|
|
3613
|
+
refs.push({ name: raw, line: lineOf(masked, m.index ?? 0) });
|
|
3614
|
+
}
|
|
3615
|
+
return refs;
|
|
3616
|
+
}
|
|
3617
|
+
function collectFkRefs(masked) {
|
|
3618
|
+
const refs = [];
|
|
3619
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3620
|
+
const rx = new RegExp(`\\bREFERENCES\\s+(${exports2.QUAL_NAME})`, "gi");
|
|
3621
|
+
for (const m of masked.matchAll(rx)) {
|
|
3622
|
+
const raw = m[1];
|
|
3623
|
+
const key = normIdent(raw);
|
|
3624
|
+
if (seen.has(key))
|
|
3625
|
+
continue;
|
|
3626
|
+
seen.add(key);
|
|
3627
|
+
refs.push({ name: raw, line: lineOf(masked, m.index ?? 0) });
|
|
3628
|
+
}
|
|
3629
|
+
return refs;
|
|
3630
|
+
}
|
|
3631
|
+
}
|
|
3632
|
+
});
|
|
3633
|
+
|
|
3634
|
+
// dist/languages/sql/patterns.js
|
|
3635
|
+
var require_patterns = __commonJS({
|
|
3636
|
+
"dist/languages/sql/patterns.js"(exports2) {
|
|
3637
|
+
"use strict";
|
|
3638
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
3639
|
+
exports2.sqlEntityPatterns = void 0;
|
|
3640
|
+
var helpers_1 = require_helpers();
|
|
3641
|
+
exports2.sqlEntityPatterns = [
|
|
3642
|
+
{
|
|
3643
|
+
regex: new RegExp(`\\bCREATE\\s+(?:TEMP(?:ORARY)?\\s+)?TABLE\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?(${helpers_1.QUAL_NAME})`, "gi"),
|
|
3644
|
+
type: "table"
|
|
3645
|
+
},
|
|
3646
|
+
{
|
|
3647
|
+
regex: new RegExp(`\\bCREATE\\s+(?:OR\\s+(?:REPLACE|ALTER)\\s+)?(?:MATERIALIZED\\s+)?VIEW\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?(${helpers_1.QUAL_NAME})`, "gi"),
|
|
3648
|
+
type: "view"
|
|
3649
|
+
},
|
|
3650
|
+
{
|
|
3651
|
+
regex: new RegExp(`\\bCREATE\\s+(?:OR\\s+(?:REPLACE|ALTER)\\s+)?FUNCTION\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?(${helpers_1.QUAL_NAME})`, "gi"),
|
|
3652
|
+
type: "function"
|
|
3653
|
+
},
|
|
3654
|
+
{
|
|
3655
|
+
regex: new RegExp(`\\bCREATE\\s+(?:OR\\s+(?:REPLACE|ALTER)\\s+)?PROC(?:EDURE)?\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?(${helpers_1.QUAL_NAME})`, "gi"),
|
|
3656
|
+
type: "procedure"
|
|
3657
|
+
},
|
|
3658
|
+
{
|
|
3659
|
+
regex: new RegExp(`\\bCREATE\\s+(?:OR\\s+(?:REPLACE|ALTER)\\s+)?TRIGGER\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?(${helpers_1.QUAL_NAME})`, "gi"),
|
|
3660
|
+
type: "trigger"
|
|
3661
|
+
},
|
|
3662
|
+
{
|
|
3663
|
+
regex: new RegExp(`\\bCREATE\\s+(?:UNIQUE\\s+)?INDEX\\s+(?:CONCURRENTLY\\s+)?(?:IF\\s+NOT\\s+EXISTS\\s+)?(${helpers_1.QUAL_NAME})\\s+ON\\b`, "gi"),
|
|
3664
|
+
type: "index"
|
|
3665
|
+
}
|
|
3666
|
+
];
|
|
3667
|
+
}
|
|
3668
|
+
});
|
|
3669
|
+
|
|
3670
|
+
// dist/languages/sql/extractor.js
|
|
3671
|
+
var require_extractor = __commonJS({
|
|
3672
|
+
"dist/languages/sql/extractor.js"(exports2) {
|
|
3673
|
+
"use strict";
|
|
3674
|
+
var __importDefault2 = exports2 && exports2.__importDefault || function(mod) {
|
|
3675
|
+
return mod && mod.__esModule ? mod : { "default": mod };
|
|
3676
|
+
};
|
|
3677
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
3678
|
+
exports2.extractEntities = extractEntities;
|
|
3679
|
+
exports2.extractImports = extractImports;
|
|
3680
|
+
exports2.extractExports = extractExports;
|
|
3681
|
+
var path_1 = __importDefault2(require("path"));
|
|
3682
|
+
var patterns_1 = require_patterns();
|
|
3683
|
+
var helpers_1 = require_helpers();
|
|
3684
|
+
var _currentFile = "";
|
|
3685
|
+
function extractEntities(code, filePath) {
|
|
3686
|
+
_currentFile = filePath;
|
|
3687
|
+
const masked = (0, helpers_1.maskSqlComments)(code);
|
|
3688
|
+
const entities = [];
|
|
3689
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
3690
|
+
for (const { regex, type } of patterns_1.sqlEntityPatterns) {
|
|
3691
|
+
regex.lastIndex = 0;
|
|
3692
|
+
for (const m of masked.matchAll(regex)) {
|
|
3693
|
+
const raw = m[1].trim();
|
|
3694
|
+
const key = (0, helpers_1.normIdent)(raw);
|
|
3695
|
+
if (seenNames.has(key))
|
|
3696
|
+
continue;
|
|
3697
|
+
seenNames.add(key);
|
|
3698
|
+
entities.push({ name: raw, type, line: (0, helpers_1.lineOf)(code, m.index ?? 0), complexity: "low" });
|
|
3699
|
+
}
|
|
3700
|
+
}
|
|
3701
|
+
helpers_1.ROUTINE_RECOVERY_RX.lastIndex = 0;
|
|
3702
|
+
for (const m of masked.matchAll(helpers_1.ROUTINE_RECOVERY_RX)) {
|
|
3703
|
+
const raw = m[1].trim();
|
|
3704
|
+
const key = (0, helpers_1.normIdent)(raw);
|
|
3705
|
+
if (seenNames.has(key))
|
|
3706
|
+
continue;
|
|
3707
|
+
seenNames.add(key);
|
|
3708
|
+
entities.push({ name: `${raw}()`, type: "procedure", line: (0, helpers_1.lineOf)(code, m.index ?? 0), complexity: "low" });
|
|
3709
|
+
}
|
|
3710
|
+
return entities;
|
|
3711
|
+
}
|
|
3712
|
+
function extractImports(code) {
|
|
3713
|
+
const masked = (0, helpers_1.maskSqlComments)(code);
|
|
3714
|
+
const imports = [];
|
|
3715
|
+
const fileBase = path_1.default.basename(_currentFile, path_1.default.extname(_currentFile));
|
|
3716
|
+
if (!fileBase)
|
|
3717
|
+
return imports;
|
|
3718
|
+
const ctes = (0, helpers_1.collectCteNames)(masked);
|
|
3719
|
+
for (const ref of (0, helpers_1.collectFkRefs)(masked)) {
|
|
3720
|
+
imports.push({ source: fileBase, names: [ref.name], isLocal: true });
|
|
3721
|
+
}
|
|
3722
|
+
for (const ref of (0, helpers_1.collectTableRefs)(masked, ctes)) {
|
|
3723
|
+
imports.push({ source: fileBase, names: [ref.name], isLocal: true });
|
|
3724
|
+
}
|
|
3725
|
+
return imports;
|
|
3726
|
+
}
|
|
3727
|
+
function extractExports(code) {
|
|
3728
|
+
const masked = (0, helpers_1.maskSqlComments)(code);
|
|
3729
|
+
const names = [];
|
|
3730
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3731
|
+
for (const { regex } of patterns_1.sqlEntityPatterns) {
|
|
3732
|
+
regex.lastIndex = 0;
|
|
3733
|
+
for (const m of masked.matchAll(regex)) {
|
|
3734
|
+
const key = (0, helpers_1.normIdent)(m[1].trim());
|
|
3735
|
+
if (!seen.has(key)) {
|
|
3736
|
+
seen.add(key);
|
|
3737
|
+
names.push(m[1].trim());
|
|
3738
|
+
}
|
|
3739
|
+
}
|
|
3740
|
+
}
|
|
3741
|
+
return names;
|
|
3742
|
+
}
|
|
3743
|
+
}
|
|
3744
|
+
});
|
|
3745
|
+
|
|
3746
|
+
// dist/languages/sql/index.js
|
|
3747
|
+
var require_sql = __commonJS({
|
|
3748
|
+
"dist/languages/sql/index.js"(exports2) {
|
|
3749
|
+
"use strict";
|
|
3750
|
+
var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
|
|
3751
|
+
if (k2 === void 0) k2 = k;
|
|
3752
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
3753
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
3754
|
+
desc = { enumerable: true, get: function() {
|
|
3755
|
+
return m[k];
|
|
3756
|
+
} };
|
|
3757
|
+
}
|
|
3758
|
+
Object.defineProperty(o, k2, desc);
|
|
3759
|
+
}) : (function(o, m, k, k2) {
|
|
3760
|
+
if (k2 === void 0) k2 = k;
|
|
3761
|
+
o[k2] = m[k];
|
|
3762
|
+
}));
|
|
3763
|
+
var __exportStar = exports2 && exports2.__exportStar || function(m, exports3) {
|
|
3764
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
|
|
3765
|
+
};
|
|
3766
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
3767
|
+
exports2.SqlParser = void 0;
|
|
3768
|
+
var registry_1 = require_registry();
|
|
3769
|
+
var patterns_1 = require_patterns();
|
|
3770
|
+
var extractor_1 = require_extractor();
|
|
3771
|
+
__exportStar(require_helpers(), exports2);
|
|
3772
|
+
__exportStar(require_patterns(), exports2);
|
|
3773
|
+
__exportStar(require_extractor(), exports2);
|
|
3774
|
+
exports2.SqlParser = {
|
|
3775
|
+
lang: "sql",
|
|
3776
|
+
extensions: [".sql"],
|
|
3777
|
+
extractEntities: extractor_1.extractEntities,
|
|
3778
|
+
extractImports: extractor_1.extractImports,
|
|
3779
|
+
extractExports: extractor_1.extractExports,
|
|
3780
|
+
entityPatterns: patterns_1.sqlEntityPatterns
|
|
3781
|
+
};
|
|
3782
|
+
(0, registry_1.registerParser)(exports2.SqlParser);
|
|
3783
|
+
}
|
|
3784
|
+
});
|
|
3785
|
+
|
|
3469
3786
|
// dist/stages/collector.js
|
|
3470
3787
|
var require_collector = __commonJS({
|
|
3471
3788
|
"dist/stages/collector.js"(exports2) {
|
|
@@ -3683,7 +4000,8 @@ var require_graph = __commonJS({
|
|
|
3683
4000
|
`${base}/index.ts`,
|
|
3684
4001
|
`${base}/index.js`,
|
|
3685
4002
|
`${base}.dart`,
|
|
3686
|
-
`${base}.rs
|
|
4003
|
+
`${base}.rs`,
|
|
4004
|
+
`${base}.sql`
|
|
3687
4005
|
];
|
|
3688
4006
|
for (const candidate of candidates) {
|
|
3689
4007
|
const normalized = candidate.replace(/\\/g, "/");
|
|
@@ -4145,6 +4463,7 @@ require_ruby();
|
|
|
4145
4463
|
require_swift();
|
|
4146
4464
|
require_dart();
|
|
4147
4465
|
require_rust();
|
|
4466
|
+
require_sql();
|
|
4148
4467
|
var fs_1 = __importDefault(require("fs"));
|
|
4149
4468
|
var collector_1 = require_collector();
|
|
4150
4469
|
var parser_1 = require_parser();
|
|
@@ -4173,7 +4492,7 @@ function getFlag(flag) {
|
|
|
4173
4492
|
}
|
|
4174
4493
|
function printHelp() {
|
|
4175
4494
|
console.log(`
|
|
4176
|
-
${bold("DepGraph")} ${dim("v1.
|
|
4495
|
+
${bold("DepGraph")} ${dim("v1.9.1")}
|
|
4177
4496
|
${dim("Dependency mapping \xB7 Impact simulation \xB7 Developer intelligence")}
|
|
4178
4497
|
|
|
4179
4498
|
${bold("USAGE")}
|
|
@@ -4222,7 +4541,7 @@ ${bold("GIT EXAMPLES")}
|
|
|
4222
4541
|
function printBanner() {
|
|
4223
4542
|
console.log(`
|
|
4224
4543
|
${bold("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501")}
|
|
4225
|
-
${bold(" DepGraph")} ${dim("v1.
|
|
4544
|
+
${bold(" DepGraph")} ${dim("v1.9.1")}
|
|
4226
4545
|
${bold("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501")}
|
|
4227
4546
|
`);
|
|
4228
4547
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "depgraph-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.1",
|
|
4
4
|
"description": "Dependency mapping and impact simulation for JS/TS projects",
|
|
5
5
|
"main": "depgraph.js",
|
|
6
6
|
"bin": {
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"release:mcp": "npm run build && npm run bundle:mcp",
|
|
16
16
|
"release:all": "npm run build && npm run bundle && npm run bundle:mcp",
|
|
17
17
|
"mcp:register": "claude mcp add depgraph -- node $(pwd)/depgraph-mcp.js",
|
|
18
|
+
"benchmark": "node scripts/benchmark.js",
|
|
18
19
|
"test": "vitest",
|
|
19
20
|
"test:run": "vitest run"
|
|
20
21
|
},
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Measures real response sizes from depgraph-mcp.js for each tool variant.
|
|
3
|
+
// Usage:
|
|
4
|
+
// node scripts/benchmark.js
|
|
5
|
+
// BENCH_DIR=/other/project node scripts/benchmark.js
|
|
6
|
+
'use strict';
|
|
7
|
+
const { execSync } = require('child_process');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const SERVER = path.resolve(__dirname, '..', 'depgraph-mcp.js');
|
|
11
|
+
const DIR = process.env.BENCH_DIR ?? path.resolve(__dirname, '..');
|
|
12
|
+
|
|
13
|
+
const os = require('os');
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const TMPF = path.join(os.tmpdir(), `depgraph-bench-${process.pid}.txt`);
|
|
16
|
+
|
|
17
|
+
function call(toolName, args) {
|
|
18
|
+
const messages = [
|
|
19
|
+
{
|
|
20
|
+
jsonrpc: '2.0', id: 1, method: 'initialize',
|
|
21
|
+
params: { protocolVersion: '2024-11-05', capabilities: {},
|
|
22
|
+
clientInfo: { name: 'bench', version: '1.0' } },
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
jsonrpc: '2.0', id: 2, method: 'tools/call',
|
|
26
|
+
params: { name: toolName, arguments: args },
|
|
27
|
+
},
|
|
28
|
+
].map(m => JSON.stringify(m)).join('\n') + '\n';
|
|
29
|
+
|
|
30
|
+
fs.writeFileSync(TMPF, messages, 'utf-8');
|
|
31
|
+
|
|
32
|
+
const out = execSync(
|
|
33
|
+
`cat ${TMPF} | node ${SERVER}`,
|
|
34
|
+
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const lines = out.trim().split('\n').filter(Boolean);
|
|
38
|
+
const result = JSON.parse(lines[lines.length - 1]);
|
|
39
|
+
return result?.result?.content?.[0]?.text ?? '';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ~3.5 chars/token for dense JSON; ~4.0 for prose (matches Claude/GPT-4 closely enough)
|
|
43
|
+
const tokEst = (s, prose = false) => Math.round(s.length / (prose ? 4.0 : 3.5));
|
|
44
|
+
|
|
45
|
+
const CASES = [
|
|
46
|
+
{
|
|
47
|
+
label: 'analyze_project — full (BASELINE)',
|
|
48
|
+
fn: () => call('analyze_project', { projectDir: DIR }),
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
label: 'analyze_project — verbosity:"overview"',
|
|
52
|
+
fn: () => call('analyze_project', { projectDir: DIR, verbosity: 'overview' }),
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
label: 'analyze_project — verbosity:"sketch"',
|
|
56
|
+
fn: () => call('analyze_project', { projectDir: DIR, verbosity: 'sketch' }),
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
label: 'analyze_project — focus + depth:2 (overview)',
|
|
60
|
+
fn: () => call('analyze_project',
|
|
61
|
+
{ projectDir: DIR, focus: 'buildGraph', depth: 2, verbosity: 'overview' }),
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
label: 'analyze_project — focus + depth:1 (overview)',
|
|
65
|
+
fn: () => call('analyze_project',
|
|
66
|
+
{ projectDir: DIR, focus: 'buildGraph', depth: 1, verbosity: 'overview' }),
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
label: 'get_graph_summary — overview',
|
|
70
|
+
fn: () => call('get_graph_summary', { projectDir: DIR }),
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
label: 'get_graph_summary — verbosity:"sketch"',
|
|
74
|
+
fn: () => call('get_graph_summary', { projectDir: DIR, verbosity: 'sketch' }),
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
label: 'get_graph_summary — format:"prose"',
|
|
78
|
+
fn: () => call('get_graph_summary', { projectDir: DIR, format: 'prose' }),
|
|
79
|
+
prose: true,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
label: 'describe_node("buildGraph")',
|
|
83
|
+
fn: () => call('describe_node', { projectDir: DIR, nodeName: 'buildGraph' }),
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
label: 'describe_node("simulateImpact")',
|
|
87
|
+
fn: () => call('describe_node', { projectDir: DIR, nodeName: 'simulateImpact' }),
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
label: 'simulate_impact("buildGraph", ...)',
|
|
91
|
+
fn: () => call('simulate_impact', {
|
|
92
|
+
projectDir: DIR,
|
|
93
|
+
targetNode: 'buildGraph',
|
|
94
|
+
changeDescription: 'removing parsed arg',
|
|
95
|
+
}),
|
|
96
|
+
},
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
console.log(`\nBenchmarking against: ${DIR}`);
|
|
100
|
+
console.log(`MCP server: ${SERVER}\n`);
|
|
101
|
+
|
|
102
|
+
const rows = CASES.map(c => {
|
|
103
|
+
process.stdout.write(` ${c.label} ...`);
|
|
104
|
+
const text = c.fn();
|
|
105
|
+
const chars = text.length;
|
|
106
|
+
const tokens = tokEst(text, c.prose ?? false);
|
|
107
|
+
process.stdout.write(` ${tokens.toLocaleString()} tokens\n`);
|
|
108
|
+
return { label: c.label, chars, tokens };
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const baseline = rows[0].tokens;
|
|
112
|
+
|
|
113
|
+
const W = { label: 50, chars: 10, tokens: 9, pct: 14 };
|
|
114
|
+
const rule = '─'.repeat(W.label + W.chars + W.tokens + W.pct + 4);
|
|
115
|
+
|
|
116
|
+
console.log('\n' + rule);
|
|
117
|
+
console.log(
|
|
118
|
+
'Tool call'.padEnd(W.label) +
|
|
119
|
+
'Chars'.padStart(W.chars) +
|
|
120
|
+
'Tokens'.padStart(W.tokens) +
|
|
121
|
+
'vs baseline'.padStart(W.pct)
|
|
122
|
+
);
|
|
123
|
+
console.log(rule);
|
|
124
|
+
|
|
125
|
+
rows.forEach((r, i) => {
|
|
126
|
+
const pct = i === 0
|
|
127
|
+
? '(baseline)'
|
|
128
|
+
: `−${Math.round((1 - r.tokens / baseline) * 100)} %`;
|
|
129
|
+
console.log(
|
|
130
|
+
r.label.padEnd(W.label) +
|
|
131
|
+
r.chars.toLocaleString().padStart(W.chars) +
|
|
132
|
+
r.tokens.toLocaleString().padStart(W.tokens) +
|
|
133
|
+
pct.padStart(W.pct)
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
console.log(rule);
|
|
138
|
+
console.log(`\nBaseline: ${baseline.toLocaleString()} tokens (${rows[0].chars.toLocaleString()} chars)\n`);
|