scai 0.1.104 → 0.1.106
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 +13 -1
- package/dist/agent/agentManager.js +27 -0
- package/dist/{workflowManager.js → agent/workflowManager.js} +4 -3
- package/dist/context.js +1 -26
- package/dist/index.js +47 -31
- package/dist/lib/generate.js +24 -19
- package/dist/lib/spinner.js +11 -5
- package/dist/pipeline/modules/cleanGeneratedTestsModule.js +2 -1
- package/dist/pipeline/modules/commentModule.js +3 -7
- package/dist/pipeline/modules/generateTestsModule.js +33 -15
- package/dist/pipeline/registry/moduleRegistry.js +75 -11
- package/package.json +1 -1
package/dist/CHANGELOG.md
CHANGED
|
@@ -150,4 +150,16 @@ Type handling with the module pipeline
|
|
|
150
150
|
|
|
151
151
|
## 2025-08-30
|
|
152
152
|
|
|
153
|
-
* Add new workflow management functionality to handle file writes.
|
|
153
|
+
* Add new workflow management functionality to handle file writes.
|
|
154
|
+
|
|
155
|
+
## 2025-08-31
|
|
156
|
+
|
|
157
|
+
• Introduce Agent class with minimal implementation
|
|
158
|
+
• Improve test-module's filepath handling and variable naming
|
|
159
|
+
• Rename workflowManager.ts to agent/workflowManager.ts
|
|
160
|
+
• Improved formatting of agent run summary output
|
|
161
|
+
|
|
162
|
+
## 2025-08-31
|
|
163
|
+
|
|
164
|
+
• Update Spinner class to correctly display text and frames
|
|
165
|
+
• Update CLI help to reflect new agent workflow examples.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// src/agent/agentManager.ts
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import fs from "fs/promises";
|
|
4
|
+
import { resolveModuleOrder } from "../pipeline/registry/moduleRegistry.js";
|
|
5
|
+
import { handleAgentRun } from "./workflowManager.js";
|
|
6
|
+
// Minimal agent: resolves modules (with before/after dependencies) and delegates to handleAgentRun
|
|
7
|
+
export class Agent {
|
|
8
|
+
constructor(goals) {
|
|
9
|
+
// Trim goal names to avoid whitespace issues
|
|
10
|
+
this.goals = goals.map((g) => g.trim());
|
|
11
|
+
}
|
|
12
|
+
resolveModules(goals) {
|
|
13
|
+
// Use the registry helper to get the correct order
|
|
14
|
+
return resolveModuleOrder(goals);
|
|
15
|
+
}
|
|
16
|
+
async execute(filepath) {
|
|
17
|
+
console.log(chalk.cyan(`🤖 Agent starting on: ${filepath}`));
|
|
18
|
+
// Resolve modules (with before/after dependencies)
|
|
19
|
+
const modules = this.resolveModules(this.goals);
|
|
20
|
+
console.log(chalk.green("📋 Modules to run:"), modules.map((m) => m.name).join(" → "));
|
|
21
|
+
// Read file content (optional, could be used by modules in workflow)
|
|
22
|
+
await fs.readFile(filepath, "utf-8");
|
|
23
|
+
// Delegate everything to handleAgentRun (like CLI commands do)
|
|
24
|
+
await handleAgentRun(filepath, modules);
|
|
25
|
+
console.log(chalk.green("✅ Agent finished!"));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// agentManager.ts
|
|
2
2
|
import fs from 'fs/promises';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
|
-
import { runModulePipeline } from '
|
|
5
|
-
import { countTokens, splitCodeIntoChunks } from '
|
|
6
|
-
import { normalizePath } from '
|
|
4
|
+
import { runModulePipeline } from '../pipeline/runModulePipeline.js';
|
|
5
|
+
import { countTokens, splitCodeIntoChunks } from '../utils/splitCodeIntoChunk.js';
|
|
6
|
+
import { normalizePath } from '../utils/contentUtils.js';
|
|
7
|
+
// basically handles all input (chunk if large), and writing of output (overwrite, append, new file)
|
|
7
8
|
export async function handleAgentRun(filepath, modules) {
|
|
8
9
|
try {
|
|
9
10
|
filepath = normalizePath(filepath);
|
package/dist/context.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
// context.ts
|
|
2
|
-
import { readConfig, writeConfig
|
|
2
|
+
import { readConfig, writeConfig } from "./config.js";
|
|
3
3
|
import { normalizePath } from "./utils/contentUtils.js";
|
|
4
4
|
import { getHashedRepoKey } from "./utils/repoKey.js";
|
|
5
5
|
import { getDbForRepo, getDbPathForRepo } from "./db/client.js";
|
|
6
6
|
import fs from "fs";
|
|
7
7
|
import chalk from "chalk";
|
|
8
|
-
import { generate } from "./lib/generate.js"; // 👈 use your existing generate wrapper
|
|
9
|
-
import { startModelProcess } from "./utils/checkModel.js";
|
|
10
8
|
export async function updateContext() {
|
|
11
9
|
const cwd = normalizePath(process.cwd());
|
|
12
10
|
const cfg = readConfig();
|
|
@@ -56,17 +54,6 @@ export async function updateContext() {
|
|
|
56
54
|
else if (isNewRepo || activeRepoChanged) {
|
|
57
55
|
console.log(chalk.green("✅ Database present"));
|
|
58
56
|
}
|
|
59
|
-
// ✅ NEW: Ensure model is available
|
|
60
|
-
if (ok) {
|
|
61
|
-
const modelReady = await ensureModelReady();
|
|
62
|
-
if (modelReady) {
|
|
63
|
-
console.log(chalk.green("✅ Model ready"));
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
console.log(chalk.red("❌ Model not available"));
|
|
67
|
-
ok = false;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
57
|
// Final context status
|
|
71
58
|
if (ok) {
|
|
72
59
|
console.log(chalk.bold.green("\n✅ Context OK\n"));
|
|
@@ -76,15 +63,3 @@ export async function updateContext() {
|
|
|
76
63
|
}
|
|
77
64
|
return ok;
|
|
78
65
|
}
|
|
79
|
-
async function ensureModelReady() {
|
|
80
|
-
try {
|
|
81
|
-
// simple "ping" prompt that costs almost nothing
|
|
82
|
-
const res = await generate({ content: "ping" }, Config.getModel());
|
|
83
|
-
return Boolean(res?.content);
|
|
84
|
-
}
|
|
85
|
-
catch {
|
|
86
|
-
console.log(chalk.yellow("⚡ Model not responding. Attempting to start..."));
|
|
87
|
-
await startModelProcess();
|
|
88
|
-
return false;
|
|
89
|
-
}
|
|
90
|
-
}
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,6 @@ import { suggestCommitMessage } from "./commands/CommitSuggesterCmd.js";
|
|
|
8
8
|
import { bootstrap } from './modelSetup.js';
|
|
9
9
|
import { summarizeFile } from "./commands/SummaryCmd.js";
|
|
10
10
|
import { handleStandaloneChangelogUpdate } from './commands/ChangeLogUpdateCmd.js';
|
|
11
|
-
import { runModulePipelineFromCLI } from './commands/ModulePipelineCmd.js';
|
|
12
11
|
import { runIndexCommand } from './commands/IndexCmd.js';
|
|
13
12
|
import { resetDatabase } from './commands/ResetDbCmd.js';
|
|
14
13
|
import { runFindCommand } from './commands/FindCmd.js';
|
|
@@ -26,14 +25,10 @@ import { runInteractiveSwitch } from "./commands/SwitchCmd.js";
|
|
|
26
25
|
import { execSync } from "child_process";
|
|
27
26
|
import { fileURLToPath } from "url";
|
|
28
27
|
import { dirname, resolve } from "path";
|
|
29
|
-
import { handleAgentRun } from './workflowManager.js';
|
|
30
|
-
import { addCommentsModule } from './pipeline/modules/commentModule.js';
|
|
31
|
-
import { generateTestsModule } from './pipeline/modules/generateTestsModule.js';
|
|
32
|
-
import { preserveCodeModule } from './pipeline/modules/preserveCodeModule.js';
|
|
33
28
|
import { runInteractiveDelete } from './commands/DeleteIndex.js';
|
|
34
29
|
import { resolveTargetsToFiles } from './utils/resolveTargetsToFiles.js';
|
|
35
30
|
import { updateContext } from './context.js';
|
|
36
|
-
import {
|
|
31
|
+
import { Agent } from './agent/agentManager.js';
|
|
37
32
|
// 🎛️ CLI Setup
|
|
38
33
|
const cmd = new Command('scai')
|
|
39
34
|
.version(version)
|
|
@@ -47,6 +42,38 @@ cmd
|
|
|
47
42
|
await bootstrap();
|
|
48
43
|
console.log('✅ Model initialization completed!');
|
|
49
44
|
});
|
|
45
|
+
// 🔧 Group: Agent-related commands
|
|
46
|
+
const agent = cmd
|
|
47
|
+
.command('agent')
|
|
48
|
+
.description(`Run an agent workflow. Example available tools:\n` +
|
|
49
|
+
` - summary\n` +
|
|
50
|
+
` - comments\n` +
|
|
51
|
+
` - tests\n\n` +
|
|
52
|
+
`Example usage:\n` +
|
|
53
|
+
` $ scai agent run comments tests -f path/to/myfile\n` +
|
|
54
|
+
` This will run the agent with the goals: comments → tests\n`);
|
|
55
|
+
// Run workflow subcommand
|
|
56
|
+
const runCmd = agent
|
|
57
|
+
.command('run <goals...>')
|
|
58
|
+
.description('Run an agent workflow with a list of goals')
|
|
59
|
+
.option('-f, --file <filepath>', 'File to process', 'example.txt')
|
|
60
|
+
.action(async (cmdGoals, cmd) => {
|
|
61
|
+
await withContext(async () => {
|
|
62
|
+
const goals = cmdGoals;
|
|
63
|
+
const file = cmd.file;
|
|
64
|
+
console.log('Agent will execute:', goals.join(' → '));
|
|
65
|
+
console.log('On file:', file);
|
|
66
|
+
const agentInstance = new Agent(goals);
|
|
67
|
+
await agentInstance.execute(file);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
// Optional: show example modules on --help
|
|
71
|
+
runCmd.on('--help', () => {
|
|
72
|
+
console.log('\nExample tools:');
|
|
73
|
+
console.log(' - summary');
|
|
74
|
+
console.log(' - comments');
|
|
75
|
+
console.log(' - tests');
|
|
76
|
+
});
|
|
50
77
|
// 🔧 Group: Git-related commands
|
|
51
78
|
const git = cmd.command('git').description('Git utilities');
|
|
52
79
|
git
|
|
@@ -126,10 +153,22 @@ gen
|
|
|
126
153
|
.description("Write comments for the given file(s) or folder(s)")
|
|
127
154
|
.action(async (targets) => {
|
|
128
155
|
await withContext(async () => {
|
|
129
|
-
// Remove the file type filter to allow any file
|
|
130
156
|
const files = await resolveTargetsToFiles(targets);
|
|
131
157
|
for (const file of files) {
|
|
132
|
-
|
|
158
|
+
const agent = new Agent(["comments"]); // goals: "comments"
|
|
159
|
+
await agent.execute(file); // run on the file
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
gen
|
|
164
|
+
.command("test <targets...>")
|
|
165
|
+
.description("Generate tests for the given file(s) or folder(s)")
|
|
166
|
+
.action(async (targets) => {
|
|
167
|
+
await withContext(async () => {
|
|
168
|
+
const files = await resolveTargetsToFiles(targets);
|
|
169
|
+
for (const file of files) {
|
|
170
|
+
const agent = new Agent(["tests"]); // goals: "tests"
|
|
171
|
+
await agent.execute(file);
|
|
133
172
|
}
|
|
134
173
|
});
|
|
135
174
|
});
|
|
@@ -149,17 +188,6 @@ gen
|
|
|
149
188
|
summarizeFile(file);
|
|
150
189
|
});
|
|
151
190
|
});
|
|
152
|
-
gen
|
|
153
|
-
.command("test <targets...>")
|
|
154
|
-
.description("Generate tests for the given file(s) or folder(s)")
|
|
155
|
-
.action(async (targets, options) => {
|
|
156
|
-
await withContext(async () => {
|
|
157
|
-
const files = await resolveTargetsToFiles(targets);
|
|
158
|
-
for (const file of files) {
|
|
159
|
-
await handleAgentRun(file, [generateTestsModule, cleanGeneratedTestsModule]);
|
|
160
|
-
}
|
|
161
|
-
});
|
|
162
|
-
});
|
|
163
191
|
// ⚙️ Group: Configuration settings
|
|
164
192
|
const config = cmd.command('config').description('Manage SCAI configuration');
|
|
165
193
|
config
|
|
@@ -301,18 +329,6 @@ cmd
|
|
|
301
329
|
const fullQuery = questionParts?.join(' ');
|
|
302
330
|
await runAskCommand(fullQuery);
|
|
303
331
|
}));
|
|
304
|
-
cmd
|
|
305
|
-
.command('pipe')
|
|
306
|
-
.description('Run a module pipeline on a given file')
|
|
307
|
-
.argument('<file>', 'Target file')
|
|
308
|
-
.option('-m, --modules <modules>', 'Comma-separated list of modules to run (e.g., comments,cleanup,summary)')
|
|
309
|
-
.action((file, options) => {
|
|
310
|
-
if (!options.modules) {
|
|
311
|
-
console.error('❌ You must specify modules with -m or --modules');
|
|
312
|
-
process.exit(1);
|
|
313
|
-
}
|
|
314
|
-
runModulePipelineFromCLI(file, options);
|
|
315
|
-
});
|
|
316
332
|
cmd.addHelpText('after', `
|
|
317
333
|
🚨 Alpha Features:
|
|
318
334
|
- The "index", "daemon", "stop-daemon", "reset-db" commands are considered alpha features.
|
package/dist/lib/generate.js
CHANGED
|
@@ -1,35 +1,40 @@
|
|
|
1
1
|
// File: lib/generate.ts
|
|
2
2
|
import { Spinner } from './spinner.js';
|
|
3
3
|
import { readConfig } from '../config.js';
|
|
4
|
+
import { startModelProcess } from '../utils/checkModel.js';
|
|
4
5
|
export async function generate(input, model) {
|
|
5
6
|
const contextLength = readConfig().contextLength ?? 8192;
|
|
6
7
|
let prompt = input.content;
|
|
7
8
|
if (prompt.length > contextLength) {
|
|
8
|
-
console.warn(`⚠️ Warning: Input prompt length (${prompt.length}) exceeds model context length (${contextLength})
|
|
9
|
+
console.warn(`⚠️ Warning: Input prompt length (${prompt.length}) exceeds model context length (${contextLength}).`);
|
|
9
10
|
}
|
|
10
11
|
const spinner = new Spinner(`🧠 Thinking with ${model}...`);
|
|
11
12
|
spinner.start();
|
|
12
13
|
try {
|
|
13
|
-
|
|
14
|
-
method: 'POST',
|
|
15
|
-
headers: { 'Content-Type': 'application/json' },
|
|
16
|
-
body: JSON.stringify({
|
|
17
|
-
model,
|
|
18
|
-
prompt, // use truncated prompt here
|
|
19
|
-
stream: false,
|
|
20
|
-
}),
|
|
21
|
-
});
|
|
22
|
-
const data = await res.json();
|
|
23
|
-
spinner.succeed('Model response received.');
|
|
24
|
-
process.stdout.write('\n');
|
|
25
|
-
return {
|
|
26
|
-
content: data.response?.trim() ?? '',
|
|
27
|
-
filepath: input.filepath,
|
|
28
|
-
};
|
|
14
|
+
return await doGenerate(prompt, model, spinner, false);
|
|
29
15
|
}
|
|
30
16
|
catch (err) {
|
|
31
|
-
spinner.fail('Model request failed.');
|
|
17
|
+
spinner.fail('Model request failed. Attempting to start model...');
|
|
32
18
|
process.stdout.write('\n');
|
|
33
|
-
|
|
19
|
+
await startModelProcess();
|
|
20
|
+
spinner.update(`🧠 Retrying with ${model}...`);
|
|
21
|
+
return await doGenerate(prompt, model, spinner, true); // retry once
|
|
34
22
|
}
|
|
35
23
|
}
|
|
24
|
+
async function doGenerate(prompt, model, spinner, retrying) {
|
|
25
|
+
const res = await fetch('http://localhost:11434/api/generate', {
|
|
26
|
+
method: 'POST',
|
|
27
|
+
headers: { 'Content-Type': 'application/json' },
|
|
28
|
+
body: JSON.stringify({
|
|
29
|
+
model,
|
|
30
|
+
prompt,
|
|
31
|
+
stream: false,
|
|
32
|
+
}),
|
|
33
|
+
});
|
|
34
|
+
const data = await res.json();
|
|
35
|
+
spinner.succeed(retrying ? 'Model response received after restart.' : 'Model response received.');
|
|
36
|
+
process.stdout.write('\n');
|
|
37
|
+
return {
|
|
38
|
+
content: data.response?.trim() ?? '',
|
|
39
|
+
};
|
|
40
|
+
}
|
package/dist/lib/spinner.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// lib/spinner.ts
|
|
2
1
|
export class Spinner {
|
|
3
2
|
constructor(message) {
|
|
4
3
|
this.frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
@@ -8,18 +7,25 @@ export class Spinner {
|
|
|
8
7
|
}
|
|
9
8
|
start() {
|
|
10
9
|
process.stdout.write('\x1b[?25l'); // hide cursor
|
|
10
|
+
process.stdout.write(` ${this.text}`); // print text once
|
|
11
11
|
this.interval = setInterval(() => {
|
|
12
|
-
|
|
13
|
-
process.stdout.write(
|
|
12
|
+
process.stdout.write('\r'); // return to start of line
|
|
13
|
+
process.stdout.write(`${this.frames[this.i]} ${this.text}`);
|
|
14
|
+
this.i = (this.i + 1) % this.frames.length;
|
|
14
15
|
}, 80);
|
|
15
16
|
}
|
|
17
|
+
update(text) {
|
|
18
|
+
this.text = text;
|
|
19
|
+
}
|
|
16
20
|
succeed(msg) {
|
|
17
21
|
this.stop();
|
|
18
|
-
process.stdout.write(
|
|
22
|
+
process.stdout.write('\r'); // go to start
|
|
23
|
+
console.log(`✅ ${msg}`);
|
|
19
24
|
}
|
|
20
25
|
fail(msg) {
|
|
21
26
|
this.stop();
|
|
22
|
-
process.stdout.write(
|
|
27
|
+
process.stdout.write('\r'); // go to start
|
|
28
|
+
console.log(`❌ ${msg}`);
|
|
23
29
|
}
|
|
24
30
|
stop() {
|
|
25
31
|
if (this.interval) {
|
|
@@ -9,7 +9,8 @@ export const cleanGeneratedTestsModule = {
|
|
|
9
9
|
const stripped = stripMarkdownFences(normalized);
|
|
10
10
|
// filter non-code lines
|
|
11
11
|
const lines = stripped.split("\n");
|
|
12
|
-
|
|
12
|
+
// filter non-code lines, but keep blank ones
|
|
13
|
+
const codeLines = lines.filter(line => line.trim() === "" || isCodeLike(line));
|
|
13
14
|
const cleanedCode = codeLines.join("\n");
|
|
14
15
|
return {
|
|
15
16
|
originalContent: content,
|
|
@@ -43,16 +43,12 @@ You are a senior engineer reviewing a ${fileType} file.
|
|
|
43
43
|
Please:
|
|
44
44
|
|
|
45
45
|
- Add summary comments (2-3 lines) at relevant points for greater class insights
|
|
46
|
-
- Add clear, helpful inline
|
|
46
|
+
- Add clear, helpful inline-comments to explain non-obvious logic inside functions.
|
|
47
47
|
- Use "${commentSyntax}" as the comment syntax appropriate for ${fileType}.
|
|
48
|
-
- Preserve all original formatting, whitespace, and code exactly.
|
|
49
48
|
|
|
50
49
|
Rules:
|
|
51
|
-
- Return the full original chunk of code with added comments
|
|
52
|
-
-
|
|
53
|
-
- Summaries should be brief but meaningful.
|
|
54
|
-
- Inline comments should clarify complex or tricky parts only.
|
|
55
|
-
- Do NOT add any “chunk” start/end markers, numbering, or metadata to the output.
|
|
50
|
+
- Return the full original chunk of code with added comments.
|
|
51
|
+
- Inline comments should clarify complex or tricky parts.
|
|
56
52
|
|
|
57
53
|
${input.content}
|
|
58
54
|
|
|
@@ -10,32 +10,50 @@ export const generateTestsModule = {
|
|
|
10
10
|
throw new Error('Missing filepath in pipeline context');
|
|
11
11
|
const model = Config.getModel();
|
|
12
12
|
const lang = detectFileType(filepath);
|
|
13
|
+
const repoRoot = Config.getIndexDir();
|
|
14
|
+
// Compute relative import path (repo-relative, without extension)
|
|
15
|
+
const relativePath = path.relative(repoRoot, filepath);
|
|
16
|
+
const { dir, name, ext } = path.parse(relativePath);
|
|
17
|
+
const importPath = './' + path.join(dir, name).replace(/\\/g, '/');
|
|
18
|
+
// Where the test should be written (next to source file)
|
|
19
|
+
const absParsed = path.parse(filepath);
|
|
20
|
+
const testPath = path.join(absParsed.dir, `${absParsed.name}.test${ext}`);
|
|
13
21
|
const prompt = `
|
|
14
|
-
|
|
22
|
+
You are a senior ${lang.toUpperCase()} engineer. Generate a Jest test file for the module below.
|
|
15
23
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
Requirements:
|
|
25
|
+
- Use the 'jest' test framework.
|
|
26
|
+
- Always include imports at the top:
|
|
27
|
+
import { describe, it, expect } from '@jest/globals';
|
|
28
|
+
import * as moduleUnderTest from '${importPath}';
|
|
29
|
+
- Cover only one public method: the most relevant or central function.
|
|
30
|
+
- Include one edge case for that method.
|
|
31
|
+
- Preserve and consider existing code comments in the module.
|
|
32
|
+
- Only output valid ${lang} code; do not include markdown fences or explanations.
|
|
33
|
+
- Use this scaffold at minimum:
|
|
23
34
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
35
|
+
import { describe, it, expect } from '@jest/globals';
|
|
36
|
+
import * as moduleUnderTest from '${importPath}';
|
|
37
|
+
|
|
38
|
+
describe('moduleUnderTest', () => {
|
|
39
|
+
it('should ...', () => {
|
|
40
|
+
// test implementation
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
--- MODULE CODE ---
|
|
45
|
+
${content}
|
|
46
|
+
--- END MODULE CODE ---
|
|
47
|
+
`.trim();
|
|
28
48
|
const response = await generate({ content: prompt }, model);
|
|
29
49
|
if (!response)
|
|
30
50
|
throw new Error('⚠️ No test code returned from model');
|
|
31
|
-
const { dir, name } = path.parse(filepath);
|
|
32
|
-
const testPath = path.join(dir, `${name}.test.ts`);
|
|
33
51
|
return {
|
|
34
52
|
originalContent: content,
|
|
35
53
|
content: response.content, // the test code
|
|
36
54
|
filepath, // original file path
|
|
37
55
|
newFilepath: testPath,
|
|
38
|
-
mode: "newFile" //
|
|
56
|
+
mode: "newFile" // ensure it gets written as a new file
|
|
39
57
|
};
|
|
40
58
|
}
|
|
41
59
|
};
|
|
@@ -6,24 +6,88 @@ 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
|
-
//
|
|
10
|
-
const builtInModules = {
|
|
11
|
-
comments:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
cleanComments:
|
|
9
|
+
// Built-in modules with metadata
|
|
10
|
+
export const builtInModules = {
|
|
11
|
+
comments: {
|
|
12
|
+
...addCommentsModule,
|
|
13
|
+
group: 'documentation',
|
|
14
|
+
dependencies: {
|
|
15
|
+
after: ['cleanComments'], // run cleanComments after comments
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
cleanComments: {
|
|
19
|
+
...preserveCodeModule,
|
|
20
|
+
group: 'documentation',
|
|
21
|
+
},
|
|
22
|
+
cleanup: {
|
|
23
|
+
...cleanupModule,
|
|
24
|
+
group: 'maintenance',
|
|
25
|
+
},
|
|
26
|
+
summary: {
|
|
27
|
+
...summaryModule,
|
|
28
|
+
group: 'documentation',
|
|
29
|
+
},
|
|
30
|
+
tests: {
|
|
31
|
+
...generateTestsModule,
|
|
32
|
+
group: 'testing',
|
|
33
|
+
dependencies: {
|
|
34
|
+
after: ['cleanTests'], // run cleanTests after tests
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
cleanTests: {
|
|
38
|
+
...cleanGeneratedTestsModule,
|
|
39
|
+
group: 'testing',
|
|
40
|
+
},
|
|
41
|
+
suggest: {
|
|
42
|
+
...commitSuggesterModule,
|
|
43
|
+
group: 'git',
|
|
44
|
+
},
|
|
45
|
+
changelog: {
|
|
46
|
+
...changelogModule,
|
|
47
|
+
group: 'git',
|
|
48
|
+
},
|
|
19
49
|
};
|
|
50
|
+
// Get module by name
|
|
20
51
|
export function getModuleByName(name) {
|
|
21
52
|
return builtInModules[name];
|
|
22
53
|
}
|
|
23
54
|
// Return module metadata for CLI or UI
|
|
24
55
|
export function listAvailableModules() {
|
|
25
|
-
return Object.values(builtInModules).map(({ name, description }) => ({
|
|
56
|
+
return Object.values(builtInModules).map(({ name, description, group, dependencies }) => ({
|
|
26
57
|
name,
|
|
27
58
|
description: description || 'No description available',
|
|
59
|
+
group,
|
|
60
|
+
dependencies,
|
|
28
61
|
}));
|
|
29
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Resolve module execution order including before/after dependencies.
|
|
65
|
+
* Returns a unique ordered array of PromptModuleMeta.
|
|
66
|
+
*/
|
|
67
|
+
export function resolveModuleOrder(moduleNames) {
|
|
68
|
+
const resolved = [];
|
|
69
|
+
const visited = new Set();
|
|
70
|
+
function visit(name) {
|
|
71
|
+
if (visited.has(name))
|
|
72
|
+
return;
|
|
73
|
+
const mod = getModuleByName(name);
|
|
74
|
+
if (!mod)
|
|
75
|
+
return;
|
|
76
|
+
// Handle before dependencies first
|
|
77
|
+
if (mod.dependencies?.before) {
|
|
78
|
+
for (const dep of mod.dependencies.before)
|
|
79
|
+
visit(dep);
|
|
80
|
+
}
|
|
81
|
+
// Add the module itself
|
|
82
|
+
resolved.push(mod);
|
|
83
|
+
visited.add(name);
|
|
84
|
+
// Handle after dependencies
|
|
85
|
+
if (mod.dependencies?.after) {
|
|
86
|
+
for (const dep of mod.dependencies.after)
|
|
87
|
+
visit(dep);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
for (const name of moduleNames)
|
|
91
|
+
visit(name);
|
|
92
|
+
return resolved;
|
|
93
|
+
}
|