@levnikolaevich/hex-line-mcp 1.23.1 → 1.24.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 +88 -30
- package/package.json +1 -1
package/dist/server.mjs
CHANGED
|
@@ -400,6 +400,20 @@ function getGraphDB(filePath, { allowStale = false } = {}) {
|
|
|
400
400
|
return null;
|
|
401
401
|
}
|
|
402
402
|
}
|
|
403
|
+
function diagnoseFreshness(db, projectRoot, filePath) {
|
|
404
|
+
try {
|
|
405
|
+
const stat = statSync3(filePath);
|
|
406
|
+
if (!stat.isFile()) return "ok";
|
|
407
|
+
const relativeFile = normalizeRelativeFile(projectRoot, filePath);
|
|
408
|
+
if (!relativeFile) return "ok";
|
|
409
|
+
const indexedMtime = lookupIndexedMtime(db, relativeFile);
|
|
410
|
+
if (indexedMtime == null) return "file_not_indexed";
|
|
411
|
+
if (Math.abs(indexedMtime - stat.mtimeMs) < FRESHNESS_TOLERANCE_MS) return "ok";
|
|
412
|
+
return "stale";
|
|
413
|
+
} catch {
|
|
414
|
+
return "ok";
|
|
415
|
+
}
|
|
416
|
+
}
|
|
403
417
|
function diagnoseGraph(filePath) {
|
|
404
418
|
if (_driverUnavailable) return { reason: "driver_missing" };
|
|
405
419
|
try {
|
|
@@ -409,10 +423,7 @@ function diagnoseGraph(filePath) {
|
|
|
409
423
|
if (!existsSync2(dbPath)) return { reason: "index_missing", projectRoot };
|
|
410
424
|
if (_dbs.has(dbPath)) {
|
|
411
425
|
const cached = _dbs.get(dbPath);
|
|
412
|
-
|
|
413
|
-
return { reason: "stale", projectRoot };
|
|
414
|
-
}
|
|
415
|
-
return { reason: "ok", projectRoot };
|
|
426
|
+
return { reason: diagnoseFreshness(cached, projectRoot, filePath), projectRoot };
|
|
416
427
|
}
|
|
417
428
|
const require2 = createRequire(import.meta.url);
|
|
418
429
|
const Database = require2("better-sqlite3");
|
|
@@ -421,9 +432,30 @@ function diagnoseGraph(filePath) {
|
|
|
421
432
|
db.close();
|
|
422
433
|
return { reason: "contract_mismatch", projectRoot };
|
|
423
434
|
}
|
|
424
|
-
|
|
435
|
+
_dbs.set(dbPath, db);
|
|
436
|
+
return { reason: diagnoseFreshness(db, projectRoot, filePath), projectRoot };
|
|
437
|
+
} catch {
|
|
438
|
+
_driverUnavailable = true;
|
|
439
|
+
return { reason: "driver_missing" };
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
function diagnoseGraphForProject(directoryPath) {
|
|
443
|
+
if (_driverUnavailable) return { reason: "driver_missing" };
|
|
444
|
+
try {
|
|
445
|
+
if (!directoryPath) return { reason: "no_project_root" };
|
|
446
|
+
const projectRoot = findProjectRoot(join3(directoryPath, "__hex-line_probe__"));
|
|
447
|
+
if (!projectRoot) return { reason: "no_project_root" };
|
|
448
|
+
const dbPath = join3(projectRoot, ".hex-skills/codegraph", "index.db");
|
|
449
|
+
if (!existsSync2(dbPath)) return { reason: "index_missing", projectRoot };
|
|
450
|
+
if (_dbs.has(dbPath)) {
|
|
451
|
+
return { reason: "ok", projectRoot };
|
|
452
|
+
}
|
|
453
|
+
const require2 = createRequire(import.meta.url);
|
|
454
|
+
const Database = require2("better-sqlite3");
|
|
455
|
+
const db = new Database(dbPath, { readonly: true });
|
|
456
|
+
if (!validateContract(db)) {
|
|
425
457
|
db.close();
|
|
426
|
-
return { reason: "
|
|
458
|
+
return { reason: "contract_mismatch", projectRoot };
|
|
427
459
|
}
|
|
428
460
|
_dbs.set(dbPath, db);
|
|
429
461
|
return { reason: "ok", projectRoot };
|
|
@@ -432,9 +464,15 @@ function diagnoseGraph(filePath) {
|
|
|
432
464
|
return { reason: "driver_missing" };
|
|
433
465
|
}
|
|
434
466
|
}
|
|
467
|
+
function getGraphDBForProject(directoryPath) {
|
|
468
|
+
const { reason, projectRoot } = diagnoseGraphForProject(directoryPath);
|
|
469
|
+
if (reason !== "ok" || !projectRoot) return null;
|
|
470
|
+
const dbPath = join3(projectRoot, ".hex-skills/codegraph", "index.db");
|
|
471
|
+
return _dbs.get(dbPath) || null;
|
|
472
|
+
}
|
|
435
473
|
function graphUnavailableHint(filePath) {
|
|
436
474
|
const { reason, projectRoot } = diagnoseGraph(filePath);
|
|
437
|
-
if (reason === "ok") return [];
|
|
475
|
+
if (reason === "ok" || reason === "file_not_indexed") return [];
|
|
438
476
|
const at = projectRoot ? ` at ${projectRoot.replace(/\\/g, "/")}` : "";
|
|
439
477
|
switch (reason) {
|
|
440
478
|
case "driver_missing":
|
|
@@ -451,6 +489,23 @@ function graphUnavailableHint(filePath) {
|
|
|
451
489
|
return ["graph_enrichment: unavailable"];
|
|
452
490
|
}
|
|
453
491
|
}
|
|
492
|
+
function graphUnavailableHintForProject(projectRoot) {
|
|
493
|
+
const { reason } = diagnoseGraphForProject(projectRoot);
|
|
494
|
+
if (reason === "ok") return [];
|
|
495
|
+
const at = projectRoot ? ` at ${projectRoot.replace(/\\/g, "/")}` : "";
|
|
496
|
+
switch (reason) {
|
|
497
|
+
case "driver_missing":
|
|
498
|
+
return ["graph_enrichment: unavailable", "graph_fix: install better-sqlite3 in hex-line-mcp package"];
|
|
499
|
+
case "no_project_root":
|
|
500
|
+
return ["graph_enrichment: unavailable", "graph_fix: directory is outside any project root"];
|
|
501
|
+
case "index_missing":
|
|
502
|
+
return ["graph_enrichment: unavailable", `graph_fix: run mcp__hex-graph__index_project${at}`];
|
|
503
|
+
case "contract_mismatch":
|
|
504
|
+
return ["graph_enrichment: unavailable", `graph_fix: index built by incompatible hex-graph version; re-run mcp__hex-graph__index_project${at}`];
|
|
505
|
+
default:
|
|
506
|
+
return ["graph_enrichment: unavailable"];
|
|
507
|
+
}
|
|
508
|
+
}
|
|
454
509
|
function validateContract(db) {
|
|
455
510
|
try {
|
|
456
511
|
for (const viewName of REQUIRED_VIEWS) {
|
|
@@ -3707,16 +3762,17 @@ function entrySummary(entry) {
|
|
|
3707
3762
|
if (entry.status === "STALE") return "content changed since checksum capture";
|
|
3708
3763
|
return entry.reason;
|
|
3709
3764
|
}
|
|
3710
|
-
function renderEntry(entry, index, total) {
|
|
3765
|
+
function renderEntry(entry, index, total, topLevelNextAction) {
|
|
3711
3766
|
const parts = [
|
|
3712
3767
|
`entry: ${index}/${total}`,
|
|
3713
3768
|
`status: ${entry.status}`,
|
|
3714
3769
|
entry.span ? `span: ${entry.span}` : null,
|
|
3715
3770
|
`checksum: ${entry.checksum}`,
|
|
3716
|
-
entry.currentChecksum && entry.currentChecksum !== entry.checksum ? `current_checksum: ${entry.currentChecksum}` : null
|
|
3717
|
-
`next_action: ${entryNextAction(entry)}`,
|
|
3718
|
-
`summary: ${entrySummary(entry)}`
|
|
3771
|
+
entry.currentChecksum && entry.currentChecksum !== entry.checksum ? `current_checksum: ${entry.currentChecksum}` : null
|
|
3719
3772
|
].filter(Boolean);
|
|
3773
|
+
const action = entryNextAction(entry);
|
|
3774
|
+
if (action !== topLevelNextAction) parts.push(`next_action: ${action}`);
|
|
3775
|
+
if (entry.status !== "VALID") parts.push(`summary: ${entrySummary(entry)}`);
|
|
3720
3776
|
return parts.join(" | ");
|
|
3721
3777
|
}
|
|
3722
3778
|
function verifyChecksums(filePath, checksums, opts = {}) {
|
|
@@ -3730,18 +3786,20 @@ function verifyChecksums(filePath, checksums, opts = {}) {
|
|
|
3730
3786
|
const summary = summarizeStatuses(results);
|
|
3731
3787
|
const status = summary.invalid > 0 ? STATUS.INVALID : summary.stale > 0 ? STATUS.STALE : STATUS.OK;
|
|
3732
3788
|
const staleRanges = results.filter((entry) => entry.status === "STALE" && entry.span).map((entry) => entry.span);
|
|
3789
|
+
const topLevelNextAction = overallNextAction(summary);
|
|
3790
|
+
const verboseSummary = results.length > 1 || summary.stale > 0 || summary.invalid > 0;
|
|
3733
3791
|
const lines = [
|
|
3734
3792
|
`status: ${status}`,
|
|
3735
3793
|
`reason: ${overallReason(status)}`,
|
|
3736
|
-
`revision: ${currentSnapshot.revision}
|
|
3737
|
-
`file: ${currentSnapshot.fileChecksum}`,
|
|
3738
|
-
`summary: valid=${summary.valid} stale=${summary.stale} invalid=${summary.invalid}`,
|
|
3739
|
-
`next_action: ${overallNextAction(summary)}`
|
|
3794
|
+
`revision: ${currentSnapshot.revision}`
|
|
3740
3795
|
];
|
|
3741
|
-
if (
|
|
3796
|
+
if (verboseSummary) lines.push(`summary: valid=${summary.valid} stale=${summary.stale} invalid=${summary.invalid}`);
|
|
3797
|
+
lines.push(`next_action: ${topLevelNextAction}`);
|
|
3798
|
+
if (opts.baseRevision && opts.baseRevision !== currentSnapshot.revision) {
|
|
3742
3799
|
lines.push(`base_revision: ${opts.baseRevision}`);
|
|
3743
3800
|
if (hasBaseSnapshot) {
|
|
3744
|
-
|
|
3801
|
+
const changed = describeChangedRanges(computeChangedRanges(baseSnapshot.lines, currentSnapshot.lines));
|
|
3802
|
+
if (changed !== "none") lines.push(`changed_ranges: ${changed}`);
|
|
3745
3803
|
} else {
|
|
3746
3804
|
lines.push("base_revision_status: evicted");
|
|
3747
3805
|
}
|
|
@@ -3749,7 +3807,7 @@ function verifyChecksums(filePath, checksums, opts = {}) {
|
|
|
3749
3807
|
const suggestedReadCall = buildSuggestedReadCall2(filePath, staleRanges);
|
|
3750
3808
|
if (suggestedReadCall) lines.push(`suggested_read_call: ${suggestedReadCall}`);
|
|
3751
3809
|
if (results.length > 0) {
|
|
3752
|
-
lines.push("", ...results.map((entry, index) => renderEntry(entry, index + 1, results.length)));
|
|
3810
|
+
lines.push("", ...results.map((entry, index) => renderEntry(entry, index + 1, results.length, topLevelNextAction)));
|
|
3753
3811
|
}
|
|
3754
3812
|
return lines.join("\n");
|
|
3755
3813
|
}
|
|
@@ -3851,12 +3909,13 @@ function findByPattern(dirPath, opts) {
|
|
|
3851
3909
|
const truncated = shown.length < matches.length;
|
|
3852
3910
|
const groups = topPatternGroups(matches);
|
|
3853
3911
|
const lines = [
|
|
3854
|
-
`Found ${matches.length} match${matches.length === 1 ? "" : "es"} for "${opts.pattern}" in ${rootName}
|
|
3855
|
-
`match_count: ${matches.length}`,
|
|
3856
|
-
`shown_count: ${shown.length}`,
|
|
3857
|
-
`truncated: ${truncated}`
|
|
3912
|
+
`Found ${matches.length} match${matches.length === 1 ? "" : "es"} for "${opts.pattern}" in ${rootName}/`
|
|
3858
3913
|
];
|
|
3859
|
-
if (
|
|
3914
|
+
if (truncated) {
|
|
3915
|
+
lines.push(`shown_count: ${shown.length}`);
|
|
3916
|
+
lines.push(`truncated: true`);
|
|
3917
|
+
}
|
|
3918
|
+
if (groups.length > 1) {
|
|
3860
3919
|
lines.push(`top_groups: ${groups.map(([group, count]) => `${group} (${count})`).join(", ")}`);
|
|
3861
3920
|
}
|
|
3862
3921
|
if (truncated) {
|
|
@@ -4225,7 +4284,6 @@ function autoSync() {
|
|
|
4225
4284
|
|
|
4226
4285
|
// lib/changes.mjs
|
|
4227
4286
|
import { statSync as statSync11 } from "node:fs";
|
|
4228
|
-
import { join as join6 } from "node:path";
|
|
4229
4287
|
|
|
4230
4288
|
// ../hex-common/src/git/semantic-diff.mjs
|
|
4231
4289
|
import { execFileSync } from "node:child_process";
|
|
@@ -4528,9 +4586,9 @@ async function fileChanges(filePath, compareAgainst = "HEAD") {
|
|
|
4528
4586
|
filePath = normalizePath(filePath);
|
|
4529
4587
|
const real = validatePath(filePath);
|
|
4530
4588
|
if (statSync11(real).isDirectory()) {
|
|
4531
|
-
const db2 =
|
|
4589
|
+
const db2 = getGraphDBForProject(real);
|
|
4532
4590
|
const diff2 = await semanticGitDiff(real, { baseRef: compareAgainst });
|
|
4533
|
-
const graphHint2 =
|
|
4591
|
+
const graphHint2 = graphUnavailableHintForProject(real);
|
|
4534
4592
|
if (diff2.summary.changed_file_count === 0) {
|
|
4535
4593
|
return [
|
|
4536
4594
|
"status: NO_CHANGES",
|
|
@@ -4667,7 +4725,7 @@ async function fileChanges(filePath, compareAgainst = "HEAD") {
|
|
|
4667
4725
|
|
|
4668
4726
|
// lib/bulk-replace.mjs
|
|
4669
4727
|
import { writeFileSync as writeFileSync3, readdirSync as readdirSync3, renameSync as renameSync2, unlinkSync as unlinkSync2 } from "node:fs";
|
|
4670
|
-
import { resolve as resolve9, relative as relative4, join as
|
|
4728
|
+
import { resolve as resolve9, relative as relative4, join as join6 } from "node:path";
|
|
4671
4729
|
var ignoreMod;
|
|
4672
4730
|
try {
|
|
4673
4731
|
ignoreMod = await import("ignore");
|
|
@@ -4683,7 +4741,7 @@ function walkFiles(dir, rootDir, ig) {
|
|
|
4683
4741
|
}
|
|
4684
4742
|
for (const e of entries) {
|
|
4685
4743
|
if (e.name === ".git" || e.name === "node_modules") continue;
|
|
4686
|
-
const full =
|
|
4744
|
+
const full = join6(dir, e.name);
|
|
4687
4745
|
const rel = relative4(rootDir, full).replace(/\\/g, "/");
|
|
4688
4746
|
if (ig && ig.ignores(rel)) continue;
|
|
4689
4747
|
if (e.isDirectory()) {
|
|
@@ -4702,7 +4760,7 @@ function loadGitignore2(rootDir) {
|
|
|
4702
4760
|
if (!ignoreMod) return null;
|
|
4703
4761
|
const ig = (ignoreMod.default || ignoreMod)();
|
|
4704
4762
|
try {
|
|
4705
|
-
const content = readText(
|
|
4763
|
+
const content = readText(join6(rootDir, ".gitignore"));
|
|
4706
4764
|
ig.add(content);
|
|
4707
4765
|
} catch {
|
|
4708
4766
|
}
|
|
@@ -4808,7 +4866,7 @@ function errorResult(code, message, recovery, { large = false, extra = null } =
|
|
|
4808
4866
|
}
|
|
4809
4867
|
|
|
4810
4868
|
// server.mjs
|
|
4811
|
-
var version = true ? "1.
|
|
4869
|
+
var version = true ? "1.24.1" : (await null).createRequire(import.meta.url)("./package.json").version;
|
|
4812
4870
|
var STATUS_ENUM = z2.enum(["OK", "ERROR", "AUTO_REBASED", "CONFLICT", "STALE", "INVALID", "NO_CHANGES", "CHANGED", "UNSUPPORTED"]);
|
|
4813
4871
|
var ERROR_SHAPE = z2.object({ code: z2.string(), message: z2.string(), recovery: z2.string() }).optional();
|
|
4814
4872
|
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.24.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.",
|