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/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
+ }
@@ -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 %>`)<% } %>