scai 0.1.13 β†’ 0.1.14

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/README.md CHANGED
@@ -102,6 +102,13 @@ scai summ <file>
102
102
 
103
103
  Prints a summary of what the code does directly to your terminal.
104
104
 
105
+ To pipe input for summarization:
106
+
107
+ ```bash
108
+ cat <file> | scai summ
109
+ ```
110
+
111
+
105
112
  ---
106
113
 
107
114
  ### πŸ“ Update the changelog
@@ -1,14 +1,16 @@
1
+ // File: src/commands/CommitSuggesterCmd.ts
1
2
  import { execSync } from 'child_process';
2
3
  import readline from 'readline';
4
+ import { commitSuggesterModule } from '../pipeline/modules/commitSuggesterModule.js';
3
5
  function askUserToChoose(suggestions) {
4
6
  return new Promise((resolve) => {
5
7
  console.log('\nπŸ’‘ AI-suggested commit messages:\n');
6
- suggestions.slice(0, 3).forEach((msg, i) => {
8
+ suggestions.forEach((msg, i) => {
7
9
  console.log(`${i + 1}) ${msg}`);
8
10
  });
9
11
  console.log('\n---');
10
- console.log('4) πŸ” Regenerate suggestions');
11
- console.log('5) ✍️ Write your own commit message');
12
+ console.log(`${suggestions.length + 1}) πŸ” Regenerate suggestions`);
13
+ console.log(`${suggestions.length + 2}) ✍️ Write your own commit message`);
12
14
  const rl = readline.createInterface({
13
15
  input: process.stdin,
14
16
  output: process.stdout,
@@ -24,32 +26,11 @@ function askUserToChoose(suggestions) {
24
26
  resolve('custom');
25
27
  }
26
28
  else {
27
- resolve(choice - 1); // Return 0-based index (0 to 3)
29
+ resolve(choice - 1);
28
30
  }
29
31
  });
30
32
  });
31
33
  }
32
- async function generateSuggestions(prompt) {
33
- const res = await fetch("http://localhost:11434/api/generate", {
34
- method: "POST",
35
- headers: { "Content-Type": "application/json" },
36
- body: JSON.stringify({
37
- model: "llama3",
38
- prompt,
39
- stream: false,
40
- }),
41
- });
42
- const { response } = await res.json();
43
- if (!response || typeof response !== 'string') {
44
- throw new Error('Invalid LLM response');
45
- }
46
- const lines = response.trim().split('\n').filter(line => /^\d+\.\s+/.test(line));
47
- const messages = lines.map(line => line.replace(/^\d+\.\s+/, '').replace(/^"(.*)"$/, '$1').trim());
48
- if (messages.length === 0) {
49
- throw new Error('No valid commit messages found in LLM response.');
50
- }
51
- return messages;
52
- }
53
34
  function promptCustomMessage() {
54
35
  return new Promise((resolve) => {
55
36
  const rl = readline.createInterface({
@@ -72,19 +53,12 @@ export async function suggestCommitMessage(options) {
72
53
  console.log('⚠️ No staged changes to suggest a message for.');
73
54
  return;
74
55
  }
75
- const prompt = `Suggest 3 concise, conventional Git commit message options for this diff. Return ONLY the commit messages, numbered 1 to 3, like so:
76
- 1. ...
77
- 2. ...
78
- 3. ...
79
-
80
- Here is the diff:
81
- ${diff}`;
56
+ let suggestions = [];
82
57
  let message = null;
83
58
  while (message === null) {
84
- const suggestions = await generateSuggestions(prompt);
59
+ suggestions = await commitSuggesterModule.run({ code: diff }).then(out => out.suggestions || []);
85
60
  const choice = await askUserToChoose(suggestions);
86
61
  if (choice === suggestions.length) {
87
- // User chose "Regenerate"
88
62
  console.log('\nπŸ”„ Regenerating suggestions...\n');
89
63
  continue;
90
64
  }
@@ -0,0 +1,22 @@
1
+ import ora from 'ora';
2
+ export async function generate(prompt, model) {
3
+ const spinner = ora(`🧠 Thinking with ${model}...`).start();
4
+ try {
5
+ const res = await fetch('http://localhost:11434/api/generate', {
6
+ method: 'POST',
7
+ headers: { 'Content-Type': 'application/json' },
8
+ body: JSON.stringify({
9
+ model,
10
+ prompt,
11
+ stream: false,
12
+ }),
13
+ });
14
+ const data = await res.json();
15
+ spinner.succeed('βœ… Model response received.');
16
+ return data.response?.trim() ?? '';
17
+ }
18
+ catch (err) {
19
+ spinner.fail('❌ Model request failed.');
20
+ throw err;
21
+ }
22
+ }
@@ -1,4 +1,5 @@
1
1
  import { ModelConfig } from '../../config/ModelConfig.js';
2
+ import { generate } from '../../lib/generate.js';
2
3
  export const changelogModule = {
3
4
  name: 'changelogModule',
4
5
  description: 'Generates changelog entry based on Git diff',
@@ -14,17 +15,7 @@ ${input.code}
14
15
  βœ… If the changes are significant, return a changelog entry.
15
16
  ❌ If not, return ONLY: "NO UPDATE".
16
17
  `.trim();
17
- const res = await fetch('http://localhost:11434/api/generate', {
18
- method: 'POST',
19
- headers: { 'Content-Type': 'application/json' },
20
- body: JSON.stringify({
21
- model,
22
- prompt,
23
- stream: false,
24
- }),
25
- });
26
- const data = await res.json();
27
- const output = data.response?.trim() ?? '';
18
+ const output = await generate(prompt, model);
28
19
  return { code: output === 'NO UPDATE' ? '' : output };
29
20
  },
30
21
  };
@@ -1,4 +1,5 @@
1
1
  import { ModelConfig } from '../../config/ModelConfig.js';
2
+ import { generate } from '../../lib/generate.js';
2
3
  export const addCommentsModule = {
3
4
  name: 'addComments',
4
5
  description: 'Adds meaningful // comments to each block of code',
@@ -21,16 +22,7 @@ Your task is to add clear and helpful single-line // comments to complex or non-
21
22
  ${input.code}
22
23
  --- CODE END ---
23
24
  `.trim();
24
- const res = await fetch('http://localhost:11434/api/generate', {
25
- method: 'POST',
26
- headers: { 'Content-Type': 'application/json' },
27
- body: JSON.stringify({
28
- model,
29
- prompt,
30
- stream: false,
31
- }),
32
- });
33
- const data = await res.json();
34
- return { code: data.response?.trim() ?? '' };
25
+ const output = await generate(prompt, model);
26
+ return { code: output === 'NO UPDATE' ? '' : output };
35
27
  },
36
28
  };
@@ -0,0 +1,30 @@
1
+ import { generate } from '../../lib/generate.js';
2
+ import { ModelConfig } from '../../config/ModelConfig.js';
3
+ export const commitSuggesterModule = {
4
+ name: 'commitSuggester',
5
+ description: 'Suggests conventional commit messages from Git diff',
6
+ async run({ code }) {
7
+ const model = ModelConfig.getModel();
8
+ const prompt = `
9
+ Suggest 3 concise, conventional Git commit messages based on this diff.
10
+ Use this format ONLY:
11
+
12
+ 1. feat: ...
13
+ 2. fix: ...
14
+ 3. refactor: ...
15
+
16
+ Here is the diff:
17
+ ${code}
18
+ `.trim();
19
+ const raw = await generate(prompt, model);
20
+ const lines = raw
21
+ .split('\n')
22
+ .map(line => line.trim())
23
+ .filter(line => /^\d+\.\s+/.test(line));
24
+ const suggestions = lines.map(line => line.replace(/^\d+\.\s+/, '').replace(/^"(.*)"$/, '$1').trim());
25
+ return {
26
+ code,
27
+ suggestions
28
+ };
29
+ }
30
+ };
@@ -1,12 +1,16 @@
1
+ // src/pipeline/modules/generateTestsModule.ts
1
2
  import fs from 'fs/promises';
2
3
  import path from 'path';
3
4
  import { ModelConfig } from '../../config/ModelConfig.js';
5
+ import { generate } from '../../lib/generate.js';
4
6
  export const generateTestsModule = {
5
7
  name: 'generateTests',
6
8
  description: 'Generate a Jest test file for the class/module',
7
9
  async run({ code, filepath }) {
8
10
  const model = ModelConfig.getModel();
9
11
  const lang = ModelConfig.getLanguage();
12
+ if (!filepath)
13
+ throw new Error('Missing filepath in pipeline context');
10
14
  const prompt = `
11
15
  You are a senior ${lang.toUpperCase()} engineer. Given the following class or module, generate a Jest test file.
12
16
 
@@ -20,22 +24,9 @@ Guidelines:
20
24
  ${code}
21
25
  --- CODE END ---
22
26
  `.trim();
23
- const res = await fetch('http://localhost:11434/api/generate', {
24
- method: 'POST',
25
- headers: { 'Content-Type': 'application/json' },
26
- body: JSON.stringify({
27
- model,
28
- prompt,
29
- stream: false
30
- })
31
- });
32
- const data = await res.json();
33
- const testCode = data.response?.trim();
27
+ const testCode = await generate(prompt, model);
34
28
  if (!testCode)
35
29
  throw new Error('⚠️ No test code returned from model');
36
- // Determine test file path next to refactored file
37
- if (!filepath)
38
- throw new Error('Missing filepath in pipeline context');
39
30
  const { dir, name } = path.parse(filepath);
40
31
  const testPath = path.join(dir, `${name}.test.ts`);
41
32
  await fs.writeFile(testPath, testCode, 'utf-8');
@@ -1,4 +1,5 @@
1
1
  import { ModelConfig } from '../../config/ModelConfig.js';
2
+ import { generate } from '../../lib/generate.js';
2
3
  export const refactorModule = {
3
4
  name: 'refactor',
4
5
  description: 'Break code into small, clean functions',
@@ -9,37 +10,19 @@ export const refactorModule = {
9
10
  You are a senior ${lang.toUpperCase()} engineer.
10
11
 
11
12
  Refactor the following code:
12
- - Refactor only long and complex functions
13
- - Keep original names and semantics.
13
+ - Only split up long and complex functions
14
+ - Preserve original names and semantics
14
15
  - Do NOT insert comments
16
+ - Output the full, valid ${lang.toUpperCase()} code
15
17
 
16
18
  --- CODE START ---
17
19
  ${input.code}
18
20
  --- CODE END ---
19
- `.trim();
20
- try {
21
- const res = await fetch('http://localhost:11434/api/generate', {
22
- method: 'POST',
23
- headers: { 'Content-Type': 'application/json' },
24
- body: JSON.stringify({
25
- model,
26
- prompt,
27
- stream: false
28
- })
29
- });
30
- if (!res.ok) {
31
- const errBody = await res.text();
32
- throw new Error(`❌ Refactor failed: ${res.status} ${res.statusText} - ${errBody}`);
33
- }
34
- const data = await res.json();
35
- if (!data.response) {
36
- throw new Error('❌ No response field returned from model.');
37
- }
38
- return { code: data.response.trim() };
39
- }
40
- catch (err) {
41
- console.error('πŸ”₯ Error in refactorModule:', err);
42
- throw new Error('❌ Error in refactor module: ' + err.message);
21
+ `.trim();
22
+ const response = await generate(prompt, model);
23
+ if (!response) {
24
+ throw new Error('❌ Model returned empty response for refactoring.');
43
25
  }
26
+ return { code: response };
44
27
  }
45
28
  };
@@ -1,4 +1,5 @@
1
1
  import { ModelConfig } from '../../config/ModelConfig.js';
2
+ import { generate } from '../../lib/generate.js';
2
3
  export const summaryModule = {
3
4
  name: 'summary',
4
5
  description: 'Prints a summary of changes to the terminal',
@@ -26,14 +27,8 @@ Take the following source code and do NOT modify it in any way. Your task is:
26
27
  --- CODE START ---
27
28
  ${code}
28
29
  --- CODE END ---
29
- `.trim();
30
- const res = await fetch('http://localhost:11434/api/generate', {
31
- method: 'POST',
32
- headers: { 'Content-Type': 'application/json' },
33
- body: JSON.stringify({ model, prompt, stream: false })
34
- });
35
- const data = await res.json();
36
- const summary = data.response?.trim();
30
+ `.trim();
31
+ const summary = await generate(prompt, model);
37
32
  if (summary) {
38
33
  console.log('\nπŸ“ Code Summary:\n');
39
34
  console.log(summary);
@@ -41,7 +36,7 @@ ${code}
41
36
  else {
42
37
  console.warn('⚠️ No summary generated.');
43
38
  }
44
- // Return unchanged input so it’s pipe-safe, if reused in pipelines
39
+ // Return unchanged input for composability
45
40
  return { code };
46
41
  }
47
42
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scai",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "scai": "./dist/index.js"
@@ -24,7 +24,8 @@
24
24
  "start": "node dist/index.js"
25
25
  },
26
26
  "dependencies": {
27
- "commander": "^11.0.0"
27
+ "commander": "^11.0.0",
28
+ "ora": "^8.2.0"
28
29
  },
29
30
  "devDependencies": {
30
31
  "@types/jest": "^30.0.0",