vibecodingmachine-cli 2026.2.26-1752 → 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 +4 -0
- 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
|
@@ -9,8 +9,9 @@ function formatCheckedAt(checkedAt) {
|
|
|
9
9
|
const d = new Date(checkedAt);
|
|
10
10
|
if (isNaN(d.getTime())) return checkedAt;
|
|
11
11
|
const months = ['January','February','March','April','May','June','July','August','September','October','November','December'];
|
|
12
|
-
const timePart = d.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true })
|
|
13
|
-
|
|
12
|
+
const timePart = d.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true });
|
|
13
|
+
const tzPart = d.toLocaleTimeString('en-US', { timeZoneName: 'short' }).split(' ').pop().toUpperCase();
|
|
14
|
+
return `on ${months[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()} at ${timePart} ${tzPart}`;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
module.exports = {
|
|
@@ -104,7 +104,7 @@ async function showProviderManagerMenu() {
|
|
|
104
104
|
console.log(chalk.bold.cyan('⚙ ' + t('provider.title') + '\n'));
|
|
105
105
|
|
|
106
106
|
// Header
|
|
107
|
-
console.log(chalk.gray('Use ↑↓ to move, Space to toggle, </> to reorder (< down, > up), → to configure, !/1 to check agents, X/ESC to exit\n'));
|
|
107
|
+
console.log(chalk.gray('Use ↑↓ to move, Space to toggle, </> to reorder (< down, > up), → to configure, ← to go back, !/1 to check agents, X/ESC to exit\n'));
|
|
108
108
|
|
|
109
109
|
// Display providers
|
|
110
110
|
order.forEach((id, index) => {
|
|
@@ -193,11 +193,12 @@ async function showProviderManagerMenu() {
|
|
|
193
193
|
await saveProviderPreferences({ order, enabled });
|
|
194
194
|
await setProviderCache(Object.fromEntries(installationStatus));
|
|
195
195
|
}
|
|
196
|
-
// Trigger agents check after returning
|
|
196
|
+
// Trigger agents check after returning - only check enabled agents
|
|
197
197
|
setTimeout(() => {
|
|
198
198
|
console.clear();
|
|
199
199
|
console.log(chalk.bold.cyan('🔍 Checking agents...\n'));
|
|
200
|
-
require('../commands/agents-check')
|
|
200
|
+
const { checkAgents } = require('../commands/agents-check');
|
|
201
|
+
checkAgents({ enabledOnly: true, enabledAgents: enabled }).then(() => {
|
|
201
202
|
process.exit(0);
|
|
202
203
|
}).catch((err) => {
|
|
203
204
|
console.log(chalk.red('Agents check error: ' + err.message));
|
|
@@ -224,6 +225,14 @@ async function showProviderManagerMenu() {
|
|
|
224
225
|
break;
|
|
225
226
|
|
|
226
227
|
case 'left':
|
|
228
|
+
// Go back to previous menu
|
|
229
|
+
isMenuActive = false;
|
|
230
|
+
if (dirty) {
|
|
231
|
+
await saveProviderPreferences({ order, enabled });
|
|
232
|
+
await setProviderCache(Object.fromEntries(installationStatus));
|
|
233
|
+
}
|
|
234
|
+
return;
|
|
235
|
+
|
|
227
236
|
case '<':
|
|
228
237
|
// Move selected provider down in order
|
|
229
238
|
if (selectedIndex < order.length - 1) {
|
|
@@ -236,6 +245,22 @@ async function showProviderManagerMenu() {
|
|
|
236
245
|
break;
|
|
237
246
|
|
|
238
247
|
case 'right':
|
|
248
|
+
// Configure selected provider
|
|
249
|
+
const selectedId = order[selectedIndex];
|
|
250
|
+
const def = defMap.get(selectedId);
|
|
251
|
+
if (def && def.configOptions && def.configOptions.length > 0) {
|
|
252
|
+
console.log(chalk.cyan(`\nConfiguring ${getAgentDisplayName(selectedId)}...\n`));
|
|
253
|
+
// Here you would implement configuration logic
|
|
254
|
+
console.log(chalk.gray('Configuration not yet implemented in CLI'));
|
|
255
|
+
const inquirer = require('inquirer');
|
|
256
|
+
await inquirer.prompt([{
|
|
257
|
+
type: 'input',
|
|
258
|
+
name: 'continue',
|
|
259
|
+
message: 'Press Enter to continue...'
|
|
260
|
+
}]);
|
|
261
|
+
}
|
|
262
|
+
break;
|
|
263
|
+
|
|
239
264
|
case '>':
|
|
240
265
|
// Move selected provider up in order
|
|
241
266
|
if (selectedIndex > 0) {
|
|
@@ -254,23 +279,6 @@ async function showProviderManagerMenu() {
|
|
|
254
279
|
dirty = true;
|
|
255
280
|
break;
|
|
256
281
|
|
|
257
|
-
case 'c':
|
|
258
|
-
// Configure selected provider
|
|
259
|
-
const selectedId = order[selectedIndex];
|
|
260
|
-
const def = defMap.get(selectedId);
|
|
261
|
-
if (def && def.configOptions && def.configOptions.length > 0) {
|
|
262
|
-
console.log(chalk.cyan(`\nConfiguring ${getAgentDisplayName(selectedId)}...\n`));
|
|
263
|
-
// Here you would implement configuration logic
|
|
264
|
-
console.log(chalk.gray('Configuration not yet implemented in CLI'));
|
|
265
|
-
const inquirer = require('inquirer');
|
|
266
|
-
await inquirer.prompt([{
|
|
267
|
-
type: 'input',
|
|
268
|
-
name: 'continue',
|
|
269
|
-
message: 'Press Enter to continue...'
|
|
270
|
-
}]);
|
|
271
|
-
}
|
|
272
|
-
break;
|
|
273
|
-
|
|
274
282
|
default:
|
|
275
283
|
return;
|
|
276
284
|
}
|
|
@@ -118,6 +118,14 @@ const PROVIDER_DEFINITIONS = [
|
|
|
118
118
|
defaultModel: 'opencode-cli',
|
|
119
119
|
estimatedSpeed: 40000
|
|
120
120
|
},
|
|
121
|
+
{
|
|
122
|
+
id: 'vscode-copilot-cli',
|
|
123
|
+
name: 'VS Code Copilot CLI',
|
|
124
|
+
type: 'direct',
|
|
125
|
+
category: 'llm',
|
|
126
|
+
defaultModel: 'copilot-cli',
|
|
127
|
+
estimatedSpeed: 45000
|
|
128
|
+
},
|
|
121
129
|
{
|
|
122
130
|
id: 'replit',
|
|
123
131
|
name: 'Replit Agent',
|
|
@@ -166,6 +174,7 @@ function getProviderDisplayName(id) {
|
|
|
166
174
|
'cline': 'ide.agent.cline',
|
|
167
175
|
'claude-code': 'ide.agent.claude.code',
|
|
168
176
|
'opencode': 'ide.agent.opencode',
|
|
177
|
+
'vscode-copilot-cli': 'ide.agent.vscode.copilot.cli',
|
|
169
178
|
'antigravity': 'ide.agent.antigravity',
|
|
170
179
|
'github-copilot': 'ide.agent.github.copilot',
|
|
171
180
|
'amazon-q': 'ide.agent.amazon.q',
|
|
@@ -199,8 +208,8 @@ function mergeProviderPreferences(autoConfig) {
|
|
|
199
208
|
cleanedOrder.push(id);
|
|
200
209
|
}
|
|
201
210
|
if (enabled[id] === undefined) {
|
|
202
|
-
//
|
|
203
|
-
enabled[id] =
|
|
211
|
+
// Default to disabled, not enabled
|
|
212
|
+
enabled[id] = false;
|
|
204
213
|
}
|
|
205
214
|
});
|
|
206
215
|
|
|
@@ -209,7 +218,30 @@ function mergeProviderPreferences(autoConfig) {
|
|
|
209
218
|
|
|
210
219
|
async function getProviderPreferences() {
|
|
211
220
|
const autoConfig = await getAutoConfig();
|
|
212
|
-
return
|
|
221
|
+
// Directly return providerPreferences from auto config if it exists
|
|
222
|
+
if (autoConfig.providerPreferences) {
|
|
223
|
+
const prefs = autoConfig.providerPreferences;
|
|
224
|
+
// Ensure we never return empty preferences
|
|
225
|
+
if (!prefs.order || prefs.order.length === 0) {
|
|
226
|
+
const defaultOrder = getDefaultProviderOrder();
|
|
227
|
+
const defaultEnabled = {};
|
|
228
|
+
defaultOrder.forEach((id, index) => {
|
|
229
|
+
defaultEnabled[id] = false; // Default to disabled, not enabled
|
|
230
|
+
});
|
|
231
|
+
// Auto-fix empty preferences
|
|
232
|
+
await saveProviderPreferences(defaultOrder, defaultEnabled);
|
|
233
|
+
return { order: defaultOrder, enabled: defaultEnabled };
|
|
234
|
+
}
|
|
235
|
+
return prefs;
|
|
236
|
+
}
|
|
237
|
+
// If no preferences exist, create default disabled state
|
|
238
|
+
const defaultOrder = getDefaultProviderOrder();
|
|
239
|
+
const defaultEnabled = {};
|
|
240
|
+
defaultOrder.forEach((id, index) => {
|
|
241
|
+
defaultEnabled[id] = false; // Default to disabled
|
|
242
|
+
});
|
|
243
|
+
await saveProviderPreferences(defaultOrder, defaultEnabled);
|
|
244
|
+
return { order: defaultOrder, enabled: defaultEnabled };
|
|
213
245
|
}
|
|
214
246
|
|
|
215
247
|
async function saveProviderPreferences(order, enabled) {
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const inquirer = require('inquirer');
|
|
3
|
+
const { t } = require('vibecodingmachine-core');
|
|
4
|
+
const { buildTreeStructure } = require('./tree-builder');
|
|
5
|
+
const { loadSection, loadVerified, loadClarification } = require('./section-loader');
|
|
6
|
+
const { renderTree, calculateScrollWindow } = require('./tree-renderer');
|
|
7
|
+
const { readKeypress, handleKeypress } = require('./input-handler');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Tree-style requirements navigator
|
|
11
|
+
*/
|
|
12
|
+
async function showRequirementsTree() {
|
|
13
|
+
console.log(chalk.bold.cyan('\n📋 ' + t('requirements.navigator.title') + '\n'));
|
|
14
|
+
console.log(chalk.gray(t('requirements.navigator.basic.instructions') + '\n'));
|
|
15
|
+
console.log(chalk.gray('💡 Press F for feedback - Share your thoughts anytime\n'));
|
|
16
|
+
|
|
17
|
+
const tree = {
|
|
18
|
+
expanded: { root: true },
|
|
19
|
+
selected: 0,
|
|
20
|
+
items: []
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Build tree structure
|
|
24
|
+
const buildTree = async () => {
|
|
25
|
+
await buildTreeStructure(tree);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Load all sections upfront to show counts immediately
|
|
29
|
+
tree.todoReqs = await loadSection('todo', '⏳ Requirements not yet completed');
|
|
30
|
+
tree.verifyReqs = await loadSection('verify', '✅ Verified by AI screenshot');
|
|
31
|
+
tree.clarificationReqs = await loadClarification();
|
|
32
|
+
tree.verifiedReqs = await loadVerified();
|
|
33
|
+
tree.recycledReqs = await loadSection('recycled', '♻️ Recycled');
|
|
34
|
+
|
|
35
|
+
let inTree = true;
|
|
36
|
+
await buildTree();
|
|
37
|
+
|
|
38
|
+
const loaders = { loadSection, loadVerified, loadClarification };
|
|
39
|
+
|
|
40
|
+
while (inTree) {
|
|
41
|
+
console.clear();
|
|
42
|
+
console.log(chalk.bold.cyan('\n📋 ' + t('requirements.navigator.title') + '\n'));
|
|
43
|
+
console.log(chalk.gray(t('requirements.navigator.instructions') + '\n'));
|
|
44
|
+
|
|
45
|
+
// Safety check: ensure tree.selected is within bounds
|
|
46
|
+
if (tree.items.length === 0) {
|
|
47
|
+
console.log(chalk.yellow('No items to display.'));
|
|
48
|
+
await inquirer.prompt([{
|
|
49
|
+
type: 'input',
|
|
50
|
+
name: 'continue',
|
|
51
|
+
message: `${t('interactive.press.any.key.return')}`
|
|
52
|
+
}]);
|
|
53
|
+
inTree = false;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (tree.selected >= tree.items.length) {
|
|
58
|
+
tree.selected = tree.items.length - 1;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (tree.selected < 0) {
|
|
62
|
+
tree.selected = 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Calculate window for scrolling (show max 20 items at a time)
|
|
66
|
+
const { startIdx, endIdx } = calculateScrollWindow(tree, 20);
|
|
67
|
+
|
|
68
|
+
// Show indicator if there are items above
|
|
69
|
+
if (startIdx > 0) {
|
|
70
|
+
console.log(chalk.gray(` ↑ ${startIdx} more above...`));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Display visible tree items
|
|
74
|
+
renderTree(tree, startIdx, endIdx);
|
|
75
|
+
|
|
76
|
+
// Show indicator if there are items below
|
|
77
|
+
if (endIdx < tree.items.length) {
|
|
78
|
+
console.log(chalk.gray(` ↓ ${tree.items.length - endIdx} more below...`));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
console.log();
|
|
82
|
+
|
|
83
|
+
// Handle input
|
|
84
|
+
const key = await readKeypress();
|
|
85
|
+
const result = await handleKeypress(key, tree, loaders, buildTree);
|
|
86
|
+
inTree = result.inTree;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
process.stdin.pause();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = {
|
|
93
|
+
showRequirementsTree
|
|
94
|
+
};
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
const readline = require('readline');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const { t } = require('vibecodingmachine-core');
|
|
4
|
+
const {
|
|
5
|
+
confirmAndExit,
|
|
6
|
+
showRequirementActions,
|
|
7
|
+
showClarificationActions,
|
|
8
|
+
handleAddRequirement,
|
|
9
|
+
deleteRequirement,
|
|
10
|
+
deleteClarification,
|
|
11
|
+
permanentlyDeleteRequirement,
|
|
12
|
+
moveRequirementDown,
|
|
13
|
+
moveRequirementUp,
|
|
14
|
+
promoteRequirement,
|
|
15
|
+
demoteRequirement,
|
|
16
|
+
moveClarificationToTodo,
|
|
17
|
+
handleFeedbackSubmission
|
|
18
|
+
} = require('../requirement-actions');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Reads a single keypress from user
|
|
22
|
+
*/
|
|
23
|
+
async function readKeypress() {
|
|
24
|
+
return new Promise((resolve) => {
|
|
25
|
+
readline.emitKeypressEvents(process.stdin);
|
|
26
|
+
if (process.stdin.isTTY) {
|
|
27
|
+
process.stdin.setRawMode(true);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const handler = (str, key) => {
|
|
31
|
+
process.stdin.removeListener('keypress', handler);
|
|
32
|
+
if (process.stdin.isTTY) {
|
|
33
|
+
process.stdin.setRawMode(false);
|
|
34
|
+
}
|
|
35
|
+
resolve(key);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
process.stdin.on('keypress', handler);
|
|
39
|
+
process.stdin.resume();
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Handles key input and performs actions
|
|
45
|
+
*/
|
|
46
|
+
async function handleKeypress(key, tree, loaders, buildTree) {
|
|
47
|
+
const { loadSection, loadVerified, loadClarification } = loaders;
|
|
48
|
+
|
|
49
|
+
if (!key) return { inTree: true };
|
|
50
|
+
|
|
51
|
+
// Handle key presses
|
|
52
|
+
if (key.ctrl && key.name === 'c') {
|
|
53
|
+
// Ctrl+C always exits immediately
|
|
54
|
+
process.exit(0);
|
|
55
|
+
} else if (key.name === 'x' || key.name === 'escape') {
|
|
56
|
+
// X or ESC key - exit CLI with confirmation
|
|
57
|
+
await confirmAndExit();
|
|
58
|
+
} else if (key.name === 'left') {
|
|
59
|
+
const current = tree.items[tree.selected];
|
|
60
|
+
if (!current) return { inTree: true }; // Safety check
|
|
61
|
+
|
|
62
|
+
if (tree.expanded[current.key]) {
|
|
63
|
+
// Collapse expanded section
|
|
64
|
+
tree.expanded[current.key] = false;
|
|
65
|
+
await buildTree();
|
|
66
|
+
} else if (current.level > 0) {
|
|
67
|
+
// Go to parent
|
|
68
|
+
for (let i = tree.selected - 1; i >= 0; i--) {
|
|
69
|
+
if (tree.items[i].level < current.level) {
|
|
70
|
+
tree.selected = i;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
// At root level, go back to main menu
|
|
76
|
+
return { inTree: false };
|
|
77
|
+
}
|
|
78
|
+
} else if (key.name === 'k' || key.name === 'up') {
|
|
79
|
+
tree.selected = Math.max(0, tree.selected - 1);
|
|
80
|
+
await buildTree();
|
|
81
|
+
} else if (key.name === 'j' || key.name === 'down') {
|
|
82
|
+
tree.selected = Math.min(tree.items.length - 1, tree.selected + 1);
|
|
83
|
+
await buildTree();
|
|
84
|
+
} else if (key.name === 'right' || key.name === 'return' || key.name === 'space') {
|
|
85
|
+
await handleSelectAction(tree, loaders, buildTree);
|
|
86
|
+
} else if (key.name === 'f') {
|
|
87
|
+
// Feedback button ( megaphone 📣 )
|
|
88
|
+
await handleFeedbackSubmission();
|
|
89
|
+
await buildTree();
|
|
90
|
+
} else if (key.name === 'r') {
|
|
91
|
+
await handleRecycleAction(tree, loaders, buildTree);
|
|
92
|
+
} else if (key.name === 'u') {
|
|
93
|
+
const current = tree.items[tree.selected];
|
|
94
|
+
if (!current) return { inTree: true }; // Safety check
|
|
95
|
+
|
|
96
|
+
if (current.type === 'requirement') {
|
|
97
|
+
await promoteRequirement(current.req, current.sectionKey, tree, loadSection, loadVerified);
|
|
98
|
+
await buildTree();
|
|
99
|
+
}
|
|
100
|
+
} else if (key.name === 'd') {
|
|
101
|
+
await handleDemoteAction(tree, loaders, buildTree);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return { inTree: true };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Handles select/enter action on tree items
|
|
109
|
+
*/
|
|
110
|
+
async function handleSelectAction(tree, loaders, buildTree) {
|
|
111
|
+
const { loadSection, loadVerified, loadClarification } = loaders;
|
|
112
|
+
const current = tree.items[tree.selected];
|
|
113
|
+
if (!current) return; // Safety check
|
|
114
|
+
|
|
115
|
+
if (current.type === 'section') {
|
|
116
|
+
if (!tree.expanded[current.key]) {
|
|
117
|
+
tree.expanded[current.key] = true;
|
|
118
|
+
// Load requirements for this section
|
|
119
|
+
if (current.key === 'todo') {
|
|
120
|
+
tree.todoReqs = await loadSection(current.key, current.section);
|
|
121
|
+
} else if (current.key === 'verify') {
|
|
122
|
+
tree.verifyReqs = await loadSection(current.key, current.section);
|
|
123
|
+
} else if (current.key === 'verified') {
|
|
124
|
+
tree.verifiedReqs = await loadVerified();
|
|
125
|
+
} else if (current.key === 'recycled') {
|
|
126
|
+
tree.recycledReqs = await loadSection(current.key, current.section);
|
|
127
|
+
}
|
|
128
|
+
await buildTree();
|
|
129
|
+
} else {
|
|
130
|
+
tree.expanded[current.key] = false;
|
|
131
|
+
await buildTree();
|
|
132
|
+
}
|
|
133
|
+
} else if (current.type === 'requirement') {
|
|
134
|
+
// Show requirement actions
|
|
135
|
+
await showRequirementActions(current.req, current.sectionKey, tree);
|
|
136
|
+
await buildTree();
|
|
137
|
+
} else if (current.type === 'clarification') {
|
|
138
|
+
// Show clarification requirement with questions
|
|
139
|
+
await showClarificationActions(current.req, tree, loadClarification);
|
|
140
|
+
await buildTree();
|
|
141
|
+
} else if (current.type === 'verified') {
|
|
142
|
+
// Show verified item details (read-only)
|
|
143
|
+
await showVerifiedDetails(current);
|
|
144
|
+
} else if (current.type === 'add') {
|
|
145
|
+
// Handle add requirement
|
|
146
|
+
await handleAddRequirement(current.key);
|
|
147
|
+
// Reload TODO section
|
|
148
|
+
tree.todoReqs = await loadSection('todo', '⏳ Requirements not yet completed');
|
|
149
|
+
await buildTree();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Shows verified requirement details
|
|
155
|
+
*/
|
|
156
|
+
async function showVerifiedDetails(item) {
|
|
157
|
+
console.clear();
|
|
158
|
+
console.log(chalk.bold.green(`\n${item.label}\n`));
|
|
159
|
+
console.log(chalk.gray('(From CHANGELOG.md - read only)'));
|
|
160
|
+
console.log(chalk.gray(`\n${t('interactive.press.any.key.back')}`));
|
|
161
|
+
await readKeypress();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Handles recycle action
|
|
166
|
+
*/
|
|
167
|
+
async function handleRecycleAction(tree, loaders, buildTree) {
|
|
168
|
+
const { loadSection, loadClarification } = loaders;
|
|
169
|
+
const current = tree.items[tree.selected];
|
|
170
|
+
if (!current) return; // Safety check
|
|
171
|
+
|
|
172
|
+
if (current.type === 'requirement') {
|
|
173
|
+
await deleteRequirement(current.req, current.sectionKey, tree);
|
|
174
|
+
// Reload the section that the requirement was deleted from
|
|
175
|
+
if (current.sectionKey === 'todo') {
|
|
176
|
+
tree.todoReqs = await loadSection('todo', '⏳ Requirements not yet completed');
|
|
177
|
+
} else if (current.sectionKey === 'verify') {
|
|
178
|
+
tree.verifyReqs = await loadSection('verify', '✅ Verified by AI screenshot');
|
|
179
|
+
}
|
|
180
|
+
await buildTree();
|
|
181
|
+
} else if (current.type === 'clarification') {
|
|
182
|
+
await deleteClarification(current.req, tree);
|
|
183
|
+
tree.clarificationReqs = await loadClarification();
|
|
184
|
+
await buildTree();
|
|
185
|
+
} else if (current.type === 'recycled') {
|
|
186
|
+
await permanentlyDeleteRequirement(current.req, current.sectionKey, tree);
|
|
187
|
+
tree.recycledReqs = await loadSection('recycled', '♻️ Recycled');
|
|
188
|
+
await buildTree();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Handles demote action
|
|
194
|
+
*/
|
|
195
|
+
async function handleDemoteAction(tree, loaders, buildTree) {
|
|
196
|
+
const { loadSection, loadVerified, loadClarification } = loaders;
|
|
197
|
+
const current = tree.items[tree.selected];
|
|
198
|
+
if (!current) return; // Safety check
|
|
199
|
+
|
|
200
|
+
if (current.type === 'clarification') {
|
|
201
|
+
// D on clarification item = Move to TODO
|
|
202
|
+
await moveClarificationToTodo(current.req, tree);
|
|
203
|
+
tree.clarificationReqs = await loadClarification();
|
|
204
|
+
tree.todoReqs = await loadSection('todo', '⏳ Requirements not yet completed');
|
|
205
|
+
await buildTree();
|
|
206
|
+
} else if (current.type === 'requirement' || current.type === 'verified') {
|
|
207
|
+
const sectionKey = current.type === 'verified' ? 'verified' : current.sectionKey;
|
|
208
|
+
const reqTitle = current.type === 'verified' ? current.label : current.req.title;
|
|
209
|
+
await demoteRequirement(reqTitle, sectionKey, tree, loadSection, loadVerified);
|
|
210
|
+
await buildTree();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
module.exports = {
|
|
215
|
+
readKeypress,
|
|
216
|
+
handleKeypress
|
|
217
|
+
};
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { getRequirementsPath, getVibeCodingMachineDir, checkVibeCodingMachineExists } = require('vibecodingmachine-core');
|
|
4
|
+
const { parseRequirementsFromContent } = require('../requirements-parser');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Load requirements for a section
|
|
8
|
+
*/
|
|
9
|
+
async function loadSection(sectionKey, sectionTitle) {
|
|
10
|
+
const reqPath = await getRequirementsPath();
|
|
11
|
+
|
|
12
|
+
if (!reqPath || !await fs.pathExists(reqPath)) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const content = await fs.readFile(reqPath, 'utf8');
|
|
17
|
+
|
|
18
|
+
// Delegate to reusable parser
|
|
19
|
+
const allReqs = parseRequirementsFromContent(content, sectionKey, sectionTitle);
|
|
20
|
+
|
|
21
|
+
// For TODO section, only show primary heading requirements (those marked from '###' titles)
|
|
22
|
+
if (sectionKey === 'todo') return allReqs.filter(r => r.source === 'heading');
|
|
23
|
+
|
|
24
|
+
return allReqs;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Load VERIFIED requirements from CHANGELOG
|
|
29
|
+
*/
|
|
30
|
+
async function loadVerified() {
|
|
31
|
+
const allnightStatus = await checkVibeCodingMachineExists();
|
|
32
|
+
let changelogPath;
|
|
33
|
+
|
|
34
|
+
if (allnightStatus.insideExists) {
|
|
35
|
+
const allnightDir = await getVibeCodingMachineDir();
|
|
36
|
+
changelogPath = path.join(path.dirname(allnightDir), 'CHANGELOG.md');
|
|
37
|
+
} else if (allnightStatus.siblingExists) {
|
|
38
|
+
changelogPath = path.join(process.cwd(), 'CHANGELOG.md');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!changelogPath || !await fs.pathExists(changelogPath)) {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const content = await fs.readFile(changelogPath, 'utf8');
|
|
46
|
+
const lines = content.split('\n');
|
|
47
|
+
const requirements = [];
|
|
48
|
+
let inVerifiedSection = false;
|
|
49
|
+
|
|
50
|
+
for (const line of lines) {
|
|
51
|
+
const trimmed = line.trim();
|
|
52
|
+
|
|
53
|
+
// Check for Verified Requirements section
|
|
54
|
+
if (trimmed.includes('## Verified Requirements')) {
|
|
55
|
+
inVerifiedSection = true;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Exit section if we hit another ## header
|
|
60
|
+
if (inVerifiedSection && trimmed.startsWith('##') && !trimmed.includes('Verified Requirements')) {
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Only collect items from within the Verified Requirements section
|
|
65
|
+
if (inVerifiedSection && trimmed.startsWith('- ') && trimmed.length > 10) {
|
|
66
|
+
requirements.push(trimmed.substring(2));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return requirements;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Load clarification requirements with questions
|
|
75
|
+
*/
|
|
76
|
+
async function loadClarification() {
|
|
77
|
+
const reqPath = await getRequirementsPath();
|
|
78
|
+
|
|
79
|
+
if (!reqPath || !await fs.pathExists(reqPath)) {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const content = await fs.readFile(reqPath, 'utf8');
|
|
84
|
+
const lines = content.split('\n');
|
|
85
|
+
|
|
86
|
+
let inSection = false;
|
|
87
|
+
const requirements = [];
|
|
88
|
+
|
|
89
|
+
for (let i = 0; i < lines.length; i++) {
|
|
90
|
+
const line = lines[i];
|
|
91
|
+
const trimmed = line.trim();
|
|
92
|
+
|
|
93
|
+
// Check if we're entering the clarification section
|
|
94
|
+
if (trimmed.includes('❓ Requirements needing manual feedback')) {
|
|
95
|
+
inSection = true;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Check if we're leaving the section (hit another ## section)
|
|
100
|
+
if (inSection && trimmed.startsWith('##') && !trimmed.startsWith('###')) {
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Read requirements in new format (### header)
|
|
105
|
+
if (inSection && trimmed.startsWith('###')) {
|
|
106
|
+
const title = trimmed.replace(/^###\s*/, '').trim();
|
|
107
|
+
|
|
108
|
+
// Skip empty titles
|
|
109
|
+
if (!title || title.length === 0) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const details = [];
|
|
114
|
+
const questions = [];
|
|
115
|
+
let pkg = null;
|
|
116
|
+
let findings = null;
|
|
117
|
+
let currentQuestion = null;
|
|
118
|
+
|
|
119
|
+
// Read package, description, and clarifying questions
|
|
120
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
121
|
+
const nextLine = lines[j].trim();
|
|
122
|
+
|
|
123
|
+
// Stop if we hit another requirement or section
|
|
124
|
+
if (nextLine.startsWith('###') || (nextLine.startsWith('##') && !nextLine.startsWith('###'))) {
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Check for PACKAGE line
|
|
129
|
+
if (nextLine.startsWith('PACKAGE:')) {
|
|
130
|
+
pkg = nextLine.replace(/^PACKAGE:\s*/, '').trim();
|
|
131
|
+
}
|
|
132
|
+
// Check for AI findings
|
|
133
|
+
else if (nextLine.startsWith('**AI found in codebase:**')) {
|
|
134
|
+
// Next line will be the findings
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
else if (nextLine.startsWith('**What went wrong')) {
|
|
138
|
+
// Description line
|
|
139
|
+
details.push(nextLine);
|
|
140
|
+
}
|
|
141
|
+
else if (nextLine.startsWith('**Clarifying questions:**')) {
|
|
142
|
+
// Start of questions section
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
else if (nextLine.match(/^\d+\./)) {
|
|
146
|
+
// Save previous question if exists
|
|
147
|
+
if (currentQuestion) {
|
|
148
|
+
questions.push(currentQuestion);
|
|
149
|
+
}
|
|
150
|
+
// This is a new question
|
|
151
|
+
currentQuestion = { question: nextLine, response: null };
|
|
152
|
+
}
|
|
153
|
+
else if (currentQuestion && nextLine && !nextLine.startsWith('PACKAGE:') && !nextLine.startsWith('**')) {
|
|
154
|
+
// This might be a response to the current question or description/findings
|
|
155
|
+
if (!findings && !currentQuestion.response && questions.length === 0 && !nextLine.match(/^\d+\./)) {
|
|
156
|
+
// This is findings content
|
|
157
|
+
findings = nextLine;
|
|
158
|
+
} else if (currentQuestion && !currentQuestion.response) {
|
|
159
|
+
// This is a response to the current question
|
|
160
|
+
currentQuestion.response = nextLine;
|
|
161
|
+
} else {
|
|
162
|
+
// Description line
|
|
163
|
+
details.push(nextLine);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
else if (nextLine && !nextLine.startsWith('PACKAGE:') && !nextLine.startsWith('**')) {
|
|
167
|
+
// Description line
|
|
168
|
+
details.push(nextLine);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Save last question if exists
|
|
173
|
+
if (currentQuestion) {
|
|
174
|
+
questions.push(currentQuestion);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
requirements.push({ title, details, pkg, questions, findings, lineIndex: i });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return requirements;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
module.exports = {
|
|
185
|
+
loadSection,
|
|
186
|
+
loadVerified,
|
|
187
|
+
loadClarification
|
|
188
|
+
};
|