claudex-setup 0.1.0 → 0.2.1
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/CHANGELOG.md +24 -0
- package/README.md +181 -52
- package/bin/cli.js +10 -2
- package/package.json +9 -3
- package/src/audit.js +41 -4
- package/src/badge.js +13 -0
- package/src/claudex-sync.json +7 -0
- package/src/setup.js +459 -18
- package/src/techniques.js +667 -50
- package/.claude/agents/security-reviewer.md +0 -11
- package/.claude/commands/review.md +0 -6
- package/.claude/commands/test.md +0 -6
- package/.claude/hooks/on-edit-lint.sh +0 -5
- package/.claude/rules/frontend.md +0 -4
- package/.claude/skills/fix-issue/SKILL.md +0 -11
- package/APF.md +0 -121
- package/CLAUDE.md +0 -22
package/src/setup.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Setup engine - applies recommended Claude Code configuration to a project.
|
|
3
|
+
* v0.3.0 - Smart CLAUDE.md generation with project analysis.
|
|
3
4
|
*/
|
|
4
5
|
|
|
5
6
|
const fs = require('fs');
|
|
@@ -8,31 +9,376 @@ const { TECHNIQUES, STACKS } = require('./techniques');
|
|
|
8
9
|
const { ProjectContext } = require('./context');
|
|
9
10
|
const { audit } = require('./audit');
|
|
10
11
|
|
|
12
|
+
// ============================================================
|
|
13
|
+
// Helper: detect project scripts from package.json
|
|
14
|
+
// ============================================================
|
|
15
|
+
function detectScripts(ctx) {
|
|
16
|
+
const pkg = ctx.jsonFile('package.json');
|
|
17
|
+
if (!pkg || !pkg.scripts) return {};
|
|
18
|
+
const relevant = ['test', 'build', 'lint', 'dev', 'start', 'format', 'typecheck', 'check'];
|
|
19
|
+
const found = {};
|
|
20
|
+
for (const key of relevant) {
|
|
21
|
+
if (pkg.scripts[key]) {
|
|
22
|
+
found[key] = pkg.scripts[key];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return found;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ============================================================
|
|
29
|
+
// Helper: detect main directories
|
|
30
|
+
// ============================================================
|
|
31
|
+
function detectMainDirs(ctx) {
|
|
32
|
+
const candidates = ['src', 'lib', 'app', 'pages', 'components', 'api', 'routes', 'utils', 'helpers', 'services', 'models', 'controllers', 'views', 'public', 'assets', 'config', 'tests', 'test', '__tests__', 'spec', 'scripts', 'prisma', 'db', 'middleware'];
|
|
33
|
+
const found = [];
|
|
34
|
+
for (const dir of candidates) {
|
|
35
|
+
if (ctx.hasDir(dir)) {
|
|
36
|
+
const files = ctx.dirFiles(dir);
|
|
37
|
+
found.push({ name: dir, fileCount: files.length, files: files.slice(0, 10) });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return found;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ============================================================
|
|
44
|
+
// Helper: generate Mermaid diagram from directory structure
|
|
45
|
+
// ============================================================
|
|
46
|
+
function generateMermaid(dirs, stacks) {
|
|
47
|
+
const stackKeys = stacks.map(s => s.key);
|
|
48
|
+
const dirNames = dirs.map(d => d.name);
|
|
49
|
+
|
|
50
|
+
// Build nodes based on what exists
|
|
51
|
+
const nodes = [];
|
|
52
|
+
const edges = [];
|
|
53
|
+
let nodeId = 0;
|
|
54
|
+
const ids = {};
|
|
55
|
+
|
|
56
|
+
function addNode(label, shape) {
|
|
57
|
+
const id = String.fromCharCode(65 + nodeId++); // A, B, C...
|
|
58
|
+
ids[label] = id;
|
|
59
|
+
if (shape === 'db') return ` ${id}[(${label})]`;
|
|
60
|
+
if (shape === 'round') return ` ${id}(${label})`;
|
|
61
|
+
return ` ${id}[${label}]`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Entry point
|
|
65
|
+
nodes.push(addNode('Entry Point', 'round'));
|
|
66
|
+
|
|
67
|
+
// Detect layers
|
|
68
|
+
if (dirNames.includes('app') || dirNames.includes('pages')) {
|
|
69
|
+
nodes.push(addNode('Pages / Routes', 'default'));
|
|
70
|
+
edges.push(` ${ids['Entry Point']} --> ${ids['Pages / Routes']}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (dirNames.includes('components')) {
|
|
74
|
+
nodes.push(addNode('Components', 'default'));
|
|
75
|
+
const parent = ids['Pages / Routes'] || ids['Entry Point'];
|
|
76
|
+
edges.push(` ${parent} --> ${ids['Components']}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (dirNames.includes('src')) {
|
|
80
|
+
nodes.push(addNode('src/', 'default'));
|
|
81
|
+
const parent = ids['Pages / Routes'] || ids['Entry Point'];
|
|
82
|
+
edges.push(` ${parent} --> ${ids['src/']}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (dirNames.includes('lib')) {
|
|
86
|
+
nodes.push(addNode('lib/', 'default'));
|
|
87
|
+
const parent = ids['src/'] || ids['Entry Point'];
|
|
88
|
+
edges.push(` ${parent} --> ${ids['lib/']}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (dirNames.includes('api') || dirNames.includes('routes') || dirNames.includes('controllers')) {
|
|
92
|
+
const label = dirNames.includes('api') ? 'API Layer' : 'Routes';
|
|
93
|
+
nodes.push(addNode(label, 'default'));
|
|
94
|
+
const parent = ids['src/'] || ids['Entry Point'];
|
|
95
|
+
edges.push(` ${parent} --> ${ids[label]}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (dirNames.includes('services')) {
|
|
99
|
+
nodes.push(addNode('Services', 'default'));
|
|
100
|
+
const parent = ids['API Layer'] || ids['Routes'] || ids['src/'] || ids['Entry Point'];
|
|
101
|
+
edges.push(` ${parent} --> ${ids['Services']}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (dirNames.includes('models') || dirNames.includes('prisma') || dirNames.includes('db')) {
|
|
105
|
+
nodes.push(addNode('Data Layer', 'default'));
|
|
106
|
+
const parent = ids['Services'] || ids['API Layer'] || ids['Routes'] || ids['src/'] || ids['Entry Point'];
|
|
107
|
+
edges.push(` ${parent} --> ${ids['Data Layer']}`);
|
|
108
|
+
nodes.push(addNode('Database', 'db'));
|
|
109
|
+
edges.push(` ${ids['Data Layer']} --> ${ids['Database']}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (dirNames.includes('utils') || dirNames.includes('helpers')) {
|
|
113
|
+
nodes.push(addNode('Utils', 'default'));
|
|
114
|
+
const parent = ids['src/'] || ids['Services'] || ids['Entry Point'];
|
|
115
|
+
edges.push(` ${parent} --> ${ids['Utils']}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (dirNames.includes('middleware')) {
|
|
119
|
+
nodes.push(addNode('Middleware', 'default'));
|
|
120
|
+
const parent = ids['API Layer'] || ids['Routes'] || ids['Entry Point'];
|
|
121
|
+
edges.push(` ${parent} --> ${ids['Middleware']}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (dirNames.includes('tests') || dirNames.includes('test') || dirNames.includes('__tests__') || dirNames.includes('spec')) {
|
|
125
|
+
nodes.push(addNode('Tests', 'round'));
|
|
126
|
+
const parent = ids['src/'] || ids['Entry Point'];
|
|
127
|
+
edges.push(` ${ids['Tests']} -.-> ${parent}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Fallback: if we only have Entry Point, make a generic diagram
|
|
131
|
+
if (nodes.length <= 1) {
|
|
132
|
+
return `\`\`\`mermaid
|
|
133
|
+
graph TD
|
|
134
|
+
A[Entry Point] --> B[Core Logic]
|
|
135
|
+
B --> C[Data Layer]
|
|
136
|
+
B --> D[API / Routes]
|
|
137
|
+
C --> E[(Database)]
|
|
138
|
+
D --> F[External Services]
|
|
139
|
+
\`\`\`
|
|
140
|
+
<!-- Update this diagram to match your actual architecture -->`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return '```mermaid\ngraph TD\n' + nodes.join('\n') + '\n' + edges.join('\n') + '\n```';
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ============================================================
|
|
147
|
+
// Helper: framework-specific instructions
|
|
148
|
+
// ============================================================
|
|
149
|
+
function getFrameworkInstructions(stacks) {
|
|
150
|
+
const stackKeys = stacks.map(s => s.key);
|
|
151
|
+
const sections = [];
|
|
152
|
+
|
|
153
|
+
if (stackKeys.includes('nextjs')) {
|
|
154
|
+
sections.push(`### Next.js
|
|
155
|
+
- Use App Router conventions (app/ directory) when applicable
|
|
156
|
+
- Prefer Server Components by default; add 'use client' only when needed
|
|
157
|
+
- Use next/image for images, next/link for navigation
|
|
158
|
+
- API routes go in app/api/ (App Router) or pages/api/ (Pages Router)
|
|
159
|
+
- Use loading.tsx, error.tsx, and not-found.tsx for route-level UX`);
|
|
160
|
+
} else if (stackKeys.includes('react')) {
|
|
161
|
+
sections.push(`### React
|
|
162
|
+
- Use functional components with hooks exclusively
|
|
163
|
+
- Prefer named exports over default exports
|
|
164
|
+
- Keep components under 150 lines; extract sub-components
|
|
165
|
+
- Use custom hooks to share stateful logic
|
|
166
|
+
- Colocate styles, tests, and types with components`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (stackKeys.includes('vue')) {
|
|
170
|
+
sections.push(`### Vue
|
|
171
|
+
- Use Composition API with \`<script setup>\` syntax
|
|
172
|
+
- Prefer defineProps/defineEmits macros
|
|
173
|
+
- Keep components under 200 lines
|
|
174
|
+
- Use composables for shared logic`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (stackKeys.includes('angular')) {
|
|
178
|
+
sections.push(`### Angular
|
|
179
|
+
- Use standalone components when possible
|
|
180
|
+
- Follow Angular style guide naming conventions
|
|
181
|
+
- Use reactive forms over template-driven forms
|
|
182
|
+
- Keep services focused on a single responsibility`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (stackKeys.includes('typescript')) {
|
|
186
|
+
sections.push(`### TypeScript
|
|
187
|
+
- Use \`interface\` for object shapes, \`type\` for unions/intersections
|
|
188
|
+
- Enable strict mode in tsconfig.json
|
|
189
|
+
- Avoid \`any\` — use \`unknown\` and narrow with type guards
|
|
190
|
+
- Prefer \`as const\` assertions over enum when practical
|
|
191
|
+
- Export types alongside their implementations`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (stackKeys.includes('django')) {
|
|
195
|
+
sections.push(`### Django
|
|
196
|
+
- Follow fat models, thin views pattern
|
|
197
|
+
- Use class-based views for complex logic, function views for simple
|
|
198
|
+
- Always use Django ORM; avoid raw SQL unless necessary
|
|
199
|
+
- Keep business logic in models or services, not views`);
|
|
200
|
+
} else if (stackKeys.includes('fastapi')) {
|
|
201
|
+
sections.push(`### FastAPI
|
|
202
|
+
- Use Pydantic models for request/response validation
|
|
203
|
+
- Use dependency injection for shared logic
|
|
204
|
+
- Keep route handlers thin; delegate to service functions
|
|
205
|
+
- Use async def for I/O-bound endpoints`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (stackKeys.includes('python') || stackKeys.includes('django') || stackKeys.includes('fastapi')) {
|
|
209
|
+
sections.push(`### Python
|
|
210
|
+
- Use type hints on all function signatures and return types
|
|
211
|
+
- Follow PEP 8; use f-strings for formatting
|
|
212
|
+
- Prefer pathlib over os.path
|
|
213
|
+
- Use dataclasses or pydantic for structured data
|
|
214
|
+
- Raise specific exceptions; never bare \`except:\``);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (stackKeys.includes('rust')) {
|
|
218
|
+
sections.push(`### Rust
|
|
219
|
+
- Prefer Result<T, E> over unwrap/expect in library code
|
|
220
|
+
- Use clippy warnings as errors
|
|
221
|
+
- Derive common traits (Debug, Clone, PartialEq) where appropriate
|
|
222
|
+
- Use modules to organize code; keep lib.rs/main.rs thin`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (stackKeys.includes('go')) {
|
|
226
|
+
sections.push(`### Go
|
|
227
|
+
- Follow standard project layout conventions
|
|
228
|
+
- Handle all errors explicitly; no blank _ for errors
|
|
229
|
+
- Use interfaces for testability and abstraction
|
|
230
|
+
- Keep packages focused; avoid circular dependencies`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const hasJS = stackKeys.some(k => ['react', 'vue', 'angular', 'nextjs', 'node', 'svelte'].includes(k));
|
|
234
|
+
if (hasJS && !stackKeys.includes('typescript')) {
|
|
235
|
+
sections.push(`### JavaScript
|
|
236
|
+
- Use \`const\` by default, \`let\` when reassignment needed; never \`var\`
|
|
237
|
+
- Use \`async/await\` over raw Promises
|
|
238
|
+
- Use named exports over default exports
|
|
239
|
+
- Import order: stdlib > external > internal > relative`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return sections.join('\n\n');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ============================================================
|
|
246
|
+
// TEMPLATES
|
|
247
|
+
// ============================================================
|
|
248
|
+
|
|
11
249
|
const TEMPLATES = {
|
|
12
|
-
'claude-md': (stacks) => {
|
|
250
|
+
'claude-md': (stacks, ctx) => {
|
|
13
251
|
const stackNames = stacks.map(s => s.label).join(', ') || 'General';
|
|
14
|
-
|
|
252
|
+
const stackKeys = stacks.map(s => s.key);
|
|
15
253
|
|
|
16
|
-
|
|
17
|
-
|
|
254
|
+
// --- Detect project details ---
|
|
255
|
+
const scripts = detectScripts(ctx);
|
|
256
|
+
const mainDirs = detectMainDirs(ctx);
|
|
257
|
+
const hasTS = stackKeys.includes('typescript') || ctx.files.includes('tsconfig.json');
|
|
258
|
+
const hasPython = stackKeys.includes('python') || stackKeys.includes('django') || stackKeys.includes('fastapi');
|
|
259
|
+
const hasJS = stackKeys.some(k => ['react', 'vue', 'angular', 'nextjs', 'node', 'svelte'].includes(k));
|
|
260
|
+
|
|
261
|
+
// --- Build commands section ---
|
|
262
|
+
let buildSection = '';
|
|
263
|
+
if (Object.keys(scripts).length > 0) {
|
|
264
|
+
const lines = [];
|
|
265
|
+
if (scripts.dev) lines.push(`npm run dev # ${scripts.dev}`);
|
|
266
|
+
if (scripts.start) lines.push(`npm start # ${scripts.start}`);
|
|
267
|
+
if (scripts.build) lines.push(`npm run build # ${scripts.build}`);
|
|
268
|
+
if (scripts.test) lines.push(`npm test # ${scripts.test}`);
|
|
269
|
+
if (scripts.lint) lines.push(`npm run lint # ${scripts.lint}`);
|
|
270
|
+
if (scripts.format) lines.push(`npm run format # ${scripts.format}`);
|
|
271
|
+
if (scripts.typecheck) lines.push(`npm run typecheck # ${scripts.typecheck}`);
|
|
272
|
+
if (scripts.check) lines.push(`npm run check # ${scripts.check}`);
|
|
273
|
+
buildSection = lines.join('\n');
|
|
274
|
+
} else if (hasPython) {
|
|
275
|
+
buildSection = `python -m pytest # run tests
|
|
276
|
+
python -m mypy . # type checking
|
|
277
|
+
ruff check . # lint`;
|
|
278
|
+
} else if (hasJS) {
|
|
279
|
+
buildSection = `npm run build # or: npx tsc --noEmit
|
|
280
|
+
npm test # or: npx jest / npx vitest
|
|
281
|
+
npm run lint # or: npx eslint .`;
|
|
282
|
+
} else {
|
|
283
|
+
buildSection = '# Add your build command\n# Add your test command\n# Add your lint command';
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// --- Architecture description ---
|
|
287
|
+
const mermaid = generateMermaid(mainDirs, stacks);
|
|
288
|
+
|
|
289
|
+
let dirDescription = '';
|
|
290
|
+
if (mainDirs.length > 0) {
|
|
291
|
+
dirDescription = '\n### Directory Structure\n';
|
|
292
|
+
for (const dir of mainDirs) {
|
|
293
|
+
const suffix = dir.fileCount > 0 ? ` (${dir.fileCount} files)` : '';
|
|
294
|
+
dirDescription += `- \`${dir.name}/\`${suffix}\n`;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
18
297
|
|
|
298
|
+
// --- Framework-specific instructions ---
|
|
299
|
+
const frameworkInstructions = getFrameworkInstructions(stacks);
|
|
300
|
+
const stackSection = frameworkInstructions
|
|
301
|
+
? `\n## Stack-Specific Guidelines\n\n${frameworkInstructions}\n`
|
|
302
|
+
: '';
|
|
303
|
+
|
|
304
|
+
// --- TypeScript-specific additions ---
|
|
305
|
+
let tsSection = '';
|
|
306
|
+
if (hasTS) {
|
|
307
|
+
const tsconfig = ctx.jsonFile('tsconfig.json');
|
|
308
|
+
if (tsconfig) {
|
|
309
|
+
const strict = tsconfig.compilerOptions && tsconfig.compilerOptions.strict;
|
|
310
|
+
tsSection = `
|
|
311
|
+
## TypeScript Configuration
|
|
312
|
+
- Strict mode: ${strict ? '**enabled**' : '**disabled** (consider enabling)'}
|
|
313
|
+
- Always fix type errors before committing — do not use \`@ts-ignore\`
|
|
314
|
+
- Run type checking: \`${scripts.typecheck ? 'npm run typecheck' : 'npx tsc --noEmit'}\`
|
|
315
|
+
`;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// --- Verification criteria based on detected commands ---
|
|
320
|
+
const verificationSteps = [];
|
|
321
|
+
verificationSteps.push('1. All existing tests still pass');
|
|
322
|
+
verificationSteps.push('2. New code has test coverage');
|
|
323
|
+
if (scripts.lint || hasPython) {
|
|
324
|
+
verificationSteps.push(`3. No linting errors (\`${scripts.lint ? 'npm run lint' : 'ruff check .'}\`)`);
|
|
325
|
+
} else if (hasJS) {
|
|
326
|
+
verificationSteps.push('3. No linting errors (`npx eslint .`)');
|
|
327
|
+
} else {
|
|
328
|
+
verificationSteps.push('3. No linting errors introduced');
|
|
329
|
+
}
|
|
330
|
+
if (scripts.build) {
|
|
331
|
+
verificationSteps.push(`4. Build succeeds (\`npm run build\`)`);
|
|
332
|
+
}
|
|
333
|
+
if (hasTS) {
|
|
334
|
+
verificationSteps.push(`${verificationSteps.length + 1}. No TypeScript errors (\`${scripts.typecheck ? 'npm run typecheck' : 'npx tsc --noEmit'}\`)`);
|
|
335
|
+
}
|
|
336
|
+
verificationSteps.push(`${verificationSteps.length + 1}. Changes match the requested scope (no gold-plating)`);
|
|
337
|
+
|
|
338
|
+
// --- Read package.json for project name/description ---
|
|
339
|
+
const pkg = ctx.jsonFile('package.json');
|
|
340
|
+
const projectName = (pkg && pkg.name) ? pkg.name : path.basename(ctx.dir);
|
|
341
|
+
const projectDesc = (pkg && pkg.description) ? ` — ${pkg.description}` : '';
|
|
342
|
+
|
|
343
|
+
// --- Assemble the final CLAUDE.md ---
|
|
344
|
+
return `# ${projectName}${projectDesc}
|
|
345
|
+
|
|
346
|
+
## Architecture
|
|
347
|
+
${mermaid}
|
|
348
|
+
${dirDescription}
|
|
19
349
|
## Stack
|
|
20
350
|
${stackNames}
|
|
21
|
-
|
|
351
|
+
${stackSection}${tsSection}
|
|
22
352
|
## Build & Test
|
|
23
353
|
\`\`\`bash
|
|
24
|
-
|
|
25
|
-
# Add your test command
|
|
26
|
-
# Add your lint command
|
|
354
|
+
${buildSection}
|
|
27
355
|
\`\`\`
|
|
28
356
|
|
|
29
357
|
## Code Style
|
|
30
358
|
- Follow existing patterns in the codebase
|
|
31
359
|
- Write tests for new features
|
|
360
|
+
- Keep functions small and focused (< 50 lines)
|
|
361
|
+
- Use descriptive variable names; avoid abbreviations
|
|
362
|
+
|
|
363
|
+
<constraints>
|
|
364
|
+
- Never commit secrets, API keys, or .env files
|
|
365
|
+
- Always run tests before marking work complete
|
|
366
|
+
- Prefer editing existing files over creating new ones
|
|
367
|
+
- When uncertain about architecture, ask before implementing
|
|
368
|
+
${hasTS ? '- Do not use @ts-ignore or @ts-expect-error without a tracking issue\n' : ''}\
|
|
369
|
+
${hasJS ? '- Use const by default; never use var\n' : ''}\
|
|
370
|
+
</constraints>
|
|
371
|
+
|
|
372
|
+
<verification>
|
|
373
|
+
Before completing any task, confirm:
|
|
374
|
+
${verificationSteps.join('\n')}
|
|
375
|
+
</verification>
|
|
32
376
|
|
|
33
377
|
## Workflow
|
|
34
378
|
- Verify changes with tests before committing
|
|
35
|
-
- Use descriptive commit messages
|
|
379
|
+
- Use descriptive commit messages (why, not what)
|
|
380
|
+
- Create focused PRs — one concern per PR
|
|
381
|
+
- Document non-obvious decisions in code comments
|
|
36
382
|
`;
|
|
37
383
|
},
|
|
38
384
|
|
|
@@ -42,6 +388,50 @@ ${stackNames}
|
|
|
42
388
|
# Customize the linter command for your project
|
|
43
389
|
TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S")
|
|
44
390
|
echo "[$TIMESTAMP] File changed: $(cat -)" >> .claude/logs/changes.txt
|
|
391
|
+
`,
|
|
392
|
+
'protect-secrets.sh': `#!/bin/bash
|
|
393
|
+
# PreToolUse hook - warn before touching sensitive files
|
|
394
|
+
# Prevents accidental reads/writes to files containing secrets
|
|
395
|
+
|
|
396
|
+
INPUT=$(cat -)
|
|
397
|
+
FILE=$(echo "$INPUT" | grep -oP '"file_path"\\s*:\\s*"\\K[^"]+' 2>/dev/null || echo "")
|
|
398
|
+
|
|
399
|
+
if [ -z "$FILE" ]; then
|
|
400
|
+
exit 0
|
|
401
|
+
fi
|
|
402
|
+
|
|
403
|
+
BASENAME=$(basename "$FILE")
|
|
404
|
+
|
|
405
|
+
case "$BASENAME" in
|
|
406
|
+
.env|.env.*|*.pem|*.key|credentials.json|secrets.yaml|secrets.yml)
|
|
407
|
+
echo "WARN: Attempting to access sensitive file: $BASENAME"
|
|
408
|
+
echo "This file may contain secrets. Proceed with caution."
|
|
409
|
+
;;
|
|
410
|
+
esac
|
|
411
|
+
|
|
412
|
+
exit 0
|
|
413
|
+
`,
|
|
414
|
+
'log-changes.sh': `#!/bin/bash
|
|
415
|
+
# PostToolUse hook - logs all file changes with timestamps
|
|
416
|
+
# Appends to .claude/logs/file-changes.log
|
|
417
|
+
|
|
418
|
+
INPUT=$(cat -)
|
|
419
|
+
TOOL_NAME=$(echo "$INPUT" | grep -oP '"tool_name"\\s*:\\s*"\\K[^"]+' 2>/dev/null || echo "unknown")
|
|
420
|
+
FILE_PATH=$(echo "$INPUT" | grep -oP '"file_path"\\s*:\\s*"\\K[^"]+' 2>/dev/null || echo "")
|
|
421
|
+
|
|
422
|
+
if [ -z "$FILE_PATH" ]; then
|
|
423
|
+
exit 0
|
|
424
|
+
fi
|
|
425
|
+
|
|
426
|
+
LOG_DIR=".claude/logs"
|
|
427
|
+
LOG_FILE="$LOG_DIR/file-changes.log"
|
|
428
|
+
|
|
429
|
+
mkdir -p "$LOG_DIR"
|
|
430
|
+
|
|
431
|
+
TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S")
|
|
432
|
+
echo "[$TIMESTAMP] $TOOL_NAME: $FILE_PATH" >> "$LOG_FILE"
|
|
433
|
+
|
|
434
|
+
exit 0
|
|
45
435
|
`,
|
|
46
436
|
}),
|
|
47
437
|
|
|
@@ -59,6 +449,32 @@ echo "[$TIMESTAMP] File changed: $(cat -)" >> .claude/logs/changes.txt
|
|
|
59
449
|
1. Run \`git diff\` to see all changes
|
|
60
450
|
2. Check for: bugs, security issues, missing tests, code style
|
|
61
451
|
3. Provide actionable feedback
|
|
452
|
+
`,
|
|
453
|
+
'deploy.md': `Pre-deployment checklist and deployment steps.
|
|
454
|
+
|
|
455
|
+
## Pre-deploy checks:
|
|
456
|
+
1. Run \`git status\` — working tree must be clean
|
|
457
|
+
2. Run full test suite — all tests must pass
|
|
458
|
+
3. Run linter — no errors allowed
|
|
459
|
+
4. Check for TODO/FIXME/HACK comments in changed files
|
|
460
|
+
5. Verify no secrets in staged changes (\`git diff --cached\`)
|
|
461
|
+
|
|
462
|
+
## Deploy steps:
|
|
463
|
+
1. Confirm target environment (staging vs production)
|
|
464
|
+
2. Review the diff since last deploy tag
|
|
465
|
+
3. Run the deployment command
|
|
466
|
+
4. Verify deployment succeeded (health check / smoke test)
|
|
467
|
+
5. Tag the release: \`git tag -a vX.Y.Z -m "Release vX.Y.Z"\`
|
|
468
|
+
`,
|
|
469
|
+
'fix.md': `Fix the issue described: $ARGUMENTS
|
|
470
|
+
|
|
471
|
+
## Steps:
|
|
472
|
+
1. Understand the issue — read relevant code and error messages
|
|
473
|
+
2. Identify the root cause (not just the symptom)
|
|
474
|
+
3. Implement the minimal fix
|
|
475
|
+
4. Write or update tests to cover the fix
|
|
476
|
+
5. Run the full test suite to verify no regressions
|
|
477
|
+
6. Summarize what was wrong and how the fix addresses it
|
|
62
478
|
`,
|
|
63
479
|
}),
|
|
64
480
|
|
|
@@ -83,19 +499,33 @@ Fix the GitHub issue: $ARGUMENTS
|
|
|
83
499
|
const hasPython = stacks.some(s => s.key === 'python');
|
|
84
500
|
|
|
85
501
|
if (hasTS || stacks.some(s => ['react', 'vue', 'angular', 'nextjs', 'node'].includes(s.key))) {
|
|
86
|
-
rules['frontend.md'] = `When editing
|
|
87
|
-
- Use functional components with hooks
|
|
88
|
-
-
|
|
89
|
-
-
|
|
502
|
+
rules['frontend.md'] = `When editing JavaScript/TypeScript files (*.ts, *.tsx, *.js, *.jsx, *.vue):
|
|
503
|
+
- Use functional components with hooks (React/Vue 3)
|
|
504
|
+
- Add TypeScript interfaces for all props and function params
|
|
505
|
+
- Prefer \`const\` over \`let\`; never use \`var\`
|
|
506
|
+
- Use named exports over default exports
|
|
507
|
+
- Handle errors explicitly — no empty catch blocks
|
|
508
|
+
- Keep component files under 200 lines; extract sub-components
|
|
90
509
|
`;
|
|
91
510
|
}
|
|
92
511
|
if (hasPython) {
|
|
93
|
-
rules['python.md'] = `When editing Python files:
|
|
94
|
-
- Use type hints for function signatures
|
|
95
|
-
- Follow PEP 8 conventions
|
|
96
|
-
- Use f-strings for formatting
|
|
512
|
+
rules['python.md'] = `When editing Python files (*.py):
|
|
513
|
+
- Use type hints for all function signatures and return types
|
|
514
|
+
- Follow PEP 8 conventions; max line length 88 (black default)
|
|
515
|
+
- Use f-strings for string formatting
|
|
516
|
+
- Prefer pathlib.Path over os.path
|
|
517
|
+
- Use \`if __name__ == "__main__":\` guard in scripts
|
|
518
|
+
- Raise specific exceptions, never bare \`except:\`
|
|
97
519
|
`;
|
|
98
520
|
}
|
|
521
|
+
rules['tests.md'] = `When writing or editing test files:
|
|
522
|
+
- Each test must have a clear, descriptive name (test_should_X_when_Y)
|
|
523
|
+
- Follow Arrange-Act-Assert (AAA) pattern
|
|
524
|
+
- One assertion per test when practical
|
|
525
|
+
- Never skip or disable tests without a tracking issue
|
|
526
|
+
- Mock external dependencies, not internal logic
|
|
527
|
+
- Include both happy path and edge case tests
|
|
528
|
+
`;
|
|
99
529
|
return rules;
|
|
100
530
|
},
|
|
101
531
|
|
|
@@ -113,6 +543,16 @@ Review code for security issues:
|
|
|
113
543
|
- Insecure data handling
|
|
114
544
|
`,
|
|
115
545
|
}),
|
|
546
|
+
|
|
547
|
+
'mermaid': () => `\`\`\`mermaid
|
|
548
|
+
graph TD
|
|
549
|
+
A[Entry Point] --> B[Core Logic]
|
|
550
|
+
B --> C[Data Layer]
|
|
551
|
+
B --> D[API / Routes]
|
|
552
|
+
C --> E[(Database)]
|
|
553
|
+
D --> F[External Services]
|
|
554
|
+
\`\`\`
|
|
555
|
+
`,
|
|
116
556
|
};
|
|
117
557
|
|
|
118
558
|
async function setup(options) {
|
|
@@ -137,7 +577,8 @@ async function setup(options) {
|
|
|
137
577
|
const template = TEMPLATES[technique.template];
|
|
138
578
|
if (!template) continue;
|
|
139
579
|
|
|
140
|
-
|
|
580
|
+
// Pass ctx as second argument — only claude-md uses it
|
|
581
|
+
const result = template(stacks, ctx);
|
|
141
582
|
|
|
142
583
|
if (typeof result === 'string') {
|
|
143
584
|
// Single file template (like CLAUDE.md)
|