@bobsworkshop/cli 0.1.0 → 0.1.2

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 (34) hide show
  1. package/dist/bin/{analyse-auto-WQUK5YPO.js → analyse-auto-KKWLMLHZ.js} +4 -5
  2. package/dist/bin/{analyse-results-FIDS4635.js → analyse-results-N5QLJNND.js} +2 -2
  3. package/dist/bin/bob.js +1 -0
  4. package/dist/bin/chunk-WEHSNZKO.js +880 -0
  5. package/package.json +11 -5
  6. package/bin/bob.ts +0 -74
  7. package/dist/bin/analyse-auto-AAWSETKY.js +0 -540
  8. package/dist/bin/analyse-auto-FQ62GYPV.js +0 -533
  9. package/dist/bin/analyse-auto-JAD24IQ5.js +0 -511
  10. package/dist/bin/analyse-auto-R4MZA7SX.js +0 -511
  11. package/dist/bin/analyse-auto-V3SH4MS7.js +0 -524
  12. package/dist/bin/analyse-auto-WVAY6467.js +0 -280
  13. package/dist/bin/analyse-results-3NSD6MAY.js +0 -363
  14. package/dist/bin/analyse-results-B7LONUXU.js +0 -265
  15. package/dist/bin/analyse-results-E6NBAVDB.js +0 -265
  16. package/dist/bin/analyse-results-LMGVKAUX.js +0 -342
  17. package/dist/bin/analyse-results-LSMLUEIB.js +0 -338
  18. package/dist/bin/analyse-results-MOCLBCD7.js +0 -326
  19. package/dist/bin/analyse-results-NLAEAOOP.js +0 -10
  20. package/dist/bin/analyse-results-PYQIKWYL.js +0 -9
  21. package/dist/bin/analyse-results-R3MG5H7G.js +0 -329
  22. package/dist/bin/analyse-results-UYZZSBHB.js +0 -9
  23. package/dist/bin/analyse-results-YYGHIK2Q.js +0 -9
  24. package/dist/bin/analysis-tracker-N5VANTLH.js +0 -12
  25. package/dist/bin/chunk-3RSDDQE2.js +0 -420
  26. package/dist/bin/chunk-6KWC4HDO.js +0 -97
  27. package/dist/bin/chunk-6W7WDF4Q.js +0 -589
  28. package/dist/bin/chunk-7CXM3RLM.js +0 -287
  29. package/dist/bin/chunk-FGYL6SWO.js +0 -465
  30. package/dist/bin/chunk-J4BSKFCW.js +0 -624
  31. package/dist/bin/chunk-KWOQFI6L.js +0 -287
  32. package/dist/bin/chunk-OOGLZ2QB.js +0 -322
  33. package/dist/bin/chunk-TEVQLSGD.js +0 -287
  34. package/dist/bin/chunk-VUS7R7SO.js +0 -479
@@ -1,280 +0,0 @@
1
- import {
2
- loadLocalSuggestions
3
- } from "./chunk-TEVQLSGD.js";
4
- import {
5
- callLocalModel,
6
- getConfig,
7
- readFileContent
8
- } from "./chunk-6W7WDF4Q.js";
9
-
10
- // src/commands/analyse-auto.ts
11
- import chalk from "chalk";
12
- import ora from "ora";
13
- import * as fs from "fs";
14
- import * as path from "path";
15
- var RED = chalk.hex("#EF5350");
16
- var GREEN = chalk.hex("#66BB6A");
17
- var AMBER = chalk.hex("#FFAB00");
18
- var BLUE = chalk.hex("#42A5F5");
19
- var GRAY = chalk.gray;
20
- var BORDER = chalk.hex("#455A64");
21
- async function runAutoFix(options) {
22
- const config = getConfig();
23
- if (config.provider !== "local" || !config.localEndpoint) {
24
- console.log("");
25
- console.log(chalk.red(" \u274C Auto-fix requires a local model."));
26
- console.log(GRAY(" Run `bob config set provider local`"));
27
- console.log(GRAY(" Run `bob config set localEndpoint http://127.0.0.1:11434/api/chat`"));
28
- console.log("");
29
- return;
30
- }
31
- const confidenceGate = options.confidence || 90;
32
- const priorityGate = options.priority || "critical";
33
- const categories = options.category ? [options.category] : ["bugs", "features", "improvements", "upgrades"];
34
- console.log("");
35
- console.log(chalk.bold.cyan(" \u26A1 MiniBob Auto-Fix Mode"));
36
- console.log(GRAY(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
37
- console.log(GRAY(` Confidence gate: ${confidenceGate}%`));
38
- console.log(GRAY(` Priority gate: ${priorityGate}+`));
39
- console.log(GRAY(` Categories: ${categories.join(", ")}`));
40
- console.log(GRAY(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
41
- console.log("");
42
- let allSuggestions = [];
43
- for (const cat of categories) {
44
- const catSuggestions = loadLocalSuggestions(cat);
45
- allSuggestions.push(...catSuggestions);
46
- }
47
- const priorityOrder = ["critical", "high", "medium", "low"];
48
- const gateIndex = priorityOrder.indexOf(priorityGate.toLowerCase());
49
- if (gateIndex >= 0) {
50
- allSuggestions = allSuggestions.filter((s) => {
51
- const idx = priorityOrder.indexOf(s.priority?.toLowerCase());
52
- return idx >= 0 && idx <= gateIndex;
53
- });
54
- }
55
- if (allSuggestions.length === 0) {
56
- console.log(chalk.green(" \u2705 No suggestions match your gates. Project is clean!"));
57
- console.log("");
58
- return;
59
- }
60
- console.log(GRAY(` Found ${allSuggestions.length} suggestions matching criteria.`));
61
- console.log("");
62
- console.log(AMBER(" \u{1F9E0} Phase 1: Triage \u2014 Bob is evaluating suggestions..."));
63
- console.log("");
64
- const triageResults = [];
65
- const triageSpinner = ora({ text: chalk.cyan(" Triaging..."), spinner: "dots" }).start();
66
- const triagePrompt = `You are a senior engineering lead triaging code suggestions. For each suggestion below, decide whether to WORK on it or DISMISS it.
67
-
68
- DECISION CRITERIA:
69
- - WORK: The fix is clear, well-defined, and will improve code quality. You are ${confidenceGate}%+ confident the fix is correct and won't break anything.
70
- - DISMISS: The suggestion is vague, risky, or the effort outweighs the benefit.
71
-
72
- SUGGESTIONS:
73
- ${allSuggestions.map((s, i) => `[${i}] ${s.priority?.toUpperCase()} | ${s.filePath} | ${s.title} | ${s.description}`).join("\n")}
74
-
75
- Respond with ONLY a JSON array. Each element: {"index": 0, "action": "work"|"dismiss", "confidence": 0-100, "reason": "brief reason"}
76
-
77
- Example:
78
- [{"index": 0, "action": "work", "confidence": 95, "reason": "Clear fix, low risk"}, {"index": 1, "action": "dismiss", "confidence": 40, "reason": "Too vague to implement safely"}]
79
-
80
- Return ONLY the JSON array:`;
81
- try {
82
- const messages = [
83
- { role: "system", content: "You are a senior engineering lead. Respond with ONLY a valid JSON array. No explanation." },
84
- { role: "user", content: triagePrompt }
85
- ];
86
- const triageResponse = await callLocalModel(config.localEndpoint, messages);
87
- triageSpinner.stop();
88
- const jsonMatch = triageResponse.match(/\[[\s\S]*\]/);
89
- if (jsonMatch) {
90
- const parsed = JSON.parse(jsonMatch[0]);
91
- for (const decision of parsed) {
92
- if (decision.index !== void 0 && decision.index < allSuggestions.length) {
93
- triageResults.push({
94
- action: decision.action === "work" ? "work" : "dismiss",
95
- confidence: decision.confidence || 0,
96
- reason: decision.reason || "",
97
- suggestion: allSuggestions[decision.index]
98
- });
99
- }
100
- }
101
- }
102
- } catch (error) {
103
- triageSpinner.stop();
104
- console.log(chalk.red(` \u274C Triage failed: ${error.message}`));
105
- console.log("");
106
- return;
107
- }
108
- const workItems = triageResults.filter((r) => r.action === "work" && r.confidence >= confidenceGate);
109
- const dismissItems = triageResults.filter((r) => r.action === "dismiss" || r.confidence < confidenceGate);
110
- console.log("");
111
- console.log(BORDER(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
112
- console.log(BORDER(" \u2551") + AMBER(" \u25C6 TRIAGE COMPLETE"));
113
- console.log(BORDER(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
114
- console.log(BORDER(" \u2551") + GREEN(` \u2705 Work: ${workItems.length} items (confidence \u2265 ${confidenceGate}%)`));
115
- console.log(BORDER(" \u2551") + GRAY(` \u23F8\uFE0F Held: ${dismissItems.length} items (dismissed or low confidence)`));
116
- console.log(BORDER(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
117
- console.log("");
118
- if (workItems.length === 0) {
119
- console.log(chalk.yellow(" \u26A0\uFE0F No items passed the confidence gate. Nothing to auto-fix."));
120
- console.log("");
121
- if (dismissItems.length > 0) {
122
- console.log(GRAY(" Held items:"));
123
- for (const item of dismissItems.slice(0, 5)) {
124
- console.log(GRAY(` \u2022 ${item.suggestion.filePath}: ${item.reason} (${item.confidence}%)`));
125
- }
126
- console.log("");
127
- }
128
- return;
129
- }
130
- console.log(AMBER(" \u{1F4CB} Phase 2: MiniBob Work Queue"));
131
- console.log(GRAY(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
132
- console.log("");
133
- for (let i = 0; i < workItems.length; i++) {
134
- const item = workItems[i];
135
- console.log(GRAY(` \u2610 [${i + 1}/${workItems.length}] ${item.suggestion.filePath}`));
136
- console.log(GRAY(` ${item.suggestion.title} (${item.confidence}% confidence)`));
137
- }
138
- console.log("");
139
- console.log(AMBER(" \u{1F527} Phase 3: MiniBob Implementing..."));
140
- console.log(GRAY(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
141
- console.log("");
142
- console.log("");
143
- console.log("");
144
- console.log("");
145
- let fixed = 0;
146
- let failed = 0;
147
- const fixedItems = [];
148
- const failedItems = [];
149
- for (let i = 0; i < workItems.length; i++) {
150
- const item = workItems[i];
151
- const suggestion = item.suggestion;
152
- printAutoProgress(i + 1, workItems.length, suggestion.filePath, "working");
153
- const fileContent = readFileContent(suggestion.filePath);
154
- if (!fileContent) {
155
- failed++;
156
- failedItems.push(`${suggestion.filePath}: Could not read file`);
157
- printAutoProgress(i + 1, workItems.length, suggestion.filePath, "failed");
158
- continue;
159
- }
160
- const implPrompt = `You are an expert programmer implementing a specific code change.
161
-
162
- CURRENT FILE: ${suggestion.filePath}
163
- ${fileContent}
164
-
165
- CHANGE TO IMPLEMENT:
166
- Title: ${suggestion.title}
167
- Description: ${suggestion.description}
168
- Implementation Instructions: ${suggestion.implementation || "Apply the fix described above."}
169
-
170
- RULES:
171
- - Return ONLY the complete updated file content with the change applied.
172
- - Start the code with: // File: ${suggestion.filePath}
173
- - PRESERVE all existing code structure. Only change what's needed.
174
- - Do NOT include any explanation outside the code.`;
175
- try {
176
- const messages = [
177
- { role: "system", content: "You are an expert programmer. Return ONLY the complete updated file. Start with // File: path comment. Preserve existing structure." },
178
- { role: "user", content: implPrompt }
179
- ];
180
- const response = await callLocalModel(config.localEndpoint, messages);
181
- const lines = response.split("\n");
182
- const firstLine = lines[0].trim();
183
- let newContent;
184
- if (firstLine.match(/^\/\/\s*(File:)?\s*/)) {
185
- newContent = lines.slice(1).join("\n").trim();
186
- } else {
187
- newContent = response.trim();
188
- }
189
- const absolutePath = path.join(process.cwd(), suggestion.filePath);
190
- const dir = path.dirname(absolutePath);
191
- if (!fs.existsSync(dir)) {
192
- fs.mkdirSync(dir, { recursive: true });
193
- }
194
- const backupDir = path.join(process.cwd(), ".bob-backups");
195
- if (!fs.existsSync(backupDir)) fs.mkdirSync(backupDir, { recursive: true });
196
- if (fs.existsSync(absolutePath)) {
197
- const timestamp = Date.now();
198
- const backupName = suggestion.filePath.replace(/[\/\\]/g, "_") + `.${timestamp}.bak`;
199
- fs.copyFileSync(absolutePath, path.join(backupDir, backupName));
200
- }
201
- fs.writeFileSync(absolutePath, newContent, "utf-8");
202
- fixed++;
203
- fixedItems.push(suggestion.filePath);
204
- printAutoProgress(i + 1, workItems.length, suggestion.filePath, "done");
205
- } catch (error) {
206
- failed++;
207
- failedItems.push(`${suggestion.filePath}: ${error.message}`);
208
- printAutoProgress(i + 1, workItems.length, suggestion.filePath, "failed");
209
- }
210
- }
211
- console.log("");
212
- console.log("");
213
- console.log(BORDER(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
214
- console.log(BORDER(" \u2551") + AMBER(" \u25C6 MINIBOB AUTO-FIX REPORT"));
215
- console.log(BORDER(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
216
- console.log(BORDER(" \u2551") + GREEN(` \u2705 Fixed: ${fixed} items`));
217
- console.log(BORDER(" \u2551") + GRAY(` \u23F8\uFE0F Held: ${dismissItems.length} items (low confidence/dismissed)`));
218
- if (failed > 0) {
219
- console.log(BORDER(" \u2551") + RED(` \u274C Failed: ${failed} items`));
220
- }
221
- console.log(BORDER(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
222
- if (fixedItems.length > 0) {
223
- console.log(BORDER(" \u2551") + chalk.gray(" Fixed files:"));
224
- for (const file of fixedItems) {
225
- console.log(BORDER(" \u2551") + GREEN(` \u2705 ${file}`));
226
- }
227
- }
228
- if (failedItems.length > 0) {
229
- console.log(BORDER(" \u2551") + chalk.gray(" Failed:"));
230
- for (const item of failedItems) {
231
- console.log(BORDER(" \u2551") + RED(` \u274C ${item}`));
232
- }
233
- }
234
- if (dismissItems.length > 0) {
235
- console.log(BORDER(" \u2551") + chalk.gray(" Held (not auto-fixed):"));
236
- for (const item of dismissItems.slice(0, 5)) {
237
- console.log(BORDER(" \u2551") + GRAY(` \u23F8\uFE0F ${item.suggestion.filePath}: ${item.reason}`));
238
- }
239
- if (dismissItems.length > 5) {
240
- console.log(BORDER(" \u2551") + GRAY(` ... and ${dismissItems.length - 5} more`));
241
- }
242
- }
243
- console.log(BORDER(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
244
- console.log("");
245
- console.log(GRAY(" \u{1F4E6} All original files backed up to .bob-backups/"));
246
- console.log(GRAY(' Run `bob push "MiniBob auto-fix batch"` to commit changes.'));
247
- console.log("");
248
- }
249
- function printAutoProgress(current, total, filePath, status) {
250
- const percent = current / total;
251
- const barLength = 30;
252
- const filled = Math.round(percent * barLength);
253
- let barColor;
254
- if (percent < 0.25) barColor = chalk.red;
255
- else if (percent < 0.5) barColor = chalk.hex("#FF8C00");
256
- else if (percent < 0.75) barColor = chalk.yellow;
257
- else barColor = chalk.green;
258
- const filledBar = barColor("\u2588".repeat(filled));
259
- const emptyBar = GRAY("\u2591".repeat(barLength - filled));
260
- let statusIcon;
261
- let statusColor;
262
- if (status === "working") {
263
- statusIcon = "\u23F3";
264
- statusColor = AMBER;
265
- } else if (status === "done") {
266
- statusIcon = "\u2705";
267
- statusColor = GREEN;
268
- } else {
269
- statusIcon = "\u274C";
270
- statusColor = RED;
271
- }
272
- process.stdout.write("\x1B[2K\x1B[1A\x1B[2K\x1B[1A\x1B[2K\x1B[1A\x1B[2K\r");
273
- console.log(` [${filledBar}${emptyBar}] ${current}/${total}`);
274
- console.log(statusColor(` ${statusIcon} MiniBob ${status === "working" ? "working" : status === "done" ? "completed" : "failed"}: ${filePath}`));
275
- console.log(status === "working" ? AMBER(` Assigned \u2192 Working...`) : status === "done" ? GREEN(` Assigned \u2192 Working \u2192 Completed \u2713`) : RED(` Assigned \u2192 Failed \u2717`));
276
- console.log("");
277
- }
278
- export {
279
- runAutoFix
280
- };
@@ -1,363 +0,0 @@
1
- import {
2
- callCloudFunction,
3
- callLocalModel,
4
- ensureProjectStructure,
5
- proposeAndWriteFile,
6
- readFileContent
7
- } from "./chunk-6W7WDF4Q.js";
8
-
9
- // src/commands/analyse-results.ts
10
- import chalk from "chalk";
11
- import * as fs from "fs";
12
- import * as path from "path";
13
- var RED = chalk.hex("#EF5350");
14
- var PURPLE = chalk.hex("#AB47BC");
15
- var BLUE = chalk.hex("#42A5F5");
16
- var TEAL = chalk.hex("#26A69A");
17
- var AMBER = chalk.hex("#FFAB00");
18
- var GRAY = chalk.gray;
19
- var BORDER = chalk.hex("#455A64");
20
- var HIGHLIGHT = chalk.hex("#FFAB00");
21
- var PRIORITY_COLORS = {
22
- "critical": chalk.bgHex("#B71C1C").white,
23
- "high": chalk.hex("#FF6D00"),
24
- "medium": chalk.hex("#FFA726"),
25
- "low": chalk.hex("#66BB6A")
26
- };
27
- var CATEGORY_COLORS = {
28
- "bugs": RED,
29
- "features": PURPLE,
30
- "improvements": BLUE,
31
- "upgrades": TEAL
32
- };
33
- var PAGE_SIZE = 5;
34
- async function showInteractiveResults(config, category2, sort, search) {
35
- let suggestions = [];
36
- if (config.tier === "platform" && config.provider !== "local" && config.loggedIn && config.conversationId) {
37
- try {
38
- const result = await callCloudFunction("getCLIAnalysisResults", {
39
- conversationId: config.conversationId,
40
- category: category2,
41
- sort: sort || "priority",
42
- search: search || null
43
- });
44
- suggestions = result?.suggestions || [];
45
- } catch (error) {
46
- console.log(chalk.red(` \u274C ${error.message}`));
47
- return;
48
- }
49
- } else {
50
- suggestions = loadLocalSuggestions(category2);
51
- }
52
- if (search) {
53
- const query = search.toLowerCase();
54
- suggestions = suggestions.filter(
55
- (s) => (s.description || "").toLowerCase().includes(query) || (s.title || "").toLowerCase().includes(query) || (s.filePath || "").toLowerCase().includes(query)
56
- );
57
- }
58
- if (sort === "file") {
59
- suggestions.sort((a, b) => (a.filePath || "").localeCompare(b.filePath || ""));
60
- } else {
61
- const priorityMap = { "critical": 0, "high": 1, "medium": 2, "low": 3 };
62
- suggestions.sort((a, b) => {
63
- const pA = priorityMap[a.priority?.toLowerCase()] ?? 99;
64
- const pB = priorityMap[b.priority?.toLowerCase()] ?? 99;
65
- return pA - pB;
66
- });
67
- }
68
- if (suggestions.length === 0) {
69
- console.log("");
70
- console.log(chalk.green(" \u2705 No items found. Clean!"));
71
- console.log("");
72
- return;
73
- }
74
- await interactiveSelector(suggestions, category2, config);
75
- }
76
- async function interactiveSelector(suggestions, category2, config) {
77
- let selectedIndex = 0;
78
- let page = 0;
79
- const totalPages = Math.ceil(suggestions.length / PAGE_SIZE);
80
- const color = CATEGORY_COLORS[category2] || GRAY;
81
- const getRenderedLines = () => {
82
- const lines = [];
83
- const start = page * PAGE_SIZE;
84
- const end = Math.min(start + PAGE_SIZE, suggestions.length);
85
- const pageItems = suggestions.slice(start, end);
86
- lines.push("");
87
- lines.push(color(` \u25C6 ${category2.toUpperCase()} (${suggestions.length} items) \u2014 Page ${page + 1}/${totalPages}`));
88
- lines.push(GRAY(" \u2191\u2193 Navigate | Enter: Expand | n: Next | p: Prev | q: Quit"));
89
- lines.push(GRAY(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
90
- lines.push("");
91
- for (let i = 0; i < pageItems.length; i++) {
92
- const item = pageItems[i];
93
- const globalIndex = start + i;
94
- const isSelected = globalIndex === selectedIndex;
95
- const pColor = PRIORITY_COLORS[item.priority?.toLowerCase()] || GRAY;
96
- const priorityLabel = (item.priority || "MEDIUM").toUpperCase().padEnd(10);
97
- const filePath = (item.filePath || "unknown").slice(-24);
98
- const description = (item.description || item.title || "No description").slice(0, 50);
99
- if (isSelected) {
100
- lines.push(HIGHLIGHT(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
101
- lines.push(HIGHLIGHT(" \u2551 ") + pColor(priorityLabel) + " " + chalk.cyan(filePath) + HIGHLIGHT(""));
102
- lines.push(HIGHLIGHT(" \u2551 ") + chalk.white.bold(description));
103
- lines.push(HIGHLIGHT(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
104
- } else {
105
- lines.push(BORDER(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
106
- lines.push(BORDER(" \u2502 ") + pColor(priorityLabel) + " " + chalk.cyan(filePath));
107
- lines.push(BORDER(" \u2502 ") + chalk.white(description));
108
- lines.push(BORDER(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
109
- }
110
- lines.push("");
111
- }
112
- return lines;
113
- };
114
- let lastLineCount = 0;
115
- const render = () => {
116
- const lines = getRenderedLines();
117
- if (lastLineCount > 0) {
118
- process.stdout.write(`\x1B[${lastLineCount}A`);
119
- for (let i = 0; i < lastLineCount; i++) {
120
- process.stdout.write("\x1B[2K\n");
121
- }
122
- process.stdout.write(`\x1B[${lastLineCount}A`);
123
- }
124
- for (const line of lines) {
125
- process.stdout.write(line + "\n");
126
- }
127
- lastLineCount = lines.length;
128
- };
129
- const initialLines = getRenderedLines();
130
- for (let i = 0; i < initialLines.length; i++) {
131
- console.log("");
132
- }
133
- lastLineCount = initialLines.length;
134
- render();
135
- return new Promise((resolve) => {
136
- if (!process.stdin.isTTY) {
137
- console.log(GRAY(" (Interactive mode requires a TTY terminal)"));
138
- resolve();
139
- return;
140
- }
141
- process.stdin.setRawMode(true);
142
- process.stdin.resume();
143
- process.stdin.setEncoding("utf8");
144
- const cleanup = () => {
145
- process.stdin.setRawMode(false);
146
- process.stdin.pause();
147
- process.stdin.removeAllListeners("data");
148
- };
149
- const onKey = async (key) => {
150
- const pageStart = page * PAGE_SIZE;
151
- const pageEnd = Math.min(pageStart + PAGE_SIZE, suggestions.length) - 1;
152
- if (key === "" || key === "q") {
153
- cleanup();
154
- console.log("");
155
- resolve();
156
- return;
157
- }
158
- if (key === "\x1B[A") {
159
- if (selectedIndex > pageStart) {
160
- selectedIndex--;
161
- render();
162
- }
163
- return;
164
- }
165
- if (key === "\x1B[B") {
166
- if (selectedIndex < pageEnd) {
167
- selectedIndex++;
168
- render();
169
- }
170
- return;
171
- }
172
- if (key === "n") {
173
- if (page < totalPages - 1) {
174
- page++;
175
- selectedIndex = page * PAGE_SIZE;
176
- render();
177
- }
178
- return;
179
- }
180
- if (key === "p") {
181
- if (page > 0) {
182
- page--;
183
- selectedIndex = page * PAGE_SIZE;
184
- render();
185
- }
186
- return;
187
- }
188
- if (key === "\r" || key === "\n") {
189
- cleanup();
190
- const selected = suggestions[selectedIndex];
191
- await showExpandedView(selected, category2, config);
192
- process.stdin.setRawMode(true);
193
- process.stdin.resume();
194
- process.stdin.on("data", onKey);
195
- lastLineCount = 0;
196
- const lines = getRenderedLines();
197
- for (let i = 0; i < lines.length; i++) {
198
- console.log("");
199
- }
200
- lastLineCount = lines.length;
201
- render();
202
- return;
203
- }
204
- };
205
- process.stdin.on("data", onKey);
206
- });
207
- }
208
- async function showExpandedView(item, category2, config) {
209
- const color = CATEGORY_COLORS[category2] || GRAY;
210
- const pColor = PRIORITY_COLORS[item.priority?.toLowerCase()] || GRAY;
211
- process.stdout.write("\x1B[H\x1B[2J");
212
- console.log("");
213
- console.log(color(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
214
- console.log(color(" \u2551 ") + pColor(`${(item.priority || "MEDIUM").toUpperCase()} ${category2.toUpperCase().slice(0, -1)}`) + color(""));
215
- console.log(color(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
216
- console.log(color(" \u2551") + chalk.gray(" File: ") + chalk.cyan(item.filePath || "unknown"));
217
- console.log(color(" \u2551") + chalk.gray(" Priority: ") + pColor((item.priority || "medium").toUpperCase()));
218
- console.log(color(" \u2551"));
219
- console.log(color(" \u2551") + chalk.gray(" Title:"));
220
- console.log(color(" \u2551") + chalk.white.bold(` ${item.title || "No title"}`));
221
- console.log(color(" \u2551"));
222
- console.log(color(" \u2551") + chalk.gray(" Description:"));
223
- const descLines = wrapText(item.description || "No description", 54);
224
- for (const line of descLines) {
225
- console.log(color(" \u2551") + chalk.white(` ${line}`));
226
- }
227
- if (item.implementation) {
228
- console.log(color(" \u2551"));
229
- console.log(color(" \u2551") + chalk.gray(" Implementation:"));
230
- const implLines = wrapText(item.implementation, 54);
231
- for (const line of implLines) {
232
- console.log(color(" \u2551") + chalk.white(` ${line}`));
233
- }
234
- }
235
- console.log(color(" \u2551"));
236
- console.log(color(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
237
- console.log(color(" \u2551") + chalk.white(" [i] Implement [d] Dismiss [esc/q] Back"));
238
- console.log(color(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
239
- console.log("");
240
- return new Promise((resolve) => {
241
- process.stdin.setRawMode(true);
242
- process.stdin.resume();
243
- const handler = async (key) => {
244
- process.stdin.removeListener("data", handler);
245
- process.stdin.setRawMode(false);
246
- process.stdin.pause();
247
- if (key === "i") {
248
- await handleImplement(item, config);
249
- } else if (key === "d") {
250
- console.log(chalk.gray(" \u23ED\uFE0F Dismissed."));
251
- console.log("");
252
- }
253
- process.stdout.write("\x1B[H\x1B[2J");
254
- resolve();
255
- };
256
- process.stdin.on("data", handler);
257
- });
258
- }
259
- async function handleImplement(item, config) {
260
- console.log("");
261
- console.log(chalk.cyan(" \u{1F527} Implementing fix..."));
262
- console.log("");
263
- if (config.provider === "local" && config.localEndpoint) {
264
- const fileContent = readFileContent(item.filePath);
265
- if (!fileContent) {
266
- console.log(chalk.red(` \u274C Could not read file: ${item.filePath}`));
267
- return;
268
- }
269
- const prompt = `You are an expert programmer implementing a specific code change.
270
-
271
- CURRENT FILE: ${item.filePath}
272
- ${fileContent}
273
-
274
- CHANGE TO IMPLEMENT:
275
- Title: ${item.title}
276
- Description: ${item.description}
277
- Implementation Instructions: ${item.implementation || "Apply the fix described above."}
278
-
279
- RULES:
280
- - Return ONLY the complete updated file content with the change applied.
281
- - Start the code with: // File: ${item.filePath}
282
- - PRESERVE all existing code structure. Only change what's needed.
283
- - Do NOT include any explanation outside the code.`;
284
- try {
285
- const messages = [
286
- { role: "system", content: "You are an expert programmer. Return ONLY the complete updated file. Start with // File: path comment. Preserve existing structure." },
287
- { role: "user", content: prompt }
288
- ];
289
- const response = await callLocalModel(config.localEndpoint, messages);
290
- const lines = response.split("\n");
291
- const firstLine = lines[0].trim();
292
- let newContent;
293
- if (firstLine.match(/^\/\/\s*(File:)?\s*/)) {
294
- newContent = lines.slice(1).join("\n").trim();
295
- } else {
296
- newContent = response.trim();
297
- }
298
- await proposeAndWriteFile({
299
- filePath: item.filePath,
300
- content: newContent,
301
- isNew: false
302
- });
303
- } catch (error) {
304
- console.log(chalk.red(` \u274C Implementation failed: ${error.message}`));
305
- }
306
- } else if (config.loggedIn && config.conversationId) {
307
- try {
308
- const result = await callCloudFunction("implementSuggestion", {
309
- conversationId: config.conversationId,
310
- filePath: item.filePath,
311
- suggestionId: item.id || "unknown",
312
- category,
313
- jobId: `cli_impl_${Date.now()}`
314
- });
315
- if (result?.success) {
316
- console.log(chalk.green(` \u2705 ${result.message}`));
317
- } else {
318
- console.log(chalk.red(" \u274C Implementation failed on platform."));
319
- }
320
- } catch (error) {
321
- console.log(chalk.red(` \u274C ${error.message}`));
322
- }
323
- } else {
324
- console.log(chalk.red(" \u274C No provider configured for implementation."));
325
- }
326
- console.log("");
327
- await new Promise((resolve) => setTimeout(resolve, 2e3));
328
- }
329
- function loadLocalSuggestions(category2) {
330
- const cwd = process.cwd();
331
- const { analysisDir } = ensureProjectStructure(cwd);
332
- const analysisPath = path.join(analysisDir, "results", "analysis.json");
333
- if (!fs.existsSync(analysisPath)) return [];
334
- const allResults = JSON.parse(fs.readFileSync(analysisPath, "utf-8"));
335
- const suggestions = [];
336
- for (const [filePath, fileResults] of Object.entries(allResults)) {
337
- const items = fileResults[category2] || [];
338
- suggestions.push(...items.map((item, idx) => ({
339
- ...item,
340
- filePath,
341
- id: `${filePath.replace(/[\/\\]/g, "_")}_${idx}`
342
- })));
343
- }
344
- return suggestions;
345
- }
346
- function wrapText(text, maxWidth) {
347
- const words = text.split(" ");
348
- const lines = [];
349
- let currentLine = "";
350
- for (const word of words) {
351
- if (currentLine.length + word.length + 1 > maxWidth) {
352
- lines.push(currentLine);
353
- currentLine = word;
354
- } else {
355
- currentLine += (currentLine ? " " : "") + word;
356
- }
357
- }
358
- if (currentLine) lines.push(currentLine);
359
- return lines;
360
- }
361
- export {
362
- showInteractiveResults
363
- };