vibecodingmachine-cli 2025.12.6-1702 → 2025.12.22-2230
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/vibecodingmachine.js +209 -8
- package/package.json +2 -2
- package/reproduce_issue.js +160 -0
- package/src/commands/auth.js +0 -1
- package/src/commands/auto-direct.js +55 -40
- package/src/commands/auto.js +154 -57
- package/src/commands/computers.js +4 -4
- package/src/commands/repo.js +0 -1
- package/src/commands/requirements-remote.js +10 -6
- package/src/commands/requirements.js +29 -3
- package/src/commands/status.js +0 -1
- package/src/commands/sync.js +4 -4
- package/src/utils/agent-selector.js +50 -0
- package/src/utils/antigravity-installer.js +212 -0
- package/src/utils/antigravity-js-handler.js +60 -0
- package/src/utils/asset-cleanup.js +0 -1
- package/src/utils/auth.js +149 -2
- package/src/utils/auto-mode-ansi-ui.js +0 -1
- package/src/utils/auto-mode-simple-ui.js +1 -1
- package/src/utils/compliance-check.js +166 -0
- package/src/utils/config.js +27 -1
- package/src/utils/copy-with-progress.js +167 -0
- package/src/utils/download-with-progress.js +84 -0
- package/src/utils/first-run.js +185 -68
- package/src/utils/interactive.js +259 -263
- package/src/utils/kiro-installer.js +56 -24
- package/src/utils/persistent-header.js +1 -3
- package/src/utils/provider-registry.js +5 -4
- package/src/utils/user-tracking.js +300 -0
- package/tests/requirements-navigator-buildtree-await.test.js +28 -0
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
const fs = require('fs-extra');
|
|
2
|
-
const path = require('path');
|
|
3
2
|
const os = require('os');
|
|
4
|
-
const { spawn } = require('child_process');
|
|
5
3
|
const ora = require('ora');
|
|
6
4
|
const chalk = require('chalk');
|
|
7
5
|
|
|
@@ -46,36 +44,47 @@ async function installKiro() {
|
|
|
46
44
|
}
|
|
47
45
|
|
|
48
46
|
try {
|
|
49
|
-
const fetch = require('node-fetch');
|
|
50
47
|
const fs = require('fs-extra');
|
|
51
48
|
const path = require('path');
|
|
52
49
|
const os = require('os');
|
|
53
50
|
const { execSync } = require('child_process');
|
|
54
51
|
|
|
55
52
|
console.log(chalk.cyan(`\n⬇️ Downloading AWS Kiro for macOS (${arch})...`));
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
if (!response.ok) {
|
|
60
|
-
downloadSpinner.fail(`Failed to download: ${response.statusText}`);
|
|
61
|
-
console.log(chalk.red(`\nURL: ${downloadUrl}`));
|
|
62
|
-
throw new Error(`Download failed with status ${response.status}`);
|
|
63
|
-
}
|
|
64
|
-
|
|
53
|
+
const cacheDir = path.join(os.homedir(), '.vibecodingmachine', 'cache');
|
|
54
|
+
await fs.ensureDir(cacheDir);
|
|
55
|
+
const cachedPath = path.join(cacheDir, 'Kiro.dmg');
|
|
65
56
|
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'kiro-install-'));
|
|
66
57
|
const dmgPath = path.join(tempDir, 'Kiro.dmg');
|
|
67
|
-
const
|
|
58
|
+
const { downloadWithProgress } = require('./download-with-progress');
|
|
68
59
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
60
|
+
// Try to reuse cached DMG to avoid re-downloading
|
|
61
|
+
let usedCache = false;
|
|
62
|
+
try {
|
|
63
|
+
if (await fs.pathExists(cachedPath)) {
|
|
64
|
+
try {
|
|
65
|
+
const fetch = require('node-fetch');
|
|
66
|
+
const head = await fetch(downloadUrl, { method: 'HEAD' });
|
|
67
|
+
const total = Number(head.headers.get('content-length')) || 0;
|
|
68
|
+
const stat = await fs.stat(cachedPath);
|
|
69
|
+
if (total && stat.size === total) {
|
|
70
|
+
await fs.copy(cachedPath, dmgPath);
|
|
71
|
+
usedCache = true;
|
|
72
|
+
console.log(chalk.gray('Using cached Kiro DMG'));
|
|
73
|
+
}
|
|
74
|
+
} catch (e) {
|
|
75
|
+
await fs.copy(cachedPath, dmgPath);
|
|
76
|
+
usedCache = true;
|
|
77
|
+
console.log(chalk.gray('Using cached Kiro DMG (no HEAD)'));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
} catch (e) {
|
|
81
|
+
usedCache = false;
|
|
82
|
+
}
|
|
77
83
|
|
|
78
|
-
|
|
84
|
+
if (!usedCache) {
|
|
85
|
+
await downloadWithProgress(downloadUrl, dmgPath, { label: 'Downloading AWS Kiro...' });
|
|
86
|
+
try { await fs.copy(dmgPath, cachedPath); } catch (e) { /* ignore */ }
|
|
87
|
+
}
|
|
79
88
|
|
|
80
89
|
const installSpinner = ora('Installing AWS Kiro...').start();
|
|
81
90
|
|
|
@@ -104,8 +113,31 @@ async function installKiro() {
|
|
|
104
113
|
installSpinner.text = 'Replacing existing installation...';
|
|
105
114
|
await fs.remove(destinationPath);
|
|
106
115
|
}
|
|
107
|
-
|
|
108
|
-
|
|
116
|
+
// Use robust copy with progress (rsync -> ditto -> node stream)
|
|
117
|
+
try {
|
|
118
|
+
const { copyAppWithProgress } = require('./copy-with-progress');
|
|
119
|
+
installSpinner.stop();
|
|
120
|
+
const ok = await copyAppWithProgress(fullSourcePath, destinationPath, { spinner: installSpinner });
|
|
121
|
+
if (!ok) {
|
|
122
|
+
console.log('Falling back to cp -R for installation...');
|
|
123
|
+
installSpinner.start();
|
|
124
|
+
try {
|
|
125
|
+
execSync(`cp -R "${fullSourcePath}" "/Applications/"`, { stdio: 'inherit' });
|
|
126
|
+
} catch (e) {
|
|
127
|
+
installSpinner.text = 'Copying (fs fallback) to /Applications...';
|
|
128
|
+
await fs.copy(fullSourcePath, destinationPath);
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
installSpinner.start();
|
|
132
|
+
}
|
|
133
|
+
} catch (e) {
|
|
134
|
+
installSpinner.text = 'Copying (cp/fs fallback) to /Applications...';
|
|
135
|
+
try {
|
|
136
|
+
execSync(`cp -R "${fullSourcePath}" "/Applications/"`, { stdio: 'inherit' });
|
|
137
|
+
} catch (err) {
|
|
138
|
+
await fs.copy(fullSourcePath, destinationPath);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
109
141
|
|
|
110
142
|
// Unmount
|
|
111
143
|
installSpinner.text = 'Cleaning up...';
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
const path = require('path');
|
|
2
|
-
const os = require('os');
|
|
3
1
|
const { getAutoConfig, setAutoConfig } = require('./config');
|
|
4
2
|
|
|
5
3
|
const PROVIDER_DEFINITIONS = [
|
|
@@ -9,6 +7,7 @@ const PROVIDER_DEFINITIONS = [
|
|
|
9
7
|
type: 'direct',
|
|
10
8
|
category: 'llm',
|
|
11
9
|
defaultModel: 'llama-3.3-70b-versatile',
|
|
10
|
+
fallbackModels: ['llama-3.1-70b-versatile', 'llama-3.1-8b-instant', 'mixtral-8x7b-32768'],
|
|
12
11
|
configKeys: { apiKey: 'groqApiKey', model: 'groqModel' },
|
|
13
12
|
estimatedSpeed: 15000
|
|
14
13
|
},
|
|
@@ -17,7 +16,8 @@ const PROVIDER_DEFINITIONS = [
|
|
|
17
16
|
name: 'Anthropic (Claude Sonnet 4)',
|
|
18
17
|
type: 'direct',
|
|
19
18
|
category: 'llm',
|
|
20
|
-
defaultModel: 'claude-sonnet-
|
|
19
|
+
defaultModel: 'claude-3-5-sonnet-20241022',
|
|
20
|
+
fallbackModels: ['claude-3-5-haiku-20241022', 'claude-3-opus-20240229'],
|
|
21
21
|
configKeys: { apiKey: 'anthropicApiKey', model: 'anthropicModel' },
|
|
22
22
|
estimatedSpeed: 25000
|
|
23
23
|
},
|
|
@@ -26,7 +26,8 @@ const PROVIDER_DEFINITIONS = [
|
|
|
26
26
|
name: 'AWS Bedrock (Claude)',
|
|
27
27
|
type: 'direct',
|
|
28
28
|
category: 'llm',
|
|
29
|
-
defaultModel: 'anthropic.claude-sonnet-
|
|
29
|
+
defaultModel: 'anthropic.claude-3-5-sonnet-20241022-v2:0',
|
|
30
|
+
fallbackModels: ['anthropic.claude-3-5-haiku-20241022-v1:0', 'anthropic.claude-3-sonnet-20240229-v1:0'],
|
|
30
31
|
configKeys: { region: 'awsRegion', accessKeyId: 'awsAccessKeyId', secretAccessKey: 'awsSecretAccessKey' },
|
|
31
32
|
estimatedSpeed: 40000
|
|
32
33
|
},
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User Tracking Integration for CLI
|
|
3
|
+
*
|
|
4
|
+
* This module integrates the CLI with the enhanced user tracking system,
|
|
5
|
+
* automatically logging user activities and updating usage statistics.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const auth = require('./auth')
|
|
9
|
+
|
|
10
|
+
class CLIUserTracking {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.sessionStartTime = Date.now()
|
|
13
|
+
this.activityBuffer = []
|
|
14
|
+
this.flushInterval = null
|
|
15
|
+
|
|
16
|
+
// Start periodic flush of activity data
|
|
17
|
+
this.startActivityFlush()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Track CLI command execution
|
|
22
|
+
*/
|
|
23
|
+
async trackCommand(command, args = [], metadata = {}) {
|
|
24
|
+
try {
|
|
25
|
+
await auth.trackCLIActivity('command_executed', {
|
|
26
|
+
command,
|
|
27
|
+
args: args.filter(arg => !arg.includes('password') && !arg.includes('token')), // Filter sensitive data
|
|
28
|
+
duration: 0,
|
|
29
|
+
...metadata
|
|
30
|
+
})
|
|
31
|
+
} catch (error) {
|
|
32
|
+
// Silently fail - don't disrupt user experience
|
|
33
|
+
console.debug('Failed to track command:', error.message)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Track requirement operations
|
|
39
|
+
*/
|
|
40
|
+
async trackRequirementActivity(action, requirementData = {}) {
|
|
41
|
+
try {
|
|
42
|
+
await auth.trackCLIActivity('requirement_activity', {
|
|
43
|
+
action, // 'created', 'updated', 'completed', 'verified', etc.
|
|
44
|
+
requirementId: requirementData.id,
|
|
45
|
+
package: requirementData.package,
|
|
46
|
+
duration: 0,
|
|
47
|
+
metadata: {
|
|
48
|
+
hasDescription: !!requirementData.description,
|
|
49
|
+
descriptionLength: requirementData.description?.length || 0,
|
|
50
|
+
timestamp: Date.now()
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.debug('Failed to track requirement activity:', error.message)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Track auto mode sessions
|
|
60
|
+
*/
|
|
61
|
+
async trackAutoModeSession(action, sessionData = {}) {
|
|
62
|
+
try {
|
|
63
|
+
await auth.trackCLIActivity('auto_mode', {
|
|
64
|
+
action, // 'started', 'stopped', 'iteration_completed'
|
|
65
|
+
duration: sessionData.duration || 0,
|
|
66
|
+
metadata: {
|
|
67
|
+
maxIterations: sessionData.maxIterations,
|
|
68
|
+
completedIterations: sessionData.completedIterations,
|
|
69
|
+
ide: sessionData.ide,
|
|
70
|
+
timestamp: Date.now()
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.debug('Failed to track auto mode session:', error.message)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Track IDE interactions
|
|
80
|
+
*/
|
|
81
|
+
async trackIDEInteraction(ide, action, metadata = {}) {
|
|
82
|
+
try {
|
|
83
|
+
await auth.trackCLIActivity('ide_interaction', {
|
|
84
|
+
action, // 'opened', 'sent_message', 'received_response'
|
|
85
|
+
ide,
|
|
86
|
+
duration: metadata.duration || 0,
|
|
87
|
+
metadata: {
|
|
88
|
+
messageLength: metadata.messageLength,
|
|
89
|
+
responseLength: metadata.responseLength,
|
|
90
|
+
success: metadata.success !== false,
|
|
91
|
+
timestamp: Date.now()
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.debug('Failed to track IDE interaction:', error.message)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Track project operations
|
|
101
|
+
*/
|
|
102
|
+
async trackProjectActivity(action, projectData = {}) {
|
|
103
|
+
try {
|
|
104
|
+
await auth.trackCLIActivity('project_activity', {
|
|
105
|
+
action, // 'created', 'opened', 'switched'
|
|
106
|
+
duration: 0,
|
|
107
|
+
metadata: {
|
|
108
|
+
projectPath: projectData.path,
|
|
109
|
+
hasDescription: !!projectData.description,
|
|
110
|
+
techStack: projectData.techStack || [],
|
|
111
|
+
timestamp: Date.now()
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.debug('Failed to track project activity:', error.message)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Track sync operations
|
|
121
|
+
*/
|
|
122
|
+
async trackSyncActivity(action, syncData = {}) {
|
|
123
|
+
try {
|
|
124
|
+
await auth.trackCLIActivity('sync_activity', {
|
|
125
|
+
action, // 'started', 'completed', 'failed'
|
|
126
|
+
duration: syncData.duration || 0,
|
|
127
|
+
metadata: {
|
|
128
|
+
itemsSync: syncData.itemsSync || 0,
|
|
129
|
+
conflicts: syncData.conflicts || 0,
|
|
130
|
+
success: syncData.success !== false,
|
|
131
|
+
timestamp: Date.now()
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.debug('Failed to track sync activity:', error.message)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Track session duration and end session
|
|
141
|
+
*/
|
|
142
|
+
async endSession() {
|
|
143
|
+
try {
|
|
144
|
+
const sessionDuration = Date.now() - this.sessionStartTime
|
|
145
|
+
|
|
146
|
+
await auth.trackCLIActivity('session_ended', {
|
|
147
|
+
duration: Math.round(sessionDuration / 1000), // Convert to seconds
|
|
148
|
+
metadata: {
|
|
149
|
+
sessionStartTime: this.sessionStartTime,
|
|
150
|
+
sessionEndTime: Date.now(),
|
|
151
|
+
totalCommands: this.activityBuffer.length,
|
|
152
|
+
timestamp: Date.now()
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
// Stop activity flushing
|
|
157
|
+
if (this.flushInterval) {
|
|
158
|
+
clearInterval(this.flushInterval)
|
|
159
|
+
}
|
|
160
|
+
} catch (error) {
|
|
161
|
+
console.debug('Failed to track session end:', error.message)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Start periodic flushing of activity data
|
|
167
|
+
*/
|
|
168
|
+
startActivityFlush() {
|
|
169
|
+
// Flush activity buffer every 5 minutes
|
|
170
|
+
this.flushInterval = setInterval(() => {
|
|
171
|
+
this.flushActivityBuffer()
|
|
172
|
+
}, 5 * 60 * 1000)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Flush buffered activity data
|
|
177
|
+
*/
|
|
178
|
+
async flushActivityBuffer() {
|
|
179
|
+
if (this.activityBuffer.length === 0) return
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
// Update user activity timestamp
|
|
183
|
+
const token = await auth.getToken()
|
|
184
|
+
if (token && token.id_token) {
|
|
185
|
+
await auth._updateUserActivity()
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Clear buffer after successful flush
|
|
189
|
+
this.activityBuffer = []
|
|
190
|
+
} catch (error) {
|
|
191
|
+
console.debug('Failed to flush activity buffer:', error.message)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Register project with enhanced tracking
|
|
197
|
+
*/
|
|
198
|
+
async registerProject(projectInfo) {
|
|
199
|
+
try {
|
|
200
|
+
const token = await auth.getToken()
|
|
201
|
+
if (!token || !token.id_token) return null
|
|
202
|
+
|
|
203
|
+
const payload = JSON.parse(Buffer.from(token.id_token.split('.')[1], 'base64').toString())
|
|
204
|
+
|
|
205
|
+
const UserDatabase = require('vibecodingmachine-core/src/database/user-schema')
|
|
206
|
+
const userDb = new UserDatabase()
|
|
207
|
+
|
|
208
|
+
const userId = userDb.generateUserId(payload.email)
|
|
209
|
+
|
|
210
|
+
const project = await userDb.registerProject(userId, {
|
|
211
|
+
name: projectInfo.name || 'Unnamed Project',
|
|
212
|
+
description: projectInfo.description || '',
|
|
213
|
+
path: projectInfo.path || process.cwd(),
|
|
214
|
+
techStack: projectInfo.techStack || [],
|
|
215
|
+
framework: projectInfo.framework || '',
|
|
216
|
+
language: projectInfo.language || '',
|
|
217
|
+
goals: projectInfo.goals || '',
|
|
218
|
+
category: projectInfo.category || 'general'
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
// Track project creation
|
|
222
|
+
await this.trackProjectActivity('created', {
|
|
223
|
+
path: project.path,
|
|
224
|
+
description: project.description,
|
|
225
|
+
techStack: project.techStack
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
return project
|
|
229
|
+
} catch (error) {
|
|
230
|
+
console.debug('Failed to register project:', error.message)
|
|
231
|
+
return null
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Check and prompt for compliance if needed
|
|
237
|
+
*/
|
|
238
|
+
async checkCompliance() {
|
|
239
|
+
try {
|
|
240
|
+
const token = await auth.getToken()
|
|
241
|
+
if (!token || !token.id_token) return { compliant: false }
|
|
242
|
+
|
|
243
|
+
const payload = JSON.parse(Buffer.from(token.id_token.split('.')[1], 'base64').toString())
|
|
244
|
+
|
|
245
|
+
const ComplianceManager = require('vibecodingmachine-core/src/compliance/compliance-manager')
|
|
246
|
+
const compliance = new ComplianceManager()
|
|
247
|
+
|
|
248
|
+
const UserDatabase = require('vibecodingmachine-core/src/database/user-schema')
|
|
249
|
+
const userDb = new UserDatabase()
|
|
250
|
+
const userId = userDb.generateUserId(payload.email)
|
|
251
|
+
|
|
252
|
+
const status = await compliance.checkComplianceStatus(userId)
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
compliant: !status.needsAcknowledgment,
|
|
256
|
+
termsAccepted: status.termsAccepted,
|
|
257
|
+
privacyAccepted: status.privacyAccepted,
|
|
258
|
+
requiredActions: status.requiredActions
|
|
259
|
+
}
|
|
260
|
+
} catch (error) {
|
|
261
|
+
console.debug('Failed to check compliance:', error.message)
|
|
262
|
+
return { compliant: true } // Default to compliant to not block users
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Record compliance acknowledgment
|
|
268
|
+
*/
|
|
269
|
+
async recordCompliance(acknowledgments) {
|
|
270
|
+
try {
|
|
271
|
+
const token = await auth.getToken()
|
|
272
|
+
if (!token || !token.id_token) return false
|
|
273
|
+
|
|
274
|
+
const payload = JSON.parse(Buffer.from(token.id_token.split('.')[1], 'base64').toString())
|
|
275
|
+
|
|
276
|
+
const ComplianceManager = require('vibecodingmachine-core/src/compliance/compliance-manager')
|
|
277
|
+
const compliance = new ComplianceManager()
|
|
278
|
+
|
|
279
|
+
const UserDatabase = require('vibecodingmachine-core/src/database/user-schema')
|
|
280
|
+
const userDb = new UserDatabase()
|
|
281
|
+
const userId = userDb.generateUserId(payload.email)
|
|
282
|
+
|
|
283
|
+
await compliance.recordAcknowledgment(userId, acknowledgments)
|
|
284
|
+
|
|
285
|
+
// Track compliance activity
|
|
286
|
+
await this.trackCommand('compliance_acknowledged', [], {
|
|
287
|
+
termsAccepted: acknowledgments.terms,
|
|
288
|
+
privacyAccepted: acknowledgments.privacy
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
return true
|
|
292
|
+
} catch (error) {
|
|
293
|
+
console.debug('Failed to record compliance:', error.message)
|
|
294
|
+
return false
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Export singleton instance
|
|
300
|
+
module.exports = new CLIUserTracking()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
describe('Requirements Navigator regression: buildTree must be awaited', () => {
|
|
5
|
+
test('interactive.js does not call buildTree() without await', () => {
|
|
6
|
+
const interactivePath = path.join(__dirname, '../src/utils/interactive.js');
|
|
7
|
+
const content = fs.readFileSync(interactivePath, 'utf8');
|
|
8
|
+
const lines = content.split('\n');
|
|
9
|
+
|
|
10
|
+
const offending = [];
|
|
11
|
+
|
|
12
|
+
for (let i = 0; i < lines.length; i++) {
|
|
13
|
+
const line = lines[i];
|
|
14
|
+
if (!line.includes('buildTree();')) continue;
|
|
15
|
+
|
|
16
|
+
const trimmed = line.trim();
|
|
17
|
+
|
|
18
|
+
// Ignore the function definition line(s)
|
|
19
|
+
if (trimmed.startsWith('const buildTree') || trimmed.startsWith('function buildTree')) continue;
|
|
20
|
+
|
|
21
|
+
if (trimmed !== 'await buildTree();') {
|
|
22
|
+
offending.push({ lineNumber: i + 1, line: trimmed });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
expect(offending).toEqual([]);
|
|
27
|
+
});
|
|
28
|
+
});
|