@jigyasudham/veto 3.1.0 → 3.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/LICENSE +21 -21
- package/README.md +633 -627
- package/dist/agents/llm-runner.js +57 -57
- package/dist/cli/statusline.js +2 -2
- package/dist/cli.js +26 -26
- package/dist/memory/decisions.js +1 -1
- package/dist/memory/local.js +144 -144
- package/dist/memory/schema.js +228 -228
- package/dist/memory/sync.js +27 -27
- package/dist/memory/sync.js.map +1 -1
- package/dist/router/implicit-outcomes.js +4 -4
- package/dist/router/learning-updater.js +45 -45
- package/dist/server.js +1 -1
- package/dist/tools/compact.js +3 -3
- package/dist/transcripts/archive.js +5 -5
- package/dist/transcripts/ingest.js +6 -6
- package/dist/transcripts/mapping.js +5 -5
- package/dist/transcripts/pyramid.js +2 -2
- package/dist/transcripts/schema.js +104 -104
- package/dist/transcripts/search.js +3 -3
- package/dist/transcripts/vectors.js +2 -2
- package/package.json +70 -62
- package/server.json +23 -23
- package/dist/skills/development/skill-api-design.d.ts +0 -15
- package/dist/skills/development/skill-api-design.d.ts.map +0 -1
- package/dist/skills/development/skill-api-design.js +0 -313
- package/dist/skills/development/skill-api-design.js.map +0 -1
- package/dist/skills/development/skill-auth.d.ts +0 -15
- package/dist/skills/development/skill-auth.d.ts.map +0 -1
- package/dist/skills/development/skill-auth.js +0 -255
- package/dist/skills/development/skill-auth.js.map +0 -1
- package/dist/skills/development/skill-ci-cd.d.ts +0 -2
- package/dist/skills/development/skill-ci-cd.d.ts.map +0 -1
- package/dist/skills/development/skill-ci-cd.js +0 -2
- package/dist/skills/development/skill-ci-cd.js.map +0 -1
- package/dist/skills/development/skill-crud.d.ts +0 -15
- package/dist/skills/development/skill-crud.d.ts.map +0 -1
- package/dist/skills/development/skill-crud.js +0 -193
- package/dist/skills/development/skill-crud.js.map +0 -1
- package/dist/skills/development/skill-db-schema.d.ts +0 -2
- package/dist/skills/development/skill-db-schema.d.ts.map +0 -1
- package/dist/skills/development/skill-db-schema.js +0 -2
- package/dist/skills/development/skill-db-schema.js.map +0 -1
- package/dist/skills/development/skill-docker.d.ts +0 -2
- package/dist/skills/development/skill-docker.d.ts.map +0 -1
- package/dist/skills/development/skill-docker.js +0 -2
- package/dist/skills/development/skill-docker.js.map +0 -1
- package/dist/skills/development/skill-env-setup.d.ts +0 -2
- package/dist/skills/development/skill-env-setup.d.ts.map +0 -1
- package/dist/skills/development/skill-env-setup.js +0 -2
- package/dist/skills/development/skill-env-setup.js.map +0 -1
- package/dist/skills/development/skill-scaffold.d.ts +0 -15
- package/dist/skills/development/skill-scaffold.d.ts.map +0 -1
- package/dist/skills/development/skill-scaffold.js +0 -299
- package/dist/skills/development/skill-scaffold.js.map +0 -1
- package/dist/skills/intelligence/skill-complexity-score.d.ts +0 -15
- package/dist/skills/intelligence/skill-complexity-score.d.ts.map +0 -1
- package/dist/skills/intelligence/skill-complexity-score.js +0 -66
- package/dist/skills/intelligence/skill-complexity-score.js.map +0 -1
- package/dist/skills/intelligence/skill-cost-track.d.ts +0 -14
- package/dist/skills/intelligence/skill-cost-track.d.ts.map +0 -1
- package/dist/skills/intelligence/skill-cost-track.js +0 -36
- package/dist/skills/intelligence/skill-cost-track.js.map +0 -1
- package/dist/skills/intelligence/skill-learning-loop.d.ts +0 -15
- package/dist/skills/intelligence/skill-learning-loop.d.ts.map +0 -1
- package/dist/skills/intelligence/skill-learning-loop.js +0 -66
- package/dist/skills/intelligence/skill-learning-loop.js.map +0 -1
- package/dist/skills/intelligence/skill-pattern-detect.d.ts +0 -14
- package/dist/skills/intelligence/skill-pattern-detect.d.ts.map +0 -1
- package/dist/skills/intelligence/skill-pattern-detect.js +0 -35
- package/dist/skills/intelligence/skill-pattern-detect.js.map +0 -1
- package/dist/skills/intelligence/skill-rate-watch.d.ts +0 -15
- package/dist/skills/intelligence/skill-rate-watch.d.ts.map +0 -1
- package/dist/skills/intelligence/skill-rate-watch.js +0 -58
- package/dist/skills/intelligence/skill-rate-watch.js.map +0 -1
- package/dist/skills/memory/skill-context-compress.d.ts +0 -15
- package/dist/skills/memory/skill-context-compress.d.ts.map +0 -1
- package/dist/skills/memory/skill-context-compress.js +0 -82
- package/dist/skills/memory/skill-context-compress.js.map +0 -1
- package/dist/skills/memory/skill-cross-sync.d.ts +0 -15
- package/dist/skills/memory/skill-cross-sync.d.ts.map +0 -1
- package/dist/skills/memory/skill-cross-sync.js +0 -88
- package/dist/skills/memory/skill-cross-sync.js.map +0 -1
- package/dist/skills/memory/skill-decision-log.d.ts +0 -15
- package/dist/skills/memory/skill-decision-log.d.ts.map +0 -1
- package/dist/skills/memory/skill-decision-log.js +0 -103
- package/dist/skills/memory/skill-decision-log.js.map +0 -1
- package/dist/skills/memory/skill-session-restore.d.ts +0 -15
- package/dist/skills/memory/skill-session-restore.d.ts.map +0 -1
- package/dist/skills/memory/skill-session-restore.js +0 -39
- package/dist/skills/memory/skill-session-restore.js.map +0 -1
- package/dist/skills/memory/skill-session-save.d.ts +0 -15
- package/dist/skills/memory/skill-session-save.d.ts.map +0 -1
- package/dist/skills/memory/skill-session-save.js +0 -74
- package/dist/skills/memory/skill-session-save.js.map +0 -1
- package/dist/skills/quality/skill-accessibility.d.ts +0 -2
- package/dist/skills/quality/skill-accessibility.d.ts.map +0 -1
- package/dist/skills/quality/skill-accessibility.js +0 -2
- package/dist/skills/quality/skill-accessibility.js.map +0 -1
- package/dist/skills/quality/skill-code-review.d.ts +0 -15
- package/dist/skills/quality/skill-code-review.d.ts.map +0 -1
- package/dist/skills/quality/skill-code-review.js +0 -60
- package/dist/skills/quality/skill-code-review.js.map +0 -1
- package/dist/skills/quality/skill-docs-gen.d.ts +0 -2
- package/dist/skills/quality/skill-docs-gen.d.ts.map +0 -1
- package/dist/skills/quality/skill-docs-gen.js +0 -2
- package/dist/skills/quality/skill-docs-gen.js.map +0 -1
- package/dist/skills/quality/skill-perf-audit.d.ts +0 -2
- package/dist/skills/quality/skill-perf-audit.d.ts.map +0 -1
- package/dist/skills/quality/skill-perf-audit.js +0 -2
- package/dist/skills/quality/skill-perf-audit.js.map +0 -1
- package/dist/skills/quality/skill-security-scan.d.ts +0 -15
- package/dist/skills/quality/skill-security-scan.d.ts.map +0 -1
- package/dist/skills/quality/skill-security-scan.js +0 -67
- package/dist/skills/quality/skill-security-scan.js.map +0 -1
- package/dist/skills/quality/skill-test-suite.d.ts +0 -15
- package/dist/skills/quality/skill-test-suite.d.ts.map +0 -1
- package/dist/skills/quality/skill-test-suite.js +0 -274
- package/dist/skills/quality/skill-test-suite.js.map +0 -1
- package/dist/skills/workflow/skill-deploy.d.ts +0 -2
- package/dist/skills/workflow/skill-deploy.d.ts.map +0 -1
- package/dist/skills/workflow/skill-deploy.js +0 -2
- package/dist/skills/workflow/skill-deploy.js.map +0 -1
- package/dist/skills/workflow/skill-git-workflow.d.ts +0 -2
- package/dist/skills/workflow/skill-git-workflow.d.ts.map +0 -1
- package/dist/skills/workflow/skill-git-workflow.js +0 -2
- package/dist/skills/workflow/skill-git-workflow.js.map +0 -1
- package/dist/skills/workflow/skill-rollback.d.ts +0 -2
- package/dist/skills/workflow/skill-rollback.d.ts.map +0 -1
- package/dist/skills/workflow/skill-rollback.js +0 -2
- package/dist/skills/workflow/skill-rollback.js.map +0 -1
- package/dist/skills/workflow/skill-task-breakdown.d.ts +0 -2
- package/dist/skills/workflow/skill-task-breakdown.d.ts.map +0 -1
- package/dist/skills/workflow/skill-task-breakdown.js +0 -2
- package/dist/skills/workflow/skill-task-breakdown.js.map +0 -1
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
// Skill: auth — JWT + bcrypt + refresh token implementation guide
|
|
2
|
-
const TEMPLATE = `
|
|
3
|
-
// ── Types (src/models/auth.ts) ────────────────────────────────────────────
|
|
4
|
-
export interface User {
|
|
5
|
-
id: string;
|
|
6
|
-
email: string;
|
|
7
|
-
passwordHash: string;
|
|
8
|
-
role: 'admin' | 'user';
|
|
9
|
-
failedLoginAttempts: number;
|
|
10
|
-
lockedUntil?: Date | null;
|
|
11
|
-
createdAt: Date;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface TokenPair {
|
|
15
|
-
accessToken: string; // short-lived, stateless JWT
|
|
16
|
-
refreshToken: string; // long-lived, stored in DB
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// ── Token utilities (src/utils/tokens.ts) ────────────────────────────────
|
|
20
|
-
import jwt from 'jsonwebtoken';
|
|
21
|
-
import crypto from 'crypto';
|
|
22
|
-
|
|
23
|
-
const ACCESS_SECRET = process.env.JWT_ACCESS_SECRET!; // 256-bit random
|
|
24
|
-
const REFRESH_SECRET = process.env.JWT_REFRESH_SECRET!; // separate secret
|
|
25
|
-
const ACCESS_TTL = '15m';
|
|
26
|
-
const REFRESH_TTL = '7d';
|
|
27
|
-
|
|
28
|
-
export function signAccessToken(userId: string, role: string): string {
|
|
29
|
-
return jwt.sign({ sub: userId, role }, ACCESS_SECRET, {
|
|
30
|
-
expiresIn: ACCESS_TTL,
|
|
31
|
-
algorithm: 'HS256',
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function verifyAccessToken(token: string): { sub: string; role: string } {
|
|
36
|
-
return jwt.verify(token, ACCESS_SECRET, { algorithms: ['HS256'] }) as { sub: string; role: string };
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function generateRefreshToken(): string {
|
|
40
|
-
return crypto.randomBytes(64).toString('hex'); // 512 bits of entropy
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// ── Auth service (src/services/auth.service.ts) ───────────────────────────
|
|
44
|
-
import bcrypt from 'bcrypt';
|
|
45
|
-
|
|
46
|
-
const BCRYPT_ROUNDS = 12;
|
|
47
|
-
const MAX_ATTEMPTS = 5;
|
|
48
|
-
const LOCKOUT_MINUTES = 30;
|
|
49
|
-
|
|
50
|
-
export class AuthService {
|
|
51
|
-
constructor(
|
|
52
|
-
private readonly userRepo: UserRepository,
|
|
53
|
-
private readonly tokenRepo: RefreshTokenRepository,
|
|
54
|
-
) {}
|
|
55
|
-
|
|
56
|
-
async register(email: string, password: string): Promise<User> {
|
|
57
|
-
const existing = await this.userRepo.findByEmail(email);
|
|
58
|
-
if (existing) throw new ConflictError('Email already registered');
|
|
59
|
-
const passwordHash = await bcrypt.hash(password, BCRYPT_ROUNDS);
|
|
60
|
-
return this.userRepo.create({ email: email.toLowerCase().trim(), passwordHash });
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async login(email: string, password: string): Promise<TokenPair> {
|
|
64
|
-
const user = await this.userRepo.findByEmail(email.toLowerCase().trim());
|
|
65
|
-
if (!user) throw new UnauthorizedError('Invalid credentials');
|
|
66
|
-
|
|
67
|
-
if (user.lockedUntil && user.lockedUntil > new Date()) {
|
|
68
|
-
throw new UnauthorizedError('Account locked. Check your email to unlock.');
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const valid = await bcrypt.compare(password, user.passwordHash);
|
|
72
|
-
if (!valid) {
|
|
73
|
-
await this.recordFailedAttempt(user);
|
|
74
|
-
throw new UnauthorizedError('Invalid credentials');
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
await this.userRepo.resetFailedAttempts(user.id);
|
|
78
|
-
return this.issueTokenPair(user.id, user.role);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async refresh(rawRefreshToken: string): Promise<TokenPair> {
|
|
82
|
-
const tokenHash = hashToken(rawRefreshToken);
|
|
83
|
-
const stored = await this.tokenRepo.findByHash(tokenHash);
|
|
84
|
-
if (!stored || stored.revokedAt || stored.expiresAt < new Date()) {
|
|
85
|
-
throw new UnauthorizedError('Invalid or expired refresh token');
|
|
86
|
-
}
|
|
87
|
-
// Rotate: revoke old, issue new
|
|
88
|
-
await this.tokenRepo.revoke(stored.id);
|
|
89
|
-
return this.issueTokenPair(stored.userId, stored.role);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async logout(rawRefreshToken: string): Promise<void> {
|
|
93
|
-
const tokenHash = hashToken(rawRefreshToken);
|
|
94
|
-
const stored = await this.tokenRepo.findByHash(tokenHash);
|
|
95
|
-
if (stored) await this.tokenRepo.revoke(stored.id);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
private async issueTokenPair(userId: string, role: string): Promise<TokenPair> {
|
|
99
|
-
const rawRefreshToken = generateRefreshToken();
|
|
100
|
-
await this.tokenRepo.create({
|
|
101
|
-
userId,
|
|
102
|
-
tokenHash: hashToken(rawRefreshToken),
|
|
103
|
-
role,
|
|
104
|
-
expiresAt: addDays(new Date(), 7),
|
|
105
|
-
});
|
|
106
|
-
return {
|
|
107
|
-
accessToken: signAccessToken(userId, role),
|
|
108
|
-
refreshToken: rawRefreshToken,
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
private async recordFailedAttempt(user: User): Promise<void> {
|
|
113
|
-
const attempts = user.failedLoginAttempts + 1;
|
|
114
|
-
if (attempts >= MAX_ATTEMPTS) {
|
|
115
|
-
const lockedUntil = addMinutes(new Date(), LOCKOUT_MINUTES);
|
|
116
|
-
await this.userRepo.lockAccount(user.id, lockedUntil);
|
|
117
|
-
// TODO: send unlock email
|
|
118
|
-
} else {
|
|
119
|
-
await this.userRepo.incrementFailedAttempts(user.id);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// ── Auth middleware (src/middleware/auth.ts) ───────────────────────────────
|
|
125
|
-
import { Request, Response, NextFunction } from 'express';
|
|
126
|
-
|
|
127
|
-
export function requireAuth(req: Request, res: Response, next: NextFunction): void {
|
|
128
|
-
const header = req.headers.authorization;
|
|
129
|
-
if (!header?.startsWith('Bearer ')) {
|
|
130
|
-
res.status(401).json({ error: 'Missing or malformed Authorization header' });
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
try {
|
|
134
|
-
const payload = verifyAccessToken(header.slice(7));
|
|
135
|
-
req.user = { id: payload.sub, role: payload.role };
|
|
136
|
-
next();
|
|
137
|
-
} catch {
|
|
138
|
-
res.status(401).json({ error: 'Invalid or expired access token' });
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export function requireRole(role: string) {
|
|
143
|
-
return (req: Request, res: Response, next: NextFunction): void => {
|
|
144
|
-
if (req.user?.role !== role) {
|
|
145
|
-
res.status(403).json({ error: 'Insufficient permissions' });
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
next();
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// ── Auth routes (src/routes/auth.ts) ─────────────────────────────────────
|
|
153
|
-
import { Router } from 'express';
|
|
154
|
-
import { z } from 'zod';
|
|
155
|
-
import rateLimit from 'express-rate-limit';
|
|
156
|
-
|
|
157
|
-
const authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 10, standardHeaders: true });
|
|
158
|
-
|
|
159
|
-
const RegisterSchema = z.object({
|
|
160
|
-
email: z.string().email().max(255),
|
|
161
|
-
password: z.string().min(12).max(128),
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
const LoginSchema = RegisterSchema;
|
|
165
|
-
|
|
166
|
-
export function createAuthRouter(service: AuthService): Router {
|
|
167
|
-
const router = Router();
|
|
168
|
-
router.use(authLimiter);
|
|
169
|
-
|
|
170
|
-
router.post('/register', validate(RegisterSchema), async (req, res) => {
|
|
171
|
-
const user = await service.register(req.body.email, req.body.password);
|
|
172
|
-
res.status(201).json({ data: { id: user.id, email: user.email } });
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
router.post('/login', validate(LoginSchema), async (req, res) => {
|
|
176
|
-
const tokens = await service.login(req.body.email, req.body.password);
|
|
177
|
-
res
|
|
178
|
-
.cookie('refreshToken', tokens.refreshToken, {
|
|
179
|
-
httpOnly: true, secure: true, sameSite: 'strict', maxAge: 7 * 24 * 60 * 60 * 1000,
|
|
180
|
-
})
|
|
181
|
-
.json({ data: { accessToken: tokens.accessToken } });
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
router.post('/refresh', async (req, res) => {
|
|
185
|
-
const raw = req.cookies?.refreshToken;
|
|
186
|
-
if (!raw) { res.status(401).json({ error: 'No refresh token' }); return; }
|
|
187
|
-
const tokens = await service.refresh(raw);
|
|
188
|
-
res
|
|
189
|
-
.cookie('refreshToken', tokens.refreshToken, {
|
|
190
|
-
httpOnly: true, secure: true, sameSite: 'strict', maxAge: 7 * 24 * 60 * 60 * 1000,
|
|
191
|
-
})
|
|
192
|
-
.json({ data: { accessToken: tokens.accessToken } });
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
router.post('/logout', async (req, res) => {
|
|
196
|
-
const raw = req.cookies?.refreshToken;
|
|
197
|
-
if (raw) await service.logout(raw);
|
|
198
|
-
res.clearCookie('refreshToken').status(204).send();
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
return router;
|
|
202
|
-
}
|
|
203
|
-
`.trim();
|
|
204
|
-
export function run(input) {
|
|
205
|
-
return {
|
|
206
|
-
skill: 'auth',
|
|
207
|
-
template: TEMPLATE,
|
|
208
|
-
checklist: [
|
|
209
|
-
'Install: npm install bcrypt jsonwebtoken cookie-parser express-rate-limit && npm install -D @types/bcrypt @types/jsonwebtoken @types/cookie-parser',
|
|
210
|
-
'Generate two separate 256-bit secrets: JWT_ACCESS_SECRET and JWT_REFRESH_SECRET via crypto.randomBytes(32).toString("hex")',
|
|
211
|
-
'Store both secrets in environment variables; never hardcode them',
|
|
212
|
-
'Implement User model with passwordHash (never password), failedLoginAttempts, lockedUntil fields',
|
|
213
|
-
'Implement RefreshToken model with userId, tokenHash (SHA-256 of raw token), expiresAt, revokedAt',
|
|
214
|
-
'Set bcrypt cost factor to 12; adjust up if server can handle the latency',
|
|
215
|
-
'Set access token TTL to 15 minutes; set refresh token TTL to 7 days',
|
|
216
|
-
'Always call jwt.verify with explicit { algorithms: ["HS256"] } — never allow alg negotiation',
|
|
217
|
-
'Implement refresh token rotation: revoke old token, issue new token on every /refresh call',
|
|
218
|
-
'Store the SHA-256 hash of the refresh token in DB, never the raw value',
|
|
219
|
-
'Deliver access token in response body; deliver refresh token in httpOnly Secure SameSite=Strict cookie',
|
|
220
|
-
'Apply express-rate-limit (10 req/15 min) to all auth endpoints',
|
|
221
|
-
'Implement account lockout: after 5 failures, lock for 30 minutes and send unlock email',
|
|
222
|
-
'Normalise email input: .toLowerCase().trim() before lookup or storage',
|
|
223
|
-
'Return identical error messages for invalid credentials regardless of whether user exists (prevents enumeration)',
|
|
224
|
-
'Implement /logout that revokes refresh token from DB and clears cookie',
|
|
225
|
-
'Implement password reset: generate a single-use HMAC token, send via email, expire in 1 hour',
|
|
226
|
-
'Log every auth event (login success/fail, logout, token refresh, lockout) with userId, IP, user-agent',
|
|
227
|
-
'Write unit tests for AuthService mocking repositories',
|
|
228
|
-
'Write integration tests: register → login → refresh → logout → verify refresh fails after logout',
|
|
229
|
-
],
|
|
230
|
-
patterns: [
|
|
231
|
-
'Stateless access token + stateful refresh token hybrid',
|
|
232
|
-
'Refresh token rotation with database revocation',
|
|
233
|
-
'httpOnly cookie for refresh token (XSS-safe delivery)',
|
|
234
|
-
'Hash-before-store for refresh tokens (never store raw token)',
|
|
235
|
-
'Rate limiter middleware applied at router level, not per-route',
|
|
236
|
-
],
|
|
237
|
-
gotchas: [
|
|
238
|
-
'Not rotating refresh tokens on use — a stolen refresh token can be used indefinitely',
|
|
239
|
-
'Accepting jwt.verify without specifying algorithms — "alg":"none" bypass attack',
|
|
240
|
-
'Storing refresh tokens in localStorage — immediately exposed to any XSS vulnerability',
|
|
241
|
-
'Using a short bcrypt salt rounds (< 10) — trivial brute force offline after DB leak',
|
|
242
|
-
'Reusing the same secret for access and refresh tokens — compromise of one breaks both',
|
|
243
|
-
'Not invalidating access tokens on logout — they remain valid until expiry (15 min window)',
|
|
244
|
-
'Not sending identical error messages for bad username vs bad password — enables enumeration',
|
|
245
|
-
],
|
|
246
|
-
resources: [
|
|
247
|
-
'https://www.npmjs.com/package/jsonwebtoken',
|
|
248
|
-
'https://www.npmjs.com/package/bcrypt',
|
|
249
|
-
'https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html',
|
|
250
|
-
'https://cheatsheetseries.owasp.org/cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.html',
|
|
251
|
-
'https://datatracker.ietf.org/doc/html/rfc6749 (OAuth 2.0)',
|
|
252
|
-
],
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
//# sourceMappingURL=skill-auth.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"skill-auth.js","sourceRoot":"","sources":["../../../src/skills/development/skill-auth.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAiBlE,MAAM,QAAQ,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyMhB,CAAC,IAAI,EAAE,CAAC;AAET,MAAM,UAAU,GAAG,CAAC,KAAiB;IACnC,OAAO;QACL,KAAK,EAAE,MAAM;QACb,QAAQ,EAAE,QAAQ;QAClB,SAAS,EAAE;YACT,oJAAoJ;YACpJ,4HAA4H;YAC5H,kEAAkE;YAClE,kGAAkG;YAClG,kGAAkG;YAClG,0EAA0E;YAC1E,qEAAqE;YACrE,8FAA8F;YAC9F,4FAA4F;YAC5F,wEAAwE;YACxE,wGAAwG;YACxG,gEAAgE;YAChE,wFAAwF;YACxF,uEAAuE;YACvE,kHAAkH;YAClH,wEAAwE;YACxE,8FAA8F;YAC9F,uGAAuG;YACvG,uDAAuD;YACvD,kGAAkG;SACnG;QACD,QAAQ,EAAE;YACR,wDAAwD;YACxD,iDAAiD;YACjD,uDAAuD;YACvD,8DAA8D;YAC9D,gEAAgE;SACjE;QACD,OAAO,EAAE;YACP,sFAAsF;YACtF,iFAAiF;YACjF,uFAAuF;YACvF,qFAAqF;YACrF,uFAAuF;YACvF,2FAA2F;YAC3F,6FAA6F;SAC9F;QACD,SAAS,EAAE;YACT,4CAA4C;YAC5C,sCAAsC;YACtC,gFAAgF;YAChF,yFAAyF;YACzF,2DAA2D;SAC5D;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"skill-ci-cd.d.ts","sourceRoot":"","sources":["../../../src/skills/development/skill-ci-cd.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"skill-ci-cd.js","sourceRoot":"","sources":["../../../src/skills/development/skill-ci-cd.ts"],"names":[],"mappings":""}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export interface SkillInput {
|
|
2
|
-
task: string;
|
|
3
|
-
context?: string;
|
|
4
|
-
options?: Record<string, unknown>;
|
|
5
|
-
}
|
|
6
|
-
export interface SkillOutput {
|
|
7
|
-
skill: string;
|
|
8
|
-
template?: string;
|
|
9
|
-
checklist: string[];
|
|
10
|
-
patterns: string[];
|
|
11
|
-
gotchas: string[];
|
|
12
|
-
resources: string[];
|
|
13
|
-
}
|
|
14
|
-
export declare function run(input: SkillInput): SkillOutput;
|
|
15
|
-
//# sourceMappingURL=skill-crud.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"skill-crud.d.ts","sourceRoot":"","sources":["../../../src/skills/development/skill-crud.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAmJD,wBAAgB,GAAG,CAAC,KAAK,EAAE,UAAU,GAAG,WAAW,CA8ClD"}
|
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
// Skill: crud — complete CRUD implementation guide
|
|
2
|
-
const TEMPLATE = `
|
|
3
|
-
// ── 1. Model (src/models/item.ts) ─────────────────────────────────────────
|
|
4
|
-
export interface Item {
|
|
5
|
-
id: string;
|
|
6
|
-
name: string;
|
|
7
|
-
description?: string;
|
|
8
|
-
userId: string; // ownership field — always include
|
|
9
|
-
createdAt: Date;
|
|
10
|
-
updatedAt: Date;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface CreateItemInput {
|
|
14
|
-
name: string;
|
|
15
|
-
description?: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface UpdateItemInput {
|
|
19
|
-
name?: string;
|
|
20
|
-
description?: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// ── 2. Repository (src/repositories/item.repository.ts) ──────────────────
|
|
24
|
-
export interface ItemRepository {
|
|
25
|
-
findById(id: string): Promise<Item | null>;
|
|
26
|
-
findAllByUser(userId: string): Promise<Item[]>;
|
|
27
|
-
create(userId: string, input: CreateItemInput): Promise<Item>;
|
|
28
|
-
update(id: string, input: UpdateItemInput): Promise<Item>;
|
|
29
|
-
delete(id: string): Promise<void>;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// PrismaItemRepository implements ItemRepository
|
|
33
|
-
export class PrismaItemRepository implements ItemRepository {
|
|
34
|
-
constructor(private readonly db: PrismaClient) {}
|
|
35
|
-
|
|
36
|
-
async findById(id: string): Promise<Item | null> {
|
|
37
|
-
return this.db.item.findUnique({ where: { id } });
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async findAllByUser(userId: string): Promise<Item[]> {
|
|
41
|
-
return this.db.item.findMany({ where: { userId }, orderBy: { createdAt: 'desc' } });
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async create(userId: string, input: CreateItemInput): Promise<Item> {
|
|
45
|
-
return this.db.item.create({ data: { ...input, userId } });
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async update(id: string, input: UpdateItemInput): Promise<Item> {
|
|
49
|
-
return this.db.item.update({ where: { id }, data: input });
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async delete(id: string): Promise<void> {
|
|
53
|
-
await this.db.item.delete({ where: { id } });
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// ── 3. Service (src/services/item.service.ts) ────────────────────────────
|
|
58
|
-
export class ItemService {
|
|
59
|
-
constructor(private readonly repo: ItemRepository) {}
|
|
60
|
-
|
|
61
|
-
async getItem(id: string, requestingUserId: string): Promise<Item> {
|
|
62
|
-
const item = await this.repo.findById(id);
|
|
63
|
-
if (!item) throw new NotFoundError('Item not found');
|
|
64
|
-
if (item.userId !== requestingUserId) throw new ForbiddenError('Access denied');
|
|
65
|
-
return item;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async listItems(userId: string): Promise<Item[]> {
|
|
69
|
-
return this.repo.findAllByUser(userId);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async createItem(userId: string, input: CreateItemInput): Promise<Item> {
|
|
73
|
-
return this.repo.create(userId, input);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async updateItem(id: string, input: UpdateItemInput, requestingUserId: string): Promise<Item> {
|
|
77
|
-
await this.getItem(id, requestingUserId); // throws if not found or not owned
|
|
78
|
-
return this.repo.update(id, input);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async deleteItem(id: string, requestingUserId: string): Promise<void> {
|
|
82
|
-
await this.getItem(id, requestingUserId); // ownership check
|
|
83
|
-
return this.repo.delete(id);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// ── 4. Controller (src/controllers/item.controller.ts) ───────────────────
|
|
88
|
-
export class ItemController {
|
|
89
|
-
constructor(private readonly service: ItemService) {}
|
|
90
|
-
|
|
91
|
-
// GET /items
|
|
92
|
-
list = async (req: Request, res: Response): Promise<void> => {
|
|
93
|
-
const items = await this.service.listItems(req.user.id);
|
|
94
|
-
res.json({ data: items });
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
// GET /items/:id
|
|
98
|
-
get = async (req: Request, res: Response): Promise<void> => {
|
|
99
|
-
const item = await this.service.getItem(req.params.id, req.user.id);
|
|
100
|
-
res.json({ data: item });
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
// POST /items
|
|
104
|
-
create = async (req: Request, res: Response): Promise<void> => {
|
|
105
|
-
const item = await this.service.createItem(req.user.id, req.body);
|
|
106
|
-
res.status(201).json({ data: item });
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
// PATCH /items/:id
|
|
110
|
-
update = async (req: Request, res: Response): Promise<void> => {
|
|
111
|
-
const item = await this.service.updateItem(req.params.id, req.body, req.user.id);
|
|
112
|
-
res.json({ data: item });
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
// DELETE /items/:id
|
|
116
|
-
delete = async (req: Request, res: Response): Promise<void> => {
|
|
117
|
-
await this.service.deleteItem(req.params.id, req.user.id);
|
|
118
|
-
res.status(204).send();
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// ── 5. Routes (src/routes/items.ts) ─────────────────────────────────────
|
|
123
|
-
import { Router } from 'express';
|
|
124
|
-
import { z } from 'zod';
|
|
125
|
-
|
|
126
|
-
const CreateItemSchema = z.object({
|
|
127
|
-
name: z.string().min(1).max(100),
|
|
128
|
-
description: z.string().max(500).optional(),
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
const UpdateItemSchema = z.object({
|
|
132
|
-
name: z.string().min(1).max(100).optional(),
|
|
133
|
-
description: z.string().max(500).optional(),
|
|
134
|
-
}).refine(obj => Object.keys(obj).length > 0, { message: 'At least one field required' });
|
|
135
|
-
|
|
136
|
-
export function createItemRouter(controller: ItemController): Router {
|
|
137
|
-
const router = Router();
|
|
138
|
-
router.get('/', controller.list);
|
|
139
|
-
router.get('/:id', controller.get);
|
|
140
|
-
router.post('/', validate(CreateItemSchema), controller.create);
|
|
141
|
-
router.patch('/:id', validate(UpdateItemSchema), controller.update);
|
|
142
|
-
router.delete('/:id', controller.delete);
|
|
143
|
-
return router;
|
|
144
|
-
}
|
|
145
|
-
`.trim();
|
|
146
|
-
export function run(input) {
|
|
147
|
-
return {
|
|
148
|
-
skill: 'crud',
|
|
149
|
-
template: TEMPLATE,
|
|
150
|
-
checklist: [
|
|
151
|
-
'Define TypeScript model interface with id, ownership field (userId), createdAt, updatedAt',
|
|
152
|
-
'Define separate CreateInput and UpdateInput types (no id, no userId, all update fields optional)',
|
|
153
|
-
'Create repository interface — program to the interface, not the implementation',
|
|
154
|
-
'Implement repository with Prisma/Drizzle/raw queries; keep DB logic isolated here',
|
|
155
|
-
'Implement service layer: all ownership and business rule checks happen here',
|
|
156
|
-
'Add service method getById that always checks ownership before returning data',
|
|
157
|
-
'Implement controller: parse request → call service → format response; no DB access',
|
|
158
|
-
'Create Zod schemas for create and update request bodies',
|
|
159
|
-
'Add validate middleware that runs Zod schema and returns 400 on failure',
|
|
160
|
-
'Register routes: GET / (list), GET /:id, POST / (create), PATCH /:id, DELETE /:id',
|
|
161
|
-
'Mount routes behind auth middleware so req.user is always populated',
|
|
162
|
-
'Add 204 No Content for successful DELETE (no body)',
|
|
163
|
-
'Add 201 Created with Location header for successful POST',
|
|
164
|
-
'Write unit tests for service layer (mock the repository)',
|
|
165
|
-
'Write integration tests covering happy path, 404, 403 (wrong user), 400 (bad input)',
|
|
166
|
-
'Add pagination to the list endpoint: limit/offset or cursor-based',
|
|
167
|
-
'Add filtering and sorting parameters to the list endpoint',
|
|
168
|
-
'Document all endpoints with JSDoc or OpenAPI comments',
|
|
169
|
-
],
|
|
170
|
-
patterns: [
|
|
171
|
-
'Controller → Service → Repository three-layer architecture',
|
|
172
|
-
'Repository pattern: data access is behind an interface for testability',
|
|
173
|
-
'Ownership check in every service read/write operation (prevents IDOR)',
|
|
174
|
-
'Zod schema validation at the route boundary',
|
|
175
|
-
'Error-first service methods: throw typed errors, not returning null for auth failures',
|
|
176
|
-
],
|
|
177
|
-
gotchas: [
|
|
178
|
-
'Forgetting ownership check in the service — returns any user\'s data via IDOR',
|
|
179
|
-
'Putting DB queries directly in controllers — bypasses repository layer and breaks tests',
|
|
180
|
-
'Not returning 404 before 403 — leaks resource existence to unauthorised users (use 404 for both)',
|
|
181
|
-
'Updating with empty body — add a Zod refine check that at least one field is present for PATCH',
|
|
182
|
-
'Not invalidating caches after update/delete — stale data returned to other users',
|
|
183
|
-
'Using findMany without a userId filter on list — returns all users\' data',
|
|
184
|
-
],
|
|
185
|
-
resources: [
|
|
186
|
-
'https://www.prisma.io/docs/concepts/components/prisma-client/crud',
|
|
187
|
-
'https://zod.dev/',
|
|
188
|
-
'https://owasp.org/www-project-api-security/ (API4:2023 Broken Object Level Authorization)',
|
|
189
|
-
'https://expressjs.com/en/guide/routing.html',
|
|
190
|
-
],
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
//# sourceMappingURL=skill-crud.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"skill-crud.js","sourceRoot":"","sources":["../../../src/skills/development/skill-crud.ts"],"names":[],"mappings":"AAAA,mDAAmD;AAiBnD,MAAM,QAAQ,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+IhB,CAAC,IAAI,EAAE,CAAC;AAET,MAAM,UAAU,GAAG,CAAC,KAAiB;IACnC,OAAO;QACL,KAAK,EAAE,MAAM;QACb,QAAQ,EAAE,QAAQ;QAClB,SAAS,EAAE;YACT,2FAA2F;YAC3F,kGAAkG;YAClG,gFAAgF;YAChF,mFAAmF;YACnF,6EAA6E;YAC7E,+EAA+E;YAC/E,oFAAoF;YACpF,yDAAyD;YACzD,yEAAyE;YACzE,mFAAmF;YACnF,qEAAqE;YACrE,oDAAoD;YACpD,0DAA0D;YAC1D,0DAA0D;YAC1D,qFAAqF;YACrF,mEAAmE;YACnE,2DAA2D;YAC3D,uDAAuD;SACxD;QACD,QAAQ,EAAE;YACR,4DAA4D;YAC5D,wEAAwE;YACxE,uEAAuE;YACvE,6CAA6C;YAC7C,uFAAuF;SACxF;QACD,OAAO,EAAE;YACP,+EAA+E;YAC/E,yFAAyF;YACzF,kGAAkG;YAClG,gGAAgG;YAChG,kFAAkF;YAClF,2EAA2E;SAC5E;QACD,SAAS,EAAE;YACT,mEAAmE;YACnE,kBAAkB;YAClB,2FAA2F;YAC3F,6CAA6C;SAC9C;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"skill-db-schema.d.ts","sourceRoot":"","sources":["../../../src/skills/development/skill-db-schema.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"skill-db-schema.js","sourceRoot":"","sources":["../../../src/skills/development/skill-db-schema.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"skill-docker.d.ts","sourceRoot":"","sources":["../../../src/skills/development/skill-docker.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"skill-docker.js","sourceRoot":"","sources":["../../../src/skills/development/skill-docker.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"skill-env-setup.d.ts","sourceRoot":"","sources":["../../../src/skills/development/skill-env-setup.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"skill-env-setup.js","sourceRoot":"","sources":["../../../src/skills/development/skill-env-setup.ts"],"names":[],"mappings":""}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export interface SkillInput {
|
|
2
|
-
task: string;
|
|
3
|
-
context?: string;
|
|
4
|
-
options?: Record<string, unknown>;
|
|
5
|
-
}
|
|
6
|
-
export interface SkillOutput {
|
|
7
|
-
skill: string;
|
|
8
|
-
template?: string;
|
|
9
|
-
checklist: string[];
|
|
10
|
-
patterns: string[];
|
|
11
|
-
gotchas: string[];
|
|
12
|
-
resources: string[];
|
|
13
|
-
}
|
|
14
|
-
export declare function run(input: SkillInput): SkillOutput;
|
|
15
|
-
//# sourceMappingURL=skill-scaffold.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"skill-scaffold.d.ts","sourceRoot":"","sources":["../../../src/skills/development/skill-scaffold.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAwSD,wBAAgB,GAAG,CAAC,KAAK,EAAE,UAAU,GAAG,WAAW,CAWlD"}
|