@levnikolaevich/hex-line-mcp 1.21.1 → 1.23.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/dist/hook.mjs +5 -92
- package/dist/server.mjs +107 -100
- package/package.json +1 -1
package/dist/hook.mjs
CHANGED
|
@@ -54,7 +54,7 @@ function normalizeOutput(text, opts = {}) {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
// hook.mjs
|
|
57
|
-
import { readFileSync, writeSync
|
|
57
|
+
import { readFileSync, writeSync } from "node:fs";
|
|
58
58
|
import { resolve } from "node:path";
|
|
59
59
|
import { homedir } from "node:os";
|
|
60
60
|
|
|
@@ -361,53 +361,6 @@ function extractBashText(response) {
|
|
|
361
361
|
}
|
|
362
362
|
return "";
|
|
363
363
|
}
|
|
364
|
-
var ERROR_RECOVERY_DIR = ".hex-skills/logs/error_recovery";
|
|
365
|
-
var ERROR_RECOVERY_MAX_FILES = 20;
|
|
366
|
-
var ERROR_RECOVERY_MAX_BYTES = 1024 * 1024;
|
|
367
|
-
function isBashFailure(response) {
|
|
368
|
-
if (!response || typeof response !== "object") return false;
|
|
369
|
-
if (response.is_error === true) return true;
|
|
370
|
-
if (response.interrupted === true) return true;
|
|
371
|
-
if (typeof response.exit_code === "number" && response.exit_code !== 0) return true;
|
|
372
|
-
return false;
|
|
373
|
-
}
|
|
374
|
-
function saveErrorArtifact(rawText, commandType, command, cwd) {
|
|
375
|
-
try {
|
|
376
|
-
const dir = resolve(cwd, ERROR_RECOVERY_DIR);
|
|
377
|
-
mkdirSync(dir, { recursive: true });
|
|
378
|
-
try {
|
|
379
|
-
const entries = readdirSync(dir).filter((name) => name.endsWith(".log")).map((name) => {
|
|
380
|
-
try {
|
|
381
|
-
return { name, mtime: statSync(resolve(dir, name)).mtimeMs };
|
|
382
|
-
} catch {
|
|
383
|
-
return null;
|
|
384
|
-
}
|
|
385
|
-
}).filter((e) => e !== null).sort((a, b) => b.mtime - a.mtime);
|
|
386
|
-
for (const entry of entries.slice(ERROR_RECOVERY_MAX_FILES - 1)) {
|
|
387
|
-
try {
|
|
388
|
-
unlinkSync(resolve(dir, entry.name));
|
|
389
|
-
} catch {
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
} catch {
|
|
393
|
-
}
|
|
394
|
-
let content = `# command: ${String(command).slice(0, 500)}
|
|
395
|
-
# type: ${commandType}
|
|
396
|
-
# timestamp: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
397
|
-
|
|
398
|
-
${rawText}`;
|
|
399
|
-
if (Buffer.byteLength(content, "utf-8") > ERROR_RECOVERY_MAX_BYTES) {
|
|
400
|
-
content = content.slice(0, ERROR_RECOVERY_MAX_BYTES) + "\n[TRUNCATED at 1 MB]";
|
|
401
|
-
}
|
|
402
|
-
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
403
|
-
const fileName = `${ts}_${commandType}.log`;
|
|
404
|
-
const filePath = resolve(dir, fileName);
|
|
405
|
-
writeFileSync(filePath, content, "utf-8");
|
|
406
|
-
return `${ERROR_RECOVERY_DIR}/${fileName}`;
|
|
407
|
-
} catch {
|
|
408
|
-
return null;
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
364
|
var _hexLineDisabled = null;
|
|
412
365
|
function isHexLineDisabled(configPath) {
|
|
413
366
|
if (_hexLineDisabled !== null) return _hexLineDisabled;
|
|
@@ -600,18 +553,10 @@ function handlePostToolUse(data) {
|
|
|
600
553
|
if (!rawText) {
|
|
601
554
|
process.exit(0);
|
|
602
555
|
}
|
|
603
|
-
const failure = isBashFailure(data.tool_response);
|
|
604
556
|
const type = detectCommandType(command);
|
|
605
|
-
let recoveryPath = null;
|
|
606
|
-
if (failure) {
|
|
607
|
-
recoveryPath = saveErrorArtifact(rawText, type, command, process.cwd());
|
|
608
|
-
}
|
|
609
557
|
const lines = rawText.split("\n");
|
|
610
558
|
const originalCount = lines.length;
|
|
611
559
|
if (originalCount < HOOK_OUTPUT_POLICY.lineThreshold) {
|
|
612
|
-
if (recoveryPath) {
|
|
613
|
-
safeExit(2, `Full output preserved at: ${recoveryPath}`, 2);
|
|
614
|
-
}
|
|
615
560
|
process.exit(0);
|
|
616
561
|
}
|
|
617
562
|
const filtered = normalizeOutput(lines.join("\n"), {
|
|
@@ -620,7 +565,7 @@ function handlePostToolUse(data) {
|
|
|
620
565
|
});
|
|
621
566
|
const filteredCount = filtered.split("\n").length;
|
|
622
567
|
const header = `RTK FILTERED: ${type} (${originalCount} lines -> ${filteredCount} lines)`;
|
|
623
|
-
const
|
|
568
|
+
const output = [
|
|
624
569
|
"=".repeat(50),
|
|
625
570
|
header,
|
|
626
571
|
"=".repeat(50),
|
|
@@ -628,42 +573,11 @@ function handlePostToolUse(data) {
|
|
|
628
573
|
filtered,
|
|
629
574
|
"",
|
|
630
575
|
"-".repeat(50),
|
|
631
|
-
`Original: ${originalCount} lines | Filtered: ${filteredCount} lines
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
outputParts.push(`Full output preserved at: ${recoveryPath}`);
|
|
635
|
-
}
|
|
636
|
-
outputParts.push("=".repeat(50));
|
|
637
|
-
const output = outputParts.join("\n");
|
|
576
|
+
`Original: ${originalCount} lines | Filtered: ${filteredCount} lines`,
|
|
577
|
+
"=".repeat(50)
|
|
578
|
+
].join("\n");
|
|
638
579
|
safeExit(2, output, 2);
|
|
639
580
|
}
|
|
640
|
-
function handlePostToolUseFailure(data) {
|
|
641
|
-
const toolName = data.tool_name || "";
|
|
642
|
-
if (toolName !== "Bash") {
|
|
643
|
-
process.exit(0);
|
|
644
|
-
}
|
|
645
|
-
const toolInput = data.tool_input || {};
|
|
646
|
-
const command = toolInput.command || "";
|
|
647
|
-
const error = typeof data.error === "string" ? data.error : "";
|
|
648
|
-
const isInterrupt = data.is_interrupt === true;
|
|
649
|
-
if (!command && !error) {
|
|
650
|
-
process.exit(0);
|
|
651
|
-
}
|
|
652
|
-
const type = detectCommandType(command);
|
|
653
|
-
const body = `error: ${error}
|
|
654
|
-
is_interrupt: ${isInterrupt}`;
|
|
655
|
-
const recoveryPath = saveErrorArtifact(body, type, command, process.cwd());
|
|
656
|
-
if (!recoveryPath) {
|
|
657
|
-
process.exit(0);
|
|
658
|
-
}
|
|
659
|
-
const output = {
|
|
660
|
-
hookSpecificOutput: {
|
|
661
|
-
hookEventName: "PostToolUseFailure",
|
|
662
|
-
additionalContext: `Failure metadata logged at: ${recoveryPath}`
|
|
663
|
-
}
|
|
664
|
-
};
|
|
665
|
-
safeExit(1, JSON.stringify(output), 0);
|
|
666
|
-
}
|
|
667
581
|
function handleSessionStart() {
|
|
668
582
|
const settingsFiles = [
|
|
669
583
|
resolve(process.cwd(), ".claude/settings.local.json"),
|
|
@@ -721,7 +635,6 @@ if (_norm(process.argv[1]) === _norm(fileURLToPath(import.meta.url))) {
|
|
|
721
635
|
if (event === "SessionStart") handleSessionStart();
|
|
722
636
|
else if (event === "PreToolUse") handlePreToolUse(data);
|
|
723
637
|
else if (event === "PostToolUse") handlePostToolUse(data);
|
|
724
|
-
else if (event === "PostToolUseFailure") handlePostToolUseFailure(data);
|
|
725
638
|
else if (event === "PermissionDenied") handlePermissionDenied(data);
|
|
726
639
|
else process.exit(0);
|
|
727
640
|
} catch {
|
package/dist/server.mjs
CHANGED
|
@@ -202,6 +202,13 @@ function readText(filePath) {
|
|
|
202
202
|
|
|
203
203
|
// lib/security.mjs
|
|
204
204
|
var MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
205
|
+
var EXTERNAL_SAFE_FOLDERS = [
|
|
206
|
+
".hex-skills/",
|
|
207
|
+
".claude/"
|
|
208
|
+
];
|
|
209
|
+
function isInExternalSafeFolder(absPath) {
|
|
210
|
+
return EXTERNAL_SAFE_FOLDERS.some((folder) => absPath.includes(folder));
|
|
211
|
+
}
|
|
205
212
|
function normalizePath(p) {
|
|
206
213
|
if (process.platform === "win32") {
|
|
207
214
|
if (p === "/tmp" || p.startsWith("/tmp/")) {
|
|
@@ -234,6 +241,7 @@ function assertProjectScopedPath(filePath, { allowExternal = false } = {}) {
|
|
|
234
241
|
if (!filePath) throw new Error("Empty file path");
|
|
235
242
|
const abs = resolveInputPath(filePath);
|
|
236
243
|
if (allowExternal) return abs;
|
|
244
|
+
if (isInExternalSafeFolder(abs)) return abs;
|
|
237
245
|
const projectRoot = resolve(process.cwd()).replace(/\\/g, "/");
|
|
238
246
|
if (isWithinRoot(projectRoot, abs)) return abs;
|
|
239
247
|
throw new Error(
|
|
@@ -392,6 +400,57 @@ function getGraphDB(filePath, { allowStale = false } = {}) {
|
|
|
392
400
|
return null;
|
|
393
401
|
}
|
|
394
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
|
+
}
|
|
395
454
|
function validateContract(db) {
|
|
396
455
|
try {
|
|
397
456
|
for (const viewName of REQUIRED_VIEWS) {
|
|
@@ -1259,7 +1318,7 @@ function serializeSearchBlock(block, opts = {}) {
|
|
|
1259
1318
|
}
|
|
1260
1319
|
if (block.meta.summary) lines.push(`summary: ${block.meta.summary}`);
|
|
1261
1320
|
lines.push(...renderMetaLines(Object.fromEntries(
|
|
1262
|
-
Object.entries(block.meta).filter(([key]) => key !== "matchLines" && key !== "summary")
|
|
1321
|
+
Object.entries(block.meta).filter(([key]) => key !== "matchLines" && key !== "summary" && key !== "graphScore")
|
|
1263
1322
|
)));
|
|
1264
1323
|
lines.push(...block.entries.map((entry) => serializeSearchEntry(entry, opts)));
|
|
1265
1324
|
lines.push(`checksum: ${block.checksum}`);
|
|
@@ -1893,8 +1952,6 @@ retry_edit: ${entry.retry_edit}`;
|
|
|
1893
1952
|
if (entry.remapped_refs) msg += `
|
|
1894
1953
|
remapped_refs: ${entry.remapped_refs}`;
|
|
1895
1954
|
msg += `
|
|
1896
|
-
summary: ${entry.summary}`;
|
|
1897
|
-
msg += `
|
|
1898
1955
|
snippet: ${entry.snippet.range}
|
|
1899
1956
|
${entry.snippet.text}`;
|
|
1900
1957
|
return msg;
|
|
@@ -1929,8 +1986,7 @@ function buildSingleConflictReport({
|
|
|
1929
1986
|
function renderSingleConflictReport(report) {
|
|
1930
1987
|
let msg = `status: ${report.status}
|
|
1931
1988
|
reason: ${report.reason}
|
|
1932
|
-
revision: ${report.revision}
|
|
1933
|
-
file: ${report.file}`;
|
|
1989
|
+
revision: ${report.revision}`;
|
|
1934
1990
|
if (report.path) msg += `
|
|
1935
1991
|
path: ${report.path}`;
|
|
1936
1992
|
if (report.changed_ranges) msg += `
|
|
@@ -1950,8 +2006,6 @@ retry_plan: ${report.retry_plan}`;
|
|
|
1950
2006
|
if (report.remapped_refs) msg += `
|
|
1951
2007
|
remapped_refs: ${report.remapped_refs}`;
|
|
1952
2008
|
msg += `
|
|
1953
|
-
summary: ${report.summary}`;
|
|
1954
|
-
msg += `
|
|
1955
2009
|
snippet: ${report.snippet.range}
|
|
1956
2010
|
${report.snippet.text}`;
|
|
1957
2011
|
return msg;
|
|
@@ -2005,12 +2059,11 @@ function buildSuggestedReadCall(filePath, recoveryRanges) {
|
|
|
2005
2059
|
}
|
|
2006
2060
|
});
|
|
2007
2061
|
}
|
|
2008
|
-
function buildRetryPlan(filePath, {
|
|
2062
|
+
function buildRetryPlan(filePath, { retryEdit = null, retryEdits = null } = {}) {
|
|
2009
2063
|
if (!filePath) return null;
|
|
2010
2064
|
const steps = [];
|
|
2011
2065
|
const parsedRetryEdits = Array.isArray(retryEdits) ? retryEdits.map(parseRetryEdit).filter(Boolean) : [];
|
|
2012
2066
|
const parsedRetryEdit = parsedRetryEdits.length === 0 ? parseRetryEdit(retryEdit) : null;
|
|
2013
|
-
const dedupedRanges = dedupeRanges(recoveryRanges);
|
|
2014
2067
|
if (parsedRetryEdits.length > 0) {
|
|
2015
2068
|
steps.push({
|
|
2016
2069
|
tool: "mcp__hex-line__edit_file",
|
|
@@ -2029,14 +2082,6 @@ function buildRetryPlan(filePath, { recoveryRanges = [], retryEdit = null, retry
|
|
|
2029
2082
|
conflict_policy: "conservative"
|
|
2030
2083
|
}
|
|
2031
2084
|
});
|
|
2032
|
-
} else if (dedupedRanges.length > 0) {
|
|
2033
|
-
steps.push({
|
|
2034
|
-
tool: "mcp__hex-line__read_file",
|
|
2035
|
-
arguments: {
|
|
2036
|
-
path: filePath,
|
|
2037
|
-
ranges: dedupedRanges
|
|
2038
|
-
}
|
|
2039
|
-
});
|
|
2040
2085
|
}
|
|
2041
2086
|
if (steps.length === 0) return null;
|
|
2042
2087
|
return JSON.stringify({ steps });
|
|
@@ -2119,7 +2164,7 @@ function buildRetryEdit(edit, lines, options = {}) {
|
|
|
2119
2164
|
function buildBatchConflictMessage({
|
|
2120
2165
|
filePath,
|
|
2121
2166
|
revision,
|
|
2122
|
-
fileChecksum,
|
|
2167
|
+
fileChecksum: _fileChecksum,
|
|
2123
2168
|
lines,
|
|
2124
2169
|
changedRanges,
|
|
2125
2170
|
conflicts
|
|
@@ -2147,7 +2192,6 @@ function buildBatchConflictMessage({
|
|
|
2147
2192
|
let msg = `status: ${STATUS.CONFLICT}
|
|
2148
2193
|
reason: ${REASON.BATCH_CONFLICT}
|
|
2149
2194
|
revision: ${revision}
|
|
2150
|
-
file: ${fileChecksum}
|
|
2151
2195
|
edit_conflicts: ${conflicts.length}`;
|
|
2152
2196
|
if (filePath) msg += `
|
|
2153
2197
|
path: ${filePath}`;
|
|
@@ -2629,8 +2673,7 @@ function editFile(filePath, edits, opts = {}) {
|
|
|
2629
2673
|
const nextAction = deriveNextAction({ retryEdit, suggestedReadCall });
|
|
2630
2674
|
let msg2 = `status: ${STATUS.CONFLICT}
|
|
2631
2675
|
reason: ${reason}
|
|
2632
|
-
revision: ${currentSnapshot.revision}
|
|
2633
|
-
file: ${currentSnapshot.fileChecksum}`;
|
|
2676
|
+
revision: ${currentSnapshot.revision}`;
|
|
2634
2677
|
if (filePath) msg2 += `
|
|
2635
2678
|
path: ${filePath}`;
|
|
2636
2679
|
if (staleRevision && hasBaseSnapshot) msg2 += `
|
|
@@ -2648,8 +2691,6 @@ suggested_read_call: ${suggestedReadCall}`;
|
|
|
2648
2691
|
if (retryPlan) msg2 += `
|
|
2649
2692
|
retry_plan: ${retryPlan}`;
|
|
2650
2693
|
msg2 += `
|
|
2651
|
-
summary: ${summarizeConflictDetails(details)}`;
|
|
2652
|
-
msg2 += `
|
|
2653
2694
|
snippet: ${snip.start}-${snip.end}
|
|
2654
2695
|
${snip.text}`;
|
|
2655
2696
|
return new Error(msg2);
|
|
@@ -2804,21 +2845,16 @@ ${snip.text}`;
|
|
|
2804
2845
|
if (opts.dryRun) {
|
|
2805
2846
|
let msg2 = `status: ${autoRebased ? STATUS.AUTO_REBASED : STATUS.OK}
|
|
2806
2847
|
reason: ${REASON.DRY_RUN_PREVIEW}
|
|
2807
|
-
revision: ${currentSnapshot.revision}
|
|
2808
|
-
file: ${currentSnapshot.fileChecksum}`;
|
|
2848
|
+
revision: ${currentSnapshot.revision}`;
|
|
2809
2849
|
if (staleRevision && hasBaseSnapshot) msg2 += `
|
|
2810
2850
|
changed_ranges: ${describeChangedRanges(changedRanges)}`;
|
|
2811
2851
|
msg2 += `
|
|
2812
2852
|
summary: lines_changed=${changedSpan} diff_entries=${diffEntryCount} lines_after=${lines.length}`;
|
|
2813
2853
|
msg2 += `
|
|
2814
|
-
next_action: ${ACTION.KEEP_USING}`;
|
|
2815
|
-
msg2 += `
|
|
2816
2854
|
payload_sections: ${payloadSections(displayDiff ? ["diff"] : [])}`;
|
|
2817
|
-
|
|
2818
|
-
msg2 +=
|
|
2819
|
-
|
|
2820
|
-
msg2 += "\nclone_warning_count: 0";
|
|
2821
|
-
msg2 += "\nprovenance_summary: edit_protocol=snapshot+anchors graph=unavailable";
|
|
2855
|
+
const hint = graphUnavailableHint(real);
|
|
2856
|
+
if (hint.length > 0) msg2 += `
|
|
2857
|
+
${hint.join("\n")}`;
|
|
2822
2858
|
msg2 += `
|
|
2823
2859
|
Dry run: ${filePath} would change (${lines.length} lines)`;
|
|
2824
2860
|
if (displayDiff) msg2 += `
|
|
@@ -2834,23 +2870,23 @@ ${displayDiff}
|
|
|
2834
2870
|
const nextSnapshot = rememberSnapshot(real, content, { mtimeMs: nextStat.mtimeMs, size: nextStat.size });
|
|
2835
2871
|
let msg = `status: ${autoRebased ? STATUS.AUTO_REBASED : STATUS.OK}
|
|
2836
2872
|
reason: ${autoRebased ? REASON.EDIT_AUTO_REBASED : REASON.EDIT_APPLIED}
|
|
2837
|
-
revision: ${nextSnapshot.revision}
|
|
2838
|
-
file: ${nextSnapshot.fileChecksum}`;
|
|
2873
|
+
revision: ${nextSnapshot.revision}`;
|
|
2839
2874
|
if (autoRebased && staleRevision && hasBaseSnapshot) {
|
|
2840
2875
|
msg += `
|
|
2841
2876
|
changed_ranges: ${describeChangedRanges(changedRanges)}`;
|
|
2842
|
-
|
|
2843
|
-
msg += `
|
|
2877
|
+
msg += `
|
|
2844
2878
|
next_action: ${ACTION.KEEP_USING}`;
|
|
2879
|
+
}
|
|
2845
2880
|
if (remaps.length > 0) {
|
|
2846
2881
|
msg += `
|
|
2847
2882
|
remapped_refs:
|
|
2848
2883
|
${remaps.map(({ from, to }) => `${from} -> ${to}`).join("\n")}`;
|
|
2849
2884
|
}
|
|
2850
2885
|
let hasPostEditBlock = false;
|
|
2851
|
-
let graphEnrichment = "unavailable";
|
|
2852
2886
|
let semanticImpacts = [];
|
|
2853
2887
|
let cloneWarnings = [];
|
|
2888
|
+
let graphDbAvailable = false;
|
|
2889
|
+
let graphFresh = true;
|
|
2854
2890
|
msg += `
|
|
2855
2891
|
Updated ${filePath} (${lines.length} lines)`;
|
|
2856
2892
|
if (fullDiff && minLine <= maxLine) {
|
|
@@ -2877,7 +2913,8 @@ ${serializeReadBlock(block)}`;
|
|
|
2877
2913
|
const db = getGraphDB(real, { allowStale: true });
|
|
2878
2914
|
const relFile = db ? getRelativePath(real) : null;
|
|
2879
2915
|
if (db && relFile && fullDiff && minLine <= maxLine) {
|
|
2880
|
-
|
|
2916
|
+
graphDbAvailable = true;
|
|
2917
|
+
graphFresh = ensureGraphFreshForFile(db, real);
|
|
2881
2918
|
semanticImpacts = semanticImpact(db, relFile, minLine, maxLine);
|
|
2882
2919
|
if (semanticImpacts.length > 0) {
|
|
2883
2920
|
const sections = semanticImpacts.map((impact) => {
|
|
@@ -2910,7 +2947,7 @@ ${sections.join("\n")}`;
|
|
|
2910
2947
|
}
|
|
2911
2948
|
cloneWarnings = cloneWarning(db, relFile, minLine, maxLine);
|
|
2912
2949
|
if (cloneWarnings.length > 0) {
|
|
2913
|
-
const list = cloneWarnings.map((c) => `${c.file}:${c.line}`).join(", ");
|
|
2950
|
+
const list = cloneWarnings.map((c) => `${c.file}:${c.line}${c.cloneType ? ` (${c.cloneType})` : ""}`).join(", ");
|
|
2914
2951
|
msg += `
|
|
2915
2952
|
|
|
2916
2953
|
\u26A0 ${cloneWarnings.length} clone(s): ${list}`;
|
|
@@ -2918,21 +2955,18 @@ ${sections.join("\n")}`;
|
|
|
2918
2955
|
}
|
|
2919
2956
|
} catch {
|
|
2920
2957
|
}
|
|
2958
|
+
if (!graphFresh) msg += "\ngraph_fresh: stale";
|
|
2921
2959
|
const payloadKinds = [];
|
|
2922
2960
|
if (hasPostEditBlock) payloadKinds.push("post_edit");
|
|
2923
2961
|
if (semanticImpacts.length > 0) payloadKinds.push("semantic_impact");
|
|
2924
2962
|
if (cloneWarnings.length > 0) payloadKinds.push("clone_warning");
|
|
2925
2963
|
if (displayDiff) payloadKinds.push("diff");
|
|
2926
|
-
const
|
|
2927
|
-
const summaryLines = [
|
|
2964
|
+
const summaryLineParts = [
|
|
2928
2965
|
`summary: lines_changed=${changedSpan} diff_entries=${diffEntryCount} lines_after=${lines.length}${editContext.corrections.length > 0 ? ` boundary_echo_stripped=${editContext.corrections.length}` : ``}`,
|
|
2929
|
-
`payload_sections: ${payloadSections(payloadKinds)}
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
`clone_warning_count: ${cloneWarnings.length}`,
|
|
2934
|
-
`provenance_summary: edit_protocol=snapshot+anchors graph=${graphEnrichment === "available" ? "hex_line_contract" : "unavailable"}`
|
|
2935
|
-
].join("\n");
|
|
2966
|
+
`payload_sections: ${payloadSections(payloadKinds)}`
|
|
2967
|
+
];
|
|
2968
|
+
if (!graphDbAvailable) summaryLineParts.push(...graphUnavailableHint(real));
|
|
2969
|
+
const summaryLines = summaryLineParts.join("\n");
|
|
2936
2970
|
msg = msg.replace(`
|
|
2937
2971
|
Updated ${filePath} (${lines.length} lines)`, `
|
|
2938
2972
|
${summaryLines}
|
|
@@ -4050,10 +4084,6 @@ var CLAUDE_HOOKS = {
|
|
|
4050
4084
|
PostToolUse: {
|
|
4051
4085
|
matcher: "Bash",
|
|
4052
4086
|
hooks: [{ type: "command", command: HOOK_COMMAND, timeout: 10 }]
|
|
4053
|
-
},
|
|
4054
|
-
PostToolUseFailure: {
|
|
4055
|
-
matcher: "Bash",
|
|
4056
|
-
hooks: [{ type: "command", command: HOOK_COMMAND, timeout: 5 }]
|
|
4057
4087
|
}
|
|
4058
4088
|
};
|
|
4059
4089
|
function readJson(filePath) {
|
|
@@ -4114,6 +4144,15 @@ function writeHooksToFile(settingsPath) {
|
|
|
4114
4144
|
changed = true;
|
|
4115
4145
|
}
|
|
4116
4146
|
}
|
|
4147
|
+
for (const event of Object.keys(config.hooks)) {
|
|
4148
|
+
if (event in CLAUDE_HOOKS) continue;
|
|
4149
|
+
if (!Array.isArray(config.hooks[event])) continue;
|
|
4150
|
+
const idx = findEntryByCommand(config.hooks[event]);
|
|
4151
|
+
if (idx < 0) continue;
|
|
4152
|
+
config.hooks[event].splice(idx, 1);
|
|
4153
|
+
if (config.hooks[event].length === 0) delete config.hooks[event];
|
|
4154
|
+
changed = true;
|
|
4155
|
+
}
|
|
4117
4156
|
if (changed) writeJson(settingsPath, config);
|
|
4118
4157
|
return changed;
|
|
4119
4158
|
}
|
|
@@ -4433,12 +4472,6 @@ async function semanticGitDiff(targetPath, { baseRef = "HEAD", headRef = null }
|
|
|
4433
4472
|
}
|
|
4434
4473
|
|
|
4435
4474
|
// lib/changes.mjs
|
|
4436
|
-
function graphEnrichmentState(db) {
|
|
4437
|
-
return db ? "available" : "unavailable";
|
|
4438
|
-
}
|
|
4439
|
-
function provenanceSummary(semanticDiffState, db) {
|
|
4440
|
-
return `semantic_diff=${semanticDiffState} graph=${db ? "hex_line_contract" : "unavailable"}`;
|
|
4441
|
-
}
|
|
4442
4475
|
function payloadSections2(sections) {
|
|
4443
4476
|
return sections.length > 0 ? sections.join(",") : "summary_only";
|
|
4444
4477
|
}
|
|
@@ -4483,7 +4516,7 @@ async function fileChanges(filePath, compareAgainst = "HEAD") {
|
|
|
4483
4516
|
if (statSync11(real).isDirectory()) {
|
|
4484
4517
|
const db2 = getGraphDB(join6(real, "__hex-line_probe__"));
|
|
4485
4518
|
const diff2 = await semanticGitDiff(real, { baseRef: compareAgainst });
|
|
4486
|
-
const
|
|
4519
|
+
const graphHint2 = graphUnavailableHint(join6(real, "__hex-line_probe__"));
|
|
4487
4520
|
if (diff2.summary.changed_file_count === 0) {
|
|
4488
4521
|
return [
|
|
4489
4522
|
"status: NO_CHANGES",
|
|
@@ -4492,16 +4525,9 @@ async function fileChanges(filePath, compareAgainst = "HEAD") {
|
|
|
4492
4525
|
`compare_against: ${compareAgainst}`,
|
|
4493
4526
|
"scope: directory",
|
|
4494
4527
|
"summary: changed_files=0",
|
|
4495
|
-
|
|
4496
|
-
`graph_enrichment: ${graphEnrichment2}`,
|
|
4497
|
-
"risk_summary_count: 0",
|
|
4498
|
-
"removed_api_warning_count: 0",
|
|
4499
|
-
`payload_sections: ${payloadSections2([])}`,
|
|
4500
|
-
`provenance_summary: ${provenanceSummary("clean", db2)}`
|
|
4528
|
+
...graphHint2
|
|
4501
4529
|
].join("\n");
|
|
4502
4530
|
}
|
|
4503
|
-
const supportedCount = diff2.changed_files.filter((entry) => entry.semantic_supported).length;
|
|
4504
|
-
const semanticDiffState = supportedCount === 0 ? "unsupported" : supportedCount === diff2.changed_files.length ? "semantic" : "mixed";
|
|
4505
4531
|
let emittedRiskCount = 0;
|
|
4506
4532
|
let emittedRemovedApiWarnings = 0;
|
|
4507
4533
|
const sectionKinds2 = ["files"];
|
|
@@ -4513,7 +4539,7 @@ async function fileChanges(filePath, compareAgainst = "HEAD") {
|
|
|
4513
4539
|
"scope: directory",
|
|
4514
4540
|
`summary: changed_files=${diff2.summary.changed_file_count}`,
|
|
4515
4541
|
`next_action: ${ACTION.INSPECT_FILE}`,
|
|
4516
|
-
|
|
4542
|
+
...graphHint2,
|
|
4517
4543
|
""
|
|
4518
4544
|
];
|
|
4519
4545
|
for (const file2 of diff2.changed_files) {
|
|
@@ -4535,20 +4561,15 @@ async function fileChanges(filePath, compareAgainst = "HEAD") {
|
|
|
4535
4561
|
}
|
|
4536
4562
|
if (emittedRiskCount > 0) sectionKinds2.push("risk_summary");
|
|
4537
4563
|
if (emittedRemovedApiWarnings > 0) sectionKinds2.push("removed_api_warning");
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
`risk_summary_count: ${emittedRiskCount}`,
|
|
4542
|
-
`removed_api_warning_count: ${emittedRemovedApiWarnings}`,
|
|
4543
|
-
`payload_sections: ${payloadSections2(sectionKinds2)}`,
|
|
4544
|
-
`provenance_summary: ${provenanceSummary(semanticDiffState, db2)}`
|
|
4545
|
-
);
|
|
4564
|
+
const spliceLines = [];
|
|
4565
|
+
if (sectionKinds2.length > 0) spliceLines.push(`payload_sections: ${payloadSections2(sectionKinds2)}`);
|
|
4566
|
+
sections.splice(7 + graphHint2.length, 0, ...spliceLines);
|
|
4546
4567
|
return sections.join("\n");
|
|
4547
4568
|
}
|
|
4548
4569
|
const db = getGraphDB(real);
|
|
4549
4570
|
const diff = await semanticGitDiff(real, { baseRef: compareAgainst });
|
|
4550
4571
|
const file = diff.changed_files[0];
|
|
4551
|
-
const
|
|
4572
|
+
const graphHint = graphUnavailableHint(real);
|
|
4552
4573
|
if (!file) {
|
|
4553
4574
|
return [
|
|
4554
4575
|
"status: NO_CHANGES",
|
|
@@ -4557,12 +4578,7 @@ async function fileChanges(filePath, compareAgainst = "HEAD") {
|
|
|
4557
4578
|
`compare_against: ${compareAgainst}`,
|
|
4558
4579
|
"scope: file",
|
|
4559
4580
|
"summary: added=0 removed=0 modified=0",
|
|
4560
|
-
|
|
4561
|
-
`graph_enrichment: ${graphEnrichment}`,
|
|
4562
|
-
"risk_summary_count: 0",
|
|
4563
|
-
"removed_api_warning_count: 0",
|
|
4564
|
-
`payload_sections: ${payloadSections2([])}`,
|
|
4565
|
-
`provenance_summary: ${provenanceSummary("clean", db)}`
|
|
4581
|
+
...graphHint
|
|
4566
4582
|
].join("\n");
|
|
4567
4583
|
}
|
|
4568
4584
|
if (!file.semantic_supported) {
|
|
@@ -4574,11 +4590,7 @@ async function fileChanges(filePath, compareAgainst = "HEAD") {
|
|
|
4574
4590
|
"scope: file",
|
|
4575
4591
|
`summary: semantic diff unavailable for ${file.extension} files`,
|
|
4576
4592
|
`next_action: ${ACTION.INSPECT_RAW_DIFF}`,
|
|
4577
|
-
|
|
4578
|
-
"risk_summary_count: 0",
|
|
4579
|
-
"removed_api_warning_count: 0",
|
|
4580
|
-
`payload_sections: ${payloadSections2([])}`,
|
|
4581
|
-
`provenance_summary: ${provenanceSummary("unsupported", db)}`
|
|
4593
|
+
...graphHint
|
|
4582
4594
|
].join("\n");
|
|
4583
4595
|
}
|
|
4584
4596
|
const relFile = getRelativePath(real) || file.path?.replace(/\\/g, "/");
|
|
@@ -4592,9 +4604,7 @@ async function fileChanges(filePath, compareAgainst = "HEAD") {
|
|
|
4592
4604
|
`compare_against: ${compareAgainst}`,
|
|
4593
4605
|
"scope: file",
|
|
4594
4606
|
`summary: ${symbolCountSummary(file)}`,
|
|
4595
|
-
|
|
4596
|
-
`risk_summary_count: ${riskLines.length}`,
|
|
4597
|
-
`removed_api_warning_count: ${removedApiWarnings.length}`
|
|
4607
|
+
...graphHint
|
|
4598
4608
|
];
|
|
4599
4609
|
if (file.added_symbols.length) {
|
|
4600
4610
|
sectionKinds.push("added");
|
|
@@ -4628,12 +4638,10 @@ async function fileChanges(filePath, compareAgainst = "HEAD") {
|
|
|
4628
4638
|
}
|
|
4629
4639
|
if (riskLines.length > 0) sectionKinds.push("risk_summary");
|
|
4630
4640
|
if (removedApiWarnings.length > 0) sectionKinds.push("removed_api_warning");
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
0,
|
|
4634
|
-
|
|
4635
|
-
`provenance_summary: ${provenanceSummary("semantic", db)}`
|
|
4636
|
-
);
|
|
4641
|
+
if (sectionKinds.length > 0) {
|
|
4642
|
+
const insertIdx = 6 + graphHint.length;
|
|
4643
|
+
parts.splice(insertIdx, 0, `payload_sections: ${payloadSections2(sectionKinds)}`);
|
|
4644
|
+
}
|
|
4637
4645
|
if (riskLines.length || removedApiWarnings.length) {
|
|
4638
4646
|
parts.push("");
|
|
4639
4647
|
parts.push("risk_summary:");
|
|
@@ -4777,8 +4785,7 @@ function result(structured, { large = false } = {}) {
|
|
|
4777
4785
|
function errorResult(code, message, recovery, { large = false, extra = null } = {}) {
|
|
4778
4786
|
const payload = {
|
|
4779
4787
|
status: "ERROR",
|
|
4780
|
-
error: { code, message, recovery }
|
|
4781
|
-
summary: message
|
|
4788
|
+
error: { code, message, recovery }
|
|
4782
4789
|
};
|
|
4783
4790
|
if (extra && typeof extra === "object") {
|
|
4784
4791
|
Object.assign(payload, extra);
|
|
@@ -4787,7 +4794,7 @@ function errorResult(code, message, recovery, { large = false, extra = null } =
|
|
|
4787
4794
|
}
|
|
4788
4795
|
|
|
4789
4796
|
// server.mjs
|
|
4790
|
-
var version = true ? "1.
|
|
4797
|
+
var version = true ? "1.23.0" : (await null).createRequire(import.meta.url)("./package.json").version;
|
|
4791
4798
|
var STATUS_ENUM = z2.enum(["OK", "ERROR", "AUTO_REBASED", "CONFLICT", "STALE", "INVALID", "NO_CHANGES", "CHANGED", "UNSUPPORTED"]);
|
|
4792
4799
|
var ERROR_SHAPE = z2.object({ code: z2.string(), message: z2.string(), recovery: z2.string() }).optional();
|
|
4793
4800
|
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.0",
|
|
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.",
|