claudex-setup 1.6.0 → 1.8.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 +30 -0
- package/LICENSE +21 -0
- package/README.md +120 -15
- package/bin/cli.js +246 -12
- package/package.json +3 -2
- package/src/activity.js +60 -0
- package/src/analyze.js +423 -0
- package/src/audit.js +27 -22
- package/src/benchmark.js +250 -0
- package/src/context.js +3 -2
- package/src/domain-packs.js +160 -0
- package/src/governance.js +348 -0
- package/src/index.js +21 -1
- package/src/interactive.js +2 -2
- package/src/mcp-packs.js +85 -0
- package/src/plans.js +625 -0
- package/src/setup.js +201 -75
- package/src/techniques.js +27 -6
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.8.0 - Starter-safe setup engine with reusable planning primitives.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const fs = require('fs');
|
|
@@ -8,6 +8,7 @@ const path = require('path');
|
|
|
8
8
|
const { TECHNIQUES, STACKS } = require('./techniques');
|
|
9
9
|
const { ProjectContext } = require('./context');
|
|
10
10
|
const { audit } = require('./audit');
|
|
11
|
+
const { buildSettingsForProfile } = require('./governance');
|
|
11
12
|
|
|
12
13
|
// ============================================================
|
|
13
14
|
// Helper: detect project scripts from package.json
|
|
@@ -248,9 +249,9 @@ function detectDependencies(ctx) {
|
|
|
248
249
|
// Helper: detect main directories
|
|
249
250
|
// ============================================================
|
|
250
251
|
function detectMainDirs(ctx) {
|
|
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'];
|
|
252
|
+
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'];
|
|
252
253
|
// Also check inside src/ for nested structure (common in Next.js, React)
|
|
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'];
|
|
254
|
+
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'];
|
|
254
255
|
const found = [];
|
|
255
256
|
const seenNames = new Set();
|
|
256
257
|
|
|
@@ -267,6 +268,55 @@ function detectMainDirs(ctx) {
|
|
|
267
268
|
return found;
|
|
268
269
|
}
|
|
269
270
|
|
|
271
|
+
function escapeRegex(value) {
|
|
272
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function extractTomlSection(content, sectionName) {
|
|
276
|
+
const pattern = new RegExp(`\\[${escapeRegex(sectionName)}\\]([\\s\\S]*?)(?:\\n\\s*\\[|$)`);
|
|
277
|
+
const match = content.match(pattern);
|
|
278
|
+
return match ? match[1] : null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function extractTomlValue(sectionContent, key) {
|
|
282
|
+
if (!sectionContent) return null;
|
|
283
|
+
const pattern = new RegExp(`^\\s*${escapeRegex(key)}\\s*=\\s*["']([^"']+)["']`, 'm');
|
|
284
|
+
const match = sectionContent.match(pattern);
|
|
285
|
+
return match ? match[1].trim() : null;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function detectProjectMetadata(ctx) {
|
|
289
|
+
const pkg = ctx.jsonFile('package.json');
|
|
290
|
+
if (pkg && (pkg.name || pkg.description)) {
|
|
291
|
+
return {
|
|
292
|
+
name: pkg.name || path.basename(ctx.dir),
|
|
293
|
+
description: pkg.description || '',
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const pyproject = ctx.fileContent('pyproject.toml') || '';
|
|
298
|
+
if (pyproject) {
|
|
299
|
+
const projectSection = extractTomlSection(pyproject, 'project');
|
|
300
|
+
const poetrySection = extractTomlSection(pyproject, 'tool.poetry');
|
|
301
|
+
const name = extractTomlValue(projectSection, 'name') ||
|
|
302
|
+
extractTomlValue(poetrySection, 'name');
|
|
303
|
+
const description = extractTomlValue(projectSection, 'description') ||
|
|
304
|
+
extractTomlValue(poetrySection, 'description');
|
|
305
|
+
|
|
306
|
+
if (name || description) {
|
|
307
|
+
return {
|
|
308
|
+
name: name || path.basename(ctx.dir),
|
|
309
|
+
description: description || '',
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
name: path.basename(ctx.dir),
|
|
316
|
+
description: '',
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
270
320
|
// ============================================================
|
|
271
321
|
// Helper: generate Mermaid diagram from directory structure
|
|
272
322
|
// ============================================================
|
|
@@ -295,6 +345,11 @@ function generateMermaid(dirs, stacks) {
|
|
|
295
345
|
const hasSrcComponents = dirNames.includes('src/components') || dirNames.includes('components');
|
|
296
346
|
const hasSrcHooks = dirNames.includes('src/hooks') || dirNames.includes('hooks');
|
|
297
347
|
const hasSrcLib = dirNames.includes('src/lib') || dirNames.includes('lib');
|
|
348
|
+
const hasSrcNode = dirNames.includes('src');
|
|
349
|
+
const hasAgents = dirNames.includes('src/agents') || dirNames.includes('agents');
|
|
350
|
+
const hasChains = dirNames.includes('src/chains') || dirNames.includes('chains');
|
|
351
|
+
const hasWorkers = dirNames.includes('src/workers') || dirNames.includes('workers') || dirNames.includes('jobs');
|
|
352
|
+
const hasPipelines = dirNames.includes('dags') || dirNames.includes('macros');
|
|
298
353
|
|
|
299
354
|
// Smart entry point based on framework
|
|
300
355
|
const isNextJs = stackKeys.includes('nextjs');
|
|
@@ -312,6 +367,7 @@ function generateMermaid(dirs, stacks) {
|
|
|
312
367
|
}
|
|
313
368
|
|
|
314
369
|
const root = ids['Next.js'] || ids['Django'] || ids['FastAPI'] || ids['Entry Point'];
|
|
370
|
+
const pickNodeId = (...labels) => labels.map(label => ids[label]).find(Boolean) || root;
|
|
315
371
|
|
|
316
372
|
// Detect layers
|
|
317
373
|
if (hasAppRouter || hasPages) {
|
|
@@ -340,9 +396,9 @@ function generateMermaid(dirs, stacks) {
|
|
|
340
396
|
|
|
341
397
|
if (hasSrcLib) {
|
|
342
398
|
nodes.push(addNode('lib/', 'default'));
|
|
343
|
-
const parent =
|
|
399
|
+
const parent = pickNodeId('API Routes', 'Hooks', 'Components');
|
|
344
400
|
edges.push(` ${parent} --> ${ids['lib/']}`);
|
|
345
|
-
} else if (
|
|
401
|
+
} else if (hasSrcNode && !hasAppRouter && !hasPages) {
|
|
346
402
|
nodes.push(addNode('src/', 'default'));
|
|
347
403
|
edges.push(` ${root} --> ${ids['src/']}`);
|
|
348
404
|
}
|
|
@@ -350,19 +406,19 @@ function generateMermaid(dirs, stacks) {
|
|
|
350
406
|
if (dirNames.includes('api') || dirNames.includes('routes') || dirNames.includes('controllers')) {
|
|
351
407
|
const label = dirNames.includes('api') ? 'API Layer' : 'Routes';
|
|
352
408
|
nodes.push(addNode(label, 'default'));
|
|
353
|
-
const parent =
|
|
409
|
+
const parent = pickNodeId('src/', 'App Router', 'Pages');
|
|
354
410
|
edges.push(` ${parent} --> ${ids[label]}`);
|
|
355
411
|
}
|
|
356
412
|
|
|
357
413
|
if (dirNames.includes('services')) {
|
|
358
414
|
nodes.push(addNode('Services', 'default'));
|
|
359
|
-
const parent =
|
|
415
|
+
const parent = pickNodeId('API Layer', 'Routes', 'src/', 'App Router', 'Pages');
|
|
360
416
|
edges.push(` ${parent} --> ${ids['Services']}`);
|
|
361
417
|
}
|
|
362
418
|
|
|
363
419
|
if (dirNames.includes('models') || dirNames.includes('prisma') || dirNames.includes('db')) {
|
|
364
420
|
nodes.push(addNode('Data Layer', 'default'));
|
|
365
|
-
const parent =
|
|
421
|
+
const parent = pickNodeId('Services', 'API Layer', 'Routes', 'src/', 'App Router', 'Pages');
|
|
366
422
|
edges.push(` ${parent} --> ${ids['Data Layer']}`);
|
|
367
423
|
nodes.push(addNode('Database', 'db'));
|
|
368
424
|
edges.push(` ${ids['Data Layer']} --> ${ids['Database']}`);
|
|
@@ -370,19 +426,43 @@ function generateMermaid(dirs, stacks) {
|
|
|
370
426
|
|
|
371
427
|
if (dirNames.includes('utils') || dirNames.includes('helpers')) {
|
|
372
428
|
nodes.push(addNode('Utils', 'default'));
|
|
373
|
-
const parent =
|
|
429
|
+
const parent = pickNodeId('src/', 'Services', 'lib/', 'Components');
|
|
374
430
|
edges.push(` ${parent} --> ${ids['Utils']}`);
|
|
375
431
|
}
|
|
376
432
|
|
|
377
433
|
if (dirNames.includes('middleware')) {
|
|
378
434
|
nodes.push(addNode('Middleware', 'default'));
|
|
379
|
-
const parent =
|
|
435
|
+
const parent = pickNodeId('API Layer', 'Routes', 'App Router', 'Pages');
|
|
380
436
|
edges.push(` ${parent} --> ${ids['Middleware']}`);
|
|
381
437
|
}
|
|
382
438
|
|
|
439
|
+
if (hasChains) {
|
|
440
|
+
nodes.push(addNode('Chains', 'default'));
|
|
441
|
+
const parent = pickNodeId('Services', 'src/', 'lib/', 'API Layer');
|
|
442
|
+
edges.push(` ${parent} --> ${ids['Chains']}`);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (hasAgents) {
|
|
446
|
+
nodes.push(addNode('Agents', 'default'));
|
|
447
|
+
const parent = pickNodeId('Chains', 'Services', 'src/', 'lib/');
|
|
448
|
+
edges.push(` ${parent} --> ${ids['Agents']}`);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (hasWorkers) {
|
|
452
|
+
nodes.push(addNode('Workers', 'default'));
|
|
453
|
+
const parent = pickNodeId('Services', 'API Layer', 'src/');
|
|
454
|
+
edges.push(` ${parent} --> ${ids['Workers']}`);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (hasPipelines) {
|
|
458
|
+
nodes.push(addNode('Pipelines', 'default'));
|
|
459
|
+
const parent = pickNodeId('Services', 'Data Layer', 'src/');
|
|
460
|
+
edges.push(` ${parent} --> ${ids['Pipelines']}`);
|
|
461
|
+
}
|
|
462
|
+
|
|
383
463
|
if (dirNames.includes('tests') || dirNames.includes('test') || dirNames.includes('__tests__') || dirNames.includes('spec')) {
|
|
384
464
|
nodes.push(addNode('Tests', 'round'));
|
|
385
|
-
const parent =
|
|
465
|
+
const parent = pickNodeId('src/', 'App Router', 'Pages', 'Services', 'Components');
|
|
386
466
|
edges.push(` ${ids['Tests']} -.-> ${parent}`);
|
|
387
467
|
}
|
|
388
468
|
|
|
@@ -416,13 +496,9 @@ function getFrameworkInstructions(stacks) {
|
|
|
416
496
|
- Use next/image for images, next/link for navigation
|
|
417
497
|
- API routes go in app/api/ (App Router) or pages/api/ (Pages Router)
|
|
418
498
|
- Use loading.tsx, error.tsx, and not-found.tsx for route-level UX
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
-
|
|
422
|
-
- Use Server Actions for mutations. Validate with Zod, call revalidatePath after writes
|
|
423
|
-
- Route handlers in app/api/ export named functions: GET, POST, PUT, DELETE
|
|
424
|
-
- Use loading.tsx, error.tsx, not-found.tsx for route-level UI states
|
|
425
|
-
- Middleware in middleware.ts for auth checks, redirects, headers`);
|
|
499
|
+
- If app/ exists, use Server Actions for mutations, validate with Zod, and call revalidatePath after writes
|
|
500
|
+
- Route handlers in app/api/ should export named functions: GET, POST, PUT, DELETE
|
|
501
|
+
- Middleware in middleware.ts should handle auth checks, redirects, and headers`);
|
|
426
502
|
} else if (stackKeys.includes('react')) {
|
|
427
503
|
sections.push(`### React
|
|
428
504
|
- Use functional components with hooks exclusively
|
|
@@ -655,10 +731,10 @@ ${depGuidelines.join('\n')}
|
|
|
655
731
|
}
|
|
656
732
|
verificationSteps.push(`${verificationSteps.length + 1}. Changes match the requested scope (no gold-plating)`);
|
|
657
733
|
|
|
658
|
-
// --- Read package.json
|
|
659
|
-
const
|
|
660
|
-
const projectName =
|
|
661
|
-
const projectDesc =
|
|
734
|
+
// --- Read project metadata from package.json or pyproject.toml ---
|
|
735
|
+
const projectMeta = detectProjectMetadata(ctx);
|
|
736
|
+
const projectName = projectMeta.name;
|
|
737
|
+
const projectDesc = projectMeta.description ? ` — ${projectMeta.description}` : '';
|
|
662
738
|
|
|
663
739
|
// --- Assemble the final CLAUDE.md ---
|
|
664
740
|
return `# ${projectName}${projectDesc}
|
|
@@ -674,11 +750,10 @@ ${stackSection}${tsSection}${depSection}
|
|
|
674
750
|
${buildSection}
|
|
675
751
|
\`\`\`
|
|
676
752
|
|
|
677
|
-
##
|
|
678
|
-
-
|
|
679
|
-
-
|
|
680
|
-
- Keep
|
|
681
|
-
- Use descriptive variable names; avoid abbreviations
|
|
753
|
+
## Working Notes
|
|
754
|
+
- You are a careful engineer working inside this repository. Preserve its existing architecture and naming patterns unless the task requires a change
|
|
755
|
+
- Prefer extending existing modules over creating parallel abstractions
|
|
756
|
+
- Keep changes scoped to the requested task and verify them before marking work complete
|
|
682
757
|
|
|
683
758
|
<constraints>
|
|
684
759
|
- Never commit secrets, API keys, or .env files
|
|
@@ -700,12 +775,6 @@ ${verificationSteps.join('\n')}
|
|
|
700
775
|
- If a session gets too long, start fresh with /clear
|
|
701
776
|
- Use subagents for research tasks to keep main context clean
|
|
702
777
|
|
|
703
|
-
## Workflow
|
|
704
|
-
- Verify changes with tests before committing
|
|
705
|
-
- Use descriptive commit messages (why, not what)
|
|
706
|
-
- Create focused PRs — one concern per PR
|
|
707
|
-
- Document non-obvious decisions in code comments
|
|
708
|
-
|
|
709
778
|
---
|
|
710
779
|
*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.*
|
|
711
780
|
`;
|
|
@@ -758,6 +827,19 @@ mkdir -p "$LOG_DIR"
|
|
|
758
827
|
TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S")
|
|
759
828
|
echo "[$TIMESTAMP] $TOOL_NAME: $FILE_PATH" >> "$LOG_FILE"
|
|
760
829
|
|
|
830
|
+
exit 0
|
|
831
|
+
`,
|
|
832
|
+
'session-start.sh': `#!/bin/bash
|
|
833
|
+
# SessionStart hook - prepares logs and records session entry
|
|
834
|
+
|
|
835
|
+
LOG_DIR=".claude/logs"
|
|
836
|
+
LOG_FILE="$LOG_DIR/sessions.log"
|
|
837
|
+
|
|
838
|
+
mkdir -p "$LOG_DIR"
|
|
839
|
+
|
|
840
|
+
TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S")
|
|
841
|
+
echo "[$TIMESTAMP] session started" >> "$LOG_FILE"
|
|
842
|
+
|
|
761
843
|
exit 0
|
|
762
844
|
`,
|
|
763
845
|
}),
|
|
@@ -809,6 +891,15 @@ exit 0
|
|
|
809
891
|
1. Run \`git diff\` to see all changes
|
|
810
892
|
2. Check for: bugs, security issues, missing tests, code style
|
|
811
893
|
3. Provide actionable feedback
|
|
894
|
+
`;
|
|
895
|
+
|
|
896
|
+
cmds['security-review.md'] = `Run a focused security review using Claude Code's built-in security workflow.
|
|
897
|
+
|
|
898
|
+
## Steps:
|
|
899
|
+
1. Review auth, permissions, secrets handling, and data access paths
|
|
900
|
+
2. Run \`/security-review\` for OWASP-focused analysis
|
|
901
|
+
3. Check for unsafe shell commands, token leakage, and risky file access
|
|
902
|
+
4. Report findings ordered by severity with concrete fixes
|
|
812
903
|
`;
|
|
813
904
|
|
|
814
905
|
// Deploy - stack-specific
|
|
@@ -912,6 +1003,17 @@ Fix the GitHub issue: $ARGUMENTS
|
|
|
912
1003
|
3. Implement the fix
|
|
913
1004
|
4. Write tests
|
|
914
1005
|
5. Create a descriptive commit
|
|
1006
|
+
`,
|
|
1007
|
+
'release-check/SKILL.md': `---
|
|
1008
|
+
name: release-check
|
|
1009
|
+
description: Prepare a release candidate and verify publish readiness
|
|
1010
|
+
---
|
|
1011
|
+
Prepare a release candidate for: $ARGUMENTS
|
|
1012
|
+
|
|
1013
|
+
1. Read CHANGELOG.md and package.json version
|
|
1014
|
+
2. Run the test suite and packaging checks
|
|
1015
|
+
3. Verify docs, tags, and release notes are aligned
|
|
1016
|
+
4. Flag anything that would make the release unsafe or misleading
|
|
915
1017
|
`,
|
|
916
1018
|
}),
|
|
917
1019
|
|
|
@@ -959,6 +1061,12 @@ Fix the GitHub issue: $ARGUMENTS
|
|
|
959
1061
|
- Never skip or disable tests without a tracking issue
|
|
960
1062
|
- Mock external dependencies, not internal logic
|
|
961
1063
|
- Include both happy path and edge case tests
|
|
1064
|
+
`;
|
|
1065
|
+
rules['repository.md'] = `When changing release, packaging, or workflow files:
|
|
1066
|
+
- Keep package.json, CHANGELOG.md, README.md, and docs in sync
|
|
1067
|
+
- Prefer tagged release references over floating branch references in public docs
|
|
1068
|
+
- Preserve backward compatibility in CLI flags where practical
|
|
1069
|
+
- Any automation that writes files must document rollback expectations
|
|
962
1070
|
`;
|
|
963
1071
|
return rules;
|
|
964
1072
|
},
|
|
@@ -969,12 +1077,26 @@ name: security-reviewer
|
|
|
969
1077
|
description: Reviews code for security vulnerabilities
|
|
970
1078
|
tools: [Read, Grep, Glob]
|
|
971
1079
|
model: sonnet
|
|
1080
|
+
maxTurns: 50
|
|
972
1081
|
---
|
|
973
1082
|
Review code for security issues:
|
|
974
1083
|
- Injection vulnerabilities (SQL, XSS, command injection)
|
|
975
1084
|
- Authentication and authorization flaws
|
|
976
1085
|
- Secrets or credentials in code
|
|
977
1086
|
- Insecure data handling
|
|
1087
|
+
`,
|
|
1088
|
+
'release-manager.md': `---
|
|
1089
|
+
name: release-manager
|
|
1090
|
+
description: Checks release readiness and packaging consistency
|
|
1091
|
+
tools: [Read, Grep, Glob]
|
|
1092
|
+
model: sonnet
|
|
1093
|
+
maxTurns: 50
|
|
1094
|
+
---
|
|
1095
|
+
Review release readiness:
|
|
1096
|
+
- version alignment across package.json, changelog, and docs
|
|
1097
|
+
- publish safety and packaging scope
|
|
1098
|
+
- missing rollback or migration notes
|
|
1099
|
+
- documentation drift that would confuse adopters
|
|
978
1100
|
`,
|
|
979
1101
|
}),
|
|
980
1102
|
|
|
@@ -992,15 +1114,24 @@ graph TD
|
|
|
992
1114
|
async function setup(options) {
|
|
993
1115
|
const ctx = new ProjectContext(options.dir);
|
|
994
1116
|
const stacks = ctx.detectStacks(STACKS);
|
|
1117
|
+
const silent = options.silent === true;
|
|
1118
|
+
const writtenFiles = [];
|
|
1119
|
+
const preservedFiles = [];
|
|
1120
|
+
|
|
1121
|
+
function log(message = '') {
|
|
1122
|
+
if (!silent) {
|
|
1123
|
+
console.log(message);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
995
1126
|
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1127
|
+
log('');
|
|
1128
|
+
log('\x1b[1m claudex-setup\x1b[0m');
|
|
1129
|
+
log('\x1b[2m ═══════════════════════════════════════\x1b[0m');
|
|
999
1130
|
|
|
1000
1131
|
if (stacks.length > 0) {
|
|
1001
|
-
|
|
1132
|
+
log(`\x1b[36m Detected: ${stacks.map(s => s.label).join(', ')}\x1b[0m`);
|
|
1002
1133
|
}
|
|
1003
|
-
|
|
1134
|
+
log('');
|
|
1004
1135
|
|
|
1005
1136
|
let created = 0;
|
|
1006
1137
|
let skipped = 0;
|
|
@@ -1038,10 +1169,12 @@ async function setup(options) {
|
|
|
1038
1169
|
|
|
1039
1170
|
if (!fs.existsSync(fullPath)) {
|
|
1040
1171
|
fs.writeFileSync(fullPath, result, 'utf8');
|
|
1041
|
-
|
|
1172
|
+
writtenFiles.push(filePath);
|
|
1173
|
+
log(` \x1b[32m✅\x1b[0m Created ${filePath}`);
|
|
1042
1174
|
created++;
|
|
1043
1175
|
} else {
|
|
1044
|
-
|
|
1176
|
+
preservedFiles.push(filePath);
|
|
1177
|
+
log(` \x1b[2m⏭️ Skipped ${filePath} (already exists — your version is kept)\x1b[0m`);
|
|
1045
1178
|
skipped++;
|
|
1046
1179
|
}
|
|
1047
1180
|
} else if (typeof result === 'object') {
|
|
@@ -1068,9 +1201,11 @@ async function setup(options) {
|
|
|
1068
1201
|
}
|
|
1069
1202
|
if (!fs.existsSync(filePath)) {
|
|
1070
1203
|
fs.writeFileSync(filePath, content, 'utf8');
|
|
1071
|
-
|
|
1204
|
+
writtenFiles.push(path.relative(options.dir, filePath));
|
|
1205
|
+
log(` \x1b[32m✅\x1b[0m Created ${path.relative(options.dir, filePath)}`);
|
|
1072
1206
|
created++;
|
|
1073
1207
|
} else {
|
|
1208
|
+
preservedFiles.push(path.relative(options.dir, filePath));
|
|
1074
1209
|
skipped++;
|
|
1075
1210
|
}
|
|
1076
1211
|
}
|
|
@@ -1083,50 +1218,41 @@ async function setup(options) {
|
|
|
1083
1218
|
if (fs.existsSync(hooksDir) && !fs.existsSync(settingsPath)) {
|
|
1084
1219
|
const hookFiles = fs.readdirSync(hooksDir).filter(f => f.endsWith('.sh'));
|
|
1085
1220
|
if (hookFiles.length > 0) {
|
|
1086
|
-
const settings = {
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
type: "command",
|
|
1092
|
-
command: `bash .claude/hooks/${f}`,
|
|
1093
|
-
timeout: 10
|
|
1094
|
-
}))
|
|
1095
|
-
}]
|
|
1096
|
-
}
|
|
1097
|
-
};
|
|
1098
|
-
// Add protect-secrets as PreToolUse if it exists
|
|
1099
|
-
if (hookFiles.includes('protect-secrets.sh')) {
|
|
1100
|
-
settings.hooks.PreToolUse = [{
|
|
1101
|
-
matcher: "Read|Write|Edit",
|
|
1102
|
-
hooks: [{
|
|
1103
|
-
type: "command",
|
|
1104
|
-
command: "bash .claude/hooks/protect-secrets.sh",
|
|
1105
|
-
timeout: 5
|
|
1106
|
-
}]
|
|
1107
|
-
}];
|
|
1108
|
-
}
|
|
1221
|
+
const settings = buildSettingsForProfile({
|
|
1222
|
+
profileKey: options.profile || 'safe-write',
|
|
1223
|
+
hookFiles,
|
|
1224
|
+
mcpPackKeys: options.mcpPacks || [],
|
|
1225
|
+
});
|
|
1109
1226
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
1110
|
-
|
|
1227
|
+
writtenFiles.push('.claude/settings.json');
|
|
1228
|
+
log(` \x1b[32m✅\x1b[0m Created .claude/settings.json (hooks registered)`);
|
|
1111
1229
|
created++;
|
|
1112
1230
|
}
|
|
1113
1231
|
}
|
|
1114
1232
|
|
|
1115
|
-
|
|
1233
|
+
log('');
|
|
1116
1234
|
if (created === 0 && skipped > 0) {
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1235
|
+
log(' \x1b[32m✅\x1b[0m Your project is already well configured!');
|
|
1236
|
+
log(` \x1b[2m ${skipped} files already exist and were preserved.\x1b[0m`);
|
|
1237
|
+
log(' \x1b[2m We never overwrite your existing config — your setup is kept.\x1b[0m');
|
|
1120
1238
|
} else if (created > 0) {
|
|
1121
|
-
|
|
1239
|
+
log(` \x1b[1m${created} files created.\x1b[0m`);
|
|
1122
1240
|
if (skipped > 0) {
|
|
1123
|
-
|
|
1241
|
+
log(` \x1b[2m${skipped} existing files preserved (not overwritten).\x1b[0m`);
|
|
1124
1242
|
}
|
|
1125
1243
|
}
|
|
1126
1244
|
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1245
|
+
log('');
|
|
1246
|
+
log(' Run \x1b[1mnpx claudex-setup audit\x1b[0m to check your score.');
|
|
1247
|
+
log('');
|
|
1248
|
+
|
|
1249
|
+
return {
|
|
1250
|
+
created,
|
|
1251
|
+
skipped,
|
|
1252
|
+
writtenFiles,
|
|
1253
|
+
preservedFiles,
|
|
1254
|
+
stacks,
|
|
1255
|
+
};
|
|
1130
1256
|
}
|
|
1131
1257
|
|
|
1132
|
-
module.exports = { setup };
|
|
1258
|
+
module.exports = { setup, TEMPLATES };
|
package/src/techniques.js
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
* Each technique includes: what to check, how to fix, impact level.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
function hasFrontendSignals(ctx) {
|
|
8
|
+
const pkg = ctx.fileContent('package.json') || '';
|
|
9
|
+
return /react|vue|angular|next|svelte|tailwind|vite|astro/i.test(pkg) ||
|
|
10
|
+
ctx.files.some(f => /tailwind\.config|vite\.config|next\.config|svelte\.config|nuxt\.config|pages\/|components\/|app\//i.test(f));
|
|
11
|
+
}
|
|
12
|
+
|
|
7
13
|
const TECHNIQUES = {
|
|
8
14
|
// ============================================================
|
|
9
15
|
// === MEMORY & CONTEXT (category: 'memory') ==================
|
|
@@ -142,8 +148,13 @@ const TECHNIQUES = {
|
|
|
142
148
|
name: '.claude/ tracked in git',
|
|
143
149
|
check: (ctx) => {
|
|
144
150
|
if (!ctx.fileContent('.gitignore')) return true; // no gitignore = ok
|
|
145
|
-
const
|
|
146
|
-
|
|
151
|
+
const lines = ctx.fileContent('.gitignore')
|
|
152
|
+
.split(/\r?\n/)
|
|
153
|
+
.map(line => line.trim())
|
|
154
|
+
.filter(line => line && !line.startsWith('#'));
|
|
155
|
+
const ignoresClaudeDir = lines.some(line => /^(\/|\*\*\/)?\.claude\/?$/.test(line));
|
|
156
|
+
const unignoresClaudeDir = lines.some(line => /^!(\/)?\.claude(\/|\*\*)?$/.test(line));
|
|
157
|
+
return !ignoresClaudeDir || unignoresClaudeDir;
|
|
147
158
|
},
|
|
148
159
|
impact: 'high',
|
|
149
160
|
rating: 4,
|
|
@@ -170,6 +181,10 @@ const TECHNIQUES = {
|
|
|
170
181
|
id: 91701,
|
|
171
182
|
name: '.gitignore blocks node_modules',
|
|
172
183
|
check: (ctx) => {
|
|
184
|
+
const hasNodeSignals = ctx.files.includes('package.json') ||
|
|
185
|
+
ctx.files.includes('tsconfig.json') ||
|
|
186
|
+
ctx.files.some(f => /package-lock\.json|pnpm-lock\.yaml|yarn\.lock|next\.config|vite\.config/i.test(f));
|
|
187
|
+
if (!hasNodeSignals) return null;
|
|
173
188
|
const gitignore = ctx.fileContent('.gitignore') || '';
|
|
174
189
|
return gitignore.includes('node_modules');
|
|
175
190
|
},
|
|
@@ -314,7 +329,7 @@ const TECHNIQUES = {
|
|
|
314
329
|
name: 'Permission configuration',
|
|
315
330
|
check: (ctx) => {
|
|
316
331
|
const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
|
|
317
|
-
return settings && settings.permissions;
|
|
332
|
+
return !!(settings && settings.permissions);
|
|
318
333
|
},
|
|
319
334
|
impact: 'medium',
|
|
320
335
|
rating: 4,
|
|
@@ -467,6 +482,7 @@ const TECHNIQUES = {
|
|
|
467
482
|
id: 1025,
|
|
468
483
|
name: 'Frontend design skill for anti-AI-slop',
|
|
469
484
|
check: (ctx) => {
|
|
485
|
+
if (!hasFrontendSignals(ctx)) return null;
|
|
470
486
|
const md = ctx.fileContent('CLAUDE.md') || '';
|
|
471
487
|
return md.includes('frontend_aesthetics') || md.includes('anti-AI-slop') || md.includes('frontend-design');
|
|
472
488
|
},
|
|
@@ -481,6 +497,7 @@ const TECHNIQUES = {
|
|
|
481
497
|
id: 102501,
|
|
482
498
|
name: 'Tailwind CSS configured',
|
|
483
499
|
check: (ctx) => {
|
|
500
|
+
if (!hasFrontendSignals(ctx)) return null;
|
|
484
501
|
const pkg = ctx.fileContent('package.json') || '';
|
|
485
502
|
return pkg.includes('tailwind') ||
|
|
486
503
|
ctx.files.some(f => /tailwind\.config/.test(f));
|
|
@@ -603,9 +620,13 @@ const TECHNIQUES = {
|
|
|
603
620
|
id: 5002,
|
|
604
621
|
name: 'Node version pinned',
|
|
605
622
|
check: (ctx) => {
|
|
623
|
+
const hasNodeSignals = ctx.files.includes('package.json') ||
|
|
624
|
+
ctx.files.includes('tsconfig.json') ||
|
|
625
|
+
ctx.files.some(f => /package-lock\.json|pnpm-lock\.yaml|yarn\.lock|next\.config|vite\.config/i.test(f));
|
|
626
|
+
if (!hasNodeSignals) return null;
|
|
606
627
|
if (ctx.files.includes('.nvmrc') || ctx.files.includes('.node-version')) return true;
|
|
607
628
|
const pkg = ctx.jsonFile('package.json');
|
|
608
|
-
return pkg && pkg.engines && pkg.engines.node;
|
|
629
|
+
return !!(pkg && pkg.engines && pkg.engines.node);
|
|
609
630
|
},
|
|
610
631
|
impact: 'low',
|
|
611
632
|
rating: 3,
|
|
@@ -655,7 +676,7 @@ const TECHNIQUES = {
|
|
|
655
676
|
name: 'MCP servers configured',
|
|
656
677
|
check: (ctx) => {
|
|
657
678
|
const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
|
|
658
|
-
return settings && settings.mcpServers && Object.keys(settings.mcpServers).length > 0;
|
|
679
|
+
return !!(settings && settings.mcpServers && Object.keys(settings.mcpServers).length > 0);
|
|
659
680
|
},
|
|
660
681
|
impact: 'medium',
|
|
661
682
|
rating: 3,
|
|
@@ -669,7 +690,7 @@ const TECHNIQUES = {
|
|
|
669
690
|
name: '2+ MCP servers for rich tooling',
|
|
670
691
|
check: (ctx) => {
|
|
671
692
|
const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
|
|
672
|
-
return settings && settings.mcpServers && Object.keys(settings.mcpServers).length >= 2;
|
|
693
|
+
return !!(settings && settings.mcpServers && Object.keys(settings.mcpServers).length >= 2);
|
|
673
694
|
},
|
|
674
695
|
impact: 'medium',
|
|
675
696
|
rating: 4,
|