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
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
// File: src/commands/CommitSuggesterCmd.ts
|
|
2
|
-
import { execSync } from 'child_process';
|
|
3
|
-
import readline from 'readline';
|
|
4
|
-
import { commitSuggesterModule } from '../pipeline/modules/commitSuggesterModule.js';
|
|
5
|
-
import { handleChangelogWithCommitMessage } from './ChangeLogUpdateCmd.js';
|
|
6
|
-
import os from 'os';
|
|
2
|
+
import { execSync, spawnSync } from 'child_process';
|
|
7
3
|
import fs from 'fs';
|
|
4
|
+
import os from 'os';
|
|
8
5
|
import path from 'path';
|
|
9
|
-
import { spawnSync } from 'child_process';
|
|
10
6
|
import chalk from 'chalk';
|
|
11
|
-
|
|
7
|
+
import { commitSuggesterModule } from '../pipeline/modules/commitSuggesterModule.js';
|
|
8
|
+
import { handleChangelogWithCommitMessage } from './ChangeLogUpdateCmd.js';
|
|
9
|
+
import { getRl } from './ReadlineSingleton.js';
|
|
10
|
+
// --- Use the passed readline instance ---
|
|
11
|
+
function askUserToChoose(rl, suggestions) {
|
|
12
12
|
return new Promise((resolve) => {
|
|
13
13
|
console.log('\n💡 AI-suggested commit messages:\n');
|
|
14
14
|
suggestions.forEach((msg, i) => {
|
|
@@ -19,28 +19,18 @@ function askUserToChoose(suggestions) {
|
|
|
19
19
|
console.log(`${suggestions.length + 2}) ✍️ Write your own commit message`);
|
|
20
20
|
console.log(`${suggestions.length + 3}) 🖋️ Edit a suggested commit message`);
|
|
21
21
|
console.log(`${suggestions.length + 4}) ❌ Cancel`);
|
|
22
|
-
const rl = readline.createInterface({
|
|
23
|
-
input: process.stdin,
|
|
24
|
-
output: process.stdout,
|
|
25
|
-
});
|
|
26
22
|
rl.question(`\n👉 Choose a commit message [1-${suggestions.length + 4}]: `, (answer) => {
|
|
27
|
-
rl.close();
|
|
28
23
|
const choice = parseInt(answer, 10);
|
|
29
|
-
if (choice === suggestions.length + 1)
|
|
24
|
+
if (choice === suggestions.length + 1)
|
|
30
25
|
resolve('regenerate');
|
|
31
|
-
|
|
32
|
-
else if (choice === suggestions.length + 2) {
|
|
26
|
+
else if (choice === suggestions.length + 2)
|
|
33
27
|
resolve('custom');
|
|
34
|
-
|
|
35
|
-
else if (choice === suggestions.length + 3) {
|
|
28
|
+
else if (choice === suggestions.length + 3)
|
|
36
29
|
resolve('edit');
|
|
37
|
-
|
|
38
|
-
else if (choice === suggestions.length + 4) {
|
|
30
|
+
else if (choice === suggestions.length + 4)
|
|
39
31
|
resolve('cancel');
|
|
40
|
-
|
|
41
|
-
else if (!isNaN(choice) && choice >= 1 && choice <= suggestions.length) {
|
|
32
|
+
else if (!isNaN(choice) && choice >= 1 && choice <= suggestions.length)
|
|
42
33
|
resolve(choice - 1);
|
|
43
|
-
}
|
|
44
34
|
else {
|
|
45
35
|
console.log('⚠️ Invalid selection. Using the first suggestion by default.');
|
|
46
36
|
resolve(0);
|
|
@@ -48,39 +38,18 @@ function askUserToChoose(suggestions) {
|
|
|
48
38
|
});
|
|
49
39
|
});
|
|
50
40
|
}
|
|
51
|
-
|
|
52
|
-
const tmpFilePath = path.join(os.tmpdir(), 'scai-commit-msg.txt');
|
|
53
|
-
fs.writeFileSync(tmpFilePath, `# Edit your commit message below.\n# Lines starting with '#' will be ignored.\n\n${suggestedMessage}`);
|
|
54
|
-
const editor = process.env.EDITOR || (process.platform === 'win32' ? 'notepad' : 'vi');
|
|
55
|
-
spawnSync(editor, [tmpFilePath], { stdio: 'inherit' });
|
|
56
|
-
const editedContent = fs.readFileSync(tmpFilePath, 'utf-8');
|
|
57
|
-
return editedContent
|
|
58
|
-
.split('\n')
|
|
59
|
-
.filter(line => !line.trim().startsWith('#'))
|
|
60
|
-
.join('\n')
|
|
61
|
-
.trim() || suggestedMessage;
|
|
62
|
-
}
|
|
63
|
-
function askWhichSuggestionToEdit(suggestions) {
|
|
41
|
+
function askWhichSuggestionToEdit(rl, suggestions) {
|
|
64
42
|
return new Promise((resolve) => {
|
|
65
43
|
console.log('\n🖋️ Select a commit message to edit:\n');
|
|
66
|
-
suggestions.forEach((msg, i) => {
|
|
67
|
-
console.log(`${i + 1}) ${chalk.hex('#FFA500')(`\`${msg}\``)}`);
|
|
68
|
-
});
|
|
44
|
+
suggestions.forEach((msg, i) => console.log(`${i + 1}) ${chalk.hex('#FFA500')(`\`${msg}\``)}`));
|
|
69
45
|
console.log(`${suggestions.length + 1}) ❌ Cancel`);
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
output: process.stdout,
|
|
73
|
-
});
|
|
74
|
-
const editPrompt = chalk.magenta(`\n👉 Choose a commit message to edit [1-${suggestions.length + 1}]: `);
|
|
75
|
-
rl.question(editPrompt, (answer) => {
|
|
76
|
-
rl.close();
|
|
46
|
+
const promptText = chalk.magenta(`\n👉 Choose a commit message to edit [1-${suggestions.length + 1}]: `);
|
|
47
|
+
rl.question(promptText, (answer) => {
|
|
77
48
|
const choice = parseInt(answer, 10);
|
|
78
|
-
if (!isNaN(choice) && choice >= 1 && choice <= suggestions.length)
|
|
49
|
+
if (!isNaN(choice) && choice >= 1 && choice <= suggestions.length)
|
|
79
50
|
resolve(choice - 1);
|
|
80
|
-
|
|
81
|
-
else if (choice === suggestions.length + 1) {
|
|
51
|
+
else if (choice === suggestions.length + 1)
|
|
82
52
|
resolve('cancel');
|
|
83
|
-
}
|
|
84
53
|
else {
|
|
85
54
|
console.log('⚠️ Invalid selection.');
|
|
86
55
|
resolve('cancel');
|
|
@@ -88,30 +57,38 @@ function askWhichSuggestionToEdit(suggestions) {
|
|
|
88
57
|
});
|
|
89
58
|
});
|
|
90
59
|
}
|
|
91
|
-
function promptCustomMessage() {
|
|
60
|
+
function promptCustomMessage(rl) {
|
|
92
61
|
return new Promise((resolve) => {
|
|
93
|
-
const rl = readline.createInterface({
|
|
94
|
-
input: process.stdin,
|
|
95
|
-
output: process.stdout,
|
|
96
|
-
});
|
|
97
62
|
rl.question('\n📝 Enter your custom commit message:\n> ', (input) => {
|
|
98
|
-
rl.close();
|
|
99
63
|
resolve(input.trim());
|
|
100
64
|
});
|
|
101
65
|
});
|
|
102
66
|
}
|
|
67
|
+
async function promptEditCommitMessage(suggestedMessage) {
|
|
68
|
+
const tmpFilePath = path.join(os.tmpdir(), 'scai-commit-msg.txt');
|
|
69
|
+
fs.writeFileSync(tmpFilePath, `# Edit your commit message below.\n# Lines starting with '#' will be ignored.\n\n${suggestedMessage}`);
|
|
70
|
+
const editor = process.env.EDITOR || (process.platform === 'win32' ? 'notepad' : 'vi');
|
|
71
|
+
spawnSync(editor, [tmpFilePath], { stdio: 'inherit' });
|
|
72
|
+
const editedContent = fs.readFileSync(tmpFilePath, 'utf-8');
|
|
73
|
+
return editedContent
|
|
74
|
+
.split('\n')
|
|
75
|
+
.filter(line => !line.trim().startsWith('#'))
|
|
76
|
+
.join('\n')
|
|
77
|
+
.trim() || suggestedMessage;
|
|
78
|
+
}
|
|
79
|
+
// --- Main function ---
|
|
103
80
|
export async function suggestCommitMessage(options) {
|
|
81
|
+
const { rl, isTemporary } = getRl();
|
|
104
82
|
try {
|
|
105
83
|
let diff = execSync("git diff --cached", { encoding: "utf-8", stdio: "pipe" }).trim();
|
|
106
|
-
if (!diff)
|
|
84
|
+
if (!diff)
|
|
107
85
|
diff = execSync("git diff", { encoding: "utf-8", stdio: "pipe" }).trim();
|
|
108
|
-
}
|
|
109
86
|
if (!diff) {
|
|
110
87
|
console.log('⚠️ No staged changes to suggest a message for.');
|
|
111
88
|
return;
|
|
112
89
|
}
|
|
113
|
-
|
|
114
|
-
const response = await commitSuggesterModule.run(
|
|
90
|
+
const input = { query: 'Generate commit messages', content: diff };
|
|
91
|
+
const response = await commitSuggesterModule.run(input);
|
|
115
92
|
const suggestions = response.suggestions || [];
|
|
116
93
|
if (!suggestions.length) {
|
|
117
94
|
console.log('⚠️ No commit suggestions generated.');
|
|
@@ -119,22 +96,19 @@ export async function suggestCommitMessage(options) {
|
|
|
119
96
|
}
|
|
120
97
|
let message = null;
|
|
121
98
|
while (message === null) {
|
|
122
|
-
const choice = await askUserToChoose(suggestions);
|
|
99
|
+
const choice = await askUserToChoose(rl, suggestions);
|
|
123
100
|
if (choice === 'regenerate') {
|
|
124
101
|
console.log('\n🔄 Regenerating suggestions...\n');
|
|
125
|
-
const
|
|
126
|
-
suggestions.splice(0, suggestions.length, ...(
|
|
102
|
+
const newResponse = await commitSuggesterModule.run(input);
|
|
103
|
+
suggestions.splice(0, suggestions.length, ...(newResponse.suggestions || []));
|
|
127
104
|
continue;
|
|
128
105
|
}
|
|
129
|
-
if (choice === 'custom')
|
|
130
|
-
message = await promptCustomMessage();
|
|
131
|
-
}
|
|
106
|
+
if (choice === 'custom')
|
|
107
|
+
message = await promptCustomMessage(rl);
|
|
132
108
|
else if (choice === 'edit') {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (typeof editChoice === 'number') {
|
|
109
|
+
const editChoice = await askWhichSuggestionToEdit(rl, suggestions);
|
|
110
|
+
if (typeof editChoice === 'number')
|
|
136
111
|
message = await promptEditCommitMessage(suggestions[editChoice]);
|
|
137
|
-
}
|
|
138
112
|
else {
|
|
139
113
|
console.log('⚠️ Edit cancelled, returning to main menu.');
|
|
140
114
|
continue;
|
|
@@ -144,26 +118,27 @@ export async function suggestCommitMessage(options) {
|
|
|
144
118
|
console.log('❌ Commit cancelled.');
|
|
145
119
|
return;
|
|
146
120
|
}
|
|
147
|
-
else
|
|
121
|
+
else
|
|
148
122
|
message = suggestions[choice];
|
|
149
|
-
}
|
|
150
123
|
}
|
|
151
124
|
console.log(`\n✅ Selected commit message:\n${message}\n`);
|
|
152
|
-
|
|
153
|
-
if (options.changelog) {
|
|
125
|
+
if (options.changelog)
|
|
154
126
|
await handleChangelogWithCommitMessage(message);
|
|
155
|
-
}
|
|
156
127
|
const staged = execSync("git diff --cached", { encoding: "utf-8" }).trim();
|
|
157
128
|
if (!staged) {
|
|
158
129
|
console.log("⚠️ No files are currently staged for commit.");
|
|
159
130
|
console.log("👉 Please stage your changes with 'git add <files>' and rerun the command.");
|
|
160
131
|
return;
|
|
161
132
|
}
|
|
162
|
-
// Automatically commit the suggested message
|
|
163
133
|
execSync(`git commit -m "${message.replace(/"/g, '\\"')}"`, { stdio: 'inherit' });
|
|
164
134
|
console.log('✅ Committed with selected message.');
|
|
165
135
|
}
|
|
166
136
|
catch (err) {
|
|
167
137
|
console.error('❌ Error in commit message suggestion:', err.message);
|
|
168
138
|
}
|
|
139
|
+
finally {
|
|
140
|
+
if (isTemporary) {
|
|
141
|
+
rl.close(); // 👈 THIS is what allows the process to exit
|
|
142
|
+
}
|
|
143
|
+
}
|
|
169
144
|
}
|
|
@@ -1,44 +1,134 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import { log } from '../utils/log.js';
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
4
3
|
import { spawn } from 'child_process';
|
|
5
4
|
import { fileURLToPath } from 'url';
|
|
6
|
-
import
|
|
5
|
+
import { LOG_PATH, PID_PATH, CONFIG_LOCK_PATH } from '../constants.js';
|
|
6
|
+
import { Config } from '../config.js';
|
|
7
|
+
import { getDbPathForRepo } from '../db/client.js';
|
|
8
|
+
// --- Helpers ---
|
|
9
|
+
function isProcessRunning(pid) {
|
|
10
|
+
try {
|
|
11
|
+
process.kill(pid, 0);
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function lockConfig(repoKey) {
|
|
19
|
+
fs.writeFileSync(CONFIG_LOCK_PATH, repoKey, { encoding: 'utf8' });
|
|
20
|
+
}
|
|
21
|
+
function unlockConfigFile() {
|
|
22
|
+
if (fs.existsSync(CONFIG_LOCK_PATH)) {
|
|
23
|
+
fs.unlinkSync(CONFIG_LOCK_PATH);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function getLockedRepo() {
|
|
27
|
+
return fs.existsSync(CONFIG_LOCK_PATH)
|
|
28
|
+
? fs.readFileSync(CONFIG_LOCK_PATH, 'utf8')
|
|
29
|
+
: null;
|
|
30
|
+
}
|
|
31
|
+
// --- Commands ---
|
|
7
32
|
export async function startDaemon() {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
33
|
+
const cfg = Config.getRaw();
|
|
34
|
+
const activeRepo = cfg.activeRepo;
|
|
35
|
+
if (!activeRepo) {
|
|
36
|
+
console.log('❌ No active repo configured. Use `askcmd repo set <path>` first.');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const dbPath = getDbPathForRepo();
|
|
40
|
+
if (!fs.existsSync(dbPath)) {
|
|
41
|
+
console.log(`❌ Cannot start daemon. Index/database not initialized for repo '${activeRepo}'.`);
|
|
42
|
+
console.log(` Run the index command first: scai index start`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const lockedRepo = getLockedRepo();
|
|
46
|
+
if (lockedRepo && lockedRepo !== activeRepo) {
|
|
47
|
+
console.log(`🔒 Daemon already locked to repo: ${lockedRepo}`);
|
|
48
|
+
console.log(` Current active repo: ${activeRepo}`);
|
|
49
|
+
console.log(` Stop the daemon before starting another.`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (fs.existsSync(PID_PATH)) {
|
|
53
|
+
const pid = parseInt(fs.readFileSync(PID_PATH, 'utf8'), 10);
|
|
54
|
+
if (isProcessRunning(pid)) {
|
|
55
|
+
console.log(`⚠️ Daemon already running with PID ${pid}.`);
|
|
14
56
|
return;
|
|
15
57
|
}
|
|
16
|
-
|
|
17
|
-
log(
|
|
18
|
-
|
|
58
|
+
else {
|
|
59
|
+
console.log(`🧹 Removing stale PID file...`);
|
|
60
|
+
fs.unlinkSync(PID_PATH);
|
|
19
61
|
}
|
|
20
62
|
}
|
|
21
|
-
log(
|
|
22
|
-
log(`📝 Logs will be saved to: ${LOG_PATH}`);
|
|
63
|
+
console.log(`🚀 Starting daemon for repo: ${activeRepo}`);
|
|
23
64
|
const __filename = fileURLToPath(import.meta.url);
|
|
24
65
|
const __dirname = path.dirname(__filename);
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
66
|
+
const workerPath = path.join(__dirname, '../daemon/daemonWorker.js');
|
|
67
|
+
fs.mkdirSync(path.dirname(LOG_PATH), { recursive: true });
|
|
68
|
+
fs.mkdirSync(path.dirname(PID_PATH), { recursive: true });
|
|
69
|
+
const out = fs.openSync(LOG_PATH, 'a');
|
|
70
|
+
const err = fs.openSync(LOG_PATH, 'a');
|
|
71
|
+
const child = spawn(process.execPath, [workerPath], {
|
|
29
72
|
detached: true,
|
|
30
|
-
stdio: ['ignore', out, err],
|
|
31
|
-
env: {
|
|
32
|
-
...process.env,
|
|
33
|
-
BACKGROUND_MODE: 'true',
|
|
34
|
-
}
|
|
73
|
+
stdio: ['ignore', out, err],
|
|
74
|
+
env: { ...process.env, BACKGROUND_MODE: 'true' },
|
|
35
75
|
});
|
|
36
76
|
child.unref();
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
77
|
+
fs.writeFileSync(PID_PATH, String(child.pid));
|
|
78
|
+
lockConfig(activeRepo);
|
|
79
|
+
console.log(`✅ Daemon started (PID ${child.pid})`);
|
|
80
|
+
}
|
|
81
|
+
export async function stopDaemon() {
|
|
82
|
+
if (!fs.existsSync(PID_PATH)) {
|
|
83
|
+
console.log(`ℹ️ No PID file found.`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const pid = parseInt(fs.readFileSync(PID_PATH, 'utf8'), 10);
|
|
87
|
+
if (isProcessRunning(pid)) {
|
|
88
|
+
console.log(`🛑 Stopping daemon (PID ${pid})...`);
|
|
89
|
+
process.kill(pid);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
console.log(`⚠️ Stale PID ${pid}.`);
|
|
93
|
+
}
|
|
94
|
+
fs.unlinkSync(PID_PATH);
|
|
95
|
+
unlockConfigFile();
|
|
96
|
+
console.log(`✅ Daemon stopped and unlocked.`);
|
|
97
|
+
}
|
|
98
|
+
export async function statusDaemon() {
|
|
99
|
+
const lockedRepo = getLockedRepo();
|
|
100
|
+
if (!fs.existsSync(PID_PATH)) {
|
|
101
|
+
console.log(`🔴 Daemon not running.`);
|
|
102
|
+
if (lockedRepo)
|
|
103
|
+
console.log(` (Locked to repo ${lockedRepo})`);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const pid = parseInt(fs.readFileSync(PID_PATH, 'utf8'), 10);
|
|
107
|
+
if (isProcessRunning(pid)) {
|
|
108
|
+
console.log(`🟢 Daemon running (PID ${pid})`);
|
|
109
|
+
if (lockedRepo)
|
|
110
|
+
console.log(` ↳ Repo: ${lockedRepo}`);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
console.log(`🟡 Stale PID file (${pid}) found.`);
|
|
40
114
|
}
|
|
41
|
-
|
|
42
|
-
|
|
115
|
+
}
|
|
116
|
+
export async function restartDaemon() {
|
|
117
|
+
console.log(`♻️ Restarting daemon...`);
|
|
118
|
+
await stopDaemon();
|
|
119
|
+
await startDaemon();
|
|
120
|
+
}
|
|
121
|
+
export async function unlockConfig() {
|
|
122
|
+
unlockConfigFile();
|
|
123
|
+
console.log(`🔓 Configuration unlocked.`);
|
|
124
|
+
}
|
|
125
|
+
export async function showLogs(lines = 20) {
|
|
126
|
+
if (!fs.existsSync(LOG_PATH)) {
|
|
127
|
+
console.log(`ℹ️ No logs yet.`);
|
|
128
|
+
return;
|
|
43
129
|
}
|
|
130
|
+
const content = fs.readFileSync(LOG_PATH, 'utf8');
|
|
131
|
+
const tail = content.split('\n').slice(-lines).join('\n');
|
|
132
|
+
console.log(`\n--- Daemon Logs (last ${lines} lines) ---\n`);
|
|
133
|
+
console.log(tail);
|
|
44
134
|
}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import fg from 'fast-glob';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { initSchema } from '../db/schema.js';
|
|
5
|
-
import { indexFile } from '../db/fileIndex.js';
|
|
6
5
|
import { detectFileType } from '../fileRules/detectFileType.js';
|
|
7
6
|
import { startDaemon } from './DaemonCmd.js';
|
|
8
7
|
import { IGNORED_FOLDER_GLOBS } from '../fileRules/ignoredPaths.js';
|
|
@@ -10,11 +9,11 @@ import { Config } from '../config.js';
|
|
|
10
9
|
import { log } from '../utils/log.js';
|
|
11
10
|
import lockfile from 'proper-lockfile';
|
|
12
11
|
import { classifyFile } from '../fileRules/classifyFile.js';
|
|
13
|
-
import { getDbPathForRepo } from '../db/client.js';
|
|
12
|
+
import { getDbForRepo, getDbPathForRepo } from '../db/client.js';
|
|
13
|
+
import { upsertFileTemplate } from '../db/sqlTemplates.js';
|
|
14
14
|
async function lockDb() {
|
|
15
15
|
try {
|
|
16
|
-
|
|
17
|
-
return lock;
|
|
16
|
+
return await lockfile.lock(getDbPathForRepo());
|
|
18
17
|
}
|
|
19
18
|
catch (err) {
|
|
20
19
|
log('❌ Failed to acquire DB lock: ' + err);
|
|
@@ -37,29 +36,47 @@ export async function runIndexCommand() {
|
|
|
37
36
|
ignore: IGNORED_FOLDER_GLOBS,
|
|
38
37
|
absolute: true,
|
|
39
38
|
});
|
|
39
|
+
const db = getDbForRepo();
|
|
40
|
+
const release = await lockDb();
|
|
40
41
|
const countByExt = {};
|
|
41
42
|
let count = 0;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
43
|
+
try {
|
|
44
|
+
for (const file of files) {
|
|
45
|
+
const classification = classifyFile(file);
|
|
46
|
+
if (classification !== 'valid') {
|
|
47
|
+
log(`⏭️ Skipping (${classification}): ${file}`);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const normalizedPath = path.normalize(file).replace(/\\/g, '/');
|
|
52
|
+
const filename = path.basename(normalizedPath);
|
|
53
|
+
const type = detectFileType(file);
|
|
54
|
+
// -----------------------------
|
|
55
|
+
// ENQUEUE: mark file as unprocessed
|
|
56
|
+
// -----------------------------
|
|
57
|
+
db.prepare(upsertFileTemplate).run({
|
|
58
|
+
path: normalizedPath,
|
|
59
|
+
filename,
|
|
60
|
+
summary: null,
|
|
61
|
+
type,
|
|
62
|
+
lastModified: null,
|
|
63
|
+
indexedAt: null,
|
|
64
|
+
});
|
|
65
|
+
const ext = path.extname(file);
|
|
66
|
+
countByExt[ext] = (countByExt[ext] || 0) + 1;
|
|
67
|
+
log(`📄 Enqueued: ${path.relative(indexDir, file)}`);
|
|
68
|
+
count++;
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
log(`⚠️ Skipped in indexCmd ${file}: ${err instanceof Error ? err.message : err}`);
|
|
72
|
+
}
|
|
59
73
|
}
|
|
60
74
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
75
|
+
finally {
|
|
76
|
+
await release();
|
|
77
|
+
}
|
|
78
|
+
log('📊 Files by extension:', JSON.stringify(countByExt, null, 2));
|
|
79
|
+
log(`✅ Done. Enqueued ${count} files for indexing.`);
|
|
80
|
+
// Kick the daemon — it now owns all processing
|
|
64
81
|
startDaemon();
|
|
65
82
|
}
|
|
@@ -25,7 +25,6 @@ export async function runInspectCommand(fileArg) {
|
|
|
25
25
|
console.log(`🆔 File ID: ${file.id}`);
|
|
26
26
|
console.log(`📄 Indexed at: ${file.indexed_at || '❌ Not yet'}`);
|
|
27
27
|
console.log(`🧠 Summary present: ${file.summary ? '✅' : '❌'}`);
|
|
28
|
-
console.log(`🧠 Embedding present: ${file.embedding ? '✅' : '❌'}`);
|
|
29
28
|
const isExtracted = file.processing_status?.includes('extracted');
|
|
30
29
|
console.log(`📌 Functions extracted: ${isExtracted ? '✅' : '❌'}`);
|
|
31
30
|
console.log(`📆 Extracted at: ${file.functions_extracted_at || '❌ Not yet'}`);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// File: src/commands/ReadlineSingleton.ts
|
|
2
|
+
import readline from 'readline';
|
|
3
|
+
let currentRl = null;
|
|
4
|
+
export function setRl(rl) {
|
|
5
|
+
currentRl = rl;
|
|
6
|
+
}
|
|
7
|
+
export function getRl() {
|
|
8
|
+
if (currentRl) {
|
|
9
|
+
return { rl: currentRl, isTemporary: false };
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
rl: readline.createInterface({
|
|
13
|
+
input: process.stdin,
|
|
14
|
+
output: process.stdout
|
|
15
|
+
}),
|
|
16
|
+
isTemporary: true
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -43,29 +43,28 @@ export async function resetDatabase() {
|
|
|
43
43
|
catch (err) {
|
|
44
44
|
console.warn('⚠️ Failed to release database lock:', err instanceof Error ? err.message : err);
|
|
45
45
|
}
|
|
46
|
-
// Delete DB
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
46
|
+
// Delete DB and related files
|
|
47
|
+
const dbDir = path.dirname(dbPath);
|
|
48
|
+
const dbBase = path.basename(dbPath);
|
|
49
|
+
const dbPrefix = dbBase.replace(/\.sqlite$/, '');
|
|
50
|
+
const filesToDelete = [
|
|
51
|
+
`${dbPath}`,
|
|
52
|
+
path.join(dbDir, `${dbPrefix}.sqlite-wal`),
|
|
53
|
+
path.join(dbDir, `${dbPrefix}.sqlite-shm`),
|
|
54
|
+
];
|
|
55
|
+
const lockDir = `${dbPath}.lock`;
|
|
56
|
+
for (const file of filesToDelete) {
|
|
57
|
+
if (fs.existsSync(file)) {
|
|
58
|
+
try {
|
|
59
|
+
fs.unlinkSync(file);
|
|
60
|
+
console.log(`🧹 Deleted ${path.basename(file)}`);
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
console.error(`❌ Failed to delete ${file}:`, err instanceof Error ? err.message : err);
|
|
64
|
+
}
|
|
54
65
|
}
|
|
55
66
|
}
|
|
56
|
-
else {
|
|
57
|
-
console.log('ℹ️ No existing database found at:', dbPath);
|
|
58
|
-
}
|
|
59
|
-
// Ensure directory exists
|
|
60
|
-
try {
|
|
61
|
-
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
62
|
-
console.log('📁 Ensured that the database directory exists.');
|
|
63
|
-
}
|
|
64
|
-
catch (err) {
|
|
65
|
-
console.warn('⚠️ Could not ensure DB directory exists:', err instanceof Error ? err.message : err);
|
|
66
|
-
}
|
|
67
67
|
// Clean up lock directory
|
|
68
|
-
const lockDir = `${dbPath}.lock`;
|
|
69
68
|
if (fs.existsSync(lockDir)) {
|
|
70
69
|
try {
|
|
71
70
|
fs.rmSync(lockDir, { recursive: true, force: true });
|
|
@@ -75,5 +74,5 @@ export async function resetDatabase() {
|
|
|
75
74
|
console.warn('⚠️ Failed to remove lock directory:', err instanceof Error ? err.message : err);
|
|
76
75
|
}
|
|
77
76
|
}
|
|
78
|
-
console.log('✅ Database has been reset.' + chalk.yellow('You can now re-run: scai index start'));
|
|
77
|
+
console.log('✅ Database has been fully reset. ' + chalk.yellow('You can now re-run: scai index start'));
|
|
79
78
|
}
|