@levnikolaevich/hex-line-mcp 1.22.0 → 1.23.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/dist/server.mjs +87 -28
- package/package.json +1 -1
package/dist/server.mjs
CHANGED
|
@@ -400,6 +400,57 @@ function getGraphDB(filePath, { allowStale = false } = {}) {
|
|
|
400
400
|
return null;
|
|
401
401
|
}
|
|
402
402
|
}
|
|
403
|
+
function diagnoseGraph(filePath) {
|
|
404
|
+
if (_driverUnavailable) return { reason: "driver_missing" };
|
|
405
|
+
try {
|
|
406
|
+
const projectRoot = findProjectRoot(filePath);
|
|
407
|
+
if (!projectRoot) return { reason: "no_project_root" };
|
|
408
|
+
const dbPath = join3(projectRoot, ".hex-skills/codegraph", "index.db");
|
|
409
|
+
if (!existsSync2(dbPath)) return { reason: "index_missing", projectRoot };
|
|
410
|
+
if (_dbs.has(dbPath)) {
|
|
411
|
+
const cached = _dbs.get(dbPath);
|
|
412
|
+
if (!isFilePathFresh(cached, projectRoot, filePath)) {
|
|
413
|
+
return { reason: "stale", projectRoot };
|
|
414
|
+
}
|
|
415
|
+
return { reason: "ok", projectRoot };
|
|
416
|
+
}
|
|
417
|
+
const require2 = createRequire(import.meta.url);
|
|
418
|
+
const Database = require2("better-sqlite3");
|
|
419
|
+
const db = new Database(dbPath, { readonly: true });
|
|
420
|
+
if (!validateContract(db)) {
|
|
421
|
+
db.close();
|
|
422
|
+
return { reason: "contract_mismatch", projectRoot };
|
|
423
|
+
}
|
|
424
|
+
if (!isFilePathFresh(db, projectRoot, filePath)) {
|
|
425
|
+
db.close();
|
|
426
|
+
return { reason: "stale", projectRoot };
|
|
427
|
+
}
|
|
428
|
+
_dbs.set(dbPath, db);
|
|
429
|
+
return { reason: "ok", projectRoot };
|
|
430
|
+
} catch {
|
|
431
|
+
_driverUnavailable = true;
|
|
432
|
+
return { reason: "driver_missing" };
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
function graphUnavailableHint(filePath) {
|
|
436
|
+
const { reason, projectRoot } = diagnoseGraph(filePath);
|
|
437
|
+
if (reason === "ok") return [];
|
|
438
|
+
const at = projectRoot ? ` at ${projectRoot.replace(/\\/g, "/")}` : "";
|
|
439
|
+
switch (reason) {
|
|
440
|
+
case "driver_missing":
|
|
441
|
+
return ["graph_enrichment: unavailable", "graph_fix: install better-sqlite3 in hex-line-mcp package"];
|
|
442
|
+
case "no_project_root":
|
|
443
|
+
return ["graph_enrichment: unavailable", "graph_fix: file is outside any project root (no package.json / pyproject.toml / .git marker)"];
|
|
444
|
+
case "index_missing":
|
|
445
|
+
return ["graph_enrichment: unavailable", `graph_fix: run mcp__hex-graph__index_project${at}`];
|
|
446
|
+
case "contract_mismatch":
|
|
447
|
+
return ["graph_enrichment: unavailable", `graph_fix: index built by incompatible hex-graph version; re-run mcp__hex-graph__index_project${at}`];
|
|
448
|
+
case "stale":
|
|
449
|
+
return ["graph_enrichment: unavailable", `graph_fix: file modified after last index; re-run mcp__hex-graph__index_project${at} or wait for background refresh`];
|
|
450
|
+
default:
|
|
451
|
+
return ["graph_enrichment: unavailable"];
|
|
452
|
+
}
|
|
453
|
+
}
|
|
403
454
|
function validateContract(db) {
|
|
404
455
|
try {
|
|
405
456
|
for (const viewName of REQUIRED_VIEWS) {
|
|
@@ -497,6 +548,20 @@ function ensureGraphFreshForFile(db, absoluteFilePath) {
|
|
|
497
548
|
return true;
|
|
498
549
|
}
|
|
499
550
|
}
|
|
551
|
+
function isGraphFreshAtMtime(db, absoluteFilePath, mtimeMs) {
|
|
552
|
+
if (!db) return false;
|
|
553
|
+
try {
|
|
554
|
+
const projectRoot = findProjectRoot(absoluteFilePath);
|
|
555
|
+
if (!projectRoot) return true;
|
|
556
|
+
const relativeFile = normalizeRelativeFile(projectRoot, absoluteFilePath);
|
|
557
|
+
if (!relativeFile) return true;
|
|
558
|
+
const indexedMtime = lookupIndexedMtime(db, relativeFile);
|
|
559
|
+
if (indexedMtime == null) return false;
|
|
560
|
+
return mtimeMs <= indexedMtime + FRESHNESS_TOLERANCE_MS;
|
|
561
|
+
} catch {
|
|
562
|
+
return true;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
500
565
|
function fileAnnotations(db, file, { startLine = null, endLine = null, limit = 8 } = {}) {
|
|
501
566
|
try {
|
|
502
567
|
const hasRange = Number.isInteger(startLine) && Number.isInteger(endLine);
|
|
@@ -1267,7 +1332,7 @@ function serializeSearchBlock(block, opts = {}) {
|
|
|
1267
1332
|
}
|
|
1268
1333
|
if (block.meta.summary) lines.push(`summary: ${block.meta.summary}`);
|
|
1269
1334
|
lines.push(...renderMetaLines(Object.fromEntries(
|
|
1270
|
-
Object.entries(block.meta).filter(([key]) => key !== "matchLines" && key !== "summary")
|
|
1335
|
+
Object.entries(block.meta).filter(([key]) => key !== "matchLines" && key !== "summary" && key !== "graphScore")
|
|
1271
1336
|
)));
|
|
1272
1337
|
lines.push(...block.entries.map((entry) => serializeSearchEntry(entry, opts)));
|
|
1273
1338
|
lines.push(`checksum: ${block.checksum}`);
|
|
@@ -2801,7 +2866,9 @@ changed_ranges: ${describeChangedRanges(changedRanges)}`;
|
|
|
2801
2866
|
summary: lines_changed=${changedSpan} diff_entries=${diffEntryCount} lines_after=${lines.length}`;
|
|
2802
2867
|
msg2 += `
|
|
2803
2868
|
payload_sections: ${payloadSections(displayDiff ? ["diff"] : [])}`;
|
|
2804
|
-
|
|
2869
|
+
const hint = graphUnavailableHint(real);
|
|
2870
|
+
if (hint.length > 0) msg2 += `
|
|
2871
|
+
${hint.join("\n")}`;
|
|
2805
2872
|
msg2 += `
|
|
2806
2873
|
Dry run: ${filePath} would change (${lines.length} lines)`;
|
|
2807
2874
|
if (displayDiff) msg2 += `
|
|
@@ -2830,9 +2897,10 @@ remapped_refs:
|
|
|
2830
2897
|
${remaps.map(({ from, to }) => `${from} -> ${to}`).join("\n")}`;
|
|
2831
2898
|
}
|
|
2832
2899
|
let hasPostEditBlock = false;
|
|
2833
|
-
let graphEnrichment = "unavailable";
|
|
2834
2900
|
let semanticImpacts = [];
|
|
2835
2901
|
let cloneWarnings = [];
|
|
2902
|
+
let graphDbAvailable = false;
|
|
2903
|
+
let graphFresh = true;
|
|
2836
2904
|
msg += `
|
|
2837
2905
|
Updated ${filePath} (${lines.length} lines)`;
|
|
2838
2906
|
if (fullDiff && minLine <= maxLine) {
|
|
@@ -2859,7 +2927,8 @@ ${serializeReadBlock(block)}`;
|
|
|
2859
2927
|
const db = getGraphDB(real, { allowStale: true });
|
|
2860
2928
|
const relFile = db ? getRelativePath(real) : null;
|
|
2861
2929
|
if (db && relFile && fullDiff && minLine <= maxLine) {
|
|
2862
|
-
|
|
2930
|
+
graphDbAvailable = true;
|
|
2931
|
+
graphFresh = isGraphFreshAtMtime(db, real, currentSnapshot.mtimeMs);
|
|
2863
2932
|
semanticImpacts = semanticImpact(db, relFile, minLine, maxLine);
|
|
2864
2933
|
if (semanticImpacts.length > 0) {
|
|
2865
2934
|
const sections = semanticImpacts.map((impact) => {
|
|
@@ -2892,7 +2961,7 @@ ${sections.join("\n")}`;
|
|
|
2892
2961
|
}
|
|
2893
2962
|
cloneWarnings = cloneWarning(db, relFile, minLine, maxLine);
|
|
2894
2963
|
if (cloneWarnings.length > 0) {
|
|
2895
|
-
const list = cloneWarnings.map((c) => `${c.file}:${c.line}`).join(", ");
|
|
2964
|
+
const list = cloneWarnings.map((c) => `${c.file}:${c.line}${c.cloneType ? ` (${c.cloneType})` : ""}`).join(", ");
|
|
2896
2965
|
msg += `
|
|
2897
2966
|
|
|
2898
2967
|
\u26A0 ${cloneWarnings.length} clone(s): ${list}`;
|
|
@@ -2900,20 +2969,17 @@ ${sections.join("\n")}`;
|
|
|
2900
2969
|
}
|
|
2901
2970
|
} catch {
|
|
2902
2971
|
}
|
|
2972
|
+
if (!graphFresh) msg += "\ngraph_fresh: stale";
|
|
2903
2973
|
const payloadKinds = [];
|
|
2904
2974
|
if (hasPostEditBlock) payloadKinds.push("post_edit");
|
|
2905
2975
|
if (semanticImpacts.length > 0) payloadKinds.push("semantic_impact");
|
|
2906
2976
|
if (cloneWarnings.length > 0) payloadKinds.push("clone_warning");
|
|
2907
2977
|
if (displayDiff) payloadKinds.push("diff");
|
|
2908
|
-
const semanticFactCount = semanticImpacts.reduce((sum, impact) => sum + (impact.facts?.length || 0), 0);
|
|
2909
2978
|
const summaryLineParts = [
|
|
2910
2979
|
`summary: lines_changed=${changedSpan} diff_entries=${diffEntryCount} lines_after=${lines.length}${editContext.corrections.length > 0 ? ` boundary_echo_stripped=${editContext.corrections.length}` : ``}`,
|
|
2911
|
-
`payload_sections: ${payloadSections(payloadKinds)}
|
|
2912
|
-
`graph_enrichment: ${graphEnrichment}`
|
|
2980
|
+
`payload_sections: ${payloadSections(payloadKinds)}`
|
|
2913
2981
|
];
|
|
2914
|
-
if (
|
|
2915
|
-
if (semanticFactCount > 0) summaryLineParts.push(`semantic_fact_count: ${semanticFactCount}`);
|
|
2916
|
-
if (cloneWarnings.length > 0) summaryLineParts.push(`clone_warning_count: ${cloneWarnings.length}`);
|
|
2982
|
+
if (!graphDbAvailable) summaryLineParts.push(...graphUnavailableHint(real));
|
|
2917
2983
|
const summaryLines = summaryLineParts.join("\n");
|
|
2918
2984
|
msg = msg.replace(`
|
|
2919
2985
|
Updated ${filePath} (${lines.length} lines)`, `
|
|
@@ -4420,9 +4486,6 @@ async function semanticGitDiff(targetPath, { baseRef = "HEAD", headRef = null }
|
|
|
4420
4486
|
}
|
|
4421
4487
|
|
|
4422
4488
|
// lib/changes.mjs
|
|
4423
|
-
function graphEnrichmentState(db) {
|
|
4424
|
-
return db ? "available" : "unavailable";
|
|
4425
|
-
}
|
|
4426
4489
|
function payloadSections2(sections) {
|
|
4427
4490
|
return sections.length > 0 ? sections.join(",") : "summary_only";
|
|
4428
4491
|
}
|
|
@@ -4467,7 +4530,7 @@ async function fileChanges(filePath, compareAgainst = "HEAD") {
|
|
|
4467
4530
|
if (statSync11(real).isDirectory()) {
|
|
4468
4531
|
const db2 = getGraphDB(join6(real, "__hex-line_probe__"));
|
|
4469
4532
|
const diff2 = await semanticGitDiff(real, { baseRef: compareAgainst });
|
|
4470
|
-
const
|
|
4533
|
+
const graphHint2 = graphUnavailableHint(join6(real, "__hex-line_probe__"));
|
|
4471
4534
|
if (diff2.summary.changed_file_count === 0) {
|
|
4472
4535
|
return [
|
|
4473
4536
|
"status: NO_CHANGES",
|
|
@@ -4476,7 +4539,7 @@ async function fileChanges(filePath, compareAgainst = "HEAD") {
|
|
|
4476
4539
|
`compare_against: ${compareAgainst}`,
|
|
4477
4540
|
"scope: directory",
|
|
4478
4541
|
"summary: changed_files=0",
|
|
4479
|
-
|
|
4542
|
+
...graphHint2
|
|
4480
4543
|
].join("\n");
|
|
4481
4544
|
}
|
|
4482
4545
|
let emittedRiskCount = 0;
|
|
@@ -4490,7 +4553,7 @@ async function fileChanges(filePath, compareAgainst = "HEAD") {
|
|
|
4490
4553
|
"scope: directory",
|
|
4491
4554
|
`summary: changed_files=${diff2.summary.changed_file_count}`,
|
|
4492
4555
|
`next_action: ${ACTION.INSPECT_FILE}`,
|
|
4493
|
-
|
|
4556
|
+
...graphHint2,
|
|
4494
4557
|
""
|
|
4495
4558
|
];
|
|
4496
4559
|
for (const file2 of diff2.changed_files) {
|
|
@@ -4513,16 +4576,14 @@ async function fileChanges(filePath, compareAgainst = "HEAD") {
|
|
|
4513
4576
|
if (emittedRiskCount > 0) sectionKinds2.push("risk_summary");
|
|
4514
4577
|
if (emittedRemovedApiWarnings > 0) sectionKinds2.push("removed_api_warning");
|
|
4515
4578
|
const spliceLines = [];
|
|
4516
|
-
if (emittedRiskCount > 0) spliceLines.push(`risk_summary_count: ${emittedRiskCount}`);
|
|
4517
|
-
if (emittedRemovedApiWarnings > 0) spliceLines.push(`removed_api_warning_count: ${emittedRemovedApiWarnings}`);
|
|
4518
4579
|
if (sectionKinds2.length > 0) spliceLines.push(`payload_sections: ${payloadSections2(sectionKinds2)}`);
|
|
4519
|
-
sections.splice(
|
|
4580
|
+
sections.splice(7 + graphHint2.length, 0, ...spliceLines);
|
|
4520
4581
|
return sections.join("\n");
|
|
4521
4582
|
}
|
|
4522
4583
|
const db = getGraphDB(real);
|
|
4523
4584
|
const diff = await semanticGitDiff(real, { baseRef: compareAgainst });
|
|
4524
4585
|
const file = diff.changed_files[0];
|
|
4525
|
-
const
|
|
4586
|
+
const graphHint = graphUnavailableHint(real);
|
|
4526
4587
|
if (!file) {
|
|
4527
4588
|
return [
|
|
4528
4589
|
"status: NO_CHANGES",
|
|
@@ -4531,7 +4592,7 @@ async function fileChanges(filePath, compareAgainst = "HEAD") {
|
|
|
4531
4592
|
`compare_against: ${compareAgainst}`,
|
|
4532
4593
|
"scope: file",
|
|
4533
4594
|
"summary: added=0 removed=0 modified=0",
|
|
4534
|
-
|
|
4595
|
+
...graphHint
|
|
4535
4596
|
].join("\n");
|
|
4536
4597
|
}
|
|
4537
4598
|
if (!file.semantic_supported) {
|
|
@@ -4543,7 +4604,7 @@ async function fileChanges(filePath, compareAgainst = "HEAD") {
|
|
|
4543
4604
|
"scope: file",
|
|
4544
4605
|
`summary: semantic diff unavailable for ${file.extension} files`,
|
|
4545
4606
|
`next_action: ${ACTION.INSPECT_RAW_DIFF}`,
|
|
4546
|
-
|
|
4607
|
+
...graphHint
|
|
4547
4608
|
].join("\n");
|
|
4548
4609
|
}
|
|
4549
4610
|
const relFile = getRelativePath(real) || file.path?.replace(/\\/g, "/");
|
|
@@ -4557,10 +4618,8 @@ async function fileChanges(filePath, compareAgainst = "HEAD") {
|
|
|
4557
4618
|
`compare_against: ${compareAgainst}`,
|
|
4558
4619
|
"scope: file",
|
|
4559
4620
|
`summary: ${symbolCountSummary(file)}`,
|
|
4560
|
-
|
|
4621
|
+
...graphHint
|
|
4561
4622
|
];
|
|
4562
|
-
if (riskLines.length > 0) parts.push(`risk_summary_count: ${riskLines.length}`);
|
|
4563
|
-
if (removedApiWarnings.length > 0) parts.push(`removed_api_warning_count: ${removedApiWarnings.length}`);
|
|
4564
4623
|
if (file.added_symbols.length) {
|
|
4565
4624
|
sectionKinds.push("added");
|
|
4566
4625
|
parts.push(`next_action: ${ACTION.REVIEW_RISKS}`);
|
|
@@ -4594,7 +4653,7 @@ async function fileChanges(filePath, compareAgainst = "HEAD") {
|
|
|
4594
4653
|
if (riskLines.length > 0) sectionKinds.push("risk_summary");
|
|
4595
4654
|
if (removedApiWarnings.length > 0) sectionKinds.push("removed_api_warning");
|
|
4596
4655
|
if (sectionKinds.length > 0) {
|
|
4597
|
-
const insertIdx =
|
|
4656
|
+
const insertIdx = 6 + graphHint.length;
|
|
4598
4657
|
parts.splice(insertIdx, 0, `payload_sections: ${payloadSections2(sectionKinds)}`);
|
|
4599
4658
|
}
|
|
4600
4659
|
if (riskLines.length || removedApiWarnings.length) {
|
|
@@ -4749,7 +4808,7 @@ function errorResult(code, message, recovery, { large = false, extra = null } =
|
|
|
4749
4808
|
}
|
|
4750
4809
|
|
|
4751
4810
|
// server.mjs
|
|
4752
|
-
var version = true ? "1.
|
|
4811
|
+
var version = true ? "1.23.1" : (await null).createRequire(import.meta.url)("./package.json").version;
|
|
4753
4812
|
var STATUS_ENUM = z2.enum(["OK", "ERROR", "AUTO_REBASED", "CONFLICT", "STALE", "INVALID", "NO_CHANGES", "CHANGED", "UNSUPPORTED"]);
|
|
4754
4813
|
var ERROR_SHAPE = z2.object({ code: z2.string(), message: z2.string(), recovery: z2.string() }).optional();
|
|
4755
4814
|
var LINE_REPORT_KEYS = /* @__PURE__ */ new Set([
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@levnikolaevich/hex-line-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.23.1",
|
|
4
4
|
"mcpName": "io.github.levnikolaevich/hex-line-mcp",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Hash-verified file editing MCP + token efficiency hook for AI coding agents. 9 tools: inspect_path, read, edit, write, grep, outline, verify, changes, bulk_replace.",
|