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
@@ -2,718 +2,439 @@
2
2
  * TRUI Requirements Tree Navigator
3
3
  *
4
4
  * Shows expandable sections using showQuickMenu with letter shortcuts.
5
- * Sections: TODO SPECIFICATIONS, Verified, To Verify, TODO REQUIREMENTS
5
+ * Sections: VERIFIED TO VERIFY TODO (correct order)
6
6
  * Each section is collapsible; requirements shown as sub-items when expanded.
7
7
  */
8
8
 
9
9
  const chalk = require('chalk');
10
- const fs = require('fs-extra');
11
- const path = require('path');
12
- const os = require('os');
13
-
14
- // CLI state management functions
15
- const getStateFilePath = () => path.join(os.homedir(), '.vibecodingmachine-cli-states.json');
16
-
17
- const saveCliStates = async (expanded, expandedSpecs) => {
18
- try {
19
- const states = {
20
- expanded: expanded,
21
- expandedSpecs: Array.from(expandedSpecs)
22
- };
23
- await fs.writeFile(getStateFilePath(), JSON.stringify(states, null, 2));
24
- console.log('💾 Saved CLI list states');
25
- } catch (error) {
26
- console.error('❌ Error saving CLI states:', error.message);
27
- }
28
- };
29
-
30
- const loadCliStates = async () => {
31
- try {
32
- const stateFile = getStateFilePath();
33
- if (await fs.pathExists(stateFile)) {
34
- const content = await fs.readFile(stateFile, 'utf8');
35
- const states = JSON.parse(content);
36
- console.log('📂 Loaded CLI list states');
37
- return {
38
- expanded: states.expanded || { specifications: false, verified: false, verify: false, todo: false, recycled: false },
39
- expandedSpecs: new Set(states.expandedSpecs || [])
40
- };
41
- }
42
- console.log('📂 No saved CLI states found, using defaults (all closed)');
43
- return null;
44
- } catch (error) {
45
- console.error('❌ Error loading CLI states:', error.message);
46
- return null;
47
- }
48
- };
10
+ const { showQuickMenu } = require('./trui-quick-menu');
11
+ const { debugLogger } = require('./trui-debug');
12
+ const { hasClarificationResponse } = require('./trui-clarifications');
13
+ const inquirer = require('inquirer');
49
14
 
50
15
  /**
51
-
52
- /**
53
- * Create progress bar string with green/orange gradient based on completion percentage
54
- * @param {number} percentage - Completion percentage (0-100)
55
- * @param {number} done - Number of completed tasks
56
- * @param {number} total - Total number of tasks
57
- * @returns {string} Progress bar with colors
16
+ * Get status icon for requirement status
58
17
  */
59
- function createProgressBar(percentage, done, total) {
60
- if (total === 0) return chalk.gray(' TODO');
61
-
62
- const isComplete = percentage === 100;
63
- if (isComplete) {
64
- return chalk.bgGreen.black(` ${percentage}%, ${done}/${total} tasks complete`);
18
+ function getStatusIcon(status) {
19
+ switch (status) {
20
+ case 'verified': return '🎉';
21
+ case 'to-verify': return '✅';
22
+ case 'todo': return '⏳';
23
+ default: return '❓';
65
24
  }
66
-
67
- // For partial progress, split the text background based on actual percentage
68
- const progressText = `${percentage}%, ${done}/${total} tasks complete`;
69
- const textLength = progressText.length;
70
- const splitPoint = Math.floor((percentage / 100) * textLength);
71
-
72
- const firstHalf = progressText.substring(0, splitPoint);
73
- const secondHalf = progressText.substring(splitPoint);
74
-
75
- // Apply green background to percentage portion, orange to remaining
76
- const greenHalf = chalk.bgGreen.black(firstHalf);
77
- const orangeHalf = chalk.bgHex('#f59e0b').black(secondHalf);
78
-
79
- return ` ${greenHalf}${orangeHalf}`;
80
25
  }
81
26
 
82
- // ─── Data loading (ported from old interactive.js) ───────────────────────────
83
-
84
27
  /**
85
- * Load all requirement sections using parseRequirementsFile from core.
86
- *
87
- * Section mapping (matches old interactive.js behaviour):
88
- * verified (🎉) ← parsed.verified (## ✅ Verified by AI screenshot)
89
- * verify (✅) ← parsed.completed (## 📝 VERIFIED — fully human-verified)
90
- * todo (⏳) ← parsed.requirements (## ⏳ Requirements not yet completed)
91
- *
92
- * Each item is a string; we normalise to { title } objects for the tree.
28
+ * Create progress bar with colors
93
29
  */
94
- async function loadAllSections() {
95
- try {
96
- const { getRequirementsPath, parseRequirementsFile } = require('vibecodingmachine-core');
97
- const { getRepoPath } = require('./config');
98
- const repoPath = await getRepoPath();
99
- const reqPath = await getRequirementsPath(repoPath);
100
- if (!reqPath || !await fs.pathExists(reqPath)) {
101
- return { verified: [], verify: [], todo: [], recycled: [] };
102
- }
103
-
104
- const content = await fs.readFile(reqPath, 'utf8');
105
- const parsed = parseRequirementsFile(content);
106
-
107
- const toItems = arr =>
108
- (arr || [])
109
- .filter(r => r && (typeof r === 'string' ? r.trim() : r.title))
110
- .map(r => typeof r === 'string' ? { title: r.trim() } : r);
111
-
112
- return {
113
- verified: toItems(parsed.verified), // AI-screenshot verified items
114
- verify: toItems(parsed.completed), // fully human-verified items
115
- todo: toItems(parsed.requirements), // not yet completed
116
- recycled: toItems(parsed.recycled),
117
- };
118
- } catch (_) {
119
- return { verified: [], verify: [], todo: [], recycled: [] };
120
- }
30
+ function createProgressBar(percentage) {
31
+ const filled = Math.round(percentage / 10);
32
+ const empty = 10 - filled;
33
+ return chalk.green('█'.repeat(filled)) + chalk.gray(''.repeat(empty));
121
34
  }
122
35
 
123
- // Keep loadSection exported for backwards compat with other modules
124
- async function loadSection(sectionKey) {
125
- const sections = await loadAllSections();
126
- return sections[sectionKey] || [];
127
- }
36
+ /**
37
+ * Load requirements data via RUI resolver
38
+ */
39
+ async function loadRequirementsData(navigation) {
40
+ try {
41
+ const result = navigation.resolver.resolve('list requirements');
42
+ if (!result.success) return null;
128
43
 
129
- // ─── Menu building ────────────────────────────────────────────────────────────
44
+ const commandResult = await result.command.execute();
45
+ if (!commandResult.success || !commandResult.data) return null;
130
46
 
131
- function getStatusIcon(status) {
132
- switch (status) {
133
- case 'completed': return '✅';
134
- case 'in-progress': return '🔄';
135
- case 'todo': return '📋';
136
- default: return '❓';
47
+ return commandResult.data;
48
+ } catch (err) {
49
+ debugLogger.error('Error loading requirements data', { error: err.message });
50
+ return null;
137
51
  }
138
52
  }
139
53
 
140
54
  /**
141
- * Build showQuickMenu items for the requirements tree
142
- * @param {Object} sections - requirements sections
143
- * @param {Object} expanded - which sections are expanded
144
- * @param {Set} [expandedSpecs] - set of expanded spec indices
145
- * @param {Map} [specUserStories] - cache of specIdx -> story title strings
55
+ * Build section items with counts and percentages
146
56
  */
147
- async function buildTreeItems(sections, expanded, expandedSpecs, specUserStories) {
57
+ function buildSectionItems(sections, expandedSections = {}) {
148
58
  const items = [];
149
- const total = sections.verified.length + sections.verify.length + sections.todo.length;
150
- const pct = n => total > 0 ? Math.round((n / total) * 100) : 0;
151
-
152
- const addSection = (key, icon, label, reqs) => {
153
- const count = reqs.length;
154
- const isOpen = expanded[key];
155
- const arrow = isOpen ? '▾' : '▸';
156
- const percentage = total > 0 ? pct(count) : 0;
157
-
158
- // Use createProgressBar for consistent colored progress bars
159
- const progressStr = total > 0 ? ' ' + createProgressBar(percentage, count, total) : ` (${count})`;
160
-
161
- items.push({
162
- type: isOpen ? 'header' : 'action',
163
- name: `${arrow} ${icon} ${label}${progressStr}`,
164
- value: `section:${key}`,
165
- });
166
- if (expanded[key]) {
167
- if (count === 0) {
168
- items.push({ type: 'info', name: ' (empty)', value: `empty:${key}` });
169
- } else {
170
- reqs.forEach((req, idx) => {
171
- const title = req.title || req;
172
- const isDisabled = typeof title === 'string' && title.startsWith('DISABLED: ');
173
- const displayTitle = isDisabled
174
- ? chalk.gray(`⊘ ${title.slice('DISABLED: '.length)}`)
175
- : title;
176
- items.push({
177
- type: 'action',
178
- name: ` ${displayTitle}`,
179
- value: `req:${key}:${idx}`,
180
- });
181
- });
182
- }
183
- }
184
- };
185
-
186
- // TODO SPECIFICATIONS first
187
- try {
188
- const { getSpecsList } = require('./trui-specifications');
189
- const specs = await getSpecsList();
190
- const specsCount = specs.length;
191
-
192
- // Calculate overall specs progress
193
- let specsTotalTasks = 0;
194
- let specsDoneTasks = 0;
195
- specs.forEach(spec => {
196
- if (spec.taskTotal > 0) {
197
- specsTotalTasks += spec.taskTotal;
198
- specsDoneTasks += spec.taskDone;
199
- }
200
- });
201
- const specsPct = specsTotalTasks > 0 ? Math.round((specsDoneTasks / specsTotalTasks) * 100) : 0;
202
-
203
- const specsOpen = expanded.specifications;
204
- const specsArrow = specsOpen ? '▾' : '▸';
205
-
206
- // Use createProgressBar for consistent colored progress bars
207
- const specsProgressStr = specsTotalTasks > 0 ? ' ' + createProgressBar(specsPct, specsDoneTasks, specsTotalTasks) : '';
208
-
209
- items.push({
210
- type: specsOpen ? 'header' : 'action',
211
- name: `${specsArrow} 📋 TODO Specifications${specsProgressStr}`,
212
- value: 'section:specifications',
213
- });
214
- if (specsOpen) {
215
- if (specsCount === 0) {
216
- items.push({ type: 'info', name: ' (no specifications found)', value: 'empty:specifications' });
217
- } else {
218
- specs.forEach((spec, idx) => {
219
- let progressStr = '';
220
- if (spec.taskTotal > 0) {
221
- progressStr = ' ' + createProgressBar(spec.pct, spec.taskDone, spec.taskTotal);
222
- }
223
- const isSpecExpanded = expandedSpecs && expandedSpecs.has(idx);
224
- const specArrow = isSpecExpanded ? '▾' : '▸';
225
- const isDisabled = spec.disabled || spec.id.startsWith('DISABLED-');
226
- const displayId = isDisabled ? spec.id.slice('DISABLED-'.length) : spec.id;
227
- const specLabel = isDisabled ? chalk.gray(`⊘ ${displayId}`) : displayId;
228
- // Spec items always keep their letter so the user can navigate to and collapse them
229
- items.push({
230
- type: 'action',
231
- name: ` ${specArrow} ${specLabel}${progressStr}`,
232
- value: `spec:${idx}`,
233
- });
234
- if (isSpecExpanded) {
235
- const phases = (specUserStories && specUserStories.get(idx)) || [];
236
- if (phases.length === 0) {
237
- items.push({ type: 'info', name: ' (no phases found)', value: `spec-phase-empty:${idx}` });
238
- } else {
239
- phases.forEach((phase, pIdx) => {
240
- const phaseTitle = typeof phase === 'string' ? phase : phase.title;
241
- let phaseProgress = '';
242
- if (phase && typeof phase === 'object' && phase.total > 0) {
243
- phaseProgress = chalk.gray(` ${phase.pct}%, ${phase.done}/${phase.total}`);
244
- }
245
- items.push({
246
- type: 'info',
247
- name: ` ${chalk.gray(phaseTitle)}${phaseProgress}`,
248
- value: `spec-phase:${idx}:${pIdx}`,
249
- });
250
- });
251
- }
252
- }
253
- });
254
- }
255
- }
256
- } catch (_) {
257
- items.push({ type: 'header', name: '▸ 📋 TODO SPECIFICATIONS', value: 'section:specifications' });
258
- }
259
-
260
- addSection('verified', '🎉', 'Verified', sections.verified);
261
- addSection('verify', '✅', 'To Verify', sections.verify);
262
-
263
- // Calculate requirements progress
264
- let reqsTotalTasks = 0;
265
- let reqsDoneTasks = 0;
266
- if (sections.todo && sections.todo.length > 0) {
267
- sections.todo.forEach(req => {
268
- if (req.total > 0) {
269
- reqsTotalTasks += req.total;
270
- reqsDoneTasks += req.done;
271
- }
272
- });
273
- }
274
- const reqsPct = reqsTotalTasks > 0 ? Math.round((reqsDoneTasks / reqsTotalTasks) * 100) : 0;
59
+ const total = sections.verified.length + sections['to-verify'].length + sections.todo.length;
275
60
 
276
- // Use createProgressBar for consistent colored progress bars
277
- const reqsProgressStr = reqsTotalTasks > 0 ? ' ' + createProgressBar(reqsPct, reqsDoneTasks, reqsTotalTasks) : '';
278
-
279
- const reqsTitle = reqsTotalTasks > 0
280
- ? `TODO Requirements${reqsProgressStr}`
281
- : 'TODO Requirements';
282
-
283
- addSection('todo', '', reqsTitle, sections.todo);
284
-
285
- if (sections.recycled && sections.recycled.length > 0) {
286
- addSection('recycled', '♻️', 'RECYCLED', sections.recycled);
287
- }
61
+ // VERIFIED section
62
+ const verifiedExpanded = expandedSections.verified || false;
63
+ const verifiedIcon = verifiedExpanded ? '▼' : '▶';
64
+ const verifiedPct = total > 0 ? Math.round((sections.verified.length / total) * 100) : 0;
65
+ items.push({
66
+ type: 'action',
67
+ name: `${verifiedIcon} ${getStatusIcon('verified')} VERIFIED (${sections.verified.length}) ${verifiedPct}%`,
68
+ value: 'section:verified'
69
+ });
70
+
71
+ // TO VERIFY section
72
+ const toVerifyExpanded = expandedSections['to-verify'] || false;
73
+ const toVerifyIcon = toVerifyExpanded ? '▼' : '▶';
74
+ const toVerifyPct = total > 0 ? Math.round((sections['to-verify'].length / total) * 100) : 0;
75
+ items.push({
76
+ type: 'action',
77
+ name: `${toVerifyIcon} ${getStatusIcon('to-verify')} TO VERIFY (${sections['to-verify'].length}) ${toVerifyPct}%`,
78
+ value: 'section:to-verify'
79
+ });
80
+
81
+ // TODO section
82
+ const todoExpanded = expandedSections.todo || false;
83
+ const todoIcon = todoExpanded ? '▼' : '▶';
84
+ const todoPct = total > 0 ? Math.round((sections.todo.length / total) * 100) : 0;
85
+ items.push({
86
+ type: 'action',
87
+ name: `${todoIcon} ${getStatusIcon('todo')} TODO (${sections.todo.length}) ${todoPct}%`,
88
+ value: 'section:todo'
89
+ });
288
90
 
289
91
  return items;
290
92
  }
291
93
 
292
- // ─── Main navigator ───────────────────────────────────────────────────────────
94
+ /**
95
+ * Build requirement items for an expanded section
96
+ */
97
+ function buildRequirementItems(requirements, sectionKey) {
98
+ return requirements.map((req, index) => {
99
+ const hasClarification = hasClarificationResponse(req);
100
+ const clarificationIcon = hasClarification ? ' 💬' : '';
101
+
102
+ return {
103
+ type: 'sub-item',
104
+ name: ` ${req.title || req.name || 'Untitled'}${clarificationIcon}`,
105
+ value: `requirement:${sectionKey}:${index}`
106
+ };
107
+ });
108
+ }
293
109
 
294
- async function showRequirementsTree() {
295
- const { showQuickMenu } = require('./trui-quick-menu');
296
- const { showRequirementActions, addRequirementFlow, _moveRequirement, _deleteRequirement } = require('./trui-req-actions');
297
- const { getOrCreateRequirementsFilePath, getRequirementsPath } = require('vibecodingmachine-core');
298
- const { getRepoPath } = require('./config');
110
+ /**
111
+ * Show requirements tree navigator
112
+ */
113
+ async function showRequirementsTree(navigation, expandedSections = {}) {
114
+ debugLogger.info('showRequirementsTree called', { expandedSections });
299
115
 
300
- // Toggle all items functionality
301
- const extraKeys = (str, key, selectedIndex, context) => {
302
- if (str === '*' || str === '8') {
303
- context.resolveWith({ value: 'toggle-all', selectedIndex });
304
- return true;
305
- }
306
- return false;
307
- };
308
-
309
- // Resolve requirements file path exactly like loadAllSections does (getRequirementsPath,
310
- // NOT getOrCreate) so reads and writes always go to the same file regardless of
311
- // whether it lives inside the repo or in a sibling directory.
312
- const repoPath = await getRepoPath();
313
- const getReqPath = async () => {
314
- // Mirror loadAllSections: call getRequirementsPath with repoPath (even null) first
315
- const p = await getRequirementsPath(repoPath);
316
- if (p && await fs.pathExists(p)) return p;
317
- // File doesn't exist yet — create it (first-time use).
318
- // Use effectiveRepoPath (git root) so .vibecodingmachine is never created inside
319
- // a sub-package directory when repoPath is null.
320
- const { getEffectiveRepoPath } = require('./config');
321
- const effectiveRepoPath = await getEffectiveRepoPath();
322
- return getOrCreateRequirementsFilePath(effectiveRepoPath);
323
- };
324
-
325
- const expanded = { specifications: false, verified: false, verify: false, todo: false, recycled: false };
326
- const expandedSpecs = new Set(); // indices of expanded spec items
327
- const specPhases = new Map(); // specIdx -> string[]
328
- let sections = await loadAllSections();
329
- let lastIndex = 0;
330
-
331
- // Load saved states from file
332
- const savedStates = await loadCliStates();
333
- if (savedStates) {
334
- Object.assign(expanded, savedStates.expanded);
335
- savedStates.expandedSpecs.forEach(idx => expandedSpecs.add(idx));
116
+ const data = await loadRequirementsData(navigation);
117
+ if (!data || !data.sections) {
118
+ console.log(chalk.yellow('No requirements data available'));
119
+ return;
336
120
  }
337
121
 
338
- const printHeader = () => {
339
- console.clear();
340
- process.stdout.write(chalk.bold.cyan('📋 Requirements\n\n'));
341
- };
342
-
343
- printHeader();
122
+ const { sections } = data;
344
123
 
345
124
  while (true) {
346
- const items = await buildTreeItems(sections, expanded, expandedSpecs, specPhases);
347
- const hintText = '[→ expand ← collapse/back < move item down > move item up - delete + add Space toggle]';
348
-
349
- const extraKeys = (str, key, selectedIndex, { resolveWith }) => {
350
- const keyName = key && key.name;
351
-
352
- // = or + → add requirement (works anywhere)
353
- if (str === '=' || str === '+') {
354
- resolveWith('add-req');
355
- return true;
125
+ // Build menu items
126
+ const items = [];
127
+
128
+ // Add section headers
129
+ const sectionItems = buildSectionItems(sections, expandedSections);
130
+ items.push(...sectionItems);
131
+
132
+ // Add expanded section requirements
133
+ for (const [sectionKey, isExpanded] of Object.entries(expandedSections)) {
134
+ if (isExpanded && sections[sectionKey]) {
135
+ items.push({ type: 'separator', name: chalk.gray(` ── ${sectionKey.toUpperCase()} ──`), value: 'separator' });
136
+ const reqItems = buildRequirementItems(sections[sectionKey], sectionKey);
137
+ items.push(...reqItems);
356
138
  }
139
+ }
357
140
 
358
- // right arrow: expand sections and specs (non-toggling — only opens, never closes)
359
- if (keyName === 'right') {
360
- const item = items[selectedIndex];
361
- if (item && item.value) {
362
- if (item.value.startsWith('section:')) {
363
- const sectionKey = item.value.slice('section:'.length);
364
- if (!expanded[sectionKey]) {
365
- resolveWith(`expand-section:${sectionKey}`);
366
- }
367
- return true; // consume even if already expanded
368
- }
369
- if (item.value.startsWith('spec:')) {
370
- const specIdx = parseInt(item.value.split(':')[1], 10);
371
- if (!expandedSpecs.has(specIdx)) {
372
- resolveWith(`expand-spec:${specIdx}`);
373
- }
374
- return true; // consume even if already expanded
141
+ // Add blank separator and actions
142
+ items.push({ type: 'blank', name: '', value: 'blank' });
143
+ items.push({ type: 'action', name: '[🔍 Search Requirements]', value: 'search' });
144
+ items.push({ type: 'action', name: '[🔽 Filter Requirements]', value: 'filter' });
145
+ items.push({ type: 'action', name: '[+ Add Requirement]', value: 'add-requirement' });
146
+
147
+ // Show menu with extra keys for section expansion/collapse and requirement navigation
148
+ const result = await showQuickMenu(items, 0, {
149
+ extraKeys: (str, key, selectedIndex, context) => {
150
+ // Handle section expansion/collapse
151
+ if (key.name === 'return' || key.name === 'right') {
152
+ const currentItem = items[selectedIndex];
153
+ if (currentItem && currentItem.value && currentItem.value.startsWith('section:')) {
154
+ const sectionKey = currentItem.value.substring(8);
155
+ expandedSections[sectionKey] = !expandedSections[sectionKey];
156
+ return true; // Signal handled
375
157
  }
376
158
  }
377
- return false; // let right arrow act as Enter for other items
378
- }
379
159
 
380
- // left arrow: collapse expanded sections/specs; otherwise pass through (→ back)
381
- if (keyName === 'left') {
382
- const item = items[selectedIndex];
383
- if (item && item.value) {
384
- // On a collapsed section header → go back (fall through)
385
- if (item.value.startsWith('section:')) {
386
- const sectionKey = item.value.slice('section:'.length);
387
- if (expanded[sectionKey]) {
388
- resolveWith(`collapse-section:${sectionKey}`);
389
- return true;
390
- }
391
- }
392
- // On a spec item inside expanded SPECIFICATIONS section
393
- if (item.value.startsWith('spec:')) {
394
- const specIdx = parseInt(item.value.split(':')[1], 10);
395
- if (expandedSpecs.has(specIdx)) {
396
- // Spec is expanded (showing phases) → collapse phases first
397
- resolveWith(`collapse-spec:${specIdx}`);
398
- return true;
399
- }
400
- // Spec not expanded → collapse parent SPECIFICATIONS section
401
- if (expanded.specifications) {
402
- resolveWith('collapse-section:specifications');
403
- return true;
404
- }
405
- }
406
- // On a req item → collapse its parent section
407
- if (item.value.startsWith('req:')) {
408
- const sectionKey = item.value.split(':')[1];
409
- if (expanded[sectionKey]) {
410
- resolveWith(`collapse-section:${sectionKey}`);
411
- return true;
412
- }
160
+ // Handle requirement actions
161
+ if (key.name === 'return' || key.name === 'right') {
162
+ const currentItem = items[selectedIndex];
163
+ if (currentItem && currentItem.value && currentItem.value.startsWith('requirement:')) {
164
+ const [, sectionKey, indexStr] = currentItem.value.split(':');
165
+ const index = parseInt(indexStr, 10);
166
+ context.resolveWith(`requirement-action:${sectionKey}:${index}`);
167
+ return true;
413
168
  }
414
169
  }
415
- return false; // not on a sub-item → default cancel (go back)
416
- }
417
170
 
418
- // Space toggle req or spec enabled/disabled
419
- if (str === ' ' || keyName === 'space') {
420
- const item = items[selectedIndex];
421
- if (item && item.value && item.value.startsWith('req:')) {
422
- resolveWith(`toggle-disabled:${selectedIndex}`);
171
+ // Handle move up/down (u/d keys)
172
+ if ((str === 'u' || str === 'U') && currentItem && currentItem.value && currentItem.value.startsWith('requirement:')) {
173
+ const [, sectionKey, indexStr] = currentItem.value.split(':');
174
+ const index = parseInt(indexStr, 10);
175
+ context.resolveWith(`requirement-move-up:${sectionKey}:${index}`);
423
176
  return true;
424
177
  }
425
- if (item && item.value && item.value.startsWith('spec:')) {
426
- resolveWith(`toggle-disabled-spec:${selectedIndex}`);
178
+
179
+ if ((str === 'd' || str === 'D') && currentItem && currentItem.value && currentItem.value.startsWith('requirement:')) {
180
+ const [, sectionKey, indexStr] = currentItem.value.split(':');
181
+ const index = parseInt(indexStr, 10);
182
+ context.resolveWith(`requirement-move-down:${sectionKey}:${index}`);
427
183
  return true;
428
184
  }
429
- return true; // consume silently on other items
430
- }
431
185
 
432
- // , or < move requirement down
433
- // . or > move requirement up
434
- // - or _ or Delete key → delete requirement
435
- const isDown = str === ',' || str === '<' || keyName === ',' || keyName === '<';
436
- const isUp = str === '.' || str === '>' || keyName === '.' || keyName === '>';
437
- const isDelete = str === '-' || str === '_' || keyName === '-' || keyName === '_' ||
438
- (key && (key.name === 'delete' || key.name === 'backspace'));
439
- if (isDown || isUp || isDelete) {
440
- const item = items[selectedIndex];
441
- if (item && item.value && item.value.startsWith('req:')) {
442
- const action = isDown ? ',' : isUp ? '.' : '-';
443
- resolveWith(`jkd:${action}:${selectedIndex}`);
186
+ // Handle new requirement (n key)
187
+ if (str === 'n' || str === 'N') {
188
+ context.resolveWith('add-requirement');
444
189
  return true;
445
190
  }
446
- return true; // consume the key even when not on a req (no-op, avoids confusion)
447
- }
448
-
449
- return false;
450
- };
451
191
 
452
- const result = await showQuickMenu(items, lastIndex, { extraKeys, hintText });
453
- lastIndex = result.selectedIndex;
454
- const value = result.value;
455
-
456
- if (value === '__cancel__' || value === 'exit') break;
192
+ return false;
193
+ },
194
+ hintText: 'Enter/right=expand/action u/d=move n=new ←=back'
195
+ });
457
196
 
458
- if (value === 'add-req') {
459
- try { await addRequirementFlow(); } catch (_) {}
460
- sections = await loadAllSections();
461
- printHeader();
197
+ // Handle actions
198
+ if (result.value === 'add-requirement') {
199
+ const { addRequirementFlow } = require('./trui-req-actions');
200
+ await addRequirementFlow(navigation);
462
201
  continue;
463
202
  }
464
203
 
465
- // Handle Space: toggle requirement enabled/disabled
466
- if (value.startsWith('toggle-disabled:')) {
467
- const itemIdx = parseInt(value.split(':')[1], 10);
468
- const item = items[itemIdx];
469
- if (item && item.value && item.value.startsWith('req:')) {
470
- const reqParts = item.value.split(':');
471
- const sectionKey = reqParts[1];
472
- const reqIdx = parseInt(reqParts[2], 10);
473
- const req = sections[sectionKey] && sections[sectionKey][reqIdx];
474
- if (req) {
475
- try {
476
- const reqPath = await getReqPath();
477
- const content = await fs.readFile(reqPath, 'utf8');
478
- const title = req.title;
479
- let newContent;
480
- if (title.startsWith('DISABLED: ')) {
481
- // Enable: remove DISABLED: prefix
482
- const cleanTitle = title.slice('DISABLED: '.length);
483
- const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
484
- newContent = content.replace(new RegExp(`^(###\\s*)${escaped}\\s*$`, 'm'), `$1${cleanTitle}`);
485
- } else {
486
- // Disable: add DISABLED: prefix
487
- const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
488
- newContent = content.replace(new RegExp(`^(###\\s*)${escaped}\\s*$`, 'm'), `$1DISABLED: ${title}`);
489
- }
490
- if (newContent !== content) {
491
- await fs.writeFile(reqPath, newContent, 'utf8');
492
- sections = await loadAllSections();
493
- }
494
- } catch (_) {}
495
- }
496
- }
497
- printHeader();
204
+ if (result.value.startsWith('section:')) {
205
+ // Section toggle handled by extraKeys
498
206
  continue;
499
207
  }
500
208
 
501
- // Handle */8: toggle all items
502
- if (value === 'toggle-all') {
503
- try {
504
- const reqPath = await getReqPath();
505
- let content = await fs.readFile(reqPath, 'utf8');
506
- let hasChanges = false;
507
-
508
- // Toggle all requirements
509
- if (sections.todo && sections.todo.length > 0) {
510
- sections.todo.forEach(req => {
511
- const title = req.title;
512
- if (!title.startsWith('DISABLED: ')) {
513
- // Disable all
514
- const escaped = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
515
- content = content.replace(new RegExp(`^(###\\s*)${escaped}\\s*$`, 'm'), `$1DISABLED: ${title}`);
516
- hasChanges = true;
517
- }
518
- });
519
- }
520
-
521
- // Toggle all specifications
522
- const { getSpecsList } = require('./trui-specifications');
523
- const specs = await getSpecsList();
524
- for (const spec of specs) {
525
- if (spec.taskTotal > 0) {
526
- const specDir = spec.path || path.join(await getRepoPath(), 'specs', spec.directory);
527
- const tasksMdPath = path.join(specDir, 'tasks.md');
528
- if (await fs.pathExists(tasksMdPath)) {
529
- let specContent = await fs.readFile(tasksMdPath, 'utf8');
530
- const specTitle = spec.title || spec.directory;
531
- if (!specContent.includes('DISABLED:')) {
532
- // Disable spec
533
- specContent = `DISABLED: ${specTitle}\n${specContent}`;
534
- hasChanges = true;
535
- } else {
536
- // Enable spec
537
- specContent = specContent.replace(/^DISABLED: .+\n/, '');
538
- hasChanges = true;
539
- }
540
- if (hasChanges) {
541
- await fs.writeFile(tasksMdPath, specContent, 'utf8');
542
- }
543
- }
544
- }
545
- }
546
-
547
- if (hasChanges) {
548
- sections = await loadAllSections();
549
- }
550
- } catch (_) {}
551
- printHeader();
209
+ if (result.value.startsWith('requirement-action:')) {
210
+ const { handleRequirementSelection } = require('./trui-req-actions');
211
+ const [, sectionKey, indexStr] = result.value.split(':');
212
+ const index = parseInt(indexStr, 10);
213
+ await handleRequirementSelection(index, sections[sectionKey], navigation);
552
214
  continue;
553
215
  }
554
216
 
555
- // Handle Space: toggle spec enabled/disabled (rename directory DISABLED- prefix)
556
- if (value.startsWith('toggle-disabled-spec:')) {
557
- const itemIdx = parseInt(value.split(':')[1], 10);
558
- const item = items[itemIdx];
559
- if (item && item.value && item.value.startsWith('spec:')) {
560
- const specIdx = parseInt(item.value.split(':')[1], 10);
561
- try {
562
- const { getSpecsList } = require('./trui-specifications');
563
- const specs = await getSpecsList();
564
- const spec = specs[specIdx];
565
- if (spec && spec.path) {
566
- const parentDir = path.dirname(spec.path);
567
- const dirName = path.basename(spec.path);
568
- const newDirName = dirName.startsWith('DISABLED-')
569
- ? dirName.slice('DISABLED-'.length)
570
- : 'DISABLED-' + dirName;
571
- await fs.rename(spec.path, path.join(parentDir, newDirName));
572
- // Clear spec caches — indices may shift after rename changes sort order
573
- expandedSpecs.clear();
574
- specPhases.clear();
575
- }
576
- } catch (_) {}
217
+ if (result.value.startsWith('requirement-move-up:')) {
218
+ const { handleRequirementMove } = require('./trui-req-actions');
219
+ const [, sectionKey, indexStr] = result.value.split(':');
220
+ const index = parseInt(indexStr, 10);
221
+ const changed = await handleRequirementMove(index, sections[sectionKey], 'up');
222
+ if (changed) {
223
+ // Reload data to get updated order
224
+ const newData = await loadRequirementsData(navigation);
225
+ if (newData && newData.sections) {
226
+ Object.assign(sections, newData.sections);
227
+ }
577
228
  }
578
- printHeader();
579
229
  continue;
580
230
  }
581
231
 
582
- // Handle j/k/d inline shortcuts
583
- if (value.startsWith('jkd:')) {
584
- const parts = value.split(':');
585
- const action = parts[1];
586
- const itemIdx = parseInt(parts[2], 10);
587
- const item = items[itemIdx];
588
- if (item && item.value && item.value.startsWith('req:')) {
589
- const reqParts = item.value.split(':');
590
- const sectionKey = reqParts[1];
591
- const reqIdx = parseInt(reqParts[2], 10);
592
- const req = sections[sectionKey] && sections[sectionKey][reqIdx];
593
- if (req) {
594
- try {
595
- const reqPath = await getReqPath();
596
- if (action === ',') {
597
- const moved = await _moveRequirement(reqPath, req.title, sectionKey, 'down');
598
- if (moved) { sections = await loadAllSections(); lastIndex = Math.min(lastIndex + 1, items.length - 1); }
599
- } else if (action === '.') {
600
- const moved = await _moveRequirement(reqPath, req.title, sectionKey, 'up');
601
- if (moved) { sections = await loadAllSections(); lastIndex = Math.max(lastIndex - 1, 0); }
602
- } else if (action === '-') {
603
- console.clear();
604
- const deleted = await _deleteRequirement(reqPath, req.title);
605
- if (deleted) { sections = await loadAllSections(); lastIndex = Math.max(lastIndex - 1, 0); }
606
- }
607
- } catch (_) {}
232
+ if (result.value.startsWith('requirement-move-down:')) {
233
+ const { handleRequirementMove } = require('./trui-req-actions');
234
+ const [, sectionKey, indexStr] = result.value.split(':');
235
+ const index = parseInt(indexStr, 10);
236
+ const changed = await handleRequirementMove(index, sections[sectionKey], 'down');
237
+ if (changed) {
238
+ // Reload data to get updated order
239
+ const newData = await loadRequirementsData(navigation);
240
+ if (newData && newData.sections) {
241
+ Object.assign(sections, newData.sections);
608
242
  }
609
243
  }
610
- printHeader();
611
244
  continue;
612
245
  }
613
246
 
614
- // Enter on section header → toggle; cursor auto-advances on open, returns to header on close
615
- if (value.startsWith('section:')) {
616
- const key = value.slice('section:'.length);
617
- expanded[key] = !expanded[key];
618
- await saveCliStates(expanded, expandedSpecs);
619
- if (!expanded[key]) {
620
- // Closing: position cursor back on header
621
- const newItems = await buildTreeItems(sections, expanded, expandedSpecs, specPhases);
622
- const headerIdx = newItems.findIndex(item => item.value === `section:${key}`);
623
- if (headerIdx !== -1) lastIndex = headerIdx;
624
- }
625
- printHeader();
247
+ if (result.value === 'search') {
248
+ await searchRequirements(sections, navigation);
626
249
  continue;
627
250
  }
628
251
 
629
- if (value.startsWith('req:')) {
630
- const parts = value.split(':');
631
- const sectionKey = parts[1];
632
- const idx = parseInt(parts[2], 10);
633
- const req = sections[sectionKey] && sections[sectionKey][idx];
634
- if (req) {
635
- console.clear();
636
- try {
637
- const changed = await showRequirementActions(req, sectionKey);
638
- if (changed) sections = await loadAllSections();
639
- } catch (_) {}
640
- printHeader();
641
- }
252
+ if (result.value === 'filter') {
253
+ await filterRequirements(sections, navigation);
642
254
  continue;
643
255
  }
644
256
 
645
- // Expand section (right arrow)
646
- if (value.startsWith('expand-section:')) {
647
- const key = value.slice('expand-section:'.length);
648
- expanded[key] = true;
649
- await saveCliStates(expanded, expandedSpecs);
650
- printHeader();
651
- continue;
257
+ if (result.value === '__cancel__') {
258
+ break;
652
259
  }
260
+ }
261
+ }
653
262
 
654
- // Collapse section — position cursor back on the section header
655
- if (value.startsWith('collapse-section:')) {
656
- const key = value.slice('collapse-section:'.length);
657
- expanded[key] = false;
658
- await saveCliStates(expanded, expandedSpecs);
659
- // Rebuild to find the now-selectable (action) section header position
660
- const newItems = await buildTreeItems(sections, expanded, expandedSpecs, specPhases);
661
- const headerIdx = newItems.findIndex(item => item.value === `section:${key}`);
662
- if (headerIdx !== -1) lastIndex = headerIdx;
663
- printHeader();
664
- continue;
665
- }
263
+ /**
264
+ * Search requirements across all sections
265
+ */
266
+ async function searchRequirements(sections, navigation) {
267
+ console.clear();
268
+ console.log(chalk.bold.cyan('🔍 Search Requirements\n'));
269
+ console.log(chalk.gray('Search across all requirement sections\n'));
666
270
 
667
- // Expand spec phases (right arrow on spec item) — specIdx is the spec array index
668
- if (value.startsWith('expand-spec:')) {
669
- const specIdx = parseInt(value.split(':')[1], 10);
670
- expandedSpecs.add(specIdx);
671
- await saveCliStates(expanded, expandedSpecs);
672
- if (!specPhases.has(specIdx)) {
673
- try {
674
- const { getSpecsList, extractPhases } = require('./trui-specifications');
675
- const specs = await getSpecsList();
676
- const spec = specs[specIdx];
677
- if (spec && spec.path) specPhases.set(specIdx, extractPhases(spec.path));
678
- } catch (_) {}
271
+ try {
272
+ const { query } = await inquirer.prompt([
273
+ {
274
+ type: 'input',
275
+ name: 'query',
276
+ message: 'Enter search term:',
277
+ validate: input => input.trim().length > 0 || 'Search term cannot be empty'
679
278
  }
680
- printHeader();
681
- continue;
279
+ ]);
280
+
281
+ if (!query) return;
282
+
283
+ // Search across all sections
284
+ const allRequirements = [];
285
+ Object.entries(sections).forEach(([sectionKey, sectionReqs]) => {
286
+ sectionReqs.forEach((req, index) => {
287
+ allRequirements.push({
288
+ ...req,
289
+ section: sectionKey,
290
+ index,
291
+ displaySection: sectionKey.toUpperCase()
292
+ });
293
+ });
294
+ });
295
+
296
+ // Filter requirements based on search query
297
+ const searchLower = query.toLowerCase();
298
+ const matches = allRequirements.filter(req => {
299
+ const title = (req.title || req.name || '').toLowerCase();
300
+ const content = (req.content || req.description || '').toLowerCase();
301
+ return title.includes(searchLower) || content.includes(searchLower);
302
+ });
303
+
304
+ if (matches.length === 0) {
305
+ console.log(chalk.yellow(`\nNo requirements found matching "${query}"`));
306
+ await inquirer.prompt([{ type: 'input', name: 'c', message: chalk.gray('Press Enter to continue...') }]);
307
+ return;
682
308
  }
683
309
 
684
- // Collapse spec phases — position cursor back on the spec item
685
- if (value.startsWith('collapse-spec:')) {
686
- const specIdx = parseInt(value.split(':')[1], 10);
687
- expandedSpecs.delete(specIdx);
688
- await saveCliStates(expanded, expandedSpecs);
689
- // Rebuild to find the now-selectable spec item position
690
- const newItems = await buildTreeItems(sections, expanded, expandedSpecs, specPhases);
691
- const specItemIdx = newItems.findIndex(item => item.value === `spec:${specIdx}`);
692
- if (specItemIdx !== -1) lastIndex = specItemIdx;
693
- printHeader();
694
- continue;
310
+ // Display search results
311
+ console.log(chalk.green(`\nFound ${matches.length} requirement(s) matching "${query}":\n`));
312
+
313
+ const result = await showQuickMenu(
314
+ matches.map((req, index) => ({
315
+ type: 'search-result',
316
+ name: `${getStatusIcon(req.status)} ${req.title || req.name} (${req.displaySection})`,
317
+ value: `search-result:${index}`
318
+ })),
319
+ 0
320
+ );
321
+
322
+ if (result.value && result.value.startsWith('search-result:')) {
323
+ const index = parseInt(result.value.substring(13), 10);
324
+ const selectedReq = matches[index];
325
+
326
+ // Show requirement actions for search result
327
+ const { handleRequirementSelection } = require('./trui-req-actions');
328
+ await handleRequirementSelection(selectedReq.index, sections[selectedReq.section], navigation);
695
329
  }
696
330
 
697
- // Enter on spec item → toggle expansion
698
- if (value.startsWith('spec:')) {
699
- const specIdx = parseInt(value.split(':')[1], 10);
700
- if (expandedSpecs.has(specIdx)) {
701
- expandedSpecs.delete(specIdx);
702
- } else {
703
- expandedSpecs.add(specIdx);
704
- if (!specPhases.has(specIdx)) {
705
- try {
706
- const { getSpecsList, extractPhases } = require('./trui-specifications');
707
- const specs = await getSpecsList();
708
- const spec = specs[specIdx];
709
- if (spec && spec.path) specPhases.set(specIdx, extractPhases(spec.path));
710
- } catch (_) {}
331
+ } catch (error) {
332
+ debugLogger.error('Error in search', { error: error.message });
333
+ console.log(chalk.red('Search error: ' + error.message));
334
+ }
335
+ }
336
+
337
+ /**
338
+ * Filter requirements by status or section
339
+ */
340
+ async function filterRequirements(sections, navigation) {
341
+ console.clear();
342
+ console.log(chalk.bold.cyan('🔽 Filter Requirements\n'));
343
+ console.log(chalk.gray('Filter requirements by status or section\n'));
344
+
345
+ try {
346
+ const { filterType, filterValue } = await inquirer.prompt([
347
+ {
348
+ type: 'list',
349
+ name: 'filterType',
350
+ message: 'Filter by:',
351
+ choices: [
352
+ { name: 'Status (TODO/TO VERIFY/VERIFIED)', value: 'status' },
353
+ { name: 'Section', value: 'section' },
354
+ { name: 'Clear All Filters', value: 'clear' }
355
+ ]
356
+ },
357
+ {
358
+ type: 'list',
359
+ name: 'filterValue',
360
+ message: 'Select filter value:',
361
+ when: (answers) => answers.filterType !== 'clear',
362
+ choices: (answers) => {
363
+ if (answers.filterType === 'status') {
364
+ return [
365
+ { name: 'TODO items only', value: 'todo' },
366
+ { name: 'TO VERIFY items only', value: 'to-verify' },
367
+ { name: 'VERIFIED items only', value: 'verified' }
368
+ ];
369
+ } else if (answers.filterType === 'section') {
370
+ return Object.keys(sections).map(key => ({
371
+ name: `${key.toUpperCase()} (${sections[key].length} items)`,
372
+ value: key
373
+ }));
374
+ }
375
+ return [];
711
376
  }
712
377
  }
713
- printHeader();
714
- continue;
378
+ ]);
379
+
380
+ if (filterType === 'clear') {
381
+ console.log(chalk.green('\n✓ All filters cleared'));
382
+ await inquirer.prompt([{ type: 'input', name: 'c', message: chalk.gray('Press Enter to continue...') }]);
383
+ return;
384
+ }
385
+
386
+ // Apply filter and show filtered results
387
+ let filteredReqs = [];
388
+ let filterDescription = '';
389
+
390
+ if (filterType === 'status' && filterValue) {
391
+ filteredReqs = sections[filterValue] || [];
392
+ filterDescription = `Status: ${filterValue.toUpperCase()}`;
393
+ } else if (filterType === 'section' && filterValue) {
394
+ filteredReqs = sections[filterValue] || [];
395
+ filterDescription = `Section: ${filterValue.toUpperCase()}`;
396
+ }
397
+
398
+ if (filteredReqs.length === 0) {
399
+ console.log(chalk.yellow(`\nNo requirements found for filter: ${filterDescription}`));
400
+ await inquirer.prompt([{ type: 'input', name: 'c', message: chalk.gray('Press Enter to continue...') }]);
401
+ return;
715
402
  }
403
+
404
+ // Display filtered results
405
+ console.log(chalk.green(`\nShowing ${filteredReqs.length} requirement(s) - ${filterDescription}\n`));
406
+
407
+ const result = await showQuickMenu(
408
+ filteredReqs.map((req, index) => ({
409
+ type: 'filtered-result',
410
+ name: `${getStatusIcon(req.status)} ${req.title || req.name}`,
411
+ value: `filtered-result:${filterType}:${filterValue}:${index}`
412
+ })),
413
+ 0
414
+ );
415
+
416
+ if (result.value && result.value.startsWith('filtered-result:')) {
417
+ const [, type, value, indexStr] = result.value.split(':');
418
+ const index = parseInt(indexStr, 10);
419
+ const selectedReq = filteredReqs[index];
420
+
421
+ // Show requirement actions for filtered result
422
+ const { handleRequirementSelection } = require('./trui-req-actions');
423
+ const sectionKey = type === 'status' ? value : filterValue;
424
+ await handleRequirementSelection(selectedReq.index, sections[sectionKey], navigation);
425
+ }
426
+
427
+ } catch (error) {
428
+ debugLogger.error('Error in filter', { error: error.message });
429
+ console.log(chalk.red('Filter error: ' + error.message));
716
430
  }
717
431
  }
718
432
 
719
- module.exports = { showRequirementsTree, loadSection, loadAllSections, buildTreeItems };
433
+ module.exports = {
434
+ showRequirementsTree,
435
+ loadRequirementsData,
436
+ buildSectionItems,
437
+ buildRequirementItems,
438
+ searchRequirements,
439
+ filterRequirements
440
+ };