dotclaudemd 0.1.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 +108 -0
- package/dist/browse-7V4CRGTH.js +96 -0
- package/dist/browse-7V4CRGTH.js.map +1 -0
- package/dist/chunk-2D66CC54.js +17 -0
- package/dist/chunk-2D66CC54.js.map +1 -0
- package/dist/chunk-3ERFQLAD.js +145 -0
- package/dist/chunk-3ERFQLAD.js.map +1 -0
- package/dist/chunk-3R6PUA3E.js +79 -0
- package/dist/chunk-3R6PUA3E.js.map +1 -0
- package/dist/chunk-3WTPUEHL.js +42 -0
- package/dist/chunk-3WTPUEHL.js.map +1 -0
- package/dist/chunk-YHVOBZLV.js +28 -0
- package/dist/chunk-YHVOBZLV.js.map +1 -0
- package/dist/cli.js +40 -0
- package/dist/cli.js.map +1 -0
- package/dist/doctor-4B7J2EH3.js +318 -0
- package/dist/doctor-4B7J2EH3.js.map +1 -0
- package/dist/init-GLWLFVHN.js +287 -0
- package/dist/init-GLWLFVHN.js.map +1 -0
- package/dist/lint-W7ZIDPL7.js +281 -0
- package/dist/lint-W7ZIDPL7.js.map +1 -0
- package/package.json +50 -0
- package/templates/_global/default.md +52 -0
- package/templates/go/go-api.md +55 -0
- package/templates/javascript/express-mongodb.md +56 -0
- package/templates/javascript/mern-stack.md +58 -0
- package/templates/javascript/nextjs-prisma-tailwind.md +58 -0
- package/templates/javascript/nextjs-typescript.md +57 -0
- package/templates/javascript/node-cli-tool.md +55 -0
- package/templates/javascript/react-vite.md +55 -0
- package/templates/python/django-rest.md +55 -0
- package/templates/python/fastapi-sqlalchemy.md +54 -0
- package/templates/python/flask-basic.md +51 -0
- package/templates/rust/cargo-workspace.md +50 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/init.ts","../src/core/project-detector.ts"],"sourcesContent":["import { join, dirname } from \"node:path\";\nimport { mkdirSync } from \"node:fs\";\nimport { select, input, confirm } from \"@inquirer/prompts\";\nimport { renderTemplate } from \"../core/template-engine.js\";\nimport {\n listTemplates,\n suggestTemplates,\n} from \"../core/template-registry.js\";\nimport { detectStack } from \"../core/project-detector.js\";\nimport type { Template, FsDeps } from \"../types.js\";\nimport { defaultFsDeps } from \"../utils/fs.js\";\nimport { findProjectRoot, findClaudeMd } from \"../utils/paths.js\";\nimport * as logger from \"../utils/logger.js\";\nimport { createSpinner } from \"../utils/ui.js\";\n\nexport interface InitOptions {\n stack?: string;\n global?: boolean;\n noInteractive?: boolean;\n force?: boolean;\n}\n\nexport async function initCommand(\n options: InitOptions = {},\n deps: FsDeps = defaultFsDeps,\n promptFn?: typeof promptForVariables,\n): Promise<void> {\n const projectRoot = options.global\n ? join(process.env.HOME ?? \"~\", \".claude\")\n : findProjectRoot();\n\n const outputPath = options.global\n ? join(process.env.HOME ?? \"~\", \".claude\", \"CLAUDE.md\")\n : join(projectRoot, \"CLAUDE.md\");\n\n // Check for existing file\n const existing = findClaudeMd(projectRoot);\n if (existing && !options.force) {\n if (options.noInteractive) {\n logger.error(\n `CLAUDE.md already exists at ${existing}. Use --force to overwrite.`,\n );\n return;\n }\n const overwrite = await confirm({\n message: `CLAUDE.md already exists at ${existing}. Overwrite?`,\n default: false,\n });\n if (!overwrite) {\n logger.info(\"Cancelled.\");\n return;\n }\n }\n\n // Detect stack\n const spinner = createSpinner(\"Detecting project stack...\");\n spinner.start();\n const detected = options.stack\n ? null\n : await detectStack(projectRoot, deps);\n spinner.stop();\n\n if (detected) {\n logger.info(\n `Detected: ${detected.language}${detected.framework ? ` / ${detected.framework}` : \"\"}${detected.packageManager ? ` (${detected.packageManager})` : \"\"}`,\n );\n }\n\n // Find matching templates\n let template: Template;\n const allTemplates = await listTemplates();\n\n if (options.stack) {\n // Use specified stack name directly\n const match = allTemplates.find(\n (t) => t.frontmatter.name === options.stack,\n );\n if (!match) {\n logger.error(`No template found for stack: ${options.stack}`);\n logger.info(\n `Available: ${allTemplates.map((t) => t.frontmatter.name).join(\", \")}`,\n );\n return;\n }\n template = match;\n } else if (detected) {\n const suggestions = await suggestTemplates(detected);\n if (suggestions.length === 1 || options.noInteractive) {\n template = suggestions[0] ?? allTemplates[0];\n } else if (suggestions.length > 1) {\n template = await selectTemplate(suggestions);\n } else {\n template = await selectTemplate(allTemplates);\n }\n } else {\n if (options.noInteractive) {\n template = allTemplates.find((t) => t.frontmatter.name === \"default\") ?? allTemplates[0];\n } else {\n template = await selectTemplate(allTemplates);\n }\n }\n\n logger.info(`Using template: ${template.frontmatter.displayName}`);\n\n // Prompt for variables\n const variables: Record<string, string> = {};\n const doPrompt = promptFn ?? promptForVariables;\n\n if (template.frontmatter.variables.length > 0 && !options.noInteractive) {\n await doPrompt(template, variables, detected);\n } else {\n // Use defaults\n for (const v of template.frontmatter.variables) {\n if (v.default) variables[v.name] = v.default;\n }\n }\n\n // Render and write\n const rendered = renderTemplate(template, variables);\n\n // Ensure directory exists\n mkdirSync(dirname(outputPath), { recursive: true });\n await deps.writeFile(outputPath, rendered);\n\n logger.success(`Created ${outputPath}`);\n logger.info(\"Next steps:\");\n logger.dim(\" 1. Review and customize your CLAUDE.md\");\n logger.dim(\" 2. Run `dotclaudemd lint` to check for issues\");\n logger.dim(\" 3. Run `dotclaudemd doctor` to verify accuracy\");\n}\n\nasync function selectTemplate(templates: Template[]): Promise<Template> {\n const answer = await select({\n message: \"Choose a template:\",\n choices: templates.map((t) => ({\n name: `${t.frontmatter.displayName} — ${t.frontmatter.description}`,\n value: t.frontmatter.name,\n })),\n });\n\n return templates.find((t) => t.frontmatter.name === answer)!;\n}\n\nasync function promptForVariables(\n template: Template,\n variables: Record<string, string>,\n detected?: { packageManager?: string; testFramework?: string } | null,\n): Promise<void> {\n for (const v of template.frontmatter.variables) {\n const defaultValue =\n v.default ??\n (v.name === \"package_manager\" && detected?.packageManager\n ? detected.packageManager\n : undefined) ??\n (v.name === \"test_framework\" && detected?.testFramework\n ? detected.testFramework\n : undefined);\n\n if (v.options && v.options.length > 0) {\n const answer = await select({\n message: v.prompt,\n choices: v.options.map((o) => ({ name: o, value: o })),\n default: defaultValue,\n });\n variables[v.name] = answer;\n } else {\n const answer = await input({\n message: v.prompt,\n default: defaultValue,\n });\n variables[v.name] = answer;\n }\n }\n}\n","import { join } from \"node:path\";\nimport type { DetectedStack, FsDeps } from \"../types.js\";\nimport { defaultFsDeps } from \"../utils/fs.js\";\n\nexport async function detectStack(\n projectRoot: string,\n deps: FsDeps = defaultFsDeps,\n): Promise<DetectedStack | null> {\n // Try each detector in order of specificity\n const detectors = [\n detectNode,\n detectPython,\n detectRust,\n detectGo,\n ];\n\n for (const detector of detectors) {\n const result = await detector(projectRoot, deps);\n if (result) return result;\n }\n\n return null;\n}\n\nasync function detectNode(\n projectRoot: string,\n deps: FsDeps,\n): Promise<DetectedStack | null> {\n const pkgPath = join(projectRoot, \"package.json\");\n if (!(await deps.fileExists(pkgPath))) return null;\n\n let pkg: Record<string, unknown>;\n try {\n pkg = JSON.parse(await deps.readFile(pkgPath));\n } catch {\n return null;\n }\n\n const allDeps = (pkg.dependencies ?? {}) as Record<string, string>;\n const allDevDeps = (pkg.devDependencies ?? {}) as Record<string, string>;\n const depNames = Object.keys(allDeps);\n const devDepNames = Object.keys(allDevDeps);\n\n const stack: DetectedStack = {\n language: \"javascript\",\n dependencies: depNames,\n devDependencies: devDepNames,\n };\n\n // Detect TypeScript\n if (\n devDepNames.includes(\"typescript\") ||\n (await deps.fileExists(join(projectRoot, \"tsconfig.json\")))\n ) {\n stack.language = \"javascript\"; // category stays javascript\n }\n\n // Detect framework\n if (depNames.includes(\"next\")) {\n stack.framework = \"Next.js\";\n } else if (depNames.includes(\"express\") && depNames.includes(\"react\")) {\n stack.framework = \"MERN\";\n } else if (depNames.includes(\"express\")) {\n stack.framework = \"Express\";\n } else if (depNames.includes(\"react\")) {\n stack.framework = \"React\";\n }\n\n // Detect package manager from lockfiles\n if (await deps.fileExists(join(projectRoot, \"pnpm-lock.yaml\"))) {\n stack.packageManager = \"pnpm\";\n } else if (await deps.fileExists(join(projectRoot, \"yarn.lock\"))) {\n stack.packageManager = \"yarn\";\n } else if (await deps.fileExists(join(projectRoot, \"bun.lockb\"))) {\n stack.packageManager = \"bun\";\n } else if (await deps.fileExists(join(projectRoot, \"package-lock.json\"))) {\n stack.packageManager = \"npm\";\n }\n\n // Detect test framework\n if (devDepNames.includes(\"vitest\")) {\n stack.testFramework = \"vitest\";\n } else if (devDepNames.includes(\"jest\")) {\n stack.testFramework = \"jest\";\n } else if (devDepNames.includes(\"mocha\")) {\n stack.testFramework = \"mocha\";\n }\n\n return stack;\n}\n\nasync function detectPython(\n projectRoot: string,\n deps: FsDeps,\n): Promise<DetectedStack | null> {\n const hasPyproject = await deps.fileExists(\n join(projectRoot, \"pyproject.toml\"),\n );\n const hasRequirements = await deps.fileExists(\n join(projectRoot, \"requirements.txt\"),\n );\n\n if (!hasPyproject && !hasRequirements) return null;\n\n const stack: DetectedStack = {\n language: \"python\",\n dependencies: [],\n devDependencies: [],\n };\n\n // Try to detect framework from requirements.txt\n if (hasRequirements) {\n try {\n const content = await deps.readFile(\n join(projectRoot, \"requirements.txt\"),\n );\n const lines = content\n .split(\"\\n\")\n .map((l) => l.trim().toLowerCase().split(\"==\")[0].split(\">=\")[0]);\n stack.dependencies = lines.filter((l) => l && !l.startsWith(\"#\"));\n\n if (lines.includes(\"fastapi\")) stack.framework = \"FastAPI\";\n else if (lines.includes(\"django\")) stack.framework = \"Django\";\n else if (lines.includes(\"flask\")) stack.framework = \"Flask\";\n } catch {\n // ignore\n }\n }\n\n // Try to detect from pyproject.toml\n if (hasPyproject) {\n try {\n const content = await deps.readFile(\n join(projectRoot, \"pyproject.toml\"),\n );\n if (content.includes(\"fastapi\")) stack.framework = \"FastAPI\";\n else if (content.includes(\"django\")) stack.framework = \"Django\";\n else if (content.includes(\"flask\")) stack.framework = \"Flask\";\n\n if (content.includes(\"pytest\")) stack.testFramework = \"pytest\";\n } catch {\n // ignore\n }\n }\n\n // Detect package manager\n if (await deps.fileExists(join(projectRoot, \"poetry.lock\"))) {\n stack.packageManager = \"poetry\";\n } else if (await deps.fileExists(join(projectRoot, \"Pipfile.lock\"))) {\n stack.packageManager = \"pipenv\";\n } else if (await deps.fileExists(join(projectRoot, \"uv.lock\"))) {\n stack.packageManager = \"uv\";\n } else {\n stack.packageManager = \"pip\";\n }\n\n return stack;\n}\n\nasync function detectRust(\n projectRoot: string,\n deps: FsDeps,\n): Promise<DetectedStack | null> {\n if (!(await deps.fileExists(join(projectRoot, \"Cargo.toml\")))) return null;\n\n const stack: DetectedStack = {\n language: \"rust\",\n packageManager: \"cargo\",\n dependencies: [],\n devDependencies: [],\n };\n\n try {\n const content = await deps.readFile(join(projectRoot, \"Cargo.toml\"));\n if (content.includes(\"actix-web\")) stack.framework = \"Actix\";\n else if (content.includes(\"axum\")) stack.framework = \"Axum\";\n else if (content.includes(\"rocket\")) stack.framework = \"Rocket\";\n } catch {\n // ignore\n }\n\n return stack;\n}\n\nasync function detectGo(\n projectRoot: string,\n deps: FsDeps,\n): Promise<DetectedStack | null> {\n if (!(await deps.fileExists(join(projectRoot, \"go.mod\")))) return null;\n\n const stack: DetectedStack = {\n language: \"go\",\n packageManager: \"go\",\n dependencies: [],\n devDependencies: [],\n };\n\n try {\n const content = await deps.readFile(join(projectRoot, \"go.mod\"));\n if (content.includes(\"gin-gonic/gin\")) stack.framework = \"Gin\";\n else if (content.includes(\"gofiber/fiber\")) stack.framework = \"Fiber\";\n else if (content.includes(\"go-chi/chi\")) stack.framework = \"Chi\";\n else if (content.includes(\"labstack/echo\")) stack.framework = \"Echo\";\n } catch {\n // ignore\n }\n\n return stack;\n}\n"],"mappings":"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","names":["join","join"]}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
defaultFsDeps,
|
|
4
|
+
findClaudeMd,
|
|
5
|
+
findProjectRoot
|
|
6
|
+
} from "./chunk-3R6PUA3E.js";
|
|
7
|
+
import {
|
|
8
|
+
error,
|
|
9
|
+
success
|
|
10
|
+
} from "./chunk-YHVOBZLV.js";
|
|
11
|
+
|
|
12
|
+
// src/commands/lint.ts
|
|
13
|
+
import chalk from "chalk";
|
|
14
|
+
|
|
15
|
+
// src/core/linter.ts
|
|
16
|
+
import { existsSync } from "fs";
|
|
17
|
+
import { join } from "path";
|
|
18
|
+
var builtinRules = [
|
|
19
|
+
{
|
|
20
|
+
name: "line-count",
|
|
21
|
+
description: "CLAUDE.md should be concise",
|
|
22
|
+
severity: "warn",
|
|
23
|
+
check(content) {
|
|
24
|
+
const lines = content.split("\n").length;
|
|
25
|
+
if (lines > 150) {
|
|
26
|
+
return [
|
|
27
|
+
{
|
|
28
|
+
rule: "line-count",
|
|
29
|
+
severity: "error",
|
|
30
|
+
message: `CLAUDE.md is ${lines} lines (max recommended: 150). Very long files waste context window.`
|
|
31
|
+
}
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
if (lines > 80) {
|
|
35
|
+
return [
|
|
36
|
+
{
|
|
37
|
+
rule: "line-count",
|
|
38
|
+
severity: "warn",
|
|
39
|
+
message: `CLAUDE.md is ${lines} lines (recommended: <80). Consider trimming to essential instructions.`
|
|
40
|
+
}
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "has-commands",
|
|
48
|
+
description: "Should include build/test/dev commands",
|
|
49
|
+
severity: "warn",
|
|
50
|
+
check(content) {
|
|
51
|
+
const lower = content.toLowerCase();
|
|
52
|
+
const hasCommands = lower.includes("npm run") || lower.includes("pnpm ") || lower.includes("yarn ") || lower.includes("make ") || lower.includes("cargo ") || lower.includes("go ") || lower.includes("python ") || lower.includes("pytest") || lower.includes("```sh") || lower.includes("```bash") || lower.includes("```shell");
|
|
53
|
+
if (!hasCommands) {
|
|
54
|
+
return [
|
|
55
|
+
{
|
|
56
|
+
rule: "has-commands",
|
|
57
|
+
severity: "warn",
|
|
58
|
+
message: "No build/test/dev commands found. Include commands so Claude can build and test."
|
|
59
|
+
}
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: "no-personality",
|
|
67
|
+
description: "Avoid personality instructions",
|
|
68
|
+
severity: "warn",
|
|
69
|
+
check(content) {
|
|
70
|
+
const patterns = [
|
|
71
|
+
/be a senior engineer/i,
|
|
72
|
+
/think step by step/i,
|
|
73
|
+
/you are an? (?:expert|senior|experienced)/i,
|
|
74
|
+
/act as an? /i,
|
|
75
|
+
/pretend you are/i,
|
|
76
|
+
/take a deep breath/i
|
|
77
|
+
];
|
|
78
|
+
const results = [];
|
|
79
|
+
const lines = content.split("\n");
|
|
80
|
+
for (let i = 0; i < lines.length; i++) {
|
|
81
|
+
for (const pattern of patterns) {
|
|
82
|
+
if (pattern.test(lines[i])) {
|
|
83
|
+
results.push({
|
|
84
|
+
rule: "no-personality",
|
|
85
|
+
severity: "warn",
|
|
86
|
+
message: `Line ${i + 1}: Personality instruction detected ("${lines[i].trim().slice(0, 60)}..."). CLAUDE.md should focus on project facts, not persona.`,
|
|
87
|
+
line: i + 1
|
|
88
|
+
});
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return results;
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: "no-at-file-refs",
|
|
98
|
+
description: "Avoid @file references that embed entire files",
|
|
99
|
+
severity: "warn",
|
|
100
|
+
check(content) {
|
|
101
|
+
const results = [];
|
|
102
|
+
const lines = content.split("\n");
|
|
103
|
+
for (let i = 0; i < lines.length; i++) {
|
|
104
|
+
if (/@\w+\/[^\s]+/.test(lines[i]) && !lines[i].includes("@types")) {
|
|
105
|
+
results.push({
|
|
106
|
+
rule: "no-at-file-refs",
|
|
107
|
+
severity: "warn",
|
|
108
|
+
message: `Line ${i + 1}: @file reference detected. These embed entire file contents into context, wasting tokens.`,
|
|
109
|
+
line: i + 1
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return results;
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: "no-negative-only",
|
|
118
|
+
description: 'Avoid "never X" without "prefer Y"',
|
|
119
|
+
severity: "warn",
|
|
120
|
+
check(content) {
|
|
121
|
+
const results = [];
|
|
122
|
+
const lines = content.split("\n");
|
|
123
|
+
for (let i = 0; i < lines.length; i++) {
|
|
124
|
+
const line = lines[i];
|
|
125
|
+
if (/\b(?:never|don't|do not|avoid)\b/i.test(line)) {
|
|
126
|
+
const hasAlternative = /\b(?:instead|prefer|use|rather)\b/i.test(line) || i + 1 < lines.length && /\b(?:instead|prefer|use|rather)\b/i.test(lines[i + 1]);
|
|
127
|
+
if (!hasAlternative) {
|
|
128
|
+
results.push({
|
|
129
|
+
rule: "no-negative-only",
|
|
130
|
+
severity: "warn",
|
|
131
|
+
message: `Line ${i + 1}: Negative-only instruction without alternative. Add "instead, prefer X" for better results.`,
|
|
132
|
+
line: i + 1
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return results;
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: "stale-file-refs",
|
|
142
|
+
description: "Referenced file paths should exist",
|
|
143
|
+
severity: "warn",
|
|
144
|
+
check(content, projectRoot) {
|
|
145
|
+
if (!projectRoot) return [];
|
|
146
|
+
const results = [];
|
|
147
|
+
const lines = content.split("\n");
|
|
148
|
+
const pathRegex = /(?:^|\s)(?:\.\/)?([a-zA-Z][\w\-./]*\.\w{1,10})(?:\s|$|`|"|\))/;
|
|
149
|
+
for (let i = 0; i < lines.length; i++) {
|
|
150
|
+
const match = pathRegex.exec(lines[i]);
|
|
151
|
+
if (match) {
|
|
152
|
+
const filePath = match[1];
|
|
153
|
+
if (filePath.includes("example") || filePath.startsWith("http") || filePath.includes("*"))
|
|
154
|
+
continue;
|
|
155
|
+
const fullPath = join(projectRoot, filePath);
|
|
156
|
+
if (!existsSync(fullPath)) {
|
|
157
|
+
results.push({
|
|
158
|
+
rule: "stale-file-refs",
|
|
159
|
+
severity: "warn",
|
|
160
|
+
message: `Line ${i + 1}: Referenced file "${filePath}" does not exist.`,
|
|
161
|
+
line: i + 1
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return results;
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: "no-unicode-bullets",
|
|
171
|
+
description: "Use markdown list syntax instead of unicode bullets",
|
|
172
|
+
severity: "info",
|
|
173
|
+
check(content) {
|
|
174
|
+
const results = [];
|
|
175
|
+
const lines = content.split("\n");
|
|
176
|
+
for (let i = 0; i < lines.length; i++) {
|
|
177
|
+
if (/^\s*[•‣⁃◦▪▸►]/.test(lines[i])) {
|
|
178
|
+
results.push({
|
|
179
|
+
rule: "no-unicode-bullets",
|
|
180
|
+
severity: "info",
|
|
181
|
+
message: `Line ${i + 1}: Unicode bullet character detected. Use markdown "- " or "* " instead.`,
|
|
182
|
+
line: i + 1
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return results;
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
name: "no-placeholder-vars",
|
|
191
|
+
description: "No unreplaced {{variable}} placeholders",
|
|
192
|
+
severity: "error",
|
|
193
|
+
check(content) {
|
|
194
|
+
const results = [];
|
|
195
|
+
const lines = content.split("\n");
|
|
196
|
+
for (let i = 0; i < lines.length; i++) {
|
|
197
|
+
const matches = lines[i].match(/\{\{\w+\}\}/g);
|
|
198
|
+
if (matches) {
|
|
199
|
+
results.push({
|
|
200
|
+
rule: "no-placeholder-vars",
|
|
201
|
+
severity: "error",
|
|
202
|
+
message: `Line ${i + 1}: Unreplaced placeholder(s): ${matches.join(", ")}`,
|
|
203
|
+
line: i + 1
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return results;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
];
|
|
211
|
+
function lint(content, filePath, projectRoot, rules = builtinRules) {
|
|
212
|
+
const results = [];
|
|
213
|
+
for (const rule of rules) {
|
|
214
|
+
results.push(...rule.check(content, projectRoot));
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
file: filePath,
|
|
218
|
+
results,
|
|
219
|
+
errorCount: results.filter((r) => r.severity === "error").length,
|
|
220
|
+
warnCount: results.filter((r) => r.severity === "warn").length,
|
|
221
|
+
infoCount: results.filter((r) => r.severity === "info").length
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// src/commands/lint.ts
|
|
226
|
+
async function lintCommand(filePath, options = {}, deps = defaultFsDeps) {
|
|
227
|
+
const projectRoot = findProjectRoot();
|
|
228
|
+
const target = filePath ?? findClaudeMd(projectRoot);
|
|
229
|
+
if (!target) {
|
|
230
|
+
error(
|
|
231
|
+
"No CLAUDE.md found. Run `dotclaudemd init` to create one."
|
|
232
|
+
);
|
|
233
|
+
process.exitCode = 1;
|
|
234
|
+
return {
|
|
235
|
+
file: "",
|
|
236
|
+
results: [],
|
|
237
|
+
errorCount: 0,
|
|
238
|
+
warnCount: 0,
|
|
239
|
+
infoCount: 0
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
const content = await deps.readFile(target);
|
|
243
|
+
const report = lint(content, target, projectRoot);
|
|
244
|
+
if (options.json) {
|
|
245
|
+
console.log(JSON.stringify(report, null, 2));
|
|
246
|
+
} else {
|
|
247
|
+
printReport(report);
|
|
248
|
+
}
|
|
249
|
+
if (report.errorCount > 0) {
|
|
250
|
+
process.exitCode = 1;
|
|
251
|
+
}
|
|
252
|
+
return report;
|
|
253
|
+
}
|
|
254
|
+
function printReport(report) {
|
|
255
|
+
console.log();
|
|
256
|
+
console.log(chalk.bold(`Linting ${report.file}`));
|
|
257
|
+
console.log();
|
|
258
|
+
if (report.results.length === 0) {
|
|
259
|
+
success("No issues found!");
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
for (const result of report.results) {
|
|
263
|
+
const icon = result.severity === "error" ? chalk.red("\u2717") : result.severity === "warn" ? chalk.yellow("\u26A0") : chalk.blue("\u2139");
|
|
264
|
+
const severity = result.severity === "error" ? chalk.red(result.severity) : result.severity === "warn" ? chalk.yellow(result.severity) : chalk.blue(result.severity);
|
|
265
|
+
console.log(` ${icon} ${severity} ${chalk.dim(result.rule)} ${result.message}`);
|
|
266
|
+
}
|
|
267
|
+
console.log();
|
|
268
|
+
const summary = [];
|
|
269
|
+
if (report.errorCount > 0)
|
|
270
|
+
summary.push(chalk.red(`${report.errorCount} error(s)`));
|
|
271
|
+
if (report.warnCount > 0)
|
|
272
|
+
summary.push(chalk.yellow(`${report.warnCount} warning(s)`));
|
|
273
|
+
if (report.infoCount > 0)
|
|
274
|
+
summary.push(chalk.blue(`${report.infoCount} info`));
|
|
275
|
+
console.log(` ${summary.join(", ")}`);
|
|
276
|
+
console.log();
|
|
277
|
+
}
|
|
278
|
+
export {
|
|
279
|
+
lintCommand
|
|
280
|
+
};
|
|
281
|
+
//# sourceMappingURL=lint-W7ZIDPL7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/lint.ts","../src/core/linter.ts"],"sourcesContent":["import chalk from \"chalk\";\nimport { lint as runLint } from \"../core/linter.js\";\nimport { findClaudeMd, findProjectRoot } from \"../utils/paths.js\";\nimport { defaultFsDeps } from \"../utils/fs.js\";\nimport * as logger from \"../utils/logger.js\";\nimport type { LintReport, FsDeps } from \"../types.js\";\n\nexport interface LintOptions {\n json?: boolean;\n}\n\nexport async function lintCommand(\n filePath?: string,\n options: LintOptions = {},\n deps: FsDeps = defaultFsDeps,\n): Promise<LintReport> {\n const projectRoot = findProjectRoot();\n const target =\n filePath ?? findClaudeMd(projectRoot);\n\n if (!target) {\n logger.error(\n \"No CLAUDE.md found. Run `dotclaudemd init` to create one.\",\n );\n process.exitCode = 1;\n return {\n file: \"\",\n results: [],\n errorCount: 0,\n warnCount: 0,\n infoCount: 0,\n };\n }\n\n const content = await deps.readFile(target);\n const report = runLint(content, target, projectRoot);\n\n if (options.json) {\n console.log(JSON.stringify(report, null, 2));\n } else {\n printReport(report);\n }\n\n if (report.errorCount > 0) {\n process.exitCode = 1;\n }\n\n return report;\n}\n\nfunction printReport(report: LintReport): void {\n console.log();\n console.log(chalk.bold(`Linting ${report.file}`));\n console.log();\n\n if (report.results.length === 0) {\n logger.success(\"No issues found!\");\n return;\n }\n\n for (const result of report.results) {\n const icon =\n result.severity === \"error\"\n ? chalk.red(\"✗\")\n : result.severity === \"warn\"\n ? chalk.yellow(\"⚠\")\n : chalk.blue(\"ℹ\");\n const severity =\n result.severity === \"error\"\n ? chalk.red(result.severity)\n : result.severity === \"warn\"\n ? chalk.yellow(result.severity)\n : chalk.blue(result.severity);\n\n console.log(` ${icon} ${severity} ${chalk.dim(result.rule)} ${result.message}`);\n }\n\n console.log();\n const summary: string[] = [];\n if (report.errorCount > 0)\n summary.push(chalk.red(`${report.errorCount} error(s)`));\n if (report.warnCount > 0)\n summary.push(chalk.yellow(`${report.warnCount} warning(s)`));\n if (report.infoCount > 0)\n summary.push(chalk.blue(`${report.infoCount} info`));\n console.log(` ${summary.join(\", \")}`);\n console.log();\n}\n","import { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { LintRule, LintResult, LintReport } from \"../types.js\";\n\nexport const builtinRules: LintRule[] = [\n {\n name: \"line-count\",\n description: \"CLAUDE.md should be concise\",\n severity: \"warn\",\n check(content: string): LintResult[] {\n const lines = content.split(\"\\n\").length;\n if (lines > 150) {\n return [\n {\n rule: \"line-count\",\n severity: \"error\",\n message: `CLAUDE.md is ${lines} lines (max recommended: 150). Very long files waste context window.`,\n },\n ];\n }\n if (lines > 80) {\n return [\n {\n rule: \"line-count\",\n severity: \"warn\",\n message: `CLAUDE.md is ${lines} lines (recommended: <80). Consider trimming to essential instructions.`,\n },\n ];\n }\n return [];\n },\n },\n {\n name: \"has-commands\",\n description: \"Should include build/test/dev commands\",\n severity: \"warn\",\n check(content: string): LintResult[] {\n const lower = content.toLowerCase();\n const hasCommands =\n lower.includes(\"npm run\") ||\n lower.includes(\"pnpm \") ||\n lower.includes(\"yarn \") ||\n lower.includes(\"make \") ||\n lower.includes(\"cargo \") ||\n lower.includes(\"go \") ||\n lower.includes(\"python \") ||\n lower.includes(\"pytest\") ||\n lower.includes(\"```sh\") ||\n lower.includes(\"```bash\") ||\n lower.includes(\"```shell\");\n if (!hasCommands) {\n return [\n {\n rule: \"has-commands\",\n severity: \"warn\",\n message:\n \"No build/test/dev commands found. Include commands so Claude can build and test.\",\n },\n ];\n }\n return [];\n },\n },\n {\n name: \"no-personality\",\n description: \"Avoid personality instructions\",\n severity: \"warn\",\n check(content: string): LintResult[] {\n const patterns = [\n /be a senior engineer/i,\n /think step by step/i,\n /you are an? (?:expert|senior|experienced)/i,\n /act as an? /i,\n /pretend you are/i,\n /take a deep breath/i,\n ];\n const results: LintResult[] = [];\n const lines = content.split(\"\\n\");\n for (let i = 0; i < lines.length; i++) {\n for (const pattern of patterns) {\n if (pattern.test(lines[i])) {\n results.push({\n rule: \"no-personality\",\n severity: \"warn\",\n message: `Line ${i + 1}: Personality instruction detected (\"${lines[i].trim().slice(0, 60)}...\"). CLAUDE.md should focus on project facts, not persona.`,\n line: i + 1,\n });\n break; // One match per line\n }\n }\n }\n return results;\n },\n },\n {\n name: \"no-at-file-refs\",\n description: \"Avoid @file references that embed entire files\",\n severity: \"warn\",\n check(content: string): LintResult[] {\n const results: LintResult[] = [];\n const lines = content.split(\"\\n\");\n for (let i = 0; i < lines.length; i++) {\n if (/@\\w+\\/[^\\s]+/.test(lines[i]) && !lines[i].includes(\"@types\")) {\n results.push({\n rule: \"no-at-file-refs\",\n severity: \"warn\",\n message: `Line ${i + 1}: @file reference detected. These embed entire file contents into context, wasting tokens.`,\n line: i + 1,\n });\n }\n }\n return results;\n },\n },\n {\n name: \"no-negative-only\",\n description: 'Avoid \"never X\" without \"prefer Y\"',\n severity: \"warn\",\n check(content: string): LintResult[] {\n const results: LintResult[] = [];\n const lines = content.split(\"\\n\");\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n if (/\\b(?:never|don't|do not|avoid)\\b/i.test(line)) {\n const hasAlternative =\n /\\b(?:instead|prefer|use|rather)\\b/i.test(line) ||\n (i + 1 < lines.length &&\n /\\b(?:instead|prefer|use|rather)\\b/i.test(lines[i + 1]));\n if (!hasAlternative) {\n results.push({\n rule: \"no-negative-only\",\n severity: \"warn\",\n message: `Line ${i + 1}: Negative-only instruction without alternative. Add \"instead, prefer X\" for better results.`,\n line: i + 1,\n });\n }\n }\n }\n return results;\n },\n },\n {\n name: \"stale-file-refs\",\n description: \"Referenced file paths should exist\",\n severity: \"warn\",\n check(content: string, projectRoot?: string): LintResult[] {\n if (!projectRoot) return [];\n const results: LintResult[] = [];\n const lines = content.split(\"\\n\");\n // Match paths like src/foo.ts, ./config/bar.json, etc.\n const pathRegex =\n /(?:^|\\s)(?:\\.\\/)?([a-zA-Z][\\w\\-./]*\\.\\w{1,10})(?:\\s|$|`|\"|\\))/;\n for (let i = 0; i < lines.length; i++) {\n const match = pathRegex.exec(lines[i]);\n if (match) {\n const filePath = match[1];\n // Skip common false positives\n if (\n filePath.includes(\"example\") ||\n filePath.startsWith(\"http\") ||\n filePath.includes(\"*\")\n )\n continue;\n const fullPath = join(projectRoot, filePath);\n if (!existsSync(fullPath)) {\n results.push({\n rule: \"stale-file-refs\",\n severity: \"warn\",\n message: `Line ${i + 1}: Referenced file \"${filePath}\" does not exist.`,\n line: i + 1,\n });\n }\n }\n }\n return results;\n },\n },\n {\n name: \"no-unicode-bullets\",\n description: \"Use markdown list syntax instead of unicode bullets\",\n severity: \"info\",\n check(content: string): LintResult[] {\n const results: LintResult[] = [];\n const lines = content.split(\"\\n\");\n for (let i = 0; i < lines.length; i++) {\n if (/^\\s*[•‣⁃◦▪▸►]/.test(lines[i])) {\n results.push({\n rule: \"no-unicode-bullets\",\n severity: \"info\",\n message: `Line ${i + 1}: Unicode bullet character detected. Use markdown \"- \" or \"* \" instead.`,\n line: i + 1,\n });\n }\n }\n return results;\n },\n },\n {\n name: \"no-placeholder-vars\",\n description: \"No unreplaced {{variable}} placeholders\",\n severity: \"error\",\n check(content: string): LintResult[] {\n const results: LintResult[] = [];\n const lines = content.split(\"\\n\");\n for (let i = 0; i < lines.length; i++) {\n const matches = lines[i].match(/\\{\\{\\w+\\}\\}/g);\n if (matches) {\n results.push({\n rule: \"no-placeholder-vars\",\n severity: \"error\",\n message: `Line ${i + 1}: Unreplaced placeholder(s): ${matches.join(\", \")}`,\n line: i + 1,\n });\n }\n }\n return results;\n },\n },\n];\n\nexport function lint(\n content: string,\n filePath: string,\n projectRoot?: string,\n rules: LintRule[] = builtinRules,\n): LintReport {\n const results: LintResult[] = [];\n\n for (const rule of rules) {\n results.push(...rule.check(content, projectRoot));\n }\n\n return {\n file: filePath,\n results,\n errorCount: results.filter((r) => r.severity === \"error\").length,\n warnCount: results.filter((r) => r.severity === \"warn\").length,\n infoCount: results.filter((r) => r.severity === \"info\").length,\n };\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dotclaudemd",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "CLAUDE.md Template Registry CLI — scaffold, lint, and health-check your CLAUDE.md files",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"dotclaudemd": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"templates"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=20"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsup",
|
|
18
|
+
"dev": "tsup --watch",
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"test:watch": "vitest",
|
|
21
|
+
"test:coverage": "vitest run --coverage",
|
|
22
|
+
"lint": "tsc --noEmit",
|
|
23
|
+
"prepublishOnly": "npm run build"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"claude",
|
|
27
|
+
"claude-code",
|
|
28
|
+
"claudemd",
|
|
29
|
+
"template",
|
|
30
|
+
"scaffold",
|
|
31
|
+
"lint",
|
|
32
|
+
"cli"
|
|
33
|
+
],
|
|
34
|
+
"author": "",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@inquirer/prompts": "^8.0.0",
|
|
38
|
+
"chalk": "^5.4.1",
|
|
39
|
+
"commander": "^14.0.0",
|
|
40
|
+
"gray-matter": "^4.0.3",
|
|
41
|
+
"ora": "^9.0.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "^22.0.0",
|
|
45
|
+
"@vitest/coverage-v8": "^3.0.0",
|
|
46
|
+
"tsup": "^8.0.0",
|
|
47
|
+
"typescript": "^5.7.0",
|
|
48
|
+
"vitest": "^3.0.0"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: default
|
|
3
|
+
displayName: Default
|
|
4
|
+
description: Generic CLAUDE.md template for any project
|
|
5
|
+
category: _global
|
|
6
|
+
tags: [general, default]
|
|
7
|
+
variables:
|
|
8
|
+
- name: project_name
|
|
9
|
+
prompt: "Project name?"
|
|
10
|
+
default: my-project
|
|
11
|
+
- name: language
|
|
12
|
+
prompt: "Primary language?"
|
|
13
|
+
options: [TypeScript, JavaScript, Python, Go, Rust, Java, Other]
|
|
14
|
+
default: TypeScript
|
|
15
|
+
priority: 0
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# {{project_name}}
|
|
19
|
+
|
|
20
|
+
## Commands
|
|
21
|
+
|
|
22
|
+
<!-- Add your build, test, and dev commands here -->
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Build
|
|
26
|
+
# npm run build
|
|
27
|
+
|
|
28
|
+
# Test
|
|
29
|
+
# npm test
|
|
30
|
+
|
|
31
|
+
# Dev
|
|
32
|
+
# npm run dev
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Architecture
|
|
36
|
+
|
|
37
|
+
<!-- Describe your project structure and key directories -->
|
|
38
|
+
|
|
39
|
+
## Code Conventions
|
|
40
|
+
|
|
41
|
+
- Follow the existing code style in the repository
|
|
42
|
+
- Write types/interfaces for all data structures
|
|
43
|
+
- Keep functions small and focused
|
|
44
|
+
- Write tests for new functionality
|
|
45
|
+
|
|
46
|
+
## Key Files
|
|
47
|
+
|
|
48
|
+
<!-- List important files that Claude should know about -->
|
|
49
|
+
|
|
50
|
+
## Notes
|
|
51
|
+
|
|
52
|
+
<!-- Any additional context that helps Claude understand this project -->
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: go-api
|
|
3
|
+
displayName: Go API
|
|
4
|
+
description: Go REST API application
|
|
5
|
+
category: go
|
|
6
|
+
tags: [go, api, backend]
|
|
7
|
+
variables:
|
|
8
|
+
- name: router
|
|
9
|
+
prompt: "HTTP router?"
|
|
10
|
+
options: [net/http, Chi, Gin, Echo]
|
|
11
|
+
default: net/http
|
|
12
|
+
- name: db_type
|
|
13
|
+
prompt: "Database?"
|
|
14
|
+
options: [PostgreSQL, SQLite, None]
|
|
15
|
+
default: PostgreSQL
|
|
16
|
+
detects:
|
|
17
|
+
files: [go.mod]
|
|
18
|
+
priority: 5
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# Project
|
|
22
|
+
|
|
23
|
+
Go REST API using {{router}}.
|
|
24
|
+
|
|
25
|
+
## Commands
|
|
26
|
+
|
|
27
|
+
- `go run ./cmd/server` — Start the server
|
|
28
|
+
- `go test ./...` — Run all tests
|
|
29
|
+
- `go vet ./...` — Run static analysis
|
|
30
|
+
- `golangci-lint run` — Run linter
|
|
31
|
+
- `go build -o bin/server ./cmd/server` — Build binary
|
|
32
|
+
|
|
33
|
+
## Architecture
|
|
34
|
+
|
|
35
|
+
- `cmd/server/` — Application entry point
|
|
36
|
+
- `internal/handler/` — HTTP handlers
|
|
37
|
+
- `internal/service/` — Business logic
|
|
38
|
+
- `internal/repository/` — Data access layer
|
|
39
|
+
- `internal/model/` — Domain types
|
|
40
|
+
- `internal/middleware/` — HTTP middleware
|
|
41
|
+
- `pkg/` — Reusable packages (if any)
|
|
42
|
+
|
|
43
|
+
## Conventions
|
|
44
|
+
|
|
45
|
+
- Accept interfaces, return structs
|
|
46
|
+
- Use table-driven tests
|
|
47
|
+
- Handle all errors explicitly; prefer `fmt.Errorf("context: %w", err)` for wrapping
|
|
48
|
+
- Use `context.Context` as the first parameter for functions that do I/O
|
|
49
|
+
- Keep handlers thin: decode request, call service, encode response
|
|
50
|
+
- Use dependency injection via struct fields, not globals
|
|
51
|
+
- Log with structured logging (slog or zerolog)
|
|
52
|
+
|
|
53
|
+
## Testing
|
|
54
|
+
|
|
55
|
+
Table-driven tests with `testing.T`. Use `httptest.NewRequest` and `httptest.NewRecorder` for handler tests. Use testcontainers or an in-memory DB for integration tests.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: express-mongodb
|
|
3
|
+
displayName: Express + MongoDB
|
|
4
|
+
description: Express.js REST API with MongoDB
|
|
5
|
+
category: javascript
|
|
6
|
+
tags: [javascript, express, mongodb, api, backend]
|
|
7
|
+
variables:
|
|
8
|
+
- name: auth_method
|
|
9
|
+
prompt: "Authentication method?"
|
|
10
|
+
options: [JWT, Session, API Key]
|
|
11
|
+
default: JWT
|
|
12
|
+
- name: validation
|
|
13
|
+
prompt: "Validation library?"
|
|
14
|
+
options: [Zod, Joi, express-validator]
|
|
15
|
+
default: Zod
|
|
16
|
+
detects:
|
|
17
|
+
files: [package.json]
|
|
18
|
+
dependencies: [express, mongoose]
|
|
19
|
+
priority: 8
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
# Project
|
|
23
|
+
|
|
24
|
+
Express.js REST API with MongoDB.
|
|
25
|
+
|
|
26
|
+
## Commands
|
|
27
|
+
|
|
28
|
+
- `npm run dev` — Start with hot reload (nodemon)
|
|
29
|
+
- `npm start` — Start production server
|
|
30
|
+
- `npm test` — Run tests
|
|
31
|
+
- `npm run lint` — Run ESLint
|
|
32
|
+
|
|
33
|
+
## Architecture
|
|
34
|
+
|
|
35
|
+
- `src/routes/` — Route definitions (thin: validate + call controller)
|
|
36
|
+
- `src/controllers/` — Request handlers
|
|
37
|
+
- `src/models/` — Mongoose models and schemas
|
|
38
|
+
- `src/middleware/` — Auth, error handling, validation
|
|
39
|
+
- `src/services/` — Business logic
|
|
40
|
+
- `src/config/` — Environment and DB config
|
|
41
|
+
|
|
42
|
+
## API Conventions
|
|
43
|
+
|
|
44
|
+
- RESTful resource naming: `/api/v1/users`, `/api/v1/posts/:id`
|
|
45
|
+
- Validate all input with {{validation}}
|
|
46
|
+
- Consistent error responses: `{ error: { code, message, details } }`
|
|
47
|
+
- Use proper HTTP status codes
|
|
48
|
+
- Paginate list endpoints: `?page=1&limit=20`
|
|
49
|
+
|
|
50
|
+
## Auth
|
|
51
|
+
|
|
52
|
+
{{auth_method}} authentication. Apply auth middleware to protected routes.
|
|
53
|
+
|
|
54
|
+
## Database
|
|
55
|
+
|
|
56
|
+
MongoDB with Mongoose. Define schemas with validation. Use `.lean()` for read queries. Index frequently queried fields.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mern-stack
|
|
3
|
+
displayName: MERN Stack
|
|
4
|
+
description: MongoDB, Express, React, Node full-stack application
|
|
5
|
+
category: javascript
|
|
6
|
+
tags: [javascript, mongodb, express, react, node, fullstack]
|
|
7
|
+
variables:
|
|
8
|
+
- name: auth_method
|
|
9
|
+
prompt: "Authentication method?"
|
|
10
|
+
options: [JWT, Passport, Auth0]
|
|
11
|
+
default: JWT
|
|
12
|
+
- name: test_framework
|
|
13
|
+
prompt: "Testing framework?"
|
|
14
|
+
options: [Jest, Vitest]
|
|
15
|
+
default: Jest
|
|
16
|
+
detects:
|
|
17
|
+
files: [package.json]
|
|
18
|
+
dependencies: [express, react, mongoose]
|
|
19
|
+
priority: 10
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
# Project
|
|
23
|
+
|
|
24
|
+
MERN stack application (MongoDB, Express, React, Node.js).
|
|
25
|
+
|
|
26
|
+
## Commands
|
|
27
|
+
|
|
28
|
+
- `npm run dev` — Start development server (client + API)
|
|
29
|
+
- `npm run build` — Build for production
|
|
30
|
+
- `npm test` — Run tests with {{test_framework}}
|
|
31
|
+
- `npm run lint` — Run ESLint
|
|
32
|
+
|
|
33
|
+
## Architecture
|
|
34
|
+
|
|
35
|
+
- `client/` — React frontend (Vite or CRA)
|
|
36
|
+
- `server/` — Express API server
|
|
37
|
+
- `server/models/` — Mongoose schemas
|
|
38
|
+
- `server/routes/` — Express route handlers
|
|
39
|
+
- `server/middleware/` — Auth and error middleware
|
|
40
|
+
|
|
41
|
+
## Auth
|
|
42
|
+
|
|
43
|
+
Authentication uses {{auth_method}}. Protect routes with the auth middleware.
|
|
44
|
+
|
|
45
|
+
## Database
|
|
46
|
+
|
|
47
|
+
MongoDB with Mongoose ODM. Define schemas in `server/models/`. Use `lean()` for read-only queries.
|
|
48
|
+
|
|
49
|
+
## Code Conventions
|
|
50
|
+
|
|
51
|
+
- Use async/await for all async operations; avoid raw callbacks
|
|
52
|
+
- Validate request bodies with a schema validator (e.g., Zod, Joi)
|
|
53
|
+
- Return consistent JSON response shapes: `{ data, error, message }`
|
|
54
|
+
- Use HTTP status codes correctly (201 for creation, 404 for not found, etc.)
|
|
55
|
+
|
|
56
|
+
## Testing
|
|
57
|
+
|
|
58
|
+
Run `npm test` to execute the {{test_framework}} test suite. Place tests adjacent to source files or in `__tests__/` directories.
|