ai-spec-dev 0.42.0 → 0.55.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 (70) hide show
  1. package/README.md +86 -40
  2. package/cli/commands/config.ts +129 -1
  3. package/cli/commands/create.ts +246 -11
  4. package/cli/commands/fix-history.ts +176 -0
  5. package/cli/commands/init.ts +344 -106
  6. package/cli/index.ts +3 -7
  7. package/cli/pipeline/helpers.ts +6 -0
  8. package/cli/pipeline/multi-repo.ts +291 -26
  9. package/cli/pipeline/single-repo.ts +103 -2
  10. package/cli/utils.ts +95 -4
  11. package/core/code-generator.ts +63 -14
  12. package/core/config-defaults.ts +44 -0
  13. package/core/constitution-generator.ts +2 -1
  14. package/core/cross-stack-verifier.ts +395 -0
  15. package/core/dsl-extractor.ts +2 -1
  16. package/core/error-feedback.ts +3 -2
  17. package/core/fix-history.ts +333 -0
  18. package/core/import-fixer.ts +827 -0
  19. package/core/import-verifier.ts +569 -0
  20. package/core/knowledge-memory.ts +55 -6
  21. package/core/openapi-exporter.ts +3 -2
  22. package/core/repo-store.ts +95 -0
  23. package/core/reviewer.ts +14 -13
  24. package/core/run-logger.ts +3 -4
  25. package/core/run-snapshot.ts +2 -3
  26. package/core/run-trend.ts +3 -4
  27. package/core/self-evaluator.ts +44 -7
  28. package/core/spec-generator.ts +30 -45
  29. package/core/token-budget.ts +3 -8
  30. package/core/types-generator.ts +2 -2
  31. package/core/vcr.ts +3 -1
  32. package/dist/cli/index.js +3889 -1937
  33. package/dist/cli/index.js.map +1 -1
  34. package/dist/cli/index.mjs +3888 -1936
  35. package/dist/cli/index.mjs.map +1 -1
  36. package/dist/index.d.mts +17 -2
  37. package/dist/index.d.ts +17 -2
  38. package/dist/index.js +292 -181
  39. package/dist/index.js.map +1 -1
  40. package/dist/index.mjs +292 -181
  41. package/dist/index.mjs.map +1 -1
  42. package/package.json +2 -2
  43. package/tests/cross-stack-verifier.test.ts +301 -0
  44. package/tests/fix-history.test.ts +335 -0
  45. package/tests/import-fixer.test.ts +944 -0
  46. package/tests/import-verifier.test.ts +420 -0
  47. package/tests/knowledge-memory.test.ts +40 -0
  48. package/tests/self-evaluator.test.ts +97 -0
  49. package/cli/commands/model.ts +0 -156
  50. package/cli/commands/scan.ts +0 -99
  51. package/cli/commands/workspace.ts +0 -219
  52. package/demo-backend/.ai-spec-constitution.md +0 -65
  53. package/demo-backend/package.json +0 -21
  54. package/demo-backend/prisma/schema.prisma +0 -22
  55. package/demo-backend/specs/feature-1-bookmark-id-uuid-title-string-required-url-str-v1.dsl.json +0 -186
  56. package/demo-backend/specs/feature-1-bookmark-id-uuid-title-string-required-url-str-v1.md +0 -211
  57. package/demo-backend/src/controllers/bookmark.controller.test.ts +0 -255
  58. package/demo-backend/src/controllers/bookmark.controller.ts +0 -187
  59. package/demo-backend/src/index.ts +0 -17
  60. package/demo-backend/src/routes/bookmark.routes.test.ts +0 -264
  61. package/demo-backend/src/routes/bookmark.routes.ts +0 -11
  62. package/demo-backend/src/routes/index.ts +0 -8
  63. package/demo-backend/src/services/bookmark.service.test.ts +0 -433
  64. package/demo-backend/src/services/bookmark.service.ts +0 -261
  65. package/demo-backend/tsconfig.json +0 -12
  66. package/demo-frontend/.ai-spec-constitution.md +0 -95
  67. package/demo-frontend/package.json +0 -23
  68. package/demo-frontend/src/App.tsx +0 -12
  69. package/demo-frontend/src/main.tsx +0 -9
  70. package/demo-frontend/tsconfig.json +0 -13
@@ -1,187 +0,0 @@
1
- import { Request, Response, NextFunction } from 'express';
2
- import { AppError } from '../middleware/error.middleware';
3
- import * as bookmarkService from '../services/bookmark.service';
4
-
5
- interface BookmarkQueryParams {
6
- limit?: string;
7
- offset?: string;
8
- }
9
-
10
- interface CreateBookmarkBody {
11
- title?: string;
12
- url?: string;
13
- tags?: string[];
14
- }
15
-
16
- interface UpdateBookmarkBody {
17
- title?: string;
18
- url?: string;
19
- tags?: string[];
20
- }
21
-
22
- interface BookmarkParams {
23
- id?: string;
24
- }
25
-
26
- export class BookmarkController {
27
- static async getBookmarks(
28
- req: Request<{}, {}, {}, BookmarkQueryParams>,
29
- res: Response,
30
- next: NextFunction
31
- ) {
32
- try {
33
- const { limit: limitStr, offset: offsetStr } = req.query;
34
-
35
- const limit = limitStr ? parseInt(limitStr, 10) : 20;
36
- const offset = offsetStr ? parseInt(offsetStr, 10) : 0;
37
-
38
- if (isNaN(limit) || limit <= 0) {
39
- throw new AppError(400, '"limit" must be a positive integer');
40
- }
41
-
42
- if (isNaN(offset) || offset < 0) {
43
- throw new AppError(400, '"offset" must be a non-negative integer');
44
- }
45
-
46
- const result = await bookmarkService.getBookmarks(limit, offset);
47
-
48
- res.status(200).json({
49
- code: 200,
50
- message: 'success',
51
- data: {
52
- bookmarks: result.bookmarks,
53
- total: result.total
54
- }
55
- });
56
- } catch (error) {
57
- next(error);
58
- }
59
- }
60
-
61
- static async createBookmark(
62
- req: Request<{}, {}, CreateBookmarkBody>,
63
- res: Response,
64
- next: NextFunction
65
- ) {
66
- try {
67
- const { title, url, tags } = req.body;
68
-
69
- if (!title || typeof title !== 'string' || title.trim() === '') {
70
- throw new AppError(400, '"title" is required and must be a non-empty string');
71
- }
72
-
73
- if (!url || typeof url !== 'string') {
74
- throw new AppError(400, '"url" is required and must be a string');
75
- }
76
-
77
- try {
78
- new URL(url);
79
- } catch {
80
- throw new AppError(400, '"url" must be a valid URL');
81
- }
82
-
83
- if (tags !== undefined && !Array.isArray(tags)) {
84
- throw new AppError(400, '"tags" must be an array of strings');
85
- }
86
-
87
- if (tags && !tags.every(tag => typeof tag === 'string')) {
88
- throw new AppError(400, '"tags" must be an array of strings');
89
- }
90
-
91
- const bookmark = await bookmarkService.createBookmark({
92
- title: title.trim(),
93
- url: url.trim(),
94
- tags: tags || []
95
- });
96
-
97
- res.status(201).json({
98
- code: 201,
99
- message: 'success',
100
- data: bookmark
101
- });
102
- } catch (error) {
103
- next(error);
104
- }
105
- }
106
-
107
- static async updateBookmark(
108
- req: Request<BookmarkParams, {}, UpdateBookmarkBody>,
109
- res: Response,
110
- next: NextFunction
111
- ) {
112
- try {
113
- const { id } = req.params;
114
- const { title, url, tags } = req.body;
115
-
116
- if (!id) {
117
- throw new AppError(400, '"id" parameter is required');
118
- }
119
-
120
- const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
121
- if (!uuidRegex.test(id)) {
122
- throw new AppError(400, '"id" must be a valid UUID');
123
- }
124
-
125
- if (!title || typeof title !== 'string' || title.trim() === '') {
126
- throw new AppError(400, '"title" is required and must be a non-empty string');
127
- }
128
-
129
- if (!url || typeof url !== 'string') {
130
- throw new AppError(400, '"url" is required and must be a string');
131
- }
132
-
133
- try {
134
- new URL(url);
135
- } catch {
136
- throw new AppError(400, '"url" must be a valid URL');
137
- }
138
-
139
- if (!tags || !Array.isArray(tags)) {
140
- throw new AppError(400, '"tags" is required and must be an array of strings');
141
- }
142
-
143
- if (!tags.every(tag => typeof tag === 'string')) {
144
- throw new AppError(400, '"tags" must be an array of strings');
145
- }
146
-
147
- const bookmark = await bookmarkService.updateBookmark(id, {
148
- title: title.trim(),
149
- url: url.trim(),
150
- tags
151
- });
152
-
153
- res.status(200).json({
154
- code: 200,
155
- message: 'success',
156
- data: bookmark
157
- });
158
- } catch (error) {
159
- next(error);
160
- }
161
- }
162
-
163
- static async deleteBookmark(
164
- req: Request<BookmarkParams>,
165
- res: Response,
166
- next: NextFunction
167
- ) {
168
- try {
169
- const { id } = req.params;
170
-
171
- if (!id) {
172
- throw new AppError(400, '"id" parameter is required');
173
- }
174
-
175
- const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
176
- if (!uuidRegex.test(id)) {
177
- throw new AppError(400, '"id" must be a valid UUID');
178
- }
179
-
180
- await bookmarkService.deleteBookmark(id);
181
-
182
- res.status(204).send();
183
- } catch (error) {
184
- next(error);
185
- }
186
- }
187
- }
@@ -1,17 +0,0 @@
1
- import express from "express";
2
- import cors from "cors";
3
-
4
- const app = express();
5
- app.use(cors());
6
- app.use(express.json());
7
-
8
- app.get("/health", (_req, res) => {
9
- res.json({ status: "ok" });
10
- });
11
-
12
- const PORT = process.env.PORT || 3001;
13
- app.listen(PORT, () => {
14
- console.log(`Server running on http://localhost:${PORT}`);
15
- });
16
-
17
- export default app;
@@ -1,264 +0,0 @@
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
- });
@@ -1,11 +0,0 @@
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;
@@ -1,8 +0,0 @@
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;