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 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
- **A linter for your Claude Code configuration.** Scores your setup, auto-fixes issues, and tests if Claude actually follows your rules.
3
+ [![npm version](https://img.shields.io/npm/v/claude-launchpad?style=flat-square)](https://www.npmjs.com/package/claude-launchpad)
4
+ [![npm downloads](https://img.shields.io/npm/dm/claude-launchpad?style=flat-square)](https://www.npmjs.com/package/claude-launchpad)
5
+ [![GitHub stars](https://img.shields.io/github/stars/mboss37/claude-launchpad?style=flat-square)](https://github.com/mboss37/claude-launchpad)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](https://github.com/mboss37/claude-launchpad/blob/master/LICENSE)
4
7
 
5
- You write a `CLAUDE.md`, add some hooks, configure settings — but is any of it actually working? Claude Launchpad scans your config, gives you a score out of 100, fixes what's broken, and runs Claude against test scenarios to prove it.
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
- npx claude-launchpad
17
+ npm i -g claude-launchpad
18
+ cd your-project
9
19
  ```
10
20
 
11
- That's it. One command. You get a score. You see what's wrong. You fix it.
21
+ ## Two Paths, One Tool
12
22
 
13
- ## What It Does
23
+ ### Starting a new project?
14
24
 
15
- | Command | What it does | Cost |
16
- |---|---|---|
17
- | `claude-launchpad` | Scans your config, scores it 0-100, lists issues | Free |
18
- | `claude-launchpad doctor --fix` | Auto-fixes issues (adds hooks, rules, missing sections) | Free |
19
- | `claude-launchpad doctor --watch` | Live score that updates when you edit config files | Free |
20
- | `claude-launchpad init` | Detects your stack, generates config from scratch | Free |
21
- | `claude-launchpad enhance` | Opens Claude to read your code and complete CLAUDE.md | Uses Claude |
22
- | `claude-launchpad eval --suite security` | Runs Claude against test scenarios, proves your config works | Uses Claude |
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 ~86% with zero manual work.
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 (TypeScript) project
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 (with hooks)
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` — session continuity across Claude Code sessions
106
- - `.claude/settings.json` — auto-format hooks and .env file protection
107
- - `.claudeignore` — keeps Claude from reading node_modules, dist, lockfiles, etc.
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 config with safe, hardcoded formatter hooks never interpolates user-controlled strings.
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 ? `${pmRun(m.pkgJson)} dev` : null,
256
- buildCommand: scripts.build ? `${pmRun(m.pkgJson)} build` : null,
257
- testCommand: scripts.test ? `${pmRun(m.pkgJson)} test` : null,
258
- lintCommand: scripts.lint ? `${pmRun(m.pkgJson)} lint` : null,
259
- formatCommand: scripts.format ? `${pmRun(m.pkgJson)} format` : null
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 runner = m.pyProject?.includes("[tool.uv]") ? "uv run" : "python -m";
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 === "Rust") {
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 \u2014 use .env.example for documentation' && exit 1; exit 0`
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 Object.keys(hooks).length > 0 ? { hooks } : {};
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 (merged with existing)");
669
- if (!hasClaudeignore) {
670
- log.success("Generated .claudeignore");
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
- async function tryFix(issue, root, detected) {
1251
- if (issue.analyzer === "Hooks" && issue.message.includes("No hooks configured")) {
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
- if (issue.analyzer === "Hooks" && issue.message.includes(".env file protection")) {
1258
- return addEnvProtectionHook(root);
1259
- }
1260
- if (issue.analyzer === "Hooks" && issue.message.includes("auto-format")) {
1261
- return addAutoFormatHook(root, detected);
1262
- }
1263
- if (issue.analyzer === "Hooks" && issue.message.includes("No PreToolUse")) {
1264
- return addEnvProtectionHook(root);
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", stackContent);
1280
- }
1281
- if (issue.analyzer === "Quality" && issue.message.includes("Session Start")) {
1282
- return addClaudeMdSection(root, "## Session Start", "- ALWAYS read @TASKS.md first \u2014 it tracks progress across sessions\n- Update TASKS.md as you complete work");
1283
- }
1284
- if (issue.analyzer === "Rules" && issue.message.includes("No .claudeignore")) {
1285
- return createClaudeignore(root, detected);
1286
- }
1287
- if (issue.analyzer === "Rules" && issue.message.includes("No .claude/rules/")) {
1288
- return createStarterRules(root);
1289
- }
1290
- if (issue.analyzer === "Permissions" && issue.message.includes("force-push")) {
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.3.4", "-v, --version").action(async () => {
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" });