scai 0.1.87 → 0.1.89
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/CHANGELOG.md
CHANGED
|
@@ -107,4 +107,9 @@ Type handling with the module pipeline
|
|
|
107
107
|
* Split file processing into logical chunks with logging.
|
|
108
108
|
* Improved commit suggestion generation logic.
|
|
109
109
|
* Removed unnecessary files and updated index.ts to use handleAgentRun instead.
|
|
110
|
-
* Fetch download statistics from NPM API.
|
|
110
|
+
* Fetch download statistics from NPM API.
|
|
111
|
+
|
|
112
|
+
## 2025-08-16
|
|
113
|
+
|
|
114
|
+
• Update preserveCodeModule to compare blocks correctly and handle duplicate blocks
|
|
115
|
+
• Normalize line endings and detect comments in preserveCodeModule
|
package/dist/agentManager.js
CHANGED
|
@@ -17,10 +17,10 @@ export async function handleAgentRun(filepath, modules) {
|
|
|
17
17
|
const processedChunks = [];
|
|
18
18
|
for (const [i, chunk] of chunks.entries()) {
|
|
19
19
|
const chunkTokens = countTokens(chunk);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
if (i === 0) {
|
|
21
|
+
console.log(chalk.cyan(`🔍 Processing ${chunks.length} chunks of file:`), chalk.white(filepath));
|
|
22
|
+
}
|
|
23
|
+
console.log(chalk.gray(` - Chunk ${i + 1} tokens:`), chalk.yellow(chunkTokens.toString()));
|
|
24
24
|
const chunkInput = {
|
|
25
25
|
originalContent: chunk,
|
|
26
26
|
content: chunk,
|
|
@@ -308,8 +308,8 @@ export async function promptChunkReviewMenu() {
|
|
|
308
308
|
console.log(' 2) ✍️ Edit the review before posting');
|
|
309
309
|
console.log(' 3) ⌨️ Write a custom comment');
|
|
310
310
|
console.log(' 4) ❌ Mark this chunk as needing changes');
|
|
311
|
-
console.log(' 5) ⏭️
|
|
312
|
-
console.log(chalk.gray(' (Press [space] to skip chunk, [q] to quit review, or any other key to show menu)\n'));
|
|
311
|
+
console.log(' 5) ⏭️ Approve this chunk without commenting');
|
|
312
|
+
console.log(chalk.gray(' (Press [space] to skip and approve chunk, [q] to quit review, or any other key to show menu)\n'));
|
|
313
313
|
// Fallback to menu input if key was not space/q
|
|
314
314
|
function askWithReadline() {
|
|
315
315
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -434,8 +434,8 @@ export async function reviewPullRequestCmd(branch = 'main', showAll = false) {
|
|
|
434
434
|
// We always submit comments first if any
|
|
435
435
|
const initialReviewState = shouldApprove ? 'APPROVE' : 'REQUEST_CHANGES';
|
|
436
436
|
const initialReviewBody = shouldApprove
|
|
437
|
-
? 'Reviewed
|
|
438
|
-
: 'Requested changes based on
|
|
437
|
+
? '✅ Reviewed.'
|
|
438
|
+
: '⛔ Requested changes based on review.';
|
|
439
439
|
console.log(shouldApprove && !hasInlineComments
|
|
440
440
|
? chalk.green('✔️ All chunks approved. Submitting final PR approval.')
|
|
441
441
|
: !shouldApprove
|
|
@@ -3,91 +3,138 @@ export const preserveCodeModule = {
|
|
|
3
3
|
name: "preserveCodeModule",
|
|
4
4
|
description: "Ensure code matches original exactly, preserving comments with clear before/after output",
|
|
5
5
|
async run(input) {
|
|
6
|
-
const { originalContent, content, filepath
|
|
6
|
+
const { originalContent, content, filepath } = input;
|
|
7
7
|
if (!originalContent)
|
|
8
8
|
throw new Error("Requires `originalContent`.");
|
|
9
9
|
const syntax = {
|
|
10
10
|
singleLine: ["//"],
|
|
11
11
|
multiLine: [{ start: "/*", end: "*/" }]
|
|
12
12
|
};
|
|
13
|
-
//
|
|
14
|
-
const
|
|
15
|
-
|
|
13
|
+
// --- Normalize line endings ---
|
|
14
|
+
const normalize = (txt) => txt.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
15
|
+
const origLines = normalize(originalContent).split("\n");
|
|
16
|
+
const newLines = normalize(content).split("\n");
|
|
17
|
+
// Detect if a line is a comment line, and classify
|
|
18
|
+
let inBlockComment = false;
|
|
19
|
+
const classifyLine = (line) => {
|
|
20
|
+
const trimmed = line.trimStart();
|
|
21
|
+
// Single-line
|
|
16
22
|
for (const s of syntax.singleLine) {
|
|
17
|
-
if (
|
|
18
|
-
return
|
|
23
|
+
if (trimmed.startsWith(s))
|
|
24
|
+
return "single-comment";
|
|
19
25
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
// Multi-line start/end
|
|
27
|
+
for (const { start, end } of syntax.multiLine) {
|
|
28
|
+
if (trimmed.startsWith(start) && trimmed.includes(end)) {
|
|
29
|
+
return "multi-comment (start+end)";
|
|
30
|
+
}
|
|
31
|
+
if (trimmed.startsWith(start)) {
|
|
32
|
+
inBlockComment = true;
|
|
33
|
+
return "multi-comment (start)";
|
|
34
|
+
}
|
|
35
|
+
if (inBlockComment) {
|
|
36
|
+
if (trimmed.includes(end)) {
|
|
37
|
+
inBlockComment = false;
|
|
38
|
+
return "multi-comment (end)";
|
|
39
|
+
}
|
|
40
|
+
return "multi-comment (mid)";
|
|
24
41
|
}
|
|
25
42
|
}
|
|
26
|
-
return
|
|
43
|
+
return "code";
|
|
27
44
|
};
|
|
28
|
-
|
|
29
|
-
|
|
45
|
+
// Collect consecutive comment lines as one block
|
|
46
|
+
function collectBlock(lines, startIndex) {
|
|
47
|
+
const block = [];
|
|
48
|
+
let i = startIndex;
|
|
49
|
+
while (i < lines.length && classifyLine(lines[i]) !== "code") {
|
|
50
|
+
block.push(lines[i]);
|
|
51
|
+
i++;
|
|
52
|
+
}
|
|
53
|
+
return block;
|
|
54
|
+
}
|
|
55
|
+
const trimBlock = (block) => block.map(line => line.trim());
|
|
56
|
+
const blocksEqual = (a, b) => JSON.stringify(trimBlock(a)) === JSON.stringify(trimBlock(b));
|
|
30
57
|
const fixedLines = [];
|
|
31
58
|
let origIndex = 0;
|
|
32
59
|
let newIndex = 0;
|
|
33
|
-
while (origIndex < origLines.length) {
|
|
60
|
+
while (origIndex < origLines.length && newIndex < newLines.length) {
|
|
34
61
|
const origLine = origLines[origIndex];
|
|
35
|
-
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
62
|
+
const newLine = newLines[newIndex];
|
|
63
|
+
// If either current line is a comment → treat whole comment block
|
|
64
|
+
let lastInsertedModelBlock = [];
|
|
65
|
+
if (classifyLine(origLine) !== "code" || classifyLine(newLine) !== "code") {
|
|
66
|
+
const origBlock = collectBlock(origLines, origIndex);
|
|
67
|
+
const modelBlock = collectBlock(newLines, newIndex);
|
|
68
|
+
// Compare with last inserted block
|
|
69
|
+
if (!blocksEqual(modelBlock, lastInsertedModelBlock)) {
|
|
70
|
+
if (blocksEqual(origBlock, modelBlock)) {
|
|
71
|
+
fixedLines.push(...origBlock);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
const seen = new Set(trimBlock(modelBlock));
|
|
75
|
+
fixedLines.push(...modelBlock);
|
|
76
|
+
for (const line of origBlock) {
|
|
77
|
+
if (!seen.has(line.trim())) {
|
|
78
|
+
fixedLines.push(line);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Update lastInsertedModelBlock
|
|
83
|
+
lastInsertedModelBlock = [...modelBlock];
|
|
43
84
|
}
|
|
44
85
|
else {
|
|
45
|
-
|
|
46
|
-
fixedLines.push(newLine);
|
|
47
|
-
newIndex++;
|
|
86
|
+
console.log("Skipping duplicate block (already inserted)");
|
|
48
87
|
}
|
|
49
|
-
|
|
88
|
+
origIndex += origBlock.length;
|
|
89
|
+
newIndex += modelBlock.length;
|
|
90
|
+
continue;
|
|
50
91
|
}
|
|
51
|
-
|
|
92
|
+
// Non-comment lines
|
|
93
|
+
if (origLine.trim() === newLine.trim()) {
|
|
52
94
|
fixedLines.push(newLine);
|
|
53
|
-
origIndex++;
|
|
54
|
-
newIndex++;
|
|
55
95
|
}
|
|
56
96
|
else {
|
|
57
97
|
fixedLines.push(origLine);
|
|
58
|
-
origIndex++;
|
|
59
|
-
if (!isComment(newLine)) {
|
|
60
|
-
newIndex++;
|
|
61
|
-
}
|
|
62
98
|
}
|
|
99
|
+
origIndex++;
|
|
100
|
+
newIndex++;
|
|
101
|
+
}
|
|
102
|
+
// Add trailing lines from original (if model ran out first)
|
|
103
|
+
while (origIndex < origLines.length) {
|
|
104
|
+
fixedLines.push(origLines[origIndex++]);
|
|
63
105
|
}
|
|
64
106
|
// Add trailing comments from model
|
|
65
107
|
while (newIndex < newLines.length) {
|
|
66
|
-
if (
|
|
108
|
+
if (classifyLine(newLines[newIndex]) !== "code") {
|
|
67
109
|
fixedLines.push(newLines[newIndex]);
|
|
68
110
|
}
|
|
69
111
|
newIndex++;
|
|
70
112
|
}
|
|
71
|
-
//
|
|
72
|
-
console.log(chalk.bold.blue("\n===
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
113
|
+
// --- Logging for debugging ---
|
|
114
|
+
console.log(chalk.bold.blue("\n=== LINE CLASSIFICATION (original) ==="));
|
|
115
|
+
origLines.forEach((line, i) => {
|
|
116
|
+
const type = classifyLine(line);
|
|
117
|
+
const colored = type === "code"
|
|
118
|
+
? chalk.green(line)
|
|
119
|
+
: chalk.yellow(line);
|
|
120
|
+
console.log(`${i + 1}: ${colored} ${chalk.gray(`[${type}]`)}`);
|
|
121
|
+
});
|
|
122
|
+
console.log(chalk.bold.blue("\n=== LINE CLASSIFICATION (model) ==="));
|
|
123
|
+
newLines.forEach((line, i) => {
|
|
124
|
+
const type = classifyLine(line);
|
|
125
|
+
const colored = type === "code"
|
|
126
|
+
? chalk.green(line)
|
|
127
|
+
: chalk.yellow(line);
|
|
128
|
+
console.log(`${i + 1}: ${colored} ${chalk.gray(`[${type}]`)}`);
|
|
129
|
+
});
|
|
76
130
|
console.log(chalk.bold.blue("\n=== FIXED CONTENT ==="));
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return chalk.yellow(line);
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
return chalk.red(line);
|
|
88
|
-
}
|
|
89
|
-
})
|
|
90
|
-
.join("\n"));
|
|
131
|
+
fixedLines.forEach((line, i) => {
|
|
132
|
+
const type = classifyLine(line);
|
|
133
|
+
const colored = type === "code"
|
|
134
|
+
? chalk.green(line)
|
|
135
|
+
: chalk.yellow(line);
|
|
136
|
+
console.log(`${i + 1}: ${colored} ${chalk.gray(`[${type}]`)}`);
|
|
137
|
+
});
|
|
91
138
|
return { content: fixedLines.join("\n"), filepath };
|
|
92
139
|
}
|
|
93
140
|
};
|