claudenv 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +156 -0
- package/bin/cli.js +205 -0
- package/package.json +60 -0
- package/scaffold/.claude/agents/doc-analyzer.md +70 -0
- package/scaffold/.claude/commands/init-docs.md +69 -0
- package/scaffold/.claude/commands/update-docs.md +49 -0
- package/scaffold/.claude/commands/validate-docs.md +40 -0
- package/scaffold/.claude/skills/doc-generator/SKILL.md +56 -0
- package/scaffold/.claude/skills/doc-generator/scripts/validate.sh +108 -0
- package/scaffold/.claude/skills/doc-generator/templates/detection-patterns.md +76 -0
- package/src/constants.js +237 -0
- package/src/detector.js +346 -0
- package/src/generator.js +259 -0
- package/src/index.js +5 -0
- package/src/prompts.js +267 -0
- package/src/validator.js +206 -0
- package/templates/claude-md.ejs +49 -0
- package/templates/rules-code-style.ejs +64 -0
- package/templates/rules-testing.ejs +64 -0
- package/templates/rules-workflow.ejs +49 -0
- package/templates/settings-json.ejs +28 -0
- package/templates/state-md.ejs +23 -0
package/src/prompts.js
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { input, select, confirm, checkbox } from '@inquirer/prompts';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { FRAMEWORKS_BY_LANGUAGE } from './constants.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Interactive flow for an existing project with detected tech stack.
|
|
8
|
+
* @param {object} detected - Output from detectTechStack()
|
|
9
|
+
* @returns {Promise<object>} Config object for the generator
|
|
10
|
+
*/
|
|
11
|
+
export async function runExistingProjectFlow(detected) {
|
|
12
|
+
console.log('\nDetected tech stack:');
|
|
13
|
+
console.log(` Language: ${detected.language || 'unknown'}`);
|
|
14
|
+
if (detected.framework) console.log(` Framework: ${detected.framework}`);
|
|
15
|
+
if (detected.packageManager) console.log(` Package manager: ${detected.packageManager}`);
|
|
16
|
+
if (detected.testFramework) console.log(` Test framework: ${detected.testFramework}`);
|
|
17
|
+
if (detected.linter) console.log(` Linter: ${detected.linter}`);
|
|
18
|
+
if (detected.formatter) console.log(` Formatter: ${detected.formatter}`);
|
|
19
|
+
if (detected.ci) console.log(` CI/CD: ${detected.ci}`);
|
|
20
|
+
if (detected.monorepo) console.log(` Monorepo: ${detected.monorepo}`);
|
|
21
|
+
console.log();
|
|
22
|
+
|
|
23
|
+
const projectDescription = await input({
|
|
24
|
+
message: 'Brief project description:',
|
|
25
|
+
default: `${detected.framework || detected.language} project`,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const projectType = await select({
|
|
29
|
+
message: 'What type of project is this?',
|
|
30
|
+
choices: [
|
|
31
|
+
{ value: 'web-app', name: 'Web application' },
|
|
32
|
+
{ value: 'api', name: 'API service' },
|
|
33
|
+
{ value: 'cli', name: 'CLI tool' },
|
|
34
|
+
{ value: 'library', name: 'Library / package' },
|
|
35
|
+
{ value: 'monorepo', name: 'Monorepo' },
|
|
36
|
+
{ value: 'other', name: 'Other' },
|
|
37
|
+
],
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const deployment = await select({
|
|
41
|
+
message: 'Deployment target?',
|
|
42
|
+
choices: [
|
|
43
|
+
{ value: 'vercel', name: 'Vercel' },
|
|
44
|
+
{ value: 'aws', name: 'AWS' },
|
|
45
|
+
{ value: 'docker', name: 'Docker / Kubernetes' },
|
|
46
|
+
{ value: 'fly-io', name: 'Fly.io' },
|
|
47
|
+
{ value: 'railway', name: 'Railway' },
|
|
48
|
+
{ value: 'bare-metal', name: 'Bare metal / VPS' },
|
|
49
|
+
{ value: 'none', name: 'Not yet decided' },
|
|
50
|
+
],
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const conventions = await input({
|
|
54
|
+
message: 'Any team conventions not captured in config files? (leave empty to skip)',
|
|
55
|
+
default: '',
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const focusAreas = await input({
|
|
59
|
+
message: 'Areas of the codebase Claude should pay special attention to? (leave empty to skip)',
|
|
60
|
+
default: '',
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const generateRules = await confirm({
|
|
64
|
+
message: 'Generate .claude/rules/ files for code style and testing?',
|
|
65
|
+
default: true,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const generateHooks = await confirm({
|
|
69
|
+
message: 'Generate validation hooks in .claude/settings.json?',
|
|
70
|
+
default: true,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
...detected,
|
|
75
|
+
projectDescription,
|
|
76
|
+
projectType,
|
|
77
|
+
deployment: deployment === 'none' ? null : deployment,
|
|
78
|
+
conventions: conventions || null,
|
|
79
|
+
focusAreas: focusAreas || null,
|
|
80
|
+
generateRules,
|
|
81
|
+
generateHooks,
|
|
82
|
+
rules: buildRules(detected, focusAreas),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Interactive flow for a new/empty project (cold start).
|
|
88
|
+
* @returns {Promise<object>} Config object for the generator
|
|
89
|
+
*/
|
|
90
|
+
export async function runColdStartFlow() {
|
|
91
|
+
console.log('\nNo project files detected. Starting from scratch.\n');
|
|
92
|
+
|
|
93
|
+
const projectDescription = await input({
|
|
94
|
+
message: 'What is this project?',
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const language = await select({
|
|
98
|
+
message: 'Primary language?',
|
|
99
|
+
choices: [
|
|
100
|
+
{ value: 'typescript', name: 'TypeScript' },
|
|
101
|
+
{ value: 'javascript', name: 'JavaScript' },
|
|
102
|
+
{ value: 'python', name: 'Python' },
|
|
103
|
+
{ value: 'go', name: 'Go' },
|
|
104
|
+
{ value: 'rust', name: 'Rust' },
|
|
105
|
+
{ value: 'ruby', name: 'Ruby' },
|
|
106
|
+
{ value: 'php', name: 'PHP' },
|
|
107
|
+
{ value: 'java', name: 'Java' },
|
|
108
|
+
{ value: 'kotlin', name: 'Kotlin' },
|
|
109
|
+
{ value: 'csharp', name: 'C#' },
|
|
110
|
+
],
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const frameworks = FRAMEWORKS_BY_LANGUAGE[language] || ['None'];
|
|
114
|
+
const framework = await select({
|
|
115
|
+
message: 'Framework?',
|
|
116
|
+
choices: frameworks.map((f) => ({ value: f.toLowerCase().replace(/\s+/g, '-'), name: f })),
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const projectType = await select({
|
|
120
|
+
message: 'Project type?',
|
|
121
|
+
choices: [
|
|
122
|
+
{ value: 'web-app', name: 'Web application' },
|
|
123
|
+
{ value: 'api', name: 'API service' },
|
|
124
|
+
{ value: 'cli', name: 'CLI tool' },
|
|
125
|
+
{ value: 'library', name: 'Library / package' },
|
|
126
|
+
{ value: 'other', name: 'Other' },
|
|
127
|
+
],
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const deployment = await select({
|
|
131
|
+
message: 'Deployment target?',
|
|
132
|
+
choices: [
|
|
133
|
+
{ value: 'vercel', name: 'Vercel' },
|
|
134
|
+
{ value: 'aws', name: 'AWS' },
|
|
135
|
+
{ value: 'docker', name: 'Docker / Kubernetes' },
|
|
136
|
+
{ value: 'fly-io', name: 'Fly.io' },
|
|
137
|
+
{ value: 'railway', name: 'Railway' },
|
|
138
|
+
{ value: 'bare-metal', name: 'Bare metal / VPS' },
|
|
139
|
+
{ value: 'none', name: 'Not yet decided' },
|
|
140
|
+
],
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const conventions = await input({
|
|
144
|
+
message: 'Any coding conventions to enforce? (leave empty to skip)',
|
|
145
|
+
default: '',
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const generateRules = await confirm({
|
|
149
|
+
message: 'Generate .claude/rules/ files?',
|
|
150
|
+
default: true,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const generateHooks = await confirm({
|
|
154
|
+
message: 'Generate validation hooks?',
|
|
155
|
+
default: true,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
language,
|
|
160
|
+
runtime: inferRuntime(language),
|
|
161
|
+
framework: framework === 'none' ? null : framework,
|
|
162
|
+
packageManager: inferPackageManager(language),
|
|
163
|
+
testFramework: null,
|
|
164
|
+
linter: null,
|
|
165
|
+
formatter: null,
|
|
166
|
+
ci: null,
|
|
167
|
+
containerized: false,
|
|
168
|
+
monorepo: null,
|
|
169
|
+
projectDescription,
|
|
170
|
+
projectType,
|
|
171
|
+
deployment: deployment === 'none' ? null : deployment,
|
|
172
|
+
conventions: conventions || null,
|
|
173
|
+
generateRules,
|
|
174
|
+
generateHooks,
|
|
175
|
+
suggestedDevCmd: null,
|
|
176
|
+
suggestedBuildCmd: null,
|
|
177
|
+
suggestedTestCmd: null,
|
|
178
|
+
rules: [],
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function inferRuntime(language) {
|
|
183
|
+
const runtimeMap = {
|
|
184
|
+
typescript: 'node',
|
|
185
|
+
javascript: 'node',
|
|
186
|
+
python: 'python',
|
|
187
|
+
go: 'go',
|
|
188
|
+
rust: 'rust',
|
|
189
|
+
ruby: 'ruby',
|
|
190
|
+
php: 'php',
|
|
191
|
+
java: 'jvm',
|
|
192
|
+
kotlin: 'jvm',
|
|
193
|
+
csharp: 'dotnet',
|
|
194
|
+
};
|
|
195
|
+
return runtimeMap[language] || language;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function inferPackageManager(language) {
|
|
199
|
+
const pmMap = {
|
|
200
|
+
typescript: 'npm',
|
|
201
|
+
javascript: 'npm',
|
|
202
|
+
python: 'pip',
|
|
203
|
+
ruby: 'bundler',
|
|
204
|
+
php: 'composer',
|
|
205
|
+
rust: 'cargo',
|
|
206
|
+
go: 'go-modules',
|
|
207
|
+
};
|
|
208
|
+
return pmMap[language] || null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Build config from auto-detected values without prompting (--yes mode).
|
|
213
|
+
* @param {object} detected - Output from detectTechStack()
|
|
214
|
+
* @param {string} projectDir - Project root directory
|
|
215
|
+
* @returns {Promise<object>} Config object for the generator
|
|
216
|
+
*/
|
|
217
|
+
export async function buildDefaultConfig(detected, projectDir) {
|
|
218
|
+
let projectDescription = `${detected.framework || detected.language || 'Unknown'} project`;
|
|
219
|
+
|
|
220
|
+
// Try to get a better name from package.json
|
|
221
|
+
try {
|
|
222
|
+
const pkgRaw = await readFile(join(projectDir, 'package.json'), 'utf-8');
|
|
223
|
+
const pkg = JSON.parse(pkgRaw);
|
|
224
|
+
if (pkg.description) {
|
|
225
|
+
projectDescription = pkg.description;
|
|
226
|
+
} else if (pkg.name) {
|
|
227
|
+
projectDescription = `${pkg.name} — ${detected.framework || detected.language} project`;
|
|
228
|
+
}
|
|
229
|
+
} catch {
|
|
230
|
+
// No package.json or parse error
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
...detected,
|
|
235
|
+
projectDescription,
|
|
236
|
+
projectType: null,
|
|
237
|
+
deployment: null,
|
|
238
|
+
conventions: null,
|
|
239
|
+
focusAreas: null,
|
|
240
|
+
generateRules: true,
|
|
241
|
+
generateHooks: true,
|
|
242
|
+
rules: buildRules(detected, null),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function buildRules(detected, focusAreas) {
|
|
247
|
+
const rules = [];
|
|
248
|
+
|
|
249
|
+
if (detected.framework === 'next.js') {
|
|
250
|
+
rules.push('Use server components by default; add \'use client\' only when needed');
|
|
251
|
+
rules.push('Prefer server actions for mutations over API routes');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (detected.framework === 'django') {
|
|
255
|
+
rules.push('NEVER modify migration files after they have been committed');
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (detected.linter) {
|
|
259
|
+
rules.push(`Run \`${detected.suggestedLintCmd || detected.linter}\` before committing`);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (focusAreas) {
|
|
263
|
+
rules.push(`Pay special attention to: ${focusAreas}`);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return rules;
|
|
267
|
+
}
|
package/src/validator.js
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { readFile, access } from 'node:fs/promises';
|
|
2
|
+
import { join, dirname } from 'node:path';
|
|
3
|
+
import { REQUIRED_SECTIONS } from './constants.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Validate a CLAUDE.md file for required structure and content.
|
|
7
|
+
* @param {string} filePath - Absolute path to CLAUDE.md
|
|
8
|
+
* @returns {Promise<{valid: boolean, errors: string[], warnings: string[]}>}
|
|
9
|
+
*/
|
|
10
|
+
export async function validateClaudeMd(filePath) {
|
|
11
|
+
const errors = [];
|
|
12
|
+
const warnings = [];
|
|
13
|
+
|
|
14
|
+
// Check file exists and is non-empty
|
|
15
|
+
let content;
|
|
16
|
+
try {
|
|
17
|
+
content = await readFile(filePath, 'utf-8');
|
|
18
|
+
} catch {
|
|
19
|
+
errors.push(`File not found: ${filePath}`);
|
|
20
|
+
return { valid: false, errors, warnings };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!content.trim()) {
|
|
24
|
+
errors.push('CLAUDE.md is empty');
|
|
25
|
+
return { valid: false, errors, warnings };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Check required sections
|
|
29
|
+
for (const section of REQUIRED_SECTIONS) {
|
|
30
|
+
if (!content.includes(section)) {
|
|
31
|
+
errors.push(`Missing required section: ${section}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Check for a top-level heading
|
|
36
|
+
if (!content.match(/^#\s+\S/m)) {
|
|
37
|
+
warnings.push('No top-level heading (# Title) found');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Check that ## Commands section has at least one command
|
|
41
|
+
const commandsMatch = content.match(/## Commands\n([\s\S]*?)(?=\n## |\n# |$)/);
|
|
42
|
+
if (commandsMatch) {
|
|
43
|
+
const commandsContent = commandsMatch[1].trim();
|
|
44
|
+
if (!commandsContent.includes('`')) {
|
|
45
|
+
warnings.push('## Commands section has no inline code (expected command examples)');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Check @imports resolve to real files
|
|
50
|
+
const importErrors = await validateImports(content, dirname(filePath));
|
|
51
|
+
errors.push(...importErrors);
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
valid: errors.length === 0,
|
|
55
|
+
errors,
|
|
56
|
+
warnings,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Validate the .claude/ directory structure.
|
|
62
|
+
* @param {string} projectDir - Project root directory
|
|
63
|
+
* @returns {Promise<{valid: boolean, errors: string[], warnings: string[]}>}
|
|
64
|
+
*/
|
|
65
|
+
export async function validateStructure(projectDir) {
|
|
66
|
+
const errors = [];
|
|
67
|
+
const warnings = [];
|
|
68
|
+
|
|
69
|
+
// Check CLAUDE.md exists
|
|
70
|
+
const claudeMdPath = join(projectDir, 'CLAUDE.md');
|
|
71
|
+
try {
|
|
72
|
+
await access(claudeMdPath);
|
|
73
|
+
} catch {
|
|
74
|
+
errors.push('CLAUDE.md not found in project root');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Check .claude directory structure
|
|
78
|
+
const optionalPaths = [
|
|
79
|
+
{ path: '.claude/commands', label: 'commands directory' },
|
|
80
|
+
{ path: '.claude/rules', label: 'rules directory' },
|
|
81
|
+
{ path: '.claude/settings.json', label: 'settings.json' },
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
for (const { path, label } of optionalPaths) {
|
|
85
|
+
try {
|
|
86
|
+
await access(join(projectDir, path));
|
|
87
|
+
} catch {
|
|
88
|
+
warnings.push(`Optional ${label} not found at ${path}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// If settings.json exists, validate it's valid JSON
|
|
93
|
+
try {
|
|
94
|
+
const settingsRaw = await readFile(join(projectDir, '.claude/settings.json'), 'utf-8');
|
|
95
|
+
JSON.parse(settingsRaw);
|
|
96
|
+
} catch (err) {
|
|
97
|
+
if (err.code !== 'ENOENT') {
|
|
98
|
+
errors.push(`.claude/settings.json is not valid JSON: ${err.message}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
valid: errors.length === 0,
|
|
104
|
+
errors,
|
|
105
|
+
warnings,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Cross-reference documentation with actual project files.
|
|
111
|
+
* @param {string} projectDir - Project root directory
|
|
112
|
+
* @returns {Promise<{valid: boolean, errors: string[], warnings: string[]}>}
|
|
113
|
+
*/
|
|
114
|
+
export async function crossReferenceCheck(projectDir) {
|
|
115
|
+
const errors = [];
|
|
116
|
+
const warnings = [];
|
|
117
|
+
|
|
118
|
+
const claudeMdPath = join(projectDir, 'CLAUDE.md');
|
|
119
|
+
let content;
|
|
120
|
+
try {
|
|
121
|
+
content = await readFile(claudeMdPath, 'utf-8');
|
|
122
|
+
} catch {
|
|
123
|
+
errors.push('CLAUDE.md not found — cannot cross-reference');
|
|
124
|
+
return { valid: false, errors, warnings };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Check that directories mentioned in Architecture section exist
|
|
128
|
+
const archMatch = content.match(/## Architecture\n([\s\S]*?)(?=\n## |\n# |$)/);
|
|
129
|
+
if (archMatch) {
|
|
130
|
+
const archContent = archMatch[1];
|
|
131
|
+
const dirRefs = archContent.match(/`([^`]+\/)`/g);
|
|
132
|
+
if (dirRefs) {
|
|
133
|
+
for (const ref of dirRefs) {
|
|
134
|
+
const dirPath = ref.replace(/`/g, '');
|
|
135
|
+
try {
|
|
136
|
+
await access(join(projectDir, dirPath));
|
|
137
|
+
} catch {
|
|
138
|
+
warnings.push(`Architecture references directory "${dirPath}" which does not exist`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Check that referenced scripts in Commands section exist in package.json
|
|
145
|
+
const pkgJsonPath = join(projectDir, 'package.json');
|
|
146
|
+
try {
|
|
147
|
+
const pkgRaw = await readFile(pkgJsonPath, 'utf-8');
|
|
148
|
+
const pkg = JSON.parse(pkgRaw);
|
|
149
|
+
const scripts = pkg.scripts || {};
|
|
150
|
+
|
|
151
|
+
const cmdMatch = content.match(/## Commands\n([\s\S]*?)(?=\n## |\n# |$)/);
|
|
152
|
+
if (cmdMatch) {
|
|
153
|
+
const cmdContent = cmdMatch[1];
|
|
154
|
+
// Look for `npm run <script>` or `pnpm <script>` or `yarn <script>` patterns
|
|
155
|
+
const scriptRefs = cmdContent.matchAll(/(?:npm run|pnpm|yarn|bun(?:x| run)?)\s+([a-z][\w:-]*)/g);
|
|
156
|
+
for (const match of scriptRefs) {
|
|
157
|
+
const scriptName = match[1];
|
|
158
|
+
if (!scripts[scriptName]) {
|
|
159
|
+
warnings.push(`Commands section references script "${scriptName}" not found in package.json`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
} catch {
|
|
164
|
+
// No package.json or parse error — skip script checks
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
valid: errors.length === 0,
|
|
169
|
+
errors,
|
|
170
|
+
warnings,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Validate @import references in CLAUDE.md content.
|
|
176
|
+
*/
|
|
177
|
+
async function validateImports(content, baseDir) {
|
|
178
|
+
const errors = [];
|
|
179
|
+
const lines = content.split('\n');
|
|
180
|
+
let inCodeBlock = false;
|
|
181
|
+
|
|
182
|
+
for (const line of lines) {
|
|
183
|
+
if (line.trimStart().startsWith('```')) {
|
|
184
|
+
inCodeBlock = !inCodeBlock;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (inCodeBlock) continue;
|
|
188
|
+
|
|
189
|
+
// Match bare @path references (not inside code blocks, not email addresses)
|
|
190
|
+
const importMatch = line.match(/^@([^\s@]+)$/);
|
|
191
|
+
if (importMatch) {
|
|
192
|
+
const importPath = importMatch[1];
|
|
193
|
+
const resolvedPath = importPath.startsWith('~/')
|
|
194
|
+
? join(process.env.HOME || '', importPath.slice(2))
|
|
195
|
+
: join(baseDir, importPath);
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
await access(resolvedPath);
|
|
199
|
+
} catch {
|
|
200
|
+
errors.push(`@import reference "${importPath}" does not resolve to a file`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return errors;
|
|
206
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
## Project overview
|
|
4
|
+
<%= projectDescription || `${language || 'Unknown'} project` %><% if (framework) { %> built with <%= framework %><% } %><% if (runtime && runtime !== language) { %> on <%= runtime %><% } %>.
|
|
5
|
+
<% if (deployment) { %>Deployed to <%= deployment %>.<% } %>
|
|
6
|
+
|
|
7
|
+
## Commands
|
|
8
|
+
<% if (commands.dev) { %>- `<%= commands.dev %>` — Start development server
|
|
9
|
+
<% } -%>
|
|
10
|
+
<% if (commands.build) { %>- `<%= commands.build %>` — Production build
|
|
11
|
+
<% } -%>
|
|
12
|
+
<% if (commands.test) { %>- `<%= commands.test %>` — Run tests
|
|
13
|
+
<% } -%>
|
|
14
|
+
<% if (commands.lint) { %>- `<%= commands.lint %>` — Run linter
|
|
15
|
+
<% } -%>
|
|
16
|
+
<% if (commands.migrate) { %>- `<%= commands.migrate %>` — Run database migrations
|
|
17
|
+
<% } -%>
|
|
18
|
+
<% if (commands.format) { %>- `<%= commands.format %>` — Format code
|
|
19
|
+
<% } -%>
|
|
20
|
+
<% if (additionalCommands && additionalCommands.length > 0) { -%>
|
|
21
|
+
<% for (const cmd of additionalCommands) { %>- `<%= cmd.command %>` — <%= cmd.description %>
|
|
22
|
+
<% } -%>
|
|
23
|
+
<% } %>
|
|
24
|
+
## Architecture
|
|
25
|
+
<% if (directories && directories.length > 0) { -%>
|
|
26
|
+
<% for (const dir of directories) { %>- `<%= dir.path %>` — <%= dir.description %>
|
|
27
|
+
<% } -%>
|
|
28
|
+
<% } else { %>Describe the key directories and their purposes here.
|
|
29
|
+
<% } %>
|
|
30
|
+
## Conventions
|
|
31
|
+
<% if (generateRules) { %>@.claude/rules/code-style.md
|
|
32
|
+
@.claude/rules/testing.md
|
|
33
|
+
<% } -%>
|
|
34
|
+
<% if (conventions) { %><%= conventions %>
|
|
35
|
+
<% } %>
|
|
36
|
+
## Workflow
|
|
37
|
+
<% if (generateRules) { %>@.claude/rules/workflow.md
|
|
38
|
+
<% } else { %>- Use plan mode for complex tasks; read code before editing
|
|
39
|
+
- Use `/compact` when context fills up, `/clear` between unrelated tasks
|
|
40
|
+
- NEVER create .md files (README, CONTRIBUTING, CHANGELOG, etc.) unless explicitly asked
|
|
41
|
+
<% } %>
|
|
42
|
+
## Memory
|
|
43
|
+
Project state is tracked in `_state.md` at the project root. Review it at session start, update after significant decisions.
|
|
44
|
+
<% if (rules && rules.length > 0) { %>
|
|
45
|
+
## Rules
|
|
46
|
+
<% for (const rule of rules) { %>- <%- rule %>
|
|
47
|
+
<% } -%>
|
|
48
|
+
<% } %>
|
|
49
|
+
- NEVER create documentation files (.md) unless the user explicitly requests it
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<% if (pathGlobs && pathGlobs.length > 0) { %>---
|
|
2
|
+
paths:
|
|
3
|
+
<% for (const g of pathGlobs) { %> - "<%= g %>"
|
|
4
|
+
<% } %>---
|
|
5
|
+
<% } %># Code Style Rules
|
|
6
|
+
|
|
7
|
+
<% if (language === 'typescript' || language === 'javascript') { %>## TypeScript/JavaScript
|
|
8
|
+
- Use <%= formatter || 'consistent' %> formatting<% if (linter) { %> with <%= linter %> for linting<% } %>
|
|
9
|
+
<% if (language === 'typescript') { %>- Prefer `interface` over `type` for object shapes that may be extended
|
|
10
|
+
- Use strict TypeScript (`"strict": true`) — avoid `any`, prefer `unknown`
|
|
11
|
+
<% } -%>
|
|
12
|
+
- Use named exports over default exports
|
|
13
|
+
- Prefer `const` over `let`; never use `var`
|
|
14
|
+
- Use template literals for string interpolation
|
|
15
|
+
<% if (framework === 'next.js') { %>
|
|
16
|
+
## Next.js Conventions
|
|
17
|
+
- Use server components by default; add `'use client'` only when needed
|
|
18
|
+
- Prefer server actions for mutations over API routes
|
|
19
|
+
- Colocate page-specific components in the route directory
|
|
20
|
+
<% } -%>
|
|
21
|
+
<% if (framework === 'vite') { %>
|
|
22
|
+
## Component Conventions
|
|
23
|
+
- One component per file
|
|
24
|
+
- Name files after the component they export
|
|
25
|
+
<% } -%>
|
|
26
|
+
<% } -%>
|
|
27
|
+
|
|
28
|
+
<% if (language === 'python') { %>## Python
|
|
29
|
+
<% if (linter === 'ruff') { %>- Use Ruff for linting and formatting
|
|
30
|
+
<% } else { %>- Follow PEP 8 style guidelines
|
|
31
|
+
<% } -%>
|
|
32
|
+
- Use type hints for all function signatures
|
|
33
|
+
- Prefer f-strings for string formatting
|
|
34
|
+
- Use `pathlib.Path` over `os.path`
|
|
35
|
+
<% if (framework === 'django') { %>
|
|
36
|
+
## Django Conventions
|
|
37
|
+
- Fat models, thin views
|
|
38
|
+
- Use class-based views for CRUD operations
|
|
39
|
+
- Keep business logic in model methods or services
|
|
40
|
+
<% } -%>
|
|
41
|
+
<% if (framework === 'fastapi') { %>
|
|
42
|
+
## FastAPI Conventions
|
|
43
|
+
- Use Pydantic models for request/response schemas
|
|
44
|
+
- Group routes by resource in separate routers
|
|
45
|
+
<% } -%>
|
|
46
|
+
<% } -%>
|
|
47
|
+
|
|
48
|
+
<% if (language === 'go') { %>## Go
|
|
49
|
+
- Follow Effective Go and Go Code Review Comments
|
|
50
|
+
- Use `errors.Is` / `errors.As` for error comparison
|
|
51
|
+
- Accept interfaces, return structs
|
|
52
|
+
- Keep packages small and focused
|
|
53
|
+
<% } -%>
|
|
54
|
+
|
|
55
|
+
<% if (language === 'rust') { %>## Rust
|
|
56
|
+
- Use `clippy` for linting
|
|
57
|
+
- Prefer `Result` over `panic!` for recoverable errors
|
|
58
|
+
- Use `thiserror` for library error types, `anyhow` for applications
|
|
59
|
+
<% } -%>
|
|
60
|
+
|
|
61
|
+
## General
|
|
62
|
+
- Keep functions focused — one responsibility per function
|
|
63
|
+
- Name variables and functions descriptively; avoid abbreviations
|
|
64
|
+
<% if (conventions) { %><%= conventions %><% } %>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<% if (testPathGlobs && testPathGlobs.length > 0) { %>---
|
|
2
|
+
paths:
|
|
3
|
+
<% for (const g of testPathGlobs) { %> - "<%= g %>"
|
|
4
|
+
<% } %>---
|
|
5
|
+
<% } %># Testing Rules
|
|
6
|
+
|
|
7
|
+
## Test Framework
|
|
8
|
+
<% if (testFramework) { %>Using **<%= testFramework %>** for testing.
|
|
9
|
+
<% } else { %>No test framework detected. Configure one before writing tests.
|
|
10
|
+
<% } %>
|
|
11
|
+
|
|
12
|
+
## Commands
|
|
13
|
+
<% if (commands.test) { %>- Run all tests: `<%= commands.test %>`
|
|
14
|
+
<% } -%>
|
|
15
|
+
<% if (commands.testSingle) { %>- Run single test: `<%= commands.testSingle %>`
|
|
16
|
+
<% } -%>
|
|
17
|
+
<% if (commands.testWatch) { %>- Watch mode: `<%= commands.testWatch %>`
|
|
18
|
+
<% } -%>
|
|
19
|
+
<% if (commands.testCoverage) { %>- Coverage: `<%= commands.testCoverage %>`
|
|
20
|
+
<% } %>
|
|
21
|
+
|
|
22
|
+
<% if (testFramework === 'vitest' || testFramework === 'jest') { %>## Patterns
|
|
23
|
+
- Place tests adjacent to source files as `*.test.ts` or in `__tests__/` directories
|
|
24
|
+
- Use `describe` blocks to group related tests
|
|
25
|
+
- Use `it` (not `test`) for test cases
|
|
26
|
+
- Name tests descriptively: `it('should return 404 when user not found')`
|
|
27
|
+
- Prefer `toEqual` for objects, `toBe` for primitives
|
|
28
|
+
- Use `vi.mock()` / `jest.mock()` for module mocking
|
|
29
|
+
<% } -%>
|
|
30
|
+
|
|
31
|
+
<% if (testFramework === 'pytest') { %>## Patterns
|
|
32
|
+
- Place tests in `tests/` directory mirroring `src/` structure
|
|
33
|
+
- Name test files `test_*.py` and test functions `test_*`
|
|
34
|
+
- Use fixtures for shared setup (`conftest.py`)
|
|
35
|
+
- Use `parametrize` for testing multiple inputs
|
|
36
|
+
- Prefer `assert` statements over `self.assert*` methods
|
|
37
|
+
<% } -%>
|
|
38
|
+
|
|
39
|
+
<% if (testFramework === 'rspec') { %>## Patterns
|
|
40
|
+
- Place specs in `spec/` mirroring `app/` structure
|
|
41
|
+
- Use `let` and `before` for shared setup
|
|
42
|
+
- Use `context` blocks for conditional scenarios
|
|
43
|
+
- Use factories (FactoryBot) over fixtures
|
|
44
|
+
<% } -%>
|
|
45
|
+
|
|
46
|
+
<% if (language === 'go') { %>## Patterns
|
|
47
|
+
- Place tests in the same package as the code under test
|
|
48
|
+
- Name test files `*_test.go`
|
|
49
|
+
- Use table-driven tests for multiple cases
|
|
50
|
+
- Use `testify/assert` or standard `testing.T` methods
|
|
51
|
+
<% } -%>
|
|
52
|
+
|
|
53
|
+
<% if (language === 'rust') { %>## Patterns
|
|
54
|
+
- Place unit tests in `#[cfg(test)] mod tests` at the bottom of each file
|
|
55
|
+
- Place integration tests in `tests/` directory
|
|
56
|
+
- Use `#[test]` attribute for test functions
|
|
57
|
+
- Use `assert_eq!`, `assert_ne!`, `assert!` macros
|
|
58
|
+
<% } -%>
|
|
59
|
+
|
|
60
|
+
## Guidelines
|
|
61
|
+
- Every bug fix should include a regression test
|
|
62
|
+
- Test behavior, not implementation details
|
|
63
|
+
- Keep tests independent — no shared mutable state between tests
|
|
64
|
+
<% if (testConventions) { %><%= testConventions %><% } %>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
<% if (pathGlobs && pathGlobs.length > 0) { -%>
|
|
3
|
+
paths:
|
|
4
|
+
<% for (const glob of pathGlobs) { %> - "<%= glob %>"
|
|
5
|
+
<% } -%>
|
|
6
|
+
<% } -%>
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Workflow Best Practices
|
|
10
|
+
|
|
11
|
+
## Planning
|
|
12
|
+
- Use **plan mode** for complex tasks, unclear requirements, or multi-file changes
|
|
13
|
+
- Break large changes into small, reviewable steps
|
|
14
|
+
- Read existing code before proposing modifications — understand context first
|
|
15
|
+
|
|
16
|
+
## Context Management
|
|
17
|
+
- Use `/compact` when the conversation gets long and context fills up
|
|
18
|
+
- Use `/clear` when switching between unrelated tasks
|
|
19
|
+
- Keep CLAUDE.md concise (< 60 lines) — move details into @imported rule files
|
|
20
|
+
|
|
21
|
+
## Subagents
|
|
22
|
+
- Delegate research and exploration to subagents when scanning large codebases
|
|
23
|
+
- Use subagents for parallel investigation of independent questions
|
|
24
|
+
- Keep the main conversation focused on implementation decisions
|
|
25
|
+
|
|
26
|
+
## Memory
|
|
27
|
+
- Use `_state.md` in the project root to track decisions, current focus, and known issues
|
|
28
|
+
- Update `_state.md` when making significant architectural decisions
|
|
29
|
+
- Review `_state.md` at the start of each session to restore context
|
|
30
|
+
|
|
31
|
+
## Commits and Git
|
|
32
|
+
- NEVER commit unless the user explicitly asks
|
|
33
|
+
- NEVER push to remote without explicit permission
|
|
34
|
+
- NEVER amend published commits or force-push without explicit permission
|
|
35
|
+
- Write clear, descriptive commit messages explaining "why" not "what"
|
|
36
|
+
|
|
37
|
+
## File Discipline
|
|
38
|
+
- NEVER create documentation files (.md) unless explicitly requested by the user
|
|
39
|
+
- This includes: README.md, CONTRIBUTING.md, CHANGELOG.md, docs/*.md, and any other markdown files
|
|
40
|
+
- Only modify or create files that are directly needed for the current task
|
|
41
|
+
- Prefer editing existing files over creating new ones
|
|
42
|
+
- Do not add comments, docstrings, or type annotations to code you did not change
|
|
43
|
+
|
|
44
|
+
## Code Changes
|
|
45
|
+
- Always read a file before editing it
|
|
46
|
+
- Make minimal, focused changes — do not refactor surrounding code unless asked
|
|
47
|
+
- Do not add error handling, validation, or abstractions for hypothetical scenarios
|
|
48
|
+
- Do not add features or "improvements" beyond what was requested
|
|
49
|
+
- Run linters and tests after making changes<% if (commands.lint) { %> (`<%= commands.lint %>`)<% } %><% if (commands.test) { %> (`<%= commands.test %>`)<% } %>
|