claude-cli-advanced-starter-pack 1.0.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/LICENSE +21 -0
- package/OVERVIEW.md +597 -0
- package/README.md +439 -0
- package/bin/gtask.js +282 -0
- package/bin/postinstall.js +53 -0
- package/package.json +69 -0
- package/src/agents/phase-dev-templates.js +1011 -0
- package/src/agents/templates.js +668 -0
- package/src/analysis/checklist-parser.js +414 -0
- package/src/analysis/codebase.js +481 -0
- package/src/cli/menu.js +958 -0
- package/src/commands/claude-audit.js +1482 -0
- package/src/commands/claude-settings.js +2243 -0
- package/src/commands/create-agent.js +681 -0
- package/src/commands/create-command.js +337 -0
- package/src/commands/create-hook.js +262 -0
- package/src/commands/create-phase-dev/codebase-analyzer.js +813 -0
- package/src/commands/create-phase-dev/documentation-generator.js +352 -0
- package/src/commands/create-phase-dev/post-completion.js +404 -0
- package/src/commands/create-phase-dev/scale-calculator.js +344 -0
- package/src/commands/create-phase-dev/wizard.js +492 -0
- package/src/commands/create-phase-dev.js +481 -0
- package/src/commands/create-skill.js +313 -0
- package/src/commands/create.js +446 -0
- package/src/commands/decompose.js +392 -0
- package/src/commands/detect-tech-stack.js +768 -0
- package/src/commands/explore-mcp/claude-md-updater.js +252 -0
- package/src/commands/explore-mcp/mcp-installer.js +346 -0
- package/src/commands/explore-mcp/mcp-registry.js +438 -0
- package/src/commands/explore-mcp.js +638 -0
- package/src/commands/gtask-init.js +641 -0
- package/src/commands/help.js +128 -0
- package/src/commands/init.js +1890 -0
- package/src/commands/install.js +250 -0
- package/src/commands/list.js +116 -0
- package/src/commands/roadmap.js +750 -0
- package/src/commands/setup-wizard.js +482 -0
- package/src/commands/setup.js +351 -0
- package/src/commands/sync.js +534 -0
- package/src/commands/test-run.js +456 -0
- package/src/commands/test-setup.js +456 -0
- package/src/commands/validate.js +67 -0
- package/src/config/tech-stack.defaults.json +182 -0
- package/src/config/tech-stack.schema.json +502 -0
- package/src/github/client.js +359 -0
- package/src/index.js +84 -0
- package/src/templates/claude-command.js +244 -0
- package/src/templates/issue-body.js +284 -0
- package/src/testing/config.js +411 -0
- package/src/utils/template-engine.js +398 -0
- package/src/utils/validate-templates.js +223 -0
- package/src/utils.js +396 -0
- package/templates/commands/ccasp-setup.template.md +113 -0
- package/templates/commands/context-audit.template.md +97 -0
- package/templates/commands/create-task-list.template.md +382 -0
- package/templates/commands/deploy-full.template.md +261 -0
- package/templates/commands/github-task-start.template.md +99 -0
- package/templates/commands/github-update.template.md +69 -0
- package/templates/commands/happy-start.template.md +117 -0
- package/templates/commands/phase-track.template.md +142 -0
- package/templates/commands/tunnel-start.template.md +127 -0
- package/templates/commands/tunnel-stop.template.md +106 -0
- package/templates/hooks/context-guardian.template.js +173 -0
- package/templates/hooks/deployment-orchestrator.template.js +219 -0
- package/templates/hooks/github-progress-hook.template.js +197 -0
- package/templates/hooks/happy-checkpoint-manager.template.js +222 -0
- package/templates/hooks/phase-dev-enforcer.template.js +183 -0
|
@@ -0,0 +1,1482 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Audit Command
|
|
3
|
+
*
|
|
4
|
+
* Audits CLAUDE.md files and .claude/ folder structure against
|
|
5
|
+
* Anthropic's Claude Code CLI best practices.
|
|
6
|
+
*
|
|
7
|
+
* Reference: https://code.claude.com/docs/en/best-practices
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
import ora from 'ora';
|
|
12
|
+
import inquirer from 'inquirer';
|
|
13
|
+
import { existsSync, readFileSync, writeFileSync, readdirSync, statSync, mkdirSync } from 'fs';
|
|
14
|
+
import { join, basename, extname } from 'path';
|
|
15
|
+
import { showHeader } from '../cli/menu.js';
|
|
16
|
+
import { detectTechStack } from './detect-tech-stack.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Audit rules based on Anthropic documentation
|
|
20
|
+
*/
|
|
21
|
+
const AUDIT_RULES = {
|
|
22
|
+
claudeMd: {
|
|
23
|
+
maxLines: 300,
|
|
24
|
+
recommendedLines: 60,
|
|
25
|
+
warningLines: 150,
|
|
26
|
+
requiredSections: [],
|
|
27
|
+
antiPatterns: [
|
|
28
|
+
{ pattern: /```[\s\S]{500,}```/g, message: 'Long code blocks (>500 chars) bloat context - link to docs instead' },
|
|
29
|
+
{ pattern: /\b(obvious|obviously|of course|everyone knows)\b/gi, message: 'Self-evident statements waste tokens' },
|
|
30
|
+
{ pattern: /^\s*-\s*Write clean code/mi, message: 'Generic advice like "write clean code" adds no value' },
|
|
31
|
+
{ pattern: /^\s*-\s*Follow best practices/mi, message: 'Vague instructions get ignored - be specific' },
|
|
32
|
+
{ pattern: /^#{1,6}\s+.{100,}/gm, message: 'Headers over 100 chars are too long' },
|
|
33
|
+
],
|
|
34
|
+
goodPatterns: [
|
|
35
|
+
{ pattern: /IMPORTANT|YOU MUST|CRITICAL|NEVER|ALWAYS/i, message: 'Has emphasis keywords for critical rules' },
|
|
36
|
+
{ pattern: /```(bash|sh|shell)/i, message: 'Includes runnable bash commands' },
|
|
37
|
+
{ pattern: /@[\w\/\.-]+\.(md|json|txt)/g, message: 'Uses @import syntax for modularity' },
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
claudeFolder: {
|
|
41
|
+
expectedStructure: {
|
|
42
|
+
'commands/': { type: 'dir', description: 'Slash commands (.md files)' },
|
|
43
|
+
'skills/': { type: 'dir', description: 'Skills with SKILL.md files' },
|
|
44
|
+
'agents/': { type: 'dir', description: 'Subagent definitions' },
|
|
45
|
+
'hooks/': { type: 'dir', description: 'Hook scripts and configs' },
|
|
46
|
+
'settings.json': { type: 'file', description: 'Project settings (git-tracked)' },
|
|
47
|
+
'settings.local.json': { type: 'file', description: 'Local settings (gitignored)' },
|
|
48
|
+
},
|
|
49
|
+
filePatterns: {
|
|
50
|
+
commands: { ext: '.md', frontmatter: false },
|
|
51
|
+
skills: { ext: '.md', frontmatter: true, requiredFrontmatter: ['name', 'description'] },
|
|
52
|
+
agents: { ext: '.md', frontmatter: true, requiredFrontmatter: ['name', 'description', 'tools'] },
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Enhancement templates for generating CLAUDE.md content
|
|
59
|
+
*/
|
|
60
|
+
const ENHANCEMENT_TEMPLATES = {
|
|
61
|
+
// Quick Start section - most important
|
|
62
|
+
quickStart: (techStack) => {
|
|
63
|
+
const lines = ['## Quick Start', ''];
|
|
64
|
+
|
|
65
|
+
// Build commands based on detected stack
|
|
66
|
+
if (techStack.devEnvironment?.packageManager) {
|
|
67
|
+
const pm = techStack.devEnvironment.packageManager;
|
|
68
|
+
const installCmd = pm === 'yarn' ? 'yarn' : pm === 'pnpm' ? 'pnpm install' : pm === 'bun' ? 'bun install' : 'npm install';
|
|
69
|
+
lines.push('```bash');
|
|
70
|
+
lines.push(`# Install dependencies`);
|
|
71
|
+
lines.push(installCmd);
|
|
72
|
+
lines.push('');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (techStack.frontend?.framework) {
|
|
76
|
+
lines.push(`# Run frontend (${techStack.frontend.framework})`);
|
|
77
|
+
lines.push(`${techStack.devEnvironment?.packageManager || 'npm'} run dev`);
|
|
78
|
+
lines.push('');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (techStack.backend?.framework) {
|
|
82
|
+
lines.push(`# Run backend (${techStack.backend.framework})`);
|
|
83
|
+
if (techStack.backend.language === 'python') {
|
|
84
|
+
lines.push('python -m uvicorn main:app --reload # or python run_api.py');
|
|
85
|
+
} else {
|
|
86
|
+
lines.push(`${techStack.devEnvironment?.packageManager || 'npm'} run server`);
|
|
87
|
+
}
|
|
88
|
+
lines.push('');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (techStack.testing?.e2e?.framework) {
|
|
92
|
+
lines.push(`# Run E2E tests (${techStack.testing.e2e.framework})`);
|
|
93
|
+
if (techStack.testing.e2e.framework === 'playwright') {
|
|
94
|
+
lines.push('npx playwright test');
|
|
95
|
+
} else if (techStack.testing.e2e.framework === 'cypress') {
|
|
96
|
+
lines.push('npx cypress run');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
lines.push('```');
|
|
101
|
+
lines.push('');
|
|
102
|
+
return lines.join('\n');
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
// Tech stack overview table
|
|
106
|
+
techStackTable: (techStack) => {
|
|
107
|
+
const lines = ['## Tech Stack', '', '| Layer | Technology |', '|-------|------------|'];
|
|
108
|
+
|
|
109
|
+
if (techStack.frontend?.framework) {
|
|
110
|
+
let frontend = techStack.frontend.framework;
|
|
111
|
+
if (techStack.frontend.buildTool) frontend += ` + ${techStack.frontend.buildTool}`;
|
|
112
|
+
if (techStack.frontend.stateManager) frontend += ` + ${techStack.frontend.stateManager}`;
|
|
113
|
+
lines.push(`| Frontend | ${frontend} |`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (techStack.backend?.framework) {
|
|
117
|
+
lines.push(`| Backend | ${techStack.backend.framework} (${techStack.backend.language || 'unknown'}) |`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (techStack.database?.primary) {
|
|
121
|
+
let db = techStack.database.primary;
|
|
122
|
+
if (techStack.database.orm) db += ` + ${techStack.database.orm}`;
|
|
123
|
+
lines.push(`| Database | ${db} |`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (techStack.testing?.e2e?.framework) {
|
|
127
|
+
lines.push(`| E2E Testing | ${techStack.testing.e2e.framework} |`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (techStack.testing?.unit?.framework) {
|
|
131
|
+
lines.push(`| Unit Testing | ${techStack.testing.unit.framework} |`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
lines.push('');
|
|
135
|
+
return lines.join('\n');
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
// Key locations section
|
|
139
|
+
keyLocations: (techStack) => {
|
|
140
|
+
const lines = ['## Key Locations', '', '| Type | Path |', '|------|------|'];
|
|
141
|
+
|
|
142
|
+
if (techStack.frontend?.framework) {
|
|
143
|
+
lines.push('| Frontend Entry | `src/main.tsx` or `src/index.tsx` |');
|
|
144
|
+
lines.push('| Components | `src/components/` |');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (techStack.backend?.framework) {
|
|
148
|
+
if (techStack.backend.language === 'python') {
|
|
149
|
+
lines.push('| Backend Entry | `main.py` or `run_api.py` |');
|
|
150
|
+
lines.push('| API Routes | `routes/` or `routers/` |');
|
|
151
|
+
} else {
|
|
152
|
+
lines.push('| Backend Entry | `src/index.ts` or `server.js` |');
|
|
153
|
+
lines.push('| API Routes | `src/routes/` |');
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (techStack.testing?.e2e?.framework) {
|
|
158
|
+
lines.push('| E2E Tests | `tests/` or `e2e/` |');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
lines.push('| Config | `.env`, `package.json` |');
|
|
162
|
+
lines.push('');
|
|
163
|
+
return lines.join('\n');
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
// Import patterns based on language/framework
|
|
167
|
+
importPatterns: (techStack) => {
|
|
168
|
+
const lines = ['## Import Patterns', ''];
|
|
169
|
+
|
|
170
|
+
if (techStack.frontend?.framework === 'react') {
|
|
171
|
+
lines.push('**React/TypeScript:**');
|
|
172
|
+
lines.push('```typescript');
|
|
173
|
+
lines.push("import { useState, useEffect } from 'react';");
|
|
174
|
+
lines.push("import { Button } from '@/components/ui';");
|
|
175
|
+
if (techStack.frontend.stateManager === 'zustand') {
|
|
176
|
+
lines.push("import { useStore } from '@/store';");
|
|
177
|
+
} else if (techStack.frontend.stateManager === 'redux') {
|
|
178
|
+
lines.push("import { useSelector, useDispatch } from 'react-redux';");
|
|
179
|
+
}
|
|
180
|
+
lines.push('```');
|
|
181
|
+
lines.push('');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (techStack.backend?.language === 'python') {
|
|
185
|
+
lines.push('**Python (FastAPI):**');
|
|
186
|
+
lines.push('```python');
|
|
187
|
+
lines.push('from fastapi import APIRouter, Depends, HTTPException');
|
|
188
|
+
lines.push('from sqlalchemy.orm import Session');
|
|
189
|
+
lines.push('from app.database import get_db');
|
|
190
|
+
lines.push('```');
|
|
191
|
+
lines.push('');
|
|
192
|
+
} else if (techStack.backend?.framework === 'express') {
|
|
193
|
+
lines.push('**Node.js (Express):**');
|
|
194
|
+
lines.push('```typescript');
|
|
195
|
+
lines.push("import express from 'express';");
|
|
196
|
+
lines.push("import { Router } from 'express';");
|
|
197
|
+
lines.push('```');
|
|
198
|
+
lines.push('');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return lines.join('\n');
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
// Testing section
|
|
205
|
+
testingInstructions: (techStack) => {
|
|
206
|
+
const lines = ['## Testing', ''];
|
|
207
|
+
|
|
208
|
+
if (techStack.testing?.e2e?.framework === 'playwright') {
|
|
209
|
+
lines.push('**Playwright E2E:**');
|
|
210
|
+
lines.push('```bash');
|
|
211
|
+
lines.push('npx playwright test # Run all tests');
|
|
212
|
+
lines.push('npx playwright test --ui # Open UI mode');
|
|
213
|
+
lines.push('npx playwright test --headed # Show browser');
|
|
214
|
+
lines.push('npx playwright codegen # Generate tests');
|
|
215
|
+
lines.push('```');
|
|
216
|
+
lines.push('');
|
|
217
|
+
} else if (techStack.testing?.e2e?.framework === 'cypress') {
|
|
218
|
+
lines.push('**Cypress E2E:**');
|
|
219
|
+
lines.push('```bash');
|
|
220
|
+
lines.push('npx cypress run # Run headless');
|
|
221
|
+
lines.push('npx cypress open # Open UI');
|
|
222
|
+
lines.push('```');
|
|
223
|
+
lines.push('');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (techStack.testing?.unit?.framework === 'vitest') {
|
|
227
|
+
lines.push('**Vitest Unit Tests:**');
|
|
228
|
+
lines.push('```bash');
|
|
229
|
+
lines.push('npm run test # Run tests');
|
|
230
|
+
lines.push('npm run test -- --coverage # With coverage');
|
|
231
|
+
lines.push('```');
|
|
232
|
+
lines.push('');
|
|
233
|
+
} else if (techStack.testing?.unit?.framework === 'jest') {
|
|
234
|
+
lines.push('**Jest Unit Tests:**');
|
|
235
|
+
lines.push('```bash');
|
|
236
|
+
lines.push('npm test # Run tests');
|
|
237
|
+
lines.push('npm test -- --coverage # With coverage');
|
|
238
|
+
lines.push('```');
|
|
239
|
+
lines.push('');
|
|
240
|
+
} else if (techStack.testing?.unit?.framework === 'pytest') {
|
|
241
|
+
lines.push('**Pytest:**');
|
|
242
|
+
lines.push('```bash');
|
|
243
|
+
lines.push('pytest # Run all tests');
|
|
244
|
+
lines.push('pytest -v # Verbose');
|
|
245
|
+
lines.push('pytest --cov=app # With coverage');
|
|
246
|
+
lines.push('```');
|
|
247
|
+
lines.push('');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return lines.join('\n');
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
// Deployment section
|
|
254
|
+
deploymentSection: (techStack) => {
|
|
255
|
+
const lines = ['## Deployment', ''];
|
|
256
|
+
|
|
257
|
+
if (techStack.deployment?.frontend?.platform) {
|
|
258
|
+
lines.push(`**Frontend (${techStack.deployment.frontend.platform}):**`);
|
|
259
|
+
lines.push('```bash');
|
|
260
|
+
|
|
261
|
+
if (techStack.deployment.frontend.platform === 'cloudflare') {
|
|
262
|
+
lines.push('npm run build');
|
|
263
|
+
lines.push('npx wrangler pages deploy dist --project-name=YOUR_PROJECT');
|
|
264
|
+
} else if (techStack.deployment.frontend.platform === 'vercel') {
|
|
265
|
+
lines.push('vercel --prod');
|
|
266
|
+
} else if (techStack.deployment.frontend.platform === 'netlify') {
|
|
267
|
+
lines.push('netlify deploy --prod');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
lines.push('```');
|
|
271
|
+
lines.push('');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (techStack.deployment?.backend?.platform) {
|
|
275
|
+
lines.push(`**Backend (${techStack.deployment.backend.platform}):**`);
|
|
276
|
+
lines.push('```bash');
|
|
277
|
+
|
|
278
|
+
if (techStack.deployment.backend.platform === 'railway') {
|
|
279
|
+
lines.push('# Use Railway MCP or dashboard');
|
|
280
|
+
lines.push('# railway up');
|
|
281
|
+
} else if (techStack.deployment.backend.platform === 'docker') {
|
|
282
|
+
lines.push('docker build -t app .');
|
|
283
|
+
lines.push('docker push registry/app');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
lines.push('```');
|
|
287
|
+
lines.push('');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return lines.join('\n');
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
// Reference documentation links
|
|
294
|
+
referenceLinks: (techStack) => {
|
|
295
|
+
const lines = ['## Reference Documentation', ''];
|
|
296
|
+
const links = [];
|
|
297
|
+
|
|
298
|
+
// Frontend
|
|
299
|
+
if (techStack.frontend?.framework === 'react') {
|
|
300
|
+
links.push('- [React Docs](https://react.dev/)');
|
|
301
|
+
} else if (techStack.frontend?.framework === 'vue') {
|
|
302
|
+
links.push('- [Vue.js Docs](https://vuejs.org/)');
|
|
303
|
+
} else if (techStack.frontend?.framework === 'angular') {
|
|
304
|
+
links.push('- [Angular Docs](https://angular.io/docs)');
|
|
305
|
+
} else if (techStack.frontend?.framework === 'svelte') {
|
|
306
|
+
links.push('- [Svelte Docs](https://svelte.dev/docs)');
|
|
307
|
+
} else if (techStack.frontend?.framework === 'nextjs') {
|
|
308
|
+
links.push('- [Next.js Docs](https://nextjs.org/docs)');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Build tools
|
|
312
|
+
if (techStack.frontend?.buildTool === 'vite') {
|
|
313
|
+
links.push('- [Vite Docs](https://vitejs.dev/)');
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// State
|
|
317
|
+
if (techStack.frontend?.stateManager === 'zustand') {
|
|
318
|
+
links.push('- [Zustand Docs](https://github.com/pmndrs/zustand)');
|
|
319
|
+
} else if (techStack.frontend?.stateManager === 'redux') {
|
|
320
|
+
links.push('- [Redux Toolkit](https://redux-toolkit.js.org/)');
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Styling
|
|
324
|
+
if (techStack.frontend?.styling === 'tailwind') {
|
|
325
|
+
links.push('- [Tailwind CSS](https://tailwindcss.com/docs)');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Backend
|
|
329
|
+
if (techStack.backend?.framework === 'fastapi') {
|
|
330
|
+
links.push('- [FastAPI Docs](https://fastapi.tiangolo.com/)');
|
|
331
|
+
} else if (techStack.backend?.framework === 'express') {
|
|
332
|
+
links.push('- [Express.js Docs](https://expressjs.com/)');
|
|
333
|
+
} else if (techStack.backend?.framework === 'nestjs') {
|
|
334
|
+
links.push('- [NestJS Docs](https://docs.nestjs.com/)');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Database/ORM
|
|
338
|
+
if (techStack.database?.orm === 'prisma') {
|
|
339
|
+
links.push('- [Prisma Docs](https://www.prisma.io/docs)');
|
|
340
|
+
} else if (techStack.database?.orm === 'sqlalchemy') {
|
|
341
|
+
links.push('- [SQLAlchemy Docs](https://docs.sqlalchemy.org/)');
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Testing
|
|
345
|
+
if (techStack.testing?.e2e?.framework === 'playwright') {
|
|
346
|
+
links.push('- [Playwright Docs](https://playwright.dev/)');
|
|
347
|
+
} else if (techStack.testing?.e2e?.framework === 'cypress') {
|
|
348
|
+
links.push('- [Cypress Docs](https://docs.cypress.io/)');
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (links.length === 0) {
|
|
352
|
+
return '';
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
lines.push(...links, '');
|
|
356
|
+
return lines.join('\n');
|
|
357
|
+
},
|
|
358
|
+
|
|
359
|
+
// Architecture rules based on stack
|
|
360
|
+
architectureRules: (techStack) => {
|
|
361
|
+
const lines = ['## Architecture Rules', ''];
|
|
362
|
+
|
|
363
|
+
if (techStack.frontend?.framework === 'react') {
|
|
364
|
+
lines.push('**React Guidelines:**');
|
|
365
|
+
lines.push('- Use functional components with hooks');
|
|
366
|
+
lines.push('- Colocate component styles and tests');
|
|
367
|
+
lines.push('- Extract shared logic into custom hooks');
|
|
368
|
+
lines.push('');
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (techStack.backend?.language === 'python') {
|
|
372
|
+
lines.push('**Python Guidelines:**');
|
|
373
|
+
lines.push('- Use type hints for all function signatures');
|
|
374
|
+
lines.push('- Keep route handlers thin - delegate to services');
|
|
375
|
+
lines.push('- Use dependency injection for database sessions');
|
|
376
|
+
lines.push('');
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (techStack.database?.primary === 'postgresql') {
|
|
380
|
+
lines.push('**Database Guidelines:**');
|
|
381
|
+
lines.push('- Use migrations for schema changes');
|
|
382
|
+
lines.push('- Index frequently queried columns');
|
|
383
|
+
lines.push('- Use connection pooling in production');
|
|
384
|
+
lines.push('');
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return lines.join('\n');
|
|
388
|
+
},
|
|
389
|
+
|
|
390
|
+
// Critical rules with emphasis
|
|
391
|
+
criticalRules: () => {
|
|
392
|
+
return `## Critical Rules
|
|
393
|
+
|
|
394
|
+
**IMPORTANT**: These rules MUST be followed:
|
|
395
|
+
|
|
396
|
+
1. **NEVER commit secrets** - Use environment variables
|
|
397
|
+
2. **Run tests before committing** - All tests must pass
|
|
398
|
+
3. **Use conventional commits** - feat:, fix:, chore:, etc.
|
|
399
|
+
|
|
400
|
+
`;
|
|
401
|
+
},
|
|
402
|
+
|
|
403
|
+
// Full CLAUDE.md template
|
|
404
|
+
fullTemplate: (techStack, projectName) => {
|
|
405
|
+
const sections = [];
|
|
406
|
+
|
|
407
|
+
sections.push(`# ${projectName || 'Project'} - Quick Reference`);
|
|
408
|
+
sections.push('');
|
|
409
|
+
sections.push(ENHANCEMENT_TEMPLATES.quickStart(techStack));
|
|
410
|
+
sections.push(ENHANCEMENT_TEMPLATES.techStackTable(techStack));
|
|
411
|
+
sections.push(ENHANCEMENT_TEMPLATES.keyLocations(techStack));
|
|
412
|
+
sections.push(ENHANCEMENT_TEMPLATES.importPatterns(techStack));
|
|
413
|
+
sections.push(ENHANCEMENT_TEMPLATES.testingInstructions(techStack));
|
|
414
|
+
sections.push(ENHANCEMENT_TEMPLATES.deploymentSection(techStack));
|
|
415
|
+
sections.push(ENHANCEMENT_TEMPLATES.architectureRules(techStack));
|
|
416
|
+
sections.push(ENHANCEMENT_TEMPLATES.criticalRules());
|
|
417
|
+
sections.push(ENHANCEMENT_TEMPLATES.referenceLinks(techStack));
|
|
418
|
+
|
|
419
|
+
return sections.filter(s => s.trim()).join('\n');
|
|
420
|
+
},
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Audit result structure
|
|
425
|
+
*/
|
|
426
|
+
function createAuditResult() {
|
|
427
|
+
return {
|
|
428
|
+
passed: [],
|
|
429
|
+
warnings: [],
|
|
430
|
+
errors: [],
|
|
431
|
+
suggestions: [],
|
|
432
|
+
score: 100,
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Main audit runner
|
|
438
|
+
*/
|
|
439
|
+
export async function runClaudeAudit(options = {}) {
|
|
440
|
+
const cwd = options.cwd || process.cwd();
|
|
441
|
+
|
|
442
|
+
showHeader('Claude Code Audit');
|
|
443
|
+
|
|
444
|
+
console.log(chalk.dim('Auditing against Anthropic best practices...'));
|
|
445
|
+
console.log(chalk.dim('Reference: https://code.claude.com/docs/en/best-practices'));
|
|
446
|
+
console.log('');
|
|
447
|
+
|
|
448
|
+
const spinner = ora('Scanning project...').start();
|
|
449
|
+
|
|
450
|
+
const results = {
|
|
451
|
+
claudeMd: createAuditResult(),
|
|
452
|
+
claudeFolder: createAuditResult(),
|
|
453
|
+
overall: createAuditResult(),
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
// Audit CLAUDE.md files
|
|
457
|
+
spinner.text = 'Auditing CLAUDE.md files...';
|
|
458
|
+
auditClaudeMdFiles(cwd, results.claudeMd);
|
|
459
|
+
|
|
460
|
+
// Audit .claude folder
|
|
461
|
+
spinner.text = 'Auditing .claude/ folder structure...';
|
|
462
|
+
auditClaudeFolder(cwd, results.claudeFolder);
|
|
463
|
+
|
|
464
|
+
// Calculate overall score
|
|
465
|
+
calculateOverallScore(results);
|
|
466
|
+
|
|
467
|
+
spinner.succeed('Audit complete');
|
|
468
|
+
console.log('');
|
|
469
|
+
|
|
470
|
+
// Display results
|
|
471
|
+
displayAuditResults(results);
|
|
472
|
+
|
|
473
|
+
// Show fix suggestions
|
|
474
|
+
if (options.interactive !== false) {
|
|
475
|
+
await showFixSuggestions(results, cwd);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return results;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Audit CLAUDE.md files at various locations
|
|
483
|
+
*/
|
|
484
|
+
function auditClaudeMdFiles(cwd, result) {
|
|
485
|
+
const locations = [
|
|
486
|
+
{ path: join(cwd, 'CLAUDE.md'), name: 'Root CLAUDE.md', required: true },
|
|
487
|
+
{ path: join(cwd, 'CLAUDE.local.md'), name: 'Local CLAUDE.md', required: false },
|
|
488
|
+
];
|
|
489
|
+
|
|
490
|
+
// Check for CLAUDE.md in parent directories (monorepo support)
|
|
491
|
+
let parentDir = join(cwd, '..');
|
|
492
|
+
const checkedPaths = new Set([cwd]);
|
|
493
|
+
while (parentDir !== cwd && !checkedPaths.has(parentDir)) {
|
|
494
|
+
checkedPaths.add(parentDir);
|
|
495
|
+
const parentClaudeMd = join(parentDir, 'CLAUDE.md');
|
|
496
|
+
if (existsSync(parentClaudeMd)) {
|
|
497
|
+
locations.push({ path: parentClaudeMd, name: `Parent CLAUDE.md (${basename(parentDir)})`, required: false });
|
|
498
|
+
}
|
|
499
|
+
cwd = parentDir;
|
|
500
|
+
parentDir = join(cwd, '..');
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
let foundAny = false;
|
|
504
|
+
|
|
505
|
+
for (const loc of locations) {
|
|
506
|
+
if (existsSync(loc.path)) {
|
|
507
|
+
foundAny = true;
|
|
508
|
+
auditSingleClaudeMd(loc.path, loc.name, result);
|
|
509
|
+
} else if (loc.required) {
|
|
510
|
+
result.errors.push({
|
|
511
|
+
file: loc.path,
|
|
512
|
+
message: `Missing ${loc.name} - run /init to generate one`,
|
|
513
|
+
fix: 'Run `claude /init` to generate a starter CLAUDE.md',
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (!foundAny) {
|
|
519
|
+
result.errors.push({
|
|
520
|
+
file: 'CLAUDE.md',
|
|
521
|
+
message: 'No CLAUDE.md found in project',
|
|
522
|
+
fix: 'Create a CLAUDE.md file with project-specific instructions',
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Audit a single CLAUDE.md file
|
|
529
|
+
*/
|
|
530
|
+
function auditSingleClaudeMd(filePath, name, result) {
|
|
531
|
+
const content = readFileSync(filePath, 'utf8');
|
|
532
|
+
const lines = content.split('\n');
|
|
533
|
+
const lineCount = lines.length;
|
|
534
|
+
|
|
535
|
+
// Line count checks
|
|
536
|
+
if (lineCount > AUDIT_RULES.claudeMd.maxLines) {
|
|
537
|
+
result.errors.push({
|
|
538
|
+
file: name,
|
|
539
|
+
message: `${lineCount} lines exceeds max recommended ${AUDIT_RULES.claudeMd.maxLines}`,
|
|
540
|
+
fix: 'Prune ruthlessly - if Claude already does it correctly, remove the instruction',
|
|
541
|
+
});
|
|
542
|
+
} else if (lineCount > AUDIT_RULES.claudeMd.warningLines) {
|
|
543
|
+
result.warnings.push({
|
|
544
|
+
file: name,
|
|
545
|
+
message: `${lineCount} lines is getting long (recommended: <${AUDIT_RULES.claudeMd.recommendedLines})`,
|
|
546
|
+
fix: 'Consider moving domain-specific content to skills',
|
|
547
|
+
});
|
|
548
|
+
} else if (lineCount <= AUDIT_RULES.claudeMd.recommendedLines) {
|
|
549
|
+
result.passed.push({
|
|
550
|
+
file: name,
|
|
551
|
+
message: `Good length: ${lineCount} lines`,
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Check for anti-patterns
|
|
556
|
+
for (const anti of AUDIT_RULES.claudeMd.antiPatterns) {
|
|
557
|
+
const matches = content.match(anti.pattern);
|
|
558
|
+
if (matches) {
|
|
559
|
+
result.warnings.push({
|
|
560
|
+
file: name,
|
|
561
|
+
message: anti.message,
|
|
562
|
+
fix: `Found ${matches.length} occurrence(s)`,
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Check for good patterns
|
|
568
|
+
for (const good of AUDIT_RULES.claudeMd.goodPatterns) {
|
|
569
|
+
const matches = content.match(good.pattern);
|
|
570
|
+
if (matches) {
|
|
571
|
+
result.passed.push({
|
|
572
|
+
file: name,
|
|
573
|
+
message: good.message,
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Check for common content issues
|
|
579
|
+
if (content.length > 15000) {
|
|
580
|
+
result.errors.push({
|
|
581
|
+
file: name,
|
|
582
|
+
message: `File is ${Math.round(content.length / 1000)}KB - too large for efficient loading`,
|
|
583
|
+
fix: 'Split into skills or use @imports',
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Check for gitignore mention if .local.md
|
|
588
|
+
if (name.includes('local') && !content.includes('.gitignore')) {
|
|
589
|
+
result.suggestions.push({
|
|
590
|
+
file: name,
|
|
591
|
+
message: 'Local files should be gitignored',
|
|
592
|
+
fix: 'Add CLAUDE.local.md to .gitignore',
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Check for import usage
|
|
597
|
+
const imports = content.match(/@[\w\/\.-]+/g) || [];
|
|
598
|
+
if (imports.length > 0) {
|
|
599
|
+
result.passed.push({
|
|
600
|
+
file: name,
|
|
601
|
+
message: `Uses ${imports.length} @imports for modularity`,
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// Check for empty or minimal content
|
|
606
|
+
const meaningfulContent = content.replace(/^#.*$/gm, '').replace(/\s+/g, '').length;
|
|
607
|
+
if (meaningfulContent < 100) {
|
|
608
|
+
result.warnings.push({
|
|
609
|
+
file: name,
|
|
610
|
+
message: 'Very little content - might not be useful',
|
|
611
|
+
fix: 'Add bash commands, code style rules, and workflow instructions',
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Audit .claude folder structure
|
|
618
|
+
*/
|
|
619
|
+
function auditClaudeFolder(cwd, result) {
|
|
620
|
+
const claudeDir = join(cwd, '.claude');
|
|
621
|
+
|
|
622
|
+
if (!existsSync(claudeDir)) {
|
|
623
|
+
result.errors.push({
|
|
624
|
+
file: '.claude/',
|
|
625
|
+
message: 'No .claude folder found',
|
|
626
|
+
fix: 'Create .claude/ directory for commands, skills, hooks, and settings',
|
|
627
|
+
});
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
result.passed.push({
|
|
632
|
+
file: '.claude/',
|
|
633
|
+
message: '.claude folder exists',
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
// Check expected structure
|
|
637
|
+
for (const [name, spec] of Object.entries(AUDIT_RULES.claudeFolder.expectedStructure)) {
|
|
638
|
+
const itemPath = join(claudeDir, name);
|
|
639
|
+
const exists = existsSync(itemPath);
|
|
640
|
+
|
|
641
|
+
if (!exists) {
|
|
642
|
+
result.suggestions.push({
|
|
643
|
+
file: `.claude/${name}`,
|
|
644
|
+
message: `Missing ${spec.description}`,
|
|
645
|
+
fix: `Create .claude/${name}`,
|
|
646
|
+
});
|
|
647
|
+
} else {
|
|
648
|
+
if (spec.type === 'dir') {
|
|
649
|
+
const isDir = statSync(itemPath).isDirectory();
|
|
650
|
+
if (isDir) {
|
|
651
|
+
result.passed.push({
|
|
652
|
+
file: `.claude/${name}`,
|
|
653
|
+
message: `${spec.description} exists`,
|
|
654
|
+
});
|
|
655
|
+
// Audit contents
|
|
656
|
+
auditClaudeFolderContents(itemPath, name.replace('/', ''), result);
|
|
657
|
+
} else {
|
|
658
|
+
result.errors.push({
|
|
659
|
+
file: `.claude/${name}`,
|
|
660
|
+
message: `Expected directory, found file`,
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
} else {
|
|
664
|
+
result.passed.push({
|
|
665
|
+
file: `.claude/${name}`,
|
|
666
|
+
message: `${spec.description} exists`,
|
|
667
|
+
});
|
|
668
|
+
// Validate JSON files
|
|
669
|
+
if (name.endsWith('.json')) {
|
|
670
|
+
validateJsonFile(itemPath, `.claude/${name}`, result);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// Check for common misconfigurations
|
|
677
|
+
checkCommonMisconfigurations(claudeDir, result);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Audit contents of .claude subdirectories
|
|
682
|
+
*/
|
|
683
|
+
function auditClaudeFolderContents(dirPath, type, result) {
|
|
684
|
+
const files = readdirSync(dirPath);
|
|
685
|
+
const spec = AUDIT_RULES.claudeFolder.filePatterns[type];
|
|
686
|
+
|
|
687
|
+
if (!spec) return;
|
|
688
|
+
|
|
689
|
+
for (const file of files) {
|
|
690
|
+
const filePath = join(dirPath, file);
|
|
691
|
+
const stat = statSync(filePath);
|
|
692
|
+
|
|
693
|
+
if (stat.isDirectory()) {
|
|
694
|
+
// Skills can have subdirectories with SKILL.md
|
|
695
|
+
if (type === 'skills') {
|
|
696
|
+
const skillMdPath = join(filePath, 'SKILL.md');
|
|
697
|
+
if (existsSync(skillMdPath)) {
|
|
698
|
+
validateSkillOrAgent(skillMdPath, `.claude/skills/${file}/SKILL.md`, 'skill', result);
|
|
699
|
+
} else {
|
|
700
|
+
result.warnings.push({
|
|
701
|
+
file: `.claude/skills/${file}/`,
|
|
702
|
+
message: 'Skill directory missing SKILL.md',
|
|
703
|
+
fix: 'Add SKILL.md with name, description frontmatter',
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
const ext = extname(file);
|
|
711
|
+
|
|
712
|
+
if (ext !== spec.ext) {
|
|
713
|
+
result.warnings.push({
|
|
714
|
+
file: `.claude/${type}/${file}`,
|
|
715
|
+
message: `Unexpected file extension (expected ${spec.ext})`,
|
|
716
|
+
});
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
if (spec.frontmatter) {
|
|
721
|
+
if (type === 'skills') {
|
|
722
|
+
validateSkillOrAgent(filePath, `.claude/${type}/${file}`, 'skill', result);
|
|
723
|
+
} else if (type === 'agents') {
|
|
724
|
+
validateSkillOrAgent(filePath, `.claude/${type}/${file}`, 'agent', result);
|
|
725
|
+
}
|
|
726
|
+
} else {
|
|
727
|
+
// Commands just need to be valid markdown
|
|
728
|
+
result.passed.push({
|
|
729
|
+
file: `.claude/${type}/${file}`,
|
|
730
|
+
message: 'Command file found',
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
if (files.length === 0) {
|
|
736
|
+
result.suggestions.push({
|
|
737
|
+
file: `.claude/${type}/`,
|
|
738
|
+
message: `Empty ${type} directory`,
|
|
739
|
+
fix: `Add ${type} to extend Claude's capabilities`,
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* Validate skill or agent markdown file
|
|
746
|
+
*/
|
|
747
|
+
function validateSkillOrAgent(filePath, displayPath, type, result) {
|
|
748
|
+
const content = readFileSync(filePath, 'utf8');
|
|
749
|
+
|
|
750
|
+
// Check for frontmatter
|
|
751
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
752
|
+
|
|
753
|
+
if (!frontmatterMatch) {
|
|
754
|
+
result.errors.push({
|
|
755
|
+
file: displayPath,
|
|
756
|
+
message: `Missing frontmatter (required for ${type}s)`,
|
|
757
|
+
fix: `Add ---\\nname: ...\\ndescription: ...\\n--- at top of file`,
|
|
758
|
+
});
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const frontmatter = frontmatterMatch[1];
|
|
763
|
+
const required = AUDIT_RULES.claudeFolder.filePatterns[type === 'skill' ? 'skills' : 'agents'].requiredFrontmatter;
|
|
764
|
+
|
|
765
|
+
for (const field of required) {
|
|
766
|
+
if (!frontmatter.includes(`${field}:`)) {
|
|
767
|
+
result.errors.push({
|
|
768
|
+
file: displayPath,
|
|
769
|
+
message: `Missing required frontmatter field: ${field}`,
|
|
770
|
+
fix: `Add ${field}: value to frontmatter`,
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Check for tools in agents
|
|
776
|
+
if (type === 'agent' && !frontmatter.includes('tools:')) {
|
|
777
|
+
result.errors.push({
|
|
778
|
+
file: displayPath,
|
|
779
|
+
message: 'Agent missing tools specification',
|
|
780
|
+
fix: 'Add tools: Read, Grep, Glob, Bash (or specific tools)',
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// Check for model specification in agents (optional but recommended)
|
|
785
|
+
if (type === 'agent' && !frontmatter.includes('model:')) {
|
|
786
|
+
result.suggestions.push({
|
|
787
|
+
file: displayPath,
|
|
788
|
+
message: 'Agent has no model specified (will use default)',
|
|
789
|
+
fix: 'Add model: opus, sonnet, or haiku',
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// Content length check
|
|
794
|
+
const body = content.replace(/^---[\s\S]*?---\n?/, '').trim();
|
|
795
|
+
if (body.length < 50) {
|
|
796
|
+
result.warnings.push({
|
|
797
|
+
file: displayPath,
|
|
798
|
+
message: `${type} has minimal instructions`,
|
|
799
|
+
fix: 'Add detailed instructions for the skill/agent',
|
|
800
|
+
});
|
|
801
|
+
} else {
|
|
802
|
+
result.passed.push({
|
|
803
|
+
file: displayPath,
|
|
804
|
+
message: `Valid ${type} with frontmatter`,
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* Validate JSON file
|
|
811
|
+
*/
|
|
812
|
+
function validateJsonFile(filePath, displayPath, result) {
|
|
813
|
+
try {
|
|
814
|
+
const content = readFileSync(filePath, 'utf8');
|
|
815
|
+
JSON.parse(content);
|
|
816
|
+
result.passed.push({
|
|
817
|
+
file: displayPath,
|
|
818
|
+
message: 'Valid JSON',
|
|
819
|
+
});
|
|
820
|
+
} catch (e) {
|
|
821
|
+
result.errors.push({
|
|
822
|
+
file: displayPath,
|
|
823
|
+
message: `Invalid JSON: ${e.message}`,
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* Check for common misconfigurations
|
|
830
|
+
*/
|
|
831
|
+
function checkCommonMisconfigurations(claudeDir, result) {
|
|
832
|
+
// Check if settings.local.json is gitignored
|
|
833
|
+
const gitignorePath = join(claudeDir, '..', '.gitignore');
|
|
834
|
+
if (existsSync(gitignorePath)) {
|
|
835
|
+
const gitignore = readFileSync(gitignorePath, 'utf8');
|
|
836
|
+
if (!gitignore.includes('settings.local.json') && !gitignore.includes('.claude/settings.local.json')) {
|
|
837
|
+
const localSettingsPath = join(claudeDir, 'settings.local.json');
|
|
838
|
+
if (existsSync(localSettingsPath)) {
|
|
839
|
+
result.warnings.push({
|
|
840
|
+
file: '.claude/settings.local.json',
|
|
841
|
+
message: 'Local settings may not be gitignored',
|
|
842
|
+
fix: 'Add .claude/settings.local.json to .gitignore',
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// Check for MCP configuration
|
|
849
|
+
const mcpPath = join(claudeDir, '..', '.mcp.json');
|
|
850
|
+
if (existsSync(mcpPath)) {
|
|
851
|
+
validateJsonFile(mcpPath, '.mcp.json', result);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// Check for hooks configuration
|
|
855
|
+
const hooksDir = join(claudeDir, 'hooks');
|
|
856
|
+
if (existsSync(hooksDir)) {
|
|
857
|
+
const hookFiles = readdirSync(hooksDir).filter(f => f.endsWith('.js') || f.endsWith('.sh'));
|
|
858
|
+
if (hookFiles.length > 0) {
|
|
859
|
+
result.passed.push({
|
|
860
|
+
file: '.claude/hooks/',
|
|
861
|
+
message: `${hookFiles.length} hook script(s) configured`,
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Check settings.json for permissions
|
|
867
|
+
const settingsPath = join(claudeDir, 'settings.json');
|
|
868
|
+
if (existsSync(settingsPath)) {
|
|
869
|
+
try {
|
|
870
|
+
const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
871
|
+
if (settings.permissions?.allow?.length > 0) {
|
|
872
|
+
result.passed.push({
|
|
873
|
+
file: '.claude/settings.json',
|
|
874
|
+
message: `${settings.permissions.allow.length} allowed tool patterns configured`,
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
if (settings.permissions?.deny?.length > 0) {
|
|
878
|
+
result.passed.push({
|
|
879
|
+
file: '.claude/settings.json',
|
|
880
|
+
message: `${settings.permissions.deny.length} denied patterns configured`,
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
} catch (e) {
|
|
884
|
+
// Already reported in JSON validation
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/**
|
|
890
|
+
* Calculate overall score
|
|
891
|
+
*/
|
|
892
|
+
function calculateOverallScore(results) {
|
|
893
|
+
let totalPassed = 0;
|
|
894
|
+
let totalWarnings = 0;
|
|
895
|
+
let totalErrors = 0;
|
|
896
|
+
|
|
897
|
+
for (const key of ['claudeMd', 'claudeFolder']) {
|
|
898
|
+
totalPassed += results[key].passed.length;
|
|
899
|
+
totalWarnings += results[key].warnings.length;
|
|
900
|
+
totalErrors += results[key].errors.length;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// Score calculation: start at 100, -5 per warning, -15 per error
|
|
904
|
+
let score = 100 - (totalWarnings * 5) - (totalErrors * 15);
|
|
905
|
+
score = Math.max(0, Math.min(100, score));
|
|
906
|
+
|
|
907
|
+
results.overall = {
|
|
908
|
+
passed: totalPassed,
|
|
909
|
+
warnings: totalWarnings,
|
|
910
|
+
errors: totalErrors,
|
|
911
|
+
suggestions: results.claudeMd.suggestions.length + results.claudeFolder.suggestions.length,
|
|
912
|
+
score,
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
/**
|
|
917
|
+
* Display audit results
|
|
918
|
+
*/
|
|
919
|
+
function displayAuditResults(results) {
|
|
920
|
+
const { overall } = results;
|
|
921
|
+
|
|
922
|
+
// Score display with color
|
|
923
|
+
let scoreColor = chalk.green;
|
|
924
|
+
let scoreEmoji = '✅';
|
|
925
|
+
if (overall.score < 50) {
|
|
926
|
+
scoreColor = chalk.red;
|
|
927
|
+
scoreEmoji = '❌';
|
|
928
|
+
} else if (overall.score < 75) {
|
|
929
|
+
scoreColor = chalk.yellow;
|
|
930
|
+
scoreEmoji = '⚠️';
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
console.log(chalk.bold('━'.repeat(60)));
|
|
934
|
+
console.log(`${scoreEmoji} ${chalk.bold('Audit Score:')} ${scoreColor.bold(overall.score + '/100')}`);
|
|
935
|
+
console.log(chalk.bold('━'.repeat(60)));
|
|
936
|
+
console.log('');
|
|
937
|
+
|
|
938
|
+
console.log(` ${chalk.green('✓')} Passed: ${overall.passed}`);
|
|
939
|
+
console.log(` ${chalk.yellow('⚠')} Warnings: ${overall.warnings}`);
|
|
940
|
+
console.log(` ${chalk.red('✗')} Errors: ${overall.errors}`);
|
|
941
|
+
console.log(` ${chalk.blue('💡')} Suggestions: ${overall.suggestions}`);
|
|
942
|
+
console.log('');
|
|
943
|
+
|
|
944
|
+
// Display errors first
|
|
945
|
+
if (results.claudeMd.errors.length > 0 || results.claudeFolder.errors.length > 0) {
|
|
946
|
+
console.log(chalk.red.bold('Errors (must fix):'));
|
|
947
|
+
for (const err of [...results.claudeMd.errors, ...results.claudeFolder.errors]) {
|
|
948
|
+
console.log(` ${chalk.red('✗')} ${chalk.dim(err.file)} - ${err.message}`);
|
|
949
|
+
if (err.fix) {
|
|
950
|
+
console.log(` ${chalk.dim('Fix:')} ${err.fix}`);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
console.log('');
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
// Display warnings
|
|
957
|
+
if (results.claudeMd.warnings.length > 0 || results.claudeFolder.warnings.length > 0) {
|
|
958
|
+
console.log(chalk.yellow.bold('Warnings (should fix):'));
|
|
959
|
+
for (const warn of [...results.claudeMd.warnings, ...results.claudeFolder.warnings]) {
|
|
960
|
+
console.log(` ${chalk.yellow('⚠')} ${chalk.dim(warn.file)} - ${warn.message}`);
|
|
961
|
+
if (warn.fix) {
|
|
962
|
+
console.log(` ${chalk.dim('Fix:')} ${warn.fix}`);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
console.log('');
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// Display suggestions (collapsed by default)
|
|
969
|
+
if (overall.suggestions > 0) {
|
|
970
|
+
console.log(chalk.blue.bold('Suggestions (optional):'));
|
|
971
|
+
for (const sug of [...results.claudeMd.suggestions, ...results.claudeFolder.suggestions]) {
|
|
972
|
+
console.log(` ${chalk.blue('💡')} ${chalk.dim(sug.file)} - ${sug.message}`);
|
|
973
|
+
}
|
|
974
|
+
console.log('');
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// Display passes (summarized)
|
|
978
|
+
if (overall.passed > 0) {
|
|
979
|
+
console.log(chalk.green.bold(`Passed Checks (${overall.passed}):`));
|
|
980
|
+
const passedItems = [...results.claudeMd.passed, ...results.claudeFolder.passed];
|
|
981
|
+
// Show first 5, summarize rest
|
|
982
|
+
const toShow = passedItems.slice(0, 5);
|
|
983
|
+
for (const pass of toShow) {
|
|
984
|
+
console.log(` ${chalk.green('✓')} ${chalk.dim(pass.file)} - ${pass.message}`);
|
|
985
|
+
}
|
|
986
|
+
if (passedItems.length > 5) {
|
|
987
|
+
console.log(` ${chalk.dim(`... and ${passedItems.length - 5} more`)}`);
|
|
988
|
+
}
|
|
989
|
+
console.log('');
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
/**
|
|
994
|
+
* Show fix suggestions interactively
|
|
995
|
+
*/
|
|
996
|
+
async function showFixSuggestions(results, cwd) {
|
|
997
|
+
const { overall } = results;
|
|
998
|
+
|
|
999
|
+
if (overall.errors === 0 && overall.warnings === 0) {
|
|
1000
|
+
console.log(chalk.green.bold('🎉 Great job! Your Claude configuration follows best practices.'));
|
|
1001
|
+
console.log('');
|
|
1002
|
+
|
|
1003
|
+
// Still offer enhancement even if passing
|
|
1004
|
+
const { wantEnhance } = await inquirer.prompt([
|
|
1005
|
+
{
|
|
1006
|
+
type: 'confirm',
|
|
1007
|
+
name: 'wantEnhance',
|
|
1008
|
+
message: 'Would you like to enhance CLAUDE.md with additional sections based on your tech stack?',
|
|
1009
|
+
default: false,
|
|
1010
|
+
},
|
|
1011
|
+
]);
|
|
1012
|
+
|
|
1013
|
+
if (wantEnhance) {
|
|
1014
|
+
await runEnhancement(cwd);
|
|
1015
|
+
}
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
const { action } = await inquirer.prompt([
|
|
1020
|
+
{
|
|
1021
|
+
type: 'list',
|
|
1022
|
+
name: 'action',
|
|
1023
|
+
message: 'What would you like to do?',
|
|
1024
|
+
choices: [
|
|
1025
|
+
{ name: chalk.cyan('✨ Auto-enhance CLAUDE.md (Recommended)'), value: 'enhance' },
|
|
1026
|
+
{ name: 'Show detailed fix instructions', value: 'details' },
|
|
1027
|
+
{ name: 'Show best practices reference', value: 'reference' },
|
|
1028
|
+
{ name: 'Exit', value: 'exit' },
|
|
1029
|
+
],
|
|
1030
|
+
},
|
|
1031
|
+
]);
|
|
1032
|
+
|
|
1033
|
+
if (action === 'enhance') {
|
|
1034
|
+
await runEnhancement(cwd);
|
|
1035
|
+
} else if (action === 'details') {
|
|
1036
|
+
showDetailedFixes(results);
|
|
1037
|
+
} else if (action === 'reference') {
|
|
1038
|
+
showBestPracticesReference();
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
/**
|
|
1043
|
+
* Show detailed fix instructions
|
|
1044
|
+
*/
|
|
1045
|
+
function showDetailedFixes(results) {
|
|
1046
|
+
console.log('');
|
|
1047
|
+
console.log(chalk.bold('━'.repeat(60)));
|
|
1048
|
+
console.log(chalk.bold('Detailed Fix Instructions'));
|
|
1049
|
+
console.log(chalk.bold('━'.repeat(60)));
|
|
1050
|
+
console.log('');
|
|
1051
|
+
|
|
1052
|
+
const allIssues = [
|
|
1053
|
+
...results.claudeMd.errors.map(e => ({ ...e, type: 'error' })),
|
|
1054
|
+
...results.claudeFolder.errors.map(e => ({ ...e, type: 'error' })),
|
|
1055
|
+
...results.claudeMd.warnings.map(w => ({ ...w, type: 'warning' })),
|
|
1056
|
+
...results.claudeFolder.warnings.map(w => ({ ...w, type: 'warning' })),
|
|
1057
|
+
];
|
|
1058
|
+
|
|
1059
|
+
for (let i = 0; i < allIssues.length; i++) {
|
|
1060
|
+
const issue = allIssues[i];
|
|
1061
|
+
const icon = issue.type === 'error' ? chalk.red('✗') : chalk.yellow('⚠');
|
|
1062
|
+
console.log(`${i + 1}. ${icon} ${chalk.bold(issue.file)}`);
|
|
1063
|
+
console.log(` Issue: ${issue.message}`);
|
|
1064
|
+
if (issue.fix) {
|
|
1065
|
+
console.log(` ${chalk.green('Fix:')} ${issue.fix}`);
|
|
1066
|
+
}
|
|
1067
|
+
console.log('');
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
/**
|
|
1072
|
+
* Show best practices reference
|
|
1073
|
+
*/
|
|
1074
|
+
function showBestPracticesReference() {
|
|
1075
|
+
console.log('');
|
|
1076
|
+
console.log(chalk.bold('━'.repeat(60)));
|
|
1077
|
+
console.log(chalk.bold('Claude Code Best Practices Reference'));
|
|
1078
|
+
console.log(chalk.bold('━'.repeat(60)));
|
|
1079
|
+
console.log('');
|
|
1080
|
+
|
|
1081
|
+
console.log(chalk.cyan.bold('CLAUDE.md Best Practices:'));
|
|
1082
|
+
console.log('');
|
|
1083
|
+
console.log(` ${chalk.green('✓')} Keep under 60-150 lines (max 300)`);
|
|
1084
|
+
console.log(` ${chalk.green('✓')} Include bash commands Claude can't guess`);
|
|
1085
|
+
console.log(` ${chalk.green('✓')} Document code style rules that differ from defaults`);
|
|
1086
|
+
console.log(` ${chalk.green('✓')} Add testing instructions and preferred runners`);
|
|
1087
|
+
console.log(` ${chalk.green('✓')} Use emphasis (IMPORTANT, YOU MUST) for critical rules`);
|
|
1088
|
+
console.log(` ${chalk.green('✓')} Use @imports to keep files modular`);
|
|
1089
|
+
console.log('');
|
|
1090
|
+
console.log(` ${chalk.red('✗')} Avoid long code blocks (link to docs instead)`);
|
|
1091
|
+
console.log(` ${chalk.red('✗')} Skip obvious/generic advice ("write clean code")`);
|
|
1092
|
+
console.log(` ${chalk.red('✗')} Don't include API docs (link instead)`);
|
|
1093
|
+
console.log(` ${chalk.red('✗')} Remove anything Claude does correctly without instruction`);
|
|
1094
|
+
console.log('');
|
|
1095
|
+
|
|
1096
|
+
console.log(chalk.cyan.bold('.claude/ Folder Structure:'));
|
|
1097
|
+
console.log('');
|
|
1098
|
+
console.log(' .claude/');
|
|
1099
|
+
console.log(' ├── commands/ # Slash commands (.md files)');
|
|
1100
|
+
console.log(' ├── skills/ # Skills with SKILL.md');
|
|
1101
|
+
console.log(' ├── agents/ # Subagent definitions');
|
|
1102
|
+
console.log(' ├── hooks/ # Hook scripts');
|
|
1103
|
+
console.log(' ├── settings.json # Shared settings (git-tracked)');
|
|
1104
|
+
console.log(' └── settings.local.json # Local settings (gitignored)');
|
|
1105
|
+
console.log('');
|
|
1106
|
+
|
|
1107
|
+
console.log(chalk.cyan.bold('Skill/Agent Frontmatter:'));
|
|
1108
|
+
console.log('');
|
|
1109
|
+
console.log(' ---');
|
|
1110
|
+
console.log(' name: my-skill');
|
|
1111
|
+
console.log(' description: What this skill does');
|
|
1112
|
+
console.log(' tools: Read, Grep, Glob, Bash # agents only');
|
|
1113
|
+
console.log(' model: opus # optional');
|
|
1114
|
+
console.log(' ---');
|
|
1115
|
+
console.log('');
|
|
1116
|
+
|
|
1117
|
+
console.log(chalk.dim('Reference: https://code.claude.com/docs/en/best-practices'));
|
|
1118
|
+
console.log('');
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
/**
|
|
1122
|
+
* Run CLAUDE.md enhancement
|
|
1123
|
+
*/
|
|
1124
|
+
async function runEnhancement(cwd) {
|
|
1125
|
+
console.log('');
|
|
1126
|
+
console.log(chalk.bold('━'.repeat(60)));
|
|
1127
|
+
console.log(chalk.bold.cyan('🚀 CLAUDE.md Enhancement Mode'));
|
|
1128
|
+
console.log(chalk.bold('━'.repeat(60)));
|
|
1129
|
+
console.log('');
|
|
1130
|
+
|
|
1131
|
+
const spinner = ora('Detecting tech stack...').start();
|
|
1132
|
+
|
|
1133
|
+
// Detect tech stack
|
|
1134
|
+
const techStack = await detectTechStack(cwd, { silent: true });
|
|
1135
|
+
|
|
1136
|
+
spinner.succeed('Tech stack detected');
|
|
1137
|
+
|
|
1138
|
+
// Show what was detected
|
|
1139
|
+
console.log('');
|
|
1140
|
+
console.log(chalk.cyan('Detected Technologies:'));
|
|
1141
|
+
const detected = techStack._detected || [];
|
|
1142
|
+
for (const item of detected.slice(0, 8)) {
|
|
1143
|
+
console.log(` ${chalk.green('✓')} ${item}`);
|
|
1144
|
+
}
|
|
1145
|
+
if (detected.length > 8) {
|
|
1146
|
+
console.log(chalk.dim(` ... and ${detected.length - 8} more`));
|
|
1147
|
+
}
|
|
1148
|
+
console.log('');
|
|
1149
|
+
|
|
1150
|
+
// Get project name
|
|
1151
|
+
const projectName = techStack.project?.name || basename(cwd);
|
|
1152
|
+
|
|
1153
|
+
// Ask what to enhance
|
|
1154
|
+
const { enhanceMode } = await inquirer.prompt([
|
|
1155
|
+
{
|
|
1156
|
+
type: 'list',
|
|
1157
|
+
name: 'enhanceMode',
|
|
1158
|
+
message: 'What would you like to do?',
|
|
1159
|
+
choices: [
|
|
1160
|
+
{ name: 'Generate full CLAUDE.md from scratch', value: 'full' },
|
|
1161
|
+
{ name: 'Add missing sections to existing CLAUDE.md', value: 'add' },
|
|
1162
|
+
{ name: 'Generate specific section', value: 'section' },
|
|
1163
|
+
{ name: 'Preview generated content', value: 'preview' },
|
|
1164
|
+
{ name: 'Back', value: 'back' },
|
|
1165
|
+
],
|
|
1166
|
+
},
|
|
1167
|
+
]);
|
|
1168
|
+
|
|
1169
|
+
if (enhanceMode === 'back') return;
|
|
1170
|
+
|
|
1171
|
+
if (enhanceMode === 'preview') {
|
|
1172
|
+
console.log('');
|
|
1173
|
+
console.log(chalk.bold('━'.repeat(60)));
|
|
1174
|
+
console.log(chalk.bold('Generated CLAUDE.md Preview:'));
|
|
1175
|
+
console.log(chalk.bold('━'.repeat(60)));
|
|
1176
|
+
console.log('');
|
|
1177
|
+
console.log(ENHANCEMENT_TEMPLATES.fullTemplate(techStack, projectName));
|
|
1178
|
+
console.log(chalk.bold('━'.repeat(60)));
|
|
1179
|
+
console.log('');
|
|
1180
|
+
|
|
1181
|
+
const { savePreview } = await inquirer.prompt([
|
|
1182
|
+
{
|
|
1183
|
+
type: 'confirm',
|
|
1184
|
+
name: 'savePreview',
|
|
1185
|
+
message: 'Save this to CLAUDE.md?',
|
|
1186
|
+
default: false,
|
|
1187
|
+
},
|
|
1188
|
+
]);
|
|
1189
|
+
|
|
1190
|
+
if (savePreview) {
|
|
1191
|
+
await saveClaudeMd(cwd, techStack, projectName, 'full');
|
|
1192
|
+
}
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
if (enhanceMode === 'section') {
|
|
1197
|
+
const { section } = await inquirer.prompt([
|
|
1198
|
+
{
|
|
1199
|
+
type: 'list',
|
|
1200
|
+
name: 'section',
|
|
1201
|
+
message: 'Which section to generate?',
|
|
1202
|
+
choices: [
|
|
1203
|
+
{ name: 'Quick Start (commands to run)', value: 'quickStart' },
|
|
1204
|
+
{ name: 'Tech Stack Table', value: 'techStackTable' },
|
|
1205
|
+
{ name: 'Key Locations', value: 'keyLocations' },
|
|
1206
|
+
{ name: 'Import Patterns', value: 'importPatterns' },
|
|
1207
|
+
{ name: 'Testing Instructions', value: 'testingInstructions' },
|
|
1208
|
+
{ name: 'Deployment Section', value: 'deploymentSection' },
|
|
1209
|
+
{ name: 'Architecture Rules', value: 'architectureRules' },
|
|
1210
|
+
{ name: 'Critical Rules', value: 'criticalRules' },
|
|
1211
|
+
{ name: 'Reference Documentation Links', value: 'referenceLinks' },
|
|
1212
|
+
],
|
|
1213
|
+
},
|
|
1214
|
+
]);
|
|
1215
|
+
|
|
1216
|
+
const content = ENHANCEMENT_TEMPLATES[section](techStack);
|
|
1217
|
+
console.log('');
|
|
1218
|
+
console.log(chalk.bold('Generated Content:'));
|
|
1219
|
+
console.log(chalk.bold('━'.repeat(60)));
|
|
1220
|
+
console.log(content);
|
|
1221
|
+
console.log(chalk.bold('━'.repeat(60)));
|
|
1222
|
+
console.log('');
|
|
1223
|
+
|
|
1224
|
+
const { appendSection } = await inquirer.prompt([
|
|
1225
|
+
{
|
|
1226
|
+
type: 'confirm',
|
|
1227
|
+
name: 'appendSection',
|
|
1228
|
+
message: 'Append this section to CLAUDE.md?',
|
|
1229
|
+
default: true,
|
|
1230
|
+
},
|
|
1231
|
+
]);
|
|
1232
|
+
|
|
1233
|
+
if (appendSection) {
|
|
1234
|
+
await appendToClaudeMd(cwd, content);
|
|
1235
|
+
}
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
if (enhanceMode === 'full') {
|
|
1240
|
+
await saveClaudeMd(cwd, techStack, projectName, 'full');
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
if (enhanceMode === 'add') {
|
|
1245
|
+
await addMissingSections(cwd, techStack, projectName);
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
/**
|
|
1251
|
+
* Save full CLAUDE.md
|
|
1252
|
+
*/
|
|
1253
|
+
async function saveClaudeMd(cwd, techStack, projectName, mode) {
|
|
1254
|
+
const claudeMdPath = join(cwd, 'CLAUDE.md');
|
|
1255
|
+
const backupPath = join(cwd, 'CLAUDE.md.backup');
|
|
1256
|
+
|
|
1257
|
+
// Check for existing file
|
|
1258
|
+
if (existsSync(claudeMdPath)) {
|
|
1259
|
+
const { overwrite } = await inquirer.prompt([
|
|
1260
|
+
{
|
|
1261
|
+
type: 'list',
|
|
1262
|
+
name: 'overwrite',
|
|
1263
|
+
message: 'CLAUDE.md already exists. What would you like to do?',
|
|
1264
|
+
choices: [
|
|
1265
|
+
{ name: 'Backup existing and replace', value: 'backup' },
|
|
1266
|
+
{ name: 'Merge with existing (add missing sections)', value: 'merge' },
|
|
1267
|
+
{ name: 'Cancel', value: 'cancel' },
|
|
1268
|
+
],
|
|
1269
|
+
},
|
|
1270
|
+
]);
|
|
1271
|
+
|
|
1272
|
+
if (overwrite === 'cancel') {
|
|
1273
|
+
console.log(chalk.yellow('Cancelled.'));
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
if (overwrite === 'backup') {
|
|
1278
|
+
const existingContent = readFileSync(claudeMdPath, 'utf8');
|
|
1279
|
+
writeFileSync(backupPath, existingContent, 'utf8');
|
|
1280
|
+
console.log(chalk.dim(`Backed up to ${backupPath}`));
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
if (overwrite === 'merge') {
|
|
1284
|
+
await addMissingSections(cwd, techStack, projectName);
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
// Generate and save
|
|
1290
|
+
const content = ENHANCEMENT_TEMPLATES.fullTemplate(techStack, projectName);
|
|
1291
|
+
writeFileSync(claudeMdPath, content, 'utf8');
|
|
1292
|
+
|
|
1293
|
+
console.log(chalk.green.bold('✓ CLAUDE.md generated successfully!'));
|
|
1294
|
+
console.log(chalk.dim(` Path: ${claudeMdPath}`));
|
|
1295
|
+
console.log('');
|
|
1296
|
+
|
|
1297
|
+
// Show stats
|
|
1298
|
+
const lines = content.split('\n').length;
|
|
1299
|
+
console.log(chalk.cyan('Stats:'));
|
|
1300
|
+
console.log(` Lines: ${lines} (recommended: <60, max: 300)`);
|
|
1301
|
+
console.log(` Size: ${(content.length / 1024).toFixed(1)} KB`);
|
|
1302
|
+
console.log('');
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
/**
|
|
1306
|
+
* Append content to existing CLAUDE.md
|
|
1307
|
+
*/
|
|
1308
|
+
async function appendToClaudeMd(cwd, content) {
|
|
1309
|
+
const claudeMdPath = join(cwd, 'CLAUDE.md');
|
|
1310
|
+
|
|
1311
|
+
let existingContent = '';
|
|
1312
|
+
if (existsSync(claudeMdPath)) {
|
|
1313
|
+
existingContent = readFileSync(claudeMdPath, 'utf8');
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
const newContent = existingContent.trim() + '\n\n' + content;
|
|
1317
|
+
writeFileSync(claudeMdPath, newContent, 'utf8');
|
|
1318
|
+
|
|
1319
|
+
console.log(chalk.green.bold('✓ Section appended to CLAUDE.md'));
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
/**
|
|
1323
|
+
* Add missing sections to existing CLAUDE.md
|
|
1324
|
+
*/
|
|
1325
|
+
async function addMissingSections(cwd, techStack, projectName) {
|
|
1326
|
+
const claudeMdPath = join(cwd, 'CLAUDE.md');
|
|
1327
|
+
|
|
1328
|
+
let existingContent = '';
|
|
1329
|
+
if (existsSync(claudeMdPath)) {
|
|
1330
|
+
existingContent = readFileSync(claudeMdPath, 'utf8');
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
const missingSections = [];
|
|
1334
|
+
|
|
1335
|
+
// Check which sections are missing
|
|
1336
|
+
const sectionChecks = [
|
|
1337
|
+
{ pattern: /##\s*quick\s*start/i, name: 'Quick Start', key: 'quickStart' },
|
|
1338
|
+
{ pattern: /##\s*tech\s*stack/i, name: 'Tech Stack', key: 'techStackTable' },
|
|
1339
|
+
{ pattern: /##\s*key\s*locations/i, name: 'Key Locations', key: 'keyLocations' },
|
|
1340
|
+
{ pattern: /##\s*import\s*patterns/i, name: 'Import Patterns', key: 'importPatterns' },
|
|
1341
|
+
{ pattern: /##\s*testing/i, name: 'Testing', key: 'testingInstructions' },
|
|
1342
|
+
{ pattern: /##\s*deploy/i, name: 'Deployment', key: 'deploymentSection' },
|
|
1343
|
+
{ pattern: /##\s*architecture/i, name: 'Architecture Rules', key: 'architectureRules' },
|
|
1344
|
+
{ pattern: /##\s*critical/i, name: 'Critical Rules', key: 'criticalRules' },
|
|
1345
|
+
{ pattern: /##\s*reference/i, name: 'Reference Links', key: 'referenceLinks' },
|
|
1346
|
+
];
|
|
1347
|
+
|
|
1348
|
+
for (const check of sectionChecks) {
|
|
1349
|
+
if (!check.pattern.test(existingContent)) {
|
|
1350
|
+
missingSections.push(check);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
if (missingSections.length === 0) {
|
|
1355
|
+
console.log(chalk.green.bold('✓ CLAUDE.md already has all recommended sections!'));
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
console.log('');
|
|
1360
|
+
console.log(chalk.yellow(`Found ${missingSections.length} missing sections:`));
|
|
1361
|
+
for (const section of missingSections) {
|
|
1362
|
+
console.log(` ${chalk.dim('-')} ${section.name}`);
|
|
1363
|
+
}
|
|
1364
|
+
console.log('');
|
|
1365
|
+
|
|
1366
|
+
const { sectionsToAdd } = await inquirer.prompt([
|
|
1367
|
+
{
|
|
1368
|
+
type: 'checkbox',
|
|
1369
|
+
name: 'sectionsToAdd',
|
|
1370
|
+
message: 'Select sections to add:',
|
|
1371
|
+
choices: missingSections.map((s) => ({
|
|
1372
|
+
name: s.name,
|
|
1373
|
+
value: s.key,
|
|
1374
|
+
checked: true,
|
|
1375
|
+
})),
|
|
1376
|
+
},
|
|
1377
|
+
]);
|
|
1378
|
+
|
|
1379
|
+
if (sectionsToAdd.length === 0) {
|
|
1380
|
+
console.log(chalk.yellow('No sections selected.'));
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
// Generate and append selected sections
|
|
1385
|
+
let newContent = existingContent.trim();
|
|
1386
|
+
|
|
1387
|
+
for (const key of sectionsToAdd) {
|
|
1388
|
+
const sectionContent = ENHANCEMENT_TEMPLATES[key](techStack);
|
|
1389
|
+
if (sectionContent.trim()) {
|
|
1390
|
+
newContent += '\n\n' + sectionContent;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
writeFileSync(claudeMdPath, newContent, 'utf8');
|
|
1395
|
+
|
|
1396
|
+
console.log(chalk.green.bold(`✓ Added ${sectionsToAdd.length} sections to CLAUDE.md`));
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
/**
|
|
1400
|
+
* Suggest enhancements based on audit results
|
|
1401
|
+
*/
|
|
1402
|
+
function generateEnhancementSuggestions(results, techStack) {
|
|
1403
|
+
const suggestions = [];
|
|
1404
|
+
|
|
1405
|
+
// Check if missing Quick Start
|
|
1406
|
+
const hasQuickStart = results.claudeMd.passed.some((p) =>
|
|
1407
|
+
p.message?.includes('Quick Start')
|
|
1408
|
+
);
|
|
1409
|
+
if (!hasQuickStart) {
|
|
1410
|
+
suggestions.push({
|
|
1411
|
+
type: 'section',
|
|
1412
|
+
name: 'Quick Start',
|
|
1413
|
+
priority: 'high',
|
|
1414
|
+
reason: 'Every CLAUDE.md should have runnable commands',
|
|
1415
|
+
content: ENHANCEMENT_TEMPLATES.quickStart(techStack),
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
// Check if file is too short
|
|
1420
|
+
const veryShort = results.claudeMd.warnings.some((w) =>
|
|
1421
|
+
w.message?.includes('Very little content')
|
|
1422
|
+
);
|
|
1423
|
+
if (veryShort) {
|
|
1424
|
+
suggestions.push({
|
|
1425
|
+
type: 'full',
|
|
1426
|
+
name: 'Full Enhancement',
|
|
1427
|
+
priority: 'high',
|
|
1428
|
+
reason: 'CLAUDE.md has minimal content',
|
|
1429
|
+
});
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
// Suggest reference docs based on tech stack
|
|
1433
|
+
if (techStack.frontend?.framework || techStack.backend?.framework) {
|
|
1434
|
+
suggestions.push({
|
|
1435
|
+
type: 'section',
|
|
1436
|
+
name: 'Reference Links',
|
|
1437
|
+
priority: 'medium',
|
|
1438
|
+
reason: 'Add framework documentation links',
|
|
1439
|
+
content: ENHANCEMENT_TEMPLATES.referenceLinks(techStack),
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
return suggestions;
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
/**
|
|
1447
|
+
* Interactive menu entry point
|
|
1448
|
+
*/
|
|
1449
|
+
export async function showClaudeAuditMenu() {
|
|
1450
|
+
const { mode } = await inquirer.prompt([
|
|
1451
|
+
{
|
|
1452
|
+
type: 'list',
|
|
1453
|
+
name: 'mode',
|
|
1454
|
+
message: 'Claude Code Configuration Audit:',
|
|
1455
|
+
choices: [
|
|
1456
|
+
{ name: 'Run Full Audit', value: 'full' },
|
|
1457
|
+
{ name: 'Audit CLAUDE.md only', value: 'claudemd' },
|
|
1458
|
+
{ name: 'Audit .claude/ folder only', value: 'folder' },
|
|
1459
|
+
{ name: chalk.cyan('✨ Enhance CLAUDE.md (NEW)'), value: 'enhance' },
|
|
1460
|
+
{ name: 'Show Best Practices Reference', value: 'reference' },
|
|
1461
|
+
{ name: 'Back', value: 'back' },
|
|
1462
|
+
],
|
|
1463
|
+
},
|
|
1464
|
+
]);
|
|
1465
|
+
|
|
1466
|
+
if (mode === 'back') return;
|
|
1467
|
+
|
|
1468
|
+
if (mode === 'reference') {
|
|
1469
|
+
showBestPracticesReference();
|
|
1470
|
+
return;
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
if (mode === 'enhance') {
|
|
1474
|
+
await runEnhancement(process.cwd());
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
await runClaudeAudit({ mode });
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
// Export enhancement templates for use by other modules
|
|
1482
|
+
export { ENHANCEMENT_TEMPLATES, runEnhancement };
|