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.
Files changed (96) hide show
  1. package/README.md +88 -503
  2. package/dist/agents/MainAgent.js +255 -0
  3. package/dist/agents/contextReviewStep.js +104 -0
  4. package/dist/agents/finalPlanGenStep.js +123 -0
  5. package/dist/agents/infoPlanGenStep.js +126 -0
  6. package/dist/agents/planGeneratorStep.js +118 -0
  7. package/dist/agents/planResolverStep.js +95 -0
  8. package/dist/agents/planTargetFilesStep.js +48 -0
  9. package/dist/agents/preFileSearchCheckStep.js +95 -0
  10. package/dist/agents/selectRelevantSourcesStep.js +100 -0
  11. package/dist/agents/semanticAnalysisStep.js +144 -0
  12. package/dist/agents/structuralAnalysisStep.js +46 -0
  13. package/dist/agents/transformPlanGenStep.js +107 -0
  14. package/dist/agents/understandIntentStep.js +72 -0
  15. package/dist/agents/validationAnalysisStep.js +87 -0
  16. package/dist/commands/AskCmd.js +47 -116
  17. package/dist/commands/ChangeLogUpdateCmd.js +11 -5
  18. package/dist/commands/CommitSuggesterCmd.js +50 -75
  19. package/dist/commands/DaemonCmd.js +119 -29
  20. package/dist/commands/IndexCmd.js +41 -24
  21. package/dist/commands/InspectCmd.js +0 -1
  22. package/dist/commands/ReadlineSingleton.js +18 -0
  23. package/dist/commands/ResetDbCmd.js +20 -21
  24. package/dist/commands/ReviewCmd.js +89 -54
  25. package/dist/commands/SummaryCmd.js +12 -18
  26. package/dist/commands/WorkflowCmd.js +41 -0
  27. package/dist/commands/factory.js +254 -0
  28. package/dist/config.js +67 -15
  29. package/dist/constants.js +20 -4
  30. package/dist/context.js +10 -11
  31. package/dist/daemon/daemonQueues.js +63 -0
  32. package/dist/daemon/daemonWorker.js +40 -63
  33. package/dist/daemon/generateSummaries.js +58 -0
  34. package/dist/daemon/runFolderCapsuleBatch.js +247 -0
  35. package/dist/daemon/runIndexingBatch.js +147 -0
  36. package/dist/daemon/runKgBatch.js +104 -0
  37. package/dist/db/fileIndex.js +168 -63
  38. package/dist/db/functionExtractors/extractFromJava.js +210 -6
  39. package/dist/db/functionExtractors/extractFromJs.js +173 -214
  40. package/dist/db/functionExtractors/extractFromTs.js +159 -160
  41. package/dist/db/functionExtractors/index.js +7 -5
  42. package/dist/db/schema.js +55 -20
  43. package/dist/db/sqlTemplates.js +50 -19
  44. package/dist/fileRules/builtins.js +31 -14
  45. package/dist/fileRules/codeAllowedExtensions.js +4 -0
  46. package/dist/fileRules/fileExceptions.js +0 -13
  47. package/dist/fileRules/ignoredExtensions.js +10 -0
  48. package/dist/index.js +128 -325
  49. package/dist/lib/generate.js +37 -14
  50. package/dist/lib/generateFolderCapsules.js +109 -0
  51. package/dist/lib/spinner.js +12 -5
  52. package/dist/modelSetup.js +1 -11
  53. package/dist/pipeline/modules/changeLogModule.js +16 -19
  54. package/dist/pipeline/modules/chunkManagerModule.js +24 -0
  55. package/dist/pipeline/modules/cleanupModule.js +95 -91
  56. package/dist/pipeline/modules/codeTransformModule.js +208 -0
  57. package/dist/pipeline/modules/commentModule.js +20 -11
  58. package/dist/pipeline/modules/commitSuggesterModule.js +36 -14
  59. package/dist/pipeline/modules/contextReviewModule.js +52 -0
  60. package/dist/pipeline/modules/fileReaderModule.js +72 -0
  61. package/dist/pipeline/modules/fileSearchModule.js +136 -0
  62. package/dist/pipeline/modules/finalAnswerModule.js +53 -0
  63. package/dist/pipeline/modules/gatherInfoModule.js +176 -0
  64. package/dist/pipeline/modules/generateTestsModule.js +63 -54
  65. package/dist/pipeline/modules/kgModule.js +26 -11
  66. package/dist/pipeline/modules/preserveCodeModule.js +91 -49
  67. package/dist/pipeline/modules/refactorModule.js +19 -7
  68. package/dist/pipeline/modules/repairTestsModule.js +44 -36
  69. package/dist/pipeline/modules/reviewModule.js +23 -13
  70. package/dist/pipeline/modules/summaryModule.js +27 -35
  71. package/dist/pipeline/modules/writeFileModule.js +86 -0
  72. package/dist/pipeline/registry/moduleRegistry.js +38 -93
  73. package/dist/pipeline/runModulePipeline.js +22 -19
  74. package/dist/scripts/dbcheck.js +143 -228
  75. package/dist/utils/buildContextualPrompt.js +245 -172
  76. package/dist/utils/debugContext.js +24 -0
  77. package/dist/utils/fileTree.js +16 -6
  78. package/dist/utils/loadRelevantFolderCapsules.js +64 -0
  79. package/dist/utils/log.js +2 -0
  80. package/dist/utils/normalizeData.js +23 -0
  81. package/dist/utils/planActions.js +60 -0
  82. package/dist/utils/promptBuilderHelper.js +67 -0
  83. package/dist/utils/promptLogHelper.js +52 -0
  84. package/dist/utils/sanitizeQuery.js +20 -8
  85. package/dist/utils/sleep.js +3 -0
  86. package/dist/utils/splitCodeIntoChunk.js +65 -32
  87. package/dist/utils/vscode.js +49 -0
  88. package/dist/workflow/workflowResolver.js +14 -0
  89. package/dist/workflow/workflowRunner.js +103 -0
  90. package/package.json +6 -5
  91. package/dist/agent/agentManager.js +0 -39
  92. package/dist/agent/workflowManager.js +0 -95
  93. package/dist/commands/ModulePipelineCmd.js +0 -31
  94. package/dist/daemon/daemonBatch.js +0 -186
  95. package/dist/fileRules/scoreFiles.js +0 -71
  96. 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
- function askUserToChoose(suggestions) {
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
- async function promptEditCommitMessage(suggestedMessage) {
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 rl = readline.createInterface({
71
- input: process.stdin,
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
- // Continue with commit suggestions
114
- const response = await commitSuggesterModule.run({ content: diff });
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 response = await commitSuggesterModule.run({ content: diff });
126
- suggestions.splice(0, suggestions.length, ...(response.suggestions || []));
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
- // Ask which suggestion to edit using a dedicated prompt
134
- const editChoice = await askWhichSuggestionToEdit(suggestions);
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
- // If changelog option is enabled, generate changelog including the selected commit message
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 fsSync, { mkdirSync } from 'fs';
2
- import { LOG_PATH, PID_PATH } from '../constants.js';
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 path from 'path';
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
- // If there's a PID file, check if the process is still running
9
- if (fsSync.existsSync(PID_PATH)) {
10
- const pid = parseInt(fsSync.readFileSync(PID_PATH, 'utf8'));
11
- try {
12
- process.kill(pid, 0); // Check if process exists
13
- log(`⚠️ Daemon already running with PID ${pid}.`);
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
- catch {
17
- log(`⚠️ Stale PID file found. Removing and restarting daemon...`);
18
- fsSync.unlinkSync(PID_PATH);
58
+ else {
59
+ console.log(`🧹 Removing stale PID file...`);
60
+ fs.unlinkSync(PID_PATH);
19
61
  }
20
62
  }
21
- log('🚀 Starting summarizer daemon in background mode...');
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 daemonWorkerPath = path.join(__dirname, '../daemon/daemonWorker.js');
26
- const out = fsSync.openSync(LOG_PATH, 'a');
27
- const err = fsSync.openSync(LOG_PATH, 'a');
28
- const child = spawn(process.execPath, [daemonWorkerPath], {
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], // stdout/stderr -> log file
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
- try {
38
- mkdirSync(path.dirname(PID_PATH), { recursive: true });
39
- fsSync.writeFileSync(PID_PATH, String(child.pid));
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
- catch (err) {
42
- log(`❌ Failed to write PID file: ${err instanceof Error ? err.message : err}`);
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
- const lock = await lockfile.lock(getDbPathForRepo());
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
- const release = await lockDb();
43
- for (const file of files) {
44
- const classification = classifyFile(file);
45
- if (classification !== 'valid') {
46
- log(`⏭️ Skipping (${classification}): ${file}`);
47
- continue;
48
- }
49
- try {
50
- const type = detectFileType(file);
51
- indexFile(file, null, type);
52
- const ext = path.extname(file);
53
- countByExt[ext] = (countByExt[ext] || 0) + 1;
54
- log(`📄 Indexed: ${path.relative(indexDir, file)}`);
55
- count++;
56
- }
57
- catch (err) {
58
- log(`⚠️ Skipped in indexCmd ${file}: ${err instanceof Error ? err.message : err}`);
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
- log('📊 Indexed files by extension:', JSON.stringify(countByExt, null, 2));
62
- log(`✅ Done. Indexed ${count} files.`);
63
- await release();
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 file
47
- if (fs.existsSync(dbPath)) {
48
- try {
49
- fs.unlinkSync(dbPath);
50
- console.log(`🧹 Deleted existing database at ${dbPath}`);
51
- }
52
- catch (err) {
53
- console.error('❌ Failed to delete DB file:', err instanceof Error ? err.message : err);
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
  }