vibecodingmachine-cli 2026.2.26-1739 → 2026.3.9-1621
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/auth/auth-compliance.js +7 -1
- package/bin/commands/agent-commands.js +150 -228
- package/bin/commands/command-aliases.js +68 -0
- package/bin/vibecodingmachine.js +1 -2
- package/package.json +2 -2
- package/src/commands/agents/list.js +71 -115
- package/src/commands/agents-check.js +16 -4
- package/src/commands/analyze-file-sizes.js +1 -1
- package/src/commands/auto-direct/auto-provider-manager.js +290 -0
- package/src/commands/auto-direct/auto-status-display.js +331 -0
- package/src/commands/auto-direct/auto-utils.js +439 -0
- package/src/commands/auto-direct/file-operations.js +110 -0
- package/src/commands/auto-direct/provider-config.js +1 -1
- package/src/commands/auto-direct/provider-manager.js +1 -1
- package/src/commands/auto-direct/status-display.js +1 -1
- package/src/commands/auto-direct/utils.js +24 -18
- package/src/commands/auto-direct-refactored.js +413 -0
- package/src/commands/auto-direct.js +594 -188
- package/src/commands/requirements/commands.js +353 -0
- package/src/commands/requirements/default-handlers.js +272 -0
- package/src/commands/requirements/disable.js +97 -0
- package/src/commands/requirements/enable.js +97 -0
- package/src/commands/requirements/utils.js +194 -0
- package/src/commands/requirements-refactored.js +60 -0
- package/src/commands/requirements.js +38 -771
- package/src/commands/specs/disable.js +96 -0
- package/src/commands/specs/enable.js +96 -0
- package/src/trui/TruiInterface.js +5 -11
- package/src/trui/agents/AgentInterface.js +24 -396
- package/src/trui/agents/handlers/CommandHandler.js +93 -0
- package/src/trui/agents/handlers/ContextManager.js +117 -0
- package/src/trui/agents/handlers/DisplayHandler.js +243 -0
- package/src/trui/agents/handlers/HelpHandler.js +51 -0
- package/src/utils/auth.js +13 -111
- package/src/utils/config.js +5 -1
- package/src/utils/interactive/requirements-navigation.js +17 -15
- package/src/utils/interactive-broken.js +2 -2
- package/src/utils/provider-checker/agent-runner.js +15 -1
- package/src/utils/provider-checker/cli-installer.js +149 -7
- package/src/utils/provider-checker/opencode-checker.js +588 -0
- package/src/utils/provider-checker/provider-validator.js +88 -3
- package/src/utils/provider-checker/time-formatter.js +3 -2
- package/src/utils/provider-manager.js +28 -20
- package/src/utils/provider-registry.js +35 -3
- package/src/utils/requirements-navigator/index.js +94 -0
- package/src/utils/requirements-navigator/input-handler.js +217 -0
- package/src/utils/requirements-navigator/section-loader.js +188 -0
- package/src/utils/requirements-navigator/tree-builder.js +105 -0
- package/src/utils/requirements-navigator/tree-renderer.js +50 -0
- package/src/utils/requirements-navigator.js +2 -583
- package/src/utils/trui-clarifications.js +188 -0
- package/src/utils/trui-feedback.js +54 -1
- package/src/utils/trui-kiro-integration.js +398 -0
- package/src/utils/trui-main-handlers.js +194 -0
- package/src/utils/trui-main-menu.js +235 -0
- package/src/utils/trui-nav-agents.js +178 -25
- package/src/utils/trui-nav-requirements.js +203 -27
- package/src/utils/trui-nav-settings.js +114 -1
- package/src/utils/trui-nav-specifications.js +44 -3
- package/src/utils/trui-navigation-backup.js +603 -0
- package/src/utils/trui-navigation.js +70 -228
- package/src/utils/trui-provider-health.js +274 -0
- package/src/utils/trui-provider-manager.js +376 -0
- package/src/utils/trui-quick-menu.js +25 -1
- package/src/utils/trui-req-actions-backup.js +507 -0
- package/src/utils/trui-req-actions.js +148 -216
- package/src/utils/trui-req-editor.js +170 -0
- package/src/utils/trui-req-file-ops.js +278 -0
- package/src/utils/trui-req-tree-old.js +719 -0
- package/src/utils/trui-req-tree.js +348 -627
- package/src/utils/trui-specifications.js +25 -7
- package/src/utils/trui-windsurf.js +231 -10
- package/src/utils/welcome-screen-extracted.js +2 -2
- package/src/utils/welcome-screen.js +2 -2
|
@@ -1,586 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
const
|
|
3
|
-
const fs = require('fs-extra');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const readline = require('readline');
|
|
6
|
-
const { t, getRequirementsPath, getVibeCodingMachineDir, checkVibeCodingMachineExists } = require('vibecodingmachine-core');
|
|
7
|
-
const { parseRequirementsFromContent } = require('./requirements-parser');
|
|
8
|
-
const { countRequirements } = require('./status-helpers');
|
|
9
|
-
|
|
10
|
-
// Import helper functions that will be extracted separately
|
|
11
|
-
// These will need to be imported from requirement-actions.js once created
|
|
12
|
-
const {
|
|
13
|
-
confirmAndExit,
|
|
14
|
-
showRequirementActions,
|
|
15
|
-
showClarificationActions,
|
|
16
|
-
handleAddRequirement,
|
|
17
|
-
deleteRequirement,
|
|
18
|
-
deleteClarification,
|
|
19
|
-
permanentlyDeleteRequirement,
|
|
20
|
-
moveRequirementDown,
|
|
21
|
-
moveRequirementUp,
|
|
22
|
-
promoteRequirement,
|
|
23
|
-
demoteRequirement,
|
|
24
|
-
moveClarificationToTodo,
|
|
25
|
-
handleFeedbackSubmission
|
|
26
|
-
} = require('./requirement-actions');
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Tree-style requirements navigator
|
|
30
|
-
*/
|
|
31
|
-
async function showRequirementsTree() {
|
|
32
|
-
console.log(chalk.bold.cyan('\n📋 ' + t('requirements.navigator.title') + '\n'));
|
|
33
|
-
console.log(chalk.gray(t('requirements.navigator.basic.instructions') + '\n'));
|
|
34
|
-
console.log(chalk.gray('💡 Press F for feedback - Share your thoughts anytime\n'));
|
|
35
|
-
|
|
36
|
-
const tree = {
|
|
37
|
-
expanded: { root: true },
|
|
38
|
-
selected: 0,
|
|
39
|
-
items: []
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
// Build tree structure
|
|
43
|
-
const buildTree = async () => {
|
|
44
|
-
tree.items = [];
|
|
45
|
-
|
|
46
|
-
// Root: Requirements
|
|
47
|
-
tree.items.push({ level: 0, type: 'root', label: `📋 ${t('requirements.menu.label')}`, key: 'root' });
|
|
48
|
-
|
|
49
|
-
if (tree.expanded.root) {
|
|
50
|
-
tree.items.push({ level: 1, type: 'add', label: '➕ ' + t('requirements.add.new'), key: 'add-one' });
|
|
51
|
-
tree.items.push({ level: 1, type: 'add', label: '➕ ' + t('requirements.add.multiple'), key: 'add-many' });
|
|
52
|
-
|
|
53
|
-
// Use pre-calculated stats and labels from shared logic
|
|
54
|
-
const stats = await countRequirements();
|
|
55
|
-
const { todoCount, toVerifyCount, verifiedCount, total, todoLabel, toVerifyLabel, verifiedLabel } = stats || {
|
|
56
|
-
todoCount: 0, toVerifyCount: 0, verifiedCount: 0, total: 0,
|
|
57
|
-
todoLabel: '⏳ TODO (0 - 0%)', toVerifyLabel: '✅ TO VERIFY (0 - 0%)', verifiedLabel: '🎉 VERIFIED (0 - 0%)'
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
// Create localized labels
|
|
61
|
-
const localizedTodoLabel = todoCount > 0 ?
|
|
62
|
-
`⏳ ${t('requirements.section.todo')} (${todoCount} - ${Math.round((todoCount / total) * 100)}%)` :
|
|
63
|
-
`⏳ ${t('requirements.section.todo')} (0 - 0%)`;
|
|
64
|
-
|
|
65
|
-
const localizedToVerifyLabel = toVerifyCount > 0 ?
|
|
66
|
-
`✅ ${t('requirements.section.to.verify')} (${toVerifyCount} - ${Math.round((toVerifyCount / total) * 100)}%)` :
|
|
67
|
-
`✅ ${t('requirements.section.to.verify')} (0 - 0%)`;
|
|
68
|
-
|
|
69
|
-
const localizedVerifiedLabel = verifiedCount > 0 ?
|
|
70
|
-
`🎉 ${t('requirements.section.verified')} (${verifiedCount} - ${Math.round((verifiedCount / total) * 100)}%)` :
|
|
71
|
-
`🎉 ${t('requirements.section.verified')} (0 - 0%)`;
|
|
72
|
-
|
|
73
|
-
const verifiedReqs = tree.verifiedReqs || [];
|
|
74
|
-
const verifyReqs = tree.verifyReqs || [];
|
|
75
|
-
const clarificationReqs = tree.clarificationReqs || [];
|
|
76
|
-
const todoReqs = tree.todoReqs || [];
|
|
77
|
-
const recycledReqs = tree.recycledReqs || [];
|
|
78
|
-
|
|
79
|
-
// Calculate percentages for clarification and recycled sections
|
|
80
|
-
const clarificationPercent = total > 0 ? Math.round((clarificationReqs.length / total) * 100) : 0;
|
|
81
|
-
const recycledPercent = total > 0 ? Math.round((recycledReqs.length / total) * 100) : 0;
|
|
82
|
-
|
|
83
|
-
// VERIFIED section (first) - only show if has requirements
|
|
84
|
-
if (verifiedReqs.length > 0 || verifiedCount > 0) {
|
|
85
|
-
tree.items.push({ level: 1, type: 'section', label: localizedVerifiedLabel, key: 'verified' });
|
|
86
|
-
|
|
87
|
-
if (tree.expanded.verified) {
|
|
88
|
-
verifiedReqs.forEach((req, idx) => {
|
|
89
|
-
tree.items.push({ level: 2, type: 'verified', label: req, key: `verified-${idx}` });
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// TO VERIFY section (second) - only show if has requirements
|
|
95
|
-
if (verifyReqs.length > 0 || toVerifyCount > 0) {
|
|
96
|
-
tree.items.push({ level: 1, type: 'section', label: localizedToVerifyLabel, key: 'verify', section: '✅ Verified by AI screenshot' });
|
|
97
|
-
|
|
98
|
-
if (tree.expanded.verify) {
|
|
99
|
-
verifyReqs.forEach((req, idx) => {
|
|
100
|
-
tree.items.push({ level: 2, type: 'requirement', label: req.title, key: `verify-${idx}`, req, sectionKey: 'verify' });
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// NEEDING CLARIFICATION section (third) - only show if has requirements
|
|
106
|
-
if (clarificationReqs.length > 0) {
|
|
107
|
-
tree.items.push({ level: 1, type: 'section', label: `❓ NEEDING CLARIFICATION (${clarificationReqs.length} - ${clarificationPercent}%)`, key: 'clarification', section: '❓ Requirements needing manual feedback' });
|
|
108
|
-
|
|
109
|
-
if (tree.expanded.clarification) {
|
|
110
|
-
clarificationReqs.forEach((req, idx) => {
|
|
111
|
-
tree.items.push({ level: 2, type: 'clarification', label: req.title, key: `clarification-${idx}`, req, sectionKey: 'clarification' });
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// TODO section (fourth) - only show if has requirements
|
|
117
|
-
if (todoReqs.length > 0 || todoCount > 0) {
|
|
118
|
-
tree.items.push({ level: 1, type: 'section', label: localizedTodoLabel, key: 'todo', section: '⏳ Requirements not yet completed' });
|
|
119
|
-
|
|
120
|
-
if (tree.expanded.todo) {
|
|
121
|
-
todoReqs.forEach((req, idx) => {
|
|
122
|
-
tree.items.push({ level: 2, type: 'requirement', label: req.title, key: `todo-${idx}`, req, sectionKey: 'todo' });
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// RECYCLED section (last) - only show if has requirements
|
|
128
|
-
if (recycledReqs.length > 0) {
|
|
129
|
-
tree.items.push({ level: 1, type: 'section', label: `♻️ RECYCLED (${recycledReqs.length} - ${recycledPercent}%)`, key: 'recycled', section: '♻️ Recycled' });
|
|
130
|
-
|
|
131
|
-
if (tree.expanded.recycled) {
|
|
132
|
-
recycledReqs.forEach((req, idx) => {
|
|
133
|
-
tree.items.push({ level: 2, type: 'recycled', label: req.title, key: `recycled-${idx}`, req, sectionKey: 'recycled' });
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
// Load requirements for a section
|
|
141
|
-
const loadSection = async (sectionKey, sectionTitle) => {
|
|
142
|
-
const reqPath = await getRequirementsPath();
|
|
143
|
-
|
|
144
|
-
if (!reqPath || !await fs.pathExists(reqPath)) {
|
|
145
|
-
return [];
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const content = await fs.readFile(reqPath, 'utf8');
|
|
149
|
-
|
|
150
|
-
// Delegate to reusable parser
|
|
151
|
-
const allReqs = parseRequirementsFromContent(content, sectionKey, sectionTitle);
|
|
152
|
-
|
|
153
|
-
// For TODO section, only show primary heading requirements (those marked from '###' titles)
|
|
154
|
-
if (sectionKey === 'todo') return allReqs.filter(r => r.source === 'heading');
|
|
155
|
-
|
|
156
|
-
return allReqs;
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
// Load VERIFIED requirements from CHANGELOG
|
|
160
|
-
const loadVerified = async () => {
|
|
161
|
-
const allnightStatus = await checkVibeCodingMachineExists();
|
|
162
|
-
let changelogPath;
|
|
163
|
-
|
|
164
|
-
if (allnightStatus.insideExists) {
|
|
165
|
-
const allnightDir = await getVibeCodingMachineDir();
|
|
166
|
-
changelogPath = path.join(path.dirname(allnightDir), 'CHANGELOG.md');
|
|
167
|
-
} else if (allnightStatus.siblingExists) {
|
|
168
|
-
changelogPath = path.join(process.cwd(), 'CHANGELOG.md');
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (!changelogPath || !await fs.pathExists(changelogPath)) {
|
|
172
|
-
return [];
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const content = await fs.readFile(changelogPath, 'utf8');
|
|
176
|
-
const lines = content.split('\n');
|
|
177
|
-
const requirements = [];
|
|
178
|
-
let inVerifiedSection = false;
|
|
179
|
-
|
|
180
|
-
for (const line of lines) {
|
|
181
|
-
const trimmed = line.trim();
|
|
182
|
-
|
|
183
|
-
// Check for Verified Requirements section
|
|
184
|
-
if (trimmed.includes('## Verified Requirements')) {
|
|
185
|
-
inVerifiedSection = true;
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Exit section if we hit another ## header
|
|
190
|
-
if (inVerifiedSection && trimmed.startsWith('##') && !trimmed.includes('Verified Requirements')) {
|
|
191
|
-
break;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Only collect items from within the Verified Requirements section
|
|
195
|
-
if (inVerifiedSection && trimmed.startsWith('- ') && trimmed.length > 10) {
|
|
196
|
-
requirements.push(trimmed.substring(2));
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return requirements;
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
// Load clarification requirements with questions
|
|
204
|
-
const loadClarification = async () => {
|
|
205
|
-
const reqPath = await getRequirementsPath();
|
|
206
|
-
|
|
207
|
-
if (!reqPath || !await fs.pathExists(reqPath)) {
|
|
208
|
-
return [];
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const content = await fs.readFile(reqPath, 'utf8');
|
|
212
|
-
const lines = content.split('\n');
|
|
213
|
-
|
|
214
|
-
let inSection = false;
|
|
215
|
-
const requirements = [];
|
|
216
|
-
|
|
217
|
-
for (let i = 0; i < lines.length; i++) {
|
|
218
|
-
const line = lines[i];
|
|
219
|
-
const trimmed = line.trim();
|
|
220
|
-
|
|
221
|
-
// Check if we're entering the clarification section
|
|
222
|
-
if (trimmed.includes('❓ Requirements needing manual feedback')) {
|
|
223
|
-
inSection = true;
|
|
224
|
-
continue;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Check if we're leaving the section (hit another ## section)
|
|
228
|
-
if (inSection && trimmed.startsWith('##') && !trimmed.startsWith('###')) {
|
|
229
|
-
break;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Read requirements in new format (### header)
|
|
233
|
-
if (inSection && trimmed.startsWith('###')) {
|
|
234
|
-
const title = trimmed.replace(/^###\s*/, '').trim();
|
|
235
|
-
|
|
236
|
-
// Skip empty titles
|
|
237
|
-
if (!title || title.length === 0) {
|
|
238
|
-
continue;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const details = [];
|
|
242
|
-
const questions = [];
|
|
243
|
-
let pkg = null;
|
|
244
|
-
let findings = null;
|
|
245
|
-
let currentQuestion = null;
|
|
246
|
-
|
|
247
|
-
// Read package, description, and clarifying questions
|
|
248
|
-
for (let j = i + 1; j < lines.length; j++) {
|
|
249
|
-
const nextLine = lines[j].trim();
|
|
250
|
-
|
|
251
|
-
// Stop if we hit another requirement or section
|
|
252
|
-
if (nextLine.startsWith('###') || (nextLine.startsWith('##') && !nextLine.startsWith('###'))) {
|
|
253
|
-
break;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Check for PACKAGE line
|
|
257
|
-
if (nextLine.startsWith('PACKAGE:')) {
|
|
258
|
-
pkg = nextLine.replace(/^PACKAGE:\s*/, '').trim();
|
|
259
|
-
}
|
|
260
|
-
// Check for AI findings
|
|
261
|
-
else if (nextLine.startsWith('**AI found in codebase:**')) {
|
|
262
|
-
// Next line will be the findings
|
|
263
|
-
continue;
|
|
264
|
-
}
|
|
265
|
-
else if (nextLine.startsWith('**What went wrong')) {
|
|
266
|
-
// Description line
|
|
267
|
-
details.push(nextLine);
|
|
268
|
-
}
|
|
269
|
-
else if (nextLine.startsWith('**Clarifying questions:**')) {
|
|
270
|
-
// Start of questions section
|
|
271
|
-
continue;
|
|
272
|
-
}
|
|
273
|
-
else if (nextLine.match(/^\d+\./)) {
|
|
274
|
-
// Save previous question if exists
|
|
275
|
-
if (currentQuestion) {
|
|
276
|
-
questions.push(currentQuestion);
|
|
277
|
-
}
|
|
278
|
-
// This is a new question
|
|
279
|
-
currentQuestion = { question: nextLine, response: null };
|
|
280
|
-
}
|
|
281
|
-
else if (currentQuestion && nextLine && !nextLine.startsWith('PACKAGE:') && !nextLine.startsWith('**')) {
|
|
282
|
-
// This might be a response to the current question or description/findings
|
|
283
|
-
if (!findings && !currentQuestion.response && questions.length === 0 && !nextLine.match(/^\d+\./)) {
|
|
284
|
-
// This is findings content
|
|
285
|
-
findings = nextLine;
|
|
286
|
-
} else if (currentQuestion && !currentQuestion.response) {
|
|
287
|
-
// This is a response to the current question
|
|
288
|
-
currentQuestion.response = nextLine;
|
|
289
|
-
} else {
|
|
290
|
-
// Description line
|
|
291
|
-
details.push(nextLine);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
else if (nextLine && !nextLine.startsWith('PACKAGE:') && !nextLine.startsWith('**')) {
|
|
295
|
-
// Description line
|
|
296
|
-
details.push(nextLine);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Save last question if exists
|
|
301
|
-
if (currentQuestion) {
|
|
302
|
-
questions.push(currentQuestion);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
requirements.push({ title, details, pkg, questions, findings, lineIndex: i });
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
return requirements;
|
|
310
|
-
};
|
|
311
|
-
|
|
312
|
-
// Load all sections upfront to show counts immediately
|
|
313
|
-
tree.todoReqs = await loadSection('todo', '⏳ Requirements not yet completed');
|
|
314
|
-
tree.verifyReqs = await loadSection('verify', '✅ Verified by AI screenshot');
|
|
315
|
-
tree.clarificationReqs = await loadClarification();
|
|
316
|
-
tree.verifiedReqs = await loadVerified();
|
|
317
|
-
tree.recycledReqs = await loadSection('recycled', '♻️ Recycled');
|
|
318
|
-
|
|
319
|
-
let inTree = true;
|
|
320
|
-
await buildTree();
|
|
321
|
-
|
|
322
|
-
while (inTree) {
|
|
323
|
-
console.clear();
|
|
324
|
-
console.log(chalk.bold.cyan('\n📋 ' + t('requirements.navigator.title') + '\n'));
|
|
325
|
-
console.log(chalk.gray(t('requirements.navigator.instructions') + '\n'));
|
|
326
|
-
|
|
327
|
-
// Safety check: ensure tree.selected is within bounds
|
|
328
|
-
if (tree.items.length === 0) {
|
|
329
|
-
console.log(chalk.yellow('No items to display.'));
|
|
330
|
-
const inquirer = require('inquirer');
|
|
331
|
-
await inquirer.prompt([{
|
|
332
|
-
type: 'input',
|
|
333
|
-
name: 'continue',
|
|
334
|
-
message: `${t('interactive.press.any.key.return')}`
|
|
335
|
-
}]);
|
|
336
|
-
inTree = false;
|
|
337
|
-
continue;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
if (tree.selected >= tree.items.length) {
|
|
341
|
-
tree.selected = tree.items.length - 1;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
if (tree.selected < 0) {
|
|
345
|
-
tree.selected = 0;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// Calculate window for scrolling (show max 20 items at a time)
|
|
349
|
-
const maxVisible = 20;
|
|
350
|
-
let startIdx = 0;
|
|
351
|
-
let endIdx = tree.items.length;
|
|
352
|
-
|
|
353
|
-
if (tree.items.length > maxVisible) {
|
|
354
|
-
// Center the selected item in the window
|
|
355
|
-
startIdx = Math.max(0, tree.selected - Math.floor(maxVisible / 2));
|
|
356
|
-
endIdx = Math.min(tree.items.length, startIdx + maxVisible);
|
|
357
|
-
|
|
358
|
-
// Adjust if we're near the end
|
|
359
|
-
if (endIdx - startIdx < maxVisible) {
|
|
360
|
-
startIdx = Math.max(0, endIdx - maxVisible);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// Show indicator if there are items above
|
|
365
|
-
if (startIdx > 0) {
|
|
366
|
-
console.log(chalk.gray(` ↑ ${startIdx} more above...`));
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// Display visible tree items
|
|
370
|
-
for (let idx = startIdx; idx < endIdx; idx++) {
|
|
371
|
-
const item = tree.items[idx];
|
|
372
|
-
const indent = ' '.repeat(item.level);
|
|
373
|
-
const arrow = tree.expanded[item.key] ? '▼' : (item.type === 'section' ? '▶' : ' ');
|
|
374
|
-
const prefix = item.type === 'section' || item.type === 'root' ? arrow + ' ' : ' ';
|
|
375
|
-
const selected = idx === tree.selected ? chalk.cyan('❯ ') : ' ';
|
|
376
|
-
|
|
377
|
-
// Truncate long labels to fit terminal width (max 120 chars)
|
|
378
|
-
const maxLabelWidth = 120;
|
|
379
|
-
let label = item.label;
|
|
380
|
-
if (label.length > maxLabelWidth) {
|
|
381
|
-
label = label.substring(0, maxLabelWidth - 3) + '...';
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
console.log(selected + indent + prefix + (idx === tree.selected ? chalk.cyan(label) : chalk.gray(label)));
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
// Show indicator if there are items below
|
|
388
|
-
if (endIdx < tree.items.length) {
|
|
389
|
-
console.log(chalk.gray(` ↓ ${tree.items.length - endIdx} more below...`));
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
console.log();
|
|
393
|
-
|
|
394
|
-
// Handle input
|
|
395
|
-
const key = await new Promise((resolve) => {
|
|
396
|
-
readline.emitKeypressEvents(process.stdin);
|
|
397
|
-
if (process.stdin.isTTY) {
|
|
398
|
-
process.stdin.setRawMode(true);
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
const handler = (str, key) => {
|
|
402
|
-
process.stdin.removeListener('keypress', handler);
|
|
403
|
-
if (process.stdin.isTTY) {
|
|
404
|
-
process.stdin.setRawMode(false);
|
|
405
|
-
}
|
|
406
|
-
resolve(key);
|
|
407
|
-
};
|
|
408
|
-
|
|
409
|
-
process.stdin.on('keypress', handler);
|
|
410
|
-
process.stdin.resume();
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
if (!key) continue;
|
|
414
|
-
|
|
415
|
-
// Handle key presses
|
|
416
|
-
if (key.ctrl && key.name === 'c') {
|
|
417
|
-
// Ctrl+C always exits immediately
|
|
418
|
-
process.exit(0);
|
|
419
|
-
} else if (key.name === 'x' || key.name === 'escape') {
|
|
420
|
-
// X or ESC key - exit CLI with confirmation
|
|
421
|
-
await confirmAndExit();
|
|
422
|
-
} else if (key.name === 'left') {
|
|
423
|
-
const current = tree.items[tree.selected];
|
|
424
|
-
if (!current) continue; // Safety check
|
|
425
|
-
|
|
426
|
-
if (tree.expanded[current.key]) {
|
|
427
|
-
// Collapse expanded section
|
|
428
|
-
tree.expanded[current.key] = false;
|
|
429
|
-
await buildTree();
|
|
430
|
-
} else if (current.level > 0) {
|
|
431
|
-
// Go to parent
|
|
432
|
-
for (let i = tree.selected - 1; i >= 0; i--) {
|
|
433
|
-
if (tree.items[i].level < current.level) {
|
|
434
|
-
tree.selected = i;
|
|
435
|
-
break;
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
} else {
|
|
439
|
-
// At root level, go back to main menu
|
|
440
|
-
inTree = false;
|
|
441
|
-
}
|
|
442
|
-
} else if (key.name === 'k' || key.name === 'up') {
|
|
443
|
-
tree.selected = Math.max(0, tree.selected - 1);
|
|
444
|
-
await buildTree();
|
|
445
|
-
} else if (key.name === 'j' || key.name === 'down') {
|
|
446
|
-
tree.selected = Math.min(tree.items.length - 1, tree.selected + 1);
|
|
447
|
-
await buildTree();
|
|
448
|
-
} else if (key.name === 'right' || key.name === 'return' || key.name === 'space') {
|
|
449
|
-
const current = tree.items[tree.selected];
|
|
450
|
-
if (!current) continue; // Safety check
|
|
451
|
-
if (current.type === 'section') {
|
|
452
|
-
if (!tree.expanded[current.key]) {
|
|
453
|
-
tree.expanded[current.key] = true;
|
|
454
|
-
// Load requirements for this section
|
|
455
|
-
if (current.key === 'todo') {
|
|
456
|
-
tree.todoReqs = await loadSection(current.key, current.section);
|
|
457
|
-
} else if (current.key === 'verify') {
|
|
458
|
-
tree.verifyReqs = await loadSection(current.key, current.section);
|
|
459
|
-
} else if (current.key === 'verified') {
|
|
460
|
-
tree.verifiedReqs = await loadVerified();
|
|
461
|
-
} else if (current.key === 'recycled') {
|
|
462
|
-
tree.recycledReqs = await loadSection(current.key, current.section);
|
|
463
|
-
}
|
|
464
|
-
await buildTree();
|
|
465
|
-
} else {
|
|
466
|
-
tree.expanded[current.key] = false;
|
|
467
|
-
await buildTree();
|
|
468
|
-
}
|
|
469
|
-
} else if (current.type === 'requirement') {
|
|
470
|
-
// Show requirement actions
|
|
471
|
-
await showRequirementActions(current.req, current.sectionKey, tree);
|
|
472
|
-
await buildTree();
|
|
473
|
-
} else if (current.type === 'clarification') {
|
|
474
|
-
// Show clarification requirement with questions
|
|
475
|
-
await showClarificationActions(current.req, tree, loadClarification);
|
|
476
|
-
await buildTree();
|
|
477
|
-
} else if (current.type === 'verified') {
|
|
478
|
-
// Show verified item details (read-only)
|
|
479
|
-
console.clear();
|
|
480
|
-
console.log(chalk.bold.green(`\n${current.label}\n`));
|
|
481
|
-
console.log(chalk.gray('(From CHANGELOG.md - read only)'));
|
|
482
|
-
console.log(chalk.gray(`\n${t('interactive.press.any.key.back')}`));
|
|
483
|
-
await new Promise((resolve) => {
|
|
484
|
-
readline.emitKeypressEvents(process.stdin);
|
|
485
|
-
if (process.stdin.isTTY) {
|
|
486
|
-
process.stdin.setRawMode(true);
|
|
487
|
-
}
|
|
488
|
-
const handler = (str, key) => {
|
|
489
|
-
process.stdin.removeListener('keypress', handler);
|
|
490
|
-
if (process.stdin.isTTY) {
|
|
491
|
-
process.stdin.setRawMode(false);
|
|
492
|
-
}
|
|
493
|
-
resolve();
|
|
494
|
-
};
|
|
495
|
-
process.stdin.on('keypress', handler);
|
|
496
|
-
process.stdin.resume();
|
|
497
|
-
});
|
|
498
|
-
} else if (current.type === 'add') {
|
|
499
|
-
// Handle add requirement
|
|
500
|
-
await handleAddRequirement(current.key);
|
|
501
|
-
// Reload TODO section
|
|
502
|
-
tree.todoReqs = await loadSection('todo', '⏳ Requirements not yet completed');
|
|
503
|
-
await buildTree();
|
|
504
|
-
}
|
|
505
|
-
} else if (key.name === 'f') {
|
|
506
|
-
// Feedback button ( megaphone 📣 )
|
|
507
|
-
await handleFeedbackSubmission();
|
|
508
|
-
await buildTree();
|
|
509
|
-
} else if (key.name === 'r') {
|
|
510
|
-
const current = tree.items[tree.selected];
|
|
511
|
-
if (!current) continue; // Safety check
|
|
512
|
-
|
|
513
|
-
if (current.type === 'requirement') {
|
|
514
|
-
await deleteRequirement(current.req, current.sectionKey, tree);
|
|
515
|
-
// Reload the section that the requirement was deleted from
|
|
516
|
-
if (current.sectionKey === 'todo') {
|
|
517
|
-
tree.todoReqs = await loadSection('todo', '⏳ Requirements not yet completed');
|
|
518
|
-
} else if (current.sectionKey === 'verify') {
|
|
519
|
-
tree.verifyReqs = await loadSection('verify', '✅ Verified by AI screenshot');
|
|
520
|
-
}
|
|
521
|
-
await buildTree();
|
|
522
|
-
} else if (current.type === 'clarification') {
|
|
523
|
-
await deleteClarification(current.req, tree);
|
|
524
|
-
tree.clarificationReqs = await loadClarification();
|
|
525
|
-
await buildTree();
|
|
526
|
-
} else if (current.type === 'recycled') {
|
|
527
|
-
await permanentlyDeleteRequirement(current.req, current.sectionKey, tree);
|
|
528
|
-
tree.recycledReqs = await loadSection('recycled', '♻️ Recycled');
|
|
529
|
-
await buildTree();
|
|
530
|
-
}
|
|
531
|
-
} else if (key.name === 'j') {
|
|
532
|
-
const current = tree.items[tree.selected];
|
|
533
|
-
if (!current) continue; // Safety check
|
|
534
|
-
|
|
535
|
-
if (current.type === 'requirement') {
|
|
536
|
-
await moveRequirementDown(current.req, current.sectionKey, tree);
|
|
537
|
-
await buildTree();
|
|
538
|
-
// Move selection down to follow the item
|
|
539
|
-
if (tree.selected < tree.items.length - 1) {
|
|
540
|
-
tree.selected++;
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
} else if (key.name === 'k') {
|
|
544
|
-
const current = tree.items[tree.selected];
|
|
545
|
-
if (!current) continue; // Safety check
|
|
546
|
-
|
|
547
|
-
if (current.type === 'requirement') {
|
|
548
|
-
await moveRequirementUp(current.req, current.sectionKey, tree);
|
|
549
|
-
await buildTree();
|
|
550
|
-
// Move selection up to follow the item
|
|
551
|
-
if (tree.selected > 0) {
|
|
552
|
-
tree.selected--;
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
} else if (key.name === 'u') {
|
|
556
|
-
const current = tree.items[tree.selected];
|
|
557
|
-
if (!current) continue; // Safety check
|
|
558
|
-
|
|
559
|
-
if (current.type === 'requirement') {
|
|
560
|
-
await promoteRequirement(current.req, current.sectionKey, tree, loadSection, loadVerified);
|
|
561
|
-
await buildTree();
|
|
562
|
-
}
|
|
563
|
-
} else if (key.name === 'd') {
|
|
564
|
-
const current = tree.items[tree.selected];
|
|
565
|
-
if (!current) continue; // Safety check
|
|
566
|
-
|
|
567
|
-
if (current.type === 'clarification') {
|
|
568
|
-
// D on clarification item = Move to TODO
|
|
569
|
-
await moveClarificationToTodo(current.req, tree);
|
|
570
|
-
tree.clarificationReqs = await loadClarification();
|
|
571
|
-
tree.todoReqs = await loadSection('todo', '⏳ Requirements not yet completed');
|
|
572
|
-
await buildTree();
|
|
573
|
-
} else if (current.type === 'requirement' || current.type === 'verified') {
|
|
574
|
-
const sectionKey = current.type === 'verified' ? 'verified' : current.sectionKey;
|
|
575
|
-
const reqTitle = current.type === 'verified' ? current.label : current.req.title;
|
|
576
|
-
await demoteRequirement(reqTitle, sectionKey, tree, loadSection, loadVerified);
|
|
577
|
-
await buildTree();
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
process.stdin.pause();
|
|
583
|
-
}
|
|
1
|
+
// Re-export from modular implementation
|
|
2
|
+
const { showRequirementsTree } = require('./requirements-navigator/index');
|
|
584
3
|
|
|
585
4
|
module.exports = {
|
|
586
5
|
showRequirementsTree
|