scai 0.1.117 → 0.1.119
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 +88 -503
- 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 +173 -214
- package/dist/db/functionExtractors/extractFromTs.js +159 -160
- 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 -14
- 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 +1 -11
- package/dist/pipeline/modules/changeLogModule.js +16 -19
- package/dist/pipeline/modules/chunkManagerModule.js +24 -0
- package/dist/pipeline/modules/cleanupModule.js +95 -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 +143 -228
- package/dist/utils/buildContextualPrompt.js +245 -172
- 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/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
package/dist/index.js
CHANGED
|
@@ -1,341 +1,144 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import readline from 'readline';
|
|
3
|
+
import { spawn } from 'child_process';
|
|
2
4
|
import { createRequire } from 'module';
|
|
3
5
|
const require = createRequire(import.meta.url);
|
|
4
|
-
const
|
|
5
|
-
import {
|
|
6
|
-
import { Config } from './config.js';
|
|
7
|
-
import { suggestCommitMessage } from "./commands/CommitSuggesterCmd.js";
|
|
8
|
-
import { bootstrap } from './modelSetup.js';
|
|
9
|
-
import { summarizeFile } from "./commands/SummaryCmd.js";
|
|
10
|
-
import { handleStandaloneChangelogUpdate } from './commands/ChangeLogUpdateCmd.js';
|
|
11
|
-
import { runIndexCommand } from './commands/IndexCmd.js';
|
|
12
|
-
import { resetDatabase } from './commands/ResetDbCmd.js';
|
|
13
|
-
import { runFindCommand } from './commands/FindCmd.js';
|
|
14
|
-
import { startDaemon } from './commands/DaemonCmd.js';
|
|
15
|
-
import { runStopDaemonCommand } from "./commands/StopDaemonCmd.js";
|
|
6
|
+
const shellQuote = require('shell-quote');
|
|
7
|
+
import { createProgram as cmdFactory, withContext } from './commands/factory.js';
|
|
16
8
|
import { runAskCommand } from './commands/AskCmd.js';
|
|
17
|
-
import {
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
.
|
|
88
|
-
.description('Suggest a commit message from staged changes and optionally commit')
|
|
89
|
-
.option('-l, --changelog', 'Generate and optionally stage a changelog entry')
|
|
90
|
-
.action(async (options) => {
|
|
91
|
-
await withContext(async () => {
|
|
92
|
-
suggestCommitMessage(options);
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
git
|
|
96
|
-
.command('check')
|
|
97
|
-
.description('Check Git working directory and branch status')
|
|
98
|
-
.action(async () => {
|
|
99
|
-
await withContext(async () => {
|
|
100
|
-
checkGit();
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
// Add auth-related commands
|
|
104
|
-
const auth = cmd.command('auth').description('GitHub authentication commands');
|
|
105
|
-
// ⚡ Auth commands
|
|
106
|
-
auth
|
|
107
|
-
.command('check')
|
|
108
|
-
.description('Check if GitHub authentication is set up and valid')
|
|
109
|
-
.action(async () => {
|
|
110
|
-
await withContext(async () => {
|
|
9
|
+
import { setRl } from './commands/ReadlineSingleton.js';
|
|
10
|
+
const program = cmdFactory(); // single Commander instance
|
|
11
|
+
let inShell = false;
|
|
12
|
+
const customCommands = {};
|
|
13
|
+
// =====================================================
|
|
14
|
+
// TEST QUERIES
|
|
15
|
+
// =====================================================
|
|
16
|
+
const testQueries = [
|
|
17
|
+
'please write me comprehensive comments for spatialmap.js and typescript.ts files',
|
|
18
|
+
'refactor spatialmap.js to improve readability and reduce nesting',
|
|
19
|
+
'explain the intent and architecture of the semantic analysis module',
|
|
20
|
+
'add validation and error handling to the context review step',
|
|
21
|
+
'summarize this repo architecture and identify weak coupling points',
|
|
22
|
+
'Where are all the database queries defined?',
|
|
23
|
+
'Which functions have no tests written for them yet?',
|
|
24
|
+
'What could cause memory leaks in this codebase?',
|
|
25
|
+
'How does authentication work here?',
|
|
26
|
+
'Is there a clear separation between frontend and backend code?',
|
|
27
|
+
'please refactor the buildContextualPrompt into smaller functions',
|
|
28
|
+
'Is error handling consistent across the codebase?',
|
|
29
|
+
'What’s missing from the README?',
|
|
30
|
+
'How do I run the test suite?',
|
|
31
|
+
'Are there any flaky tests in this repo?',
|
|
32
|
+
'Are there any security vulnerabilities in our dependencies?',
|
|
33
|
+
'Is there any dead code we can safely remove?'
|
|
34
|
+
];
|
|
35
|
+
// =====================================================
|
|
36
|
+
// HELPERS
|
|
37
|
+
// =====================================================
|
|
38
|
+
function pickRandom(items) {
|
|
39
|
+
return items[Math.floor(Math.random() * items.length)];
|
|
40
|
+
}
|
|
41
|
+
async function runQuery(query) {
|
|
42
|
+
await withContext(() => runAskCommand(query));
|
|
43
|
+
}
|
|
44
|
+
// =====================================================
|
|
45
|
+
// BUILT-IN COMMANDS
|
|
46
|
+
// =====================================================
|
|
47
|
+
customCommands.test = async () => {
|
|
48
|
+
await runQuery(testQueries[0]);
|
|
49
|
+
};
|
|
50
|
+
customCommands['test-random'] = async () => {
|
|
51
|
+
const query = pickRandom(testQueries);
|
|
52
|
+
console.log(`\n🎲 [test-random] Selected query:\n→ ${query}\n`);
|
|
53
|
+
await runQuery(query);
|
|
54
|
+
};
|
|
55
|
+
customCommands.exit = async () => {
|
|
56
|
+
console.log('Exiting...');
|
|
57
|
+
process.exit(0);
|
|
58
|
+
};
|
|
59
|
+
customCommands.q = customCommands.exit;
|
|
60
|
+
customCommands.quit = customCommands.exit;
|
|
61
|
+
// =====================================================
|
|
62
|
+
// EXTENSION API
|
|
63
|
+
// =====================================================
|
|
64
|
+
export function registerCommand(name, fn) {
|
|
65
|
+
customCommands[name] = fn;
|
|
66
|
+
}
|
|
67
|
+
async function startShell() {
|
|
68
|
+
if (inShell)
|
|
69
|
+
return;
|
|
70
|
+
inShell = true;
|
|
71
|
+
const rl = readline.createInterface({
|
|
72
|
+
input: process.stdin,
|
|
73
|
+
output: process.stdout,
|
|
74
|
+
prompt: 'scai> ',
|
|
75
|
+
historySize: 200,
|
|
76
|
+
});
|
|
77
|
+
setRl(rl);
|
|
78
|
+
rl.prompt();
|
|
79
|
+
rl.on('line', async (line) => {
|
|
111
80
|
try {
|
|
112
|
-
const
|
|
113
|
-
if (!
|
|
114
|
-
|
|
81
|
+
const trimmed = line.trim();
|
|
82
|
+
if (!trimmed) {
|
|
83
|
+
rl.prompt();
|
|
115
84
|
return;
|
|
116
85
|
}
|
|
117
|
-
|
|
118
|
-
|
|
86
|
+
// --- Shell command
|
|
87
|
+
if (trimmed.startsWith('!')) {
|
|
88
|
+
const child = spawn(trimmed.slice(1).trim(), { shell: true, stdio: 'inherit' });
|
|
89
|
+
child.on('exit', () => rl.prompt());
|
|
90
|
+
child.on('error', (err) => { console.error('Shell command error:', err); rl.prompt(); });
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
// --- Slash commands
|
|
94
|
+
if (trimmed.startsWith('/')) {
|
|
95
|
+
const argvParts = shellQuote.parse(trimmed.slice(1))
|
|
96
|
+
.map((tok) => typeof tok === 'object' ? tok.op ?? tok.pattern ?? '' : String(tok))
|
|
97
|
+
.filter(Boolean);
|
|
98
|
+
const cmdName = argvParts[0];
|
|
99
|
+
if (customCommands[cmdName]) {
|
|
100
|
+
await customCommands[cmdName]();
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
try {
|
|
104
|
+
await program.parseAsync(argvParts, { from: 'user' });
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
console.error('Command error:', err instanceof Error ? err.message : err);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
rl.prompt();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
// --- Bare input → LLM
|
|
114
|
+
await withContext(() => runAskCommand(trimmed));
|
|
115
|
+
rl.prompt();
|
|
119
116
|
}
|
|
120
117
|
catch (err) {
|
|
121
|
-
console.error(
|
|
122
|
-
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
auth
|
|
126
|
-
.command('reset')
|
|
127
|
-
.description('Reset GitHub authentication credentials')
|
|
128
|
-
.action(async () => {
|
|
129
|
-
await withContext(async () => {
|
|
130
|
-
Config.setGitHubToken('');
|
|
131
|
-
console.log('🔄 GitHub authentication has been reset.');
|
|
132
|
-
const token = Config.getGitHubToken();
|
|
133
|
-
console.log(token ? '❌ Token still exists in the configuration.' : '✅ Token successfully removed.');
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
auth
|
|
137
|
-
.command('set')
|
|
138
|
-
.description('Set your GitHub Personal Access Token')
|
|
139
|
-
.action(async () => {
|
|
140
|
-
await withContext(async () => {
|
|
141
|
-
const token = await promptForToken();
|
|
142
|
-
Config.setGitHubToken(token.trim());
|
|
143
|
-
console.log('🔑 GitHub token set successfully.');
|
|
144
|
-
});
|
|
145
|
-
});
|
|
146
|
-
// 🛠️ Group: `gen` commands for content generation
|
|
147
|
-
const gen = cmd.command('gen').description('Generate code-related output');
|
|
148
|
-
gen
|
|
149
|
-
.command("comm <targets...>")
|
|
150
|
-
.description("Write comments for the given file(s) or folder(s)")
|
|
151
|
-
.action(async (targets) => {
|
|
152
|
-
await withContext(async () => {
|
|
153
|
-
const files = await resolveTargetsToFiles(targets);
|
|
154
|
-
for (const file of files) {
|
|
155
|
-
const agent = new Agent(["comments"]); // goals: "comments"
|
|
156
|
-
await agent.execute(file); // run on the file
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
gen
|
|
161
|
-
.command("test <targets...>")
|
|
162
|
-
.description("Generate tests for the given file(s) or folder(s)")
|
|
163
|
-
.action(async (targets) => {
|
|
164
|
-
await withContext(async () => {
|
|
165
|
-
const files = await resolveTargetsToFiles(targets);
|
|
166
|
-
for (const file of files) {
|
|
167
|
-
const agent = new Agent(["tests"]); // goals: "tests"
|
|
168
|
-
await agent.execute(file);
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
gen
|
|
173
|
-
.command('changelog')
|
|
174
|
-
.description('Update or create the CHANGELOG.md based on current Git diff')
|
|
175
|
-
.action(async () => {
|
|
176
|
-
await withContext(async () => {
|
|
177
|
-
await handleStandaloneChangelogUpdate();
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
gen
|
|
181
|
-
.command('summ [file]')
|
|
182
|
-
.description('Print a summary of the given file to the terminal')
|
|
183
|
-
.action(async (file) => {
|
|
184
|
-
await withContext(async () => {
|
|
185
|
-
summarizeFile(file);
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
// ⚙️ Group: Configuration settings
|
|
189
|
-
const config = cmd.command('config').description('Manage SCAI configuration');
|
|
190
|
-
config
|
|
191
|
-
.command('set-model <model>')
|
|
192
|
-
.description('Set the model to use')
|
|
193
|
-
.option('-g, --global', 'Set the global default model instead of the active repo')
|
|
194
|
-
.action(async (model, options) => {
|
|
195
|
-
await withContext(async () => {
|
|
196
|
-
const scope = options.global ? 'global' : 'repo';
|
|
197
|
-
Config.setModel(model, scope);
|
|
198
|
-
Config.show();
|
|
199
|
-
});
|
|
200
|
-
});
|
|
201
|
-
config
|
|
202
|
-
.command('set-lang <lang>')
|
|
203
|
-
.description('Set the programming language')
|
|
204
|
-
.action(async (lang) => {
|
|
205
|
-
await withContext(async () => {
|
|
206
|
-
Config.setLanguage(lang);
|
|
207
|
-
Config.show();
|
|
208
|
-
});
|
|
209
|
-
});
|
|
210
|
-
config
|
|
211
|
-
.command("show")
|
|
212
|
-
.option("--raw", "Show full raw config")
|
|
213
|
-
.description("Display current configuration")
|
|
214
|
-
.action(async (options) => {
|
|
215
|
-
await withContext(async () => {
|
|
216
|
-
if (options.raw) {
|
|
217
|
-
console.log(JSON.stringify(Config.getRaw(), null, 2));
|
|
218
|
-
}
|
|
219
|
-
else {
|
|
220
|
-
Config.show();
|
|
118
|
+
console.error('REPL error:', err instanceof Error ? err.stack : err);
|
|
119
|
+
rl.prompt();
|
|
221
120
|
}
|
|
222
121
|
});
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
.
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
.
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
});
|
|
238
|
-
index
|
|
239
|
-
.command('list')
|
|
240
|
-
.description('List all indexed repositories')
|
|
241
|
-
.action(async () => await withContext(async () => {
|
|
242
|
-
await Config.printAllRepos();
|
|
243
|
-
}));
|
|
244
|
-
index
|
|
245
|
-
.command('switch')
|
|
246
|
-
.description('Switch active repository (by interactive list only)')
|
|
247
|
-
.action(async () => await withContext(async () => {
|
|
248
|
-
await runInteractiveSwitch();
|
|
249
|
-
}));
|
|
250
|
-
index
|
|
251
|
-
.command('delete')
|
|
252
|
-
.description('Delete a repository from the index (interactive)')
|
|
253
|
-
.action(async () => await withContext(async () => {
|
|
254
|
-
await runInteractiveDelete();
|
|
255
|
-
}));
|
|
256
|
-
const db = cmd.command('db').description('Database operations');
|
|
257
|
-
db
|
|
258
|
-
.command('check')
|
|
259
|
-
.description('Run the dbcheck script to check the database status')
|
|
260
|
-
.action(async () => await withContext(async () => {
|
|
261
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
262
|
-
const __dirname = dirname(__filename);
|
|
263
|
-
const scriptPath = resolve(__dirname, '..', 'dist/scripts', 'dbcheck.js');
|
|
264
|
-
console.log(`🚀 Running database check script: ${scriptPath}`);
|
|
122
|
+
rl.on('close', () => { console.log('Bye!'); process.exit(0); });
|
|
123
|
+
process.on('SIGINT', () => { console.log('\nExiting REPL...'); rl.close(); });
|
|
124
|
+
}
|
|
125
|
+
// ---------------- Main -----------------
|
|
126
|
+
async function main() {
|
|
127
|
+
process.on('unhandledRejection', (reason) => console.error('Unhandled Rejection:', reason));
|
|
128
|
+
process.on('uncaughtException', (err) => { console.error('Uncaught Exception:', err.stack ?? err); process.exit(1); });
|
|
129
|
+
const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
|
|
130
|
+
const args = process.argv.slice(2);
|
|
131
|
+
// Run REPL if no args OR user typed `scai shell`
|
|
132
|
+
if (isInteractive && (args.length === 0 || (args.length === 1 && args[0] === 'shell'))) {
|
|
133
|
+
await startShell();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
265
136
|
try {
|
|
266
|
-
|
|
137
|
+
await program.parseAsync(process.argv);
|
|
267
138
|
}
|
|
268
139
|
catch (err) {
|
|
269
|
-
console.error('
|
|
140
|
+
console.error('CLI Error:', err instanceof Error ? err.message : err);
|
|
141
|
+
process.exit(1);
|
|
270
142
|
}
|
|
271
|
-
}));
|
|
272
|
-
db
|
|
273
|
-
.command('reset')
|
|
274
|
-
.description('Delete and reset the SQLite database')
|
|
275
|
-
.action(async () => await withContext(async () => {
|
|
276
|
-
await resetDatabase();
|
|
277
|
-
}));
|
|
278
|
-
db
|
|
279
|
-
.command('inspect')
|
|
280
|
-
.argument('<filepath>', 'Path to the file to inspect')
|
|
281
|
-
.description('Inspect a specific file and print its indexed summary and functions')
|
|
282
|
-
.action(async (filepath) => await withContext(async () => {
|
|
283
|
-
await runInspectCommand(filepath);
|
|
284
|
-
}));
|
|
285
|
-
const daemon = cmd
|
|
286
|
-
.command('daemon')
|
|
287
|
-
.description('Background summarizer operations');
|
|
288
|
-
// Start the daemon
|
|
289
|
-
daemon
|
|
290
|
-
.command('start')
|
|
291
|
-
.description('Run background summarization of indexed files')
|
|
292
|
-
.action(async () => {
|
|
293
|
-
await withContext(async () => {
|
|
294
|
-
await startDaemon();
|
|
295
|
-
});
|
|
296
|
-
});
|
|
297
|
-
// Stop the daemon
|
|
298
|
-
daemon
|
|
299
|
-
.command('stop')
|
|
300
|
-
.description('Stop the background summarizer daemon')
|
|
301
|
-
.action(async () => {
|
|
302
|
-
await withContext(async () => {
|
|
303
|
-
await runStopDaemonCommand();
|
|
304
|
-
});
|
|
305
|
-
});
|
|
306
|
-
cmd
|
|
307
|
-
.command('backup')
|
|
308
|
-
.description('Backup the current .scai folder')
|
|
309
|
-
.action(async () => await withContext(async () => {
|
|
310
|
-
await runBackupCommand();
|
|
311
|
-
}));
|
|
312
|
-
cmd
|
|
313
|
-
.command('find <query>')
|
|
314
|
-
.description('Search indexed files by keyword')
|
|
315
|
-
.action(async (query) => await withContext(async () => {
|
|
316
|
-
await runFindCommand(query);
|
|
317
|
-
}));
|
|
318
|
-
cmd
|
|
319
|
-
.command('ask [question...]')
|
|
320
|
-
.description('Ask a question based on indexed files')
|
|
321
|
-
.action(async (questionParts) => await withContext(async () => {
|
|
322
|
-
const fullQuery = questionParts?.join(' ');
|
|
323
|
-
await runAskCommand(fullQuery);
|
|
324
|
-
}));
|
|
325
|
-
cmd.addHelpText('after', `
|
|
326
|
-
🚨 Alpha Features:
|
|
327
|
-
- The "index", "daemon", "stop-daemon", "reset-db" commands are considered alpha features.
|
|
328
|
-
- These commands are in active development and may change in the future.
|
|
329
|
-
|
|
330
|
-
💡 Use with caution and expect possible changes or instability.
|
|
331
|
-
`);
|
|
332
|
-
async function withContext(action) {
|
|
333
|
-
const ok = await updateContext();
|
|
334
|
-
//if (!ok) process.exit(1);
|
|
335
|
-
await action();
|
|
336
143
|
}
|
|
337
|
-
|
|
338
|
-
cmd.parseAsync(process.argv).catch((err) => {
|
|
339
|
-
console.error(err.message); // only show our styled message
|
|
340
|
-
process.exit(1);
|
|
341
|
-
});
|
|
144
|
+
main();
|
package/dist/lib/generate.js
CHANGED
|
@@ -2,24 +2,36 @@
|
|
|
2
2
|
import { Spinner } from './spinner.js';
|
|
3
3
|
import { Config, readConfig } from '../config.js';
|
|
4
4
|
import { startModelProcess } from '../utils/checkModel.js';
|
|
5
|
+
/**
|
|
6
|
+
* The generate module uses the local model API to produce output
|
|
7
|
+
* based on the `content` of the input. It returns the response
|
|
8
|
+
* on the `data` property of ModuleIO.
|
|
9
|
+
*/
|
|
5
10
|
export async function generate(input) {
|
|
6
11
|
const model = Config.getModel();
|
|
7
|
-
const contextLength = readConfig()
|
|
8
|
-
|
|
9
|
-
const
|
|
12
|
+
const { contextLength } = readConfig();
|
|
13
|
+
// Safely build prompt
|
|
14
|
+
const queryPart = input.query ? `User query:\n${input.query}\n\n` : "";
|
|
15
|
+
const contentPart = input.content
|
|
16
|
+
? (typeof input.content === "string"
|
|
17
|
+
? input.content
|
|
18
|
+
: JSON.stringify(input.content, null, 2))
|
|
19
|
+
: "";
|
|
20
|
+
const prompt = `${queryPart}${contentPart}`.trim();
|
|
21
|
+
const spinner = new Spinner();
|
|
10
22
|
spinner.start();
|
|
11
23
|
try {
|
|
12
|
-
|
|
24
|
+
const data = await doGenerate(prompt, model, contextLength, spinner, false);
|
|
25
|
+
return { query: input.query, data };
|
|
13
26
|
}
|
|
14
|
-
catch
|
|
15
|
-
spinner.fail('Model request failed. Attempting
|
|
16
|
-
process.stdout.write('\n');
|
|
27
|
+
catch {
|
|
28
|
+
spinner.fail('Model request failed. Attempting restart...');
|
|
17
29
|
await startModelProcess();
|
|
18
|
-
|
|
19
|
-
return
|
|
30
|
+
const data = await doGenerate(prompt, model, contextLength, spinner, true);
|
|
31
|
+
return { query: input.query, data };
|
|
20
32
|
}
|
|
21
33
|
}
|
|
22
|
-
async function doGenerate(prompt, model, spinner, retrying) {
|
|
34
|
+
async function doGenerate(prompt, model, contextLength, spinner, retrying) {
|
|
23
35
|
const res = await fetch('http://localhost:11434/api/generate', {
|
|
24
36
|
method: 'POST',
|
|
25
37
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -27,12 +39,23 @@ async function doGenerate(prompt, model, spinner, retrying) {
|
|
|
27
39
|
model,
|
|
28
40
|
prompt,
|
|
29
41
|
stream: false,
|
|
42
|
+
options: {
|
|
43
|
+
num_ctx: contextLength,
|
|
44
|
+
},
|
|
30
45
|
}),
|
|
31
46
|
});
|
|
47
|
+
if (!res.ok) {
|
|
48
|
+
throw new Error(`Model request failed with status ${res.status}`);
|
|
49
|
+
}
|
|
32
50
|
const data = await res.json();
|
|
33
|
-
|
|
51
|
+
if (retrying) {
|
|
52
|
+
spinner.succeed('Model response received after restart.');
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
spinner.succeed();
|
|
56
|
+
}
|
|
57
|
+
process.stdout.write('\n');
|
|
58
|
+
return data.response?.trim() ?? '';
|
|
34
59
|
process.stdout.write('\n');
|
|
35
|
-
return
|
|
36
|
-
content: data.response?.trim() ?? '',
|
|
37
|
-
};
|
|
60
|
+
return data.response?.trim() ?? '';
|
|
38
61
|
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { getDbForRepo } from "../db/client.js";
|
|
2
|
+
import { generate } from "../lib/generate.js";
|
|
3
|
+
import { cleanupModule } from "../pipeline/modules/cleanupModule.js";
|
|
4
|
+
import path from "path";
|
|
5
|
+
export async function generateFolderCapsules() {
|
|
6
|
+
const db = getDbForRepo();
|
|
7
|
+
// --- Load files (paths only) ---
|
|
8
|
+
const files = db
|
|
9
|
+
.prepare(`SELECT path FROM files`)
|
|
10
|
+
.all();
|
|
11
|
+
// --- Group files by folder ---
|
|
12
|
+
const folders = new Map();
|
|
13
|
+
for (const { path: filePath } of files) {
|
|
14
|
+
const dir = path.dirname(filePath) || "/";
|
|
15
|
+
const depth = dir === "/" ? 0 : dir.split("/").length;
|
|
16
|
+
if (!folders.has(dir)) {
|
|
17
|
+
folders.set(dir, {
|
|
18
|
+
path: dir,
|
|
19
|
+
depth,
|
|
20
|
+
files: [],
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
folders.get(dir).files.push({
|
|
24
|
+
name: path.basename(filePath),
|
|
25
|
+
ext: path.extname(filePath).replace(".", "") || "unknown",
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
// --- Generate one capsule per folder ---
|
|
29
|
+
for (const folder of folders.values()) {
|
|
30
|
+
const byType = {};
|
|
31
|
+
for (const f of folder.files) {
|
|
32
|
+
byType[f.ext] = (byType[f.ext] ?? 0) + 1;
|
|
33
|
+
}
|
|
34
|
+
const inputPayload = {
|
|
35
|
+
path: folder.path,
|
|
36
|
+
depth: folder.depth,
|
|
37
|
+
fileCount: folder.files.length,
|
|
38
|
+
byType,
|
|
39
|
+
files: folder.files.map((f) => f.name),
|
|
40
|
+
};
|
|
41
|
+
const input = {
|
|
42
|
+
query: `Generate a FolderCapsule for ${folder.path}`,
|
|
43
|
+
content: `
|
|
44
|
+
You are generating a structured FolderCapsule describing the architectural role of a folder.
|
|
45
|
+
|
|
46
|
+
Return ONLY valid JSON matching this TypeScript interface:
|
|
47
|
+
|
|
48
|
+
interface FolderCapsule {
|
|
49
|
+
path: string;
|
|
50
|
+
depth: number;
|
|
51
|
+
stats: {
|
|
52
|
+
fileCount: number;
|
|
53
|
+
byType: Record<string, number>;
|
|
54
|
+
};
|
|
55
|
+
roles: string[];
|
|
56
|
+
concerns: string[];
|
|
57
|
+
keyFiles: { path: string; reason: string }[];
|
|
58
|
+
dependencies: {
|
|
59
|
+
importsFrom: string[];
|
|
60
|
+
usedBy: string[];
|
|
61
|
+
};
|
|
62
|
+
confidence: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
Folder facts:
|
|
66
|
+
${JSON.stringify(inputPayload, null, 2)}
|
|
67
|
+
`.trim(),
|
|
68
|
+
};
|
|
69
|
+
const response = await generate(input);
|
|
70
|
+
// --- Cleanup model output ---
|
|
71
|
+
const cleaned = await cleanupModule.run({
|
|
72
|
+
query: input.query,
|
|
73
|
+
content: response.content,
|
|
74
|
+
});
|
|
75
|
+
let capsule = null;
|
|
76
|
+
try {
|
|
77
|
+
capsule = JSON.parse(String(cleaned.content));
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
console.warn(`⚠️ Failed to parse FolderCapsule for ${folder.path}`);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
db.prepare(`
|
|
84
|
+
INSERT INTO folder_capsules
|
|
85
|
+
(path, depth, capsule_json, confidence, last_generated, source_file_count)
|
|
86
|
+
VALUES
|
|
87
|
+
(
|
|
88
|
+
@path,
|
|
89
|
+
@depth,
|
|
90
|
+
@capsule_json,
|
|
91
|
+
@confidence,
|
|
92
|
+
datetime('now'),
|
|
93
|
+
@source_file_count
|
|
94
|
+
)
|
|
95
|
+
ON CONFLICT(path) DO UPDATE SET
|
|
96
|
+
capsule_json = excluded.capsule_json,
|
|
97
|
+
confidence = excluded.confidence,
|
|
98
|
+
last_generated = datetime('now'),
|
|
99
|
+
source_file_count = excluded.source_file_count
|
|
100
|
+
`).run({
|
|
101
|
+
path: folder.path,
|
|
102
|
+
depth: folder.depth,
|
|
103
|
+
capsule_json: JSON.stringify(capsule),
|
|
104
|
+
confidence: capsule?.confidence ?? null,
|
|
105
|
+
source_file_count: folder.files.length,
|
|
106
|
+
});
|
|
107
|
+
console.log(`📦 Folder capsule generated for ${folder.path}`);
|
|
108
|
+
}
|
|
109
|
+
}
|