scai 0.1.101 → 0.1.103
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/fileRules/detectFileType.js +45 -48
- package/dist/index.js +2 -1
- package/dist/pipeline/modules/commentModule.js +4 -14
- package/dist/pipeline/modules/generateTestsModule.js +16 -13
- package/dist/pipeline/modules/preserveCodeModule.js +71 -73
- package/dist/pipeline/runModulePipeline.js +5 -2
- package/dist/utils/commentMap.js +2 -1
- package/dist/utils/resolveTargetsToFiles.js +1 -1
- package/package.json +1 -1
|
@@ -1,52 +1,49 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
-
export function detectFileType(
|
|
3
|
-
|
|
2
|
+
export function detectFileType(filepathOrExt) {
|
|
3
|
+
// If it's already an extension (starts with '.'), use it directly
|
|
4
|
+
const ext = filepathOrExt.startsWith(".")
|
|
5
|
+
? filepathOrExt.toLowerCase()
|
|
6
|
+
: path.extname(filepathOrExt).toLowerCase();
|
|
4
7
|
const map = {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
'.docx': 'word',
|
|
45
|
-
'.pdf': 'pdf',
|
|
46
|
-
'.ppt': 'powerpoint',
|
|
47
|
-
'.pptx': 'powerpoint',
|
|
48
|
-
'.xls': 'excel',
|
|
49
|
-
'.xlsx': 'excel',
|
|
8
|
+
".ts": "typescript",
|
|
9
|
+
".tsx": "typescript",
|
|
10
|
+
".js": "javascript",
|
|
11
|
+
".jsx": "javascript",
|
|
12
|
+
".java": "java",
|
|
13
|
+
".py": "python",
|
|
14
|
+
".rb": "ruby",
|
|
15
|
+
".php": "php",
|
|
16
|
+
".go": "go",
|
|
17
|
+
".rs": "rust",
|
|
18
|
+
".c": "c",
|
|
19
|
+
".cpp": "cpp",
|
|
20
|
+
".cs": "csharp",
|
|
21
|
+
".swift": "swift",
|
|
22
|
+
".kt": "kotlin",
|
|
23
|
+
".scala": "scala",
|
|
24
|
+
".md": "markdown",
|
|
25
|
+
".html": "html",
|
|
26
|
+
".htm": "html",
|
|
27
|
+
".xml": "xml",
|
|
28
|
+
".json": "json",
|
|
29
|
+
".yaml": "yaml",
|
|
30
|
+
".yml": "yaml",
|
|
31
|
+
".ini": "config",
|
|
32
|
+
".toml": "config",
|
|
33
|
+
".env": "config",
|
|
34
|
+
".sql": "sql",
|
|
35
|
+
".csv": "csv",
|
|
36
|
+
".tsv": "tsv",
|
|
37
|
+
".txt": "text",
|
|
38
|
+
".log": "log",
|
|
39
|
+
".rst": "text",
|
|
40
|
+
".doc": "word",
|
|
41
|
+
".docx": "word",
|
|
42
|
+
".pdf": "pdf",
|
|
43
|
+
".ppt": "powerpoint",
|
|
44
|
+
".pptx": "powerpoint",
|
|
45
|
+
".xls": "excel",
|
|
46
|
+
".xlsx": "excel",
|
|
50
47
|
};
|
|
51
|
-
return map[ext] || ext.replace(
|
|
48
|
+
return map[ext] || ext.replace(".", "") || "unknown";
|
|
52
49
|
}
|
package/dist/index.js
CHANGED
|
@@ -125,7 +125,8 @@ gen
|
|
|
125
125
|
.description("Write comments for the given file(s) or folder(s)")
|
|
126
126
|
.action(async (targets) => {
|
|
127
127
|
await withContext(async () => {
|
|
128
|
-
|
|
128
|
+
// Remove the file type filter to allow any file
|
|
129
|
+
const files = await resolveTargetsToFiles(targets);
|
|
129
130
|
for (const file of files) {
|
|
130
131
|
await handleAgentRun(file, [addCommentsModule, preserveCodeModule]);
|
|
131
132
|
}
|
|
@@ -42,18 +42,10 @@ You are a senior engineer reviewing a ${fileType} file.
|
|
|
42
42
|
|
|
43
43
|
Please:
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
2. Add clear, helpful inline comments to explain non-obvious logic inside functions.
|
|
50
|
-
- Preserve all existing comments as-is.
|
|
51
|
-
|
|
52
|
-
3. Use "${commentSyntax}" as the comment syntax appropriate for ${fileType}.
|
|
53
|
-
|
|
54
|
-
4. Preserve all original formatting, whitespace, and code exactly.
|
|
55
|
-
|
|
56
|
-
5. You may only be shown parts of a file. Add comments as instructed.
|
|
45
|
+
- Add summary comments (2-3 lines) at relevant points for greater class insights
|
|
46
|
+
- Add clear, helpful inline comments to explain non-obvious logic inside functions.
|
|
47
|
+
- Use "${commentSyntax}" as the comment syntax appropriate for ${fileType}.
|
|
48
|
+
- Preserve all original formatting, whitespace, and code exactly.
|
|
57
49
|
|
|
58
50
|
Rules:
|
|
59
51
|
- Return the full original chunk of code with added comments only.
|
|
@@ -62,8 +54,6 @@ Rules:
|
|
|
62
54
|
- Inline comments should clarify complex or tricky parts only.
|
|
63
55
|
- Do NOT add any “chunk” start/end markers, numbering, or metadata to the output.
|
|
64
56
|
|
|
65
|
-
Code to comments is below this line:
|
|
66
|
-
|
|
67
57
|
${input.content}
|
|
68
58
|
|
|
69
59
|
`.trim();
|
|
@@ -1,28 +1,31 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { Config } from '../../config.js';
|
|
4
3
|
import { generate } from '../../lib/generate.js';
|
|
4
|
+
import { detectFileType } from '../../fileRules/detectFileType.js';
|
|
5
|
+
import { Config } from '../../config.js';
|
|
5
6
|
export const generateTestsModule = {
|
|
6
7
|
name: 'tests',
|
|
7
8
|
description: 'Generate a Jest test file for the class/module',
|
|
8
9
|
async run({ content, filepath }) {
|
|
9
|
-
const model = Config.getModel();
|
|
10
|
-
const lang = Config.getLanguage();
|
|
11
10
|
if (!filepath)
|
|
12
11
|
throw new Error('Missing filepath in pipeline context');
|
|
12
|
+
const model = Config.getModel();
|
|
13
|
+
const lang = detectFileType(filepath);
|
|
13
14
|
const prompt = `
|
|
14
|
-
You are a senior ${lang.toUpperCase()} engineer. Given the following class or module, generate a Jest test file.
|
|
15
|
+
You are a senior ${lang.toUpperCase()} engineer. Given the following class or module, generate a Jest test file.
|
|
15
16
|
|
|
16
|
-
Guidelines:
|
|
17
|
-
- Use the 'jest' test framework
|
|
18
|
-
- Cover public
|
|
19
|
-
-
|
|
20
|
-
-
|
|
17
|
+
Guidelines:
|
|
18
|
+
- Use the 'jest' test framework
|
|
19
|
+
- Cover only one public method: the most relevant or central function
|
|
20
|
+
- Include one edge case for that method
|
|
21
|
+
- Preserve and consider existing code comments
|
|
22
|
+
- Name the file <original>.test.${path.extname(filepath).slice(1)}
|
|
23
|
+
- Only return valid ${lang} code
|
|
21
24
|
|
|
22
|
-
--- CODE START ---
|
|
23
|
-
${content}
|
|
24
|
-
--- CODE END ---
|
|
25
|
-
|
|
25
|
+
--- CODE START ---
|
|
26
|
+
${content}
|
|
27
|
+
--- CODE END ---
|
|
28
|
+
`.trim();
|
|
26
29
|
const response = await generate({ content: prompt }, model);
|
|
27
30
|
if (!response)
|
|
28
31
|
throw new Error('⚠️ No test code returned from model');
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
+
import { getCommentSyntax } from "../../utils/commentMap.js";
|
|
3
|
+
import { detectFileType } from "../../fileRules/detectFileType.js";
|
|
2
4
|
export const preserveCodeModule = {
|
|
3
5
|
name: "preserveCodeModule",
|
|
4
6
|
description: "Ensure code matches original exactly, preserving comments with clear before/after output",
|
|
@@ -6,10 +8,13 @@ export const preserveCodeModule = {
|
|
|
6
8
|
const { originalContent, content, filepath } = input;
|
|
7
9
|
if (!originalContent)
|
|
8
10
|
throw new Error("Requires `originalContent`.");
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
// Determine language from filepath extension
|
|
12
|
+
console.log("Filepath: ", filepath);
|
|
13
|
+
const ext = "." + (filepath?.split(".").pop() || "ts");
|
|
14
|
+
console.log("Extension: ", ext);
|
|
15
|
+
const language = detectFileType(filepath ?? ext); // returns "javascript", "python", etc.
|
|
16
|
+
const syntax = getCommentSyntax(language);
|
|
17
|
+
console.log(`Using comment syntax for extension '${language}':`, syntax);
|
|
13
18
|
// --- Normalize line endings ---
|
|
14
19
|
const normalize = (txt) => txt.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
15
20
|
const origLines = normalize(originalContent).split("\n");
|
|
@@ -17,34 +22,47 @@ export const preserveCodeModule = {
|
|
|
17
22
|
// --- Classify line ---
|
|
18
23
|
let inBlockComment = false;
|
|
19
24
|
let blockLines = [];
|
|
25
|
+
// --- Classify line ---
|
|
26
|
+
// returns: "code" | comment string | null
|
|
20
27
|
const classifyLine = (line) => {
|
|
21
28
|
const trimmed = line.trimStart();
|
|
22
29
|
// --- Single-line comment ---
|
|
23
30
|
for (const s of syntax.singleLine) {
|
|
24
31
|
if (trimmed.startsWith(s))
|
|
25
|
-
return
|
|
32
|
+
return line;
|
|
26
33
|
}
|
|
27
|
-
// --- Multi-line comment ---
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
34
|
+
// --- Multi-line comment (optional) ---
|
|
35
|
+
const multiLineComments = syntax.multiLine ?? [];
|
|
36
|
+
if (!inBlockComment) {
|
|
37
|
+
// check if line starts a multi-line comment
|
|
38
|
+
for (const { start, end } of multiLineComments) {
|
|
33
39
|
if (trimmed.startsWith(start)) {
|
|
34
|
-
inBlockComment = true;
|
|
35
40
|
blockLines = [line];
|
|
36
|
-
|
|
41
|
+
if (trimmed.includes(end) && trimmed.indexOf(end) > trimmed.indexOf(start)) {
|
|
42
|
+
// entire block on one line
|
|
43
|
+
return blockLines.join("\n");
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
inBlockComment = true;
|
|
47
|
+
return null; // wait for block to finish
|
|
48
|
+
}
|
|
37
49
|
}
|
|
38
50
|
}
|
|
39
|
-
|
|
40
|
-
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
// currently inside a multi-line block
|
|
54
|
+
blockLines.push(line);
|
|
55
|
+
for (const { end } of multiLineComments) {
|
|
41
56
|
if (trimmed.includes(end)) {
|
|
42
57
|
inBlockComment = false;
|
|
43
|
-
|
|
58
|
+
const fullBlock = blockLines.join("\n");
|
|
59
|
+
blockLines = [];
|
|
60
|
+
return fullBlock; // emit entire block
|
|
44
61
|
}
|
|
45
|
-
return ""; // inside block
|
|
46
62
|
}
|
|
63
|
+
return null; // still inside block
|
|
47
64
|
}
|
|
65
|
+
// --- default: code ---
|
|
48
66
|
return "code";
|
|
49
67
|
};
|
|
50
68
|
// --- Helper: collect comment blocks into map ---
|
|
@@ -52,97 +70,77 @@ export const preserveCodeModule = {
|
|
|
52
70
|
const map = new Map();
|
|
53
71
|
let commentBuffer = [];
|
|
54
72
|
for (const line of lines) {
|
|
55
|
-
const
|
|
56
|
-
if (
|
|
57
|
-
// Collect full comment line
|
|
58
|
-
commentBuffer.push(line);
|
|
59
|
-
}
|
|
60
|
-
else if (type === "code") {
|
|
61
|
-
// Flush buffer when hitting code
|
|
73
|
+
const result = classifyLine(line);
|
|
74
|
+
if (result === "code") {
|
|
62
75
|
if (commentBuffer.length > 0) {
|
|
63
76
|
const key = line.trim().toLowerCase();
|
|
77
|
+
const block = commentBuffer.join("\n").trim().toLowerCase();
|
|
64
78
|
if (!map.has(key))
|
|
65
79
|
map.set(key, new Set());
|
|
66
|
-
|
|
67
|
-
map.get(key).add(commentBlock);
|
|
80
|
+
map.get(key).add(block);
|
|
68
81
|
commentBuffer = [];
|
|
69
82
|
}
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (typeof result === "string") {
|
|
86
|
+
// comment line or block
|
|
87
|
+
commentBuffer.push(result);
|
|
70
88
|
}
|
|
89
|
+
// result === null => inside multi-line block, do nothing
|
|
71
90
|
}
|
|
72
|
-
//
|
|
91
|
+
// flush at EOF
|
|
73
92
|
if (commentBuffer.length > 0) {
|
|
74
93
|
const key = "";
|
|
94
|
+
const block = commentBuffer.join("\n").trim().toLowerCase();
|
|
75
95
|
if (!map.has(key))
|
|
76
96
|
map.set(key, new Set());
|
|
77
|
-
|
|
78
|
-
map.get(key).add(commentBlock);
|
|
97
|
+
map.get(key).add(block);
|
|
79
98
|
}
|
|
80
99
|
return map;
|
|
81
100
|
}
|
|
82
101
|
// --- Step 1: Collect comments ---
|
|
83
102
|
const modelComments = collectCommentsMap(newLines); // model first
|
|
84
103
|
const origComments = collectCommentsMap(origLines); // original
|
|
85
|
-
// --- Step 2: Remove duplicates ---
|
|
86
|
-
for (const [key,
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (
|
|
92
|
-
|
|
104
|
+
// --- Step 2: Remove duplicates from model (since we insert model) ---
|
|
105
|
+
for (const [key, modelSet] of modelComments.entries()) {
|
|
106
|
+
const origSet = origComments.get(key);
|
|
107
|
+
if (!origSet)
|
|
108
|
+
continue;
|
|
109
|
+
for (const c of Array.from(modelSet)) {
|
|
110
|
+
if (origSet.has(c))
|
|
111
|
+
modelSet.delete(c);
|
|
93
112
|
}
|
|
113
|
+
if (modelSet.size === 0)
|
|
114
|
+
modelComments.delete(key);
|
|
94
115
|
}
|
|
95
|
-
// --- Step 3: Build fixed lines
|
|
116
|
+
// --- Step 3: Build fixed lines ---
|
|
96
117
|
const fixedLines = [];
|
|
97
118
|
for (const origLine of origLines) {
|
|
98
119
|
const key = origLine.trim().toLowerCase();
|
|
99
|
-
// Insert model comment blocks if any
|
|
100
120
|
if (modelComments.has(key)) {
|
|
101
|
-
modelComments.get(key)
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
121
|
+
for (const block of modelComments.get(key)) {
|
|
122
|
+
for (const line of block.split("\n")) {
|
|
123
|
+
const norm = line.trim().toLowerCase();
|
|
124
|
+
const already = fixedLines.some(l => l.trim().toLowerCase() === norm);
|
|
125
|
+
if (!already) {
|
|
105
126
|
fixedLines.push(line);
|
|
106
127
|
console.log(chalk.blue("Inserted comment:"), line.trim());
|
|
107
128
|
}
|
|
108
129
|
else {
|
|
109
|
-
console.log(chalk.gray("Skipped duplicate
|
|
130
|
+
console.log(chalk.gray("Skipped duplicate:"), line.trim());
|
|
110
131
|
}
|
|
111
132
|
}
|
|
112
|
-
}
|
|
133
|
+
}
|
|
113
134
|
}
|
|
114
|
-
fixedLines.push(origLine);
|
|
135
|
+
fixedLines.push(origLine); // always keep original
|
|
115
136
|
}
|
|
116
|
-
// --- Logging
|
|
117
|
-
console.log(chalk.bold.blue("\n=== LINE CLASSIFICATION (original) ==="));
|
|
118
|
-
origLines.forEach((line, i) => {
|
|
119
|
-
const type = classifyLine(line);
|
|
120
|
-
const colored = type === "code"
|
|
121
|
-
? chalk.green(line)
|
|
122
|
-
: type === "comment"
|
|
123
|
-
? chalk.yellow(line)
|
|
124
|
-
: chalk.gray(line); // "" means middle of block
|
|
125
|
-
console.log(`${i + 1}: ${colored} ${chalk.gray(`[${type}]`)}`);
|
|
126
|
-
});
|
|
127
|
-
console.log(chalk.bold.blue("\n=== LINE CLASSIFICATION (model) ==="));
|
|
128
|
-
newLines.forEach((line, i) => {
|
|
129
|
-
const type = classifyLine(line);
|
|
130
|
-
const colored = type === "code"
|
|
131
|
-
? chalk.green(line)
|
|
132
|
-
: type === "comment"
|
|
133
|
-
? chalk.yellow(line)
|
|
134
|
-
: chalk.gray(line);
|
|
135
|
-
console.log(`${i + 1}: ${colored} ${chalk.gray(`[${type}]`)}`);
|
|
136
|
-
});
|
|
137
|
+
// --- Logging ---
|
|
137
138
|
console.log(chalk.bold.blue("\n=== FIXED CONTENT ==="));
|
|
138
139
|
fixedLines.forEach((line, i) => {
|
|
139
|
-
// classifyLine might not be ideal here since fixedLines are final
|
|
140
|
-
// so we treat anything starting with a comment marker as "comment"
|
|
141
140
|
const trimmed = line.trimStart();
|
|
142
|
-
const
|
|
143
|
-
syntax.multiLine.some(({ start }) => trimmed.startsWith(start))
|
|
144
|
-
|
|
145
|
-
: "code";
|
|
141
|
+
const isComment = syntax.singleLine.some(s => trimmed.startsWith(s)) ||
|
|
142
|
+
(syntax.multiLine ?? []).some(({ start }) => trimmed.startsWith(start));
|
|
143
|
+
const type = isComment ? "comment" : "code";
|
|
146
144
|
const colored = type === "code" ? chalk.green(line) : chalk.yellow(line);
|
|
147
145
|
console.log(`${i + 1}: ${colored} ${chalk.gray(`[${type}]`)}`);
|
|
148
146
|
});
|
|
@@ -4,15 +4,18 @@ export async function runModulePipeline(modules, input) {
|
|
|
4
4
|
const isDebug = false;
|
|
5
5
|
for (const mod of modules) {
|
|
6
6
|
try {
|
|
7
|
+
if (isDebug)
|
|
8
|
+
console.log(current.filepath);
|
|
7
9
|
const response = await mod.run(current);
|
|
8
10
|
console.log(`⚙️ Running: ${mod.name}`);
|
|
9
11
|
if (isDebug) {
|
|
10
12
|
console.log(chalk.yellow('➡️ Output:', response.content));
|
|
11
13
|
}
|
|
12
|
-
// Safeguard
|
|
14
|
+
// Safeguard to preserve filepath + originalContent
|
|
13
15
|
current = {
|
|
14
16
|
...response,
|
|
15
|
-
originalContent: current.originalContent
|
|
17
|
+
originalContent: current.originalContent,
|
|
18
|
+
filepath: current.filepath,
|
|
16
19
|
};
|
|
17
20
|
}
|
|
18
21
|
catch (error) {
|
package/dist/utils/commentMap.js
CHANGED
|
@@ -75,5 +75,6 @@ export const commentMap = {
|
|
|
75
75
|
*/
|
|
76
76
|
export function getCommentSyntax(language) {
|
|
77
77
|
const normalized = language.toLowerCase();
|
|
78
|
-
return commentMap[normalized] ||
|
|
78
|
+
return (commentMap[normalized] ||
|
|
79
|
+
{ singleLine: ["//"], multiLine: ["/*", "*/"] });
|
|
79
80
|
}
|
|
@@ -21,7 +21,7 @@ async function collectFilesRecursive(dir, exts) {
|
|
|
21
21
|
if (entry.isDirectory()) {
|
|
22
22
|
files.push(...await collectFilesRecursive(fullPath, exts));
|
|
23
23
|
}
|
|
24
|
-
else if (exts.includes(path.extname(entry.name))) {
|
|
24
|
+
else if (!exts || exts.includes(path.extname(entry.name))) {
|
|
25
25
|
files.push(fullPath);
|
|
26
26
|
}
|
|
27
27
|
}
|