claude-launchpad 0.3.0 → 0.3.2

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/README.md CHANGED
@@ -113,24 +113,26 @@ You see Claude working in real-time — same experience as running `claude` your
113
113
 
114
114
  ### `eval` — Prove your config works
115
115
 
116
- Runs Claude headless against 9 reproducible scenarios using the [Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk) and **scores how well your config actually drives correct behavior**.
116
+ Runs Claude headless against 11 reproducible scenarios using the [Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk) and **scores how well your config actually drives correct behavior**.
117
117
 
118
118
  ```bash
119
119
  claude-launchpad eval --suite common
120
120
  ```
121
121
 
122
122
  ```
123
- ✓ security/sql-injection 10/10 PASS
124
- ✓ security/env-protection 10/10 PASS
125
- ✓ security/secret-exposure 10/10 PASS
126
- ✓ security/input-validation 10/10 PASS
127
- ✓ conventions/error-handling 10/10 PASS
128
- ✓ conventions/immutability 10/10 PASS
129
- ✓ conventions/no-hardcoded-values 10/10 PASS
130
- ✓ conventions/file-size 10/10 PASS
131
- workflow/git-conventions 7/10 WARN
132
-
133
- Config Eval Score ━━━━━━━━━━━━━━━━━━── 89%
123
+ ✓ security/sql-injection 10/10 PASS
124
+ ✓ security/env-protection 10/10 PASS
125
+ ✓ security/secret-exposure 10/10 PASS
126
+ ✓ security/input-validation 10/10 PASS
127
+ ✓ conventions/error-handling 10/10 PASS
128
+ ✓ conventions/immutability 10/10 PASS
129
+ ✓ conventions/no-hardcoded-values 10/10 PASS
130
+ ✓ conventions/naming-conventions 10/10 PASS
131
+ conventions/file-size 10/10 PASS
132
+ ✓ workflow/git-conventions 10/10 PASS
133
+ workflow/session-continuity 7/10 WARN
134
+
135
+ Config Eval Score ━━━━━━━━━━━━━━━━━━━─ 95%
134
136
  ```
135
137
 
136
138
  Each scenario is a YAML file. [Write your own](scenarios/CONTRIBUTING.md).
@@ -180,7 +182,9 @@ jobs:
180
182
 
181
183
  Exit code is 1 when score is below the threshold, 0 when it passes.
182
184
 
183
- ## Install as a Plugin
185
+ ## Plugin (pending marketplace review)
186
+
187
+ The plugin has been submitted to the Claude Code marketplace. Once approved:
184
188
 
185
189
  ```bash
186
190
  claude plugin install claude-launchpad
@@ -206,7 +210,7 @@ Claude Launchpad gives you a number. Fix the issues, re-run, watch the number go
206
210
  - **Enhance uses Claude.** Spawns an interactive session to understand your codebase — costs tokens but produces a CLAUDE.md that actually knows your project.
207
211
  - **Eval uses the Agent SDK.** Runs Claude headless in sandboxes with explicit tool permissions — proof that your config works.
208
212
  - **Works with any stack.** Auto-detects your project. No fixed menu of supported frameworks.
209
- - **50 tests.** The tool that tests configs is itself well-tested.
213
+ - **57 tests.** The tool that tests configs is itself well-tested.
210
214
  - **You never clone this repo.** It's a tool you run with `npx`, not a template you fork.
211
215
 
212
216
  ## License
package/dist/cli.js CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import { Command as Command5 } from "commander";
5
- import { access as access8 } from "fs/promises";
6
5
  import { join as join10 } from "path";
7
6
 
8
7
  // src/commands/init/index.ts
@@ -70,9 +69,60 @@ function printIssue(severity, analyzer, message, fix) {
70
69
  }
71
70
  console.log();
72
71
  }
72
+ function renderDoctorReport(results) {
73
+ const overallScore = Math.round(
74
+ results.reduce((sum, r) => sum + r.score, 0) / results.length
75
+ );
76
+ for (const result of results) {
77
+ printScoreCard(result.name, result.score);
78
+ }
79
+ log.blank();
80
+ printScoreCard("Overall", overallScore);
81
+ log.blank();
82
+ const allIssues = results.flatMap((r) => r.issues);
83
+ const actionable = allIssues.filter((i) => i.severity !== "info");
84
+ if (actionable.length === 0) {
85
+ log.success("No issues found. Your configuration looks solid.");
86
+ return { overallScore, actionableCount: 0 };
87
+ }
88
+ const sorted = [...actionable].sort((a, b) => {
89
+ const order = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
90
+ return (order[a.severity] ?? 4) - (order[b.severity] ?? 4);
91
+ });
92
+ for (const issue of sorted) {
93
+ printIssue(issue.severity, issue.analyzer, issue.message, issue.fix);
94
+ }
95
+ log.info(`${actionable.length} issue(s) found. Fix critical/high first.`);
96
+ return { overallScore, actionableCount: actionable.length };
97
+ }
73
98
 
74
- // src/lib/detect.ts
99
+ // src/lib/fs-utils.ts
75
100
  import { readFile, access } from "fs/promises";
101
+ async function fileExists(path) {
102
+ try {
103
+ await access(path);
104
+ return true;
105
+ } catch {
106
+ return false;
107
+ }
108
+ }
109
+ async function readFileOrNull(path) {
110
+ try {
111
+ return await readFile(path, "utf-8");
112
+ } catch {
113
+ return null;
114
+ }
115
+ }
116
+ async function readJsonOrNull(path) {
117
+ try {
118
+ const content = await readFile(path, "utf-8");
119
+ return JSON.parse(content);
120
+ } catch {
121
+ return null;
122
+ }
123
+ }
124
+
125
+ // src/lib/detect.ts
76
126
  import { join, basename } from "path";
77
127
  async function detectProject(root) {
78
128
  const name = basename(root);
@@ -300,29 +350,6 @@ function pmRun(pkg) {
300
350
  if (pm?.startsWith("bun")) return "bun";
301
351
  return "npm run";
302
352
  }
303
- async function readJsonOrNull(path) {
304
- try {
305
- const content = await readFile(path, "utf-8");
306
- return JSON.parse(content);
307
- } catch {
308
- return null;
309
- }
310
- }
311
- async function readFileOrNull(path) {
312
- try {
313
- return await readFile(path, "utf-8");
314
- } catch {
315
- return null;
316
- }
317
- }
318
- async function fileExists(path) {
319
- try {
320
- await access(path);
321
- return true;
322
- } catch {
323
- return false;
324
- }
325
- }
326
353
  async function globExists(dir, pattern) {
327
354
  const { readdir: readdir5 } = await import("fs/promises");
328
355
  try {
@@ -425,63 +452,24 @@ function generateSettings(detected) {
425
452
  if (postToolUse.length > 0) hooks.PostToolUse = postToolUse;
426
453
  return Object.keys(hooks).length > 0 ? { hooks } : {};
427
454
  }
455
+ var SAFE_FORMATTERS = {
456
+ TypeScript: { extensions: ["ts", "tsx"], command: "npx prettier --write" },
457
+ JavaScript: { extensions: ["js", "jsx"], command: "npx prettier --write" },
458
+ Python: { extensions: ["py"], command: "ruff format" },
459
+ Go: { extensions: ["go"], command: "gofmt -w" },
460
+ Rust: { extensions: ["rs"], command: "rustfmt" },
461
+ Ruby: { extensions: ["rb"], command: "rubocop -A" },
462
+ Dart: { extensions: ["dart"], command: "dart format" },
463
+ PHP: { extensions: ["php"], command: "vendor/bin/pint" },
464
+ Kotlin: { extensions: ["kt", "kts"], command: "ktlint -F" },
465
+ Java: { extensions: ["java"], command: "google-java-format -i" },
466
+ Swift: { extensions: ["swift"], command: "swift-format format -i" },
467
+ Elixir: { extensions: ["ex", "exs"], command: "mix format" },
468
+ "C#": { extensions: ["cs"], command: "dotnet format" }
469
+ };
428
470
  function buildFormatHook(detected) {
429
471
  if (!detected.language) return null;
430
- const formatters = {
431
- TypeScript: {
432
- extensions: ["ts", "tsx"],
433
- command: detected.formatCommand ?? "npx prettier --write"
434
- },
435
- JavaScript: {
436
- extensions: ["js", "jsx"],
437
- command: detected.formatCommand ?? "npx prettier --write"
438
- },
439
- Python: {
440
- extensions: ["py"],
441
- command: detected.formatCommand ?? "ruff format"
442
- },
443
- Go: {
444
- extensions: ["go"],
445
- command: "gofmt -w"
446
- },
447
- Rust: {
448
- extensions: ["rs"],
449
- command: "rustfmt"
450
- },
451
- Ruby: {
452
- extensions: ["rb"],
453
- command: "rubocop -A"
454
- },
455
- Dart: {
456
- extensions: ["dart"],
457
- command: "dart format"
458
- },
459
- PHP: {
460
- extensions: ["php"],
461
- command: detected.formatCommand ?? "vendor/bin/pint"
462
- },
463
- Kotlin: {
464
- extensions: ["kt", "kts"],
465
- command: "ktlint -F"
466
- },
467
- Java: {
468
- extensions: ["java"],
469
- command: "google-java-format -i"
470
- },
471
- Swift: {
472
- extensions: ["swift"],
473
- command: "swift-format format -i"
474
- },
475
- Elixir: {
476
- extensions: ["ex", "exs"],
477
- command: "mix format"
478
- },
479
- "C#": {
480
- extensions: ["cs"],
481
- command: "dotnet format"
482
- }
483
- };
484
- const config = formatters[detected.language];
472
+ const config = SAFE_FORMATTERS[detected.language];
485
473
  if (!config) return null;
486
474
  const extChecks = config.extensions.map((ext) => `[ "$ext" = "${ext}" ]`).join(" || ");
487
475
  return {
@@ -640,7 +628,7 @@ function createInitCommand() {
640
628
  message: "One-line description (optional):"
641
629
  });
642
630
  const options = { name: name.trim(), description: description.trim() };
643
- const hasClaudeMd = await fileExists2(join2(root, "CLAUDE.md"));
631
+ const hasClaudeMd = await fileExists(join2(root, "CLAUDE.md"));
644
632
  if (hasClaudeMd && !opts.yes) {
645
633
  const overwrite = await confirm({
646
634
  message: "CLAUDE.md already exists. Overwrite?",
@@ -665,7 +653,7 @@ async function scaffold(root, options, detected) {
665
653
  const settingsPath = join2(root, ".claude", "settings.json");
666
654
  const mergedSettings = await mergeSettings(settingsPath, settings);
667
655
  const claudeignorePath = join2(root, ".claudeignore");
668
- const hasClaudeignore = await fileExists2(claudeignorePath);
656
+ const hasClaudeignore = await fileExists(claudeignorePath);
669
657
  const writes = [
670
658
  writeFile(join2(root, "CLAUDE.md"), claudeMd),
671
659
  writeFile(join2(root, "TASKS.md"), tasksMd),
@@ -686,14 +674,6 @@ async function scaffold(root, options, detected) {
686
674
  log.info("Run `claude-launchpad doctor` to check your config quality.");
687
675
  log.blank();
688
676
  }
689
- async function fileExists2(path) {
690
- try {
691
- await readFile2(path);
692
- return true;
693
- } catch {
694
- return false;
695
- }
696
- }
697
677
  async function mergeSettings(existingPath, generated) {
698
678
  try {
699
679
  const existing = JSON.parse(await readFile2(existingPath, "utf-8"));
@@ -719,7 +699,7 @@ async function mergeSettings(existingPath, generated) {
719
699
  import { Command as Command2 } from "commander";
720
700
 
721
701
  // src/lib/parser.ts
722
- import { readFile as readFile3, readdir, access as access2 } from "fs/promises";
702
+ import { readdir, access as access2 } from "fs/promises";
723
703
  import { join as join3, resolve } from "path";
724
704
  var CLAUDE_MD = "CLAUDE.md";
725
705
  var CLAUDE_DIR = ".claude";
@@ -750,7 +730,7 @@ async function parseClaudeConfig(projectRoot) {
750
730
  };
751
731
  }
752
732
  async function readClaudeMd(root) {
753
- return readFileOrNull2(join3(root, CLAUDE_MD));
733
+ return readFileOrNull(join3(root, CLAUDE_MD));
754
734
  }
755
735
  function countInstructions(content) {
756
736
  const lines = content.split("\n");
@@ -766,7 +746,7 @@ function countInstructions(content) {
766
746
  return count;
767
747
  }
768
748
  async function readSettings(claudeDir) {
769
- const raw = await readFileOrNull2(join3(claudeDir, SETTINGS_FILE));
749
+ const raw = await readFileOrNull(join3(claudeDir, SETTINGS_FILE));
770
750
  if (raw === null) return null;
771
751
  try {
772
752
  return JSON.parse(raw);
@@ -775,7 +755,7 @@ async function readSettings(claudeDir) {
775
755
  }
776
756
  }
777
757
  async function readHooks(claudeDir) {
778
- const settingsRaw = await readFileOrNull2(join3(claudeDir, SETTINGS_FILE));
758
+ const settingsRaw = await readFileOrNull(join3(claudeDir, SETTINGS_FILE));
779
759
  if (settingsRaw === null) return [];
780
760
  try {
781
761
  const settings = JSON.parse(settingsRaw);
@@ -818,7 +798,7 @@ async function readRules(claudeDir) {
818
798
  return listFilesRecursive(rulesDir, ".md");
819
799
  }
820
800
  async function readMcpServers(claudeDir) {
821
- const settingsRaw = await readFileOrNull2(join3(claudeDir, SETTINGS_FILE));
801
+ const settingsRaw = await readFileOrNull(join3(claudeDir, SETTINGS_FILE));
822
802
  if (settingsRaw === null) return [];
823
803
  try {
824
804
  const settings = JSON.parse(settingsRaw);
@@ -848,13 +828,6 @@ async function readSkills(claudeDir) {
848
828
  ]);
849
829
  return [...commands, ...skills];
850
830
  }
851
- async function readFileOrNull2(path) {
852
- try {
853
- return await readFile3(path, "utf-8");
854
- } catch {
855
- return null;
856
- }
857
- }
858
831
  async function listFilesRecursive(dir, ext) {
859
832
  try {
860
833
  await access2(dir);
@@ -1028,12 +1001,12 @@ async function analyzeHooks(config) {
1028
1001
  }
1029
1002
 
1030
1003
  // src/commands/doctor/analyzers/rules.ts
1031
- import { readFile as readFile4, access as access3 } from "fs/promises";
1004
+ import { readFile as readFile3 } from "fs/promises";
1032
1005
  import { basename as basename2, join as join4, dirname } from "path";
1033
1006
  async function analyzeRules(config) {
1034
1007
  const issues = [];
1035
1008
  const projectRoot = config.claudeMdPath ? dirname(config.claudeMdPath) : process.cwd();
1036
- const hasClaudeignore = await fileExists3(join4(projectRoot, ".claudeignore"));
1009
+ const hasClaudeignore = await fileExists(join4(projectRoot, ".claudeignore"));
1037
1010
  if (!hasClaudeignore) {
1038
1011
  issues.push({
1039
1012
  analyzer: "Rules",
@@ -1053,7 +1026,7 @@ async function analyzeRules(config) {
1053
1026
  }
1054
1027
  for (const rulePath of config.rules) {
1055
1028
  try {
1056
- const content = await readFile4(rulePath, "utf-8");
1029
+ const content = await readFile3(rulePath, "utf-8");
1057
1030
  const trimmed = content.trim();
1058
1031
  if (trimmed.length === 0) {
1059
1032
  issues.push({
@@ -1080,14 +1053,6 @@ async function analyzeRules(config) {
1080
1053
  const score = Math.max(0, 100 - issues.length * 10);
1081
1054
  return { name: "Rules", issues, score };
1082
1055
  }
1083
- async function fileExists3(path) {
1084
- try {
1085
- await access3(path);
1086
- return true;
1087
- } catch {
1088
- return false;
1089
- }
1090
- }
1091
1056
 
1092
1057
  // src/commands/doctor/analyzers/permissions.ts
1093
1058
  async function analyzePermissions(config) {
@@ -1134,7 +1099,7 @@ async function analyzePermissions(config) {
1134
1099
  }
1135
1100
 
1136
1101
  // src/commands/doctor/analyzers/mcp.ts
1137
- import { access as access4 } from "fs/promises";
1102
+ import { access as access3 } from "fs/promises";
1138
1103
  async function analyzeMcp(config) {
1139
1104
  const issues = [];
1140
1105
  const servers = config.mcpServers;
@@ -1167,7 +1132,7 @@ async function analyzeMcp(config) {
1167
1132
  const executable = server.command.split(" ")[0];
1168
1133
  if (executable.startsWith("/") || executable.startsWith("./")) {
1169
1134
  try {
1170
- await access4(executable);
1135
+ await access3(executable);
1171
1136
  } catch {
1172
1137
  issues.push({
1173
1138
  analyzer: "MCP",
@@ -1266,7 +1231,7 @@ async function analyzeQuality(config) {
1266
1231
  }
1267
1232
 
1268
1233
  // src/commands/doctor/fixer.ts
1269
- import { readFile as readFile5, writeFile as writeFile2, mkdir as mkdir2, access as access5 } from "fs/promises";
1234
+ import { readFile as readFile4, writeFile as writeFile2, mkdir as mkdir2, access as access4 } from "fs/promises";
1270
1235
  import { join as join5 } from "path";
1271
1236
  async function applyFixes(issues, projectRoot) {
1272
1237
  const detected = await detectProject(projectRoot);
@@ -1351,13 +1316,13 @@ async function addEnvProtectionHook(root) {
1351
1316
  async function addAutoFormatHook(root, detected) {
1352
1317
  if (!detected.language) return false;
1353
1318
  const formatters = {
1354
- TypeScript: { extensions: ["ts", "tsx"], command: detected.formatCommand ?? "npx prettier --write" },
1355
- JavaScript: { extensions: ["js", "jsx"], command: detected.formatCommand ?? "npx prettier --write" },
1356
- Python: { extensions: ["py"], command: detected.formatCommand ?? "ruff format" },
1319
+ TypeScript: { extensions: ["ts", "tsx"], command: "npx prettier --write" },
1320
+ JavaScript: { extensions: ["js", "jsx"], command: "npx prettier --write" },
1321
+ Python: { extensions: ["py"], command: "ruff format" },
1357
1322
  Go: { extensions: ["go"], command: "gofmt -w" },
1358
1323
  Rust: { extensions: ["rs"], command: "rustfmt" },
1359
1324
  Ruby: { extensions: ["rb"], command: "rubocop -A" },
1360
- PHP: { extensions: ["php"], command: detected.formatCommand ?? "vendor/bin/pint" }
1325
+ PHP: { extensions: ["php"], command: "vendor/bin/pint" }
1361
1326
  };
1362
1327
  const config = formatters[detected.language];
1363
1328
  if (!config) return false;
@@ -1407,7 +1372,7 @@ async function addClaudeMdSection(root, heading, content) {
1407
1372
  const claudeMdPath = join5(root, "CLAUDE.md");
1408
1373
  let existing;
1409
1374
  try {
1410
- existing = await readFile5(claudeMdPath, "utf-8");
1375
+ existing = await readFile4(claudeMdPath, "utf-8");
1411
1376
  } catch {
1412
1377
  return false;
1413
1378
  }
@@ -1427,7 +1392,7 @@ ${content}
1427
1392
  async function createClaudeignore(root, detected) {
1428
1393
  const ignorePath = join5(root, ".claudeignore");
1429
1394
  try {
1430
- await access5(ignorePath);
1395
+ await access4(ignorePath);
1431
1396
  return false;
1432
1397
  } catch {
1433
1398
  }
@@ -1439,7 +1404,7 @@ async function createClaudeignore(root, detected) {
1439
1404
  async function createStarterRules(root) {
1440
1405
  const rulesDir = join5(root, ".claude", "rules");
1441
1406
  try {
1442
- await access5(rulesDir);
1407
+ await access4(rulesDir);
1443
1408
  return false;
1444
1409
  } catch {
1445
1410
  }
@@ -1460,7 +1425,7 @@ async function createStarterRules(root) {
1460
1425
  async function readSettingsJson(root) {
1461
1426
  const path = join5(root, ".claude", "settings.json");
1462
1427
  try {
1463
- const content = await readFile5(path, "utf-8");
1428
+ const content = await readFile4(path, "utf-8");
1464
1429
  return JSON.parse(content);
1465
1430
  } catch {
1466
1431
  return {};
@@ -1540,29 +1505,7 @@ async function runAndDisplay(projectRoot) {
1540
1505
  analyzePermissions(config),
1541
1506
  analyzeMcp(config)
1542
1507
  ]);
1543
- const overallScore = Math.round(
1544
- results.reduce((sum, r) => sum + r.score, 0) / results.length
1545
- );
1546
- for (const result of results) {
1547
- printScoreCard(result.name, result.score);
1548
- }
1549
- log.blank();
1550
- printScoreCard("Overall", overallScore);
1551
- log.blank();
1552
- const allIssues = results.flatMap((r) => r.issues);
1553
- const actionable = allIssues.filter((i) => i.severity !== "info");
1554
- if (actionable.length === 0) {
1555
- log.success("No issues found. Your configuration looks solid.");
1556
- return;
1557
- }
1558
- const sorted = [...actionable].sort((a, b) => {
1559
- const order = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
1560
- return (order[a.severity] ?? 4) - (order[b.severity] ?? 4);
1561
- });
1562
- for (const issue of sorted) {
1563
- printIssue(issue.severity, issue.analyzer, issue.message, issue.fix);
1564
- }
1565
- log.info(`${actionable.length} issue(s) found. Fix critical/high first.`);
1508
+ renderDoctorReport(results);
1566
1509
  }
1567
1510
 
1568
1511
  // src/commands/doctor/index.ts
@@ -1591,13 +1534,13 @@ function createDoctorCommand() {
1591
1534
  analyzeMcp(config)
1592
1535
  ]);
1593
1536
  if (opts.json) {
1594
- const overallScore = Math.round(
1537
+ const overallScore2 = Math.round(
1595
1538
  results.reduce((sum, r) => sum + r.score, 0) / results.length
1596
1539
  );
1597
- console.log(JSON.stringify({ overallScore, analyzers: results, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
1540
+ console.log(JSON.stringify({ overallScore: overallScore2, analyzers: results, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
1598
1541
  return;
1599
1542
  }
1600
- renderReport(results);
1543
+ const { overallScore } = renderDoctorReport(results);
1601
1544
  if (opts.fix) {
1602
1545
  const allIssues = results.flatMap((r) => r.issues);
1603
1546
  const fixable = allIssues.filter((i) => i.severity !== "info");
@@ -1616,9 +1559,6 @@ function createDoctorCommand() {
1616
1559
  }
1617
1560
  }
1618
1561
  if (opts.minScore) {
1619
- const overallScore = Math.round(
1620
- results.reduce((sum, r) => sum + r.score, 0) / results.length
1621
- );
1622
1562
  const threshold = parseInt(opts.minScore, 10);
1623
1563
  if (overallScore < threshold) {
1624
1564
  process.exit(1);
@@ -1626,31 +1566,6 @@ function createDoctorCommand() {
1626
1566
  }
1627
1567
  });
1628
1568
  }
1629
- function renderReport(results) {
1630
- const overallScore = Math.round(
1631
- results.reduce((sum, r) => sum + r.score, 0) / results.length
1632
- );
1633
- for (const result of results) {
1634
- printScoreCard(result.name, result.score);
1635
- }
1636
- log.blank();
1637
- printScoreCard("Overall", overallScore);
1638
- log.blank();
1639
- const allIssues = results.flatMap((r) => r.issues);
1640
- const actionable = allIssues.filter((i) => i.severity !== "info");
1641
- if (actionable.length === 0) {
1642
- log.success("No issues found. Your configuration looks solid.");
1643
- return;
1644
- }
1645
- for (const issue of sortBySeverity(actionable)) {
1646
- printIssue(issue.severity, issue.analyzer, issue.message, issue.fix);
1647
- }
1648
- log.info(`${actionable.length} issue(s) found. Fix critical/high first.`);
1649
- }
1650
- function sortBySeverity(issues) {
1651
- const order = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
1652
- return [...issues].sort((a, b) => (order[a.severity] ?? 4) - (order[b.severity] ?? 4));
1653
- }
1654
1569
 
1655
1570
  // src/commands/eval/index.ts
1656
1571
  import { Command as Command3 } from "commander";
@@ -1658,7 +1573,7 @@ import ora from "ora";
1658
1573
  import chalk2 from "chalk";
1659
1574
 
1660
1575
  // src/commands/eval/loader.ts
1661
- import { readFile as readFile6, readdir as readdir3, access as access6 } from "fs/promises";
1576
+ import { readFile as readFile5, readdir as readdir3, access as access5 } from "fs/promises";
1662
1577
  import { join as join7, resolve as resolve2, dirname as dirname2 } from "path";
1663
1578
  import { fileURLToPath } from "url";
1664
1579
  import { parse as parseYaml } from "yaml";
@@ -1773,7 +1688,7 @@ async function findScenariosDir() {
1773
1688
  }
1774
1689
  async function dirExists(path) {
1775
1690
  try {
1776
- await access6(path);
1691
+ await access5(path);
1777
1692
  return true;
1778
1693
  } catch {
1779
1694
  return false;
@@ -1789,7 +1704,7 @@ async function loadScenarios(options) {
1789
1704
  const files = await listYamlFiles(dir);
1790
1705
  for (const file of files) {
1791
1706
  try {
1792
- const content = await readFile6(file, "utf-8");
1707
+ const content = await readFile5(file, "utf-8");
1793
1708
  const raw = parseYaml(content);
1794
1709
  const scenario = validateScenario(raw, file);
1795
1710
  scenarios.push(scenario);
@@ -1819,7 +1734,7 @@ async function listYamlFiles(dir) {
1819
1734
  }
1820
1735
 
1821
1736
  // src/commands/eval/runner.ts
1822
- import { mkdir as mkdir3, writeFile as writeFile3, readFile as readFile7, readdir as readdir4, rm } from "fs/promises";
1737
+ import { mkdir as mkdir3, writeFile as writeFile3, readFile as readFile6, readdir as readdir4, rm } from "fs/promises";
1823
1738
  import { join as join8, dirname as dirname3 } from "path";
1824
1739
  import { tmpdir } from "os";
1825
1740
  import { randomUUID } from "crypto";
@@ -1972,7 +1887,7 @@ async function evaluateSingleCheck(check, sandboxDir) {
1972
1887
  async function checkGrep(check, sandboxDir) {
1973
1888
  if (!check.pattern) return false;
1974
1889
  try {
1975
- const content = await readFile7(join8(sandboxDir, check.target), "utf-8");
1890
+ const content = await readFile6(join8(sandboxDir, check.target), "utf-8");
1976
1891
  let found;
1977
1892
  try {
1978
1893
  found = new RegExp(check.pattern).test(content);
@@ -1986,7 +1901,7 @@ async function checkGrep(check, sandboxDir) {
1986
1901
  }
1987
1902
  async function checkFileExists(check, sandboxDir) {
1988
1903
  try {
1989
- await readFile7(join8(sandboxDir, check.target));
1904
+ await readFile6(join8(sandboxDir, check.target));
1990
1905
  return check.expect === "present";
1991
1906
  } catch {
1992
1907
  return check.expect === "absent";
@@ -1994,7 +1909,7 @@ async function checkFileExists(check, sandboxDir) {
1994
1909
  }
1995
1910
  async function checkFileAbsent(check, sandboxDir) {
1996
1911
  try {
1997
- await readFile7(join8(sandboxDir, check.target));
1912
+ await readFile6(join8(sandboxDir, check.target));
1998
1913
  return check.expect === "absent";
1999
1914
  } catch {
2000
1915
  return check.expect === "present";
@@ -2005,7 +1920,7 @@ async function checkMaxLines(check, sandboxDir) {
2005
1920
  try {
2006
1921
  const files = await listAllFiles(join8(sandboxDir, check.target));
2007
1922
  for (const file of files) {
2008
- const content = await readFile7(file, "utf-8");
1923
+ const content = await readFile6(file, "utf-8");
2009
1924
  if (content.split("\n").length > maxLines) {
2010
1925
  return check.expect === "absent";
2011
1926
  }
@@ -2145,7 +2060,7 @@ async function checkClaudeCli() {
2145
2060
  import { Command as Command4 } from "commander";
2146
2061
  import { spawn, execFile as execFile2 } from "child_process";
2147
2062
  import { promisify as promisify2 } from "util";
2148
- import { access as access7 } from "fs/promises";
2063
+ import { access as access6 } from "fs/promises";
2149
2064
  import { join as join9 } from "path";
2150
2065
  var execAsync = promisify2(execFile2);
2151
2066
  var ENHANCE_PROMPT = `Read CLAUDE.md and the project's codebase, then update CLAUDE.md to fill in missing or incomplete sections.
@@ -2163,6 +2078,12 @@ Sections to fill in or preserve (DO NOT remove any existing section):
2163
2078
  5. **## Key Decisions** \u2014 only decisions that affect how Claude should work in this codebase
2164
2079
  6. **MCP server suggestions** \u2014 look at what external services the project uses (databases, APIs, storage). If you spot Postgres, Redis, Stripe, GitHub API, or similar, suggest relevant MCP servers the user could add. Print these as suggestions at the end, not in CLAUDE.md.
2165
2080
 
2081
+ Also review .claude/settings.json hooks:
2082
+ - Read the existing hooks in .claude/settings.json
2083
+ - If you see project-specific patterns that deserve hooks (e.g., protected directories, test file patterns, migration files), suggest adding them
2084
+ - DO NOT overwrite existing hooks \u2014 only add new ones that are specific to this project
2085
+ - Print hook suggestions at the end with the exact JSON to add, don't modify settings.json directly
2086
+
2166
2087
  Rules:
2167
2088
  - Don't remove existing content \u2014 only add or improve
2168
2089
  - Be specific to THIS project, not generic advice
@@ -2175,7 +2096,7 @@ function createEnhanceCommand() {
2175
2096
  const root = opts.path;
2176
2097
  const claudeMdPath = join9(root, "CLAUDE.md");
2177
2098
  try {
2178
- await access7(claudeMdPath);
2099
+ await access6(claudeMdPath);
2179
2100
  } catch {
2180
2101
  log.error("No CLAUDE.md found. Run `claude-launchpad init` first.");
2181
2102
  process.exit(1);
@@ -2202,8 +2123,8 @@ function createEnhanceCommand() {
2202
2123
  }
2203
2124
 
2204
2125
  // src/cli.ts
2205
- var program = new Command5().name("claude-launchpad").description("CLI toolkit that makes Claude Code setups measurably good").version("0.3.0", "-v, --version").action(async () => {
2206
- const hasConfig = await fileExists4(join10(process.cwd(), "CLAUDE.md")) || await fileExists4(join10(process.cwd(), ".claude", "settings.json"));
2126
+ var program = new Command5().name("claude-launchpad").description("CLI toolkit that makes Claude Code setups measurably good").version("0.3.2", "-v, --version").action(async () => {
2127
+ const hasConfig = await fileExists(join10(process.cwd(), "CLAUDE.md")) || await fileExists(join10(process.cwd(), ".claude", "settings.json"));
2207
2128
  if (hasConfig) {
2208
2129
  await program.commands.find((c) => c.name() === "doctor")?.parseAsync([], { from: "user" });
2209
2130
  } else {
@@ -2221,12 +2142,4 @@ program.addCommand(createDoctorCommand());
2221
2142
  program.addCommand(createEnhanceCommand());
2222
2143
  program.addCommand(createEvalCommand());
2223
2144
  program.parse();
2224
- async function fileExists4(path) {
2225
- try {
2226
- await access8(path);
2227
- return true;
2228
- } catch {
2229
- return false;
2230
- }
2231
- }
2232
2145
  //# sourceMappingURL=cli.js.map