@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.
- package/dist/bin/{analyse-auto-WQUK5YPO.js → analyse-auto-KKWLMLHZ.js} +4 -5
- package/dist/bin/{analyse-results-FIDS4635.js → analyse-results-N5QLJNND.js} +2 -2
- package/dist/bin/bob.js +1 -0
- package/dist/bin/chunk-WEHSNZKO.js +880 -0
- package/package.json +11 -5
- package/bin/bob.ts +0 -74
- package/dist/bin/analyse-auto-AAWSETKY.js +0 -540
- package/dist/bin/analyse-auto-FQ62GYPV.js +0 -533
- package/dist/bin/analyse-auto-JAD24IQ5.js +0 -511
- package/dist/bin/analyse-auto-R4MZA7SX.js +0 -511
- package/dist/bin/analyse-auto-V3SH4MS7.js +0 -524
- package/dist/bin/analyse-auto-WVAY6467.js +0 -280
- package/dist/bin/analyse-results-3NSD6MAY.js +0 -363
- package/dist/bin/analyse-results-B7LONUXU.js +0 -265
- package/dist/bin/analyse-results-E6NBAVDB.js +0 -265
- package/dist/bin/analyse-results-LMGVKAUX.js +0 -342
- package/dist/bin/analyse-results-LSMLUEIB.js +0 -338
- package/dist/bin/analyse-results-MOCLBCD7.js +0 -326
- package/dist/bin/analyse-results-NLAEAOOP.js +0 -10
- package/dist/bin/analyse-results-PYQIKWYL.js +0 -9
- package/dist/bin/analyse-results-R3MG5H7G.js +0 -329
- package/dist/bin/analyse-results-UYZZSBHB.js +0 -9
- package/dist/bin/analyse-results-YYGHIK2Q.js +0 -9
- package/dist/bin/analysis-tracker-N5VANTLH.js +0 -12
- package/dist/bin/chunk-3RSDDQE2.js +0 -420
- package/dist/bin/chunk-6KWC4HDO.js +0 -97
- package/dist/bin/chunk-6W7WDF4Q.js +0 -589
- package/dist/bin/chunk-7CXM3RLM.js +0 -287
- package/dist/bin/chunk-FGYL6SWO.js +0 -465
- package/dist/bin/chunk-J4BSKFCW.js +0 -624
- package/dist/bin/chunk-KWOQFI6L.js +0 -287
- package/dist/bin/chunk-OOGLZ2QB.js +0 -322
- package/dist/bin/chunk-TEVQLSGD.js +0 -287
- package/dist/bin/chunk-VUS7R7SO.js +0 -479
|
@@ -1,511 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
loadLocalSuggestions
|
|
3
|
-
} from "./chunk-KWOQFI6L.js";
|
|
4
|
-
import {
|
|
5
|
-
callLocalModel,
|
|
6
|
-
getConfig,
|
|
7
|
-
readFileContent
|
|
8
|
-
} from "./chunk-VUS7R7SO.js";
|
|
9
|
-
|
|
10
|
-
// src/commands/analyse-auto.ts
|
|
11
|
-
import chalk from "chalk";
|
|
12
|
-
import inquirer from "inquirer";
|
|
13
|
-
import * as fs from "fs";
|
|
14
|
-
import * as path from "path";
|
|
15
|
-
import * as readline from "readline";
|
|
16
|
-
var RED = chalk.hex("#EF5350");
|
|
17
|
-
var GREEN = chalk.hex("#66BB6A");
|
|
18
|
-
var AMBER = chalk.hex("#FFAB00");
|
|
19
|
-
var BLUE = chalk.hex("#42A5F5");
|
|
20
|
-
var GRAY = chalk.gray;
|
|
21
|
-
var BORDER = chalk.hex("#455A64");
|
|
22
|
-
async function runAutoFix(options) {
|
|
23
|
-
const config = getConfig();
|
|
24
|
-
if (config.provider !== "local" || !config.localEndpoint) {
|
|
25
|
-
console.log("");
|
|
26
|
-
console.log(chalk.red(" \u274C Auto-fix requires a local model."));
|
|
27
|
-
console.log(GRAY(" Run `bob config set provider local`"));
|
|
28
|
-
console.log(GRAY(" Run `bob config set localEndpoint http://127.0.0.1:11434/api/chat`"));
|
|
29
|
-
console.log("");
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
const confidenceGate = options.confidence || 90;
|
|
33
|
-
const priorityGate = options.priority || "critical";
|
|
34
|
-
const categories = options.category ? [options.category] : ["bugs", "features", "improvements", "upgrades"];
|
|
35
|
-
const isAutoMode = config.autoMode || false;
|
|
36
|
-
console.log("");
|
|
37
|
-
console.log(chalk.bold.cyan(" \u26A1 MiniBob Auto-Fix Mode"));
|
|
38
|
-
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"));
|
|
39
|
-
console.log(GRAY(` Confidence gate: ${confidenceGate}%`));
|
|
40
|
-
console.log(GRAY(` Priority gate: ${priorityGate}+`));
|
|
41
|
-
console.log(GRAY(` Categories: ${categories.join(", ")}`));
|
|
42
|
-
console.log(GRAY(` Auto mode: ${isAutoMode ? "ON (no approval prompts)" : "OFF (approval required)"}`));
|
|
43
|
-
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"));
|
|
44
|
-
console.log("");
|
|
45
|
-
let allSuggestions = [];
|
|
46
|
-
for (const cat of categories) {
|
|
47
|
-
allSuggestions.push(...loadLocalSuggestions(cat));
|
|
48
|
-
}
|
|
49
|
-
const priorityOrder = ["critical", "high", "medium", "low"];
|
|
50
|
-
const gateIndex = priorityOrder.indexOf(priorityGate.toLowerCase());
|
|
51
|
-
if (gateIndex >= 0) {
|
|
52
|
-
allSuggestions = allSuggestions.filter((s) => {
|
|
53
|
-
const idx = priorityOrder.indexOf(s.priority?.toLowerCase());
|
|
54
|
-
return idx >= 0 && idx <= gateIndex;
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
if (allSuggestions.length === 0) {
|
|
58
|
-
console.log(chalk.green(" \u2705 No suggestions match your gates. Project is clean!"));
|
|
59
|
-
console.log("");
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
console.log(GRAY(` Found ${allSuggestions.length} suggestions matching criteria.`));
|
|
63
|
-
console.log("");
|
|
64
|
-
console.log(AMBER(" \u{1F9E0} Phase 1: Triage \u2014 Bob is evaluating suggestions..."));
|
|
65
|
-
console.log("");
|
|
66
|
-
const triageResults = await performTriage(allSuggestions, confidenceGate, config.localEndpoint);
|
|
67
|
-
if (!triageResults) return;
|
|
68
|
-
const autoApprove = triageResults.filter((r) => r.action === "work" && r.confidence >= confidenceGate);
|
|
69
|
-
const needsReview = triageResults.filter((r) => r.action === "review" || r.action === "work" && r.confidence < confidenceGate && r.confidence >= confidenceGate - 15);
|
|
70
|
-
const dismissed = triageResults.filter((r) => r.action === "dismiss" || r.confidence < confidenceGate - 15);
|
|
71
|
-
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"));
|
|
72
|
-
console.log(BORDER(" \u2551") + AMBER(" \u25C6 TRIAGE COMPLETE"));
|
|
73
|
-
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"));
|
|
74
|
-
console.log(BORDER(" \u2551") + GREEN(` \u2705 Auto-approve: ${autoApprove.length} items (confidence \u2265 ${confidenceGate}%)`));
|
|
75
|
-
if (needsReview.length > 0) {
|
|
76
|
-
console.log(BORDER(" \u2551") + AMBER(` \u{1F914} Needs review: ${needsReview.length} items`));
|
|
77
|
-
}
|
|
78
|
-
console.log(BORDER(" \u2551") + GRAY(` \u23F8\uFE0F Dismissed: ${dismissed.length} items`));
|
|
79
|
-
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"));
|
|
80
|
-
console.log("");
|
|
81
|
-
if (autoApprove.length > 0) {
|
|
82
|
-
console.log(GREEN(" \u2705 APPROVE (auto-fix these):"));
|
|
83
|
-
for (let i = 0; i < autoApprove.length; i++) {
|
|
84
|
-
const item = autoApprove[i];
|
|
85
|
-
console.log(GRAY(` ${i + 1}. ${item.suggestion.filePath} \u2014 ${item.suggestion.title || item.suggestion.description?.slice(0, 40) || "No title"} (${item.confidence}%)`));
|
|
86
|
-
}
|
|
87
|
-
console.log("");
|
|
88
|
-
}
|
|
89
|
-
if (needsReview.length > 0) {
|
|
90
|
-
console.log(AMBER(" \u{1F914} REVIEW (Bob wants your input):"));
|
|
91
|
-
for (let i = 0; i < needsReview.length; i++) {
|
|
92
|
-
const item = needsReview[i];
|
|
93
|
-
console.log(GRAY(` ${i + 1}. ${item.suggestion.filePath} \u2014 ${item.reason} (${item.confidence}%)`));
|
|
94
|
-
}
|
|
95
|
-
console.log("");
|
|
96
|
-
}
|
|
97
|
-
let workQueue = [];
|
|
98
|
-
if (isAutoMode) {
|
|
99
|
-
workQueue = autoApprove.map((r) => ({
|
|
100
|
-
suggestion: r.suggestion,
|
|
101
|
-
confidence: r.confidence,
|
|
102
|
-
reason: r.reason,
|
|
103
|
-
status: "pending"
|
|
104
|
-
}));
|
|
105
|
-
console.log(GRAY(" [Auto mode] Proceeding without approval prompt."));
|
|
106
|
-
} else {
|
|
107
|
-
const { choice } = await inquirer.prompt([
|
|
108
|
-
{
|
|
109
|
-
type: "select",
|
|
110
|
-
name: "choice",
|
|
111
|
-
message: AMBER("How would you like to proceed?"),
|
|
112
|
-
choices: [
|
|
113
|
-
{ name: GREEN(` \u2705 Auto-fix approved items only (${autoApprove.length} items)`), value: "approved_only" },
|
|
114
|
-
{ name: GREEN(` \u2705 Auto-fix ALL including review items (${autoApprove.length + needsReview.length} items)`), value: "all" },
|
|
115
|
-
{ name: BLUE(" \u{1F5E3}\uFE0F Talk to Bob about these suggestions"), value: "talk" },
|
|
116
|
-
{ name: GRAY(" \u2190 Cancel"), value: "cancel" }
|
|
117
|
-
]
|
|
118
|
-
}
|
|
119
|
-
]);
|
|
120
|
-
if (choice === "cancel") {
|
|
121
|
-
console.log(GRAY(" Cancelled."));
|
|
122
|
-
console.log("");
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
if (choice === "talk") {
|
|
126
|
-
const updatedQueue = await talkToBobAboutSuggestions(autoApprove, needsReview, dismissed, config.localEndpoint);
|
|
127
|
-
if (updatedQueue.length === 0) {
|
|
128
|
-
console.log(GRAY(" No items to implement after discussion."));
|
|
129
|
-
console.log("");
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
workQueue = updatedQueue;
|
|
133
|
-
} else if (choice === "approved_only") {
|
|
134
|
-
workQueue = autoApprove.map((r) => ({
|
|
135
|
-
suggestion: r.suggestion,
|
|
136
|
-
confidence: r.confidence,
|
|
137
|
-
reason: r.reason,
|
|
138
|
-
status: "pending"
|
|
139
|
-
}));
|
|
140
|
-
} else {
|
|
141
|
-
workQueue = [...autoApprove, ...needsReview].map((r) => ({
|
|
142
|
-
suggestion: r.suggestion,
|
|
143
|
-
confidence: r.confidence,
|
|
144
|
-
reason: r.reason,
|
|
145
|
-
status: "pending"
|
|
146
|
-
}));
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
if (workQueue.length === 0) {
|
|
150
|
-
console.log(chalk.yellow(" \u26A0\uFE0F Nothing to implement."));
|
|
151
|
-
console.log("");
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
console.log("");
|
|
155
|
-
console.log(AMBER(" \u{1F527} Phase 3: MiniBob Implementing..."));
|
|
156
|
-
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"));
|
|
157
|
-
console.log(GRAY(" \u{1F4AC} You can type messages to Bob while MiniBobs work."));
|
|
158
|
-
console.log(GRAY(" Your input adjusts remaining tasks. Type /done when finished."));
|
|
159
|
-
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"));
|
|
160
|
-
console.log("");
|
|
161
|
-
await executeWithChat(workQueue, config);
|
|
162
|
-
const fixed = workQueue.filter((t) => t.status === "done");
|
|
163
|
-
const failed = workQueue.filter((t) => t.status === "failed");
|
|
164
|
-
const skipped = workQueue.filter((t) => t.status === "skipped");
|
|
165
|
-
console.log("");
|
|
166
|
-
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"));
|
|
167
|
-
console.log(BORDER(" \u2551") + AMBER(" \u25C6 MINIBOB AUTO-FIX REPORT"));
|
|
168
|
-
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"));
|
|
169
|
-
console.log(BORDER(" \u2551") + GREEN(` \u2705 Fixed: ${fixed.length} items`));
|
|
170
|
-
console.log(BORDER(" \u2551") + GRAY(` \u23F8\uFE0F Held: ${dismissed.length + skipped.length} items`));
|
|
171
|
-
if (failed.length > 0) {
|
|
172
|
-
console.log(BORDER(" \u2551") + RED(` \u274C Failed: ${failed.length} items`));
|
|
173
|
-
}
|
|
174
|
-
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"));
|
|
175
|
-
if (fixed.length > 0) {
|
|
176
|
-
console.log(BORDER(" \u2551") + GRAY(" Fixed files:"));
|
|
177
|
-
for (const item of fixed) {
|
|
178
|
-
console.log(BORDER(" \u2551") + GREEN(` \u2705 ${item.suggestion.filePath}`));
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
if (failed.length > 0) {
|
|
182
|
-
console.log(BORDER(" \u2551") + GRAY(" Failed:"));
|
|
183
|
-
for (const item of failed) {
|
|
184
|
-
console.log(BORDER(" \u2551") + RED(` \u274C ${item.suggestion.filePath}`));
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
if (skipped.length > 0) {
|
|
188
|
-
console.log(BORDER(" \u2551") + GRAY(" Skipped (by user):"));
|
|
189
|
-
for (const item of skipped) {
|
|
190
|
-
console.log(BORDER(" \u2551") + GRAY(` \u23F8\uFE0F ${item.suggestion.filePath}`));
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
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"));
|
|
194
|
-
console.log("");
|
|
195
|
-
console.log(GRAY(" \u{1F4E6} All original files backed up to .bob-backups/"));
|
|
196
|
-
console.log(GRAY(' Run `bob push "MiniBob auto-fix batch"` to commit changes.'));
|
|
197
|
-
console.log("");
|
|
198
|
-
}
|
|
199
|
-
async function performTriage(suggestions, confidenceGate, endpoint) {
|
|
200
|
-
const triagePrompt = `You are a senior engineering lead triaging code suggestions. For each suggestion, decide: WORK (safe to auto-fix), REVIEW (needs human input), or DISMISS (skip it).
|
|
201
|
-
|
|
202
|
-
CRITERIA:
|
|
203
|
-
- WORK: Clear fix, well-defined, won't break anything. High confidence.
|
|
204
|
-
- REVIEW: Fix is good but has side effects or behavioral changes that a human should approve.
|
|
205
|
-
- DISMISS: Vague, risky, or effort outweighs benefit.
|
|
206
|
-
|
|
207
|
-
SUGGESTIONS:
|
|
208
|
-
${suggestions.map((s, i) => `[${i}] ${s.priority?.toUpperCase()} | ${s.filePath} | ${s.title || "No title"} | ${s.description || "No description"}`).join("\n")}
|
|
209
|
-
|
|
210
|
-
Respond with ONLY a JSON array:
|
|
211
|
-
[{"index": 0, "action": "work"|"review"|"dismiss", "confidence": 0-100, "reason": "brief reason"}]`;
|
|
212
|
-
try {
|
|
213
|
-
const messages = [
|
|
214
|
-
{ role: "system", content: "You are a senior engineering lead. Respond with ONLY a valid JSON array. No explanation." },
|
|
215
|
-
{ role: "user", content: triagePrompt }
|
|
216
|
-
];
|
|
217
|
-
const response = await callLocalModel(endpoint, messages);
|
|
218
|
-
const jsonMatch = response.match(/\[[\s\S]*\]/);
|
|
219
|
-
if (!jsonMatch) {
|
|
220
|
-
console.log(chalk.red(" \u274C Triage failed: Could not parse response."));
|
|
221
|
-
return null;
|
|
222
|
-
}
|
|
223
|
-
const parsed = JSON.parse(jsonMatch[0]);
|
|
224
|
-
const results = [];
|
|
225
|
-
for (const decision of parsed) {
|
|
226
|
-
if (decision.index !== void 0 && decision.index < suggestions.length) {
|
|
227
|
-
results.push({
|
|
228
|
-
action: decision.action === "work" ? "work" : decision.action === "review" ? "review" : "dismiss",
|
|
229
|
-
confidence: decision.confidence || 0,
|
|
230
|
-
reason: decision.reason || "",
|
|
231
|
-
suggestion: suggestions[decision.index]
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
return results;
|
|
236
|
-
} catch (error) {
|
|
237
|
-
console.log(chalk.red(` \u274C Triage failed: ${error.message}`));
|
|
238
|
-
return null;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
async function talkToBobAboutSuggestions(approved, review, dismissed, endpoint) {
|
|
242
|
-
console.log("");
|
|
243
|
-
console.log(BLUE(" \u{1F5E3}\uFE0F Chat with Bob about the suggestions"));
|
|
244
|
-
console.log(GRAY(" Ask questions, adjust the plan. Type /done when ready."));
|
|
245
|
-
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"));
|
|
246
|
-
console.log("");
|
|
247
|
-
const allItems = [...approved, ...review, ...dismissed];
|
|
248
|
-
const context = `You are Bob, helping the user decide which code suggestions to implement.
|
|
249
|
-
|
|
250
|
-
APPROVED (will auto-fix): ${approved.map((r) => `${r.suggestion.filePath}: ${r.suggestion.title || r.suggestion.description} (${r.confidence}% \u2014 ${r.reason})`).join("\n")}
|
|
251
|
-
|
|
252
|
-
NEEDS REVIEW: ${review.map((r) => `${r.suggestion.filePath}: ${r.suggestion.title || r.suggestion.description} (${r.confidence}% \u2014 ${r.reason})`).join("\n")}
|
|
253
|
-
|
|
254
|
-
DISMISSED: ${dismissed.map((r) => `${r.suggestion.filePath}: ${r.suggestion.title || r.suggestion.description} (${r.confidence}% \u2014 ${r.reason})`).join("\n")}
|
|
255
|
-
|
|
256
|
-
Help the user understand the suggestions and decide what to implement. Be concise and direct.
|
|
257
|
-
If the user says to add/remove items, acknowledge it.`;
|
|
258
|
-
const history = [
|
|
259
|
-
{ role: "system", content: context }
|
|
260
|
-
];
|
|
261
|
-
let finalApproved = [...approved];
|
|
262
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
263
|
-
return new Promise((resolve) => {
|
|
264
|
-
const prompt = () => {
|
|
265
|
-
rl.question(chalk.green(" You: "), async (input) => {
|
|
266
|
-
const trimmed = input.trim();
|
|
267
|
-
if (!trimmed) {
|
|
268
|
-
prompt();
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
if (trimmed === "/done") {
|
|
272
|
-
rl.close();
|
|
273
|
-
console.log("");
|
|
274
|
-
console.log(GRAY(` Proceeding with ${finalApproved.length} items.`));
|
|
275
|
-
console.log("");
|
|
276
|
-
resolve(finalApproved.map((r) => ({
|
|
277
|
-
suggestion: r.suggestion,
|
|
278
|
-
confidence: r.confidence,
|
|
279
|
-
reason: r.reason,
|
|
280
|
-
status: "pending"
|
|
281
|
-
})));
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
if (trimmed.toLowerCase().startsWith("skip ") || trimmed.toLowerCase().startsWith("remove ")) {
|
|
285
|
-
const target = trimmed.slice(trimmed.indexOf(" ") + 1).trim().toLowerCase();
|
|
286
|
-
const before = finalApproved.length;
|
|
287
|
-
finalApproved = finalApproved.filter((r) => !r.suggestion.filePath.toLowerCase().includes(target));
|
|
288
|
-
const removed = before - finalApproved.length;
|
|
289
|
-
if (removed > 0) {
|
|
290
|
-
console.log(chalk.yellow(` \u23F8\uFE0F Removed ${removed} item(s) matching "${target}"`));
|
|
291
|
-
} else {
|
|
292
|
-
console.log(GRAY(` No items found matching "${target}"`));
|
|
293
|
-
}
|
|
294
|
-
console.log("");
|
|
295
|
-
prompt();
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
if (trimmed.toLowerCase().startsWith("add ")) {
|
|
299
|
-
const target = trimmed.slice(4).trim().toLowerCase();
|
|
300
|
-
const toAdd = [...review, ...dismissed].filter((r) => r.suggestion.filePath.toLowerCase().includes(target));
|
|
301
|
-
if (toAdd.length > 0) {
|
|
302
|
-
finalApproved.push(...toAdd);
|
|
303
|
-
console.log(chalk.green(` \u2705 Added ${toAdd.length} item(s) matching "${target}"`));
|
|
304
|
-
} else {
|
|
305
|
-
console.log(GRAY(` No items found matching "${target}"`));
|
|
306
|
-
}
|
|
307
|
-
console.log("");
|
|
308
|
-
prompt();
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
history.push({ role: "user", content: trimmed });
|
|
312
|
-
try {
|
|
313
|
-
const response = await callLocalModel(endpoint, history);
|
|
314
|
-
history.push({ role: "assistant", content: response });
|
|
315
|
-
console.log("");
|
|
316
|
-
console.log(chalk.bold.cyan(" \u{1F916} Bob:"));
|
|
317
|
-
const lines = response.split("\n");
|
|
318
|
-
for (const line of lines) {
|
|
319
|
-
console.log(` ${line}`);
|
|
320
|
-
}
|
|
321
|
-
console.log("");
|
|
322
|
-
} catch (error) {
|
|
323
|
-
console.log(chalk.red(` \u274C ${error.message}`));
|
|
324
|
-
console.log("");
|
|
325
|
-
}
|
|
326
|
-
prompt();
|
|
327
|
-
});
|
|
328
|
-
};
|
|
329
|
-
prompt();
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
async function executeWithChat(workQueue, config) {
|
|
333
|
-
renderTodoList(workQueue);
|
|
334
|
-
let userMessages = [];
|
|
335
|
-
let chatActive = true;
|
|
336
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
337
|
-
const inputPromise = new Promise((resolve) => {
|
|
338
|
-
const askForInput = () => {
|
|
339
|
-
if (!chatActive) {
|
|
340
|
-
resolve();
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
rl.question(chalk.gray(" \u{1F4AC} (type to talk to Bob, /skip <file>, /done to finish early): "), (input) => {
|
|
344
|
-
const trimmed = input.trim();
|
|
345
|
-
if (trimmed === "/done") {
|
|
346
|
-
for (const task of workQueue) {
|
|
347
|
-
if (task.status === "pending") {
|
|
348
|
-
task.status = "skipped";
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
chatActive = false;
|
|
352
|
-
resolve();
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
if (trimmed.startsWith("/skip ")) {
|
|
356
|
-
const target = trimmed.slice(6).trim().toLowerCase();
|
|
357
|
-
for (const task of workQueue) {
|
|
358
|
-
if (task.status === "pending" && task.suggestion.filePath.toLowerCase().includes(target)) {
|
|
359
|
-
task.status = "skipped";
|
|
360
|
-
console.log(chalk.yellow(` \u23F8\uFE0F Skipping: ${task.suggestion.filePath}`));
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
} else if (trimmed) {
|
|
364
|
-
userMessages.push(trimmed);
|
|
365
|
-
}
|
|
366
|
-
if (chatActive) {
|
|
367
|
-
askForInput();
|
|
368
|
-
} else {
|
|
369
|
-
resolve();
|
|
370
|
-
}
|
|
371
|
-
});
|
|
372
|
-
};
|
|
373
|
-
askForInput();
|
|
374
|
-
});
|
|
375
|
-
for (let i = 0; i < workQueue.length; i++) {
|
|
376
|
-
const task = workQueue[i];
|
|
377
|
-
if (task.status === "skipped") continue;
|
|
378
|
-
task.status = "working";
|
|
379
|
-
renderTodoList(workQueue);
|
|
380
|
-
if (userMessages.length > 0) {
|
|
381
|
-
const userMsg = userMessages.shift();
|
|
382
|
-
try {
|
|
383
|
-
const bobResponse = await callLocalModel(config.localEndpoint, [
|
|
384
|
-
{ role: "system", content: `You are Bob supervising MiniBob auto-fixes. The user said something during execution. Respond briefly (1-2 sentences). Current task: ${task.suggestion.filePath} \u2014 ${task.suggestion.title || task.suggestion.description}` },
|
|
385
|
-
{ role: "user", content: userMsg }
|
|
386
|
-
]);
|
|
387
|
-
console.log(chalk.bold.cyan(` \u{1F916} Bob: `) + bobResponse.split("\n")[0]);
|
|
388
|
-
console.log("");
|
|
389
|
-
} catch {
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
const success = await implementTask(task, config.localEndpoint);
|
|
393
|
-
task.status = success ? "done" : "failed";
|
|
394
|
-
renderTodoList(workQueue);
|
|
395
|
-
}
|
|
396
|
-
chatActive = false;
|
|
397
|
-
rl.close();
|
|
398
|
-
await Promise.race([inputPromise, new Promise((resolve) => setTimeout(resolve, 100))]);
|
|
399
|
-
}
|
|
400
|
-
async function implementTask(task, endpoint) {
|
|
401
|
-
const suggestion = task.suggestion;
|
|
402
|
-
const fileContent = readFileContent(suggestion.filePath);
|
|
403
|
-
if (!fileContent) return false;
|
|
404
|
-
const prompt = `You are an expert programmer implementing a specific code change.
|
|
405
|
-
|
|
406
|
-
CURRENT FILE: ${suggestion.filePath}
|
|
407
|
-
${fileContent}
|
|
408
|
-
|
|
409
|
-
CHANGE TO IMPLEMENT:
|
|
410
|
-
Title: ${suggestion.title || "Fix"}
|
|
411
|
-
Description: ${suggestion.description}
|
|
412
|
-
Implementation Instructions: ${suggestion.implementation || "Apply the fix described above."}
|
|
413
|
-
|
|
414
|
-
RULES:
|
|
415
|
-
- Return ONLY the complete updated file content.
|
|
416
|
-
- Start with: // File: ${suggestion.filePath}
|
|
417
|
-
- PRESERVE existing structure. Only change what's needed.
|
|
418
|
-
- No explanation outside the code.`;
|
|
419
|
-
try {
|
|
420
|
-
const messages = [
|
|
421
|
-
{ role: "system", content: "You are an expert programmer. Return ONLY the complete updated file. Preserve existing structure." },
|
|
422
|
-
{ role: "user", content: prompt }
|
|
423
|
-
];
|
|
424
|
-
const response = await callLocalModel(endpoint, messages);
|
|
425
|
-
const lines = response.split("\n");
|
|
426
|
-
const firstLine = lines[0].trim();
|
|
427
|
-
let newContent;
|
|
428
|
-
if (firstLine.match(/^\/\/\s*(File:)?\s*/)) {
|
|
429
|
-
newContent = lines.slice(1).join("\n").trim();
|
|
430
|
-
} else {
|
|
431
|
-
newContent = response.trim();
|
|
432
|
-
}
|
|
433
|
-
const absolutePath = path.join(process.cwd(), suggestion.filePath);
|
|
434
|
-
const backupDir = path.join(process.cwd(), ".bob-backups");
|
|
435
|
-
if (!fs.existsSync(backupDir)) fs.mkdirSync(backupDir, { recursive: true });
|
|
436
|
-
if (fs.existsSync(absolutePath)) {
|
|
437
|
-
const timestamp = Date.now();
|
|
438
|
-
const backupName = suggestion.filePath.replace(/[\/\\]/g, "_") + `.${timestamp}.bak`;
|
|
439
|
-
fs.copyFileSync(absolutePath, path.join(backupDir, backupName));
|
|
440
|
-
}
|
|
441
|
-
const dir = path.dirname(absolutePath);
|
|
442
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
443
|
-
fs.writeFileSync(absolutePath, newContent, "utf-8");
|
|
444
|
-
return true;
|
|
445
|
-
} catch {
|
|
446
|
-
return false;
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
var lastTodoLines = 0;
|
|
450
|
-
function renderTodoList(queue) {
|
|
451
|
-
const lines = [];
|
|
452
|
-
lines.push("");
|
|
453
|
-
lines.push(AMBER(" \u{1F4CB} MiniBob Work Queue"));
|
|
454
|
-
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"));
|
|
455
|
-
for (let i = 0; i < queue.length; i++) {
|
|
456
|
-
const task = queue[i];
|
|
457
|
-
const label = task.suggestion.title || task.suggestion.description?.slice(0, 40) || "No title";
|
|
458
|
-
let icon;
|
|
459
|
-
let color;
|
|
460
|
-
switch (task.status) {
|
|
461
|
-
case "done":
|
|
462
|
-
icon = "\u2611";
|
|
463
|
-
color = GREEN;
|
|
464
|
-
break;
|
|
465
|
-
case "working":
|
|
466
|
-
icon = "\u23F3";
|
|
467
|
-
color = AMBER;
|
|
468
|
-
break;
|
|
469
|
-
case "failed":
|
|
470
|
-
icon = "\u2717";
|
|
471
|
-
color = RED;
|
|
472
|
-
break;
|
|
473
|
-
case "skipped":
|
|
474
|
-
icon = "\u23F8\uFE0F";
|
|
475
|
-
color = GRAY;
|
|
476
|
-
break;
|
|
477
|
-
default:
|
|
478
|
-
icon = "\u2610";
|
|
479
|
-
color = GRAY;
|
|
480
|
-
}
|
|
481
|
-
lines.push(color(` ${icon} [${i + 1}/${queue.length}] ${task.suggestion.filePath}`));
|
|
482
|
-
lines.push(color(` ${label} (${task.confidence}%)`));
|
|
483
|
-
}
|
|
484
|
-
const completed = queue.filter((t) => t.status === "done" || t.status === "failed" || t.status === "skipped").length;
|
|
485
|
-
const total = queue.length;
|
|
486
|
-
const percent = total > 0 ? completed / total : 0;
|
|
487
|
-
const barLen = 30;
|
|
488
|
-
const filled = Math.round(percent * barLen);
|
|
489
|
-
let barColor;
|
|
490
|
-
if (percent < 0.25) barColor = chalk.red;
|
|
491
|
-
else if (percent < 0.5) barColor = chalk.hex("#FF8C00");
|
|
492
|
-
else if (percent < 0.75) barColor = chalk.yellow;
|
|
493
|
-
else barColor = chalk.green;
|
|
494
|
-
lines.push("");
|
|
495
|
-
lines.push(` [${barColor("\u2588".repeat(filled))}${GRAY("\u2591".repeat(barLen - filled))}] ${completed}/${total} ${barColor(Math.round(percent * 100) + "%")}`);
|
|
496
|
-
lines.push("");
|
|
497
|
-
if (lastTodoLines > 0) {
|
|
498
|
-
process.stdout.write(`\x1B[${lastTodoLines}A`);
|
|
499
|
-
for (let i = 0; i < lastTodoLines; i++) {
|
|
500
|
-
process.stdout.write("\x1B[2K\n");
|
|
501
|
-
}
|
|
502
|
-
process.stdout.write(`\x1B[${lastTodoLines}A`);
|
|
503
|
-
}
|
|
504
|
-
for (const line of lines) {
|
|
505
|
-
process.stdout.write(line + "\n");
|
|
506
|
-
}
|
|
507
|
-
lastTodoLines = lines.length;
|
|
508
|
-
}
|
|
509
|
-
export {
|
|
510
|
-
runAutoFix
|
|
511
|
-
};
|