vibecodingmachine-cli 2025.12.1-534 → 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.
@@ -3,10 +3,12 @@
3
3
  const path = require('path');
4
4
  const fs = require('fs');
5
5
 
6
- // Auto-load .env.auth if it exists
7
- const envAuthPath = path.join(__dirname, '..', '..', '.env.auth');
8
- if (fs.existsSync(envAuthPath)) {
9
- const envContent = fs.readFileSync(envAuthPath, 'utf8');
6
+ // Auto-load .env.cognito from root
7
+ const rootDir = path.join(__dirname, '..', '..', '..');
8
+ const envCognitoPath = path.join(rootDir, '.env.cognito');
9
+
10
+ if (fs.existsSync(envCognitoPath)) {
11
+ const envContent = fs.readFileSync(envCognitoPath, 'utf8');
10
12
  envContent.split('\n').forEach(line => {
11
13
  const trimmed = line.trim();
12
14
  if (trimmed && !trimmed.startsWith('#')) {
@@ -23,10 +25,23 @@ if (fs.existsSync(envAuthPath)) {
23
25
  * "Big Dreams + AI + VibeCodingMachine.com = Your money making apps"
24
26
  */
25
27
 
28
+ const { cleanupBrokenAssets } = require('../src/utils/asset-cleanup');
29
+
30
+ (async () => {
31
+ // mitigate broken assets issue
32
+ await cleanupBrokenAssets();
33
+ })();
34
+
26
35
  const { program } = require('commander');
27
36
  const chalk = require('chalk');
28
37
  const inquirer = require('inquirer');
29
- const { execSync } = require('child_process');
38
+ const { execSync, spawn } = require('child_process');
39
+ const ora = require('ora');
40
+ const https = require('https');
41
+ const os = require('os');
42
+ const { pipeline } = require('stream');
43
+ const { promisify } = require('util');
44
+ const streamPipeline = promisify(pipeline);
30
45
  // const path = require('path'); // Moved to top for .env.auth loading
31
46
  const packageJson = require('../package.json');
32
47
 
@@ -108,6 +123,11 @@ program
108
123
  .option('--no-never-stop', 'Disable never stop mode')
109
124
  .action(autoCommands.config);
110
125
 
126
+ program
127
+ .command('auto:agents')
128
+ .description('List available agents and their quota status')
129
+ .action(autoCommands.listAgents);
130
+
111
131
  // Requirements management commands
112
132
  program
113
133
  .command('req:list')
@@ -140,6 +160,11 @@ program
140
160
  .description('Watch requirements file for changes')
141
161
  .action(reqCommands.watch);
142
162
 
163
+ program
164
+ .command('req:rename <old-title> <new-title> [description...]')
165
+ .description('Rename requirement and optionally update description')
166
+ .action((oldTitle, newTitle, description) => reqCommands.rename(oldTitle, newTitle, description));
167
+
143
168
  // IDE integration commands
144
169
  program
145
170
  .command('ide:list')
@@ -201,6 +226,76 @@ program
201
226
  .description('Check authentication status')
202
227
  .action(authCommands.status);
203
228
 
229
+ // Multi-computer management commands
230
+ const computerCommands = require('../src/commands/computers');
231
+ const remoteReqCommands = require('../src/commands/requirements-remote');
232
+ program
233
+ .command('computers')
234
+ .description('List all registered computers')
235
+ .option('-f, --focus <area>', 'Filter by focus area')
236
+ .option('-s, --status <status>', 'Filter by status (active, idle, error)')
237
+ .action(computerCommands.listComputers);
238
+
239
+ program
240
+ .command('computer:status <computerId>')
241
+ .description('Show detailed status of a specific computer')
242
+ .action(computerCommands.showComputerStatus);
243
+
244
+ program
245
+ .command('computer:register <focusArea>')
246
+ .description('Register current computer with focus area')
247
+ .action(computerCommands.registerComputer);
248
+
249
+ program
250
+ .command('computer:focus [newFocusArea]')
251
+ .description('View or update focus area for current computer')
252
+ .action((newFocusArea) => {
253
+ if (newFocusArea) {
254
+ computerCommands.updateFocus(newFocusArea);
255
+ } else {
256
+ // Show current focus
257
+ computerCommands.showComputerStatus(require('os').hostname());
258
+ }
259
+ });
260
+
261
+ program
262
+ .command('computer:requirements <computerId>')
263
+ .description('View requirements for another computer')
264
+ .action(remoteReqCommands.listRemoteRequirements);
265
+
266
+ program
267
+ .command('computer:add-requirement <computerId> <requirement>')
268
+ .description('Add requirement to another computer')
269
+ .action(remoteReqCommands.addRemoteRequirement);
270
+
271
+ // Sync management commands
272
+ const syncCommands = require('../src/commands/sync');
273
+ program
274
+ .command('sync:now')
275
+ .description('Trigger immediate sync')
276
+ .action(syncCommands.syncNow);
277
+
278
+ program
279
+ .command('sync:status')
280
+ .description('Show sync status and statistics')
281
+ .action(syncCommands.syncStatus);
282
+
283
+ program
284
+ .command('sync:queue')
285
+ .description('View pending changes in offline queue')
286
+ .action(syncCommands.viewQueue);
287
+
288
+ program
289
+ .command('sync:force')
290
+ .description('Force sync even if offline')
291
+ .action(syncCommands.forceSync);
292
+
293
+ program
294
+ .command('sync:history')
295
+ .description('View sync history')
296
+ .option('-n, --limit <number>', 'Number of history entries to show', '50')
297
+ .action(syncCommands.viewHistory);
298
+
204
299
  // Interactive mode
205
300
  program
206
301
  .command('interactive')
@@ -214,18 +309,33 @@ program
214
309
  // Error handling
215
310
  process.on('uncaughtException', (error) => {
216
311
  console.error(chalk.red('Error:'), error.message);
312
+ if (process.env.DEBUG) {
313
+ console.error(chalk.gray('Stack:'), error.stack);
314
+ }
217
315
  process.exit(1);
218
316
  });
219
317
 
220
318
  process.on('unhandledRejection', (error) => {
221
319
  console.error(chalk.red('Error:'), error.message);
320
+ if (process.env.DEBUG) {
321
+ console.error(chalk.gray('Stack:'), error.stack);
322
+ }
222
323
  process.exit(1);
223
324
  });
224
325
 
225
326
  // Check for updates and display notification
226
327
  async function checkForUpdates() {
227
328
  try {
329
+ if (process.env.VCM_SKIP_UPDATE_CHECK === '1' || process.env.VCM_SKIP_UPDATE_CHECK === 'true') {
330
+ return;
331
+ }
228
332
  const { checkForCLIUpdates } = require('vibecodingmachine-core');
333
+ // If running inside the repository (local development), skip update checks entirely
334
+ const isDevWorkspace = fs.existsSync(path.join(rootDir, '.git'));
335
+ if (isDevWorkspace) {
336
+ console.log(chalk.yellow('\nDetected local development workspace; skipping update check.'));
337
+ return;
338
+ }
229
339
  console.log(chalk.gray(`\n🔍 Checking for updates... (current: v${packageJson.version})`));
230
340
  const updateInfo = await checkForCLIUpdates(packageJson.version);
231
341
  console.log(chalk.gray(` Update check result: ${JSON.stringify(updateInfo)}\n`));
@@ -251,14 +361,136 @@ async function checkForUpdates() {
251
361
  if (answer.shouldUpdate) {
252
362
  console.log(chalk.cyan('\n🔄 Updating VibeCodingMachine CLI...\n'));
253
363
  try {
254
- // Run npm install with inherited stdio to show progress
255
- execSync('npm install -g vibecodingmachine-cli@latest', {
256
- stdio: 'inherit',
257
- encoding: 'utf8'
364
+ // If we're running from the repository (local dev), avoid performing a global install
365
+ const isDevWorkspace = fs.existsSync(path.join(rootDir, '.git'));
366
+ if (isDevWorkspace) {
367
+ console.log(chalk.yellow('\nDetected local development workspace; skipping automatic global install.'));
368
+ console.log(chalk.gray(' To update the globally installed CLI run:') + ' ' + chalk.bold.white('npm install -g vibecodingmachine-cli@latest\n'));
369
+ // Do not attempt global install during development to avoid confusing local dev flow
370
+ return;
371
+ }
372
+ const spinner = ora('Fetching package metadata...').start();
373
+
374
+ // Get latest package metadata from npm registry
375
+ const registryUrl = 'https://registry.npmjs.org/vibecodingmachine-cli/latest';
376
+ const meta = await new Promise((resolve, reject) => {
377
+ https.get(registryUrl, (res) => {
378
+ let data = '';
379
+ res.on('data', (chunk) => data += chunk);
380
+ res.on('end', () => {
381
+ try {
382
+ resolve(JSON.parse(data));
383
+ } catch (err) {
384
+ reject(err);
385
+ }
386
+ });
387
+ }).on('error', reject);
388
+ });
389
+
390
+ const tarball = meta && meta.dist && meta.dist.tarball;
391
+ if (!tarball) {
392
+ spinner.fail('Could not determine tarball URL for update');
393
+ console.log(chalk.yellow(' You can manually update with: ') + chalk.bold.white('npm install -g vibecodingmachine-cli@latest\n'));
394
+ return;
395
+ }
396
+
397
+ spinner.text = 'Resolving tarball...';
398
+
399
+ // HEAD to get content-length
400
+ const totalBytes = await new Promise((resolve) => {
401
+ const req = https.request(tarball, { method: 'HEAD' }, (res) => {
402
+ const len = parseInt(res.headers['content-length'] || '0', 10);
403
+ resolve(Number.isFinite(len) ? len : 0);
404
+ });
405
+ req.on('error', () => resolve(0));
406
+ req.end();
258
407
  });
259
- console.log(chalk.green('\n✅ Successfully updated to v' + updateInfo.latestVersion + '!'));
260
- console.log(chalk.gray(' Please restart your command to use the new version.\n'));
261
- process.exit(0);
408
+
409
+ const tmpFile = path.join(os.tmpdir(), `vcm-update-${Date.now()}.tgz`);
410
+
411
+ // Download tarball with progress
412
+ spinner.text = 'Downloading update...';
413
+ // Stop the spinner so we can write a single-line progress indicator without conflicts
414
+ try { spinner.stop(); } catch (e) {}
415
+ // Print initial progress line (clear line first)
416
+ process.stdout.write('\r\x1b[2KDownloading: 0% — 0.0 MB');
417
+ const progressStart = Date.now();
418
+ function formatEta(sec) {
419
+ if (!isFinite(sec) || sec === null) return '--:--';
420
+ const s = Math.max(0, Math.round(sec));
421
+ const m = Math.floor(s / 60);
422
+ const ss = s % 60;
423
+ return `${m}:${ss.toString().padStart(2, '0')}`;
424
+ }
425
+ await new Promise((resolve, reject) => {
426
+ https.get(tarball, (res) => {
427
+ const fileStream = fs.createWriteStream(tmpFile);
428
+ let downloaded = 0;
429
+
430
+ // Print an updating single-line progress indicator (percent + MB)
431
+ let lastPercent = -1;
432
+ let lastMbReported = -1;
433
+ res.on('data', (chunk) => {
434
+ downloaded += chunk.length;
435
+ const percent = totalBytes ? Math.round((downloaded / totalBytes) * 100) : null;
436
+ const mbDownloaded = +(downloaded / (1024 * 1024));
437
+ const mbTotal = totalBytes ? (totalBytes / (1024 * 1024)) : null;
438
+
439
+ // ETA and speed
440
+ const elapsedSec = Math.max(0.001, (Date.now() - progressStart) / 1000);
441
+ const speed = downloaded / elapsedSec; // bytes/sec
442
+ const remaining = totalBytes ? Math.max(0, totalBytes - downloaded) : null;
443
+ const etaSec = remaining && speed > 0 ? remaining / speed : null;
444
+
445
+ // Build simple ASCII progress bar
446
+ const width = 30;
447
+ const fill = percent !== null ? Math.round((percent / 100) * width) : Math.min(width, Math.max(0, Math.round((mbDownloaded / (mbTotal || 1)) * width)));
448
+ const bar = '█'.repeat(fill) + '-'.repeat(Math.max(0, width - fill));
449
+
450
+ const pctText = percent !== null ? `${percent}%` : '--%';
451
+ const mbText = mbTotal ? `${mbDownloaded.toFixed(1)} MB / ${mbTotal.toFixed(1)} MB` : `${mbDownloaded.toFixed(1)} MB`;
452
+ const etaText = etaSec ? formatEta(etaSec) : '--:--';
453
+
454
+ // Update only when percent changes or every 0.5 MB to reduce noise
455
+ const mbReport = Math.floor(mbDownloaded * 2) / 2;
456
+ if ((percent !== null && percent !== lastPercent) || (percent === null && mbReport !== lastMbReported) || Math.random() < 0.001) {
457
+ lastPercent = percent;
458
+ lastMbReported = mbReport;
459
+ process.stdout.write(`\r\x1b[2K[${bar}] ${pctText} ${mbText} ETA: ${etaText}`);
460
+ }
461
+ });
462
+
463
+ res.on('end', () => fileStream.end());
464
+ res.on('error', (err) => reject(err));
465
+
466
+ fileStream.on('finish', () => resolve());
467
+ fileStream.on('error', (err) => reject(err));
468
+
469
+ res.pipe(fileStream);
470
+ }).on('error', reject);
471
+ });
472
+
473
+ // Ensure progress line ends and move to next line
474
+ process.stdout.write('\n');
475
+ spinner.start();
476
+ spinner.succeed('Downloaded update');
477
+
478
+ // Install the downloaded tarball
479
+ spinner.start('Installing update...');
480
+ try {
481
+ execSync(`npm install -g "${tmpFile}"`, { stdio: 'inherit', encoding: 'utf8' });
482
+ spinner.succeed('Installed update');
483
+ console.log(chalk.green('\n✅ Successfully updated to v' + updateInfo.latestVersion + '!'));
484
+ console.log(chalk.gray(' Please restart your command to use the new version.\n'));
485
+ // Cleanup
486
+ try { fs.unlinkSync(tmpFile); } catch (e) {}
487
+ process.exit(0);
488
+ } catch (err) {
489
+ spinner.fail('Installation failed');
490
+ console.log(chalk.red('\n❌ Update failed:'), err.message);
491
+ console.log(chalk.yellow(' You can manually update with: ') + chalk.bold.white('npm install -g vibecodingmachine-cli@latest\n'));
492
+ try { fs.unlinkSync(tmpFile); } catch (e) {}
493
+ }
262
494
  } catch (error) {
263
495
  console.log(chalk.red('\n❌ Update failed:'), error.message);
264
496
  console.log(chalk.yellow(' You can manually update with: ') + chalk.bold.white('npm install -g vibecodingmachine-cli@latest\n'));
@@ -281,6 +513,10 @@ if (!process.argv.slice(2).length) {
281
513
  // Check for updates first
282
514
  await checkForUpdates();
283
515
 
516
+ // Check for first run experience
517
+ const { checkFirstRun } = require('../src/utils/first-run');
518
+ await checkFirstRun();
519
+
284
520
  // Check authentication before allowing interactive mode
285
521
  const auth = require('../src/utils/auth');
286
522
  const isAuth = await auth.isAuthenticated();
@@ -296,6 +532,18 @@ if (!process.argv.slice(2).length) {
296
532
  }
297
533
  }
298
534
 
535
+ // Check compliance after authentication
536
+ const { checkCompliance } = require('../src/utils/compliance-check');
537
+ const isCompliant = await checkCompliance();
538
+
539
+ if (!isCompliant) {
540
+ console.log(chalk.red('\n✗ Compliance check failed. Exiting.\n'));
541
+ process.exit(1);
542
+ }
543
+
544
+ // Track CLI usage - interactive mode
545
+ await auth.trackCLIActivity('interactive_mode_started');
546
+
299
547
  const { startInteractive } = require('../src/utils/interactive');
300
548
  await startInteractive();
301
549
  })();
@@ -303,6 +551,47 @@ if (!process.argv.slice(2).length) {
303
551
  // Check for updates before parsing commands
304
552
  (async () => {
305
553
  await checkForUpdates();
554
+
555
+ // Check authentication for all commands (except auth commands)
556
+ const command = process.argv[2] || 'unknown';
557
+ const authCommands = ['auth:login', 'auth:logout', 'auth:status'];
558
+ const skipAuthCheck = authCommands.includes(command);
559
+
560
+ const auth = require('../src/utils/auth');
561
+
562
+ if (!skipAuthCheck) {
563
+ const isAuth = await auth.isAuthenticated();
564
+
565
+ if (!isAuth) {
566
+ console.log(chalk.cyan('\n🔐 Opening browser for authentication...\n'));
567
+ try {
568
+ await auth.login();
569
+ console.log(chalk.green('\n✓ Authentication successful!\n'));
570
+ } catch (error) {
571
+ console.log(chalk.red('\n✗ Authentication failed:'), error.message);
572
+ process.exit(1);
573
+ }
574
+ }
575
+ }
576
+
577
+ // Check compliance after authentication (skip for auth commands)
578
+ if (!skipAuthCheck) {
579
+ const { checkCompliance } = require('../src/utils/compliance-check');
580
+ const isCompliant = await checkCompliance();
581
+
582
+ if (!isCompliant) {
583
+ console.log(chalk.red('\n✗ Compliance check failed. Exiting.\n'));
584
+ process.exit(1);
585
+ }
586
+
587
+ // Track CLI usage - command execution
588
+ const args = process.argv.slice(3);
589
+ await auth.trackCLIActivity('command_executed', {
590
+ command,
591
+ args: args.filter(arg => !arg.includes('password') && !arg.includes('token'))
592
+ });
593
+ }
594
+
306
595
  // Parse arguments only if commands were provided
307
596
  program.parse(process.argv);
308
597
  })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibecodingmachine-cli",
3
- "version": "2025.12.01-0534",
3
+ "version": "2025.12.22-2230",
4
4
  "description": "Command-line interface for Vibe Coding Machine - Autonomous development",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -25,10 +25,13 @@
25
25
  "author": "Vibe Coding Machine Team",
26
26
  "license": "MIT",
27
27
  "dependencies": {
28
- "vibecodingmachine-core": "^2025.12.01-0534",
28
+ "vibecodingmachine-core": "^2025.12.22-2230",
29
+ "@aws-sdk/client-dynamodb": "^3.600.0",
30
+ "@aws-sdk/lib-dynamodb": "^3.600.0",
29
31
  "boxen": "^5.1.2",
30
32
  "chalk": "^4.1.2",
31
33
  "chokidar": "^3.6.0",
34
+ "cli-table3": "^0.6.3",
32
35
  "commander": "^11.1.0",
33
36
  "fs-extra": "^11.2.0",
34
37
  "ink": "^6.4.0",
package/repro_open.js ADDED
@@ -0,0 +1,13 @@
1
+ try {
2
+ const open = require('open');
3
+ console.log('Type of open:', typeof open);
4
+ console.log('open:', open);
5
+ if (typeof open !== 'function') {
6
+ console.log('Exports:', Object.keys(open));
7
+ if (open.default) {
8
+ console.log('Type of open.default:', typeof open.default);
9
+ }
10
+ }
11
+ } catch (e) {
12
+ console.error('Require failed:', e.message);
13
+ }
@@ -0,0 +1,160 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const { getRequirementsPath } = require('vibecodingmachine-core');
4
+ const { getRepoPath } = require('./src/utils/config');
5
+ const requirements = require('./src/commands/requirements');
6
+
7
+ // Mock specific functions we need from interactive.js's logic
8
+ // helping function to move requirement to recycled (deletion logic)
9
+ async function moveRequirementToRecycled(reqPath, requirementTitle, fromSection) {
10
+ const content = await fs.readFile(reqPath, 'utf8');
11
+ const lines = content.split('\n');
12
+
13
+ let requirementStartIndex = -1;
14
+ let requirementEndIndex = -1;
15
+
16
+ for (let i = 0; i < lines.length; i++) {
17
+ const line = lines[i].trim();
18
+ if (line.startsWith('###')) {
19
+ const title = line.replace(/^###\s*/, '').trim();
20
+ // Logic from interactive.js
21
+ if (title && title.includes(requirementTitle)) {
22
+ requirementStartIndex = i;
23
+ for (let j = i + 1; j < lines.length; j++) {
24
+ const nextLine = lines[j].trim();
25
+ if (nextLine.startsWith('###') || (nextLine.startsWith('##') && !nextLine.startsWith('###'))) {
26
+ requirementEndIndex = j;
27
+ break;
28
+ }
29
+ }
30
+ if (requirementEndIndex === -1) {
31
+ requirementEndIndex = lines.length;
32
+ }
33
+ break;
34
+ }
35
+ }
36
+ }
37
+
38
+ if (requirementStartIndex === -1) {
39
+ console.log('⚠️ Could not find requirement to recycle');
40
+ return false;
41
+ }
42
+
43
+ console.log(`Found requirement at lines ${requirementStartIndex}-${requirementEndIndex}`);
44
+
45
+ const requirementBlock = lines.slice(requirementStartIndex, requirementEndIndex);
46
+ lines.splice(requirementStartIndex, requirementEndIndex - requirementStartIndex);
47
+
48
+ if (requirementStartIndex < lines.length) {
49
+ const nextLine = lines[requirementStartIndex]?.trim();
50
+ const packageNames = ['cli', 'core', 'electron-app', 'web', 'mobile', 'vscode-extension', 'sync-server'];
51
+ if (nextLine && packageNames.includes(nextLine.toLowerCase()) &&
52
+ !nextLine.startsWith('###') && !nextLine.startsWith('PACKAGE:')) {
53
+ lines.splice(requirementStartIndex, 1);
54
+ }
55
+ while (requirementStartIndex < lines.length && lines[requirementStartIndex]?.trim() === '') {
56
+ lines.splice(requirementStartIndex, 1);
57
+ }
58
+ }
59
+
60
+ let recycledIndex = -1;
61
+ for (let i = 0; i < lines.length; i++) {
62
+ if (lines[i].includes('♻️ Recycled') || lines[i].includes('🗑️ Recycled')) {
63
+ recycledIndex = i;
64
+ break;
65
+ }
66
+ }
67
+
68
+ if (recycledIndex === -1) {
69
+ let lastSectionIndex = -1;
70
+ for (let i = lines.length - 1; i >= 0; i--) {
71
+ if (lines[i].startsWith('##') && !lines[i].startsWith('###')) {
72
+ lastSectionIndex = i;
73
+ break;
74
+ }
75
+ }
76
+ const insertIndex = lastSectionIndex > 0 ? lastSectionIndex : lines.length;
77
+ lines.splice(insertIndex, 0, '', '## ♻️ Recycled', '');
78
+ recycledIndex = insertIndex + 1;
79
+ }
80
+
81
+ let insertIndex = recycledIndex + 1;
82
+ while (insertIndex < lines.length && lines[insertIndex].trim() === '') {
83
+ insertIndex++;
84
+ }
85
+ lines.splice(insertIndex, 0, ...requirementBlock);
86
+
87
+ if (insertIndex + requirementBlock.length < lines.length && lines[insertIndex + requirementBlock.length].trim() !== '') {
88
+ lines.splice(insertIndex + requirementBlock.length, 0, '');
89
+ }
90
+
91
+ await fs.writeFile(reqPath, lines.join('\n'));
92
+ return true;
93
+ }
94
+
95
+ async function run() {
96
+ try {
97
+ const repoPath = '/Users/jesse/code/mediawink/vibecodingmachine'; // Hardcoded valid repo path
98
+ console.log('Repo path:', repoPath);
99
+
100
+ const reqPath = await getRequirementsPath(repoPath);
101
+ console.log('<<< PATH >>>', reqPath);
102
+
103
+ if (await fs.pathExists(reqPath)) {
104
+ const content = await fs.readFile(reqPath, 'utf8');
105
+ console.log('Current content length:', content.length);
106
+ console.log('Current content PRE-TEST:\n', content);
107
+ } else {
108
+ console.log('Requirements file does not exist');
109
+ }
110
+
111
+ console.log('\n--- Adding TESTREQ1 ---');
112
+ await requirements.add('TESTREQ1', 'all', 'Description 1');
113
+
114
+ console.log('\n--- Adding TESTREQ2 ---');
115
+ await requirements.add('TESTREQ2', 'all', 'Description 2');
116
+
117
+ let content = await fs.readFile(reqPath, 'utf8');
118
+ console.log('Content after adding:\n', content);
119
+
120
+ // Verify order
121
+ // We expect TESTREQ2 to be above TESTREQ1 if it inserts at the top of the section
122
+ const lines = content.split('\n');
123
+ let idx1 = -1, idx2 = -1;
124
+ for (let i = 0; i < lines.length; i++) {
125
+ if (lines[i].includes('### TESTREQ1')) idx1 = i;
126
+ if (lines[i].includes('### TESTREQ2')) idx2 = i;
127
+ }
128
+ console.log(`TESTREQ1 line: ${idx1}, TESTREQ2 line: ${idx2}`);
129
+ if (idx2 < idx1 && idx2 > -1) {
130
+ console.log('SUCCESS: TESTREQ2 is above TESTREQ1 (Correct LIFO/Stack behavior for "Top of list")');
131
+ } else {
132
+ console.log('FAIL: Order is not correct for "Top of list" insertion');
133
+ }
134
+
135
+ console.log('\n--- Deleting TESTREQ1 ---');
136
+ const success = await moveRequirementToRecycled(reqPath, 'TESTREQ1', 'todo');
137
+ console.log('Delete success:', success);
138
+
139
+ content = await fs.readFile(reqPath, 'utf8');
140
+ console.log('Content after delete:\n', content);
141
+
142
+ if (content.includes('### TESTREQ1')) {
143
+ // It should be in recycled section
144
+ const recycledIndex = content.indexOf('## ♻️ Recycled');
145
+ const reqIndex = content.indexOf('### TESTREQ1');
146
+ if (reqIndex > recycledIndex) {
147
+ console.log('SUCCESS: TESTREQ1 moved to Recycled');
148
+ } else {
149
+ console.log('FAIL: TESTREQ1 is still in TODO or wrong place');
150
+ }
151
+ } else {
152
+ console.log('FAIL: TESTREQ1 disappeared completely (should be recycled)');
153
+ }
154
+
155
+ } catch (error) {
156
+ console.error('Error:', error);
157
+ }
158
+ }
159
+
160
+ run();