claude-launchpad 0.15.2 → 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.
- package/README.md +3 -3
- package/dist/{chunk-4D3EBDNB.js → chunk-24VLPHJU.js} +16 -2
- package/dist/chunk-24VLPHJU.js.map +1 -0
- package/dist/{chunk-Z6FBT44W.js → chunk-IZFQTQ4C.js} +2 -2
- package/dist/{chunk-IPVN6SO4.js → chunk-JQDMBE7W.js} +14 -65
- package/dist/chunk-JQDMBE7W.js.map +1 -0
- package/dist/{chunk-EDKY7JWY.js → chunk-NLZGJHE5.js} +3 -3
- package/dist/chunk-S4WIADYE.js +1139 -0
- package/dist/chunk-S4WIADYE.js.map +1 -0
- package/dist/{chunk-AHEX2RG7.js → chunk-SVWFSAYP.js} +3 -3
- package/dist/chunk-TSTTFR4B.js +68 -0
- package/dist/chunk-TSTTFR4B.js.map +1 -0
- package/dist/cli.js +224 -991
- package/dist/cli.js.map +1 -1
- package/dist/commands/memory/server.js +4 -4
- package/dist/{context-XPXKLF5V.js → context-53PKQOMI.js} +6 -6
- package/dist/{install-UXO5BISS.js → install-PFTFTNIF.js} +57 -16
- package/dist/install-PFTFTNIF.js.map +1 -0
- package/dist/{pull-3O4B6EL2.js → pull-4QTS57DQ.js} +13 -11
- package/dist/{pull-3O4B6EL2.js.map → pull-4QTS57DQ.js.map} +1 -1
- package/dist/{push-BGN3DVV5.js → push-JOCEW3VG.js} +22 -12
- package/dist/push-JOCEW3VG.js.map +1 -0
- package/dist/{require-deps-NKRCPVAO.js → require-deps-SUGLVBM2.js} +3 -3
- package/dist/{stats-P7S3FTCQ.js → stats-7WFCVXBX.js} +7 -7
- package/dist/{tui-GUCKDWDM.js → tui-DKWRY5PT.js} +116 -49
- package/dist/tui-DKWRY5PT.js.map +1 -0
- package/package.json +3 -2
- package/dist/chunk-4D3EBDNB.js.map +0 -1
- package/dist/chunk-IPVN6SO4.js.map +0 -1
- package/dist/chunk-KOSJII4R.js +0 -62
- package/dist/chunk-KOSJII4R.js.map +0 -1
- package/dist/chunk-RJGXPH7P.js +0 -107
- package/dist/chunk-RJGXPH7P.js.map +0 -1
- package/dist/install-UXO5BISS.js.map +0 -1
- package/dist/push-BGN3DVV5.js.map +0 -1
- package/dist/tui-GUCKDWDM.js.map +0 -1
- /package/dist/{chunk-Z6FBT44W.js.map → chunk-IZFQTQ4C.js.map} +0 -0
- /package/dist/{chunk-EDKY7JWY.js.map → chunk-NLZGJHE5.js.map} +0 -0
- /package/dist/{chunk-AHEX2RG7.js.map → chunk-SVWFSAYP.js.map} +0 -0
- /package/dist/{context-XPXKLF5V.js.map → context-53PKQOMI.js.map} +0 -0
- /package/dist/{require-deps-NKRCPVAO.js.map → require-deps-SUGLVBM2.js.map} +0 -0
- /package/dist/{stats-P7S3FTCQ.js.map → stats-7WFCVXBX.js.map} +0 -0
|
@@ -0,0 +1,1139 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __export = (target, all) => {
|
|
4
|
+
for (var name in all)
|
|
5
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
// src/lib/fs-utils.ts
|
|
9
|
+
import { readFile, access } from "fs/promises";
|
|
10
|
+
async function fileExists(path) {
|
|
11
|
+
try {
|
|
12
|
+
await access(path);
|
|
13
|
+
return true;
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function readFileOrNull(path) {
|
|
19
|
+
try {
|
|
20
|
+
return await readFile(path, "utf-8");
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function readJsonOrNull(path) {
|
|
26
|
+
try {
|
|
27
|
+
const content = await readFile(path, "utf-8");
|
|
28
|
+
return JSON.parse(content);
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/lib/settings.ts
|
|
35
|
+
import { readFile as readFile2, writeFile, mkdir } from "fs/promises";
|
|
36
|
+
import { join } from "path";
|
|
37
|
+
async function readSettingsJson(root) {
|
|
38
|
+
const path = join(root, ".claude", "settings.json");
|
|
39
|
+
try {
|
|
40
|
+
const content = await readFile2(path, "utf-8");
|
|
41
|
+
return JSON.parse(content);
|
|
42
|
+
} catch {
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function writeSettingsJson(root, settings) {
|
|
47
|
+
const dir = join(root, ".claude");
|
|
48
|
+
await mkdir(dir, { recursive: true });
|
|
49
|
+
await writeFile(join(dir, "settings.json"), JSON.stringify(settings, null, 2) + "\n");
|
|
50
|
+
}
|
|
51
|
+
async function readSettingsLocalJson(root) {
|
|
52
|
+
const path = join(root, ".claude", "settings.local.json");
|
|
53
|
+
try {
|
|
54
|
+
const content = await readFile2(path, "utf-8");
|
|
55
|
+
return JSON.parse(content);
|
|
56
|
+
} catch {
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function writeSettingsLocalJson(root, settings) {
|
|
61
|
+
const dir = join(root, ".claude");
|
|
62
|
+
await mkdir(dir, { recursive: true });
|
|
63
|
+
await writeFile(join(dir, "settings.local.json"), JSON.stringify(settings, null, 2) + "\n");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/lib/memory-placement.ts
|
|
67
|
+
import { select } from "@inquirer/prompts";
|
|
68
|
+
function hasMemoryPermissions(settings) {
|
|
69
|
+
const permissions = settings.permissions;
|
|
70
|
+
const allow = permissions?.allow ?? [];
|
|
71
|
+
return allow.some((p) => p.includes("agentic-memory"));
|
|
72
|
+
}
|
|
73
|
+
async function getMemoryPlacement(root, skipPrompt = false) {
|
|
74
|
+
const local = await readSettingsLocalJson(root);
|
|
75
|
+
const persisted = local.memoryPlacement;
|
|
76
|
+
if (persisted === "shared" || persisted === "local") {
|
|
77
|
+
return persisted;
|
|
78
|
+
}
|
|
79
|
+
if (hasMemoryPermissions(local)) {
|
|
80
|
+
await writeSettingsLocalJson(root, { ...local, memoryPlacement: "local" });
|
|
81
|
+
return "local";
|
|
82
|
+
}
|
|
83
|
+
const shared = await readSettingsJson(root);
|
|
84
|
+
if (hasMemoryPermissions(shared)) {
|
|
85
|
+
await writeSettingsLocalJson(root, { ...local, memoryPlacement: "shared" });
|
|
86
|
+
return "shared";
|
|
87
|
+
}
|
|
88
|
+
if (skipPrompt) return "shared";
|
|
89
|
+
const choice = await select({
|
|
90
|
+
message: "Where should memory config go?",
|
|
91
|
+
choices: [
|
|
92
|
+
{ value: "shared", name: "Shared (team sees it) \u2014 CLAUDE.md + settings.json" },
|
|
93
|
+
{ value: "local", name: "Local (only you) \u2014 .claude/CLAUDE.md + settings.local.json" }
|
|
94
|
+
]
|
|
95
|
+
});
|
|
96
|
+
await writeSettingsLocalJson(root, { ...local, memoryPlacement: choice });
|
|
97
|
+
return choice;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/lib/output.ts
|
|
101
|
+
import chalk from "chalk";
|
|
102
|
+
|
|
103
|
+
// src/commands/doctor/fixer.ts
|
|
104
|
+
import { readFile as readFile4, writeFile as writeFile2, mkdir as mkdir2, access as access2 } from "fs/promises";
|
|
105
|
+
import { join as join4 } from "path";
|
|
106
|
+
import { homedir } from "os";
|
|
107
|
+
|
|
108
|
+
// src/lib/detect.ts
|
|
109
|
+
import { join as join2, basename } from "path";
|
|
110
|
+
async function detectProject(root) {
|
|
111
|
+
const name = basename(root);
|
|
112
|
+
const [pkgJson, goMod, pyProject, gemfile, cargo, pubspec, composerJson, pomXml, buildGradleGroovy, buildGradleKts, packageSwift, mixExs, csproj, lockfiles] = await Promise.all([
|
|
113
|
+
readJsonOrNull(join2(root, "package.json")),
|
|
114
|
+
fileExists(join2(root, "go.mod")),
|
|
115
|
+
readFileOrNull(join2(root, "pyproject.toml")),
|
|
116
|
+
fileExists(join2(root, "Gemfile")),
|
|
117
|
+
fileExists(join2(root, "Cargo.toml")),
|
|
118
|
+
fileExists(join2(root, "pubspec.yaml")),
|
|
119
|
+
readJsonOrNull(join2(root, "composer.json")),
|
|
120
|
+
fileExists(join2(root, "pom.xml")),
|
|
121
|
+
fileExists(join2(root, "build.gradle")),
|
|
122
|
+
fileExists(join2(root, "build.gradle.kts")),
|
|
123
|
+
fileExists(join2(root, "Package.swift")),
|
|
124
|
+
fileExists(join2(root, "mix.exs")),
|
|
125
|
+
globExists(root, "*.csproj"),
|
|
126
|
+
detectLockfiles(root)
|
|
127
|
+
]);
|
|
128
|
+
const buildGradle = buildGradleGroovy || buildGradleKts;
|
|
129
|
+
const manifests = {
|
|
130
|
+
pkgJson,
|
|
131
|
+
goMod,
|
|
132
|
+
pyProject,
|
|
133
|
+
gemfile,
|
|
134
|
+
cargo,
|
|
135
|
+
pubspec,
|
|
136
|
+
composerJson,
|
|
137
|
+
pomXml,
|
|
138
|
+
buildGradle,
|
|
139
|
+
packageSwift,
|
|
140
|
+
mixExs,
|
|
141
|
+
csproj
|
|
142
|
+
};
|
|
143
|
+
const language = detectLanguage(manifests);
|
|
144
|
+
const framework = detectFramework(manifests);
|
|
145
|
+
const packageManager = detectPackageManager(manifests, lockfiles);
|
|
146
|
+
const scripts = detectScripts({ pkgJson, pyProject, goMod, gemfile, composerJson, language });
|
|
147
|
+
return {
|
|
148
|
+
name,
|
|
149
|
+
language,
|
|
150
|
+
framework,
|
|
151
|
+
packageManager,
|
|
152
|
+
hasTests: scripts.testCommand !== null,
|
|
153
|
+
hasLinter: scripts.lintCommand !== null,
|
|
154
|
+
hasFormatter: scripts.formatCommand !== null,
|
|
155
|
+
...scripts
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function detectLanguage(m) {
|
|
159
|
+
if (m.pkgJson?.devDependencies?.typescript || m.pkgJson?.dependencies?.typescript) return "TypeScript";
|
|
160
|
+
if (m.pkgJson) return "JavaScript";
|
|
161
|
+
if (m.goMod) return "Go";
|
|
162
|
+
if (m.pyProject) return "Python";
|
|
163
|
+
if (m.gemfile) return "Ruby";
|
|
164
|
+
if (m.cargo) return "Rust";
|
|
165
|
+
if (m.pubspec) return "Dart";
|
|
166
|
+
if (m.composerJson) return "PHP";
|
|
167
|
+
if (m.buildGradle) return "Kotlin";
|
|
168
|
+
if (m.pomXml) return "Java";
|
|
169
|
+
if (m.packageSwift) return "Swift";
|
|
170
|
+
if (m.mixExs) return "Elixir";
|
|
171
|
+
if (m.csproj) return "C#";
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
function detectFramework(m) {
|
|
175
|
+
const deps = { ...m.pkgJson?.dependencies, ...m.pkgJson?.devDependencies };
|
|
176
|
+
if (deps.next) return "Next.js";
|
|
177
|
+
if (deps.nuxt) return "Nuxt";
|
|
178
|
+
if (deps.svelte || deps["@sveltejs/kit"]) return "SvelteKit";
|
|
179
|
+
if (deps.astro) return "Astro";
|
|
180
|
+
if (deps["@angular/core"]) return "Angular";
|
|
181
|
+
if (deps.remix || deps["@remix-run/react"]) return "Remix";
|
|
182
|
+
if (deps.vue) return "Vue";
|
|
183
|
+
if (deps.react && !deps.next) return "React";
|
|
184
|
+
if (deps.express) return "Express";
|
|
185
|
+
if (deps.fastify) return "Fastify";
|
|
186
|
+
if (deps.hono) return "Hono";
|
|
187
|
+
if (deps.nestjs || deps["@nestjs/core"]) return "NestJS";
|
|
188
|
+
if (m.pyProject) {
|
|
189
|
+
if (m.pyProject.includes("fastapi")) return "FastAPI";
|
|
190
|
+
if (m.pyProject.includes("django")) return "Django";
|
|
191
|
+
if (m.pyProject.includes("flask")) return "Flask";
|
|
192
|
+
}
|
|
193
|
+
if (m.composerJson) {
|
|
194
|
+
const phpDeps = { ...m.composerJson.require, ...m.composerJson["require-dev"] };
|
|
195
|
+
if (phpDeps["laravel/framework"]) return "Laravel";
|
|
196
|
+
if (phpDeps["symfony/framework-bundle"]) return "Symfony";
|
|
197
|
+
}
|
|
198
|
+
if (m.gemfile) return "Rails";
|
|
199
|
+
if (m.buildGradle) return "Gradle";
|
|
200
|
+
if (m.pomXml) return "Maven";
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
async function detectLockfiles(root) {
|
|
204
|
+
const [pnpmLock, yarnLock, bunLock, npmLock] = await Promise.all([
|
|
205
|
+
fileExists(join2(root, "pnpm-lock.yaml")),
|
|
206
|
+
fileExists(join2(root, "yarn.lock")),
|
|
207
|
+
fileExists(join2(root, "bun.lockb")),
|
|
208
|
+
fileExists(join2(root, "package-lock.json"))
|
|
209
|
+
]);
|
|
210
|
+
return { pnpmLock, yarnLock, bunLock, npmLock };
|
|
211
|
+
}
|
|
212
|
+
function detectPackageManager(m, lockfiles) {
|
|
213
|
+
if (m.pkgJson) {
|
|
214
|
+
const pm = m.pkgJson.packageManager;
|
|
215
|
+
if (pm?.startsWith("pnpm")) return "pnpm";
|
|
216
|
+
if (pm?.startsWith("yarn")) return "yarn";
|
|
217
|
+
if (pm?.startsWith("bun")) return "bun";
|
|
218
|
+
if (pm?.startsWith("npm")) return "npm";
|
|
219
|
+
if (lockfiles.pnpmLock) return "pnpm";
|
|
220
|
+
if (lockfiles.yarnLock) return "yarn";
|
|
221
|
+
if (lockfiles.bunLock) return "bun";
|
|
222
|
+
if (lockfiles.npmLock) return "npm";
|
|
223
|
+
return "npm";
|
|
224
|
+
}
|
|
225
|
+
if (m.goMod) return "go modules";
|
|
226
|
+
if (m.pyProject) {
|
|
227
|
+
if (m.pyProject.includes("[tool.uv]")) return "uv";
|
|
228
|
+
if (m.pyProject.includes("[tool.poetry]")) return "poetry";
|
|
229
|
+
return "pip";
|
|
230
|
+
}
|
|
231
|
+
if (m.gemfile) return "bundler";
|
|
232
|
+
if (m.cargo) return "cargo";
|
|
233
|
+
if (m.composerJson) return "composer";
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
var LANGUAGE_SCRIPTS = {
|
|
237
|
+
Go: { devCommand: "go run .", buildCommand: "go build .", testCommand: "go test ./...", lintCommand: "golangci-lint run", formatCommand: "gofmt -w ." },
|
|
238
|
+
Ruby: { devCommand: "bin/dev", buildCommand: null, testCommand: "bin/rails test", lintCommand: "bin/rubocop", formatCommand: null },
|
|
239
|
+
PHP: { devCommand: "php artisan serve", buildCommand: null, testCommand: "php artisan test", lintCommand: "vendor/bin/phpstan analyse", formatCommand: "vendor/bin/pint" },
|
|
240
|
+
Rust: { devCommand: "cargo run", buildCommand: "cargo build", testCommand: "cargo test", lintCommand: "cargo clippy", formatCommand: "cargo fmt" },
|
|
241
|
+
Java: { devCommand: null, buildCommand: "mvn package", testCommand: "mvn test", lintCommand: null, formatCommand: null },
|
|
242
|
+
Kotlin: { devCommand: null, buildCommand: "mvn package", testCommand: "mvn test", lintCommand: null, formatCommand: null },
|
|
243
|
+
Swift: { devCommand: null, buildCommand: "swift build", testCommand: "swift test", lintCommand: "swiftlint", formatCommand: "swift-format format -r ." },
|
|
244
|
+
Elixir: { devCommand: "mix phx.server", buildCommand: "mix compile", testCommand: "mix test", lintCommand: "mix credo", formatCommand: "mix format" },
|
|
245
|
+
"C#": { devCommand: "dotnet run", buildCommand: "dotnet build", testCommand: "dotnet test", lintCommand: null, formatCommand: "dotnet format" }
|
|
246
|
+
};
|
|
247
|
+
function detectScripts(m) {
|
|
248
|
+
if (m.pkgJson) {
|
|
249
|
+
const scripts = m.pkgJson.scripts ?? {};
|
|
250
|
+
const run = pmRun(m.pkgJson);
|
|
251
|
+
return {
|
|
252
|
+
devCommand: scripts.dev ? `${run} dev` : null,
|
|
253
|
+
buildCommand: scripts.build ? `${run} build` : null,
|
|
254
|
+
testCommand: scripts.test ? `${run} test` : null,
|
|
255
|
+
lintCommand: scripts.lint ? `${run} lint` : null,
|
|
256
|
+
formatCommand: scripts.format ? `${run} format` : null
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
if (m.language === "Python") {
|
|
260
|
+
const r = m.pyProject?.includes("[tool.uv]") ? "uv run" : "python -m";
|
|
261
|
+
return { devCommand: null, buildCommand: null, testCommand: `${r} pytest`, lintCommand: `${r} ruff check .`, formatCommand: `${r} ruff format .` };
|
|
262
|
+
}
|
|
263
|
+
if (m.language && LANGUAGE_SCRIPTS[m.language]) {
|
|
264
|
+
return LANGUAGE_SCRIPTS[m.language];
|
|
265
|
+
}
|
|
266
|
+
return { devCommand: null, buildCommand: null, testCommand: null, lintCommand: null, formatCommand: null };
|
|
267
|
+
}
|
|
268
|
+
function pmRun(pkg) {
|
|
269
|
+
const pm = pkg.packageManager;
|
|
270
|
+
if (pm?.startsWith("pnpm")) return "pnpm";
|
|
271
|
+
if (pm?.startsWith("yarn")) return "yarn";
|
|
272
|
+
if (pm?.startsWith("bun")) return "bun";
|
|
273
|
+
return "npm run";
|
|
274
|
+
}
|
|
275
|
+
async function globExists(dir, pattern) {
|
|
276
|
+
const { readdir } = await import("fs/promises");
|
|
277
|
+
try {
|
|
278
|
+
const entries = await readdir(dir);
|
|
279
|
+
return entries.some((e) => e.endsWith(pattern.replace("*", "")));
|
|
280
|
+
} catch {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// src/commands/init/generators/claudeignore.ts
|
|
286
|
+
function generateClaudeignore(detected) {
|
|
287
|
+
const sections = ["# Generated by claude-launchpad"];
|
|
288
|
+
sections.push(`
|
|
289
|
+
# Dependencies
|
|
290
|
+
node_modules/
|
|
291
|
+
.pnp/
|
|
292
|
+
.yarn/
|
|
293
|
+
|
|
294
|
+
# Build output
|
|
295
|
+
dist/
|
|
296
|
+
build/
|
|
297
|
+
out/
|
|
298
|
+
.next/
|
|
299
|
+
.nuxt/
|
|
300
|
+
.output/
|
|
301
|
+
.svelte-kit/
|
|
302
|
+
.vercel/
|
|
303
|
+
.turbo/
|
|
304
|
+
|
|
305
|
+
# Package manager
|
|
306
|
+
pnpm-lock.yaml
|
|
307
|
+
package-lock.json
|
|
308
|
+
yarn.lock
|
|
309
|
+
bun.lockb
|
|
310
|
+
|
|
311
|
+
# IDE & OS
|
|
312
|
+
.vscode/
|
|
313
|
+
.idea/
|
|
314
|
+
*.swp
|
|
315
|
+
*.swo
|
|
316
|
+
.DS_Store
|
|
317
|
+
Thumbs.db
|
|
318
|
+
|
|
319
|
+
# Test & coverage
|
|
320
|
+
coverage/
|
|
321
|
+
.nyc_output/
|
|
322
|
+
__snapshots__/
|
|
323
|
+
|
|
324
|
+
# Environment (should never be read)
|
|
325
|
+
.env
|
|
326
|
+
.env.*
|
|
327
|
+
!.env.example`);
|
|
328
|
+
const lang = detected.language;
|
|
329
|
+
if (lang === "Python") {
|
|
330
|
+
sections.push(`
|
|
331
|
+
# Python
|
|
332
|
+
__pycache__/
|
|
333
|
+
*.pyc
|
|
334
|
+
*.pyo
|
|
335
|
+
.venv/
|
|
336
|
+
venv/
|
|
337
|
+
.mypy_cache/
|
|
338
|
+
.ruff_cache/
|
|
339
|
+
.pytest_cache/
|
|
340
|
+
*.egg-info/`);
|
|
341
|
+
}
|
|
342
|
+
if (lang === "Go") {
|
|
343
|
+
sections.push(`
|
|
344
|
+
# Go
|
|
345
|
+
bin/
|
|
346
|
+
vendor/`);
|
|
347
|
+
}
|
|
348
|
+
if (lang === "Rust") {
|
|
349
|
+
sections.push(`
|
|
350
|
+
# Rust
|
|
351
|
+
target/
|
|
352
|
+
Cargo.lock`);
|
|
353
|
+
}
|
|
354
|
+
if (lang === "Ruby") {
|
|
355
|
+
sections.push(`
|
|
356
|
+
# Ruby
|
|
357
|
+
vendor/bundle/
|
|
358
|
+
.bundle/
|
|
359
|
+
tmp/
|
|
360
|
+
log/`);
|
|
361
|
+
}
|
|
362
|
+
if (lang === "Java" || lang === "Kotlin") {
|
|
363
|
+
sections.push(`
|
|
364
|
+
# JVM
|
|
365
|
+
target/
|
|
366
|
+
build/
|
|
367
|
+
.gradle/
|
|
368
|
+
*.class
|
|
369
|
+
*.jar`);
|
|
370
|
+
}
|
|
371
|
+
if (lang === "Dart") {
|
|
372
|
+
sections.push(`
|
|
373
|
+
# Dart/Flutter
|
|
374
|
+
.dart_tool/
|
|
375
|
+
.packages
|
|
376
|
+
build/`);
|
|
377
|
+
}
|
|
378
|
+
if (lang === "PHP") {
|
|
379
|
+
sections.push(`
|
|
380
|
+
# PHP
|
|
381
|
+
vendor/
|
|
382
|
+
composer.lock`);
|
|
383
|
+
}
|
|
384
|
+
if (lang === "C#") {
|
|
385
|
+
sections.push(`
|
|
386
|
+
# .NET
|
|
387
|
+
bin/
|
|
388
|
+
obj/
|
|
389
|
+
*.dll`);
|
|
390
|
+
}
|
|
391
|
+
if (lang === "Elixir") {
|
|
392
|
+
sections.push(`
|
|
393
|
+
# Elixir
|
|
394
|
+
_build/
|
|
395
|
+
deps/
|
|
396
|
+
.elixir_ls/`);
|
|
397
|
+
}
|
|
398
|
+
if (lang === "Swift") {
|
|
399
|
+
sections.push(`
|
|
400
|
+
# Swift
|
|
401
|
+
.build/
|
|
402
|
+
DerivedData/
|
|
403
|
+
*.xcuserdata`);
|
|
404
|
+
}
|
|
405
|
+
return sections.join("\n") + "\n";
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// src/commands/init/generators/skill-enhance.ts
|
|
409
|
+
var ENHANCE_SKILL_VERSION = 5;
|
|
410
|
+
function generateEnhanceSkill() {
|
|
411
|
+
return [
|
|
412
|
+
"---",
|
|
413
|
+
"name: lp-enhance",
|
|
414
|
+
"description: |",
|
|
415
|
+
" AI-improve your CLAUDE.md based on codebase analysis. Fills in architecture, conventions, guardrails, and suggests hooks and MCP servers.",
|
|
416
|
+
' TRIGGER when: user runs /lp-enhance, asks to "improve CLAUDE.md", "fill in architecture", or after major refactors.',
|
|
417
|
+
" DO NOT TRIGGER when: user is editing CLAUDE.md manually, doing normal coding, or running doctor/eval.",
|
|
418
|
+
"allowed-tools: Read, Glob, Grep, Edit, Write",
|
|
419
|
+
"argument-hint: (no arguments needed)",
|
|
420
|
+
"---",
|
|
421
|
+
"",
|
|
422
|
+
`<!-- lp-enhance-version: ${ENHANCE_SKILL_VERSION} -->`,
|
|
423
|
+
"",
|
|
424
|
+
"# lp-enhance - AI-powered CLAUDE.md improver",
|
|
425
|
+
"",
|
|
426
|
+
"Read CLAUDE.md and the project's codebase, then update CLAUDE.md to fill in missing or incomplete sections.",
|
|
427
|
+
"",
|
|
428
|
+
"## Phase 1: Research",
|
|
429
|
+
"",
|
|
430
|
+
"1. Read CLAUDE.md (if it exists)",
|
|
431
|
+
"2. Read .claude/settings.json (hooks, permissions, MCP)",
|
|
432
|
+
"3. Read .claude/rules/*.md (existing rules)",
|
|
433
|
+
"4. Read .claudeignore (if it exists)",
|
|
434
|
+
"5. Scan src/ directory structure (top-level dirs, key files)",
|
|
435
|
+
"6. Read package.json / go.mod / pyproject.toml for stack detection",
|
|
436
|
+
"7. Check for monorepo indicators (workspaces, nx.json, lerna.json)",
|
|
437
|
+
"8. Check scenarios/ directory for existing eval scenarios",
|
|
438
|
+
"",
|
|
439
|
+
"**Done when:** you have a mental model of the stack, architecture, and existing config.",
|
|
440
|
+
"",
|
|
441
|
+
"## Phase 2: Plan",
|
|
442
|
+
"",
|
|
443
|
+
"Count current CLAUDE.md actionable lines. Budget is 200 lines max. Plan which sections to add or improve:",
|
|
444
|
+
"",
|
|
445
|
+
"1. **## Stack** - detect language, framework, package manager",
|
|
446
|
+
"2. **## Architecture** - 3-5 bullets describing codebase shape",
|
|
447
|
+
"3. **## Conventions** - max 8 key patterns. Overflow to .claude/rules/conventions.md",
|
|
448
|
+
"4. **## Off-Limits** - max 8 guardrails specific to this project",
|
|
449
|
+
"5. **## Memory** - ONLY if agentic-memory is configured in settings.json. Max 6 bullets.",
|
|
450
|
+
"6. **## Key Decisions** - only decisions that affect how Claude works in this codebase",
|
|
451
|
+
"",
|
|
452
|
+
"7. **Skill Authoring** - if .claude/rules/conventions.md lacks a Skill Authoring section, plan to add one",
|
|
453
|
+
"",
|
|
454
|
+
"If any section would exceed 8 bullets, plan a .claude/rules/ file for the overflow.",
|
|
455
|
+
"",
|
|
456
|
+
"**Done when:** you know exactly what to add/change and the line count stays under 200.",
|
|
457
|
+
"",
|
|
458
|
+
"## Phase 3: Execute",
|
|
459
|
+
"",
|
|
460
|
+
"Edit CLAUDE.md with the planned changes. Then:",
|
|
461
|
+
"",
|
|
462
|
+
"1. Create or update .claude/rules/ files for overflow content",
|
|
463
|
+
"2. Generate path-scoped rules if the project has distinct areas (see below)",
|
|
464
|
+
"3. Review .claudeignore and print suggestions (see below)",
|
|
465
|
+
"4. Generate 2-3 custom eval scenarios in scenarios/custom/ (see below)",
|
|
466
|
+
"5. Verify line count is under 200",
|
|
467
|
+
"",
|
|
468
|
+
"**Rules:**",
|
|
469
|
+
"- Don't remove existing content, only add or improve",
|
|
470
|
+
"- Be specific to THIS project, not generic advice",
|
|
471
|
+
"- Use bullet points, not paragraphs",
|
|
472
|
+
"",
|
|
473
|
+
"## Phase 4: Verify",
|
|
474
|
+
"",
|
|
475
|
+
"1. Run `claude-launchpad doctor` to check the score improved",
|
|
476
|
+
"2. Print suggested hooks (exact JSON) for .claude/settings.json but don't modify it",
|
|
477
|
+
"3. Print suggested MCP servers if external services detected (Postgres, Redis, Stripe, etc.)",
|
|
478
|
+
'4. If eval scenarios were generated, print: "Run this in your terminal (not inside Claude Code): `claude-launchpad eval --scenarios scenarios/ --runs 1`"',
|
|
479
|
+
"",
|
|
480
|
+
"**Done when:** doctor score is equal or higher, suggestions printed, eval scenarios created if applicable.",
|
|
481
|
+
"",
|
|
482
|
+
"## Path-scoped rules generation",
|
|
483
|
+
"",
|
|
484
|
+
"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.",
|
|
485
|
+
"",
|
|
486
|
+
"**How to detect areas:**",
|
|
487
|
+
"1. List top-level directories under src/ (or equivalent). Each distinct area (api, components, lib, tests) is a candidate.",
|
|
488
|
+
"2. Check for monorepo indicators: workspaces in package.json, pnpm-workspace.yaml, nx.json, lerna.json. Each workspace is a candidate.",
|
|
489
|
+
"3. Check for docs/, tests/, scripts/ as separate scopes.",
|
|
490
|
+
"",
|
|
491
|
+
"**For each detected area, create a rules file with this format:**",
|
|
492
|
+
"",
|
|
493
|
+
"---",
|
|
494
|
+
'paths: ["src/api/**"]',
|
|
495
|
+
"---",
|
|
496
|
+
"# API Rules",
|
|
497
|
+
"- Validate all request input with zod schemas",
|
|
498
|
+
"- Return typed error responses, never throw raw errors",
|
|
499
|
+
"- Keep route handlers under 30 lines",
|
|
500
|
+
"",
|
|
501
|
+
"**Stack-specific patterns to include:**",
|
|
502
|
+
`- Next.js app/: "Use Server Components by default, add 'use client' only when needed"`,
|
|
503
|
+
'- API routes / src/api/: "Validate input at boundaries, typed error responses"',
|
|
504
|
+
'- React components: "Colocate components near usage, props interface above component"',
|
|
505
|
+
'- Tests: "One assertion per test when possible, descriptive test names"',
|
|
506
|
+
'- Database / prisma/ / drizzle/: "Never write raw SQL, use the ORM, migrations required"',
|
|
507
|
+
'- Docs: "No em dashes, max 3 sentences per paragraph, code examples required"',
|
|
508
|
+
"",
|
|
509
|
+
"**When NOT to generate:**",
|
|
510
|
+
"- Small projects with < 5 source files (one conventions.md is enough)",
|
|
511
|
+
"- Projects where all code is in one flat directory",
|
|
512
|
+
"- If path-scoped rules already exist, don't overwrite them",
|
|
513
|
+
"",
|
|
514
|
+
"**Monorepo handling:**",
|
|
515
|
+
"- Each package gets its own rules file: .claude/rules/packages-<name>.md",
|
|
516
|
+
"- Suggest claudeMdExcludes in settings.json to skip irrelevant package CLAUDE.md files",
|
|
517
|
+
"",
|
|
518
|
+
"## Skill authoring conventions",
|
|
519
|
+
"",
|
|
520
|
+
"If .claude/rules/conventions.md exists but has no Skill Authoring section, add this:",
|
|
521
|
+
"",
|
|
522
|
+
"## Skill Authoring",
|
|
523
|
+
"",
|
|
524
|
+
"When creating Claude Code skills (.claude/skills/*/SKILL.md):",
|
|
525
|
+
"",
|
|
526
|
+
"- Add TRIGGER when / DO NOT TRIGGER when clauses in the description for auto-invocation",
|
|
527
|
+
"- Add allowed-tools in frontmatter to restrict tool access (e.g. Read, Glob, Grep for read-only skills)",
|
|
528
|
+
"- Add argument-hint in frontmatter showing the expected input format",
|
|
529
|
+
'- Structure as phases: Research, Plan, Execute, Verify with "Done when:" success criteria per phase',
|
|
530
|
+
"- Handle edge cases and preconditions before execution",
|
|
531
|
+
"",
|
|
532
|
+
"## Hook review",
|
|
533
|
+
"",
|
|
534
|
+
"Review .claude/settings.json hooks:",
|
|
535
|
+
"- If you see project-specific patterns that deserve hooks, suggest them",
|
|
536
|
+
"- If no PostCompact hook exists, suggest one that re-injects TASKS.md",
|
|
537
|
+
"- If no SessionStart hook exists, suggest one that injects TASKS.md",
|
|
538
|
+
"- DO NOT modify settings.json directly. Print exact JSON to add.",
|
|
539
|
+
"",
|
|
540
|
+
"## .claudeignore review",
|
|
541
|
+
"",
|
|
542
|
+
"Read .claudeignore and check if the patterns make sense for the detected stack:",
|
|
543
|
+
"",
|
|
544
|
+
"**Always flag:**",
|
|
545
|
+
"- Missing node_modules/ (JS/TS projects)",
|
|
546
|
+
"- Missing __pycache__/ or .venv/ (Python projects)",
|
|
547
|
+
"- Missing target/ (Rust/Java projects)",
|
|
548
|
+
"- Missing .env / .env.* patterns",
|
|
549
|
+
"- Missing lock files (pnpm-lock.yaml, package-lock.json, yarn.lock, etc.)",
|
|
550
|
+
"- Missing coverage/ directory",
|
|
551
|
+
"- Large generated files that waste context (*.min.js, *.map, migrations/)",
|
|
552
|
+
"",
|
|
553
|
+
"**Never flag:**",
|
|
554
|
+
"- Patterns the user clearly added intentionally",
|
|
555
|
+
"- Test fixtures or seed data (might be needed for context)",
|
|
556
|
+
"",
|
|
557
|
+
"If .claudeignore is missing entirely, create one with sensible defaults for the detected stack.",
|
|
558
|
+
"If it exists but has gaps, print suggested additions. Do NOT modify it directly.",
|
|
559
|
+
"",
|
|
560
|
+
"## Eval scenario generation",
|
|
561
|
+
"",
|
|
562
|
+
"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.",
|
|
563
|
+
"",
|
|
564
|
+
"**Scenario YAML format:**",
|
|
565
|
+
"```yaml",
|
|
566
|
+
"name: custom/scenario-name",
|
|
567
|
+
"description: What this scenario tests",
|
|
568
|
+
"setup:",
|
|
569
|
+
" files:",
|
|
570
|
+
" - path: src/example.ts",
|
|
571
|
+
" content: |",
|
|
572
|
+
" // Starter file that tempts Claude to break a rule",
|
|
573
|
+
" instructions: |",
|
|
574
|
+
" The specific rule from CLAUDE.md being tested.",
|
|
575
|
+
'prompt: "A task that would tempt Claude to break the rule"',
|
|
576
|
+
"checks:",
|
|
577
|
+
" - type: grep",
|
|
578
|
+
' pattern: "expected_pattern"',
|
|
579
|
+
" target: src/example.ts",
|
|
580
|
+
" expect: present",
|
|
581
|
+
" points: 5",
|
|
582
|
+
" label: What this check verifies",
|
|
583
|
+
" - type: file-exists",
|
|
584
|
+
" target: path/to/expected/file",
|
|
585
|
+
" expect: present",
|
|
586
|
+
" points: 5",
|
|
587
|
+
" label: What this check verifies",
|
|
588
|
+
"passingScore: 7",
|
|
589
|
+
"runs: 3",
|
|
590
|
+
"```",
|
|
591
|
+
"",
|
|
592
|
+
"**How to choose scenarios:**",
|
|
593
|
+
"1. Pick the 2-3 most important rules from ## Off-Limits and ## Conventions",
|
|
594
|
+
"2. Design a task that naturally tempts Claude to break each rule",
|
|
595
|
+
"3. Write checks that verify compliance (grep for patterns, file-exists for structure)",
|
|
596
|
+
"",
|
|
597
|
+
"**Check types available:** `grep` (pattern in file), `file-exists` (present/absent), `max-lines` (file length)",
|
|
598
|
+
"",
|
|
599
|
+
"**Examples of good custom scenarios:**",
|
|
600
|
+
'- Off-limits says "never use any" \u2192 task asks to build types, check for no `any` keyword',
|
|
601
|
+
'- Convention says "max 400 lines per file" \u2192 task asks to generate a large module, check line count',
|
|
602
|
+
'- Off-limits says "no raw SQL" \u2192 task asks to add a query, check for ORM usage',
|
|
603
|
+
"",
|
|
604
|
+
"**Skip if:** scenarios/ already has 3+ YAML files, or CLAUDE.md has no project-specific rules worth testing.",
|
|
605
|
+
"",
|
|
606
|
+
"## Other advanced configuration",
|
|
607
|
+
"",
|
|
608
|
+
"- If the project uses external APIs, suggest sandbox.network.allowedDomains",
|
|
609
|
+
"- If you detect a monorepo, suggest claudeMdExcludes in settings.json"
|
|
610
|
+
].join("\n");
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// src/commands/doctor/fixer-memory.ts
|
|
614
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
615
|
+
import { join as join3 } from "path";
|
|
616
|
+
async function addPlacementHook(root, placement, event, dedupKeyword, entry, prepend, successMsg) {
|
|
617
|
+
const read = placement === "local" ? readSettingsLocalJson : readSettingsJson;
|
|
618
|
+
const write = placement === "local" ? writeSettingsLocalJson : writeSettingsJson;
|
|
619
|
+
const settings = await read(root);
|
|
620
|
+
const hooks = settings.hooks ?? {};
|
|
621
|
+
const hookList = hooks[event] ?? [];
|
|
622
|
+
const alreadyHas = hookList.some((g) => {
|
|
623
|
+
const nested = g.hooks;
|
|
624
|
+
return nested?.some((h) => String(h.command ?? "").includes(dedupKeyword));
|
|
625
|
+
});
|
|
626
|
+
if (alreadyHas) return false;
|
|
627
|
+
hooks[event] = prepend ? [entry, ...hookList] : [...hookList, entry];
|
|
628
|
+
settings.hooks = hooks;
|
|
629
|
+
await write(root, settings);
|
|
630
|
+
log.success(successMsg);
|
|
631
|
+
return true;
|
|
632
|
+
}
|
|
633
|
+
async function disableAutoMemory(root, placement) {
|
|
634
|
+
const read = placement === "local" ? readSettingsLocalJson : readSettingsJson;
|
|
635
|
+
const write = placement === "local" ? writeSettingsLocalJson : writeSettingsJson;
|
|
636
|
+
const settings = await read(root);
|
|
637
|
+
if (settings.autoMemoryEnabled === false) return false;
|
|
638
|
+
settings.autoMemoryEnabled = false;
|
|
639
|
+
await write(root, settings);
|
|
640
|
+
const target = placement === "local" ? "settings.local.json" : "settings.json";
|
|
641
|
+
log.success(`Set autoMemoryEnabled: false in ${target}`);
|
|
642
|
+
return true;
|
|
643
|
+
}
|
|
644
|
+
async function addMemoryToolPermissions(root, placement) {
|
|
645
|
+
const read = placement === "local" ? readSettingsLocalJson : readSettingsJson;
|
|
646
|
+
const write = placement === "local" ? writeSettingsLocalJson : writeSettingsJson;
|
|
647
|
+
const settings = await read(root);
|
|
648
|
+
const permissions = settings.permissions ?? {};
|
|
649
|
+
const allow = permissions.allow ?? [];
|
|
650
|
+
const tools = [
|
|
651
|
+
"mcp__agentic-memory__memory_store",
|
|
652
|
+
"mcp__agentic-memory__memory_search",
|
|
653
|
+
"mcp__agentic-memory__memory_recent",
|
|
654
|
+
"mcp__agentic-memory__memory_forget",
|
|
655
|
+
"mcp__agentic-memory__memory_relate",
|
|
656
|
+
"mcp__agentic-memory__memory_stats",
|
|
657
|
+
"mcp__agentic-memory__memory_update"
|
|
658
|
+
];
|
|
659
|
+
const missing = tools.filter((t) => !allow.includes(t));
|
|
660
|
+
if (missing.length === 0) return false;
|
|
661
|
+
settings.permissions = { ...permissions, allow: [...allow, ...missing] };
|
|
662
|
+
await write(root, settings);
|
|
663
|
+
const target = placement === "local" ? "settings.local.json" : "settings.json";
|
|
664
|
+
log.success(`Added agentic-memory MCP tool permissions to ${target}`);
|
|
665
|
+
return true;
|
|
666
|
+
}
|
|
667
|
+
async function addSessionStartPullHook(root, placement) {
|
|
668
|
+
const target = placement === "local" ? "settings.local.json" : "settings.json";
|
|
669
|
+
return addPlacementHook(root, placement, "SessionStart", "memory pull", {
|
|
670
|
+
matcher: "startup",
|
|
671
|
+
hooks: [{ type: "command", command: "claude-launchpad memory pull -y 2>/dev/null; exit 0" }]
|
|
672
|
+
}, true, `Added SessionStart hook for memory sync to ${target}`);
|
|
673
|
+
}
|
|
674
|
+
async function addSessionEndPushHook(root, placement) {
|
|
675
|
+
const target = placement === "local" ? "settings.local.json" : "settings.json";
|
|
676
|
+
return addPlacementHook(root, placement, "SessionEnd", "memory push", {
|
|
677
|
+
hooks: [{ type: "command", command: "claude-launchpad memory push -y >/dev/null 2>&1 & exit 0" }]
|
|
678
|
+
}, false, `Added SessionEnd hook for memory sync to ${target}`);
|
|
679
|
+
}
|
|
680
|
+
async function removeStaleStopHook(root) {
|
|
681
|
+
const settings = await readSettingsJson(root);
|
|
682
|
+
const hooks = settings.hooks;
|
|
683
|
+
if (!hooks?.Stop) return false;
|
|
684
|
+
const stopHooks = hooks.Stop;
|
|
685
|
+
const filtered = stopHooks.filter((h) => {
|
|
686
|
+
const innerHooks = h.hooks;
|
|
687
|
+
return !innerHooks?.some(
|
|
688
|
+
(ih) => typeof ih.command === "string" && ih.command.includes("memory extract")
|
|
689
|
+
);
|
|
690
|
+
});
|
|
691
|
+
if (filtered.length === stopHooks.length) return false;
|
|
692
|
+
const updated = filtered.length === 0 ? (({ Stop: _, ...rest }) => rest)(hooks) : { ...hooks, Stop: filtered };
|
|
693
|
+
settings.hooks = updated;
|
|
694
|
+
await writeSettingsJson(root, settings);
|
|
695
|
+
log.success("Removed deprecated Stop hook (memory extract)");
|
|
696
|
+
return true;
|
|
697
|
+
}
|
|
698
|
+
async function addAllowedMcpServers(root, placement) {
|
|
699
|
+
const read = placement === "local" ? readSettingsLocalJson : readSettingsJson;
|
|
700
|
+
const write = placement === "local" ? writeSettingsLocalJson : writeSettingsJson;
|
|
701
|
+
const settings = await read(root);
|
|
702
|
+
if (settings.allowedMcpServers) return false;
|
|
703
|
+
const other = placement === "local" ? await readSettingsJson(root) : await readSettingsLocalJson(root);
|
|
704
|
+
if (other.allowedMcpServers) return false;
|
|
705
|
+
const serverNames = /* @__PURE__ */ new Set();
|
|
706
|
+
const settingsServers = settings.mcpServers;
|
|
707
|
+
if (settingsServers && typeof settingsServers === "object") {
|
|
708
|
+
for (const name of Object.keys(settingsServers)) serverNames.add(name);
|
|
709
|
+
}
|
|
710
|
+
const mcpJsonPath = join3(root, ".mcp.json");
|
|
711
|
+
try {
|
|
712
|
+
const mcpJson = JSON.parse(await readFile3(mcpJsonPath, "utf-8"));
|
|
713
|
+
const mcpServers = mcpJson.mcpServers;
|
|
714
|
+
if (mcpServers && typeof mcpServers === "object") {
|
|
715
|
+
for (const name of Object.keys(mcpServers)) serverNames.add(name);
|
|
716
|
+
}
|
|
717
|
+
} catch {
|
|
718
|
+
}
|
|
719
|
+
if (serverNames.size === 0) return false;
|
|
720
|
+
settings.allowedMcpServers = [...serverNames].map(
|
|
721
|
+
(name) => ({ serverName: name })
|
|
722
|
+
);
|
|
723
|
+
await write(root, settings);
|
|
724
|
+
const target = placement === "local" ? "settings.local.json" : "settings.json";
|
|
725
|
+
log.success(`Added allowedMcpServers from configured servers to ${target}`);
|
|
726
|
+
return true;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// src/commands/doctor/fixer.ts
|
|
730
|
+
async function applyFixes(issues, projectRoot) {
|
|
731
|
+
const detected = await detectProject(projectRoot);
|
|
732
|
+
const hasMemoryIssues = issues.some((i) => i.analyzer === "Memory");
|
|
733
|
+
const placement = hasMemoryIssues ? await getMemoryPlacement(projectRoot) : "shared";
|
|
734
|
+
let fixed = 0;
|
|
735
|
+
let skipped = 0;
|
|
736
|
+
for (const issue of issues) {
|
|
737
|
+
const applied = await tryFix(issue, projectRoot, detected, placement);
|
|
738
|
+
if (applied) {
|
|
739
|
+
fixed++;
|
|
740
|
+
} else {
|
|
741
|
+
skipped++;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
return { fixed, skipped };
|
|
745
|
+
}
|
|
746
|
+
var FIX_TABLE = [
|
|
747
|
+
{ analyzer: "Hooks", match: "No hooks configured", fix: async (root, detected) => {
|
|
748
|
+
const a = await addEnvProtectionHook(root);
|
|
749
|
+
const b = await addAutoFormatHook(root, detected);
|
|
750
|
+
const c = await addForcePushProtection(root);
|
|
751
|
+
const d = await addSessionStartHook(root);
|
|
752
|
+
return a || b || c || d;
|
|
753
|
+
} },
|
|
754
|
+
{ analyzer: "Hooks", match: ".env file protection", fix: (root) => addEnvProtectionHook(root) },
|
|
755
|
+
{ analyzer: "Hooks", match: "auto-format", fix: (root, detected) => addAutoFormatHook(root, detected) },
|
|
756
|
+
{ analyzer: "Hooks", match: "No PreToolUse", fix: (root) => addEnvProtectionHook(root) },
|
|
757
|
+
{ analyzer: "Quality", match: "Architecture", fix: (root) => addClaudeMdSection(root, "## Architecture", "<!-- TODO: Describe your codebase structure. Run `/lp-enhance` to auto-fill this. -->") },
|
|
758
|
+
{ 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") },
|
|
759
|
+
{ analyzer: "Quality", match: "Commands", fix: (root) => addClaudeMdSection(root, "## Commands", "<!-- TODO: Add your dev/build/test commands -->") },
|
|
760
|
+
{ analyzer: "Quality", match: "Stack", fix: (root, detected) => {
|
|
761
|
+
const content = detected.language ? `- **Language**: ${detected.language}${detected.framework ? `
|
|
762
|
+
- **Framework**: ${detected.framework}` : ""}${detected.packageManager ? `
|
|
763
|
+
- **Package Manager**: ${detected.packageManager}` : ""}` : "<!-- TODO: Define your tech stack -->";
|
|
764
|
+
return addClaudeMdSection(root, "## Stack", content);
|
|
765
|
+
} },
|
|
766
|
+
{ 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") },
|
|
767
|
+
{ 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") },
|
|
768
|
+
{ analyzer: "Rules", match: "No BACKLOG.md", fix: (root) => createBacklogMd(root) },
|
|
769
|
+
{ analyzer: "Rules", match: "No .claudeignore", fix: (root, detected) => createClaudeignore(root, detected) },
|
|
770
|
+
{ analyzer: "Rules", match: "No .claude/rules/", fix: (root) => createStarterRules(root) },
|
|
771
|
+
{ analyzer: "Hooks", match: "PostCompact", fix: (root) => addPostCompactHook(root) },
|
|
772
|
+
{ analyzer: "Permissions", match: "force-push", fix: (root) => addForcePushProtection(root) },
|
|
773
|
+
{ analyzer: "Permissions", match: "Credential files not blocked", fix: (root) => addCredentialDenyRules(root) },
|
|
774
|
+
{ analyzer: "Permissions", match: "Bypass permissions mode", fix: (root) => addBypassDisable(root) },
|
|
775
|
+
{ analyzer: "Permissions", match: "Sandbox not enabled", fix: (root) => addSandboxSettings(root) },
|
|
776
|
+
{ analyzer: "Permissions", match: ".env is protected by hooks but not in .claudeignore", fix: (root) => addEnvToClaudeignore(root) },
|
|
777
|
+
{ analyzer: "Rules", match: "No /lp-enhance skill", fix: (root) => createEnhanceSkill(root) },
|
|
778
|
+
{ analyzer: "Rules", match: "lp-enhance skill is outdated", fix: (root) => updateEnhanceSkill(root) },
|
|
779
|
+
{ analyzer: "Settings", match: "Deprecated includeCoAuthoredBy", fix: (root) => migrateAttribution(root) },
|
|
780
|
+
{ analyzer: "Hooks", match: "SessionStart", fix: (root) => addSessionStartHook(root) },
|
|
781
|
+
{ analyzer: "Memory", match: "Deprecated Stop hook", fix: (root) => removeStaleStopHook(root) },
|
|
782
|
+
{ analyzer: "Memory", match: "autoMemoryEnabled not disabled", fix: (root, _det, placement) => disableAutoMemory(root, placement) },
|
|
783
|
+
{ analyzer: "Memory", match: "MCP tool permission", fix: (root, _det, placement) => addMemoryToolPermissions(root, placement) },
|
|
784
|
+
{ analyzer: "MCP", match: "no allowedMcpServers", fix: (root, _det, placement) => addAllowedMcpServers(root, placement) },
|
|
785
|
+
{ analyzer: "Memory", match: "SessionStart hook to auto-pull", fix: (root, _det, placement) => addSessionStartPullHook(root, placement) },
|
|
786
|
+
{ analyzer: "Memory", match: "SessionEnd hook to auto-push", fix: (root, _det, placement) => addSessionEndPushHook(root, placement) },
|
|
787
|
+
{ analyzer: "Memory", match: "CLAUDE.md missing memory guidance", fix: (root, _det, placement) => {
|
|
788
|
+
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";
|
|
789
|
+
const target = placement === "local" ? join4(root, ".claude", "CLAUDE.md") : void 0;
|
|
790
|
+
return addClaudeMdSection(root, "## Memory", content, target);
|
|
791
|
+
} }
|
|
792
|
+
];
|
|
793
|
+
function hasAutoFix(issue) {
|
|
794
|
+
return FIX_TABLE.some(
|
|
795
|
+
(e) => e.analyzer === issue.analyzer && issue.message.includes(e.match)
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
async function tryFix(issue, root, detected, placement) {
|
|
799
|
+
const entry = FIX_TABLE.find(
|
|
800
|
+
(e) => e.analyzer === issue.analyzer && issue.message.includes(e.match)
|
|
801
|
+
);
|
|
802
|
+
return entry ? entry.fix(root, detected, placement) : false;
|
|
803
|
+
}
|
|
804
|
+
async function addHook(root, event, dedupKeyword, entry, successMsg) {
|
|
805
|
+
const settings = await readSettingsJson(root);
|
|
806
|
+
const hooks = settings.hooks ?? {};
|
|
807
|
+
const hookList = hooks[event] ?? [];
|
|
808
|
+
const alreadyHas = hookList.some((g) => {
|
|
809
|
+
const nested = g.hooks;
|
|
810
|
+
return nested?.some((h) => String(h.command ?? "").includes(dedupKeyword));
|
|
811
|
+
});
|
|
812
|
+
if (alreadyHas) return false;
|
|
813
|
+
hookList.push(entry);
|
|
814
|
+
settings.hooks = { ...hooks, [event]: hookList };
|
|
815
|
+
await writeSettingsJson(root, settings);
|
|
816
|
+
log.success(successMsg);
|
|
817
|
+
return true;
|
|
818
|
+
}
|
|
819
|
+
async function addEnvProtectionHook(root) {
|
|
820
|
+
return addHook(root, "PreToolUse", ".env", {
|
|
821
|
+
matcher: "Read|Write|Edit",
|
|
822
|
+
hooks: [{
|
|
823
|
+
type: "command",
|
|
824
|
+
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`
|
|
825
|
+
}]
|
|
826
|
+
}, "Added .env file protection hook (PreToolUse)");
|
|
827
|
+
}
|
|
828
|
+
async function addAutoFormatHook(root, detected) {
|
|
829
|
+
if (!detected.language) return false;
|
|
830
|
+
const formatters = {
|
|
831
|
+
TypeScript: { extensions: ["ts", "tsx"], command: "npx prettier --write" },
|
|
832
|
+
JavaScript: { extensions: ["js", "jsx"], command: "npx prettier --write" },
|
|
833
|
+
Python: { extensions: ["py"], command: "ruff format" },
|
|
834
|
+
Go: { extensions: ["go"], command: "gofmt -w" },
|
|
835
|
+
Rust: { extensions: ["rs"], command: "rustfmt" },
|
|
836
|
+
Ruby: { extensions: ["rb"], command: "rubocop -A" },
|
|
837
|
+
PHP: { extensions: ["php"], command: "vendor/bin/pint" }
|
|
838
|
+
};
|
|
839
|
+
const config = formatters[detected.language];
|
|
840
|
+
if (!config) return false;
|
|
841
|
+
const extChecks = config.extensions.map((ext) => `[ "$ext" = "${ext}" ]`).join(" || ");
|
|
842
|
+
return addHook(root, "PostToolUse", "format", {
|
|
843
|
+
matcher: "Write|Edit",
|
|
844
|
+
hooks: [{
|
|
845
|
+
type: "command",
|
|
846
|
+
command: `ext=\${TOOL_INPUT_FILE_PATH##*.}; (${extChecks}) && ${config.command} "$TOOL_INPUT_FILE_PATH" 2>/dev/null; exit 0`
|
|
847
|
+
}]
|
|
848
|
+
}, `Added auto-format hook (PostToolUse \u2192 ${config.command})`);
|
|
849
|
+
}
|
|
850
|
+
async function addForcePushProtection(root) {
|
|
851
|
+
return addHook(root, "PreToolUse", "force", {
|
|
852
|
+
matcher: "Bash",
|
|
853
|
+
hooks: [{
|
|
854
|
+
type: "command",
|
|
855
|
+
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`
|
|
856
|
+
}]
|
|
857
|
+
}, "Added force-push protection hook (PreToolUse \u2192 Bash)");
|
|
858
|
+
}
|
|
859
|
+
async function addPostCompactHook(root) {
|
|
860
|
+
return addHook(root, "PostCompact", "TASKS.md", {
|
|
861
|
+
matcher: "",
|
|
862
|
+
hooks: [{
|
|
863
|
+
type: "command",
|
|
864
|
+
command: "cat TASKS.md 2>/dev/null; exit 0"
|
|
865
|
+
}]
|
|
866
|
+
}, "Added PostCompact hook (re-injects TASKS.md after compaction)");
|
|
867
|
+
}
|
|
868
|
+
async function addSessionStartHook(root) {
|
|
869
|
+
return addHook(root, "SessionStart", "TASKS.md", {
|
|
870
|
+
matcher: "startup|resume",
|
|
871
|
+
hooks: [{
|
|
872
|
+
type: "command",
|
|
873
|
+
command: "cat TASKS.md 2>/dev/null; exit 0"
|
|
874
|
+
}]
|
|
875
|
+
}, "Added SessionStart hook (injects TASKS.md at startup)");
|
|
876
|
+
}
|
|
877
|
+
async function migrateAttribution(root) {
|
|
878
|
+
const settings = await readSettingsJson(root);
|
|
879
|
+
if (settings.includeCoAuthoredBy === void 0) return false;
|
|
880
|
+
const { includeCoAuthoredBy: _, ...rest } = settings;
|
|
881
|
+
const updated = { ...rest, attribution: { commit: "", pr: "" } };
|
|
882
|
+
await writeSettingsJson(root, updated);
|
|
883
|
+
log.success("Migrated includeCoAuthoredBy \u2192 attribution object");
|
|
884
|
+
return true;
|
|
885
|
+
}
|
|
886
|
+
async function addCredentialDenyRules(root) {
|
|
887
|
+
const settings = await readSettingsJson(root);
|
|
888
|
+
const permissions = settings.permissions ?? {};
|
|
889
|
+
const deny = permissions.deny ?? [];
|
|
890
|
+
const toAdd = ["Read(~/.ssh/*)", "Read(~/.aws/*)", "Read(~/.npmrc)"];
|
|
891
|
+
const missing = toAdd.filter((p) => !deny.includes(p));
|
|
892
|
+
if (missing.length === 0) return false;
|
|
893
|
+
settings.permissions = { ...permissions, deny: [...deny, ...missing] };
|
|
894
|
+
await writeSettingsJson(root, settings);
|
|
895
|
+
log.success("Added credential deny rules (SSH, AWS, npm)");
|
|
896
|
+
return true;
|
|
897
|
+
}
|
|
898
|
+
async function addBypassDisable(root) {
|
|
899
|
+
const settings = await readSettingsJson(root);
|
|
900
|
+
if (settings.disableBypassPermissionsMode === "disable") return false;
|
|
901
|
+
settings.disableBypassPermissionsMode = "disable";
|
|
902
|
+
await writeSettingsJson(root, settings);
|
|
903
|
+
log.success("Added disableBypassPermissionsMode: disable");
|
|
904
|
+
return true;
|
|
905
|
+
}
|
|
906
|
+
async function addSandboxSettings(root) {
|
|
907
|
+
const settings = await readSettingsJson(root);
|
|
908
|
+
const sandbox = settings.sandbox;
|
|
909
|
+
if (sandbox?.enabled === true) return false;
|
|
910
|
+
settings.sandbox = { enabled: true, failIfUnavailable: true };
|
|
911
|
+
await writeSettingsJson(root, settings);
|
|
912
|
+
log.success("Enabled sandbox with failIfUnavailable");
|
|
913
|
+
return true;
|
|
914
|
+
}
|
|
915
|
+
async function addEnvToClaudeignore(root) {
|
|
916
|
+
const ignorePath = join4(root, ".claudeignore");
|
|
917
|
+
let content;
|
|
918
|
+
try {
|
|
919
|
+
content = await readFile4(ignorePath, "utf-8");
|
|
920
|
+
} catch {
|
|
921
|
+
return false;
|
|
922
|
+
}
|
|
923
|
+
const lines = content.split("\n").map((l) => l.trim());
|
|
924
|
+
if (lines.some((l) => l === ".env" || l === ".env.*" || l === ".env*")) return false;
|
|
925
|
+
await writeFile2(ignorePath, content.trimEnd() + "\n.env\n.env.*\n");
|
|
926
|
+
log.success("Added .env to .claudeignore");
|
|
927
|
+
return true;
|
|
928
|
+
}
|
|
929
|
+
async function addClaudeMdSection(root, heading, content, targetPath) {
|
|
930
|
+
const claudeMdPath = targetPath ?? join4(root, "CLAUDE.md");
|
|
931
|
+
let existing;
|
|
932
|
+
try {
|
|
933
|
+
existing = await readFile4(claudeMdPath, "utf-8");
|
|
934
|
+
} catch {
|
|
935
|
+
if (!targetPath) return false;
|
|
936
|
+
await mkdir2(join4(root, ".claude"), { recursive: true });
|
|
937
|
+
existing = "# Local Claude Config\n";
|
|
938
|
+
}
|
|
939
|
+
if (existing.includes(heading)) return false;
|
|
940
|
+
const keyDecisionsIdx = existing.indexOf("## Key Decisions");
|
|
941
|
+
const insertAt = keyDecisionsIdx > -1 ? keyDecisionsIdx : existing.length;
|
|
942
|
+
const section = `
|
|
943
|
+
${heading}
|
|
944
|
+
${content}
|
|
945
|
+
|
|
946
|
+
`;
|
|
947
|
+
const updated = existing.slice(0, insertAt) + section + existing.slice(insertAt);
|
|
948
|
+
await writeFile2(claudeMdPath, updated);
|
|
949
|
+
const label = targetPath ? ".claude/CLAUDE.md" : "CLAUDE.md";
|
|
950
|
+
log.success(`Added "${heading}" section to ${label}`);
|
|
951
|
+
return true;
|
|
952
|
+
}
|
|
953
|
+
async function createBacklogMd(root) {
|
|
954
|
+
const backlogPath = join4(root, "BACKLOG.md");
|
|
955
|
+
try {
|
|
956
|
+
await access2(backlogPath);
|
|
957
|
+
return false;
|
|
958
|
+
} catch {
|
|
959
|
+
}
|
|
960
|
+
const name = root.split("/").pop() ?? "Project";
|
|
961
|
+
await writeFile2(backlogPath, `# ${name} - Backlog
|
|
962
|
+
|
|
963
|
+
> Features discussed but deferred. Pick up when relevant.
|
|
964
|
+
> Priority: P0 = next sprint, P1 = soon, P2 = when relevant.
|
|
965
|
+
`);
|
|
966
|
+
log.success("Generated BACKLOG.md");
|
|
967
|
+
return true;
|
|
968
|
+
}
|
|
969
|
+
async function createClaudeignore(root, detected) {
|
|
970
|
+
const ignorePath = join4(root, ".claudeignore");
|
|
971
|
+
try {
|
|
972
|
+
await access2(ignorePath);
|
|
973
|
+
return false;
|
|
974
|
+
} catch {
|
|
975
|
+
}
|
|
976
|
+
const content = generateClaudeignore(detected);
|
|
977
|
+
await writeFile2(ignorePath, content);
|
|
978
|
+
log.success("Generated .claudeignore with language-specific ignore patterns");
|
|
979
|
+
return true;
|
|
980
|
+
}
|
|
981
|
+
async function createStarterRules(root) {
|
|
982
|
+
const rulesDir = join4(root, ".claude", "rules");
|
|
983
|
+
try {
|
|
984
|
+
await access2(rulesDir);
|
|
985
|
+
return false;
|
|
986
|
+
} catch {
|
|
987
|
+
}
|
|
988
|
+
await mkdir2(rulesDir, { recursive: true });
|
|
989
|
+
await writeFile2(
|
|
990
|
+
join4(rulesDir, "conventions.md"),
|
|
991
|
+
`# Project Conventions
|
|
992
|
+
|
|
993
|
+
- Use conventional commits (feat:, fix:, docs:, refactor:, test:, chore:)
|
|
994
|
+
- Keep files under 400 lines, functions under 50 lines
|
|
995
|
+
- Handle errors explicitly \u2014 no empty catch blocks
|
|
996
|
+
- Validate input at system boundaries
|
|
997
|
+
`
|
|
998
|
+
);
|
|
999
|
+
log.success("Created .claude/rules/conventions.md with starter rules");
|
|
1000
|
+
return true;
|
|
1001
|
+
}
|
|
1002
|
+
async function createEnhanceSkill(root) {
|
|
1003
|
+
const skillDir = join4(root, ".claude", "skills", "lp-enhance");
|
|
1004
|
+
const skillPath = join4(skillDir, "SKILL.md");
|
|
1005
|
+
const globalPath = join4(homedir(), ".claude", "skills", "lp-enhance", "SKILL.md");
|
|
1006
|
+
const legacyProject = join4(root, ".claude", "commands", "lp-enhance.md");
|
|
1007
|
+
const legacyGlobal = join4(homedir(), ".claude", "commands", "lp-enhance.md");
|
|
1008
|
+
if (await fileExists(skillPath) || await fileExists(globalPath) || await fileExists(legacyProject) || await fileExists(legacyGlobal)) return false;
|
|
1009
|
+
await mkdir2(skillDir, { recursive: true });
|
|
1010
|
+
await writeFile2(skillPath, generateEnhanceSkill());
|
|
1011
|
+
log.success("Generated /lp-enhance skill (.claude/skills/lp-enhance/)");
|
|
1012
|
+
return true;
|
|
1013
|
+
}
|
|
1014
|
+
async function updateEnhanceSkill(root) {
|
|
1015
|
+
const projectPath = join4(root, ".claude", "skills", "lp-enhance", "SKILL.md");
|
|
1016
|
+
const globalPath = join4(homedir(), ".claude", "skills", "lp-enhance", "SKILL.md");
|
|
1017
|
+
const targetPath = await fileExists(projectPath) ? projectPath : await fileExists(globalPath) ? globalPath : null;
|
|
1018
|
+
if (!targetPath) return false;
|
|
1019
|
+
await writeFile2(targetPath, generateEnhanceSkill());
|
|
1020
|
+
log.success("Updated /lp-enhance skill to latest version");
|
|
1021
|
+
return true;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// src/lib/output.ts
|
|
1025
|
+
var colors = {
|
|
1026
|
+
success: chalk.green,
|
|
1027
|
+
error: chalk.red,
|
|
1028
|
+
warn: chalk.yellow,
|
|
1029
|
+
info: chalk.cyan,
|
|
1030
|
+
dim: chalk.dim,
|
|
1031
|
+
bold: chalk.bold,
|
|
1032
|
+
score: (score) => {
|
|
1033
|
+
if (score >= 80) return chalk.green.bold(`${score}%`);
|
|
1034
|
+
if (score >= 60) return chalk.yellow.bold(`${score}%`);
|
|
1035
|
+
return chalk.red.bold(`${score}%`);
|
|
1036
|
+
},
|
|
1037
|
+
severity: (sev) => {
|
|
1038
|
+
const map = {
|
|
1039
|
+
critical: chalk.bgRed.white.bold,
|
|
1040
|
+
high: chalk.red.bold,
|
|
1041
|
+
medium: chalk.yellow,
|
|
1042
|
+
low: chalk.cyan,
|
|
1043
|
+
info: chalk.dim
|
|
1044
|
+
};
|
|
1045
|
+
return map[sev](` ${sev.toUpperCase()} `);
|
|
1046
|
+
}
|
|
1047
|
+
};
|
|
1048
|
+
var log = {
|
|
1049
|
+
success: (msg) => console.log(` ${chalk.green("\u2713")} ${msg}`),
|
|
1050
|
+
error: (msg) => console.log(` ${chalk.red("\u2717")} ${msg}`),
|
|
1051
|
+
warn: (msg) => console.log(` ${chalk.yellow("!")} ${msg}`),
|
|
1052
|
+
step: (msg) => console.log(` ${chalk.cyan("\u2192")} ${msg}`),
|
|
1053
|
+
info: (msg) => console.log(` ${chalk.dim("\xB7")} ${msg}`),
|
|
1054
|
+
blank: () => console.log()
|
|
1055
|
+
};
|
|
1056
|
+
function printBanner() {
|
|
1057
|
+
log.blank();
|
|
1058
|
+
console.log(chalk.cyan.bold(" Claude Launchpad"));
|
|
1059
|
+
console.log(chalk.dim(" Scaffold \xB7 Diagnose \xB7 Evaluate \xB7 Remember"));
|
|
1060
|
+
log.blank();
|
|
1061
|
+
}
|
|
1062
|
+
function printScoreCard(label, score, max = 100) {
|
|
1063
|
+
const pct = Math.round(score / max * 100);
|
|
1064
|
+
const bar = renderBar(pct, 20);
|
|
1065
|
+
console.log(` ${chalk.bold(label.padEnd(22))} ${bar} ${colors.score(pct).padStart(12)}`);
|
|
1066
|
+
}
|
|
1067
|
+
function renderBar(pct, width) {
|
|
1068
|
+
const filled = Math.round(pct / 100 * width);
|
|
1069
|
+
const empty = width - filled;
|
|
1070
|
+
const color = pct >= 80 ? chalk.green : pct >= 60 ? chalk.yellow : chalk.red;
|
|
1071
|
+
return color("\u2501".repeat(filled)) + chalk.dim("\u2500".repeat(empty));
|
|
1072
|
+
}
|
|
1073
|
+
function printIssue(severity, _analyzer, message) {
|
|
1074
|
+
const sevLabel = {
|
|
1075
|
+
critical: chalk.bgRed.white.bold(" CRIT "),
|
|
1076
|
+
high: chalk.red.bold("HIGH"),
|
|
1077
|
+
medium: chalk.yellow("MED "),
|
|
1078
|
+
low: chalk.dim("LOW "),
|
|
1079
|
+
info: chalk.dim("INFO")
|
|
1080
|
+
};
|
|
1081
|
+
console.log(` ${sevLabel[severity]} ${message}`);
|
|
1082
|
+
}
|
|
1083
|
+
function renderDoctorReport(results, options) {
|
|
1084
|
+
const overallScore = Math.round(
|
|
1085
|
+
results.reduce((sum, r) => sum + r.score, 0) / results.length
|
|
1086
|
+
);
|
|
1087
|
+
for (const result of results) {
|
|
1088
|
+
printScoreCard(result.name, result.score);
|
|
1089
|
+
}
|
|
1090
|
+
log.blank();
|
|
1091
|
+
printScoreCard("Overall", overallScore);
|
|
1092
|
+
log.blank();
|
|
1093
|
+
const allIssues = results.flatMap((r) => r.issues);
|
|
1094
|
+
const actionable = allIssues.filter((i) => i.severity !== "info");
|
|
1095
|
+
if (actionable.length === 0) {
|
|
1096
|
+
log.success("No issues found. Your configuration looks solid.");
|
|
1097
|
+
return { overallScore, actionableCount: 0 };
|
|
1098
|
+
}
|
|
1099
|
+
const sorted = [...actionable].sort((a, b) => {
|
|
1100
|
+
const order = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
|
|
1101
|
+
return (order[a.severity] ?? 4) - (order[b.severity] ?? 4);
|
|
1102
|
+
});
|
|
1103
|
+
for (const issue of sorted) {
|
|
1104
|
+
printIssue(issue.severity, issue.analyzer, issue.message);
|
|
1105
|
+
}
|
|
1106
|
+
log.blank();
|
|
1107
|
+
if (options?.afterFix) {
|
|
1108
|
+
log.info(`${actionable.length} remaining issue(s) require manual intervention.`);
|
|
1109
|
+
} else {
|
|
1110
|
+
const fixable = actionable.filter((i) => hasAutoFix(i));
|
|
1111
|
+
if (fixable.length > 0) {
|
|
1112
|
+
log.info(`${actionable.length} issue(s). Run ${chalk.bold("--fix")} to auto-repair or ${chalk.bold("--fix --dry-run")} to preview.`);
|
|
1113
|
+
} else {
|
|
1114
|
+
log.info(`${actionable.length} issue(s) require manual intervention.`);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
return { overallScore, actionableCount: actionable.length };
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
export {
|
|
1121
|
+
__export,
|
|
1122
|
+
fileExists,
|
|
1123
|
+
readFileOrNull,
|
|
1124
|
+
detectProject,
|
|
1125
|
+
generateClaudeignore,
|
|
1126
|
+
ENHANCE_SKILL_VERSION,
|
|
1127
|
+
generateEnhanceSkill,
|
|
1128
|
+
readSettingsJson,
|
|
1129
|
+
writeSettingsJson,
|
|
1130
|
+
readSettingsLocalJson,
|
|
1131
|
+
writeSettingsLocalJson,
|
|
1132
|
+
getMemoryPlacement,
|
|
1133
|
+
applyFixes,
|
|
1134
|
+
log,
|
|
1135
|
+
printBanner,
|
|
1136
|
+
printScoreCard,
|
|
1137
|
+
renderDoctorReport
|
|
1138
|
+
};
|
|
1139
|
+
//# sourceMappingURL=chunk-S4WIADYE.js.map
|