claude-launchpad 0.2.0 → 0.2.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/README.md +40 -30
- package/dist/cli.js +119 -35
- package/dist/cli.js.map +1 -1
- package/package.json +4 -2
- package/scenarios/CONTRIBUTING.md +62 -0
- package/scenarios/common/git-conventions.yaml +36 -0
- package/scenarios/common/no-hardcoded-values.yaml +33 -0
package/README.md
CHANGED
|
@@ -13,14 +13,14 @@ npx claude-launchpad
|
|
|
13
13
|
## The Workflow
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
npx claude-launchpad init
|
|
17
|
-
npx claude-launchpad enhance
|
|
18
|
-
npx claude-launchpad
|
|
16
|
+
npx claude-launchpad init # 1. Auto-detect stack, generate config + hooks + .claudeignore
|
|
17
|
+
npx claude-launchpad enhance # 2. Claude reads your code, completes CLAUDE.md
|
|
18
|
+
npx claude-launchpad # 3. Check your score (42%)
|
|
19
19
|
npx claude-launchpad doctor --fix # 4. Auto-fix everything (→ 86%)
|
|
20
|
-
npx claude-launchpad
|
|
20
|
+
npx claude-launchpad eval # 5. Prove your config works (89% eval score)
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
> See the [full story on the landing page](https://mboss37.github.io/claude-launchpad/) — a 42% →
|
|
23
|
+
> See the [full story on the landing page](https://mboss37.github.io/claude-launchpad/) — a 42% → 89% journey.
|
|
24
24
|
|
|
25
25
|
## Commands
|
|
26
26
|
|
|
@@ -31,32 +31,32 @@ Runs 7 static analyzers against your `.claude/` directory and `CLAUDE.md`. No AP
|
|
|
31
31
|
```
|
|
32
32
|
Instruction Budget ━━━━━━━━━━━━━━━━━━━━ 100%
|
|
33
33
|
CLAUDE.md Quality ━━━━━━━━━━━━━━━━━━━━ 100%
|
|
34
|
-
Settings
|
|
34
|
+
Settings ━━━━━━━━━━━━━━━━━━━━ 100%
|
|
35
35
|
Hooks ━━━━━━━━━━━━━━━━━━━━ 100%
|
|
36
|
-
Rules
|
|
37
|
-
Permissions
|
|
36
|
+
Rules ━━━━━━━━━━━━━━━━━━━━ 100%
|
|
37
|
+
Permissions ━━━━━━━━━━━━━━━━━━━━ 100%
|
|
38
38
|
MCP Servers ━━━━━━━━━━────────── 50%
|
|
39
39
|
|
|
40
|
-
Overall
|
|
40
|
+
Overall ━━━━━━━━━━━━━━━━━━━─ 93%
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
No .env file protection hook
|
|
44
|
-
Fix: Add a PreToolUse hook that blocks writes to .env files
|
|
45
|
-
|
|
46
|
-
LOW Permissions
|
|
47
|
-
No force-push protection hook
|
|
48
|
-
Fix: Add a PreToolUse hook that warns on `git push --force` commands
|
|
42
|
+
✓ No issues found. Your configuration looks solid.
|
|
49
43
|
```
|
|
50
44
|
|
|
51
45
|
Running bare `claude-launchpad` with no subcommand auto-detects your config and runs doctor.
|
|
52
46
|
|
|
47
|
+
**Flags:**
|
|
48
|
+
- `--fix` — Auto-apply deterministic fixes (42% → 86% in one command)
|
|
49
|
+
- `--watch` — Live score that updates every time you save a config file
|
|
50
|
+
- `--json` — JSON output for programmatic use
|
|
51
|
+
- `--min-score <n>` — Exit non-zero if score drops below threshold (for CI)
|
|
52
|
+
|
|
53
53
|
**What it checks:**
|
|
54
54
|
|
|
55
55
|
| Analyzer | What it catches |
|
|
56
56
|
|---|---|
|
|
57
57
|
| **Instruction Budget** | Are you over the ~150 instruction limit where Claude starts ignoring rules? |
|
|
58
58
|
| **CLAUDE.md Quality** | Missing essential sections, vague instructions ("write good code"), hardcoded secrets |
|
|
59
|
-
| **Settings** |
|
|
59
|
+
| **Settings** | Hooks configured, dangerous tool access without safety nets |
|
|
60
60
|
| **Hooks** | Missing auto-format, no .env protection, no PreToolUse security gates |
|
|
61
61
|
| **Rules** | Dead rule files, stale references, empty configs |
|
|
62
62
|
| **Permissions** | Bash auto-allowed without security hooks, no force-push protection |
|
|
@@ -80,6 +80,7 @@ claude-launchpad init
|
|
|
80
80
|
✓ Generated CLAUDE.md
|
|
81
81
|
✓ Generated TASKS.md
|
|
82
82
|
✓ Generated .claude/settings.json (with hooks)
|
|
83
|
+
✓ Generated .claudeignore
|
|
83
84
|
```
|
|
84
85
|
|
|
85
86
|
**Detects 13 languages:** TypeScript, JavaScript, Python, Go, Ruby, Rust, Dart, PHP, Java, Kotlin, Swift, Elixir, C#
|
|
@@ -91,7 +92,8 @@ claude-launchpad init
|
|
|
91
92
|
**What you get:**
|
|
92
93
|
- `CLAUDE.md` with your detected stack, commands, and essential sections
|
|
93
94
|
- `TASKS.md` for session continuity across Claude sessions
|
|
94
|
-
- `.claude/settings.json` with auto-format hooks and .env file protection
|
|
95
|
+
- `.claude/settings.json` with auto-format hooks and .env file protection (merges with existing)
|
|
96
|
+
- `.claudeignore` with language-specific ignore patterns (node_modules, __pycache__, dist, etc.)
|
|
95
97
|
|
|
96
98
|
### `enhance` — Let Claude finish what init started
|
|
97
99
|
|
|
@@ -109,23 +111,30 @@ Claude opens, reads your code, and updates CLAUDE.md with:
|
|
|
109
111
|
|
|
110
112
|
You see Claude working in real-time — same experience as running `claude` yourself.
|
|
111
113
|
|
|
112
|
-
### `eval` —
|
|
114
|
+
### `eval` — Prove your config works
|
|
113
115
|
|
|
114
|
-
Runs Claude headless against reproducible scenarios and **scores how well your config actually
|
|
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**.
|
|
115
117
|
|
|
116
118
|
```bash
|
|
117
|
-
claude-launchpad eval --suite
|
|
119
|
+
claude-launchpad eval --suite common
|
|
118
120
|
```
|
|
119
121
|
|
|
120
122
|
```
|
|
121
123
|
✓ security/sql-injection 10/10 PASS
|
|
122
|
-
✓ security/env-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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%
|
|
127
134
|
```
|
|
128
135
|
|
|
136
|
+
Each scenario creates an isolated sandbox, runs Claude with explicit tool permissions, and verifies the output with grep/file assertions. [Write your own scenarios](scenarios/CONTRIBUTING.md) in YAML.
|
|
137
|
+
|
|
129
138
|
This is the part nobody else has built. Template repos scaffold. Audit tools diagnose. **Nobody tests whether your config actually makes Claude better.** Until now.
|
|
130
139
|
|
|
131
140
|
## Use in CI
|
|
@@ -148,7 +157,7 @@ jobs:
|
|
|
148
157
|
- run: npx claude-launchpad@latest doctor --min-score 80 --json
|
|
149
158
|
```
|
|
150
159
|
|
|
151
|
-
Exit code is 1 when score is below the threshold, 0 when it passes.
|
|
160
|
+
Exit code is 1 when score is below the threshold, 0 when it passes.
|
|
152
161
|
|
|
153
162
|
## Install as a Plugin
|
|
154
163
|
|
|
@@ -156,7 +165,7 @@ Exit code is 1 when score is below the threshold, 0 when it passes. The workflow
|
|
|
156
165
|
claude plugin install claude-launchpad
|
|
157
166
|
```
|
|
158
167
|
|
|
159
|
-
Then use `/doctor
|
|
168
|
+
Then use `/launchpad:doctor`, `/launchpad:init`, `/launchpad:enhance`, and `/launchpad:eval` directly inside Claude Code. The plugin also nudges you to re-check your score when you edit config files.
|
|
160
169
|
|
|
161
170
|
## Why this exists
|
|
162
171
|
|
|
@@ -174,10 +183,11 @@ Claude Launchpad gives you a number. Fix the issues, re-run, watch the number go
|
|
|
174
183
|
- **Zero dependencies on third-party Claude plugins.** Generates its own hooks and settings.
|
|
175
184
|
- **Doctor is free.** No API calls, no secrets, works offline and air-gapped.
|
|
176
185
|
- **Enhance uses Claude.** Spawns an interactive session to understand your codebase — costs tokens but produces a CLAUDE.md that actually knows your project.
|
|
177
|
-
- **Eval
|
|
186
|
+
- **Eval uses the Agent SDK.** Runs Claude headless in sandboxes with explicit tool permissions — proof that your config works.
|
|
178
187
|
- **Works with any stack.** Auto-detects your project. No fixed menu of supported frameworks.
|
|
188
|
+
- **50 tests.** The tool that tests configs is itself well-tested.
|
|
179
189
|
- **You never clone this repo.** It's a tool you run with `npx`, not a template you fork.
|
|
180
190
|
|
|
181
191
|
## License
|
|
182
192
|
|
|
183
|
-
MIT
|
|
193
|
+
MIT — Built by [McLovin](https://github.com/mboss37) (the AI behind [@mboss37](https://github.com/mboss37))
|
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { Command as Command5 } from "commander";
|
|
5
5
|
import { access as access7 } from "fs/promises";
|
|
6
|
-
import { join as
|
|
6
|
+
import { join as join9 } from "path";
|
|
7
7
|
|
|
8
8
|
// src/commands/init/index.ts
|
|
9
9
|
import { Command } from "commander";
|
|
@@ -947,34 +947,34 @@ async function analyzeSettings(config) {
|
|
|
947
947
|
});
|
|
948
948
|
return { name: "Settings", issues, score: 40 };
|
|
949
949
|
}
|
|
950
|
-
const
|
|
951
|
-
if (!
|
|
950
|
+
const hooks = config.settings.hooks;
|
|
951
|
+
if (!hooks || Object.keys(hooks).length === 0) {
|
|
952
952
|
issues.push({
|
|
953
953
|
analyzer: "Settings",
|
|
954
|
-
severity: "
|
|
955
|
-
message: "
|
|
956
|
-
fix: "
|
|
954
|
+
severity: "medium",
|
|
955
|
+
message: "settings.json has no hooks configured",
|
|
956
|
+
fix: "Run `claude-launchpad doctor --fix` to generate hooks"
|
|
957
957
|
});
|
|
958
958
|
}
|
|
959
|
-
const
|
|
960
|
-
if (!
|
|
959
|
+
const plugins = config.settings.enabledPlugins;
|
|
960
|
+
if (!plugins || Object.keys(plugins).length === 0) {
|
|
961
961
|
issues.push({
|
|
962
962
|
analyzer: "Settings",
|
|
963
|
-
severity: "
|
|
964
|
-
message: "No
|
|
965
|
-
fix: "Add permission rules to control which tools Claude can use automatically"
|
|
963
|
+
severity: "info",
|
|
964
|
+
message: "No plugins enabled \u2014 plugins are optional but can add capabilities"
|
|
966
965
|
});
|
|
967
966
|
}
|
|
968
|
-
const
|
|
969
|
-
if (
|
|
967
|
+
const allowedTools = config.settings.allowedTools;
|
|
968
|
+
if (allowedTools && allowedTools.length > 0 && config.hooks.length === 0) {
|
|
970
969
|
issues.push({
|
|
971
970
|
analyzer: "Settings",
|
|
972
|
-
severity: "
|
|
973
|
-
message: "
|
|
974
|
-
fix: "
|
|
971
|
+
severity: "medium",
|
|
972
|
+
message: "Tools auto-allowed without any hooks \u2014 no safety net for dangerous operations",
|
|
973
|
+
fix: "Add PreToolUse hooks for security or remove allowedTools to use interactive prompting"
|
|
975
974
|
});
|
|
976
975
|
}
|
|
977
|
-
const
|
|
976
|
+
const actionableCount = issues.filter((i) => i.severity !== "info").length;
|
|
977
|
+
const score = Math.max(0, 100 - actionableCount * 20);
|
|
978
978
|
return { name: "Settings", issues, score };
|
|
979
979
|
}
|
|
980
980
|
|
|
@@ -1440,9 +1440,93 @@ async function writeSettingsJson(root, settings) {
|
|
|
1440
1440
|
await writeFile2(join4(dir, "settings.json"), JSON.stringify(settings, null, 2) + "\n");
|
|
1441
1441
|
}
|
|
1442
1442
|
|
|
1443
|
+
// src/commands/doctor/watcher.ts
|
|
1444
|
+
import { watch } from "fs";
|
|
1445
|
+
import { join as join5 } from "path";
|
|
1446
|
+
async function watchConfig(projectRoot) {
|
|
1447
|
+
const claudeDir = join5(projectRoot, ".claude");
|
|
1448
|
+
const claudeMd = join5(projectRoot, "CLAUDE.md");
|
|
1449
|
+
const claudeignore = join5(projectRoot, ".claudeignore");
|
|
1450
|
+
await runAndDisplay(projectRoot);
|
|
1451
|
+
log.blank();
|
|
1452
|
+
log.info("Watching for changes... (Ctrl+C to stop)");
|
|
1453
|
+
log.blank();
|
|
1454
|
+
let debounce = null;
|
|
1455
|
+
const onChange = () => {
|
|
1456
|
+
if (debounce) clearTimeout(debounce);
|
|
1457
|
+
debounce = setTimeout(async () => {
|
|
1458
|
+
console.clear();
|
|
1459
|
+
await runAndDisplay(projectRoot);
|
|
1460
|
+
log.blank();
|
|
1461
|
+
log.info("Watching for changes... (Ctrl+C to stop)");
|
|
1462
|
+
log.blank();
|
|
1463
|
+
}, 300);
|
|
1464
|
+
};
|
|
1465
|
+
try {
|
|
1466
|
+
watch(claudeDir, { recursive: true }, onChange);
|
|
1467
|
+
} catch {
|
|
1468
|
+
}
|
|
1469
|
+
try {
|
|
1470
|
+
watch(claudeMd, onChange);
|
|
1471
|
+
} catch {
|
|
1472
|
+
}
|
|
1473
|
+
try {
|
|
1474
|
+
watch(claudeignore, onChange);
|
|
1475
|
+
} catch {
|
|
1476
|
+
}
|
|
1477
|
+
await new Promise(() => {
|
|
1478
|
+
});
|
|
1479
|
+
}
|
|
1480
|
+
async function runAndDisplay(projectRoot) {
|
|
1481
|
+
console.log("\x1B[36m\x1B[1m Claude Launchpad\x1B[0m");
|
|
1482
|
+
console.log("\x1B[2m Scaffold \xB7 Diagnose \xB7 Evaluate\x1B[0m");
|
|
1483
|
+
log.blank();
|
|
1484
|
+
const config = await parseClaudeConfig(projectRoot);
|
|
1485
|
+
if (config.claudeMdContent === null && config.settings === null) {
|
|
1486
|
+
log.error("No Claude Code configuration found.");
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1489
|
+
const results = await Promise.all([
|
|
1490
|
+
analyzeBudget(config),
|
|
1491
|
+
analyzeQuality(config),
|
|
1492
|
+
analyzeSettings(config),
|
|
1493
|
+
analyzeHooks(config),
|
|
1494
|
+
analyzeRules(config),
|
|
1495
|
+
analyzePermissions(config),
|
|
1496
|
+
analyzeMcp(config)
|
|
1497
|
+
]);
|
|
1498
|
+
const overallScore = Math.round(
|
|
1499
|
+
results.reduce((sum, r) => sum + r.score, 0) / results.length
|
|
1500
|
+
);
|
|
1501
|
+
for (const result of results) {
|
|
1502
|
+
printScoreCard(result.name, result.score);
|
|
1503
|
+
}
|
|
1504
|
+
log.blank();
|
|
1505
|
+
printScoreCard("Overall", overallScore);
|
|
1506
|
+
log.blank();
|
|
1507
|
+
const allIssues = results.flatMap((r) => r.issues);
|
|
1508
|
+
const actionable = allIssues.filter((i) => i.severity !== "info");
|
|
1509
|
+
if (actionable.length === 0) {
|
|
1510
|
+
log.success("No issues found. Your configuration looks solid.");
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1513
|
+
const sorted = [...actionable].sort((a, b) => {
|
|
1514
|
+
const order = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
|
|
1515
|
+
return (order[a.severity] ?? 4) - (order[b.severity] ?? 4);
|
|
1516
|
+
});
|
|
1517
|
+
for (const issue of sorted) {
|
|
1518
|
+
printIssue(issue.severity, issue.analyzer, issue.message, issue.fix);
|
|
1519
|
+
}
|
|
1520
|
+
log.info(`${actionable.length} issue(s) found. Fix critical/high first.`);
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1443
1523
|
// src/commands/doctor/index.ts
|
|
1444
1524
|
function createDoctorCommand() {
|
|
1445
|
-
return new Command2("doctor").description("Diagnose your Claude Code configuration and report issues").option("-p, --path <path>", "Project root path", process.cwd()).option("--json", "Output as JSON").option("--min-score <n>", "Exit non-zero if overall score is below this threshold (for CI)").option("--fix", "Auto-apply deterministic fixes for detected issues").action(async (opts) => {
|
|
1525
|
+
return new Command2("doctor").description("Diagnose your Claude Code configuration and report issues").option("-p, --path <path>", "Project root path", process.cwd()).option("--json", "Output as JSON").option("--min-score <n>", "Exit non-zero if overall score is below this threshold (for CI)").option("--fix", "Auto-apply deterministic fixes for detected issues").option("--watch", "Watch for config changes and re-run automatically").action(async (opts) => {
|
|
1526
|
+
if (opts.watch) {
|
|
1527
|
+
await watchConfig(opts.path);
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1446
1530
|
printBanner();
|
|
1447
1531
|
log.step("Scanning Claude Code configuration...");
|
|
1448
1532
|
log.blank();
|
|
@@ -1530,7 +1614,7 @@ import chalk2 from "chalk";
|
|
|
1530
1614
|
|
|
1531
1615
|
// src/commands/eval/loader.ts
|
|
1532
1616
|
import { readFile as readFile6, readdir as readdir2, access as access5 } from "fs/promises";
|
|
1533
|
-
import { join as
|
|
1617
|
+
import { join as join6, resolve as resolve2, dirname } from "path";
|
|
1534
1618
|
import { fileURLToPath } from "url";
|
|
1535
1619
|
import { parse as parseYaml } from "yaml";
|
|
1536
1620
|
|
|
@@ -1653,7 +1737,7 @@ async function dirExists(path) {
|
|
|
1653
1737
|
async function loadScenarios(options) {
|
|
1654
1738
|
const { suite, customPath } = options;
|
|
1655
1739
|
const scenarioDir = customPath ? resolve2(customPath) : await findScenariosDir();
|
|
1656
|
-
const dirs = suite ? [
|
|
1740
|
+
const dirs = suite ? [join6(scenarioDir, suite)] : await getSubdirectories(scenarioDir);
|
|
1657
1741
|
const allDirs = [scenarioDir, ...dirs];
|
|
1658
1742
|
const scenarios = [];
|
|
1659
1743
|
for (const dir of allDirs) {
|
|
@@ -1675,7 +1759,7 @@ async function loadScenarios(options) {
|
|
|
1675
1759
|
async function getSubdirectories(dir) {
|
|
1676
1760
|
try {
|
|
1677
1761
|
const entries = await readdir2(dir, { withFileTypes: true });
|
|
1678
|
-
return entries.filter((e) => e.isDirectory()).map((e) =>
|
|
1762
|
+
return entries.filter((e) => e.isDirectory()).map((e) => join6(dir, e.name));
|
|
1679
1763
|
} catch {
|
|
1680
1764
|
return [];
|
|
1681
1765
|
}
|
|
@@ -1683,7 +1767,7 @@ async function getSubdirectories(dir) {
|
|
|
1683
1767
|
async function listYamlFiles(dir) {
|
|
1684
1768
|
try {
|
|
1685
1769
|
const entries = await readdir2(dir, { withFileTypes: true });
|
|
1686
|
-
return entries.filter((e) => e.isFile() && (e.name.endsWith(".yaml") || e.name.endsWith(".yml"))).map((e) =>
|
|
1770
|
+
return entries.filter((e) => e.isFile() && (e.name.endsWith(".yaml") || e.name.endsWith(".yml"))).map((e) => join6(dir, e.name));
|
|
1687
1771
|
} catch {
|
|
1688
1772
|
return [];
|
|
1689
1773
|
}
|
|
@@ -1691,14 +1775,14 @@ async function listYamlFiles(dir) {
|
|
|
1691
1775
|
|
|
1692
1776
|
// src/commands/eval/runner.ts
|
|
1693
1777
|
import { mkdir as mkdir3, writeFile as writeFile3, readFile as readFile7, readdir as readdir3, rm } from "fs/promises";
|
|
1694
|
-
import { join as
|
|
1778
|
+
import { join as join7, dirname as dirname2 } from "path";
|
|
1695
1779
|
import { tmpdir } from "os";
|
|
1696
1780
|
import { randomUUID } from "crypto";
|
|
1697
1781
|
import { execFile } from "child_process";
|
|
1698
1782
|
import { promisify } from "util";
|
|
1699
1783
|
var exec = promisify(execFile);
|
|
1700
1784
|
async function runScenario(scenario, options) {
|
|
1701
|
-
const sandboxDir =
|
|
1785
|
+
const sandboxDir = join7(tmpdir(), `claude-eval-${randomUUID()}`);
|
|
1702
1786
|
try {
|
|
1703
1787
|
await setupSandbox(sandboxDir, scenario);
|
|
1704
1788
|
await runClaudeInSandbox(sandboxDir, scenario.prompt, options.timeout);
|
|
@@ -1724,13 +1808,13 @@ async function runScenarioWithRetries(scenario, options) {
|
|
|
1724
1808
|
async function setupSandbox(sandboxDir, scenario) {
|
|
1725
1809
|
await mkdir3(sandboxDir, { recursive: true });
|
|
1726
1810
|
for (const file of scenario.setup.files) {
|
|
1727
|
-
const filePath =
|
|
1811
|
+
const filePath = join7(sandboxDir, file.path);
|
|
1728
1812
|
await mkdir3(dirname2(filePath), { recursive: true });
|
|
1729
1813
|
await writeFile3(filePath, file.content);
|
|
1730
1814
|
}
|
|
1731
1815
|
if (scenario.setup.instructions) {
|
|
1732
1816
|
await writeFile3(
|
|
1733
|
-
|
|
1817
|
+
join7(sandboxDir, "CLAUDE.md"),
|
|
1734
1818
|
`# Eval Scenario
|
|
1735
1819
|
|
|
1736
1820
|
${scenario.setup.instructions}
|
|
@@ -1843,7 +1927,7 @@ async function evaluateSingleCheck(check, sandboxDir) {
|
|
|
1843
1927
|
async function checkGrep(check, sandboxDir) {
|
|
1844
1928
|
if (!check.pattern) return false;
|
|
1845
1929
|
try {
|
|
1846
|
-
const content = await readFile7(
|
|
1930
|
+
const content = await readFile7(join7(sandboxDir, check.target), "utf-8");
|
|
1847
1931
|
let found;
|
|
1848
1932
|
try {
|
|
1849
1933
|
found = new RegExp(check.pattern).test(content);
|
|
@@ -1857,7 +1941,7 @@ async function checkGrep(check, sandboxDir) {
|
|
|
1857
1941
|
}
|
|
1858
1942
|
async function checkFileExists(check, sandboxDir) {
|
|
1859
1943
|
try {
|
|
1860
|
-
await readFile7(
|
|
1944
|
+
await readFile7(join7(sandboxDir, check.target));
|
|
1861
1945
|
return check.expect === "present";
|
|
1862
1946
|
} catch {
|
|
1863
1947
|
return check.expect === "absent";
|
|
@@ -1865,7 +1949,7 @@ async function checkFileExists(check, sandboxDir) {
|
|
|
1865
1949
|
}
|
|
1866
1950
|
async function checkFileAbsent(check, sandboxDir) {
|
|
1867
1951
|
try {
|
|
1868
|
-
await readFile7(
|
|
1952
|
+
await readFile7(join7(sandboxDir, check.target));
|
|
1869
1953
|
return check.expect === "absent";
|
|
1870
1954
|
} catch {
|
|
1871
1955
|
return check.expect === "present";
|
|
@@ -1874,7 +1958,7 @@ async function checkFileAbsent(check, sandboxDir) {
|
|
|
1874
1958
|
async function checkMaxLines(check, sandboxDir) {
|
|
1875
1959
|
const maxLines = parseInt(check.pattern ?? "800", 10);
|
|
1876
1960
|
try {
|
|
1877
|
-
const files = await listAllFiles(
|
|
1961
|
+
const files = await listAllFiles(join7(sandboxDir, check.target));
|
|
1878
1962
|
for (const file of files) {
|
|
1879
1963
|
const content = await readFile7(file, "utf-8");
|
|
1880
1964
|
if (content.split("\n").length > maxLines) {
|
|
@@ -1891,7 +1975,7 @@ async function listAllFiles(dir) {
|
|
|
1891
1975
|
try {
|
|
1892
1976
|
const entries = await readdir3(dir, { withFileTypes: true });
|
|
1893
1977
|
for (const entry of entries) {
|
|
1894
|
-
const fullPath =
|
|
1978
|
+
const fullPath = join7(dir, entry.name);
|
|
1895
1979
|
if (entry.isDirectory()) {
|
|
1896
1980
|
results.push(...await listAllFiles(fullPath));
|
|
1897
1981
|
} else {
|
|
@@ -2017,7 +2101,7 @@ import { Command as Command4 } from "commander";
|
|
|
2017
2101
|
import { spawn, execFile as execFile2 } from "child_process";
|
|
2018
2102
|
import { promisify as promisify2 } from "util";
|
|
2019
2103
|
import { access as access6 } from "fs/promises";
|
|
2020
|
-
import { join as
|
|
2104
|
+
import { join as join8 } from "path";
|
|
2021
2105
|
var execAsync = promisify2(execFile2);
|
|
2022
2106
|
var ENHANCE_PROMPT = `Read CLAUDE.md and the project's codebase, then update CLAUDE.md to fill in missing or incomplete sections:
|
|
2023
2107
|
|
|
@@ -2035,7 +2119,7 @@ function createEnhanceCommand() {
|
|
|
2035
2119
|
return new Command4("enhance").description("Use Claude to analyze your codebase and complete CLAUDE.md").option("-p, --path <path>", "Project root path", process.cwd()).action(async (opts) => {
|
|
2036
2120
|
printBanner();
|
|
2037
2121
|
const root = opts.path;
|
|
2038
|
-
const claudeMdPath =
|
|
2122
|
+
const claudeMdPath = join8(root, "CLAUDE.md");
|
|
2039
2123
|
try {
|
|
2040
2124
|
await access6(claudeMdPath);
|
|
2041
2125
|
} catch {
|
|
@@ -2064,8 +2148,8 @@ function createEnhanceCommand() {
|
|
|
2064
2148
|
}
|
|
2065
2149
|
|
|
2066
2150
|
// src/cli.ts
|
|
2067
|
-
var program = new Command5().name("claude-launchpad").description("CLI toolkit that makes Claude Code setups measurably good").version("0.1
|
|
2068
|
-
const hasConfig = await fileExists3(
|
|
2151
|
+
var program = new Command5().name("claude-launchpad").description("CLI toolkit that makes Claude Code setups measurably good").version("0.2.1").action(async () => {
|
|
2152
|
+
const hasConfig = await fileExists3(join9(process.cwd(), "CLAUDE.md")) || await fileExists3(join9(process.cwd(), ".claude", "settings.json"));
|
|
2069
2153
|
if (hasConfig) {
|
|
2070
2154
|
await program.commands.find((c) => c.name() === "doctor")?.parseAsync([], { from: "user" });
|
|
2071
2155
|
} else {
|