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
|
@@ -2,718 +2,439 @@
|
|
|
2
2
|
* TRUI Requirements Tree Navigator
|
|
3
3
|
*
|
|
4
4
|
* Shows expandable sections using showQuickMenu with letter shortcuts.
|
|
5
|
-
* Sections:
|
|
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
|
|
11
|
-
const
|
|
12
|
-
const
|
|
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
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return
|
|
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
|
-
*
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
44
|
+
const commandResult = await result.command.execute();
|
|
45
|
+
if (!commandResult.success || !commandResult.data) return null;
|
|
130
46
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
|
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
|
-
|
|
57
|
+
function buildSectionItems(sections, expandedSections = {}) {
|
|
148
58
|
const items = [];
|
|
149
|
-
const total = sections.verified.length + sections
|
|
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
|
-
//
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
: '
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
-
|
|
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
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
110
|
+
/**
|
|
111
|
+
* Show requirements tree navigator
|
|
112
|
+
*/
|
|
113
|
+
async function showRequirementsTree(navigation, expandedSections = {}) {
|
|
114
|
+
debugLogger.info('showRequirementsTree called', { expandedSections });
|
|
299
115
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
|
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
|
-
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
resolveWith(`
|
|
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
|
-
|
|
426
|
-
|
|
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
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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
|
-
|
|
453
|
-
|
|
454
|
-
|
|
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
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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
|
-
|
|
466
|
-
|
|
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
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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
|
-
|
|
556
|
-
|
|
557
|
-
const
|
|
558
|
-
const
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
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
|
-
|
|
583
|
-
|
|
584
|
-
const
|
|
585
|
-
const
|
|
586
|
-
const
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
const
|
|
590
|
-
|
|
591
|
-
|
|
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
|
-
|
|
615
|
-
|
|
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
|
|
630
|
-
|
|
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
|
-
|
|
646
|
-
|
|
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
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
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
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
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
|
-
|
|
681
|
-
|
|
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
|
-
//
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
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
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
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
|
-
|
|
714
|
-
|
|
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 = {
|
|
433
|
+
module.exports = {
|
|
434
|
+
showRequirementsTree,
|
|
435
|
+
loadRequirementsData,
|
|
436
|
+
buildSectionItems,
|
|
437
|
+
buildRequirementItems,
|
|
438
|
+
searchRequirements,
|
|
439
|
+
filterRequirements
|
|
440
|
+
};
|