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.
@@ -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.