claude-launchpad 1.12.0 → 1.15.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.
- package/README.md +11 -3
- package/dist/{chunk-DTCCQWOU.js → chunk-7KB5YGQZ.js} +3 -3
- package/dist/{chunk-DTCCQWOU.js.map → chunk-7KB5YGQZ.js.map} +1 -1
- package/dist/chunk-DLDVXHI3.js +184 -0
- package/dist/chunk-DLDVXHI3.js.map +1 -0
- package/dist/chunk-HSGSTSVN.js +212 -0
- package/dist/chunk-HSGSTSVN.js.map +1 -0
- package/dist/{chunk-DXDOVWOA.js → chunk-IDH3M4EI.js} +20 -232
- package/dist/chunk-IDH3M4EI.js.map +1 -0
- package/dist/{chunk-YZ53W47Z.js → chunk-IIWOSSL4.js} +2 -1
- package/dist/chunk-IIWOSSL4.js.map +1 -0
- package/dist/{chunk-ZLI4LI33.js → chunk-IPGNXDJF.js} +2 -2
- package/dist/{chunk-PH5OWJ42.js → chunk-LCKV24Q3.js} +9 -5
- package/dist/{chunk-PH5OWJ42.js.map → chunk-LCKV24Q3.js.map} +1 -1
- package/dist/chunk-MX3T2VEX.js +225 -0
- package/dist/chunk-MX3T2VEX.js.map +1 -0
- package/dist/{chunk-F6SLV2FR.js → chunk-N2SCF7DB.js} +5 -6
- package/dist/chunk-N2SCF7DB.js.map +1 -0
- package/dist/{chunk-KPO4YURF.js → chunk-U2TFIUGE.js} +513 -89
- package/dist/chunk-U2TFIUGE.js.map +1 -0
- package/dist/cli.js +424 -130
- package/dist/cli.js.map +1 -1
- package/dist/commands/memory/server.js +48 -14
- package/dist/commands/memory/server.js.map +1 -1
- package/dist/{context-JALSYDYO.js → context-3GHG7KW6.js} +58 -55
- package/dist/context-3GHG7KW6.js.map +1 -0
- package/dist/doctor-EKGJ2YAO.js +108 -0
- package/dist/doctor-EKGJ2YAO.js.map +1 -0
- package/dist/{install-Q23F37GK.js → install-543ZKMXX.js} +12 -10
- package/dist/install-543ZKMXX.js.map +1 -0
- package/dist/{pull-FYKNNLYC.js → pull-ULSZEVCR.js} +17 -19
- package/dist/pull-ULSZEVCR.js.map +1 -0
- package/dist/{push-QSBK7BQU.js → push-JXCWX4NY.js} +13 -12
- package/dist/push-JXCWX4NY.js.map +1 -0
- package/dist/{require-deps-ZISUZLYD.js → require-deps-AEDO5YRM.js} +3 -3
- package/dist/{stats-SV53G6PQ.js → stats-63L7C24W.js} +9 -7
- package/dist/{stats-SV53G6PQ.js.map → stats-63L7C24W.js.map} +1 -1
- package/dist/{sync-2LDM44U4.js → sync-QTJ6BXQ4.js} +5 -5
- package/dist/{sync-clean-HQ523N7V.js → sync-clean-OJEK4XH4.js} +4 -5
- package/dist/sync-clean-OJEK4XH4.js.map +1 -0
- package/dist/{sync-status-V6WTI32X.js → sync-status-EAX527SC.js} +13 -12
- package/dist/sync-status-EAX527SC.js.map +1 -0
- package/dist/{tui-2UJ2OIHB.js → tui-3KITZCNS.js} +11 -7
- package/dist/{tui-2UJ2OIHB.js.map → tui-3KITZCNS.js.map} +1 -1
- package/package.json +27 -7
- package/scenarios/workflow/premature-victory.yaml +44 -0
- package/dist/chunk-DXDOVWOA.js.map +0 -1
- package/dist/chunk-F6SLV2FR.js.map +0 -1
- package/dist/chunk-KPO4YURF.js.map +0 -1
- package/dist/chunk-YZ53W47Z.js.map +0 -1
- package/dist/chunk-ZIG75MOB.js +0 -366
- package/dist/chunk-ZIG75MOB.js.map +0 -1
- package/dist/context-JALSYDYO.js.map +0 -1
- package/dist/install-Q23F37GK.js.map +0 -1
- package/dist/pull-FYKNNLYC.js.map +0 -1
- package/dist/push-QSBK7BQU.js.map +0 -1
- package/dist/sync-clean-HQ523N7V.js.map +0 -1
- package/dist/sync-status-V6WTI32X.js.map +0 -1
- /package/dist/{chunk-ZLI4LI33.js.map → chunk-IPGNXDJF.js.map} +0 -0
- /package/dist/{require-deps-ZISUZLYD.js.map → require-deps-AEDO5YRM.js.map} +0 -0
- /package/dist/{sync-2LDM44U4.js.map → sync-QTJ6BXQ4.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
readSyncConfig
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-IPGNXDJF.js";
|
|
5
5
|
import {
|
|
6
6
|
BACKLOG_CONTENT,
|
|
7
7
|
ENHANCE_SKILL_VERSION,
|
|
8
|
+
FORCE_PUSH_ERE,
|
|
8
9
|
LP_STUB_OPEN,
|
|
9
10
|
OFF_LIMITS_CONTENT,
|
|
10
11
|
SESSION_START_CONTENT,
|
|
@@ -16,6 +17,7 @@ import {
|
|
|
16
17
|
STALE_SWARM_PHRASE,
|
|
17
18
|
STOP_AND_SWARM_CONTENT,
|
|
18
19
|
TESTING_DISCIPLINE_CONTENT,
|
|
20
|
+
VERIFICATION_RULE_VERSION,
|
|
19
21
|
WORKFLOW_CHECK_WRAPPER,
|
|
20
22
|
WORKFLOW_RULE_VERSION,
|
|
21
23
|
applyFixes,
|
|
@@ -26,6 +28,7 @@ import {
|
|
|
26
28
|
generateEnhanceSkill,
|
|
27
29
|
generateHooksRule,
|
|
28
30
|
generateReviewerAgent,
|
|
31
|
+
generateVerificationRule,
|
|
29
32
|
generateWorkflowRule,
|
|
30
33
|
hasEnvVarHookPattern,
|
|
31
34
|
isJqAvailable,
|
|
@@ -38,11 +41,11 @@ import {
|
|
|
38
41
|
sprintReviewsContent,
|
|
39
42
|
writeSprintHygieneScripts,
|
|
40
43
|
writeWorkflowCheckScript
|
|
41
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-U2TFIUGE.js";
|
|
42
45
|
|
|
43
46
|
// src/cli.ts
|
|
44
47
|
import { Command as Command5 } from "commander";
|
|
45
|
-
import { join as
|
|
48
|
+
import { join as join14 } from "path";
|
|
46
49
|
|
|
47
50
|
// src/commands/init/index.ts
|
|
48
51
|
import { Command } from "commander";
|
|
@@ -93,7 +96,8 @@ ${STOP_AND_SWARM_CONTENT}`);
|
|
|
93
96
|
sections.push("", `## Off-Limits
|
|
94
97
|
${OFF_LIMITS_CONTENT}`);
|
|
95
98
|
sections.push("", `## Key Decisions
|
|
96
|
-
<!--
|
|
99
|
+
<!-- Append one entry per non-obvious choice, at the moment it's made \u2014 not at sprint close: -->
|
|
100
|
+
<!-- YYYY-MM-DD \u2014 Chose X over Y because Z. Revisit if W. -->`);
|
|
97
101
|
return sections.join("\n") + "\n";
|
|
98
102
|
}
|
|
99
103
|
|
|
@@ -142,7 +146,7 @@ function generateSettings(detected) {
|
|
|
142
146
|
matcher: "Bash",
|
|
143
147
|
hooks: [{
|
|
144
148
|
type: "command",
|
|
145
|
-
command: `cmd=${jqField("command")}; echo "$cmd" | grep -qE 'rm\\s+-rf\\s+/|DROP\\s+TABLE|DROP\\s+DATABASE
|
|
149
|
+
command: `cmd=${jqField("command")}; echo "$cmd" | grep -qE 'rm\\s+-rf\\s+/|DROP\\s+TABLE|DROP\\s+DATABASE|${FORCE_PUSH_ERE}' && { echo 'BLOCKED: Destructive command detected' >&2; exit 2; }; exit 0`
|
|
146
150
|
}]
|
|
147
151
|
});
|
|
148
152
|
const editWriteHooks = [];
|
|
@@ -250,16 +254,22 @@ function dirTreeContains(dir, needle, depth) {
|
|
|
250
254
|
|
|
251
255
|
// src/commands/init/index.ts
|
|
252
256
|
function createInitCommand() {
|
|
253
|
-
return new Command("init").description("Set up Claude Code configuration for any project").option("-n, --name <name>", "Project name").option(
|
|
257
|
+
return new Command("init").description("Set up Claude Code configuration for any project").option("-n, --name <name>", "Project name").option(
|
|
258
|
+
"-y, --yes",
|
|
259
|
+
"Accept all defaults (does not overwrite existing files)"
|
|
260
|
+
).option("-f, --force", "Overwrite existing CLAUDE.md").action(async (opts) => {
|
|
254
261
|
printBanner();
|
|
255
262
|
const root = process.cwd();
|
|
256
263
|
log.step("Detecting project...");
|
|
257
264
|
const detected = await detectProject(root);
|
|
258
265
|
if (detected.language) {
|
|
259
266
|
log.success(`Found ${detected.framework ?? detected.language} project`);
|
|
260
|
-
if (detected.packageManager)
|
|
261
|
-
|
|
262
|
-
if (detected.
|
|
267
|
+
if (detected.packageManager)
|
|
268
|
+
log.info(`Package manager: ${detected.packageManager}`);
|
|
269
|
+
if (detected.devCommand)
|
|
270
|
+
log.info(`Dev command: ${detected.devCommand}`);
|
|
271
|
+
if (detected.testCommand)
|
|
272
|
+
log.info(`Test command: ${detected.testCommand}`);
|
|
263
273
|
} else {
|
|
264
274
|
log.warn("Could not detect project type \u2014 generating minimal config");
|
|
265
275
|
}
|
|
@@ -271,13 +281,18 @@ function createInitCommand() {
|
|
|
271
281
|
const description = opts.yes ? "" : await input({
|
|
272
282
|
message: "One-line description (optional):"
|
|
273
283
|
});
|
|
274
|
-
const options = {
|
|
284
|
+
const options = {
|
|
285
|
+
name: name.trim(),
|
|
286
|
+
description: description.trim()
|
|
287
|
+
};
|
|
275
288
|
const hasClaudeMd = await fileExists(join2(root, "CLAUDE.md"));
|
|
276
289
|
if (hasClaudeMd) {
|
|
277
290
|
if (opts.force) {
|
|
278
291
|
log.warn("Overwriting existing CLAUDE.md (--force)");
|
|
279
292
|
} else if (opts.yes) {
|
|
280
|
-
log.error(
|
|
293
|
+
log.error(
|
|
294
|
+
"CLAUDE.md already exists. `--yes` will not overwrite existing files."
|
|
295
|
+
);
|
|
281
296
|
log.info("Run `claude-launchpad doctor --fix` to update in place,");
|
|
282
297
|
log.info("or re-run with `--force` to overwrite.");
|
|
283
298
|
process.exitCode = 1;
|
|
@@ -290,7 +305,9 @@ function createInitCommand() {
|
|
|
290
305
|
if (!overwrite) {
|
|
291
306
|
log.info("Keeping existing CLAUDE.md");
|
|
292
307
|
await createEnhanceSkillPrompt(root, false);
|
|
293
|
-
log.step(
|
|
308
|
+
log.step(
|
|
309
|
+
"Tip: run `claude-launchpad doctor` to check your existing config"
|
|
310
|
+
);
|
|
294
311
|
return;
|
|
295
312
|
}
|
|
296
313
|
}
|
|
@@ -300,14 +317,19 @@ function createInitCommand() {
|
|
|
300
317
|
}
|
|
301
318
|
async function scaffold(root, options, detected, skipPrompts) {
|
|
302
319
|
log.step("Generating configuration...");
|
|
303
|
-
const claudeMd = generateClaudeMd(options, detected, {
|
|
320
|
+
const claudeMd = generateClaudeMd(options, detected, {
|
|
321
|
+
superpowers: isSuperpowersInstalled()
|
|
322
|
+
});
|
|
304
323
|
const tasksMd = generateTasksMd(options);
|
|
305
324
|
const backlogMd = generateBacklogMd(options);
|
|
306
325
|
const settings = generateSettings(detected);
|
|
307
326
|
const claudeignore = generateClaudeignore(detected);
|
|
308
327
|
await mkdir(join2(root, ".claude", "rules"), { recursive: true });
|
|
309
328
|
const settingsPath = join2(root, ".claude", "settings.json");
|
|
310
|
-
const mergedSettings = await mergeSettings(
|
|
329
|
+
const mergedSettings = await mergeSettings(
|
|
330
|
+
settingsPath,
|
|
331
|
+
settings
|
|
332
|
+
);
|
|
311
333
|
const backlogPath = join2(root, "BACKLOG.md");
|
|
312
334
|
const hasBacklog = await fileExists(backlogPath);
|
|
313
335
|
const claudeignorePath = join2(root, ".claudeignore");
|
|
@@ -320,6 +342,13 @@ async function scaffold(root, options, detected, skipPrompts) {
|
|
|
320
342
|
const hasWorkflowRule = await fileExists(workflowRulePath);
|
|
321
343
|
const hooksRulePath = join2(root, ".claude", "rules", "hooks.md");
|
|
322
344
|
const hasHooksRule = await fileExists(hooksRulePath);
|
|
345
|
+
const verificationRulePath = join2(
|
|
346
|
+
root,
|
|
347
|
+
".claude",
|
|
348
|
+
"rules",
|
|
349
|
+
"verification.md"
|
|
350
|
+
);
|
|
351
|
+
const hasVerificationRule = await fileExists(verificationRulePath);
|
|
323
352
|
const writes = [
|
|
324
353
|
writeFile(join2(root, "CLAUDE.md"), claudeMd),
|
|
325
354
|
writeFile(join2(root, "TASKS.md"), tasksMd),
|
|
@@ -332,16 +361,21 @@ async function scaffold(root, options, detected, skipPrompts) {
|
|
|
332
361
|
writes.push(writeFile(claudeignorePath, claudeignore));
|
|
333
362
|
}
|
|
334
363
|
if (!hasClaudeGitignore) {
|
|
335
|
-
writes.push(
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
364
|
+
writes.push(
|
|
365
|
+
writeFile(
|
|
366
|
+
claudeGitignorePath,
|
|
367
|
+
[
|
|
368
|
+
"# Local-only Claude Code files (never commit these)",
|
|
369
|
+
"CLAUDE.md",
|
|
370
|
+
"settings.local.json",
|
|
371
|
+
"plans/",
|
|
372
|
+
"memory/",
|
|
373
|
+
"sessions/",
|
|
374
|
+
"tmp/",
|
|
375
|
+
""
|
|
376
|
+
].join("\n")
|
|
377
|
+
)
|
|
378
|
+
);
|
|
345
379
|
}
|
|
346
380
|
if (!hasRules) {
|
|
347
381
|
const rulesContent = generateStarterRules(detected);
|
|
@@ -353,6 +387,9 @@ async function scaffold(root, options, detected, skipPrompts) {
|
|
|
353
387
|
if (!hasHooksRule) {
|
|
354
388
|
writes.push(writeFile(hooksRulePath, generateHooksRule()));
|
|
355
389
|
}
|
|
390
|
+
if (!hasVerificationRule) {
|
|
391
|
+
writes.push(writeFile(verificationRulePath, generateVerificationRule()));
|
|
392
|
+
}
|
|
356
393
|
const reviewerAgentPath = join2(root, ".claude", "agents", "code-reviewer.md");
|
|
357
394
|
const hasReviewerAgent = await fileExists(reviewerAgentPath);
|
|
358
395
|
if (!hasReviewerAgent) {
|
|
@@ -363,26 +400,47 @@ async function scaffold(root, options, detected, skipPrompts) {
|
|
|
363
400
|
await writeSprintHygieneScripts(root);
|
|
364
401
|
await writeWorkflowCheckScript(root);
|
|
365
402
|
log.success("Generated CLAUDE.md");
|
|
366
|
-
if (!hasReviewerAgent)
|
|
403
|
+
if (!hasReviewerAgent)
|
|
404
|
+
log.success(
|
|
405
|
+
"Generated .claude/agents/code-reviewer.md (independent sprint reviewer)"
|
|
406
|
+
);
|
|
367
407
|
log.success("Generated TASKS.md");
|
|
368
408
|
if (!hasBacklog) log.success("Generated BACKLOG.md");
|
|
369
409
|
log.success("Generated .claude/settings.json (schema, permissions, hooks)");
|
|
370
410
|
if (!hasClaudeGitignore) log.success("Generated .claude/.gitignore");
|
|
371
411
|
if (!hasClaudeignore) log.success("Generated .claudeignore");
|
|
372
412
|
if (!hasRules) log.success("Generated .claude/rules/conventions.md");
|
|
373
|
-
if (!hasWorkflowRule)
|
|
374
|
-
|
|
375
|
-
|
|
413
|
+
if (!hasWorkflowRule)
|
|
414
|
+
log.success(
|
|
415
|
+
"Generated .claude/rules/workflow.md (workflow rules, path-scoped)"
|
|
416
|
+
);
|
|
417
|
+
if (!hasHooksRule)
|
|
418
|
+
log.success(
|
|
419
|
+
"Generated .claude/rules/hooks.md (hook authoring rules, path-scoped)"
|
|
420
|
+
);
|
|
421
|
+
if (!hasVerificationRule)
|
|
422
|
+
log.success(
|
|
423
|
+
"Generated .claude/rules/verification.md (evidence-before-assertion discipline)"
|
|
424
|
+
);
|
|
425
|
+
log.success(
|
|
426
|
+
"Generated .claude/hooks/sprint-{size,open}-check.sh + workflow-check.sh"
|
|
427
|
+
);
|
|
376
428
|
await createEnhanceSkillPrompt(root, skipPrompts);
|
|
377
429
|
log.blank();
|
|
378
430
|
if (!isJqAvailable()) {
|
|
379
|
-
log.warn(
|
|
431
|
+
log.warn(
|
|
432
|
+
"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/"
|
|
433
|
+
);
|
|
380
434
|
}
|
|
381
435
|
if (!isSuperpowersInstalled()) {
|
|
382
|
-
log.info(
|
|
436
|
+
log.info(
|
|
437
|
+
"Optional: the superpowers plugin adds brainstorm/plan/TDD/review discipline \u2014 /plugin install superpowers@claude-plugins-official"
|
|
438
|
+
);
|
|
383
439
|
}
|
|
384
440
|
log.success("Done! Run `claude` to start.");
|
|
385
|
-
log.info(
|
|
441
|
+
log.info(
|
|
442
|
+
"Use `/lp-enhance` inside Claude Code to have AI complete your CLAUDE.md."
|
|
443
|
+
);
|
|
386
444
|
log.info("Run `claude-launchpad doctor` to check your config quality.");
|
|
387
445
|
log.blank();
|
|
388
446
|
}
|
|
@@ -414,19 +472,32 @@ function generateStarterRules(detected) {
|
|
|
414
472
|
lines.push("", "## Testing Discipline", "", TESTING_DISCIPLINE_CONTENT);
|
|
415
473
|
const verify = [detected.testCommand, detected.lintCommand].filter((c) => !!c).map((c) => `\`${c}\``).join(" && ");
|
|
416
474
|
lines.push("", "## Pre-Commit Checklist", "");
|
|
417
|
-
lines.push(
|
|
418
|
-
|
|
419
|
-
|
|
475
|
+
lines.push(
|
|
476
|
+
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"
|
|
477
|
+
);
|
|
478
|
+
lines.push(
|
|
479
|
+
"2. For hard-TDD surfaces, confirm the test was written before the implementation"
|
|
480
|
+
);
|
|
481
|
+
lines.push(
|
|
482
|
+
"3. Before sprint-ending commits: run /code-review on the sprint diff (see ## Sprint Reviews in CLAUDE.md)"
|
|
483
|
+
);
|
|
420
484
|
lines.push("", "## Skill Authoring", "", SKILL_AUTHORING_CONTENT);
|
|
421
485
|
lines.push("");
|
|
422
486
|
return lines.join("\n");
|
|
423
487
|
}
|
|
424
488
|
async function createEnhanceSkillPrompt(root, skipPrompts) {
|
|
425
489
|
const projectPath = join2(root, ".claude", "skills", "lp-enhance", "SKILL.md");
|
|
426
|
-
const globalPath = join2(
|
|
490
|
+
const globalPath = join2(
|
|
491
|
+
homedir2(),
|
|
492
|
+
".claude",
|
|
493
|
+
"skills",
|
|
494
|
+
"lp-enhance",
|
|
495
|
+
"SKILL.md"
|
|
496
|
+
);
|
|
427
497
|
const legacyProject = join2(root, ".claude", "commands", "lp-enhance.md");
|
|
428
498
|
const legacyGlobal = join2(homedir2(), ".claude", "commands", "lp-enhance.md");
|
|
429
|
-
if (await fileExists(projectPath) || await fileExists(globalPath) || await fileExists(legacyProject) || await fileExists(legacyGlobal))
|
|
499
|
+
if (await fileExists(projectPath) || await fileExists(globalPath) || await fileExists(legacyProject) || await fileExists(legacyGlobal))
|
|
500
|
+
return;
|
|
430
501
|
const scope = skipPrompts ? "project" : await select({
|
|
431
502
|
message: "Install /lp-enhance skill (AI-powered CLAUDE.md improver):",
|
|
432
503
|
choices: [
|
|
@@ -443,7 +514,9 @@ async function createEnhanceSkillPrompt(root, skipPrompts) {
|
|
|
443
514
|
}
|
|
444
515
|
async function mergeSettings(existingPath, generated) {
|
|
445
516
|
try {
|
|
446
|
-
const existing = JSON.parse(
|
|
517
|
+
const existing = JSON.parse(
|
|
518
|
+
await readFile(existingPath, "utf-8")
|
|
519
|
+
);
|
|
447
520
|
const existingHooks = existing.hooks ?? {};
|
|
448
521
|
const generatedHooks = generated.hooks ?? {};
|
|
449
522
|
const mergedHooks = { ...existingHooks };
|
|
@@ -834,7 +907,28 @@ async function analyzeHooks(config, projectRoot = "") {
|
|
|
834
907
|
});
|
|
835
908
|
return { name: "Hooks", issues, score: 30 };
|
|
836
909
|
}
|
|
837
|
-
const
|
|
910
|
+
const hasStaleForcePattern = config.hooks.some(
|
|
911
|
+
(h) => h.command?.includes("push.*--force|push.*-f")
|
|
912
|
+
);
|
|
913
|
+
if (hasStaleForcePattern) {
|
|
914
|
+
issues.push({
|
|
915
|
+
analyzer: "Hooks",
|
|
916
|
+
severity: "low",
|
|
917
|
+
message: "Force-push guard uses the outdated pattern \u2014 it false-positives on unrelated commands (git stash push, --frozen-lockfile, filenames)",
|
|
918
|
+
fix: "Run `doctor --fix` to upgrade it to the anchored pattern"
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
const formatPatterns = [
|
|
922
|
+
"format",
|
|
923
|
+
"prettier",
|
|
924
|
+
"gofmt",
|
|
925
|
+
"rustfmt",
|
|
926
|
+
"rubocop",
|
|
927
|
+
"pint",
|
|
928
|
+
"ktlint",
|
|
929
|
+
"swift-format",
|
|
930
|
+
"dotnet format"
|
|
931
|
+
];
|
|
838
932
|
const hasPostFormat = hooks.some(
|
|
839
933
|
(h) => h.event === "PostToolUse" && h.matcher?.includes("Write") && formatPatterns.some((p) => h.command?.includes(p))
|
|
840
934
|
);
|
|
@@ -976,10 +1070,14 @@ async function analyzeHooks(config, projectRoot = "") {
|
|
|
976
1070
|
}
|
|
977
1071
|
async function hasStaleHygieneScript(projectRoot) {
|
|
978
1072
|
const { readFile: readFile7 } = await import("fs/promises");
|
|
979
|
-
const { join:
|
|
1073
|
+
const { join: join15 } = await import("path");
|
|
980
1074
|
for (const name of ["workflow-check.sh", "sprint-open-check.sh"]) {
|
|
981
|
-
const content = await readFile7(
|
|
982
|
-
|
|
1075
|
+
const content = await readFile7(
|
|
1076
|
+
join15(projectRoot, ".claude", "hooks", name),
|
|
1077
|
+
"utf-8"
|
|
1078
|
+
).catch(() => null);
|
|
1079
|
+
if (content !== null && !content.includes("hookSpecificOutput"))
|
|
1080
|
+
return true;
|
|
983
1081
|
}
|
|
984
1082
|
return false;
|
|
985
1083
|
}
|
|
@@ -1000,7 +1098,9 @@ async function analyzeRules(config) {
|
|
|
1000
1098
|
fix: "Run `claude-launchpad init` or `doctor --fix` to generate one"
|
|
1001
1099
|
});
|
|
1002
1100
|
}
|
|
1003
|
-
const hasWorkflowRule = await fileExists(
|
|
1101
|
+
const hasWorkflowRule = await fileExists(
|
|
1102
|
+
join4(projectRoot, ".claude", "rules", "workflow.md")
|
|
1103
|
+
);
|
|
1004
1104
|
if (!hasWorkflowRule) {
|
|
1005
1105
|
issues.push({
|
|
1006
1106
|
analyzer: "Rules",
|
|
@@ -1009,7 +1109,9 @@ async function analyzeRules(config) {
|
|
|
1009
1109
|
fix: "Run `doctor --fix` to generate it"
|
|
1010
1110
|
});
|
|
1011
1111
|
}
|
|
1012
|
-
const hasReviewerAgent = await fileExists(
|
|
1112
|
+
const hasReviewerAgent = await fileExists(
|
|
1113
|
+
join4(projectRoot, ".claude", "agents", "code-reviewer.md")
|
|
1114
|
+
);
|
|
1013
1115
|
if (!hasReviewerAgent) {
|
|
1014
1116
|
issues.push({
|
|
1015
1117
|
analyzer: "Rules",
|
|
@@ -1026,7 +1128,10 @@ async function analyzeRules(config) {
|
|
|
1026
1128
|
});
|
|
1027
1129
|
}
|
|
1028
1130
|
if (hasWorkflowRule) {
|
|
1029
|
-
const wfContent = await readFile2(
|
|
1131
|
+
const wfContent = await readFile2(
|
|
1132
|
+
join4(projectRoot, ".claude", "rules", "workflow.md"),
|
|
1133
|
+
"utf-8"
|
|
1134
|
+
).catch(() => "");
|
|
1030
1135
|
const wfMatch = wfContent.match(/<!-- lp-workflow-version: (\d+) -->/);
|
|
1031
1136
|
const wfVersion = wfMatch ? parseInt(wfMatch[1], 10) : null;
|
|
1032
1137
|
if (wfVersion !== null && wfVersion < WORKFLOW_RULE_VERSION) {
|
|
@@ -1038,7 +1143,9 @@ async function analyzeRules(config) {
|
|
|
1038
1143
|
});
|
|
1039
1144
|
}
|
|
1040
1145
|
}
|
|
1041
|
-
const hasHooksRule = await fileExists(
|
|
1146
|
+
const hasHooksRule = await fileExists(
|
|
1147
|
+
join4(projectRoot, ".claude", "rules", "hooks.md")
|
|
1148
|
+
);
|
|
1042
1149
|
if (!hasHooksRule) {
|
|
1043
1150
|
issues.push({
|
|
1044
1151
|
analyzer: "Rules",
|
|
@@ -1047,6 +1154,33 @@ async function analyzeRules(config) {
|
|
|
1047
1154
|
fix: "Run `doctor --fix` to generate it"
|
|
1048
1155
|
});
|
|
1049
1156
|
}
|
|
1157
|
+
const verificationPath = join4(
|
|
1158
|
+
projectRoot,
|
|
1159
|
+
".claude",
|
|
1160
|
+
"rules",
|
|
1161
|
+
"verification.md"
|
|
1162
|
+
);
|
|
1163
|
+
const hasVerificationRule = await fileExists(verificationPath);
|
|
1164
|
+
if (!hasVerificationRule) {
|
|
1165
|
+
issues.push({
|
|
1166
|
+
analyzer: "Rules",
|
|
1167
|
+
severity: "medium",
|
|
1168
|
+
message: "No .claude/rules/verification.md found \u2014 nothing stops premature 'done' claims without evidence",
|
|
1169
|
+
fix: "Run `doctor --fix` to generate it"
|
|
1170
|
+
});
|
|
1171
|
+
} else {
|
|
1172
|
+
const vContent = await readFile2(verificationPath, "utf-8").catch(() => "");
|
|
1173
|
+
const vMatch = vContent.match(/<!-- lp-verification-version: (\d+) -->/);
|
|
1174
|
+
const vVersion = vMatch ? parseInt(vMatch[1], 10) : null;
|
|
1175
|
+
if (vVersion !== null && vVersion < VERIFICATION_RULE_VERSION) {
|
|
1176
|
+
issues.push({
|
|
1177
|
+
analyzer: "Rules",
|
|
1178
|
+
severity: "low",
|
|
1179
|
+
message: `verification.md rule is outdated (v${vVersion}, latest v${VERIFICATION_RULE_VERSION})`,
|
|
1180
|
+
fix: "Run `doctor --fix` to update it"
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1050
1184
|
const hasClaudeignore = await fileExists(join4(projectRoot, ".claudeignore"));
|
|
1051
1185
|
if (!hasClaudeignore) {
|
|
1052
1186
|
issues.push({
|
|
@@ -1059,7 +1193,9 @@ async function analyzeRules(config) {
|
|
|
1059
1193
|
const hasSkillInProject = config.skills.some(
|
|
1060
1194
|
(s) => basename(s) === "SKILL.md" && s.includes("lp-enhance") || basename(s) === "lp-enhance.md"
|
|
1061
1195
|
);
|
|
1062
|
-
const hasSkillGlobal = await fileExists(
|
|
1196
|
+
const hasSkillGlobal = await fileExists(
|
|
1197
|
+
join4(homedir3(), ".claude", "skills", "lp-enhance", "SKILL.md")
|
|
1198
|
+
) || await fileExists(join4(homedir3(), ".claude", "commands", "lp-enhance.md"));
|
|
1063
1199
|
if (!hasSkillInProject && !hasSkillGlobal) {
|
|
1064
1200
|
issues.push({
|
|
1065
1201
|
analyzer: "Rules",
|
|
@@ -1371,6 +1507,11 @@ function extractAllow(settings) {
|
|
|
1371
1507
|
return Array.isArray(allow) ? allow.filter((e) => typeof e === "string") : [];
|
|
1372
1508
|
}
|
|
1373
1509
|
|
|
1510
|
+
// src/commands/doctor/analyzers/quality.ts
|
|
1511
|
+
import { execSync } from "child_process";
|
|
1512
|
+
import { existsSync as existsSync2 } from "fs";
|
|
1513
|
+
import { join as join6 } from "path";
|
|
1514
|
+
|
|
1374
1515
|
// src/commands/doctor/analyzers/hook-resolver.ts
|
|
1375
1516
|
import { readFile as readFile3, realpath } from "fs/promises";
|
|
1376
1517
|
import { resolve as resolve2, sep } from "path";
|
|
@@ -1530,6 +1671,17 @@ async function analyzeMemory(config, projectRoot) {
|
|
|
1530
1671
|
fix: "Run `doctor --fix` to add agentic-memory to allowedMcpServers"
|
|
1531
1672
|
});
|
|
1532
1673
|
}
|
|
1674
|
+
const hasBareBinaryHook = config.hooks.some(
|
|
1675
|
+
(h) => h.command?.includes("claude-launchpad memory") && !h.command.includes("npx claude-launchpad")
|
|
1676
|
+
);
|
|
1677
|
+
if (hasBareBinaryHook) {
|
|
1678
|
+
issues.push({
|
|
1679
|
+
analyzer: "Memory",
|
|
1680
|
+
severity: "medium",
|
|
1681
|
+
message: "Memory hook(s) call the bare claude-launchpad binary \u2014 fails silently on machines without a global install",
|
|
1682
|
+
fix: "Run `doctor --fix` to rewrite them to the npx form"
|
|
1683
|
+
});
|
|
1684
|
+
}
|
|
1533
1685
|
const syncConfig = readSyncConfig();
|
|
1534
1686
|
if (syncConfig) {
|
|
1535
1687
|
const hasSessionStartPull = config.hooks.some(
|
|
@@ -1774,7 +1926,10 @@ function documentSatisfiesIntent(sections, rule) {
|
|
|
1774
1926
|
|
|
1775
1927
|
// src/commands/doctor/analyzers/quality.ts
|
|
1776
1928
|
var VAGUE_PATTERNS = [
|
|
1777
|
-
{
|
|
1929
|
+
{
|
|
1930
|
+
pattern: /write (good|clean|quality|nice) code/i,
|
|
1931
|
+
label: "write good code"
|
|
1932
|
+
},
|
|
1778
1933
|
{ pattern: /be (careful|thorough|diligent)/i, label: "be careful" },
|
|
1779
1934
|
{ pattern: /follow best practices/i, label: "follow best practices" },
|
|
1780
1935
|
{ pattern: /make sure (everything|it) works/i, label: "make sure it works" }
|
|
@@ -1857,17 +2012,56 @@ async function analyzeQuality(config, projectRoot) {
|
|
|
1857
2012
|
fix: "Run `doctor --fix` to collapse them"
|
|
1858
2013
|
});
|
|
1859
2014
|
}
|
|
2015
|
+
if (isKeyDecisionsPlaceholder(content) && commitCount(projectRoot) >= 20) {
|
|
2016
|
+
issues.push({
|
|
2017
|
+
analyzer: "Quality",
|
|
2018
|
+
severity: "low",
|
|
2019
|
+
message: "Key Decisions is still placeholder-only after 20+ commits \u2014 decisions are being made but not logged",
|
|
2020
|
+
fix: "Append entries at decision time: `YYYY-MM-DD \u2014 Chose X over Y because Z. Revisit if W.`"
|
|
2021
|
+
});
|
|
2022
|
+
}
|
|
1860
2023
|
const criticals = issues.filter((i) => i.severity === "critical").length;
|
|
1861
2024
|
const highs = issues.filter((i) => i.severity === "high").length;
|
|
1862
2025
|
const mediums = issues.filter((i) => i.severity === "medium").length;
|
|
1863
2026
|
const lows = issues.filter((i) => i.severity === "low").length;
|
|
1864
|
-
const score = Math.max(
|
|
2027
|
+
const score = Math.max(
|
|
2028
|
+
0,
|
|
2029
|
+
100 - criticals * 40 - highs * 30 - mediums * 15 - lows * 5
|
|
2030
|
+
);
|
|
1865
2031
|
return { name: "CLAUDE.md Quality", issues, score };
|
|
1866
2032
|
}
|
|
2033
|
+
function isKeyDecisionsPlaceholder(content) {
|
|
2034
|
+
const lines = content.split("\n");
|
|
2035
|
+
const start = lines.findIndex((l) => /^## Key Decisions\s*$/.test(l));
|
|
2036
|
+
if (start === -1) return false;
|
|
2037
|
+
let end = lines.length;
|
|
2038
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
2039
|
+
if (/^## /.test(lines[i])) {
|
|
2040
|
+
end = i;
|
|
2041
|
+
break;
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
const body = lines.slice(start + 1, end).join("\n").replace(/<!--[\s\S]*?-->/g, "").trim();
|
|
2045
|
+
return body.length === 0;
|
|
2046
|
+
}
|
|
2047
|
+
function commitCount(projectRoot) {
|
|
2048
|
+
if (!existsSync2(join6(projectRoot, ".git"))) return 0;
|
|
2049
|
+
try {
|
|
2050
|
+
return parseInt(
|
|
2051
|
+
execSync("git rev-list --count HEAD", {
|
|
2052
|
+
cwd: projectRoot,
|
|
2053
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2054
|
+
}).toString().trim(),
|
|
2055
|
+
10
|
|
2056
|
+
) || 0;
|
|
2057
|
+
} catch {
|
|
2058
|
+
return 0;
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
1867
2061
|
|
|
1868
2062
|
// src/commands/doctor/analyzers/workflow.ts
|
|
1869
2063
|
import { readFile as readFile4 } from "fs/promises";
|
|
1870
|
-
import { join as
|
|
2064
|
+
import { join as join7 } from "path";
|
|
1871
2065
|
var WP_FIELDS = [
|
|
1872
2066
|
"Priority:",
|
|
1873
2067
|
"Proposed:",
|
|
@@ -1880,7 +2074,7 @@ var WP_FIELDS = [
|
|
|
1880
2074
|
var STALE_P0_DAYS = 14;
|
|
1881
2075
|
var STALE_CHANGELOG_DAYS = 30;
|
|
1882
2076
|
async function analyzeWorkflow(projectRoot, now = /* @__PURE__ */ new Date()) {
|
|
1883
|
-
const backlog = await readFile4(
|
|
2077
|
+
const backlog = await readFile4(join7(projectRoot, "BACKLOG.md"), "utf-8").catch(() => null);
|
|
1884
2078
|
if (backlog === null) return null;
|
|
1885
2079
|
const issues = [];
|
|
1886
2080
|
const pSections = extractPSections(backlog);
|
|
@@ -1961,7 +2155,7 @@ function daysBetween(isoDate, now) {
|
|
|
1961
2155
|
|
|
1962
2156
|
// src/commands/doctor/watcher.ts
|
|
1963
2157
|
import { readdir as readdir2, stat } from "fs/promises";
|
|
1964
|
-
import { join as
|
|
2158
|
+
import { join as join8 } from "path";
|
|
1965
2159
|
async function watchConfig(projectRoot) {
|
|
1966
2160
|
await runAndDisplay(projectRoot);
|
|
1967
2161
|
log.blank();
|
|
@@ -1984,16 +2178,16 @@ async function watchConfig(projectRoot) {
|
|
|
1984
2178
|
}
|
|
1985
2179
|
async function getFileSnapshot(projectRoot) {
|
|
1986
2180
|
const files = [
|
|
1987
|
-
|
|
1988
|
-
|
|
2181
|
+
join8(projectRoot, "CLAUDE.md"),
|
|
2182
|
+
join8(projectRoot, ".claudeignore")
|
|
1989
2183
|
];
|
|
1990
|
-
const claudeDir =
|
|
2184
|
+
const claudeDir = join8(projectRoot, ".claude");
|
|
1991
2185
|
try {
|
|
1992
2186
|
const entries = await readdir2(claudeDir, { withFileTypes: true, recursive: true });
|
|
1993
2187
|
for (const entry of entries) {
|
|
1994
2188
|
if (entry.isFile()) {
|
|
1995
2189
|
const parentPath = entry.parentPath ?? claudeDir;
|
|
1996
|
-
files.push(
|
|
2190
|
+
files.push(join8(parentPath, entry.name));
|
|
1997
2191
|
}
|
|
1998
2192
|
}
|
|
1999
2193
|
} catch {
|
|
@@ -2137,11 +2331,11 @@ import { select as select2 } from "@inquirer/prompts";
|
|
|
2137
2331
|
import ora from "ora";
|
|
2138
2332
|
import chalk2 from "chalk";
|
|
2139
2333
|
import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
|
|
2140
|
-
import { join as
|
|
2334
|
+
import { join as join12 } from "path";
|
|
2141
2335
|
|
|
2142
2336
|
// src/commands/eval/loader.ts
|
|
2143
2337
|
import { readFile as readFile5, readdir as readdir3 } from "fs/promises";
|
|
2144
|
-
import { join as
|
|
2338
|
+
import { join as join9, resolve as resolve3, dirname as dirname2 } from "path";
|
|
2145
2339
|
import { fileURLToPath } from "url";
|
|
2146
2340
|
import { parse as parseYaml } from "yaml";
|
|
2147
2341
|
|
|
@@ -2270,7 +2464,7 @@ async function findScenariosDir() {
|
|
|
2270
2464
|
async function loadScenarios(options) {
|
|
2271
2465
|
const { suite, customPath } = options;
|
|
2272
2466
|
const scenarioDir = customPath ? resolve3(customPath) : await findScenariosDir();
|
|
2273
|
-
const dirs = suite ? [
|
|
2467
|
+
const dirs = suite ? [join9(scenarioDir, suite)] : await getSubdirectories(scenarioDir);
|
|
2274
2468
|
const allDirs = [scenarioDir, ...dirs];
|
|
2275
2469
|
const scenarios = [];
|
|
2276
2470
|
for (const dir of allDirs) {
|
|
@@ -2292,7 +2486,7 @@ async function loadScenarios(options) {
|
|
|
2292
2486
|
async function getSubdirectories(dir) {
|
|
2293
2487
|
try {
|
|
2294
2488
|
const entries = await readdir3(dir, { withFileTypes: true });
|
|
2295
|
-
return entries.filter((e) => e.isDirectory()).map((e) =>
|
|
2489
|
+
return entries.filter((e) => e.isDirectory()).map((e) => join9(dir, e.name));
|
|
2296
2490
|
} catch {
|
|
2297
2491
|
return [];
|
|
2298
2492
|
}
|
|
@@ -2300,15 +2494,19 @@ async function getSubdirectories(dir) {
|
|
|
2300
2494
|
async function listYamlFiles(dir) {
|
|
2301
2495
|
try {
|
|
2302
2496
|
const entries = await readdir3(dir, { withFileTypes: true });
|
|
2303
|
-
return entries.filter((e) => e.isFile() && (e.name.endsWith(".yaml") || e.name.endsWith(".yml"))).map((e) =>
|
|
2497
|
+
return entries.filter((e) => e.isFile() && (e.name.endsWith(".yaml") || e.name.endsWith(".yml"))).map((e) => join9(dir, e.name));
|
|
2304
2498
|
} catch {
|
|
2305
2499
|
return [];
|
|
2306
2500
|
}
|
|
2307
2501
|
}
|
|
2502
|
+
function resolveRuns(scenarioRuns, cliRuns, userChoseRuns) {
|
|
2503
|
+
if (userChoseRuns) return cliRuns;
|
|
2504
|
+
return scenarioRuns ?? cliRuns;
|
|
2505
|
+
}
|
|
2308
2506
|
|
|
2309
2507
|
// src/commands/eval/runner.ts
|
|
2310
2508
|
import { mkdir as mkdir2, writeFile as writeFile2, rm, cp } from "fs/promises";
|
|
2311
|
-
import { join as
|
|
2509
|
+
import { join as join11, dirname as dirname3 } from "path";
|
|
2312
2510
|
import { tmpdir as tmpdir2 } from "os";
|
|
2313
2511
|
import { randomUUID } from "crypto";
|
|
2314
2512
|
import { execFile as execFile2 } from "child_process";
|
|
@@ -2316,7 +2514,7 @@ import { promisify as promisify2 } from "util";
|
|
|
2316
2514
|
|
|
2317
2515
|
// src/commands/eval/checks.ts
|
|
2318
2516
|
import { readFile as readFile6, readdir as readdir4 } from "fs/promises";
|
|
2319
|
-
import { join as
|
|
2517
|
+
import { join as join10 } from "path";
|
|
2320
2518
|
import { tmpdir } from "os";
|
|
2321
2519
|
import { execFile } from "child_process";
|
|
2322
2520
|
import { promisify } from "util";
|
|
@@ -2351,7 +2549,7 @@ async function evaluateSingleCheck(check, sandboxDir, context) {
|
|
|
2351
2549
|
async function checkGrep(check, sandboxDir) {
|
|
2352
2550
|
if (!check.pattern || !check.target) return false;
|
|
2353
2551
|
try {
|
|
2354
|
-
const content = await readFile6(
|
|
2552
|
+
const content = await readFile6(join10(sandboxDir, check.target), "utf-8");
|
|
2355
2553
|
let found;
|
|
2356
2554
|
try {
|
|
2357
2555
|
found = new RegExp(check.pattern).test(content);
|
|
@@ -2366,7 +2564,7 @@ async function checkGrep(check, sandboxDir) {
|
|
|
2366
2564
|
async function checkFilePresence(check, sandboxDir) {
|
|
2367
2565
|
if (!check.target) return false;
|
|
2368
2566
|
try {
|
|
2369
|
-
await readFile6(
|
|
2567
|
+
await readFile6(join10(sandboxDir, check.target));
|
|
2370
2568
|
return check.expect === "present";
|
|
2371
2569
|
} catch {
|
|
2372
2570
|
return check.expect === "absent";
|
|
@@ -2376,7 +2574,7 @@ async function checkMaxLines(check, sandboxDir) {
|
|
|
2376
2574
|
if (!check.target) return false;
|
|
2377
2575
|
const maxLines = parseInt(check.pattern ?? "800", 10);
|
|
2378
2576
|
try {
|
|
2379
|
-
const files = await listAllFiles(
|
|
2577
|
+
const files = await listAllFiles(join10(sandboxDir, check.target));
|
|
2380
2578
|
for (const file of files) {
|
|
2381
2579
|
const content = await readFile6(file, "utf-8");
|
|
2382
2580
|
if (content.split("\n").length > maxLines) {
|
|
@@ -2452,7 +2650,7 @@ async function listAllFiles(dir) {
|
|
|
2452
2650
|
try {
|
|
2453
2651
|
const entries = await readdir4(dir, { withFileTypes: true });
|
|
2454
2652
|
for (const entry of entries) {
|
|
2455
|
-
const fullPath =
|
|
2653
|
+
const fullPath = join10(dir, entry.name);
|
|
2456
2654
|
if (entry.isDirectory()) {
|
|
2457
2655
|
results.push(...await listAllFiles(fullPath));
|
|
2458
2656
|
} else {
|
|
@@ -2467,7 +2665,7 @@ async function listAllFiles(dir) {
|
|
|
2467
2665
|
// src/commands/eval/runner.ts
|
|
2468
2666
|
var exec2 = promisify2(execFile2);
|
|
2469
2667
|
async function runScenario(scenario, options) {
|
|
2470
|
-
const sandboxDir =
|
|
2668
|
+
const sandboxDir = join11(tmpdir2(), `claude-eval-${randomUUID()}`);
|
|
2471
2669
|
try {
|
|
2472
2670
|
await setupSandbox(sandboxDir, scenario, options.projectRoot);
|
|
2473
2671
|
const transcript = await runClaudeInSandbox(sandboxDir, scenario.prompt, options.timeout, options.model);
|
|
@@ -2493,14 +2691,14 @@ async function runScenarioWithRetries(scenario, options) {
|
|
|
2493
2691
|
async function setupSandbox(sandboxDir, scenario, projectRoot) {
|
|
2494
2692
|
await mkdir2(sandboxDir, { recursive: true });
|
|
2495
2693
|
for (const file of scenario.setup.files) {
|
|
2496
|
-
const filePath =
|
|
2694
|
+
const filePath = join11(sandboxDir, file.path);
|
|
2497
2695
|
await mkdir2(dirname3(filePath), { recursive: true });
|
|
2498
2696
|
await writeFile2(filePath, file.content);
|
|
2499
2697
|
}
|
|
2500
2698
|
await copyProjectConfig(sandboxDir, projectRoot);
|
|
2501
2699
|
if (scenario.setup.instructions) {
|
|
2502
2700
|
await writeFile2(
|
|
2503
|
-
|
|
2701
|
+
join11(sandboxDir, "CLAUDE.md"),
|
|
2504
2702
|
`# Eval Scenario
|
|
2505
2703
|
|
|
2506
2704
|
${scenario.setup.instructions}
|
|
@@ -2521,20 +2719,20 @@ ${scenario.setup.instructions}
|
|
|
2521
2719
|
], { cwd: sandboxDir });
|
|
2522
2720
|
}
|
|
2523
2721
|
async function copyProjectConfig(sandboxDir, projectRoot) {
|
|
2524
|
-
const claudeDir =
|
|
2525
|
-
const sandboxClaudeDir =
|
|
2526
|
-
const settingsPath =
|
|
2722
|
+
const claudeDir = join11(projectRoot, ".claude");
|
|
2723
|
+
const sandboxClaudeDir = join11(sandboxDir, ".claude");
|
|
2724
|
+
const settingsPath = join11(claudeDir, "settings.json");
|
|
2527
2725
|
if (await fileExists(settingsPath)) {
|
|
2528
2726
|
await mkdir2(sandboxClaudeDir, { recursive: true });
|
|
2529
|
-
await cp(settingsPath,
|
|
2727
|
+
await cp(settingsPath, join11(sandboxClaudeDir, "settings.json"));
|
|
2530
2728
|
}
|
|
2531
|
-
const rulesDir =
|
|
2729
|
+
const rulesDir = join11(claudeDir, "rules");
|
|
2532
2730
|
if (await fileExists(rulesDir)) {
|
|
2533
|
-
await cp(rulesDir,
|
|
2731
|
+
await cp(rulesDir, join11(sandboxClaudeDir, "rules"), { recursive: true });
|
|
2534
2732
|
}
|
|
2535
|
-
const ignorePath =
|
|
2733
|
+
const ignorePath = join11(projectRoot, ".claudeignore");
|
|
2536
2734
|
if (await fileExists(ignorePath)) {
|
|
2537
|
-
await cp(ignorePath,
|
|
2735
|
+
await cp(ignorePath, join11(sandboxDir, ".claudeignore"));
|
|
2538
2736
|
}
|
|
2539
2737
|
}
|
|
2540
2738
|
async function runClaudeInSandbox(cwd, prompt, timeout, model) {
|
|
@@ -2616,8 +2814,19 @@ async function scoreResults(scenario, sandboxDir, transcript, model) {
|
|
|
2616
2814
|
|
|
2617
2815
|
// src/commands/eval/index.ts
|
|
2618
2816
|
function createEvalCommand() {
|
|
2619
|
-
return new Command3("eval").description("Test your Claude Code config against eval scenarios").option(
|
|
2817
|
+
return new Command3("eval").description("Test your Claude Code config against eval scenarios").option(
|
|
2818
|
+
"-s, --suite <suite>",
|
|
2819
|
+
"Eval suite to run (e.g., security, conventions, workflow)"
|
|
2820
|
+
).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(
|
|
2821
|
+
"--timeout <ms>",
|
|
2822
|
+
"Timeout per run in ms (default: 120000)",
|
|
2823
|
+
"120000"
|
|
2824
|
+
).option("--json", "Output as JSON").option("--debug", "Keep sandbox directories for inspection").option(
|
|
2825
|
+
"--model <model>",
|
|
2826
|
+
"Model to use for eval (e.g., sonnet, haiku, opus)"
|
|
2827
|
+
).action(async (opts, command) => {
|
|
2620
2828
|
printBanner();
|
|
2829
|
+
let userChoseRuns = command.getOptionValueSource("runs") === "cli";
|
|
2621
2830
|
const hasFlags = opts.suite || opts.model || opts.runs !== "3" || opts.timeout !== "120000" || opts.path !== process.cwd() || Boolean(opts.scenarios) || opts.json || opts.debug;
|
|
2622
2831
|
if (!hasFlags) {
|
|
2623
2832
|
opts.suite = await select2({
|
|
@@ -2625,8 +2834,8 @@ function createEvalCommand() {
|
|
|
2625
2834
|
choices: [
|
|
2626
2835
|
{ name: "security (7 scenarios)", value: "security" },
|
|
2627
2836
|
{ name: "conventions (5 scenarios)", value: "conventions" },
|
|
2628
|
-
{ name: "workflow (
|
|
2629
|
-
{ name: "all (
|
|
2837
|
+
{ name: "workflow (5 scenarios)", value: "workflow" },
|
|
2838
|
+
{ name: "all (17 scenarios)", value: void 0 }
|
|
2630
2839
|
]
|
|
2631
2840
|
});
|
|
2632
2841
|
opts.runs = await select2({
|
|
@@ -2637,6 +2846,7 @@ function createEvalCommand() {
|
|
|
2637
2846
|
{ name: "5 \u2014 thorough", value: "5" }
|
|
2638
2847
|
]
|
|
2639
2848
|
});
|
|
2849
|
+
userChoseRuns = true;
|
|
2640
2850
|
opts.model = await select2({
|
|
2641
2851
|
message: "Model",
|
|
2642
2852
|
choices: [
|
|
@@ -2649,8 +2859,12 @@ function createEvalCommand() {
|
|
|
2649
2859
|
}
|
|
2650
2860
|
const claudeAvailable = await checkClaudeCli();
|
|
2651
2861
|
if (!claudeAvailable) {
|
|
2652
|
-
log.error(
|
|
2653
|
-
|
|
2862
|
+
log.error(
|
|
2863
|
+
"Claude CLI not found. Install it: https://docs.anthropic.com/en/docs/claude-code"
|
|
2864
|
+
);
|
|
2865
|
+
log.info(
|
|
2866
|
+
"The eval command runs Claude headless against scenarios \u2014 it requires the CLI."
|
|
2867
|
+
);
|
|
2654
2868
|
process.exit(1);
|
|
2655
2869
|
}
|
|
2656
2870
|
log.step("Loading eval scenarios...");
|
|
@@ -2661,7 +2875,9 @@ function createEvalCommand() {
|
|
|
2661
2875
|
if (scenarios.length === 0) {
|
|
2662
2876
|
log.warn("No matching scenarios found.");
|
|
2663
2877
|
if (opts.suite) {
|
|
2664
|
-
log.info(
|
|
2878
|
+
log.info(
|
|
2879
|
+
`Check that the suite "${opts.suite}" exists in the scenarios directory.`
|
|
2880
|
+
);
|
|
2665
2881
|
}
|
|
2666
2882
|
return;
|
|
2667
2883
|
}
|
|
@@ -2670,10 +2886,11 @@ function createEvalCommand() {
|
|
|
2670
2886
|
log.info(`Model: ${opts.model}`);
|
|
2671
2887
|
}
|
|
2672
2888
|
log.blank();
|
|
2673
|
-
const
|
|
2889
|
+
const cliRuns = parseInt(opts.runs, 10);
|
|
2674
2890
|
const timeout = parseInt(opts.timeout, 10);
|
|
2675
2891
|
const results = [];
|
|
2676
2892
|
for (const scenario of scenarios) {
|
|
2893
|
+
const runs = resolveRuns(scenario.runs, cliRuns, userChoseRuns);
|
|
2677
2894
|
const spinner = ora({
|
|
2678
2895
|
text: `Running: ${scenario.name} (${runs} run${runs > 1 ? "s" : ""})`,
|
|
2679
2896
|
prefixText: " "
|
|
@@ -2681,13 +2898,22 @@ function createEvalCommand() {
|
|
|
2681
2898
|
try {
|
|
2682
2899
|
const result = await runScenarioWithRetries(
|
|
2683
2900
|
{ ...scenario, runs },
|
|
2684
|
-
{
|
|
2901
|
+
{
|
|
2902
|
+
projectRoot: opts.path,
|
|
2903
|
+
timeout,
|
|
2904
|
+
debug: opts.debug,
|
|
2905
|
+
model: opts.model
|
|
2906
|
+
}
|
|
2685
2907
|
);
|
|
2686
2908
|
results.push(result);
|
|
2687
2909
|
if (result.passed) {
|
|
2688
|
-
spinner.succeed(
|
|
2910
|
+
spinner.succeed(
|
|
2911
|
+
`${scenario.name} ${result.score}/${result.maxScore}`
|
|
2912
|
+
);
|
|
2689
2913
|
} else {
|
|
2690
|
-
spinner.fail(
|
|
2914
|
+
spinner.fail(
|
|
2915
|
+
`${scenario.name} ${result.score}/${result.maxScore}`
|
|
2916
|
+
);
|
|
2691
2917
|
}
|
|
2692
2918
|
} catch (error) {
|
|
2693
2919
|
spinner.fail(`${scenario.name} ERROR`);
|
|
@@ -2698,7 +2924,11 @@ function createEvalCommand() {
|
|
|
2698
2924
|
score: 0,
|
|
2699
2925
|
maxScore: scenario.checks.reduce((s, c) => s + c.points, 0),
|
|
2700
2926
|
passed: false,
|
|
2701
|
-
checks: scenario.checks.map((c) => ({
|
|
2927
|
+
checks: scenario.checks.map((c) => ({
|
|
2928
|
+
label: c.label,
|
|
2929
|
+
passed: false,
|
|
2930
|
+
points: c.points
|
|
2931
|
+
}))
|
|
2702
2932
|
});
|
|
2703
2933
|
}
|
|
2704
2934
|
}
|
|
@@ -2706,13 +2936,19 @@ function createEvalCommand() {
|
|
|
2706
2936
|
if (opts.json) {
|
|
2707
2937
|
const overallScore = results.reduce((s, r) => s + r.score, 0);
|
|
2708
2938
|
const overallMax = results.reduce((s, r) => s + r.maxScore, 0);
|
|
2709
|
-
console.log(
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2939
|
+
console.log(
|
|
2940
|
+
JSON.stringify(
|
|
2941
|
+
{
|
|
2942
|
+
results,
|
|
2943
|
+
overallScore,
|
|
2944
|
+
overallMax,
|
|
2945
|
+
passed: overallScore >= overallMax * 0.8,
|
|
2946
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2947
|
+
},
|
|
2948
|
+
null,
|
|
2949
|
+
2
|
|
2950
|
+
)
|
|
2951
|
+
);
|
|
2716
2952
|
return;
|
|
2717
2953
|
}
|
|
2718
2954
|
renderEvalReport(results);
|
|
@@ -2724,7 +2960,9 @@ function renderEvalReport(results) {
|
|
|
2724
2960
|
const icon = result.passed ? chalk2.green("\u2713") : chalk2.red("\u2717");
|
|
2725
2961
|
const status = result.passed ? chalk2.green("PASS") : chalk2.red("FAIL");
|
|
2726
2962
|
const score = `${result.score}/${result.maxScore}`;
|
|
2727
|
-
console.log(
|
|
2963
|
+
console.log(
|
|
2964
|
+
` ${icon} ${chalk2.bold(result.scenario)} ${score} ${status}`
|
|
2965
|
+
);
|
|
2728
2966
|
const failedChecks = result.checks.filter((c) => !c.passed);
|
|
2729
2967
|
for (const check of failedChecks) {
|
|
2730
2968
|
console.log(` ${chalk2.red("\u2717")} ${chalk2.dim(check.label)}`);
|
|
@@ -2741,7 +2979,9 @@ function renderEvalReport(results) {
|
|
|
2741
2979
|
if (failed === 0) {
|
|
2742
2980
|
log.success(`All ${passed} scenario(s) passed.`);
|
|
2743
2981
|
} else {
|
|
2744
|
-
log.warn(
|
|
2982
|
+
log.warn(
|
|
2983
|
+
`${passed} passed, ${failed} failed out of ${results.length} scenario(s).`
|
|
2984
|
+
);
|
|
2745
2985
|
}
|
|
2746
2986
|
}
|
|
2747
2987
|
async function saveEvalReport(results, projectRoot, suite, model) {
|
|
@@ -2765,7 +3005,9 @@ async function saveEvalReport(results, projectRoot, suite, model) {
|
|
|
2765
3005
|
];
|
|
2766
3006
|
for (const result of results) {
|
|
2767
3007
|
const status = result.passed ? "PASS" : "FAIL";
|
|
2768
|
-
lines.push(
|
|
3008
|
+
lines.push(
|
|
3009
|
+
`### ${result.scenario} \u2014 ${result.score}/${result.maxScore} ${status}`
|
|
3010
|
+
);
|
|
2769
3011
|
const failedChecks = result.checks.filter((c) => !c.passed);
|
|
2770
3012
|
const passedChecks = result.checks.filter((c) => c.passed);
|
|
2771
3013
|
for (const check of passedChecks) {
|
|
@@ -2783,15 +3025,17 @@ async function saveEvalReport(results, projectRoot, suite, model) {
|
|
|
2783
3025
|
lines.push(`### Fix: ${result.scenario}`);
|
|
2784
3026
|
const failedChecks = result.checks.filter((c) => !c.passed);
|
|
2785
3027
|
for (const check of failedChecks) {
|
|
2786
|
-
lines.push(
|
|
3028
|
+
lines.push(
|
|
3029
|
+
`- ${check.label} \u2014 update CLAUDE.md instructions or add hooks to enforce this behavior`
|
|
3030
|
+
);
|
|
2787
3031
|
}
|
|
2788
3032
|
lines.push("");
|
|
2789
3033
|
}
|
|
2790
3034
|
}
|
|
2791
|
-
const evalDir =
|
|
3035
|
+
const evalDir = join12(projectRoot, ".claude", "eval");
|
|
2792
3036
|
await mkdir3(evalDir, { recursive: true });
|
|
2793
3037
|
const filename = `eval-${suite ?? "all"}-${timestamp}.md`;
|
|
2794
|
-
await writeFile3(
|
|
3038
|
+
await writeFile3(join12(evalDir, filename), lines.join("\n"));
|
|
2795
3039
|
log.success(`Report saved to .claude/eval/${filename}`);
|
|
2796
3040
|
}
|
|
2797
3041
|
async function checkClaudeCli() {
|
|
@@ -2808,7 +3052,7 @@ async function checkClaudeCli() {
|
|
|
2808
3052
|
|
|
2809
3053
|
// src/commands/memory/index.ts
|
|
2810
3054
|
import { readFileSync as readFileSync2 } from "fs";
|
|
2811
|
-
import { join as
|
|
3055
|
+
import { join as join13 } from "path";
|
|
2812
3056
|
import { Command as Command4 } from "commander";
|
|
2813
3057
|
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
2814
3058
|
async function handleSyncErrors(fn) {
|
|
@@ -2821,7 +3065,7 @@ async function handleSyncErrors(fn) {
|
|
|
2821
3065
|
}
|
|
2822
3066
|
function isMemoryInstalled() {
|
|
2823
3067
|
const cwd = process.cwd();
|
|
2824
|
-
const hookPresent = hasMemoryHook(
|
|
3068
|
+
const hookPresent = hasMemoryHook(join13(cwd, ".claude", "settings.json")) || hasMemoryHook(join13(cwd, ".claude", "settings.local.json"));
|
|
2825
3069
|
if (!hookPresent) return false;
|
|
2826
3070
|
return isMemoryMcpRegistered(cwd);
|
|
2827
3071
|
}
|
|
@@ -2833,43 +3077,66 @@ function hasMemoryHook(path) {
|
|
|
2833
3077
|
const sessionStart = hooks.SessionStart;
|
|
2834
3078
|
return sessionStart?.some((h) => {
|
|
2835
3079
|
const inner = h.hooks;
|
|
2836
|
-
return inner?.some(
|
|
3080
|
+
return inner?.some(
|
|
3081
|
+
(ih) => String(ih.command ?? "").includes("memory context")
|
|
3082
|
+
);
|
|
2837
3083
|
}) ?? false;
|
|
2838
3084
|
} catch {
|
|
2839
3085
|
return false;
|
|
2840
3086
|
}
|
|
2841
3087
|
}
|
|
2842
3088
|
function createMemoryCommand() {
|
|
2843
|
-
const memory = new Command4("memory").description("Project-scoped memory with decay, sync, and a TUI dashboard").
|
|
3089
|
+
const memory = new Command4("memory").description("Project-scoped memory with decay, sync, and a TUI dashboard").showHelpAfterError(
|
|
3090
|
+
"(see `claude-launchpad memory --help` for the available subcommands)"
|
|
3091
|
+
).showSuggestionAfterError(true).option("--dashboard", "Open the memory dashboard").action(async (opts) => {
|
|
2844
3092
|
if (opts.dashboard) {
|
|
2845
3093
|
if (!isMemoryInstalled()) {
|
|
2846
|
-
log.error(
|
|
3094
|
+
log.error(
|
|
3095
|
+
"Knowledge base not set up yet. Run `claude-launchpad memory` first."
|
|
3096
|
+
);
|
|
2847
3097
|
return;
|
|
2848
3098
|
}
|
|
2849
|
-
const { requireMemoryDeps } = await import("./require-deps-
|
|
3099
|
+
const { requireMemoryDeps } = await import("./require-deps-AEDO5YRM.js");
|
|
2850
3100
|
await requireMemoryDeps();
|
|
2851
|
-
const { startTui } = await import("./tui-
|
|
3101
|
+
const { startTui } = await import("./tui-3KITZCNS.js");
|
|
2852
3102
|
await startTui();
|
|
2853
3103
|
return;
|
|
2854
3104
|
}
|
|
2855
3105
|
if (!isMemoryInstalled()) {
|
|
2856
|
-
|
|
3106
|
+
if (!process.stdin.isTTY) {
|
|
3107
|
+
log.error(
|
|
3108
|
+
"Knowledge base not set up and no TTY to prompt. Run `claude-launchpad memory install -y`."
|
|
3109
|
+
);
|
|
3110
|
+
process.exitCode = 1;
|
|
3111
|
+
return;
|
|
3112
|
+
}
|
|
3113
|
+
const { detectExistingSetup } = await import("./install-543ZKMXX.js");
|
|
2857
3114
|
const existing = detectExistingSetup(process.cwd());
|
|
2858
3115
|
const mcpMissing = existing !== null && !isMemoryMcpRegistered(process.cwd());
|
|
2859
3116
|
if (existing) {
|
|
2860
3117
|
const location = existing === "local" ? ".claude/CLAUDE.md + settings.local.json" : "CLAUDE.md + settings.json";
|
|
2861
3118
|
log.blank();
|
|
2862
|
-
log.success(
|
|
3119
|
+
log.success(
|
|
3120
|
+
`Memory config found (${location}) but ${mcpMissing ? "MCP server not registered" : "database not set up"}.`
|
|
3121
|
+
);
|
|
2863
3122
|
log.info("Run `claude-launchpad memory install` to complete setup.");
|
|
2864
3123
|
log.blank();
|
|
2865
3124
|
} else {
|
|
2866
3125
|
log.blank();
|
|
2867
|
-
log.step(
|
|
3126
|
+
log.step(
|
|
3127
|
+
"Claude doesn't have a knowledge base for this project yet."
|
|
3128
|
+
);
|
|
2868
3129
|
log.blank();
|
|
2869
3130
|
log.info("After setup, Claude will:");
|
|
2870
|
-
log.info(
|
|
2871
|
-
|
|
2872
|
-
|
|
3131
|
+
log.info(
|
|
3132
|
+
" - Remember decisions, gotchas, and learnings across sessions"
|
|
3133
|
+
);
|
|
3134
|
+
log.info(
|
|
3135
|
+
" - Automatically recall relevant context when you start a session"
|
|
3136
|
+
);
|
|
3137
|
+
log.info(
|
|
3138
|
+
" - Save important facts as you work, so nothing gets lost"
|
|
3139
|
+
);
|
|
2873
3140
|
log.blank();
|
|
2874
3141
|
}
|
|
2875
3142
|
const proceed = await confirm2({
|
|
@@ -2880,29 +3147,51 @@ function createMemoryCommand() {
|
|
|
2880
3147
|
log.info("Skipped.");
|
|
2881
3148
|
return;
|
|
2882
3149
|
}
|
|
2883
|
-
const { runInstall } = await import("./install-
|
|
3150
|
+
const { runInstall } = await import("./install-543ZKMXX.js");
|
|
2884
3151
|
await runInstall({});
|
|
2885
3152
|
} else {
|
|
2886
|
-
const { requireMemoryDeps } = await import("./require-deps-
|
|
3153
|
+
const { requireMemoryDeps } = await import("./require-deps-AEDO5YRM.js");
|
|
2887
3154
|
await requireMemoryDeps();
|
|
2888
|
-
const { runStats } = await import("./stats-
|
|
3155
|
+
const { runStats } = await import("./stats-63L7C24W.js");
|
|
2889
3156
|
await runStats({});
|
|
2890
3157
|
}
|
|
2891
3158
|
});
|
|
2892
3159
|
memory.addCommand(
|
|
2893
|
-
new Command4("install").description(
|
|
3160
|
+
new Command4("install").description(
|
|
3161
|
+
"Install (or re-install) the knowledge base for this project"
|
|
3162
|
+
).option("--db-path <path>", "Override the default data directory").option(
|
|
3163
|
+
"-y, --yes",
|
|
3164
|
+
"Non-interactive: accept defaults (shared placement)"
|
|
3165
|
+
).action(async (opts) => {
|
|
2894
3166
|
try {
|
|
2895
|
-
const { runInstall } = await import("./install-
|
|
2896
|
-
await runInstall({
|
|
3167
|
+
const { runInstall } = await import("./install-543ZKMXX.js");
|
|
3168
|
+
await runInstall({
|
|
3169
|
+
...opts.dbPath ? { dbPath: opts.dbPath } : {},
|
|
3170
|
+
yes: opts.yes === true
|
|
3171
|
+
});
|
|
2897
3172
|
} catch (err) {
|
|
2898
3173
|
log.error(err instanceof Error ? err.message : String(err));
|
|
2899
3174
|
process.exitCode = 1;
|
|
2900
3175
|
}
|
|
2901
3176
|
})
|
|
2902
3177
|
);
|
|
3178
|
+
memory.addCommand(
|
|
3179
|
+
new Command4("stats").description("Show memory counts, health, and injection stats").option("--json", "JSON output").action(async (opts) => {
|
|
3180
|
+
const { requireMemoryDeps } = await import("./require-deps-AEDO5YRM.js");
|
|
3181
|
+
await requireMemoryDeps();
|
|
3182
|
+
const { runStats } = await import("./stats-63L7C24W.js");
|
|
3183
|
+
await runStats(opts);
|
|
3184
|
+
})
|
|
3185
|
+
);
|
|
3186
|
+
memory.addCommand(
|
|
3187
|
+
new Command4("doctor").description("Diagnose the memory setup (DB, hooks, MCP registration)").option("--json", "JSON output").action(async (opts) => {
|
|
3188
|
+
const { runMemoryDoctor } = await import("./doctor-EKGJ2YAO.js");
|
|
3189
|
+
await runMemoryDoctor(opts);
|
|
3190
|
+
})
|
|
3191
|
+
);
|
|
2903
3192
|
memory.addCommand(
|
|
2904
3193
|
new Command4("context").description("Load session context (hook handler)").option("--json", "JSON output").action(async (opts) => {
|
|
2905
|
-
const { runContext } = await import("./context-
|
|
3194
|
+
const { runContext } = await import("./context-3GHG7KW6.js");
|
|
2906
3195
|
await runContext(opts);
|
|
2907
3196
|
}).helpCommand(false),
|
|
2908
3197
|
{ hidden: true }
|
|
@@ -2917,29 +3206,34 @@ function createMemoryCommand() {
|
|
|
2917
3206
|
memory.addCommand(
|
|
2918
3207
|
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
3208
|
await handleSyncErrors(async () => {
|
|
2920
|
-
const { runPush } = await import("./push-
|
|
3209
|
+
const { runPush } = await import("./push-JXCWX4NY.js");
|
|
2921
3210
|
await runPush(opts);
|
|
2922
3211
|
});
|
|
2923
3212
|
})
|
|
2924
3213
|
);
|
|
2925
3214
|
memory.addCommand(
|
|
2926
|
-
new Command4("pull").description("Pull current project's memories from GitHub Gist").option("--all", "Pull all projects").option(
|
|
3215
|
+
new Command4("pull").description("Pull current project's memories from GitHub Gist").option("--all", "Pull all projects").option(
|
|
3216
|
+
"-y, --yes",
|
|
3217
|
+
"Non-interactive (accepted for symmetry with push; pull never prompts)"
|
|
3218
|
+
).action(async (opts) => {
|
|
2927
3219
|
await handleSyncErrors(async () => {
|
|
2928
|
-
const { runPull } = await import("./pull-
|
|
3220
|
+
const { runPull } = await import("./pull-ULSZEVCR.js");
|
|
2929
3221
|
await runPull(opts);
|
|
2930
3222
|
});
|
|
2931
3223
|
})
|
|
2932
3224
|
);
|
|
2933
|
-
const sync = new Command4("sync").description(
|
|
3225
|
+
const sync = new Command4("sync").description(
|
|
3226
|
+
"Sync memories with the gist (pull + push); subcommands manage sync state"
|
|
3227
|
+
).option("--all", "Sync all projects").option("-y, --yes", "Skip confirmation prompt").action(async (opts) => {
|
|
2934
3228
|
await handleSyncErrors(async () => {
|
|
2935
|
-
const { runSync } = await import("./sync-
|
|
3229
|
+
const { runSync } = await import("./sync-QTJ6BXQ4.js");
|
|
2936
3230
|
await runSync(opts);
|
|
2937
3231
|
});
|
|
2938
3232
|
});
|
|
2939
3233
|
sync.addCommand(
|
|
2940
3234
|
new Command4("status").description("Show local vs remote memory counts per project").action(async () => {
|
|
2941
3235
|
await handleSyncErrors(async () => {
|
|
2942
|
-
const { runSyncStatus } = await import("./sync-status-
|
|
3236
|
+
const { runSyncStatus } = await import("./sync-status-EAX527SC.js");
|
|
2943
3237
|
await runSyncStatus();
|
|
2944
3238
|
});
|
|
2945
3239
|
})
|
|
@@ -2947,7 +3241,7 @@ function createMemoryCommand() {
|
|
|
2947
3241
|
sync.addCommand(
|
|
2948
3242
|
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
3243
|
await handleSyncErrors(async () => {
|
|
2950
|
-
const { runSyncClean } = await import("./sync-clean-
|
|
3244
|
+
const { runSyncClean } = await import("./sync-clean-OJEK4XH4.js");
|
|
2951
3245
|
await runSyncClean(project, opts);
|
|
2952
3246
|
});
|
|
2953
3247
|
})
|
|
@@ -2957,8 +3251,8 @@ function createMemoryCommand() {
|
|
|
2957
3251
|
}
|
|
2958
3252
|
|
|
2959
3253
|
// 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.
|
|
2961
|
-
const hasConfig = await fileExists(
|
|
3254
|
+
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 () => {
|
|
3255
|
+
const hasConfig = await fileExists(join14(process.cwd(), "CLAUDE.md")) || await fileExists(join14(process.cwd(), ".claude", "settings.json"));
|
|
2962
3256
|
if (hasConfig) {
|
|
2963
3257
|
await program.commands.find((c) => c.name() === "doctor")?.parseAsync([], { from: "user" });
|
|
2964
3258
|
} else {
|