claude-launchpad 0.6.1 → 0.7.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/dist/cli.js CHANGED
@@ -1,4 +1,15 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ readSettingsJson,
4
+ writeSettingsJson
5
+ } from "./chunk-CSLWJEGD.js";
6
+ import {
7
+ log,
8
+ printBanner,
9
+ printScoreCard,
10
+ renderDoctorReport
11
+ } from "./chunk-6ZVXZ4EF.js";
12
+ import "./chunk-2H7UOFLK.js";
2
13
 
3
14
  // src/cli.ts
4
15
  import { Command as Command5 } from "commander";
@@ -6,102 +17,11 @@ import { join as join11 } from "path";
6
17
 
7
18
  // src/commands/init/index.ts
8
19
  import { Command } from "commander";
9
- import { input, confirm } from "@inquirer/prompts";
20
+ import { input, confirm, select } from "@inquirer/prompts";
10
21
  import { writeFile, mkdir, readFile as readFile2 } from "fs/promises";
22
+ import { homedir } from "os";
11
23
  import { join as join2 } from "path";
12
24
 
13
- // src/lib/output.ts
14
- import chalk from "chalk";
15
- var colors = {
16
- success: chalk.green,
17
- error: chalk.red,
18
- warn: chalk.yellow,
19
- info: chalk.cyan,
20
- dim: chalk.dim,
21
- bold: chalk.bold,
22
- score: (score) => {
23
- if (score >= 80) return chalk.green.bold(`${score}%`);
24
- if (score >= 60) return chalk.yellow.bold(`${score}%`);
25
- return chalk.red.bold(`${score}%`);
26
- },
27
- severity: (sev) => {
28
- const map = {
29
- critical: chalk.bgRed.white.bold,
30
- high: chalk.red.bold,
31
- medium: chalk.yellow,
32
- low: chalk.cyan,
33
- info: chalk.dim
34
- };
35
- return map[sev](` ${sev.toUpperCase()} `);
36
- }
37
- };
38
- var log = {
39
- success: (msg) => console.log(` ${chalk.green("\u2713")} ${msg}`),
40
- error: (msg) => console.log(` ${chalk.red("\u2717")} ${msg}`),
41
- warn: (msg) => console.log(` ${chalk.yellow("!")} ${msg}`),
42
- step: (msg) => console.log(` ${chalk.cyan("\u2192")} ${msg}`),
43
- info: (msg) => console.log(` ${chalk.dim("\xB7")} ${msg}`),
44
- blank: () => console.log()
45
- };
46
- function printBanner() {
47
- log.blank();
48
- console.log(chalk.cyan.bold(" Claude Launchpad"));
49
- console.log(chalk.dim(" Scaffold \xB7 Diagnose \xB7 Evaluate"));
50
- log.blank();
51
- }
52
- function printScoreCard(label, score, max = 100) {
53
- const pct = Math.round(score / max * 100);
54
- const bar = renderBar(pct, 20);
55
- console.log(` ${chalk.bold(label.padEnd(22))} ${bar} ${colors.score(pct).padStart(12)}`);
56
- }
57
- function renderBar(pct, width) {
58
- const filled = Math.round(pct / 100 * width);
59
- const empty = width - filled;
60
- const color = pct >= 80 ? chalk.green : pct >= 60 ? chalk.yellow : chalk.red;
61
- return color("\u2501".repeat(filled)) + chalk.dim("\u2500".repeat(empty));
62
- }
63
- function printIssue(severity, _analyzer, message) {
64
- const sevLabel = {
65
- critical: chalk.bgRed.white.bold(" CRIT "),
66
- high: chalk.red.bold("HIGH"),
67
- medium: chalk.yellow("MED "),
68
- low: chalk.dim("LOW "),
69
- info: chalk.dim("INFO")
70
- };
71
- console.log(` ${sevLabel[severity]} ${message}`);
72
- }
73
- function renderDoctorReport(results, options) {
74
- const overallScore = Math.round(
75
- results.reduce((sum, r) => sum + r.score, 0) / results.length
76
- );
77
- for (const result of results) {
78
- printScoreCard(result.name, result.score);
79
- }
80
- log.blank();
81
- printScoreCard("Overall", overallScore);
82
- log.blank();
83
- const allIssues = results.flatMap((r) => r.issues);
84
- const actionable = allIssues.filter((i) => i.severity !== "info");
85
- if (actionable.length === 0) {
86
- log.success("No issues found. Your configuration looks solid.");
87
- return { overallScore, actionableCount: 0 };
88
- }
89
- const sorted = [...actionable].sort((a, b) => {
90
- const order = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
91
- return (order[a.severity] ?? 4) - (order[b.severity] ?? 4);
92
- });
93
- for (const issue of sorted) {
94
- printIssue(issue.severity, issue.analyzer, issue.message);
95
- }
96
- log.blank();
97
- if (options?.afterFix) {
98
- log.info(`${actionable.length} remaining issue(s) require manual intervention.`);
99
- } else {
100
- log.info(`${actionable.length} issue(s). Run ${chalk.bold("--fix")} to auto-repair or ${chalk.bold("--fix --dry-run")} to preview.`);
101
- }
102
- return { overallScore, actionableCount: actionable.length };
103
- }
104
-
105
25
  // src/lib/fs-utils.ts
106
26
  import { readFile, access } from "fs/promises";
107
27
  async function fileExists(path) {
@@ -617,6 +537,62 @@ DerivedData/
617
537
  return sections.join("\n") + "\n";
618
538
  }
619
539
 
540
+ // src/commands/init/generators/skill-enhance.ts
541
+ function generateEnhanceSkill() {
542
+ return `---
543
+ name: lp-enhance
544
+ description: AI-improve your CLAUDE.md based on codebase analysis. Fills in architecture, conventions, guardrails, and suggests hooks and MCP servers.
545
+ disable-model-invocation: true
546
+ ---
547
+
548
+ # lp-enhance - AI-powered CLAUDE.md improver
549
+
550
+ Read CLAUDE.md and the project's codebase, then update CLAUDE.md to fill in missing or incomplete sections.
551
+
552
+ ## Budget Rule
553
+
554
+ CLAUDE.md must stay UNDER 120 lines of actionable content (not counting headings, blank lines, or comments). Claude Code starts ignoring rules past ~150 instructions. If you need more detail, create .claude/rules/ files instead:
555
+ - Create .claude/rules/conventions.md for detailed coding patterns
556
+ - Create .claude/rules/architecture.md for detailed structure docs
557
+ - Keep CLAUDE.md to HIGH-LEVEL summaries only (3-5 bullets per section max)
558
+
559
+ ## Sections to fill or preserve (DO NOT remove existing sections)
560
+
561
+ 1. **## Stack** - if missing or incomplete, detect and add language, framework, package manager
562
+ 2. **## Architecture** - 3-5 bullet points describing the codebase shape (not a full directory tree)
563
+ 3. **## Conventions** - max 8 key patterns. Move detailed rules to .claude/rules/conventions.md
564
+ 4. **## Off-Limits** - max 8 guardrails specific to this project
565
+ 5. **## Memory & Learnings** - max 6 bullets. If missing, add instructions for using the built-in memory system: what to save (gotchas, decisions, deferred issues, references), where (project vs global memory), and the rule to check existing memories before creating duplicates
566
+ 6. **## Key Decisions** - only decisions that affect how Claude should work in this codebase
567
+ 7. **MCP server suggestions** - 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. Print as suggestions at the end, not in CLAUDE.md.
568
+
569
+ ## Hook review
570
+
571
+ Also review .claude/settings.json hooks:
572
+ - Read the existing hooks in .claude/settings.json
573
+ - If you see project-specific patterns that deserve hooks (e.g., protected directories, test file patterns, migration files), suggest adding them
574
+ - If no PostCompact hook exists, suggest adding one that re-injects TASKS.md after context compaction
575
+ - If no SessionStart hook exists, suggest adding one that injects TASKS.md at session startup
576
+ - DO NOT overwrite existing hooks - only add new ones specific to this project
577
+ - Print hook suggestions at the end with the exact JSON to add, don't modify settings.json directly
578
+
579
+ ## Advanced configuration opportunities
580
+
581
+ - If the project has both app code and tests, suggest creating path-scoped .claude/rules/ files with paths: frontmatter
582
+ - If the project uses external APIs, suggest sandbox.network.allowedDomains to restrict outbound traffic
583
+ - If you detect a monorepo, suggest claudeMdExcludes in settings.json
584
+
585
+ ## Rules
586
+
587
+ - Don't remove existing content - only add or improve
588
+ - Be specific to THIS project, not generic advice
589
+ - Use bullet points, not paragraphs
590
+ - If a section would exceed 8 bullets, split into a .claude/rules/ file and reference it
591
+ - After editing, count the actionable lines. If over 120, move content to rules files until under
592
+ - Run \`claude-launchpad doctor\` after to verify score improved
593
+ `;
594
+ }
595
+
620
596
  // src/commands/init/index.ts
621
597
  function createInitCommand() {
622
598
  return new Command("init").description("Set up Claude Code configuration for any project").option("-n, --name <name>", "Project name").option("-y, --yes", "Accept all defaults").action(async (opts) => {
@@ -649,14 +625,15 @@ function createInitCommand() {
649
625
  });
650
626
  if (!overwrite) {
651
627
  log.info("Keeping existing CLAUDE.md");
628
+ await createEnhanceSkillPrompt(root, false);
652
629
  log.step("Tip: run `claude-launchpad doctor` to check your existing config");
653
630
  return;
654
631
  }
655
632
  }
656
- await scaffold(root, options, detected);
633
+ await scaffold(root, options, detected, opts.yes);
657
634
  });
658
635
  }
659
- async function scaffold(root, options, detected) {
636
+ async function scaffold(root, options, detected, skipPrompts) {
660
637
  log.step("Generating configuration...");
661
638
  const claudeMd = generateClaudeMd(options, detected);
662
639
  const tasksMd = generateTasksMd(options);
@@ -701,8 +678,10 @@ async function scaffold(root, options, detected) {
701
678
  if (!hasClaudeGitignore) log.success("Generated .claude/.gitignore");
702
679
  if (!hasClaudeignore) log.success("Generated .claudeignore");
703
680
  if (!hasRules) log.success("Generated .claude/rules/conventions.md");
681
+ await createEnhanceSkillPrompt(root, skipPrompts);
704
682
  log.blank();
705
683
  log.success("Done! Run `claude` to start.");
684
+ log.info("Use `/lp-enhance` inside Claude Code to have AI complete your CLAUDE.md.");
706
685
  log.info("Run `claude-launchpad doctor` to check your config quality.");
707
686
  log.blank();
708
687
  }
@@ -734,6 +713,26 @@ function generateStarterRules(detected) {
734
713
  lines.push("");
735
714
  return lines.join("\n");
736
715
  }
716
+ async function createEnhanceSkillPrompt(root, skipPrompts) {
717
+ const projectPath = join2(root, ".claude", "skills", "lp-enhance", "SKILL.md");
718
+ const globalPath = join2(homedir(), ".claude", "skills", "lp-enhance", "SKILL.md");
719
+ const legacyProject = join2(root, ".claude", "commands", "lp-enhance.md");
720
+ const legacyGlobal = join2(homedir(), ".claude", "commands", "lp-enhance.md");
721
+ if (await fileExists(projectPath) || await fileExists(globalPath) || await fileExists(legacyProject) || await fileExists(legacyGlobal)) return;
722
+ const scope = skipPrompts ? "project" : await select({
723
+ message: "Install /lp-enhance skill (AI-powered CLAUDE.md improver):",
724
+ choices: [
725
+ { value: "project", name: "Project scope (.claude/skills/)" },
726
+ { value: "global", name: "Global scope (~/.claude/skills/)" },
727
+ { value: "skip", name: "Skip" }
728
+ ]
729
+ });
730
+ if (scope === "skip") return;
731
+ const targetDir = scope === "global" ? join2(homedir(), ".claude", "skills", "lp-enhance") : join2(root, ".claude", "skills", "lp-enhance");
732
+ await mkdir(targetDir, { recursive: true });
733
+ await writeFile(join2(targetDir, "SKILL.md"), generateEnhanceSkill());
734
+ log.success(`Generated /lp-enhance skill (${scope} scope)`);
735
+ }
737
736
  async function mergeSettings(existingPath, generated) {
738
737
  try {
739
738
  const existing = JSON.parse(await readFile2(existingPath, "utf-8"));
@@ -757,7 +756,7 @@ async function mergeSettings(existingPath, generated) {
757
756
 
758
757
  // src/commands/doctor/index.ts
759
758
  import { Command as Command2 } from "commander";
760
- import chalk2 from "chalk";
759
+ import chalk from "chalk";
761
760
 
762
761
  // src/lib/parser.ts
763
762
  import { readdir, access as access2 } from "fs/promises";
@@ -1027,18 +1026,6 @@ async function analyzeSettings(config) {
1027
1026
  message: "No claudeMdExcludes configured \u2014 consider adding this if you have a monorepo"
1028
1027
  });
1029
1028
  }
1030
- const broadMatchers = ["Bash", "Write", "Edit", "Read"];
1031
- const hooksWithoutTimeout = config.hooks.filter(
1032
- (h) => !h.timeout && broadMatchers.some((m) => h.matcher?.includes(m))
1033
- );
1034
- if (hooksWithoutTimeout.length > 0) {
1035
- issues.push({
1036
- analyzer: "Settings",
1037
- severity: "low",
1038
- message: `${hooksWithoutTimeout.length} hook(s) on broad matchers without timeout \u2014 defaults to 60s per invocation`,
1039
- fix: "Add timeout (in seconds) to hooks on Bash, Write, Edit, or Read matchers"
1040
- });
1041
- }
1042
1029
  if (config.settings.autoMemoryEnabled === false) {
1043
1030
  const hasMemorySection = config.claudeMdContent?.includes("## Memory") ?? false;
1044
1031
  if (!hasMemorySection) {
@@ -1125,6 +1112,7 @@ async function analyzeHooks(config) {
1125
1112
  // src/commands/doctor/analyzers/rules.ts
1126
1113
  import { readFile as readFile3 } from "fs/promises";
1127
1114
  import { basename as basename2, join as join4, dirname } from "path";
1115
+ import { homedir as homedir2 } from "os";
1128
1116
  async function analyzeRules(config) {
1129
1117
  const issues = [];
1130
1118
  const projectRoot = config.claudeMdPath ? dirname(config.claudeMdPath) : process.cwd();
@@ -1137,6 +1125,18 @@ async function analyzeRules(config) {
1137
1125
  fix: "Run `claude-launchpad init` or `doctor --fix` to generate one"
1138
1126
  });
1139
1127
  }
1128
+ const hasSkillInProject = config.skills.some(
1129
+ (s) => basename2(s) === "SKILL.md" && s.includes("lp-enhance") || basename2(s) === "lp-enhance.md"
1130
+ );
1131
+ const hasSkillGlobal = await fileExists(join4(homedir2(), ".claude", "skills", "lp-enhance", "SKILL.md")) || await fileExists(join4(homedir2(), ".claude", "commands", "lp-enhance.md"));
1132
+ if (!hasSkillInProject && !hasSkillGlobal) {
1133
+ issues.push({
1134
+ analyzer: "Rules",
1135
+ severity: "low",
1136
+ message: "No /lp-enhance skill found \u2014 use it inside Claude Code to AI-complete your CLAUDE.md",
1137
+ fix: "Run `claude-launchpad init` or `doctor --fix` to generate the skill"
1138
+ });
1139
+ }
1140
1140
  if (config.rules.length === 0) {
1141
1141
  issues.push({
1142
1142
  analyzer: "Rules",
@@ -1282,7 +1282,7 @@ async function analyzeMcp(config) {
1282
1282
  issues.push({
1283
1283
  analyzer: "MCP",
1284
1284
  severity: "info",
1285
- message: "No MCP servers configured. Run `claude-launchpad enhance` to get stack-specific recommendations."
1285
+ message: "No MCP servers configured. Use `/lp-enhance` in Claude Code to get stack-specific recommendations."
1286
1286
  });
1287
1287
  return { name: "MCP Servers", issues, score: 50 };
1288
1288
  }
@@ -1406,9 +1406,97 @@ async function analyzeQuality(config) {
1406
1406
  return { name: "CLAUDE.md Quality", issues, score };
1407
1407
  }
1408
1408
 
1409
+ // src/commands/doctor/analyzers/memory.ts
1410
+ var MEMORY_MCP_TOOLS = [
1411
+ "mcp__agentic-memory__memory_store",
1412
+ "mcp__agentic-memory__memory_search",
1413
+ "mcp__agentic-memory__memory_recent",
1414
+ "mcp__agentic-memory__memory_forget",
1415
+ "mcp__agentic-memory__memory_relate",
1416
+ "mcp__agentic-memory__memory_stats",
1417
+ "mcp__agentic-memory__memory_update"
1418
+ ];
1419
+ function hasMemoryIndicators(config) {
1420
+ const hasMcpServer = config.mcpServers.some((s) => s.name === "agentic-memory");
1421
+ const hasHookRef = config.hooks.some(
1422
+ (h) => h.command?.includes("memory context") || h.command?.includes("memory extract")
1423
+ );
1424
+ return hasMcpServer || hasHookRef;
1425
+ }
1426
+ async function analyzeMemory(config) {
1427
+ if (!hasMemoryIndicators(config)) return null;
1428
+ const issues = [];
1429
+ const hasMcpServer = config.mcpServers.some((s) => s.name === "agentic-memory");
1430
+ if (!hasMcpServer) {
1431
+ issues.push({
1432
+ analyzer: "Memory",
1433
+ severity: "high",
1434
+ message: "agentic-memory MCP server not found in mcpServers",
1435
+ fix: "Add agentic-memory to mcpServers in .claude/settings.json"
1436
+ });
1437
+ }
1438
+ const hasSessionStart = config.hooks.some(
1439
+ (h) => h.event === "SessionStart" && h.command?.includes("memory context")
1440
+ );
1441
+ if (!hasSessionStart) {
1442
+ issues.push({
1443
+ analyzer: "Memory",
1444
+ severity: "high",
1445
+ message: "No SessionStart hook with memory context injection",
1446
+ fix: "Add a SessionStart hook that runs `memory context` to inject relevant memories"
1447
+ });
1448
+ }
1449
+ const hasStopHook = config.hooks.some(
1450
+ (h) => h.event === "Stop" && h.command?.includes("memory extract")
1451
+ );
1452
+ if (!hasStopHook) {
1453
+ issues.push({
1454
+ analyzer: "Memory",
1455
+ severity: "medium",
1456
+ message: "No Stop hook with memory extract for session learnings",
1457
+ fix: "Add a Stop hook that runs `memory extract` to capture session insights"
1458
+ });
1459
+ }
1460
+ const autoMemoryDisabled = config.settings?.autoMemoryEnabled === false;
1461
+ if (!autoMemoryDisabled) {
1462
+ issues.push({
1463
+ analyzer: "Memory",
1464
+ severity: "medium",
1465
+ message: "autoMemoryEnabled not disabled \u2014 built-in memory may conflict with agentic-memory",
1466
+ fix: "Set autoMemoryEnabled: false in .claude/settings.json"
1467
+ });
1468
+ }
1469
+ const hasMemoryGuidance = config.claudeMdContent?.includes("agentic-memory") || config.claudeMdContent?.includes("## Memory");
1470
+ if (!hasMemoryGuidance) {
1471
+ issues.push({
1472
+ analyzer: "Memory",
1473
+ severity: "low",
1474
+ message: "CLAUDE.md missing memory guidance section",
1475
+ fix: "Add a ## Memory section to CLAUDE.md describing when and how to use agentic-memory"
1476
+ });
1477
+ }
1478
+ const allowList = config.settings?.allowedTools ?? [];
1479
+ const missingTools = MEMORY_MCP_TOOLS.filter((t) => !allowList.includes(t));
1480
+ if (missingTools.length > 0) {
1481
+ issues.push({
1482
+ analyzer: "Memory",
1483
+ severity: "low",
1484
+ message: `${missingTools.length} agentic-memory MCP tool permission(s) missing from allowedTools`,
1485
+ fix: "Add all agentic-memory tool names to allowedTools in .claude/settings.json"
1486
+ });
1487
+ }
1488
+ const critical = issues.filter((i) => i.severity === "critical").length;
1489
+ const high = issues.filter((i) => i.severity === "high").length;
1490
+ const medium = issues.filter((i) => i.severity === "medium").length;
1491
+ const low = issues.filter((i) => i.severity === "low").length;
1492
+ const score = Math.max(0, 100 - (critical * 40 + high * 20 + medium * 10 + low * 5));
1493
+ return { name: "Memory", issues, score };
1494
+ }
1495
+
1409
1496
  // src/commands/doctor/fixer.ts
1410
1497
  import { readFile as readFile4, writeFile as writeFile2, mkdir as mkdir2, access as access4 } from "fs/promises";
1411
1498
  import { join as join5 } from "path";
1499
+ import { homedir as homedir3 } from "os";
1412
1500
  async function applyFixes(issues, projectRoot) {
1413
1501
  const detected = await detectProject(projectRoot);
1414
1502
  let fixed = 0;
@@ -1433,7 +1521,7 @@ var FIX_TABLE = [
1433
1521
  { analyzer: "Hooks", match: ".env file protection", fix: (root) => addEnvProtectionHook(root) },
1434
1522
  { analyzer: "Hooks", match: "auto-format", fix: (root, detected) => addAutoFormatHook(root, detected) },
1435
1523
  { analyzer: "Hooks", match: "No PreToolUse", fix: (root) => addEnvProtectionHook(root) },
1436
- { analyzer: "Quality", match: "Architecture", fix: (root) => addClaudeMdSection(root, "## Architecture", "<!-- TODO: Describe your codebase structure. Run `claude-launchpad enhance` to auto-fill this. -->") },
1524
+ { analyzer: "Quality", match: "Architecture", fix: (root) => addClaudeMdSection(root, "## Architecture", "<!-- TODO: Describe your codebase structure. Run `/lp-enhance` to auto-fill this. -->") },
1437
1525
  { 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") },
1438
1526
  { analyzer: "Quality", match: "Commands", fix: (root) => addClaudeMdSection(root, "## Commands", "<!-- TODO: Add your dev/build/test commands -->") },
1439
1527
  { analyzer: "Quality", match: "Stack", fix: (root, detected) => {
@@ -1452,8 +1540,12 @@ var FIX_TABLE = [
1452
1540
  { analyzer: "Permissions", match: "Bypass permissions mode", fix: (root) => addBypassDisable(root) },
1453
1541
  { analyzer: "Permissions", match: "Sandbox not enabled", fix: (root) => addSandboxSettings(root) },
1454
1542
  { analyzer: "Permissions", match: ".env is protected by hooks but not in .claudeignore", fix: (root) => addEnvToClaudeignore(root) },
1543
+ { analyzer: "Rules", match: "No /lp-enhance skill", fix: (root) => createEnhanceSkill(root) },
1455
1544
  { analyzer: "Settings", match: "Deprecated includeCoAuthoredBy", fix: (root) => migrateAttribution(root) },
1456
- { analyzer: "Hooks", match: "SessionStart", fix: (root) => addSessionStartHook(root) }
1545
+ { analyzer: "Hooks", match: "SessionStart", fix: (root) => addSessionStartHook(root) },
1546
+ { analyzer: "Memory", match: "autoMemoryEnabled not disabled", fix: (root) => disableAutoMemory(root) },
1547
+ { analyzer: "Memory", match: "MCP tool permission", fix: (root) => addMemoryToolPermissions(root) },
1548
+ { analyzer: "Memory", match: "CLAUDE.md missing memory guidance", fix: (root) => addClaudeMdSection(root, "## Memory", "Use agentic-memory to persist knowledge across sessions:\n- Memories are automatically injected at session start and extracted at session end\n- Save non-obvious decisions, gotchas, and deferred issues\n- Check memory for relevant context before starting work") }
1457
1549
  ];
1458
1550
  async function tryFix(issue, root, detected) {
1459
1551
  const entry = FIX_TABLE.find(
@@ -1678,19 +1770,45 @@ async function createStarterRules(root) {
1678
1770
  log.success("Created .claude/rules/conventions.md with starter rules");
1679
1771
  return true;
1680
1772
  }
1681
- async function readSettingsJson(root) {
1682
- const path = join5(root, ".claude", "settings.json");
1683
- try {
1684
- const content = await readFile4(path, "utf-8");
1685
- return JSON.parse(content);
1686
- } catch {
1687
- return {};
1688
- }
1773
+ async function disableAutoMemory(root) {
1774
+ const settings = await readSettingsJson(root);
1775
+ if (settings.autoMemoryEnabled === false) return false;
1776
+ settings.autoMemoryEnabled = false;
1777
+ await writeSettingsJson(root, settings);
1778
+ log.success("Set autoMemoryEnabled: false (prevents conflict with agentic-memory)");
1779
+ return true;
1780
+ }
1781
+ async function addMemoryToolPermissions(root) {
1782
+ const settings = await readSettingsJson(root);
1783
+ const permissions = settings.permissions ?? {};
1784
+ const allow = permissions.allow ?? [];
1785
+ const tools = [
1786
+ "mcp__agentic-memory__memory_store",
1787
+ "mcp__agentic-memory__memory_search",
1788
+ "mcp__agentic-memory__memory_recent",
1789
+ "mcp__agentic-memory__memory_forget",
1790
+ "mcp__agentic-memory__memory_relate",
1791
+ "mcp__agentic-memory__memory_stats",
1792
+ "mcp__agentic-memory__memory_update"
1793
+ ];
1794
+ const missing = tools.filter((t) => !allow.includes(t));
1795
+ if (missing.length === 0) return false;
1796
+ settings.permissions = { ...permissions, allow: [...allow, ...missing] };
1797
+ await writeSettingsJson(root, settings);
1798
+ log.success("Added agentic-memory MCP tool permissions to allowedTools");
1799
+ return true;
1689
1800
  }
1690
- async function writeSettingsJson(root, settings) {
1691
- const dir = join5(root, ".claude");
1692
- await mkdir2(dir, { recursive: true });
1693
- await writeFile2(join5(dir, "settings.json"), JSON.stringify(settings, null, 2) + "\n");
1801
+ async function createEnhanceSkill(root) {
1802
+ const skillDir = join5(root, ".claude", "skills", "lp-enhance");
1803
+ const skillPath = join5(skillDir, "SKILL.md");
1804
+ const globalPath = join5(homedir3(), ".claude", "skills", "lp-enhance", "SKILL.md");
1805
+ const legacyProject = join5(root, ".claude", "commands", "lp-enhance.md");
1806
+ const legacyGlobal = join5(homedir3(), ".claude", "commands", "lp-enhance.md");
1807
+ if (await fileExists(skillPath) || await fileExists(globalPath) || await fileExists(legacyProject) || await fileExists(legacyGlobal)) return false;
1808
+ await mkdir2(skillDir, { recursive: true });
1809
+ await writeFile2(skillPath, generateEnhanceSkill());
1810
+ log.success("Generated /lp-enhance skill (.claude/skills/lp-enhance/)");
1811
+ return true;
1694
1812
  }
1695
1813
 
1696
1814
  // src/commands/doctor/watcher.ts
@@ -1745,7 +1863,7 @@ async function getFileSnapshot(projectRoot) {
1745
1863
  }
1746
1864
  async function runAndDisplay(projectRoot) {
1747
1865
  console.log("\x1B[36m\x1B[1m Claude Launchpad\x1B[0m");
1748
- console.log("\x1B[2m Scaffold \xB7 Diagnose \xB7 Evaluate\x1B[0m");
1866
+ console.log("\x1B[2m Scaffold \xB7 Diagnose \xB7 Evaluate \xB7 Remember\x1B[0m");
1749
1867
  log.blank();
1750
1868
  const config = await parseClaudeConfig(projectRoot);
1751
1869
  if (config.claudeMdContent === null && config.settings === null) {
@@ -1791,6 +1909,10 @@ function createDoctorCommand() {
1791
1909
  analyzePermissions(config),
1792
1910
  analyzeMcp(config)
1793
1911
  ]);
1912
+ const memoryResult = await analyzeMemory(config);
1913
+ if (memoryResult) {
1914
+ results.push(memoryResult);
1915
+ }
1794
1916
  if (opts.json) {
1795
1917
  const overallScore2 = Math.round(
1796
1918
  results.reduce((sum, r) => sum + r.score, 0) / results.length
@@ -1805,7 +1927,10 @@ function createDoctorCommand() {
1805
1927
  if (opts.fix) {
1806
1928
  const allIssues = results.flatMap((r) => r.issues);
1807
1929
  const fixable = allIssues.filter((i) => i.severity !== "info");
1808
- if (fixable.length > 0) {
1930
+ if (fixable.length === 0) {
1931
+ renderDoctorReport(results);
1932
+ log.success("Nothing to fix.");
1933
+ } else if (fixable.length > 0) {
1809
1934
  if (opts.dryRun) {
1810
1935
  const withFix = fixable.filter((i) => i.fix);
1811
1936
  log.blank();
@@ -1820,7 +1945,7 @@ function createDoctorCommand() {
1820
1945
  if (skipped > 0) {
1821
1946
  log.info(`${skipped} issue(s) require manual intervention.`);
1822
1947
  }
1823
- log.info(`Then run ${chalk2.bold("claude-launchpad enhance")} to have Claude restructure and complete your CLAUDE.md.`);
1948
+ log.info(`Then use ${chalk.bold("/lp-enhance")} inside Claude Code to have Claude restructure and complete your CLAUDE.md.`);
1824
1949
  return;
1825
1950
  }
1826
1951
  log.blank();
@@ -1841,8 +1966,12 @@ function createDoctorCommand() {
1841
1966
  analyzePermissions(updatedConfig),
1842
1967
  analyzeMcp(updatedConfig)
1843
1968
  ]);
1969
+ const updatedMemoryResult = await analyzeMemory(updatedConfig);
1970
+ if (updatedMemoryResult) {
1971
+ updatedResults.push(updatedMemoryResult);
1972
+ }
1844
1973
  renderDoctorReport(updatedResults, { afterFix: true });
1845
- log.info(`Then run ${chalk2.bold("claude-launchpad enhance")} to have Claude restructure and complete your CLAUDE.md.`);
1974
+ log.info(`Then use ${chalk.bold("/lp-enhance")} inside Claude Code to have Claude restructure and complete your CLAUDE.md.`);
1846
1975
  }
1847
1976
  }
1848
1977
  }
@@ -1857,9 +1986,9 @@ function createDoctorCommand() {
1857
1986
 
1858
1987
  // src/commands/eval/index.ts
1859
1988
  import { Command as Command3 } from "commander";
1860
- import { select } from "@inquirer/prompts";
1989
+ import { select as select2 } from "@inquirer/prompts";
1861
1990
  import ora from "ora";
1862
- import chalk3 from "chalk";
1991
+ import chalk2 from "chalk";
1863
1992
  import { mkdir as mkdir4, writeFile as writeFile4 } from "fs/promises";
1864
1993
  import { join as join9 } from "path";
1865
1994
 
@@ -2267,18 +2396,18 @@ async function listAllFiles(dir) {
2267
2396
  function createEvalCommand() {
2268
2397
  return new Command3("eval").description("Test your Claude Code config against eval scenarios").option("-s, --suite <suite>", "Eval suite to run (e.g., security, conventions, workflow)").option("-p, --path <path>", "Project root path", process.cwd()).option("--scenarios <path>", "Custom scenarios directory").option("--runs <n>", "Runs per scenario (default: 3)", "3").option("--timeout <ms>", "Timeout per run in ms (default: 120000)", "120000").option("--json", "Output as JSON").option("--debug", "Keep sandbox directories for inspection").option("--model <model>", "Model to use for eval (e.g., sonnet, haiku, opus)").action(async (opts) => {
2269
2398
  printBanner();
2270
- const hasFlags = opts.suite || opts.model || opts.runs !== "3" || opts.json || opts.debug;
2399
+ const hasFlags = opts.suite || opts.model || opts.runs !== "3" || opts.timeout !== "120000" || opts.path !== process.cwd() || Boolean(opts.scenarios) || opts.json || opts.debug;
2271
2400
  if (!hasFlags) {
2272
- opts.suite = await select({
2401
+ opts.suite = await select2({
2273
2402
  message: "Suite",
2274
2403
  choices: [
2275
2404
  { name: "security (6 scenarios)", value: "security" },
2276
2405
  { name: "conventions (5 scenarios)", value: "conventions" },
2277
- { name: "workflow (2 scenarios)", value: "workflow" },
2278
- { name: "all (13 scenarios)", value: void 0 }
2406
+ { name: "workflow (4 scenarios)", value: "workflow" },
2407
+ { name: "all (15 scenarios)", value: void 0 }
2279
2408
  ]
2280
2409
  });
2281
- opts.runs = await select({
2410
+ opts.runs = await select2({
2282
2411
  message: "Runs per scenario",
2283
2412
  choices: [
2284
2413
  { name: "1 \u2014 fast", value: "1" },
@@ -2286,7 +2415,7 @@ function createEvalCommand() {
2286
2415
  { name: "5 \u2014 thorough", value: "5" }
2287
2416
  ]
2288
2417
  });
2289
- opts.model = await select({
2418
+ opts.model = await select2({
2290
2419
  message: "Model",
2291
2420
  choices: [
2292
2421
  { name: "haiku \u2014 cheapest", value: "haiku" },
@@ -2370,13 +2499,13 @@ function createEvalCommand() {
2370
2499
  }
2371
2500
  function renderEvalReport(results) {
2372
2501
  for (const result of results) {
2373
- const icon = result.passed ? chalk3.green("\u2713") : chalk3.red("\u2717");
2374
- const status = result.passed ? chalk3.green("PASS") : chalk3.red("FAIL");
2502
+ const icon = result.passed ? chalk2.green("\u2713") : chalk2.red("\u2717");
2503
+ const status = result.passed ? chalk2.green("PASS") : chalk2.red("FAIL");
2375
2504
  const score = `${result.score}/${result.maxScore}`;
2376
- console.log(` ${icon} ${chalk3.bold(result.scenario)} ${score} ${status}`);
2505
+ console.log(` ${icon} ${chalk2.bold(result.scenario)} ${score} ${status}`);
2377
2506
  const failedChecks = result.checks.filter((c) => !c.passed);
2378
2507
  for (const check of failedChecks) {
2379
- console.log(` ${chalk3.red("\u2717")} ${chalk3.dim(check.label)}`);
2508
+ console.log(` ${chalk2.red("\u2717")} ${chalk2.dim(check.label)}`);
2380
2509
  }
2381
2510
  }
2382
2511
  log.blank();
@@ -2444,9 +2573,9 @@ async function saveEvalReport(results, projectRoot, suite, model) {
2444
2573
  log.success(`Report saved to .claude/eval/${filename}`);
2445
2574
  }
2446
2575
  async function checkClaudeCli() {
2447
- const { execFile: execFile3 } = await import("child_process");
2448
- const { promisify: promisify3 } = await import("util");
2449
- const exec2 = promisify3(execFile3);
2576
+ const { execFile: execFile2 } = await import("child_process");
2577
+ const { promisify: promisify2 } = await import("util");
2578
+ const exec2 = promisify2(execFile2);
2450
2579
  try {
2451
2580
  await exec2("claude", ["--version"]);
2452
2581
  return true;
@@ -2455,82 +2584,88 @@ async function checkClaudeCli() {
2455
2584
  }
2456
2585
  }
2457
2586
 
2458
- // src/commands/enhance/index.ts
2459
- import { Command as Command4 } from "commander";
2460
- import { spawn, execFile as execFile2 } from "child_process";
2461
- import { promisify as promisify2 } from "util";
2462
- import { access as access7 } from "fs/promises";
2587
+ // src/commands/memory/index.ts
2588
+ import { readFileSync } from "fs";
2463
2589
  import { join as join10 } from "path";
2464
- var execAsync = promisify2(execFile2);
2465
- var ENHANCE_PROMPT = `Read CLAUDE.md and the project's codebase, then update CLAUDE.md to fill in missing or incomplete sections.
2466
-
2467
- CRITICAL BUDGET RULE: CLAUDE.md must stay UNDER 120 lines of actionable content (not counting headings, blank lines, or comments). Claude Code starts ignoring rules past ~150 instructions. If you need more detail, create .claude/rules/ files instead:
2468
- - Create .claude/rules/conventions.md for detailed coding patterns
2469
- - Create .claude/rules/architecture.md for detailed structure docs
2470
- - Keep CLAUDE.md to HIGH-LEVEL summaries only (3-5 bullets per section max)
2471
-
2472
- Sections to fill in or preserve (DO NOT remove any existing section):
2473
- 1. **## Stack** \u2014 if missing or incomplete, detect and add language, framework, package manager
2474
- 2. **## Architecture** \u2014 3-5 bullet points describing the codebase shape (not a full directory tree)
2475
- 3. **## Conventions** \u2014 max 8 key patterns. Move detailed rules to .claude/rules/conventions.md
2476
- 4. **## Off-Limits** \u2014 max 8 guardrails specific to this project
2477
- 5. **## Memory & Learnings** \u2014 max 6 bullets. If missing, add instructions for using the built-in memory system: what to save (gotchas, decisions, deferred issues, references), where (project vs global memory), and the rule to check existing memories before creating duplicates
2478
- 6. **## Key Decisions** \u2014 only decisions that affect how Claude should work in this codebase
2479
- 7. **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.
2480
-
2481
- Also review .claude/settings.json hooks:
2482
- - Read the existing hooks in .claude/settings.json
2483
- - If you see project-specific patterns that deserve hooks (e.g., protected directories, test file patterns, migration files), suggest adding them
2484
- - If no PostCompact hook exists, suggest adding one that re-injects TASKS.md after context compaction (critical for session continuity)
2485
- - If no SessionStart hook exists, suggest adding one that injects TASKS.md at session startup
2486
- - DO NOT overwrite existing hooks \u2014 only add new ones that are specific to this project
2487
- - Print hook suggestions at the end with the exact JSON to add, don't modify settings.json directly
2488
-
2489
- Also check for advanced configuration opportunities:
2490
- - If the project has both app code and tests, suggest creating path-scoped .claude/rules/ files with paths: frontmatter (e.g., test conventions only load when editing test files)
2491
- - If the project uses external APIs (Stripe, GitHub, AWS SDKs, etc.), suggest sandbox.network.allowedDomains to restrict outbound traffic
2492
- - If you detect a monorepo (Turborepo, Lerna, pnpm workspaces, multiple package.json), suggest claudeMdExcludes in settings.json
2493
-
2494
- Rules:
2495
- - Don't remove existing content \u2014 only add or improve
2496
- - Be specific to THIS project, not generic advice
2497
- - Use bullet points, not paragraphs
2498
- - If a section would exceed 8 bullets, split into a .claude/rules/ file and reference it
2499
- - After editing, count the actionable lines. If over 120, move content to rules files until under`;
2500
- function createEnhanceCommand() {
2501
- 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) => {
2502
- printBanner();
2503
- const root = opts.path;
2504
- const claudeMdPath = join10(root, "CLAUDE.md");
2505
- try {
2506
- await access7(claudeMdPath);
2507
- } catch {
2508
- log.error("No CLAUDE.md found. Run `claude-launchpad init` first.");
2509
- process.exit(1);
2590
+ import { Command as Command4 } from "commander";
2591
+ import { confirm as confirm2 } from "@inquirer/prompts";
2592
+ function isMemoryInstalled() {
2593
+ try {
2594
+ const settingsPath = join10(process.cwd(), ".claude", "settings.json");
2595
+ const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
2596
+ const hooks = settings.hooks;
2597
+ if (!hooks) return false;
2598
+ const sessionStart = hooks.SessionStart;
2599
+ return sessionStart?.some((h) => {
2600
+ const inner = h.hooks;
2601
+ return inner?.some((ih) => String(ih.command ?? "").includes("memory context"));
2602
+ }) ?? false;
2603
+ } catch {
2604
+ return false;
2605
+ }
2606
+ }
2607
+ function createMemoryCommand() {
2608
+ const memory = new Command4("memory").description("Persistent memory system for Claude Code sessions").option("--dashboard", "Open the memory dashboard").action(async (opts) => {
2609
+ if (opts.dashboard) {
2610
+ if (!isMemoryInstalled()) {
2611
+ log.error("Memory system is not installed. Run `claude-launchpad memory` first.");
2612
+ return;
2613
+ }
2614
+ const { startTui } = await import("./tui-R25NTQ4K.js");
2615
+ await startTui();
2616
+ return;
2510
2617
  }
2511
- try {
2512
- await execAsync("claude", ["--version"]);
2513
- } catch {
2514
- log.error("Claude CLI not found. Install it: https://docs.anthropic.com/en/docs/claude-code");
2515
- process.exit(1);
2618
+ if (!isMemoryInstalled()) {
2619
+ log.blank();
2620
+ log.step("Agentic memory is not set up for this project.");
2621
+ log.blank();
2622
+ log.info("This will (skipping what's already in place):");
2623
+ log.info(" - Set up SQLite database at ~/.agentic-memory/");
2624
+ log.info(" - Add SessionStart + Stop hooks to .claude/settings.json");
2625
+ log.info(" - Register the MCP server with Claude Code (global)");
2626
+ log.info(" - Add memory guidance to CLAUDE.md");
2627
+ log.blank();
2628
+ const proceed = await confirm2({
2629
+ message: "Install agentic-memory?",
2630
+ default: true
2631
+ });
2632
+ if (!proceed) {
2633
+ log.info("Skipped.");
2634
+ return;
2635
+ }
2636
+ const { runInstall } = await import("./install-65P6LMUN.js");
2637
+ await runInstall({});
2638
+ } else {
2639
+ const { runStats } = await import("./stats-FYAK7KZW.js");
2640
+ await runStats({});
2516
2641
  }
2517
- log.step("Launching Claude to enhance your CLAUDE.md...");
2518
- log.blank();
2519
- const child = spawn(
2520
- "claude",
2521
- [ENHANCE_PROMPT],
2522
- { cwd: root, stdio: "inherit" }
2523
- );
2524
- await new Promise((resolve3) => {
2525
- child.on("close", (code) => resolve3(code ?? 0));
2526
- });
2527
- log.blank();
2528
- log.success("Run `claude-launchpad doctor` to check your updated score.");
2529
2642
  });
2643
+ memory.addCommand(
2644
+ new Command4("context").description("Load session context (hook handler)").option("--json", "JSON output").action(async (opts) => {
2645
+ const { runContext } = await import("./context-LNUZ4GCF.js");
2646
+ await runContext(opts);
2647
+ }).helpCommand(false),
2648
+ { hidden: true }
2649
+ );
2650
+ memory.addCommand(
2651
+ new Command4("extract").description("Extract facts from transcript (hook handler)").action(async () => {
2652
+ const { runExtract } = await import("./extract-NVAXO5CK.js");
2653
+ await runExtract();
2654
+ }).helpCommand(false),
2655
+ { hidden: true }
2656
+ );
2657
+ memory.addCommand(
2658
+ new Command4("serve").description("Start MCP server (Claude Code)").action(async () => {
2659
+ const { startServer } = await import("./commands/memory/server.js");
2660
+ await startServer();
2661
+ }).helpCommand(false),
2662
+ { hidden: true }
2663
+ );
2664
+ return memory;
2530
2665
  }
2531
2666
 
2532
2667
  // src/cli.ts
2533
- 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 () => {
2668
+ var program = new Command5().name("claude-launchpad").description("CLI toolkit that makes Claude Code setups measurably good").version("0.7.1", "-v, --version").action(async () => {
2534
2669
  const hasConfig = await fileExists(join11(process.cwd(), "CLAUDE.md")) || await fileExists(join11(process.cwd(), ".claude", "settings.json"));
2535
2670
  if (hasConfig) {
2536
2671
  await program.commands.find((c) => c.name() === "doctor")?.parseAsync([], { from: "user" });
@@ -2540,13 +2675,14 @@ var program = new Command5().name("claude-launchpad").description("CLI toolkit t
2540
2675
  log.blank();
2541
2676
  log.step("Run `claude-launchpad init` to set up your project");
2542
2677
  log.step("Run `claude-launchpad doctor` to diagnose an existing config");
2678
+ log.step("Use `/lp-enhance` skill inside Claude Code to AI-complete your CLAUDE.md");
2543
2679
  log.step("Run `claude-launchpad eval` to test your config quality");
2544
2680
  log.blank();
2545
2681
  }
2546
2682
  });
2547
2683
  program.addCommand(createInitCommand());
2548
2684
  program.addCommand(createDoctorCommand());
2549
- program.addCommand(createEnhanceCommand());
2550
2685
  program.addCommand(createEvalCommand());
2686
+ program.addCommand(createMemoryCommand());
2551
2687
  program.parse();
2552
2688
  //# sourceMappingURL=cli.js.map