@bobsworkshop/cli 0.1.0
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/bin/bob.ts +74 -0
- package/dist/bin/analyse-auto-AAWSETKY.js +540 -0
- package/dist/bin/analyse-auto-FQ62GYPV.js +533 -0
- package/dist/bin/analyse-auto-JAD24IQ5.js +511 -0
- package/dist/bin/analyse-auto-OBCDWYWX.js +529 -0
- package/dist/bin/analyse-auto-R4MZA7SX.js +511 -0
- package/dist/bin/analyse-auto-V3SH4MS7.js +524 -0
- package/dist/bin/analyse-auto-WQUK5YPO.js +531 -0
- package/dist/bin/analyse-auto-WVAY6467.js +280 -0
- package/dist/bin/analyse-results-3NSD6MAY.js +363 -0
- package/dist/bin/analyse-results-B7LONUXU.js +265 -0
- package/dist/bin/analyse-results-E6NBAVDB.js +265 -0
- package/dist/bin/analyse-results-FIDS4635.js +9 -0
- package/dist/bin/analyse-results-LMGVKAUX.js +342 -0
- package/dist/bin/analyse-results-LSMLUEIB.js +338 -0
- package/dist/bin/analyse-results-MOCLBCD7.js +326 -0
- package/dist/bin/analyse-results-NLAEAOOP.js +10 -0
- package/dist/bin/analyse-results-PYQIKWYL.js +9 -0
- package/dist/bin/analyse-results-QSOD3KVC.js +8 -0
- package/dist/bin/analyse-results-R3MG5H7G.js +329 -0
- package/dist/bin/analyse-results-UYZZSBHB.js +9 -0
- package/dist/bin/analyse-results-YYGHIK2Q.js +9 -0
- package/dist/bin/analysis-tracker-N5VANTLH.js +12 -0
- package/dist/bin/bob.js +3006 -0
- package/dist/bin/chunk-3RSDDQE2.js +420 -0
- package/dist/bin/chunk-6KWC4HDO.js +97 -0
- package/dist/bin/chunk-6W7WDF4Q.js +589 -0
- package/dist/bin/chunk-7CXM3RLM.js +287 -0
- package/dist/bin/chunk-FGYL6SWO.js +465 -0
- package/dist/bin/chunk-J4BSKFCW.js +624 -0
- package/dist/bin/chunk-KWOQFI6L.js +287 -0
- package/dist/bin/chunk-LHWBSCJ4.js +878 -0
- package/dist/bin/chunk-OOGLZ2QB.js +322 -0
- package/dist/bin/chunk-TEVQLSGD.js +287 -0
- package/dist/bin/chunk-VUS7R7SO.js +479 -0
- package/package.json +47 -0
|
@@ -0,0 +1,265 @@
|
|
|
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 inquirer from "inquirer";
|
|
12
|
+
import * as fs from "fs";
|
|
13
|
+
import * as path from "path";
|
|
14
|
+
var RED = chalk.hex("#EF5350");
|
|
15
|
+
var PURPLE = chalk.hex("#AB47BC");
|
|
16
|
+
var BLUE = chalk.hex("#42A5F5");
|
|
17
|
+
var TEAL = chalk.hex("#26A69A");
|
|
18
|
+
var AMBER = chalk.hex("#FFAB00");
|
|
19
|
+
var GRAY = chalk.gray;
|
|
20
|
+
var BORDER = chalk.hex("#455A64");
|
|
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
|
+
async function showInteractiveResults(config, category, sort, search) {
|
|
34
|
+
let suggestions = [];
|
|
35
|
+
if (config.tier === "platform" && config.provider !== "local" && config.loggedIn && config.conversationId) {
|
|
36
|
+
try {
|
|
37
|
+
const result = await callCloudFunction("getCLIAnalysisResults", {
|
|
38
|
+
conversationId: config.conversationId,
|
|
39
|
+
category,
|
|
40
|
+
sort: sort || "priority",
|
|
41
|
+
search: search || null
|
|
42
|
+
});
|
|
43
|
+
suggestions = result?.suggestions || [];
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.log(chalk.red(` \u274C ${error.message}`));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
suggestions = loadLocalSuggestions(category);
|
|
50
|
+
}
|
|
51
|
+
if (search) {
|
|
52
|
+
const query = search.toLowerCase();
|
|
53
|
+
suggestions = suggestions.filter(
|
|
54
|
+
(s) => (s.description || "").toLowerCase().includes(query) || (s.title || "").toLowerCase().includes(query) || (s.filePath || "").toLowerCase().includes(query)
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
if (sort === "file") {
|
|
58
|
+
suggestions.sort((a, b) => (a.filePath || "").localeCompare(b.filePath || ""));
|
|
59
|
+
} else {
|
|
60
|
+
const priorityMap = { "critical": 0, "high": 1, "medium": 2, "low": 3 };
|
|
61
|
+
suggestions.sort((a, b) => {
|
|
62
|
+
const pA = priorityMap[a.priority?.toLowerCase()] ?? 99;
|
|
63
|
+
const pB = priorityMap[b.priority?.toLowerCase()] ?? 99;
|
|
64
|
+
return pA - pB;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
if (suggestions.length === 0) {
|
|
68
|
+
console.log("");
|
|
69
|
+
console.log(chalk.green(" \u2705 No items found. Clean!"));
|
|
70
|
+
console.log("");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const color = CATEGORY_COLORS[category] || GRAY;
|
|
74
|
+
let running = true;
|
|
75
|
+
while (running) {
|
|
76
|
+
console.log("");
|
|
77
|
+
console.log(color(` \u25C6 ${category.toUpperCase()} (${suggestions.length} items)`));
|
|
78
|
+
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"));
|
|
79
|
+
console.log("");
|
|
80
|
+
const choices = suggestions.map((item2, idx) => {
|
|
81
|
+
const pColor = PRIORITY_COLORS[item2.priority?.toLowerCase()] || GRAY;
|
|
82
|
+
const priorityLabel = (item2.priority || "MEDIUM").toUpperCase().padEnd(9);
|
|
83
|
+
const filePath = (item2.filePath || "unknown").split("/").pop() || "unknown";
|
|
84
|
+
const desc = (item2.description || item2.title || "No description").slice(0, 45);
|
|
85
|
+
return {
|
|
86
|
+
name: `${pColor(priorityLabel)} ${chalk.cyan(filePath.padEnd(20))} ${chalk.white(desc)}`,
|
|
87
|
+
value: idx,
|
|
88
|
+
short: item2.title || item2.description?.slice(0, 30) || "Item"
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
choices.push({
|
|
92
|
+
name: chalk.gray(" \u2190 Back (quit)"),
|
|
93
|
+
value: -1,
|
|
94
|
+
short: "Quit"
|
|
95
|
+
});
|
|
96
|
+
const { selected } = await inquirer.prompt([
|
|
97
|
+
{
|
|
98
|
+
type: "list",
|
|
99
|
+
name: "selected",
|
|
100
|
+
message: color("Select a suggestion:"),
|
|
101
|
+
choices,
|
|
102
|
+
pageSize: 10,
|
|
103
|
+
loop: false
|
|
104
|
+
}
|
|
105
|
+
]);
|
|
106
|
+
if (selected === -1) {
|
|
107
|
+
running = false;
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
const item = suggestions[selected];
|
|
111
|
+
const action = await showExpandedView(item, category);
|
|
112
|
+
if (action === "implement") {
|
|
113
|
+
await handleImplement(item, config);
|
|
114
|
+
} else if (action === "dismiss") {
|
|
115
|
+
suggestions.splice(selected, 1);
|
|
116
|
+
console.log(chalk.gray(" \u23ED\uFE0F Dismissed."));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async function showExpandedView(item, category) {
|
|
121
|
+
const color = CATEGORY_COLORS[category] || GRAY;
|
|
122
|
+
const pColor = PRIORITY_COLORS[item.priority?.toLowerCase()] || GRAY;
|
|
123
|
+
console.log("");
|
|
124
|
+
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"));
|
|
125
|
+
console.log(color(" \u2551 ") + pColor(`${(item.priority || "MEDIUM").toUpperCase()} ${category.toUpperCase().slice(0, -1)}`));
|
|
126
|
+
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"));
|
|
127
|
+
console.log(color(" \u2551") + chalk.gray(" File: ") + chalk.cyan(item.filePath || "unknown"));
|
|
128
|
+
console.log(color(" \u2551") + chalk.gray(" Priority: ") + pColor((item.priority || "medium").toUpperCase()));
|
|
129
|
+
console.log(color(" \u2551"));
|
|
130
|
+
console.log(color(" \u2551") + chalk.gray(" Title:"));
|
|
131
|
+
console.log(color(" \u2551") + chalk.white.bold(` ${item.title || "No title"}`));
|
|
132
|
+
console.log(color(" \u2551"));
|
|
133
|
+
console.log(color(" \u2551") + chalk.gray(" Description:"));
|
|
134
|
+
const descLines = wrapText(item.description || "No description", 54);
|
|
135
|
+
for (const line of descLines) {
|
|
136
|
+
console.log(color(" \u2551") + chalk.white(` ${line}`));
|
|
137
|
+
}
|
|
138
|
+
if (item.implementation) {
|
|
139
|
+
console.log(color(" \u2551"));
|
|
140
|
+
console.log(color(" \u2551") + chalk.gray(" Implementation:"));
|
|
141
|
+
const implLines = wrapText(item.implementation, 54);
|
|
142
|
+
for (const line of implLines) {
|
|
143
|
+
console.log(color(" \u2551") + chalk.white(` ${line}`));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
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"));
|
|
147
|
+
console.log("");
|
|
148
|
+
const { action } = await inquirer.prompt([
|
|
149
|
+
{
|
|
150
|
+
type: "list",
|
|
151
|
+
name: "action",
|
|
152
|
+
message: "What do you want to do?",
|
|
153
|
+
choices: [
|
|
154
|
+
{ name: chalk.green(" \u{1F527} Implement this fix"), value: "implement" },
|
|
155
|
+
{ name: chalk.red(" \u{1F5D1}\uFE0F Dismiss"), value: "dismiss" },
|
|
156
|
+
{ name: chalk.gray(" \u2190 Back to list"), value: "back" }
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
]);
|
|
160
|
+
return action;
|
|
161
|
+
}
|
|
162
|
+
async function handleImplement(item, config) {
|
|
163
|
+
console.log("");
|
|
164
|
+
console.log(chalk.cyan(" \u{1F527} Implementing fix..."));
|
|
165
|
+
console.log("");
|
|
166
|
+
if (config.provider === "local" && config.localEndpoint) {
|
|
167
|
+
const fileContent = readFileContent(item.filePath);
|
|
168
|
+
if (!fileContent) {
|
|
169
|
+
console.log(chalk.red(` \u274C Could not read file: ${item.filePath}`));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const prompt = `You are an expert programmer implementing a specific code change.
|
|
173
|
+
|
|
174
|
+
CURRENT FILE: ${item.filePath}
|
|
175
|
+
${fileContent}
|
|
176
|
+
|
|
177
|
+
CHANGE TO IMPLEMENT:
|
|
178
|
+
Title: ${item.title}
|
|
179
|
+
Description: ${item.description}
|
|
180
|
+
Implementation Instructions: ${item.implementation || "Apply the fix described above."}
|
|
181
|
+
|
|
182
|
+
RULES:
|
|
183
|
+
- Return ONLY the complete updated file content with the change applied.
|
|
184
|
+
- Start the code with: // File: ${item.filePath}
|
|
185
|
+
- PRESERVE all existing code structure. Only change what's needed.
|
|
186
|
+
- Do NOT include any explanation outside the code.`;
|
|
187
|
+
try {
|
|
188
|
+
const messages = [
|
|
189
|
+
{ role: "system", content: "You are an expert programmer. Return ONLY the complete updated file. Start with // File: path comment. Preserve existing structure." },
|
|
190
|
+
{ role: "user", content: prompt }
|
|
191
|
+
];
|
|
192
|
+
const response = await callLocalModel(config.localEndpoint, messages);
|
|
193
|
+
const lines = response.split("\n");
|
|
194
|
+
const firstLine = lines[0].trim();
|
|
195
|
+
let newContent;
|
|
196
|
+
if (firstLine.match(/^\/\/\s*(File:)?\s*/)) {
|
|
197
|
+
newContent = lines.slice(1).join("\n").trim();
|
|
198
|
+
} else {
|
|
199
|
+
newContent = response.trim();
|
|
200
|
+
}
|
|
201
|
+
await proposeAndWriteFile({
|
|
202
|
+
filePath: item.filePath,
|
|
203
|
+
content: newContent,
|
|
204
|
+
isNew: false
|
|
205
|
+
});
|
|
206
|
+
} catch (error) {
|
|
207
|
+
console.log(chalk.red(` \u274C Implementation failed: ${error.message}`));
|
|
208
|
+
}
|
|
209
|
+
} else if (config.loggedIn && config.conversationId) {
|
|
210
|
+
try {
|
|
211
|
+
const result = await callCloudFunction("implementSuggestion", {
|
|
212
|
+
conversationId: config.conversationId,
|
|
213
|
+
filePath: item.filePath,
|
|
214
|
+
suggestionId: item.id || "unknown",
|
|
215
|
+
category: "bugs",
|
|
216
|
+
jobId: `cli_impl_${Date.now()}`
|
|
217
|
+
});
|
|
218
|
+
if (result?.success) {
|
|
219
|
+
console.log(chalk.green(` \u2705 ${result.message}`));
|
|
220
|
+
} else {
|
|
221
|
+
console.log(chalk.red(" \u274C Implementation failed on platform."));
|
|
222
|
+
}
|
|
223
|
+
} catch (error) {
|
|
224
|
+
console.log(chalk.red(` \u274C ${error.message}`));
|
|
225
|
+
}
|
|
226
|
+
} else {
|
|
227
|
+
console.log(chalk.red(" \u274C No provider configured for implementation."));
|
|
228
|
+
}
|
|
229
|
+
console.log("");
|
|
230
|
+
}
|
|
231
|
+
function loadLocalSuggestions(category) {
|
|
232
|
+
const cwd = process.cwd();
|
|
233
|
+
const { analysisDir } = ensureProjectStructure(cwd);
|
|
234
|
+
const analysisPath = path.join(analysisDir, "results", "analysis.json");
|
|
235
|
+
if (!fs.existsSync(analysisPath)) return [];
|
|
236
|
+
const allResults = JSON.parse(fs.readFileSync(analysisPath, "utf-8"));
|
|
237
|
+
const suggestions = [];
|
|
238
|
+
for (const [filePath, fileResults] of Object.entries(allResults)) {
|
|
239
|
+
const items = fileResults[category] || [];
|
|
240
|
+
suggestions.push(...items.map((item, idx) => ({
|
|
241
|
+
...item,
|
|
242
|
+
filePath,
|
|
243
|
+
id: `${filePath.replace(/[\/\\]/g, "_")}_${idx}`
|
|
244
|
+
})));
|
|
245
|
+
}
|
|
246
|
+
return suggestions;
|
|
247
|
+
}
|
|
248
|
+
function wrapText(text, maxWidth) {
|
|
249
|
+
const words = text.split(" ");
|
|
250
|
+
const lines = [];
|
|
251
|
+
let currentLine = "";
|
|
252
|
+
for (const word of words) {
|
|
253
|
+
if (currentLine.length + word.length + 1 > maxWidth) {
|
|
254
|
+
lines.push(currentLine);
|
|
255
|
+
currentLine = word;
|
|
256
|
+
} else {
|
|
257
|
+
currentLine += (currentLine ? " " : "") + word;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (currentLine) lines.push(currentLine);
|
|
261
|
+
return lines;
|
|
262
|
+
}
|
|
263
|
+
export {
|
|
264
|
+
showInteractiveResults
|
|
265
|
+
};
|
|
@@ -0,0 +1,265 @@
|
|
|
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 inquirer from "inquirer";
|
|
12
|
+
import * as fs from "fs";
|
|
13
|
+
import * as path from "path";
|
|
14
|
+
var RED = chalk.hex("#EF5350");
|
|
15
|
+
var PURPLE = chalk.hex("#AB47BC");
|
|
16
|
+
var BLUE = chalk.hex("#42A5F5");
|
|
17
|
+
var TEAL = chalk.hex("#26A69A");
|
|
18
|
+
var AMBER = chalk.hex("#FFAB00");
|
|
19
|
+
var GRAY = chalk.gray;
|
|
20
|
+
var BORDER = chalk.hex("#455A64");
|
|
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
|
+
async function showInteractiveResults(config, category, sort, search) {
|
|
34
|
+
let suggestions = [];
|
|
35
|
+
if (config.tier === "platform" && config.provider !== "local" && config.loggedIn && config.conversationId) {
|
|
36
|
+
try {
|
|
37
|
+
const result = await callCloudFunction("getCLIAnalysisResults", {
|
|
38
|
+
conversationId: config.conversationId,
|
|
39
|
+
category,
|
|
40
|
+
sort: sort || "priority",
|
|
41
|
+
search: search || null
|
|
42
|
+
});
|
|
43
|
+
suggestions = result?.suggestions || [];
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.log(chalk.red(` \u274C ${error.message}`));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
suggestions = loadLocalSuggestions(category);
|
|
50
|
+
}
|
|
51
|
+
if (search) {
|
|
52
|
+
const query = search.toLowerCase();
|
|
53
|
+
suggestions = suggestions.filter(
|
|
54
|
+
(s) => (s.description || "").toLowerCase().includes(query) || (s.title || "").toLowerCase().includes(query) || (s.filePath || "").toLowerCase().includes(query)
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
if (sort === "file") {
|
|
58
|
+
suggestions.sort((a, b) => (a.filePath || "").localeCompare(b.filePath || ""));
|
|
59
|
+
} else {
|
|
60
|
+
const priorityMap = { "critical": 0, "high": 1, "medium": 2, "low": 3 };
|
|
61
|
+
suggestions.sort((a, b) => {
|
|
62
|
+
const pA = priorityMap[a.priority?.toLowerCase()] ?? 99;
|
|
63
|
+
const pB = priorityMap[b.priority?.toLowerCase()] ?? 99;
|
|
64
|
+
return pA - pB;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
if (suggestions.length === 0) {
|
|
68
|
+
console.log("");
|
|
69
|
+
console.log(chalk.green(" \u2705 No items found. Clean!"));
|
|
70
|
+
console.log("");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const color = CATEGORY_COLORS[category] || GRAY;
|
|
74
|
+
let running = true;
|
|
75
|
+
while (running) {
|
|
76
|
+
console.log("");
|
|
77
|
+
console.log(color(` \u25C6 ${category.toUpperCase()} (${suggestions.length} items)`));
|
|
78
|
+
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"));
|
|
79
|
+
console.log("");
|
|
80
|
+
const choices = suggestions.map((item2, idx) => {
|
|
81
|
+
const pColor = PRIORITY_COLORS[item2.priority?.toLowerCase()] || GRAY;
|
|
82
|
+
const priorityLabel = (item2.priority || "MEDIUM").toUpperCase().padEnd(9);
|
|
83
|
+
const filePath = (item2.filePath || "unknown").split("/").pop() || "unknown";
|
|
84
|
+
const desc = (item2.description || item2.title || "No description").slice(0, 45);
|
|
85
|
+
return {
|
|
86
|
+
name: `${pColor(priorityLabel)} ${chalk.cyan(filePath.padEnd(20))} ${chalk.white(desc)}`,
|
|
87
|
+
value: idx,
|
|
88
|
+
short: item2.title || item2.description?.slice(0, 30) || "Item"
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
choices.push({
|
|
92
|
+
name: chalk.gray(" \u2190 Back (quit)"),
|
|
93
|
+
value: -1,
|
|
94
|
+
short: "Quit"
|
|
95
|
+
});
|
|
96
|
+
const { selected } = await inquirer.prompt([
|
|
97
|
+
{
|
|
98
|
+
type: "select",
|
|
99
|
+
name: "selected",
|
|
100
|
+
message: color("Select a suggestion:"),
|
|
101
|
+
choices,
|
|
102
|
+
pageSize: 10,
|
|
103
|
+
loop: false
|
|
104
|
+
}
|
|
105
|
+
]);
|
|
106
|
+
if (selected === -1) {
|
|
107
|
+
running = false;
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
const item = suggestions[selected];
|
|
111
|
+
const action = await showExpandedView(item, category);
|
|
112
|
+
if (action === "implement") {
|
|
113
|
+
await handleImplement(item, config);
|
|
114
|
+
} else if (action === "dismiss") {
|
|
115
|
+
suggestions.splice(selected, 1);
|
|
116
|
+
console.log(chalk.gray(" \u23ED\uFE0F Dismissed."));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async function showExpandedView(item, category) {
|
|
121
|
+
const color = CATEGORY_COLORS[category] || GRAY;
|
|
122
|
+
const pColor = PRIORITY_COLORS[item.priority?.toLowerCase()] || GRAY;
|
|
123
|
+
console.log("");
|
|
124
|
+
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"));
|
|
125
|
+
console.log(color(" \u2551 ") + pColor(`${(item.priority || "MEDIUM").toUpperCase()} ${category.toUpperCase().slice(0, -1)}`));
|
|
126
|
+
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"));
|
|
127
|
+
console.log(color(" \u2551") + chalk.gray(" File: ") + chalk.cyan(item.filePath || "unknown"));
|
|
128
|
+
console.log(color(" \u2551") + chalk.gray(" Priority: ") + pColor((item.priority || "medium").toUpperCase()));
|
|
129
|
+
console.log(color(" \u2551"));
|
|
130
|
+
console.log(color(" \u2551") + chalk.gray(" Title:"));
|
|
131
|
+
console.log(color(" \u2551") + chalk.white.bold(` ${item.title || "No title"}`));
|
|
132
|
+
console.log(color(" \u2551"));
|
|
133
|
+
console.log(color(" \u2551") + chalk.gray(" Description:"));
|
|
134
|
+
const descLines = wrapText(item.description || "No description", 54);
|
|
135
|
+
for (const line of descLines) {
|
|
136
|
+
console.log(color(" \u2551") + chalk.white(` ${line}`));
|
|
137
|
+
}
|
|
138
|
+
if (item.implementation) {
|
|
139
|
+
console.log(color(" \u2551"));
|
|
140
|
+
console.log(color(" \u2551") + chalk.gray(" Implementation:"));
|
|
141
|
+
const implLines = wrapText(item.implementation, 54);
|
|
142
|
+
for (const line of implLines) {
|
|
143
|
+
console.log(color(" \u2551") + chalk.white(` ${line}`));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
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"));
|
|
147
|
+
console.log("");
|
|
148
|
+
const { action } = await inquirer.prompt([
|
|
149
|
+
{
|
|
150
|
+
type: "select",
|
|
151
|
+
name: "action",
|
|
152
|
+
message: "What do you want to do?",
|
|
153
|
+
choices: [
|
|
154
|
+
{ name: chalk.green(" \u{1F527} Implement this fix"), value: "implement" },
|
|
155
|
+
{ name: chalk.red(" \u{1F5D1}\uFE0F Dismiss"), value: "dismiss" },
|
|
156
|
+
{ name: chalk.gray(" \u2190 Back to list"), value: "back" }
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
]);
|
|
160
|
+
return action;
|
|
161
|
+
}
|
|
162
|
+
async function handleImplement(item, config) {
|
|
163
|
+
console.log("");
|
|
164
|
+
console.log(chalk.cyan(" \u{1F527} Implementing fix..."));
|
|
165
|
+
console.log("");
|
|
166
|
+
if (config.provider === "local" && config.localEndpoint) {
|
|
167
|
+
const fileContent = readFileContent(item.filePath);
|
|
168
|
+
if (!fileContent) {
|
|
169
|
+
console.log(chalk.red(` \u274C Could not read file: ${item.filePath}`));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const prompt = `You are an expert programmer implementing a specific code change.
|
|
173
|
+
|
|
174
|
+
CURRENT FILE: ${item.filePath}
|
|
175
|
+
${fileContent}
|
|
176
|
+
|
|
177
|
+
CHANGE TO IMPLEMENT:
|
|
178
|
+
Title: ${item.title}
|
|
179
|
+
Description: ${item.description}
|
|
180
|
+
Implementation Instructions: ${item.implementation || "Apply the fix described above."}
|
|
181
|
+
|
|
182
|
+
RULES:
|
|
183
|
+
- Return ONLY the complete updated file content with the change applied.
|
|
184
|
+
- Start the code with: // File: ${item.filePath}
|
|
185
|
+
- PRESERVE all existing code structure. Only change what's needed.
|
|
186
|
+
- Do NOT include any explanation outside the code.`;
|
|
187
|
+
try {
|
|
188
|
+
const messages = [
|
|
189
|
+
{ role: "system", content: "You are an expert programmer. Return ONLY the complete updated file. Start with // File: path comment. Preserve existing structure." },
|
|
190
|
+
{ role: "user", content: prompt }
|
|
191
|
+
];
|
|
192
|
+
const response = await callLocalModel(config.localEndpoint, messages);
|
|
193
|
+
const lines = response.split("\n");
|
|
194
|
+
const firstLine = lines[0].trim();
|
|
195
|
+
let newContent;
|
|
196
|
+
if (firstLine.match(/^\/\/\s*(File:)?\s*/)) {
|
|
197
|
+
newContent = lines.slice(1).join("\n").trim();
|
|
198
|
+
} else {
|
|
199
|
+
newContent = response.trim();
|
|
200
|
+
}
|
|
201
|
+
await proposeAndWriteFile({
|
|
202
|
+
filePath: item.filePath,
|
|
203
|
+
content: newContent,
|
|
204
|
+
isNew: false
|
|
205
|
+
});
|
|
206
|
+
} catch (error) {
|
|
207
|
+
console.log(chalk.red(` \u274C Implementation failed: ${error.message}`));
|
|
208
|
+
}
|
|
209
|
+
} else if (config.loggedIn && config.conversationId) {
|
|
210
|
+
try {
|
|
211
|
+
const result = await callCloudFunction("implementSuggestion", {
|
|
212
|
+
conversationId: config.conversationId,
|
|
213
|
+
filePath: item.filePath,
|
|
214
|
+
suggestionId: item.id || "unknown",
|
|
215
|
+
category: "bugs",
|
|
216
|
+
jobId: `cli_impl_${Date.now()}`
|
|
217
|
+
});
|
|
218
|
+
if (result?.success) {
|
|
219
|
+
console.log(chalk.green(` \u2705 ${result.message}`));
|
|
220
|
+
} else {
|
|
221
|
+
console.log(chalk.red(" \u274C Implementation failed on platform."));
|
|
222
|
+
}
|
|
223
|
+
} catch (error) {
|
|
224
|
+
console.log(chalk.red(` \u274C ${error.message}`));
|
|
225
|
+
}
|
|
226
|
+
} else {
|
|
227
|
+
console.log(chalk.red(" \u274C No provider configured for implementation."));
|
|
228
|
+
}
|
|
229
|
+
console.log("");
|
|
230
|
+
}
|
|
231
|
+
function loadLocalSuggestions(category) {
|
|
232
|
+
const cwd = process.cwd();
|
|
233
|
+
const { analysisDir } = ensureProjectStructure(cwd);
|
|
234
|
+
const analysisPath = path.join(analysisDir, "results", "analysis.json");
|
|
235
|
+
if (!fs.existsSync(analysisPath)) return [];
|
|
236
|
+
const allResults = JSON.parse(fs.readFileSync(analysisPath, "utf-8"));
|
|
237
|
+
const suggestions = [];
|
|
238
|
+
for (const [filePath, fileResults] of Object.entries(allResults)) {
|
|
239
|
+
const items = fileResults[category] || [];
|
|
240
|
+
suggestions.push(...items.map((item, idx) => ({
|
|
241
|
+
...item,
|
|
242
|
+
filePath,
|
|
243
|
+
id: `${filePath.replace(/[\/\\]/g, "_")}_${idx}`
|
|
244
|
+
})));
|
|
245
|
+
}
|
|
246
|
+
return suggestions;
|
|
247
|
+
}
|
|
248
|
+
function wrapText(text, maxWidth) {
|
|
249
|
+
const words = text.split(" ");
|
|
250
|
+
const lines = [];
|
|
251
|
+
let currentLine = "";
|
|
252
|
+
for (const word of words) {
|
|
253
|
+
if (currentLine.length + word.length + 1 > maxWidth) {
|
|
254
|
+
lines.push(currentLine);
|
|
255
|
+
currentLine = word;
|
|
256
|
+
} else {
|
|
257
|
+
currentLine += (currentLine ? " " : "") + word;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (currentLine) lines.push(currentLine);
|
|
261
|
+
return lines;
|
|
262
|
+
}
|
|
263
|
+
export {
|
|
264
|
+
showInteractiveResults
|
|
265
|
+
};
|