scai 0.1.105 → 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 CHANGED
@@ -157,4 +157,9 @@ Type handling with the module pipeline
157
157
  • Introduce Agent class with minimal implementation
158
158
  • Improve test-module's filepath handling and variable naming
159
159
  • Rename workflowManager.ts to agent/workflowManager.ts
160
- • Improved formatting of agent run summary output
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.
@@ -1,26 +1,25 @@
1
1
  // src/agent/agentManager.ts
2
2
  import chalk from "chalk";
3
3
  import fs from "fs/promises";
4
- import { getModuleByName } from "../pipeline/registry/moduleRegistry.js";
4
+ import { resolveModuleOrder } from "../pipeline/registry/moduleRegistry.js";
5
5
  import { handleAgentRun } from "./workflowManager.js";
6
- // Minimal agent: just collects modules and delegates to handleAgentRun
6
+ // Minimal agent: resolves modules (with before/after dependencies) and delegates to handleAgentRun
7
7
  export class Agent {
8
8
  constructor(goals) {
9
9
  // Trim goal names to avoid whitespace issues
10
- this.goals = goals.map(g => g.trim());
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);
11
15
  }
12
16
  async execute(filepath) {
13
17
  console.log(chalk.cyan(`🤖 Agent starting on: ${filepath}`));
14
- // Map goal names module objects
15
- const modules = this.goals.map(goal => {
16
- const mod = getModuleByName(goal);
17
- if (!mod)
18
- throw new Error(`❌ Unknown module: ${goal}`);
19
- return mod;
20
- });
21
- console.log(chalk.green("📋 Modules to run:"), modules.map(m => m.name).join(" → "));
22
- // Read file content
23
- const content = await fs.readFile(filepath, "utf-8");
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");
24
23
  // Delegate everything to handleAgentRun (like CLI commands do)
25
24
  await handleAgentRun(filepath, modules);
26
25
  console.log(chalk.green("✅ Agent finished!"));
@@ -4,6 +4,7 @@ import chalk from 'chalk';
4
4
  import { runModulePipeline } from '../pipeline/runModulePipeline.js';
5
5
  import { countTokens, splitCodeIntoChunks } from '../utils/splitCodeIntoChunk.js';
6
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, Config } from "./config.js";
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
@@ -25,16 +25,10 @@ import { runInteractiveSwitch } from "./commands/SwitchCmd.js";
25
25
  import { execSync } from "child_process";
26
26
  import { fileURLToPath } from "url";
27
27
  import { dirname, resolve } from "path";
28
- import { handleAgentRun } from './agent/workflowManager.js';
29
- import { addCommentsModule } from './pipeline/modules/commentModule.js';
30
- import { generateTestsModule } from './pipeline/modules/generateTestsModule.js';
31
- import { preserveCodeModule } from './pipeline/modules/preserveCodeModule.js';
32
28
  import { runInteractiveDelete } from './commands/DeleteIndex.js';
33
29
  import { resolveTargetsToFiles } from './utils/resolveTargetsToFiles.js';
34
30
  import { updateContext } from './context.js';
35
- import { cleanGeneratedTestsModule } from './pipeline/modules/cleanGeneratedTestsModule.js';
36
31
  import { Agent } from './agent/agentManager.js';
37
- import { builtInModules } from './pipeline/registry/moduleRegistry.js';
38
32
  // 🎛️ CLI Setup
39
33
  const cmd = new Command('scai')
40
34
  .version(version)
@@ -51,11 +45,13 @@ cmd
51
45
  // 🔧 Group: Agent-related commands
52
46
  const agent = cmd
53
47
  .command('agent')
54
- .description(`Run an agent workflow. Available tools:\n` +
55
- Object.keys(builtInModules).map(m => ` - ${m}`).join('\n') +
56
- `\n\nExample usage:\n` +
57
- ` $ scai agent run summary cleanup tests\n` +
58
- ` This will run the agent with the goals: summary → cleanup → tests\n`);
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`);
59
55
  // Run workflow subcommand
60
56
  const runCmd = agent
61
57
  .command('run <goals...>')
@@ -71,10 +67,12 @@ const runCmd = agent
71
67
  await agentInstance.execute(file);
72
68
  });
73
69
  });
74
- // Inject modules list into the --help output
70
+ // Optional: show example modules on --help
75
71
  runCmd.on('--help', () => {
76
- console.log('\nAvailable tools:');
77
- Object.keys(builtInModules).forEach(m => console.log(` - ${m}`));
72
+ console.log('\nExample tools:');
73
+ console.log(' - summary');
74
+ console.log(' - comments');
75
+ console.log(' - tests');
78
76
  });
79
77
  // 🔧 Group: Git-related commands
80
78
  const git = cmd.command('git').description('Git utilities');
@@ -155,10 +153,22 @@ gen
155
153
  .description("Write comments for the given file(s) or folder(s)")
156
154
  .action(async (targets) => {
157
155
  await withContext(async () => {
158
- // Remove the file type filter to allow any file
159
156
  const files = await resolveTargetsToFiles(targets);
160
157
  for (const file of files) {
161
- await handleAgentRun(file, [addCommentsModule, preserveCodeModule]);
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);
162
172
  }
163
173
  });
164
174
  });
@@ -178,17 +188,6 @@ gen
178
188
  summarizeFile(file);
179
189
  });
180
190
  });
181
- gen
182
- .command("test <targets...>")
183
- .description("Generate tests for the given file(s) or folder(s)")
184
- .action(async (targets, options) => {
185
- await withContext(async () => {
186
- const files = await resolveTargetsToFiles(targets);
187
- for (const file of files) {
188
- await handleAgentRun(file, [generateTestsModule, cleanGeneratedTestsModule]);
189
- }
190
- });
191
- });
192
191
  // ⚙️ Group: Configuration settings
193
192
  const config = cmd.command('config').description('Manage SCAI configuration');
194
193
  config
@@ -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
- const res = await fetch('http://localhost:11434/api/generate', {
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
- throw err;
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
+ }
@@ -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
- const frame = this.frames[this.i = ++this.i % this.frames.length];
13
- process.stdout.write(`\r${frame} ${this.text} `);
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(`\r✅ ${msg}\n`);
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(`\r❌ ${msg}\n`);
27
+ process.stdout.write('\r'); // go to start
28
+ console.log(`❌ ${msg}`);
23
29
  }
24
30
  stop() {
25
31
  if (this.interval) {
@@ -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 comments to explain non-obvious logic inside functions.
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 only.
52
- - Do NOT remove, change, or overwrite any existing code or comments.
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
 
@@ -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
- // Add more as needed...
9
+ // Built-in modules with metadata
10
10
  export const builtInModules = {
11
- comments: addCommentsModule,
12
- cleanup: cleanupModule,
13
- summary: summaryModule,
14
- tests: generateTestsModule,
15
- suggest: commitSuggesterModule,
16
- changelog: changelogModule,
17
- cleanTests: cleanGeneratedTestsModule,
18
- cleanComments: preserveCodeModule
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scai",
3
- "version": "0.1.105",
3
+ "version": "0.1.106",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "scai": "./dist/index.js"