ai-spec-dev 0.41.0 → 0.42.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 (37) hide show
  1. package/.ai-spec-workspace.json +17 -0
  2. package/.ai-spec.json +7 -0
  3. package/cli/pipeline/single-repo.ts +19 -10
  4. package/core/cli-ui.ts +136 -0
  5. package/core/code-generator.ts +4 -2
  6. package/core/error-feedback.ts +4 -2
  7. package/core/provider-utils.ts +8 -7
  8. package/demo-backend/.ai-spec-constitution.md +65 -0
  9. package/demo-backend/package.json +21 -0
  10. package/demo-backend/prisma/schema.prisma +22 -0
  11. package/demo-backend/specs/feature-1-bookmark-id-uuid-title-string-required-url-str-v1.dsl.json +186 -0
  12. package/demo-backend/specs/feature-1-bookmark-id-uuid-title-string-required-url-str-v1.md +211 -0
  13. package/demo-backend/src/controllers/bookmark.controller.test.ts +255 -0
  14. package/demo-backend/src/controllers/bookmark.controller.ts +187 -0
  15. package/demo-backend/src/index.ts +17 -0
  16. package/demo-backend/src/routes/bookmark.routes.test.ts +264 -0
  17. package/demo-backend/src/routes/bookmark.routes.ts +11 -0
  18. package/demo-backend/src/routes/index.ts +8 -0
  19. package/demo-backend/src/services/bookmark.service.test.ts +433 -0
  20. package/demo-backend/src/services/bookmark.service.ts +261 -0
  21. package/demo-backend/tsconfig.json +12 -0
  22. package/demo-frontend/.ai-spec-constitution.md +95 -0
  23. package/demo-frontend/package.json +23 -0
  24. package/demo-frontend/src/App.tsx +12 -0
  25. package/demo-frontend/src/main.tsx +9 -0
  26. package/demo-frontend/tsconfig.json +13 -0
  27. package/dist/cli/index.js +130 -21
  28. package/dist/cli/index.js.map +1 -1
  29. package/dist/cli/index.mjs +130 -21
  30. package/dist/cli/index.mjs.map +1 -1
  31. package/dist/index.js +80 -8
  32. package/dist/index.js.map +1 -1
  33. package/dist/index.mjs +80 -8
  34. package/dist/index.mjs.map +1 -1
  35. package/package.json +1 -1
  36. package/RELEASE_LOG.md +0 -2962
  37. package/purpose.md +0 -1434
@@ -0,0 +1,264 @@
1
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2
+ import http from 'http';
3
+ import { app } from '../app';
4
+
5
+ let server: http.Server;
6
+ let baseUrl: string;
7
+
8
+ beforeAll(async () => {
9
+ server = app.listen(0);
10
+ const address = server.address();
11
+ if (typeof address === 'object' && address !== null) {
12
+ baseUrl = `http://localhost:${address.port}`;
13
+ } else {
14
+ throw new Error('Server address is not available');
15
+ }
16
+ });
17
+
18
+ afterAll(() => {
19
+ server.close();
20
+ });
21
+
22
+ function request(method: string, path: string, body?: any): Promise<{ statusCode: number; body: any }> {
23
+ return new Promise((resolve, reject) => {
24
+ const url = new URL(path, baseUrl);
25
+ const options = {
26
+ hostname: url.hostname,
27
+ port: url.port,
28
+ path: url.pathname + url.search,
29
+ method,
30
+ headers: {
31
+ 'Content-Type': 'application/json',
32
+ },
33
+ };
34
+
35
+ const req = http.request(options, (res) => {
36
+ let data = '';
37
+ res.on('data', (chunk) => {
38
+ data += chunk;
39
+ });
40
+ res.on('end', () => {
41
+ try {
42
+ const parsedBody = data ? JSON.parse(data) : {};
43
+ resolve({ statusCode: res.statusCode || 500, body: parsedBody });
44
+ } catch (e) {
45
+ resolve({ statusCode: res.statusCode || 500, body: data });
46
+ }
47
+ });
48
+ });
49
+
50
+ req.on('error', reject);
51
+
52
+ if (body) {
53
+ req.write(JSON.stringify(body));
54
+ }
55
+
56
+ req.end();
57
+ });
58
+ }
59
+
60
+ describe('Bookmark API Endpoints', () => {
61
+ let createdBookmarkId: string;
62
+
63
+ describe('POST /api/v1/bookmarks', () => {
64
+ it('should create a new bookmark with valid data', async () => {
65
+ const bookmarkData = {
66
+ title: 'Test Bookmark',
67
+ url: 'https://example.com',
68
+ tags: ['test', 'example'],
69
+ };
70
+
71
+ const response = await request('POST', '/api/v1/bookmarks', bookmarkData);
72
+
73
+ expect(response.statusCode).toBe(201);
74
+ expect(response.body.code).toBe(201);
75
+ expect(response.body.message).toBe('success');
76
+ expect(response.body.data).toHaveProperty('id');
77
+ expect(response.body.data.title).toBe(bookmarkData.title);
78
+ expect(response.body.data.url).toBe(bookmarkData.url);
79
+ expect(response.body.data.tags).toEqual(bookmarkData.tags);
80
+ expect(response.body.data).toHaveProperty('createdAt');
81
+ expect(response.body.data).toHaveProperty('updatedAt');
82
+
83
+ createdBookmarkId = response.body.data.id;
84
+ });
85
+
86
+ it('should return 400 when title is missing', async () => {
87
+ const bookmarkData = {
88
+ url: 'https://example.com',
89
+ };
90
+
91
+ const response = await request('POST', '/api/v1/bookmarks', bookmarkData);
92
+
93
+ expect(response.statusCode).toBe(400);
94
+ expect(response.body.code).toBe(400);
95
+ expect(response.body.message).toBeDefined();
96
+ });
97
+
98
+ it('should return 400 when URL is invalid', async () => {
99
+ const bookmarkData = {
100
+ title: 'Invalid URL',
101
+ url: 'invalid-url',
102
+ };
103
+
104
+ const response = await request('POST', '/api/v1/bookmarks', bookmarkData);
105
+
106
+ expect(response.statusCode).toBe(400);
107
+ expect(response.body.code).toBe(400);
108
+ expect(response.body.message).toBeDefined();
109
+ });
110
+
111
+ it('should return 400 when URL is missing', async () => {
112
+ const bookmarkData = {
113
+ title: 'No URL',
114
+ };
115
+
116
+ const response = await request('POST', '/api/v1/bookmarks', bookmarkData);
117
+
118
+ expect(response.statusCode).toBe(400);
119
+ expect(response.body.code).toBe(400);
120
+ expect(response.body.message).toBeDefined();
121
+ });
122
+ });
123
+
124
+ describe('GET /api/v1/bookmarks', () => {
125
+ it('should return paginated list of bookmarks', async () => {
126
+ const response = await request('GET', '/api/v1/bookmarks?limit=10&offset=0');
127
+
128
+ expect(response.statusCode).toBe(200);
129
+ expect(response.body.code).toBe(200);
130
+ expect(response.body.message).toBe('success');
131
+ expect(response.body.data).toHaveProperty('bookmarks');
132
+ expect(Array.isArray(response.body.data.bookmarks)).toBe(true);
133
+ expect(response.body.data).toHaveProperty('total');
134
+ expect(typeof response.body.data.total).toBe('number');
135
+ });
136
+
137
+ it('should use default pagination when no parameters provided', async () => {
138
+ const response = await request('GET', '/api/v1/bookmarks');
139
+
140
+ expect(response.statusCode).toBe(200);
141
+ expect(response.body.code).toBe(200);
142
+ expect(response.body.message).toBe('success');
143
+ expect(response.body.data).toHaveProperty('bookmarks');
144
+ expect(response.body.data).toHaveProperty('total');
145
+ });
146
+
147
+ it('should return 400 for invalid limit parameter', async () => {
148
+ const response = await request('GET', '/api/v1/bookmarks?limit=invalid');
149
+
150
+ expect(response.statusCode).toBe(400);
151
+ expect(response.body.code).toBe(400);
152
+ expect(response.body.message).toBeDefined();
153
+ });
154
+
155
+ it('should return 400 for invalid offset parameter', async () => {
156
+ const response = await request('GET', '/api/v1/bookmarks?offset=-1');
157
+
158
+ expect(response.statusCode).toBe(400);
159
+ expect(response.body.code).toBe(400);
160
+ expect(response.body.message).toBeDefined();
161
+ });
162
+ });
163
+
164
+ describe('PUT /api/v1/bookmarks/:id', () => {
165
+ it('should update an existing bookmark', async () => {
166
+ if (!createdBookmarkId) {
167
+ throw new Error('No bookmark created to update');
168
+ }
169
+
170
+ const updateData = {
171
+ title: 'Updated Title',
172
+ url: 'https://updated-example.com',
173
+ tags: ['updated', 'test'],
174
+ };
175
+
176
+ const response = await request('PUT', `/api/v1/bookmarks/${createdBookmarkId}`, updateData);
177
+
178
+ expect(response.statusCode).toBe(200);
179
+ expect(response.body.code).toBe(200);
180
+ expect(response.body.message).toBe('success');
181
+ expect(response.body.data.id).toBe(createdBookmarkId);
182
+ expect(response.body.data.title).toBe(updateData.title);
183
+ expect(response.body.data.url).toBe(updateData.url);
184
+ expect(response.body.data.tags).toEqual(updateData.tags);
185
+ });
186
+
187
+ it('should return 404 for non-existent bookmark', async () => {
188
+ const nonExistentId = '00000000-0000-0000-0000-000000000000';
189
+ const updateData = {
190
+ title: 'Updated Title',
191
+ url: 'https://example.com',
192
+ tags: ['test'],
193
+ };
194
+
195
+ const response = await request('PUT', `/api/v1/bookmarks/${nonExistentId}`, updateData);
196
+
197
+ expect(response.statusCode).toBe(404);
198
+ expect(response.body.code).toBe(404);
199
+ expect(response.body.message).toBeDefined();
200
+ });
201
+
202
+ it('should return 400 for invalid bookmark ID format', async () => {
203
+ const invalidId = 'invalid-uuid';
204
+ const updateData = {
205
+ title: 'Updated Title',
206
+ url: 'https://example.com',
207
+ tags: ['test'],
208
+ };
209
+
210
+ const response = await request('PUT', `/api/v1/bookmarks/${invalidId}`, updateData);
211
+
212
+ expect(response.statusCode).toBe(400);
213
+ expect(response.body.code).toBe(400);
214
+ expect(response.body.message).toBeDefined();
215
+ });
216
+
217
+ it('should return 400 when required fields are missing', async () => {
218
+ if (!createdBookmarkId) {
219
+ throw new Error('No bookmark created to update');
220
+ }
221
+
222
+ const updateData = {
223
+ title: 'Updated Title',
224
+ };
225
+
226
+ const response = await request('PUT', `/api/v1/bookmarks/${createdBookmarkId}`, updateData);
227
+
228
+ expect(response.statusCode).toBe(400);
229
+ expect(response.body.code).toBe(400);
230
+ expect(response.body.message).toBeDefined();
231
+ });
232
+ });
233
+
234
+ describe('DELETE /api/v1/bookmarks/:id', () => {
235
+ it('should delete an existing bookmark', async () => {
236
+ if (!createdBookmarkId) {
237
+ throw new Error('No bookmark created to delete');
238
+ }
239
+
240
+ const response = await request('DELETE', `/api/v1/bookmarks/${createdBookmarkId}`);
241
+
242
+ expect(response.statusCode).toBe(204);
243
+ expect(response.body).toBe('');
244
+ });
245
+
246
+ it('should return 404 for non-existent bookmark', async () => {
247
+ const nonExistentId = '00000000-0000-0000-0000-000000000000';
248
+ const response = await request('DELETE', `/api/v1/bookmarks/${nonExistentId}`);
249
+
250
+ expect(response.statusCode).toBe(404);
251
+ expect(response.body.code).toBe(404);
252
+ expect(response.body.message).toBeDefined();
253
+ });
254
+
255
+ it('should return 400 for invalid bookmark ID format', async () => {
256
+ const invalidId = 'invalid-uuid';
257
+ const response = await request('DELETE', `/api/v1/bookmarks/${invalidId}`);
258
+
259
+ expect(response.statusCode).toBe(400);
260
+ expect(response.body.code).toBe(400);
261
+ expect(response.body.message).toBeDefined();
262
+ });
263
+ });
264
+ });
@@ -0,0 +1,11 @@
1
+ import { Router } from 'express';
2
+ import { getBookmarks, createBookmark, updateBookmark, deleteBookmark } from '../controllers/bookmark.controller';
3
+
4
+ const router = Router();
5
+
6
+ router.get('/', getBookmarks);
7
+ router.post('/', createBookmark);
8
+ router.put('/:id', updateBookmark);
9
+ router.delete('/:id', deleteBookmark);
10
+
11
+ export default router;
@@ -0,0 +1,8 @@
1
+ import { Router } from 'express';
2
+ import bookmarkRoutes from './bookmark.routes';
3
+
4
+ const router = Router();
5
+
6
+ router.use('/api/v1/bookmarks', bookmarkRoutes);
7
+
8
+ export default router;