@bobsworkshop/cli 0.1.0 → 0.1.1

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 (36) hide show
  1. package/dist/bin/{analyse-auto-OBCDWYWX.js → analyse-auto-KKWLMLHZ.js} +2 -1
  2. package/dist/bin/{analyse-results-QSOD3KVC.js → analyse-results-N5QLJNND.js} +2 -1
  3. package/dist/bin/bob.js +4 -3
  4. package/dist/bin/{chunk-LHWBSCJ4.js → chunk-WEHSNZKO.js} +2 -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-WQUK5YPO.js +0 -531
  13. package/dist/bin/analyse-auto-WVAY6467.js +0 -280
  14. package/dist/bin/analyse-results-3NSD6MAY.js +0 -363
  15. package/dist/bin/analyse-results-B7LONUXU.js +0 -265
  16. package/dist/bin/analyse-results-E6NBAVDB.js +0 -265
  17. package/dist/bin/analyse-results-FIDS4635.js +0 -9
  18. package/dist/bin/analyse-results-LMGVKAUX.js +0 -342
  19. package/dist/bin/analyse-results-LSMLUEIB.js +0 -338
  20. package/dist/bin/analyse-results-MOCLBCD7.js +0 -326
  21. package/dist/bin/analyse-results-NLAEAOOP.js +0 -10
  22. package/dist/bin/analyse-results-PYQIKWYL.js +0 -9
  23. package/dist/bin/analyse-results-R3MG5H7G.js +0 -329
  24. package/dist/bin/analyse-results-UYZZSBHB.js +0 -9
  25. package/dist/bin/analyse-results-YYGHIK2Q.js +0 -9
  26. package/dist/bin/analysis-tracker-N5VANTLH.js +0 -12
  27. package/dist/bin/chunk-3RSDDQE2.js +0 -420
  28. package/dist/bin/chunk-6KWC4HDO.js +0 -97
  29. package/dist/bin/chunk-6W7WDF4Q.js +0 -589
  30. package/dist/bin/chunk-7CXM3RLM.js +0 -287
  31. package/dist/bin/chunk-FGYL6SWO.js +0 -465
  32. package/dist/bin/chunk-J4BSKFCW.js +0 -624
  33. package/dist/bin/chunk-KWOQFI6L.js +0 -287
  34. package/dist/bin/chunk-OOGLZ2QB.js +0 -322
  35. package/dist/bin/chunk-TEVQLSGD.js +0 -287
  36. package/dist/bin/chunk-VUS7R7SO.js +0 -479
@@ -1,326 +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, category, 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,
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(category);
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, category, config);
75
- }
76
- async function interactiveSelector(suggestions, category, config) {
77
- let selectedIndex = 0;
78
- let page = 0;
79
- const totalPages = Math.ceil(suggestions.length / PAGE_SIZE);
80
- const color = CATEGORY_COLORS[category] || GRAY;
81
- const render = () => {
82
- console.clear();
83
- const start = page * PAGE_SIZE;
84
- const end = Math.min(start + PAGE_SIZE, suggestions.length);
85
- const pageItems = suggestions.slice(start, end);
86
- console.log("");
87
- console.log(color(` \u25C6 ${category.toUpperCase()} (${suggestions.length} items) \u2014 Page ${page + 1}/${totalPages}`));
88
- console.log(GRAY(" \u2191\u2193 Navigate | Enter: Expand | n: Next page | p: Prev | q: Quit"));
89
- 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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
90
- console.log("");
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(8);
97
- const filePath = (item.filePath || "unknown").slice(-28).padStart(28);
98
- const description = (item.description || item.title || "No description").slice(0, 52).padEnd(52);
99
- if (isSelected) {
100
- console.log(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\u2550\u2550\u2557"));
101
- console.log(HIGHLIGHT(" \u2551 ") + pColor(priorityLabel) + " ".repeat(Math.max(0, 20 - priorityLabel.length)) + chalk.cyan(filePath) + HIGHLIGHT(" \u2551"));
102
- console.log(HIGHLIGHT(" \u2551 ") + chalk.white(description) + HIGHLIGHT(" \u2551"));
103
- console.log(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\u2550\u2550\u255D"));
104
- } else {
105
- console.log(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\u2500\u2500\u2510"));
106
- console.log(BORDER(" \u2502 ") + pColor(priorityLabel) + " ".repeat(Math.max(0, 20 - priorityLabel.length)) + chalk.cyan(filePath) + BORDER(" \u2502"));
107
- console.log(BORDER(" \u2502 ") + chalk.white(description) + BORDER(" \u2502"));
108
- console.log(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\u2500\u2500\u2518"));
109
- }
110
- console.log("");
111
- }
112
- };
113
- render();
114
- return new Promise((resolve) => {
115
- if (!process.stdin.isTTY) {
116
- console.log(GRAY(" (Interactive mode requires a TTY terminal)"));
117
- resolve();
118
- return;
119
- }
120
- process.stdin.setRawMode(true);
121
- process.stdin.resume();
122
- process.stdin.setEncoding("utf8");
123
- const cleanup = () => {
124
- process.stdin.setRawMode(false);
125
- process.stdin.pause();
126
- process.stdin.removeAllListeners("data");
127
- };
128
- process.stdin.on("data", async (key) => {
129
- const pageStart = page * PAGE_SIZE;
130
- const pageEnd = Math.min(pageStart + PAGE_SIZE, suggestions.length) - 1;
131
- if (key === "" || key === "q") {
132
- cleanup();
133
- console.log("");
134
- resolve();
135
- return;
136
- }
137
- if (key === "\x1B[A") {
138
- if (selectedIndex > pageStart) {
139
- selectedIndex--;
140
- }
141
- render();
142
- return;
143
- }
144
- if (key === "\x1B[B") {
145
- if (selectedIndex < pageEnd) {
146
- selectedIndex++;
147
- }
148
- render();
149
- return;
150
- }
151
- if (key === "n") {
152
- if (page < totalPages - 1) {
153
- page++;
154
- selectedIndex = page * PAGE_SIZE;
155
- render();
156
- }
157
- return;
158
- }
159
- if (key === "p") {
160
- if (page > 0) {
161
- page--;
162
- selectedIndex = page * PAGE_SIZE;
163
- render();
164
- }
165
- return;
166
- }
167
- if (key === "\r" || key === "\n") {
168
- cleanup();
169
- const selected = suggestions[selectedIndex];
170
- await showExpandedView(selected, category, config);
171
- process.stdin.setRawMode(true);
172
- process.stdin.resume();
173
- render();
174
- return;
175
- }
176
- });
177
- });
178
- }
179
- async function showExpandedView(item, category, config) {
180
- const color = CATEGORY_COLORS[category] || GRAY;
181
- const pColor = PRIORITY_COLORS[item.priority?.toLowerCase()] || GRAY;
182
- console.clear();
183
- console.log("");
184
- 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"));
185
- console.log(color(" \u2551 ") + pColor(`${(item.priority || "MEDIUM").toUpperCase()} ${category.toUpperCase().slice(0, -1)}`) + color(" \u2551"));
186
- 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"));
187
- console.log(color(" \u2551") + chalk.gray(" File: ") + chalk.cyan(item.filePath || "unknown") + color(""));
188
- console.log(color(" \u2551") + chalk.gray(" Priority: ") + pColor((item.priority || "medium").toUpperCase()) + color(""));
189
- console.log(color(" \u2551") + color(""));
190
- console.log(color(" \u2551") + chalk.gray(" Title:") + color(""));
191
- console.log(color(" \u2551") + chalk.white(` ${item.title || "No title"}`) + color(""));
192
- console.log(color(" \u2551") + color(""));
193
- console.log(color(" \u2551") + chalk.gray(" Description:") + color(""));
194
- const descLines = wrapText(item.description || "No description", 54);
195
- for (const line of descLines) {
196
- console.log(color(" \u2551") + chalk.white(` ${line}`) + color(""));
197
- }
198
- if (item.implementation) {
199
- console.log(color(" \u2551") + color(""));
200
- console.log(color(" \u2551") + chalk.gray(" Implementation:") + color(""));
201
- const implLines = wrapText(item.implementation, 54);
202
- for (const line of implLines) {
203
- console.log(color(" \u2551") + chalk.white(` ${line}`) + color(""));
204
- }
205
- }
206
- console.log(color(" \u2551") + color(""));
207
- 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"));
208
- console.log(color(" \u2551") + chalk.white(" [i] Implement [d] Dismiss [esc] Back") + color(" \u2551"));
209
- 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"));
210
- console.log("");
211
- return new Promise((resolve) => {
212
- process.stdin.setRawMode(true);
213
- process.stdin.resume();
214
- const handler = async (key) => {
215
- process.stdin.removeListener("data", handler);
216
- process.stdin.setRawMode(false);
217
- process.stdin.pause();
218
- if (key === "i") {
219
- await handleImplement(item, config);
220
- } else if (key === "d") {
221
- console.log(chalk.gray(" \u23ED\uFE0F Dismissed."));
222
- console.log("");
223
- }
224
- resolve();
225
- };
226
- process.stdin.on("data", handler);
227
- });
228
- }
229
- async function handleImplement(item, config) {
230
- console.log("");
231
- console.log(chalk.cyan(" \u{1F527} Implementing fix..."));
232
- if (config.provider === "local" && config.localEndpoint) {
233
- const fileContent = readFileContent(item.filePath);
234
- if (!fileContent) {
235
- console.log(chalk.red(` \u274C Could not read file: ${item.filePath}`));
236
- return;
237
- }
238
- const prompt = `You are an expert programmer implementing a specific code change.
239
-
240
- CURRENT FILE: ${item.filePath}
241
- ${fileContent}
242
-
243
- CHANGE TO IMPLEMENT:
244
- Title: ${item.title}
245
- Description: ${item.description}
246
- Implementation Instructions: ${item.implementation || "Apply the fix described above."}
247
-
248
- Return ONLY the complete updated file content with the change applied.
249
- Start with: // File: ${item.filePath}
250
- Do not include explanations outside the code.`;
251
- try {
252
- const messages = [
253
- { role: "system", content: "You are an expert programmer. Return ONLY the complete updated file. Start with // File: path comment." },
254
- { role: "user", content: prompt }
255
- ];
256
- const response = await callLocalModel(config.localEndpoint, messages);
257
- const lines = response.split("\n");
258
- const filePathLine = lines[0];
259
- let newContent;
260
- if (filePathLine.match(/^\/\/\s*(File:)?\s*/)) {
261
- newContent = lines.slice(1).join("\n").trim();
262
- } else {
263
- newContent = response.trim();
264
- }
265
- await proposeAndWriteFile({
266
- filePath: item.filePath,
267
- content: newContent,
268
- isNew: false
269
- });
270
- } catch (error) {
271
- console.log(chalk.red(` \u274C Implementation failed: ${error.message}`));
272
- }
273
- } else if (config.loggedIn && config.conversationId) {
274
- try {
275
- const result = await callCloudFunction("implementSuggestion", {
276
- conversationId: config.conversationId,
277
- filePath: item.filePath,
278
- suggestionId: item.id || "unknown",
279
- category: "bugs",
280
- // TODO: pass actual category
281
- jobId: `cli_impl_${Date.now()}`
282
- });
283
- if (result?.success) {
284
- console.log(chalk.green(` \u2705 ${result.message}`));
285
- } else {
286
- console.log(chalk.red(" \u274C Implementation failed on platform."));
287
- }
288
- } catch (error) {
289
- console.log(chalk.red(` \u274C ${error.message}`));
290
- }
291
- } else {
292
- console.log(chalk.red(" \u274C No provider configured for implementation."));
293
- }
294
- console.log("");
295
- }
296
- function loadLocalSuggestions(category) {
297
- const cwd = process.cwd();
298
- const { analysisDir } = ensureProjectStructure(cwd);
299
- const analysisPath = path.join(analysisDir, "results", "analysis.json");
300
- if (!fs.existsSync(analysisPath)) return [];
301
- const allResults = JSON.parse(fs.readFileSync(analysisPath, "utf-8"));
302
- const suggestions = [];
303
- for (const [filePath, fileResults] of Object.entries(allResults)) {
304
- const items = fileResults[category] || [];
305
- suggestions.push(...items.map((item) => ({ ...item, filePath })));
306
- }
307
- return suggestions;
308
- }
309
- function wrapText(text, maxWidth) {
310
- const words = text.split(" ");
311
- const lines = [];
312
- let currentLine = "";
313
- for (const word of words) {
314
- if (currentLine.length + word.length + 1 > maxWidth) {
315
- lines.push(currentLine);
316
- currentLine = word;
317
- } else {
318
- currentLine += (currentLine ? " " : "") + word;
319
- }
320
- }
321
- if (currentLine) lines.push(currentLine);
322
- return lines;
323
- }
324
- export {
325
- showInteractiveResults
326
- };
@@ -1,10 +0,0 @@
1
- import {
2
- loadLocalSuggestions,
3
- showInteractiveResults
4
- } from "./chunk-OOGLZ2QB.js";
5
- import "./chunk-FGYL6SWO.js";
6
- import "./chunk-6KWC4HDO.js";
7
- export {
8
- loadLocalSuggestions,
9
- showInteractiveResults
10
- };
@@ -1,9 +0,0 @@
1
- import {
2
- loadLocalSuggestions,
3
- showInteractiveResults
4
- } from "./chunk-TEVQLSGD.js";
5
- import "./chunk-6W7WDF4Q.js";
6
- export {
7
- loadLocalSuggestions,
8
- showInteractiveResults
9
- };
@@ -1,329 +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, category, 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,
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(category);
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, category, config);
75
- }
76
- async function interactiveSelector(suggestions, category, config) {
77
- let selectedIndex = 0;
78
- let page = 0;
79
- const totalPages = Math.ceil(suggestions.length / PAGE_SIZE);
80
- const color = CATEGORY_COLORS[category] || GRAY;
81
- const render = () => {
82
- console.clear();
83
- const start = page * PAGE_SIZE;
84
- const end = Math.min(start + PAGE_SIZE, suggestions.length);
85
- const pageItems = suggestions.slice(start, end);
86
- console.log("");
87
- console.log(color(` \u25C6 ${category.toUpperCase()} (${suggestions.length} items) \u2014 Page ${page + 1}/${totalPages}`));
88
- console.log(GRAY(" \u2191\u2193 Navigate | Enter: Expand | n: Next page | p: Prev | q: Quit"));
89
- 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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
90
- console.log("");
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(8);
97
- const filePath = (item.filePath || "unknown").slice(-28).padStart(28);
98
- const description = (item.description || item.title || "No description").slice(0, 52).padEnd(52);
99
- if (isSelected) {
100
- console.log(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\u2550\u2550\u2557"));
101
- console.log(HIGHLIGHT(" \u2551 ") + pColor(priorityLabel) + " ".repeat(22) + chalk.cyan(filePath) + HIGHLIGHT(" \u2551"));
102
- console.log(HIGHLIGHT(" \u2551 ") + chalk.white(description) + HIGHLIGHT(" \u2551"));
103
- console.log(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\u2550\u2550\u255D"));
104
- } else {
105
- console.log(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\u2500\u2500\u2510"));
106
- console.log(BORDER(" \u2502 ") + pColor(priorityLabel) + " ".repeat(22) + chalk.cyan(filePath) + BORDER(" \u2502"));
107
- console.log(BORDER(" \u2502 ") + chalk.white(description) + BORDER(" \u2502"));
108
- console.log(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\u2500\u2500\u2518"));
109
- }
110
- console.log("");
111
- }
112
- };
113
- render();
114
- return new Promise((resolve) => {
115
- if (!process.stdin.isTTY) {
116
- console.log(GRAY(" (Interactive mode requires a TTY terminal)"));
117
- resolve();
118
- return;
119
- }
120
- process.stdin.setRawMode(true);
121
- process.stdin.resume();
122
- process.stdin.setEncoding("utf8");
123
- const onKey = async (key) => {
124
- const pageStart = page * PAGE_SIZE;
125
- const pageEnd = Math.min(pageStart + PAGE_SIZE, suggestions.length) - 1;
126
- if (key === "" || key === "q") {
127
- cleanup();
128
- console.log("");
129
- resolve();
130
- return;
131
- }
132
- if (key === "\x1B[A") {
133
- if (selectedIndex > pageStart) selectedIndex--;
134
- render();
135
- return;
136
- }
137
- if (key === "\x1B[B") {
138
- if (selectedIndex < pageEnd) selectedIndex++;
139
- render();
140
- return;
141
- }
142
- if (key === "n" && page < totalPages - 1) {
143
- page++;
144
- selectedIndex = page * PAGE_SIZE;
145
- render();
146
- return;
147
- }
148
- if (key === "p" && page > 0) {
149
- page--;
150
- selectedIndex = page * PAGE_SIZE;
151
- render();
152
- return;
153
- }
154
- if (key === "\r" || key === "\n") {
155
- cleanup();
156
- const selected = suggestions[selectedIndex];
157
- await showExpandedView(selected, category, config);
158
- process.stdin.setRawMode(true);
159
- process.stdin.resume();
160
- process.stdin.on("data", onKey);
161
- render();
162
- return;
163
- }
164
- };
165
- const cleanup = () => {
166
- process.stdin.setRawMode(false);
167
- process.stdin.pause();
168
- process.stdin.removeListener("data", onKey);
169
- };
170
- process.stdin.on("data", onKey);
171
- });
172
- }
173
- async function showExpandedView(item, category, config) {
174
- const color = CATEGORY_COLORS[category] || GRAY;
175
- const pColor = PRIORITY_COLORS[item.priority?.toLowerCase()] || GRAY;
176
- console.clear();
177
- console.log("");
178
- 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"));
179
- const headerText = `${(item.priority || "MEDIUM").toUpperCase()} ${category.toUpperCase().slice(0, -1)}`;
180
- const headerPad = " ".repeat(58 - headerText.length);
181
- console.log(color(" \u2551 ") + pColor(headerText) + color(headerPad + " \u2551"));
182
- 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"));
183
- const printRow = (label, value, valueColor) => {
184
- const rawVisibleString = ` ${label}${value}`;
185
- const padding = " ".repeat(Math.max(0, 58 - rawVisibleString.length));
186
- console.log(color(" \u2551") + chalk.gray(` ${label}`) + valueColor(value) + color(padding + "\u2551"));
187
- };
188
- printRow("File: ", item.filePath || "unknown", chalk.cyan);
189
- printRow("Priority: ", (item.priority || "medium").toUpperCase(), pColor);
190
- printRow("", "", chalk.white);
191
- printRow("Title:", "", chalk.white);
192
- printRow("", item.title || "No title", chalk.white);
193
- printRow("", "", chalk.white);
194
- printRow("Description:", "", chalk.white);
195
- const descLines = wrapText(item.description || "No description", 54);
196
- for (const line of descLines) {
197
- printRow("", line, chalk.white);
198
- }
199
- if (item.implementation) {
200
- printRow("", "", chalk.white);
201
- printRow("Implementation:", "", chalk.white);
202
- const implLines = wrapText(item.implementation, 54);
203
- for (const line of implLines) {
204
- printRow("", line, chalk.white);
205
- }
206
- }
207
- printRow("", "", chalk.white);
208
- 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"));
209
- const footerText = " [i] Implement [d] Dismiss [esc] Back";
210
- const footerPad = " ".repeat(58 - footerText.length);
211
- console.log(color(" \u2551") + chalk.white(footerText) + color(footerPad + "\u2551"));
212
- 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"));
213
- console.log("");
214
- return new Promise((resolve) => {
215
- process.stdin.setRawMode(true);
216
- process.stdin.resume();
217
- const handler = async (key) => {
218
- process.stdin.removeListener("data", handler);
219
- process.stdin.setRawMode(false);
220
- process.stdin.pause();
221
- if (key === "i") {
222
- await handleImplement(item, config);
223
- } else if (key === "d") {
224
- console.log(chalk.gray(" \u23ED\uFE0F Dismissed."));
225
- console.log("");
226
- }
227
- resolve();
228
- };
229
- process.stdin.on("data", handler);
230
- });
231
- }
232
- async function handleImplement(item, config) {
233
- console.log("");
234
- console.log(chalk.cyan(" \u{1F527} Implementing fix..."));
235
- if (config.provider === "local" && config.localEndpoint) {
236
- const fileContent = readFileContent(item.filePath);
237
- if (!fileContent) {
238
- console.log(chalk.red(` \u274C Could not read file: ${item.filePath}`));
239
- return;
240
- }
241
- const prompt = `You are an expert programmer implementing a specific code change.
242
-
243
- CURRENT FILE: ${item.filePath}
244
- ${fileContent}
245
-
246
- CHANGE TO IMPLEMENT:
247
- Title: ${item.title}
248
- Description: ${item.description}
249
- Implementation Instructions: ${item.implementation || "Apply the fix described above."}
250
-
251
- Return ONLY the complete updated file content with the change applied.
252
- Start with: // File: ${item.filePath}
253
- Do not include explanations outside the code.`;
254
- try {
255
- const messages = [
256
- { role: "system", content: "You are an expert programmer. Return ONLY the complete updated file. Start with // File: path comment." },
257
- { role: "user", content: prompt }
258
- ];
259
- const response = await callLocalModel(config.localEndpoint, messages);
260
- const lines = response.split("\n");
261
- const filePathLine = lines[0];
262
- let newContent;
263
- if (filePathLine.match(/^\/\/\s*(File:)?\s*/)) {
264
- newContent = lines.slice(1).join("\n").trim();
265
- } else {
266
- newContent = response.trim();
267
- }
268
- await proposeAndWriteFile({
269
- filePath: item.filePath,
270
- content: newContent,
271
- isNew: false
272
- });
273
- } catch (error) {
274
- console.log(chalk.red(` \u274C Implementation failed: ${error.message}`));
275
- }
276
- } else if (config.loggedIn && config.conversationId) {
277
- try {
278
- const result = await callCloudFunction("implementSuggestion", {
279
- conversationId: config.conversationId,
280
- filePath: item.filePath,
281
- suggestionId: item.id || "unknown",
282
- category: "bugs",
283
- // TODO: pass actual category
284
- jobId: `cli_impl_${Date.now()}`
285
- });
286
- if (result?.success) {
287
- console.log(chalk.green(` \u2705 ${result.message}`));
288
- } else {
289
- console.log(chalk.red(" \u274C Implementation failed on platform."));
290
- }
291
- } catch (error) {
292
- console.log(chalk.red(` \u274C ${error.message}`));
293
- }
294
- } else {
295
- console.log(chalk.red(" \u274C No provider configured for implementation."));
296
- }
297
- console.log("");
298
- }
299
- function loadLocalSuggestions(category) {
300
- const cwd = process.cwd();
301
- const { analysisDir } = ensureProjectStructure(cwd);
302
- const analysisPath = path.join(analysisDir, "results", "analysis.json");
303
- if (!fs.existsSync(analysisPath)) return [];
304
- const allResults = JSON.parse(fs.readFileSync(analysisPath, "utf-8"));
305
- const suggestions = [];
306
- for (const [filePath, fileResults] of Object.entries(allResults)) {
307
- const items = fileResults[category] || [];
308
- suggestions.push(...items.map((item) => ({ ...item, filePath })));
309
- }
310
- return suggestions;
311
- }
312
- function wrapText(text, maxWidth) {
313
- const words = text.split(" ");
314
- const lines = [];
315
- let currentLine = "";
316
- for (const word of words) {
317
- if (currentLine.length + word.length + 1 > maxWidth) {
318
- lines.push(currentLine);
319
- currentLine = word;
320
- } else {
321
- currentLine += (currentLine ? " " : "") + word;
322
- }
323
- }
324
- if (currentLine) lines.push(currentLine);
325
- return lines;
326
- }
327
- export {
328
- showInteractiveResults
329
- };