scai 0.1.108 → 0.1.109
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 +6 -1
- package/dist/__tests__/example.test.js +10 -0
- package/dist/agent/agentManager.js +14 -2
- package/dist/agent/workflowManager.js +5 -0
- package/dist/pipeline/modules/cleanGeneratedTestsModule.js +17 -6
- package/dist/pipeline/modules/repairTestsModule.js +40 -0
- package/dist/pipeline/modules/runTestsModule.js +37 -0
- package/dist/pipeline/registry/moduleRegistry.js +17 -0
- package/package.json +1 -1
- package/dist/jest.config.js +0 -11
- package/dist/jest.setup.js +0 -14
- package/dist/utils/runTests.js +0 -11
package/dist/CHANGELOG.md
CHANGED
|
@@ -166,4 +166,9 @@ Type handling with the module pipeline
|
|
|
166
166
|
|
|
167
167
|
## 2025-09-01
|
|
168
168
|
|
|
169
|
-
* Improve handling of GitHub repository URLs and paths by extracting the owner and name separately
|
|
169
|
+
* Improve handling of GitHub repository URLs and paths by extracting the owner and name separately
|
|
170
|
+
|
|
171
|
+
## 2025-09-02
|
|
172
|
+
|
|
173
|
+
• Added test configuration for project and generated tests
|
|
174
|
+
• Add runTestsModule and repairTestsModule for testing pipeline
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { SCAI_HOME } from "../constants"; // example constant
|
|
2
|
+
// cli/src/__tests__/example.test.ts
|
|
3
|
+
describe("CLI src basic test", () => {
|
|
4
|
+
it("should pass a simple truthy test", () => {
|
|
5
|
+
expect(true).toBe(true);
|
|
6
|
+
});
|
|
7
|
+
it("should import a constant from cli/src/constants.ts", () => {
|
|
8
|
+
expect(typeof SCAI_HOME).not.toBe("undefined");
|
|
9
|
+
});
|
|
10
|
+
});
|
|
@@ -18,8 +18,20 @@ export class Agent {
|
|
|
18
18
|
// Resolve modules (with before/after dependencies)
|
|
19
19
|
const modules = this.resolveModules(this.goals);
|
|
20
20
|
console.log(chalk.green("📋 Modules to run:"), modules.map((m) => m.name).join(" → "));
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
try {
|
|
22
|
+
// Check that the file exists before trying to read it
|
|
23
|
+
await fs.access(filepath);
|
|
24
|
+
// Read file content (optional, could be used by modules in workflow)
|
|
25
|
+
await fs.readFile(filepath, "utf-8");
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
if (err.code === "ENOENT") {
|
|
29
|
+
console.error(chalk.redBright("❌ Error:"), `File not found: ${chalk.yellow(filepath)}`);
|
|
30
|
+
console.error(`Make sure the path is correct. (cwd: ${chalk.gray(process.cwd())})`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
throw err; // rethrow for unexpected errors
|
|
34
|
+
}
|
|
23
35
|
// Delegate everything to handleAgentRun (like CLI commands do)
|
|
24
36
|
await handleAgentRun(filepath, modules);
|
|
25
37
|
console.log(chalk.green("✅ Agent finished!"));
|
|
@@ -76,6 +76,11 @@ export async function handleAgentRun(filepath, modules) {
|
|
|
76
76
|
baseChunks.length = 0;
|
|
77
77
|
baseChunks.push(...reset);
|
|
78
78
|
break;
|
|
79
|
+
case 'skip':
|
|
80
|
+
console.log(chalk.gray(`⏭️ Skipped writing for module ${mod.name}`));
|
|
81
|
+
// don’t touch files, but keep chunks flowing
|
|
82
|
+
workingChunks = processed;
|
|
83
|
+
break;
|
|
79
84
|
default:
|
|
80
85
|
console.log(chalk.yellow(`⚠️ Unknown mode; skipping write`));
|
|
81
86
|
// still move pipeline forward with processed
|
|
@@ -7,16 +7,27 @@ export const cleanGeneratedTestsModule = {
|
|
|
7
7
|
// normalize + strip markdown
|
|
8
8
|
const normalized = normalizeText(content);
|
|
9
9
|
const stripped = stripMarkdownFences(normalized);
|
|
10
|
-
// filter non-code lines
|
|
11
|
-
const lines = stripped.split("\n");
|
|
12
10
|
// filter non-code lines, but keep blank ones
|
|
11
|
+
const lines = stripped.split("\n");
|
|
13
12
|
const codeLines = lines.filter(line => line.trim() === "" || isCodeLike(line));
|
|
14
|
-
|
|
13
|
+
// remove duplicate imports (normalize spacing/semicolon)
|
|
14
|
+
const seenImports = new Set();
|
|
15
|
+
const dedupedLines = codeLines.filter(line => {
|
|
16
|
+
if (line.trim().startsWith("import")) {
|
|
17
|
+
const key = line.trim().replace(/;$/, "");
|
|
18
|
+
if (seenImports.has(key)) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
seenImports.add(key);
|
|
22
|
+
}
|
|
23
|
+
return true;
|
|
24
|
+
});
|
|
25
|
+
const cleanedCode = dedupedLines.join("\n").trimEnd();
|
|
15
26
|
return {
|
|
16
27
|
originalContent: content,
|
|
17
|
-
content: cleanedCode,
|
|
18
|
-
filepath,
|
|
19
|
-
mode: "overwrite",
|
|
28
|
+
content: cleanedCode,
|
|
29
|
+
filepath,
|
|
30
|
+
mode: "overwrite",
|
|
20
31
|
};
|
|
21
32
|
}
|
|
22
33
|
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { generate } from "../../lib/generate.js";
|
|
2
|
+
import { Config } from "../../config.js";
|
|
3
|
+
export const repairTestsModule = {
|
|
4
|
+
name: "repairTestsModule",
|
|
5
|
+
description: "Fix failing Jest tests using AI",
|
|
6
|
+
async run({ content, filepath, summary }) {
|
|
7
|
+
const model = Config.getModel();
|
|
8
|
+
const prompt = `
|
|
9
|
+
You are a senior engineer tasked with repairing Jest tests.
|
|
10
|
+
|
|
11
|
+
The following test file failed:
|
|
12
|
+
|
|
13
|
+
--- BEGIN TEST FILE ---
|
|
14
|
+
${content}
|
|
15
|
+
--- END TEST FILE ---
|
|
16
|
+
|
|
17
|
+
Failure summary:
|
|
18
|
+
${summary || "No summary provided."}
|
|
19
|
+
|
|
20
|
+
Instructions:
|
|
21
|
+
- Keep the overall structure, imports, and test cases.
|
|
22
|
+
- Only fix syntax errors, invalid Jest matchers, or broken references.
|
|
23
|
+
- Do NOT remove or replace entire test suites unless strictly necessary.
|
|
24
|
+
- Do NOT generate trivial placeholder tests (like add(2,3) examples).
|
|
25
|
+
- Only return valid Jest code, no explanations, no markdown fences.
|
|
26
|
+
|
|
27
|
+
Output the repaired test file:
|
|
28
|
+
`.trim();
|
|
29
|
+
const response = await generate({ content: prompt }, model);
|
|
30
|
+
if (!response)
|
|
31
|
+
throw new Error("⚠️ No repaired test code returned from model");
|
|
32
|
+
return {
|
|
33
|
+
originalContent: content,
|
|
34
|
+
content: response.content, // repaired test code
|
|
35
|
+
filepath, // repair in-place
|
|
36
|
+
summary: `Repaired tests based on failure: ${summary || "unknown error"}`,
|
|
37
|
+
mode: "overwrite" // signal to overwrite existing test file
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import path from "path";
|
|
3
|
+
export const runTestsModule = {
|
|
4
|
+
name: "runTestsModule",
|
|
5
|
+
description: "Runs generated Jest tests with safety checks for a single file",
|
|
6
|
+
async run(input) {
|
|
7
|
+
const { filepath } = input;
|
|
8
|
+
if (!filepath) {
|
|
9
|
+
throw new Error("No filepath provided to runTestsModule.");
|
|
10
|
+
}
|
|
11
|
+
const absoluteFilePath = path.resolve(filepath);
|
|
12
|
+
try {
|
|
13
|
+
// Step 1: TypeScript syntax check for this file only using tsconfig.test.json
|
|
14
|
+
// Step 1: TypeScript syntax check for this file only
|
|
15
|
+
execSync(`npx tsc --noEmit --project tsconfig.test.json`, { stdio: "inherit" });
|
|
16
|
+
// Step 2: Dry-run Jest for this file only
|
|
17
|
+
execSync(`npx jest --config ${path.resolve("jest.config.ts")} --dryRun ${absoluteFilePath}`, { stdio: "inherit" });
|
|
18
|
+
// Step 3: Full Jest run for this file only
|
|
19
|
+
execSync(`npx jest --config ${path.resolve("jest.config.ts")} ${absoluteFilePath}`, { stdio: "inherit" });
|
|
20
|
+
return {
|
|
21
|
+
content: "✅ Tests ran successfully",
|
|
22
|
+
filepath,
|
|
23
|
+
mode: "skip",
|
|
24
|
+
summary: "All tests passed successfully."
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
const errorMessage = error?.message || String(error);
|
|
29
|
+
return {
|
|
30
|
+
content: `❌ Test run failed:\n${errorMessage}`,
|
|
31
|
+
filepath,
|
|
32
|
+
mode: "skip",
|
|
33
|
+
summary: errorMessage // provides failure context for repair
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
@@ -6,6 +6,8 @@ import { commitSuggesterModule } from '../modules/commitSuggesterModule.js';
|
|
|
6
6
|
import { changelogModule } from '../modules/changeLogModule.js';
|
|
7
7
|
import { cleanGeneratedTestsModule } from '../modules/cleanGeneratedTestsModule.js';
|
|
8
8
|
import { preserveCodeModule } from '../modules/preserveCodeModule.js';
|
|
9
|
+
import { runTestsModule } from '../modules/runTestsModule.js';
|
|
10
|
+
import { repairTestsModule } from '../modules/repairTestsModule.js';
|
|
9
11
|
// Built-in modules with metadata
|
|
10
12
|
export const builtInModules = {
|
|
11
13
|
comments: {
|
|
@@ -38,6 +40,21 @@ export const builtInModules = {
|
|
|
38
40
|
...cleanGeneratedTestsModule,
|
|
39
41
|
group: 'testing',
|
|
40
42
|
},
|
|
43
|
+
runTests: {
|
|
44
|
+
...runTestsModule,
|
|
45
|
+
group: 'testing',
|
|
46
|
+
dependencies: {
|
|
47
|
+
before: ['tests'], // must exist after tests are generated
|
|
48
|
+
after: ['cleanTests'], // run after cleaning
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
repairTests: {
|
|
52
|
+
...repairTestsModule,
|
|
53
|
+
group: 'testing',
|
|
54
|
+
dependencies: {
|
|
55
|
+
after: ['runTests'], // repair runs after tests have been executed
|
|
56
|
+
},
|
|
57
|
+
},
|
|
41
58
|
suggest: {
|
|
42
59
|
...commitSuggesterModule,
|
|
43
60
|
group: 'git',
|
package/package.json
CHANGED
package/dist/jest.config.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
const config = {
|
|
2
|
-
preset: 'ts-jest',
|
|
3
|
-
testEnvironment: 'node',
|
|
4
|
-
setupFilesAfterEnv: ['./jest.setup.ts'],
|
|
5
|
-
transform: {
|
|
6
|
-
'^.+\\.ts$': 'ts-jest',
|
|
7
|
-
},
|
|
8
|
-
moduleFileExtensions: ['ts', 'js', 'json', 'node'],
|
|
9
|
-
testPathIgnorePatterns: ['/node_modules/', '/dist/'],
|
|
10
|
-
};
|
|
11
|
-
export default config;
|
package/dist/jest.setup.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { jest } from '@jest/globals';
|
|
2
|
-
// jest.setup.ts
|
|
3
|
-
// Mock the global fetch function in Jest for Node.js 18+
|
|
4
|
-
global.fetch = jest.fn(() => Promise.resolve({
|
|
5
|
-
json: () => Promise.resolve({ response: 'Mocked Commit Message' }),
|
|
6
|
-
ok: true,
|
|
7
|
-
status: 200,
|
|
8
|
-
statusText: 'OK',
|
|
9
|
-
headers: new Headers(),
|
|
10
|
-
redirected: false,
|
|
11
|
-
type: 'default',
|
|
12
|
-
url: 'https://mocked-url.com',
|
|
13
|
-
}) // Type assertion for `Response` object
|
|
14
|
-
);
|
package/dist/utils/runTests.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { execSync } from 'child_process';
|
|
2
|
-
export function runJestTest(testFile) {
|
|
3
|
-
try {
|
|
4
|
-
execSync(`npx jest ${testFile} --silent`, { stdio: 'inherit' });
|
|
5
|
-
return true;
|
|
6
|
-
}
|
|
7
|
-
catch (err) {
|
|
8
|
-
console.error(`❌ Tests failed for ${testFile}`);
|
|
9
|
-
return false;
|
|
10
|
-
}
|
|
11
|
-
}
|