llm-relay 0.54.0 → 0.55.2
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/cli.js +23 -1
- package/dist/cli.js.map +1 -1
- package/dist/delegate-gate/cast-necessity.d.ts +14 -0
- package/dist/delegate-gate/cast-necessity.js +150 -0
- package/dist/delegate-gate/cast-necessity.js.map +1 -0
- package/dist/delegate-gate/cli.d.ts +20 -0
- package/dist/delegate-gate/cli.js +51 -0
- package/dist/delegate-gate/cli.js.map +1 -0
- package/dist/delegate-gate/diff-parser.d.ts +59 -0
- package/dist/delegate-gate/diff-parser.js +147 -0
- package/dist/delegate-gate/diff-parser.js.map +1 -0
- package/dist/delegate-gate/fix.d.ts +16 -0
- package/dist/delegate-gate/fix.js +43 -0
- package/dist/delegate-gate/fix.js.map +1 -0
- package/dist/delegate-gate/gate.d.ts +18 -0
- package/dist/delegate-gate/gate.js +42 -0
- package/dist/delegate-gate/gate.js.map +1 -0
- package/dist/delegate-gate/minimality.d.ts +30 -0
- package/dist/delegate-gate/minimality.js +120 -0
- package/dist/delegate-gate/minimality.js.map +1 -0
- package/dist/delegate-gate/post-image.d.ts +10 -0
- package/dist/delegate-gate/post-image.js +45 -0
- package/dist/delegate-gate/post-image.js.map +1 -0
- package/dist/delegate-gate/shared-state.d.ts +15 -0
- package/dist/delegate-gate/shared-state.js +158 -0
- package/dist/delegate-gate/shared-state.js.map +1 -0
- package/dist/delegate-gate/test-assertions.d.ts +18 -0
- package/dist/delegate-gate/test-assertions.js +273 -0
- package/dist/delegate-gate/test-assertions.js.map +1 -0
- package/dist/delegate-gate/types.d.ts +26 -0
- package/dist/delegate-gate/types.js +19 -0
- package/dist/delegate-gate/types.js.map +1 -0
- package/dist/refusal-interpretation.d.ts +3 -0
- package/dist/refusal-interpretation.js +7 -1
- package/dist/refusal-interpretation.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { reportedPath } from "./diff-parser.js";
|
|
3
|
+
import { reconstructPostImage } from "./post-image.js";
|
|
4
|
+
const TS_FILE = /\.(ts|tsx|mts|cts)$/;
|
|
5
|
+
const PRIMITIVE_KEYWORD_TO_LITERAL_KIND = new Map([
|
|
6
|
+
[ts.SyntaxKind.StringKeyword, [ts.SyntaxKind.StringLiteral, ts.SyntaxKind.NoSubstitutionTemplateLiteral]],
|
|
7
|
+
[ts.SyntaxKind.NumberKeyword, [ts.SyntaxKind.NumericLiteral]],
|
|
8
|
+
[ts.SyntaxKind.BooleanKeyword, [ts.SyntaxKind.TrueKeyword, ts.SyntaxKind.FalseKeyword]],
|
|
9
|
+
]);
|
|
10
|
+
/** `x as unknown as T` (or any AsExpression whose own expression is itself an AsExpression to
|
|
11
|
+
* `unknown`) — a double cast that exists only to defeat the type checker. */
|
|
12
|
+
function isDoubleUnknownCast(node) {
|
|
13
|
+
const inner = node.expression;
|
|
14
|
+
if (!ts.isAsExpression(inner))
|
|
15
|
+
return false;
|
|
16
|
+
return ts.isTypeReferenceNode(inner.type) === false
|
|
17
|
+
&& inner.type.kind === ts.SyntaxKind.UnknownKeyword;
|
|
18
|
+
}
|
|
19
|
+
function isAnyCast(node) {
|
|
20
|
+
return node.type.kind === ts.SyntaxKind.AnyKeyword;
|
|
21
|
+
}
|
|
22
|
+
/** `"x" as string`, `5 as number`, `true as boolean` — a cast of a literal to the primitive type
|
|
23
|
+
* it already trivially has. Deliberately narrow: this is a syntactic check, not a type-checker
|
|
24
|
+
* one, so it only fires on the unambiguous literal-vs-keyword-type case and never guesses about
|
|
25
|
+
* an identifier's inferred type. */
|
|
26
|
+
function isTriviallyRedundantLiteralCast(node) {
|
|
27
|
+
const allowedKinds = PRIMITIVE_KEYWORD_TO_LITERAL_KIND.get(node.type.kind);
|
|
28
|
+
if (allowedKinds === undefined)
|
|
29
|
+
return false;
|
|
30
|
+
return allowedKinds.includes(node.expression.kind);
|
|
31
|
+
}
|
|
32
|
+
function findAsExpressionsOnAddedLines(sourceFile, addedLines) {
|
|
33
|
+
const found = [];
|
|
34
|
+
const visit = (node) => {
|
|
35
|
+
if (ts.isAsExpression(node)) {
|
|
36
|
+
const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
37
|
+
if (addedLines.has(line + 1))
|
|
38
|
+
found.push(node);
|
|
39
|
+
}
|
|
40
|
+
ts.forEachChild(node, visit);
|
|
41
|
+
};
|
|
42
|
+
visit(sourceFile);
|
|
43
|
+
return found;
|
|
44
|
+
}
|
|
45
|
+
function describeCast(node) {
|
|
46
|
+
if (isAnyCast(node)) {
|
|
47
|
+
return { detail: `\`${node.getText()}\` casts to \`any\`, discarding type safety`, autoFixable: false };
|
|
48
|
+
}
|
|
49
|
+
if (isDoubleUnknownCast(node)) {
|
|
50
|
+
return { detail: `\`${node.getText()}\` is a double cast through \`unknown\` — it exists only to bypass the type checker`, autoFixable: false };
|
|
51
|
+
}
|
|
52
|
+
if (isTriviallyRedundantLiteralCast(node)) {
|
|
53
|
+
return { detail: `\`${node.getText()}\` casts a literal to the primitive type it already has — the cast adds nothing`, autoFixable: true };
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Flag `as` casts introduced by the diff that add nothing.
|
|
59
|
+
*
|
|
60
|
+
* Always flagged: `as any` and `x as unknown as T` double casts. Best-effort beyond that: a
|
|
61
|
+
* literal cast to the primitive type it already trivially has (`"x" as string`). Deciding
|
|
62
|
+
* necessity in the general case needs a full type-checker `Program` over the whole repo — slow,
|
|
63
|
+
* and only as good as whether the diff's target compiles in isolation — so this deliberately stays
|
|
64
|
+
* syntactic rather than risk a false positive on a cast that genuinely narrows or widens a type.
|
|
65
|
+
*/
|
|
66
|
+
function findingsForFile(file, path, readOriginal) {
|
|
67
|
+
const original = file.isNew ? null : readOriginal(path);
|
|
68
|
+
const { lines, addedLines } = reconstructPostImage(original, file);
|
|
69
|
+
if (addedLines.size === 0)
|
|
70
|
+
return [];
|
|
71
|
+
const content = lines.join("\n");
|
|
72
|
+
const sourceFile = ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true, path.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
|
|
73
|
+
const findings = [];
|
|
74
|
+
for (const node of findAsExpressionsOnAddedLines(sourceFile, addedLines)) {
|
|
75
|
+
const described = describeCast(node);
|
|
76
|
+
if (described === null)
|
|
77
|
+
continue;
|
|
78
|
+
const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
79
|
+
findings.push({
|
|
80
|
+
class: "unnecessary-cast",
|
|
81
|
+
file: path,
|
|
82
|
+
line: line + 1,
|
|
83
|
+
detail: described.detail,
|
|
84
|
+
autoFixable: described.autoFixable,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return findings;
|
|
88
|
+
}
|
|
89
|
+
export function analyzeCastNecessity(files, readOriginal) {
|
|
90
|
+
const findings = [];
|
|
91
|
+
for (const file of files) {
|
|
92
|
+
const path = reportedPath(file);
|
|
93
|
+
if (!TS_FILE.test(path) || file.hunks.length === 0)
|
|
94
|
+
continue;
|
|
95
|
+
findings.push(...findingsForFile(file, path, readOriginal));
|
|
96
|
+
}
|
|
97
|
+
return findings;
|
|
98
|
+
}
|
|
99
|
+
/** The diff's own `add` line at post-image line `newLine`, or undefined if none matches — used to
|
|
100
|
+
* translate an AST node's line number back into the raw diff line the auto-fix edits. */
|
|
101
|
+
function addedDiffLine(file, newLine) {
|
|
102
|
+
for (const hunk of file.hunks) {
|
|
103
|
+
for (const line of hunk.lines) {
|
|
104
|
+
if (line.kind === "add" && line.newLine === newLine)
|
|
105
|
+
return line;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
function castEditsForFile(file, path, readOriginal) {
|
|
111
|
+
const original = file.isNew ? null : readOriginal(path);
|
|
112
|
+
const { lines, addedLines } = reconstructPostImage(original, file);
|
|
113
|
+
if (addedLines.size === 0)
|
|
114
|
+
return [];
|
|
115
|
+
const content = lines.join("\n");
|
|
116
|
+
const sourceFile = ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true, path.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
|
|
117
|
+
const edits = [];
|
|
118
|
+
for (const node of findAsExpressionsOnAddedLines(sourceFile, addedLines)) {
|
|
119
|
+
if (!isTriviallyRedundantLiteralCast(node))
|
|
120
|
+
continue; // the only auto-fixable cast shape
|
|
121
|
+
const start = node.getStart(sourceFile);
|
|
122
|
+
const { line: lineIndex, character: startCol } = sourceFile.getLineAndCharacterOfPosition(start);
|
|
123
|
+
const endCol = sourceFile.getLineAndCharacterOfPosition(node.getEnd()).character;
|
|
124
|
+
const lineText = lines[lineIndex];
|
|
125
|
+
if (lineText === undefined)
|
|
126
|
+
continue;
|
|
127
|
+
const diffLine = addedDiffLine(file, lineIndex + 1);
|
|
128
|
+
if (diffLine === undefined)
|
|
129
|
+
continue;
|
|
130
|
+
const replacementText = node.expression.getText(sourceFile);
|
|
131
|
+
const newContent = lineText.slice(0, startCol) + replacementText + lineText.slice(endCol);
|
|
132
|
+
edits.push({ rawIndex: diffLine.rawIndex, newContent });
|
|
133
|
+
}
|
|
134
|
+
return edits;
|
|
135
|
+
}
|
|
136
|
+
/** Mechanical corrections for the `unnecessary-cast` findings this module can safely apply on its
|
|
137
|
+
* own: only the trivially-redundant-literal-cast shape (`"x" as string` -> `"x"`). `as any` and
|
|
138
|
+
* the double-unknown cast are never auto-fixed — removing them changes what the surrounding code
|
|
139
|
+
* type-checks as, which is a judgment call for a human, not a mechanical rewrite. */
|
|
140
|
+
export function findFixableCastEdits(files, readOriginal) {
|
|
141
|
+
const edits = [];
|
|
142
|
+
for (const file of files) {
|
|
143
|
+
const path = reportedPath(file);
|
|
144
|
+
if (!TS_FILE.test(path) || file.hunks.length === 0)
|
|
145
|
+
continue;
|
|
146
|
+
edits.push(...castEditsForFile(file, path, readOriginal));
|
|
147
|
+
}
|
|
148
|
+
return edits;
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=cast-necessity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cast-necessity.js","sourceRoot":"","sources":["../../src/delegate-gate/cast-necessity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface DelegateGateCliOptions {
|
|
2
|
+
readonly diffPath: string | undefined;
|
|
3
|
+
readonly repoRoot: string | undefined;
|
|
4
|
+
readonly fix: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface DelegateGateCliResult {
|
|
7
|
+
readonly exitCode: 0 | 1;
|
|
8
|
+
readonly stdout: string;
|
|
9
|
+
readonly stderr: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* `llm-relay delegate-gate <diff-file> --repo <root> [--fix]`
|
|
13
|
+
*
|
|
14
|
+
* Pure over injected IO so the contract tests can drive it without touching the real filesystem.
|
|
15
|
+
* `readFile`/`writeFile` default to Node's `fs`; a caller only overrides them in tests.
|
|
16
|
+
*/
|
|
17
|
+
export declare function runDelegateGateCli(options: DelegateGateCliOptions, io?: {
|
|
18
|
+
readonly readFile?: (path: string) => string;
|
|
19
|
+
readonly writeFile?: (path: string, content: string) => void;
|
|
20
|
+
}): DelegateGateCliResult;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { evaluateDiffText, repoFileReader } from "./gate.js";
|
|
4
|
+
import { autoRepairDiff } from "./fix.js";
|
|
5
|
+
/**
|
|
6
|
+
* `llm-relay delegate-gate <diff-file> --repo <root> [--fix]`
|
|
7
|
+
*
|
|
8
|
+
* Pure over injected IO so the contract tests can drive it without touching the real filesystem.
|
|
9
|
+
* `readFile`/`writeFile` default to Node's `fs`; a caller only overrides them in tests.
|
|
10
|
+
*/
|
|
11
|
+
export function runDelegateGateCli(options, io = {}) {
|
|
12
|
+
const readFile = io.readFile ?? ((path) => readFileSync(path, "utf8"));
|
|
13
|
+
const writeFile = io.writeFile ?? ((path, content) => writeFileSync(path, content, "utf8"));
|
|
14
|
+
if (options.diffPath === undefined) {
|
|
15
|
+
return { exitCode: 1, stdout: "", stderr: "delegate-gate: missing required <diff-file> argument\n" };
|
|
16
|
+
}
|
|
17
|
+
if (options.repoRoot === undefined) {
|
|
18
|
+
return { exitCode: 1, stdout: "", stderr: "delegate-gate: --repo <root> is required\n" };
|
|
19
|
+
}
|
|
20
|
+
let diffText;
|
|
21
|
+
try {
|
|
22
|
+
diffText = readFile(options.diffPath);
|
|
23
|
+
}
|
|
24
|
+
catch (e) {
|
|
25
|
+
return { exitCode: 1, stdout: "", stderr: `delegate-gate: could not read "${options.diffPath}": ${e.message}\n` };
|
|
26
|
+
}
|
|
27
|
+
const repoRoot = resolve(options.repoRoot);
|
|
28
|
+
const readOriginal = repoFileReader(repoRoot);
|
|
29
|
+
let verdict;
|
|
30
|
+
try {
|
|
31
|
+
verdict = evaluateDiffText(diffText, readOriginal);
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
return { exitCode: 1, stdout: "", stderr: `delegate-gate: could not analyze the diff: ${e.message}\n` };
|
|
35
|
+
}
|
|
36
|
+
let stderr = "";
|
|
37
|
+
if (options.fix) {
|
|
38
|
+
const repaired = autoRepairDiff(diffText, readOriginal);
|
|
39
|
+
if (repaired.fixedCount > 0) {
|
|
40
|
+
const fixedPath = `${options.diffPath}.fixed.patch`;
|
|
41
|
+
writeFile(fixedPath, repaired.fixedText);
|
|
42
|
+
stderr += `delegate-gate: wrote ${repaired.fixedCount} mechanical fix(es) to ${fixedPath}\n`;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
stderr += "delegate-gate: no mechanically fixable findings; no .fixed.patch written\n";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const stdout = `${JSON.stringify(verdict, null, 2)}\n`;
|
|
49
|
+
return { exitCode: verdict.pass ? 0 : 1, stdout, stderr };
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/delegate-gate/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAe1C;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAA+B,EAC/B,KAGI,EAAE;IAEN,MAAM,QAAQ,GAAG,EAAE,CAAC,QAAQ,IAAI,CAAC,CAAC,IAAY,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IAC/E,MAAM,SAAS,GAAG,EAAE,CAAC,SAAS,IAAI,CAAC,CAAC,IAAY,EAAE,OAAe,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;IAE5G,IAAI,OAAO,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;QACnC,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,wDAAwD,EAAE,CAAC;IACvG,CAAC;IACD,IAAI,OAAO,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;QACnC,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,4CAA4C,EAAE,CAAC;IAC3F,CAAC;IAED,IAAI,QAAgB,CAAC;IACrB,IAAI,CAAC;QACH,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACxC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,kCAAkC,OAAO,CAAC,QAAQ,MAAO,CAAW,CAAC,OAAO,IAAI,EAAE,CAAC;IAC/H,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC3C,MAAM,YAAY,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;IAE9C,IAAI,OAAgB,CAAC;IACrB,IAAI,CAAC;QACH,OAAO,GAAG,gBAAgB,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;IACrD,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,8CAA+C,CAAW,CAAC,OAAO,IAAI,EAAE,CAAC;IACrH,CAAC;IAED,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QAChB,MAAM,QAAQ,GAAG,cAAc,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;QACxD,IAAI,QAAQ,CAAC,UAAU,GAAG,CAAC,EAAE,CAAC;YAC5B,MAAM,SAAS,GAAG,GAAG,OAAO,CAAC,QAAQ,cAAc,CAAC;YACpD,SAAS,CAAC,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;YACzC,MAAM,IAAI,wBAAwB,QAAQ,CAAC,UAAU,0BAA0B,SAAS,IAAI,CAAC;QAC/F,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,4EAA4E,CAAC;QACzF,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;IACvD,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC5D,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A minimal unified-diff parser (git's `diff --git` dialect) — just enough structure for the
|
|
3
|
+
* detectors in this module: per-file hunks, each line typed context/add/remove and carrying its
|
|
4
|
+
* line number in whichever image it belongs to.
|
|
5
|
+
*
|
|
6
|
+
* Deliberately narrow: this is not a general patch-apply library. It accepts the shape `git diff`
|
|
7
|
+
* / `git format-patch` produce and refuses (throws `DiffParseError`) rather than guess on anything
|
|
8
|
+
* else — the same "refuse, don't mangle" rule `documents.ts` and `openai-request.ts` follow
|
|
9
|
+
* elsewhere in this repo.
|
|
10
|
+
*/
|
|
11
|
+
export type DiffLineKind = "context" | "add" | "remove";
|
|
12
|
+
export interface DiffLine {
|
|
13
|
+
readonly kind: DiffLineKind;
|
|
14
|
+
/** Line content, WITHOUT the leading `+`/`-`/` ` marker and without a trailing newline. */
|
|
15
|
+
readonly content: string;
|
|
16
|
+
/** 1-based line number in the pre-image; undefined for an added line. */
|
|
17
|
+
readonly oldLine: number | undefined;
|
|
18
|
+
/** 1-based line number in the post-image; undefined for a removed line. */
|
|
19
|
+
readonly newLine: number | undefined;
|
|
20
|
+
/** Index of this line's raw (marker-included) text in the diff's full line array. The
|
|
21
|
+
* auto-repair pass edits/removes raw lines by this index rather than re-deriving offsets — the
|
|
22
|
+
* ONE place the diff's own text layout is exposed outside this parser. */
|
|
23
|
+
readonly rawIndex: number;
|
|
24
|
+
}
|
|
25
|
+
export interface DiffHunk {
|
|
26
|
+
readonly oldStart: number;
|
|
27
|
+
readonly oldLines: number;
|
|
28
|
+
readonly newStart: number;
|
|
29
|
+
readonly newLines: number;
|
|
30
|
+
readonly lines: readonly DiffLine[];
|
|
31
|
+
/** Raw-line index of this hunk's `@@ ... @@` header. */
|
|
32
|
+
readonly headerRawIndex: number;
|
|
33
|
+
/** Raw-line index one past this hunk's last body line — `[headerRawIndex, endRawIndex)` is the
|
|
34
|
+
* whole hunk, header included, safe to delete as a unit. */
|
|
35
|
+
readonly endRawIndex: number;
|
|
36
|
+
}
|
|
37
|
+
export interface DiffFile {
|
|
38
|
+
/** Pre-image path (`a/...` with the prefix stripped), or null for a newly created file. */
|
|
39
|
+
readonly oldPath: string | null;
|
|
40
|
+
/** Post-image path (`b/...` with the prefix stripped), or null for a deleted file. */
|
|
41
|
+
readonly newPath: string | null;
|
|
42
|
+
readonly isNew: boolean;
|
|
43
|
+
readonly isDeleted: boolean;
|
|
44
|
+
readonly hunks: readonly DiffHunk[];
|
|
45
|
+
}
|
|
46
|
+
export declare class DiffParseError extends Error {
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Parse a unified diff into per-file hunks.
|
|
50
|
+
*
|
|
51
|
+
* Accepts multiple `diff --git` sections. Each file section must open with a `diff --git a/X b/Y`
|
|
52
|
+
* line; `---`/`+++`/`@@` lines follow standard git conventions. A file section with no hunks
|
|
53
|
+
* (e.g. a pure rename or mode change) is skipped — there is nothing here for a line-level
|
|
54
|
+
* detector to look at.
|
|
55
|
+
*/
|
|
56
|
+
export declare function parseUnifiedDiff(text: string): readonly DiffFile[];
|
|
57
|
+
/** The path a detector should report findings under — the post-image path, falling back to the
|
|
58
|
+
* pre-image path for a deletion (which carries no post-image). */
|
|
59
|
+
export declare function reportedPath(file: DiffFile): string;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A minimal unified-diff parser (git's `diff --git` dialect) — just enough structure for the
|
|
3
|
+
* detectors in this module: per-file hunks, each line typed context/add/remove and carrying its
|
|
4
|
+
* line number in whichever image it belongs to.
|
|
5
|
+
*
|
|
6
|
+
* Deliberately narrow: this is not a general patch-apply library. It accepts the shape `git diff`
|
|
7
|
+
* / `git format-patch` produce and refuses (throws `DiffParseError`) rather than guess on anything
|
|
8
|
+
* else — the same "refuse, don't mangle" rule `documents.ts` and `openai-request.ts` follow
|
|
9
|
+
* elsewhere in this repo.
|
|
10
|
+
*/
|
|
11
|
+
export class DiffParseError extends Error {
|
|
12
|
+
}
|
|
13
|
+
const HUNK_HEADER = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
|
|
14
|
+
/** Strip a single leading `a/` or `b/` path prefix, the form git always emits. */
|
|
15
|
+
function stripPrefix(path) {
|
|
16
|
+
if (path.startsWith("a/") || path.startsWith("b/"))
|
|
17
|
+
return path.slice(2);
|
|
18
|
+
return path;
|
|
19
|
+
}
|
|
20
|
+
function pathFromHeaderLine(line, marker) {
|
|
21
|
+
const rest = line.slice(marker.length + 1);
|
|
22
|
+
// A trailing tab introduces a timestamp in some diff dialects; git's own output never does,
|
|
23
|
+
// but tolerate it rather than folding it into the path.
|
|
24
|
+
const tabAt = rest.indexOf("\t");
|
|
25
|
+
const raw = (tabAt === -1 ? rest : rest.slice(0, tabAt)).trim();
|
|
26
|
+
if (raw === "/dev/null")
|
|
27
|
+
return null;
|
|
28
|
+
return stripPrefix(raw);
|
|
29
|
+
}
|
|
30
|
+
/** Consume the file-header block (index/mode/rename/similarity lines, then `---`/`+++`) starting
|
|
31
|
+
* at `rawLines[start]`. Returns the parsed header and the index of the first line after it. */
|
|
32
|
+
function parseFileHeader(rawLines, start) {
|
|
33
|
+
let isNew = false;
|
|
34
|
+
let isDeleted = false;
|
|
35
|
+
let oldPath = null;
|
|
36
|
+
let newPath = null;
|
|
37
|
+
let i = start;
|
|
38
|
+
while (i < rawLines.length) {
|
|
39
|
+
const line = rawLines[i];
|
|
40
|
+
if (line.startsWith("diff --git ") || line.startsWith("@@"))
|
|
41
|
+
break;
|
|
42
|
+
if (line.startsWith("new file mode"))
|
|
43
|
+
isNew = true;
|
|
44
|
+
else if (line.startsWith("deleted file mode"))
|
|
45
|
+
isDeleted = true;
|
|
46
|
+
else if (line.startsWith("--- "))
|
|
47
|
+
oldPath = pathFromHeaderLine(line, "---");
|
|
48
|
+
else if (line.startsWith("+++ "))
|
|
49
|
+
newPath = pathFromHeaderLine(line, "+++");
|
|
50
|
+
i += 1;
|
|
51
|
+
if (line.startsWith("+++ "))
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
return { header: { isNew, isDeleted, oldPath, newPath }, next: i };
|
|
55
|
+
}
|
|
56
|
+
/** Classify one hunk body line and advance the pre/post cursors it carries. */
|
|
57
|
+
function classifyHunkBodyLine(body, oldCursor, newCursor, rawIndex) {
|
|
58
|
+
const marker = body.charAt(0);
|
|
59
|
+
if (marker === "+")
|
|
60
|
+
return { kind: "add", content: body.slice(1), oldLine: undefined, newLine: newCursor, rawIndex };
|
|
61
|
+
if (marker === "-")
|
|
62
|
+
return { kind: "remove", content: body.slice(1), oldLine: oldCursor, newLine: undefined, rawIndex };
|
|
63
|
+
// A context line always starts with a single space in a conforming diff; an empty string (a
|
|
64
|
+
// blank context line some tools emit unmarked) is accepted the same way.
|
|
65
|
+
const content = marker === " " ? body.slice(1) : body;
|
|
66
|
+
return { kind: "context", content, oldLine: oldCursor, newLine: newCursor, rawIndex };
|
|
67
|
+
}
|
|
68
|
+
/** Parse one `@@ -l,s +l,s @@` hunk starting at `rawLines[start]`. Returns the hunk and the
|
|
69
|
+
* index of the first line after it. */
|
|
70
|
+
function parseHunk(rawLines, start) {
|
|
71
|
+
const headerLine = rawLines[start];
|
|
72
|
+
const match = HUNK_HEADER.exec(headerLine);
|
|
73
|
+
if (!match)
|
|
74
|
+
throw new DiffParseError(`malformed hunk header: ${headerLine}`);
|
|
75
|
+
const oldStart = Number(match[1]);
|
|
76
|
+
const oldLines = match[2] === undefined ? 1 : Number(match[2]);
|
|
77
|
+
const newStart = Number(match[3]);
|
|
78
|
+
const newLines = match[4] === undefined ? 1 : Number(match[4]);
|
|
79
|
+
const lines = [];
|
|
80
|
+
let oldCursor = oldStart;
|
|
81
|
+
let newCursor = newStart;
|
|
82
|
+
let consumedOld = 0;
|
|
83
|
+
let consumedNew = 0;
|
|
84
|
+
let i = start + 1;
|
|
85
|
+
while (consumedOld < oldLines || consumedNew < newLines) {
|
|
86
|
+
if (i >= rawLines.length)
|
|
87
|
+
break;
|
|
88
|
+
const body = rawLines[i];
|
|
89
|
+
if (body.startsWith("diff --git ") || body.startsWith("@@"))
|
|
90
|
+
break;
|
|
91
|
+
// A bare "" marker carries no content of its own.
|
|
92
|
+
if (body.startsWith("\\ ")) {
|
|
93
|
+
i += 1;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const line = classifyHunkBodyLine(body, oldCursor, newCursor, i);
|
|
97
|
+
lines.push(line);
|
|
98
|
+
if (line.kind !== "remove") {
|
|
99
|
+
newCursor += 1;
|
|
100
|
+
consumedNew += 1;
|
|
101
|
+
}
|
|
102
|
+
if (line.kind !== "add") {
|
|
103
|
+
oldCursor += 1;
|
|
104
|
+
consumedOld += 1;
|
|
105
|
+
}
|
|
106
|
+
i += 1;
|
|
107
|
+
}
|
|
108
|
+
return { hunk: { oldStart, oldLines, newStart, newLines, lines, headerRawIndex: start, endRawIndex: i }, next: i };
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Parse a unified diff into per-file hunks.
|
|
112
|
+
*
|
|
113
|
+
* Accepts multiple `diff --git` sections. Each file section must open with a `diff --git a/X b/Y`
|
|
114
|
+
* line; `---`/`+++`/`@@` lines follow standard git conventions. A file section with no hunks
|
|
115
|
+
* (e.g. a pure rename or mode change) is skipped — there is nothing here for a line-level
|
|
116
|
+
* detector to look at.
|
|
117
|
+
*/
|
|
118
|
+
export function parseUnifiedDiff(text) {
|
|
119
|
+
// Normalize line endings so a CRLF-authored diff parses identically to LF.
|
|
120
|
+
const rawLines = text.replace(/\r\n/g, "\n").split("\n");
|
|
121
|
+
const files = [];
|
|
122
|
+
let i = 0;
|
|
123
|
+
while (i < rawLines.length) {
|
|
124
|
+
if (!rawLines[i].startsWith("diff --git ")) {
|
|
125
|
+
i += 1;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const { header, next: afterHeader } = parseFileHeader(rawLines, i + 1);
|
|
129
|
+
i = afterHeader;
|
|
130
|
+
const hunks = [];
|
|
131
|
+
while (i < rawLines.length && rawLines[i].startsWith("@@")) {
|
|
132
|
+
const { hunk, next } = parseHunk(rawLines, i);
|
|
133
|
+
hunks.push(hunk);
|
|
134
|
+
i = next;
|
|
135
|
+
}
|
|
136
|
+
if (hunks.length > 0 || header.isNew || header.isDeleted) {
|
|
137
|
+
files.push({ oldPath: header.oldPath, newPath: header.newPath, isNew: header.isNew, isDeleted: header.isDeleted, hunks });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return files;
|
|
141
|
+
}
|
|
142
|
+
/** The path a detector should report findings under — the post-image path, falling back to the
|
|
143
|
+
* pre-image path for a deletion (which carries no post-image). */
|
|
144
|
+
export function reportedPath(file) {
|
|
145
|
+
return file.newPath ?? file.oldPath ?? "(unknown)";
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=diff-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff-parser.js","sourceRoot":"","sources":["../../src/delegate-gate/diff-parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ReadOriginal } from "./gate.js";
|
|
2
|
+
export interface AutoRepairResult {
|
|
3
|
+
readonly fixedText: string;
|
|
4
|
+
readonly fixedCount: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Produce a corrected diff for the two MECHANICAL finding classes: pure indentation-churn hunks
|
|
8
|
+
* (dropped whole) and trivially-redundant literal casts (rewritten in place). Every other finding
|
|
9
|
+
* class is a judgment call and is left in the verdict for a human/reviewer to act on — this
|
|
10
|
+
* function never touches the target repo, only the diff text itself.
|
|
11
|
+
*
|
|
12
|
+
* Both transforms are line-count-preserving or hunk-boundary-preserving by construction (see
|
|
13
|
+
* `isPureIndentationChurnHunk` and `findFixableCastEdits`), so no other hunk's `@@` header ever
|
|
14
|
+
* needs recomputing.
|
|
15
|
+
*/
|
|
16
|
+
export declare function autoRepairDiff(diffText: string, readOriginal: ReadOriginal): AutoRepairResult;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { parseUnifiedDiff } from "./diff-parser.js";
|
|
2
|
+
import { isPureIndentationChurnHunk } from "./minimality.js";
|
|
3
|
+
import { findFixableCastEdits } from "./cast-necessity.js";
|
|
4
|
+
/**
|
|
5
|
+
* Produce a corrected diff for the two MECHANICAL finding classes: pure indentation-churn hunks
|
|
6
|
+
* (dropped whole) and trivially-redundant literal casts (rewritten in place). Every other finding
|
|
7
|
+
* class is a judgment call and is left in the verdict for a human/reviewer to act on — this
|
|
8
|
+
* function never touches the target repo, only the diff text itself.
|
|
9
|
+
*
|
|
10
|
+
* Both transforms are line-count-preserving or hunk-boundary-preserving by construction (see
|
|
11
|
+
* `isPureIndentationChurnHunk` and `findFixableCastEdits`), so no other hunk's `@@` header ever
|
|
12
|
+
* needs recomputing.
|
|
13
|
+
*/
|
|
14
|
+
export function autoRepairDiff(diffText, readOriginal) {
|
|
15
|
+
const normalized = diffText.replace(/\r\n/g, "\n");
|
|
16
|
+
const rawLines = normalized.split("\n");
|
|
17
|
+
const files = parseUnifiedDiff(normalized);
|
|
18
|
+
const dropped = new Set();
|
|
19
|
+
let fixedCount = 0;
|
|
20
|
+
for (const file of files) {
|
|
21
|
+
for (const hunk of file.hunks) {
|
|
22
|
+
if (!isPureIndentationChurnHunk(hunk))
|
|
23
|
+
continue;
|
|
24
|
+
for (let i = hunk.headerRawIndex; i < hunk.endRawIndex; i++)
|
|
25
|
+
dropped.add(i);
|
|
26
|
+
fixedCount += 1;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const replacements = new Map();
|
|
30
|
+
for (const edit of findFixableCastEdits(files, readOriginal)) {
|
|
31
|
+
replacements.set(edit.rawIndex, edit.newContent);
|
|
32
|
+
fixedCount += 1;
|
|
33
|
+
}
|
|
34
|
+
const out = [];
|
|
35
|
+
for (let i = 0; i < rawLines.length; i++) {
|
|
36
|
+
if (dropped.has(i))
|
|
37
|
+
continue;
|
|
38
|
+
const replacement = replacements.get(i);
|
|
39
|
+
out.push(replacement === undefined ? rawLines[i] : `+${replacement}`);
|
|
40
|
+
}
|
|
41
|
+
return { fixedText: out.join("\n"), fixedCount };
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=fix.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fix.js","sourceRoot":"","sources":["../../src/delegate-gate/fix.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAQ3D;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgB,EAAE,YAA0B;IACzE,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACnD,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,KAAK,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAE3C,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC9B,IAAI,CAAC,0BAA0B,CAAC,IAAI,CAAC;gBAAE,SAAS;YAChD,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC,EAAE;gBAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAC5E,UAAU,IAAI,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IAED,MAAM,YAAY,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC/C,KAAK,MAAM,IAAI,IAAI,oBAAoB,CAAC,KAAK,EAAE,YAAY,CAAC,EAAE,CAAC;QAC7D,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QACjD,UAAU,IAAI,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;YAAE,SAAS;QAC7B,MAAM,WAAW,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACxC,GAAG,CAAC,IAAI,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC,CAAC;IACzE,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,CAAC;AACnD,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type DiffFile } from "./diff-parser.js";
|
|
2
|
+
import type { Verdict } from "./types.js";
|
|
3
|
+
export type ReadOriginal = (path: string) => string | null;
|
|
4
|
+
/** Read a file the diff modifies from `--repo` — the PRE-image every AST detector reconstructs
|
|
5
|
+
* the post-image against. Absent/unreadable is a legitimate outcome (a file the diff claims to
|
|
6
|
+
* modify but that does not exist at this repo root — quarantine to "no finding", never throw:
|
|
7
|
+
* the gate must still report on every OTHER file). */
|
|
8
|
+
export declare function repoFileReader(repoRoot: string): ReadOriginal;
|
|
9
|
+
/**
|
|
10
|
+
* Run every detector over a parsed diff and fold the results into one verdict.
|
|
11
|
+
*
|
|
12
|
+
* Each detector is independent and pure over (files, readOriginal); a detector throwing is a
|
|
13
|
+
* detector bug, not a caller concern, so callers should treat any throw here as "the gate could
|
|
14
|
+
* not evaluate this diff" rather than catch and silently drop a class.
|
|
15
|
+
*/
|
|
16
|
+
export declare function evaluateDiff(files: readonly DiffFile[], readOriginal: ReadOriginal): Verdict;
|
|
17
|
+
/** Parse a unified diff and evaluate it in one call — the entry point the CLI and tests use. */
|
|
18
|
+
export declare function evaluateDiffText(diffText: string, readOriginal: ReadOriginal): Verdict;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { parseUnifiedDiff } from "./diff-parser.js";
|
|
4
|
+
import { analyzeDiffMinimality } from "./minimality.js";
|
|
5
|
+
import { analyzeTestAssertions } from "./test-assertions.js";
|
|
6
|
+
import { analyzeCastNecessity } from "./cast-necessity.js";
|
|
7
|
+
import { analyzeSharedStateMutation } from "./shared-state.js";
|
|
8
|
+
/** Read a file the diff modifies from `--repo` — the PRE-image every AST detector reconstructs
|
|
9
|
+
* the post-image against. Absent/unreadable is a legitimate outcome (a file the diff claims to
|
|
10
|
+
* modify but that does not exist at this repo root — quarantine to "no finding", never throw:
|
|
11
|
+
* the gate must still report on every OTHER file). */
|
|
12
|
+
export function repoFileReader(repoRoot) {
|
|
13
|
+
return (path) => {
|
|
14
|
+
try {
|
|
15
|
+
return readFileSync(join(repoRoot, path), "utf8");
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Run every detector over a parsed diff and fold the results into one verdict.
|
|
24
|
+
*
|
|
25
|
+
* Each detector is independent and pure over (files, readOriginal); a detector throwing is a
|
|
26
|
+
* detector bug, not a caller concern, so callers should treat any throw here as "the gate could
|
|
27
|
+
* not evaluate this diff" rather than catch and silently drop a class.
|
|
28
|
+
*/
|
|
29
|
+
export function evaluateDiff(files, readOriginal) {
|
|
30
|
+
const findings = [
|
|
31
|
+
...analyzeDiffMinimality(files),
|
|
32
|
+
...analyzeTestAssertions(files, readOriginal),
|
|
33
|
+
...analyzeCastNecessity(files, readOriginal),
|
|
34
|
+
...analyzeSharedStateMutation(files, readOriginal),
|
|
35
|
+
];
|
|
36
|
+
return { pass: findings.length === 0, findings };
|
|
37
|
+
}
|
|
38
|
+
/** Parse a unified diff and evaluate it in one call — the entry point the CLI and tests use. */
|
|
39
|
+
export function evaluateDiffText(diffText, readOriginal) {
|
|
40
|
+
return evaluateDiff(parseUnifiedDiff(diffText), readOriginal);
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate.js","sourceRoot":"","sources":["../../src/delegate-gate/gate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,gBAAgB,EAAiB,MAAM,kBAAkB,CAAC;AACnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAK/D;;;sDAGsD;AACtD,MAAM,UAAU,cAAc,CAAC,QAAgB;IAC7C,OAAO,CAAC,IAAY,EAAiB,EAAE;QACrC,IAAI,CAAC;YACH,OAAO,YAAY,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;QACpD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,KAA0B,EAAE,YAA0B;IACjF,MAAM,QAAQ,GAAc;QAC1B,GAAG,qBAAqB,CAAC,KAAK,CAAC;QAC/B,GAAG,qBAAqB,CAAC,KAAK,EAAE,YAAY,CAAC;QAC7C,GAAG,oBAAoB,CAAC,KAAK,EAAE,YAAY,CAAC;QAC5C,GAAG,0BAA0B,CAAC,KAAK,EAAE,YAAY,CAAC;KACnD,CAAC;IACF,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC;AACnD,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,gBAAgB,CAAC,QAAgB,EAAE,YAA0B;IAC3E,OAAO,YAAY,CAAC,gBAAgB,CAAC,QAAQ,CAAC,EAAE,YAAY,CAAC,CAAC;AAChE,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { DiffFile, DiffHunk } from "./diff-parser.js";
|
|
2
|
+
import type { Finding } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Is this hunk PURE indentation churn: every removed and every added line accounted for by a
|
|
5
|
+
* whitespace-only pair, with nothing left unpaired?
|
|
6
|
+
*
|
|
7
|
+
* The "fully paired" requirement (every remove and every add line in the hunk is consumed by a
|
|
8
|
+
* 1:1 pair) is what makes the auto-fix in `fix.ts` safe: dropping a fully-paired churn hunk
|
|
9
|
+
* removes an equal number of old-side and new-side lines, so it can never shift another hunk's
|
|
10
|
+
* line numbers. A hunk containing an unequal-length remove/add run alongside pure churn is NOT
|
|
11
|
+
* flagged here — `pairedRemoveAddRuns` silently skips unequal runs, and treating the REST of the
|
|
12
|
+
* hunk as "100% churn" while ignoring lines it could not pair would both misreport the hunk and
|
|
13
|
+
* make an auto-fix unsafe.
|
|
14
|
+
*/
|
|
15
|
+
export declare function isPureIndentationChurnHunk(hunk: DiffHunk): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Flag indentation-only churn and count changed lines with no semantic delta.
|
|
18
|
+
*
|
|
19
|
+
* Two finding shapes:
|
|
20
|
+
* - `indentation-churn`: a hunk whose every line is accounted for by a whitespace-only
|
|
21
|
+
* remove/add pair — the hunk changed formatting and nothing else. Auto-fixable: dropping the
|
|
22
|
+
* whole hunk is safe (see `isPureIndentationChurnHunk`).
|
|
23
|
+
* - `non-minimal-diff`: a hunk that is NOT pure churn but where at least half its PAIRED changed
|
|
24
|
+
* lines (≥6 of them) carry no semantic delta — evidence of a diff that reformats lines it did
|
|
25
|
+
* not need to touch alongside a real change, i.e. class (e), "non-minimal diffs touching
|
|
26
|
+
* unrelated lines". Not auto-fixable: a partial hunk mixes a real edit with the noise, and
|
|
27
|
+
* removing only the noise without re-deriving the hunk boundaries risks corrupting the real
|
|
28
|
+
* change.
|
|
29
|
+
*/
|
|
30
|
+
export declare function analyzeDiffMinimality(files: readonly DiffFile[]): Finding[];
|