vibecodingmachine-cli 2026.2.26-1752 → 2026.3.9-1621
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/auth/auth-compliance.js +7 -1
- package/bin/commands/agent-commands.js +150 -228
- package/bin/commands/command-aliases.js +68 -0
- package/bin/vibecodingmachine.js +1 -2
- package/package.json +2 -2
- package/src/commands/agents/list.js +71 -115
- package/src/commands/agents-check.js +16 -4
- package/src/commands/analyze-file-sizes.js +1 -1
- package/src/commands/auto-direct/auto-provider-manager.js +290 -0
- package/src/commands/auto-direct/auto-status-display.js +331 -0
- package/src/commands/auto-direct/auto-utils.js +439 -0
- package/src/commands/auto-direct/file-operations.js +110 -0
- package/src/commands/auto-direct/provider-config.js +1 -1
- package/src/commands/auto-direct/provider-manager.js +1 -1
- package/src/commands/auto-direct/status-display.js +1 -1
- package/src/commands/auto-direct/utils.js +24 -18
- package/src/commands/auto-direct-refactored.js +413 -0
- package/src/commands/auto-direct.js +594 -188
- package/src/commands/requirements/commands.js +353 -0
- package/src/commands/requirements/default-handlers.js +272 -0
- package/src/commands/requirements/disable.js +97 -0
- package/src/commands/requirements/enable.js +97 -0
- package/src/commands/requirements/utils.js +194 -0
- package/src/commands/requirements-refactored.js +60 -0
- package/src/commands/requirements.js +38 -771
- package/src/commands/specs/disable.js +96 -0
- package/src/commands/specs/enable.js +96 -0
- package/src/trui/TruiInterface.js +5 -11
- package/src/trui/agents/AgentInterface.js +24 -396
- package/src/trui/agents/handlers/CommandHandler.js +93 -0
- package/src/trui/agents/handlers/ContextManager.js +117 -0
- package/src/trui/agents/handlers/DisplayHandler.js +243 -0
- package/src/trui/agents/handlers/HelpHandler.js +51 -0
- package/src/utils/auth.js +13 -111
- package/src/utils/config.js +4 -0
- package/src/utils/interactive/requirements-navigation.js +17 -15
- package/src/utils/interactive-broken.js +2 -2
- package/src/utils/provider-checker/agent-runner.js +15 -1
- package/src/utils/provider-checker/cli-installer.js +149 -7
- package/src/utils/provider-checker/opencode-checker.js +588 -0
- package/src/utils/provider-checker/provider-validator.js +88 -3
- package/src/utils/provider-checker/time-formatter.js +3 -2
- package/src/utils/provider-manager.js +28 -20
- package/src/utils/provider-registry.js +35 -3
- package/src/utils/requirements-navigator/index.js +94 -0
- package/src/utils/requirements-navigator/input-handler.js +217 -0
- package/src/utils/requirements-navigator/section-loader.js +188 -0
- package/src/utils/requirements-navigator/tree-builder.js +105 -0
- package/src/utils/requirements-navigator/tree-renderer.js +50 -0
- package/src/utils/requirements-navigator.js +2 -583
- package/src/utils/trui-clarifications.js +188 -0
- package/src/utils/trui-feedback.js +54 -1
- package/src/utils/trui-kiro-integration.js +398 -0
- package/src/utils/trui-main-handlers.js +194 -0
- package/src/utils/trui-main-menu.js +235 -0
- package/src/utils/trui-nav-agents.js +178 -25
- package/src/utils/trui-nav-requirements.js +203 -27
- package/src/utils/trui-nav-settings.js +114 -1
- package/src/utils/trui-nav-specifications.js +44 -3
- package/src/utils/trui-navigation-backup.js +603 -0
- package/src/utils/trui-navigation.js +70 -228
- package/src/utils/trui-provider-health.js +274 -0
- package/src/utils/trui-provider-manager.js +376 -0
- package/src/utils/trui-quick-menu.js +25 -1
- package/src/utils/trui-req-actions-backup.js +507 -0
- package/src/utils/trui-req-actions.js +148 -216
- package/src/utils/trui-req-editor.js +170 -0
- package/src/utils/trui-req-file-ops.js +278 -0
- package/src/utils/trui-req-tree-old.js +719 -0
- package/src/utils/trui-req-tree.js +348 -627
- package/src/utils/trui-specifications.js +25 -7
- package/src/utils/trui-windsurf.js +231 -10
- package/src/utils/welcome-screen-extracted.js +2 -2
- package/src/utils/welcome-screen.js +2 -2
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto Mode Utilities
|
|
3
|
+
*
|
|
4
|
+
* Shared utility functions for auto mode operations
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const stringWidth = require('string-width');
|
|
9
|
+
const { getRequirementsPath } = require('vibecodingmachine-core');
|
|
10
|
+
const fs = require('fs-extra');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get timestamp for logging
|
|
15
|
+
*/
|
|
16
|
+
function getTimestamp() {
|
|
17
|
+
const now = new Date();
|
|
18
|
+
return now.toLocaleTimeString('en-US', {
|
|
19
|
+
hour: '2-digit',
|
|
20
|
+
minute: '2-digit',
|
|
21
|
+
second: '2-digit'
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get a human-friendly timestamp for log prefixes that includes date, time, and timezone
|
|
27
|
+
* Example: "2025-01-02 3:45 PM MST"
|
|
28
|
+
*/
|
|
29
|
+
function getLogTimestamp(date = new Date()) {
|
|
30
|
+
const datePart = date.toISOString().split('T')[0]; // YYYY-MM-DD
|
|
31
|
+
const timePart = date.toLocaleTimeString('en-US', {
|
|
32
|
+
hour: 'numeric',
|
|
33
|
+
minute: '2-digit',
|
|
34
|
+
timeZoneName: 'short'
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return `${datePart} ${timePart}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Translate workflow stage names
|
|
42
|
+
*/
|
|
43
|
+
function translateStage(stage) {
|
|
44
|
+
const stageMap = {
|
|
45
|
+
'PREPARE': 'workflow.stage.prepare',
|
|
46
|
+
'REPRODUCE': 'workflow.stage.reproduce',
|
|
47
|
+
'ACT': 'workflow.stage.act',
|
|
48
|
+
'CLEAN UP': 'workflow.stage.cleanup',
|
|
49
|
+
'VERIFY': 'workflow.stage.verify',
|
|
50
|
+
'DONE': 'workflow.stage.done'
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return stageMap[stage] || stage;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Strip ANSI escape codes from a string
|
|
58
|
+
*/
|
|
59
|
+
function stripAnsi(str) {
|
|
60
|
+
// eslint-disable-next-line no-control-regex
|
|
61
|
+
return str.replace(/\x1B\[[0-9;]*[mGKH]/g, '');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get visual width of a string accounting for ANSI codes, emojis, and wide characters
|
|
66
|
+
* Uses string-width library for accurate Unicode width calculation
|
|
67
|
+
*/
|
|
68
|
+
function getVisualWidth(str) {
|
|
69
|
+
return stringWidth(str);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Pad string to visual width accounting for emojis, ANSI codes, and wide characters
|
|
74
|
+
*/
|
|
75
|
+
function padToVisualWidth(str, targetWidth) {
|
|
76
|
+
const visualWidth = getVisualWidth(str);
|
|
77
|
+
const paddingNeeded = targetWidth - visualWidth;
|
|
78
|
+
if (paddingNeeded <= 0) {
|
|
79
|
+
return str;
|
|
80
|
+
}
|
|
81
|
+
return str + ' '.repeat(paddingNeeded);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Check if message indicates rate limiting
|
|
86
|
+
*/
|
|
87
|
+
function isRateLimitMessage(text) {
|
|
88
|
+
if (!text) return false;
|
|
89
|
+
const lower = text.toLowerCase();
|
|
90
|
+
return lower.includes('rate limit') ||
|
|
91
|
+
lower.includes('too many requests') ||
|
|
92
|
+
lower.includes('limit reached');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Sleep for specified milliseconds
|
|
97
|
+
*/
|
|
98
|
+
function sleep(ms) {
|
|
99
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Safely log to console with EPIPE protection
|
|
104
|
+
*/
|
|
105
|
+
function safeLog(...args) {
|
|
106
|
+
try {
|
|
107
|
+
// Check if stdout is still writable before attempting to log
|
|
108
|
+
if (process.stdout && !process.stdout.destroyed) {
|
|
109
|
+
console.log(...args);
|
|
110
|
+
}
|
|
111
|
+
} catch (error) {
|
|
112
|
+
// Ignore EPIPE errors - common when piping output
|
|
113
|
+
if (error.code !== 'EPIPE') {
|
|
114
|
+
// For other errors, try writing to stderr
|
|
115
|
+
try {
|
|
116
|
+
if (process.stderr && !process.stderr.destroyed) {
|
|
117
|
+
console.error(...args);
|
|
118
|
+
}
|
|
119
|
+
} catch (stderrError) {
|
|
120
|
+
// If both fail, silently ignore
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Update requirements status in file
|
|
128
|
+
*/
|
|
129
|
+
async function updateRequirementsStatus(repoPath, status) {
|
|
130
|
+
try {
|
|
131
|
+
const reqPath = await getRequirementsPath(repoPath);
|
|
132
|
+
if (!reqPath || !await fs.pathExists(reqPath)) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
let content = await fs.readFile(reqPath, 'utf8');
|
|
137
|
+
|
|
138
|
+
// Find and update status line
|
|
139
|
+
const statusLineRegex = /^## Current Status:.*$/m;
|
|
140
|
+
const newStatusLine = `## Current Status: ${status}`;
|
|
141
|
+
|
|
142
|
+
if (statusLineRegex.test(content)) {
|
|
143
|
+
content = content.replace(statusLineRegex, newStatusLine);
|
|
144
|
+
} else {
|
|
145
|
+
// Add status line after first line if not found
|
|
146
|
+
const lines = content.split('\n');
|
|
147
|
+
lines.splice(1, 0, newStatusLine);
|
|
148
|
+
content = lines.join('\n');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
await fs.writeFile(reqPath, content, 'utf8');
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.error('Error updating requirements status:', error.message);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Get current requirement from REQUIREMENTS file
|
|
159
|
+
*/
|
|
160
|
+
async function getCurrentRequirement(repoPath) {
|
|
161
|
+
try {
|
|
162
|
+
const reqPath = await getRequirementsPath(repoPath);
|
|
163
|
+
if (!reqPath || !await fs.pathExists(reqPath)) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const content = await fs.readFile(reqPath, 'utf8');
|
|
168
|
+
const lines = content.split('\n');
|
|
169
|
+
|
|
170
|
+
// Look for current status section
|
|
171
|
+
let currentSection = null;
|
|
172
|
+
let currentRequirement = null;
|
|
173
|
+
|
|
174
|
+
for (let i = 0; i < lines.length; i++) {
|
|
175
|
+
const line = lines[i].trim();
|
|
176
|
+
|
|
177
|
+
if (line.startsWith('## Current Status:')) {
|
|
178
|
+
currentSection = 'current';
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (line.startsWith('##') && currentSection === 'current') {
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (currentSection === 'current' && line.startsWith('###')) {
|
|
187
|
+
currentRequirement = line.replace(/^###\s*/, '').trim();
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return currentRequirement;
|
|
193
|
+
} catch (error) {
|
|
194
|
+
console.error('Error getting current requirement:', error.message);
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Count total TODO requirements
|
|
201
|
+
*/
|
|
202
|
+
async function countTodoRequirements(repoPath) {
|
|
203
|
+
try {
|
|
204
|
+
const reqPath = await getRequirementsPath(repoPath);
|
|
205
|
+
if (!reqPath || !await fs.pathExists(reqPath)) {
|
|
206
|
+
return 0;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const content = await fs.readFile(reqPath, 'utf8');
|
|
210
|
+
const lines = content.split('\n');
|
|
211
|
+
|
|
212
|
+
let inTodoSection = false;
|
|
213
|
+
let count = 0;
|
|
214
|
+
|
|
215
|
+
for (const line of lines) {
|
|
216
|
+
const trimmed = line.trim();
|
|
217
|
+
|
|
218
|
+
if (trimmed === '## TODO') {
|
|
219
|
+
inTodoSection = true;
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (trimmed.startsWith('##') && inTodoSection) {
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (inTodoSection && trimmed.startsWith('###')) {
|
|
228
|
+
count++;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return count;
|
|
233
|
+
} catch (error) {
|
|
234
|
+
console.error('Error counting TODO requirements:', error.message);
|
|
235
|
+
return 0;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Move requirement from TODO to TO VERIFY BY HUMAN
|
|
241
|
+
*/
|
|
242
|
+
async function moveRequirementToVerify(repoPath, requirementText) {
|
|
243
|
+
try {
|
|
244
|
+
const reqPath = await getRequirementsPath(repoPath);
|
|
245
|
+
if (!reqPath || !await fs.pathExists(reqPath)) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const content = await fs.readFile(reqPath, 'utf8');
|
|
250
|
+
const lines = content.split('\n');
|
|
251
|
+
|
|
252
|
+
// Find the requirement by its title
|
|
253
|
+
const normalizedRequirement = requirementText.trim();
|
|
254
|
+
const snippet = normalizedRequirement.substring(0, 80);
|
|
255
|
+
let requirementStartIndex = -1;
|
|
256
|
+
let requirementEndIndex = -1;
|
|
257
|
+
let inTodoSection = false;
|
|
258
|
+
|
|
259
|
+
for (let i = 0; i < lines.length; i++) {
|
|
260
|
+
const line = lines[i];
|
|
261
|
+
|
|
262
|
+
if (line.trim() === '## TODO') {
|
|
263
|
+
inTodoSection = true;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (line.trim().startsWith('##') && inTodoSection) {
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (inTodoSection && line.trim().startsWith('###')) {
|
|
272
|
+
const title = line.replace(/^###\s*/, '').trim();
|
|
273
|
+
if (title.includes(snippet) || title === normalizedRequirement) {
|
|
274
|
+
requirementStartIndex = i;
|
|
275
|
+
|
|
276
|
+
// Find end of requirement
|
|
277
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
278
|
+
if (lines[j].trim().startsWith('###') || lines[j].trim().startsWith('##')) {
|
|
279
|
+
requirementEndIndex = j;
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (requirementEndIndex === -1) {
|
|
285
|
+
requirementEndIndex = lines.length;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (requirementStartIndex === -1) {
|
|
294
|
+
console.warn('Requirement not found in TODO section:', requirementText);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Extract the requirement block
|
|
299
|
+
const requirementBlock = lines.slice(requirementStartIndex, requirementEndIndex);
|
|
300
|
+
|
|
301
|
+
// Remove from TODO
|
|
302
|
+
lines.splice(requirementStartIndex, requirementEndIndex - requirementStartIndex);
|
|
303
|
+
|
|
304
|
+
// Find TO VERIFY BY HUMAN section and add requirement
|
|
305
|
+
let verifyIndex = -1;
|
|
306
|
+
for (let i = 0; i < lines.length; i++) {
|
|
307
|
+
if (lines[i].trim() === '## TO VERIFY BY HUMAN') {
|
|
308
|
+
verifyIndex = i + 1;
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (verifyIndex === -1) {
|
|
314
|
+
// Create section if it doesn't exist
|
|
315
|
+
lines.push('## TO VERIFY BY HUMAN');
|
|
316
|
+
verifyIndex = lines.length;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Insert requirement
|
|
320
|
+
lines.splice(verifyIndex, 0, ...requirementBlock);
|
|
321
|
+
|
|
322
|
+
// Write back to file
|
|
323
|
+
await fs.writeFile(reqPath, lines.join('\n'), 'utf8');
|
|
324
|
+
|
|
325
|
+
console.log(chalk.green(`Moved requirement to verify: ${requirementText}`));
|
|
326
|
+
} catch (error) {
|
|
327
|
+
console.error('Error moving requirement to verify:', error.message);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Move requirement to recycled section
|
|
333
|
+
*/
|
|
334
|
+
async function moveRequirementToRecycle(repoPath, requirementText) {
|
|
335
|
+
try {
|
|
336
|
+
const reqPath = await getRequirementsPath(repoPath);
|
|
337
|
+
if (!reqPath || !await fs.pathExists(reqPath)) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const content = await fs.readFile(reqPath, 'utf8');
|
|
342
|
+
const lines = content.split('\n');
|
|
343
|
+
|
|
344
|
+
// Find and remove requirement from current section
|
|
345
|
+
const normalizedRequirement = requirementText.trim();
|
|
346
|
+
const snippet = normalizedRequirement.substring(0, 80);
|
|
347
|
+
let requirementStartIndex = -1;
|
|
348
|
+
let requirementEndIndex = -1;
|
|
349
|
+
let inCurrentSection = false;
|
|
350
|
+
|
|
351
|
+
for (let i = 0; i < lines.length; i++) {
|
|
352
|
+
const line = lines[i];
|
|
353
|
+
|
|
354
|
+
if (line.trim().startsWith('## Current Status:')) {
|
|
355
|
+
inCurrentSection = true;
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (line.trim().startsWith('##') && inCurrentSection) {
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (inCurrentSection && line.trim().startsWith('###')) {
|
|
364
|
+
const title = line.replace(/^###\s*/, '').trim();
|
|
365
|
+
if (title.includes(snippet) || title === normalizedRequirement) {
|
|
366
|
+
requirementStartIndex = i;
|
|
367
|
+
|
|
368
|
+
// Find end of requirement
|
|
369
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
370
|
+
if (lines[j].trim().startsWith('###') || lines[j].trim().startsWith('##')) {
|
|
371
|
+
requirementEndIndex = j;
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (requirementEndIndex === -1) {
|
|
377
|
+
requirementEndIndex = lines.length;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (requirementStartIndex === -1) {
|
|
386
|
+
console.warn('Requirement not found in current section:', requirementText);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Extract the requirement block
|
|
391
|
+
const requirementBlock = lines.slice(requirementStartIndex, requirementEndIndex);
|
|
392
|
+
|
|
393
|
+
// Remove from current section
|
|
394
|
+
lines.splice(requirementStartIndex, requirementEndIndex - requirementStartIndex);
|
|
395
|
+
|
|
396
|
+
// Find RECYCLED section and add requirement
|
|
397
|
+
let recycleIndex = -1;
|
|
398
|
+
for (let i = 0; i < lines.length; i++) {
|
|
399
|
+
if (lines[i].trim() === '## RECYCLED') {
|
|
400
|
+
recycleIndex = i + 1;
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (recycleIndex === -1) {
|
|
406
|
+
// Create section if it doesn't exist
|
|
407
|
+
lines.push('## RECYCLED');
|
|
408
|
+
recycleIndex = lines.length;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Add timestamp and requirement
|
|
412
|
+
const timestamp = new Date().toISOString();
|
|
413
|
+
lines.splice(recycleIndex, 0, `### ${requirementText}`, `*Recycled: ${timestamp}*`);
|
|
414
|
+
|
|
415
|
+
// Write back to file
|
|
416
|
+
await fs.writeFile(reqPath, lines.join('\n'), 'utf8');
|
|
417
|
+
|
|
418
|
+
console.log(chalk.yellow(`Recycled requirement: ${requirementText}`));
|
|
419
|
+
} catch (error) {
|
|
420
|
+
console.error('Error recycling requirement:', error.message);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
module.exports = {
|
|
425
|
+
getTimestamp,
|
|
426
|
+
getLogTimestamp,
|
|
427
|
+
translateStage,
|
|
428
|
+
stripAnsi,
|
|
429
|
+
getVisualWidth,
|
|
430
|
+
padToVisualWidth,
|
|
431
|
+
isRateLimitMessage,
|
|
432
|
+
sleep,
|
|
433
|
+
safeLog,
|
|
434
|
+
updateRequirementsStatus,
|
|
435
|
+
getCurrentRequirement,
|
|
436
|
+
countTodoRequirements,
|
|
437
|
+
moveRequirementToVerify,
|
|
438
|
+
moveRequirementToRecycle
|
|
439
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File operations for auto-direct mode
|
|
3
|
+
* Handles file searching, parsing LLM responses, and applying changes
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs-extra');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const chalk = require('chalk');
|
|
9
|
+
const { t } = require('vibecodingmachine-core');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Parse CREATE and FILE/SEARCH/REPLACE blocks from LLM response
|
|
13
|
+
*/
|
|
14
|
+
function parseSearchReplaceBlocks(response) {
|
|
15
|
+
const changes = [];
|
|
16
|
+
|
|
17
|
+
// Match CREATE: path CONTENT: ``` content ``` format for new files
|
|
18
|
+
const createRegex = /CREATE:\s*(.+?)\nCONTENT:\s*```(?:[a-z]*)\n([\s\S]+?)```/g;
|
|
19
|
+
|
|
20
|
+
let match;
|
|
21
|
+
while ((match = createRegex.exec(response)) !== null) {
|
|
22
|
+
let filePath = match[1].trim();
|
|
23
|
+
const content = match[2];
|
|
24
|
+
|
|
25
|
+
// Clean up file path - remove "---" prefix if present
|
|
26
|
+
filePath = filePath.replace(/^---\s*/, '').trim();
|
|
27
|
+
|
|
28
|
+
changes.push({
|
|
29
|
+
type: 'create',
|
|
30
|
+
file: filePath,
|
|
31
|
+
content: content
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Match FILE: path SEARCH: ``` old ``` REPLACE: ``` new ``` format for modifications
|
|
36
|
+
const blockRegex = /FILE:\s*(.+?)\nSEARCH:\s*```(?:[a-z]*)\n([\s\S]+?)```\s*REPLACE:\s*```(?:[a-z]*)\n([\s\S]+?)```/g;
|
|
37
|
+
|
|
38
|
+
while ((match = blockRegex.exec(response)) !== null) {
|
|
39
|
+
let filePath = match[1].trim();
|
|
40
|
+
const searchText = match[2];
|
|
41
|
+
const replaceText = match[3];
|
|
42
|
+
|
|
43
|
+
// Clean up file path - remove "---" prefix if present
|
|
44
|
+
filePath = filePath.replace(/^---\s*/, '').trim();
|
|
45
|
+
|
|
46
|
+
changes.push({
|
|
47
|
+
type: 'modify',
|
|
48
|
+
file: filePath,
|
|
49
|
+
search: searchText,
|
|
50
|
+
replace: replaceText
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return changes;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Normalize whitespace for comparison
|
|
59
|
+
*/
|
|
60
|
+
function normalizeWhitespace(str) {
|
|
61
|
+
return str.replace(/\s+/g, ' ').trim();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Extract key identifiers from code (variable names, function names, strings)
|
|
66
|
+
*/
|
|
67
|
+
function extractIdentifiers(code) {
|
|
68
|
+
const identifiers = new Set();
|
|
69
|
+
|
|
70
|
+
// Extract quoted strings
|
|
71
|
+
const stringMatches = code.match(/'([^']+)'|"([^"]+)"/g);
|
|
72
|
+
if (stringMatches) {
|
|
73
|
+
stringMatches.forEach(match => {
|
|
74
|
+
const str = match.slice(1, -1);
|
|
75
|
+
if (str.length > 3) {
|
|
76
|
+
identifiers.add(str);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Extract variable/function names
|
|
82
|
+
const nameMatches = code.match(/\b([a-zA-Z_$][a-zA-Z0-9_$]*)\s*[:=]/g);
|
|
83
|
+
if (nameMatches) {
|
|
84
|
+
nameMatches.forEach(match => {
|
|
85
|
+
const name = match.replace(/[:=].*$/, '').trim();
|
|
86
|
+
if (name.length > 2) {
|
|
87
|
+
identifiers.add(name);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return Array.from(identifiers);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Extract structural pattern from code
|
|
97
|
+
*/
|
|
98
|
+
function extractPattern(code) {
|
|
99
|
+
let pattern = code.replace(/'[^']+'|"[^"]+"/g, '"..."');
|
|
100
|
+
pattern = pattern.replace(/\b\d+\b/g, 'N');
|
|
101
|
+
pattern = normalizeWhitespace(pattern);
|
|
102
|
+
return pattern;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = {
|
|
106
|
+
parseSearchReplaceBlocks,
|
|
107
|
+
normalizeWhitespace,
|
|
108
|
+
extractIdentifiers,
|
|
109
|
+
extractPattern
|
|
110
|
+
};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Extracted from auto-direct.js to reduce file size
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const { getAutoConfig } = require('
|
|
6
|
+
const { getAutoConfig } = require('../../../utils/config');
|
|
7
7
|
const { getProviderPreferences, getProviderDefinition, saveProviderPreferences } = require('../../utils/provider-registry');
|
|
8
8
|
const ProviderManager = require('vibecodingmachine-core/src/ide-integration/provider-manager.cjs');
|
|
9
9
|
const { checkAntigravityRateLimit, handleAntigravityRateLimit } = require('../../utils/antigravity-js-handler');
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Extracted from auto-direct.js to reduce file size
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const { getAutoConfig } = require('
|
|
6
|
+
const { getAutoConfig } = require('../../../utils/config');
|
|
7
7
|
const { getProviderPreferences, getProviderDefinition, saveProviderPreferences } = require('../../utils/provider-registry');
|
|
8
8
|
const ProviderManager = require('vibecodingmachine-core/src/ide-integration/provider-manager.cjs');
|
|
9
9
|
const { checkAntigravityRateLimit, handleAntigravityRateLimit } = require('../../utils/antigravity-js-handler');
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const chalk = require('chalk');
|
|
7
|
-
const { getAutoConfig, getStages, DEFAULT_STAGES } = require('
|
|
7
|
+
const { getAutoConfig, getStages, DEFAULT_STAGES } = require('../../../utils/config');
|
|
8
8
|
|
|
9
9
|
// Configured stages (will be loaded from config)
|
|
10
10
|
let configuredStages = DEFAULT_STAGES;
|
|
@@ -1,32 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Utility functions for auto-direct
|
|
3
|
-
* Extracted from auto-direct.js to reduce file size
|
|
2
|
+
* Utility functions for auto-direct mode
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
5
|
const chalk = require('chalk');
|
|
7
6
|
const stringWidth = require('string-width');
|
|
7
|
+
const { t } = require('vibecodingmachine-core');
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Get timestamp
|
|
10
|
+
* Get timestamp in MST
|
|
11
11
|
*/
|
|
12
12
|
function getTimestamp() {
|
|
13
13
|
const now = new Date();
|
|
14
14
|
return now.toLocaleTimeString('en-US', {
|
|
15
15
|
hour: '2-digit',
|
|
16
16
|
minute: '2-digit',
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
hour12: false,
|
|
18
|
+
timeZone: 'America/Denver'
|
|
19
|
+
}) + ' MST';
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
|
-
* Get a human-friendly timestamp for log prefixes
|
|
23
|
-
* Example: "2025-01-02 3:45 PM MST"
|
|
23
|
+
* Get a human-friendly timestamp for log prefixes
|
|
24
24
|
*/
|
|
25
25
|
function getLogTimestamp(date = new Date()) {
|
|
26
|
-
const datePart = date.toISOString().split('T')[0];
|
|
26
|
+
const datePart = date.toISOString().split('T')[0];
|
|
27
27
|
const timePart = date.toLocaleTimeString('en-US', {
|
|
28
28
|
hour: 'numeric',
|
|
29
29
|
minute: '2-digit',
|
|
30
|
+
hour12: true,
|
|
31
|
+
timeZone: 'America/Denver',
|
|
30
32
|
timeZoneName: 'short'
|
|
31
33
|
});
|
|
32
34
|
return `${datePart} ${timePart}`;
|
|
@@ -39,32 +41,34 @@ function translateStage(stage) {
|
|
|
39
41
|
const stageMap = {
|
|
40
42
|
'PREPARE': 'workflow.stage.prepare',
|
|
41
43
|
'REPRODUCE': 'workflow.stage.reproduce',
|
|
44
|
+
'CREATE UNIT TEST': 'workflow.stage.create.unit.test',
|
|
42
45
|
'ACT': 'workflow.stage.act',
|
|
43
|
-
'CLEAN UP': 'workflow.stage.
|
|
46
|
+
'CLEAN UP': 'workflow.stage.clean.up',
|
|
44
47
|
'VERIFY': 'workflow.stage.verify',
|
|
48
|
+
'RUN UNIT TESTS': 'workflow.stage.run.unit.tests',
|
|
45
49
|
'DONE': 'workflow.stage.done'
|
|
46
50
|
};
|
|
47
|
-
|
|
51
|
+
|
|
52
|
+
const key = stageMap[stage];
|
|
53
|
+
return key ? t(key) : stage;
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
/**
|
|
51
57
|
* Strip ANSI escape codes from a string
|
|
52
58
|
*/
|
|
53
59
|
function stripAnsi(str) {
|
|
54
|
-
// eslint-disable-next-line no-control-regex
|
|
55
60
|
return str.replace(/\x1B\[[0-9;]*[mGKH]/g, '');
|
|
56
61
|
}
|
|
57
62
|
|
|
58
63
|
/**
|
|
59
|
-
* Get visual width
|
|
60
|
-
* Uses string-width library for accurate Unicode width calculation
|
|
64
|
+
* Get visual width accounting for ANSI, emojis, wide characters
|
|
61
65
|
*/
|
|
62
66
|
function getVisualWidth(str) {
|
|
63
67
|
return stringWidth(str);
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
/**
|
|
67
|
-
* Pad string to visual width
|
|
71
|
+
* Pad string to visual width
|
|
68
72
|
*/
|
|
69
73
|
function padToVisualWidth(str, targetWidth) {
|
|
70
74
|
const visualWidth = getVisualWidth(str);
|
|
@@ -82,13 +86,15 @@ function isRateLimitMessage(text) {
|
|
|
82
86
|
if (!text) return false;
|
|
83
87
|
const lower = text.toLowerCase();
|
|
84
88
|
return lower.includes('rate limit') ||
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
89
|
+
lower.includes('too many requests') ||
|
|
90
|
+
lower.includes('429') ||
|
|
91
|
+
lower.includes('weekly limit') ||
|
|
92
|
+
lower.includes('daily limit') ||
|
|
93
|
+
lower.includes('limit reached');
|
|
88
94
|
}
|
|
89
95
|
|
|
90
96
|
/**
|
|
91
|
-
* Sleep
|
|
97
|
+
* Sleep helper
|
|
92
98
|
*/
|
|
93
99
|
function sleep(ms) {
|
|
94
100
|
return new Promise(resolve => setTimeout(resolve, ms));
|