@vibedx/vibekit 0.1.0

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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +368 -0
  3. package/assets/config.yml +35 -0
  4. package/assets/default.md +47 -0
  5. package/assets/instructions/README.md +46 -0
  6. package/assets/instructions/claude.md +83 -0
  7. package/assets/instructions/codex.md +19 -0
  8. package/index.js +106 -0
  9. package/package.json +90 -0
  10. package/src/commands/close/index.js +66 -0
  11. package/src/commands/close/index.test.js +235 -0
  12. package/src/commands/get-started/index.js +138 -0
  13. package/src/commands/get-started/index.test.js +246 -0
  14. package/src/commands/init/index.js +51 -0
  15. package/src/commands/init/index.test.js +159 -0
  16. package/src/commands/link/index.js +395 -0
  17. package/src/commands/link/index.test.js +28 -0
  18. package/src/commands/lint/index.js +657 -0
  19. package/src/commands/lint/index.test.js +569 -0
  20. package/src/commands/list/index.js +131 -0
  21. package/src/commands/list/index.test.js +153 -0
  22. package/src/commands/new/index.js +305 -0
  23. package/src/commands/new/index.test.js +256 -0
  24. package/src/commands/refine/index.js +741 -0
  25. package/src/commands/refine/index.test.js +28 -0
  26. package/src/commands/review/index.js +957 -0
  27. package/src/commands/review/index.test.js +193 -0
  28. package/src/commands/start/index.js +180 -0
  29. package/src/commands/start/index.test.js +88 -0
  30. package/src/commands/unlink/index.js +123 -0
  31. package/src/commands/unlink/index.test.js +22 -0
  32. package/src/utils/arrow-select.js +233 -0
  33. package/src/utils/cli.js +489 -0
  34. package/src/utils/cli.test.js +9 -0
  35. package/src/utils/git.js +146 -0
  36. package/src/utils/git.test.js +330 -0
  37. package/src/utils/index.js +193 -0
  38. package/src/utils/index.test.js +375 -0
  39. package/src/utils/prompts.js +47 -0
  40. package/src/utils/prompts.test.js +165 -0
  41. package/src/utils/test-helpers.js +492 -0
  42. package/src/utils/ticket.js +423 -0
  43. package/src/utils/ticket.test.js +190 -0
@@ -0,0 +1,256 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import {
5
+ createTempDir,
6
+ cleanupTempDir,
7
+ mockConsole,
8
+ mockProcessCwd,
9
+ mockProcessExit,
10
+ createMockVibeProject
11
+ } from '../../utils/test-helpers.js';
12
+ import newCommand from './index.js';
13
+
14
+ describe('new command', () => {
15
+ let tempDir;
16
+ let consoleMock;
17
+ let restoreCwd;
18
+ let exitMock;
19
+
20
+ beforeEach(() => {
21
+ // Create temp directory
22
+ tempDir = createTempDir('new-test');
23
+
24
+ // Mock console and process
25
+ consoleMock = mockConsole();
26
+ restoreCwd = mockProcessCwd(tempDir);
27
+ exitMock = mockProcessExit();
28
+
29
+ // Create a basic vibe project structure
30
+ createMockVibeProject(tempDir);
31
+ });
32
+
33
+ afterEach(() => {
34
+ // Restore mocks
35
+ consoleMock.restore();
36
+ restoreCwd();
37
+ exitMock.restore();
38
+
39
+ // Cleanup temp directory
40
+ cleanupTempDir(tempDir);
41
+ });
42
+
43
+ describe('basic ticket creation', () => {
44
+ it('should create a new ticket with default settings', async () => {
45
+ // Act
46
+ await newCommand(['Fix authentication bug']);
47
+
48
+ // Assert
49
+ const ticketsDir = path.join(tempDir, '.vibe', 'tickets');
50
+ const files = fs.readdirSync(ticketsDir);
51
+ const ticketFile = files.find(f => f.startsWith('TKT-001'));
52
+
53
+ expect(ticketFile).toBeTruthy();
54
+ expect(files).toHaveLength(1);
55
+
56
+ const ticketPath = path.join(ticketsDir, ticketFile);
57
+ const content = fs.readFileSync(ticketPath, 'utf-8');
58
+ expect(content).toContain('title: Fix authentication bug');
59
+ expect(content).toContain('priority: medium');
60
+ expect(content).toContain('status: open');
61
+ });
62
+
63
+ it('should handle multi-word titles correctly', async () => {
64
+ // Act
65
+ await newCommand(['Add dark mode toggle for user interface']);
66
+
67
+ // Assert
68
+ const ticketsDir = path.join(tempDir, '.vibe', 'tickets');
69
+ const files = fs.readdirSync(ticketsDir);
70
+ const ticketFile = files[0];
71
+
72
+ const content = fs.readFileSync(path.join(ticketsDir, ticketFile), 'utf-8');
73
+ expect(content).toContain('title: Add dark mode toggle for user interface');
74
+ });
75
+
76
+ it('should log success message with ticket details', async () => {
77
+ // Act
78
+ await newCommand(['Test ticket']);
79
+
80
+ // Assert
81
+ const successMessages = consoleMock.logs.log.filter(log =>
82
+ log.includes('Created ticket:') &&
83
+ log.includes('TKT-001') &&
84
+ log.includes('priority: medium') &&
85
+ log.includes('status: open')
86
+ );
87
+ expect(successMessages.length).toBeGreaterThan(0);
88
+ });
89
+ });
90
+
91
+ describe('priority and status options', () => {
92
+ it('should accept custom priority', async () => {
93
+ // Act
94
+ await newCommand(['High priority task', '--priority', 'high']);
95
+
96
+ // Assert
97
+ const ticketsDir = path.join(tempDir, '.vibe', 'tickets');
98
+ const files = fs.readdirSync(ticketsDir);
99
+ const content = fs.readFileSync(path.join(ticketsDir, files[0]), 'utf-8');
100
+ expect(content).toContain('priority: high');
101
+ });
102
+
103
+ it('should accept custom status', async () => {
104
+ // Act
105
+ await newCommand(['In progress task', '--status', 'in_progress']);
106
+
107
+ // Assert
108
+ const ticketsDir = path.join(tempDir, '.vibe', 'tickets');
109
+ const files = fs.readdirSync(ticketsDir);
110
+ const content = fs.readFileSync(path.join(ticketsDir, files[0]), 'utf-8');
111
+ expect(content).toContain('status: in_progress');
112
+ });
113
+
114
+ it('should accept both priority and status', async () => {
115
+ // Act
116
+ await newCommand(['Custom task', '--priority', 'urgent', '--status', 'review']);
117
+
118
+ // Assert
119
+ const ticketsDir = path.join(tempDir, '.vibe', 'tickets');
120
+ const files = fs.readdirSync(ticketsDir);
121
+ const content = fs.readFileSync(path.join(ticketsDir, files[0]), 'utf-8');
122
+ expect(content).toContain('priority: urgent');
123
+ expect(content).toContain('status: review');
124
+ });
125
+ });
126
+
127
+ describe('error handling', () => {
128
+ it('should exit with error when no title provided', async () => {
129
+ // Act & Assert
130
+ await expect(async () => {
131
+ await newCommand([]);
132
+ }).rejects.toThrow('process.exit(1)');
133
+
134
+ expect(exitMock.exitCalls).toContain(1);
135
+ });
136
+
137
+ it('should exit with error when config missing', async () => {
138
+ // Arrange - remove config file
139
+ fs.unlinkSync(path.join(tempDir, '.vibe', 'config.yml'));
140
+
141
+ // Act & Assert
142
+ await expect(async () => {
143
+ await newCommand(['Test ticket']);
144
+ }).rejects.toThrow('process.exit(1)');
145
+
146
+ expect(exitMock.exitCalls).toContain(1);
147
+ });
148
+
149
+ it('should exit with error when template missing', async () => {
150
+ // Arrange - remove template file
151
+ fs.unlinkSync(path.join(tempDir, '.vibe', '.templates', 'default.md'));
152
+
153
+ // Act & Assert
154
+ await expect(async () => {
155
+ await newCommand(['Test ticket']);
156
+ }).rejects.toThrow('process.exit(1)');
157
+
158
+ expect(exitMock.exitCalls).toContain(1);
159
+ });
160
+
161
+ it('should handle empty title gracefully', async () => {
162
+ // Act & Assert
163
+ await expect(async () => {
164
+ await newCommand(['']);
165
+ }).rejects.toThrow('process.exit(1)');
166
+ });
167
+ });
168
+
169
+ describe('ticket ID generation', () => {
170
+ it('should increment ticket ID for multiple tickets', async () => {
171
+ // Act - create multiple tickets
172
+ await newCommand(['First ticket']);
173
+ await newCommand(['Second ticket']);
174
+ await newCommand(['Third ticket']);
175
+
176
+ // Assert
177
+ const ticketsDir = path.join(tempDir, '.vibe', 'tickets');
178
+ const files = fs.readdirSync(ticketsDir).sort();
179
+
180
+ expect(files).toHaveLength(3);
181
+ expect(files[0]).toMatch(/^TKT-001/);
182
+ expect(files[1]).toMatch(/^TKT-002/);
183
+ expect(files[2]).toMatch(/^TKT-003/);
184
+ });
185
+
186
+ it('should create proper slug from title', async () => {
187
+ // Act
188
+ await newCommand(['Add User Authentication System']);
189
+
190
+ // Assert
191
+ const ticketsDir = path.join(tempDir, '.vibe', 'tickets');
192
+ const files = fs.readdirSync(ticketsDir);
193
+
194
+ expect(files[0]).toMatch(/add-user-authentication-system/);
195
+ });
196
+ });
197
+
198
+ describe('template processing', () => {
199
+ it('should replace template placeholders correctly', async () => {
200
+ // Act
201
+ await newCommand(['Template test']);
202
+
203
+ // Assert
204
+ const ticketsDir = path.join(tempDir, '.vibe', 'tickets');
205
+ const files = fs.readdirSync(ticketsDir);
206
+ const content = fs.readFileSync(path.join(ticketsDir, files[0]), 'utf-8');
207
+
208
+ expect(content).toContain('id: TKT-001');
209
+ expect(content).toContain('title: Template test');
210
+ expect(content).toMatch(/created_at: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
211
+ expect(content).toMatch(/updated_at: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
212
+ });
213
+
214
+ it('should preserve template structure', async () => {
215
+ // Act
216
+ await newCommand(['Structure test']);
217
+
218
+ // Assert
219
+ const ticketsDir = path.join(tempDir, '.vibe', 'tickets');
220
+ const files = fs.readdirSync(ticketsDir);
221
+ const content = fs.readFileSync(path.join(ticketsDir, files[0]), 'utf-8');
222
+
223
+ expect(content).toContain('## Description');
224
+ expect(content).toContain('## Acceptance Criteria');
225
+ expect(content).toContain('## Code Quality');
226
+ });
227
+ });
228
+
229
+ describe('argument parsing', () => {
230
+ it('should handle flags and title correctly', async () => {
231
+ // Act
232
+ await newCommand(['My ticket title', '--priority', 'low', '--status', 'done']);
233
+
234
+ // Assert
235
+ const ticketsDir = path.join(tempDir, '.vibe', 'tickets');
236
+ const files = fs.readdirSync(ticketsDir);
237
+ const content = fs.readFileSync(path.join(ticketsDir, files[0]), 'utf-8');
238
+
239
+ expect(content).toContain('title: My ticket title');
240
+ expect(content).toContain('priority: low');
241
+ expect(content).toContain('status: done');
242
+ });
243
+
244
+ it('should handle title with multiple words', async () => {
245
+ // Act
246
+ await newCommand(['Fix', 'the', 'critical', 'authentication', 'bug']);
247
+
248
+ // Assert
249
+ const ticketsDir = path.join(tempDir, '.vibe', 'tickets');
250
+ const files = fs.readdirSync(ticketsDir);
251
+ const content = fs.readFileSync(path.join(ticketsDir, files[0]), 'utf-8');
252
+
253
+ expect(content).toContain('title: Fix the critical authentication bug');
254
+ });
255
+ });
256
+ });