vibecodingmachine-cli 2026.1.23-1010 → 2026.1.29-1432
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/package.json +14 -5
- package/src/utils/interactive.js +53 -3
- package/.allnightai/REQUIREMENTS.md +0 -11
- package/.eslintrc.js +0 -16
- package/__tests__/antigravity-js-handler.test.js +0 -23
- package/__tests__/provider-manager.test.js +0 -84
- package/__tests__/provider-rate-cache.test.js +0 -27
- package/jest.config.js +0 -8
- package/logs/audit/2025-11-07.jsonl +0 -2
- package/logs/audit/2025-12-24.jsonl +0 -2
- package/logs/audit/2025-12-27.jsonl +0 -1
- package/logs/audit/2026-01-03.jsonl +0 -2
- package/repro_open.js +0 -13
- package/reproduce_issue.js +0 -160
- package/reset_provider_order.js +0 -21
- package/scripts/README.md +0 -128
- package/scripts/auto-start-wrapper.sh +0 -92
- package/scripts/convert-requirements.js +0 -35
- package/scripts/debug-parse.js +0 -24
- package/src/commands/auto.js.bak +0 -710
- package/src/utils/auto-mode-ui.js.bak.blessed +0 -207
- package/tests/antigravity-js-handler.test.js +0 -23
- package/tests/auto-mode.test.js +0 -37
- package/tests/config.test.js +0 -34
- package/tests/home-bootstrap.test.js +0 -76
- package/tests/integration/health-tracking.integration.test.js +0 -284
- package/tests/provider-manager.test.js +0 -92
- package/tests/rate-limit-display.test.js +0 -44
- package/tests/requirements-bullet-parsing.test.js +0 -15
- package/tests/requirements-converter.test.js +0 -42
- package/tests/requirements-heading-count.test.js +0 -27
- package/tests/requirements-legacy-parsing.test.js +0 -15
- package/tests/requirements-navigator-buildtree-await.test.js +0 -28
- package/tests/requirements-parse-integration.test.js +0 -44
- package/tests/wait-for-ide-completion.test.js +0 -56
- package/tests/wait-for-ide-quota-detection-cursor-screenshot.test.js +0 -61
- package/tests/wait-for-ide-quota-detection-cursor.test.js +0 -60
- package/tests/wait-for-ide-quota-detection-negative.test.js +0 -45
- package/tests/wait-for-ide-quota-detection.test.js +0 -59
- package/verify_fix.js +0 -36
- package/verify_ui.js +0 -38
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
const providerRegistry = require('../src/utils/provider-registry');
|
|
2
|
-
|
|
3
|
-
jest.mock('../src/utils/provider-registry');
|
|
4
|
-
// Note: interactive module requires provider definitions at load time, so require it AFTER
|
|
5
|
-
// we set up mocks inside each test to avoid module initialization failures.
|
|
6
|
-
|
|
7
|
-
describe('showProviderManagerMenu', () => {
|
|
8
|
-
const origIsTTY = process.stdin.isTTY;
|
|
9
|
-
const origSetRawMode = process.stdin.setRawMode;
|
|
10
|
-
|
|
11
|
-
beforeAll(() => {
|
|
12
|
-
// Ensure stdin behaves like a TTY for the menu
|
|
13
|
-
process.stdin.isTTY = true;
|
|
14
|
-
process.stdin.setRawMode = () => {};
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
afterAll(() => {
|
|
18
|
-
process.stdin.isTTY = origIsTTY;
|
|
19
|
-
process.stdin.setRawMode = origSetRawMode;
|
|
20
|
-
if (process.stdin && typeof process.stdin.pause === 'function') process.stdin.pause();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
beforeEach(() => {
|
|
24
|
-
jest.resetAllMocks();
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test('pressing left after reordering calls saveProviderPreferences', async () => {
|
|
28
|
-
providerRegistry.getProviderDefinitions.mockReturnValue([
|
|
29
|
-
{ id: 'groq', name: 'Groq' },
|
|
30
|
-
{ id: 'antigravity', name: 'Antigravity' }
|
|
31
|
-
]);
|
|
32
|
-
|
|
33
|
-
providerRegistry.getProviderPreferences.mockResolvedValue({
|
|
34
|
-
order: ['groq', 'antigravity'],
|
|
35
|
-
enabled: { groq: true, antigravity: true }
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
providerRegistry.saveProviderPreferences.mockResolvedValue();
|
|
39
|
-
|
|
40
|
-
// Require after mocks are set up to avoid module init ordering issues
|
|
41
|
-
const interactive = require('../src/utils/interactive');
|
|
42
|
-
// Start the menu
|
|
43
|
-
const menuPromise = interactive.showProviderManagerMenu();
|
|
44
|
-
|
|
45
|
-
// Allow the menu to initialize and pass the debounce window (300ms)
|
|
46
|
-
await new Promise(resolve => setTimeout(resolve, 350));
|
|
47
|
-
|
|
48
|
-
// Simulate 'j' (reorder downward)
|
|
49
|
-
process.stdin.emit('keypress', 'j', { name: 'j' });
|
|
50
|
-
// Allow the reorder handler to process
|
|
51
|
-
await new Promise(resolve => setTimeout(resolve, 50));
|
|
52
|
-
|
|
53
|
-
// Simulate left arrow to save and exit
|
|
54
|
-
process.stdin.emit('keypress', undefined, { name: 'left' });
|
|
55
|
-
|
|
56
|
-
await menuPromise; // wait for menu to finish
|
|
57
|
-
|
|
58
|
-
expect(providerRegistry.saveProviderPreferences).toHaveBeenCalledTimes(1);
|
|
59
|
-
expect(providerRegistry.saveProviderPreferences).toHaveBeenCalledWith(['antigravity', 'groq'], { groq: true, antigravity: true });
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test('pressing escape after reordering does NOT call saveProviderPreferences', async () => {
|
|
63
|
-
providerRegistry.getProviderDefinitions.mockReturnValue([
|
|
64
|
-
{ id: 'groq', name: 'Groq' },
|
|
65
|
-
{ id: 'antigravity', name: 'Antigravity' }
|
|
66
|
-
]);
|
|
67
|
-
|
|
68
|
-
providerRegistry.getProviderPreferences.mockResolvedValue({
|
|
69
|
-
order: ['groq', 'antigravity'],
|
|
70
|
-
enabled: { groq: true, antigravity: true }
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
providerRegistry.saveProviderPreferences.mockResolvedValue();
|
|
74
|
-
|
|
75
|
-
const interactive = require('../src/utils/interactive');
|
|
76
|
-
const menuPromise = interactive.showProviderManagerMenu();
|
|
77
|
-
// Allow the menu to initialize and pass the debounce window (300ms)
|
|
78
|
-
await new Promise(resolve => setTimeout(resolve, 350));
|
|
79
|
-
|
|
80
|
-
// Make a change
|
|
81
|
-
process.stdin.emit('keypress', 'j', { name: 'j' });
|
|
82
|
-
// Allow the reorder handler to process
|
|
83
|
-
await new Promise(resolve => setTimeout(resolve, 50));
|
|
84
|
-
|
|
85
|
-
// Press escape to cancel (should not persist)
|
|
86
|
-
process.stdin.emit('keypress', undefined, { name: 'escape' });
|
|
87
|
-
|
|
88
|
-
await menuPromise;
|
|
89
|
-
|
|
90
|
-
expect(providerRegistry.saveProviderPreferences).not.toHaveBeenCalled();
|
|
91
|
-
});
|
|
92
|
-
});
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
const { formatResetsAtLabel } = require('../src/utils/date-formatter');
|
|
2
|
-
|
|
3
|
-
describe('formatResetsAtLabel', () => {
|
|
4
|
-
const originalDateNow = Date.now;
|
|
5
|
-
|
|
6
|
-
afterEach(() => {
|
|
7
|
-
Date.now = originalDateNow;
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
test('formats future date correctly absolute', () => {
|
|
11
|
-
// Mock current time: Jan 12, 2026 12:00 PM
|
|
12
|
-
const now = new Date('2026-01-12T12:00:00.000Z').getTime();
|
|
13
|
-
Date.now = jest.fn(() => now);
|
|
14
|
-
|
|
15
|
-
// Reset time: Jan 17, 2026 4:23 PM MST (MST is UTC-7)
|
|
16
|
-
// 4:23 PM MST = 16:23 MST = 23:23 UTC
|
|
17
|
-
const futureDate = new Date('2026-01-17T23:23:00.000Z');
|
|
18
|
-
|
|
19
|
-
const result = formatResetsAtLabel(futureDate);
|
|
20
|
-
|
|
21
|
-
// We expect user local time format.
|
|
22
|
-
// Since environment timezone might vary, we should check broadly or mock timezone if possible.
|
|
23
|
-
// However, the function uses toLocaleTimeString which uses system locale.
|
|
24
|
-
// For specific requirement "resets at 4:23 pm mst on January 17, 2026"
|
|
25
|
-
// We can rely on regex matching for the structure.
|
|
26
|
-
|
|
27
|
-
expect(result).toMatch(/Resets at \d{1,2}:\d{2} [ap]m [a-z]{3,4} on January 17, 2026/);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test('returns null if the reset minute has started or date is past', () => {
|
|
31
|
-
Date.now = jest.fn(() => new Date('2026-01-18T00:00:00Z').getTime());
|
|
32
|
-
const pastDate = new Date('2026-01-17T00:00:00Z');
|
|
33
|
-
expect(formatResetsAtLabel(pastDate)).toBeNull();
|
|
34
|
-
|
|
35
|
-
// Also test within the same minute - should return null (clears at start of minute)
|
|
36
|
-
Date.now = jest.fn(() => new Date('2026-01-18T00:00:30Z').getTime());
|
|
37
|
-
const sameMinute = new Date('2026-01-18T00:00:00Z');
|
|
38
|
-
expect(formatResetsAtLabel(sameMinute)).toBeNull();
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test('returns null for invalid date', () => {
|
|
42
|
-
expect(formatResetsAtLabel('invalid')).toBeNull();
|
|
43
|
-
});
|
|
44
|
-
});
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
|
|
4
|
-
describe('Requirements bullet (- ) parsing', () => {
|
|
5
|
-
test('interactive.js contains bullet parsing branch', () => {
|
|
6
|
-
const parserPath = path.join(__dirname, '../src/utils/requirements-parser.js');
|
|
7
|
-
const content = fs.readFileSync(parserPath, 'utf8');
|
|
8
|
-
|
|
9
|
-
// Check for detection of bullet format in the parser
|
|
10
|
-
expect(content).toMatch(/if\s*\(inSection\s*&&\s*line\.trim\(\)\.startsWith\('\- '\)\s*&&\s*!line\.trim\(\)\.startsWith\('PACKAGE:'\)\)/);
|
|
11
|
-
|
|
12
|
-
// Ensure it pushes a bullet requirement with empty details and null pkg (allow additional fields)
|
|
13
|
-
expect(content).toMatch(/requirements\.push\(\{[\s\S]*details:\s*\[\],[\s\S]*pkg:\s*null[\s\S]*\}\)\s*;?/);
|
|
14
|
-
});
|
|
15
|
-
});
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const { convertPackageBlocksToHeadings } = require('../src/utils/requirements-converter');
|
|
4
|
-
|
|
5
|
-
describe('requirements converter', () => {
|
|
6
|
-
test('converts PACKAGE: blocks into ### headings inside TODO section', () => {
|
|
7
|
-
const input = `## ⏳ Requirements not yet completed
|
|
8
|
-
|
|
9
|
-
PACKAGE: cli
|
|
10
|
-
|
|
11
|
-
Do something important
|
|
12
|
-
More details line
|
|
13
|
-
|
|
14
|
-
PACKAGE: core
|
|
15
|
-
|
|
16
|
-
Another important item
|
|
17
|
-
`;
|
|
18
|
-
const out = convertPackageBlocksToHeadings(input, 'todo', '⏳ Requirements not yet completed');
|
|
19
|
-
expect(out).toContain('### Do something important');
|
|
20
|
-
expect(out).toContain('PACKAGE: cli');
|
|
21
|
-
expect(out).toContain('More details line');
|
|
22
|
-
expect(out).toContain('### Another important item');
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test('converts bullet items into headings', () => {
|
|
26
|
-
const input = `## ⏳ Requirements not yet completed
|
|
27
|
-
|
|
28
|
-
- First bullet item
|
|
29
|
-
- Second bullet item
|
|
30
|
-
`;
|
|
31
|
-
const out = convertPackageBlocksToHeadings(input, 'todo', '⏳ Requirements not yet completed');
|
|
32
|
-
expect(out).toContain('### First bullet item');
|
|
33
|
-
expect(out).toContain('### Second bullet item');
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test('does not modify other sections', () => {
|
|
37
|
-
const input = `## ✅ Verified by AI screenshot\n\nPACKAGE: cli\n\nThis should remain untouched\n`;
|
|
38
|
-
const out = convertPackageBlocksToHeadings(input, 'verify', '✅ Verified by AI screenshot');
|
|
39
|
-
expect(out).toContain('PACKAGE: cli');
|
|
40
|
-
expect(out).not.toContain('### This should remain untouched');
|
|
41
|
-
});
|
|
42
|
-
});
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const { parseRequirementsFromContent } = require('../src/utils/requirements-parser');
|
|
4
|
-
|
|
5
|
-
describe('Requirements heading count vs TODO count', () => {
|
|
6
|
-
test('TODO section headings in local REQUIREMENTS file equals expected 8', async () => {
|
|
7
|
-
const reqPath = path.join(__dirname, '..', '..', '..', '.vibecodingmachine', 'REQUIREMENTS-Jesses-2025-Mac.local.md');
|
|
8
|
-
const content = fs.readFileSync(reqPath, 'utf8');
|
|
9
|
-
|
|
10
|
-
const reqs = parseRequirementsFromContent(content, 'todo', '⏳ Requirements not yet completed');
|
|
11
|
-
const headingReqs = reqs.filter(r => r.source === 'heading');
|
|
12
|
-
|
|
13
|
-
// Ensure the parser's heading count matches core's stats for the TODO section
|
|
14
|
-
const { getProjectRequirementStats } = require('vibecodingmachine-core');
|
|
15
|
-
const os = require('os');
|
|
16
|
-
jest.spyOn(os, 'hostname').mockReturnValue('Jesses-2025-Mac.local');
|
|
17
|
-
const stats = await getProjectRequirementStats(process.cwd());
|
|
18
|
-
if (stats && stats.total > 0) {
|
|
19
|
-
expect(headingReqs.length).toBe(stats.todoCount);
|
|
20
|
-
} else {
|
|
21
|
-
// In CI or developer clones where the HOST-specific REQUIREMENTS file may be missing,
|
|
22
|
-
// just assert the parser runs and returns an array (avoid hard-failing on missing host file)
|
|
23
|
-
expect(Array.isArray(reqs)).toBe(true);
|
|
24
|
-
}
|
|
25
|
-
jest.restoreAllMocks();
|
|
26
|
-
});
|
|
27
|
-
});
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
|
|
4
|
-
describe('Requirements legacy PACKAGE: parsing', () => {
|
|
5
|
-
test('interactive.js contains legacy PACKAGE parsing branch and advances index', () => {
|
|
6
|
-
const parserPath = path.join(__dirname, '../src/utils/requirements-parser.js');
|
|
7
|
-
const content = fs.readFileSync(parserPath, 'utf8');
|
|
8
|
-
|
|
9
|
-
// Check for detection of legacy PACKAGE: format inside the parser
|
|
10
|
-
expect(content).toMatch(/if\s*\(inSection\s*&&\s*line\.trim\(\)\.startsWith\('PACKAGE:'\)\)/);
|
|
11
|
-
|
|
12
|
-
// Ensure we advance the outer loop index to avoid double-parsing
|
|
13
|
-
expect(content).toMatch(/i\s*=\s*j\s*-\s*1\s*;/);
|
|
14
|
-
});
|
|
15
|
-
});
|
|
@@ -1,28 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
const path = require('path');
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const os = require('os');
|
|
4
|
-
const { parseRequirementsFromContent } = require('../src/utils/requirements-parser');
|
|
5
|
-
|
|
6
|
-
describe('parseRequirementsFromContent integration', () => {
|
|
7
|
-
test('parses 8 TODO items from mixed-format REQUIREMENTS content', async () => {
|
|
8
|
-
const content = `# REQUIREMENTS
|
|
9
|
-
|
|
10
|
-
## ⏳ Requirements not yet completed
|
|
11
|
-
|
|
12
|
-
PACKAGE: cli
|
|
13
|
-
Add filter options to ` + "`vcm req:list`" + `: --computer <hostname>
|
|
14
|
-
|
|
15
|
-
- Add
|
|
16
|
-
- bullet one
|
|
17
|
-
- bullet two
|
|
18
|
-
|
|
19
|
-
### Modify "Add Requirement" dialog to include computer selection dropdown. Show computer focus areas as hints. Allow selecting a computer.
|
|
20
|
-
|
|
21
|
-
PACKAGE: electron-app
|
|
22
|
-
Create modal dialog for resolving sync conflicts. Show side-by-side diff of local vs remote changes.
|
|
23
|
-
|
|
24
|
-
- Create network status indicator
|
|
25
|
-
|
|
26
|
-
`;
|
|
27
|
-
|
|
28
|
-
const reqs = parseRequirementsFromContent(content, 'todo', '⏳ Requirements not yet completed');
|
|
29
|
-
// Expect exactly 8 items parsed (some may be long so ensure count)
|
|
30
|
-
expect(Array.isArray(reqs)).toBe(true);
|
|
31
|
-
// titles for quick verification
|
|
32
|
-
const titles = reqs.map(r => r.title);
|
|
33
|
-
// Should contain the explicit ### title and two PACKAGE-based and bullet items
|
|
34
|
-
expect(titles).toEqual(expect.arrayContaining([
|
|
35
|
-
expect.stringContaining('Add filter options to'),
|
|
36
|
-
expect.stringContaining('Modify "Add Requirement" dialog'),
|
|
37
|
-
expect.stringContaining('Create modal dialog for resolving sync conflicts'),
|
|
38
|
-
expect.stringContaining('bullet one')
|
|
39
|
-
]));
|
|
40
|
-
|
|
41
|
-
// Ensure we got 8 or fewer but at least 4 (this is a sanity integration test)
|
|
42
|
-
expect(reqs.length).toBeGreaterThanOrEqual(4);
|
|
43
|
-
});
|
|
44
|
-
});
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const os = require('os');
|
|
4
|
-
const { waitForIdeCompletion } = require('../src/commands/auto-direct');
|
|
5
|
-
|
|
6
|
-
jest.mock('vibecodingmachine-core', () => ({
|
|
7
|
-
getRequirementsPath: jest.fn(),
|
|
8
|
-
detectLocale: () => 'en',
|
|
9
|
-
setLocale: () => {},
|
|
10
|
-
t: (k, o) => (o ? JSON.stringify(o) : k)
|
|
11
|
-
}));
|
|
12
|
-
|
|
13
|
-
const { getRequirementsPath } = require('vibecodingmachine-core');
|
|
14
|
-
|
|
15
|
-
describe('waitForIdeCompletion', () => {
|
|
16
|
-
let tmpDir;
|
|
17
|
-
let reqDir;
|
|
18
|
-
let reqPath;
|
|
19
|
-
|
|
20
|
-
beforeEach(() => {
|
|
21
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'vcm-test-'));
|
|
22
|
-
reqDir = path.join(tmpDir, '.vibecodingmachine');
|
|
23
|
-
fs.mkdirSync(reqDir, { recursive: true });
|
|
24
|
-
reqPath = path.join(reqDir, 'REQUIREMENTS-testhost.md');
|
|
25
|
-
|
|
26
|
-
// Mock getRequirementsPath to return our temporary file
|
|
27
|
-
getRequirementsPath.mockResolvedValue(reqPath);
|
|
28
|
-
|
|
29
|
-
// Create an initial file content without the verified section
|
|
30
|
-
fs.writeFileSync(reqPath, '# Requirements\n\n- TEST A: Do something\n');
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
afterEach(() => {
|
|
34
|
-
getRequirementsPath.mockReset();
|
|
35
|
-
try {
|
|
36
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
37
|
-
} catch (e) {}
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test('resolves when requirement is moved to Verified by AI section', async () => {
|
|
41
|
-
const requirementText = 'TEST A: Do something';
|
|
42
|
-
|
|
43
|
-
// Start waitForIdeCompletion (timeout 10s for test)
|
|
44
|
-
const promise = waitForIdeCompletion(tmpDir, requirementText, 'antigravity', 10000);
|
|
45
|
-
|
|
46
|
-
// After a short delay, write the verified section to the file
|
|
47
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
48
|
-
|
|
49
|
-
const newContent = `# Requirements\n\n## ✅ Verified by AI\n- ${requirementText}\n`;
|
|
50
|
-
fs.writeFileSync(reqPath, newContent);
|
|
51
|
-
|
|
52
|
-
const result = await promise;
|
|
53
|
-
|
|
54
|
-
expect(result.success).toBe(true);
|
|
55
|
-
});
|
|
56
|
-
});
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const os = require('os');
|
|
4
|
-
|
|
5
|
-
const { waitForIdeCompletion } = require('../src/commands/auto-direct');
|
|
6
|
-
|
|
7
|
-
jest.mock('vibecodingmachine-core', () => ({
|
|
8
|
-
getRequirementsPath: jest.fn(),
|
|
9
|
-
detectLocale: () => 'en',
|
|
10
|
-
setLocale: () => {},
|
|
11
|
-
t: (k, o) => (o ? JSON.stringify(o) : k)
|
|
12
|
-
}));
|
|
13
|
-
|
|
14
|
-
const { getRequirementsPath } = require('vibecodingmachine-core');
|
|
15
|
-
const ProviderManager = require('vibecodingmachine-core/src/ide-integration/provider-manager.cjs');
|
|
16
|
-
|
|
17
|
-
describe('waitForIdeCompletion - Cursor screenshot message', () => {
|
|
18
|
-
let tmpDir;
|
|
19
|
-
let reqDir;
|
|
20
|
-
let reqPath;
|
|
21
|
-
|
|
22
|
-
beforeEach(() => {
|
|
23
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'vcm-test-'));
|
|
24
|
-
reqDir = path.join(tmpDir, '.vibecodingmachine');
|
|
25
|
-
fs.mkdirSync(reqDir, { recursive: true });
|
|
26
|
-
reqPath = path.join(reqDir, 'REQUIREMENTS-testhost.md');
|
|
27
|
-
|
|
28
|
-
getRequirementsPath.mockResolvedValue(reqPath);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
afterEach(() => {
|
|
32
|
-
getRequirementsPath.mockReset();
|
|
33
|
-
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch (e) { }
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test('detects Cursor usage limit from screenshot-like message', async () => {
|
|
37
|
-
jest.setTimeout(20000);
|
|
38
|
-
|
|
39
|
-
// Simulate REQUIREMENTS file content matching the screenshot (will be written after watcher starts)
|
|
40
|
-
const msg = `# Requirements\n\nYou've hit your usage limit\nGet Cursor Pro for more Agent usage, unlimited Tab, and more.`;
|
|
41
|
-
|
|
42
|
-
const requirementText = 'TEST A: Do something';
|
|
43
|
-
|
|
44
|
-
const markSpy = jest.spyOn(ProviderManager.prototype, 'markRateLimited');
|
|
45
|
-
|
|
46
|
-
const promise = waitForIdeCompletion(tmpDir, requirementText, 'cursor', 10000);
|
|
47
|
-
|
|
48
|
-
// After a short delay, write the quota message into the file
|
|
49
|
-
await new Promise(resolve => setTimeout(resolve, 300));
|
|
50
|
-
fs.writeFileSync(reqPath, msg);
|
|
51
|
-
|
|
52
|
-
const result = await promise;
|
|
53
|
-
|
|
54
|
-
expect(result.success).toBe(false);
|
|
55
|
-
expect(result.rateLimited).toBe(true);
|
|
56
|
-
expect(result.providerRateLimited).toBe('cursor');
|
|
57
|
-
expect(markSpy).toHaveBeenCalledWith('cursor', undefined, expect.stringContaining("You've hit your usage limit"));
|
|
58
|
-
|
|
59
|
-
markSpy.mockRestore();
|
|
60
|
-
});
|
|
61
|
-
});
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const os = require('os');
|
|
4
|
-
const { waitForIdeCompletion } = require('../src/commands/auto-direct');
|
|
5
|
-
|
|
6
|
-
jest.mock('vibecodingmachine-core', () => ({
|
|
7
|
-
getRequirementsPath: jest.fn(),
|
|
8
|
-
detectLocale: () => 'en',
|
|
9
|
-
setLocale: () => {},
|
|
10
|
-
t: (k, o) => (o ? JSON.stringify(o) : k)
|
|
11
|
-
}));
|
|
12
|
-
|
|
13
|
-
const { getRequirementsPath } = require('vibecodingmachine-core');
|
|
14
|
-
const ProviderManager = require('vibecodingmachine-core/src/ide-integration/provider-manager.cjs');
|
|
15
|
-
|
|
16
|
-
describe('waitForIdeCompletion generic rate limit detection (Cursor)', () => {
|
|
17
|
-
let tmpDir;
|
|
18
|
-
let reqDir;
|
|
19
|
-
let reqPath;
|
|
20
|
-
|
|
21
|
-
beforeEach(() => {
|
|
22
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'vcm-test-'));
|
|
23
|
-
reqDir = path.join(tmpDir, '.vibecodingmachine');
|
|
24
|
-
fs.mkdirSync(reqDir, { recursive: true });
|
|
25
|
-
reqPath = path.join(reqDir, 'REQUIREMENTS-testhost.md');
|
|
26
|
-
|
|
27
|
-
getRequirementsPath.mockResolvedValue(reqPath);
|
|
28
|
-
|
|
29
|
-
fs.writeFileSync(reqPath, '# Requirements\n\n- TEST A: Do something\n');
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
afterEach(() => {
|
|
33
|
-
getRequirementsPath.mockReset();
|
|
34
|
-
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch (e) { }
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test('detects Cursor quota message in file and marks provider rate-limited', async () => {
|
|
38
|
-
// Spy on ProviderManager.markRateLimited
|
|
39
|
-
const markSpy = jest.spyOn(ProviderManager.prototype, 'markRateLimited');
|
|
40
|
-
|
|
41
|
-
const requirementText = 'TEST A: Do something';
|
|
42
|
-
|
|
43
|
-
const promise = waitForIdeCompletion(tmpDir, requirementText, 'cursor', 10000);
|
|
44
|
-
|
|
45
|
-
// After a short delay, write a quota message into the file that mimics Cursor-like output
|
|
46
|
-
await new Promise(resolve => setTimeout(resolve, 300));
|
|
47
|
-
|
|
48
|
-
const quotaMsg = `# Requirements\n\nError: Usage cap reached for model. Try again in 15m.\n`;
|
|
49
|
-
fs.writeFileSync(reqPath, quotaMsg);
|
|
50
|
-
|
|
51
|
-
const result = await promise;
|
|
52
|
-
|
|
53
|
-
expect(result.success).toBe(false);
|
|
54
|
-
expect(result.rateLimited).toBe(true);
|
|
55
|
-
expect(result.providerRateLimited).toBe('cursor');
|
|
56
|
-
expect(markSpy).toHaveBeenCalledWith('cursor', undefined, expect.stringContaining('Usage cap'));
|
|
57
|
-
|
|
58
|
-
markSpy.mockRestore();
|
|
59
|
-
});
|
|
60
|
-
});
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const os = require('os');
|
|
4
|
-
const { waitForIdeCompletion } = require('../src/commands/auto-direct');
|
|
5
|
-
|
|
6
|
-
jest.mock('vibecodingmachine-core', () => ({
|
|
7
|
-
getRequirementsPath: jest.fn(),
|
|
8
|
-
detectLocale: () => 'en',
|
|
9
|
-
setLocale: () => {},
|
|
10
|
-
t: (k, o) => (o ? JSON.stringify(o) : k)
|
|
11
|
-
}));
|
|
12
|
-
|
|
13
|
-
const { getRequirementsPath } = require('vibecodingmachine-core');
|
|
14
|
-
|
|
15
|
-
describe('waitForIdeCompletion negative rate limit detection', () => {
|
|
16
|
-
let tmpDir;
|
|
17
|
-
let reqDir;
|
|
18
|
-
let reqPath;
|
|
19
|
-
|
|
20
|
-
beforeEach(() => {
|
|
21
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'vcm-test-'));
|
|
22
|
-
reqDir = path.join(tmpDir, '.vibecodingmachine');
|
|
23
|
-
fs.mkdirSync(reqDir, { recursive: true });
|
|
24
|
-
reqPath = path.join(reqDir, 'REQUIREMENTS-testhost.md');
|
|
25
|
-
|
|
26
|
-
getRequirementsPath.mockResolvedValue(reqPath);
|
|
27
|
-
|
|
28
|
-
fs.writeFileSync(reqPath, '# Requirements\n\n### R169: When rate limit hit, that agent should have been update in the agents list to display when the quota is reset.\n');
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
afterEach(() => {
|
|
32
|
-
getRequirementsPath.mockReset();
|
|
33
|
-
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch (e) { }
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test('does not treat requirement headings that mention rate limits as a provider rate limit', async () => {
|
|
37
|
-
const requirementText = 'R169: When rate limit hit';
|
|
38
|
-
|
|
39
|
-
const result = await waitForIdeCompletion(tmpDir, requirementText, 'cursor', 500);
|
|
40
|
-
|
|
41
|
-
// With a very short timeout, it should simply time out rather than treat the heading as a quota
|
|
42
|
-
expect(result.success).toBe(false);
|
|
43
|
-
expect(result.rateLimited).not.toBe(true);
|
|
44
|
-
});
|
|
45
|
-
});
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const os = require('os');
|
|
4
|
-
const { waitForIdeCompletion } = require('../src/commands/auto-direct');
|
|
5
|
-
|
|
6
|
-
jest.mock('vibecodingmachine-core', () => ({
|
|
7
|
-
getRequirementsPath: jest.fn(),
|
|
8
|
-
detectLocale: () => 'en',
|
|
9
|
-
setLocale: () => {},
|
|
10
|
-
t: (k, o) => (o ? JSON.stringify(o) : k)
|
|
11
|
-
}));
|
|
12
|
-
|
|
13
|
-
const { getRequirementsPath } = require('vibecodingmachine-core');
|
|
14
|
-
const ProviderManager = require('vibecodingmachine-core/src/ide-integration/provider-manager.cjs');
|
|
15
|
-
|
|
16
|
-
describe('waitForIdeCompletion rate limit detection', () => {
|
|
17
|
-
let tmpDir;
|
|
18
|
-
let reqDir;
|
|
19
|
-
let reqPath;
|
|
20
|
-
|
|
21
|
-
beforeEach(() => {
|
|
22
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'vcm-test-'));
|
|
23
|
-
reqDir = path.join(tmpDir, '.vibecodingmachine');
|
|
24
|
-
fs.mkdirSync(reqDir, { recursive: true });
|
|
25
|
-
reqPath = path.join(reqDir, 'REQUIREMENTS-testhost.md');
|
|
26
|
-
|
|
27
|
-
getRequirementsPath.mockResolvedValue(reqPath);
|
|
28
|
-
|
|
29
|
-
fs.writeFileSync(reqPath, '# Requirements\n\n- TEST A: Do something\n');
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
afterEach(() => {
|
|
33
|
-
getRequirementsPath.mockReset();
|
|
34
|
-
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch (e) { }
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test('detects Antigravity quota message in file and marks provider rate-limited', async () => {
|
|
38
|
-
// Spy on ProviderManager.markRateLimited
|
|
39
|
-
const markSpy = jest.spyOn(ProviderManager.prototype, 'markRateLimited');
|
|
40
|
-
|
|
41
|
-
const requirementText = 'TEST A: Do something';
|
|
42
|
-
|
|
43
|
-
const promise = waitForIdeCompletion(tmpDir, requirementText, 'antigravity', 10000);
|
|
44
|
-
|
|
45
|
-
// After a short delay, write a quota message into the file that mimics Antigravity output
|
|
46
|
-
await new Promise(resolve => setTimeout(resolve, 300));
|
|
47
|
-
|
|
48
|
-
const quotaMsg = `# Requirements\n\nError Agent execution terminated due to error.\nYou have reached the quota limit for this model. You can resume using this model at 1/19/2026, 4:07:27 PM.\n`;
|
|
49
|
-
fs.writeFileSync(reqPath, quotaMsg);
|
|
50
|
-
|
|
51
|
-
const result = await promise;
|
|
52
|
-
|
|
53
|
-
expect(result.success).toBe(false);
|
|
54
|
-
expect(result.antigravityRateLimited).toBe(true);
|
|
55
|
-
expect(markSpy).toHaveBeenCalledWith('antigravity', undefined, expect.stringContaining('You have reached the quota limit'));
|
|
56
|
-
|
|
57
|
-
markSpy.mockRestore();
|
|
58
|
-
});
|
|
59
|
-
});
|
package/verify_fix.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
const interactive = require('./src/utils/interactive');
|
|
2
|
-
const chalk = require('chalk');
|
|
3
|
-
|
|
4
|
-
console.log(chalk.green('Interactive module loaded successfully.'));
|
|
5
|
-
|
|
6
|
-
// Basic syntax and load check passed.
|
|
7
|
-
|
|
8
|
-
// Now let's try to verify the quota logic if possible.
|
|
9
|
-
// Since the function is internal, we can't unit test it easily from outside without mocking.
|
|
10
|
-
// However, we confirmed the syntax is valid.
|
|
11
|
-
|
|
12
|
-
// We can try to mock the quota object structure to ensure our logic snippet is valid JS.
|
|
13
|
-
function testQuotaLogic() {
|
|
14
|
-
const quota = { type: 'rate-limit', remaining: 0, resetsAt: Date.now() + 10000 };
|
|
15
|
-
// The fixed code:
|
|
16
|
-
let isExceeded = false;
|
|
17
|
-
if (typeof quota.isExceeded === 'function') {
|
|
18
|
-
isExceeded = quota.isExceeded();
|
|
19
|
-
} else if (quota.remaining !== undefined) {
|
|
20
|
-
isExceeded = quota.remaining <= 0;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (isExceeded !== true) {
|
|
24
|
-
console.error('Logic test failed: Expected isExceeded to be true for remaining: 0');
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
27
|
-
console.log('Logic test passed: Safe quota check handles plain objects.');
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
testQuotaLogic();
|
|
32
|
-
console.log('Verification passed: Module loads and quota logic is safe.');
|
|
33
|
-
} catch (e) {
|
|
34
|
-
console.error('Verification failed:', e);
|
|
35
|
-
process.exit(1);
|
|
36
|
-
}
|
package/verify_ui.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
const chalk = require('chalk');
|
|
2
|
-
// Mock translation
|
|
3
|
-
const t = (key) => key === 'provider.legend' ? 'Legend' : key;
|
|
4
|
-
|
|
5
|
-
// Mock provider data
|
|
6
|
-
const defs = [
|
|
7
|
-
{ id: 'groq', name: 'Groq', type: 'direct' },
|
|
8
|
-
{ id: 'anthropic', name: 'Anthropic', type: 'direct' },
|
|
9
|
-
{ id: 'ollama', name: 'Ollama', type: 'direct' },
|
|
10
|
-
{ id: 'cursor', name: 'Cursor', type: 'ide' }
|
|
11
|
-
];
|
|
12
|
-
|
|
13
|
-
// Mock renderer snippet (simplified logic from interactive.js)
|
|
14
|
-
console.log('Rendering Legend...');
|
|
15
|
-
console.log(chalk.gray(' ' + t('provider.legend') + ': 🖥️ IDE ☁️ Cloud LLM 🐢 Local LLM'));
|
|
16
|
-
|
|
17
|
-
console.log('Rendering Items...');
|
|
18
|
-
defs.forEach((def, idx) => {
|
|
19
|
-
let typeIcon = '☁️ ';
|
|
20
|
-
if (def.type === 'ide') typeIcon = '🖥️ ';
|
|
21
|
-
else if (def.id === 'ollama') typeIcon = '🐢 ';
|
|
22
|
-
|
|
23
|
-
const prefix = ' ';
|
|
24
|
-
const statusEmoji = '🟢';
|
|
25
|
-
const line = `${prefix} ${typeIcon} ${statusEmoji} ${idx + 1}. ${def.name}`;
|
|
26
|
-
console.log(line);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
console.log('\nVerification:');
|
|
30
|
-
// Simple assertion check
|
|
31
|
-
const output = ` Legend: 🖥️ IDE ☁️ Cloud LLM 🐢 Local LLM
|
|
32
|
-
☁️ 🟢 1. Groq
|
|
33
|
-
☁️ 🟢 2. Anthropic
|
|
34
|
-
🐢 🟢 3. Ollama
|
|
35
|
-
🖥️ 🟢 4. Cursor`;
|
|
36
|
-
|
|
37
|
-
console.log('Expected output snippet:');
|
|
38
|
-
console.log(output);
|