claude-launchpad 1.12.0 → 1.14.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 (61) hide show
  1. package/README.md +11 -3
  2. package/dist/{chunk-DTCCQWOU.js → chunk-D6RAMWH6.js} +3 -3
  3. package/dist/{chunk-DTCCQWOU.js.map → chunk-D6RAMWH6.js.map} +1 -1
  4. package/dist/chunk-HSGSTSVN.js +212 -0
  5. package/dist/chunk-HSGSTSVN.js.map +1 -0
  6. package/dist/{chunk-DXDOVWOA.js → chunk-IDH3M4EI.js} +20 -232
  7. package/dist/chunk-IDH3M4EI.js.map +1 -0
  8. package/dist/{chunk-YZ53W47Z.js → chunk-IIWOSSL4.js} +2 -1
  9. package/dist/chunk-IIWOSSL4.js.map +1 -0
  10. package/dist/{chunk-ZLI4LI33.js → chunk-M6GVFN67.js} +2 -2
  11. package/dist/{chunk-F6SLV2FR.js → chunk-N2SCF7DB.js} +5 -6
  12. package/dist/chunk-N2SCF7DB.js.map +1 -0
  13. package/dist/chunk-OPORVSAF.js +225 -0
  14. package/dist/chunk-OPORVSAF.js.map +1 -0
  15. package/dist/{chunk-KPO4YURF.js → chunk-QEPE3WVF.js} +457 -85
  16. package/dist/chunk-QEPE3WVF.js.map +1 -0
  17. package/dist/chunk-UN43OVRW.js +184 -0
  18. package/dist/chunk-UN43OVRW.js.map +1 -0
  19. package/dist/{chunk-PH5OWJ42.js → chunk-VF7U63AI.js} +9 -5
  20. package/dist/{chunk-PH5OWJ42.js.map → chunk-VF7U63AI.js.map} +1 -1
  21. package/dist/cli.js +307 -87
  22. package/dist/cli.js.map +1 -1
  23. package/dist/commands/memory/server.js +48 -14
  24. package/dist/commands/memory/server.js.map +1 -1
  25. package/dist/{context-JALSYDYO.js → context-YIPO4LBS.js} +58 -55
  26. package/dist/context-YIPO4LBS.js.map +1 -0
  27. package/dist/doctor-DCHFJ3XV.js +108 -0
  28. package/dist/doctor-DCHFJ3XV.js.map +1 -0
  29. package/dist/{install-Q23F37GK.js → install-D32PV7SF.js} +12 -10
  30. package/dist/install-D32PV7SF.js.map +1 -0
  31. package/dist/{pull-FYKNNLYC.js → pull-YUQTDRUP.js} +17 -19
  32. package/dist/pull-YUQTDRUP.js.map +1 -0
  33. package/dist/{push-QSBK7BQU.js → push-U4IVTFOU.js} +13 -12
  34. package/dist/push-U4IVTFOU.js.map +1 -0
  35. package/dist/{require-deps-ZISUZLYD.js → require-deps-WDMHDGBN.js} +3 -3
  36. package/dist/{stats-SV53G6PQ.js → stats-77BYLKYP.js} +9 -7
  37. package/dist/{stats-SV53G6PQ.js.map → stats-77BYLKYP.js.map} +1 -1
  38. package/dist/{sync-2LDM44U4.js → sync-DTCWK2CY.js} +5 -5
  39. package/dist/{sync-clean-HQ523N7V.js → sync-clean-ACZCB3NZ.js} +4 -5
  40. package/dist/sync-clean-ACZCB3NZ.js.map +1 -0
  41. package/dist/{sync-status-V6WTI32X.js → sync-status-2V2RCYZO.js} +13 -12
  42. package/dist/sync-status-2V2RCYZO.js.map +1 -0
  43. package/dist/{tui-2UJ2OIHB.js → tui-HVJNLRY2.js} +11 -7
  44. package/dist/{tui-2UJ2OIHB.js.map → tui-HVJNLRY2.js.map} +1 -1
  45. package/package.json +27 -7
  46. package/scenarios/workflow/premature-victory.yaml +44 -0
  47. package/dist/chunk-DXDOVWOA.js.map +0 -1
  48. package/dist/chunk-F6SLV2FR.js.map +0 -1
  49. package/dist/chunk-KPO4YURF.js.map +0 -1
  50. package/dist/chunk-YZ53W47Z.js.map +0 -1
  51. package/dist/chunk-ZIG75MOB.js +0 -366
  52. package/dist/chunk-ZIG75MOB.js.map +0 -1
  53. package/dist/context-JALSYDYO.js.map +0 -1
  54. package/dist/install-Q23F37GK.js.map +0 -1
  55. package/dist/pull-FYKNNLYC.js.map +0 -1
  56. package/dist/push-QSBK7BQU.js.map +0 -1
  57. package/dist/sync-clean-HQ523N7V.js.map +0 -1
  58. package/dist/sync-status-V6WTI32X.js.map +0 -1
  59. /package/dist/{chunk-ZLI4LI33.js.map → chunk-M6GVFN67.js.map} +0 -0
  60. /package/dist/{require-deps-ZISUZLYD.js.map → require-deps-WDMHDGBN.js.map} +0 -0
  61. /package/dist/{sync-2LDM44U4.js.map → sync-DTCWK2CY.js.map} +0 -0
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  readSyncConfig
4
- } from "./chunk-ZLI4LI33.js";
4
+ } from "./chunk-M6GVFN67.js";
5
5
  import {
6
6
  BACKLOG_CONTENT,
7
7
  ENHANCE_SKILL_VERSION,
@@ -16,6 +16,7 @@ import {
16
16
  STALE_SWARM_PHRASE,
17
17
  STOP_AND_SWARM_CONTENT,
18
18
  TESTING_DISCIPLINE_CONTENT,
19
+ VERIFICATION_RULE_VERSION,
19
20
  WORKFLOW_CHECK_WRAPPER,
20
21
  WORKFLOW_RULE_VERSION,
21
22
  applyFixes,
@@ -26,6 +27,7 @@ import {
26
27
  generateEnhanceSkill,
27
28
  generateHooksRule,
28
29
  generateReviewerAgent,
30
+ generateVerificationRule,
29
31
  generateWorkflowRule,
30
32
  hasEnvVarHookPattern,
31
33
  isJqAvailable,
@@ -38,7 +40,7 @@ import {
38
40
  sprintReviewsContent,
39
41
  writeSprintHygieneScripts,
40
42
  writeWorkflowCheckScript
41
- } from "./chunk-KPO4YURF.js";
43
+ } from "./chunk-QEPE3WVF.js";
42
44
 
43
45
  // src/cli.ts
44
46
  import { Command as Command5 } from "commander";
@@ -250,16 +252,22 @@ function dirTreeContains(dir, needle, depth) {
250
252
 
251
253
  // src/commands/init/index.ts
252
254
  function createInitCommand() {
253
- return new Command("init").description("Set up Claude Code configuration for any project").option("-n, --name <name>", "Project name").option("-y, --yes", "Accept all defaults (does not overwrite existing files)").option("-f, --force", "Overwrite existing CLAUDE.md").action(async (opts) => {
255
+ return new Command("init").description("Set up Claude Code configuration for any project").option("-n, --name <name>", "Project name").option(
256
+ "-y, --yes",
257
+ "Accept all defaults (does not overwrite existing files)"
258
+ ).option("-f, --force", "Overwrite existing CLAUDE.md").action(async (opts) => {
254
259
  printBanner();
255
260
  const root = process.cwd();
256
261
  log.step("Detecting project...");
257
262
  const detected = await detectProject(root);
258
263
  if (detected.language) {
259
264
  log.success(`Found ${detected.framework ?? detected.language} project`);
260
- if (detected.packageManager) log.info(`Package manager: ${detected.packageManager}`);
261
- if (detected.devCommand) log.info(`Dev command: ${detected.devCommand}`);
262
- if (detected.testCommand) log.info(`Test command: ${detected.testCommand}`);
265
+ if (detected.packageManager)
266
+ log.info(`Package manager: ${detected.packageManager}`);
267
+ if (detected.devCommand)
268
+ log.info(`Dev command: ${detected.devCommand}`);
269
+ if (detected.testCommand)
270
+ log.info(`Test command: ${detected.testCommand}`);
263
271
  } else {
264
272
  log.warn("Could not detect project type \u2014 generating minimal config");
265
273
  }
@@ -271,13 +279,18 @@ function createInitCommand() {
271
279
  const description = opts.yes ? "" : await input({
272
280
  message: "One-line description (optional):"
273
281
  });
274
- const options = { name: name.trim(), description: description.trim() };
282
+ const options = {
283
+ name: name.trim(),
284
+ description: description.trim()
285
+ };
275
286
  const hasClaudeMd = await fileExists(join2(root, "CLAUDE.md"));
276
287
  if (hasClaudeMd) {
277
288
  if (opts.force) {
278
289
  log.warn("Overwriting existing CLAUDE.md (--force)");
279
290
  } else if (opts.yes) {
280
- log.error("CLAUDE.md already exists. `--yes` will not overwrite existing files.");
291
+ log.error(
292
+ "CLAUDE.md already exists. `--yes` will not overwrite existing files."
293
+ );
281
294
  log.info("Run `claude-launchpad doctor --fix` to update in place,");
282
295
  log.info("or re-run with `--force` to overwrite.");
283
296
  process.exitCode = 1;
@@ -290,7 +303,9 @@ function createInitCommand() {
290
303
  if (!overwrite) {
291
304
  log.info("Keeping existing CLAUDE.md");
292
305
  await createEnhanceSkillPrompt(root, false);
293
- log.step("Tip: run `claude-launchpad doctor` to check your existing config");
306
+ log.step(
307
+ "Tip: run `claude-launchpad doctor` to check your existing config"
308
+ );
294
309
  return;
295
310
  }
296
311
  }
@@ -300,14 +315,19 @@ function createInitCommand() {
300
315
  }
301
316
  async function scaffold(root, options, detected, skipPrompts) {
302
317
  log.step("Generating configuration...");
303
- const claudeMd = generateClaudeMd(options, detected, { superpowers: isSuperpowersInstalled() });
318
+ const claudeMd = generateClaudeMd(options, detected, {
319
+ superpowers: isSuperpowersInstalled()
320
+ });
304
321
  const tasksMd = generateTasksMd(options);
305
322
  const backlogMd = generateBacklogMd(options);
306
323
  const settings = generateSettings(detected);
307
324
  const claudeignore = generateClaudeignore(detected);
308
325
  await mkdir(join2(root, ".claude", "rules"), { recursive: true });
309
326
  const settingsPath = join2(root, ".claude", "settings.json");
310
- const mergedSettings = await mergeSettings(settingsPath, settings);
327
+ const mergedSettings = await mergeSettings(
328
+ settingsPath,
329
+ settings
330
+ );
311
331
  const backlogPath = join2(root, "BACKLOG.md");
312
332
  const hasBacklog = await fileExists(backlogPath);
313
333
  const claudeignorePath = join2(root, ".claudeignore");
@@ -320,6 +340,13 @@ async function scaffold(root, options, detected, skipPrompts) {
320
340
  const hasWorkflowRule = await fileExists(workflowRulePath);
321
341
  const hooksRulePath = join2(root, ".claude", "rules", "hooks.md");
322
342
  const hasHooksRule = await fileExists(hooksRulePath);
343
+ const verificationRulePath = join2(
344
+ root,
345
+ ".claude",
346
+ "rules",
347
+ "verification.md"
348
+ );
349
+ const hasVerificationRule = await fileExists(verificationRulePath);
323
350
  const writes = [
324
351
  writeFile(join2(root, "CLAUDE.md"), claudeMd),
325
352
  writeFile(join2(root, "TASKS.md"), tasksMd),
@@ -332,16 +359,21 @@ async function scaffold(root, options, detected, skipPrompts) {
332
359
  writes.push(writeFile(claudeignorePath, claudeignore));
333
360
  }
334
361
  if (!hasClaudeGitignore) {
335
- writes.push(writeFile(claudeGitignorePath, [
336
- "# Local-only Claude Code files (never commit these)",
337
- "CLAUDE.md",
338
- "settings.local.json",
339
- "plans/",
340
- "memory/",
341
- "sessions/",
342
- "tmp/",
343
- ""
344
- ].join("\n")));
362
+ writes.push(
363
+ writeFile(
364
+ claudeGitignorePath,
365
+ [
366
+ "# Local-only Claude Code files (never commit these)",
367
+ "CLAUDE.md",
368
+ "settings.local.json",
369
+ "plans/",
370
+ "memory/",
371
+ "sessions/",
372
+ "tmp/",
373
+ ""
374
+ ].join("\n")
375
+ )
376
+ );
345
377
  }
346
378
  if (!hasRules) {
347
379
  const rulesContent = generateStarterRules(detected);
@@ -353,6 +385,9 @@ async function scaffold(root, options, detected, skipPrompts) {
353
385
  if (!hasHooksRule) {
354
386
  writes.push(writeFile(hooksRulePath, generateHooksRule()));
355
387
  }
388
+ if (!hasVerificationRule) {
389
+ writes.push(writeFile(verificationRulePath, generateVerificationRule()));
390
+ }
356
391
  const reviewerAgentPath = join2(root, ".claude", "agents", "code-reviewer.md");
357
392
  const hasReviewerAgent = await fileExists(reviewerAgentPath);
358
393
  if (!hasReviewerAgent) {
@@ -363,26 +398,47 @@ async function scaffold(root, options, detected, skipPrompts) {
363
398
  await writeSprintHygieneScripts(root);
364
399
  await writeWorkflowCheckScript(root);
365
400
  log.success("Generated CLAUDE.md");
366
- if (!hasReviewerAgent) log.success("Generated .claude/agents/code-reviewer.md (independent sprint reviewer)");
401
+ if (!hasReviewerAgent)
402
+ log.success(
403
+ "Generated .claude/agents/code-reviewer.md (independent sprint reviewer)"
404
+ );
367
405
  log.success("Generated TASKS.md");
368
406
  if (!hasBacklog) log.success("Generated BACKLOG.md");
369
407
  log.success("Generated .claude/settings.json (schema, permissions, hooks)");
370
408
  if (!hasClaudeGitignore) log.success("Generated .claude/.gitignore");
371
409
  if (!hasClaudeignore) log.success("Generated .claudeignore");
372
410
  if (!hasRules) log.success("Generated .claude/rules/conventions.md");
373
- if (!hasWorkflowRule) log.success("Generated .claude/rules/workflow.md (workflow rules, path-scoped)");
374
- if (!hasHooksRule) log.success("Generated .claude/rules/hooks.md (hook authoring rules, path-scoped)");
375
- log.success("Generated .claude/hooks/sprint-{size,open}-check.sh + workflow-check.sh");
411
+ if (!hasWorkflowRule)
412
+ log.success(
413
+ "Generated .claude/rules/workflow.md (workflow rules, path-scoped)"
414
+ );
415
+ if (!hasHooksRule)
416
+ log.success(
417
+ "Generated .claude/rules/hooks.md (hook authoring rules, path-scoped)"
418
+ );
419
+ if (!hasVerificationRule)
420
+ log.success(
421
+ "Generated .claude/rules/verification.md (evidence-before-assertion discipline)"
422
+ );
423
+ log.success(
424
+ "Generated .claude/hooks/sprint-{size,open}-check.sh + workflow-check.sh"
425
+ );
376
426
  await createEnhanceSkillPrompt(root, skipPrompts);
377
427
  log.blank();
378
428
  if (!isJqAvailable()) {
379
- log.warn("jq not found on PATH \u2014 the generated hooks (including the .env and destructive-command guards) will silently no-op until you install it: https://jqlang.github.io/jq/download/");
429
+ log.warn(
430
+ "jq not found on PATH \u2014 the generated hooks (including the .env and destructive-command guards) will silently no-op until you install it: https://jqlang.github.io/jq/download/"
431
+ );
380
432
  }
381
433
  if (!isSuperpowersInstalled()) {
382
- log.info("Optional: the superpowers plugin adds brainstorm/plan/TDD/review discipline \u2014 /plugin install superpowers@claude-plugins-official");
434
+ log.info(
435
+ "Optional: the superpowers plugin adds brainstorm/plan/TDD/review discipline \u2014 /plugin install superpowers@claude-plugins-official"
436
+ );
383
437
  }
384
438
  log.success("Done! Run `claude` to start.");
385
- log.info("Use `/lp-enhance` inside Claude Code to have AI complete your CLAUDE.md.");
439
+ log.info(
440
+ "Use `/lp-enhance` inside Claude Code to have AI complete your CLAUDE.md."
441
+ );
386
442
  log.info("Run `claude-launchpad doctor` to check your config quality.");
387
443
  log.blank();
388
444
  }
@@ -414,19 +470,32 @@ function generateStarterRules(detected) {
414
470
  lines.push("", "## Testing Discipline", "", TESTING_DISCIPLINE_CONTENT);
415
471
  const verify = [detected.testCommand, detected.lintCommand].filter((c) => !!c).map((c) => `\`${c}\``).join(" && ");
416
472
  lines.push("", "## Pre-Commit Checklist", "");
417
- lines.push(verify ? `1. Run ${verify} \u2014 never commit if either fails` : "1. Run the project's test and typecheck commands \u2014 never commit if either fails");
418
- lines.push("2. For hard-TDD surfaces, confirm the test was written before the implementation");
419
- lines.push("3. Before sprint-ending commits: run /code-review on the sprint diff (see ## Sprint Reviews in CLAUDE.md)");
473
+ lines.push(
474
+ verify ? `1. Run ${verify} \u2014 never commit if either fails` : "1. Run the project's test and typecheck commands \u2014 never commit if either fails"
475
+ );
476
+ lines.push(
477
+ "2. For hard-TDD surfaces, confirm the test was written before the implementation"
478
+ );
479
+ lines.push(
480
+ "3. Before sprint-ending commits: run /code-review on the sprint diff (see ## Sprint Reviews in CLAUDE.md)"
481
+ );
420
482
  lines.push("", "## Skill Authoring", "", SKILL_AUTHORING_CONTENT);
421
483
  lines.push("");
422
484
  return lines.join("\n");
423
485
  }
424
486
  async function createEnhanceSkillPrompt(root, skipPrompts) {
425
487
  const projectPath = join2(root, ".claude", "skills", "lp-enhance", "SKILL.md");
426
- const globalPath = join2(homedir2(), ".claude", "skills", "lp-enhance", "SKILL.md");
488
+ const globalPath = join2(
489
+ homedir2(),
490
+ ".claude",
491
+ "skills",
492
+ "lp-enhance",
493
+ "SKILL.md"
494
+ );
427
495
  const legacyProject = join2(root, ".claude", "commands", "lp-enhance.md");
428
496
  const legacyGlobal = join2(homedir2(), ".claude", "commands", "lp-enhance.md");
429
- if (await fileExists(projectPath) || await fileExists(globalPath) || await fileExists(legacyProject) || await fileExists(legacyGlobal)) return;
497
+ if (await fileExists(projectPath) || await fileExists(globalPath) || await fileExists(legacyProject) || await fileExists(legacyGlobal))
498
+ return;
430
499
  const scope = skipPrompts ? "project" : await select({
431
500
  message: "Install /lp-enhance skill (AI-powered CLAUDE.md improver):",
432
501
  choices: [
@@ -443,7 +512,9 @@ async function createEnhanceSkillPrompt(root, skipPrompts) {
443
512
  }
444
513
  async function mergeSettings(existingPath, generated) {
445
514
  try {
446
- const existing = JSON.parse(await readFile(existingPath, "utf-8"));
515
+ const existing = JSON.parse(
516
+ await readFile(existingPath, "utf-8")
517
+ );
447
518
  const existingHooks = existing.hooks ?? {};
448
519
  const generatedHooks = generated.hooks ?? {};
449
520
  const mergedHooks = { ...existingHooks };
@@ -1000,7 +1071,9 @@ async function analyzeRules(config) {
1000
1071
  fix: "Run `claude-launchpad init` or `doctor --fix` to generate one"
1001
1072
  });
1002
1073
  }
1003
- const hasWorkflowRule = await fileExists(join4(projectRoot, ".claude", "rules", "workflow.md"));
1074
+ const hasWorkflowRule = await fileExists(
1075
+ join4(projectRoot, ".claude", "rules", "workflow.md")
1076
+ );
1004
1077
  if (!hasWorkflowRule) {
1005
1078
  issues.push({
1006
1079
  analyzer: "Rules",
@@ -1009,7 +1082,9 @@ async function analyzeRules(config) {
1009
1082
  fix: "Run `doctor --fix` to generate it"
1010
1083
  });
1011
1084
  }
1012
- const hasReviewerAgent = await fileExists(join4(projectRoot, ".claude", "agents", "code-reviewer.md"));
1085
+ const hasReviewerAgent = await fileExists(
1086
+ join4(projectRoot, ".claude", "agents", "code-reviewer.md")
1087
+ );
1013
1088
  if (!hasReviewerAgent) {
1014
1089
  issues.push({
1015
1090
  analyzer: "Rules",
@@ -1026,7 +1101,10 @@ async function analyzeRules(config) {
1026
1101
  });
1027
1102
  }
1028
1103
  if (hasWorkflowRule) {
1029
- const wfContent = await readFile2(join4(projectRoot, ".claude", "rules", "workflow.md"), "utf-8").catch(() => "");
1104
+ const wfContent = await readFile2(
1105
+ join4(projectRoot, ".claude", "rules", "workflow.md"),
1106
+ "utf-8"
1107
+ ).catch(() => "");
1030
1108
  const wfMatch = wfContent.match(/<!-- lp-workflow-version: (\d+) -->/);
1031
1109
  const wfVersion = wfMatch ? parseInt(wfMatch[1], 10) : null;
1032
1110
  if (wfVersion !== null && wfVersion < WORKFLOW_RULE_VERSION) {
@@ -1038,7 +1116,9 @@ async function analyzeRules(config) {
1038
1116
  });
1039
1117
  }
1040
1118
  }
1041
- const hasHooksRule = await fileExists(join4(projectRoot, ".claude", "rules", "hooks.md"));
1119
+ const hasHooksRule = await fileExists(
1120
+ join4(projectRoot, ".claude", "rules", "hooks.md")
1121
+ );
1042
1122
  if (!hasHooksRule) {
1043
1123
  issues.push({
1044
1124
  analyzer: "Rules",
@@ -1047,6 +1127,33 @@ async function analyzeRules(config) {
1047
1127
  fix: "Run `doctor --fix` to generate it"
1048
1128
  });
1049
1129
  }
1130
+ const verificationPath = join4(
1131
+ projectRoot,
1132
+ ".claude",
1133
+ "rules",
1134
+ "verification.md"
1135
+ );
1136
+ const hasVerificationRule = await fileExists(verificationPath);
1137
+ if (!hasVerificationRule) {
1138
+ issues.push({
1139
+ analyzer: "Rules",
1140
+ severity: "medium",
1141
+ message: "No .claude/rules/verification.md found \u2014 nothing stops premature 'done' claims without evidence",
1142
+ fix: "Run `doctor --fix` to generate it"
1143
+ });
1144
+ } else {
1145
+ const vContent = await readFile2(verificationPath, "utf-8").catch(() => "");
1146
+ const vMatch = vContent.match(/<!-- lp-verification-version: (\d+) -->/);
1147
+ const vVersion = vMatch ? parseInt(vMatch[1], 10) : null;
1148
+ if (vVersion !== null && vVersion < VERIFICATION_RULE_VERSION) {
1149
+ issues.push({
1150
+ analyzer: "Rules",
1151
+ severity: "low",
1152
+ message: `verification.md rule is outdated (v${vVersion}, latest v${VERIFICATION_RULE_VERSION})`,
1153
+ fix: "Run `doctor --fix` to update it"
1154
+ });
1155
+ }
1156
+ }
1050
1157
  const hasClaudeignore = await fileExists(join4(projectRoot, ".claudeignore"));
1051
1158
  if (!hasClaudeignore) {
1052
1159
  issues.push({
@@ -1059,7 +1166,9 @@ async function analyzeRules(config) {
1059
1166
  const hasSkillInProject = config.skills.some(
1060
1167
  (s) => basename(s) === "SKILL.md" && s.includes("lp-enhance") || basename(s) === "lp-enhance.md"
1061
1168
  );
1062
- const hasSkillGlobal = await fileExists(join4(homedir3(), ".claude", "skills", "lp-enhance", "SKILL.md")) || await fileExists(join4(homedir3(), ".claude", "commands", "lp-enhance.md"));
1169
+ const hasSkillGlobal = await fileExists(
1170
+ join4(homedir3(), ".claude", "skills", "lp-enhance", "SKILL.md")
1171
+ ) || await fileExists(join4(homedir3(), ".claude", "commands", "lp-enhance.md"));
1063
1172
  if (!hasSkillInProject && !hasSkillGlobal) {
1064
1173
  issues.push({
1065
1174
  analyzer: "Rules",
@@ -1530,6 +1639,17 @@ async function analyzeMemory(config, projectRoot) {
1530
1639
  fix: "Run `doctor --fix` to add agentic-memory to allowedMcpServers"
1531
1640
  });
1532
1641
  }
1642
+ const hasBareBinaryHook = config.hooks.some(
1643
+ (h) => h.command?.includes("claude-launchpad memory") && !h.command.includes("npx claude-launchpad")
1644
+ );
1645
+ if (hasBareBinaryHook) {
1646
+ issues.push({
1647
+ analyzer: "Memory",
1648
+ severity: "medium",
1649
+ message: "Memory hook(s) call the bare claude-launchpad binary \u2014 fails silently on machines without a global install",
1650
+ fix: "Run `doctor --fix` to rewrite them to the npx form"
1651
+ });
1652
+ }
1533
1653
  const syncConfig = readSyncConfig();
1534
1654
  if (syncConfig) {
1535
1655
  const hasSessionStartPull = config.hooks.some(
@@ -2305,6 +2425,10 @@ async function listYamlFiles(dir) {
2305
2425
  return [];
2306
2426
  }
2307
2427
  }
2428
+ function resolveRuns(scenarioRuns, cliRuns, userChoseRuns) {
2429
+ if (userChoseRuns) return cliRuns;
2430
+ return scenarioRuns ?? cliRuns;
2431
+ }
2308
2432
 
2309
2433
  // src/commands/eval/runner.ts
2310
2434
  import { mkdir as mkdir2, writeFile as writeFile2, rm, cp } from "fs/promises";
@@ -2616,8 +2740,19 @@ async function scoreResults(scenario, sandboxDir, transcript, model) {
2616
2740
 
2617
2741
  // src/commands/eval/index.ts
2618
2742
  function createEvalCommand() {
2619
- return new Command3("eval").description("Test your Claude Code config against eval scenarios").option("-s, --suite <suite>", "Eval suite to run (e.g., security, conventions, workflow)").option("-p, --path <path>", "Project root path", process.cwd()).option("--scenarios <path>", "Custom scenarios directory").option("--runs <n>", "Runs per scenario (default: 3)", "3").option("--timeout <ms>", "Timeout per run in ms (default: 120000)", "120000").option("--json", "Output as JSON").option("--debug", "Keep sandbox directories for inspection").option("--model <model>", "Model to use for eval (e.g., sonnet, haiku, opus)").action(async (opts) => {
2743
+ return new Command3("eval").description("Test your Claude Code config against eval scenarios").option(
2744
+ "-s, --suite <suite>",
2745
+ "Eval suite to run (e.g., security, conventions, workflow)"
2746
+ ).option("-p, --path <path>", "Project root path", process.cwd()).option("--scenarios <path>", "Custom scenarios directory").option("--runs <n>", "Runs per scenario (default: 3)", "3").option(
2747
+ "--timeout <ms>",
2748
+ "Timeout per run in ms (default: 120000)",
2749
+ "120000"
2750
+ ).option("--json", "Output as JSON").option("--debug", "Keep sandbox directories for inspection").option(
2751
+ "--model <model>",
2752
+ "Model to use for eval (e.g., sonnet, haiku, opus)"
2753
+ ).action(async (opts, command) => {
2620
2754
  printBanner();
2755
+ let userChoseRuns = command.getOptionValueSource("runs") === "cli";
2621
2756
  const hasFlags = opts.suite || opts.model || opts.runs !== "3" || opts.timeout !== "120000" || opts.path !== process.cwd() || Boolean(opts.scenarios) || opts.json || opts.debug;
2622
2757
  if (!hasFlags) {
2623
2758
  opts.suite = await select2({
@@ -2625,8 +2760,8 @@ function createEvalCommand() {
2625
2760
  choices: [
2626
2761
  { name: "security (7 scenarios)", value: "security" },
2627
2762
  { name: "conventions (5 scenarios)", value: "conventions" },
2628
- { name: "workflow (4 scenarios)", value: "workflow" },
2629
- { name: "all (16 scenarios)", value: void 0 }
2763
+ { name: "workflow (5 scenarios)", value: "workflow" },
2764
+ { name: "all (17 scenarios)", value: void 0 }
2630
2765
  ]
2631
2766
  });
2632
2767
  opts.runs = await select2({
@@ -2637,6 +2772,7 @@ function createEvalCommand() {
2637
2772
  { name: "5 \u2014 thorough", value: "5" }
2638
2773
  ]
2639
2774
  });
2775
+ userChoseRuns = true;
2640
2776
  opts.model = await select2({
2641
2777
  message: "Model",
2642
2778
  choices: [
@@ -2649,8 +2785,12 @@ function createEvalCommand() {
2649
2785
  }
2650
2786
  const claudeAvailable = await checkClaudeCli();
2651
2787
  if (!claudeAvailable) {
2652
- log.error("Claude CLI not found. Install it: https://docs.anthropic.com/en/docs/claude-code");
2653
- log.info("The eval command runs Claude headless against scenarios \u2014 it requires the CLI.");
2788
+ log.error(
2789
+ "Claude CLI not found. Install it: https://docs.anthropic.com/en/docs/claude-code"
2790
+ );
2791
+ log.info(
2792
+ "The eval command runs Claude headless against scenarios \u2014 it requires the CLI."
2793
+ );
2654
2794
  process.exit(1);
2655
2795
  }
2656
2796
  log.step("Loading eval scenarios...");
@@ -2661,7 +2801,9 @@ function createEvalCommand() {
2661
2801
  if (scenarios.length === 0) {
2662
2802
  log.warn("No matching scenarios found.");
2663
2803
  if (opts.suite) {
2664
- log.info(`Check that the suite "${opts.suite}" exists in the scenarios directory.`);
2804
+ log.info(
2805
+ `Check that the suite "${opts.suite}" exists in the scenarios directory.`
2806
+ );
2665
2807
  }
2666
2808
  return;
2667
2809
  }
@@ -2670,10 +2812,11 @@ function createEvalCommand() {
2670
2812
  log.info(`Model: ${opts.model}`);
2671
2813
  }
2672
2814
  log.blank();
2673
- const runs = parseInt(opts.runs, 10);
2815
+ const cliRuns = parseInt(opts.runs, 10);
2674
2816
  const timeout = parseInt(opts.timeout, 10);
2675
2817
  const results = [];
2676
2818
  for (const scenario of scenarios) {
2819
+ const runs = resolveRuns(scenario.runs, cliRuns, userChoseRuns);
2677
2820
  const spinner = ora({
2678
2821
  text: `Running: ${scenario.name} (${runs} run${runs > 1 ? "s" : ""})`,
2679
2822
  prefixText: " "
@@ -2681,13 +2824,22 @@ function createEvalCommand() {
2681
2824
  try {
2682
2825
  const result = await runScenarioWithRetries(
2683
2826
  { ...scenario, runs },
2684
- { projectRoot: opts.path, timeout, debug: opts.debug, model: opts.model }
2827
+ {
2828
+ projectRoot: opts.path,
2829
+ timeout,
2830
+ debug: opts.debug,
2831
+ model: opts.model
2832
+ }
2685
2833
  );
2686
2834
  results.push(result);
2687
2835
  if (result.passed) {
2688
- spinner.succeed(`${scenario.name} ${result.score}/${result.maxScore}`);
2836
+ spinner.succeed(
2837
+ `${scenario.name} ${result.score}/${result.maxScore}`
2838
+ );
2689
2839
  } else {
2690
- spinner.fail(`${scenario.name} ${result.score}/${result.maxScore}`);
2840
+ spinner.fail(
2841
+ `${scenario.name} ${result.score}/${result.maxScore}`
2842
+ );
2691
2843
  }
2692
2844
  } catch (error) {
2693
2845
  spinner.fail(`${scenario.name} ERROR`);
@@ -2698,7 +2850,11 @@ function createEvalCommand() {
2698
2850
  score: 0,
2699
2851
  maxScore: scenario.checks.reduce((s, c) => s + c.points, 0),
2700
2852
  passed: false,
2701
- checks: scenario.checks.map((c) => ({ label: c.label, passed: false, points: c.points }))
2853
+ checks: scenario.checks.map((c) => ({
2854
+ label: c.label,
2855
+ passed: false,
2856
+ points: c.points
2857
+ }))
2702
2858
  });
2703
2859
  }
2704
2860
  }
@@ -2706,13 +2862,19 @@ function createEvalCommand() {
2706
2862
  if (opts.json) {
2707
2863
  const overallScore = results.reduce((s, r) => s + r.score, 0);
2708
2864
  const overallMax = results.reduce((s, r) => s + r.maxScore, 0);
2709
- console.log(JSON.stringify({
2710
- results,
2711
- overallScore,
2712
- overallMax,
2713
- passed: overallScore >= overallMax * 0.8,
2714
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
2715
- }, null, 2));
2865
+ console.log(
2866
+ JSON.stringify(
2867
+ {
2868
+ results,
2869
+ overallScore,
2870
+ overallMax,
2871
+ passed: overallScore >= overallMax * 0.8,
2872
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2873
+ },
2874
+ null,
2875
+ 2
2876
+ )
2877
+ );
2716
2878
  return;
2717
2879
  }
2718
2880
  renderEvalReport(results);
@@ -2724,7 +2886,9 @@ function renderEvalReport(results) {
2724
2886
  const icon = result.passed ? chalk2.green("\u2713") : chalk2.red("\u2717");
2725
2887
  const status = result.passed ? chalk2.green("PASS") : chalk2.red("FAIL");
2726
2888
  const score = `${result.score}/${result.maxScore}`;
2727
- console.log(` ${icon} ${chalk2.bold(result.scenario)} ${score} ${status}`);
2889
+ console.log(
2890
+ ` ${icon} ${chalk2.bold(result.scenario)} ${score} ${status}`
2891
+ );
2728
2892
  const failedChecks = result.checks.filter((c) => !c.passed);
2729
2893
  for (const check of failedChecks) {
2730
2894
  console.log(` ${chalk2.red("\u2717")} ${chalk2.dim(check.label)}`);
@@ -2741,7 +2905,9 @@ function renderEvalReport(results) {
2741
2905
  if (failed === 0) {
2742
2906
  log.success(`All ${passed} scenario(s) passed.`);
2743
2907
  } else {
2744
- log.warn(`${passed} passed, ${failed} failed out of ${results.length} scenario(s).`);
2908
+ log.warn(
2909
+ `${passed} passed, ${failed} failed out of ${results.length} scenario(s).`
2910
+ );
2745
2911
  }
2746
2912
  }
2747
2913
  async function saveEvalReport(results, projectRoot, suite, model) {
@@ -2765,7 +2931,9 @@ async function saveEvalReport(results, projectRoot, suite, model) {
2765
2931
  ];
2766
2932
  for (const result of results) {
2767
2933
  const status = result.passed ? "PASS" : "FAIL";
2768
- lines.push(`### ${result.scenario} \u2014 ${result.score}/${result.maxScore} ${status}`);
2934
+ lines.push(
2935
+ `### ${result.scenario} \u2014 ${result.score}/${result.maxScore} ${status}`
2936
+ );
2769
2937
  const failedChecks = result.checks.filter((c) => !c.passed);
2770
2938
  const passedChecks = result.checks.filter((c) => c.passed);
2771
2939
  for (const check of passedChecks) {
@@ -2783,7 +2951,9 @@ async function saveEvalReport(results, projectRoot, suite, model) {
2783
2951
  lines.push(`### Fix: ${result.scenario}`);
2784
2952
  const failedChecks = result.checks.filter((c) => !c.passed);
2785
2953
  for (const check of failedChecks) {
2786
- lines.push(`- ${check.label} \u2014 update CLAUDE.md instructions or add hooks to enforce this behavior`);
2954
+ lines.push(
2955
+ `- ${check.label} \u2014 update CLAUDE.md instructions or add hooks to enforce this behavior`
2956
+ );
2787
2957
  }
2788
2958
  lines.push("");
2789
2959
  }
@@ -2833,43 +3003,66 @@ function hasMemoryHook(path) {
2833
3003
  const sessionStart = hooks.SessionStart;
2834
3004
  return sessionStart?.some((h) => {
2835
3005
  const inner = h.hooks;
2836
- return inner?.some((ih) => String(ih.command ?? "").includes("memory context"));
3006
+ return inner?.some(
3007
+ (ih) => String(ih.command ?? "").includes("memory context")
3008
+ );
2837
3009
  }) ?? false;
2838
3010
  } catch {
2839
3011
  return false;
2840
3012
  }
2841
3013
  }
2842
3014
  function createMemoryCommand() {
2843
- const memory = new Command4("memory").description("Project-scoped memory with decay, sync, and a TUI dashboard").option("--dashboard", "Open the memory dashboard").action(async (opts) => {
3015
+ const memory = new Command4("memory").description("Project-scoped memory with decay, sync, and a TUI dashboard").showHelpAfterError(
3016
+ "(see `claude-launchpad memory --help` for the available subcommands)"
3017
+ ).showSuggestionAfterError(true).option("--dashboard", "Open the memory dashboard").action(async (opts) => {
2844
3018
  if (opts.dashboard) {
2845
3019
  if (!isMemoryInstalled()) {
2846
- log.error("Knowledge base not set up yet. Run `claude-launchpad memory` first.");
3020
+ log.error(
3021
+ "Knowledge base not set up yet. Run `claude-launchpad memory` first."
3022
+ );
2847
3023
  return;
2848
3024
  }
2849
- const { requireMemoryDeps } = await import("./require-deps-ZISUZLYD.js");
3025
+ const { requireMemoryDeps } = await import("./require-deps-WDMHDGBN.js");
2850
3026
  await requireMemoryDeps();
2851
- const { startTui } = await import("./tui-2UJ2OIHB.js");
3027
+ const { startTui } = await import("./tui-HVJNLRY2.js");
2852
3028
  await startTui();
2853
3029
  return;
2854
3030
  }
2855
3031
  if (!isMemoryInstalled()) {
2856
- const { detectExistingSetup } = await import("./install-Q23F37GK.js");
3032
+ if (!process.stdin.isTTY) {
3033
+ log.error(
3034
+ "Knowledge base not set up and no TTY to prompt. Run `claude-launchpad memory install -y`."
3035
+ );
3036
+ process.exitCode = 1;
3037
+ return;
3038
+ }
3039
+ const { detectExistingSetup } = await import("./install-D32PV7SF.js");
2857
3040
  const existing = detectExistingSetup(process.cwd());
2858
3041
  const mcpMissing = existing !== null && !isMemoryMcpRegistered(process.cwd());
2859
3042
  if (existing) {
2860
3043
  const location = existing === "local" ? ".claude/CLAUDE.md + settings.local.json" : "CLAUDE.md + settings.json";
2861
3044
  log.blank();
2862
- log.success(`Memory config found (${location}) but ${mcpMissing ? "MCP server not registered" : "database not set up"}.`);
3045
+ log.success(
3046
+ `Memory config found (${location}) but ${mcpMissing ? "MCP server not registered" : "database not set up"}.`
3047
+ );
2863
3048
  log.info("Run `claude-launchpad memory install` to complete setup.");
2864
3049
  log.blank();
2865
3050
  } else {
2866
3051
  log.blank();
2867
- log.step("Claude doesn't have a knowledge base for this project yet.");
3052
+ log.step(
3053
+ "Claude doesn't have a knowledge base for this project yet."
3054
+ );
2868
3055
  log.blank();
2869
3056
  log.info("After setup, Claude will:");
2870
- log.info(" - Remember decisions, gotchas, and learnings across sessions");
2871
- log.info(" - Automatically recall relevant context when you start a session");
2872
- log.info(" - Save important facts as you work, so nothing gets lost");
3057
+ log.info(
3058
+ " - Remember decisions, gotchas, and learnings across sessions"
3059
+ );
3060
+ log.info(
3061
+ " - Automatically recall relevant context when you start a session"
3062
+ );
3063
+ log.info(
3064
+ " - Save important facts as you work, so nothing gets lost"
3065
+ );
2873
3066
  log.blank();
2874
3067
  }
2875
3068
  const proceed = await confirm2({
@@ -2880,29 +3073,51 @@ function createMemoryCommand() {
2880
3073
  log.info("Skipped.");
2881
3074
  return;
2882
3075
  }
2883
- const { runInstall } = await import("./install-Q23F37GK.js");
3076
+ const { runInstall } = await import("./install-D32PV7SF.js");
2884
3077
  await runInstall({});
2885
3078
  } else {
2886
- const { requireMemoryDeps } = await import("./require-deps-ZISUZLYD.js");
3079
+ const { requireMemoryDeps } = await import("./require-deps-WDMHDGBN.js");
2887
3080
  await requireMemoryDeps();
2888
- const { runStats } = await import("./stats-SV53G6PQ.js");
3081
+ const { runStats } = await import("./stats-77BYLKYP.js");
2889
3082
  await runStats({});
2890
3083
  }
2891
3084
  });
2892
3085
  memory.addCommand(
2893
- new Command4("install").description("Install (or re-install) the knowledge base for this project").option("--db-path <path>", "Override the default data directory").option("-y, --yes", "Non-interactive: accept defaults (shared placement)").action(async (opts) => {
3086
+ new Command4("install").description(
3087
+ "Install (or re-install) the knowledge base for this project"
3088
+ ).option("--db-path <path>", "Override the default data directory").option(
3089
+ "-y, --yes",
3090
+ "Non-interactive: accept defaults (shared placement)"
3091
+ ).action(async (opts) => {
2894
3092
  try {
2895
- const { runInstall } = await import("./install-Q23F37GK.js");
2896
- await runInstall({ ...opts.dbPath ? { dbPath: opts.dbPath } : {}, yes: opts.yes === true });
3093
+ const { runInstall } = await import("./install-D32PV7SF.js");
3094
+ await runInstall({
3095
+ ...opts.dbPath ? { dbPath: opts.dbPath } : {},
3096
+ yes: opts.yes === true
3097
+ });
2897
3098
  } catch (err) {
2898
3099
  log.error(err instanceof Error ? err.message : String(err));
2899
3100
  process.exitCode = 1;
2900
3101
  }
2901
3102
  })
2902
3103
  );
3104
+ memory.addCommand(
3105
+ new Command4("stats").description("Show memory counts, health, and injection stats").option("--json", "JSON output").action(async (opts) => {
3106
+ const { requireMemoryDeps } = await import("./require-deps-WDMHDGBN.js");
3107
+ await requireMemoryDeps();
3108
+ const { runStats } = await import("./stats-77BYLKYP.js");
3109
+ await runStats(opts);
3110
+ })
3111
+ );
3112
+ memory.addCommand(
3113
+ new Command4("doctor").description("Diagnose the memory setup (DB, hooks, MCP registration)").option("--json", "JSON output").action(async (opts) => {
3114
+ const { runMemoryDoctor } = await import("./doctor-DCHFJ3XV.js");
3115
+ await runMemoryDoctor(opts);
3116
+ })
3117
+ );
2903
3118
  memory.addCommand(
2904
3119
  new Command4("context").description("Load session context (hook handler)").option("--json", "JSON output").action(async (opts) => {
2905
- const { runContext } = await import("./context-JALSYDYO.js");
3120
+ const { runContext } = await import("./context-YIPO4LBS.js");
2906
3121
  await runContext(opts);
2907
3122
  }).helpCommand(false),
2908
3123
  { hidden: true }
@@ -2917,29 +3132,34 @@ function createMemoryCommand() {
2917
3132
  memory.addCommand(
2918
3133
  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) => {
2919
3134
  await handleSyncErrors(async () => {
2920
- const { runPush } = await import("./push-QSBK7BQU.js");
3135
+ const { runPush } = await import("./push-U4IVTFOU.js");
2921
3136
  await runPush(opts);
2922
3137
  });
2923
3138
  })
2924
3139
  );
2925
3140
  memory.addCommand(
2926
- 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) => {
3141
+ new Command4("pull").description("Pull current project's memories from GitHub Gist").option("--all", "Pull all projects").option(
3142
+ "-y, --yes",
3143
+ "Non-interactive (accepted for symmetry with push; pull never prompts)"
3144
+ ).action(async (opts) => {
2927
3145
  await handleSyncErrors(async () => {
2928
- const { runPull } = await import("./pull-FYKNNLYC.js");
3146
+ const { runPull } = await import("./pull-YUQTDRUP.js");
2929
3147
  await runPull(opts);
2930
3148
  });
2931
3149
  })
2932
3150
  );
2933
- const sync = new Command4("sync").description("Sync memories with the gist (pull + push); subcommands manage sync state").option("--all", "Sync all projects").option("-y, --yes", "Skip confirmation prompt").action(async (opts) => {
3151
+ const sync = new Command4("sync").description(
3152
+ "Sync memories with the gist (pull + push); subcommands manage sync state"
3153
+ ).option("--all", "Sync all projects").option("-y, --yes", "Skip confirmation prompt").action(async (opts) => {
2934
3154
  await handleSyncErrors(async () => {
2935
- const { runSync } = await import("./sync-2LDM44U4.js");
3155
+ const { runSync } = await import("./sync-DTCWK2CY.js");
2936
3156
  await runSync(opts);
2937
3157
  });
2938
3158
  });
2939
3159
  sync.addCommand(
2940
3160
  new Command4("status").description("Show local vs remote memory counts per project").action(async () => {
2941
3161
  await handleSyncErrors(async () => {
2942
- const { runSyncStatus } = await import("./sync-status-V6WTI32X.js");
3162
+ const { runSyncStatus } = await import("./sync-status-2V2RCYZO.js");
2943
3163
  await runSyncStatus();
2944
3164
  });
2945
3165
  })
@@ -2947,7 +3167,7 @@ function createMemoryCommand() {
2947
3167
  sync.addCommand(
2948
3168
  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) => {
2949
3169
  await handleSyncErrors(async () => {
2950
- const { runSyncClean } = await import("./sync-clean-HQ523N7V.js");
3170
+ const { runSyncClean } = await import("./sync-clean-ACZCB3NZ.js");
2951
3171
  await runSyncClean(project, opts);
2952
3172
  });
2953
3173
  })
@@ -2957,7 +3177,7 @@ function createMemoryCommand() {
2957
3177
  }
2958
3178
 
2959
3179
  // src/cli.ts
2960
- var program = new Command5().name("claude-launchpad").description("Score your Claude Code config, fix the gaps, prove Claude follows your rules.").version("1.12.0", "-v, --version").action(async () => {
3180
+ var program = new Command5().name("claude-launchpad").description("Score your Claude Code config, fix the gaps, prove Claude follows your rules.").version("1.14.0", "-v, --version").action(async () => {
2961
3181
  const hasConfig = await fileExists(join13(process.cwd(), "CLAUDE.md")) || await fileExists(join13(process.cwd(), ".claude", "settings.json"));
2962
3182
  if (hasConfig) {
2963
3183
  await program.commands.find((c) => c.name() === "doctor")?.parseAsync([], { from: "user" });