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
|
@@ -1,70 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* TRUI Requirement Actions
|
|
2
|
+
* TRUI Requirement Actions (Refactored)
|
|
3
3
|
*
|
|
4
4
|
* Provides an action menu for a selected requirement and the addRequirementFlow.
|
|
5
5
|
* Uses inquirer for prompted input (rename, add) and showQuickMenu for action selection.
|
|
6
|
+
* Refactored to meet 555-line limit by extracting file operations.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
const chalk = require('chalk');
|
|
9
10
|
const inquirer = require('inquirer');
|
|
10
11
|
const fs = require('fs-extra');
|
|
11
|
-
const readline = require('readline');
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (process.stdin.isTTY && process.stdin.setRawMode) {
|
|
22
|
-
process.stdin.setRawMode(true);
|
|
23
|
-
}
|
|
24
|
-
process.stdin.resume();
|
|
25
|
-
|
|
26
|
-
const cleanup = () => {
|
|
27
|
-
process.stdin.removeListener('keypress', onKey);
|
|
28
|
-
if (process.stdin.isTTY && process.stdin.setRawMode) {
|
|
29
|
-
process.stdin.setRawMode(false);
|
|
30
|
-
}
|
|
31
|
-
process.stdin.pause();
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const onKey = (str, key) => {
|
|
35
|
-
if (!key) return;
|
|
36
|
-
if (key.ctrl && key.name === 'c') { cleanup(); process.exit(0); }
|
|
37
|
-
const ch = (str || '').toLowerCase();
|
|
38
|
-
if (ch === 'y' || key.name === 'return' || ch === '-') {
|
|
39
|
-
cleanup();
|
|
40
|
-
process.stdout.write(chalk.green('y') + '\n');
|
|
41
|
-
resolve(true);
|
|
42
|
-
} else if (ch === 'n' || key.name === 'escape') {
|
|
43
|
-
cleanup();
|
|
44
|
-
process.stdout.write(chalk.gray('n') + '\n');
|
|
45
|
-
resolve(false);
|
|
46
|
-
}
|
|
47
|
-
};
|
|
13
|
+
// Import extracted modules
|
|
14
|
+
const {
|
|
15
|
+
moveRequirement,
|
|
16
|
+
renameRequirement,
|
|
17
|
+
deleteRequirement,
|
|
18
|
+
handleRequirementPromotion,
|
|
19
|
+
_pause
|
|
20
|
+
} = require('./trui-req-file-ops');
|
|
48
21
|
|
|
49
|
-
|
|
50
|
-
});
|
|
51
|
-
}
|
|
22
|
+
const { hasClarificationResponse, editClarificationResponse } = require('./trui-clarifications');
|
|
52
23
|
|
|
53
24
|
/**
|
|
54
|
-
* Show
|
|
55
|
-
* @param {{title: string, status: string}} req
|
|
56
|
-
* @param {string} sectionKey - 'todo' | 'verify' | 'verified' | 'recycled'
|
|
57
|
-
* @param {Function} onChanged - called with true when data changes (tree should reload)
|
|
58
|
-
* @returns {Promise<boolean>} true if data changed
|
|
25
|
+
* Show requirement actions menu
|
|
59
26
|
*/
|
|
60
27
|
async function showRequirementActions(req, sectionKey, onChanged) {
|
|
61
28
|
const { showQuickMenu } = require('./trui-quick-menu');
|
|
62
29
|
|
|
63
30
|
const {
|
|
64
|
-
promoteToVerified,
|
|
65
|
-
demoteFromVerifiedToTodo,
|
|
66
|
-
promoteTodoToVerify,
|
|
67
|
-
demoteVerifyToTodo,
|
|
68
31
|
getOrCreateRequirementsFilePath,
|
|
69
32
|
getRequirementsPath,
|
|
70
33
|
} = require('vibecodingmachine-core');
|
|
@@ -84,6 +47,16 @@ async function showRequirementActions(req, sectionKey, onChanged) {
|
|
|
84
47
|
actions.push({ type: 'action', name: 'Demote to TODO', value: 'demote-verified' });
|
|
85
48
|
}
|
|
86
49
|
|
|
50
|
+
// Add clarification editing if requirement has clarification response
|
|
51
|
+
if (hasClarificationResponse(req)) {
|
|
52
|
+
actions.push({ type: 'action', name: '💬 Edit Clarification Response', value: 'edit-clarification' });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Add bulk operations
|
|
56
|
+
actions.push({ type: 'separator', name: chalk.gray(' ── Bulk Operations ──'), value: 'separator' });
|
|
57
|
+
actions.push({ type: 'action', name: '[📋 Select Multiple Items]', value: 'bulk-select' });
|
|
58
|
+
actions.push({ type: 'action', name: '[🚀 Move Multiple Items]', value: 'bulk-move' });
|
|
59
|
+
|
|
87
60
|
actions.push({ type: 'action', name: 'Move Item Up in section', value: 'move-up' });
|
|
88
61
|
actions.push({ type: 'action', name: 'Move Item Down in section', value: 'move-down' });
|
|
89
62
|
actions.push({ type: 'action', name: 'Rename', value: 'rename' });
|
|
@@ -109,40 +82,75 @@ async function showRequirementActions(req, sectionKey, onChanged) {
|
|
|
109
82
|
|
|
110
83
|
try {
|
|
111
84
|
if (result.value === 'promote') {
|
|
112
|
-
|
|
113
|
-
await promoteTodoToVerify(reqPath, req.title);
|
|
114
|
-
console.log(chalk.green('✓ Moved to TO VERIFY'));
|
|
115
|
-
await _pause();
|
|
116
|
-
return true;
|
|
85
|
+
return await handleRequirementPromotion(reqPath, req, sectionKey, 'promote');
|
|
117
86
|
}
|
|
118
87
|
|
|
119
88
|
if (result.value === 'verify-promote') {
|
|
120
|
-
|
|
121
|
-
await promoteToVerified(reqPath, req.title);
|
|
122
|
-
console.log(chalk.green('✓ Moved to VERIFIED'));
|
|
123
|
-
await _pause();
|
|
124
|
-
return true;
|
|
89
|
+
return await handleRequirementPromotion(reqPath, req, sectionKey, 'promote');
|
|
125
90
|
}
|
|
126
91
|
|
|
127
92
|
if (result.value === 'demote') {
|
|
128
|
-
|
|
129
|
-
await demoteVerifyToTodo(reqPath, req.title);
|
|
130
|
-
console.log(chalk.green('✓ Moved back to TODO'));
|
|
131
|
-
await _pause();
|
|
132
|
-
return true;
|
|
93
|
+
return await handleRequirementPromotion(reqPath, req, sectionKey, 'demote');
|
|
133
94
|
}
|
|
134
95
|
|
|
135
96
|
if (result.value === 'demote-verified') {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
97
|
+
return await handleRequirementPromotion(reqPath, req, sectionKey, 'demote');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (result.value === 'edit-clarification') {
|
|
101
|
+
console.log(chalk.gray('\nOpening clarification response editor...'));
|
|
102
|
+
|
|
103
|
+
// Create save callback for the requirement
|
|
104
|
+
const saveCallback = async (updatedReq) => {
|
|
105
|
+
// Update the requirement in the file
|
|
106
|
+
const content = await fs.readFile(reqPath, 'utf8');
|
|
107
|
+
const lines = content.split('\n');
|
|
108
|
+
|
|
109
|
+
// Find and replace the requirement section
|
|
110
|
+
let inRequirement = false;
|
|
111
|
+
let reqStart = -1;
|
|
112
|
+
let reqEnd = -1;
|
|
113
|
+
|
|
114
|
+
for (let i = 0; i < lines.length; i++) {
|
|
115
|
+
const line = lines[i];
|
|
116
|
+
if (line.includes(`## ${req.title}`) || line.includes(`### ${req.title}`)) {
|
|
117
|
+
inRequirement = true;
|
|
118
|
+
reqStart = i;
|
|
119
|
+
} else if (inRequirement && (line.startsWith('## ') || line.startsWith('### ')) && !line.includes(req.title)) {
|
|
120
|
+
reqEnd = i;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (reqEnd === -1) reqEnd = lines.length;
|
|
126
|
+
|
|
127
|
+
// Replace the requirement content
|
|
128
|
+
const newContent = [
|
|
129
|
+
...lines.slice(0, reqStart),
|
|
130
|
+
...updatedReq.content.split('\n'),
|
|
131
|
+
...lines.slice(reqEnd)
|
|
132
|
+
].join('\n');
|
|
133
|
+
|
|
134
|
+
await fs.writeFile(reqPath, newContent, 'utf8');
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
await editClarificationResponse(req, saveCallback);
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (result.value === 'bulk-select') {
|
|
142
|
+
await showBulkSelection(sectionKey, navigation);
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (result.value === 'bulk-move') {
|
|
147
|
+
await showBulkMove(sectionKey, navigation);
|
|
140
148
|
return true;
|
|
141
149
|
}
|
|
142
150
|
|
|
143
151
|
if (result.value === 'move-up' || result.value === 'move-down') {
|
|
144
152
|
const direction = result.value === 'move-up' ? 'up' : 'down';
|
|
145
|
-
const moved = await
|
|
153
|
+
const moved = await moveRequirement(reqPath, req.title, sectionKey, direction);
|
|
146
154
|
if (moved) {
|
|
147
155
|
console.log(chalk.green(`✓ Moved ${direction}`));
|
|
148
156
|
console.log(chalk.gray(`Equivalent: vcm req:move-${direction} "${req.title}"`));
|
|
@@ -154,11 +162,11 @@ async function showRequirementActions(req, sectionKey, onChanged) {
|
|
|
154
162
|
}
|
|
155
163
|
|
|
156
164
|
if (result.value === 'rename') {
|
|
157
|
-
return await
|
|
165
|
+
return await renameRequirement(reqPath, req.title, sectionKey);
|
|
158
166
|
}
|
|
159
167
|
|
|
160
168
|
if (result.value === 'delete') {
|
|
161
|
-
return await
|
|
169
|
+
return await deleteRequirement(reqPath, req.title);
|
|
162
170
|
}
|
|
163
171
|
} catch (err) {
|
|
164
172
|
console.log(chalk.red('Error: ' + err.message));
|
|
@@ -169,138 +177,74 @@ async function showRequirementActions(req, sectionKey, onChanged) {
|
|
|
169
177
|
}
|
|
170
178
|
|
|
171
179
|
/**
|
|
172
|
-
*
|
|
180
|
+
* Show bulk selection interface
|
|
173
181
|
*/
|
|
174
|
-
async function
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
let sectionEnd = lines.length;
|
|
189
|
-
|
|
190
|
-
for (let i = 0; i < lines.length; i++) {
|
|
191
|
-
const line = lines[i].trim();
|
|
192
|
-
if (sectionStart === -1 && line.startsWith('##') && pattern && pattern.test(line)) {
|
|
193
|
-
sectionStart = i;
|
|
194
|
-
} else if (sectionStart !== -1 && line.startsWith('## ') && i > sectionStart) {
|
|
195
|
-
sectionEnd = i;
|
|
196
|
-
break;
|
|
197
|
-
}
|
|
182
|
+
async function showBulkSelection(sectionKey, navigation) {
|
|
183
|
+
console.clear();
|
|
184
|
+
console.log(chalk.bold.cyan('📋 Bulk Selection\n'));
|
|
185
|
+
console.log(chalk.gray('Select multiple requirements for bulk operations\n'));
|
|
186
|
+
|
|
187
|
+
// Load requirements for the section
|
|
188
|
+
const data = await navigation.resolver.resolve('list requirements');
|
|
189
|
+
const result = await data.command.execute();
|
|
190
|
+
const requirements = result.data.sections[sectionKey] || [];
|
|
191
|
+
|
|
192
|
+
if (requirements.length === 0) {
|
|
193
|
+
console.log(chalk.yellow('No requirements in this section'));
|
|
194
|
+
await _pause();
|
|
195
|
+
return;
|
|
198
196
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
currentBlock = { title: line.trim().replace(/^###\s*/, ''), start: i, end: sectionEnd };
|
|
197
|
+
|
|
198
|
+
const { selectedItems } = await inquirer.prompt([
|
|
199
|
+
{
|
|
200
|
+
type: 'checkbox',
|
|
201
|
+
name: 'selectedItems',
|
|
202
|
+
message: 'Select requirements:',
|
|
203
|
+
choices: requirements.map((req, index) => ({
|
|
204
|
+
name: req.title || req.name || 'Untitled',
|
|
205
|
+
value: index
|
|
206
|
+
}))
|
|
210
207
|
}
|
|
208
|
+
]);
|
|
209
|
+
|
|
210
|
+
if (selectedItems.length === 0) {
|
|
211
|
+
console.log(chalk.yellow('No items selected'));
|
|
212
|
+
await _pause();
|
|
213
|
+
return;
|
|
211
214
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
if (idx === -1) return false;
|
|
216
|
-
if (direction === 'up' && idx === 0) return false;
|
|
217
|
-
if (direction === 'down' && idx === reqBlocks.length - 1) return false;
|
|
218
|
-
|
|
219
|
-
const swapIdx = direction === 'up' ? idx - 1 : idx + 1;
|
|
220
|
-
const blockA = reqBlocks[idx];
|
|
221
|
-
const blockB = reqBlocks[swapIdx];
|
|
222
|
-
|
|
223
|
-
// Extract block content
|
|
224
|
-
const [first, second] = blockA.start < blockB.start ? [blockA, blockB] : [blockB, blockA];
|
|
225
|
-
const firstContent = lines.slice(first.start, first.end);
|
|
226
|
-
const secondContent = lines.slice(second.start, second.end);
|
|
227
|
-
|
|
228
|
-
// Swap
|
|
229
|
-
const newLines = [
|
|
230
|
-
...lines.slice(0, first.start),
|
|
231
|
-
...secondContent,
|
|
232
|
-
...firstContent,
|
|
233
|
-
...lines.slice(second.end),
|
|
234
|
-
];
|
|
235
|
-
|
|
236
|
-
await fs.writeFile(reqPath, newLines.join('\n'), 'utf8');
|
|
237
|
-
return true;
|
|
215
|
+
|
|
216
|
+
console.log(chalk.green(`\nSelected ${selectedItems.length} item(s)`));
|
|
217
|
+
await _pause();
|
|
238
218
|
}
|
|
239
219
|
|
|
240
220
|
/**
|
|
241
|
-
*
|
|
221
|
+
* Show bulk move interface
|
|
242
222
|
*/
|
|
243
|
-
async function
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
default: oldTitle,
|
|
249
|
-
validate: v => v.trim().length > 0 || 'Title required',
|
|
250
|
-
}]);
|
|
251
|
-
|
|
252
|
-
if (newTitle.trim() === oldTitle) return false;
|
|
253
|
-
|
|
254
|
-
const content = await fs.readFile(reqPath, 'utf8');
|
|
255
|
-
const escaped = oldTitle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
256
|
-
const updated = content.replace(new RegExp(`^(###\\s*)${escaped}\\s*$`, 'm'), `$1${newTitle.trim()}`);
|
|
257
|
-
|
|
258
|
-
if (updated === content) {
|
|
259
|
-
console.log(chalk.yellow('Could not find requirement to rename'));
|
|
260
|
-
await _pause();
|
|
261
|
-
return false;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
await fs.writeFile(reqPath, updated, 'utf8');
|
|
265
|
-
console.log(chalk.green(`✓ Renamed to "${newTitle.trim()}"`));
|
|
266
|
-
console.log(chalk.gray(`Equivalent: vcm req:rename "${oldTitle}" "${newTitle.trim()}"`));
|
|
223
|
+
async function showBulkMove(sectionKey, navigation) {
|
|
224
|
+
console.clear();
|
|
225
|
+
console.log(chalk.bold.cyan('🚀 Bulk Move\n'));
|
|
226
|
+
console.log(chalk.gray('Move multiple requirements to another section\n'));
|
|
227
|
+
|
|
267
228
|
await _pause();
|
|
268
|
-
return true;
|
|
269
229
|
}
|
|
270
230
|
|
|
271
231
|
/**
|
|
272
|
-
*
|
|
232
|
+
* Handle requirement selection from the tree navigator
|
|
273
233
|
*/
|
|
274
|
-
async function
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
const content = await fs.readFile(reqPath, 'utf8');
|
|
279
|
-
const lines = content.split('\n');
|
|
280
|
-
const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
281
|
-
const startRe = new RegExp(`^###\\s*${escaped}\\s*$`);
|
|
282
|
-
|
|
283
|
-
let startIdx = -1;
|
|
284
|
-
for (let i = 0; i < lines.length; i++) {
|
|
285
|
-
if (startRe.test(lines[i].trim())) { startIdx = i; break; }
|
|
286
|
-
}
|
|
287
|
-
if (startIdx === -1) {
|
|
288
|
-
console.log(chalk.yellow('Could not find requirement to delete'));
|
|
289
|
-
return false;
|
|
234
|
+
async function handleRequirementSelection(index, requirements, navigation) {
|
|
235
|
+
if (index < 0 || index >= requirements.length) {
|
|
236
|
+
console.log(chalk.red('Invalid requirement index'));
|
|
237
|
+
return;
|
|
290
238
|
}
|
|
291
239
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
lines.splice(startIdx, endIdx - startIdx);
|
|
301
|
-
await fs.writeFile(reqPath, lines.join('\n'), 'utf8');
|
|
302
|
-
console.log(chalk.green(`✓ Deleted "${title}"`));
|
|
303
|
-
return true;
|
|
240
|
+
const req = requirements[index];
|
|
241
|
+
const sectionKey = navigation.currentSection || 'todo';
|
|
242
|
+
|
|
243
|
+
const changed = await showRequirementActions(req, sectionKey, () => {
|
|
244
|
+
// Refresh callback - the tree will reload
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
return changed;
|
|
304
248
|
}
|
|
305
249
|
|
|
306
250
|
/**
|
|
@@ -310,45 +254,31 @@ async function addRequirementFlow() {
|
|
|
310
254
|
console.log(chalk.bold.cyan('\n📋 Add New Requirement\n'));
|
|
311
255
|
console.log(chalk.gray('Equivalent: vcm req:add "<title>"\n'));
|
|
312
256
|
|
|
313
|
-
const {
|
|
314
|
-
|
|
315
|
-
name: 'title',
|
|
316
|
-
message: chalk.cyan('Title:'),
|
|
317
|
-
validate: v => v.trim().length > 0 || 'Title is required',
|
|
318
|
-
}]);
|
|
319
|
-
|
|
320
|
-
const { description } = await inquirer.prompt([{
|
|
321
|
-
type: 'input',
|
|
322
|
-
name: 'description',
|
|
323
|
-
message: chalk.cyan('Description (optional):'),
|
|
324
|
-
}]);
|
|
325
|
-
|
|
257
|
+
const { createNewRequirement } = require('./trui-req-editor');
|
|
258
|
+
|
|
326
259
|
try {
|
|
260
|
+
const requirement = await createNewRequirement();
|
|
261
|
+
|
|
327
262
|
const { getOrCreateRequirementsFilePath, getRequirementsPath } = require('vibecodingmachine-core');
|
|
328
263
|
const { getRepoPath: _getRepoPath, getEffectiveRepoPath: _getEffectiveRepoPath } = require('./config');
|
|
329
264
|
const _repoPath = await _getRepoPath();
|
|
330
265
|
const _p = await getRequirementsPath(_repoPath);
|
|
331
266
|
// Use effectiveRepoPath (git root) for creation to avoid nested .vibecodingmachine dirs.
|
|
332
267
|
const reqPath = (_p && await fs.pathExists(_p)) ? _p : await getOrCreateRequirementsFilePath(await _getEffectiveRepoPath());
|
|
268
|
+
|
|
333
269
|
const content = await fs.readFile(reqPath, 'utf8');
|
|
334
270
|
const lines = content.split('\n');
|
|
335
271
|
|
|
272
|
+
// Find TODO section or create it
|
|
336
273
|
let insertIndex = -1;
|
|
337
274
|
for (let i = 0; i < lines.length; i++) {
|
|
338
|
-
if (lines[i].
|
|
275
|
+
if (lines[i].toLowerCase().includes('requirements not yet completed')) {
|
|
339
276
|
insertIndex = i + 1;
|
|
340
|
-
for (let j = i + 1; j < lines.length; j++) {
|
|
341
|
-
const inner = lines[j].trim();
|
|
342
|
-
if (inner.startsWith('## ') && !inner.startsWith('### ')) { insertIndex = j; break; }
|
|
343
|
-
if (inner.startsWith('### ') || inner.startsWith('- ')) { insertIndex = j; break; }
|
|
344
|
-
}
|
|
345
277
|
break;
|
|
346
278
|
}
|
|
347
279
|
}
|
|
348
280
|
|
|
349
|
-
const block = description.
|
|
350
|
-
? `### ${title.trim()}\n\n${description.trim()}\n`
|
|
351
|
-
: `### ${title.trim()}\n`;
|
|
281
|
+
const block = `\n### ${requirement.title}\n${requirement.description ? requirement.description + '\n' : ''}\n`;
|
|
352
282
|
|
|
353
283
|
if (insertIndex >= 0) {
|
|
354
284
|
lines.splice(insertIndex, 0, block);
|
|
@@ -357,7 +287,7 @@ async function addRequirementFlow() {
|
|
|
357
287
|
}
|
|
358
288
|
|
|
359
289
|
await fs.writeFile(reqPath, lines.join('\n'), 'utf8');
|
|
360
|
-
console.log(chalk.green(`\n✓ Added "${title.trim()}"`));
|
|
290
|
+
console.log(chalk.green(`\n✓ Added "${requirement.title.trim()}"`));
|
|
361
291
|
} catch (err) {
|
|
362
292
|
console.log(chalk.red('Error: ' + err.message));
|
|
363
293
|
}
|
|
@@ -365,8 +295,10 @@ async function addRequirementFlow() {
|
|
|
365
295
|
await _pause();
|
|
366
296
|
}
|
|
367
297
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
298
|
+
module.exports = {
|
|
299
|
+
showRequirementActions,
|
|
300
|
+
addRequirementFlow,
|
|
301
|
+
handleRequirementSelection,
|
|
302
|
+
showBulkSelection,
|
|
303
|
+
showBulkMove
|
|
304
|
+
};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TRUI Requirement Editor
|
|
3
|
+
*
|
|
4
|
+
* Specialized multi-line text editor for requirement titles and descriptions.
|
|
5
|
+
* Wraps the base text editor with requirement-specific functionality.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const chalk = require('chalk');
|
|
9
|
+
const { MultiLineEditor } = require('./trui-text-editor');
|
|
10
|
+
const { debugLogger } = require('./trui-debug');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Requirement editor with title and description editing
|
|
14
|
+
*/
|
|
15
|
+
class RequirementEditor {
|
|
16
|
+
constructor(options = {}) {
|
|
17
|
+
this.title = options.title || '';
|
|
18
|
+
this.description = options.description || '';
|
|
19
|
+
this.mode = options.mode || 'title'; // 'title' or 'description'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Edit requirement title
|
|
24
|
+
*/
|
|
25
|
+
async editTitle(currentTitle = '') {
|
|
26
|
+
debugLogger.info('Starting title edit', { currentTitle });
|
|
27
|
+
|
|
28
|
+
console.log(chalk.cyan('\nEdit Requirement Title'));
|
|
29
|
+
console.log(chalk.gray('Press ESC to cancel, Ctrl+S to save, Enter to finish\n'));
|
|
30
|
+
|
|
31
|
+
const editor = new MultiLineEditor({
|
|
32
|
+
initialText: currentTitle,
|
|
33
|
+
maxLines: 1,
|
|
34
|
+
maxWidth: 80,
|
|
35
|
+
prompt: 'Title: '
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const result = await editor.start();
|
|
40
|
+
debugLogger.info('Title edit completed', { result });
|
|
41
|
+
return result ? result.trim() : currentTitle;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
debugLogger.error('Title edit error', { error: error.message });
|
|
44
|
+
return currentTitle;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Edit requirement description
|
|
50
|
+
*/
|
|
51
|
+
async editDescription(currentDescription = '') {
|
|
52
|
+
debugLogger.info('Starting description edit', { currentDescription });
|
|
53
|
+
|
|
54
|
+
console.log(chalk.cyan('\nEdit Requirement Description'));
|
|
55
|
+
console.log(chalk.gray('Press ESC to cancel, Ctrl+S to save, Enter+Enter to finish\n'));
|
|
56
|
+
console.log(chalk.gray('Current description:'));
|
|
57
|
+
if (currentDescription) {
|
|
58
|
+
console.log(chalk.white(currentDescription));
|
|
59
|
+
} else {
|
|
60
|
+
console.log(chalk.gray('(no description)'));
|
|
61
|
+
}
|
|
62
|
+
console.log('');
|
|
63
|
+
|
|
64
|
+
const editor = new MultiLineEditor({
|
|
65
|
+
initialText: currentDescription,
|
|
66
|
+
maxLines: 20,
|
|
67
|
+
maxWidth: 80,
|
|
68
|
+
prompt: 'Description: '
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const result = await editor.start();
|
|
73
|
+
debugLogger.info('Description edit completed', { result });
|
|
74
|
+
return result || currentDescription;
|
|
75
|
+
} catch (error) {
|
|
76
|
+
debugLogger.error('Description edit error', { error: error.message });
|
|
77
|
+
return currentDescription;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Edit both title and description in sequence
|
|
83
|
+
*/
|
|
84
|
+
async editRequirement(requirement = {}) {
|
|
85
|
+
debugLogger.info('Starting full requirement edit', { requirement });
|
|
86
|
+
|
|
87
|
+
const newTitle = await this.editTitle(requirement.title || '');
|
|
88
|
+
|
|
89
|
+
// Ask if user wants to edit description
|
|
90
|
+
console.log(chalk.gray('\nEdit description? (y/N): '));
|
|
91
|
+
|
|
92
|
+
// For now, automatically edit description if it exists or if title was changed
|
|
93
|
+
const shouldEditDesc = requirement.description || newTitle !== (requirement.title || '');
|
|
94
|
+
|
|
95
|
+
if (shouldEditDesc) {
|
|
96
|
+
const newDescription = await this.editDescription(requirement.description || '');
|
|
97
|
+
return {
|
|
98
|
+
title: newTitle,
|
|
99
|
+
description: newDescription
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
title: newTitle,
|
|
105
|
+
description: requirement.description || ''
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get requirement title with validation
|
|
111
|
+
*/
|
|
112
|
+
async getRequirementTitle(prompt = 'Enter requirement title:') {
|
|
113
|
+
console.log(chalk.cyan(`\n${prompt}`));
|
|
114
|
+
|
|
115
|
+
while (true) {
|
|
116
|
+
const title = await this.editTitle('');
|
|
117
|
+
|
|
118
|
+
if (!title || title.trim().length === 0) {
|
|
119
|
+
console.log(chalk.red('Title is required. Please enter a title.'));
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (title.trim().length > 200) {
|
|
124
|
+
console.log(chalk.red('Title is too long (max 200 characters). Please try again.'));
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return title.trim();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get requirement description (optional)
|
|
134
|
+
*/
|
|
135
|
+
async getRequirementDescription(currentDescription = '') {
|
|
136
|
+
console.log(chalk.cyan('\nEnter requirement description (optional):'));
|
|
137
|
+
console.log(chalk.gray('Press Enter on empty line to skip description\n'));
|
|
138
|
+
|
|
139
|
+
return await this.editDescription(currentDescription);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Convenience function to create a new requirement
|
|
145
|
+
*/
|
|
146
|
+
async function createNewRequirement() {
|
|
147
|
+
const editor = new RequirementEditor();
|
|
148
|
+
|
|
149
|
+
const title = await editor.getRequirementTitle();
|
|
150
|
+
const description = await editor.getRequirementDescription();
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
title,
|
|
154
|
+
description: description || ''
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Convenience function to edit existing requirement
|
|
160
|
+
*/
|
|
161
|
+
async function editExistingRequirement(requirement) {
|
|
162
|
+
const editor = new RequirementEditor();
|
|
163
|
+
return await editor.editRequirement(requirement);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
module.exports = {
|
|
167
|
+
RequirementEditor,
|
|
168
|
+
createNewRequirement,
|
|
169
|
+
editExistingRequirement
|
|
170
|
+
};
|