claudex-setup 1.5.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/README.md +90 -7
- package/bin/cli.js +214 -11
- package/package.json +1 -1
- package/src/activity.js +60 -0
- package/src/analyze.js +397 -0
- package/src/audit.js +25 -20
- package/src/benchmark.js +176 -0
- package/src/governance.js +192 -0
- package/src/index.js +13 -1
- package/src/interactive.js +2 -2
- package/src/plans.js +355 -0
- package/src/setup.js +198 -52
- package/src/techniques.js +10 -0
package/src/setup.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Setup engine - applies recommended Claude Code configuration to a project.
|
|
3
|
-
*
|
|
3
|
+
* v1.7.0 - Starter-safe setup engine with reusable planning primitives.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const fs = require('fs');
|
|
@@ -95,6 +95,17 @@ function detectDependencies(ctx) {
|
|
|
95
95
|
guidelines.push('- Use Playwright for E2E tests. Keep tests in tests/ or e2e/');
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
// Testing tools
|
|
99
|
+
if (allDeps['msw']) {
|
|
100
|
+
guidelines.push('- Use MSW (Mock Service Worker) for API mocking in tests. Define handlers in __mocks__/');
|
|
101
|
+
}
|
|
102
|
+
if (allDeps['@testing-library/react']) {
|
|
103
|
+
guidelines.push('- Use Testing Library for component tests. Prefer userEvent over fireEvent, query by role/label');
|
|
104
|
+
}
|
|
105
|
+
if (allDeps['@vitest/coverage-v8'] || allDeps['@vitest/coverage-istanbul']) {
|
|
106
|
+
guidelines.push('- Coverage configured. Maintain coverage thresholds. Check reports before merging');
|
|
107
|
+
}
|
|
108
|
+
|
|
98
109
|
// tRPC
|
|
99
110
|
if (allDeps['@trpc/server'] || allDeps['@trpc/client']) {
|
|
100
111
|
guidelines.push('- Use tRPC for type-safe API calls. Define routers in server, use client hooks in components');
|
|
@@ -213,6 +224,23 @@ function detectDependencies(ctx) {
|
|
|
213
224
|
guidelines.push('- Lambda handlers: keep cold start fast, use layers for deps, set appropriate memory/timeout');
|
|
214
225
|
}
|
|
215
226
|
|
|
227
|
+
// Deprecated dependency warnings
|
|
228
|
+
if (allDeps['moment']) {
|
|
229
|
+
guidelines.push('- ⚠️ moment.js is deprecated and heavy (330KB). Migrate to date-fns or dayjs');
|
|
230
|
+
}
|
|
231
|
+
if (allDeps['request']) {
|
|
232
|
+
guidelines.push('- ⚠️ request is deprecated. Use fetch (native) or axios instead');
|
|
233
|
+
}
|
|
234
|
+
if (allDeps['lodash'] && !allDeps['lodash-es']) {
|
|
235
|
+
guidelines.push('- Consider replacing lodash with native JS methods or lodash-es for tree-shaking');
|
|
236
|
+
}
|
|
237
|
+
if (allDeps['node-sass']) {
|
|
238
|
+
guidelines.push('- ⚠️ node-sass is deprecated. Migrate to sass (dart-sass)');
|
|
239
|
+
}
|
|
240
|
+
if (allDeps['tslint']) {
|
|
241
|
+
guidelines.push('- ⚠️ TSLint is deprecated. Migrate to ESLint with @typescript-eslint');
|
|
242
|
+
}
|
|
243
|
+
|
|
216
244
|
return guidelines;
|
|
217
245
|
}
|
|
218
246
|
|
|
@@ -220,9 +248,9 @@ function detectDependencies(ctx) {
|
|
|
220
248
|
// Helper: detect main directories
|
|
221
249
|
// ============================================================
|
|
222
250
|
function detectMainDirs(ctx) {
|
|
223
|
-
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', 'hooks'];
|
|
251
|
+
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', 'hooks', 'agents', 'chains', 'workers', 'jobs', 'dags', 'macros', 'migrations'];
|
|
224
252
|
// Also check inside src/ for nested structure (common in Next.js, React)
|
|
225
|
-
const srcNested = ['src/components', 'src/app', 'src/pages', 'src/api', 'src/lib', 'src/hooks', 'src/utils', 'src/services', 'src/models', 'src/middleware', 'src/app/api', 'app/api'];
|
|
253
|
+
const srcNested = ['src/components', 'src/app', 'src/pages', 'src/api', 'src/lib', 'src/hooks', 'src/utils', 'src/services', 'src/models', 'src/middleware', 'src/app/api', 'app/api', 'src/agents', 'src/chains', 'src/workers', 'src/jobs', 'models/staging', 'models/marts'];
|
|
226
254
|
const found = [];
|
|
227
255
|
const seenNames = new Set();
|
|
228
256
|
|
|
@@ -239,6 +267,55 @@ function detectMainDirs(ctx) {
|
|
|
239
267
|
return found;
|
|
240
268
|
}
|
|
241
269
|
|
|
270
|
+
function escapeRegex(value) {
|
|
271
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function extractTomlSection(content, sectionName) {
|
|
275
|
+
const pattern = new RegExp(`\\[${escapeRegex(sectionName)}\\]([\\s\\S]*?)(?:\\n\\s*\\[|$)`);
|
|
276
|
+
const match = content.match(pattern);
|
|
277
|
+
return match ? match[1] : null;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function extractTomlValue(sectionContent, key) {
|
|
281
|
+
if (!sectionContent) return null;
|
|
282
|
+
const pattern = new RegExp(`^\\s*${escapeRegex(key)}\\s*=\\s*["']([^"']+)["']`, 'm');
|
|
283
|
+
const match = sectionContent.match(pattern);
|
|
284
|
+
return match ? match[1].trim() : null;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function detectProjectMetadata(ctx) {
|
|
288
|
+
const pkg = ctx.jsonFile('package.json');
|
|
289
|
+
if (pkg && (pkg.name || pkg.description)) {
|
|
290
|
+
return {
|
|
291
|
+
name: pkg.name || path.basename(ctx.dir),
|
|
292
|
+
description: pkg.description || '',
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const pyproject = ctx.fileContent('pyproject.toml') || '';
|
|
297
|
+
if (pyproject) {
|
|
298
|
+
const projectSection = extractTomlSection(pyproject, 'project');
|
|
299
|
+
const poetrySection = extractTomlSection(pyproject, 'tool.poetry');
|
|
300
|
+
const name = extractTomlValue(projectSection, 'name') ||
|
|
301
|
+
extractTomlValue(poetrySection, 'name');
|
|
302
|
+
const description = extractTomlValue(projectSection, 'description') ||
|
|
303
|
+
extractTomlValue(poetrySection, 'description');
|
|
304
|
+
|
|
305
|
+
if (name || description) {
|
|
306
|
+
return {
|
|
307
|
+
name: name || path.basename(ctx.dir),
|
|
308
|
+
description: description || '',
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
name: path.basename(ctx.dir),
|
|
315
|
+
description: '',
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
242
319
|
// ============================================================
|
|
243
320
|
// Helper: generate Mermaid diagram from directory structure
|
|
244
321
|
// ============================================================
|
|
@@ -267,6 +344,11 @@ function generateMermaid(dirs, stacks) {
|
|
|
267
344
|
const hasSrcComponents = dirNames.includes('src/components') || dirNames.includes('components');
|
|
268
345
|
const hasSrcHooks = dirNames.includes('src/hooks') || dirNames.includes('hooks');
|
|
269
346
|
const hasSrcLib = dirNames.includes('src/lib') || dirNames.includes('lib');
|
|
347
|
+
const hasSrcNode = dirNames.includes('src');
|
|
348
|
+
const hasAgents = dirNames.includes('src/agents') || dirNames.includes('agents');
|
|
349
|
+
const hasChains = dirNames.includes('src/chains') || dirNames.includes('chains');
|
|
350
|
+
const hasWorkers = dirNames.includes('src/workers') || dirNames.includes('workers') || dirNames.includes('jobs');
|
|
351
|
+
const hasPipelines = dirNames.includes('dags') || dirNames.includes('macros');
|
|
270
352
|
|
|
271
353
|
// Smart entry point based on framework
|
|
272
354
|
const isNextJs = stackKeys.includes('nextjs');
|
|
@@ -284,6 +366,7 @@ function generateMermaid(dirs, stacks) {
|
|
|
284
366
|
}
|
|
285
367
|
|
|
286
368
|
const root = ids['Next.js'] || ids['Django'] || ids['FastAPI'] || ids['Entry Point'];
|
|
369
|
+
const pickNodeId = (...labels) => labels.map(label => ids[label]).find(Boolean) || root;
|
|
287
370
|
|
|
288
371
|
// Detect layers
|
|
289
372
|
if (hasAppRouter || hasPages) {
|
|
@@ -312,9 +395,9 @@ function generateMermaid(dirs, stacks) {
|
|
|
312
395
|
|
|
313
396
|
if (hasSrcLib) {
|
|
314
397
|
nodes.push(addNode('lib/', 'default'));
|
|
315
|
-
const parent =
|
|
398
|
+
const parent = pickNodeId('API Routes', 'Hooks', 'Components');
|
|
316
399
|
edges.push(` ${parent} --> ${ids['lib/']}`);
|
|
317
|
-
} else if (
|
|
400
|
+
} else if (hasSrcNode && !hasAppRouter && !hasPages) {
|
|
318
401
|
nodes.push(addNode('src/', 'default'));
|
|
319
402
|
edges.push(` ${root} --> ${ids['src/']}`);
|
|
320
403
|
}
|
|
@@ -322,19 +405,19 @@ function generateMermaid(dirs, stacks) {
|
|
|
322
405
|
if (dirNames.includes('api') || dirNames.includes('routes') || dirNames.includes('controllers')) {
|
|
323
406
|
const label = dirNames.includes('api') ? 'API Layer' : 'Routes';
|
|
324
407
|
nodes.push(addNode(label, 'default'));
|
|
325
|
-
const parent =
|
|
408
|
+
const parent = pickNodeId('src/', 'App Router', 'Pages');
|
|
326
409
|
edges.push(` ${parent} --> ${ids[label]}`);
|
|
327
410
|
}
|
|
328
411
|
|
|
329
412
|
if (dirNames.includes('services')) {
|
|
330
413
|
nodes.push(addNode('Services', 'default'));
|
|
331
|
-
const parent =
|
|
414
|
+
const parent = pickNodeId('API Layer', 'Routes', 'src/', 'App Router', 'Pages');
|
|
332
415
|
edges.push(` ${parent} --> ${ids['Services']}`);
|
|
333
416
|
}
|
|
334
417
|
|
|
335
418
|
if (dirNames.includes('models') || dirNames.includes('prisma') || dirNames.includes('db')) {
|
|
336
419
|
nodes.push(addNode('Data Layer', 'default'));
|
|
337
|
-
const parent =
|
|
420
|
+
const parent = pickNodeId('Services', 'API Layer', 'Routes', 'src/', 'App Router', 'Pages');
|
|
338
421
|
edges.push(` ${parent} --> ${ids['Data Layer']}`);
|
|
339
422
|
nodes.push(addNode('Database', 'db'));
|
|
340
423
|
edges.push(` ${ids['Data Layer']} --> ${ids['Database']}`);
|
|
@@ -342,19 +425,43 @@ function generateMermaid(dirs, stacks) {
|
|
|
342
425
|
|
|
343
426
|
if (dirNames.includes('utils') || dirNames.includes('helpers')) {
|
|
344
427
|
nodes.push(addNode('Utils', 'default'));
|
|
345
|
-
const parent =
|
|
428
|
+
const parent = pickNodeId('src/', 'Services', 'lib/', 'Components');
|
|
346
429
|
edges.push(` ${parent} --> ${ids['Utils']}`);
|
|
347
430
|
}
|
|
348
431
|
|
|
349
432
|
if (dirNames.includes('middleware')) {
|
|
350
433
|
nodes.push(addNode('Middleware', 'default'));
|
|
351
|
-
const parent =
|
|
434
|
+
const parent = pickNodeId('API Layer', 'Routes', 'App Router', 'Pages');
|
|
352
435
|
edges.push(` ${parent} --> ${ids['Middleware']}`);
|
|
353
436
|
}
|
|
354
437
|
|
|
438
|
+
if (hasChains) {
|
|
439
|
+
nodes.push(addNode('Chains', 'default'));
|
|
440
|
+
const parent = pickNodeId('Services', 'src/', 'lib/', 'API Layer');
|
|
441
|
+
edges.push(` ${parent} --> ${ids['Chains']}`);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (hasAgents) {
|
|
445
|
+
nodes.push(addNode('Agents', 'default'));
|
|
446
|
+
const parent = pickNodeId('Chains', 'Services', 'src/', 'lib/');
|
|
447
|
+
edges.push(` ${parent} --> ${ids['Agents']}`);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (hasWorkers) {
|
|
451
|
+
nodes.push(addNode('Workers', 'default'));
|
|
452
|
+
const parent = pickNodeId('Services', 'API Layer', 'src/');
|
|
453
|
+
edges.push(` ${parent} --> ${ids['Workers']}`);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (hasPipelines) {
|
|
457
|
+
nodes.push(addNode('Pipelines', 'default'));
|
|
458
|
+
const parent = pickNodeId('Services', 'Data Layer', 'src/');
|
|
459
|
+
edges.push(` ${parent} --> ${ids['Pipelines']}`);
|
|
460
|
+
}
|
|
461
|
+
|
|
355
462
|
if (dirNames.includes('tests') || dirNames.includes('test') || dirNames.includes('__tests__') || dirNames.includes('spec')) {
|
|
356
463
|
nodes.push(addNode('Tests', 'round'));
|
|
357
|
-
const parent =
|
|
464
|
+
const parent = pickNodeId('src/', 'App Router', 'Pages', 'Services', 'Components');
|
|
358
465
|
edges.push(` ${ids['Tests']} -.-> ${parent}`);
|
|
359
466
|
}
|
|
360
467
|
|
|
@@ -388,13 +495,9 @@ function getFrameworkInstructions(stacks) {
|
|
|
388
495
|
- Use next/image for images, next/link for navigation
|
|
389
496
|
- API routes go in app/api/ (App Router) or pages/api/ (Pages Router)
|
|
390
497
|
- Use loading.tsx, error.tsx, and not-found.tsx for route-level UX
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
-
|
|
394
|
-
- Use Server Actions for mutations. Validate with Zod, call revalidatePath after writes
|
|
395
|
-
- Route handlers in app/api/ export named functions: GET, POST, PUT, DELETE
|
|
396
|
-
- Use loading.tsx, error.tsx, not-found.tsx for route-level UI states
|
|
397
|
-
- Middleware in middleware.ts for auth checks, redirects, headers`);
|
|
498
|
+
- If app/ exists, use Server Actions for mutations, validate with Zod, and call revalidatePath after writes
|
|
499
|
+
- Route handlers in app/api/ should export named functions: GET, POST, PUT, DELETE
|
|
500
|
+
- Middleware in middleware.ts should handle auth checks, redirects, and headers`);
|
|
398
501
|
} else if (stackKeys.includes('react')) {
|
|
399
502
|
sections.push(`### React
|
|
400
503
|
- Use functional components with hooks exclusively
|
|
@@ -473,6 +576,22 @@ function getFrameworkInstructions(stacks) {
|
|
|
473
576
|
- Organize: cmd/ for entry points, internal/ for private packages, pkg/ for public`);
|
|
474
577
|
}
|
|
475
578
|
|
|
579
|
+
if (stackKeys.includes('cpp')) {
|
|
580
|
+
sections.push(`### C++
|
|
581
|
+
- Follow project coding standards (check .clang-format if present)
|
|
582
|
+
- Use smart pointers (unique_ptr, shared_ptr) over raw pointers
|
|
583
|
+
- Run clang-tidy for static analysis
|
|
584
|
+
- Prefer const references for function parameters
|
|
585
|
+
- Use CMake targets, not raw compiler flags`);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (stackKeys.includes('bazel')) {
|
|
589
|
+
sections.push(`### Bazel
|
|
590
|
+
- Define BUILD files per package. Keep targets focused
|
|
591
|
+
- Use visibility carefully — prefer package-private
|
|
592
|
+
- Run buildifier for formatting`);
|
|
593
|
+
}
|
|
594
|
+
|
|
476
595
|
if (stackKeys.includes('terraform')) {
|
|
477
596
|
sections.push(`### Terraform
|
|
478
597
|
- Use modules for reusable infrastructure components
|
|
@@ -611,10 +730,10 @@ ${depGuidelines.join('\n')}
|
|
|
611
730
|
}
|
|
612
731
|
verificationSteps.push(`${verificationSteps.length + 1}. Changes match the requested scope (no gold-plating)`);
|
|
613
732
|
|
|
614
|
-
// --- Read package.json
|
|
615
|
-
const
|
|
616
|
-
const projectName =
|
|
617
|
-
const projectDesc =
|
|
733
|
+
// --- Read project metadata from package.json or pyproject.toml ---
|
|
734
|
+
const projectMeta = detectProjectMetadata(ctx);
|
|
735
|
+
const projectName = projectMeta.name;
|
|
736
|
+
const projectDesc = projectMeta.description ? ` — ${projectMeta.description}` : '';
|
|
618
737
|
|
|
619
738
|
// --- Assemble the final CLAUDE.md ---
|
|
620
739
|
return `# ${projectName}${projectDesc}
|
|
@@ -630,11 +749,10 @@ ${stackSection}${tsSection}${depSection}
|
|
|
630
749
|
${buildSection}
|
|
631
750
|
\`\`\`
|
|
632
751
|
|
|
633
|
-
##
|
|
634
|
-
-
|
|
635
|
-
-
|
|
636
|
-
- Keep
|
|
637
|
-
- Use descriptive variable names; avoid abbreviations
|
|
752
|
+
## Working Notes
|
|
753
|
+
- You are a careful engineer working inside this repository. Preserve its existing architecture and naming patterns unless the task requires a change
|
|
754
|
+
- Prefer extending existing modules over creating parallel abstractions
|
|
755
|
+
- Keep changes scoped to the requested task and verify them before marking work complete
|
|
638
756
|
|
|
639
757
|
<constraints>
|
|
640
758
|
- Never commit secrets, API keys, or .env files
|
|
@@ -656,12 +774,6 @@ ${verificationSteps.join('\n')}
|
|
|
656
774
|
- If a session gets too long, start fresh with /clear
|
|
657
775
|
- Use subagents for research tasks to keep main context clean
|
|
658
776
|
|
|
659
|
-
## Workflow
|
|
660
|
-
- Verify changes with tests before committing
|
|
661
|
-
- Use descriptive commit messages (why, not what)
|
|
662
|
-
- Create focused PRs — one concern per PR
|
|
663
|
-
- Document non-obvious decisions in code comments
|
|
664
|
-
|
|
665
777
|
---
|
|
666
778
|
*Generated by [claudex-setup](https://github.com/DnaFin/claudex-setup) v${require('../package.json').version} on ${new Date().toISOString().split('T')[0]}. Customize this file for your project — a hand-crafted CLAUDE.md will always be better than a generated one.*
|
|
667
779
|
`;
|
|
@@ -948,15 +1060,24 @@ graph TD
|
|
|
948
1060
|
async function setup(options) {
|
|
949
1061
|
const ctx = new ProjectContext(options.dir);
|
|
950
1062
|
const stacks = ctx.detectStacks(STACKS);
|
|
1063
|
+
const silent = options.silent === true;
|
|
1064
|
+
const writtenFiles = [];
|
|
1065
|
+
const preservedFiles = [];
|
|
951
1066
|
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1067
|
+
function log(message = '') {
|
|
1068
|
+
if (!silent) {
|
|
1069
|
+
console.log(message);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
log('');
|
|
1074
|
+
log('\x1b[1m claudex-setup\x1b[0m');
|
|
1075
|
+
log('\x1b[2m ═══════════════════════════════════════\x1b[0m');
|
|
955
1076
|
|
|
956
1077
|
if (stacks.length > 0) {
|
|
957
|
-
|
|
1078
|
+
log(`\x1b[36m Detected: ${stacks.map(s => s.label).join(', ')}\x1b[0m`);
|
|
958
1079
|
}
|
|
959
|
-
|
|
1080
|
+
log('');
|
|
960
1081
|
|
|
961
1082
|
let created = 0;
|
|
962
1083
|
let skipped = 0;
|
|
@@ -994,10 +1115,12 @@ async function setup(options) {
|
|
|
994
1115
|
|
|
995
1116
|
if (!fs.existsSync(fullPath)) {
|
|
996
1117
|
fs.writeFileSync(fullPath, result, 'utf8');
|
|
997
|
-
|
|
1118
|
+
writtenFiles.push(filePath);
|
|
1119
|
+
log(` \x1b[32m✅\x1b[0m Created ${filePath}`);
|
|
998
1120
|
created++;
|
|
999
1121
|
} else {
|
|
1000
|
-
|
|
1122
|
+
preservedFiles.push(filePath);
|
|
1123
|
+
log(` \x1b[2m⏭️ Skipped ${filePath} (already exists — your version is kept)\x1b[0m`);
|
|
1001
1124
|
skipped++;
|
|
1002
1125
|
}
|
|
1003
1126
|
} else if (typeof result === 'object') {
|
|
@@ -1024,9 +1147,11 @@ async function setup(options) {
|
|
|
1024
1147
|
}
|
|
1025
1148
|
if (!fs.existsSync(filePath)) {
|
|
1026
1149
|
fs.writeFileSync(filePath, content, 'utf8');
|
|
1027
|
-
|
|
1150
|
+
writtenFiles.push(path.relative(options.dir, filePath));
|
|
1151
|
+
log(` \x1b[32m✅\x1b[0m Created ${path.relative(options.dir, filePath)}`);
|
|
1028
1152
|
created++;
|
|
1029
1153
|
} else {
|
|
1154
|
+
preservedFiles.push(path.relative(options.dir, filePath));
|
|
1030
1155
|
skipped++;
|
|
1031
1156
|
}
|
|
1032
1157
|
}
|
|
@@ -1040,6 +1165,18 @@ async function setup(options) {
|
|
|
1040
1165
|
const hookFiles = fs.readdirSync(hooksDir).filter(f => f.endsWith('.sh'));
|
|
1041
1166
|
if (hookFiles.length > 0) {
|
|
1042
1167
|
const settings = {
|
|
1168
|
+
permissions: {
|
|
1169
|
+
defaultMode: "acceptEdits",
|
|
1170
|
+
deny: [
|
|
1171
|
+
"Read(./.env*)",
|
|
1172
|
+
"Read(./secrets/**)",
|
|
1173
|
+
"Bash(rm -rf *)",
|
|
1174
|
+
"Bash(git reset --hard *)",
|
|
1175
|
+
"Bash(git checkout -- *)",
|
|
1176
|
+
"Bash(git clean *)",
|
|
1177
|
+
"Bash(git push --force *)"
|
|
1178
|
+
]
|
|
1179
|
+
},
|
|
1043
1180
|
hooks: {
|
|
1044
1181
|
PostToolUse: [{
|
|
1045
1182
|
matcher: "Write|Edit",
|
|
@@ -1063,26 +1200,35 @@ async function setup(options) {
|
|
|
1063
1200
|
}];
|
|
1064
1201
|
}
|
|
1065
1202
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
1066
|
-
|
|
1203
|
+
writtenFiles.push('.claude/settings.json');
|
|
1204
|
+
log(` \x1b[32m✅\x1b[0m Created .claude/settings.json (hooks registered)`);
|
|
1067
1205
|
created++;
|
|
1068
1206
|
}
|
|
1069
1207
|
}
|
|
1070
1208
|
|
|
1071
|
-
|
|
1209
|
+
log('');
|
|
1072
1210
|
if (created === 0 && skipped > 0) {
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1211
|
+
log(' \x1b[32m✅\x1b[0m Your project is already well configured!');
|
|
1212
|
+
log(` \x1b[2m ${skipped} files already exist and were preserved.\x1b[0m`);
|
|
1213
|
+
log(' \x1b[2m We never overwrite your existing config — your setup is kept.\x1b[0m');
|
|
1076
1214
|
} else if (created > 0) {
|
|
1077
|
-
|
|
1215
|
+
log(` \x1b[1m${created} files created.\x1b[0m`);
|
|
1078
1216
|
if (skipped > 0) {
|
|
1079
|
-
|
|
1217
|
+
log(` \x1b[2m${skipped} existing files preserved (not overwritten).\x1b[0m`);
|
|
1080
1218
|
}
|
|
1081
1219
|
}
|
|
1082
1220
|
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1221
|
+
log('');
|
|
1222
|
+
log(' Run \x1b[1mnpx claudex-setup audit\x1b[0m to check your score.');
|
|
1223
|
+
log('');
|
|
1224
|
+
|
|
1225
|
+
return {
|
|
1226
|
+
created,
|
|
1227
|
+
skipped,
|
|
1228
|
+
writtenFiles,
|
|
1229
|
+
preservedFiles,
|
|
1230
|
+
stacks,
|
|
1231
|
+
};
|
|
1086
1232
|
}
|
|
1087
1233
|
|
|
1088
|
-
module.exports = { setup };
|
|
1234
|
+
module.exports = { setup, TEMPLATES };
|
package/src/techniques.js
CHANGED
|
@@ -170,6 +170,10 @@ const TECHNIQUES = {
|
|
|
170
170
|
id: 91701,
|
|
171
171
|
name: '.gitignore blocks node_modules',
|
|
172
172
|
check: (ctx) => {
|
|
173
|
+
const hasNodeSignals = ctx.files.includes('package.json') ||
|
|
174
|
+
ctx.files.includes('tsconfig.json') ||
|
|
175
|
+
ctx.files.some(f => /package-lock\.json|pnpm-lock\.yaml|yarn\.lock|next\.config|vite\.config/i.test(f));
|
|
176
|
+
if (!hasNodeSignals) return null;
|
|
173
177
|
const gitignore = ctx.fileContent('.gitignore') || '';
|
|
174
178
|
return gitignore.includes('node_modules');
|
|
175
179
|
},
|
|
@@ -603,6 +607,10 @@ const TECHNIQUES = {
|
|
|
603
607
|
id: 5002,
|
|
604
608
|
name: 'Node version pinned',
|
|
605
609
|
check: (ctx) => {
|
|
610
|
+
const hasNodeSignals = ctx.files.includes('package.json') ||
|
|
611
|
+
ctx.files.includes('tsconfig.json') ||
|
|
612
|
+
ctx.files.some(f => /package-lock\.json|pnpm-lock\.yaml|yarn\.lock|next\.config|vite\.config/i.test(f));
|
|
613
|
+
if (!hasNodeSignals) return null;
|
|
606
614
|
if (ctx.files.includes('.nvmrc') || ctx.files.includes('.node-version')) return true;
|
|
607
615
|
const pkg = ctx.jsonFile('package.json');
|
|
608
616
|
return pkg && pkg.engines && pkg.engines.node;
|
|
@@ -955,6 +963,8 @@ const STACKS = {
|
|
|
955
963
|
swift: { files: ['Package.swift'], content: {}, label: 'Swift' },
|
|
956
964
|
terraform: { files: ['main.tf', 'terraform'], content: {}, label: 'Terraform' },
|
|
957
965
|
kubernetes: { files: ['k8s', 'kubernetes', 'helm'], content: {}, label: 'Kubernetes' },
|
|
966
|
+
cpp: { files: ['CMakeLists.txt', 'Makefile', '.clang-format'], content: {}, label: 'C++' },
|
|
967
|
+
bazel: { files: ['BUILD', 'WORKSPACE', 'BUILD.bazel', 'WORKSPACE.bazel'], content: {}, label: 'Bazel' },
|
|
958
968
|
};
|
|
959
969
|
|
|
960
970
|
module.exports = { TECHNIQUES, STACKS };
|