scai 0.1.88 → 0.1.90
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 +8 -0
- package/dist/CHANGELOG.md +11 -1
- package/dist/commands/DeleteIndex.js +48 -0
- package/dist/config.js +9 -1
- package/dist/index.js +7 -0
- package/dist/pipeline/modules/preserveCodeModule.js +100 -53
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -301,6 +301,14 @@ scai index switch
|
|
|
301
301
|
|
|
302
302
|
Switch active repository (by key or indexDir). Run without input for an interactive list of repositories.
|
|
303
303
|
|
|
304
|
+
#### `scai index delete`
|
|
305
|
+
|
|
306
|
+
Delete a repository from the index (interactive).
|
|
307
|
+
This removes the repository entry from the `config.json` file, but does **not** delete the repository folder on disk.
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
scai index delete
|
|
311
|
+
```
|
|
304
312
|
|
|
305
313
|
---
|
|
306
314
|
|
package/dist/CHANGELOG.md
CHANGED
|
@@ -107,4 +107,14 @@ Type handling with the module pipeline
|
|
|
107
107
|
* Split file processing into logical chunks with logging.
|
|
108
108
|
* Improved commit suggestion generation logic.
|
|
109
109
|
* Removed unnecessary files and updated index.ts to use handleAgentRun instead.
|
|
110
|
-
* Fetch download statistics from NPM API.
|
|
110
|
+
* Fetch download statistics from NPM API.
|
|
111
|
+
|
|
112
|
+
## 2025-08-16
|
|
113
|
+
|
|
114
|
+
• Update preserveCodeModule to compare blocks correctly and handle duplicate blocks
|
|
115
|
+
• Normalize line endings and detect comments in preserveCodeModule
|
|
116
|
+
|
|
117
|
+
## 2025-08-18
|
|
118
|
+
|
|
119
|
+
• Add interactive delete repository command
|
|
120
|
+
• Refactor DeleteIndex command to handle repository deletion correctly
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// File: src/commands/delete.ts
|
|
2
|
+
import readline from 'readline';
|
|
3
|
+
import { Config, writeConfig } from '../config.js';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
export async function runInteractiveDelete() {
|
|
6
|
+
const config = Config.getRaw();
|
|
7
|
+
const keys = Object.keys(config.repos || {});
|
|
8
|
+
if (!keys.length) {
|
|
9
|
+
console.log('⚠️ No repositories configured.');
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
console.log('\n🗑️ Repositories Available for Deletion:\n');
|
|
13
|
+
keys.forEach((key, i) => {
|
|
14
|
+
const isActive = config.activeRepo === key ? chalk.green('(active)') : '';
|
|
15
|
+
const dir = config.repos[key]?.indexDir ?? '';
|
|
16
|
+
console.log(`${chalk.blue(`${i + 1})`)} ${key} ${isActive}`);
|
|
17
|
+
console.log(` ↳ ${chalk.grey(dir)}`);
|
|
18
|
+
});
|
|
19
|
+
const rl = readline.createInterface({
|
|
20
|
+
input: process.stdin,
|
|
21
|
+
output: process.stdout,
|
|
22
|
+
});
|
|
23
|
+
rl.question('\n👉 Select a repository number to delete: ', (answer) => {
|
|
24
|
+
rl.close();
|
|
25
|
+
const index = parseInt(answer.trim(), 10) - 1;
|
|
26
|
+
if (isNaN(index) || index < 0 || index >= keys.length) {
|
|
27
|
+
console.log('❌ Invalid selection.');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const selectedKey = keys[index];
|
|
31
|
+
console.log(`\n⚠️ Deleting repository: ${chalk.red(selectedKey)}\n`);
|
|
32
|
+
// Build update: remove repo by setting it to null
|
|
33
|
+
const update = { repos: { [selectedKey]: null } };
|
|
34
|
+
// Reset activeRepo if it pointed to the deleted one
|
|
35
|
+
if (config.activeRepo === selectedKey) {
|
|
36
|
+
const remainingKeys = keys.filter((k) => k !== selectedKey);
|
|
37
|
+
update.activeRepo = remainingKeys[0] || undefined;
|
|
38
|
+
console.log(`ℹ️ Active repo reset to: ${chalk.green(update.activeRepo ?? 'none')}`);
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
writeConfig(update); // only pass the diff
|
|
42
|
+
console.log(`✅ Repository "${selectedKey}" removed from config.json.`);
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
console.error('❌ Failed to update config.json:', err instanceof Error ? err.message : err);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
package/dist/config.js
CHANGED
|
@@ -30,7 +30,7 @@ export function readConfig() {
|
|
|
30
30
|
export function writeConfig(newCfg) {
|
|
31
31
|
ensureConfigDir();
|
|
32
32
|
const current = readConfig();
|
|
33
|
-
|
|
33
|
+
let merged = {
|
|
34
34
|
...current,
|
|
35
35
|
...newCfg,
|
|
36
36
|
repos: {
|
|
@@ -38,6 +38,14 @@ export function writeConfig(newCfg) {
|
|
|
38
38
|
...(newCfg.repos || {}),
|
|
39
39
|
},
|
|
40
40
|
};
|
|
41
|
+
// Special handling: remove repos explicitly set to null
|
|
42
|
+
if (newCfg.repos) {
|
|
43
|
+
for (const [key, value] of Object.entries(newCfg.repos)) {
|
|
44
|
+
if (value === null) {
|
|
45
|
+
delete merged.repos[key];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
41
49
|
fs.writeFileSync(CONFIG_PATH, JSON.stringify(merged, null, 2));
|
|
42
50
|
}
|
|
43
51
|
export const Config = {
|
package/dist/index.js
CHANGED
|
@@ -30,6 +30,7 @@ import { handleAgentRun } from './agentManager.js';
|
|
|
30
30
|
import { addCommentsModule } from './pipeline/modules/commentModule.js';
|
|
31
31
|
import { generateTestsModule } from './pipeline/modules/generateTestsModule.js';
|
|
32
32
|
import { preserveCodeModule } from './pipeline/modules/preserveCodeModule.js';
|
|
33
|
+
import { runInteractiveDelete } from './commands/DeleteIndex.js';
|
|
33
34
|
// 🎛️ CLI Setup
|
|
34
35
|
const cmd = new Command('scai')
|
|
35
36
|
.version(version)
|
|
@@ -177,6 +178,12 @@ index
|
|
|
177
178
|
.action(() => {
|
|
178
179
|
runInteractiveSwitch();
|
|
179
180
|
});
|
|
181
|
+
index
|
|
182
|
+
.command('delete')
|
|
183
|
+
.description('Delete a repository from the index (interactive)')
|
|
184
|
+
.action(() => {
|
|
185
|
+
runInteractiveDelete();
|
|
186
|
+
});
|
|
180
187
|
// This will help resolve the current directory in an ES Module
|
|
181
188
|
cmd
|
|
182
189
|
.command('check-db')
|
|
@@ -3,91 +3,138 @@ export const preserveCodeModule = {
|
|
|
3
3
|
name: "preserveCodeModule",
|
|
4
4
|
description: "Ensure code matches original exactly, preserving comments with clear before/after output",
|
|
5
5
|
async run(input) {
|
|
6
|
-
const { originalContent, content, filepath
|
|
6
|
+
const { originalContent, content, filepath } = input;
|
|
7
7
|
if (!originalContent)
|
|
8
8
|
throw new Error("Requires `originalContent`.");
|
|
9
9
|
const syntax = {
|
|
10
10
|
singleLine: ["//"],
|
|
11
11
|
multiLine: [{ start: "/*", end: "*/" }]
|
|
12
12
|
};
|
|
13
|
-
//
|
|
14
|
-
const
|
|
15
|
-
|
|
13
|
+
// --- Normalize line endings ---
|
|
14
|
+
const normalize = (txt) => txt.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
15
|
+
const origLines = normalize(originalContent).split("\n");
|
|
16
|
+
const newLines = normalize(content).split("\n");
|
|
17
|
+
// Detect if a line is a comment line, and classify
|
|
18
|
+
let inBlockComment = false;
|
|
19
|
+
const classifyLine = (line) => {
|
|
20
|
+
const trimmed = line.trimStart();
|
|
21
|
+
// Single-line
|
|
16
22
|
for (const s of syntax.singleLine) {
|
|
17
|
-
if (
|
|
18
|
-
return
|
|
23
|
+
if (trimmed.startsWith(s))
|
|
24
|
+
return "single-comment";
|
|
19
25
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
// Multi-line start/end
|
|
27
|
+
for (const { start, end } of syntax.multiLine) {
|
|
28
|
+
if (trimmed.startsWith(start) && trimmed.includes(end)) {
|
|
29
|
+
return "multi-comment (start+end)";
|
|
30
|
+
}
|
|
31
|
+
if (trimmed.startsWith(start)) {
|
|
32
|
+
inBlockComment = true;
|
|
33
|
+
return "multi-comment (start)";
|
|
34
|
+
}
|
|
35
|
+
if (inBlockComment) {
|
|
36
|
+
if (trimmed.includes(end)) {
|
|
37
|
+
inBlockComment = false;
|
|
38
|
+
return "multi-comment (end)";
|
|
39
|
+
}
|
|
40
|
+
return "multi-comment (mid)";
|
|
24
41
|
}
|
|
25
42
|
}
|
|
26
|
-
return
|
|
43
|
+
return "code";
|
|
27
44
|
};
|
|
28
|
-
|
|
29
|
-
|
|
45
|
+
// Collect consecutive comment lines as one block
|
|
46
|
+
function collectBlock(lines, startIndex) {
|
|
47
|
+
const block = [];
|
|
48
|
+
let i = startIndex;
|
|
49
|
+
while (i < lines.length && classifyLine(lines[i]) !== "code") {
|
|
50
|
+
block.push(lines[i]);
|
|
51
|
+
i++;
|
|
52
|
+
}
|
|
53
|
+
return block;
|
|
54
|
+
}
|
|
55
|
+
const trimBlock = (block) => block.map(line => line.trim());
|
|
56
|
+
const blocksEqual = (a, b) => JSON.stringify(trimBlock(a)) === JSON.stringify(trimBlock(b));
|
|
30
57
|
const fixedLines = [];
|
|
31
58
|
let origIndex = 0;
|
|
32
59
|
let newIndex = 0;
|
|
33
|
-
while (origIndex < origLines.length) {
|
|
60
|
+
while (origIndex < origLines.length && newIndex < newLines.length) {
|
|
34
61
|
const origLine = origLines[origIndex];
|
|
35
|
-
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
62
|
+
const newLine = newLines[newIndex];
|
|
63
|
+
// If either current line is a comment → treat whole comment block
|
|
64
|
+
let lastInsertedModelBlock = [];
|
|
65
|
+
if (classifyLine(origLine) !== "code" || classifyLine(newLine) !== "code") {
|
|
66
|
+
const origBlock = collectBlock(origLines, origIndex);
|
|
67
|
+
const modelBlock = collectBlock(newLines, newIndex);
|
|
68
|
+
// Compare with last inserted block
|
|
69
|
+
if (!blocksEqual(modelBlock, lastInsertedModelBlock)) {
|
|
70
|
+
if (blocksEqual(origBlock, modelBlock)) {
|
|
71
|
+
fixedLines.push(...origBlock);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
const seen = new Set(trimBlock(modelBlock));
|
|
75
|
+
fixedLines.push(...modelBlock);
|
|
76
|
+
for (const line of origBlock) {
|
|
77
|
+
if (!seen.has(line.trim())) {
|
|
78
|
+
fixedLines.push(line);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Update lastInsertedModelBlock
|
|
83
|
+
lastInsertedModelBlock = [...modelBlock];
|
|
43
84
|
}
|
|
44
85
|
else {
|
|
45
|
-
|
|
46
|
-
fixedLines.push(newLine);
|
|
47
|
-
newIndex++;
|
|
86
|
+
console.log("Skipping duplicate block (already inserted)");
|
|
48
87
|
}
|
|
49
|
-
|
|
88
|
+
origIndex += origBlock.length;
|
|
89
|
+
newIndex += modelBlock.length;
|
|
90
|
+
continue;
|
|
50
91
|
}
|
|
51
|
-
|
|
92
|
+
// Non-comment lines
|
|
93
|
+
if (origLine.trim() === newLine.trim()) {
|
|
52
94
|
fixedLines.push(newLine);
|
|
53
|
-
origIndex++;
|
|
54
|
-
newIndex++;
|
|
55
95
|
}
|
|
56
96
|
else {
|
|
57
97
|
fixedLines.push(origLine);
|
|
58
|
-
origIndex++;
|
|
59
|
-
if (!isComment(newLine)) {
|
|
60
|
-
newIndex++;
|
|
61
|
-
}
|
|
62
98
|
}
|
|
99
|
+
origIndex++;
|
|
100
|
+
newIndex++;
|
|
101
|
+
}
|
|
102
|
+
// Add trailing lines from original (if model ran out first)
|
|
103
|
+
while (origIndex < origLines.length) {
|
|
104
|
+
fixedLines.push(origLines[origIndex++]);
|
|
63
105
|
}
|
|
64
106
|
// Add trailing comments from model
|
|
65
107
|
while (newIndex < newLines.length) {
|
|
66
|
-
if (
|
|
108
|
+
if (classifyLine(newLines[newIndex]) !== "code") {
|
|
67
109
|
fixedLines.push(newLines[newIndex]);
|
|
68
110
|
}
|
|
69
111
|
newIndex++;
|
|
70
112
|
}
|
|
71
|
-
//
|
|
72
|
-
console.log(chalk.bold.blue("\n===
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
113
|
+
// --- Logging for debugging ---
|
|
114
|
+
console.log(chalk.bold.blue("\n=== LINE CLASSIFICATION (original) ==="));
|
|
115
|
+
origLines.forEach((line, i) => {
|
|
116
|
+
const type = classifyLine(line);
|
|
117
|
+
const colored = type === "code"
|
|
118
|
+
? chalk.green(line)
|
|
119
|
+
: chalk.yellow(line);
|
|
120
|
+
console.log(`${i + 1}: ${colored} ${chalk.gray(`[${type}]`)}`);
|
|
121
|
+
});
|
|
122
|
+
console.log(chalk.bold.blue("\n=== LINE CLASSIFICATION (model) ==="));
|
|
123
|
+
newLines.forEach((line, i) => {
|
|
124
|
+
const type = classifyLine(line);
|
|
125
|
+
const colored = type === "code"
|
|
126
|
+
? chalk.green(line)
|
|
127
|
+
: chalk.yellow(line);
|
|
128
|
+
console.log(`${i + 1}: ${colored} ${chalk.gray(`[${type}]`)}`);
|
|
129
|
+
});
|
|
76
130
|
console.log(chalk.bold.blue("\n=== FIXED CONTENT ==="));
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return chalk.yellow(line);
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
return chalk.red(line);
|
|
88
|
-
}
|
|
89
|
-
})
|
|
90
|
-
.join("\n"));
|
|
131
|
+
fixedLines.forEach((line, i) => {
|
|
132
|
+
const type = classifyLine(line);
|
|
133
|
+
const colored = type === "code"
|
|
134
|
+
? chalk.green(line)
|
|
135
|
+
: chalk.yellow(line);
|
|
136
|
+
console.log(`${i + 1}: ${colored} ${chalk.gray(`[${type}]`)}`);
|
|
137
|
+
});
|
|
91
138
|
return { content: fixedLines.join("\n"), filepath };
|
|
92
139
|
}
|
|
93
140
|
};
|