@neurcode-ai/cli 0.9.49 → 0.9.50
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/commands/fix.d.ts +1 -0
- package/dist/commands/fix.d.ts.map +1 -1
- package/dist/commands/fix.js +408 -3
- package/dist/commands/fix.js.map +1 -1
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +16 -1
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/patch-apply.d.ts +7 -0
- package/dist/commands/patch-apply.d.ts.map +1 -0
- package/dist/commands/patch-apply.js +85 -0
- package/dist/commands/patch-apply.js.map +1 -0
- package/dist/commands/start-intent.d.ts.map +1 -1
- package/dist/commands/start-intent.js +17 -2
- package/dist/commands/start-intent.js.map +1 -1
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +110 -60
- package/dist/commands/verify.js.map +1 -1
- package/dist/context-engine/graph.d.ts +6 -0
- package/dist/context-engine/graph.d.ts.map +1 -0
- package/dist/context-engine/graph.js +55 -0
- package/dist/context-engine/graph.js.map +1 -0
- package/dist/context-engine/index.d.ts +14 -0
- package/dist/context-engine/index.d.ts.map +1 -0
- package/dist/context-engine/index.js +26 -0
- package/dist/context-engine/index.js.map +1 -0
- package/dist/context-engine/scanner.d.ts +6 -0
- package/dist/context-engine/scanner.d.ts.map +1 -0
- package/dist/context-engine/scanner.js +62 -0
- package/dist/context-engine/scanner.js.map +1 -0
- package/dist/context-engine/scorer.d.ts +9 -0
- package/dist/context-engine/scorer.d.ts.map +1 -0
- package/dist/context-engine/scorer.js +112 -0
- package/dist/context-engine/scorer.js.map +1 -0
- package/dist/context-engine/suggestions.d.ts +12 -0
- package/dist/context-engine/suggestions.d.ts.map +1 -0
- package/dist/context-engine/suggestions.js +22 -0
- package/dist/context-engine/suggestions.js.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/patch-engine/diff.d.ts +12 -0
- package/dist/patch-engine/diff.d.ts.map +1 -0
- package/dist/patch-engine/diff.js +74 -0
- package/dist/patch-engine/diff.js.map +1 -0
- package/dist/patch-engine/generator.d.ts +13 -0
- package/dist/patch-engine/generator.d.ts.map +1 -0
- package/dist/patch-engine/generator.js +51 -0
- package/dist/patch-engine/generator.js.map +1 -0
- package/dist/patch-engine/index.d.ts +47 -0
- package/dist/patch-engine/index.d.ts.map +1 -0
- package/dist/patch-engine/index.js +182 -0
- package/dist/patch-engine/index.js.map +1 -0
- package/dist/patch-engine/patterns.d.ts +4 -0
- package/dist/patch-engine/patterns.d.ts.map +1 -0
- package/dist/patch-engine/patterns.js +99 -0
- package/dist/patch-engine/patterns.js.map +1 -0
- package/package.json +8 -7
- package/LICENSE +0 -201
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateUnifiedDiff = generateUnifiedDiff;
|
|
4
|
+
const CONTEXT_LINES = 3;
|
|
5
|
+
/**
|
|
6
|
+
* Generate a minimal unified diff for a single-hunk change.
|
|
7
|
+
*
|
|
8
|
+
* Handles three cases without external libraries:
|
|
9
|
+
* - 1→1 replacement (db_in_ui)
|
|
10
|
+
* - 0→1 insertion (missing_validation: comment inserted before a line)
|
|
11
|
+
* - 1→0 deletion (todo_fixme: comment line removed)
|
|
12
|
+
*
|
|
13
|
+
* Returns an empty string when original === updated.
|
|
14
|
+
*/
|
|
15
|
+
function generateUnifiedDiff(filePath, original, updated) {
|
|
16
|
+
if (original === updated)
|
|
17
|
+
return '';
|
|
18
|
+
const origLines = original.split('\n');
|
|
19
|
+
const newLines = updated.split('\n');
|
|
20
|
+
// ── Forward scan: find first line that differs ──────────────────────────
|
|
21
|
+
let firstDiff = 0;
|
|
22
|
+
while (firstDiff < origLines.length &&
|
|
23
|
+
firstDiff < newLines.length &&
|
|
24
|
+
origLines[firstDiff] === newLines[firstDiff]) {
|
|
25
|
+
firstDiff++;
|
|
26
|
+
}
|
|
27
|
+
// ── Backward scan: find last differing line in each file ─────────────────
|
|
28
|
+
// Use `>=` so the scan can match lines at exactly firstDiff:
|
|
29
|
+
// - Deletion: lastDiffNew drops to firstDiff-1 (no added lines)
|
|
30
|
+
// - Insertion: lastDiffOrig drops to firstDiff-1 (no removed lines)
|
|
31
|
+
// - Replacement: both stop at firstDiff (the differing line)
|
|
32
|
+
// Safety: at firstDiff the forward scan guaranteed origLines[firstDiff] != newLines[firstDiff],
|
|
33
|
+
// so the equality check naturally stops the loop before both go below firstDiff.
|
|
34
|
+
let lastDiffOrig = origLines.length - 1;
|
|
35
|
+
let lastDiffNew = newLines.length - 1;
|
|
36
|
+
while (lastDiffOrig >= firstDiff &&
|
|
37
|
+
lastDiffNew >= firstDiff &&
|
|
38
|
+
origLines[lastDiffOrig] === newLines[lastDiffNew]) {
|
|
39
|
+
lastDiffOrig--;
|
|
40
|
+
lastDiffNew--;
|
|
41
|
+
}
|
|
42
|
+
// ── Context window ───────────────────────────────────────────────────────
|
|
43
|
+
const contextStart = Math.max(0, firstDiff - CONTEXT_LINES);
|
|
44
|
+
const contextEndOrig = Math.min(origLines.length - 1, lastDiffOrig + CONTEXT_LINES);
|
|
45
|
+
const contextEndNew = Math.min(newLines.length - 1, lastDiffNew + CONTEXT_LINES);
|
|
46
|
+
// Hunk header line counts (unified diff convention: 1-indexed, inclusive)
|
|
47
|
+
const oldStartLine = contextStart + 1;
|
|
48
|
+
const oldCount = contextEndOrig - contextStart + 1;
|
|
49
|
+
const newStartLine = contextStart + 1;
|
|
50
|
+
const newCount = contextEndNew - contextStart + 1;
|
|
51
|
+
const out = [
|
|
52
|
+
`--- a/${filePath}`,
|
|
53
|
+
`+++ b/${filePath}`,
|
|
54
|
+
`@@ -${oldStartLine},${oldCount} +${newStartLine},${newCount} @@`,
|
|
55
|
+
];
|
|
56
|
+
// Context lines before the change
|
|
57
|
+
for (let i = contextStart; i < firstDiff; i++) {
|
|
58
|
+
out.push(` ${origLines[i]}`);
|
|
59
|
+
}
|
|
60
|
+
// Removed lines (empty range when lastDiffOrig < firstDiff = pure insertion)
|
|
61
|
+
for (let i = firstDiff; i <= lastDiffOrig; i++) {
|
|
62
|
+
out.push(`-${origLines[i]}`);
|
|
63
|
+
}
|
|
64
|
+
// Added lines (empty range when lastDiffNew < firstDiff = pure deletion)
|
|
65
|
+
for (let i = firstDiff; i <= lastDiffNew; i++) {
|
|
66
|
+
out.push(`+${newLines[i]}`);
|
|
67
|
+
}
|
|
68
|
+
// Context lines after the change (taken from orig; content is identical in new)
|
|
69
|
+
for (let i = lastDiffOrig + 1; i <= contextEndOrig; i++) {
|
|
70
|
+
out.push(` ${origLines[i]}`);
|
|
71
|
+
}
|
|
72
|
+
return out.join('\n');
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/patch-engine/diff.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type PatternKind } from './patterns';
|
|
2
|
+
export type PatchInput = {
|
|
3
|
+
filePath: string;
|
|
4
|
+
issue: string;
|
|
5
|
+
policy: string;
|
|
6
|
+
fileContent: string;
|
|
7
|
+
patternKind: PatternKind;
|
|
8
|
+
};
|
|
9
|
+
export type PatchResult = {
|
|
10
|
+
updatedContent: string;
|
|
11
|
+
} | null;
|
|
12
|
+
export declare function generatePatch(input: PatchInput): PatchResult;
|
|
13
|
+
//# sourceMappingURL=generator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../src/patch-engine/generator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAE7D,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,WAAW,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,cAAc,EAAE,MAAM,CAAC;CACxB,GAAG,IAAI,CAAC;AAgCT,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,WAAW,CAqB5D"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generatePatch = generatePatch;
|
|
4
|
+
const patterns_1 = require("./patterns");
|
|
5
|
+
function leadingWhitespace(line) {
|
|
6
|
+
return line.match(/^(\s*)/)?.[1] ?? '';
|
|
7
|
+
}
|
|
8
|
+
// Replace the matched DB call line with a service-layer redirect comment.
|
|
9
|
+
function applyDbAccessFix(lines, lineIndex) {
|
|
10
|
+
const indent = leadingWhitespace(lines[lineIndex]);
|
|
11
|
+
const updated = [...lines];
|
|
12
|
+
updated[lineIndex] = `${indent}// [NEURCODE] Move to service layer — replace direct DB call with a service method`;
|
|
13
|
+
return updated;
|
|
14
|
+
}
|
|
15
|
+
// Insert a validation reminder line immediately before the req.body/params/query access.
|
|
16
|
+
function applyValidationFix(lines, lineIndex) {
|
|
17
|
+
const indent = leadingWhitespace(lines[lineIndex]);
|
|
18
|
+
const comment = `${indent}// [NEURCODE] Add validation — e.g. const { error } = schema.validate(req.body); ` +
|
|
19
|
+
`if (error) return res.status(400).json({ error: error.message });`;
|
|
20
|
+
const updated = [...lines];
|
|
21
|
+
updated.splice(lineIndex, 0, comment);
|
|
22
|
+
return updated;
|
|
23
|
+
}
|
|
24
|
+
// Remove the TODO/FIXME comment line entirely.
|
|
25
|
+
function applyTodoRemoval(lines, lineIndex) {
|
|
26
|
+
const updated = [...lines];
|
|
27
|
+
updated.splice(lineIndex, 1);
|
|
28
|
+
return updated;
|
|
29
|
+
}
|
|
30
|
+
function generatePatch(input) {
|
|
31
|
+
const lines = input.fileContent.split('\n');
|
|
32
|
+
const lineIndex = (0, patterns_1.detectPattern)(input.fileContent, input.patternKind);
|
|
33
|
+
if (lineIndex === null)
|
|
34
|
+
return null;
|
|
35
|
+
let updatedLines;
|
|
36
|
+
switch (input.patternKind) {
|
|
37
|
+
case 'db_in_ui':
|
|
38
|
+
updatedLines = applyDbAccessFix(lines, lineIndex);
|
|
39
|
+
break;
|
|
40
|
+
case 'missing_validation':
|
|
41
|
+
updatedLines = applyValidationFix(lines, lineIndex);
|
|
42
|
+
break;
|
|
43
|
+
case 'todo_fixme':
|
|
44
|
+
updatedLines = applyTodoRemoval(lines, lineIndex);
|
|
45
|
+
break;
|
|
46
|
+
default:
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
return { updatedContent: updatedLines.join('\n') };
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generator.js","sourceRoot":"","sources":["../../src/patch-engine/generator.ts"],"names":[],"mappings":";;AA4CA,sCAqBC;AAjED,yCAA6D;AAc7D,SAAS,iBAAiB,CAAC,IAAY;IACrC,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AACzC,CAAC;AAED,0EAA0E;AAC1E,SAAS,gBAAgB,CAAC,KAAe,EAAE,SAAiB;IAC1D,MAAM,MAAM,GAAG,iBAAiB,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;IACnD,MAAM,OAAO,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC;IAC3B,OAAO,CAAC,SAAS,CAAC,GAAG,GAAG,MAAM,oFAAoF,CAAC;IACnH,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,yFAAyF;AACzF,SAAS,kBAAkB,CAAC,KAAe,EAAE,SAAiB;IAC5D,MAAM,MAAM,GAAG,iBAAiB,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;IACnD,MAAM,OAAO,GACX,GAAG,MAAM,mFAAmF;QAC5F,mEAAmE,CAAC;IACtE,MAAM,OAAO,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC;IAC3B,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC;IACtC,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,+CAA+C;AAC/C,SAAS,gBAAgB,CAAC,KAAe,EAAE,SAAiB;IAC1D,MAAM,OAAO,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC;IAC3B,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;IAC7B,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAgB,aAAa,CAAC,KAAiB;IAC7C,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,SAAS,GAAG,IAAA,wBAAa,EAAC,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;IACtE,IAAI,SAAS,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAEpC,IAAI,YAAsB,CAAC;IAC3B,QAAQ,KAAK,CAAC,WAAW,EAAE,CAAC;QAC1B,KAAK,UAAU;YACb,YAAY,GAAG,gBAAgB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;YAClD,MAAM;QACR,KAAK,oBAAoB;YACvB,YAAY,GAAG,kBAAkB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;YACpD,MAAM;QACR,KAAK,YAAY;YACf,YAAY,GAAG,gBAAgB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;YAClD,MAAM;QACR;YACE,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,OAAO,EAAE,cAAc,EAAE,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AACrD,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type PatternKind } from './patterns';
|
|
2
|
+
export type { PatternKind };
|
|
3
|
+
export type PatchConfidence = 'high' | 'medium' | 'low';
|
|
4
|
+
export type SuggestionPatch = {
|
|
5
|
+
file: string;
|
|
6
|
+
diff: string;
|
|
7
|
+
patchConfidence: PatchConfidence;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Apply a unified diff (as produced by generateUnifiedDiff) to fileContent.
|
|
11
|
+
*
|
|
12
|
+
* Parses the single-hunk diff format, verifies every context and removal line
|
|
13
|
+
* matches the current file, then reconstructs the updated content.
|
|
14
|
+
*
|
|
15
|
+
* Returns null when:
|
|
16
|
+
* - no hunk header found
|
|
17
|
+
* - a context or removal line does not match current file content (file changed)
|
|
18
|
+
*/
|
|
19
|
+
export declare function applyUnifiedDiff(fileContent: string, diff: string): string | null;
|
|
20
|
+
/**
|
|
21
|
+
* Detect the first matching patchable pattern in fileContent and return the
|
|
22
|
+
* updated content. Tries patterns in priority order: db_in_ui → missing_validation
|
|
23
|
+
* → todo_fixme. Validates safety before returning.
|
|
24
|
+
*
|
|
25
|
+
* Used by `neurcode patch --file` to apply a patch without needing suggestion metadata.
|
|
26
|
+
*/
|
|
27
|
+
export declare function applyFirstMatchingPatch(filePath: string, fileContent: string): {
|
|
28
|
+
updatedContent: string;
|
|
29
|
+
patternKind: PatternKind;
|
|
30
|
+
patchConfidence: PatchConfidence;
|
|
31
|
+
} | null;
|
|
32
|
+
/**
|
|
33
|
+
* Given a fix suggestion and the current content of suggestion.file,
|
|
34
|
+
* attempts to generate a deterministic, safety-validated code patch.
|
|
35
|
+
*
|
|
36
|
+
* Returns null when:
|
|
37
|
+
* - the violation type has no patchable pattern
|
|
38
|
+
* - the pattern is not found in the file content
|
|
39
|
+
* - the generated patch produces no diff
|
|
40
|
+
* - the patch fails the safety gate (isPatchSafe)
|
|
41
|
+
*/
|
|
42
|
+
export declare function generatePatchForSuggestion(suggestion: {
|
|
43
|
+
file: string;
|
|
44
|
+
issue: string;
|
|
45
|
+
policy: string;
|
|
46
|
+
}, fileContent: string): SuggestionPatch | null;
|
|
47
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/patch-engine/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAIjE,YAAY,EAAE,WAAW,EAAE,CAAC;AAE5B,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;AAExD,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,EAAE,eAAe,CAAC;CAClC,CAAC;AAqCF;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiEjF;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,GAClB;IAAE,cAAc,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,WAAW,CAAC;IAAC,eAAe,EAAE,eAAe,CAAA;CAAE,GAAG,IAAI,CA0B/F;AAED;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CACxC,UAAU,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAC3D,WAAW,EAAE,MAAM,GAClB,eAAe,GAAG,IAAI,CAuBxB"}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.applyUnifiedDiff = applyUnifiedDiff;
|
|
4
|
+
exports.applyFirstMatchingPatch = applyFirstMatchingPatch;
|
|
5
|
+
exports.generatePatchForSuggestion = generatePatchForSuggestion;
|
|
6
|
+
const patterns_1 = require("./patterns");
|
|
7
|
+
const generator_1 = require("./generator");
|
|
8
|
+
const diff_1 = require("./diff");
|
|
9
|
+
// Patterns that must appear in original content for a patch to be considered safe.
|
|
10
|
+
const PATCHABLE_PATTERN_RE = /db\.(query|execute|run|find[A-Za-z]*)\b|prisma\.\w+\.\w+\b|new\s+Pool\s*\(|knex\s*\(|TODO|FIXME|\bvalidat/i;
|
|
11
|
+
/**
|
|
12
|
+
* A patch is safe when:
|
|
13
|
+
* - updated content is non-empty
|
|
14
|
+
* - the diff is non-empty (something actually changed)
|
|
15
|
+
* - total added + removed lines ≤ 5 (not a full-file rewrite)
|
|
16
|
+
* - the original file contains at least one recognizable patchable pattern
|
|
17
|
+
*/
|
|
18
|
+
function isPatchSafe(original, updated) {
|
|
19
|
+
if (!updated || !updated.trim())
|
|
20
|
+
return false;
|
|
21
|
+
const diff = (0, diff_1.generateUnifiedDiff)('', original, updated);
|
|
22
|
+
if (!diff)
|
|
23
|
+
return false;
|
|
24
|
+
let changed = 0;
|
|
25
|
+
for (const line of diff.split('\n')) {
|
|
26
|
+
if (line.startsWith('-') && !line.startsWith('---'))
|
|
27
|
+
changed++;
|
|
28
|
+
if (line.startsWith('+') && !line.startsWith('+++'))
|
|
29
|
+
changed++;
|
|
30
|
+
}
|
|
31
|
+
if (changed > 5)
|
|
32
|
+
return false;
|
|
33
|
+
if (!PATCHABLE_PATTERN_RE.test(original))
|
|
34
|
+
return false;
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
function scorePatchConfidence(kind) {
|
|
38
|
+
if (kind === 'db_in_ui')
|
|
39
|
+
return 'high';
|
|
40
|
+
if (kind === 'missing_validation')
|
|
41
|
+
return 'medium';
|
|
42
|
+
return 'low'; // todo_fixme — simple removal, lowest confidence
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Apply a unified diff (as produced by generateUnifiedDiff) to fileContent.
|
|
46
|
+
*
|
|
47
|
+
* Parses the single-hunk diff format, verifies every context and removal line
|
|
48
|
+
* matches the current file, then reconstructs the updated content.
|
|
49
|
+
*
|
|
50
|
+
* Returns null when:
|
|
51
|
+
* - no hunk header found
|
|
52
|
+
* - a context or removal line does not match current file content (file changed)
|
|
53
|
+
*/
|
|
54
|
+
function applyUnifiedDiff(fileContent, diff) {
|
|
55
|
+
if (!diff)
|
|
56
|
+
return null;
|
|
57
|
+
const diffLines = diff.split('\n');
|
|
58
|
+
// Locate the hunk header (skip --- / +++ file headers)
|
|
59
|
+
let hunkIdx = -1;
|
|
60
|
+
for (let i = 0; i < diffLines.length; i++) {
|
|
61
|
+
if (diffLines[i].startsWith('@@')) {
|
|
62
|
+
hunkIdx = i;
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (hunkIdx === -1)
|
|
67
|
+
return null;
|
|
68
|
+
// Parse @@ -oldStart[,oldCount] +newStart[,newCount] @@
|
|
69
|
+
const match = diffLines[hunkIdx].match(/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
|
|
70
|
+
if (!match)
|
|
71
|
+
return null;
|
|
72
|
+
// Diff uses 1-indexed lines; convert to 0-indexed
|
|
73
|
+
const origStart = parseInt(match[1], 10) - 1;
|
|
74
|
+
const origLines = fileContent.split('\n');
|
|
75
|
+
const output = [];
|
|
76
|
+
// Lines before the hunk are copied unchanged
|
|
77
|
+
for (let i = 0; i < origStart; i++) {
|
|
78
|
+
output.push(origLines[i] ?? '');
|
|
79
|
+
}
|
|
80
|
+
let origIdx = origStart;
|
|
81
|
+
for (let i = hunkIdx + 1; i < diffLines.length; i++) {
|
|
82
|
+
const line = diffLines[i];
|
|
83
|
+
// A trailing empty string from split('\n') signals end of diff
|
|
84
|
+
if (line.length === 0 && i === diffLines.length - 1)
|
|
85
|
+
break;
|
|
86
|
+
const prefix = line[0];
|
|
87
|
+
const content = line.slice(1);
|
|
88
|
+
if (prefix === ' ') {
|
|
89
|
+
// Context: must match current file — abort on mismatch (file changed)
|
|
90
|
+
if (origIdx >= origLines.length || origLines[origIdx] !== content)
|
|
91
|
+
return null;
|
|
92
|
+
output.push(content);
|
|
93
|
+
origIdx++;
|
|
94
|
+
}
|
|
95
|
+
else if (prefix === '-') {
|
|
96
|
+
// Removal: must match current file — abort on mismatch
|
|
97
|
+
if (origIdx >= origLines.length || origLines[origIdx] !== content)
|
|
98
|
+
return null;
|
|
99
|
+
origIdx++; // consume original line without adding to output
|
|
100
|
+
}
|
|
101
|
+
else if (prefix === '+') {
|
|
102
|
+
// Addition: inject into output without consuming original
|
|
103
|
+
output.push(content);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
break; // unexpected prefix — stop hunk processing
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Copy remaining original lines after the hunk
|
|
110
|
+
while (origIdx < origLines.length) {
|
|
111
|
+
output.push(origLines[origIdx]);
|
|
112
|
+
origIdx++;
|
|
113
|
+
}
|
|
114
|
+
return output.join('\n');
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Detect the first matching patchable pattern in fileContent and return the
|
|
118
|
+
* updated content. Tries patterns in priority order: db_in_ui → missing_validation
|
|
119
|
+
* → todo_fixme. Validates safety before returning.
|
|
120
|
+
*
|
|
121
|
+
* Used by `neurcode patch --file` to apply a patch without needing suggestion metadata.
|
|
122
|
+
*/
|
|
123
|
+
function applyFirstMatchingPatch(filePath, fileContent) {
|
|
124
|
+
const kinds = ['db_in_ui', 'missing_validation', 'todo_fixme'];
|
|
125
|
+
for (const kind of kinds) {
|
|
126
|
+
const result = (0, generator_1.generatePatch)({
|
|
127
|
+
filePath,
|
|
128
|
+
issue: '',
|
|
129
|
+
policy: '',
|
|
130
|
+
fileContent,
|
|
131
|
+
patternKind: kind,
|
|
132
|
+
});
|
|
133
|
+
if (!result)
|
|
134
|
+
continue;
|
|
135
|
+
const diff = (0, diff_1.generateUnifiedDiff)(filePath, fileContent, result.updatedContent);
|
|
136
|
+
if (!diff)
|
|
137
|
+
continue;
|
|
138
|
+
if (!isPatchSafe(fileContent, result.updatedContent))
|
|
139
|
+
continue;
|
|
140
|
+
return {
|
|
141
|
+
updatedContent: result.updatedContent,
|
|
142
|
+
patternKind: kind,
|
|
143
|
+
patchConfidence: scorePatchConfidence(kind),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Given a fix suggestion and the current content of suggestion.file,
|
|
150
|
+
* attempts to generate a deterministic, safety-validated code patch.
|
|
151
|
+
*
|
|
152
|
+
* Returns null when:
|
|
153
|
+
* - the violation type has no patchable pattern
|
|
154
|
+
* - the pattern is not found in the file content
|
|
155
|
+
* - the generated patch produces no diff
|
|
156
|
+
* - the patch fails the safety gate (isPatchSafe)
|
|
157
|
+
*/
|
|
158
|
+
function generatePatchForSuggestion(suggestion, fileContent) {
|
|
159
|
+
const kind = (0, patterns_1.classifyViolation)(suggestion.issue, suggestion.policy);
|
|
160
|
+
if (!kind)
|
|
161
|
+
return null;
|
|
162
|
+
const result = (0, generator_1.generatePatch)({
|
|
163
|
+
filePath: suggestion.file,
|
|
164
|
+
issue: suggestion.issue,
|
|
165
|
+
policy: suggestion.policy,
|
|
166
|
+
fileContent,
|
|
167
|
+
patternKind: kind,
|
|
168
|
+
});
|
|
169
|
+
if (!result)
|
|
170
|
+
return null;
|
|
171
|
+
const diff = (0, diff_1.generateUnifiedDiff)(suggestion.file, fileContent, result.updatedContent);
|
|
172
|
+
if (!diff)
|
|
173
|
+
return null;
|
|
174
|
+
if (!isPatchSafe(fileContent, result.updatedContent))
|
|
175
|
+
return null;
|
|
176
|
+
return {
|
|
177
|
+
file: suggestion.file,
|
|
178
|
+
diff,
|
|
179
|
+
patchConfidence: scorePatchConfidence(kind),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/patch-engine/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type PatternKind = 'db_in_ui' | 'missing_validation' | 'todo_fixme';
|
|
2
|
+
export declare function classifyViolation(issue: string, policy: string): PatternKind | null;
|
|
3
|
+
export declare function detectPattern(content: string, kind: PatternKind): number | null;
|
|
4
|
+
//# sourceMappingURL=patterns.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"patterns.d.ts","sourceRoot":"","sources":["../../src/patch-engine/patterns.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,oBAAoB,GAAG,YAAY,CAAC;AAM3E,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAsBnF;AA2ED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,MAAM,GAAG,IAAI,CAO/E"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Detection rules for deterministic patch generation.
|
|
3
|
+
// Each function returns the 0-based line index of the first match, or null.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.classifyViolation = classifyViolation;
|
|
6
|
+
exports.detectPattern = detectPattern;
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Classifiy a fix suggestion into a patchable pattern kind
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
function classifyViolation(issue, policy) {
|
|
11
|
+
const combined = `${issue} ${policy}`.toLowerCase();
|
|
12
|
+
if (combined.includes('todo') || combined.includes('fixme'))
|
|
13
|
+
return 'todo_fixme';
|
|
14
|
+
if (combined.includes('db') || combined.includes('database') ||
|
|
15
|
+
combined.includes('query') || combined.includes('data access') ||
|
|
16
|
+
combined.includes('direct access') ||
|
|
17
|
+
policy.includes('layer') || policy.includes('layering') || policy.includes('db')) {
|
|
18
|
+
return 'db_in_ui';
|
|
19
|
+
}
|
|
20
|
+
if (combined.includes('validation') || combined.includes('validate') ||
|
|
21
|
+
combined.includes('input') || policy.includes('validation')) {
|
|
22
|
+
return 'missing_validation';
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Pattern detectors (work on pre-split line arrays)
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
const DB_ACCESS_PATTERNS = [
|
|
30
|
+
/\bdb\s*\.\s*query\s*\(/,
|
|
31
|
+
/\bdb\s*\.\s*execute\s*\(/,
|
|
32
|
+
/\bdb\s*\.\s*run\s*\(/,
|
|
33
|
+
/\bdb\s*\.\s*find\b/,
|
|
34
|
+
/\bdb\s*\.\s*findOne\s*\(/,
|
|
35
|
+
/\bprisma\s*\.\s*\w+\s*\.\s*find/,
|
|
36
|
+
/\bprisma\s*\.\s*\w+\s*\.\s*create\s*\(/,
|
|
37
|
+
/\bprisma\s*\.\s*\w+\s*\.\s*update\s*\(/,
|
|
38
|
+
/\bprisma\s*\.\s*\w+\s*\.\s*delete\s*\(/,
|
|
39
|
+
/\bnew\s+Pool\s*\(/,
|
|
40
|
+
/\bknex\s*\(/,
|
|
41
|
+
];
|
|
42
|
+
const VALIDATION_PATTERNS = [
|
|
43
|
+
/\.validate\s*\(/,
|
|
44
|
+
/schema\.parse\s*\(/,
|
|
45
|
+
/\bJoi\s*\./,
|
|
46
|
+
/\byup\s*\./,
|
|
47
|
+
/\bzod\s*\./,
|
|
48
|
+
/\bajv\s*\.\s*compile/,
|
|
49
|
+
];
|
|
50
|
+
// Matches the request/response parameter pair in a handler signature
|
|
51
|
+
const REQ_HANDLER_RE = /\b(?:req|request)\s*,\s*(?:res|response|reply)\b/;
|
|
52
|
+
// Matches direct access to incoming data without a prior validation call
|
|
53
|
+
const REQ_INPUT_RE = /\b(?:req|request)\.(?:body|params|query)\b/;
|
|
54
|
+
const TODO_FIXME_RE = /\/\/\s*(?:TODO|FIXME)\b/;
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
function findDbAccessLine(lines) {
|
|
57
|
+
for (let i = 0; i < lines.length; i++) {
|
|
58
|
+
const trimmed = lines[i].trimStart();
|
|
59
|
+
// Skip lines that are themselves comments
|
|
60
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('*'))
|
|
61
|
+
continue;
|
|
62
|
+
if (DB_ACCESS_PATTERNS.some((re) => re.test(lines[i])))
|
|
63
|
+
return i;
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
function findMissingValidationLine(lines) {
|
|
68
|
+
let handlerStartIndex = -1;
|
|
69
|
+
for (let i = 0; i < lines.length; i++) {
|
|
70
|
+
if (REQ_HANDLER_RE.test(lines[i])) {
|
|
71
|
+
handlerStartIndex = i;
|
|
72
|
+
}
|
|
73
|
+
if (handlerStartIndex !== -1 && REQ_INPUT_RE.test(lines[i])) {
|
|
74
|
+
// Look backward from this line (within the handler) for a validation call
|
|
75
|
+
const searchFrom = Math.max(handlerStartIndex, i - 30);
|
|
76
|
+
const priorLines = lines.slice(searchFrom, i);
|
|
77
|
+
const hasValidation = priorLines.some((l) => VALIDATION_PATTERNS.some((re) => re.test(l)));
|
|
78
|
+
if (!hasValidation)
|
|
79
|
+
return i;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
function findTodoLine(lines) {
|
|
85
|
+
for (let i = 0; i < lines.length; i++) {
|
|
86
|
+
if (TODO_FIXME_RE.test(lines[i]))
|
|
87
|
+
return i;
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
function detectPattern(content, kind) {
|
|
92
|
+
const lines = content.split('\n');
|
|
93
|
+
switch (kind) {
|
|
94
|
+
case 'db_in_ui': return findDbAccessLine(lines);
|
|
95
|
+
case 'missing_validation': return findMissingValidationLine(lines);
|
|
96
|
+
case 'todo_fixme': return findTodoLine(lines);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=patterns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"patterns.js","sourceRoot":"","sources":["../../src/patch-engine/patterns.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neurcode-ai/cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.50",
|
|
4
4
|
"description": "Neurcode CLI - AI code governance and diff analysis",
|
|
5
5
|
"bin": {
|
|
6
6
|
"neurcode": "dist/index.js"
|
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
"dist",
|
|
12
12
|
"README.md"
|
|
13
13
|
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "rm -rf dist && tsc && chmod +x dist/index.js",
|
|
16
|
+
"dev": "tsc --watch",
|
|
17
|
+
"start": "node dist/index.js",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
14
20
|
"keywords": [
|
|
15
21
|
"cli",
|
|
16
22
|
"code-governance",
|
|
@@ -61,10 +67,5 @@
|
|
|
61
67
|
"@types/node": "^20.10.0",
|
|
62
68
|
"@types/uuid": "^9.0.8",
|
|
63
69
|
"typescript": "^5.3.0"
|
|
64
|
-
},
|
|
65
|
-
"scripts": {
|
|
66
|
-
"build": "rm -rf dist && tsc && chmod +x dist/index.js",
|
|
67
|
-
"dev": "tsc --watch",
|
|
68
|
-
"start": "node dist/index.js"
|
|
69
70
|
}
|
|
70
|
-
}
|
|
71
|
+
}
|