claudex-setup 1.0.1 → 1.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudex-setup",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
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": {
package/src/audit.js CHANGED
@@ -61,17 +61,20 @@ async function audit(options) {
61
61
  });
62
62
  }
63
63
 
64
- const passed = results.filter(r => r.passed);
65
- const failed = results.filter(r => !r.passed);
64
+ // null = not applicable (skip), true = pass, false = fail
65
+ const applicable = results.filter(r => r.passed !== null);
66
+ const skipped = results.filter(r => r.passed === null);
67
+ const passed = applicable.filter(r => r.passed);
68
+ const failed = applicable.filter(r => !r.passed);
66
69
  const critical = failed.filter(r => r.impact === 'critical');
67
70
  const high = failed.filter(r => r.impact === 'high');
68
71
  const medium = failed.filter(r => r.impact === 'medium');
69
72
 
70
- // Calculate score
73
+ // Calculate score only from applicable checks
71
74
  const weights = { critical: 15, high: 10, medium: 5 };
72
- const maxScore = results.reduce((sum, r) => sum + (weights[r.impact] || 5), 0);
75
+ const maxScore = applicable.reduce((sum, r) => sum + (weights[r.impact] || 5), 0);
73
76
  const earnedScore = passed.reduce((sum, r) => sum + (weights[r.impact] || 5), 0);
74
- const score = Math.round((earnedScore / maxScore) * 100);
77
+ const score = maxScore > 0 ? Math.round((earnedScore / maxScore) * 100) : 0;
75
78
 
76
79
  // Silent mode: skip all output, just return result
77
80
  if (silent) {
@@ -153,7 +156,7 @@ async function audit(options) {
153
156
 
154
157
  // Summary
155
158
  console.log(colorize(' ─────────────────────────────────────', 'dim'));
156
- console.log(` ${colorize(`${passed.length}/${results.length}`, 'bold')} checks passing`);
159
+ console.log(` ${colorize(`${passed.length}/${applicable.length}`, 'bold')} checks passing${skipped.length > 0 ? colorize(` (${skipped.length} not applicable)`, 'dim') : ''}`);
157
160
 
158
161
  if (failed.length > 0) {
159
162
  console.log(` Run ${colorize('npx claudex-setup setup', 'bold')} to fix automatically`);
package/src/setup.js CHANGED
@@ -25,6 +25,92 @@ function detectScripts(ctx) {
25
25
  return found;
26
26
  }
27
27
 
28
+ // ============================================================
29
+ // Helper: detect key dependencies and generate guidelines
30
+ // ============================================================
31
+ function detectDependencies(ctx) {
32
+ const pkg = ctx.jsonFile('package.json');
33
+ if (!pkg) return [];
34
+ const allDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
35
+ const guidelines = [];
36
+
37
+ // Data fetching
38
+ if (allDeps['@tanstack/react-query']) {
39
+ guidelines.push('- Use React Query (TanStack Query) for all server data fetching — never raw useEffect + fetch');
40
+ guidelines.push('- Define query keys as constants. Invalidate related queries after mutations');
41
+ }
42
+ if (allDeps['swr']) {
43
+ guidelines.push('- Use SWR for data fetching with automatic revalidation');
44
+ }
45
+
46
+ // Validation
47
+ if (allDeps['zod']) {
48
+ guidelines.push('- Use Zod for all input validation and type inference (z.infer<typeof schema>)');
49
+ guidelines.push('- Define schemas in a shared location. Use .parse() at API boundaries');
50
+ }
51
+
52
+ // ORM / Database
53
+ if (allDeps['prisma'] || allDeps['@prisma/client']) {
54
+ guidelines.push('- Use Prisma for all database operations. Run `npx prisma generate` after schema changes');
55
+ guidelines.push('- Never write raw SQL unless Prisma cannot express the query');
56
+ }
57
+ if (allDeps['drizzle-orm']) {
58
+ guidelines.push('- Use Drizzle ORM for database operations. Schema-first approach');
59
+ }
60
+ if (allDeps['mongoose']) {
61
+ guidelines.push('- Use Mongoose for MongoDB operations. Define schemas with validation');
62
+ }
63
+
64
+ // Auth
65
+ if (allDeps['next-auth'] || allDeps['@auth/core']) {
66
+ guidelines.push('- Use NextAuth.js for authentication. Access session via auth() in Server Components');
67
+ }
68
+ if (allDeps['clerk'] || allDeps['@clerk/nextjs']) {
69
+ guidelines.push('- Use Clerk for authentication. Protect routes with middleware');
70
+ }
71
+
72
+ // State management
73
+ if (allDeps['zustand']) {
74
+ guidelines.push('- Use Zustand for client state. Keep stores small and focused');
75
+ }
76
+ if (allDeps['@reduxjs/toolkit']) {
77
+ guidelines.push('- Use Redux Toolkit for state management. Use createSlice and RTK Query');
78
+ }
79
+
80
+ // Styling
81
+ if (allDeps['tailwindcss']) {
82
+ guidelines.push('- Use Tailwind CSS for all styling. Avoid inline styles and CSS modules');
83
+ }
84
+ if (allDeps['styled-components'] || allDeps['@emotion/react']) {
85
+ guidelines.push('- Use CSS-in-JS for component styling. Colocate styles with components');
86
+ }
87
+
88
+ // Testing
89
+ if (allDeps['vitest']) {
90
+ guidelines.push('- Use Vitest for testing. Colocate test files with source (*.test.ts)');
91
+ }
92
+ if (allDeps['jest']) {
93
+ guidelines.push('- Use Jest for testing. Follow existing test patterns in the codebase');
94
+ }
95
+ if (allDeps['playwright'] || allDeps['@playwright/test']) {
96
+ guidelines.push('- Use Playwright for E2E tests. Keep tests in tests/ or e2e/');
97
+ }
98
+
99
+ // Python
100
+ const reqTxt = ctx.fileContent('requirements.txt') || '';
101
+ if (reqTxt.includes('sqlalchemy')) {
102
+ guidelines.push('- Use SQLAlchemy for all database operations');
103
+ }
104
+ if (reqTxt.includes('pydantic')) {
105
+ guidelines.push('- Use Pydantic for data validation and serialization');
106
+ }
107
+ if (reqTxt.includes('pytest')) {
108
+ guidelines.push('- Use pytest for testing. Run with `python -m pytest`');
109
+ }
110
+
111
+ return guidelines;
112
+ }
113
+
28
114
  // ============================================================
29
115
  // Helper: detect main directories
30
116
  // ============================================================
@@ -356,6 +442,13 @@ npm run lint # or: npx eslint .`;
356
442
  }
357
443
  }
358
444
 
445
+ // --- Dependency-specific guidelines ---
446
+ const depGuidelines = detectDependencies(ctx);
447
+ const depSection = depGuidelines.length > 0 ? `
448
+ ## Key Dependencies
449
+ ${depGuidelines.join('\n')}
450
+ ` : '';
451
+
359
452
  // --- Verification criteria based on detected commands ---
360
453
  const verificationSteps = [];
361
454
  verificationSteps.push('1. All existing tests still pass');
@@ -388,7 +481,7 @@ ${mermaid}
388
481
  ${dirDescription}
389
482
  ## Stack
390
483
  ${stackNames}
391
- ${stackSection}${tsSection}
484
+ ${stackSection}${tsSection}${depSection}
392
485
  ## Build & Test
393
486
  \`\`\`bash
394
487
  ${buildSection}
package/src/techniques.js CHANGED
@@ -344,8 +344,8 @@ const TECHNIQUES = {
344
344
  name: 'Default mode is not bypassPermissions',
345
345
  check: (ctx) => {
346
346
  const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
347
- if (!settings) return true; // no settings = not bypassed
348
- return settings.defaultMode !== 'bypassPermissions';
347
+ if (!settings || !settings.permissions) return null; // no settings = skip (not applicable)
348
+ return settings.permissions.defaultMode !== 'bypassPermissions';
349
349
  },
350
350
  impact: 'critical',
351
351
  rating: 5,
@@ -840,7 +840,7 @@ const TECHNIQUES = {
840
840
  name: 'Hooks use specific matchers (not catch-all)',
841
841
  check: (ctx) => {
842
842
  const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
843
- if (!settings || !settings.hooks) return true; // no hooks = skip
843
+ if (!settings || !settings.hooks) return null; // no hooks = not applicable
844
844
  const hookStr = JSON.stringify(settings.hooks);
845
845
  // Check that hooks have matchers, not just catch-all
846
846
  return hookStr.includes('matcher');
@@ -852,28 +852,15 @@ const TECHNIQUES = {
852
852
  template: null
853
853
  },
854
854
 
855
- permissionsNotBypassed: {
856
- id: 2005,
857
- name: 'Default mode is not bypassPermissions',
858
- check: (ctx) => {
859
- const settings = ctx.jsonFile('.claude/settings.local.json') || ctx.jsonFile('.claude/settings.json');
860
- if (!settings || !settings.permissions) return true;
861
- return settings.permissions.defaultMode !== 'bypassPermissions';
862
- },
863
- impact: 'high',
864
- rating: 4,
865
- category: 'quality-deep',
866
- fix: 'bypassPermissions skips all safety checks. Use "default" or "auto" mode with targeted allow rules instead.',
867
- template: null
868
- },
855
+ // permissionsNotBypassed removed - duplicate of noBypassPermissions (#24)
869
856
 
870
857
  commandsUseArguments: {
871
858
  id: 2006,
872
859
  name: 'Commands use $ARGUMENTS for flexibility',
873
860
  check: (ctx) => {
874
- if (!ctx.hasDir('.claude/commands')) return true;
861
+ if (!ctx.hasDir('.claude/commands')) return null; // not applicable
875
862
  const files = ctx.dirFiles('.claude/commands');
876
- if (files.length === 0) return true;
863
+ if (files.length === 0) return null;
877
864
  // Check if at least one command uses $ARGUMENTS
878
865
  for (const f of files) {
879
866
  const content = ctx.fileContent(`.claude/commands/${f}`) || '';
@@ -892,9 +879,9 @@ const TECHNIQUES = {
892
879
  id: 2007,
893
880
  name: 'Agents have maxTurns limit',
894
881
  check: (ctx) => {
895
- if (!ctx.hasDir('.claude/agents')) return true;
882
+ if (!ctx.hasDir('.claude/agents')) return null;
896
883
  const files = ctx.dirFiles('.claude/agents');
897
- if (files.length === 0) return true;
884
+ if (files.length === 0) return null;
898
885
  for (const f of files) {
899
886
  const content = ctx.fileContent(`.claude/agents/${f}`) || '';
900
887
  if (!content.includes('maxTurns')) return false;