@bradtaylorsf/alpha-loop 1.1.0 → 1.1.2
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/README.md +10 -12
- package/dist/cli.js +1 -1
- package/dist/commands/sync.js +9 -1
- package/dist/commands/sync.js.map +1 -1
- package/package.json +1 -1
- package/templates/agents/reviewer.md +1 -1
- package/templates/skills/api-contracts/SKILL.md +676 -0
- package/templates/skills/api-patterns/SKILL.md +346 -0
- package/templates/skills/api-patterns/examples/complete-rest-api.ts +293 -0
- package/templates/skills/api-patterns/templates/express-router-template.ts +294 -0
- package/templates/skills/docs-sync/SKILL.md +42 -0
- package/templates/skills/jest-mock-patterns/SKILL.md +397 -0
- package/templates/skills/playwright-testing/SKILL.md +124 -0
- package/templates/skills/sqlite-patterns/SKILL.md +229 -0
- package/templates/skills/test-caching/SKILL.md +99 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Express Router Template
|
|
3
|
+
* Use this template for creating new resource routers
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Router } from 'express';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { validateRequest } from '../middleware/validation';
|
|
9
|
+
import { requireAuth } from '../middleware/auth';
|
|
10
|
+
import { AppError } from '../utils/errors';
|
|
11
|
+
|
|
12
|
+
const router = Router();
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Validation Schemas
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
const CreateSchema = z.object({
|
|
19
|
+
// Define your fields here
|
|
20
|
+
name: z.string().min(1).max(255),
|
|
21
|
+
description: z.string().optional()
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const UpdateSchema = CreateSchema.partial();
|
|
25
|
+
|
|
26
|
+
const QuerySchema = z.object({
|
|
27
|
+
page: z.coerce.number().int().min(1).default(1),
|
|
28
|
+
limit: z.coerce.number().int().min(1).max(100).default(20),
|
|
29
|
+
sortBy: z.enum(['createdAt', 'updatedAt', 'name']).default('createdAt'),
|
|
30
|
+
sortOrder: z.enum(['asc', 'desc']).default('desc')
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Type inference
|
|
34
|
+
type CreateDto = z.infer<typeof CreateSchema>;
|
|
35
|
+
type UpdateDto = z.infer<typeof UpdateSchema>;
|
|
36
|
+
type QueryDto = z.infer<typeof QuerySchema>;
|
|
37
|
+
|
|
38
|
+
// ============================================================================
|
|
39
|
+
// Routes
|
|
40
|
+
// ============================================================================
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @openapi
|
|
44
|
+
* /api/resources:
|
|
45
|
+
* get:
|
|
46
|
+
* summary: List all resources
|
|
47
|
+
* tags: [Resources]
|
|
48
|
+
* security:
|
|
49
|
+
* - bearerAuth: []
|
|
50
|
+
* parameters:
|
|
51
|
+
* - in: query
|
|
52
|
+
* name: page
|
|
53
|
+
* schema:
|
|
54
|
+
* type: integer
|
|
55
|
+
* minimum: 1
|
|
56
|
+
* - in: query
|
|
57
|
+
* name: limit
|
|
58
|
+
* schema:
|
|
59
|
+
* type: integer
|
|
60
|
+
* minimum: 1
|
|
61
|
+
* maximum: 100
|
|
62
|
+
* responses:
|
|
63
|
+
* 200:
|
|
64
|
+
* description: List of resources
|
|
65
|
+
*/
|
|
66
|
+
router.get(
|
|
67
|
+
'/',
|
|
68
|
+
requireAuth,
|
|
69
|
+
validateRequest(QuerySchema),
|
|
70
|
+
async (req, res, next) => {
|
|
71
|
+
try {
|
|
72
|
+
const query = req.body as QueryDto;
|
|
73
|
+
const { page, limit, sortBy, sortOrder } = query;
|
|
74
|
+
const offset = (page - 1) * limit;
|
|
75
|
+
|
|
76
|
+
// TODO: Replace with actual database query
|
|
77
|
+
const resources = [];
|
|
78
|
+
const total = 0;
|
|
79
|
+
|
|
80
|
+
res.json({
|
|
81
|
+
data: resources,
|
|
82
|
+
pagination: {
|
|
83
|
+
page,
|
|
84
|
+
limit,
|
|
85
|
+
total,
|
|
86
|
+
totalPages: Math.ceil(total / limit)
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
} catch (err) {
|
|
90
|
+
next(err);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @openapi
|
|
97
|
+
* /api/resources/{id}:
|
|
98
|
+
* get:
|
|
99
|
+
* summary: Get a single resource
|
|
100
|
+
* tags: [Resources]
|
|
101
|
+
* security:
|
|
102
|
+
* - bearerAuth: []
|
|
103
|
+
* parameters:
|
|
104
|
+
* - in: path
|
|
105
|
+
* name: id
|
|
106
|
+
* required: true
|
|
107
|
+
* schema:
|
|
108
|
+
* type: string
|
|
109
|
+
* responses:
|
|
110
|
+
* 200:
|
|
111
|
+
* description: Resource found
|
|
112
|
+
* 404:
|
|
113
|
+
* description: Resource not found
|
|
114
|
+
*/
|
|
115
|
+
router.get('/:id', requireAuth, async (req, res, next) => {
|
|
116
|
+
try {
|
|
117
|
+
const { id } = req.params;
|
|
118
|
+
|
|
119
|
+
// TODO: Replace with actual database query
|
|
120
|
+
const resource = null;
|
|
121
|
+
|
|
122
|
+
if (!resource) {
|
|
123
|
+
throw new AppError('Resource not found', 404, 'RESOURCE_NOT_FOUND');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
res.json({ data: resource });
|
|
127
|
+
} catch (err) {
|
|
128
|
+
next(err);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @openapi
|
|
134
|
+
* /api/resources:
|
|
135
|
+
* post:
|
|
136
|
+
* summary: Create a new resource
|
|
137
|
+
* tags: [Resources]
|
|
138
|
+
* security:
|
|
139
|
+
* - bearerAuth: []
|
|
140
|
+
* requestBody:
|
|
141
|
+
* required: true
|
|
142
|
+
* content:
|
|
143
|
+
* application/json:
|
|
144
|
+
* schema:
|
|
145
|
+
* type: object
|
|
146
|
+
* required:
|
|
147
|
+
* - name
|
|
148
|
+
* properties:
|
|
149
|
+
* name:
|
|
150
|
+
* type: string
|
|
151
|
+
* description:
|
|
152
|
+
* type: string
|
|
153
|
+
* responses:
|
|
154
|
+
* 201:
|
|
155
|
+
* description: Resource created
|
|
156
|
+
*/
|
|
157
|
+
router.post(
|
|
158
|
+
'/',
|
|
159
|
+
requireAuth,
|
|
160
|
+
validateRequest(CreateSchema),
|
|
161
|
+
async (req, res, next) => {
|
|
162
|
+
try {
|
|
163
|
+
const data = req.body as CreateDto;
|
|
164
|
+
const userId = (req as any).userId;
|
|
165
|
+
|
|
166
|
+
// TODO: Replace with actual database insert
|
|
167
|
+
const resource = {
|
|
168
|
+
id: 'generated-id',
|
|
169
|
+
...data,
|
|
170
|
+
userId,
|
|
171
|
+
createdAt: new Date().toISOString(),
|
|
172
|
+
updatedAt: new Date().toISOString()
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
res.status(201).json({ data: resource });
|
|
176
|
+
} catch (err) {
|
|
177
|
+
next(err);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* @openapi
|
|
184
|
+
* /api/resources/{id}:
|
|
185
|
+
* put:
|
|
186
|
+
* summary: Update a resource
|
|
187
|
+
* tags: [Resources]
|
|
188
|
+
* security:
|
|
189
|
+
* - bearerAuth: []
|
|
190
|
+
* parameters:
|
|
191
|
+
* - in: path
|
|
192
|
+
* name: id
|
|
193
|
+
* required: true
|
|
194
|
+
* schema:
|
|
195
|
+
* type: string
|
|
196
|
+
* requestBody:
|
|
197
|
+
* required: true
|
|
198
|
+
* content:
|
|
199
|
+
* application/json:
|
|
200
|
+
* schema:
|
|
201
|
+
* type: object
|
|
202
|
+
* properties:
|
|
203
|
+
* name:
|
|
204
|
+
* type: string
|
|
205
|
+
* description:
|
|
206
|
+
* type: string
|
|
207
|
+
* responses:
|
|
208
|
+
* 200:
|
|
209
|
+
* description: Resource updated
|
|
210
|
+
* 404:
|
|
211
|
+
* description: Resource not found
|
|
212
|
+
*/
|
|
213
|
+
router.put(
|
|
214
|
+
'/:id',
|
|
215
|
+
requireAuth,
|
|
216
|
+
validateRequest(UpdateSchema),
|
|
217
|
+
async (req, res, next) => {
|
|
218
|
+
try {
|
|
219
|
+
const { id } = req.params;
|
|
220
|
+
const data = req.body as UpdateDto;
|
|
221
|
+
const userId = (req as any).userId;
|
|
222
|
+
|
|
223
|
+
// TODO: Replace with actual database query
|
|
224
|
+
const resource = null;
|
|
225
|
+
|
|
226
|
+
if (!resource) {
|
|
227
|
+
throw new AppError('Resource not found', 404, 'RESOURCE_NOT_FOUND');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Check authorization
|
|
231
|
+
if ((resource as any).userId !== userId) {
|
|
232
|
+
throw new AppError('Forbidden', 403, 'FORBIDDEN');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// TODO: Replace with actual database update
|
|
236
|
+
const updated = {
|
|
237
|
+
...resource,
|
|
238
|
+
...data,
|
|
239
|
+
updatedAt: new Date().toISOString()
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
res.json({ data: updated });
|
|
243
|
+
} catch (err) {
|
|
244
|
+
next(err);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* @openapi
|
|
251
|
+
* /api/resources/{id}:
|
|
252
|
+
* delete:
|
|
253
|
+
* summary: Delete a resource
|
|
254
|
+
* tags: [Resources]
|
|
255
|
+
* security:
|
|
256
|
+
* - bearerAuth: []
|
|
257
|
+
* parameters:
|
|
258
|
+
* - in: path
|
|
259
|
+
* name: id
|
|
260
|
+
* required: true
|
|
261
|
+
* schema:
|
|
262
|
+
* type: string
|
|
263
|
+
* responses:
|
|
264
|
+
* 204:
|
|
265
|
+
* description: Resource deleted
|
|
266
|
+
* 404:
|
|
267
|
+
* description: Resource not found
|
|
268
|
+
*/
|
|
269
|
+
router.delete('/:id', requireAuth, async (req, res, next) => {
|
|
270
|
+
try {
|
|
271
|
+
const { id } = req.params;
|
|
272
|
+
const userId = (req as any).userId;
|
|
273
|
+
|
|
274
|
+
// TODO: Replace with actual database query
|
|
275
|
+
const resource = null;
|
|
276
|
+
|
|
277
|
+
if (!resource) {
|
|
278
|
+
throw new AppError('Resource not found', 404, 'RESOURCE_NOT_FOUND');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Check authorization
|
|
282
|
+
if ((resource as any).userId !== userId) {
|
|
283
|
+
throw new AppError('Forbidden', 403, 'FORBIDDEN');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// TODO: Replace with actual database delete
|
|
287
|
+
|
|
288
|
+
res.status(204).send();
|
|
289
|
+
} catch (err) {
|
|
290
|
+
next(err);
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
export default router;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: docs-sync
|
|
3
|
+
description: Ensure documentation stays in sync with code changes. Trigger when modifying CLI commands, config options, directory structure, or public APIs.
|
|
4
|
+
when-to-use: When adding, removing, or changing CLI commands, config fields, directory layout, or user-facing behavior
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Documentation Sync
|
|
8
|
+
|
|
9
|
+
When making changes that affect user-facing behavior, always update the corresponding documentation.
|
|
10
|
+
|
|
11
|
+
## What to check
|
|
12
|
+
|
|
13
|
+
### CLI commands changed?
|
|
14
|
+
- Update `README.md` commands table
|
|
15
|
+
- Update `CLAUDE.md` commands section
|
|
16
|
+
- Update `--help` descriptions in `src/cli.ts`
|
|
17
|
+
|
|
18
|
+
### Config options changed?
|
|
19
|
+
- Update `README.md` Configuration Reference table
|
|
20
|
+
- Update `README.md` config example block
|
|
21
|
+
- Update `CLAUDE.md` if it references config
|
|
22
|
+
- Update the config template in `src/commands/init.ts`
|
|
23
|
+
|
|
24
|
+
### Directory structure changed?
|
|
25
|
+
- Update `CLAUDE.md` Directory Structure section
|
|
26
|
+
- Update `README.md` Project Artifacts table
|
|
27
|
+
|
|
28
|
+
### New skill or agent added?
|
|
29
|
+
- Skill: create `templates/skills/<name>/SKILL.md` with frontmatter
|
|
30
|
+
- Agent: create `templates/agents/<name>.md` with frontmatter
|
|
31
|
+
- Run `alpha-loop sync` to distribute
|
|
32
|
+
|
|
33
|
+
### Public API or behavior changed?
|
|
34
|
+
- Update relevant README sections
|
|
35
|
+
- Update CLAUDE.md if architectural
|
|
36
|
+
|
|
37
|
+
## Rules
|
|
38
|
+
|
|
39
|
+
- Documentation updates MUST be in the same commit as the code change
|
|
40
|
+
- Never leave README or CLAUDE.md referencing commands, options, or paths that no longer exist
|
|
41
|
+
- When removing a feature, search docs for all references before committing
|
|
42
|
+
- Keep README under 300 lines, CLAUDE.md under 200 lines
|