claude-launchpad 0.16.0 → 0.16.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.
Files changed (30) hide show
  1. package/dist/{chunk-Z6FBT44W.js → chunk-IZFQTQ4C.js} +2 -2
  2. package/dist/{chunk-EDKY7JWY.js → chunk-NLZGJHE5.js} +3 -3
  3. package/dist/chunk-S4WIADYE.js +1139 -0
  4. package/dist/chunk-S4WIADYE.js.map +1 -0
  5. package/dist/{chunk-5MWCQLNL.js → chunk-SVWFSAYP.js} +2 -2
  6. package/dist/cli.js +118 -1060
  7. package/dist/cli.js.map +1 -1
  8. package/dist/commands/memory/server.js +3 -3
  9. package/dist/{context-CWJUUTTU.js → context-53PKQOMI.js} +5 -5
  10. package/dist/{install-MVATZUXZ.js → install-PFTFTNIF.js} +11 -13
  11. package/dist/{install-MVATZUXZ.js.map → install-PFTFTNIF.js.map} +1 -1
  12. package/dist/{pull-YOESZ3UC.js → pull-4QTS57DQ.js} +6 -6
  13. package/dist/{push-74XC5CUK.js → push-JOCEW3VG.js} +6 -6
  14. package/dist/{require-deps-NKRCPVAO.js → require-deps-SUGLVBM2.js} +3 -3
  15. package/dist/{stats-QUBHHPV7.js → stats-7WFCVXBX.js} +6 -6
  16. package/dist/{tui-XIYOOUP6.js → tui-DKWRY5PT.js} +4 -4
  17. package/package.json +1 -1
  18. package/dist/chunk-RJGXPH7P.js +0 -107
  19. package/dist/chunk-RJGXPH7P.js.map +0 -1
  20. package/dist/chunk-SBA5KYQU.js +0 -76
  21. package/dist/chunk-SBA5KYQU.js.map +0 -1
  22. /package/dist/{chunk-Z6FBT44W.js.map → chunk-IZFQTQ4C.js.map} +0 -0
  23. /package/dist/{chunk-EDKY7JWY.js.map → chunk-NLZGJHE5.js.map} +0 -0
  24. /package/dist/{chunk-5MWCQLNL.js.map → chunk-SVWFSAYP.js.map} +0 -0
  25. /package/dist/{context-CWJUUTTU.js.map → context-53PKQOMI.js.map} +0 -0
  26. /package/dist/{pull-YOESZ3UC.js.map → pull-4QTS57DQ.js.map} +0 -0
  27. /package/dist/{push-74XC5CUK.js.map → push-JOCEW3VG.js.map} +0 -0
  28. /package/dist/{require-deps-NKRCPVAO.js.map → require-deps-SUGLVBM2.js.map} +0 -0
  29. /package/dist/{stats-QUBHHPV7.js.map → stats-7WFCVXBX.js.map} +0 -0
  30. /package/dist/{tui-XIYOOUP6.js.map → tui-DKWRY5PT.js.map} +0 -0
package/dist/cli.js CHANGED
@@ -1,234 +1,31 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- getMemoryPlacement,
4
- readSettingsJson,
5
- readSettingsLocalJson,
6
- writeSettingsJson,
7
- writeSettingsLocalJson
8
- } from "./chunk-SBA5KYQU.js";
9
2
  import {
10
3
  readSyncConfig
11
4
  } from "./chunk-JQDMBE7W.js";
12
5
  import {
6
+ ENHANCE_SKILL_VERSION,
7
+ applyFixes,
8
+ detectProject,
9
+ fileExists,
10
+ generateClaudeignore,
11
+ generateEnhanceSkill,
13
12
  log,
14
13
  printBanner,
15
14
  printScoreCard,
15
+ readFileOrNull,
16
16
  renderDoctorReport
17
- } from "./chunk-RJGXPH7P.js";
17
+ } from "./chunk-S4WIADYE.js";
18
18
 
19
19
  // src/cli.ts
20
20
  import { Command as Command5 } from "commander";
21
- import { join as join12 } from "path";
21
+ import { join as join9 } from "path";
22
22
 
23
23
  // src/commands/init/index.ts
24
24
  import { Command } from "commander";
25
25
  import { input, confirm, select } from "@inquirer/prompts";
26
- import { writeFile, mkdir, readFile as readFile2 } from "fs/promises";
26
+ import { writeFile, mkdir, readFile } from "fs/promises";
27
27
  import { homedir } from "os";
28
- import { join as join2 } from "path";
29
-
30
- // src/lib/fs-utils.ts
31
- import { readFile, access } from "fs/promises";
32
- async function fileExists(path) {
33
- try {
34
- await access(path);
35
- return true;
36
- } catch {
37
- return false;
38
- }
39
- }
40
- async function readFileOrNull(path) {
41
- try {
42
- return await readFile(path, "utf-8");
43
- } catch {
44
- return null;
45
- }
46
- }
47
- async function readJsonOrNull(path) {
48
- try {
49
- const content = await readFile(path, "utf-8");
50
- return JSON.parse(content);
51
- } catch {
52
- return null;
53
- }
54
- }
55
-
56
- // src/lib/detect.ts
57
- import { join, basename } from "path";
58
- async function detectProject(root) {
59
- const name = basename(root);
60
- const [pkgJson, goMod, pyProject, gemfile, cargo, pubspec, composerJson, pomXml, buildGradleGroovy, buildGradleKts, packageSwift, mixExs, csproj, lockfiles] = await Promise.all([
61
- readJsonOrNull(join(root, "package.json")),
62
- fileExists(join(root, "go.mod")),
63
- readFileOrNull(join(root, "pyproject.toml")),
64
- fileExists(join(root, "Gemfile")),
65
- fileExists(join(root, "Cargo.toml")),
66
- fileExists(join(root, "pubspec.yaml")),
67
- readJsonOrNull(join(root, "composer.json")),
68
- fileExists(join(root, "pom.xml")),
69
- fileExists(join(root, "build.gradle")),
70
- fileExists(join(root, "build.gradle.kts")),
71
- fileExists(join(root, "Package.swift")),
72
- fileExists(join(root, "mix.exs")),
73
- globExists(root, "*.csproj"),
74
- detectLockfiles(root)
75
- ]);
76
- const buildGradle = buildGradleGroovy || buildGradleKts;
77
- const manifests = {
78
- pkgJson,
79
- goMod,
80
- pyProject,
81
- gemfile,
82
- cargo,
83
- pubspec,
84
- composerJson,
85
- pomXml,
86
- buildGradle,
87
- packageSwift,
88
- mixExs,
89
- csproj
90
- };
91
- const language = detectLanguage(manifests);
92
- const framework = detectFramework(manifests);
93
- const packageManager = detectPackageManager(manifests, lockfiles);
94
- const scripts = detectScripts({ pkgJson, pyProject, goMod, gemfile, composerJson, language });
95
- return {
96
- name,
97
- language,
98
- framework,
99
- packageManager,
100
- hasTests: scripts.testCommand !== null,
101
- hasLinter: scripts.lintCommand !== null,
102
- hasFormatter: scripts.formatCommand !== null,
103
- ...scripts
104
- };
105
- }
106
- function detectLanguage(m) {
107
- if (m.pkgJson?.devDependencies?.typescript || m.pkgJson?.dependencies?.typescript) return "TypeScript";
108
- if (m.pkgJson) return "JavaScript";
109
- if (m.goMod) return "Go";
110
- if (m.pyProject) return "Python";
111
- if (m.gemfile) return "Ruby";
112
- if (m.cargo) return "Rust";
113
- if (m.pubspec) return "Dart";
114
- if (m.composerJson) return "PHP";
115
- if (m.buildGradle) return "Kotlin";
116
- if (m.pomXml) return "Java";
117
- if (m.packageSwift) return "Swift";
118
- if (m.mixExs) return "Elixir";
119
- if (m.csproj) return "C#";
120
- return null;
121
- }
122
- function detectFramework(m) {
123
- const deps = { ...m.pkgJson?.dependencies, ...m.pkgJson?.devDependencies };
124
- if (deps.next) return "Next.js";
125
- if (deps.nuxt) return "Nuxt";
126
- if (deps.svelte || deps["@sveltejs/kit"]) return "SvelteKit";
127
- if (deps.astro) return "Astro";
128
- if (deps["@angular/core"]) return "Angular";
129
- if (deps.remix || deps["@remix-run/react"]) return "Remix";
130
- if (deps.vue) return "Vue";
131
- if (deps.react && !deps.next) return "React";
132
- if (deps.express) return "Express";
133
- if (deps.fastify) return "Fastify";
134
- if (deps.hono) return "Hono";
135
- if (deps.nestjs || deps["@nestjs/core"]) return "NestJS";
136
- if (m.pyProject) {
137
- if (m.pyProject.includes("fastapi")) return "FastAPI";
138
- if (m.pyProject.includes("django")) return "Django";
139
- if (m.pyProject.includes("flask")) return "Flask";
140
- }
141
- if (m.composerJson) {
142
- const phpDeps = { ...m.composerJson.require, ...m.composerJson["require-dev"] };
143
- if (phpDeps["laravel/framework"]) return "Laravel";
144
- if (phpDeps["symfony/framework-bundle"]) return "Symfony";
145
- }
146
- if (m.gemfile) return "Rails";
147
- if (m.buildGradle) return "Gradle";
148
- if (m.pomXml) return "Maven";
149
- return null;
150
- }
151
- async function detectLockfiles(root) {
152
- const [pnpmLock, yarnLock, bunLock, npmLock] = await Promise.all([
153
- fileExists(join(root, "pnpm-lock.yaml")),
154
- fileExists(join(root, "yarn.lock")),
155
- fileExists(join(root, "bun.lockb")),
156
- fileExists(join(root, "package-lock.json"))
157
- ]);
158
- return { pnpmLock, yarnLock, bunLock, npmLock };
159
- }
160
- function detectPackageManager(m, lockfiles) {
161
- if (m.pkgJson) {
162
- const pm = m.pkgJson.packageManager;
163
- if (pm?.startsWith("pnpm")) return "pnpm";
164
- if (pm?.startsWith("yarn")) return "yarn";
165
- if (pm?.startsWith("bun")) return "bun";
166
- if (pm?.startsWith("npm")) return "npm";
167
- if (lockfiles.pnpmLock) return "pnpm";
168
- if (lockfiles.yarnLock) return "yarn";
169
- if (lockfiles.bunLock) return "bun";
170
- if (lockfiles.npmLock) return "npm";
171
- return "npm";
172
- }
173
- if (m.goMod) return "go modules";
174
- if (m.pyProject) {
175
- if (m.pyProject.includes("[tool.uv]")) return "uv";
176
- if (m.pyProject.includes("[tool.poetry]")) return "poetry";
177
- return "pip";
178
- }
179
- if (m.gemfile) return "bundler";
180
- if (m.cargo) return "cargo";
181
- if (m.composerJson) return "composer";
182
- return null;
183
- }
184
- var LANGUAGE_SCRIPTS = {
185
- Go: { devCommand: "go run .", buildCommand: "go build .", testCommand: "go test ./...", lintCommand: "golangci-lint run", formatCommand: "gofmt -w ." },
186
- Ruby: { devCommand: "bin/dev", buildCommand: null, testCommand: "bin/rails test", lintCommand: "bin/rubocop", formatCommand: null },
187
- PHP: { devCommand: "php artisan serve", buildCommand: null, testCommand: "php artisan test", lintCommand: "vendor/bin/phpstan analyse", formatCommand: "vendor/bin/pint" },
188
- Rust: { devCommand: "cargo run", buildCommand: "cargo build", testCommand: "cargo test", lintCommand: "cargo clippy", formatCommand: "cargo fmt" },
189
- Java: { devCommand: null, buildCommand: "mvn package", testCommand: "mvn test", lintCommand: null, formatCommand: null },
190
- Kotlin: { devCommand: null, buildCommand: "mvn package", testCommand: "mvn test", lintCommand: null, formatCommand: null },
191
- Swift: { devCommand: null, buildCommand: "swift build", testCommand: "swift test", lintCommand: "swiftlint", formatCommand: "swift-format format -r ." },
192
- Elixir: { devCommand: "mix phx.server", buildCommand: "mix compile", testCommand: "mix test", lintCommand: "mix credo", formatCommand: "mix format" },
193
- "C#": { devCommand: "dotnet run", buildCommand: "dotnet build", testCommand: "dotnet test", lintCommand: null, formatCommand: "dotnet format" }
194
- };
195
- function detectScripts(m) {
196
- if (m.pkgJson) {
197
- const scripts = m.pkgJson.scripts ?? {};
198
- const run = pmRun(m.pkgJson);
199
- return {
200
- devCommand: scripts.dev ? `${run} dev` : null,
201
- buildCommand: scripts.build ? `${run} build` : null,
202
- testCommand: scripts.test ? `${run} test` : null,
203
- lintCommand: scripts.lint ? `${run} lint` : null,
204
- formatCommand: scripts.format ? `${run} format` : null
205
- };
206
- }
207
- if (m.language === "Python") {
208
- const r = m.pyProject?.includes("[tool.uv]") ? "uv run" : "python -m";
209
- return { devCommand: null, buildCommand: null, testCommand: `${r} pytest`, lintCommand: `${r} ruff check .`, formatCommand: `${r} ruff format .` };
210
- }
211
- if (m.language && LANGUAGE_SCRIPTS[m.language]) {
212
- return LANGUAGE_SCRIPTS[m.language];
213
- }
214
- return { devCommand: null, buildCommand: null, testCommand: null, lintCommand: null, formatCommand: null };
215
- }
216
- function pmRun(pkg) {
217
- const pm = pkg.packageManager;
218
- if (pm?.startsWith("pnpm")) return "pnpm";
219
- if (pm?.startsWith("yarn")) return "yarn";
220
- if (pm?.startsWith("bun")) return "bun";
221
- return "npm run";
222
- }
223
- async function globExists(dir, pattern) {
224
- const { readdir: readdir5 } = await import("fs/promises");
225
- try {
226
- const entries = await readdir5(dir);
227
- return entries.some((e) => e.endsWith(pattern.replace("*", "")));
228
- } catch {
229
- return false;
230
- }
231
- }
28
+ import { join } from "path";
232
29
 
233
30
  // src/commands/init/generators/claude-md.ts
234
31
  function generateClaudeMd(options, detected) {
@@ -403,334 +200,6 @@ function buildFormatHook(detected) {
403
200
  };
404
201
  }
405
202
 
406
- // src/commands/init/generators/claudeignore.ts
407
- function generateClaudeignore(detected) {
408
- const sections = ["# Generated by claude-launchpad"];
409
- sections.push(`
410
- # Dependencies
411
- node_modules/
412
- .pnp/
413
- .yarn/
414
-
415
- # Build output
416
- dist/
417
- build/
418
- out/
419
- .next/
420
- .nuxt/
421
- .output/
422
- .svelte-kit/
423
- .vercel/
424
- .turbo/
425
-
426
- # Package manager
427
- pnpm-lock.yaml
428
- package-lock.json
429
- yarn.lock
430
- bun.lockb
431
-
432
- # IDE & OS
433
- .vscode/
434
- .idea/
435
- *.swp
436
- *.swo
437
- .DS_Store
438
- Thumbs.db
439
-
440
- # Test & coverage
441
- coverage/
442
- .nyc_output/
443
- __snapshots__/
444
-
445
- # Environment (should never be read)
446
- .env
447
- .env.*
448
- !.env.example`);
449
- const lang = detected.language;
450
- if (lang === "Python") {
451
- sections.push(`
452
- # Python
453
- __pycache__/
454
- *.pyc
455
- *.pyo
456
- .venv/
457
- venv/
458
- .mypy_cache/
459
- .ruff_cache/
460
- .pytest_cache/
461
- *.egg-info/`);
462
- }
463
- if (lang === "Go") {
464
- sections.push(`
465
- # Go
466
- bin/
467
- vendor/`);
468
- }
469
- if (lang === "Rust") {
470
- sections.push(`
471
- # Rust
472
- target/
473
- Cargo.lock`);
474
- }
475
- if (lang === "Ruby") {
476
- sections.push(`
477
- # Ruby
478
- vendor/bundle/
479
- .bundle/
480
- tmp/
481
- log/`);
482
- }
483
- if (lang === "Java" || lang === "Kotlin") {
484
- sections.push(`
485
- # JVM
486
- target/
487
- build/
488
- .gradle/
489
- *.class
490
- *.jar`);
491
- }
492
- if (lang === "Dart") {
493
- sections.push(`
494
- # Dart/Flutter
495
- .dart_tool/
496
- .packages
497
- build/`);
498
- }
499
- if (lang === "PHP") {
500
- sections.push(`
501
- # PHP
502
- vendor/
503
- composer.lock`);
504
- }
505
- if (lang === "C#") {
506
- sections.push(`
507
- # .NET
508
- bin/
509
- obj/
510
- *.dll`);
511
- }
512
- if (lang === "Elixir") {
513
- sections.push(`
514
- # Elixir
515
- _build/
516
- deps/
517
- .elixir_ls/`);
518
- }
519
- if (lang === "Swift") {
520
- sections.push(`
521
- # Swift
522
- .build/
523
- DerivedData/
524
- *.xcuserdata`);
525
- }
526
- return sections.join("\n") + "\n";
527
- }
528
-
529
- // src/commands/init/generators/skill-enhance.ts
530
- var ENHANCE_SKILL_VERSION = 5;
531
- function generateEnhanceSkill() {
532
- return [
533
- "---",
534
- "name: lp-enhance",
535
- "description: |",
536
- " AI-improve your CLAUDE.md based on codebase analysis. Fills in architecture, conventions, guardrails, and suggests hooks and MCP servers.",
537
- ' TRIGGER when: user runs /lp-enhance, asks to "improve CLAUDE.md", "fill in architecture", or after major refactors.',
538
- " DO NOT TRIGGER when: user is editing CLAUDE.md manually, doing normal coding, or running doctor/eval.",
539
- "allowed-tools: Read, Glob, Grep, Edit, Write",
540
- "argument-hint: (no arguments needed)",
541
- "---",
542
- "",
543
- `<!-- lp-enhance-version: ${ENHANCE_SKILL_VERSION} -->`,
544
- "",
545
- "# lp-enhance - AI-powered CLAUDE.md improver",
546
- "",
547
- "Read CLAUDE.md and the project's codebase, then update CLAUDE.md to fill in missing or incomplete sections.",
548
- "",
549
- "## Phase 1: Research",
550
- "",
551
- "1. Read CLAUDE.md (if it exists)",
552
- "2. Read .claude/settings.json (hooks, permissions, MCP)",
553
- "3. Read .claude/rules/*.md (existing rules)",
554
- "4. Read .claudeignore (if it exists)",
555
- "5. Scan src/ directory structure (top-level dirs, key files)",
556
- "6. Read package.json / go.mod / pyproject.toml for stack detection",
557
- "7. Check for monorepo indicators (workspaces, nx.json, lerna.json)",
558
- "8. Check scenarios/ directory for existing eval scenarios",
559
- "",
560
- "**Done when:** you have a mental model of the stack, architecture, and existing config.",
561
- "",
562
- "## Phase 2: Plan",
563
- "",
564
- "Count current CLAUDE.md actionable lines. Budget is 200 lines max. Plan which sections to add or improve:",
565
- "",
566
- "1. **## Stack** - detect language, framework, package manager",
567
- "2. **## Architecture** - 3-5 bullets describing codebase shape",
568
- "3. **## Conventions** - max 8 key patterns. Overflow to .claude/rules/conventions.md",
569
- "4. **## Off-Limits** - max 8 guardrails specific to this project",
570
- "5. **## Memory** - ONLY if agentic-memory is configured in settings.json. Max 6 bullets.",
571
- "6. **## Key Decisions** - only decisions that affect how Claude works in this codebase",
572
- "",
573
- "7. **Skill Authoring** - if .claude/rules/conventions.md lacks a Skill Authoring section, plan to add one",
574
- "",
575
- "If any section would exceed 8 bullets, plan a .claude/rules/ file for the overflow.",
576
- "",
577
- "**Done when:** you know exactly what to add/change and the line count stays under 200.",
578
- "",
579
- "## Phase 3: Execute",
580
- "",
581
- "Edit CLAUDE.md with the planned changes. Then:",
582
- "",
583
- "1. Create or update .claude/rules/ files for overflow content",
584
- "2. Generate path-scoped rules if the project has distinct areas (see below)",
585
- "3. Review .claudeignore and print suggestions (see below)",
586
- "4. Generate 2-3 custom eval scenarios in scenarios/custom/ (see below)",
587
- "5. Verify line count is under 200",
588
- "",
589
- "**Rules:**",
590
- "- Don't remove existing content, only add or improve",
591
- "- Be specific to THIS project, not generic advice",
592
- "- Use bullet points, not paragraphs",
593
- "",
594
- "## Phase 4: Verify",
595
- "",
596
- "1. Run `claude-launchpad doctor` to check the score improved",
597
- "2. Print suggested hooks (exact JSON) for .claude/settings.json but don't modify it",
598
- "3. Print suggested MCP servers if external services detected (Postgres, Redis, Stripe, etc.)",
599
- '4. If eval scenarios were generated, print: "Run this in your terminal (not inside Claude Code): `claude-launchpad eval --scenarios scenarios/ --runs 1`"',
600
- "",
601
- "**Done when:** doctor score is equal or higher, suggestions printed, eval scenarios created if applicable.",
602
- "",
603
- "## Path-scoped rules generation",
604
- "",
605
- "Scan the project structure and generate focused .claude/rules/ files with paths: frontmatter. These load ONLY when Claude works on matching files, saving context tokens.",
606
- "",
607
- "**How to detect areas:**",
608
- "1. List top-level directories under src/ (or equivalent). Each distinct area (api, components, lib, tests) is a candidate.",
609
- "2. Check for monorepo indicators: workspaces in package.json, pnpm-workspace.yaml, nx.json, lerna.json. Each workspace is a candidate.",
610
- "3. Check for docs/, tests/, scripts/ as separate scopes.",
611
- "",
612
- "**For each detected area, create a rules file with this format:**",
613
- "",
614
- "---",
615
- 'paths: ["src/api/**"]',
616
- "---",
617
- "# API Rules",
618
- "- Validate all request input with zod schemas",
619
- "- Return typed error responses, never throw raw errors",
620
- "- Keep route handlers under 30 lines",
621
- "",
622
- "**Stack-specific patterns to include:**",
623
- `- Next.js app/: "Use Server Components by default, add 'use client' only when needed"`,
624
- '- API routes / src/api/: "Validate input at boundaries, typed error responses"',
625
- '- React components: "Colocate components near usage, props interface above component"',
626
- '- Tests: "One assertion per test when possible, descriptive test names"',
627
- '- Database / prisma/ / drizzle/: "Never write raw SQL, use the ORM, migrations required"',
628
- '- Docs: "No em dashes, max 3 sentences per paragraph, code examples required"',
629
- "",
630
- "**When NOT to generate:**",
631
- "- Small projects with < 5 source files (one conventions.md is enough)",
632
- "- Projects where all code is in one flat directory",
633
- "- If path-scoped rules already exist, don't overwrite them",
634
- "",
635
- "**Monorepo handling:**",
636
- "- Each package gets its own rules file: .claude/rules/packages-<name>.md",
637
- "- Suggest claudeMdExcludes in settings.json to skip irrelevant package CLAUDE.md files",
638
- "",
639
- "## Skill authoring conventions",
640
- "",
641
- "If .claude/rules/conventions.md exists but has no Skill Authoring section, add this:",
642
- "",
643
- "## Skill Authoring",
644
- "",
645
- "When creating Claude Code skills (.claude/skills/*/SKILL.md):",
646
- "",
647
- "- Add TRIGGER when / DO NOT TRIGGER when clauses in the description for auto-invocation",
648
- "- Add allowed-tools in frontmatter to restrict tool access (e.g. Read, Glob, Grep for read-only skills)",
649
- "- Add argument-hint in frontmatter showing the expected input format",
650
- '- Structure as phases: Research, Plan, Execute, Verify with "Done when:" success criteria per phase',
651
- "- Handle edge cases and preconditions before execution",
652
- "",
653
- "## Hook review",
654
- "",
655
- "Review .claude/settings.json hooks:",
656
- "- If you see project-specific patterns that deserve hooks, suggest them",
657
- "- If no PostCompact hook exists, suggest one that re-injects TASKS.md",
658
- "- If no SessionStart hook exists, suggest one that injects TASKS.md",
659
- "- DO NOT modify settings.json directly. Print exact JSON to add.",
660
- "",
661
- "## .claudeignore review",
662
- "",
663
- "Read .claudeignore and check if the patterns make sense for the detected stack:",
664
- "",
665
- "**Always flag:**",
666
- "- Missing node_modules/ (JS/TS projects)",
667
- "- Missing __pycache__/ or .venv/ (Python projects)",
668
- "- Missing target/ (Rust/Java projects)",
669
- "- Missing .env / .env.* patterns",
670
- "- Missing lock files (pnpm-lock.yaml, package-lock.json, yarn.lock, etc.)",
671
- "- Missing coverage/ directory",
672
- "- Large generated files that waste context (*.min.js, *.map, migrations/)",
673
- "",
674
- "**Never flag:**",
675
- "- Patterns the user clearly added intentionally",
676
- "- Test fixtures or seed data (might be needed for context)",
677
- "",
678
- "If .claudeignore is missing entirely, create one with sensible defaults for the detected stack.",
679
- "If it exists but has gaps, print suggested additions. Do NOT modify it directly.",
680
- "",
681
- "## Eval scenario generation",
682
- "",
683
- "After improving CLAUDE.md, generate 2-3 custom eval scenarios that test whether Claude follows the project's specific rules. Write them as YAML files in scenarios/ at the project root.",
684
- "",
685
- "**Scenario YAML format:**",
686
- "```yaml",
687
- "name: custom/scenario-name",
688
- "description: What this scenario tests",
689
- "setup:",
690
- " files:",
691
- " - path: src/example.ts",
692
- " content: |",
693
- " // Starter file that tempts Claude to break a rule",
694
- " instructions: |",
695
- " The specific rule from CLAUDE.md being tested.",
696
- 'prompt: "A task that would tempt Claude to break the rule"',
697
- "checks:",
698
- " - type: grep",
699
- ' pattern: "expected_pattern"',
700
- " target: src/example.ts",
701
- " expect: present",
702
- " points: 5",
703
- " label: What this check verifies",
704
- " - type: file-exists",
705
- " target: path/to/expected/file",
706
- " expect: present",
707
- " points: 5",
708
- " label: What this check verifies",
709
- "passingScore: 7",
710
- "runs: 3",
711
- "```",
712
- "",
713
- "**How to choose scenarios:**",
714
- "1. Pick the 2-3 most important rules from ## Off-Limits and ## Conventions",
715
- "2. Design a task that naturally tempts Claude to break each rule",
716
- "3. Write checks that verify compliance (grep for patterns, file-exists for structure)",
717
- "",
718
- "**Check types available:** `grep` (pattern in file), `file-exists` (present/absent), `max-lines` (file length)",
719
- "",
720
- "**Examples of good custom scenarios:**",
721
- '- Off-limits says "never use any" \u2192 task asks to build types, check for no `any` keyword',
722
- '- Convention says "max 400 lines per file" \u2192 task asks to generate a large module, check line count',
723
- '- Off-limits says "no raw SQL" \u2192 task asks to add a query, check for ORM usage',
724
- "",
725
- "**Skip if:** scenarios/ already has 3+ YAML files, or CLAUDE.md has no project-specific rules worth testing.",
726
- "",
727
- "## Other advanced configuration",
728
- "",
729
- "- If the project uses external APIs, suggest sandbox.network.allowedDomains",
730
- "- If you detect a monorepo, suggest claudeMdExcludes in settings.json"
731
- ].join("\n");
732
- }
733
-
734
203
  // src/commands/init/generators/backlog.ts
735
204
  function generateBacklogMd(options) {
736
205
  return `# ${options.name} - Backlog
@@ -769,7 +238,7 @@ function createInitCommand() {
769
238
  message: "One-line description (optional):"
770
239
  });
771
240
  const options = { name: name.trim(), description: description.trim() };
772
- const hasClaudeMd = await fileExists(join2(root, "CLAUDE.md"));
241
+ const hasClaudeMd = await fileExists(join(root, "CLAUDE.md"));
773
242
  if (hasClaudeMd && !opts.yes) {
774
243
  const overwrite = await confirm({
775
244
  message: "CLAUDE.md already exists. Overwrite?",
@@ -792,20 +261,20 @@ async function scaffold(root, options, detected, skipPrompts) {
792
261
  const backlogMd = generateBacklogMd(options);
793
262
  const settings = generateSettings(detected);
794
263
  const claudeignore = generateClaudeignore(detected);
795
- await mkdir(join2(root, ".claude", "rules"), { recursive: true });
796
- const settingsPath = join2(root, ".claude", "settings.json");
264
+ await mkdir(join(root, ".claude", "rules"), { recursive: true });
265
+ const settingsPath = join(root, ".claude", "settings.json");
797
266
  const mergedSettings = await mergeSettings(settingsPath, settings);
798
- const backlogPath = join2(root, "BACKLOG.md");
267
+ const backlogPath = join(root, "BACKLOG.md");
799
268
  const hasBacklog = await fileExists(backlogPath);
800
- const claudeignorePath = join2(root, ".claudeignore");
269
+ const claudeignorePath = join(root, ".claudeignore");
801
270
  const hasClaudeignore = await fileExists(claudeignorePath);
802
- const claudeGitignorePath = join2(root, ".claude", ".gitignore");
271
+ const claudeGitignorePath = join(root, ".claude", ".gitignore");
803
272
  const hasClaudeGitignore = await fileExists(claudeGitignorePath);
804
- const rulesPath = join2(root, ".claude", "rules", "conventions.md");
273
+ const rulesPath = join(root, ".claude", "rules", "conventions.md");
805
274
  const hasRules = await fileExists(rulesPath);
806
275
  const writes = [
807
- writeFile(join2(root, "CLAUDE.md"), claudeMd),
808
- writeFile(join2(root, "TASKS.md"), tasksMd),
276
+ writeFile(join(root, "CLAUDE.md"), claudeMd),
277
+ writeFile(join(root, "TASKS.md"), tasksMd),
809
278
  writeFile(settingsPath, JSON.stringify(mergedSettings, null, 2) + "\n")
810
279
  ];
811
280
  if (!hasBacklog) {
@@ -886,10 +355,10 @@ function generateStarterRules(detected) {
886
355
  return lines.join("\n");
887
356
  }
888
357
  async function createEnhanceSkillPrompt(root, skipPrompts) {
889
- const projectPath = join2(root, ".claude", "skills", "lp-enhance", "SKILL.md");
890
- const globalPath = join2(homedir(), ".claude", "skills", "lp-enhance", "SKILL.md");
891
- const legacyProject = join2(root, ".claude", "commands", "lp-enhance.md");
892
- const legacyGlobal = join2(homedir(), ".claude", "commands", "lp-enhance.md");
358
+ const projectPath = join(root, ".claude", "skills", "lp-enhance", "SKILL.md");
359
+ const globalPath = join(homedir(), ".claude", "skills", "lp-enhance", "SKILL.md");
360
+ const legacyProject = join(root, ".claude", "commands", "lp-enhance.md");
361
+ const legacyGlobal = join(homedir(), ".claude", "commands", "lp-enhance.md");
893
362
  if (await fileExists(projectPath) || await fileExists(globalPath) || await fileExists(legacyProject) || await fileExists(legacyGlobal)) return;
894
363
  const scope = skipPrompts ? "project" : await select({
895
364
  message: "Install /lp-enhance skill (AI-powered CLAUDE.md improver):",
@@ -900,14 +369,14 @@ async function createEnhanceSkillPrompt(root, skipPrompts) {
900
369
  ]
901
370
  });
902
371
  if (scope === "skip") return;
903
- const targetDir = scope === "global" ? join2(homedir(), ".claude", "skills", "lp-enhance") : join2(root, ".claude", "skills", "lp-enhance");
372
+ const targetDir = scope === "global" ? join(homedir(), ".claude", "skills", "lp-enhance") : join(root, ".claude", "skills", "lp-enhance");
904
373
  await mkdir(targetDir, { recursive: true });
905
- await writeFile(join2(targetDir, "SKILL.md"), generateEnhanceSkill());
374
+ await writeFile(join(targetDir, "SKILL.md"), generateEnhanceSkill());
906
375
  log.success(`Generated /lp-enhance skill (${scope} scope)`);
907
376
  }
908
377
  async function mergeSettings(existingPath, generated) {
909
378
  try {
910
- const existing = JSON.parse(await readFile2(existingPath, "utf-8"));
379
+ const existing = JSON.parse(await readFile(existingPath, "utf-8"));
911
380
  const existingHooks = existing.hooks ?? {};
912
381
  const generatedHooks = generated.hooks ?? {};
913
382
  const mergedHooks = { ...existingHooks };
@@ -931,8 +400,8 @@ import { Command as Command2 } from "commander";
931
400
  import chalk from "chalk";
932
401
 
933
402
  // src/lib/parser.ts
934
- import { readdir, access as access2 } from "fs/promises";
935
- import { join as join3, resolve } from "path";
403
+ import { readdir, access } from "fs/promises";
404
+ import { join as join2, resolve } from "path";
936
405
  var CLAUDE_MD = "CLAUDE.md";
937
406
  var CLAUDE_DIR = ".claude";
938
407
  var SETTINGS_FILE = "settings.json";
@@ -940,24 +409,24 @@ var SETTINGS_LOCAL_FILE = "settings.local.json";
940
409
  var RULES_DIR = "rules";
941
410
  async function parseClaudeConfig(projectRoot) {
942
411
  const root = resolve(projectRoot);
943
- const claudeDir = join3(root, CLAUDE_DIR);
412
+ const claudeDir = join2(root, CLAUDE_DIR);
944
413
  const [claudeMd, localClaudeMd, settings, localSettings, hooks, rules, mcpServers, skills, claudeignore] = await Promise.all([
945
414
  readClaudeMd(root),
946
- readFileOrNull(join3(claudeDir, CLAUDE_MD)),
415
+ readFileOrNull(join2(claudeDir, CLAUDE_MD)),
947
416
  readSettings(claudeDir),
948
417
  readSettingsFromFile(claudeDir, SETTINGS_LOCAL_FILE),
949
418
  readHooks(claudeDir),
950
419
  readRules(claudeDir),
951
420
  readMcpServers(claudeDir, root),
952
421
  readSkills(claudeDir),
953
- readFileOrNull(join3(root, ".claudeignore"))
422
+ readFileOrNull(join2(root, ".claudeignore"))
954
423
  ]);
955
424
  const instructionCount = claudeMd ? countInstructions(claudeMd) : 0;
956
425
  return {
957
- claudeMdPath: claudeMd !== null ? join3(root, CLAUDE_MD) : null,
426
+ claudeMdPath: claudeMd !== null ? join2(root, CLAUDE_MD) : null,
958
427
  claudeMdContent: claudeMd,
959
428
  claudeMdInstructionCount: instructionCount,
960
- settingsPath: settings !== null ? join3(claudeDir, SETTINGS_FILE) : null,
429
+ settingsPath: settings !== null ? join2(claudeDir, SETTINGS_FILE) : null,
961
430
  settings,
962
431
  localClaudeMdContent: localClaudeMd,
963
432
  localSettings,
@@ -965,12 +434,12 @@ async function parseClaudeConfig(projectRoot) {
965
434
  rules,
966
435
  mcpServers,
967
436
  skills,
968
- claudeignorePath: claudeignore !== null ? join3(root, ".claudeignore") : null,
437
+ claudeignorePath: claudeignore !== null ? join2(root, ".claudeignore") : null,
969
438
  claudeignoreContent: claudeignore
970
439
  };
971
440
  }
972
441
  async function readClaudeMd(root) {
973
- return readFileOrNull(join3(root, CLAUDE_MD));
442
+ return readFileOrNull(join2(root, CLAUDE_MD));
974
443
  }
975
444
  function countInstructions(content) {
976
445
  const lines = content.split("\n");
@@ -989,7 +458,7 @@ async function readSettings(claudeDir) {
989
458
  return readSettingsFromFile(claudeDir, SETTINGS_FILE);
990
459
  }
991
460
  async function readSettingsFromFile(claudeDir, filename) {
992
- const raw = await readFileOrNull(join3(claudeDir, filename));
461
+ const raw = await readFileOrNull(join2(claudeDir, filename));
993
462
  if (raw === null) return null;
994
463
  try {
995
464
  return JSON.parse(raw);
@@ -999,8 +468,8 @@ async function readSettingsFromFile(claudeDir, filename) {
999
468
  }
1000
469
  async function readHooks(claudeDir) {
1001
470
  const [shared, local] = await Promise.all([
1002
- readHooksFromFile(join3(claudeDir, SETTINGS_FILE)),
1003
- readHooksFromFile(join3(claudeDir, SETTINGS_LOCAL_FILE))
471
+ readHooksFromFile(join2(claudeDir, SETTINGS_FILE)),
472
+ readHooksFromFile(join2(claudeDir, SETTINGS_LOCAL_FILE))
1004
473
  ]);
1005
474
  return [...shared, ...local];
1006
475
  }
@@ -1046,14 +515,14 @@ async function readHooksFromFile(settingsPath) {
1046
515
  }
1047
516
  }
1048
517
  async function readRules(claudeDir) {
1049
- const rulesDir = join3(claudeDir, RULES_DIR);
518
+ const rulesDir = join2(claudeDir, RULES_DIR);
1050
519
  return listFilesRecursive(rulesDir, ".md");
1051
520
  }
1052
521
  async function readMcpServers(claudeDir, projectRoot) {
1053
522
  const [fromMcpJson, fromSettings, fromLocalSettings] = await Promise.all([
1054
- readMcpJsonFile(join3(projectRoot, ".mcp.json")),
1055
- readMcpServersFromSettings(join3(claudeDir, SETTINGS_FILE)),
1056
- readMcpServersFromSettings(join3(claudeDir, SETTINGS_LOCAL_FILE))
523
+ readMcpJsonFile(join2(projectRoot, ".mcp.json")),
524
+ readMcpServersFromSettings(join2(claudeDir, SETTINGS_FILE)),
525
+ readMcpServersFromSettings(join2(claudeDir, SETTINGS_LOCAL_FILE))
1057
526
  ]);
1058
527
  const seen = /* @__PURE__ */ new Set();
1059
528
  const result = [];
@@ -1077,7 +546,7 @@ async function readMcpJsonFile(mcpJsonPath) {
1077
546
  const c = config;
1078
547
  result.push({
1079
548
  name,
1080
- transport: c.transport ?? "stdio",
549
+ transport: c.transport ?? c.type ?? "stdio",
1081
550
  command: c.command,
1082
551
  url: c.url
1083
552
  });
@@ -1099,7 +568,7 @@ async function readMcpServersFromSettings(settingsPath) {
1099
568
  const c = config;
1100
569
  result.push({
1101
570
  name,
1102
- transport: c.transport ?? "stdio",
571
+ transport: c.transport ?? c.type ?? "stdio",
1103
572
  command: c.command,
1104
573
  url: c.url
1105
574
  });
@@ -1110,8 +579,8 @@ async function readMcpServersFromSettings(settingsPath) {
1110
579
  }
1111
580
  }
1112
581
  async function readSkills(claudeDir) {
1113
- const commandsDir = join3(claudeDir, "commands");
1114
- const skillsDir = join3(claudeDir, "skills");
582
+ const commandsDir = join2(claudeDir, "commands");
583
+ const skillsDir = join2(claudeDir, "skills");
1115
584
  const [commands, skills] = await Promise.all([
1116
585
  listFilesRecursive(commandsDir, ".md"),
1117
586
  listFilesRecursive(skillsDir, ".md")
@@ -1120,14 +589,14 @@ async function readSkills(claudeDir) {
1120
589
  }
1121
590
  async function listFilesRecursive(dir, ext) {
1122
591
  try {
1123
- await access2(dir);
592
+ await access(dir);
1124
593
  } catch {
1125
594
  return [];
1126
595
  }
1127
596
  const results = [];
1128
597
  const entries = await readdir(dir, { withFileTypes: true });
1129
598
  for (const entry of entries) {
1130
- const fullPath = join3(dir, entry.name);
599
+ const fullPath = join2(dir, entry.name);
1131
600
  if (entry.isDirectory()) {
1132
601
  const nested = await listFilesRecursive(fullPath, ext);
1133
602
  results.push(...nested);
@@ -1327,13 +796,13 @@ async function analyzeHooks(config) {
1327
796
  }
1328
797
 
1329
798
  // src/commands/doctor/analyzers/rules.ts
1330
- import { readFile as readFile3 } from "fs/promises";
1331
- import { basename as basename2, join as join4, dirname } from "path";
799
+ import { readFile as readFile2 } from "fs/promises";
800
+ import { basename, join as join3, dirname } from "path";
1332
801
  import { homedir as homedir2 } from "os";
1333
802
  async function analyzeRules(config) {
1334
803
  const issues = [];
1335
804
  const projectRoot = config.claudeMdPath ? dirname(config.claudeMdPath) : process.cwd();
1336
- const hasBacklog = await fileExists(join4(projectRoot, "BACKLOG.md"));
805
+ const hasBacklog = await fileExists(join3(projectRoot, "BACKLOG.md"));
1337
806
  if (!hasBacklog) {
1338
807
  issues.push({
1339
808
  analyzer: "Rules",
@@ -1342,7 +811,7 @@ async function analyzeRules(config) {
1342
811
  fix: "Run `claude-launchpad init` or `doctor --fix` to generate one"
1343
812
  });
1344
813
  }
1345
- const hasClaudeignore = await fileExists(join4(projectRoot, ".claudeignore"));
814
+ const hasClaudeignore = await fileExists(join3(projectRoot, ".claudeignore"));
1346
815
  if (!hasClaudeignore) {
1347
816
  issues.push({
1348
817
  analyzer: "Rules",
@@ -1352,9 +821,9 @@ async function analyzeRules(config) {
1352
821
  });
1353
822
  }
1354
823
  const hasSkillInProject = config.skills.some(
1355
- (s) => basename2(s) === "SKILL.md" && s.includes("lp-enhance") || basename2(s) === "lp-enhance.md"
824
+ (s) => basename(s) === "SKILL.md" && s.includes("lp-enhance") || basename(s) === "lp-enhance.md"
1356
825
  );
1357
- const hasSkillGlobal = await fileExists(join4(homedir2(), ".claude", "skills", "lp-enhance", "SKILL.md")) || await fileExists(join4(homedir2(), ".claude", "commands", "lp-enhance.md"));
826
+ const hasSkillGlobal = await fileExists(join3(homedir2(), ".claude", "skills", "lp-enhance", "SKILL.md")) || await fileExists(join3(homedir2(), ".claude", "commands", "lp-enhance.md"));
1358
827
  if (!hasSkillInProject && !hasSkillGlobal) {
1359
828
  issues.push({
1360
829
  analyzer: "Rules",
@@ -1384,27 +853,27 @@ async function analyzeRules(config) {
1384
853
  }
1385
854
  for (const rulePath of config.rules) {
1386
855
  try {
1387
- const content = await readFile3(rulePath, "utf-8");
856
+ const content = await readFile2(rulePath, "utf-8");
1388
857
  const trimmed = content.trim();
1389
858
  if (trimmed.length === 0) {
1390
859
  issues.push({
1391
860
  analyzer: "Rules",
1392
861
  severity: "low",
1393
- message: `Empty rule file: ${basename2(rulePath)}`,
1394
- fix: `Add content to ${basename2(rulePath)} or delete it`
862
+ message: `Empty rule file: ${basename(rulePath)}`,
863
+ fix: `Add content to ${basename(rulePath)} or delete it`
1395
864
  });
1396
865
  } else if (trimmed.length < 20) {
1397
866
  issues.push({
1398
867
  analyzer: "Rules",
1399
868
  severity: "info",
1400
- message: `Very short rule file (${trimmed.length} chars): ${basename2(rulePath)}`
869
+ message: `Very short rule file (${trimmed.length} chars): ${basename(rulePath)}`
1401
870
  });
1402
871
  }
1403
872
  } catch {
1404
873
  issues.push({
1405
874
  analyzer: "Rules",
1406
875
  severity: "low",
1407
- message: `Could not read rule file: ${basename2(rulePath)}`
876
+ message: `Could not read rule file: ${basename(rulePath)}`
1408
877
  });
1409
878
  }
1410
879
  }
@@ -1413,12 +882,12 @@ async function analyzeRules(config) {
1413
882
  }
1414
883
  async function getSkillVersion(projectRoot) {
1415
884
  const paths = [
1416
- join4(projectRoot, ".claude", "skills", "lp-enhance", "SKILL.md"),
1417
- join4(homedir2(), ".claude", "skills", "lp-enhance", "SKILL.md")
885
+ join3(projectRoot, ".claude", "skills", "lp-enhance", "SKILL.md"),
886
+ join3(homedir2(), ".claude", "skills", "lp-enhance", "SKILL.md")
1418
887
  ];
1419
888
  for (const p of paths) {
1420
889
  try {
1421
- const content = await readFile3(p, "utf-8");
890
+ const content = await readFile2(p, "utf-8");
1422
891
  const match = content.match(/<!-- lp-enhance-version: (\d+) -->/);
1423
892
  if (match) return parseInt(match[1], 10);
1424
893
  return 0;
@@ -1527,7 +996,7 @@ async function analyzePermissions(config) {
1527
996
  }
1528
997
 
1529
998
  // src/commands/doctor/analyzers/mcp.ts
1530
- import { access as access3 } from "fs/promises";
999
+ import { access as access2 } from "fs/promises";
1531
1000
  async function analyzeMcp(config) {
1532
1001
  const issues = [];
1533
1002
  const servers = config.mcpServers;
@@ -1560,7 +1029,7 @@ async function analyzeMcp(config) {
1560
1029
  const executable = server.command.split(" ")[0];
1561
1030
  if (executable.startsWith("/") || executable.startsWith("./")) {
1562
1031
  try {
1563
- await access3(executable);
1032
+ await access2(executable);
1564
1033
  } catch {
1565
1034
  issues.push({
1566
1035
  analyzer: "MCP",
@@ -1801,420 +1270,9 @@ async function analyzeQuality(config) {
1801
1270
  return { name: "CLAUDE.md Quality", issues, score };
1802
1271
  }
1803
1272
 
1804
- // src/commands/doctor/fixer.ts
1805
- import { readFile as readFile5, writeFile as writeFile2, mkdir as mkdir2, access as access4 } from "fs/promises";
1806
- import { join as join6 } from "path";
1807
- import { homedir as homedir3 } from "os";
1808
-
1809
- // src/commands/doctor/fixer-memory.ts
1810
- import { readFile as readFile4 } from "fs/promises";
1811
- import { join as join5 } from "path";
1812
- async function addPlacementHook(root, placement, event, dedupKeyword, entry, prepend, successMsg) {
1813
- const read = placement === "local" ? readSettingsLocalJson : readSettingsJson;
1814
- const write = placement === "local" ? writeSettingsLocalJson : writeSettingsJson;
1815
- const settings = await read(root);
1816
- const hooks = settings.hooks ?? {};
1817
- const hookList = hooks[event] ?? [];
1818
- const alreadyHas = hookList.some((g) => {
1819
- const nested = g.hooks;
1820
- return nested?.some((h) => String(h.command ?? "").includes(dedupKeyword));
1821
- });
1822
- if (alreadyHas) return false;
1823
- hooks[event] = prepend ? [entry, ...hookList] : [...hookList, entry];
1824
- settings.hooks = hooks;
1825
- await write(root, settings);
1826
- log.success(successMsg);
1827
- return true;
1828
- }
1829
- async function disableAutoMemory(root, placement) {
1830
- const read = placement === "local" ? readSettingsLocalJson : readSettingsJson;
1831
- const write = placement === "local" ? writeSettingsLocalJson : writeSettingsJson;
1832
- const settings = await read(root);
1833
- if (settings.autoMemoryEnabled === false) return false;
1834
- settings.autoMemoryEnabled = false;
1835
- await write(root, settings);
1836
- const target = placement === "local" ? "settings.local.json" : "settings.json";
1837
- log.success(`Set autoMemoryEnabled: false in ${target}`);
1838
- return true;
1839
- }
1840
- async function addMemoryToolPermissions(root, placement) {
1841
- const read = placement === "local" ? readSettingsLocalJson : readSettingsJson;
1842
- const write = placement === "local" ? writeSettingsLocalJson : writeSettingsJson;
1843
- const settings = await read(root);
1844
- const permissions = settings.permissions ?? {};
1845
- const allow = permissions.allow ?? [];
1846
- const tools = [
1847
- "mcp__agentic-memory__memory_store",
1848
- "mcp__agentic-memory__memory_search",
1849
- "mcp__agentic-memory__memory_recent",
1850
- "mcp__agentic-memory__memory_forget",
1851
- "mcp__agentic-memory__memory_relate",
1852
- "mcp__agentic-memory__memory_stats",
1853
- "mcp__agentic-memory__memory_update"
1854
- ];
1855
- const missing = tools.filter((t) => !allow.includes(t));
1856
- if (missing.length === 0) return false;
1857
- settings.permissions = { ...permissions, allow: [...allow, ...missing] };
1858
- await write(root, settings);
1859
- const target = placement === "local" ? "settings.local.json" : "settings.json";
1860
- log.success(`Added agentic-memory MCP tool permissions to ${target}`);
1861
- return true;
1862
- }
1863
- async function addSessionStartPullHook(root, placement) {
1864
- const target = placement === "local" ? "settings.local.json" : "settings.json";
1865
- return addPlacementHook(root, placement, "SessionStart", "memory pull", {
1866
- matcher: "startup",
1867
- hooks: [{ type: "command", command: "claude-launchpad memory pull -y 2>/dev/null; exit 0" }]
1868
- }, true, `Added SessionStart hook for memory sync to ${target}`);
1869
- }
1870
- async function addSessionEndPushHook(root, placement) {
1871
- const target = placement === "local" ? "settings.local.json" : "settings.json";
1872
- return addPlacementHook(root, placement, "SessionEnd", "memory push", {
1873
- hooks: [{ type: "command", command: "claude-launchpad memory push -y >/dev/null 2>&1 & exit 0" }]
1874
- }, false, `Added SessionEnd hook for memory sync to ${target}`);
1875
- }
1876
- async function removeStaleStopHook(root) {
1877
- const settings = await readSettingsJson(root);
1878
- const hooks = settings.hooks;
1879
- if (!hooks?.Stop) return false;
1880
- const stopHooks = hooks.Stop;
1881
- const filtered = stopHooks.filter((h) => {
1882
- const innerHooks = h.hooks;
1883
- return !innerHooks?.some(
1884
- (ih) => typeof ih.command === "string" && ih.command.includes("memory extract")
1885
- );
1886
- });
1887
- if (filtered.length === stopHooks.length) return false;
1888
- const updated = filtered.length === 0 ? (({ Stop: _, ...rest }) => rest)(hooks) : { ...hooks, Stop: filtered };
1889
- settings.hooks = updated;
1890
- await writeSettingsJson(root, settings);
1891
- log.success("Removed deprecated Stop hook (memory extract)");
1892
- return true;
1893
- }
1894
- async function addAllowedMcpServers(root, placement) {
1895
- const read = placement === "local" ? readSettingsLocalJson : readSettingsJson;
1896
- const write = placement === "local" ? writeSettingsLocalJson : writeSettingsJson;
1897
- const settings = await read(root);
1898
- if (settings.allowedMcpServers) return false;
1899
- const other = placement === "local" ? await readSettingsJson(root) : await readSettingsLocalJson(root);
1900
- if (other.allowedMcpServers) return false;
1901
- const serverNames = /* @__PURE__ */ new Set();
1902
- const settingsServers = settings.mcpServers;
1903
- if (settingsServers && typeof settingsServers === "object") {
1904
- for (const name of Object.keys(settingsServers)) serverNames.add(name);
1905
- }
1906
- const mcpJsonPath = join5(root, ".mcp.json");
1907
- try {
1908
- const mcpJson = JSON.parse(await readFile4(mcpJsonPath, "utf-8"));
1909
- const mcpServers = mcpJson.mcpServers;
1910
- if (mcpServers && typeof mcpServers === "object") {
1911
- for (const name of Object.keys(mcpServers)) serverNames.add(name);
1912
- }
1913
- } catch {
1914
- }
1915
- if (serverNames.size === 0) return false;
1916
- settings.allowedMcpServers = [...serverNames].map(
1917
- (name) => ({ serverName: name })
1918
- );
1919
- await write(root, settings);
1920
- const target = placement === "local" ? "settings.local.json" : "settings.json";
1921
- log.success(`Added allowedMcpServers from configured servers to ${target}`);
1922
- return true;
1923
- }
1924
-
1925
- // src/commands/doctor/fixer.ts
1926
- async function applyFixes(issues, projectRoot) {
1927
- const detected = await detectProject(projectRoot);
1928
- const hasMemoryIssues = issues.some((i) => i.analyzer === "Memory");
1929
- const placement = hasMemoryIssues ? await getMemoryPlacement(projectRoot) : "shared";
1930
- let fixed = 0;
1931
- let skipped = 0;
1932
- for (const issue of issues) {
1933
- const applied = await tryFix(issue, projectRoot, detected, placement);
1934
- if (applied) {
1935
- fixed++;
1936
- } else {
1937
- skipped++;
1938
- }
1939
- }
1940
- return { fixed, skipped };
1941
- }
1942
- var FIX_TABLE = [
1943
- { analyzer: "Hooks", match: "No hooks configured", fix: async (root, detected) => {
1944
- const a = await addEnvProtectionHook(root);
1945
- const b = await addAutoFormatHook(root, detected);
1946
- const c = await addForcePushProtection(root);
1947
- const d = await addSessionStartHook(root);
1948
- return a || b || c || d;
1949
- } },
1950
- { analyzer: "Hooks", match: ".env file protection", fix: (root) => addEnvProtectionHook(root) },
1951
- { analyzer: "Hooks", match: "auto-format", fix: (root, detected) => addAutoFormatHook(root, detected) },
1952
- { analyzer: "Hooks", match: "No PreToolUse", fix: (root) => addEnvProtectionHook(root) },
1953
- { analyzer: "Quality", match: "Architecture", fix: (root) => addClaudeMdSection(root, "## Architecture", "<!-- TODO: Describe your codebase structure. Run `/lp-enhance` to auto-fill this. -->") },
1954
- { 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") },
1955
- { analyzer: "Quality", match: "Commands", fix: (root) => addClaudeMdSection(root, "## Commands", "<!-- TODO: Add your dev/build/test commands -->") },
1956
- { analyzer: "Quality", match: "Stack", fix: (root, detected) => {
1957
- const content = detected.language ? `- **Language**: ${detected.language}${detected.framework ? `
1958
- - **Framework**: ${detected.framework}` : ""}${detected.packageManager ? `
1959
- - **Package Manager**: ${detected.packageManager}` : ""}` : "<!-- TODO: Define your tech stack -->";
1960
- return addClaudeMdSection(root, "## Stack", content);
1961
- } },
1962
- { analyzer: "Quality", match: "Session Start", fix: (root) => addClaudeMdSection(root, "## Session Start", "- ALWAYS read @TASKS.md first - it tracks progress across sessions\n- Update TASKS.md as you complete work") },
1963
- { analyzer: "Quality", match: "Backlog", fix: (root) => addClaudeMdSection(root, "## Backlog", "- When a feature is discussed but deferred, add it to BACKLOG.md immediately\n- Never leave future ideas only in TASKS.md or conversation \u2014 they get lost\n- BACKLOG.md is the single source of truth for parked features") },
1964
- { analyzer: "Rules", match: "No BACKLOG.md", fix: (root) => createBacklogMd(root) },
1965
- { analyzer: "Rules", match: "No .claudeignore", fix: (root, detected) => createClaudeignore(root, detected) },
1966
- { analyzer: "Rules", match: "No .claude/rules/", fix: (root) => createStarterRules(root) },
1967
- { analyzer: "Hooks", match: "PostCompact", fix: (root) => addPostCompactHook(root) },
1968
- { analyzer: "Permissions", match: "force-push", fix: (root) => addForcePushProtection(root) },
1969
- { analyzer: "Permissions", match: "Credential files not blocked", fix: (root) => addCredentialDenyRules(root) },
1970
- { analyzer: "Permissions", match: "Bypass permissions mode", fix: (root) => addBypassDisable(root) },
1971
- { analyzer: "Permissions", match: "Sandbox not enabled", fix: (root) => addSandboxSettings(root) },
1972
- { analyzer: "Permissions", match: ".env is protected by hooks but not in .claudeignore", fix: (root) => addEnvToClaudeignore(root) },
1973
- { analyzer: "Rules", match: "No /lp-enhance skill", fix: (root) => createEnhanceSkill(root) },
1974
- { analyzer: "Rules", match: "lp-enhance skill is outdated", fix: (root) => updateEnhanceSkill(root) },
1975
- { analyzer: "Settings", match: "Deprecated includeCoAuthoredBy", fix: (root) => migrateAttribution(root) },
1976
- { analyzer: "Hooks", match: "SessionStart", fix: (root) => addSessionStartHook(root) },
1977
- { analyzer: "Memory", match: "Deprecated Stop hook", fix: (root) => removeStaleStopHook(root) },
1978
- { analyzer: "Memory", match: "autoMemoryEnabled not disabled", fix: (root, _det, placement) => disableAutoMemory(root, placement) },
1979
- { analyzer: "Memory", match: "MCP tool permission", fix: (root, _det, placement) => addMemoryToolPermissions(root, placement) },
1980
- { analyzer: "MCP", match: "no allowedMcpServers", fix: (root, _det, placement) => addAllowedMcpServers(root, placement) },
1981
- { analyzer: "Memory", match: "SessionStart hook to auto-pull", fix: (root, _det, placement) => addSessionStartPullHook(root, placement) },
1982
- { analyzer: "Memory", match: "SessionEnd hook to auto-push", fix: (root, _det, placement) => addSessionEndPushHook(root, placement) },
1983
- { analyzer: "Memory", match: "CLAUDE.md missing memory guidance", fix: (root, _det, placement) => {
1984
- const content = "Use agentic-memory to persist knowledge across sessions:\n- Memories are automatically injected at session start\n- STORE IMMEDIATELY when: a dependency strategy changes, an architecture decision is made, a convention is established, a bug pattern is discovered, or a feature is killed/added\n- Use memory_search before memory_store to check for duplicates\n- NEVER store credentials, API keys, tokens, or secrets in memories";
1985
- const target = placement === "local" ? join6(root, ".claude", "CLAUDE.md") : void 0;
1986
- return addClaudeMdSection(root, "## Memory", content, target);
1987
- } }
1988
- ];
1989
- async function tryFix(issue, root, detected, placement) {
1990
- const entry = FIX_TABLE.find(
1991
- (e) => e.analyzer === issue.analyzer && issue.message.includes(e.match)
1992
- );
1993
- return entry ? entry.fix(root, detected, placement) : false;
1994
- }
1995
- async function addHook(root, event, dedupKeyword, entry, successMsg) {
1996
- const settings = await readSettingsJson(root);
1997
- const hooks = settings.hooks ?? {};
1998
- const hookList = hooks[event] ?? [];
1999
- const alreadyHas = hookList.some((g) => {
2000
- const nested = g.hooks;
2001
- return nested?.some((h) => String(h.command ?? "").includes(dedupKeyword));
2002
- });
2003
- if (alreadyHas) return false;
2004
- hookList.push(entry);
2005
- settings.hooks = { ...hooks, [event]: hookList };
2006
- await writeSettingsJson(root, settings);
2007
- log.success(successMsg);
2008
- return true;
2009
- }
2010
- async function addEnvProtectionHook(root) {
2011
- return addHook(root, "PreToolUse", ".env", {
2012
- matcher: "Read|Write|Edit",
2013
- hooks: [{
2014
- type: "command",
2015
- command: `echo "$TOOL_INPUT_FILE_PATH" | grep -qE '\\.(env|env\\..*)$' && ! echo "$TOOL_INPUT_FILE_PATH" | grep -q '.env.example' && echo 'BLOCKED: .env files contain secrets' && exit 1; exit 0`
2016
- }]
2017
- }, "Added .env file protection hook (PreToolUse)");
2018
- }
2019
- async function addAutoFormatHook(root, detected) {
2020
- if (!detected.language) return false;
2021
- const formatters = {
2022
- TypeScript: { extensions: ["ts", "tsx"], command: "npx prettier --write" },
2023
- JavaScript: { extensions: ["js", "jsx"], command: "npx prettier --write" },
2024
- Python: { extensions: ["py"], command: "ruff format" },
2025
- Go: { extensions: ["go"], command: "gofmt -w" },
2026
- Rust: { extensions: ["rs"], command: "rustfmt" },
2027
- Ruby: { extensions: ["rb"], command: "rubocop -A" },
2028
- PHP: { extensions: ["php"], command: "vendor/bin/pint" }
2029
- };
2030
- const config = formatters[detected.language];
2031
- if (!config) return false;
2032
- const extChecks = config.extensions.map((ext) => `[ "$ext" = "${ext}" ]`).join(" || ");
2033
- return addHook(root, "PostToolUse", "format", {
2034
- matcher: "Write|Edit",
2035
- hooks: [{
2036
- type: "command",
2037
- command: `ext=\${TOOL_INPUT_FILE_PATH##*.}; (${extChecks}) && ${config.command} "$TOOL_INPUT_FILE_PATH" 2>/dev/null; exit 0`
2038
- }]
2039
- }, `Added auto-format hook (PostToolUse \u2192 ${config.command})`);
2040
- }
2041
- async function addForcePushProtection(root) {
2042
- return addHook(root, "PreToolUse", "force", {
2043
- matcher: "Bash",
2044
- hooks: [{
2045
- type: "command",
2046
- command: `echo "$TOOL_INPUT_COMMAND" | grep -qE 'push.*--force|push.*-f' && echo 'WARNING: Force push detected \u2014 this can destroy remote history' && exit 1; exit 0`
2047
- }]
2048
- }, "Added force-push protection hook (PreToolUse \u2192 Bash)");
2049
- }
2050
- async function addPostCompactHook(root) {
2051
- return addHook(root, "PostCompact", "TASKS.md", {
2052
- matcher: "",
2053
- hooks: [{
2054
- type: "command",
2055
- command: "cat TASKS.md 2>/dev/null; exit 0"
2056
- }]
2057
- }, "Added PostCompact hook (re-injects TASKS.md after compaction)");
2058
- }
2059
- async function addSessionStartHook(root) {
2060
- return addHook(root, "SessionStart", "TASKS.md", {
2061
- matcher: "startup|resume",
2062
- hooks: [{
2063
- type: "command",
2064
- command: "cat TASKS.md 2>/dev/null; exit 0"
2065
- }]
2066
- }, "Added SessionStart hook (injects TASKS.md at startup)");
2067
- }
2068
- async function migrateAttribution(root) {
2069
- const settings = await readSettingsJson(root);
2070
- if (settings.includeCoAuthoredBy === void 0) return false;
2071
- const { includeCoAuthoredBy: _, ...rest } = settings;
2072
- const updated = { ...rest, attribution: { commit: "", pr: "" } };
2073
- await writeSettingsJson(root, updated);
2074
- log.success("Migrated includeCoAuthoredBy \u2192 attribution object");
2075
- return true;
2076
- }
2077
- async function addCredentialDenyRules(root) {
2078
- const settings = await readSettingsJson(root);
2079
- const permissions = settings.permissions ?? {};
2080
- const deny = permissions.deny ?? [];
2081
- const toAdd = ["Read(~/.ssh/*)", "Read(~/.aws/*)", "Read(~/.npmrc)"];
2082
- const missing = toAdd.filter((p) => !deny.includes(p));
2083
- if (missing.length === 0) return false;
2084
- settings.permissions = { ...permissions, deny: [...deny, ...missing] };
2085
- await writeSettingsJson(root, settings);
2086
- log.success("Added credential deny rules (SSH, AWS, npm)");
2087
- return true;
2088
- }
2089
- async function addBypassDisable(root) {
2090
- const settings = await readSettingsJson(root);
2091
- if (settings.disableBypassPermissionsMode === "disable") return false;
2092
- settings.disableBypassPermissionsMode = "disable";
2093
- await writeSettingsJson(root, settings);
2094
- log.success("Added disableBypassPermissionsMode: disable");
2095
- return true;
2096
- }
2097
- async function addSandboxSettings(root) {
2098
- const settings = await readSettingsJson(root);
2099
- const sandbox = settings.sandbox;
2100
- if (sandbox?.enabled === true) return false;
2101
- settings.sandbox = { enabled: true, failIfUnavailable: true };
2102
- await writeSettingsJson(root, settings);
2103
- log.success("Enabled sandbox with failIfUnavailable");
2104
- return true;
2105
- }
2106
- async function addEnvToClaudeignore(root) {
2107
- const ignorePath = join6(root, ".claudeignore");
2108
- let content;
2109
- try {
2110
- content = await readFile5(ignorePath, "utf-8");
2111
- } catch {
2112
- return false;
2113
- }
2114
- const lines = content.split("\n").map((l) => l.trim());
2115
- if (lines.some((l) => l === ".env" || l === ".env.*" || l === ".env*")) return false;
2116
- await writeFile2(ignorePath, content.trimEnd() + "\n.env\n.env.*\n");
2117
- log.success("Added .env to .claudeignore");
2118
- return true;
2119
- }
2120
- async function addClaudeMdSection(root, heading, content, targetPath) {
2121
- const claudeMdPath = targetPath ?? join6(root, "CLAUDE.md");
2122
- let existing;
2123
- try {
2124
- existing = await readFile5(claudeMdPath, "utf-8");
2125
- } catch {
2126
- if (!targetPath) return false;
2127
- await mkdir2(join6(root, ".claude"), { recursive: true });
2128
- existing = "# Local Claude Config\n";
2129
- }
2130
- if (existing.includes(heading)) return false;
2131
- const keyDecisionsIdx = existing.indexOf("## Key Decisions");
2132
- const insertAt = keyDecisionsIdx > -1 ? keyDecisionsIdx : existing.length;
2133
- const section = `
2134
- ${heading}
2135
- ${content}
2136
-
2137
- `;
2138
- const updated = existing.slice(0, insertAt) + section + existing.slice(insertAt);
2139
- await writeFile2(claudeMdPath, updated);
2140
- const label = targetPath ? ".claude/CLAUDE.md" : "CLAUDE.md";
2141
- log.success(`Added "${heading}" section to ${label}`);
2142
- return true;
2143
- }
2144
- async function createBacklogMd(root) {
2145
- const backlogPath = join6(root, "BACKLOG.md");
2146
- try {
2147
- await access4(backlogPath);
2148
- return false;
2149
- } catch {
2150
- }
2151
- const name = root.split("/").pop() ?? "Project";
2152
- await writeFile2(backlogPath, `# ${name} - Backlog
2153
-
2154
- > Features discussed but deferred. Pick up when relevant.
2155
- > Priority: P0 = next sprint, P1 = soon, P2 = when relevant.
2156
- `);
2157
- log.success("Generated BACKLOG.md");
2158
- return true;
2159
- }
2160
- async function createClaudeignore(root, detected) {
2161
- const ignorePath = join6(root, ".claudeignore");
2162
- try {
2163
- await access4(ignorePath);
2164
- return false;
2165
- } catch {
2166
- }
2167
- const content = generateClaudeignore(detected);
2168
- await writeFile2(ignorePath, content);
2169
- log.success("Generated .claudeignore with language-specific ignore patterns");
2170
- return true;
2171
- }
2172
- async function createStarterRules(root) {
2173
- const rulesDir = join6(root, ".claude", "rules");
2174
- try {
2175
- await access4(rulesDir);
2176
- return false;
2177
- } catch {
2178
- }
2179
- await mkdir2(rulesDir, { recursive: true });
2180
- await writeFile2(
2181
- join6(rulesDir, "conventions.md"),
2182
- `# Project Conventions
2183
-
2184
- - Use conventional commits (feat:, fix:, docs:, refactor:, test:, chore:)
2185
- - Keep files under 400 lines, functions under 50 lines
2186
- - Handle errors explicitly \u2014 no empty catch blocks
2187
- - Validate input at system boundaries
2188
- `
2189
- );
2190
- log.success("Created .claude/rules/conventions.md with starter rules");
2191
- return true;
2192
- }
2193
- async function createEnhanceSkill(root) {
2194
- const skillDir = join6(root, ".claude", "skills", "lp-enhance");
2195
- const skillPath = join6(skillDir, "SKILL.md");
2196
- const globalPath = join6(homedir3(), ".claude", "skills", "lp-enhance", "SKILL.md");
2197
- const legacyProject = join6(root, ".claude", "commands", "lp-enhance.md");
2198
- const legacyGlobal = join6(homedir3(), ".claude", "commands", "lp-enhance.md");
2199
- if (await fileExists(skillPath) || await fileExists(globalPath) || await fileExists(legacyProject) || await fileExists(legacyGlobal)) return false;
2200
- await mkdir2(skillDir, { recursive: true });
2201
- await writeFile2(skillPath, generateEnhanceSkill());
2202
- log.success("Generated /lp-enhance skill (.claude/skills/lp-enhance/)");
2203
- return true;
2204
- }
2205
- async function updateEnhanceSkill(root) {
2206
- const projectPath = join6(root, ".claude", "skills", "lp-enhance", "SKILL.md");
2207
- const globalPath = join6(homedir3(), ".claude", "skills", "lp-enhance", "SKILL.md");
2208
- const targetPath = await fileExists(projectPath) ? projectPath : await fileExists(globalPath) ? globalPath : null;
2209
- if (!targetPath) return false;
2210
- await writeFile2(targetPath, generateEnhanceSkill());
2211
- log.success("Updated /lp-enhance skill to latest version");
2212
- return true;
2213
- }
2214
-
2215
1273
  // src/commands/doctor/watcher.ts
2216
1274
  import { readdir as readdir2, stat } from "fs/promises";
2217
- import { join as join7 } from "path";
1275
+ import { join as join4 } from "path";
2218
1276
  async function watchConfig(projectRoot) {
2219
1277
  await runAndDisplay(projectRoot);
2220
1278
  log.blank();
@@ -2237,16 +1295,16 @@ async function watchConfig(projectRoot) {
2237
1295
  }
2238
1296
  async function getFileSnapshot(projectRoot) {
2239
1297
  const files = [
2240
- join7(projectRoot, "CLAUDE.md"),
2241
- join7(projectRoot, ".claudeignore")
1298
+ join4(projectRoot, "CLAUDE.md"),
1299
+ join4(projectRoot, ".claudeignore")
2242
1300
  ];
2243
- const claudeDir = join7(projectRoot, ".claude");
1301
+ const claudeDir = join4(projectRoot, ".claude");
2244
1302
  try {
2245
1303
  const entries = await readdir2(claudeDir, { withFileTypes: true, recursive: true });
2246
1304
  for (const entry of entries) {
2247
1305
  if (entry.isFile()) {
2248
1306
  const parentPath = entry.parentPath ?? claudeDir;
2249
- files.push(join7(parentPath, entry.name));
1307
+ files.push(join4(parentPath, entry.name));
2250
1308
  }
2251
1309
  }
2252
1310
  } catch {
@@ -2390,12 +1448,12 @@ import { Command as Command3 } from "commander";
2390
1448
  import { select as select2 } from "@inquirer/prompts";
2391
1449
  import ora from "ora";
2392
1450
  import chalk2 from "chalk";
2393
- import { mkdir as mkdir4, writeFile as writeFile4 } from "fs/promises";
2394
- import { join as join10 } from "path";
1451
+ import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
1452
+ import { join as join7 } from "path";
2395
1453
 
2396
1454
  // src/commands/eval/loader.ts
2397
- import { readFile as readFile6, readdir as readdir3, access as access5 } from "fs/promises";
2398
- import { join as join8, resolve as resolve2, dirname as dirname2 } from "path";
1455
+ import { readFile as readFile3, readdir as readdir3, access as access3 } from "fs/promises";
1456
+ import { join as join5, resolve as resolve2, dirname as dirname2 } from "path";
2399
1457
  import { fileURLToPath } from "url";
2400
1458
  import { parse as parseYaml } from "yaml";
2401
1459
 
@@ -2509,7 +1567,7 @@ async function findScenariosDir() {
2509
1567
  }
2510
1568
  async function dirExists(path) {
2511
1569
  try {
2512
- await access5(path);
1570
+ await access3(path);
2513
1571
  return true;
2514
1572
  } catch {
2515
1573
  return false;
@@ -2518,14 +1576,14 @@ async function dirExists(path) {
2518
1576
  async function loadScenarios(options) {
2519
1577
  const { suite, customPath } = options;
2520
1578
  const scenarioDir = customPath ? resolve2(customPath) : await findScenariosDir();
2521
- const dirs = suite ? [join8(scenarioDir, suite)] : await getSubdirectories(scenarioDir);
1579
+ const dirs = suite ? [join5(scenarioDir, suite)] : await getSubdirectories(scenarioDir);
2522
1580
  const allDirs = [scenarioDir, ...dirs];
2523
1581
  const scenarios = [];
2524
1582
  for (const dir of allDirs) {
2525
1583
  const files = await listYamlFiles(dir);
2526
1584
  for (const file of files) {
2527
1585
  try {
2528
- const content = await readFile6(file, "utf-8");
1586
+ const content = await readFile3(file, "utf-8");
2529
1587
  const raw = parseYaml(content);
2530
1588
  const scenario = validateScenario(raw, file);
2531
1589
  scenarios.push(scenario);
@@ -2540,7 +1598,7 @@ async function loadScenarios(options) {
2540
1598
  async function getSubdirectories(dir) {
2541
1599
  try {
2542
1600
  const entries = await readdir3(dir, { withFileTypes: true });
2543
- return entries.filter((e) => e.isDirectory()).map((e) => join8(dir, e.name));
1601
+ return entries.filter((e) => e.isDirectory()).map((e) => join5(dir, e.name));
2544
1602
  } catch {
2545
1603
  return [];
2546
1604
  }
@@ -2548,22 +1606,22 @@ async function getSubdirectories(dir) {
2548
1606
  async function listYamlFiles(dir) {
2549
1607
  try {
2550
1608
  const entries = await readdir3(dir, { withFileTypes: true });
2551
- return entries.filter((e) => e.isFile() && (e.name.endsWith(".yaml") || e.name.endsWith(".yml"))).map((e) => join8(dir, e.name));
1609
+ return entries.filter((e) => e.isFile() && (e.name.endsWith(".yaml") || e.name.endsWith(".yml"))).map((e) => join5(dir, e.name));
2552
1610
  } catch {
2553
1611
  return [];
2554
1612
  }
2555
1613
  }
2556
1614
 
2557
1615
  // src/commands/eval/runner.ts
2558
- import { mkdir as mkdir3, writeFile as writeFile3, readFile as readFile7, readdir as readdir4, rm, cp } from "fs/promises";
2559
- import { join as join9, dirname as dirname3 } from "path";
1616
+ import { mkdir as mkdir2, writeFile as writeFile2, readFile as readFile4, readdir as readdir4, rm, cp } from "fs/promises";
1617
+ import { join as join6, dirname as dirname3 } from "path";
2560
1618
  import { tmpdir } from "os";
2561
1619
  import { randomUUID } from "crypto";
2562
1620
  import { execFile } from "child_process";
2563
1621
  import { promisify } from "util";
2564
1622
  var exec = promisify(execFile);
2565
1623
  async function runScenario(scenario, options) {
2566
- const sandboxDir = join9(tmpdir(), `claude-eval-${randomUUID()}`);
1624
+ const sandboxDir = join6(tmpdir(), `claude-eval-${randomUUID()}`);
2567
1625
  try {
2568
1626
  await setupSandbox(sandboxDir, scenario, options.projectRoot);
2569
1627
  await runClaudeInSandbox(sandboxDir, scenario.prompt, options.timeout, options.model);
@@ -2587,16 +1645,16 @@ async function runScenarioWithRetries(scenario, options) {
2587
1645
  return sorted[Math.floor(sorted.length / 2)];
2588
1646
  }
2589
1647
  async function setupSandbox(sandboxDir, scenario, projectRoot) {
2590
- await mkdir3(sandboxDir, { recursive: true });
1648
+ await mkdir2(sandboxDir, { recursive: true });
2591
1649
  for (const file of scenario.setup.files) {
2592
- const filePath = join9(sandboxDir, file.path);
2593
- await mkdir3(dirname3(filePath), { recursive: true });
2594
- await writeFile3(filePath, file.content);
1650
+ const filePath = join6(sandboxDir, file.path);
1651
+ await mkdir2(dirname3(filePath), { recursive: true });
1652
+ await writeFile2(filePath, file.content);
2595
1653
  }
2596
1654
  await copyProjectConfig(sandboxDir, projectRoot);
2597
1655
  if (scenario.setup.instructions) {
2598
- await writeFile3(
2599
- join9(sandboxDir, "CLAUDE.md"),
1656
+ await writeFile2(
1657
+ join6(sandboxDir, "CLAUDE.md"),
2600
1658
  `# Eval Scenario
2601
1659
 
2602
1660
  ${scenario.setup.instructions}
@@ -2617,20 +1675,20 @@ ${scenario.setup.instructions}
2617
1675
  ], { cwd: sandboxDir });
2618
1676
  }
2619
1677
  async function copyProjectConfig(sandboxDir, projectRoot) {
2620
- const claudeDir = join9(projectRoot, ".claude");
2621
- const sandboxClaudeDir = join9(sandboxDir, ".claude");
2622
- const settingsPath = join9(claudeDir, "settings.json");
1678
+ const claudeDir = join6(projectRoot, ".claude");
1679
+ const sandboxClaudeDir = join6(sandboxDir, ".claude");
1680
+ const settingsPath = join6(claudeDir, "settings.json");
2623
1681
  if (await fileExists(settingsPath)) {
2624
- await mkdir3(sandboxClaudeDir, { recursive: true });
2625
- await cp(settingsPath, join9(sandboxClaudeDir, "settings.json"));
1682
+ await mkdir2(sandboxClaudeDir, { recursive: true });
1683
+ await cp(settingsPath, join6(sandboxClaudeDir, "settings.json"));
2626
1684
  }
2627
- const rulesDir = join9(claudeDir, "rules");
1685
+ const rulesDir = join6(claudeDir, "rules");
2628
1686
  if (await fileExists(rulesDir)) {
2629
- await cp(rulesDir, join9(sandboxClaudeDir, "rules"), { recursive: true });
1687
+ await cp(rulesDir, join6(sandboxClaudeDir, "rules"), { recursive: true });
2630
1688
  }
2631
- const ignorePath = join9(projectRoot, ".claudeignore");
1689
+ const ignorePath = join6(projectRoot, ".claudeignore");
2632
1690
  if (await fileExists(ignorePath)) {
2633
- await cp(ignorePath, join9(sandboxDir, ".claudeignore"));
1691
+ await cp(ignorePath, join6(sandboxDir, ".claudeignore"));
2634
1692
  }
2635
1693
  }
2636
1694
  async function runClaudeInSandbox(cwd, prompt, timeout, model) {
@@ -2724,7 +1782,7 @@ async function evaluateSingleCheck(check, sandboxDir) {
2724
1782
  async function checkGrep(check, sandboxDir) {
2725
1783
  if (!check.pattern) return false;
2726
1784
  try {
2727
- const content = await readFile7(join9(sandboxDir, check.target), "utf-8");
1785
+ const content = await readFile4(join6(sandboxDir, check.target), "utf-8");
2728
1786
  let found;
2729
1787
  try {
2730
1788
  found = new RegExp(check.pattern).test(content);
@@ -2738,7 +1796,7 @@ async function checkGrep(check, sandboxDir) {
2738
1796
  }
2739
1797
  async function checkFilePresence(check, sandboxDir) {
2740
1798
  try {
2741
- await readFile7(join9(sandboxDir, check.target));
1799
+ await readFile4(join6(sandboxDir, check.target));
2742
1800
  return check.expect === "present";
2743
1801
  } catch {
2744
1802
  return check.expect === "absent";
@@ -2747,9 +1805,9 @@ async function checkFilePresence(check, sandboxDir) {
2747
1805
  async function checkMaxLines(check, sandboxDir) {
2748
1806
  const maxLines = parseInt(check.pattern ?? "800", 10);
2749
1807
  try {
2750
- const files = await listAllFiles(join9(sandboxDir, check.target));
1808
+ const files = await listAllFiles(join6(sandboxDir, check.target));
2751
1809
  for (const file of files) {
2752
- const content = await readFile7(file, "utf-8");
1810
+ const content = await readFile4(file, "utf-8");
2753
1811
  if (content.split("\n").length > maxLines) {
2754
1812
  return check.expect === "absent";
2755
1813
  }
@@ -2764,7 +1822,7 @@ async function listAllFiles(dir) {
2764
1822
  try {
2765
1823
  const entries = await readdir4(dir, { withFileTypes: true });
2766
1824
  for (const entry of entries) {
2767
- const fullPath = join9(dir, entry.name);
1825
+ const fullPath = join6(dir, entry.name);
2768
1826
  if (entry.isDirectory()) {
2769
1827
  results.push(...await listAllFiles(fullPath));
2770
1828
  } else {
@@ -2950,10 +2008,10 @@ async function saveEvalReport(results, projectRoot, suite, model) {
2950
2008
  lines.push("");
2951
2009
  }
2952
2010
  }
2953
- const evalDir = join10(projectRoot, ".claude", "eval");
2954
- await mkdir4(evalDir, { recursive: true });
2011
+ const evalDir = join7(projectRoot, ".claude", "eval");
2012
+ await mkdir3(evalDir, { recursive: true });
2955
2013
  const filename = `eval-${suite ?? "all"}-${timestamp}.md`;
2956
- await writeFile4(join10(evalDir, filename), lines.join("\n"));
2014
+ await writeFile3(join7(evalDir, filename), lines.join("\n"));
2957
2015
  log.success(`Report saved to .claude/eval/${filename}`);
2958
2016
  }
2959
2017
  async function checkClaudeCli() {
@@ -2970,12 +2028,12 @@ async function checkClaudeCli() {
2970
2028
 
2971
2029
  // src/commands/memory/index.ts
2972
2030
  import { readFileSync } from "fs";
2973
- import { join as join11 } from "path";
2031
+ import { join as join8 } from "path";
2974
2032
  import { Command as Command4 } from "commander";
2975
2033
  import { confirm as confirm2 } from "@inquirer/prompts";
2976
2034
  function isMemoryInstalled() {
2977
2035
  const cwd = process.cwd();
2978
- return hasMemoryHook(join11(cwd, ".claude", "settings.json")) || hasMemoryHook(join11(cwd, ".claude", "settings.local.json"));
2036
+ return hasMemoryHook(join8(cwd, ".claude", "settings.json")) || hasMemoryHook(join8(cwd, ".claude", "settings.local.json"));
2979
2037
  }
2980
2038
  function hasMemoryHook(path) {
2981
2039
  try {
@@ -2998,14 +2056,14 @@ function createMemoryCommand() {
2998
2056
  log.error("Knowledge base not set up yet. Run `claude-launchpad memory` first.");
2999
2057
  return;
3000
2058
  }
3001
- const { requireMemoryDeps } = await import("./require-deps-NKRCPVAO.js");
2059
+ const { requireMemoryDeps } = await import("./require-deps-SUGLVBM2.js");
3002
2060
  await requireMemoryDeps();
3003
- const { startTui } = await import("./tui-XIYOOUP6.js");
2061
+ const { startTui } = await import("./tui-DKWRY5PT.js");
3004
2062
  await startTui();
3005
2063
  return;
3006
2064
  }
3007
2065
  if (!isMemoryInstalled()) {
3008
- const { detectExistingSetup } = await import("./install-MVATZUXZ.js");
2066
+ const { detectExistingSetup } = await import("./install-PFTFTNIF.js");
3009
2067
  const existing = detectExistingSetup(process.cwd());
3010
2068
  if (existing) {
3011
2069
  const location = existing === "local" ? ".claude/CLAUDE.md + settings.local.json" : "CLAUDE.md + settings.json";
@@ -3031,18 +2089,18 @@ function createMemoryCommand() {
3031
2089
  log.info("Skipped.");
3032
2090
  return;
3033
2091
  }
3034
- const { runInstall } = await import("./install-MVATZUXZ.js");
2092
+ const { runInstall } = await import("./install-PFTFTNIF.js");
3035
2093
  await runInstall({});
3036
2094
  } else {
3037
- const { requireMemoryDeps } = await import("./require-deps-NKRCPVAO.js");
2095
+ const { requireMemoryDeps } = await import("./require-deps-SUGLVBM2.js");
3038
2096
  await requireMemoryDeps();
3039
- const { runStats } = await import("./stats-QUBHHPV7.js");
2097
+ const { runStats } = await import("./stats-7WFCVXBX.js");
3040
2098
  await runStats({});
3041
2099
  }
3042
2100
  });
3043
2101
  memory.addCommand(
3044
2102
  new Command4("context").description("Load session context (hook handler)").option("--json", "JSON output").action(async (opts) => {
3045
- const { runContext } = await import("./context-CWJUUTTU.js");
2103
+ const { runContext } = await import("./context-53PKQOMI.js");
3046
2104
  await runContext(opts);
3047
2105
  }).helpCommand(false),
3048
2106
  { hidden: true }
@@ -3056,13 +2114,13 @@ function createMemoryCommand() {
3056
2114
  );
3057
2115
  memory.addCommand(
3058
2116
  new Command4("push").description("Push current project's memories to GitHub Gist").option("--all", "Push all projects").option("-y, --yes", "Skip confirmation prompt").action(async (opts) => {
3059
- const { runPush } = await import("./push-74XC5CUK.js");
2117
+ const { runPush } = await import("./push-JOCEW3VG.js");
3060
2118
  await runPush(opts);
3061
2119
  })
3062
2120
  );
3063
2121
  memory.addCommand(
3064
2122
  new Command4("pull").description("Pull current project's memories from GitHub Gist").option("--all", "Pull all projects").action(async (opts) => {
3065
- const { runPull } = await import("./pull-YOESZ3UC.js");
2123
+ const { runPull } = await import("./pull-4QTS57DQ.js");
3066
2124
  await runPull(opts);
3067
2125
  })
3068
2126
  );
@@ -3070,8 +2128,8 @@ function createMemoryCommand() {
3070
2128
  }
3071
2129
 
3072
2130
  // src/cli.ts
3073
- var program = new Command5().name("claude-launchpad").description("CLI toolkit that makes Claude Code setups measurably good").version("0.16.0", "-v, --version").action(async () => {
3074
- const hasConfig = await fileExists(join12(process.cwd(), "CLAUDE.md")) || await fileExists(join12(process.cwd(), ".claude", "settings.json"));
2131
+ var program = new Command5().name("claude-launchpad").description("CLI toolkit that makes Claude Code setups measurably good").version("0.16.1", "-v, --version").action(async () => {
2132
+ const hasConfig = await fileExists(join9(process.cwd(), "CLAUDE.md")) || await fileExists(join9(process.cwd(), ".claude", "settings.json"));
3075
2133
  if (hasConfig) {
3076
2134
  await program.commands.find((c) => c.name() === "doctor")?.parseAsync([], { from: "user" });
3077
2135
  } else {