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.
Files changed (74) hide show
  1. package/bin/auth/auth-compliance.js +7 -1
  2. package/bin/commands/agent-commands.js +150 -228
  3. package/bin/commands/command-aliases.js +68 -0
  4. package/bin/vibecodingmachine.js +1 -2
  5. package/package.json +2 -2
  6. package/src/commands/agents/list.js +71 -115
  7. package/src/commands/agents-check.js +16 -4
  8. package/src/commands/analyze-file-sizes.js +1 -1
  9. package/src/commands/auto-direct/auto-provider-manager.js +290 -0
  10. package/src/commands/auto-direct/auto-status-display.js +331 -0
  11. package/src/commands/auto-direct/auto-utils.js +439 -0
  12. package/src/commands/auto-direct/file-operations.js +110 -0
  13. package/src/commands/auto-direct/provider-config.js +1 -1
  14. package/src/commands/auto-direct/provider-manager.js +1 -1
  15. package/src/commands/auto-direct/status-display.js +1 -1
  16. package/src/commands/auto-direct/utils.js +24 -18
  17. package/src/commands/auto-direct-refactored.js +413 -0
  18. package/src/commands/auto-direct.js +594 -188
  19. package/src/commands/requirements/commands.js +353 -0
  20. package/src/commands/requirements/default-handlers.js +272 -0
  21. package/src/commands/requirements/disable.js +97 -0
  22. package/src/commands/requirements/enable.js +97 -0
  23. package/src/commands/requirements/utils.js +194 -0
  24. package/src/commands/requirements-refactored.js +60 -0
  25. package/src/commands/requirements.js +38 -771
  26. package/src/commands/specs/disable.js +96 -0
  27. package/src/commands/specs/enable.js +96 -0
  28. package/src/trui/TruiInterface.js +5 -11
  29. package/src/trui/agents/AgentInterface.js +24 -396
  30. package/src/trui/agents/handlers/CommandHandler.js +93 -0
  31. package/src/trui/agents/handlers/ContextManager.js +117 -0
  32. package/src/trui/agents/handlers/DisplayHandler.js +243 -0
  33. package/src/trui/agents/handlers/HelpHandler.js +51 -0
  34. package/src/utils/auth.js +13 -111
  35. package/src/utils/config.js +5 -1
  36. package/src/utils/interactive/requirements-navigation.js +17 -15
  37. package/src/utils/interactive-broken.js +2 -2
  38. package/src/utils/provider-checker/agent-runner.js +15 -1
  39. package/src/utils/provider-checker/cli-installer.js +149 -7
  40. package/src/utils/provider-checker/opencode-checker.js +588 -0
  41. package/src/utils/provider-checker/provider-validator.js +88 -3
  42. package/src/utils/provider-checker/time-formatter.js +3 -2
  43. package/src/utils/provider-manager.js +28 -20
  44. package/src/utils/provider-registry.js +35 -3
  45. package/src/utils/requirements-navigator/index.js +94 -0
  46. package/src/utils/requirements-navigator/input-handler.js +217 -0
  47. package/src/utils/requirements-navigator/section-loader.js +188 -0
  48. package/src/utils/requirements-navigator/tree-builder.js +105 -0
  49. package/src/utils/requirements-navigator/tree-renderer.js +50 -0
  50. package/src/utils/requirements-navigator.js +2 -583
  51. package/src/utils/trui-clarifications.js +188 -0
  52. package/src/utils/trui-feedback.js +54 -1
  53. package/src/utils/trui-kiro-integration.js +398 -0
  54. package/src/utils/trui-main-handlers.js +194 -0
  55. package/src/utils/trui-main-menu.js +235 -0
  56. package/src/utils/trui-nav-agents.js +178 -25
  57. package/src/utils/trui-nav-requirements.js +203 -27
  58. package/src/utils/trui-nav-settings.js +114 -1
  59. package/src/utils/trui-nav-specifications.js +44 -3
  60. package/src/utils/trui-navigation-backup.js +603 -0
  61. package/src/utils/trui-navigation.js +70 -228
  62. package/src/utils/trui-provider-health.js +274 -0
  63. package/src/utils/trui-provider-manager.js +376 -0
  64. package/src/utils/trui-quick-menu.js +25 -1
  65. package/src/utils/trui-req-actions-backup.js +507 -0
  66. package/src/utils/trui-req-actions.js +148 -216
  67. package/src/utils/trui-req-editor.js +170 -0
  68. package/src/utils/trui-req-file-ops.js +278 -0
  69. package/src/utils/trui-req-tree-old.js +719 -0
  70. package/src/utils/trui-req-tree.js +348 -627
  71. package/src/utils/trui-specifications.js +25 -7
  72. package/src/utils/trui-windsurf.js +231 -10
  73. package/src/utils/welcome-screen-extracted.js +2 -2
  74. 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
- * Single-keypress confirm: Y/Enter/- = yes, N/Esc = no. No Enter required.
15
- * Shows: `message (Y/n/-)` and echoes the choice.
16
- */
17
- async function _confirmKeypress(message) {
18
- return new Promise(resolve => {
19
- process.stdout.write(message + ' (Y/n/-)\n\n');
20
- readline.emitKeypressEvents(process.stdin);
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
- process.stdin.on('keypress', onKey);
50
- });
51
- }
22
+ const { hasClarificationResponse, editClarificationResponse } = require('./trui-clarifications');
52
23
 
53
24
  /**
54
- * Show action menu for a requirement item.
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
- console.log(chalk.gray('\nEquivalent: vcm req:promote "' + req.title + '"'));
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
- console.log(chalk.gray('\nEquivalent: vcm req:verify "' + req.title + '"'));
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
- console.log(chalk.gray('\nEquivalent: vcm req:demote "' + req.title + '"'));
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
- console.log(chalk.gray('\nEquivalent: vcm req:demote "' + req.title + '"'));
137
- await demoteFromVerifiedToTodo(reqPath, req.title);
138
- console.log(chalk.green('✓ Moved back to TODO'));
139
- await _pause();
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 _moveRequirement(reqPath, req.title, sectionKey, direction);
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 _renameRequirement(reqPath, req.title, sectionKey);
165
+ return await renameRequirement(reqPath, req.title, sectionKey);
158
166
  }
159
167
 
160
168
  if (result.value === 'delete') {
161
- return await _deleteRequirement(reqPath, req.title);
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
- * Move a requirement up or down within its section
180
+ * Show bulk selection interface
173
181
  */
174
- async function _moveRequirement(reqPath, title, sectionKey, direction) {
175
- const content = await fs.readFile(reqPath, 'utf8');
176
- const lines = content.split('\n');
177
-
178
- // Find section boundaries
179
- const sectionHeadings = {
180
- todo: /requirements not yet completed/i,
181
- verify: /to verify|verified by ai/i,
182
- verified: /verified\b/i,
183
- recycled: /recycled/i,
184
- };
185
- const pattern = sectionHeadings[sectionKey];
186
-
187
- let sectionStart = -1;
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
- if (sectionStart === -1) return false;
201
-
202
- // Find all requirement blocks (### headers) within the section
203
- const reqBlocks = [];
204
- let currentBlock = null;
205
- for (let i = sectionStart + 1; i < sectionEnd; i++) {
206
- const line = lines[i];
207
- if (line.trim().startsWith('### ')) {
208
- if (currentBlock) { currentBlock.end = i; reqBlocks.push(currentBlock); }
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
- if (currentBlock) { currentBlock.end = sectionEnd; reqBlocks.push(currentBlock); }
213
-
214
- const idx = reqBlocks.findIndex(b => b.title === title);
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
- * Rename a requirement
221
+ * Show bulk move interface
242
222
  */
243
- async function _renameRequirement(reqPath, oldTitle, sectionKey) {
244
- const { newTitle } = await inquirer.prompt([{
245
- type: 'input',
246
- name: 'newTitle',
247
- message: chalk.cyan('New title:'),
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
- * Delete a requirement after confirmation
232
+ * Handle requirement selection from the tree navigator
273
233
  */
274
- async function _deleteRequirement(reqPath, title) {
275
- const confirmed = await _confirmKeypress(chalk.red(`Delete "${title}"?`));
276
- if (!confirmed) return false;
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
- // Find end of this requirement block
293
- let endIdx = startIdx + 1;
294
- while (endIdx < lines.length) {
295
- const line = lines[endIdx].trim();
296
- if (line.startsWith('### ') || (line.startsWith('## ') && !line.startsWith('### '))) break;
297
- endIdx++;
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 { title } = await inquirer.prompt([{
314
- type: 'input',
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].trim().match(/^##\s+.*(?:Requirements not yet completed|TODO)/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.trim()
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
- async function _pause() {
369
- await inquirer.prompt([{ type: 'input', name: 'c', message: chalk.gray('Press Enter to continue...') }]);
370
- }
371
-
372
- module.exports = { showRequirementActions, addRequirementFlow, _moveRequirement, _deleteRequirement };
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
+ };