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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dotclaudemd contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# dotclaudemd
|
|
2
|
+
|
|
3
|
+
CLAUDE.md Template Registry CLI — scaffold, lint, and health-check your CLAUDE.md files.
|
|
4
|
+
|
|
5
|
+
Think "github/gitignore but for CLAUDE.md."
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx dotclaudemd init
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
This auto-detects your project stack and generates a CLAUDE.md from the best matching template.
|
|
14
|
+
|
|
15
|
+
## Why
|
|
16
|
+
|
|
17
|
+
There is no standard starting point for writing CLAUDE.md files. Developers write them from scratch, often missing best practices or including anti-patterns. `dotclaudemd` provides a searchable, community-driven registry of templates with a CLI for scaffolding, linting, and health-checking.
|
|
18
|
+
|
|
19
|
+
## Commands
|
|
20
|
+
|
|
21
|
+
### `dotclaudemd init`
|
|
22
|
+
|
|
23
|
+
Scaffold a CLAUDE.md from a template with auto-detection.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
dotclaudemd init # Auto-detect stack, interactive prompts
|
|
27
|
+
dotclaudemd init --stack mern-stack # Use a specific template
|
|
28
|
+
dotclaudemd init --global # Write to ~/.claude/CLAUDE.md
|
|
29
|
+
dotclaudemd init --no-interactive # Use defaults without prompting
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### `dotclaudemd lint [file]`
|
|
33
|
+
|
|
34
|
+
Lint a CLAUDE.md for common anti-patterns.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
dotclaudemd lint # Lint CLAUDE.md in current project
|
|
38
|
+
dotclaudemd lint path/to/CLAUDE.md # Lint a specific file
|
|
39
|
+
dotclaudemd lint --json # Output as JSON
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Lint Rules:**
|
|
43
|
+
|
|
44
|
+
| Rule | Severity | Trigger |
|
|
45
|
+
|------|----------|---------|
|
|
46
|
+
| `line-count` | warn/error | >80 lines (warn), >150 lines (error) |
|
|
47
|
+
| `has-commands` | warn | Missing build/test/dev commands |
|
|
48
|
+
| `no-personality` | warn | "Be a senior engineer", persona instructions |
|
|
49
|
+
| `no-at-file-refs` | warn | `@docs/...` patterns that embed entire files |
|
|
50
|
+
| `no-negative-only` | warn | "Never use X" without "prefer Y instead" |
|
|
51
|
+
| `stale-file-refs` | warn | Referenced paths that don't exist |
|
|
52
|
+
| `no-unicode-bullets` | info | Unicode bullets instead of markdown lists |
|
|
53
|
+
| `no-placeholder-vars` | error | Unreplaced `{{variable}}` placeholders |
|
|
54
|
+
|
|
55
|
+
### `dotclaudemd doctor`
|
|
56
|
+
|
|
57
|
+
Check CLAUDE.md freshness against actual project state.
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
dotclaudemd doctor # Run health checks
|
|
61
|
+
dotclaudemd doctor --json # Output as JSON
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Health Checks:**
|
|
65
|
+
|
|
66
|
+
| Check | Description |
|
|
67
|
+
|-------|-------------|
|
|
68
|
+
| `scripts-exist` | Commands in CLAUDE.md exist in package.json scripts |
|
|
69
|
+
| `deps-mentioned` | Major dependencies are mentioned |
|
|
70
|
+
| `file-refs-valid` | File paths mentioned actually exist |
|
|
71
|
+
| `node-version-match` | Stated Node version matches .nvmrc |
|
|
72
|
+
| `test-framework-match` | Mentioned test framework matches devDeps |
|
|
73
|
+
| `package-manager-match` | Stated package manager matches lockfile |
|
|
74
|
+
|
|
75
|
+
### `dotclaudemd browse`
|
|
76
|
+
|
|
77
|
+
Browse and preview available templates.
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
dotclaudemd browse # Interactive template browser
|
|
81
|
+
dotclaudemd browse --list # Non-interactive list
|
|
82
|
+
dotclaudemd browse --category python # Filter by category
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Available Templates
|
|
86
|
+
|
|
87
|
+
| Template | Description |
|
|
88
|
+
|----------|-------------|
|
|
89
|
+
| `default` | Generic template for any project |
|
|
90
|
+
| `nextjs-typescript` | Next.js App Router with TypeScript |
|
|
91
|
+
| `nextjs-prisma-tailwind` | Full-stack Next.js with Prisma + Tailwind |
|
|
92
|
+
| `express-mongodb` | Express.js REST API with MongoDB |
|
|
93
|
+
| `mern-stack` | MERN full-stack application |
|
|
94
|
+
| `react-vite` | React SPA with Vite |
|
|
95
|
+
| `node-cli-tool` | Node.js CLI tool with TypeScript |
|
|
96
|
+
| `fastapi-sqlalchemy` | FastAPI with SQLAlchemy ORM |
|
|
97
|
+
| `django-rest` | Django REST Framework |
|
|
98
|
+
| `flask-basic` | Flask web application |
|
|
99
|
+
| `cargo-workspace` | Rust Cargo workspace |
|
|
100
|
+
| `go-api` | Go REST API |
|
|
101
|
+
|
|
102
|
+
## Contributing
|
|
103
|
+
|
|
104
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for how to add templates and contribute.
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
MIT
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
filterTemplates,
|
|
4
|
+
listTemplates,
|
|
5
|
+
renderTemplate
|
|
6
|
+
} from "./chunk-3ERFQLAD.js";
|
|
7
|
+
import "./chunk-3WTPUEHL.js";
|
|
8
|
+
import {
|
|
9
|
+
defaultFsDeps
|
|
10
|
+
} from "./chunk-3R6PUA3E.js";
|
|
11
|
+
import {
|
|
12
|
+
warn
|
|
13
|
+
} from "./chunk-YHVOBZLV.js";
|
|
14
|
+
|
|
15
|
+
// src/commands/browse.ts
|
|
16
|
+
import chalk from "chalk";
|
|
17
|
+
import { select, confirm } from "@inquirer/prompts";
|
|
18
|
+
async function browseCommand(options = {}, deps = defaultFsDeps) {
|
|
19
|
+
const allTemplates = await listTemplates();
|
|
20
|
+
if (options.list) {
|
|
21
|
+
printTemplateList(allTemplates, options.category);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
let category = options.category;
|
|
25
|
+
if (!category) {
|
|
26
|
+
const categories = [
|
|
27
|
+
...new Set(allTemplates.map((t) => t.frontmatter.category))
|
|
28
|
+
];
|
|
29
|
+
category = await select({
|
|
30
|
+
message: "Choose a category:",
|
|
31
|
+
choices: [
|
|
32
|
+
{ name: "All", value: "" },
|
|
33
|
+
...categories.map((c) => ({ name: c, value: c }))
|
|
34
|
+
]
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const filtered = category ? await filterTemplates({ category }) : allTemplates;
|
|
38
|
+
if (filtered.length === 0) {
|
|
39
|
+
warn("No templates found for this category.");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const templateName = await select({
|
|
43
|
+
message: "Choose a template to preview:",
|
|
44
|
+
choices: filtered.map((t) => ({
|
|
45
|
+
name: `${t.frontmatter.displayName} \u2014 ${t.frontmatter.description}`,
|
|
46
|
+
value: t.frontmatter.name
|
|
47
|
+
}))
|
|
48
|
+
});
|
|
49
|
+
const template = filtered.find(
|
|
50
|
+
(t) => t.frontmatter.name === templateName
|
|
51
|
+
);
|
|
52
|
+
const defaults = {};
|
|
53
|
+
for (const v of template.frontmatter.variables) {
|
|
54
|
+
if (v.default) defaults[v.name] = v.default;
|
|
55
|
+
}
|
|
56
|
+
console.log();
|
|
57
|
+
console.log(chalk.bold(`Preview: ${template.frontmatter.displayName}`));
|
|
58
|
+
console.log(chalk.dim("\u2500".repeat(60)));
|
|
59
|
+
console.log(renderTemplate(template, defaults));
|
|
60
|
+
console.log(chalk.dim("\u2500".repeat(60)));
|
|
61
|
+
console.log();
|
|
62
|
+
const useIt = await confirm({
|
|
63
|
+
message: "Use this template?",
|
|
64
|
+
default: true
|
|
65
|
+
});
|
|
66
|
+
if (useIt) {
|
|
67
|
+
const { initCommand } = await import("./init-GLWLFVHN.js");
|
|
68
|
+
await initCommand({ stack: template.frontmatter.name }, deps);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function printTemplateList(templates, categoryFilter) {
|
|
72
|
+
const filtered = categoryFilter ? templates.filter((t) => t.frontmatter.category === categoryFilter) : templates;
|
|
73
|
+
console.log();
|
|
74
|
+
console.log(chalk.bold("Available Templates"));
|
|
75
|
+
console.log();
|
|
76
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
77
|
+
for (const t of filtered) {
|
|
78
|
+
const cat = t.frontmatter.category;
|
|
79
|
+
if (!byCategory.has(cat)) byCategory.set(cat, []);
|
|
80
|
+
byCategory.get(cat).push(t);
|
|
81
|
+
}
|
|
82
|
+
for (const [category, templates2] of byCategory) {
|
|
83
|
+
console.log(chalk.bold.underline(category));
|
|
84
|
+
for (const t of templates2) {
|
|
85
|
+
console.log(
|
|
86
|
+
` ${chalk.cyan(t.frontmatter.name.padEnd(30))} ${t.frontmatter.description}`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
console.log();
|
|
90
|
+
}
|
|
91
|
+
console.log(chalk.dim(`${filtered.length} template(s) available`));
|
|
92
|
+
}
|
|
93
|
+
export {
|
|
94
|
+
browseCommand
|
|
95
|
+
};
|
|
96
|
+
//# sourceMappingURL=browse-7V4CRGTH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/browse.ts"],"sourcesContent":["import chalk from \"chalk\";\nimport { select, confirm } from \"@inquirer/prompts\";\nimport {\n listTemplates,\n filterTemplates,\n} from \"../core/template-registry.js\";\nimport { renderTemplate } from \"../core/template-engine.js\";\nimport type { Template, FsDeps } from \"../types.js\";\nimport { defaultFsDeps } from \"../utils/fs.js\";\nimport * as logger from \"../utils/logger.js\";\n\nexport interface BrowseOptions {\n category?: string;\n list?: boolean;\n}\n\nexport async function browseCommand(\n options: BrowseOptions = {},\n deps: FsDeps = defaultFsDeps,\n): Promise<void> {\n const allTemplates = await listTemplates();\n\n if (options.list) {\n printTemplateList(allTemplates, options.category);\n return;\n }\n\n // Category selection\n let category = options.category;\n if (!category) {\n const categories = [\n ...new Set(allTemplates.map((t) => t.frontmatter.category)),\n ];\n category = await select({\n message: \"Choose a category:\",\n choices: [\n { name: \"All\", value: \"\" },\n ...categories.map((c) => ({ name: c, value: c })),\n ],\n });\n }\n\n // Filter templates\n const filtered = category\n ? await filterTemplates({ category })\n : allTemplates;\n\n if (filtered.length === 0) {\n logger.warn(\"No templates found for this category.\");\n return;\n }\n\n // Template selection\n const templateName = await select({\n message: \"Choose a template to preview:\",\n choices: filtered.map((t) => ({\n name: `${t.frontmatter.displayName} — ${t.frontmatter.description}`,\n value: t.frontmatter.name,\n })),\n });\n\n const template = filtered.find(\n (t) => t.frontmatter.name === templateName,\n )!;\n\n // Preview with defaults\n const defaults: Record<string, string> = {};\n for (const v of template.frontmatter.variables) {\n if (v.default) defaults[v.name] = v.default;\n }\n\n console.log();\n console.log(chalk.bold(`Preview: ${template.frontmatter.displayName}`));\n console.log(chalk.dim(\"─\".repeat(60)));\n console.log(renderTemplate(template, defaults));\n console.log(chalk.dim(\"─\".repeat(60)));\n console.log();\n\n // Offer to use\n const useIt = await confirm({\n message: \"Use this template?\",\n default: true,\n });\n\n if (useIt) {\n // Dynamically import to avoid circular deps\n const { initCommand } = await import(\"./init.js\");\n await initCommand({ stack: template.frontmatter.name }, deps);\n }\n}\n\nfunction printTemplateList(\n templates: Template[],\n categoryFilter?: string,\n): void {\n const filtered = categoryFilter\n ? templates.filter((t) => t.frontmatter.category === categoryFilter)\n : templates;\n\n console.log();\n console.log(chalk.bold(\"Available Templates\"));\n console.log();\n\n // Group by category\n const byCategory = new Map<string, Template[]>();\n for (const t of filtered) {\n const cat = t.frontmatter.category;\n if (!byCategory.has(cat)) byCategory.set(cat, []);\n byCategory.get(cat)!.push(t);\n }\n\n for (const [category, templates] of byCategory) {\n console.log(chalk.bold.underline(category));\n for (const t of templates) {\n console.log(\n ` ${chalk.cyan(t.frontmatter.name.padEnd(30))} ${t.frontmatter.description}`,\n );\n }\n console.log();\n }\n\n console.log(chalk.dim(`${filtered.length} template(s) available`));\n}\n"],"mappings":"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","names":["templates"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/utils/ui.ts
|
|
4
|
+
import ora from "ora";
|
|
5
|
+
var isCI = process.env.CI === "true";
|
|
6
|
+
var isTest = process.env.NODE_ENV === "test";
|
|
7
|
+
function createSpinner(text) {
|
|
8
|
+
return ora({
|
|
9
|
+
text,
|
|
10
|
+
isSilent: isCI || isTest
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export {
|
|
15
|
+
createSpinner
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=chunk-2D66CC54.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/ui.ts"],"sourcesContent":["import ora, { type Ora } from \"ora\";\n\nconst isCI = process.env.CI === \"true\";\nconst isTest = process.env.NODE_ENV === \"test\";\n\nexport function createSpinner(text: string): Ora {\n return ora({\n text,\n isSilent: isCI || isTest,\n });\n}\n"],"mappings":";;;AAAA,OAAO,SAAuB;AAE9B,IAAM,OAAO,QAAQ,IAAI,OAAO;AAChC,IAAM,SAAS,QAAQ,IAAI,aAAa;AAEjC,SAAS,cAAc,MAAmB;AAC/C,SAAO,IAAI;AAAA,IACT;AAAA,IACA,UAAU,QAAQ;AAAA,EACpB,CAAC;AACH;","names":[]}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ValidationError
|
|
4
|
+
} from "./chunk-3WTPUEHL.js";
|
|
5
|
+
import {
|
|
6
|
+
defaultFsDeps,
|
|
7
|
+
getTemplatesDir
|
|
8
|
+
} from "./chunk-3R6PUA3E.js";
|
|
9
|
+
|
|
10
|
+
// src/core/template-engine.ts
|
|
11
|
+
import matter from "gray-matter";
|
|
12
|
+
async function parseTemplate(filePath, deps = defaultFsDeps) {
|
|
13
|
+
const raw = await deps.readFile(filePath);
|
|
14
|
+
const { data, content } = matter(raw);
|
|
15
|
+
validateFrontmatter(data, filePath);
|
|
16
|
+
return {
|
|
17
|
+
filePath,
|
|
18
|
+
frontmatter: {
|
|
19
|
+
name: data.name,
|
|
20
|
+
displayName: data.displayName,
|
|
21
|
+
description: data.description,
|
|
22
|
+
category: data.category,
|
|
23
|
+
tags: data.tags ?? [],
|
|
24
|
+
variables: data.variables ?? [],
|
|
25
|
+
detects: data.detects,
|
|
26
|
+
priority: data.priority ?? 0
|
|
27
|
+
},
|
|
28
|
+
content: content.trim()
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function validateFrontmatter(data, filePath) {
|
|
32
|
+
const required = ["name", "displayName", "description", "category"];
|
|
33
|
+
for (const field of required) {
|
|
34
|
+
if (!data[field]) {
|
|
35
|
+
throw new ValidationError(
|
|
36
|
+
`Template "${filePath}" is missing required frontmatter field: ${field}`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function renderTemplate(template, variables) {
|
|
42
|
+
let result = template.content;
|
|
43
|
+
const placeholders = /* @__PURE__ */ new Set();
|
|
44
|
+
const regex = /\{\{(\w+)\}\}/g;
|
|
45
|
+
let match;
|
|
46
|
+
while ((match = regex.exec(result)) !== null) {
|
|
47
|
+
placeholders.add(match[1]);
|
|
48
|
+
}
|
|
49
|
+
for (const varDef of template.frontmatter.variables) {
|
|
50
|
+
if (placeholders.has(varDef.name) && !variables[varDef.name]) {
|
|
51
|
+
if (varDef.default) {
|
|
52
|
+
variables[varDef.name] = varDef.default;
|
|
53
|
+
} else {
|
|
54
|
+
throw new ValidationError(
|
|
55
|
+
`Missing required variable: ${varDef.name}`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
result = result.replace(/\{\{(\w+)\}\}/g, (_match, name) => {
|
|
61
|
+
return variables[name] ?? _match;
|
|
62
|
+
});
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/core/template-registry.ts
|
|
67
|
+
import { join } from "path";
|
|
68
|
+
var cachedTemplates = null;
|
|
69
|
+
async function listTemplates(templatesDir, deps = defaultFsDeps) {
|
|
70
|
+
if (cachedTemplates) return cachedTemplates;
|
|
71
|
+
const dir = templatesDir ?? getTemplatesDir();
|
|
72
|
+
const templates = [];
|
|
73
|
+
const categories = await deps.readDir(dir);
|
|
74
|
+
for (const category of categories) {
|
|
75
|
+
if (category.startsWith(".")) continue;
|
|
76
|
+
const categoryPath = join(dir, category);
|
|
77
|
+
let files;
|
|
78
|
+
try {
|
|
79
|
+
files = await deps.readDir(categoryPath);
|
|
80
|
+
} catch {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
for (const file of files) {
|
|
84
|
+
if (!file.endsWith(".md")) continue;
|
|
85
|
+
try {
|
|
86
|
+
const template = await parseTemplate(join(categoryPath, file), deps);
|
|
87
|
+
templates.push(template);
|
|
88
|
+
} catch {
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
cachedTemplates = templates;
|
|
93
|
+
return templates;
|
|
94
|
+
}
|
|
95
|
+
async function filterTemplates(options = {}, templatesDir, deps = defaultFsDeps) {
|
|
96
|
+
const templates = await listTemplates(templatesDir, deps);
|
|
97
|
+
return templates.filter((t) => {
|
|
98
|
+
if (options.category && t.frontmatter.category !== options.category) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
if (options.tags && options.tags.length > 0) {
|
|
102
|
+
return options.tags.some((tag) => t.frontmatter.tags.includes(tag));
|
|
103
|
+
}
|
|
104
|
+
return true;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
async function suggestTemplates(stack, templatesDir, deps = defaultFsDeps) {
|
|
108
|
+
const templates = await listTemplates(templatesDir, deps);
|
|
109
|
+
const scored = templates.map((template) => {
|
|
110
|
+
let score = 0;
|
|
111
|
+
const detects = template.frontmatter.detects;
|
|
112
|
+
if (!detects) return { template, score };
|
|
113
|
+
if (detects.files) {
|
|
114
|
+
}
|
|
115
|
+
if (detects.dependencies) {
|
|
116
|
+
for (const dep of detects.dependencies) {
|
|
117
|
+
if (stack.dependencies.includes(dep)) score += 2;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (detects.devDependencies) {
|
|
121
|
+
for (const dep of detects.devDependencies) {
|
|
122
|
+
if (stack.devDependencies.includes(dep)) score += 1;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (template.frontmatter.category === stack.language || template.frontmatter.tags.includes(stack.language)) {
|
|
126
|
+
score += 1;
|
|
127
|
+
}
|
|
128
|
+
if (stack.framework && template.frontmatter.tags.includes(stack.framework.toLowerCase())) {
|
|
129
|
+
score += 3;
|
|
130
|
+
}
|
|
131
|
+
return { template, score };
|
|
132
|
+
}).filter((s) => s.score > 0).sort((a, b) => {
|
|
133
|
+
if (b.score !== a.score) return b.score - a.score;
|
|
134
|
+
return b.template.frontmatter.priority - a.template.frontmatter.priority;
|
|
135
|
+
});
|
|
136
|
+
return scored.map((s) => s.template);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export {
|
|
140
|
+
renderTemplate,
|
|
141
|
+
listTemplates,
|
|
142
|
+
filterTemplates,
|
|
143
|
+
suggestTemplates
|
|
144
|
+
};
|
|
145
|
+
//# sourceMappingURL=chunk-3ERFQLAD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/template-engine.ts","../src/core/template-registry.ts"],"sourcesContent":["import matter from \"gray-matter\";\nimport type { Template, TemplateFrontmatter, FsDeps } from \"../types.js\";\nimport { ValidationError } from \"../utils/errors.js\";\nimport { defaultFsDeps } from \"../utils/fs.js\";\n\nexport async function parseTemplate(\n filePath: string,\n deps: FsDeps = defaultFsDeps,\n): Promise<Template> {\n const raw = await deps.readFile(filePath);\n const { data, content } = matter(raw);\n\n validateFrontmatter(data, filePath);\n\n return {\n filePath,\n frontmatter: {\n name: data.name,\n displayName: data.displayName,\n description: data.description,\n category: data.category,\n tags: data.tags ?? [],\n variables: data.variables ?? [],\n detects: data.detects,\n priority: data.priority ?? 0,\n },\n content: content.trim(),\n };\n}\n\nfunction validateFrontmatter(\n data: Record<string, unknown>,\n filePath: string,\n): void {\n const required = [\"name\", \"displayName\", \"description\", \"category\"];\n for (const field of required) {\n if (!data[field]) {\n throw new ValidationError(\n `Template \"${filePath}\" is missing required frontmatter field: ${field}`,\n );\n }\n }\n}\n\nexport function renderTemplate(\n template: Template,\n variables: Record<string, string>,\n): string {\n let result = template.content;\n\n // Collect all variable placeholders in the template\n const placeholders = new Set<string>();\n const regex = /\\{\\{(\\w+)\\}\\}/g;\n let match: RegExpExecArray | null;\n while ((match = regex.exec(result)) !== null) {\n placeholders.add(match[1]);\n }\n\n // Check for missing required variables\n for (const varDef of template.frontmatter.variables) {\n if (placeholders.has(varDef.name) && !variables[varDef.name]) {\n if (varDef.default) {\n variables[varDef.name] = varDef.default;\n } else {\n throw new ValidationError(\n `Missing required variable: ${varDef.name}`,\n );\n }\n }\n }\n\n // Substitute all variables\n result = result.replace(/\\{\\{(\\w+)\\}\\}/g, (_match, name: string) => {\n return variables[name] ?? _match;\n });\n\n return result;\n}\n","import { join } from \"node:path\";\nimport { parseTemplate } from \"./template-engine.js\";\nimport type { Template, DetectedStack, FsDeps } from \"../types.js\";\nimport { TemplateNotFoundError } from \"../utils/errors.js\";\nimport { getTemplatesDir } from \"../utils/paths.js\";\nimport { defaultFsDeps } from \"../utils/fs.js\";\n\nlet cachedTemplates: Template[] | null = null;\n\nexport function clearCache(): void {\n cachedTemplates = null;\n}\n\nexport async function listTemplates(\n templatesDir?: string,\n deps: FsDeps = defaultFsDeps,\n): Promise<Template[]> {\n if (cachedTemplates) return cachedTemplates;\n\n const dir = templatesDir ?? getTemplatesDir();\n const templates: Template[] = [];\n\n const categories = await deps.readDir(dir);\n for (const category of categories) {\n if (category.startsWith(\".\")) continue;\n const categoryPath = join(dir, category);\n let files: string[];\n try {\n files = await deps.readDir(categoryPath);\n } catch {\n continue; // Skip non-directory entries\n }\n for (const file of files) {\n if (!file.endsWith(\".md\")) continue;\n try {\n const template = await parseTemplate(join(categoryPath, file), deps);\n templates.push(template);\n } catch {\n // Skip invalid templates\n }\n }\n }\n\n cachedTemplates = templates;\n return templates;\n}\n\nexport async function getTemplate(\n name: string,\n templatesDir?: string,\n deps: FsDeps = defaultFsDeps,\n): Promise<Template> {\n const templates = await listTemplates(templatesDir, deps);\n const template = templates.find((t) => t.frontmatter.name === name);\n if (!template) {\n throw new TemplateNotFoundError(name);\n }\n return template;\n}\n\nexport async function filterTemplates(\n options: { category?: string; tags?: string[] } = {},\n templatesDir?: string,\n deps: FsDeps = defaultFsDeps,\n): Promise<Template[]> {\n const templates = await listTemplates(templatesDir, deps);\n return templates.filter((t) => {\n if (options.category && t.frontmatter.category !== options.category) {\n return false;\n }\n if (options.tags && options.tags.length > 0) {\n return options.tags.some((tag) => t.frontmatter.tags.includes(tag));\n }\n return true;\n });\n}\n\nexport async function suggestTemplates(\n stack: DetectedStack,\n templatesDir?: string,\n deps: FsDeps = defaultFsDeps,\n): Promise<Template[]> {\n const templates = await listTemplates(templatesDir, deps);\n\n const scored = templates\n .map((template) => {\n let score = 0;\n const detects = template.frontmatter.detects;\n if (!detects) return { template, score };\n\n // Check file-based detection\n if (detects.files) {\n // File detection is handled by the project detector\n // Here we just match on deps\n }\n\n // Check dependency matches\n if (detects.dependencies) {\n for (const dep of detects.dependencies) {\n if (stack.dependencies.includes(dep)) score += 2;\n }\n }\n\n if (detects.devDependencies) {\n for (const dep of detects.devDependencies) {\n if (stack.devDependencies.includes(dep)) score += 1;\n }\n }\n\n // Language match\n if (\n template.frontmatter.category === stack.language ||\n template.frontmatter.tags.includes(stack.language)\n ) {\n score += 1;\n }\n\n // Framework match\n if (\n stack.framework &&\n template.frontmatter.tags.includes(stack.framework.toLowerCase())\n ) {\n score += 3;\n }\n\n return { template, score };\n })\n .filter((s) => s.score > 0)\n .sort((a, b) => {\n // Sort by score descending, then by priority descending\n if (b.score !== a.score) return b.score - a.score;\n return b.template.frontmatter.priority - a.template.frontmatter.priority;\n });\n\n return scored.map((s) => s.template);\n}\n"],"mappings":";;;;;;;;;;AAAA,OAAO,YAAY;AAKnB,eAAsB,cACpB,UACA,OAAe,eACI;AACnB,QAAM,MAAM,MAAM,KAAK,SAAS,QAAQ;AACxC,QAAM,EAAE,MAAM,QAAQ,IAAI,OAAO,GAAG;AAEpC,sBAAoB,MAAM,QAAQ;AAElC,SAAO;AAAA,IACL;AAAA,IACA,aAAa;AAAA,MACX,MAAM,KAAK;AAAA,MACX,aAAa,KAAK;AAAA,MAClB,aAAa,KAAK;AAAA,MAClB,UAAU,KAAK;AAAA,MACf,MAAM,KAAK,QAAQ,CAAC;AAAA,MACpB,WAAW,KAAK,aAAa,CAAC;AAAA,MAC9B,SAAS,KAAK;AAAA,MACd,UAAU,KAAK,YAAY;AAAA,IAC7B;AAAA,IACA,SAAS,QAAQ,KAAK;AAAA,EACxB;AACF;AAEA,SAAS,oBACP,MACA,UACM;AACN,QAAM,WAAW,CAAC,QAAQ,eAAe,eAAe,UAAU;AAClE,aAAW,SAAS,UAAU;AAC5B,QAAI,CAAC,KAAK,KAAK,GAAG;AAChB,YAAM,IAAI;AAAA,QACR,aAAa,QAAQ,4CAA4C,KAAK;AAAA,MACxE;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,eACd,UACA,WACQ;AACR,MAAI,SAAS,SAAS;AAGtB,QAAM,eAAe,oBAAI,IAAY;AACrC,QAAM,QAAQ;AACd,MAAI;AACJ,UAAQ,QAAQ,MAAM,KAAK,MAAM,OAAO,MAAM;AAC5C,iBAAa,IAAI,MAAM,CAAC,CAAC;AAAA,EAC3B;AAGA,aAAW,UAAU,SAAS,YAAY,WAAW;AACnD,QAAI,aAAa,IAAI,OAAO,IAAI,KAAK,CAAC,UAAU,OAAO,IAAI,GAAG;AAC5D,UAAI,OAAO,SAAS;AAClB,kBAAU,OAAO,IAAI,IAAI,OAAO;AAAA,MAClC,OAAO;AACL,cAAM,IAAI;AAAA,UACR,8BAA8B,OAAO,IAAI;AAAA,QAC3C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,WAAS,OAAO,QAAQ,kBAAkB,CAAC,QAAQ,SAAiB;AAClE,WAAO,UAAU,IAAI,KAAK;AAAA,EAC5B,CAAC;AAED,SAAO;AACT;;;AC7EA,SAAS,YAAY;AAOrB,IAAI,kBAAqC;AAMzC,eAAsB,cACpB,cACA,OAAe,eACM;AACrB,MAAI,gBAAiB,QAAO;AAE5B,QAAM,MAAM,gBAAgB,gBAAgB;AAC5C,QAAM,YAAwB,CAAC;AAE/B,QAAM,aAAa,MAAM,KAAK,QAAQ,GAAG;AACzC,aAAW,YAAY,YAAY;AACjC,QAAI,SAAS,WAAW,GAAG,EAAG;AAC9B,UAAM,eAAe,KAAK,KAAK,QAAQ;AACvC,QAAI;AACJ,QAAI;AACF,cAAQ,MAAM,KAAK,QAAQ,YAAY;AAAA,IACzC,QAAQ;AACN;AAAA,IACF;AACA,eAAW,QAAQ,OAAO;AACxB,UAAI,CAAC,KAAK,SAAS,KAAK,EAAG;AAC3B,UAAI;AACF,cAAM,WAAW,MAAM,cAAc,KAAK,cAAc,IAAI,GAAG,IAAI;AACnE,kBAAU,KAAK,QAAQ;AAAA,MACzB,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAEA,oBAAkB;AAClB,SAAO;AACT;AAeA,eAAsB,gBACpB,UAAkD,CAAC,GACnD,cACA,OAAe,eACM;AACrB,QAAM,YAAY,MAAM,cAAc,cAAc,IAAI;AACxD,SAAO,UAAU,OAAO,CAAC,MAAM;AAC7B,QAAI,QAAQ,YAAY,EAAE,YAAY,aAAa,QAAQ,UAAU;AACnE,aAAO;AAAA,IACT;AACA,QAAI,QAAQ,QAAQ,QAAQ,KAAK,SAAS,GAAG;AAC3C,aAAO,QAAQ,KAAK,KAAK,CAAC,QAAQ,EAAE,YAAY,KAAK,SAAS,GAAG,CAAC;AAAA,IACpE;AACA,WAAO;AAAA,EACT,CAAC;AACH;AAEA,eAAsB,iBACpB,OACA,cACA,OAAe,eACM;AACrB,QAAM,YAAY,MAAM,cAAc,cAAc,IAAI;AAExD,QAAM,SAAS,UACZ,IAAI,CAAC,aAAa;AACjB,QAAI,QAAQ;AACZ,UAAM,UAAU,SAAS,YAAY;AACrC,QAAI,CAAC,QAAS,QAAO,EAAE,UAAU,MAAM;AAGvC,QAAI,QAAQ,OAAO;AAAA,IAGnB;AAGA,QAAI,QAAQ,cAAc;AACxB,iBAAW,OAAO,QAAQ,cAAc;AACtC,YAAI,MAAM,aAAa,SAAS,GAAG,EAAG,UAAS;AAAA,MACjD;AAAA,IACF;AAEA,QAAI,QAAQ,iBAAiB;AAC3B,iBAAW,OAAO,QAAQ,iBAAiB;AACzC,YAAI,MAAM,gBAAgB,SAAS,GAAG,EAAG,UAAS;AAAA,MACpD;AAAA,IACF;AAGA,QACE,SAAS,YAAY,aAAa,MAAM,YACxC,SAAS,YAAY,KAAK,SAAS,MAAM,QAAQ,GACjD;AACA,eAAS;AAAA,IACX;AAGA,QACE,MAAM,aACN,SAAS,YAAY,KAAK,SAAS,MAAM,UAAU,YAAY,CAAC,GAChE;AACA,eAAS;AAAA,IACX;AAEA,WAAO,EAAE,UAAU,MAAM;AAAA,EAC3B,CAAC,EACA,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,EACzB,KAAK,CAAC,GAAG,MAAM;AAEd,QAAI,EAAE,UAAU,EAAE,MAAO,QAAO,EAAE,QAAQ,EAAE;AAC5C,WAAO,EAAE,SAAS,YAAY,WAAW,EAAE,SAAS,YAAY;AAAA,EAClE,CAAC;AAEH,SAAO,OAAO,IAAI,CAAC,MAAM,EAAE,QAAQ;AACrC;","names":[]}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/utils/fs.ts
|
|
4
|
+
import { readFile as nodeReadFile, writeFile as nodeWriteFile } from "fs/promises";
|
|
5
|
+
import { existsSync } from "fs";
|
|
6
|
+
import { readdir } from "fs/promises";
|
|
7
|
+
var defaultFsDeps = {
|
|
8
|
+
async readFile(path) {
|
|
9
|
+
return nodeReadFile(path, "utf-8");
|
|
10
|
+
},
|
|
11
|
+
async writeFile(path, content) {
|
|
12
|
+
await nodeWriteFile(path, content, "utf-8");
|
|
13
|
+
},
|
|
14
|
+
async fileExists(path) {
|
|
15
|
+
return existsSync(path);
|
|
16
|
+
},
|
|
17
|
+
async readDir(path) {
|
|
18
|
+
return readdir(path);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// src/utils/paths.ts
|
|
23
|
+
import { dirname, join, resolve } from "path";
|
|
24
|
+
import { fileURLToPath } from "url";
|
|
25
|
+
import { existsSync as existsSync2 } from "fs";
|
|
26
|
+
function getTemplatesDir() {
|
|
27
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
28
|
+
const __dirname = dirname(__filename);
|
|
29
|
+
let dir = __dirname;
|
|
30
|
+
for (let i = 0; i < 5; i++) {
|
|
31
|
+
const candidate = join(dir, "templates");
|
|
32
|
+
if (existsSync2(candidate)) {
|
|
33
|
+
return candidate;
|
|
34
|
+
}
|
|
35
|
+
dir = dirname(dir);
|
|
36
|
+
}
|
|
37
|
+
return join(dirname(dirname(__dirname)), "templates");
|
|
38
|
+
}
|
|
39
|
+
function findProjectRoot(startDir) {
|
|
40
|
+
let dir = startDir ? resolve(startDir) : process.cwd();
|
|
41
|
+
while (true) {
|
|
42
|
+
const indicators = [
|
|
43
|
+
"package.json",
|
|
44
|
+
"pyproject.toml",
|
|
45
|
+
"Cargo.toml",
|
|
46
|
+
"go.mod",
|
|
47
|
+
".git"
|
|
48
|
+
];
|
|
49
|
+
for (const indicator of indicators) {
|
|
50
|
+
if (existsSync2(join(dir, indicator))) {
|
|
51
|
+
return dir;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const parent = dirname(dir);
|
|
55
|
+
if (parent === dir) break;
|
|
56
|
+
dir = parent;
|
|
57
|
+
}
|
|
58
|
+
return startDir ? resolve(startDir) : process.cwd();
|
|
59
|
+
}
|
|
60
|
+
function findClaudeMd(projectRoot) {
|
|
61
|
+
const candidates = [
|
|
62
|
+
join(projectRoot, "CLAUDE.md"),
|
|
63
|
+
join(projectRoot, ".claude", "CLAUDE.md")
|
|
64
|
+
];
|
|
65
|
+
for (const candidate of candidates) {
|
|
66
|
+
if (existsSync2(candidate)) {
|
|
67
|
+
return candidate;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export {
|
|
74
|
+
defaultFsDeps,
|
|
75
|
+
getTemplatesDir,
|
|
76
|
+
findProjectRoot,
|
|
77
|
+
findClaudeMd
|
|
78
|
+
};
|
|
79
|
+
//# sourceMappingURL=chunk-3R6PUA3E.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/fs.ts","../src/utils/paths.ts"],"sourcesContent":["import { readFile as nodeReadFile, writeFile as nodeWriteFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport type { FsDeps } from \"../types.js\";\n\nexport const defaultFsDeps: FsDeps = {\n async readFile(path: string): Promise<string> {\n return nodeReadFile(path, \"utf-8\");\n },\n\n async writeFile(path: string, content: string): Promise<void> {\n await nodeWriteFile(path, content, \"utf-8\");\n },\n\n async fileExists(path: string): Promise<boolean> {\n return existsSync(path);\n },\n\n async readDir(path: string): Promise<string[]> {\n return readdir(path);\n },\n};\n","import { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { existsSync } from \"node:fs\";\n\nexport function getTemplatesDir(): string {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n // From dist/cli.mjs or src/utils/paths.ts, go up to package root\n let dir = __dirname;\n for (let i = 0; i < 5; i++) {\n const candidate = join(dir, \"templates\");\n if (existsSync(candidate)) {\n return candidate;\n }\n dir = dirname(dir);\n }\n // Fallback: relative to package root\n return join(dirname(dirname(__dirname)), \"templates\");\n}\n\nexport function findProjectRoot(startDir?: string): string {\n let dir = startDir ? resolve(startDir) : process.cwd();\n while (true) {\n // Check for common project root indicators\n const indicators = [\n \"package.json\",\n \"pyproject.toml\",\n \"Cargo.toml\",\n \"go.mod\",\n \".git\",\n ];\n for (const indicator of indicators) {\n if (existsSync(join(dir, indicator))) {\n return dir;\n }\n }\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n return startDir ? resolve(startDir) : process.cwd();\n}\n\nexport function findClaudeMd(projectRoot: string): string | null {\n // Check common CLAUDE.md locations\n const candidates = [\n join(projectRoot, \"CLAUDE.md\"),\n join(projectRoot, \".claude\", \"CLAUDE.md\"),\n ];\n for (const candidate of candidates) {\n if (existsSync(candidate)) {\n return candidate;\n }\n }\n return null;\n}\n"],"mappings":";;;AAAA,SAAS,YAAY,cAAc,aAAa,qBAAqB;AACrE,SAAS,kBAAkB;AAC3B,SAAS,eAAe;AAGjB,IAAM,gBAAwB;AAAA,EACnC,MAAM,SAAS,MAA+B;AAC5C,WAAO,aAAa,MAAM,OAAO;AAAA,EACnC;AAAA,EAEA,MAAM,UAAU,MAAc,SAAgC;AAC5D,UAAM,cAAc,MAAM,SAAS,OAAO;AAAA,EAC5C;AAAA,EAEA,MAAM,WAAW,MAAgC;AAC/C,WAAO,WAAW,IAAI;AAAA,EACxB;AAAA,EAEA,MAAM,QAAQ,MAAiC;AAC7C,WAAO,QAAQ,IAAI;AAAA,EACrB;AACF;;;ACrBA,SAAS,SAAS,MAAM,eAAe;AACvC,SAAS,qBAAqB;AAC9B,SAAS,cAAAA,mBAAkB;AAEpB,SAAS,kBAA0B;AACxC,QAAM,aAAa,cAAc,YAAY,GAAG;AAChD,QAAM,YAAY,QAAQ,UAAU;AAEpC,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,UAAM,YAAY,KAAK,KAAK,WAAW;AACvC,QAAIA,YAAW,SAAS,GAAG;AACzB,aAAO;AAAA,IACT;AACA,UAAM,QAAQ,GAAG;AAAA,EACnB;AAEA,SAAO,KAAK,QAAQ,QAAQ,SAAS,CAAC,GAAG,WAAW;AACtD;AAEO,SAAS,gBAAgB,UAA2B;AACzD,MAAI,MAAM,WAAW,QAAQ,QAAQ,IAAI,QAAQ,IAAI;AACrD,SAAO,MAAM;AAEX,UAAM,aAAa;AAAA,MACjB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,eAAW,aAAa,YAAY;AAClC,UAAIA,YAAW,KAAK,KAAK,SAAS,CAAC,GAAG;AACpC,eAAO;AAAA,MACT;AAAA,IACF;AACA,UAAM,SAAS,QAAQ,GAAG;AAC1B,QAAI,WAAW,IAAK;AACpB,UAAM;AAAA,EACR;AACA,SAAO,WAAW,QAAQ,QAAQ,IAAI,QAAQ,IAAI;AACpD;AAEO,SAAS,aAAa,aAAoC;AAE/D,QAAM,aAAa;AAAA,IACjB,KAAK,aAAa,WAAW;AAAA,IAC7B,KAAK,aAAa,WAAW,WAAW;AAAA,EAC1C;AACA,aAAW,aAAa,YAAY;AAClC,QAAIA,YAAW,SAAS,GAAG;AACzB,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;","names":["existsSync"]}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/utils/errors.ts
|
|
4
|
+
var TemplateNotFoundError = class extends Error {
|
|
5
|
+
constructor(name) {
|
|
6
|
+
super(`Template not found: "${name}"`);
|
|
7
|
+
this.name = "TemplateNotFoundError";
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
var ValidationError = class extends Error {
|
|
11
|
+
constructor(message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "ValidationError";
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var FileExistsError = class extends Error {
|
|
17
|
+
constructor(path) {
|
|
18
|
+
super(`File already exists: ${path}`);
|
|
19
|
+
this.name = "FileExistsError";
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
function formatError(error) {
|
|
23
|
+
if (error instanceof TemplateNotFoundError) {
|
|
24
|
+
return `Error: ${error.message}`;
|
|
25
|
+
}
|
|
26
|
+
if (error instanceof ValidationError) {
|
|
27
|
+
return `Validation Error: ${error.message}`;
|
|
28
|
+
}
|
|
29
|
+
if (error instanceof FileExistsError) {
|
|
30
|
+
return `Error: ${error.message}`;
|
|
31
|
+
}
|
|
32
|
+
if (error instanceof Error) {
|
|
33
|
+
return `Error: ${error.message}`;
|
|
34
|
+
}
|
|
35
|
+
return `Error: ${String(error)}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export {
|
|
39
|
+
ValidationError,
|
|
40
|
+
formatError
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=chunk-3WTPUEHL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/errors.ts"],"sourcesContent":["export class TemplateNotFoundError extends Error {\n constructor(name: string) {\n super(`Template not found: \"${name}\"`);\n this.name = \"TemplateNotFoundError\";\n }\n}\n\nexport class ValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ValidationError\";\n }\n}\n\nexport class FileExistsError extends Error {\n constructor(path: string) {\n super(`File already exists: ${path}`);\n this.name = \"FileExistsError\";\n }\n}\n\nexport function formatError(error: unknown): string {\n if (error instanceof TemplateNotFoundError) {\n return `Error: ${error.message}`;\n }\n if (error instanceof ValidationError) {\n return `Validation Error: ${error.message}`;\n }\n if (error instanceof FileExistsError) {\n return `Error: ${error.message}`;\n }\n if (error instanceof Error) {\n return `Error: ${error.message}`;\n }\n return `Error: ${String(error)}`;\n}\n"],"mappings":";;;AAAO,IAAM,wBAAN,cAAoC,MAAM;AAAA,EAC/C,YAAY,MAAc;AACxB,UAAM,wBAAwB,IAAI,GAAG;AACrC,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,kBAAN,cAA8B,MAAM;AAAA,EACzC,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,kBAAN,cAA8B,MAAM;AAAA,EACzC,YAAY,MAAc;AACxB,UAAM,wBAAwB,IAAI,EAAE;AACpC,SAAK,OAAO;AAAA,EACd;AACF;AAEO,SAAS,YAAY,OAAwB;AAClD,MAAI,iBAAiB,uBAAuB;AAC1C,WAAO,UAAU,MAAM,OAAO;AAAA,EAChC;AACA,MAAI,iBAAiB,iBAAiB;AACpC,WAAO,qBAAqB,MAAM,OAAO;AAAA,EAC3C;AACA,MAAI,iBAAiB,iBAAiB;AACpC,WAAO,UAAU,MAAM,OAAO;AAAA,EAChC;AACA,MAAI,iBAAiB,OAAO;AAC1B,WAAO,UAAU,MAAM,OAAO;AAAA,EAChC;AACA,SAAO,UAAU,OAAO,KAAK,CAAC;AAChC;","names":[]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/utils/logger.ts
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
function success(message) {
|
|
6
|
+
console.log(chalk.green(`\u2713 ${message}`));
|
|
7
|
+
}
|
|
8
|
+
function error(message) {
|
|
9
|
+
console.error(chalk.red(`\u2717 ${message}`));
|
|
10
|
+
}
|
|
11
|
+
function warn(message) {
|
|
12
|
+
console.log(chalk.yellow(`\u26A0 ${message}`));
|
|
13
|
+
}
|
|
14
|
+
function info(message) {
|
|
15
|
+
console.log(chalk.blue(`\u2139 ${message}`));
|
|
16
|
+
}
|
|
17
|
+
function dim(message) {
|
|
18
|
+
console.log(chalk.dim(message));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
success,
|
|
23
|
+
error,
|
|
24
|
+
warn,
|
|
25
|
+
info,
|
|
26
|
+
dim
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=chunk-YHVOBZLV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/logger.ts"],"sourcesContent":["import chalk from \"chalk\";\n\nexport function success(message: string): void {\n console.log(chalk.green(`✓ ${message}`));\n}\n\nexport function error(message: string): void {\n console.error(chalk.red(`✗ ${message}`));\n}\n\nexport function warn(message: string): void {\n console.log(chalk.yellow(`⚠ ${message}`));\n}\n\nexport function info(message: string): void {\n console.log(chalk.blue(`ℹ ${message}`));\n}\n\nexport function dim(message: string): void {\n console.log(chalk.dim(message));\n}\n"],"mappings":";;;AAAA,OAAO,WAAW;AAEX,SAAS,QAAQ,SAAuB;AAC7C,UAAQ,IAAI,MAAM,MAAM,UAAK,OAAO,EAAE,CAAC;AACzC;AAEO,SAAS,MAAM,SAAuB;AAC3C,UAAQ,MAAM,MAAM,IAAI,UAAK,OAAO,EAAE,CAAC;AACzC;AAEO,SAAS,KAAK,SAAuB;AAC1C,UAAQ,IAAI,MAAM,OAAO,UAAK,OAAO,EAAE,CAAC;AAC1C;AAEO,SAAS,KAAK,SAAuB;AAC1C,UAAQ,IAAI,MAAM,KAAK,UAAK,OAAO,EAAE,CAAC;AACxC;AAEO,SAAS,IAAI,SAAuB;AACzC,UAAQ,IAAI,MAAM,IAAI,OAAO,CAAC;AAChC;","names":[]}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
formatError
|
|
4
|
+
} from "./chunk-3WTPUEHL.js";
|
|
5
|
+
import {
|
|
6
|
+
error
|
|
7
|
+
} from "./chunk-YHVOBZLV.js";
|
|
8
|
+
|
|
9
|
+
// src/cli.ts
|
|
10
|
+
import { Command } from "commander";
|
|
11
|
+
var program = new Command();
|
|
12
|
+
program.name("dotclaudemd").description(
|
|
13
|
+
"CLAUDE.md Template Registry CLI \u2014 scaffold, lint, and health-check your CLAUDE.md files"
|
|
14
|
+
).version("0.1.1");
|
|
15
|
+
program.command("init").description("Scaffold a CLAUDE.md from a template").option("--stack <name>", "Use a specific template by name (skip detection)").option("--global", "Write to ~/.claude/CLAUDE.md").option("--no-interactive", "Use defaults without prompting").option("--force", "Overwrite existing CLAUDE.md without prompting").action(async (options) => {
|
|
16
|
+
const { initCommand } = await import("./init-GLWLFVHN.js");
|
|
17
|
+
await initCommand({
|
|
18
|
+
stack: options.stack,
|
|
19
|
+
global: options.global,
|
|
20
|
+
noInteractive: !options.interactive,
|
|
21
|
+
force: options.force
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
program.command("lint [file]").description("Lint a CLAUDE.md for anti-patterns").option("--json", "Output results as JSON").action(async (file, options) => {
|
|
25
|
+
const { lintCommand } = await import("./lint-W7ZIDPL7.js");
|
|
26
|
+
await lintCommand(file, { json: options.json });
|
|
27
|
+
});
|
|
28
|
+
program.command("doctor").description("Check CLAUDE.md freshness against project state").option("--json", "Output results as JSON").action(async (options) => {
|
|
29
|
+
const { doctorCommand } = await import("./doctor-4B7J2EH3.js");
|
|
30
|
+
await doctorCommand({ json: options.json });
|
|
31
|
+
});
|
|
32
|
+
program.command("browse").description("Browse and preview available templates").option("--category <cat>", "Filter by category").option("--list", "Non-interactive list mode").action(async (options) => {
|
|
33
|
+
const { browseCommand } = await import("./browse-7V4CRGTH.js");
|
|
34
|
+
await browseCommand({ category: options.category, list: options.list });
|
|
35
|
+
});
|
|
36
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
37
|
+
error(formatError(err));
|
|
38
|
+
process.exitCode = 1;
|
|
39
|
+
});
|
|
40
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { formatError } from \"./utils/errors.js\";\nimport * as logger from \"./utils/logger.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"dotclaudemd\")\n .description(\n \"CLAUDE.md Template Registry CLI — scaffold, lint, and health-check your CLAUDE.md files\",\n )\n .version(\"0.1.1\");\n\nprogram\n .command(\"init\")\n .description(\"Scaffold a CLAUDE.md from a template\")\n .option(\"--stack <name>\", \"Use a specific template by name (skip detection)\")\n .option(\"--global\", \"Write to ~/.claude/CLAUDE.md\")\n .option(\"--no-interactive\", \"Use defaults without prompting\")\n .option(\"--force\", \"Overwrite existing CLAUDE.md without prompting\")\n .action(async (options) => {\n const { initCommand } = await import(\"./commands/init.js\");\n await initCommand({\n stack: options.stack,\n global: options.global,\n noInteractive: !options.interactive,\n force: options.force,\n });\n });\n\nprogram\n .command(\"lint [file]\")\n .description(\"Lint a CLAUDE.md for anti-patterns\")\n .option(\"--json\", \"Output results as JSON\")\n .action(async (file, options) => {\n const { lintCommand } = await import(\"./commands/lint.js\");\n await lintCommand(file, { json: options.json });\n });\n\nprogram\n .command(\"doctor\")\n .description(\"Check CLAUDE.md freshness against project state\")\n .option(\"--json\", \"Output results as JSON\")\n .action(async (options) => {\n const { doctorCommand } = await import(\"./commands/doctor.js\");\n await doctorCommand({ json: options.json });\n });\n\nprogram\n .command(\"browse\")\n .description(\"Browse and preview available templates\")\n .option(\"--category <cat>\", \"Filter by category\")\n .option(\"--list\", \"Non-interactive list mode\")\n .action(async (options) => {\n const { browseCommand } = await import(\"./commands/browse.js\");\n await browseCommand({ category: options.category, list: options.list });\n });\n\nprogram.parseAsync(process.argv).catch((err) => {\n logger.error(formatError(err));\n process.exitCode = 1;\n});\n"],"mappings":";;;;;;;;;AAAA,SAAS,eAAe;AAIxB,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,aAAa,EAClB;AAAA,EACC;AACF,EACC,QAAQ,OAAO;AAElB,QACG,QAAQ,MAAM,EACd,YAAY,sCAAsC,EAClD,OAAO,kBAAkB,kDAAkD,EAC3E,OAAO,YAAY,8BAA8B,EACjD,OAAO,oBAAoB,gCAAgC,EAC3D,OAAO,WAAW,gDAAgD,EAClE,OAAO,OAAO,YAAY;AACzB,QAAM,EAAE,YAAY,IAAI,MAAM,OAAO,oBAAoB;AACzD,QAAM,YAAY;AAAA,IAChB,OAAO,QAAQ;AAAA,IACf,QAAQ,QAAQ;AAAA,IAChB,eAAe,CAAC,QAAQ;AAAA,IACxB,OAAO,QAAQ;AAAA,EACjB,CAAC;AACH,CAAC;AAEH,QACG,QAAQ,aAAa,EACrB,YAAY,oCAAoC,EAChD,OAAO,UAAU,wBAAwB,EACzC,OAAO,OAAO,MAAM,YAAY;AAC/B,QAAM,EAAE,YAAY,IAAI,MAAM,OAAO,oBAAoB;AACzD,QAAM,YAAY,MAAM,EAAE,MAAM,QAAQ,KAAK,CAAC;AAChD,CAAC;AAEH,QACG,QAAQ,QAAQ,EAChB,YAAY,iDAAiD,EAC7D,OAAO,UAAU,wBAAwB,EACzC,OAAO,OAAO,YAAY;AACzB,QAAM,EAAE,cAAc,IAAI,MAAM,OAAO,sBAAsB;AAC7D,QAAM,cAAc,EAAE,MAAM,QAAQ,KAAK,CAAC;AAC5C,CAAC;AAEH,QACG,QAAQ,QAAQ,EAChB,YAAY,wCAAwC,EACpD,OAAO,oBAAoB,oBAAoB,EAC/C,OAAO,UAAU,2BAA2B,EAC5C,OAAO,OAAO,YAAY;AACzB,QAAM,EAAE,cAAc,IAAI,MAAM,OAAO,sBAAsB;AAC7D,QAAM,cAAc,EAAE,UAAU,QAAQ,UAAU,MAAM,QAAQ,KAAK,CAAC;AACxE,CAAC;AAEH,QAAQ,WAAW,QAAQ,IAAI,EAAE,MAAM,CAAC,QAAQ;AAC9C,EAAO,MAAM,YAAY,GAAG,CAAC;AAC7B,UAAQ,WAAW;AACrB,CAAC;","names":[]}
|