scai 0.1.88 → 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
@@ -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, language: forcedLang } = input;
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
- // Detect comments
14
- const isComment = (line) => {
15
- const ltrim = line.trimStart();
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 (ltrim.startsWith(s))
18
- return true;
23
+ if (trimmed.startsWith(s))
24
+ return "single-comment";
19
25
  }
20
- if (syntax.multiLine) {
21
- for (const { start } of syntax.multiLine) {
22
- if (ltrim.startsWith(start))
23
- return true;
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 false;
43
+ return "code";
27
44
  };
28
- const origLines = originalContent.split("\n");
29
- const newLines = content.split("\n");
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
- let newLine = newLines[newIndex] ?? "";
36
- // Preserve comments from model output if not already in original
37
- while (newIndex < newLines.length && isComment(newLine)) {
38
- if (origLine && newLine.trim() === origLine.trim()) {
39
- // Comment already exists in original, advance both
40
- fixedLines.push(origLine);
41
- origIndex++;
42
- newIndex++;
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
- // New comment from model
46
- fixedLines.push(newLine);
47
- newIndex++;
86
+ console.log("Skipping duplicate block (already inserted)");
48
87
  }
49
- newLine = newLines[newIndex] ?? "";
88
+ origIndex += origBlock.length;
89
+ newIndex += modelBlock.length;
90
+ continue;
50
91
  }
51
- if (newLine.trim() === origLine.trim()) {
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 (isComment(newLines[newIndex])) {
108
+ if (classifyLine(newLines[newIndex]) !== "code") {
67
109
  fixedLines.push(newLines[newIndex]);
68
110
  }
69
111
  newIndex++;
70
112
  }
71
- // Diff display
72
- console.log(chalk.bold.blue("\n=== ORIGINAL CONTENT ==="));
73
- console.log(chalk.green(originalContent));
74
- console.log(chalk.bold.blue("\n=== MODEL CONTENT ==="));
75
- console.log(chalk.gray(content));
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
- console.log(fixedLines
78
- .map(line => {
79
- const comment = isComment(line);
80
- if (origLines.includes(line.trim()) && !comment) {
81
- return chalk.green(line);
82
- }
83
- else if (comment) {
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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scai",
3
- "version": "0.1.88",
3
+ "version": "0.1.89",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "scai": "./dist/index.js"