claude-launchpad 0.1.1 → 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 +55 -35
- package/dist/cli.js +264 -112
- package/dist/cli.js.map +1 -1
- package/package.json +4 -1
- package/scenarios/CONTRIBUTING.md +62 -0
- package/scenarios/common/file-size.yaml +3 -2
- package/scenarios/common/git-conventions.yaml +36 -0
- package/scenarios/common/no-hardcoded-values.yaml +33 -0
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";
|
|
@@ -324,9 +324,9 @@ async function fileExists(path) {
|
|
|
324
324
|
}
|
|
325
325
|
}
|
|
326
326
|
async function globExists(dir, pattern) {
|
|
327
|
-
const { readdir:
|
|
327
|
+
const { readdir: readdir4 } = await import("fs/promises");
|
|
328
328
|
try {
|
|
329
|
-
const entries = await
|
|
329
|
+
const entries = await readdir4(dir);
|
|
330
330
|
return entries.some((e) => e.endsWith(pattern.replace("*", "")));
|
|
331
331
|
} catch {
|
|
332
332
|
return false;
|
|
@@ -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
|
|
|
@@ -1266,6 +1266,12 @@ async function applyFixes(issues, projectRoot) {
|
|
|
1266
1266
|
return { fixed, skipped };
|
|
1267
1267
|
}
|
|
1268
1268
|
async function tryFix(issue, root, detected) {
|
|
1269
|
+
if (issue.analyzer === "Hooks" && issue.message.includes("No hooks configured")) {
|
|
1270
|
+
const a = await addEnvProtectionHook(root);
|
|
1271
|
+
const b = await addAutoFormatHook(root, detected);
|
|
1272
|
+
const c = await addForcePushProtection(root);
|
|
1273
|
+
return a || b || c;
|
|
1274
|
+
}
|
|
1269
1275
|
if (issue.analyzer === "Hooks" && issue.message.includes(".env file protection")) {
|
|
1270
1276
|
return addEnvProtectionHook(root);
|
|
1271
1277
|
}
|
|
@@ -1434,9 +1440,93 @@ async function writeSettingsJson(root, settings) {
|
|
|
1434
1440
|
await writeFile2(join4(dir, "settings.json"), JSON.stringify(settings, null, 2) + "\n");
|
|
1435
1441
|
}
|
|
1436
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
|
+
|
|
1437
1523
|
// src/commands/doctor/index.ts
|
|
1438
1524
|
function createDoctorCommand() {
|
|
1439
|
-
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
|
+
}
|
|
1440
1530
|
printBanner();
|
|
1441
1531
|
log.step("Scanning Claude Code configuration...");
|
|
1442
1532
|
log.blank();
|
|
@@ -1524,7 +1614,7 @@ import chalk2 from "chalk";
|
|
|
1524
1614
|
|
|
1525
1615
|
// src/commands/eval/loader.ts
|
|
1526
1616
|
import { readFile as readFile6, readdir as readdir2, access as access5 } from "fs/promises";
|
|
1527
|
-
import { join as
|
|
1617
|
+
import { join as join6, resolve as resolve2, dirname } from "path";
|
|
1528
1618
|
import { fileURLToPath } from "url";
|
|
1529
1619
|
import { parse as parseYaml } from "yaml";
|
|
1530
1620
|
|
|
@@ -1574,7 +1664,7 @@ function validateChecks(raw, filePath) {
|
|
|
1574
1664
|
throw new ScenarioError(filePath, `checks[${i}] must be an object`);
|
|
1575
1665
|
}
|
|
1576
1666
|
const check = c;
|
|
1577
|
-
const validTypes = ["grep", "file-exists", "file-absent", "custom"];
|
|
1667
|
+
const validTypes = ["grep", "file-exists", "file-absent", "max-lines", "custom"];
|
|
1578
1668
|
if (!validTypes.includes(check.type)) {
|
|
1579
1669
|
throw new ScenarioError(filePath, `checks[${i}].type must be one of: ${validTypes.join(", ")}`);
|
|
1580
1670
|
}
|
|
@@ -1647,7 +1737,7 @@ async function dirExists(path) {
|
|
|
1647
1737
|
async function loadScenarios(options) {
|
|
1648
1738
|
const { suite, customPath } = options;
|
|
1649
1739
|
const scenarioDir = customPath ? resolve2(customPath) : await findScenariosDir();
|
|
1650
|
-
const dirs = suite ? [
|
|
1740
|
+
const dirs = suite ? [join6(scenarioDir, suite)] : await getSubdirectories(scenarioDir);
|
|
1651
1741
|
const allDirs = [scenarioDir, ...dirs];
|
|
1652
1742
|
const scenarios = [];
|
|
1653
1743
|
for (const dir of allDirs) {
|
|
@@ -1669,7 +1759,7 @@ async function loadScenarios(options) {
|
|
|
1669
1759
|
async function getSubdirectories(dir) {
|
|
1670
1760
|
try {
|
|
1671
1761
|
const entries = await readdir2(dir, { withFileTypes: true });
|
|
1672
|
-
return entries.filter((e) => e.isDirectory()).map((e) =>
|
|
1762
|
+
return entries.filter((e) => e.isDirectory()).map((e) => join6(dir, e.name));
|
|
1673
1763
|
} catch {
|
|
1674
1764
|
return [];
|
|
1675
1765
|
}
|
|
@@ -1677,67 +1767,26 @@ async function getSubdirectories(dir) {
|
|
|
1677
1767
|
async function listYamlFiles(dir) {
|
|
1678
1768
|
try {
|
|
1679
1769
|
const entries = await readdir2(dir, { withFileTypes: true });
|
|
1680
|
-
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));
|
|
1681
1771
|
} catch {
|
|
1682
1772
|
return [];
|
|
1683
1773
|
}
|
|
1684
1774
|
}
|
|
1685
1775
|
|
|
1686
1776
|
// src/commands/eval/runner.ts
|
|
1687
|
-
import { mkdir as mkdir3, writeFile as writeFile3, readFile as readFile7,
|
|
1688
|
-
import { join as
|
|
1777
|
+
import { mkdir as mkdir3, writeFile as writeFile3, readFile as readFile7, readdir as readdir3, rm } from "fs/promises";
|
|
1778
|
+
import { join as join7, dirname as dirname2 } from "path";
|
|
1689
1779
|
import { tmpdir } from "os";
|
|
1690
1780
|
import { randomUUID } from "crypto";
|
|
1691
1781
|
import { execFile } from "child_process";
|
|
1692
1782
|
import { promisify } from "util";
|
|
1693
1783
|
var exec = promisify(execFile);
|
|
1694
1784
|
async function runScenario(scenario, options) {
|
|
1695
|
-
const sandboxDir =
|
|
1785
|
+
const sandboxDir = join7(tmpdir(), `claude-eval-${randomUUID()}`);
|
|
1696
1786
|
try {
|
|
1697
|
-
await
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
await mkdir3(dirname2(filePath), { recursive: true });
|
|
1701
|
-
await writeFile3(filePath, file.content);
|
|
1702
|
-
}
|
|
1703
|
-
const claudeDir = join6(options.projectRoot, ".claude");
|
|
1704
|
-
const sandboxClaudeDir = join6(sandboxDir, ".claude");
|
|
1705
|
-
try {
|
|
1706
|
-
await cp(claudeDir, sandboxClaudeDir, { recursive: true });
|
|
1707
|
-
} catch {
|
|
1708
|
-
}
|
|
1709
|
-
if (scenario.setup.instructions) {
|
|
1710
|
-
await writeFile3(
|
|
1711
|
-
join6(sandboxDir, "CLAUDE.md"),
|
|
1712
|
-
`# Eval Scenario
|
|
1713
|
-
|
|
1714
|
-
${scenario.setup.instructions}
|
|
1715
|
-
`
|
|
1716
|
-
);
|
|
1717
|
-
}
|
|
1718
|
-
await exec("git", ["init", "-q"], { cwd: sandboxDir });
|
|
1719
|
-
await exec("git", ["add", "-A"], { cwd: sandboxDir });
|
|
1720
|
-
await exec("git", [
|
|
1721
|
-
"-c",
|
|
1722
|
-
"user.name=eval",
|
|
1723
|
-
"-c",
|
|
1724
|
-
"user.email=eval@test",
|
|
1725
|
-
"commit",
|
|
1726
|
-
"-q",
|
|
1727
|
-
"-m",
|
|
1728
|
-
"eval setup"
|
|
1729
|
-
], { cwd: sandboxDir });
|
|
1730
|
-
await runClaude(sandboxDir, scenario.prompt, options.timeout);
|
|
1731
|
-
const checkResults = await evaluateChecks(scenario.checks, sandboxDir);
|
|
1732
|
-
const score = checkResults.filter((c) => c.passed).reduce((sum, c) => sum + c.points, 0);
|
|
1733
|
-
const maxScore = scenario.checks.reduce((sum, c) => sum + c.points, 0);
|
|
1734
|
-
return {
|
|
1735
|
-
scenario: scenario.name,
|
|
1736
|
-
score,
|
|
1737
|
-
maxScore,
|
|
1738
|
-
passed: score >= scenario.passingScore,
|
|
1739
|
-
checks: checkResults
|
|
1740
|
-
};
|
|
1787
|
+
await setupSandbox(sandboxDir, scenario);
|
|
1788
|
+
await runClaudeInSandbox(sandboxDir, scenario.prompt, options.timeout);
|
|
1789
|
+
return await scoreResults(scenario, sandboxDir);
|
|
1741
1790
|
} finally {
|
|
1742
1791
|
if (options.debug) {
|
|
1743
1792
|
console.log(` DEBUG: Sandbox preserved at ${sandboxDir}`);
|
|
@@ -1756,9 +1805,63 @@ async function runScenarioWithRetries(scenario, options) {
|
|
|
1756
1805
|
const sorted = [...results].sort((a, b) => a.score - b.score);
|
|
1757
1806
|
return sorted[Math.floor(sorted.length / 2)];
|
|
1758
1807
|
}
|
|
1759
|
-
async function
|
|
1808
|
+
async function setupSandbox(sandboxDir, scenario) {
|
|
1809
|
+
await mkdir3(sandboxDir, { recursive: true });
|
|
1810
|
+
for (const file of scenario.setup.files) {
|
|
1811
|
+
const filePath = join7(sandboxDir, file.path);
|
|
1812
|
+
await mkdir3(dirname2(filePath), { recursive: true });
|
|
1813
|
+
await writeFile3(filePath, file.content);
|
|
1814
|
+
}
|
|
1815
|
+
if (scenario.setup.instructions) {
|
|
1816
|
+
await writeFile3(
|
|
1817
|
+
join7(sandboxDir, "CLAUDE.md"),
|
|
1818
|
+
`# Eval Scenario
|
|
1819
|
+
|
|
1820
|
+
${scenario.setup.instructions}
|
|
1821
|
+
`
|
|
1822
|
+
);
|
|
1823
|
+
}
|
|
1824
|
+
await exec("git", ["init", "-q"], { cwd: sandboxDir });
|
|
1825
|
+
await exec("git", ["add", "-A"], { cwd: sandboxDir });
|
|
1826
|
+
await exec("git", [
|
|
1827
|
+
"-c",
|
|
1828
|
+
"user.name=eval",
|
|
1829
|
+
"-c",
|
|
1830
|
+
"user.email=eval@test",
|
|
1831
|
+
"commit",
|
|
1832
|
+
"-q",
|
|
1833
|
+
"-m",
|
|
1834
|
+
"eval setup"
|
|
1835
|
+
], { cwd: sandboxDir });
|
|
1836
|
+
}
|
|
1837
|
+
async function runClaudeInSandbox(cwd, prompt, timeout) {
|
|
1838
|
+
try {
|
|
1839
|
+
const sdk = await import("@anthropic-ai/claude-agent-sdk");
|
|
1840
|
+
const controller = new AbortController();
|
|
1841
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
1842
|
+
try {
|
|
1843
|
+
for await (const _message of sdk.query({
|
|
1844
|
+
prompt,
|
|
1845
|
+
options: {
|
|
1846
|
+
cwd,
|
|
1847
|
+
allowedTools: ["Bash", "Read", "Write", "Edit", "Glob", "Grep"],
|
|
1848
|
+
permissionMode: "dontAsk",
|
|
1849
|
+
settingSources: [],
|
|
1850
|
+
maxTurns: 20,
|
|
1851
|
+
abortController: controller
|
|
1852
|
+
}
|
|
1853
|
+
})) {
|
|
1854
|
+
}
|
|
1855
|
+
} finally {
|
|
1856
|
+
clearTimeout(timeoutId);
|
|
1857
|
+
}
|
|
1858
|
+
} catch {
|
|
1859
|
+
await runClaudeCli(cwd, prompt, timeout);
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
async function runClaudeCli(cwd, prompt, timeout) {
|
|
1760
1863
|
try {
|
|
1761
|
-
|
|
1864
|
+
await exec(
|
|
1762
1865
|
"claude",
|
|
1763
1866
|
[
|
|
1764
1867
|
"-p",
|
|
@@ -1769,29 +1872,34 @@ async function runClaude(cwd, prompt, timeout) {
|
|
|
1769
1872
|
"20",
|
|
1770
1873
|
"--dangerously-skip-permissions",
|
|
1771
1874
|
"--allowedTools",
|
|
1875
|
+
"Bash",
|
|
1772
1876
|
"Read",
|
|
1773
1877
|
"Write",
|
|
1774
1878
|
"Edit",
|
|
1775
|
-
"Bash",
|
|
1776
1879
|
"Glob",
|
|
1777
1880
|
"Grep"
|
|
1778
1881
|
],
|
|
1779
|
-
{
|
|
1780
|
-
cwd,
|
|
1781
|
-
timeout,
|
|
1782
|
-
maxBuffer: 10 * 1024 * 1024
|
|
1783
|
-
}
|
|
1882
|
+
{ cwd, timeout, maxBuffer: 10 * 1024 * 1024 }
|
|
1784
1883
|
);
|
|
1785
1884
|
} catch (error) {
|
|
1786
1885
|
if (error && typeof error === "object" && "stdout" in error) {
|
|
1787
|
-
return
|
|
1788
|
-
stdout: String(error.stdout ?? ""),
|
|
1789
|
-
stderr: String(error.stderr ?? "")
|
|
1790
|
-
};
|
|
1886
|
+
return;
|
|
1791
1887
|
}
|
|
1792
1888
|
throw error;
|
|
1793
1889
|
}
|
|
1794
1890
|
}
|
|
1891
|
+
async function scoreResults(scenario, sandboxDir) {
|
|
1892
|
+
const checkResults = await evaluateChecks(scenario.checks, sandboxDir);
|
|
1893
|
+
const score = checkResults.filter((c) => c.passed).reduce((sum, c) => sum + c.points, 0);
|
|
1894
|
+
const maxScore = scenario.checks.reduce((sum, c) => sum + c.points, 0);
|
|
1895
|
+
return {
|
|
1896
|
+
scenario: scenario.name,
|
|
1897
|
+
score,
|
|
1898
|
+
maxScore,
|
|
1899
|
+
passed: score >= scenario.passingScore,
|
|
1900
|
+
checks: checkResults
|
|
1901
|
+
};
|
|
1902
|
+
}
|
|
1795
1903
|
async function evaluateChecks(checks, sandboxDir) {
|
|
1796
1904
|
const results = [];
|
|
1797
1905
|
for (const check of checks) {
|
|
@@ -1802,37 +1910,81 @@ async function evaluateChecks(checks, sandboxDir) {
|
|
|
1802
1910
|
}
|
|
1803
1911
|
async function evaluateSingleCheck(check, sandboxDir) {
|
|
1804
1912
|
switch (check.type) {
|
|
1805
|
-
case "grep":
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1913
|
+
case "grep":
|
|
1914
|
+
return checkGrep(check, sandboxDir);
|
|
1915
|
+
case "file-exists":
|
|
1916
|
+
return checkFileExists(check, sandboxDir);
|
|
1917
|
+
case "file-absent":
|
|
1918
|
+
return checkFileAbsent(check, sandboxDir);
|
|
1919
|
+
case "max-lines":
|
|
1920
|
+
return checkMaxLines(check, sandboxDir);
|
|
1921
|
+
case "custom":
|
|
1922
|
+
return false;
|
|
1923
|
+
default:
|
|
1924
|
+
return false;
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
async function checkGrep(check, sandboxDir) {
|
|
1928
|
+
if (!check.pattern) return false;
|
|
1929
|
+
try {
|
|
1930
|
+
const content = await readFile7(join7(sandboxDir, check.target), "utf-8");
|
|
1931
|
+
let found;
|
|
1932
|
+
try {
|
|
1933
|
+
found = new RegExp(check.pattern).test(content);
|
|
1934
|
+
} catch {
|
|
1935
|
+
return false;
|
|
1814
1936
|
}
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1937
|
+
return check.expect === "present" ? found : !found;
|
|
1938
|
+
} catch {
|
|
1939
|
+
return check.expect === "absent";
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
async function checkFileExists(check, sandboxDir) {
|
|
1943
|
+
try {
|
|
1944
|
+
await readFile7(join7(sandboxDir, check.target));
|
|
1945
|
+
return check.expect === "present";
|
|
1946
|
+
} catch {
|
|
1947
|
+
return check.expect === "absent";
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
async function checkFileAbsent(check, sandboxDir) {
|
|
1951
|
+
try {
|
|
1952
|
+
await readFile7(join7(sandboxDir, check.target));
|
|
1953
|
+
return check.expect === "absent";
|
|
1954
|
+
} catch {
|
|
1955
|
+
return check.expect === "present";
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
async function checkMaxLines(check, sandboxDir) {
|
|
1959
|
+
const maxLines = parseInt(check.pattern ?? "800", 10);
|
|
1960
|
+
try {
|
|
1961
|
+
const files = await listAllFiles(join7(sandboxDir, check.target));
|
|
1962
|
+
for (const file of files) {
|
|
1963
|
+
const content = await readFile7(file, "utf-8");
|
|
1964
|
+
if (content.split("\n").length > maxLines) {
|
|
1820
1965
|
return check.expect === "absent";
|
|
1821
1966
|
}
|
|
1822
1967
|
}
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1968
|
+
return check.expect === "present";
|
|
1969
|
+
} catch {
|
|
1970
|
+
return check.expect === "absent";
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
async function listAllFiles(dir) {
|
|
1974
|
+
const results = [];
|
|
1975
|
+
try {
|
|
1976
|
+
const entries = await readdir3(dir, { withFileTypes: true });
|
|
1977
|
+
for (const entry of entries) {
|
|
1978
|
+
const fullPath = join7(dir, entry.name);
|
|
1979
|
+
if (entry.isDirectory()) {
|
|
1980
|
+
results.push(...await listAllFiles(fullPath));
|
|
1981
|
+
} else {
|
|
1982
|
+
results.push(fullPath);
|
|
1829
1983
|
}
|
|
1830
1984
|
}
|
|
1831
|
-
|
|
1832
|
-
return false;
|
|
1833
|
-
default:
|
|
1834
|
-
return false;
|
|
1985
|
+
} catch {
|
|
1835
1986
|
}
|
|
1987
|
+
return results;
|
|
1836
1988
|
}
|
|
1837
1989
|
|
|
1838
1990
|
// src/commands/eval/index.ts
|
|
@@ -1949,7 +2101,7 @@ import { Command as Command4 } from "commander";
|
|
|
1949
2101
|
import { spawn, execFile as execFile2 } from "child_process";
|
|
1950
2102
|
import { promisify as promisify2 } from "util";
|
|
1951
2103
|
import { access as access6 } from "fs/promises";
|
|
1952
|
-
import { join as
|
|
2104
|
+
import { join as join8 } from "path";
|
|
1953
2105
|
var execAsync = promisify2(execFile2);
|
|
1954
2106
|
var ENHANCE_PROMPT = `Read CLAUDE.md and the project's codebase, then update CLAUDE.md to fill in missing or incomplete sections:
|
|
1955
2107
|
|
|
@@ -1967,7 +2119,7 @@ function createEnhanceCommand() {
|
|
|
1967
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) => {
|
|
1968
2120
|
printBanner();
|
|
1969
2121
|
const root = opts.path;
|
|
1970
|
-
const claudeMdPath =
|
|
2122
|
+
const claudeMdPath = join8(root, "CLAUDE.md");
|
|
1971
2123
|
try {
|
|
1972
2124
|
await access6(claudeMdPath);
|
|
1973
2125
|
} catch {
|
|
@@ -1996,8 +2148,8 @@ function createEnhanceCommand() {
|
|
|
1996
2148
|
}
|
|
1997
2149
|
|
|
1998
2150
|
// src/cli.ts
|
|
1999
|
-
var program = new Command5().name("claude-launchpad").description("CLI toolkit that makes Claude Code setups measurably good").version("0.1
|
|
2000
|
-
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"));
|
|
2001
2153
|
if (hasConfig) {
|
|
2002
2154
|
await program.commands.find((c) => c.name() === "doctor")?.parseAsync([], { from: "user" });
|
|
2003
2155
|
} else {
|