scai 0.1.116 → 0.1.118
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/agents/MainAgent.js +255 -0
- package/dist/agents/contextReviewStep.js +104 -0
- package/dist/agents/finalPlanGenStep.js +123 -0
- package/dist/agents/infoPlanGenStep.js +126 -0
- package/dist/agents/planGeneratorStep.js +118 -0
- package/dist/agents/planResolverStep.js +95 -0
- package/dist/agents/planTargetFilesStep.js +48 -0
- package/dist/agents/preFileSearchCheckStep.js +95 -0
- package/dist/agents/selectRelevantSourcesStep.js +100 -0
- package/dist/agents/semanticAnalysisStep.js +144 -0
- package/dist/agents/structuralAnalysisStep.js +46 -0
- package/dist/agents/transformPlanGenStep.js +107 -0
- package/dist/agents/understandIntentStep.js +72 -0
- package/dist/agents/validationAnalysisStep.js +87 -0
- package/dist/commands/AskCmd.js +47 -116
- package/dist/commands/ChangeLogUpdateCmd.js +11 -5
- package/dist/commands/CommitSuggesterCmd.js +50 -75
- package/dist/commands/DaemonCmd.js +119 -29
- package/dist/commands/IndexCmd.js +41 -24
- package/dist/commands/InspectCmd.js +0 -1
- package/dist/commands/ReadlineSingleton.js +18 -0
- package/dist/commands/ResetDbCmd.js +20 -21
- package/dist/commands/ReviewCmd.js +89 -54
- package/dist/commands/SummaryCmd.js +12 -18
- package/dist/commands/WorkflowCmd.js +41 -0
- package/dist/commands/factory.js +254 -0
- package/dist/config.js +67 -15
- package/dist/constants.js +20 -4
- package/dist/context.js +10 -11
- package/dist/daemon/daemonQueues.js +63 -0
- package/dist/daemon/daemonWorker.js +40 -63
- package/dist/daemon/generateSummaries.js +58 -0
- package/dist/daemon/runFolderCapsuleBatch.js +247 -0
- package/dist/daemon/runIndexingBatch.js +147 -0
- package/dist/daemon/runKgBatch.js +104 -0
- package/dist/db/fileIndex.js +168 -63
- package/dist/db/functionExtractors/extractFromJava.js +210 -6
- package/dist/db/functionExtractors/extractFromJs.js +186 -198
- package/dist/db/functionExtractors/extractFromTs.js +181 -192
- package/dist/db/functionExtractors/index.js +7 -5
- package/dist/db/schema.js +55 -20
- package/dist/db/sqlTemplates.js +50 -19
- package/dist/fileRules/builtins.js +31 -0
- package/dist/fileRules/codeAllowedExtensions.js +4 -0
- package/dist/fileRules/fileExceptions.js +0 -13
- package/dist/fileRules/ignoredExtensions.js +10 -0
- package/dist/index.js +128 -325
- package/dist/lib/generate.js +37 -14
- package/dist/lib/generateFolderCapsules.js +109 -0
- package/dist/lib/spinner.js +12 -5
- package/dist/modelSetup.js +35 -6
- package/dist/pipeline/modules/changeLogModule.js +16 -19
- package/dist/pipeline/modules/chunkManagerModule.js +24 -0
- package/dist/pipeline/modules/cleanupModule.js +96 -91
- package/dist/pipeline/modules/codeTransformModule.js +208 -0
- package/dist/pipeline/modules/commentModule.js +20 -11
- package/dist/pipeline/modules/commitSuggesterModule.js +36 -14
- package/dist/pipeline/modules/contextReviewModule.js +52 -0
- package/dist/pipeline/modules/fileReaderModule.js +72 -0
- package/dist/pipeline/modules/fileSearchModule.js +136 -0
- package/dist/pipeline/modules/finalAnswerModule.js +53 -0
- package/dist/pipeline/modules/gatherInfoModule.js +176 -0
- package/dist/pipeline/modules/generateTestsModule.js +63 -54
- package/dist/pipeline/modules/kgModule.js +26 -11
- package/dist/pipeline/modules/preserveCodeModule.js +91 -49
- package/dist/pipeline/modules/refactorModule.js +19 -7
- package/dist/pipeline/modules/repairTestsModule.js +44 -36
- package/dist/pipeline/modules/reviewModule.js +23 -13
- package/dist/pipeline/modules/summaryModule.js +27 -35
- package/dist/pipeline/modules/writeFileModule.js +86 -0
- package/dist/pipeline/registry/moduleRegistry.js +38 -93
- package/dist/pipeline/runModulePipeline.js +22 -19
- package/dist/scripts/dbcheck.js +156 -91
- package/dist/utils/buildContextualPrompt.js +245 -164
- package/dist/utils/debugContext.js +24 -0
- package/dist/utils/fileTree.js +16 -6
- package/dist/utils/loadRelevantFolderCapsules.js +64 -0
- package/dist/utils/log.js +2 -0
- package/dist/utils/normalizeData.js +23 -0
- package/dist/utils/planActions.js +60 -0
- package/dist/utils/promptBuilderHelper.js +67 -0
- package/dist/utils/promptLogHelper.js +52 -0
- package/dist/utils/sanitizeQuery.js +20 -8
- package/dist/utils/sharedUtils.js +8 -0
- package/dist/utils/sleep.js +3 -0
- package/dist/utils/splitCodeIntoChunk.js +65 -32
- package/dist/utils/vscode.js +49 -0
- package/dist/workflow/workflowResolver.js +14 -0
- package/dist/workflow/workflowRunner.js +103 -0
- package/package.json +6 -5
- package/dist/agent/agentManager.js +0 -39
- package/dist/agent/workflowManager.js +0 -95
- package/dist/commands/ModulePipelineCmd.js +0 -31
- package/dist/daemon/daemonBatch.js +0 -186
- package/dist/fileRules/scoreFiles.js +0 -71
- package/dist/lib/generateEmbedding.js +0 -22
|
@@ -1,59 +1,68 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
/* import path from 'path';
|
|
3
|
+
import { PromptModule } from '../../types.js';
|
|
2
4
|
import { generate } from '../../lib/generate.js';
|
|
3
5
|
import { detectFileType } from '../../fileRules/detectFileType.js';
|
|
4
6
|
import { Config } from '../../config.js';
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
7
|
+
|
|
8
|
+
export const generateTestsModule: PromptModule = {
|
|
9
|
+
name: 'tests',
|
|
10
|
+
description: 'Generate a Jest test file for the class/module',
|
|
11
|
+
|
|
12
|
+
async run({ content, filepath }) {
|
|
13
|
+
if (!filepath) throw new Error('Missing filepath in pipeline context');
|
|
14
|
+
|
|
15
|
+
const model = Config.getModel();
|
|
16
|
+
const lang = detectFileType(filepath);
|
|
17
|
+
const repoRoot = Config.getIndexDir();
|
|
18
|
+
|
|
19
|
+
// Compute relative import path (repo-relative, without extension)
|
|
20
|
+
const relativePath = path.relative(repoRoot, filepath);
|
|
21
|
+
const { dir, name, ext } = path.parse(relativePath);
|
|
22
|
+
const importPath = './' + path.join(dir, name).replace(/\\/g, '/');
|
|
23
|
+
|
|
24
|
+
// Where the test should be written (next to source file)
|
|
25
|
+
const absParsed = path.parse(filepath);
|
|
26
|
+
const testPath = path.join(absParsed.dir, `${absParsed.name}.test${ext}`);
|
|
27
|
+
|
|
28
|
+
const prompt = `
|
|
29
|
+
You are a senior ${lang.toUpperCase()} engineer. Generate a Jest test file for the module below.
|
|
30
|
+
|
|
31
|
+
Requirements:
|
|
32
|
+
- Use the 'jest' test framework.
|
|
33
|
+
- Always include imports at the top:
|
|
34
|
+
import { describe, it, expect } from '@jest/globals';
|
|
35
|
+
import * as moduleUnderTest from '${importPath}';
|
|
36
|
+
- Cover only one public method: the most relevant or central function.
|
|
37
|
+
- Include one edge case for that method.
|
|
38
|
+
- Preserve and consider existing code comments in the module.
|
|
39
|
+
- Only output valid ${lang} code; do not include markdown fences or explanations.
|
|
40
|
+
- Use this scaffold at minimum:
|
|
41
|
+
|
|
42
|
+
import { describe, it, expect } from '@jest/globals';
|
|
43
|
+
import * as moduleUnderTest from '${importPath}';
|
|
44
|
+
|
|
45
|
+
describe('moduleUnderTest', () => {
|
|
46
|
+
it('should ...', () => {
|
|
47
|
+
// test implementation
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
--- MODULE CODE ---
|
|
52
|
+
${content}
|
|
53
|
+
--- END MODULE CODE ---
|
|
47
54
|
`.trim();
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
55
|
+
|
|
56
|
+
const response = await generate({ content: prompt });
|
|
57
|
+
if (!response) throw new Error('⚠️ No test code returned from model');
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
originalContent: content,
|
|
61
|
+
content: response.content, // the test code
|
|
62
|
+
filepath, // original file path
|
|
63
|
+
newFilepath: testPath,
|
|
64
|
+
mode: "newFile" // ensure it gets written as a new file
|
|
65
|
+
};
|
|
66
|
+
}
|
|
59
67
|
};
|
|
68
|
+
*/
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Config } from '../../config.js';
|
|
2
2
|
import { generate } from '../../lib/generate.js';
|
|
3
3
|
import path from 'path';
|
|
4
|
+
import chalk from 'chalk';
|
|
4
5
|
import { cleanupModule } from './cleanupModule.js';
|
|
6
|
+
import { normalizeData } from '../../utils/normalizeData.js';
|
|
5
7
|
export const kgModule = {
|
|
6
8
|
name: 'knowledge-graph',
|
|
7
9
|
description: 'Generates a knowledge graph of entities, tags, and relationships from file content.',
|
|
@@ -9,6 +11,7 @@ export const kgModule = {
|
|
|
9
11
|
const model = Config.getModel();
|
|
10
12
|
const ext = input.filepath ? path.extname(input.filepath).toLowerCase() : '';
|
|
11
13
|
const filename = input.filepath ? path.basename(input.filepath) : '';
|
|
14
|
+
// Build prompt for LLM
|
|
12
15
|
const prompt = `
|
|
13
16
|
You are an assistant specialized in building knowledge graphs from code or text.
|
|
14
17
|
|
|
@@ -21,7 +24,7 @@ Extension: ${ext}
|
|
|
21
24
|
- Identify all entities (functions, classes, modules, or main concepts)
|
|
22
25
|
- For each entity, generate tags describing its characteristics, purpose, or category
|
|
23
26
|
- Identify relationships between entities (e.g., "uses", "extends", "calls")
|
|
24
|
-
- Return output in JSON format with
|
|
27
|
+
- Return output in **valid JSON** format with this structure:
|
|
25
28
|
|
|
26
29
|
{
|
|
27
30
|
"entities": [
|
|
@@ -32,24 +35,36 @@ Extension: ${ext}
|
|
|
32
35
|
]
|
|
33
36
|
}
|
|
34
37
|
|
|
35
|
-
|
|
38
|
+
⚠️ Make sure all strings are safely JSON-encoded: escape quotes, backslashes, and newlines.
|
|
39
|
+
|
|
40
|
+
Do NOT include raw content from the file. Only provide structured JSON output.
|
|
36
41
|
|
|
37
42
|
--- FILE CONTENT START ---
|
|
38
43
|
${content}
|
|
39
44
|
--- FILE CONTENT END ---
|
|
40
45
|
`.trim();
|
|
41
|
-
const response = await generate({ content: prompt, filepath: input.filepath });
|
|
42
46
|
try {
|
|
43
|
-
//
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
// === INTERACTION #1: generate.ts (via ModuleIO) ===
|
|
48
|
+
const genInput = {
|
|
49
|
+
query: 'Extract entities and relationships for knowledge graph',
|
|
50
|
+
content: prompt,
|
|
51
|
+
};
|
|
52
|
+
const genOutput = await generate(genInput);
|
|
53
|
+
// === INTERACTION #2: cleanupModule (via ModuleIO) ===
|
|
54
|
+
const cleaned = await cleanupModule.run({
|
|
55
|
+
query: 'Clean up JSON for knowledge graph',
|
|
56
|
+
content: genOutput.data,
|
|
57
|
+
});
|
|
58
|
+
// === Normalize and parse JSON result ===
|
|
59
|
+
const parsed = normalizeData(cleaned.data);
|
|
60
|
+
// Ensure structure validity
|
|
61
|
+
parsed.entities ?? (parsed.entities = []);
|
|
62
|
+
parsed.edges ?? (parsed.edges = []);
|
|
48
63
|
return parsed;
|
|
49
64
|
}
|
|
50
65
|
catch (err) {
|
|
51
|
-
console.
|
|
52
|
-
return { entities: [], edges: [] };
|
|
66
|
+
console.log(chalk.yellow(`⚠️ [KG] Garbage or invalid JSON output for ${input.filepath}`));
|
|
67
|
+
return { entities: [], edges: [] };
|
|
53
68
|
}
|
|
54
|
-
}
|
|
69
|
+
},
|
|
55
70
|
};
|
|
@@ -9,63 +9,55 @@ export const preserveCodeModule = {
|
|
|
9
9
|
if (!originalContent)
|
|
10
10
|
throw new Error("Requires `originalContent`.");
|
|
11
11
|
// Determine language from filepath extension
|
|
12
|
-
console.log("Filepath: ", filepath);
|
|
13
12
|
const ext = "." + (filepath?.split(".").pop() || "ts");
|
|
14
|
-
|
|
15
|
-
const language = detectFileType(filepath ?? ext); // returns "javascript", "python", etc.
|
|
13
|
+
const language = detectFileType(filepath ?? ext);
|
|
16
14
|
const syntax = getCommentSyntax(language);
|
|
17
|
-
console.log(`Using comment syntax for extension '${language}':`, syntax);
|
|
18
15
|
// --- Normalize line endings ---
|
|
19
16
|
const normalize = (txt) => txt.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
20
17
|
const origLines = normalize(originalContent).split("\n");
|
|
21
18
|
const newLines = normalize(content).split("\n");
|
|
22
|
-
// --- Classify line ---
|
|
19
|
+
// --- Classify line for comment preservation ---
|
|
23
20
|
let inBlockComment = false;
|
|
24
21
|
let blockLines = [];
|
|
25
|
-
// --- Classify line ---
|
|
26
|
-
// returns: "code" | comment string | null
|
|
27
22
|
const classifyLine = (line) => {
|
|
28
23
|
const trimmed = line.trimStart();
|
|
29
|
-
//
|
|
24
|
+
// Single-line comments
|
|
30
25
|
for (const s of syntax.singleLine) {
|
|
31
26
|
if (trimmed.startsWith(s))
|
|
32
27
|
return line;
|
|
33
28
|
}
|
|
34
|
-
// --- Multi-line comment (optional) ---
|
|
35
29
|
const multiLineComments = syntax.multiLine ?? [];
|
|
36
30
|
if (!inBlockComment) {
|
|
37
|
-
// check if line starts a multi-line comment
|
|
38
31
|
for (const { start, end } of multiLineComments) {
|
|
39
|
-
if (trimmed.startsWith(start)) {
|
|
32
|
+
if (trimmed.startsWith(start) || trimmed.startsWith("/**")) {
|
|
40
33
|
blockLines = [line];
|
|
41
34
|
if (trimmed.includes(end) && trimmed.indexOf(end) > trimmed.indexOf(start)) {
|
|
42
|
-
// entire block on one line
|
|
43
35
|
return blockLines.join("\n");
|
|
44
36
|
}
|
|
45
37
|
else {
|
|
46
38
|
inBlockComment = true;
|
|
47
|
-
return null;
|
|
39
|
+
return null;
|
|
48
40
|
}
|
|
49
41
|
}
|
|
50
42
|
}
|
|
43
|
+
// ✅ New: Handle stray lines that look like part of a comment
|
|
44
|
+
if (trimmed.startsWith("*")) {
|
|
45
|
+
return line;
|
|
46
|
+
}
|
|
51
47
|
}
|
|
52
48
|
else {
|
|
53
|
-
// currently inside a multi-line block
|
|
54
49
|
blockLines.push(line);
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return fullBlock; // emit entire block
|
|
61
|
-
}
|
|
50
|
+
if (trimmed.includes("*/")) {
|
|
51
|
+
inBlockComment = false;
|
|
52
|
+
const fullBlock = blockLines.join("\n");
|
|
53
|
+
blockLines = [];
|
|
54
|
+
return fullBlock;
|
|
62
55
|
}
|
|
63
56
|
return null; // still inside block
|
|
64
57
|
}
|
|
65
|
-
// --- default: code ---
|
|
66
58
|
return "code";
|
|
67
59
|
};
|
|
68
|
-
// ---
|
|
60
|
+
// --- Collect comments into map ---
|
|
69
61
|
function collectCommentsMap(lines) {
|
|
70
62
|
const map = new Map();
|
|
71
63
|
let commentBuffer = [];
|
|
@@ -82,13 +74,9 @@ export const preserveCodeModule = {
|
|
|
82
74
|
}
|
|
83
75
|
continue;
|
|
84
76
|
}
|
|
85
|
-
if (typeof result === "string")
|
|
86
|
-
// comment line or block
|
|
77
|
+
if (typeof result === "string")
|
|
87
78
|
commentBuffer.push(result);
|
|
88
|
-
}
|
|
89
|
-
// result === null => inside multi-line block, do nothing
|
|
90
79
|
}
|
|
91
|
-
// flush at EOF
|
|
92
80
|
if (commentBuffer.length > 0) {
|
|
93
81
|
const key = "";
|
|
94
82
|
const block = commentBuffer.join("\n").trim().toLowerCase();
|
|
@@ -96,12 +84,20 @@ export const preserveCodeModule = {
|
|
|
96
84
|
map.set(key, new Set());
|
|
97
85
|
map.get(key).add(block);
|
|
98
86
|
}
|
|
87
|
+
if (blockLines.length > 0) {
|
|
88
|
+
const key = "";
|
|
89
|
+
const block = blockLines.join("\n").trim().toLowerCase();
|
|
90
|
+
if (!map.has(key))
|
|
91
|
+
map.set(key, new Set());
|
|
92
|
+
map.get(key).add(block);
|
|
93
|
+
blockLines = [];
|
|
94
|
+
}
|
|
99
95
|
return map;
|
|
100
96
|
}
|
|
101
97
|
// --- Step 1: Collect comments ---
|
|
102
|
-
const modelComments = collectCommentsMap(newLines);
|
|
103
|
-
const origComments = collectCommentsMap(origLines);
|
|
104
|
-
// --- Step 2: Remove duplicates
|
|
98
|
+
const modelComments = collectCommentsMap(newLines);
|
|
99
|
+
const origComments = collectCommentsMap(origLines);
|
|
100
|
+
// --- Step 2: Remove duplicates ---
|
|
105
101
|
for (const [key, modelSet] of modelComments.entries()) {
|
|
106
102
|
const origSet = origComments.get(key);
|
|
107
103
|
if (!origSet)
|
|
@@ -114,40 +110,86 @@ export const preserveCodeModule = {
|
|
|
114
110
|
modelComments.delete(key);
|
|
115
111
|
}
|
|
116
112
|
// --- Step 3: Build fixed lines ---
|
|
117
|
-
|
|
113
|
+
let fixedLines = [];
|
|
118
114
|
for (const origLine of origLines) {
|
|
119
115
|
const key = origLine.trim().toLowerCase();
|
|
120
116
|
if (modelComments.has(key)) {
|
|
121
117
|
for (const block of modelComments.get(key)) {
|
|
122
118
|
for (const line of block.split("\n")) {
|
|
123
119
|
const norm = line.trim().toLowerCase();
|
|
124
|
-
|
|
125
|
-
if (!already) {
|
|
120
|
+
if (!fixedLines.some(l => l.trim().toLowerCase() === norm)) {
|
|
126
121
|
fixedLines.push(line);
|
|
127
|
-
console.log(chalk.blue("Inserted comment:"), line.trim());
|
|
128
|
-
}
|
|
129
|
-
else {
|
|
130
|
-
console.log(chalk.gray("Skipped duplicate:"), line.trim());
|
|
131
122
|
}
|
|
132
123
|
}
|
|
133
124
|
}
|
|
134
125
|
}
|
|
135
|
-
fixedLines.push(origLine);
|
|
126
|
+
fixedLines.push(origLine);
|
|
136
127
|
}
|
|
137
|
-
// ---
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
128
|
+
// --- Step 4: Remove opening/closing fences at top/bottom ---
|
|
129
|
+
while (fixedLines.length && /^```(?:\w+)?$/.test(fixedLines[0].trim())) {
|
|
130
|
+
fixedLines.shift();
|
|
131
|
+
}
|
|
132
|
+
while (fixedLines.length && /^```(?:\w+)?$/.test(fixedLines[fixedLines.length - 1].trim())) {
|
|
133
|
+
fixedLines.pop();
|
|
134
|
+
}
|
|
135
|
+
// --- Step 5: Remove opening/closing fences at top/bottom ---
|
|
136
|
+
while (fixedLines.length && /^```(?:\w+)?$/.test(fixedLines[0].trim())) {
|
|
137
|
+
console.log(chalk.red(`[preserveCodeModule] Removing top fence: "${fixedLines[0].trim()}"`));
|
|
138
|
+
fixedLines.shift();
|
|
139
|
+
}
|
|
140
|
+
while (fixedLines.length && /^```(?:\w+)?$/.test(fixedLines[fixedLines.length - 1].trim())) {
|
|
141
|
+
console.log(chalk.red(`[preserveCodeModule] Removing bottom fence: "${fixedLines[fixedLines.length - 1].trim()}"`));
|
|
142
|
+
fixedLines.pop();
|
|
143
|
+
}
|
|
144
|
+
// --- Step 6: Remove any lingering triple ticks inside content ---
|
|
145
|
+
fixedLines = fixedLines.filter(line => {
|
|
146
|
+
if (/^```(?:\w+)?$/.test(line.trim())) {
|
|
147
|
+
console.log(chalk.red(`[preserveCodeModule] Removing lingering fence: "${line.trim()}"`));
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
return true;
|
|
146
151
|
});
|
|
152
|
+
// --- Step 7 (final): Clean stray '*' and '*/' lines ---
|
|
153
|
+
let insideBlock = false;
|
|
154
|
+
fixedLines = fixedLines.map(line => {
|
|
155
|
+
const trimmed = line.trim();
|
|
156
|
+
// Enter block comment
|
|
157
|
+
if (trimmed.startsWith("/*") || trimmed.startsWith("/**")) {
|
|
158
|
+
insideBlock = true;
|
|
159
|
+
return line;
|
|
160
|
+
}
|
|
161
|
+
// Exit block comment
|
|
162
|
+
if (trimmed.startsWith("*/")) {
|
|
163
|
+
if (insideBlock) {
|
|
164
|
+
insideBlock = false;
|
|
165
|
+
return line; // keep valid closer
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
return ""; // remove stray closer
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// If inside a block, keep '*' lines as-is
|
|
172
|
+
if (insideBlock && trimmed.startsWith("*")) {
|
|
173
|
+
return line;
|
|
174
|
+
}
|
|
175
|
+
// If not inside a block but line starts with '*'
|
|
176
|
+
if (!insideBlock && trimmed.startsWith("*")) {
|
|
177
|
+
const afterStar = trimmed.slice(1).trim();
|
|
178
|
+
// ✅ If it's a JSDoc tag (starts with @), keep as-is
|
|
179
|
+
if (afterStar.startsWith("@")) {
|
|
180
|
+
return line;
|
|
181
|
+
}
|
|
182
|
+
// Otherwise, treat as stray and convert to //
|
|
183
|
+
const indent = line.slice(0, line.indexOf("*"));
|
|
184
|
+
return indent + "//" + line.slice(line.indexOf("*") + 1);
|
|
185
|
+
}
|
|
186
|
+
return line;
|
|
187
|
+
}).filter(line => line !== ""); // remove empty stray lines
|
|
188
|
+
// --- Return PromptOutput ---
|
|
147
189
|
return {
|
|
148
190
|
content: fixedLines.join("\n"),
|
|
149
191
|
filepath,
|
|
150
|
-
mode: "overwrite"
|
|
192
|
+
mode: "overwrite",
|
|
151
193
|
};
|
|
152
194
|
}
|
|
153
195
|
};
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
+
// File: src/pipeline/modules/refactorModule.ts
|
|
1
2
|
import { Config } from '../../config.js';
|
|
2
3
|
import { generate } from '../../lib/generate.js';
|
|
3
4
|
export const refactorModule = {
|
|
4
5
|
name: 'refactor',
|
|
5
6
|
description: 'Break code into small, clean functions',
|
|
6
|
-
async
|
|
7
|
-
const model = Config.getModel();
|
|
7
|
+
run: async (input) => {
|
|
8
8
|
const lang = Config.getLanguage();
|
|
9
|
+
const code = typeof input.content === 'string' ? input.content : '';
|
|
10
|
+
if (!code) {
|
|
11
|
+
throw new Error('⚠️ No code provided for refactoring.');
|
|
12
|
+
}
|
|
9
13
|
const prompt = `
|
|
10
14
|
You are a senior ${lang.toUpperCase()} engineer.
|
|
11
15
|
|
|
@@ -16,13 +20,21 @@ Refactor the following code:
|
|
|
16
20
|
- Output the full, valid ${lang.toUpperCase()} code
|
|
17
21
|
|
|
18
22
|
--- CODE START ---
|
|
19
|
-
${
|
|
23
|
+
${code}
|
|
20
24
|
--- CODE END ---
|
|
21
25
|
`.trim();
|
|
22
|
-
const response = await generate({
|
|
23
|
-
|
|
26
|
+
const response = await generate({
|
|
27
|
+
query: input.query ?? '',
|
|
28
|
+
content: prompt,
|
|
29
|
+
});
|
|
30
|
+
if (!response || !response.content) {
|
|
24
31
|
throw new Error('❌ Model returned empty response for refactoring.');
|
|
25
32
|
}
|
|
26
|
-
|
|
27
|
-
|
|
33
|
+
const output = {
|
|
34
|
+
query: input.query ?? '',
|
|
35
|
+
content: response.content,
|
|
36
|
+
data: { refactoredCode: response.content },
|
|
37
|
+
};
|
|
38
|
+
return output;
|
|
39
|
+
},
|
|
28
40
|
};
|
|
@@ -1,40 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* import { PromptModule, PromptInput, PromptOutput } from "../../types.js";
|
|
1
3
|
import { generate } from "../../lib/generate.js";
|
|
2
4
|
import { Config } from "../../config.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
- Only
|
|
26
|
-
|
|
27
|
-
|
|
5
|
+
|
|
6
|
+
export const repairTestsModule: PromptModule = {
|
|
7
|
+
name: "repairTestsModule",
|
|
8
|
+
description: "Fix failing Jest tests using AI",
|
|
9
|
+
|
|
10
|
+
async run({ content, filepath, summary }: PromptInput): Promise<PromptOutput> {
|
|
11
|
+
const model = Config.getModel();
|
|
12
|
+
|
|
13
|
+
const prompt = `
|
|
14
|
+
You are a senior engineer tasked with repairing Jest tests.
|
|
15
|
+
|
|
16
|
+
The following test file failed:
|
|
17
|
+
|
|
18
|
+
--- BEGIN TEST FILE ---
|
|
19
|
+
${content}
|
|
20
|
+
--- END TEST FILE ---
|
|
21
|
+
|
|
22
|
+
Failure summary:
|
|
23
|
+
${summary || "No summary provided."}
|
|
24
|
+
|
|
25
|
+
Instructions:
|
|
26
|
+
- Keep the overall structure, imports, and test cases.
|
|
27
|
+
- Only fix syntax errors, invalid Jest matchers, or broken references.
|
|
28
|
+
- Do NOT remove or replace entire test suites unless strictly necessary.
|
|
29
|
+
- Do NOT generate trivial placeholder tests (like add(2,3) examples).
|
|
30
|
+
- Only return valid Jest code, no explanations, no markdown fences.
|
|
31
|
+
|
|
32
|
+
Output the repaired test file:
|
|
28
33
|
`.trim();
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
const response = await generate({ content: prompt });
|
|
37
|
+
if (!response) throw new Error("⚠️ No repaired test code returned from model");
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
originalContent: content,
|
|
41
|
+
content: response.content, // repaired test code
|
|
42
|
+
filepath, // repair in-place
|
|
43
|
+
summary: `Repaired tests based on failure: ${summary || "unknown error"}`,
|
|
44
|
+
mode: "overwrite" // signal to overwrite existing test file
|
|
45
|
+
};
|
|
46
|
+
}
|
|
40
47
|
};
|
|
48
|
+
*/
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
// File: src/pipeline/modules/reviewModule.ts
|
|
1
2
|
import { generate } from '../../lib/generate.js';
|
|
2
|
-
import { Config } from '../../config.js';
|
|
3
3
|
export const reviewModule = {
|
|
4
4
|
name: 'review',
|
|
5
|
-
description: 'Reviews code diff or PR content and provides
|
|
6
|
-
async
|
|
7
|
-
const
|
|
5
|
+
description: 'Reviews code diff or PR content and provides actionable suggestions',
|
|
6
|
+
run: async (input) => {
|
|
7
|
+
const query = input.query ?? '';
|
|
8
|
+
const codeDiff = typeof input.content === 'string' ? input.content : '';
|
|
9
|
+
if (!codeDiff) {
|
|
10
|
+
throw new Error('⚠️ No code diff provided for review.');
|
|
11
|
+
}
|
|
8
12
|
const prompt = `
|
|
9
13
|
Suggest ALWAYS 3 concise suggestions for improvements based on the input code diff.
|
|
10
14
|
|
|
@@ -18,20 +22,26 @@ Format your response exactly as:
|
|
|
18
22
|
3. <type>: <message>
|
|
19
23
|
|
|
20
24
|
Changes:
|
|
21
|
-
${
|
|
22
|
-
`.trim();
|
|
23
|
-
const response = await generate({
|
|
25
|
+
${codeDiff}
|
|
26
|
+
`.trim();
|
|
27
|
+
const response = await generate({
|
|
28
|
+
query,
|
|
29
|
+
content: prompt,
|
|
30
|
+
});
|
|
31
|
+
// Ensure content is string
|
|
32
|
+
const responseText = typeof response.content === 'string' ? response.content : JSON.stringify(response.content ?? '', null, 2);
|
|
24
33
|
// Parse response: only keep numbered lines
|
|
25
|
-
const lines =
|
|
34
|
+
const lines = responseText
|
|
26
35
|
.split('\n')
|
|
27
36
|
.map(line => line.trim())
|
|
28
37
|
.filter(line => /^\d+\.\s+/.test(line));
|
|
29
38
|
// Remove numbering and any surrounding quotes
|
|
30
39
|
const suggestions = lines.map(line => line.replace(/^\d+\.\s+/, '').replace(/^"(.*)"$/, '$1').trim());
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
suggestions
|
|
40
|
+
const output = {
|
|
41
|
+
query,
|
|
42
|
+
content: responseText, // raw model output
|
|
43
|
+
data: { suggestions }, // structured suggestions
|
|
35
44
|
};
|
|
36
|
-
|
|
45
|
+
return output;
|
|
46
|
+
},
|
|
37
47
|
};
|