scai 0.1.102 → 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.
@@ -1,52 +1,49 @@
1
1
  import path from 'path';
2
- export function detectFileType(filepath) {
3
- const ext = path.extname(filepath).toLowerCase();
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
- // Programming languages
6
- '.ts': 'typescript',
7
- '.tsx': 'typescript',
8
- '.js': 'javascript',
9
- '.jsx': 'javascript',
10
- '.java': 'java',
11
- '.py': 'python',
12
- '.rb': 'ruby',
13
- '.php': 'php',
14
- '.go': 'go',
15
- '.rs': 'rust',
16
- '.c': 'c',
17
- '.cpp': 'cpp',
18
- '.cs': 'csharp',
19
- '.swift': 'swift',
20
- '.kt': 'kotlin',
21
- '.scala': 'scala',
22
- // Markup & docs
23
- '.md': 'markdown',
24
- '.html': 'html',
25
- '.htm': 'html',
26
- '.xml': 'xml',
27
- '.json': 'json',
28
- '.yaml': 'yaml',
29
- '.yml': 'yaml',
30
- // Configs
31
- '.ini': 'config',
32
- '.toml': 'config',
33
- '.env': 'config',
34
- // Data
35
- '.sql': 'sql',
36
- '.csv': 'csv',
37
- '.tsv': 'tsv',
38
- // Text & writing
39
- '.txt': 'text',
40
- '.log': 'log',
41
- '.rst': 'text',
42
- // Office
43
- '.doc': 'word',
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('.', '') || 'unknown';
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
- const files = await resolveTargetsToFiles(targets, [".ts", ".js"]);
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
- 1. Add a concise summary comment above each function describing its purpose.
46
- - Do NOT write trivial summaries like "..." or empty comments.
47
- - Do NOT overwrite or remove any existing comments.
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 methods and one edge case
19
- - Name the file <original>.test.ts
20
- - Only return valid TypeScript code
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
- `.trim();
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
- const syntax = {
10
- singleLine: ["//"],
11
- multiLine: [{ start: "/*", end: "*/" }, { start: "/**", end: "*/" }]
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,6 +22,7 @@ export const preserveCodeModule = {
17
22
  // --- Classify line ---
18
23
  let inBlockComment = false;
19
24
  let blockLines = [];
25
+ // --- Classify line ---
20
26
  // returns: "code" | comment string | null
21
27
  const classifyLine = (line) => {
22
28
  const trimmed = line.trimStart();
@@ -25,29 +31,38 @@ export const preserveCodeModule = {
25
31
  if (trimmed.startsWith(s))
26
32
  return line;
27
33
  }
28
- // --- Multi-line comment ---
29
- for (const { start, end } of syntax.multiLine) {
30
- if (!inBlockComment) {
31
- if (trimmed.startsWith(start) && trimmed.includes(end)) {
32
- return line; // entire block on one line
33
- }
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) {
34
39
  if (trimmed.startsWith(start)) {
35
- inBlockComment = true;
36
40
  blockLines = [line];
37
- return null; // wait until block ends
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
+ }
38
49
  }
39
50
  }
40
- else {
41
- blockLines.push(line);
51
+ }
52
+ else {
53
+ // currently inside a multi-line block
54
+ blockLines.push(line);
55
+ for (const { end } of multiLineComments) {
42
56
  if (trimmed.includes(end)) {
43
57
  inBlockComment = false;
44
58
  const fullBlock = blockLines.join("\n");
45
59
  blockLines = [];
46
60
  return fullBlock; // emit entire block
47
61
  }
48
- return null; // still inside block
49
62
  }
63
+ return null; // still inside block
50
64
  }
65
+ // --- default: code ---
51
66
  return "code";
52
67
  };
53
68
  // --- Helper: collect comment blocks into map ---
@@ -123,10 +138,9 @@ export const preserveCodeModule = {
123
138
  console.log(chalk.bold.blue("\n=== FIXED CONTENT ==="));
124
139
  fixedLines.forEach((line, i) => {
125
140
  const trimmed = line.trimStart();
126
- const type = syntax.singleLine.some(s => trimmed.startsWith(s)) ||
127
- syntax.multiLine.some(({ start }) => trimmed.startsWith(start))
128
- ? "comment"
129
- : "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";
130
144
  const colored = type === "code" ? chalk.green(line) : chalk.yellow(line);
131
145
  console.log(`${i + 1}: ${colored} ${chalk.gray(`[${type}]`)}`);
132
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 if originalContent is overwritten by module
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) {
@@ -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] || { singleLine: ["#"], multiLine: [] };
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scai",
3
- "version": "0.1.102",
3
+ "version": "0.1.103",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "scai": "./dist/index.js"