claudex-setup 0.2.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/bin/cli.js +9 -1
- package/package.json +8 -2
- package/src/audit.js +40 -3
- package/src/badge.js +13 -0
- package/src/setup.js +363 -45
- 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 -42
- package/CONTRIBUTING.md +0 -53
- package/apf/state.json +0 -29
- package/apf/todo.md +0 -40
- package/content/devto-article.json +0 -8
- package/content/launch-posts.md +0 -160
- package/docs/index.html +0 -681
- package/tools/publish.js +0 -149
package/bin/cli.js
CHANGED
|
@@ -18,6 +18,7 @@ const HELP = `
|
|
|
18
18
|
npx claudex-setup audit Same as above
|
|
19
19
|
npx claudex-setup setup Apply recommended configuration
|
|
20
20
|
npx claudex-setup setup --auto Apply all recommendations without prompts
|
|
21
|
+
npx claudex-setup badge Generate shields.io badge markdown
|
|
21
22
|
|
|
22
23
|
Options:
|
|
23
24
|
--verbose Show detailed analysis
|
|
@@ -45,7 +46,14 @@ async function main() {
|
|
|
45
46
|
};
|
|
46
47
|
|
|
47
48
|
try {
|
|
48
|
-
if (command === '
|
|
49
|
+
if (command === 'badge') {
|
|
50
|
+
const { getBadgeMarkdown } = require('../src/badge');
|
|
51
|
+
const result = await audit({ ...options, silent: true });
|
|
52
|
+
console.log(getBadgeMarkdown(result.score));
|
|
53
|
+
console.log('');
|
|
54
|
+
console.log('Add this to your README.md');
|
|
55
|
+
process.exit(0);
|
|
56
|
+
} else if (command === 'setup') {
|
|
49
57
|
await setup(options);
|
|
50
58
|
} else {
|
|
51
59
|
await audit(options);
|
package/package.json
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claudex-setup",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Audit and optimize any project for Claude Code. Powered by 1107 verified techniques.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"claudex-setup": "bin/cli.js"
|
|
8
8
|
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"src",
|
|
12
|
+
"README.md",
|
|
13
|
+
"CHANGELOG.md"
|
|
14
|
+
],
|
|
9
15
|
"scripts": {
|
|
10
16
|
"start": "node bin/cli.js",
|
|
11
17
|
"test": "node test/run.js"
|
|
@@ -25,7 +31,7 @@
|
|
|
25
31
|
"license": "MIT",
|
|
26
32
|
"repository": {
|
|
27
33
|
"type": "git",
|
|
28
|
-
"url": "https://github.com/DnaFin/claudex"
|
|
34
|
+
"url": "git+https://github.com/DnaFin/claudex.git"
|
|
29
35
|
},
|
|
30
36
|
"engines": {
|
|
31
37
|
"node": ">=18.0.0"
|
package/src/audit.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
const { TECHNIQUES, STACKS } = require('./techniques');
|
|
6
6
|
const { ProjectContext } = require('./context');
|
|
7
|
+
const { getBadgeMarkdown } = require('./badge');
|
|
7
8
|
|
|
8
9
|
const COLORS = {
|
|
9
10
|
reset: '\x1b[0m',
|
|
@@ -27,7 +28,24 @@ function progressBar(score, max = 100, width = 20) {
|
|
|
27
28
|
return colorize('█'.repeat(filled), color) + colorize('░'.repeat(empty), 'dim');
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
const EFFORT_ORDER = { critical: 0, high: 1, medium: 2 };
|
|
32
|
+
|
|
33
|
+
function getQuickWins(failed) {
|
|
34
|
+
// Quick wins = medium impact items first (easiest), then high, sorted by name length (shorter = simpler)
|
|
35
|
+
return [...failed]
|
|
36
|
+
.sort((a, b) => {
|
|
37
|
+
const effortA = EFFORT_ORDER[a.impact] ?? 3;
|
|
38
|
+
const effortB = EFFORT_ORDER[b.impact] ?? 3;
|
|
39
|
+
// Prefer medium (easiest to fix), then high, then critical
|
|
40
|
+
if (effortA !== effortB) return effortB - effortA;
|
|
41
|
+
// Tie-break by fix length (shorter fix description = likely simpler)
|
|
42
|
+
return (a.fix || '').length - (b.fix || '').length;
|
|
43
|
+
})
|
|
44
|
+
.slice(0, 3);
|
|
45
|
+
}
|
|
46
|
+
|
|
30
47
|
async function audit(options) {
|
|
48
|
+
const silent = options.silent || false;
|
|
31
49
|
const ctx = new ProjectContext(options.dir);
|
|
32
50
|
const stacks = ctx.detectStacks(STACKS);
|
|
33
51
|
const results = [];
|
|
@@ -54,9 +72,14 @@ async function audit(options) {
|
|
|
54
72
|
const earnedScore = passed.reduce((sum, r) => sum + (weights[r.impact] || 5), 0);
|
|
55
73
|
const score = Math.round((earnedScore / maxScore) * 100);
|
|
56
74
|
|
|
75
|
+
// Silent mode: skip all output, just return result
|
|
76
|
+
if (silent) {
|
|
77
|
+
return { score, passed: passed.length, failed: failed.length, stacks, results };
|
|
78
|
+
}
|
|
79
|
+
|
|
57
80
|
if (options.json) {
|
|
58
81
|
console.log(JSON.stringify({ score, stacks, passed: passed.length, failed: failed.length, results }, null, 2));
|
|
59
|
-
return;
|
|
82
|
+
return { score, passed: passed.length, failed: failed.length, stacks, results };
|
|
60
83
|
}
|
|
61
84
|
|
|
62
85
|
// Display results
|
|
@@ -115,6 +138,18 @@ async function audit(options) {
|
|
|
115
138
|
console.log('');
|
|
116
139
|
}
|
|
117
140
|
|
|
141
|
+
// Quick wins
|
|
142
|
+
if (failed.length > 0) {
|
|
143
|
+
const quickWins = getQuickWins(failed);
|
|
144
|
+
console.log(colorize(' ⚡ Quick wins (easiest fixes first)', 'magenta'));
|
|
145
|
+
for (let i = 0; i < quickWins.length; i++) {
|
|
146
|
+
const r = quickWins[i];
|
|
147
|
+
console.log(` ${i + 1}. ${colorize(r.name, 'bold')}`);
|
|
148
|
+
console.log(colorize(` → ${r.fix}`, 'dim'));
|
|
149
|
+
}
|
|
150
|
+
console.log('');
|
|
151
|
+
}
|
|
152
|
+
|
|
118
153
|
// Summary
|
|
119
154
|
console.log(colorize(' ─────────────────────────────────────', 'dim'));
|
|
120
155
|
console.log(` ${colorize(`${passed.length}/${results.length}`, 'bold')} checks passing`);
|
|
@@ -123,12 +158,14 @@ async function audit(options) {
|
|
|
123
158
|
console.log(` Run ${colorize('npx claudex-setup setup', 'bold')} to fix automatically`);
|
|
124
159
|
}
|
|
125
160
|
|
|
161
|
+
console.log('');
|
|
162
|
+
console.log(` Add to README: ${getBadgeMarkdown(score)}`);
|
|
126
163
|
console.log('');
|
|
127
164
|
console.log(colorize(' Powered by CLAUDEX - 1,107 verified Claude Code techniques', 'dim'));
|
|
128
|
-
console.log(colorize(' https://github.com/
|
|
165
|
+
console.log(colorize(' https://github.com/DnaFin/claudex', 'dim'));
|
|
129
166
|
console.log('');
|
|
130
167
|
|
|
131
|
-
return { score, passed: passed.length, failed: failed.length, stacks };
|
|
168
|
+
return { score, passed: passed.length, failed: failed.length, stacks, results };
|
|
132
169
|
}
|
|
133
170
|
|
|
134
171
|
module.exports = { audit };
|
package/src/badge.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
function getBadgeUrl(score) {
|
|
2
|
+
const color = score >= 80 ? 'brightgreen' : score >= 60 ? 'yellow' : score >= 40 ? 'orange' : 'red';
|
|
3
|
+
const label = encodeURIComponent('Claude Code Ready');
|
|
4
|
+
const message = encodeURIComponent(`${score}/100`);
|
|
5
|
+
return `https://img.shields.io/badge/${label}-${message}-${color}`;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function getBadgeMarkdown(score) {
|
|
9
|
+
const url = getBadgeUrl(score);
|
|
10
|
+
return `[](https://github.com/DnaFin/claudex)`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = { getBadgeUrl, getBadgeMarkdown };
|
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,62 +9,349 @@ 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
|
-
const hasJS = stackKeys.some(k => ['typescript', 'react', 'vue', 'angular', 'nextjs', 'node'].includes(k));
|
|
16
|
-
const hasPython = stackKeys.includes('python');
|
|
17
253
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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');
|
|
23
274
|
} else if (hasPython) {
|
|
24
|
-
|
|
275
|
+
buildSection = `python -m pytest # run tests
|
|
25
276
|
python -m mypy . # type checking
|
|
26
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';
|
|
27
284
|
}
|
|
28
285
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
`;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
+
}
|
|
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'}\`
|
|
45
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\`)`);
|
|
46
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)`);
|
|
47
337
|
|
|
48
|
-
|
|
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}` : '';
|
|
49
342
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
graph TD
|
|
53
|
-
A[Entry Point] --> B[Core Logic]
|
|
54
|
-
B --> C[Data Layer]
|
|
55
|
-
B --> D[API / Routes]
|
|
56
|
-
C --> E[(Database)]
|
|
57
|
-
D --> F[External Services]
|
|
58
|
-
\`\`\`
|
|
59
|
-
<!-- Replace with your actual project architecture -->
|
|
343
|
+
// --- Assemble the final CLAUDE.md ---
|
|
344
|
+
return `# ${projectName}${projectDesc}
|
|
60
345
|
|
|
346
|
+
## Architecture
|
|
347
|
+
${mermaid}
|
|
348
|
+
${dirDescription}
|
|
61
349
|
## Stack
|
|
62
350
|
${stackNames}
|
|
63
|
-
${stackSection}
|
|
351
|
+
${stackSection}${tsSection}
|
|
64
352
|
## Build & Test
|
|
65
353
|
\`\`\`bash
|
|
66
|
-
${
|
|
354
|
+
${buildSection}
|
|
67
355
|
\`\`\`
|
|
68
356
|
|
|
69
357
|
## Code Style
|
|
@@ -72,21 +360,18 @@ ${buildCommands}
|
|
|
72
360
|
- Keep functions small and focused (< 50 lines)
|
|
73
361
|
- Use descriptive variable names; avoid abbreviations
|
|
74
362
|
|
|
75
|
-
## Constraints
|
|
76
363
|
<constraints>
|
|
77
364
|
- Never commit secrets, API keys, or .env files
|
|
78
365
|
- Always run tests before marking work complete
|
|
79
366
|
- Prefer editing existing files over creating new ones
|
|
80
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' : ''}\
|
|
81
370
|
</constraints>
|
|
82
371
|
|
|
83
|
-
## Verification
|
|
84
372
|
<verification>
|
|
85
373
|
Before completing any task, confirm:
|
|
86
|
-
|
|
87
|
-
2. New code has test coverage
|
|
88
|
-
3. No linting errors introduced
|
|
89
|
-
4. Changes match the requested scope (no gold-plating)
|
|
374
|
+
${verificationSteps.join('\n')}
|
|
90
375
|
</verification>
|
|
91
376
|
|
|
92
377
|
## Workflow
|
|
@@ -124,6 +409,28 @@ case "$BASENAME" in
|
|
|
124
409
|
;;
|
|
125
410
|
esac
|
|
126
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
|
+
|
|
127
434
|
exit 0
|
|
128
435
|
`,
|
|
129
436
|
}),
|
|
@@ -158,6 +465,16 @@ exit 0
|
|
|
158
465
|
3. Run the deployment command
|
|
159
466
|
4. Verify deployment succeeded (health check / smoke test)
|
|
160
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
|
|
161
478
|
`,
|
|
162
479
|
}),
|
|
163
480
|
|
|
@@ -260,7 +577,8 @@ async function setup(options) {
|
|
|
260
577
|
const template = TEMPLATES[technique.template];
|
|
261
578
|
if (!template) continue;
|
|
262
579
|
|
|
263
|
-
|
|
580
|
+
// Pass ctx as second argument — only claude-md uses it
|
|
581
|
+
const result = template(stacks, ctx);
|
|
264
582
|
|
|
265
583
|
if (typeof result === 'string') {
|
|
266
584
|
// Single file template (like CLAUDE.md)
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: security-reviewer
|
|
3
|
-
description: Reviews code for security vulnerabilities
|
|
4
|
-
tools: [Read, Grep, Glob]
|
|
5
|
-
model: sonnet
|
|
6
|
-
---
|
|
7
|
-
Review code for security issues:
|
|
8
|
-
- Injection vulnerabilities (SQL, XSS, command injection)
|
|
9
|
-
- Authentication and authorization flaws
|
|
10
|
-
- Secrets or credentials in code
|
|
11
|
-
- Insecure data handling
|
package/.claude/commands/test.md
DELETED