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.
@@ -0,0 +1,212 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const os = require('os');
4
+ const { execSync } = require('child_process');
5
+ const ora = require('ora');
6
+ const chalk = require('chalk');
7
+
8
+ async function installAntigravity() {
9
+ console.log(chalk.cyan('\nšŸš€ Initiating Google Antigravity IDE Installation...'));
10
+
11
+ const spinner = ora('Checking system requirements...').start();
12
+ await new Promise((r) => setTimeout(r, 800));
13
+
14
+ if (os.platform() !== 'darwin') {
15
+ spinner.fail('Automated Antigravity installation is only supported on macOS');
16
+ return false;
17
+ }
18
+
19
+ const arch = os.arch();
20
+ const isArm = arch === 'arm64';
21
+
22
+ // URLs from electron-app IDE installer
23
+ const downloadUrl = isArm
24
+ ? 'https://edgedl.me.gvt1.com/edgedl/release2/j0qc3/antigravity/stable/1.11.2-6251250307170304/darwin-arm/Antigravity.dmg'
25
+ : 'https://edgedl.me.gvt1.com/edgedl/release2/j0qc3/antigravity/stable/1.11.2-6251250307170304/darwin-x64/Antigravity.dmg';
26
+
27
+ spinner.succeed(`System OK (${isArm ? 'Apple Silicon' : 'Intel'})`);
28
+
29
+ try {
30
+ const cacheDir = path.join(os.homedir(), '.vibecodingmachine', 'cache');
31
+ await fs.ensureDir(cacheDir);
32
+ const cachedPath = path.join(cacheDir, 'Antigravity.dmg');
33
+ const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'antigravity-'));
34
+ const dmgPath = path.join(tempDir, 'Antigravity.dmg');
35
+ const { downloadWithProgress } = require('./download-with-progress');
36
+
37
+ // Prefer cached DMG if it exists and matches upstream size (or exists at all)
38
+ let usedCache = false;
39
+ try {
40
+ if (await fs.pathExists(cachedPath)) {
41
+ // Try HEAD to compare sizes
42
+ try {
43
+ const fetch = require('node-fetch');
44
+ const head = await fetch(downloadUrl, { method: 'HEAD' });
45
+ const total = Number(head.headers.get('content-length')) || 0;
46
+ const stat = await fs.stat(cachedPath);
47
+ if (total && stat.size === total) {
48
+ await fs.copy(cachedPath, dmgPath);
49
+ usedCache = true;
50
+ console.log(chalk.gray('Using cached Antigravity DMG'));
51
+ }
52
+ } catch (e) {
53
+ // If HEAD fails, still allow reuse of cache to avoid re-download
54
+ await fs.copy(cachedPath, dmgPath);
55
+ usedCache = true;
56
+ console.log(chalk.gray('Using cached Antigravity DMG (no HEAD)'));
57
+ }
58
+ }
59
+ } catch (e) {
60
+ // ignore cache errors
61
+ usedCache = false;
62
+ }
63
+
64
+ // Use download helper which displays progress and ETA if we didn't reuse cache
65
+ if (!usedCache) {
66
+ await downloadWithProgress(downloadUrl, dmgPath, { label: 'Downloading Antigravity...' });
67
+ // Save to cache for future runs
68
+ try { await fs.copy(dmgPath, cachedPath); } catch (e) { /* ignore cache write errors */ }
69
+ }
70
+
71
+ const installSpinner = ora('Installing Antigravity...').start();
72
+
73
+ try {
74
+ installSpinner.text = 'Mounting disk image...';
75
+ execSync(`hdiutil attach "${dmgPath}" -nobrowse -noverify -mountpoint "${tempDir}/mount"`);
76
+
77
+ installSpinner.text = 'Copying to /Applications...';
78
+ const files = await fs.readdir(path.join(tempDir, 'mount'));
79
+ const appName = files.find((f) => f.endsWith('.app'));
80
+ if (!appName) throw new Error('Could not find .app in DMG');
81
+
82
+ const src = path.join(tempDir, 'mount', appName);
83
+ const dest = path.join('/Applications', appName);
84
+
85
+ if (await fs.pathExists(dest)) {
86
+ installSpinner.text = 'Replacing existing application...';
87
+ await fs.remove(dest);
88
+ }
89
+
90
+ // Prefer rsync/ditto; fallback to node-stream copy with progress
91
+ try {
92
+ const { copyAppWithProgress } = require('./copy-with-progress');
93
+ installSpinner.stop();
94
+ const ok = await copyAppWithProgress(src, dest, { spinner: installSpinner });
95
+ if (!ok) {
96
+ installSpinner.text = 'Copying (fs fallback) to /Applications...';
97
+ await fs.copy(src, dest);
98
+ }
99
+ } catch (e) {
100
+ // final fallback
101
+ installSpinner.text = 'Copying (fs fallback) to /Applications...';
102
+ try {
103
+ execSync(`cp -R "${src}" "/Applications/"`, { stdio: 'inherit' });
104
+ } catch (err) {
105
+ await fs.copy(src, dest);
106
+ }
107
+ }
108
+
109
+ installSpinner.text = 'Cleaning up...';
110
+ execSync(`hdiutil detach "${tempDir}/mount" -force`);
111
+ await fs.remove(tempDir);
112
+
113
+ installSpinner.succeed('Antigravity installed');
114
+ console.log(chalk.green(`\nāœ… Installed to ${dest}`));
115
+ // Attempt to automatically configure Antigravity's first-run settings
116
+ try {
117
+ await configureAntigravityDefaults(dest);
118
+ } catch (e) {
119
+ // Non-fatal: log and continue
120
+ console.log(chalk.yellow('Warning: could not auto-configure Antigravity:'), e.message || e);
121
+ }
122
+
123
+ return true;
124
+ } catch (err) {
125
+ installSpinner.fail('Installation failed');
126
+ try {
127
+ if (await fs.pathExists(path.join(tempDir, 'mount'))) {
128
+ execSync(`hdiutil detach "${tempDir}/mount" -force`);
129
+ }
130
+ try { await fs.remove(tempDir); } catch (e) { /* ignore */ }
131
+ console.log(chalk.red('\nInstallation error:'), err.message);
132
+ return false;
133
+ }
134
+ } catch (error) {
135
+ console.log(chalk.red('\nFailed to install Antigravity:'), error.message);
136
+ return false;
137
+ }
138
+ }
139
+
140
+ async function configureAntigravityDefaults() {
141
+ // Attempt to open Antigravity and set the onboarding defaults via AppleScript
142
+ // Desired selections:
143
+ // - Agent-driven development (instead of recommended Agent-assisted)
144
+ // - Terminal execution policy: Turbo
145
+ // - Review policy: Always Proceed
146
+ // - Use the default allowlist for the browser (checked)
147
+ try {
148
+ const script = `
149
+ tell application "Antigravity"
150
+ activate
151
+ end tell
152
+ delay 0.8
153
+ tell application "System Events"
154
+ tell process "Antigravity"
155
+ set frontmost to true
156
+ delay 0.5
157
+ -- Try to select Agent-driven development radio button
158
+ try
159
+ click radio button "Agent-driven development" of radio group 1 of window 1
160
+ delay 0.2
161
+ end try
162
+
163
+ -- Set Terminal execution policy to Turbo (attempt by label, then by index)
164
+ try
165
+ try
166
+ click pop up button "Terminal execution policy" of window 1
167
+ delay 0.2
168
+ click menu item "Turbo" of menu 1 of pop up button "Terminal execution policy" of window 1
169
+ on error
170
+ -- fallback: click first pop up button and choose Turbo
171
+ click pop up button 1 of window 1
172
+ delay 0.2
173
+ click menu item "Turbo" of menu 1 of pop up button 1 of window 1
174
+ end try
175
+ delay 0.2
176
+ end try
177
+
178
+ -- Set Review policy to Always Proceed
179
+ try
180
+ click pop up button "Review policy" of window 1
181
+ delay 0.2
182
+ click menu item "Always Proceed" of menu 1 of pop up button "Review policy" of window 1
183
+ delay 0.2
184
+ end try
185
+
186
+ -- Ensure default allowlist checkbox is checked
187
+ try
188
+ set cb to checkbox "Use the default allowlist for the browser" of window 1
189
+ if (value of cb as boolean) is false then click cb
190
+ delay 0.2
191
+ end try
192
+
193
+ -- Advance the onboarding if a Next/Done button exists
194
+ try
195
+ if exists button "Next" of window 1 then click button "Next" of window 1
196
+ delay 0.3
197
+ end try
198
+ end tell
199
+ end tell
200
+ `;
201
+
202
+ // Use osascript to run the UI automation. JSON-stringify ensures safe quoting.
203
+ const { execSync } = require('child_process');
204
+ execSync(`osascript -e ${JSON.stringify(script)}`, { stdio: 'ignore', timeout: 15000 });
205
+ console.log(chalk.gray('Auto-configured Antigravity first-run preferences'));
206
+ return true;
207
+ } catch (err) {
208
+ throw new Error(err.message || err);
209
+ }
210
+ }
211
+
212
+ module.exports = { installAntigravity };
@@ -0,0 +1,60 @@
1
+ const chalk = require('chalk');
2
+ const {
3
+ getProviderPreferences,
4
+ saveProviderPreferences
5
+ } = require('./provider-registry');
6
+
7
+ /**
8
+ * Check if Antigravity agent has hit a rate limit.
9
+ * @param {string} stderr - Standard error output from the agent.
10
+ * @returns {{isRateLimited: boolean, message: string|null}} - Rate limit status and message.
11
+ */
12
+ function checkAntigravityRateLimit(stderr) {
13
+ const rateLimitPatterns = [
14
+ /quota limit/i,
15
+ /rate limit/i,
16
+ /too many requests/i,
17
+ /limit exceeded/i
18
+ ];
19
+
20
+ for (const pattern of rateLimitPatterns) {
21
+ if (pattern.test(stderr)) {
22
+ return {
23
+ isRateLimited: true,
24
+ message: 'Antigravity quota limit reached.'
25
+ };
26
+ }
27
+ }
28
+
29
+ return { isRateLimited: false, message: null };
30
+ }
31
+
32
+ /**
33
+ * Handle rate limit for Antigravity by disabling it and selecting the next available provider.
34
+ * @returns {Promise<{success: boolean, nextProvider: string|null, error: string|null}>}
35
+ */
36
+ async function handleAntigravityRateLimit() {
37
+ console.log(chalk.yellow('Antigravity rate limit detected. Disabling for this session.'));
38
+
39
+ try {
40
+ const prefs = await getProviderPreferences();
41
+ prefs.enabled.antigravity = false;
42
+ await saveProviderPreferences(prefs);
43
+
44
+ const nextProvider = prefs.order.find(p => p !== 'antigravity' && prefs.enabled[p]);
45
+
46
+ if (nextProvider) {
47
+ console.log(chalk.cyan(`Switching to next available provider: ${nextProvider}`));
48
+ return { success: true, nextProvider, error: null };
49
+ } else {
50
+ return { success: false, nextProvider: null, error: 'No fallback providers available.' };
51
+ }
52
+ } catch (error) {
53
+ return { success: false, nextProvider: null, error: 'Failed to update provider preferences.' };
54
+ }
55
+ }
56
+
57
+ module.exports = {
58
+ checkAntigravityRateLimit,
59
+ handleAntigravityRateLimit
60
+ };
@@ -1,6 +1,5 @@
1
1
  const fs = require('fs-extra');
2
2
  const path = require('path');
3
- const chalk = require('chalk');
4
3
 
5
4
  /**
6
5
  * Scans the assets directory and removes 0-byte PNG files
package/src/utils/auth.js CHANGED
@@ -2,7 +2,6 @@ const chalk = require('chalk');
2
2
  const http = require('http');
3
3
  const crypto = require('crypto');
4
4
  const fs = require('fs');
5
- const path = require('path');
6
5
  const net = require('net');
7
6
  const sharedAuth = require('vibecodingmachine-core/src/auth/shared-auth-storage');
8
7
 
@@ -33,7 +32,11 @@ class CLIAuth {
33
32
  async isAuthenticated() {
34
33
  // First check if current token is valid
35
34
  const isValid = await sharedAuth.isAuthenticated();
36
- if (isValid) return true;
35
+ if (isValid) {
36
+ // Update user activity in database
37
+ await this._updateUserActivity();
38
+ return true;
39
+ }
37
40
 
38
41
  // If not valid, try to refresh
39
42
  try {
@@ -47,6 +50,9 @@ class CLIAuth {
47
50
 
48
51
  // Save new tokens
49
52
  await sharedAuth.saveToken(newTokens);
53
+
54
+ // Update user activity in database
55
+ await this._updateUserActivity();
50
56
  return true;
51
57
  }
52
58
  } catch (error) {
@@ -398,6 +404,9 @@ class CLIAuth {
398
404
  // Save token
399
405
  await sharedAuth.saveToken(tokens);
400
406
 
407
+ // Register/update user in enhanced database
408
+ await this._registerUserInDatabase(idToken);
409
+
401
410
  // Show success page
402
411
  res.writeHead(200, {
403
412
  'Content-Type': 'text/html',
@@ -656,6 +665,9 @@ class CLIAuth {
656
665
  // Save token using shared storage (only if validation passed)
657
666
  await sharedAuth.saveToken(tokens);
658
667
 
668
+ // Register/update user in enhanced database
669
+ await this._registerUserInDatabase(idToken);
670
+
659
671
  console.log(chalk.green('\nāœ“ Authentication successful!'));
660
672
  return idToken;
661
673
  } catch (error) {
@@ -691,6 +703,141 @@ class CLIAuth {
691
703
  async activateLicense(licenseKey) {
692
704
  return await sharedAuth.activateLicense(licenseKey);
693
705
  }
706
+
707
+ /**
708
+ * Register or update user in enhanced database system
709
+ */
710
+ async _registerUserInDatabase(idToken) {
711
+ try {
712
+ // Decode JWT to get user info (without verification since we already validated)
713
+ const payload = JSON.parse(Buffer.from(idToken.split('.')[1], 'base64').toString());
714
+
715
+ const UserDatabase = require('vibecodingmachine-core/src/database/user-schema');
716
+ const userDb = new UserDatabase();
717
+
718
+ const userInfo = {
719
+ email: payload.email,
720
+ name: payload.name || payload.email.split('@')[0],
721
+ cognitoId: payload.sub
722
+ };
723
+
724
+ // Register/update user
725
+ const user = await userDb.registerUser(userInfo);
726
+
727
+ // Register computer
728
+ await userDb.registerComputer(user.userId, {
729
+ interface: 'cli'
730
+ });
731
+
732
+ // Track login activity
733
+ await userDb.trackActivity(user.userId, {
734
+ interface: 'cli',
735
+ action: 'login',
736
+ duration: 0,
737
+ metadata: {
738
+ authMethod: 'cognito',
739
+ timestamp: Date.now()
740
+ }
741
+ });
742
+
743
+ } catch (error) {
744
+ // Don't fail authentication if database registration fails
745
+ console.error('Warning: Failed to register user in database:', error.message);
746
+ }
747
+ }
748
+
749
+ /**
750
+ * Update user activity in database
751
+ */
752
+ async _updateUserActivity() {
753
+ try {
754
+ const token = await sharedAuth.getToken();
755
+ if (!token || !token.id_token) return;
756
+
757
+ // Decode JWT to get user info
758
+ const payload = JSON.parse(Buffer.from(token.id_token.split('.')[1], 'base64').toString());
759
+
760
+ const UserDatabase = require('vibecodingmachine-core/src/database/user-schema');
761
+ const userDb = new UserDatabase();
762
+
763
+ const userId = userDb.generateUserId(payload.email);
764
+
765
+ // Update last activity
766
+ await userDb.updateUserActivity(userId, {
767
+ lastActivity: Date.now()
768
+ });
769
+
770
+ } catch (error) {
771
+ // Silently fail - don't disrupt user experience
772
+ console.error('Warning: Failed to update user activity:', error.message);
773
+ }
774
+ }
775
+
776
+ /**
777
+ * Track CLI usage activity
778
+ */
779
+ async trackCLIActivity(action, metadata = {}) {
780
+ try {
781
+ const token = await sharedAuth.getToken();
782
+ if (!token) return;
783
+
784
+ // Handle both string tokens and object tokens with id_token property
785
+ const idToken = typeof token === 'string' ? token : token.id_token;
786
+ if (!idToken) return;
787
+
788
+ const payload = JSON.parse(Buffer.from(idToken.split('.')[1], 'base64').toString());
789
+
790
+ const UserDatabase = require('vibecodingmachine-core/src/database/user-schema');
791
+ const userDb = new UserDatabase();
792
+
793
+ const userId = userDb.generateUserId(payload.email);
794
+
795
+ await userDb.trackActivity(userId, {
796
+ interface: 'cli',
797
+ action,
798
+ duration: metadata.duration || 0,
799
+ metadata: {
800
+ ...metadata,
801
+ timestamp: Date.now()
802
+ }
803
+ });
804
+
805
+ } catch (error) {
806
+ // Silently fail - don't disrupt user experience
807
+ console.error('Warning: Failed to track CLI activity:', error.message);
808
+ }
809
+ }
810
+
811
+ /**
812
+ * Get current user information
813
+ */
814
+ async getCurrentUser() {
815
+ try {
816
+ const token = await sharedAuth.getToken();
817
+ if (!token) return null;
818
+
819
+ // Handle both string token and object with id_token
820
+ const idToken = typeof token === 'string' ? token : token.id_token;
821
+ if (!idToken) return null;
822
+
823
+ const payload = JSON.parse(Buffer.from(idToken.split('.')[1], 'base64').toString());
824
+
825
+ const UserDatabase = require('vibecodingmachine-core/src/database/user-schema');
826
+ const userDb = new UserDatabase();
827
+
828
+ const userId = userDb.generateUserId(payload.email);
829
+
830
+ return {
831
+ userId,
832
+ email: payload.email,
833
+ name: payload.name || payload.email.split('@')[0],
834
+ cognitoId: payload.sub
835
+ };
836
+ } catch (error) {
837
+ console.error('Error getting current user:', error.message);
838
+ return null;
839
+ }
840
+ }
694
841
  }
695
842
 
696
843
  module.exports = new CLIAuth();
@@ -1,6 +1,5 @@
1
1
  const chalk = require('chalk');
2
2
  const ansiEscapes = require('ansi-escapes');
3
- const stripAnsi = require('strip-ansi');
4
3
 
5
4
  /**
6
5
  * ANSI-based UI for Auto Mode
@@ -79,7 +79,7 @@ class AutoModeSimpleUI {
79
79
  { name: 'DONE', color: chalk.green }
80
80
  ];
81
81
 
82
- const workflowLine = stages.map((stage, index) => {
82
+ const workflowLine = stages.map((stage, _index) => {
83
83
  const icon = this.getStepIcon(stage.name, this.step);
84
84
  const isCurrent = stage.name === this.step;
85
85
  const stageColor = isCurrent ? currentStepColor.bold : stage.color;
@@ -0,0 +1,166 @@
1
+ /**
2
+ * CLI Compliance Check Utility
3
+ *
4
+ * Checks and prompts for compliance on CLI startup
5
+ */
6
+
7
+ let CompliancePrompt
8
+ try {
9
+ CompliancePrompt = require('vibecodingmachine-core/src/compliance/compliance-prompt')
10
+ } catch (e) {
11
+ // If core package isn't installed (local dev), gracefully skip compliance by returning true
12
+ CompliancePrompt = null
13
+ }
14
+ const auth = require('./auth')
15
+
16
+ async function checkCompliance() {
17
+ try {
18
+ // First check if user is authenticated
19
+ const isAuth = await auth.isAuthenticated()
20
+ if (!isAuth) {
21
+ // User not authenticated, skip compliance check (will be checked after login)
22
+ return true
23
+ }
24
+
25
+ // Get current user
26
+ const user = await auth.getCurrentUser()
27
+
28
+ if (!user || !user.userId) {
29
+ // If we can't get user info but they're authenticated, something is wrong
30
+ // But don't block them - they can still use the app
31
+ console.warn('Warning: Unable to verify compliance status')
32
+ return true
33
+ }
34
+
35
+ // If CompliancePrompt is not available (dev environment), skip compliance checks
36
+ if (!CompliancePrompt) {
37
+ if (process.env.DEBUG) console.log('Compliance checks skipped: vibecodingmachine-core not available')
38
+ return true
39
+ }
40
+
41
+ // Check and prompt for compliance
42
+ const compliancePrompt = new CompliancePrompt()
43
+ const status = await compliancePrompt.complianceManager.checkComplianceStatus(user.userId)
44
+
45
+ if (!status.needsAcknowledgment) {
46
+ return true
47
+ }
48
+
49
+ // Handle CLI prompting locally
50
+ const isCompliant = await promptCLI(user.userId, status, compliancePrompt)
51
+ return isCompliant
52
+ } catch (error) {
53
+ // Log error but don't block user
54
+ console.error('Error checking compliance:', error.message)
55
+ if (process.env.DEBUG) {
56
+ console.error(error.stack)
57
+ }
58
+ // Return true to not block the user if there's a system error
59
+ return true
60
+ }
61
+ }
62
+
63
+ /**
64
+ * CLI prompt for compliance
65
+ */
66
+ async function promptCLI(userId, status, compliancePrompt) {
67
+ const chalk = require('chalk')
68
+ const inquirer = require('inquirer')
69
+
70
+ console.log(chalk.cyan('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))
71
+ console.log(chalk.cyan.bold(' šŸ“‹ Terms & Privacy Acknowledgment Required'))
72
+ console.log(chalk.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'))
73
+
74
+ console.log(chalk.white('Before continuing, please review and accept:\n'))
75
+
76
+ const questions = []
77
+
78
+ if (status.requiredActions.includes('terms')) {
79
+ const terms = await compliancePrompt.complianceManager.getTermsOfService()
80
+
81
+ console.log(chalk.yellow('šŸ“„ Terms of Service'))
82
+ console.log(chalk.gray('─'.repeat(70)))
83
+ console.log(formatForCLI(terms.content))
84
+ console.log(chalk.gray('─'.repeat(70)))
85
+ console.log(chalk.gray(`Version ${terms.version} | Effective: ${terms.effectiveDate}\n`))
86
+
87
+ questions.push({
88
+ type: 'confirm',
89
+ name: 'acceptTerms',
90
+ message: 'Do you accept the Terms of Service?',
91
+ default: false
92
+ })
93
+ }
94
+
95
+ if (status.requiredActions.includes('privacy')) {
96
+ const privacy = await compliancePrompt.complianceManager.getPrivacyPolicy()
97
+
98
+ console.log(chalk.yellow('šŸ”’ Privacy Policy'))
99
+ console.log(chalk.gray('─'.repeat(70)))
100
+ console.log(formatForCLI(privacy.content))
101
+ console.log(chalk.gray('─'.repeat(70)))
102
+ console.log(chalk.gray(`Version ${privacy.version} | Effective: ${privacy.effectiveDate}\n`))
103
+
104
+ questions.push({
105
+ type: 'confirm',
106
+ name: 'acceptPrivacy',
107
+ message: 'Do you accept the Privacy Policy?',
108
+ default: false
109
+ })
110
+ }
111
+
112
+ const answers = await inquirer.prompt(questions)
113
+
114
+ // Check if user accepted all required items
115
+ const allAccepted =
116
+ (!status.requiredActions.includes('terms') || answers.acceptTerms) &&
117
+ (!status.requiredActions.includes('privacy') || answers.acceptPrivacy)
118
+
119
+ if (!allAccepted) {
120
+ console.log(chalk.red('\nāŒ You must accept the Terms and Privacy Policy to use VibeCodingMachine.\n'))
121
+ return false
122
+ }
123
+
124
+ // Record acceptance
125
+ await compliancePrompt.complianceManager.recordAcknowledgment(userId, {
126
+ terms: answers.acceptTerms || undefined,
127
+ privacy: answers.acceptPrivacy || undefined
128
+ })
129
+
130
+ console.log(chalk.green('\nāœ… Thank you! Your acceptance has been recorded.\n'))
131
+ return true
132
+ }
133
+
134
+ /**
135
+ * Format markdown content for CLI display
136
+ */
137
+ function formatForCLI(content) {
138
+ // Simple markdown formatting for terminal
139
+ return content
140
+ .split('\n')
141
+ .map(line => {
142
+ // Headers
143
+ if (line.startsWith('# ')) {
144
+ return ' ' + line.substring(2).toUpperCase()
145
+ }
146
+ if (line.startsWith('## ')) {
147
+ return ' ' + line.substring(3)
148
+ }
149
+ // Bold
150
+ line = line.replace(/\*\*(.*?)\*\*/g, '$1')
151
+ // Lists
152
+ if (line.startsWith('- ')) {
153
+ return ' • ' + line.substring(2)
154
+ }
155
+ if (/^\d+\./.test(line)) {
156
+ return ' ' + line
157
+ }
158
+ // Regular text
159
+ return line ? ' ' + line : ''
160
+ })
161
+ .join('\n')
162
+ }
163
+
164
+ module.exports = {
165
+ checkCompliance
166
+ }
@@ -52,13 +52,39 @@ async function setAutoConfig(autoConfig) {
52
52
  await writeConfig(cfg);
53
53
  }
54
54
 
55
+ const DEFAULT_STAGES = Object.freeze([
56
+ 'PREPARE',
57
+ 'REPRODUCE',
58
+ 'CREATE UNIT TEST',
59
+ 'ACT',
60
+ 'CLEAN UP',
61
+ 'VERIFY',
62
+ 'RUN UNIT TESTS',
63
+ 'DONE'
64
+ ]);
65
+
66
+ async function getStages() {
67
+ const config = await getAutoConfig();
68
+ // Return a copy to prevent mutation of the internal array
69
+ return [...(config.stages || DEFAULT_STAGES)];
70
+ }
71
+
72
+ async function setStages(stages) {
73
+ const cfg = await readConfig();
74
+ cfg.auto = { ...(cfg.auto || {}), stages };
75
+ await writeConfig(cfg);
76
+ }
77
+
55
78
  module.exports = {
56
79
  getRepoPath,
57
80
  setRepoPath,
58
81
  getAutoConfig,
59
82
  setAutoConfig,
60
83
  readConfig,
61
- writeConfig
84
+ writeConfig,
85
+ getStages,
86
+ setStages,
87
+ DEFAULT_STAGES
62
88
  };
63
89
 
64
90