@levnikolaevich/hex-line-mcp 1.3.3 → 1.3.4
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 +428 -0
- package/dist/server.mjs +6615 -0
- package/package.json +6 -8
- package/benchmark/atomic.mjs +0 -502
- package/benchmark/graph.mjs +0 -80
- package/benchmark/index.mjs +0 -144
- package/benchmark/workflows.mjs +0 -350
- package/hook.mjs +0 -466
- package/lib/benchmark-helpers.mjs +0 -541
- package/lib/bulk-replace.mjs +0 -65
- package/lib/changes.mjs +0 -176
- package/lib/coerce.mjs +0 -1
- package/lib/edit.mjs +0 -534
- package/lib/format.mjs +0 -138
- package/lib/graph-enrich.mjs +0 -226
- package/lib/hash.mjs +0 -1
- package/lib/info.mjs +0 -91
- package/lib/normalize.mjs +0 -1
- package/lib/outline.mjs +0 -145
- package/lib/read.mjs +0 -138
- package/lib/revisions.mjs +0 -238
- package/lib/search.mjs +0 -268
- package/lib/security.mjs +0 -112
- package/lib/setup.mjs +0 -275
- package/lib/tree.mjs +0 -236
- package/lib/update-check.mjs +0 -1
- package/lib/verify.mjs +0 -70
- package/server.mjs +0 -375
package/benchmark/index.mjs
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Hex-line workflow benchmark with optional diagnostics.
|
|
4
|
-
*
|
|
5
|
-
* Public benchmark mode reports only comparative multi-step workflows.
|
|
6
|
-
* Synthetic tool-level scenarios are available behind --diagnostics and
|
|
7
|
-
* should not be treated as headline benchmark results.
|
|
8
|
-
*
|
|
9
|
-
* Usage:
|
|
10
|
-
* node mcp/hex-line-mcp/benchmark/index.mjs [--repo /path/to/repo]
|
|
11
|
-
* node mcp/hex-line-mcp/benchmark/index.mjs --diagnostics [--with-graph]
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { writeFileSync, unlinkSync } from "node:fs";
|
|
15
|
-
import { resolve, basename } from "node:path";
|
|
16
|
-
import { tmpdir } from "node:os";
|
|
17
|
-
import {
|
|
18
|
-
walkDir, getFileLines, categorize, generateTempCode,
|
|
19
|
-
fmt, pctSavings, RUNS,
|
|
20
|
-
} from "../lib/benchmark-helpers.mjs";
|
|
21
|
-
import { runAtomic } from "./atomic.mjs";
|
|
22
|
-
import { runGraph } from "./graph.mjs";
|
|
23
|
-
import { runWorkflows } from "./workflows.mjs";
|
|
24
|
-
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
// CLI
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
|
-
|
|
29
|
-
const args = process.argv.slice(2);
|
|
30
|
-
let repoRoot = process.cwd();
|
|
31
|
-
const repoIdx = args.indexOf("--repo");
|
|
32
|
-
if (repoIdx !== -1 && args[repoIdx + 1]) {
|
|
33
|
-
repoRoot = resolve(args[repoIdx + 1]);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const diagnostics = args.includes("--diagnostics");
|
|
37
|
-
const withGraph = args.includes("--with-graph");
|
|
38
|
-
|
|
39
|
-
// ---------------------------------------------------------------------------
|
|
40
|
-
// Main
|
|
41
|
-
// ---------------------------------------------------------------------------
|
|
42
|
-
|
|
43
|
-
async function main() {
|
|
44
|
-
const allFiles = walkDir(repoRoot);
|
|
45
|
-
if (allFiles.length === 0) {
|
|
46
|
-
console.log(`No code files found in ${repoRoot}`);
|
|
47
|
-
process.exit(1);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const totalLines = allFiles.reduce((sum, f) => {
|
|
51
|
-
const lines = getFileLines(f);
|
|
52
|
-
return lines ? sum + lines.length : sum;
|
|
53
|
-
}, 0);
|
|
54
|
-
|
|
55
|
-
const cats = categorize(allFiles);
|
|
56
|
-
const repoName = basename(repoRoot);
|
|
57
|
-
|
|
58
|
-
// Top 3 largest code files for realistic tests
|
|
59
|
-
const sorted = allFiles.map(f => ({ f, lines: getFileLines(f)?.length || 0 }))
|
|
60
|
-
.sort((a, b) => b.lines - a.lines);
|
|
61
|
-
const largeFiles = sorted.slice(0, 3).map(s => s.f);
|
|
62
|
-
|
|
63
|
-
// Temp file setup
|
|
64
|
-
const ts = Date.now();
|
|
65
|
-
const tmpPath = resolve(tmpdir(), `hex-line-bench-${ts}.js`);
|
|
66
|
-
const tmpLines = generateTempCode();
|
|
67
|
-
const tmpContent = tmpLines.join("\n");
|
|
68
|
-
writeFileSync(tmpPath, tmpContent, "utf-8");
|
|
69
|
-
|
|
70
|
-
// Build config shared across all benchmark modules
|
|
71
|
-
const config = { allFiles, cats, largeFiles, tmpPath, tmpContent, tmpLines, repoRoot, ts };
|
|
72
|
-
|
|
73
|
-
// Run benchmark suites
|
|
74
|
-
const workflowResults = await runWorkflows(config);
|
|
75
|
-
const results = diagnostics ? await runAtomic(config) : [];
|
|
76
|
-
const graphOut = diagnostics && withGraph ? await runGraph(config) : [];
|
|
77
|
-
|
|
78
|
-
// Cleanup
|
|
79
|
-
try { unlinkSync(tmpPath); } catch { /* ok */ }
|
|
80
|
-
|
|
81
|
-
// ===================================================================
|
|
82
|
-
// Report
|
|
83
|
-
// ===================================================================
|
|
84
|
-
const out = [];
|
|
85
|
-
out.push("# Hex-line Workflow Benchmark");
|
|
86
|
-
out.push("");
|
|
87
|
-
out.push(`Repository: ${repoName} (${fmt(allFiles.length)} code files, ${fmt(totalLines)} lines) `);
|
|
88
|
-
out.push(`Temp file: ${tmpPath} (200 lines) `);
|
|
89
|
-
out.push(`Date: ${new Date().toISOString().slice(0, 10)} `);
|
|
90
|
-
out.push(`Runs per scenario: ${RUNS} (median) `);
|
|
91
|
-
out.push("");
|
|
92
|
-
out.push("Mode: comparative workflow benchmark");
|
|
93
|
-
out.push("");
|
|
94
|
-
out.push("## Workflow Scenarios");
|
|
95
|
-
out.push("");
|
|
96
|
-
out.push("| # | Scenario | Built-in | Hex-line | Savings | Ops |");
|
|
97
|
-
out.push("|---|----------|----------|----------|---------|-----|");
|
|
98
|
-
for (const w of workflowResults) {
|
|
99
|
-
out.push(`| ${w.id} | ${w.scenario} | ${fmt(w.without)} chars | ${fmt(w.withSL)} chars | ${pctSavings(w.without, w.withSL)} | ${w.opsWithout}\u2192${w.opsWith} |`);
|
|
100
|
-
}
|
|
101
|
-
out.push("");
|
|
102
|
-
|
|
103
|
-
if (workflowResults.length > 0) {
|
|
104
|
-
const avgWorkflowSavings = workflowResults.reduce((sum, w) => {
|
|
105
|
-
if (w.without === 0) return sum;
|
|
106
|
-
return sum + (((w.without - w.withSL) / w.without) * 100);
|
|
107
|
-
}, 0) / workflowResults.length;
|
|
108
|
-
const totalWorkflowOpsWithout = workflowResults.reduce((sum, w) => sum + w.opsWithout, 0);
|
|
109
|
-
const totalWorkflowOpsWith = workflowResults.reduce((sum, w) => sum + w.opsWith, 0);
|
|
110
|
-
const workflowOpsPct = totalWorkflowOpsWithout > 0
|
|
111
|
-
? (((totalWorkflowOpsWithout - totalWorkflowOpsWith) / totalWorkflowOpsWithout) * 100).toFixed(0)
|
|
112
|
-
: "0";
|
|
113
|
-
out.push(`Workflow summary: ${avgWorkflowSavings.toFixed(0)}% average token savings | ${totalWorkflowOpsWithout}\u2192${totalWorkflowOpsWith} ops (${workflowOpsPct}% fewer)`);
|
|
114
|
-
out.push("");
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
out.push("Note: benchmark mode reports only multi-step comparative workflows. Synthetic tool-level scenarios live under diagnostics and are not headline results.");
|
|
118
|
-
out.push("");
|
|
119
|
-
|
|
120
|
-
if (diagnostics) {
|
|
121
|
-
out.push("## Diagnostics");
|
|
122
|
-
out.push("");
|
|
123
|
-
out.push("These rows are modeled tool-level comparisons for engineering inspection only. They are not part of the public workflow benchmark score.");
|
|
124
|
-
out.push("");
|
|
125
|
-
out.push("| # | Scenario | Baseline | Hex-line | Savings |");
|
|
126
|
-
out.push("|---|----------|----------|----------|---------|");
|
|
127
|
-
for (const r of results) {
|
|
128
|
-
out.push(`| ${r.num} | ${r.scenario} | ${fmt(r.without)} chars | ${fmt(r.withSL)} chars | ${r.savings} |`);
|
|
129
|
-
}
|
|
130
|
-
out.push("");
|
|
131
|
-
if (graphOut.length > 0) {
|
|
132
|
-
out.push("### Graph Enrichment Diagnostics");
|
|
133
|
-
out.push("");
|
|
134
|
-
out.push("Graph-enrichment rows remain diagnostics only. Run with `--with-graph` to inspect them alongside the atomic rows.");
|
|
135
|
-
out.push("");
|
|
136
|
-
out.push(...graphOut);
|
|
137
|
-
out.push("");
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
console.log(out.join("\n"));
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
main();
|
package/benchmark/workflows.mjs
DELETED
|
@@ -1,350 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Session-derived workflow scenarios built from recent real Claude usage.
|
|
3
|
-
*
|
|
4
|
-
* These are still local, reproducible benchmarks, but the tasks are framed
|
|
5
|
-
* after actual day-to-day workflows observed in recent sessions:
|
|
6
|
-
* - debugging hex-line hook behavior
|
|
7
|
-
* - adjusting setup/output guidance
|
|
8
|
-
* - repo-wide benchmark wording refactor
|
|
9
|
-
* - targeted edit inside a large smoke test
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { copyFileSync, mkdirSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
13
|
-
import { resolve, dirname } from "node:path";
|
|
14
|
-
import { tmpdir } from "node:os";
|
|
15
|
-
import { fnv1a, lineTag, rangeChecksum } from "../lib/hash.mjs";
|
|
16
|
-
import { readFile } from "../lib/read.mjs";
|
|
17
|
-
import { verifyChecksums } from "../lib/verify.mjs";
|
|
18
|
-
import { editFile } from "../lib/edit.mjs";
|
|
19
|
-
import { bulkReplace } from "../lib/bulk-replace.mjs";
|
|
20
|
-
import { fileOutline } from "../lib/outline.mjs";
|
|
21
|
-
import {
|
|
22
|
-
getFileLines,
|
|
23
|
-
simBuiltInReadFull,
|
|
24
|
-
simBuiltInGrep,
|
|
25
|
-
simBuiltInEdit,
|
|
26
|
-
simHexLineEditDiff,
|
|
27
|
-
runN,
|
|
28
|
-
} from "../lib/benchmark-helpers.mjs";
|
|
29
|
-
|
|
30
|
-
function ensureLine(lines, matcher, label) {
|
|
31
|
-
const idx = lines.findIndex((line) => matcher(line));
|
|
32
|
-
if (idx === -1) throw new Error(`Benchmark fixture missing line for ${label}`);
|
|
33
|
-
return idx;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function copyIntoTemp(tempRoot, sourceRoot, relPath) {
|
|
37
|
-
const src = resolve(sourceRoot, relPath);
|
|
38
|
-
const dst = resolve(tempRoot, relPath);
|
|
39
|
-
mkdirSync(dirname(dst), { recursive: true });
|
|
40
|
-
copyFileSync(src, dst);
|
|
41
|
-
return dst;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export async function runWorkflows(config) {
|
|
45
|
-
const { repoRoot, allFiles, largeFiles } = config;
|
|
46
|
-
const workflowResults = [];
|
|
47
|
-
|
|
48
|
-
// W1: derived from "Debug hex line formatting in file listings"
|
|
49
|
-
{
|
|
50
|
-
const sourcePath = resolve(repoRoot, "hook.mjs");
|
|
51
|
-
const sourceLines = getFileLines(sourcePath);
|
|
52
|
-
if (!sourceLines) throw new Error("Unable to load hook.mjs for benchmark workflow W1");
|
|
53
|
-
|
|
54
|
-
const targetIdx = ensureLine(
|
|
55
|
-
sourceLines,
|
|
56
|
-
(line) => line.includes("ls -R, ls -laR (recursive only)"),
|
|
57
|
-
"hook redirect comment",
|
|
58
|
-
);
|
|
59
|
-
const tempPath = resolve(tmpdir(), `hex-line-wf1-${Date.now()}.mjs`);
|
|
60
|
-
copyFileSync(sourcePath, tempPath);
|
|
61
|
-
|
|
62
|
-
const updatedLine = sourceLines[targetIdx].replace("recursive only", "recursive listing only");
|
|
63
|
-
const updatedLines = [...sourceLines];
|
|
64
|
-
updatedLines[targetIdx] = updatedLine;
|
|
65
|
-
|
|
66
|
-
const { value: without } = runN(() => {
|
|
67
|
-
let total = 0;
|
|
68
|
-
total += simBuiltInGrep("ls -R", tempPath).length;
|
|
69
|
-
total += simBuiltInReadFull(tempPath, sourceLines).length;
|
|
70
|
-
total += simBuiltInEdit(tempPath, sourceLines, updatedLines).length;
|
|
71
|
-
return total;
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
const { value: withSL } = runN(() => {
|
|
75
|
-
let total = 0;
|
|
76
|
-
const tag = lineTag(fnv1a(sourceLines[targetIdx]));
|
|
77
|
-
total += `${tempPath}:>>${tag}.${targetIdx + 1}\t${sourceLines[targetIdx]}`.length;
|
|
78
|
-
try {
|
|
79
|
-
total += editFile(tempPath, [{ set_line: { anchor: `${tag}.${targetIdx + 1}`, new_text: updatedLine } }]).length;
|
|
80
|
-
} catch (e) {
|
|
81
|
-
total += e.message.length;
|
|
82
|
-
}
|
|
83
|
-
return total;
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
workflowResults.push({
|
|
87
|
-
id: "W1",
|
|
88
|
-
scenario: "Debug hook file-listing redirect",
|
|
89
|
-
without,
|
|
90
|
-
withSL,
|
|
91
|
-
opsWithout: 3,
|
|
92
|
-
opsWith: 2,
|
|
93
|
-
});
|
|
94
|
-
try { unlinkSync(tempPath); } catch {}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// W2: derived from setup / guidance updates in repo tooling sessions
|
|
98
|
-
{
|
|
99
|
-
const sourcePath = resolve(repoRoot, "lib", "setup.mjs");
|
|
100
|
-
const sourceLines = getFileLines(sourcePath);
|
|
101
|
-
if (!sourceLines) throw new Error("Unable to load lib/setup.mjs for benchmark workflow W2");
|
|
102
|
-
|
|
103
|
-
const targetIdx = ensureLine(
|
|
104
|
-
sourceLines,
|
|
105
|
-
(line) => line.includes("Codex: Not supported"),
|
|
106
|
-
"setup guidance line",
|
|
107
|
-
);
|
|
108
|
-
const tempPath = resolve(tmpdir(), `hex-line-wf2-${Date.now()}.mjs`);
|
|
109
|
-
copyFileSync(sourcePath, tempPath);
|
|
110
|
-
|
|
111
|
-
const updatedLine = sourceLines[targetIdx].replace(
|
|
112
|
-
"Add MCP Tool Preferences to AGENTS.md instead",
|
|
113
|
-
"Document MCP Tool Preferences in AGENTS.md instead",
|
|
114
|
-
);
|
|
115
|
-
const updatedLines = [...sourceLines];
|
|
116
|
-
updatedLines[targetIdx] = updatedLine;
|
|
117
|
-
const windowStart = Math.max(1, targetIdx - 3);
|
|
118
|
-
const windowLimit = Math.min(sourceLines.length - windowStart + 1, 10);
|
|
119
|
-
const hashes = sourceLines.map((line) => fnv1a(line));
|
|
120
|
-
const checksum = rangeChecksum(hashes, windowStart, windowStart + windowLimit - 1);
|
|
121
|
-
|
|
122
|
-
const { value: without } = runN(() => {
|
|
123
|
-
let total = 0;
|
|
124
|
-
total += simBuiltInReadFull(tempPath, sourceLines).length;
|
|
125
|
-
total += simBuiltInEdit(tempPath, sourceLines, updatedLines).length;
|
|
126
|
-
total += simBuiltInReadFull(tempPath, sourceLines).length;
|
|
127
|
-
return total;
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
const { value: withSL } = runN(() => {
|
|
131
|
-
let total = 0;
|
|
132
|
-
total += readFile(tempPath, { offset: windowStart, limit: windowLimit }).length;
|
|
133
|
-
copyFileSync(sourcePath, tempPath);
|
|
134
|
-
try {
|
|
135
|
-
const tag = lineTag(fnv1a(sourceLines[targetIdx]));
|
|
136
|
-
total += editFile(tempPath, [{ set_line: { anchor: `${tag}.${targetIdx + 1}`, new_text: updatedLine } }]).length;
|
|
137
|
-
} catch (e) {
|
|
138
|
-
total += e.message.length;
|
|
139
|
-
}
|
|
140
|
-
try {
|
|
141
|
-
total += verifyChecksums(tempPath, [checksum]).length;
|
|
142
|
-
} catch (e) {
|
|
143
|
-
total += e.message.length;
|
|
144
|
-
}
|
|
145
|
-
return total;
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
workflowResults.push({
|
|
149
|
-
id: "W2",
|
|
150
|
-
scenario: "Adjust setup_hooks guidance and verify",
|
|
151
|
-
without,
|
|
152
|
-
withSL,
|
|
153
|
-
opsWithout: 3,
|
|
154
|
-
opsWith: 3,
|
|
155
|
-
});
|
|
156
|
-
try { unlinkSync(tempPath); } catch {}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// W3: derived from repo-wide benchmark wording refactors
|
|
160
|
-
{
|
|
161
|
-
const tempRoot = resolve(tmpdir(), `hex-line-wf3-${Date.now()}`);
|
|
162
|
-
mkdirSync(tempRoot, { recursive: true });
|
|
163
|
-
const fixtureFiles = [
|
|
164
|
-
"README.md",
|
|
165
|
-
"package.json",
|
|
166
|
-
"benchmark/index.mjs",
|
|
167
|
-
"benchmark/atomic.mjs",
|
|
168
|
-
"benchmark/workflows.mjs",
|
|
169
|
-
];
|
|
170
|
-
const copiedFiles = fixtureFiles.map((relPath) => copyIntoTemp(tempRoot, repoRoot, relPath));
|
|
171
|
-
const fileLines = copiedFiles.map((filePath) => getFileLines(filePath));
|
|
172
|
-
const replacements = [{ old: "benchmark", new: "workflow benchmark" }];
|
|
173
|
-
|
|
174
|
-
const { value: without } = runN(() => {
|
|
175
|
-
let total = 0;
|
|
176
|
-
for (let i = 0; i < copiedFiles.length; i++) {
|
|
177
|
-
const filePath = copiedFiles[i];
|
|
178
|
-
const lines = fileLines[i];
|
|
179
|
-
if (!lines) continue;
|
|
180
|
-
total += simBuiltInGrep("benchmark", filePath).length;
|
|
181
|
-
total += simBuiltInReadFull(filePath, lines).length;
|
|
182
|
-
const updated = lines.map((line) => line.split("benchmark").join("workflow benchmark"));
|
|
183
|
-
total += simBuiltInEdit(filePath, lines, updated).length;
|
|
184
|
-
}
|
|
185
|
-
return total;
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
const { value: withSL } = runN(() => {
|
|
189
|
-
return bulkReplace(
|
|
190
|
-
tempRoot,
|
|
191
|
-
"**/*.{md,json,mjs}",
|
|
192
|
-
replacements,
|
|
193
|
-
{ dryRun: true, maxFiles: 10 },
|
|
194
|
-
).length;
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
workflowResults.push({
|
|
198
|
-
id: "W3",
|
|
199
|
-
scenario: "Repo-wide benchmark wording refresh",
|
|
200
|
-
without,
|
|
201
|
-
withSL,
|
|
202
|
-
opsWithout: copiedFiles.length * 3,
|
|
203
|
-
opsWith: 1,
|
|
204
|
-
});
|
|
205
|
-
try { rmSync(tempRoot, { recursive: true }); } catch {}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// W4: derived from reviewing large smoke tests before a focused change
|
|
209
|
-
{
|
|
210
|
-
const preferredLarge = allFiles.find((filePath) => filePath.endsWith("test\\smoke.mjs"))
|
|
211
|
-
|| largeFiles[0]
|
|
212
|
-
|| allFiles[0];
|
|
213
|
-
const largeLines = getFileLines(preferredLarge);
|
|
214
|
-
if (largeLines && largeLines.length > 100) {
|
|
215
|
-
const targetIdx = ensureLine(
|
|
216
|
-
largeLines,
|
|
217
|
-
(line) => line.includes("describe(\"hook — ls redirect\""),
|
|
218
|
-
"large smoke test anchor",
|
|
219
|
-
);
|
|
220
|
-
const sliceStart = Math.max(0, targetIdx - 5);
|
|
221
|
-
const sliceEnd = Math.min(largeLines.length, targetIdx + 15);
|
|
222
|
-
const editedSlice = largeLines.slice(sliceStart, sliceEnd).map((line, idx) =>
|
|
223
|
-
idx === (targetIdx - sliceStart) ? `${line} // benchmark-note` : line,
|
|
224
|
-
);
|
|
225
|
-
|
|
226
|
-
const { value: without } = runN(() => {
|
|
227
|
-
let total = 0;
|
|
228
|
-
total += simBuiltInReadFull(preferredLarge, largeLines).length;
|
|
229
|
-
total += simBuiltInGrep("hook — ls redirect", preferredLarge).length;
|
|
230
|
-
const updatedLines = [...largeLines];
|
|
231
|
-
updatedLines[targetIdx] = `${updatedLines[targetIdx]} // benchmark-note`;
|
|
232
|
-
total += simBuiltInEdit(preferredLarge, largeLines, updatedLines).length;
|
|
233
|
-
return total;
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
let outlineLen = 500;
|
|
237
|
-
try { outlineLen = (await fileOutline(preferredLarge)).length; } catch {}
|
|
238
|
-
|
|
239
|
-
const { value: withSL } = runN(() => {
|
|
240
|
-
let total = 0;
|
|
241
|
-
total += outlineLen;
|
|
242
|
-
total += readFile(preferredLarge, { offset: sliceStart + 1, limit: sliceEnd - sliceStart }).length;
|
|
243
|
-
total += simHexLineEditDiff(largeLines.slice(sliceStart, sliceEnd), editedSlice).length;
|
|
244
|
-
return total;
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
workflowResults.push({
|
|
248
|
-
id: "W4",
|
|
249
|
-
scenario: `Inspect large smoke test before edit (${largeLines.length}L)`,
|
|
250
|
-
without,
|
|
251
|
-
withSL,
|
|
252
|
-
opsWithout: 3,
|
|
253
|
-
opsWith: 3,
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// W5: revision-aware follow-up edit after unrelated line shift
|
|
259
|
-
{
|
|
260
|
-
const tempPath = resolve(tmpdir(), `hex-line-wf5-${Date.now()}.mjs`);
|
|
261
|
-
const prefix = Array.from({ length: 80 }, (_, i) => `pre-${i}`);
|
|
262
|
-
const suffix = Array.from({ length: 80 }, (_, i) => `post-${i}`);
|
|
263
|
-
const sourceLines = [
|
|
264
|
-
...prefix,
|
|
265
|
-
"head1",
|
|
266
|
-
"head2",
|
|
267
|
-
"targetA",
|
|
268
|
-
"targetB",
|
|
269
|
-
"tail",
|
|
270
|
-
...suffix,
|
|
271
|
-
"",
|
|
272
|
-
];
|
|
273
|
-
const sourceText = sourceLines.join("\n");
|
|
274
|
-
mkdirSync(dirname(tempPath), { recursive: true });
|
|
275
|
-
writeFileSync(tempPath, sourceText, "utf-8");
|
|
276
|
-
|
|
277
|
-
const head1Idx = prefix.length;
|
|
278
|
-
const targetAIdx = prefix.length + 2;
|
|
279
|
-
const targetBIdx = prefix.length + 3;
|
|
280
|
-
const withInsert = [
|
|
281
|
-
...prefix,
|
|
282
|
-
"head1",
|
|
283
|
-
"inserted",
|
|
284
|
-
"head2",
|
|
285
|
-
"targetA",
|
|
286
|
-
"targetB",
|
|
287
|
-
"tail",
|
|
288
|
-
...suffix,
|
|
289
|
-
"",
|
|
290
|
-
];
|
|
291
|
-
const updatedLines = [
|
|
292
|
-
...prefix,
|
|
293
|
-
"head1",
|
|
294
|
-
"inserted",
|
|
295
|
-
"head2",
|
|
296
|
-
"targetA",
|
|
297
|
-
"updatedB",
|
|
298
|
-
"tail",
|
|
299
|
-
...suffix,
|
|
300
|
-
"",
|
|
301
|
-
];
|
|
302
|
-
|
|
303
|
-
const { value: without } = runN(() => {
|
|
304
|
-
let total = 0;
|
|
305
|
-
total += simBuiltInReadFull(tempPath, sourceLines).length;
|
|
306
|
-
total += simBuiltInEdit(tempPath, sourceLines, withInsert).length;
|
|
307
|
-
total += simBuiltInReadFull(tempPath, withInsert).length;
|
|
308
|
-
total += simBuiltInEdit(tempPath, withInsert, updatedLines).length;
|
|
309
|
-
return total;
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
const { value: withSL } = runN(() => {
|
|
313
|
-
let total = 0;
|
|
314
|
-
writeFileSync(tempPath, sourceText, "utf-8");
|
|
315
|
-
const baseRead = readFile(tempPath, { offset: head1Idx + 1, limit: 8 });
|
|
316
|
-
total += baseRead.length;
|
|
317
|
-
const baseRevision = baseRead.match(/revision: (\S+)/)?.[1];
|
|
318
|
-
const headTag = lineTag(fnv1a(sourceLines[head1Idx]));
|
|
319
|
-
total += editFile(tempPath, [{ insert_after: { anchor: `${headTag}.${head1Idx + 1}`, text: "inserted" } }]).length;
|
|
320
|
-
const startTag = lineTag(fnv1a(sourceLines[targetAIdx]));
|
|
321
|
-
const endTag = lineTag(fnv1a(sourceLines[targetBIdx]));
|
|
322
|
-
const rc = rangeChecksum(
|
|
323
|
-
[fnv1a(sourceLines[targetAIdx]), fnv1a(sourceLines[targetBIdx])],
|
|
324
|
-
targetAIdx + 1,
|
|
325
|
-
targetBIdx + 1,
|
|
326
|
-
);
|
|
327
|
-
total += editFile(tempPath, [{
|
|
328
|
-
replace_lines: {
|
|
329
|
-
start_anchor: `${startTag}.${targetAIdx + 1}`,
|
|
330
|
-
end_anchor: `${endTag}.${targetBIdx + 1}`,
|
|
331
|
-
new_text: "targetA\nupdatedB",
|
|
332
|
-
range_checksum: rc,
|
|
333
|
-
}
|
|
334
|
-
}], { baseRevision, conflictPolicy: "conservative" }).length;
|
|
335
|
-
return total;
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
workflowResults.push({
|
|
339
|
-
id: "W5",
|
|
340
|
-
scenario: "Follow-up edit after unrelated line shift",
|
|
341
|
-
without,
|
|
342
|
-
withSL,
|
|
343
|
-
opsWithout: 4,
|
|
344
|
-
opsWith: 3,
|
|
345
|
-
});
|
|
346
|
-
try { unlinkSync(tempPath); } catch {}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
return workflowResults;
|
|
350
|
-
}
|