claude-launchpad 0.3.4 → 0.4.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 +70 -22
- package/dist/cli.js +119 -131
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 mboss37
|
|
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
CHANGED
|
@@ -1,25 +1,53 @@
|
|
|
1
1
|
# Claude Launchpad
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/claude-launchpad)
|
|
4
|
+
[](https://www.npmjs.com/package/claude-launchpad)
|
|
5
|
+
[](https://github.com/mboss37/claude-launchpad)
|
|
6
|
+
[](https://github.com/mboss37/claude-launchpad/blob/master/LICENSE)
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
**Everything you need to launch a project with Claude Code — and keep it healthy.**
|
|
9
|
+
|
|
10
|
+
A launchpad isn't just where you start. It's where you prepare, run checks, and make sure everything is ready before you go. Claude Launchpad does exactly that for your Claude Code setup:
|
|
11
|
+
|
|
12
|
+
- **Launch new projects** with production-ready Claude Code config from day one
|
|
13
|
+
- **Check existing projects** — score your config, find issues, auto-fix them
|
|
14
|
+
- **Prove it works** — run Claude against test scenarios and see if your rules are actually followed
|
|
6
15
|
|
|
7
16
|
```bash
|
|
8
|
-
|
|
17
|
+
npm i -g claude-launchpad
|
|
18
|
+
cd your-project
|
|
9
19
|
```
|
|
10
20
|
|
|
11
|
-
|
|
21
|
+
## Two Paths, One Tool
|
|
12
22
|
|
|
13
|
-
|
|
23
|
+
### Starting a new project?
|
|
14
24
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
```bash
|
|
26
|
+
claude-launchpad init
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Detects your stack, generates `CLAUDE.md` with your commands and conventions, creates `TASKS.md` for sprint tracking and session continuity, sets up hooks for auto-formatting and `.env` protection, and adds a `.claudeignore` so Claude doesn't waste time reading `node_modules`.
|
|
30
|
+
|
|
31
|
+
Then run `enhance` to have Claude read your codebase and fill in the architecture, conventions, and guardrails with real, project-specific content — not boilerplate.
|
|
32
|
+
|
|
33
|
+
### Already have a project?
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
claude-launchpad
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Scans your Claude Code config, gives you a score out of 100, and tells you exactly what's wrong. Run `--fix` to auto-apply fixes. Run `--watch` to see the score update live as you edit. Run `eval` to prove your config actually makes Claude behave.
|
|
40
|
+
|
|
41
|
+
## All Commands
|
|
42
|
+
|
|
43
|
+
| Command | What it does |
|
|
44
|
+
|---|---|
|
|
45
|
+
| `claude-launchpad init` | Launch a new project: detects stack, generates config, security rules, hooks, permissions |
|
|
46
|
+
| `claude-launchpad` | Check your config: score it 0-100, list issues |
|
|
47
|
+
| `claude-launchpad doctor --fix` | Auto-fix issues: adds hooks, rules, missing sections, .claudeignore |
|
|
48
|
+
| `claude-launchpad doctor --watch` | Live score that updates when you save config files |
|
|
49
|
+
| `claude-launchpad enhance` | Claude reads your code and completes CLAUDE.md with real content |
|
|
50
|
+
| `claude-launchpad eval --suite security` | Run Claude against test scenarios, prove your config works |
|
|
23
51
|
|
|
24
52
|
## Quick Start
|
|
25
53
|
|
|
@@ -40,7 +68,7 @@ claude-launchpad doctor --fix
|
|
|
40
68
|
claude-launchpad
|
|
41
69
|
```
|
|
42
70
|
|
|
43
|
-
That takes you from ~42% to ~
|
|
71
|
+
That takes you from ~42% to ~93% with zero manual work.
|
|
44
72
|
|
|
45
73
|
## The Doctor
|
|
46
74
|
|
|
@@ -88,23 +116,26 @@ Detects your project and generates Claude Code config that fits. No templates, n
|
|
|
88
116
|
|
|
89
117
|
```
|
|
90
118
|
→ Detecting project...
|
|
91
|
-
✓ Found Next.js
|
|
119
|
+
✓ Found Next.js project
|
|
92
120
|
· Package manager: pnpm
|
|
93
|
-
· Dev command: pnpm dev
|
|
94
121
|
|
|
95
122
|
✓ Generated CLAUDE.md
|
|
96
123
|
✓ Generated TASKS.md
|
|
97
|
-
✓ Generated .claude/settings.json (
|
|
124
|
+
✓ Generated .claude/settings.json (schema, permissions, hooks)
|
|
125
|
+
✓ Generated .claude/.gitignore
|
|
98
126
|
✓ Generated .claudeignore
|
|
127
|
+
✓ Generated .claude/rules/conventions.md
|
|
99
128
|
```
|
|
100
129
|
|
|
101
130
|
**Works with:** TypeScript, JavaScript, Python, Go, Ruby, Rust, Dart, PHP, Java, Kotlin, Swift, Elixir, C# — and detects frameworks (Next.js, FastAPI, Django, Rails, Laravel, Express, SvelteKit, Angular, NestJS, and 15+ more).
|
|
102
131
|
|
|
103
|
-
**What you get:**
|
|
132
|
+
**What you get (6 files):**
|
|
104
133
|
- `CLAUDE.md` — your stack, commands, conventions, guardrails
|
|
105
|
-
- `TASKS.md` —
|
|
106
|
-
- `.claude/settings.json` —
|
|
107
|
-
- `.
|
|
134
|
+
- `TASKS.md` — sprint tracking and session continuity
|
|
135
|
+
- `.claude/settings.json` — `$schema` for IDE autocomplete, `permissions.deny` for security, hooks for .env protection + destructive command blocking + auto-format
|
|
136
|
+
- `.claude/.gitignore` — prevents local settings and plans from being committed
|
|
137
|
+
- `.claudeignore` — language-specific ignore patterns
|
|
138
|
+
- `.claude/rules/conventions.md` — language-specific starter rules
|
|
108
139
|
|
|
109
140
|
## Enhance
|
|
110
141
|
|
|
@@ -210,7 +241,7 @@ Then use `/launchpad:doctor`, `/launchpad:init`, `/launchpad:enhance`, `/launchp
|
|
|
210
241
|
|
|
211
242
|
**Doctor** reads your files and runs static analysis. No API calls. No network. No cost.
|
|
212
243
|
|
|
213
|
-
**Init** scans manifest files (package.json, go.mod, pyproject.toml, etc.), detects your stack, and generates
|
|
244
|
+
**Init** scans manifest files (package.json, go.mod, pyproject.toml, etc.), detects your stack, and generates 6 files: CLAUDE.md, TASKS.md, settings.json (with $schema, permissions.deny, and hooks), .claude/.gitignore, .claudeignore, and language-specific rules. Formatter hooks use hardcoded safe commands only.
|
|
214
245
|
|
|
215
246
|
**Enhance** spawns `claude "prompt"` as an interactive child process. You see Claude's full UI. No data passes through the tool — it just launches Claude with a task.
|
|
216
247
|
|
|
@@ -225,6 +256,23 @@ Then use `/launchpad:doctor`, `/launchpad:init`, `/launchpad:enhance`, `/launchp
|
|
|
225
256
|
|
|
226
257
|
This tool gives you a number. Fix the issues, re-run, watch the number go up.
|
|
227
258
|
|
|
259
|
+
## Glossary
|
|
260
|
+
|
|
261
|
+
New to Claude Code? Here's what the terms mean:
|
|
262
|
+
|
|
263
|
+
| Term | What it is |
|
|
264
|
+
|---|---|
|
|
265
|
+
| **CLAUDE.md** | A markdown file in your project root that tells Claude how to work on your code. Think of it as instructions for your AI pair programmer. [Official docs](https://docs.anthropic.com/en/docs/claude-code/memory#claudemd) |
|
|
266
|
+
| **Hooks** | Shell commands that run automatically when Claude does something. For example: auto-format a file after Claude edits it, or block Claude from reading your `.env` file. They live in `.claude/settings.json`. |
|
|
267
|
+
| **Instruction budget** | CLAUDE.md has a soft limit of ~150 actionable lines. Past that, Claude starts ignoring rules at the bottom. Doctor counts your lines and warns you. |
|
|
268
|
+
| **Rules** | Extra markdown files in `.claude/rules/` that Claude reads alongside CLAUDE.md. Use them to offload detailed conventions so CLAUDE.md stays under budget. |
|
|
269
|
+
| **MCP Servers** | External tools Claude can connect to (databases, APIs, docs). Configured in `.claude/settings.json`. Most projects don't need them. |
|
|
270
|
+
| **.claudeignore** | Like `.gitignore` but for Claude. Tells Claude which files to skip (node_modules, dist, lockfiles) so it doesn't waste time reading noise. |
|
|
271
|
+
|
|
272
|
+
## Privacy
|
|
273
|
+
|
|
274
|
+
No telemetry. No analytics. No data sent anywhere. Doctor, init, and fix are fully offline. Enhance and eval run through your local Claude CLI — no data passes through this tool. [Full privacy policy](https://mboss37.github.io/claude-launchpad/privacy.html).
|
|
275
|
+
|
|
228
276
|
## License
|
|
229
277
|
|
|
230
278
|
MIT
|
package/dist/cli.js
CHANGED
|
@@ -248,98 +248,35 @@ function detectPackageManager(m, lockfiles) {
|
|
|
248
248
|
if (m.composerJson) return "composer";
|
|
249
249
|
return null;
|
|
250
250
|
}
|
|
251
|
+
var LANGUAGE_SCRIPTS = {
|
|
252
|
+
Go: { devCommand: "go run .", buildCommand: "go build .", testCommand: "go test ./...", lintCommand: "golangci-lint run", formatCommand: "gofmt -w ." },
|
|
253
|
+
Ruby: { devCommand: "bin/dev", buildCommand: null, testCommand: "bin/rails test", lintCommand: "bin/rubocop", formatCommand: null },
|
|
254
|
+
PHP: { devCommand: "php artisan serve", buildCommand: null, testCommand: "php artisan test", lintCommand: "vendor/bin/phpstan analyse", formatCommand: "vendor/bin/pint" },
|
|
255
|
+
Rust: { devCommand: "cargo run", buildCommand: "cargo build", testCommand: "cargo test", lintCommand: "cargo clippy", formatCommand: "cargo fmt" },
|
|
256
|
+
Java: { devCommand: null, buildCommand: "mvn package", testCommand: "mvn test", lintCommand: null, formatCommand: null },
|
|
257
|
+
Kotlin: { devCommand: null, buildCommand: "mvn package", testCommand: "mvn test", lintCommand: null, formatCommand: null },
|
|
258
|
+
Swift: { devCommand: null, buildCommand: "swift build", testCommand: "swift test", lintCommand: "swiftlint", formatCommand: "swift-format format -r ." },
|
|
259
|
+
Elixir: { devCommand: "mix phx.server", buildCommand: "mix compile", testCommand: "mix test", lintCommand: "mix credo", formatCommand: "mix format" },
|
|
260
|
+
"C#": { devCommand: "dotnet run", buildCommand: "dotnet build", testCommand: "dotnet test", lintCommand: null, formatCommand: "dotnet format" }
|
|
261
|
+
};
|
|
251
262
|
function detectScripts(m) {
|
|
252
|
-
const scripts = m.pkgJson?.scripts ?? {};
|
|
253
263
|
if (m.pkgJson) {
|
|
264
|
+
const scripts = m.pkgJson.scripts ?? {};
|
|
265
|
+
const run = pmRun(m.pkgJson);
|
|
254
266
|
return {
|
|
255
|
-
devCommand: scripts.dev ? `${
|
|
256
|
-
buildCommand: scripts.build ? `${
|
|
257
|
-
testCommand: scripts.test ? `${
|
|
258
|
-
lintCommand: scripts.lint ? `${
|
|
259
|
-
formatCommand: scripts.format ? `${
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
if (m.language === "Go") {
|
|
263
|
-
return {
|
|
264
|
-
devCommand: "go run .",
|
|
265
|
-
buildCommand: "go build .",
|
|
266
|
-
testCommand: "go test ./...",
|
|
267
|
-
lintCommand: "golangci-lint run",
|
|
268
|
-
formatCommand: "gofmt -w ."
|
|
267
|
+
devCommand: scripts.dev ? `${run} dev` : null,
|
|
268
|
+
buildCommand: scripts.build ? `${run} build` : null,
|
|
269
|
+
testCommand: scripts.test ? `${run} test` : null,
|
|
270
|
+
lintCommand: scripts.lint ? `${run} lint` : null,
|
|
271
|
+
formatCommand: scripts.format ? `${run} format` : null
|
|
269
272
|
};
|
|
270
273
|
}
|
|
271
274
|
if (m.language === "Python") {
|
|
272
|
-
const
|
|
273
|
-
return {
|
|
274
|
-
devCommand: null,
|
|
275
|
-
buildCommand: null,
|
|
276
|
-
testCommand: `${runner} pytest`,
|
|
277
|
-
lintCommand: `${runner} ruff check .`,
|
|
278
|
-
formatCommand: `${runner} ruff format .`
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
if (m.gemfile) {
|
|
282
|
-
return {
|
|
283
|
-
devCommand: "bin/dev",
|
|
284
|
-
buildCommand: null,
|
|
285
|
-
testCommand: "bin/rails test",
|
|
286
|
-
lintCommand: "bin/rubocop",
|
|
287
|
-
formatCommand: null
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
if (m.language === "PHP") {
|
|
291
|
-
return {
|
|
292
|
-
devCommand: "php artisan serve",
|
|
293
|
-
buildCommand: null,
|
|
294
|
-
testCommand: "php artisan test",
|
|
295
|
-
lintCommand: "vendor/bin/phpstan analyse",
|
|
296
|
-
formatCommand: "vendor/bin/pint"
|
|
297
|
-
};
|
|
275
|
+
const r = m.pyProject?.includes("[tool.uv]") ? "uv run" : "python -m";
|
|
276
|
+
return { devCommand: null, buildCommand: null, testCommand: `${r} pytest`, lintCommand: `${r} ruff check .`, formatCommand: `${r} ruff format .` };
|
|
298
277
|
}
|
|
299
|
-
if (m.language
|
|
300
|
-
return
|
|
301
|
-
devCommand: "cargo run",
|
|
302
|
-
buildCommand: "cargo build",
|
|
303
|
-
testCommand: "cargo test",
|
|
304
|
-
lintCommand: "cargo clippy",
|
|
305
|
-
formatCommand: "cargo fmt"
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
if (m.language === "Java" || m.language === "Kotlin") {
|
|
309
|
-
return {
|
|
310
|
-
devCommand: null,
|
|
311
|
-
buildCommand: "mvn package",
|
|
312
|
-
testCommand: "mvn test",
|
|
313
|
-
lintCommand: null,
|
|
314
|
-
formatCommand: null
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
if (m.language === "Swift") {
|
|
318
|
-
return {
|
|
319
|
-
devCommand: null,
|
|
320
|
-
buildCommand: "swift build",
|
|
321
|
-
testCommand: "swift test",
|
|
322
|
-
lintCommand: "swiftlint",
|
|
323
|
-
formatCommand: "swift-format format -r ."
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
if (m.language === "Elixir") {
|
|
327
|
-
return {
|
|
328
|
-
devCommand: "mix phx.server",
|
|
329
|
-
buildCommand: "mix compile",
|
|
330
|
-
testCommand: "mix test",
|
|
331
|
-
lintCommand: "mix credo",
|
|
332
|
-
formatCommand: "mix format"
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
|
-
if (m.language === "C#") {
|
|
336
|
-
return {
|
|
337
|
-
devCommand: "dotnet run",
|
|
338
|
-
buildCommand: "dotnet build",
|
|
339
|
-
testCommand: "dotnet test",
|
|
340
|
-
lintCommand: null,
|
|
341
|
-
formatCommand: "dotnet format"
|
|
342
|
-
};
|
|
278
|
+
if (m.language && LANGUAGE_SCRIPTS[m.language]) {
|
|
279
|
+
return LANGUAGE_SCRIPTS[m.language];
|
|
343
280
|
}
|
|
344
281
|
return { devCommand: null, buildCommand: null, testCommand: null, lintCommand: null, formatCommand: null };
|
|
345
282
|
}
|
|
@@ -440,7 +377,14 @@ function generateSettings(detected) {
|
|
|
440
377
|
matcher: "Read|Write|Edit",
|
|
441
378
|
hooks: [{
|
|
442
379
|
type: "command",
|
|
443
|
-
command: `echo "$TOOL_INPUT_FILE_PATH" | grep -qE '\\.(env|env\\..*)$' && ! echo "$TOOL_INPUT_FILE_PATH" | grep -q '.env.example' && echo 'BLOCKED: .env files contain secrets
|
|
380
|
+
command: `echo "$TOOL_INPUT_FILE_PATH" | grep -qE '\\.(env|env\\..*)$' && ! echo "$TOOL_INPUT_FILE_PATH" | grep -q '.env.example' && echo 'BLOCKED: .env files contain secrets' && exit 1; exit 0`
|
|
381
|
+
}]
|
|
382
|
+
});
|
|
383
|
+
preToolUse.push({
|
|
384
|
+
matcher: "Bash",
|
|
385
|
+
hooks: [{
|
|
386
|
+
type: "command",
|
|
387
|
+
command: `echo "$TOOL_INPUT_COMMAND" | grep -qE 'rm\\s+-rf\\s+/|DROP\\s+TABLE|DROP\\s+DATABASE|push.*--force|push.*-f' && echo 'BLOCKED: Destructive command detected' && exit 1; exit 0`
|
|
444
388
|
}]
|
|
445
389
|
});
|
|
446
390
|
const formatHook = buildFormatHook(detected);
|
|
@@ -450,7 +394,19 @@ function generateSettings(detected) {
|
|
|
450
394
|
const hooks = {};
|
|
451
395
|
if (preToolUse.length > 0) hooks.PreToolUse = preToolUse;
|
|
452
396
|
if (postToolUse.length > 0) hooks.PostToolUse = postToolUse;
|
|
453
|
-
return
|
|
397
|
+
return {
|
|
398
|
+
$schema: "https://json.schemastore.org/claude-code-settings.json",
|
|
399
|
+
permissions: {
|
|
400
|
+
deny: [
|
|
401
|
+
"Bash(rm -rf /)",
|
|
402
|
+
"Bash(rm -rf ~)",
|
|
403
|
+
"Read(.env)",
|
|
404
|
+
"Read(.env.*)",
|
|
405
|
+
"Read(secrets/**)"
|
|
406
|
+
]
|
|
407
|
+
},
|
|
408
|
+
hooks
|
|
409
|
+
};
|
|
454
410
|
}
|
|
455
411
|
var SAFE_FORMATTERS = {
|
|
456
412
|
TypeScript: { extensions: ["ts", "tsx"], command: "npx prettier --write" },
|
|
@@ -649,11 +605,15 @@ async function scaffold(root, options, detected) {
|
|
|
649
605
|
const tasksMd = generateTasksMd(options);
|
|
650
606
|
const settings = generateSettings(detected);
|
|
651
607
|
const claudeignore = generateClaudeignore(detected);
|
|
652
|
-
await mkdir(join2(root, ".claude"), { recursive: true });
|
|
608
|
+
await mkdir(join2(root, ".claude", "rules"), { recursive: true });
|
|
653
609
|
const settingsPath = join2(root, ".claude", "settings.json");
|
|
654
610
|
const mergedSettings = await mergeSettings(settingsPath, settings);
|
|
655
611
|
const claudeignorePath = join2(root, ".claudeignore");
|
|
656
612
|
const hasClaudeignore = await fileExists(claudeignorePath);
|
|
613
|
+
const claudeGitignorePath = join2(root, ".claude", ".gitignore");
|
|
614
|
+
const hasClaudeGitignore = await fileExists(claudeGitignorePath);
|
|
615
|
+
const rulesPath = join2(root, ".claude", "rules", "conventions.md");
|
|
616
|
+
const hasRules = await fileExists(rulesPath);
|
|
657
617
|
const writes = [
|
|
658
618
|
writeFile(join2(root, "CLAUDE.md"), claudeMd),
|
|
659
619
|
writeFile(join2(root, "TASKS.md"), tasksMd),
|
|
@@ -662,18 +622,61 @@ async function scaffold(root, options, detected) {
|
|
|
662
622
|
if (!hasClaudeignore) {
|
|
663
623
|
writes.push(writeFile(claudeignorePath, claudeignore));
|
|
664
624
|
}
|
|
625
|
+
if (!hasClaudeGitignore) {
|
|
626
|
+
writes.push(writeFile(claudeGitignorePath, [
|
|
627
|
+
"# Local-only Claude Code files (never commit these)",
|
|
628
|
+
"settings.local.json",
|
|
629
|
+
"plans/",
|
|
630
|
+
"memory/",
|
|
631
|
+
"sessions/",
|
|
632
|
+
"tmp/",
|
|
633
|
+
""
|
|
634
|
+
].join("\n")));
|
|
635
|
+
}
|
|
636
|
+
if (!hasRules) {
|
|
637
|
+
const rulesContent = generateStarterRules(detected);
|
|
638
|
+
writes.push(writeFile(rulesPath, rulesContent));
|
|
639
|
+
}
|
|
665
640
|
await Promise.all(writes);
|
|
666
641
|
log.success("Generated CLAUDE.md");
|
|
667
642
|
log.success("Generated TASKS.md");
|
|
668
|
-
log.success("Generated .claude/settings.json (
|
|
669
|
-
if (!
|
|
670
|
-
|
|
671
|
-
|
|
643
|
+
log.success("Generated .claude/settings.json (schema, permissions, hooks)");
|
|
644
|
+
if (!hasClaudeGitignore) log.success("Generated .claude/.gitignore");
|
|
645
|
+
if (!hasClaudeignore) log.success("Generated .claudeignore");
|
|
646
|
+
if (!hasRules) log.success("Generated .claude/rules/conventions.md");
|
|
672
647
|
log.blank();
|
|
673
648
|
log.success("Done! Run `claude` to start.");
|
|
674
649
|
log.info("Run `claude-launchpad doctor` to check your config quality.");
|
|
675
650
|
log.blank();
|
|
676
651
|
}
|
|
652
|
+
function generateStarterRules(detected) {
|
|
653
|
+
const lines = [
|
|
654
|
+
"# Project Conventions",
|
|
655
|
+
"",
|
|
656
|
+
"- Use conventional commits (feat:, fix:, docs:, refactor:, test:, chore:)",
|
|
657
|
+
"- Keep files under 400 lines, functions under 50 lines",
|
|
658
|
+
"- Handle errors explicitly - no empty catch blocks",
|
|
659
|
+
"- Validate input at system boundaries"
|
|
660
|
+
];
|
|
661
|
+
if (detected.language === "TypeScript" || detected.language === "JavaScript") {
|
|
662
|
+
lines.push("- Use named exports, no default exports except Next.js pages");
|
|
663
|
+
lines.push("- No `any` types in TypeScript");
|
|
664
|
+
}
|
|
665
|
+
if (detected.language === "Python") {
|
|
666
|
+
lines.push("- Type hints on all function signatures");
|
|
667
|
+
lines.push("- Async everywhere for I/O operations");
|
|
668
|
+
}
|
|
669
|
+
if (detected.language === "Go") {
|
|
670
|
+
lines.push("- Table-driven tests");
|
|
671
|
+
lines.push("- Errors are values - handle them, don't ignore them");
|
|
672
|
+
}
|
|
673
|
+
if (detected.language === "Rust") {
|
|
674
|
+
lines.push("- Prefer Result over unwrap/expect in library code");
|
|
675
|
+
lines.push("- No unsafe blocks without a safety comment");
|
|
676
|
+
}
|
|
677
|
+
lines.push("");
|
|
678
|
+
return lines.join("\n");
|
|
679
|
+
}
|
|
677
680
|
async function mergeSettings(existingPath, generated) {
|
|
678
681
|
try {
|
|
679
682
|
const existing = JSON.parse(await readFile2(existingPath, "utf-8"));
|
|
@@ -1247,50 +1250,35 @@ async function applyFixes(issues, projectRoot) {
|
|
|
1247
1250
|
}
|
|
1248
1251
|
return { fixed, skipped };
|
|
1249
1252
|
}
|
|
1250
|
-
|
|
1251
|
-
|
|
1253
|
+
var FIX_TABLE = [
|
|
1254
|
+
{ analyzer: "Hooks", match: "No hooks configured", fix: async (root, detected) => {
|
|
1252
1255
|
const a = await addEnvProtectionHook(root);
|
|
1253
1256
|
const b = await addAutoFormatHook(root, detected);
|
|
1254
1257
|
const c = await addForcePushProtection(root);
|
|
1255
1258
|
return a || b || c;
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
}
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
}
|
|
1266
|
-
if (issue.analyzer === "Quality" && issue.message.includes("Architecture")) {
|
|
1267
|
-
return addClaudeMdSection(root, "## Architecture", "<!-- TODO: Describe your codebase structure. Run `claude-launchpad enhance` to auto-fill this. -->");
|
|
1268
|
-
}
|
|
1269
|
-
if (issue.analyzer === "Quality" && issue.message.includes("Off-Limits")) {
|
|
1270
|
-
return addClaudeMdSection(root, "## Off-Limits", "- Never hardcode secrets \u2014 use environment variables\n- Never write to `.env` files\n- Never expose internal error details in API responses");
|
|
1271
|
-
}
|
|
1272
|
-
if (issue.analyzer === "Quality" && issue.message.includes("Commands")) {
|
|
1273
|
-
return addClaudeMdSection(root, "## Commands", "<!-- TODO: Add your dev/build/test commands -->");
|
|
1274
|
-
}
|
|
1275
|
-
if (issue.analyzer === "Quality" && issue.message.includes("Stack")) {
|
|
1276
|
-
const stackContent = detected.language ? `- **Language**: ${detected.language}${detected.framework ? `
|
|
1259
|
+
} },
|
|
1260
|
+
{ analyzer: "Hooks", match: ".env file protection", fix: (root) => addEnvProtectionHook(root) },
|
|
1261
|
+
{ analyzer: "Hooks", match: "auto-format", fix: (root, detected) => addAutoFormatHook(root, detected) },
|
|
1262
|
+
{ analyzer: "Hooks", match: "No PreToolUse", fix: (root) => addEnvProtectionHook(root) },
|
|
1263
|
+
{ analyzer: "Quality", match: "Architecture", fix: (root) => addClaudeMdSection(root, "## Architecture", "<!-- TODO: Describe your codebase structure. Run `claude-launchpad enhance` to auto-fill this. -->") },
|
|
1264
|
+
{ analyzer: "Quality", match: "Off-Limits", fix: (root) => addClaudeMdSection(root, "## Off-Limits", "- Never hardcode secrets - use environment variables\n- Never write to `.env` files\n- Never expose internal error details in API responses") },
|
|
1265
|
+
{ analyzer: "Quality", match: "Commands", fix: (root) => addClaudeMdSection(root, "## Commands", "<!-- TODO: Add your dev/build/test commands -->") },
|
|
1266
|
+
{ analyzer: "Quality", match: "Stack", fix: (root, detected) => {
|
|
1267
|
+
const content = detected.language ? `- **Language**: ${detected.language}${detected.framework ? `
|
|
1277
1268
|
- **Framework**: ${detected.framework}` : ""}${detected.packageManager ? `
|
|
1278
1269
|
- **Package Manager**: ${detected.packageManager}` : ""}` : "<!-- TODO: Define your tech stack -->";
|
|
1279
|
-
return addClaudeMdSection(root, "## Stack",
|
|
1280
|
-
}
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
}
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
return addForcePushProtection(root);
|
|
1292
|
-
}
|
|
1293
|
-
return false;
|
|
1270
|
+
return addClaudeMdSection(root, "## Stack", content);
|
|
1271
|
+
} },
|
|
1272
|
+
{ analyzer: "Quality", match: "Session Start", fix: (root) => addClaudeMdSection(root, "## Session Start", "- ALWAYS read @TASKS.md first - it tracks progress across sessions\n- Update TASKS.md as you complete work") },
|
|
1273
|
+
{ analyzer: "Rules", match: "No .claudeignore", fix: (root, detected) => createClaudeignore(root, detected) },
|
|
1274
|
+
{ analyzer: "Rules", match: "No .claude/rules/", fix: (root) => createStarterRules(root) },
|
|
1275
|
+
{ analyzer: "Permissions", match: "force-push", fix: (root) => addForcePushProtection(root) }
|
|
1276
|
+
];
|
|
1277
|
+
async function tryFix(issue, root, detected) {
|
|
1278
|
+
const entry = FIX_TABLE.find(
|
|
1279
|
+
(e) => e.analyzer === issue.analyzer && issue.message.includes(e.match)
|
|
1280
|
+
);
|
|
1281
|
+
return entry ? entry.fix(root, detected) : false;
|
|
1294
1282
|
}
|
|
1295
1283
|
async function addEnvProtectionHook(root) {
|
|
1296
1284
|
const settings = await readSettingsJson(root);
|
|
@@ -2180,7 +2168,7 @@ function createEnhanceCommand() {
|
|
|
2180
2168
|
}
|
|
2181
2169
|
|
|
2182
2170
|
// src/cli.ts
|
|
2183
|
-
var program = new Command5().name("claude-launchpad").description("CLI toolkit that makes Claude Code setups measurably good").version("0.
|
|
2171
|
+
var program = new Command5().name("claude-launchpad").description("CLI toolkit that makes Claude Code setups measurably good").version("0.4.0", "-v, --version").action(async () => {
|
|
2184
2172
|
const hasConfig = await fileExists(join11(process.cwd(), "CLAUDE.md")) || await fileExists(join11(process.cwd(), ".claude", "settings.json"));
|
|
2185
2173
|
if (hasConfig) {
|
|
2186
2174
|
await program.commands.find((c) => c.name() === "doctor")?.parseAsync([], { from: "user" });
|