claude-launchpad 1.3.0 → 1.5.0

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 (44) hide show
  1. package/README.md +2 -2
  2. package/dist/{chunk-UJP5PJTA.js → chunk-5YUKTNBM.js} +3 -3
  3. package/dist/{chunk-V4NXT4KB.js → chunk-DXDOVWOA.js} +64 -8
  4. package/dist/chunk-DXDOVWOA.js.map +1 -0
  5. package/dist/{chunk-N6X3E5AX.js → chunk-F6SLV2FR.js} +24 -5
  6. package/dist/chunk-F6SLV2FR.js.map +1 -0
  7. package/dist/{chunk-YXPJDIMK.js → chunk-GLFJ2B43.js} +56 -12
  8. package/dist/chunk-GLFJ2B43.js.map +1 -0
  9. package/dist/{chunk-AR64LWGW.js → chunk-WLD2PA3B.js} +25 -4
  10. package/dist/chunk-WLD2PA3B.js.map +1 -0
  11. package/dist/{chunk-J765H3HZ.js → chunk-YF6HCPVY.js} +2 -2
  12. package/dist/{chunk-F5PNKQKW.js → chunk-YZ53W47Z.js} +7 -2
  13. package/dist/chunk-YZ53W47Z.js.map +1 -0
  14. package/dist/cli.js +313 -58
  15. package/dist/cli.js.map +1 -1
  16. package/dist/commands/memory/server.js +5 -5
  17. package/dist/{context-VAXF3EW3.js → context-4X4CLMU3.js} +6 -6
  18. package/dist/{install-M3JWBGMK.js → install-P4TFYUJT.js} +6 -6
  19. package/dist/install-P4TFYUJT.js.map +1 -0
  20. package/dist/{pull-ZQFCMK46.js → pull-7SR7P3US.js} +9 -9
  21. package/dist/{push-5ZJNWAE7.js → push-SCTO5TZQ.js} +40 -17
  22. package/dist/push-SCTO5TZQ.js.map +1 -0
  23. package/dist/{require-deps-QW2IU6I3.js → require-deps-MCFEZOIF.js} +3 -3
  24. package/dist/{stats-NPXPJNBO.js → stats-MLWRNOHU.js} +7 -7
  25. package/dist/{sync-clean-JQLVE4WU.js → sync-clean-2BMOFDV7.js} +2 -2
  26. package/dist/{sync-status-IYG7ZYC5.js → sync-status-J7BVY6KF.js} +9 -9
  27. package/dist/{tui-74FMIMUM.js → tui-JE5L7SXC.js} +5 -5
  28. package/package.json +3 -1
  29. package/dist/chunk-AR64LWGW.js.map +0 -1
  30. package/dist/chunk-F5PNKQKW.js.map +0 -1
  31. package/dist/chunk-N6X3E5AX.js.map +0 -1
  32. package/dist/chunk-V4NXT4KB.js.map +0 -1
  33. package/dist/chunk-YXPJDIMK.js.map +0 -1
  34. package/dist/install-M3JWBGMK.js.map +0 -1
  35. package/dist/push-5ZJNWAE7.js.map +0 -1
  36. /package/dist/{chunk-UJP5PJTA.js.map → chunk-5YUKTNBM.js.map} +0 -0
  37. /package/dist/{chunk-J765H3HZ.js.map → chunk-YF6HCPVY.js.map} +0 -0
  38. /package/dist/{context-VAXF3EW3.js.map → context-4X4CLMU3.js.map} +0 -0
  39. /package/dist/{pull-ZQFCMK46.js.map → pull-7SR7P3US.js.map} +0 -0
  40. /package/dist/{require-deps-QW2IU6I3.js.map → require-deps-MCFEZOIF.js.map} +0 -0
  41. /package/dist/{stats-NPXPJNBO.js.map → stats-MLWRNOHU.js.map} +0 -0
  42. /package/dist/{sync-clean-JQLVE4WU.js.map → sync-clean-2BMOFDV7.js.map} +0 -0
  43. /package/dist/{sync-status-IYG7ZYC5.js.map → sync-status-J7BVY6KF.js.map} +0 -0
  44. /package/dist/{tui-74FMIMUM.js.map → tui-JE5L7SXC.js.map} +0 -0
package/dist/cli.js CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  import {
6
6
  BACKLOG_CONTENT,
7
7
  ENHANCE_SKILL_VERSION,
8
+ LP_STUB_OPEN,
8
9
  OFF_LIMITS_CONTENT,
9
10
  SESSION_START_CONTENT,
10
11
  SKILL_AUTHORING_CONTENT,
@@ -19,7 +20,7 @@ import {
19
20
  printScoreCard,
20
21
  readFileOrNull,
21
22
  renderDoctorReport
22
- } from "./chunk-YXPJDIMK.js";
23
+ } from "./chunk-GLFJ2B43.js";
23
24
 
24
25
  // src/cli.ts
25
26
  import { Command as Command5 } from "commander";
@@ -1084,6 +1085,60 @@ async function analyzeMcp(config) {
1084
1085
  return { name: "MCP Servers", issues, score };
1085
1086
  }
1086
1087
 
1088
+ // src/commands/doctor/analyzers/hook-resolver.ts
1089
+ import { readFile as readFile3, realpath } from "fs/promises";
1090
+ import { resolve as resolve2, sep } from "path";
1091
+ import { parse } from "shell-quote";
1092
+ async function resolveHookCommand(hook, projectRoot) {
1093
+ const command = hook.command ?? "";
1094
+ if (!command) return { command: "", expansions: [], missingScripts: [] };
1095
+ const scriptPaths = extractShellScripts(command);
1096
+ if (scriptPaths.length === 0) return { command, expansions: [], missingScripts: [] };
1097
+ let projectRootReal;
1098
+ try {
1099
+ projectRootReal = await realpath(projectRoot);
1100
+ } catch {
1101
+ return { command, expansions: [], missingScripts: [] };
1102
+ }
1103
+ const expansions = [];
1104
+ const missingScripts = [];
1105
+ for (const relPath of scriptPaths) {
1106
+ const resolved = resolve2(projectRoot, relPath);
1107
+ let realResolved;
1108
+ try {
1109
+ realResolved = await realpath(resolved);
1110
+ } catch {
1111
+ missingScripts.push(relPath);
1112
+ continue;
1113
+ }
1114
+ if (realResolved !== projectRootReal && !realResolved.startsWith(projectRootReal + sep)) {
1115
+ continue;
1116
+ }
1117
+ try {
1118
+ const body = await readFile3(realResolved, "utf-8");
1119
+ expansions.push({ path: relPath, body });
1120
+ } catch {
1121
+ missingScripts.push(relPath);
1122
+ }
1123
+ }
1124
+ return { command, expansions, missingScripts };
1125
+ }
1126
+ function effectiveCommandText(resolved) {
1127
+ if (resolved.expansions.length === 0) return resolved.command;
1128
+ return resolved.command + "\n" + resolved.expansions.map((e) => e.body).join("\n");
1129
+ }
1130
+ function extractShellScripts(command) {
1131
+ const tokens = parse(command);
1132
+ const scripts = [];
1133
+ for (const token of tokens) {
1134
+ if (typeof token !== "string") continue;
1135
+ if (!token.endsWith(".sh")) continue;
1136
+ if (token.startsWith("~") || token.startsWith("$")) continue;
1137
+ scripts.push(token);
1138
+ }
1139
+ return scripts;
1140
+ }
1141
+
1087
1142
  // src/commands/doctor/analyzers/memory.ts
1088
1143
  var MEMORY_MCP_TOOLS = [
1089
1144
  "mcp__agentic-memory__memory_store",
@@ -1094,7 +1149,10 @@ var MEMORY_MCP_TOOLS = [
1094
1149
  "mcp__agentic-memory__memory_stats",
1095
1150
  "mcp__agentic-memory__memory_update"
1096
1151
  ];
1097
- function hasMemoryIndicators(config) {
1152
+ async function resolveAllHooks(hooks, projectRoot) {
1153
+ return Promise.all(hooks.map((h) => resolveHookCommand(h, projectRoot)));
1154
+ }
1155
+ async function hasMemoryIndicators(config, projectRoot) {
1098
1156
  if (config.mcpServers.some((s) => s.name === "agentic-memory")) return true;
1099
1157
  const permissions = config.settings?.permissions ?? {};
1100
1158
  const localPermissions = config.localSettings?.permissions ?? {};
@@ -1103,14 +1161,18 @@ function hasMemoryIndicators(config) {
1103
1161
  ...localPermissions.allow ?? []
1104
1162
  ];
1105
1163
  if (allowList.some((t) => t.startsWith("mcp__agentic-memory__"))) return true;
1106
- if (config.hooks.some((h) => h.event === "SessionStart" && h.command?.includes("memory context"))) return true;
1107
- return false;
1164
+ const resolved = await resolveAllHooks(config.hooks, projectRoot);
1165
+ return config.hooks.some(
1166
+ (h, i) => h.event === "SessionStart" && effectiveCommandText(resolved[i]).includes("memory context")
1167
+ );
1108
1168
  }
1109
- async function analyzeMemory(config) {
1110
- if (!hasMemoryIndicators(config)) return null;
1169
+ async function analyzeMemory(config, projectRoot) {
1170
+ if (!await hasMemoryIndicators(config, projectRoot)) return null;
1111
1171
  const issues = [];
1172
+ const resolved = await resolveAllHooks(config.hooks, projectRoot);
1173
+ const effectiveAt = (i) => effectiveCommandText(resolved[i]);
1112
1174
  const hasSessionStart = config.hooks.some(
1113
- (h) => h.event === "SessionStart" && h.command?.includes("memory context")
1175
+ (h, i) => h.event === "SessionStart" && effectiveAt(i).includes("memory context")
1114
1176
  );
1115
1177
  if (!hasSessionStart) {
1116
1178
  issues.push({
@@ -1121,7 +1183,7 @@ async function analyzeMemory(config) {
1121
1183
  });
1122
1184
  }
1123
1185
  const hasStaleStopHook = config.hooks.some(
1124
- (h) => h.event === "Stop" && h.command?.includes("memory extract")
1186
+ (h, i) => h.event === "Stop" && effectiveAt(i).includes("memory extract")
1125
1187
  );
1126
1188
  if (hasStaleStopHook) {
1127
1189
  issues.push({
@@ -1167,7 +1229,7 @@ async function analyzeMemory(config) {
1167
1229
  const syncConfig = readSyncConfig();
1168
1230
  if (syncConfig) {
1169
1231
  const hasSessionStartPull = config.hooks.some(
1170
- (h) => h.event === "SessionStart" && h.command?.includes("memory pull")
1232
+ (h, i) => h.event === "SessionStart" && effectiveAt(i).includes("memory pull")
1171
1233
  );
1172
1234
  if (!hasSessionStartPull) {
1173
1235
  issues.push({
@@ -1178,7 +1240,7 @@ async function analyzeMemory(config) {
1178
1240
  });
1179
1241
  }
1180
1242
  const hasSessionEndPush = config.hooks.some(
1181
- (h) => h.event === "SessionEnd" && h.command?.includes("memory push")
1243
+ (h, i) => h.event === "SessionEnd" && effectiveAt(i).includes("memory push")
1182
1244
  );
1183
1245
  if (!hasSessionEndPush) {
1184
1246
  issues.push({
@@ -1188,6 +1250,29 @@ async function analyzeMemory(config) {
1188
1250
  fix: "Run `doctor --fix` to add a SessionEnd hook that pushes memories automatically"
1189
1251
  });
1190
1252
  }
1253
+ const hasStaleBackgroundedPush = config.hooks.some(
1254
+ (h) => h.event === "SessionEnd" && h.command?.includes("memory push") && /&\s*exit\s+0\s*$/.test(h.command)
1255
+ );
1256
+ if (hasStaleBackgroundedPush) {
1257
+ issues.push({
1258
+ analyzer: "Memory",
1259
+ severity: "high",
1260
+ message: "SessionEnd push hook is backgrounded \u2014 push gets killed before reaching the gist, deletions never sync",
1261
+ fix: "Run `doctor --fix` to upgrade the hook to a synchronous push"
1262
+ });
1263
+ }
1264
+ }
1265
+ for (let i = 0; i < config.hooks.length; i++) {
1266
+ const h = config.hooks[i];
1267
+ if (h.event !== "SessionStart" && h.event !== "SessionEnd") continue;
1268
+ for (const missing of resolved[i].missingScripts) {
1269
+ issues.push({
1270
+ analyzer: "Memory",
1271
+ severity: "low",
1272
+ message: `${h.event} hook references \`${missing}\` but the file is missing \u2014 wrapper can't run`,
1273
+ fix: `Create ${missing} or remove the broken hook from .claude/settings.json`
1274
+ });
1275
+ }
1191
1276
  }
1192
1277
  const critical = issues.filter((i) => i.severity === "critical").length;
1193
1278
  const high = issues.filter((i) => i.severity === "high").length;
@@ -1197,17 +1282,189 @@ async function analyzeMemory(config) {
1197
1282
  return { name: "Memory", issues, score };
1198
1283
  }
1199
1284
 
1200
- // src/commands/doctor/analyzers/quality.ts
1201
- var BASE_SECTIONS = [
1202
- { pattern: /^##\s+(Tech )?Stack/m, name: "Stack", why: "Claude performs worse without knowing the tech stack" },
1203
- { pattern: /^##\s+Commands/m, name: "Commands", why: "Claude guesses wrong without explicit dev/build/test commands" },
1204
- { pattern: /^##\s+Session Start/m, name: "Session Start", why: "Without this, Claude won't read TASKS.md or maintain continuity" },
1205
- { pattern: /^##\s+Off.?Limits/m, name: "Off-Limits", why: "Without guardrails, Claude has no boundaries beyond defaults" },
1206
- { pattern: /^##\s+(Architecture|Project Structure)/m, name: "Architecture/Structure", why: "Claude makes better decisions when it understands the codebase shape" },
1207
- { pattern: /^##\s+Backlog/m, name: "Backlog", why: "Without backlog instructions, deferred features get lost in conversation history" },
1208
- { pattern: /^##\s+(Stop.and.Swarm|When Stuck|Debug)/m, name: "Stop-and-Swarm", why: "Without a stop-and-swarm rule, Claude keeps guessing in circles instead of parallelizing research" }
1285
+ // src/commands/doctor/analyzers/quality-intents.ts
1286
+ var INTENT_RULES = [
1287
+ {
1288
+ name: "Stack",
1289
+ why: "Claude performs worse without knowing the tech stack",
1290
+ headingPatterns: [/^tech\s+stack$/i, /^stack$/i, /^technology$/i, /^tech$/i],
1291
+ bodyKeywords: [
1292
+ /\blanguage:/i,
1293
+ /\bframework:/i,
1294
+ /\bpackage\s+manager:/i,
1295
+ /\bruntime:/i,
1296
+ /\b(typescript|javascript|python|ruby|go|rust|java|php|swift|kotlin)\b/i,
1297
+ /\b(react|next\.?js|vue|svelte|angular|express|fastify|laravel|rails|django|flask)\b/i,
1298
+ /\b(node(?:\.?js)?|deno|bun|cpython)\b/i
1299
+ ],
1300
+ minBodyKeywords: 2
1301
+ },
1302
+ {
1303
+ name: "Commands",
1304
+ why: "Claude guesses wrong without explicit dev/build/test commands",
1305
+ headingPatterns: [/^commands?$/i, /^scripts$/i, /^dev\s+commands$/i, /^development$/i],
1306
+ bodyKeywords: [
1307
+ /\b(pnpm|npm|yarn|bun)\s+\w+/i,
1308
+ /\b(build|test|dev|lint|typecheck|start):/i,
1309
+ /\brun\s+(tests?|build|dev)/i,
1310
+ /\bmake\s+\w+/i,
1311
+ /\bcargo\s+\w+/i
1312
+ ],
1313
+ minBodyKeywords: 2
1314
+ },
1315
+ {
1316
+ name: "Session Start",
1317
+ why: "Without this, Claude won't read TASKS.md or maintain continuity",
1318
+ headingPatterns: [
1319
+ /^session\s+start$/i,
1320
+ /^session$/i,
1321
+ /^sprint\s+planning$/i,
1322
+ /^workflow$/i,
1323
+ /^getting\s+started$/i,
1324
+ /^at\s+session\s+start$/i
1325
+ ],
1326
+ bodyKeywords: [
1327
+ /\btasks?\.md\b/i,
1328
+ /\bsession\s+(start|log)\b/i,
1329
+ /\bsprint\s+(log|planning)\b/i,
1330
+ /\b(read|check).*at\s+(session|start)/i,
1331
+ /\btrack\s+progress/i
1332
+ ],
1333
+ minBodyKeywords: 1
1334
+ },
1335
+ {
1336
+ name: "Off-Limits",
1337
+ why: "Without guardrails, Claude has no boundaries beyond defaults",
1338
+ headingPatterns: [
1339
+ /^off.?limits$/i,
1340
+ /^constraints$/i,
1341
+ /^don'?t$/i,
1342
+ /^rules$/i,
1343
+ /^guardrails$/i,
1344
+ /^forbidden$/i,
1345
+ /^security\s+notes$/i
1346
+ ],
1347
+ bodyKeywords: [
1348
+ /\bnever\s+\w+/i,
1349
+ /\bforbidden/i,
1350
+ /\bdo\s+not\b/i,
1351
+ /\b(secret|credential|api\s+key|password|token)/i,
1352
+ /\.env\b/
1353
+ ],
1354
+ minBodyKeywords: 2
1355
+ },
1356
+ {
1357
+ name: "Architecture/Structure",
1358
+ why: "Claude makes better decisions when it understands the codebase shape",
1359
+ headingPatterns: [
1360
+ /^architecture$/i,
1361
+ /^project\s+structure$/i,
1362
+ /^structure$/i,
1363
+ /^codebase$/i,
1364
+ /^layout$/i,
1365
+ /^repo\s+layout$/i
1366
+ ],
1367
+ bodyKeywords: [
1368
+ /\bsrc\//,
1369
+ /\b(directory|directories|folder|module|layer)\b/i,
1370
+ /\barchitecture\b/i
1371
+ ],
1372
+ minBodyKeywords: 1
1373
+ },
1374
+ {
1375
+ name: "Backlog",
1376
+ why: "Without backlog instructions, deferred features get lost in conversation history",
1377
+ headingPatterns: [
1378
+ /^backlog$/i,
1379
+ /^roadmap$/i,
1380
+ /^parked$/i,
1381
+ /^future\s+work$/i,
1382
+ /^parked\s+features?$/i
1383
+ ],
1384
+ bodyKeywords: [
1385
+ /\bbacklog\.md\b/i,
1386
+ /\bbacklog\b/i,
1387
+ /\bdeferred\b/i,
1388
+ /\bparked\s+features?\b/i
1389
+ ],
1390
+ minBodyKeywords: 1
1391
+ },
1392
+ {
1393
+ name: "Stop-and-Swarm",
1394
+ why: "Without a stop-and-swarm rule, Claude keeps guessing in circles instead of parallelizing research",
1395
+ headingPatterns: [
1396
+ /^stop.and.swarm$/i,
1397
+ /^when\s+stuck$/i,
1398
+ /^debug$/i,
1399
+ /^escalation$/i,
1400
+ /^swarm$/i,
1401
+ /^parallel\s+agents?$/i
1402
+ ],
1403
+ bodyKeywords: [
1404
+ /\bstop-and-swarm\b/i,
1405
+ /\bparallel\s+agents?\b/i,
1406
+ /\bspin\s+up\b/i,
1407
+ /\b(3|three)\s+(parallel\s+)?agents?\b/i,
1408
+ /\bfailed\s+iterations?\b/i
1409
+ ],
1410
+ minBodyKeywords: 1
1411
+ }
1209
1412
  ];
1210
- var MEMORY_SECTION = { pattern: /^##\s+Memory/m, name: "Memory & Learnings", why: "Without memory instructions, Claude forgets learnings and repeats mistakes across sessions" };
1413
+ var MEMORY_INTENT = {
1414
+ name: "Memory & Learnings",
1415
+ why: "Without memory instructions, Claude forgets learnings and repeats mistakes across sessions",
1416
+ headingPatterns: [/^memory$/i, /^learnings?$/i, /^memory\s*(&|and)\s*learnings?$/i],
1417
+ bodyKeywords: [
1418
+ /\bmemory_search\b/i,
1419
+ /\bmemory_store\b/i,
1420
+ /\bagentic-memory\b/i,
1421
+ /\bstore\s+memories?\b/i,
1422
+ /\binject(ed)?\s+(at|in|into)\s+(session|startup)/i
1423
+ ],
1424
+ minBodyKeywords: 1
1425
+ };
1426
+ function parseSections(content) {
1427
+ const lines = content.split("\n");
1428
+ const sections = [];
1429
+ let currentHeading = null;
1430
+ let currentBody = [];
1431
+ const flush = () => {
1432
+ if (currentHeading === null) return;
1433
+ const body = currentBody.join("\n");
1434
+ sections.push({
1435
+ heading: currentHeading,
1436
+ body,
1437
+ isStub: body.includes(LP_STUB_OPEN)
1438
+ });
1439
+ };
1440
+ for (const line of lines) {
1441
+ const match = line.match(/^##\s+(.+?)\s*$/);
1442
+ if (match) {
1443
+ flush();
1444
+ currentHeading = match[1];
1445
+ currentBody = [];
1446
+ } else if (currentHeading !== null) {
1447
+ currentBody.push(line);
1448
+ }
1449
+ }
1450
+ flush();
1451
+ return sections;
1452
+ }
1453
+ function sectionSatisfiesIntent(section, rule) {
1454
+ if (section.isStub) return false;
1455
+ const headingMatch = rule.headingPatterns.some((p) => p.test(section.heading));
1456
+ if (headingMatch) return true;
1457
+ const keywordHits = rule.bodyKeywords.reduce(
1458
+ (n, p) => p.test(section.body) ? n + 1 : n,
1459
+ 0
1460
+ );
1461
+ return keywordHits >= rule.minBodyKeywords;
1462
+ }
1463
+ function documentSatisfiesIntent(sections, rule) {
1464
+ return sections.some((s) => sectionSatisfiesIntent(s, rule));
1465
+ }
1466
+
1467
+ // src/commands/doctor/analyzers/quality.ts
1211
1468
  var VAGUE_PATTERNS = [
1212
1469
  { pattern: /write (good|clean|quality|nice) code/i, label: "write good code" },
1213
1470
  { pattern: /be (careful|thorough|diligent)/i, label: "be careful" },
@@ -1220,7 +1477,7 @@ var SECRET_PATTERNS = [
1220
1477
  { pattern: /AKIA[0-9A-Z]{16}/, label: "AWS access key" },
1221
1478
  { pattern: /xoxb-[0-9]+-[a-zA-Z0-9]+/, label: "Slack bot token" }
1222
1479
  ];
1223
- async function analyzeQuality(config) {
1480
+ async function analyzeQuality(config, projectRoot) {
1224
1481
  const issues = [];
1225
1482
  const content = config.claudeMdContent;
1226
1483
  if (content === null) {
@@ -1232,18 +1489,16 @@ async function analyzeQuality(config) {
1232
1489
  });
1233
1490
  return { name: "CLAUDE.md Quality", issues, score: 0 };
1234
1491
  }
1235
- const sections = hasMemoryIndicators(config) ? [...BASE_SECTIONS, MEMORY_SECTION] : [...BASE_SECTIONS];
1236
- const combinedContent = [content, config.localClaudeMdContent].filter(Boolean).join("\n");
1237
- let sectionsFound = 0;
1238
- for (const section of sections) {
1239
- if (section.pattern.test(combinedContent)) {
1240
- sectionsFound++;
1241
- } else {
1492
+ const rules = await hasMemoryIndicators(config, projectRoot) ? [...INTENT_RULES, MEMORY_INTENT] : [...INTENT_RULES];
1493
+ const combinedContent = [content, config.localClaudeMdContent].filter(Boolean).join("\n\n");
1494
+ const sections = parseSections(combinedContent);
1495
+ for (const rule of rules) {
1496
+ if (!documentSatisfiesIntent(sections, rule)) {
1242
1497
  issues.push({
1243
1498
  analyzer: "Quality",
1244
1499
  severity: "medium",
1245
- message: `Missing "## ${section.name}" section \u2014 ${section.why}`,
1246
- fix: `Add a ## ${section.name} section to CLAUDE.md`
1500
+ message: `Missing "## ${rule.name}" section \u2014 ${rule.why}`,
1501
+ fix: `Add a ## ${rule.name} section to CLAUDE.md`
1247
1502
  });
1248
1503
  }
1249
1504
  }
@@ -1345,7 +1600,7 @@ async function runAndDisplay(projectRoot) {
1345
1600
  }
1346
1601
  const results = await Promise.all([
1347
1602
  analyzeBudget(config),
1348
- analyzeQuality(config),
1603
+ analyzeQuality(config, projectRoot),
1349
1604
  analyzeSettings(config),
1350
1605
  analyzeHooks(config),
1351
1606
  analyzeRules(config),
@@ -1375,14 +1630,14 @@ function createDoctorCommand() {
1375
1630
  }
1376
1631
  const results = await Promise.all([
1377
1632
  analyzeBudget(config),
1378
- analyzeQuality(config),
1633
+ analyzeQuality(config, opts.path),
1379
1634
  analyzeSettings(config),
1380
1635
  analyzeHooks(config),
1381
1636
  analyzeRules(config),
1382
1637
  analyzePermissions(config),
1383
1638
  analyzeMcp(config)
1384
1639
  ]);
1385
- const memoryResult = await analyzeMemory(config);
1640
+ const memoryResult = await analyzeMemory(config, opts.path);
1386
1641
  if (memoryResult) {
1387
1642
  results.push(memoryResult);
1388
1643
  }
@@ -1433,14 +1688,14 @@ function createDoctorCommand() {
1433
1688
  const updatedConfig = await parseClaudeConfig(opts.path);
1434
1689
  const updatedResults = await Promise.all([
1435
1690
  analyzeBudget(updatedConfig),
1436
- analyzeQuality(updatedConfig),
1691
+ analyzeQuality(updatedConfig, opts.path),
1437
1692
  analyzeSettings(updatedConfig),
1438
1693
  analyzeHooks(updatedConfig),
1439
1694
  analyzeRules(updatedConfig),
1440
1695
  analyzePermissions(updatedConfig),
1441
1696
  analyzeMcp(updatedConfig)
1442
1697
  ]);
1443
- const updatedMemoryResult = await analyzeMemory(updatedConfig);
1698
+ const updatedMemoryResult = await analyzeMemory(updatedConfig, opts.path);
1444
1699
  if (updatedMemoryResult) {
1445
1700
  updatedResults.push(updatedMemoryResult);
1446
1701
  }
@@ -1466,8 +1721,8 @@ import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
1466
1721
  import { join as join7 } from "path";
1467
1722
 
1468
1723
  // src/commands/eval/loader.ts
1469
- import { readFile as readFile3, readdir as readdir3, access as access3 } from "fs/promises";
1470
- import { join as join5, resolve as resolve2, dirname as dirname2 } from "path";
1724
+ import { readFile as readFile4, readdir as readdir3, access as access3 } from "fs/promises";
1725
+ import { join as join5, resolve as resolve3, dirname as dirname2 } from "path";
1471
1726
  import { fileURLToPath } from "url";
1472
1727
  import { parse as parseYaml } from "yaml";
1473
1728
 
@@ -1571,11 +1826,11 @@ var ScenarioError = class extends Error {
1571
1826
  // src/commands/eval/loader.ts
1572
1827
  async function findScenariosDir() {
1573
1828
  const thisDir = dirname2(fileURLToPath(import.meta.url));
1574
- const devPath = resolve2(thisDir, "../../../scenarios");
1829
+ const devPath = resolve3(thisDir, "../../../scenarios");
1575
1830
  if (await dirExists(devPath)) return devPath;
1576
- const bundledPath = resolve2(thisDir, "../scenarios");
1831
+ const bundledPath = resolve3(thisDir, "../scenarios");
1577
1832
  if (await dirExists(bundledPath)) return bundledPath;
1578
- const rootPath = resolve2(thisDir, "../../scenarios");
1833
+ const rootPath = resolve3(thisDir, "../../scenarios");
1579
1834
  if (await dirExists(rootPath)) return rootPath;
1580
1835
  return devPath;
1581
1836
  }
@@ -1589,7 +1844,7 @@ async function dirExists(path) {
1589
1844
  }
1590
1845
  async function loadScenarios(options) {
1591
1846
  const { suite, customPath } = options;
1592
- const scenarioDir = customPath ? resolve2(customPath) : await findScenariosDir();
1847
+ const scenarioDir = customPath ? resolve3(customPath) : await findScenariosDir();
1593
1848
  const dirs = suite ? [join5(scenarioDir, suite)] : await getSubdirectories(scenarioDir);
1594
1849
  const allDirs = [scenarioDir, ...dirs];
1595
1850
  const scenarios = [];
@@ -1597,7 +1852,7 @@ async function loadScenarios(options) {
1597
1852
  const files = await listYamlFiles(dir);
1598
1853
  for (const file of files) {
1599
1854
  try {
1600
- const content = await readFile3(file, "utf-8");
1855
+ const content = await readFile4(file, "utf-8");
1601
1856
  const raw = parseYaml(content);
1602
1857
  const scenario = validateScenario(raw, file);
1603
1858
  scenarios.push(scenario);
@@ -1627,7 +1882,7 @@ async function listYamlFiles(dir) {
1627
1882
  }
1628
1883
 
1629
1884
  // src/commands/eval/runner.ts
1630
- import { mkdir as mkdir2, writeFile as writeFile2, readFile as readFile4, readdir as readdir4, rm, cp } from "fs/promises";
1885
+ import { mkdir as mkdir2, writeFile as writeFile2, readFile as readFile5, readdir as readdir4, rm, cp } from "fs/promises";
1631
1886
  import { join as join6, dirname as dirname3 } from "path";
1632
1887
  import { tmpdir } from "os";
1633
1888
  import { randomUUID } from "crypto";
@@ -1796,7 +2051,7 @@ async function evaluateSingleCheck(check, sandboxDir) {
1796
2051
  async function checkGrep(check, sandboxDir) {
1797
2052
  if (!check.pattern) return false;
1798
2053
  try {
1799
- const content = await readFile4(join6(sandboxDir, check.target), "utf-8");
2054
+ const content = await readFile5(join6(sandboxDir, check.target), "utf-8");
1800
2055
  let found;
1801
2056
  try {
1802
2057
  found = new RegExp(check.pattern).test(content);
@@ -1810,7 +2065,7 @@ async function checkGrep(check, sandboxDir) {
1810
2065
  }
1811
2066
  async function checkFilePresence(check, sandboxDir) {
1812
2067
  try {
1813
- await readFile4(join6(sandboxDir, check.target));
2068
+ await readFile5(join6(sandboxDir, check.target));
1814
2069
  return check.expect === "present";
1815
2070
  } catch {
1816
2071
  return check.expect === "absent";
@@ -1821,7 +2076,7 @@ async function checkMaxLines(check, sandboxDir) {
1821
2076
  try {
1822
2077
  const files = await listAllFiles(join6(sandboxDir, check.target));
1823
2078
  for (const file of files) {
1824
- const content = await readFile4(file, "utf-8");
2079
+ const content = await readFile5(file, "utf-8");
1825
2080
  if (content.split("\n").length > maxLines) {
1826
2081
  return check.expect === "absent";
1827
2082
  }
@@ -2077,14 +2332,14 @@ function createMemoryCommand() {
2077
2332
  log.error("Knowledge base not set up yet. Run `claude-launchpad memory` first.");
2078
2333
  return;
2079
2334
  }
2080
- const { requireMemoryDeps } = await import("./require-deps-QW2IU6I3.js");
2335
+ const { requireMemoryDeps } = await import("./require-deps-MCFEZOIF.js");
2081
2336
  await requireMemoryDeps();
2082
- const { startTui } = await import("./tui-74FMIMUM.js");
2337
+ const { startTui } = await import("./tui-JE5L7SXC.js");
2083
2338
  await startTui();
2084
2339
  return;
2085
2340
  }
2086
2341
  if (!isMemoryInstalled()) {
2087
- const { detectExistingSetup } = await import("./install-M3JWBGMK.js");
2342
+ const { detectExistingSetup } = await import("./install-P4TFYUJT.js");
2088
2343
  const existing = detectExistingSetup(process.cwd());
2089
2344
  if (existing) {
2090
2345
  const location = existing === "local" ? ".claude/CLAUDE.md + settings.local.json" : "CLAUDE.md + settings.json";
@@ -2110,18 +2365,18 @@ function createMemoryCommand() {
2110
2365
  log.info("Skipped.");
2111
2366
  return;
2112
2367
  }
2113
- const { runInstall } = await import("./install-M3JWBGMK.js");
2368
+ const { runInstall } = await import("./install-P4TFYUJT.js");
2114
2369
  await runInstall({});
2115
2370
  } else {
2116
- const { requireMemoryDeps } = await import("./require-deps-QW2IU6I3.js");
2371
+ const { requireMemoryDeps } = await import("./require-deps-MCFEZOIF.js");
2117
2372
  await requireMemoryDeps();
2118
- const { runStats } = await import("./stats-NPXPJNBO.js");
2373
+ const { runStats } = await import("./stats-MLWRNOHU.js");
2119
2374
  await runStats({});
2120
2375
  }
2121
2376
  });
2122
2377
  memory.addCommand(
2123
2378
  new Command4("context").description("Load session context (hook handler)").option("--json", "JSON output").action(async (opts) => {
2124
- const { runContext } = await import("./context-VAXF3EW3.js");
2379
+ const { runContext } = await import("./context-4X4CLMU3.js");
2125
2380
  await runContext(opts);
2126
2381
  }).helpCommand(false),
2127
2382
  { hidden: true }
@@ -2136,7 +2391,7 @@ function createMemoryCommand() {
2136
2391
  memory.addCommand(
2137
2392
  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) => {
2138
2393
  await handleSyncErrors(async () => {
2139
- const { runPush } = await import("./push-5ZJNWAE7.js");
2394
+ const { runPush } = await import("./push-SCTO5TZQ.js");
2140
2395
  await runPush(opts);
2141
2396
  });
2142
2397
  })
@@ -2144,7 +2399,7 @@ function createMemoryCommand() {
2144
2399
  memory.addCommand(
2145
2400
  new Command4("pull").description("Pull current project's memories from GitHub Gist").option("--all", "Pull all projects").option("-y, --yes", "Non-interactive (accepted for symmetry with push; pull never prompts)").action(async (opts) => {
2146
2401
  await handleSyncErrors(async () => {
2147
- const { runPull } = await import("./pull-ZQFCMK46.js");
2402
+ const { runPull } = await import("./pull-7SR7P3US.js");
2148
2403
  await runPull(opts);
2149
2404
  });
2150
2405
  })
@@ -2153,7 +2408,7 @@ function createMemoryCommand() {
2153
2408
  sync.addCommand(
2154
2409
  new Command4("status").description("Show local vs remote memory counts per project").action(async () => {
2155
2410
  await handleSyncErrors(async () => {
2156
- const { runSyncStatus } = await import("./sync-status-IYG7ZYC5.js");
2411
+ const { runSyncStatus } = await import("./sync-status-J7BVY6KF.js");
2157
2412
  await runSyncStatus();
2158
2413
  });
2159
2414
  })
@@ -2161,7 +2416,7 @@ function createMemoryCommand() {
2161
2416
  sync.addCommand(
2162
2417
  new Command4("clean").description("Remove a project from the sync gist").argument("<project>", "Project slug to remove").option("-y, --yes", "Skip confirmation prompt").action(async (project, opts) => {
2163
2418
  await handleSyncErrors(async () => {
2164
- const { runSyncClean } = await import("./sync-clean-JQLVE4WU.js");
2419
+ const { runSyncClean } = await import("./sync-clean-2BMOFDV7.js");
2165
2420
  await runSyncClean(project, opts);
2166
2421
  });
2167
2422
  })
@@ -2171,7 +2426,7 @@ function createMemoryCommand() {
2171
2426
  }
2172
2427
 
2173
2428
  // src/cli.ts
2174
- var program = new Command5().name("claude-launchpad").description("CLI toolkit that makes Claude Code setups measurably good").version("1.3.0", "-v, --version").action(async () => {
2429
+ var program = new Command5().name("claude-launchpad").description("CLI toolkit that makes Claude Code setups measurably good").version("1.5.0", "-v, --version").action(async () => {
2175
2430
  const hasConfig = await fileExists(join9(process.cwd(), "CLAUDE.md")) || await fileExists(join9(process.cwd(), ".claude", "settings.json"));
2176
2431
  if (hasConfig) {
2177
2432
  await program.commands.find((c) => c.name() === "doctor")?.parseAsync([], { from: "user" });