vibecodingmachine-cli 2026.2.26-1739 ā 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 +5 -1
- 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,507 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TRUI Requirement Actions
|
|
3
|
+
*
|
|
4
|
+
* Provides an action menu for a selected requirement and the addRequirementFlow.
|
|
5
|
+
* Uses inquirer for prompted input (rename, add) and showQuickMenu for action selection.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const chalk = require('chalk');
|
|
9
|
+
const inquirer = require('inquirer');
|
|
10
|
+
const fs = require('fs-extra');
|
|
11
|
+
const readline = require('readline');
|
|
12
|
+
const { hasClarificationResponse, editClarificationResponse } = require('./trui-clarifications');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Single-keypress confirm: Y/Enter/- = yes, N/Esc = no. No Enter required.
|
|
16
|
+
* Shows: `message (Y/n/-)` and echoes the choice.
|
|
17
|
+
*/
|
|
18
|
+
async function _confirmKeypress(message) {
|
|
19
|
+
return new Promise(resolve => {
|
|
20
|
+
process.stdout.write(message + ' (Y/n/-)\n\n');
|
|
21
|
+
readline.emitKeypressEvents(process.stdin);
|
|
22
|
+
if (process.stdin.isTTY && process.stdin.setRawMode) {
|
|
23
|
+
process.stdin.setRawMode(true);
|
|
24
|
+
}
|
|
25
|
+
process.stdin.resume();
|
|
26
|
+
|
|
27
|
+
const cleanup = () => {
|
|
28
|
+
process.stdin.removeListener('keypress', onKey);
|
|
29
|
+
if (process.stdin.isTTY && process.stdin.setRawMode) {
|
|
30
|
+
process.stdin.setRawMode(false);
|
|
31
|
+
}
|
|
32
|
+
process.stdin.pause();
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const onKey = (str, key) => {
|
|
36
|
+
if (!key) return;
|
|
37
|
+
if (key.ctrl && key.name === 'c') { cleanup(); process.exit(0); }
|
|
38
|
+
const ch = (str || '').toLowerCase();
|
|
39
|
+
if (ch === 'y' || key.name === 'return' || ch === '-') {
|
|
40
|
+
cleanup();
|
|
41
|
+
process.stdout.write(chalk.green('y') + '\n');
|
|
42
|
+
resolve(true);
|
|
43
|
+
} else if (ch === 'n' || key.name === 'escape') {
|
|
44
|
+
cleanup();
|
|
45
|
+
process.stdout.write(chalk.gray('n') + '\n');
|
|
46
|
+
resolve(false);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
process.stdin.on('keypress', onKey);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Show action menu for a requirement item.
|
|
56
|
+
* @param {{title: string, status: string}} req
|
|
57
|
+
* @param {string} sectionKey - 'todo' | 'verify' | 'verified' | 'recycled'
|
|
58
|
+
* @param {Function} onChanged - called with true when data changes (tree should reload)
|
|
59
|
+
* @returns {Promise<boolean>} true if data changed
|
|
60
|
+
*/
|
|
61
|
+
async function showRequirementActions(req, sectionKey, onChanged) {
|
|
62
|
+
const { showQuickMenu } = require('./trui-quick-menu');
|
|
63
|
+
|
|
64
|
+
const {
|
|
65
|
+
promoteToVerified,
|
|
66
|
+
demoteFromVerifiedToTodo,
|
|
67
|
+
promoteTodoToVerify,
|
|
68
|
+
demoteVerifyToTodo,
|
|
69
|
+
getOrCreateRequirementsFilePath,
|
|
70
|
+
getRequirementsPath,
|
|
71
|
+
} = require('vibecodingmachine-core');
|
|
72
|
+
const { getRepoPath, getEffectiveRepoPath } = require('./config');
|
|
73
|
+
|
|
74
|
+
// Build action items based on current section
|
|
75
|
+
const actions = [];
|
|
76
|
+
|
|
77
|
+
if (sectionKey === 'todo') {
|
|
78
|
+
actions.push({ type: 'action', name: 'Promote to TO VERIFY', value: 'promote' });
|
|
79
|
+
}
|
|
80
|
+
if (sectionKey === 'verify') {
|
|
81
|
+
actions.push({ type: 'action', name: 'Promote to VERIFIED', value: 'verify-promote' });
|
|
82
|
+
actions.push({ type: 'action', name: 'Demote to TODO', value: 'demote' });
|
|
83
|
+
}
|
|
84
|
+
if (sectionKey === 'verified') {
|
|
85
|
+
actions.push({ type: 'action', name: 'Demote to TODO', value: 'demote-verified' });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Add clarification editing if requirement has clarification response
|
|
89
|
+
if (hasClarificationResponse(req)) {
|
|
90
|
+
actions.push({ type: 'action', name: 'š¬ Edit Clarification Response', value: 'edit-clarification' });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Add bulk operations
|
|
94
|
+
actions.push({ type: 'separator', name: chalk.gray(' āā Bulk Operations āā'), value: 'separator' });
|
|
95
|
+
actions.push({ type: 'action', name: '[š Select Multiple Items]', value: 'bulk-select' });
|
|
96
|
+
actions.push({ type: 'action', name: '[š Move Multiple Items]', value: 'bulk-move' });
|
|
97
|
+
|
|
98
|
+
actions.push({ type: 'action', name: 'Move Item Up in section', value: 'move-up' });
|
|
99
|
+
actions.push({ type: 'action', name: 'Move Item Down in section', value: 'move-down' });
|
|
100
|
+
actions.push({ type: 'action', name: 'Rename', value: 'rename' });
|
|
101
|
+
actions.push({ type: 'action', name: chalk.red('Delete'), value: 'delete' });
|
|
102
|
+
|
|
103
|
+
console.log(chalk.bold.cyan(`\nš ${req.title} [${sectionKey}]\n`));
|
|
104
|
+
|
|
105
|
+
const result = await showQuickMenu(actions, 0);
|
|
106
|
+
if (result.value === '__cancel__' || result.value === 'exit') return false;
|
|
107
|
+
|
|
108
|
+
let reqPath;
|
|
109
|
+
try {
|
|
110
|
+
const repoPath = await getRepoPath();
|
|
111
|
+
const p = await getRequirementsPath(repoPath);
|
|
112
|
+
// Use effectiveRepoPath (git root) for creation so .vibecodingmachine is never
|
|
113
|
+
// created inside a sub-package directory when repoPath is null.
|
|
114
|
+
reqPath = (p && await fs.pathExists(p)) ? p : await getOrCreateRequirementsFilePath(await getEffectiveRepoPath());
|
|
115
|
+
} catch (err) {
|
|
116
|
+
console.log(chalk.red('Cannot find requirements file: ' + err.message));
|
|
117
|
+
await _pause();
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
if (result.value === 'promote') {
|
|
123
|
+
console.log(chalk.gray('\nEquivalent: vcm req:promote "' + req.title + '"'));
|
|
124
|
+
await promoteTodoToVerify(reqPath, req.title);
|
|
125
|
+
console.log(chalk.green('ā Moved to TO VERIFY'));
|
|
126
|
+
await _pause();
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (result.value === 'verify-promote') {
|
|
131
|
+
console.log(chalk.gray('\nEquivalent: vcm req:verify "' + req.title + '"'));
|
|
132
|
+
await promoteToVerified(reqPath, req.title);
|
|
133
|
+
console.log(chalk.green('ā Moved to VERIFIED'));
|
|
134
|
+
await _pause();
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (result.value === 'demote') {
|
|
139
|
+
console.log(chalk.gray('\nEquivalent: vcm req:demote "' + req.title + '"'));
|
|
140
|
+
await demoteVerifyToTodo(reqPath, req.title);
|
|
141
|
+
console.log(chalk.green('ā Moved back to TODO'));
|
|
142
|
+
await _pause();
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (result.value === 'demote-verified') {
|
|
147
|
+
console.log(chalk.gray('\nEquivalent: vcm req:demote "' + req.title + '"'));
|
|
148
|
+
await demoteFromVerifiedToTodo(reqPath, req.title);
|
|
149
|
+
console.log(chalk.green('ā Moved back to TODO'));
|
|
150
|
+
await _pause();
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (result.value === 'edit-clarification') {
|
|
155
|
+
console.log(chalk.gray('\nOpening clarification response editor...'));
|
|
156
|
+
|
|
157
|
+
// Create save callback for the requirement
|
|
158
|
+
const saveCallback = async (updatedReq) => {
|
|
159
|
+
// Update the requirement in the file
|
|
160
|
+
const content = await fs.readFile(reqPath, 'utf8');
|
|
161
|
+
const lines = content.split('\n');
|
|
162
|
+
|
|
163
|
+
// Find and replace the requirement section
|
|
164
|
+
let inRequirement = false;
|
|
165
|
+
let reqStart = -1;
|
|
166
|
+
let reqEnd = -1;
|
|
167
|
+
|
|
168
|
+
for (let i = 0; i < lines.length; i++) {
|
|
169
|
+
const line = lines[i];
|
|
170
|
+
if (line.includes(`## ${req.title}`) || line.includes(`### ${req.title}`)) {
|
|
171
|
+
inRequirement = true;
|
|
172
|
+
reqStart = i;
|
|
173
|
+
} else if (inRequirement && (line.startsWith('## ') || line.startsWith('### ')) && !line.includes(req.title)) {
|
|
174
|
+
reqEnd = i;
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (reqEnd === -1) reqEnd = lines.length;
|
|
180
|
+
|
|
181
|
+
// Replace the requirement content
|
|
182
|
+
const newContent = [
|
|
183
|
+
...lines.slice(0, reqStart),
|
|
184
|
+
...updatedReq.content.split('\n'),
|
|
185
|
+
...lines.slice(reqEnd)
|
|
186
|
+
].join('\n');
|
|
187
|
+
|
|
188
|
+
await fs.writeFile(reqPath, newContent, 'utf8');
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
await editClarificationResponse(req, saveCallback);
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (result.value === 'bulk-select') {
|
|
196
|
+
await showBulkSelection(sectionKey, navigation);
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (result.value === 'bulk-move') {
|
|
201
|
+
await showBulkMove(sectionKey, navigation);
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (result.value === 'move-up' || result.value === 'move-down') {
|
|
206
|
+
const direction = result.value === 'move-up' ? 'up' : 'down';
|
|
207
|
+
const moved = await _moveRequirement(reqPath, req.title, sectionKey, direction);
|
|
208
|
+
if (moved) {
|
|
209
|
+
console.log(chalk.green(`ā Moved ${direction}`));
|
|
210
|
+
console.log(chalk.gray(`Equivalent: vcm req:move-${direction} "${req.title}"`));
|
|
211
|
+
} else {
|
|
212
|
+
console.log(chalk.yellow('Already at the ' + (direction === 'up' ? 'top' : 'bottom')));
|
|
213
|
+
}
|
|
214
|
+
await _pause();
|
|
215
|
+
return moved;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (result.value === 'rename') {
|
|
219
|
+
return await _renameRequirement(reqPath, req.title, sectionKey);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (result.value === 'delete') {
|
|
223
|
+
return await _deleteRequirement(reqPath, req.title);
|
|
224
|
+
}
|
|
225
|
+
} catch (err) {
|
|
226
|
+
console.log(chalk.red('Error: ' + err.message));
|
|
227
|
+
await _pause();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Move a requirement up or down within its section
|
|
235
|
+
*/
|
|
236
|
+
async function _moveRequirement(reqPath, title, sectionKey, direction) {
|
|
237
|
+
const content = await fs.readFile(reqPath, 'utf8');
|
|
238
|
+
const lines = content.split('\n');
|
|
239
|
+
|
|
240
|
+
// Find section boundaries
|
|
241
|
+
const sectionHeadings = {
|
|
242
|
+
todo: /requirements not yet completed/i,
|
|
243
|
+
verify: /to verify|verified by ai/i,
|
|
244
|
+
verified: /verified\b/i,
|
|
245
|
+
recycled: /recycled/i,
|
|
246
|
+
};
|
|
247
|
+
const pattern = sectionHeadings[sectionKey];
|
|
248
|
+
|
|
249
|
+
let sectionStart = -1;
|
|
250
|
+
let sectionEnd = lines.length;
|
|
251
|
+
|
|
252
|
+
for (let i = 0; i < lines.length; i++) {
|
|
253
|
+
const line = lines[i].trim();
|
|
254
|
+
if (sectionStart === -1 && line.startsWith('##') && pattern && pattern.test(line)) {
|
|
255
|
+
sectionStart = i;
|
|
256
|
+
} else if (sectionStart !== -1 && line.startsWith('## ') && i > sectionStart) {
|
|
257
|
+
sectionEnd = i;
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (sectionStart === -1) return false;
|
|
263
|
+
|
|
264
|
+
// Find all requirement blocks (### headers) within the section
|
|
265
|
+
const reqBlocks = [];
|
|
266
|
+
let currentBlock = null;
|
|
267
|
+
for (let i = sectionStart + 1; i < sectionEnd; i++) {
|
|
268
|
+
const line = lines[i];
|
|
269
|
+
if (line.trim().startsWith('### ')) {
|
|
270
|
+
if (currentBlock) { currentBlock.end = i; reqBlocks.push(currentBlock); }
|
|
271
|
+
currentBlock = { title: line.trim().replace(/^###\s*/, ''), start: i, end: sectionEnd };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (currentBlock) { currentBlock.end = sectionEnd; reqBlocks.push(currentBlock); }
|
|
275
|
+
|
|
276
|
+
const idx = reqBlocks.findIndex(b => b.title === title);
|
|
277
|
+
if (idx === -1) return false;
|
|
278
|
+
if (direction === 'up' && idx === 0) return false;
|
|
279
|
+
if (direction === 'down' && idx === reqBlocks.length - 1) return false;
|
|
280
|
+
|
|
281
|
+
const swapIdx = direction === 'up' ? idx - 1 : idx + 1;
|
|
282
|
+
const blockA = reqBlocks[idx];
|
|
283
|
+
const blockB = reqBlocks[swapIdx];
|
|
284
|
+
|
|
285
|
+
// Extract block content
|
|
286
|
+
const [first, second] = blockA.start < blockB.start ? [blockA, blockB] : [blockB, blockA];
|
|
287
|
+
const firstContent = lines.slice(first.start, first.end);
|
|
288
|
+
const secondContent = lines.slice(second.start, second.end);
|
|
289
|
+
|
|
290
|
+
// Swap
|
|
291
|
+
const newLines = [
|
|
292
|
+
...lines.slice(0, first.start),
|
|
293
|
+
...secondContent,
|
|
294
|
+
...firstContent,
|
|
295
|
+
...lines.slice(second.end),
|
|
296
|
+
];
|
|
297
|
+
|
|
298
|
+
await fs.writeFile(reqPath, newLines.join('\n'), 'utf8');
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Rename a requirement
|
|
304
|
+
*/
|
|
305
|
+
async function _renameRequirement(reqPath, oldTitle, sectionKey) {
|
|
306
|
+
const { newTitle } = await inquirer.prompt([{
|
|
307
|
+
type: 'input',
|
|
308
|
+
name: 'newTitle',
|
|
309
|
+
message: chalk.cyan('New title:'),
|
|
310
|
+
default: oldTitle,
|
|
311
|
+
validate: v => v.trim().length > 0 || 'Title required',
|
|
312
|
+
}]);
|
|
313
|
+
|
|
314
|
+
if (newTitle.trim() === oldTitle) return false;
|
|
315
|
+
|
|
316
|
+
const content = await fs.readFile(reqPath, 'utf8');
|
|
317
|
+
const escaped = oldTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
318
|
+
const updated = content.replace(new RegExp(`^(###\\s*)${escaped}\\s*$`, 'm'), `$1${newTitle.trim()}`);
|
|
319
|
+
|
|
320
|
+
if (updated === content) {
|
|
321
|
+
console.log(chalk.yellow('Could not find requirement to rename'));
|
|
322
|
+
await _pause();
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
await fs.writeFile(reqPath, updated, 'utf8');
|
|
327
|
+
console.log(chalk.green(`ā Renamed to "${newTitle.trim()}"`));
|
|
328
|
+
console.log(chalk.gray(`Equivalent: vcm req:rename "${oldTitle}" "${newTitle.trim()}"`));
|
|
329
|
+
await _pause();
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Delete a requirement after confirmation
|
|
335
|
+
*/
|
|
336
|
+
async function _deleteRequirement(reqPath, title) {
|
|
337
|
+
const confirmed = await _confirmKeypress(chalk.red(`Delete "${title}"?`));
|
|
338
|
+
if (!confirmed) return false;
|
|
339
|
+
|
|
340
|
+
const content = await fs.readFile(reqPath, 'utf8');
|
|
341
|
+
const lines = content.split('\n');
|
|
342
|
+
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
343
|
+
const startRe = new RegExp(`^###\\s*${escaped}\\s*$`);
|
|
344
|
+
|
|
345
|
+
let startIdx = -1;
|
|
346
|
+
for (let i = 0; i < lines.length; i++) {
|
|
347
|
+
if (startRe.test(lines[i].trim())) { startIdx = i; break; }
|
|
348
|
+
}
|
|
349
|
+
if (startIdx === -1) {
|
|
350
|
+
console.log(chalk.yellow('Could not find requirement to delete'));
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Find end of this requirement block
|
|
355
|
+
let endIdx = startIdx + 1;
|
|
356
|
+
while (endIdx < lines.length) {
|
|
357
|
+
const line = lines[endIdx].trim();
|
|
358
|
+
if (line.startsWith('### ') || (line.startsWith('## ') && !line.startsWith('### '))) break;
|
|
359
|
+
endIdx++;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
lines.splice(startIdx, endIdx - startIdx);
|
|
363
|
+
await fs.writeFile(reqPath, lines.join('\n'), 'utf8');
|
|
364
|
+
console.log(chalk.green(`ā Deleted "${title}"`));
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Prompt for a new requirement and append to the TODO section
|
|
370
|
+
*/
|
|
371
|
+
async function addRequirementFlow() {
|
|
372
|
+
console.log(chalk.bold.cyan('\nš Add New Requirement\n'));
|
|
373
|
+
console.log(chalk.gray('Equivalent: vcm req:add "<title>"\n'));
|
|
374
|
+
|
|
375
|
+
const { createNewRequirement } = require('./trui-req-editor');
|
|
376
|
+
|
|
377
|
+
try {
|
|
378
|
+
const requirement = await createNewRequirement();
|
|
379
|
+
|
|
380
|
+
const { getOrCreateRequirementsFilePath, getRequirementsPath } = require('vibecodingmachine-core');
|
|
381
|
+
const { getRepoPath: _getRepoPath, getEffectiveRepoPath: _getEffectiveRepoPath } = require('./config');
|
|
382
|
+
const _repoPath = await _getRepoPath();
|
|
383
|
+
const _p = await getRequirementsPath(_repoPath);
|
|
384
|
+
// Use effectiveRepoPath (git root) for creation to avoid nested .vibecodingmachine dirs.
|
|
385
|
+
const reqPath = (_p && await fs.pathExists(_p)) ? _p : await getOrCreateRequirementsFilePath(await _getEffectiveRepoPath());
|
|
386
|
+
const content = await fs.readFile(reqPath, 'utf8');
|
|
387
|
+
const lines = content.split('\n');
|
|
388
|
+
|
|
389
|
+
let insertIndex = -1;
|
|
390
|
+
for (let i = 0; i < lines.length; i++) {
|
|
391
|
+
if (lines[i].trim().match(/^##\s+.*(?:Requirements not yet completed|TODO)/i)) {
|
|
392
|
+
insertIndex = i + 1;
|
|
393
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
394
|
+
const inner = lines[j].trim();
|
|
395
|
+
if (inner.startsWith('## ') && !inner.startsWith('### ')) { insertIndex = j; break; }
|
|
396
|
+
if (inner.startsWith('### ') || inner.startsWith('- ')) { insertIndex = j; break; }
|
|
397
|
+
}
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const block = requirement.description
|
|
403
|
+
? `### ${requirement.title.trim()}\n\n${requirement.description.trim()}\n`
|
|
404
|
+
: `### ${requirement.title.trim()}\n`;
|
|
405
|
+
|
|
406
|
+
if (insertIndex >= 0) {
|
|
407
|
+
lines.splice(insertIndex, 0, block);
|
|
408
|
+
} else {
|
|
409
|
+
lines.push('', '## Requirements not yet completed', '', block);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
await fs.writeFile(reqPath, lines.join('\n'), 'utf8');
|
|
413
|
+
console.log(chalk.green(`\nā Added "${title.trim()}"`));
|
|
414
|
+
} catch (err) {
|
|
415
|
+
console.log(chalk.red('Error: ' + err.message));
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
await _pause();
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
async function _pause() {
|
|
422
|
+
await inquirer.prompt([{ type: 'input', name: 'c', message: chalk.gray('Press Enter to continue...') }]);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Handle requirement selection from the tree navigator
|
|
427
|
+
* @param {number} index - Index of the requirement in the section
|
|
428
|
+
* @param {Array} requirements - Array of requirements in the section
|
|
429
|
+
* @param {Object} navigation - Navigation object
|
|
430
|
+
*/
|
|
431
|
+
async function handleRequirementSelection(index, requirements, navigation) {
|
|
432
|
+
if (index < 0 || index >= requirements.length) {
|
|
433
|
+
console.log(chalk.red('Invalid requirement index'));
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const req = requirements[index];
|
|
438
|
+
const sectionKey = navigation.currentSection || 'todo';
|
|
439
|
+
|
|
440
|
+
const changed = await showRequirementActions(req, sectionKey, () => {
|
|
441
|
+
// Refresh callback - the tree will reload
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
return changed;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
module.exports = {
|
|
448
|
+
showRequirementActions,
|
|
449
|
+
addRequirementFlow,
|
|
450
|
+
_moveRequirement,
|
|
451
|
+
_deleteRequirement,
|
|
452
|
+
handleRequirementSelection,
|
|
453
|
+
showBulkSelection,
|
|
454
|
+
showBulkMove
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Show bulk selection interface
|
|
459
|
+
*/
|
|
460
|
+
async function showBulkSelection(sectionKey, navigation) {
|
|
461
|
+
console.clear();
|
|
462
|
+
console.log(chalk.bold.cyan('š Bulk Selection\n'));
|
|
463
|
+
console.log(chalk.gray('Select multiple requirements for bulk operations\n'));
|
|
464
|
+
|
|
465
|
+
// Load requirements for the section
|
|
466
|
+
const data = await navigation.resolver.resolve('list requirements');
|
|
467
|
+
const result = await data.command.execute();
|
|
468
|
+
const requirements = result.data.sections[sectionKey] || [];
|
|
469
|
+
|
|
470
|
+
if (requirements.length === 0) {
|
|
471
|
+
console.log(chalk.yellow('No requirements in this section'));
|
|
472
|
+
await _pause();
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const { selectedItems } = await inquirer.prompt([
|
|
477
|
+
{
|
|
478
|
+
type: 'checkbox',
|
|
479
|
+
name: 'selectedItems',
|
|
480
|
+
message: 'Select requirements:',
|
|
481
|
+
choices: requirements.map((req, index) => ({
|
|
482
|
+
name: req.title || req.name || 'Untitled',
|
|
483
|
+
value: index
|
|
484
|
+
}))
|
|
485
|
+
}
|
|
486
|
+
]);
|
|
487
|
+
|
|
488
|
+
if (selectedItems.length === 0) {
|
|
489
|
+
console.log(chalk.yellow('No items selected'));
|
|
490
|
+
await _pause();
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
console.log(chalk.green(`\nSelected ${selectedItems.length} item(s)`));
|
|
495
|
+
await _pause();
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Show bulk move interface
|
|
500
|
+
*/
|
|
501
|
+
async function showBulkMove(sectionKey, navigation) {
|
|
502
|
+
console.clear();
|
|
503
|
+
console.log(chalk.bold.cyan('š Bulk Move\n'));
|
|
504
|
+
console.log(chalk.gray('Move multiple requirements to another section\n'));
|
|
505
|
+
|
|
506
|
+
await _pause();
|
|
507
|
+
}
|