claude-launchpad 1.8.0 → 1.9.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 (34) hide show
  1. package/dist/{chunk-MDR73QT2.js → chunk-72VWDNAE.js} +2 -2
  2. package/dist/{chunk-NJQ2JFGF.js → chunk-DYHPVA6O.js} +2 -2
  3. package/dist/{chunk-GY3SK66Y.js → chunk-GA3IUQUM.js} +3 -3
  4. package/dist/{chunk-MIHOPQP4.js → chunk-I4S4Q2IV.js} +2 -2
  5. package/dist/{chunk-7QPZN27I.js → chunk-RCYLZUU6.js} +367 -197
  6. package/dist/chunk-RCYLZUU6.js.map +1 -0
  7. package/dist/cli.js +144 -35
  8. package/dist/cli.js.map +1 -1
  9. package/dist/commands/memory/server.js +3 -3
  10. package/dist/{context-XTPBYGKW.js → context-X7UP2ODK.js} +5 -5
  11. package/dist/{install-MQDN7TLA.js → install-XBCEI5QK.js} +37 -44
  12. package/dist/install-XBCEI5QK.js.map +1 -0
  13. package/dist/{pull-ELZGXQPJ.js → pull-VA62U3OP.js} +7 -7
  14. package/dist/{push-HJXEC7XC.js → push-C3M6Q4V7.js} +7 -7
  15. package/dist/{require-deps-3B3NWAEP.js → require-deps-UBU5CYM5.js} +3 -3
  16. package/dist/{stats-S3GNMUDF.js → stats-R4TWCPHW.js} +6 -6
  17. package/dist/{sync-clean-XNMLP3FZ.js → sync-clean-P4S7V2JS.js} +3 -3
  18. package/dist/{sync-status-4AGU776A.js → sync-status-TPYUF43G.js} +7 -7
  19. package/dist/{tui-CGXGXU5U.js → tui-FFLCUR7E.js} +4 -4
  20. package/package.json +1 -1
  21. package/dist/chunk-7QPZN27I.js.map +0 -1
  22. package/dist/install-MQDN7TLA.js.map +0 -1
  23. /package/dist/{chunk-MDR73QT2.js.map → chunk-72VWDNAE.js.map} +0 -0
  24. /package/dist/{chunk-NJQ2JFGF.js.map → chunk-DYHPVA6O.js.map} +0 -0
  25. /package/dist/{chunk-GY3SK66Y.js.map → chunk-GA3IUQUM.js.map} +0 -0
  26. /package/dist/{chunk-MIHOPQP4.js.map → chunk-I4S4Q2IV.js.map} +0 -0
  27. /package/dist/{context-XTPBYGKW.js.map → context-X7UP2ODK.js.map} +0 -0
  28. /package/dist/{pull-ELZGXQPJ.js.map → pull-VA62U3OP.js.map} +0 -0
  29. /package/dist/{push-HJXEC7XC.js.map → push-C3M6Q4V7.js.map} +0 -0
  30. /package/dist/{require-deps-3B3NWAEP.js.map → require-deps-UBU5CYM5.js.map} +0 -0
  31. /package/dist/{stats-S3GNMUDF.js.map → stats-R4TWCPHW.js.map} +0 -0
  32. /package/dist/{sync-clean-XNMLP3FZ.js.map → sync-clean-P4S7V2JS.js.map} +0 -0
  33. /package/dist/{sync-status-4AGU776A.js.map → sync-status-TPYUF43G.js.map} +0 -0
  34. /package/dist/{tui-CGXGXU5U.js.map → tui-FFLCUR7E.js.map} +0 -0
@@ -32,77 +32,15 @@ async function readJsonOrNull(path) {
32
32
  }
33
33
 
34
34
  // src/lib/settings.ts
35
- import { readFile as readFile2, writeFile, mkdir } from "fs/promises";
36
- import { join } from "path";
37
- async function readSettingsJson(root) {
38
- const path = join(root, ".claude", "settings.json");
39
- try {
40
- const content = await readFile2(path, "utf-8");
41
- return JSON.parse(content);
42
- } catch {
43
- return {};
44
- }
45
- }
46
- async function writeSettingsJson(root, settings) {
47
- const dir = join(root, ".claude");
48
- await mkdir(dir, { recursive: true });
49
- await writeFile(join(dir, "settings.json"), JSON.stringify(settings, null, 2) + "\n");
50
- }
51
- async function readSettingsLocalJson(root) {
52
- const path = join(root, ".claude", "settings.local.json");
53
- try {
54
- const content = await readFile2(path, "utf-8");
55
- return JSON.parse(content);
56
- } catch {
57
- return {};
58
- }
59
- }
60
- async function writeSettingsLocalJson(root, settings) {
61
- const dir = join(root, ".claude");
62
- await mkdir(dir, { recursive: true });
63
- await writeFile(join(dir, "settings.local.json"), JSON.stringify(settings, null, 2) + "\n");
64
- }
65
-
66
- // src/lib/memory-placement.ts
67
- import { select } from "@inquirer/prompts";
68
- function hasMemoryPermissions(settings) {
69
- const permissions = settings.permissions;
70
- const allow = permissions?.allow ?? [];
71
- return allow.some((p) => p.includes("agentic-memory"));
72
- }
73
- async function getMemoryPlacement(root, skipPrompt = false) {
74
- const local = await readSettingsLocalJson(root);
75
- const persisted = local.memoryPlacement;
76
- if (persisted === "shared" || persisted === "local") {
77
- return persisted;
78
- }
79
- if (hasMemoryPermissions(local)) {
80
- await writeSettingsLocalJson(root, { ...local, memoryPlacement: "local" });
81
- return "local";
82
- }
83
- const shared = await readSettingsJson(root);
84
- if (hasMemoryPermissions(shared)) {
85
- await writeSettingsLocalJson(root, { ...local, memoryPlacement: "shared" });
86
- return "shared";
87
- }
88
- if (skipPrompt) return "shared";
89
- const choice = await select({
90
- message: "Where should memory config go?",
91
- choices: [
92
- { value: "shared", name: "Shared (team sees it) \u2014 CLAUDE.md + settings.json" },
93
- { value: "local", name: "Local (only you) \u2014 .claude/CLAUDE.md + settings.local.json" }
94
- ]
95
- });
96
- await writeSettingsLocalJson(root, { ...local, memoryPlacement: choice });
97
- return choice;
98
- }
35
+ import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir3 } from "fs/promises";
36
+ import { join as join6 } from "path";
99
37
 
100
38
  // src/lib/output.ts
101
39
  import chalk from "chalk";
102
40
 
103
41
  // src/commands/doctor/fixer.ts
104
- import { readFile as readFile4, writeFile as writeFile2, mkdir as mkdir2, access as access2 } from "fs/promises";
105
- import { join as join4 } from "path";
42
+ import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir2, access as access2 } from "fs/promises";
43
+ import { join as join5 } from "path";
106
44
  import { homedir } from "os";
107
45
 
108
46
  // src/lib/sections.ts
@@ -113,22 +51,22 @@ var OFF_LIMITS_CONTENT = "- Never hardcode secrets \u2014 use environment variab
113
51
  var SKILL_AUTHORING_CONTENT = 'When creating Claude Code skills (.claude/skills/*/SKILL.md):\n\n- Keep SKILL.md under 500 lines \u2014 move reference material to supporting files in the same directory\n- Front-load description (first 250 chars shown in listings) with TRIGGER when / DO NOT TRIGGER when clauses\n- Add allowed-tools in frontmatter to restrict tool access (e.g. Read, Glob, Grep for read-only skills)\n- Add argument-hint in frontmatter showing the expected input format (use $ARGUMENTS or $0, $1 for dynamic input)\n- Set disable-model-invocation: true for skills with side effects (deploy, send messages)\n- Structure as phases: Research, Plan, Execute, Verify with "Done when:" success criteria per phase\n- Handle edge cases and preconditions before execution';
114
52
 
115
53
  // src/lib/detect.ts
116
- import { join as join2, basename } from "path";
54
+ import { join, basename } from "path";
117
55
  async function detectProject(root) {
118
56
  const name = basename(root);
119
57
  const [pkgJson, goMod, pyProject, gemfile, cargo, pubspec, composerJson, pomXml, buildGradleGroovy, buildGradleKts, packageSwift, mixExs, csproj, lockfiles] = await Promise.all([
120
- readJsonOrNull(join2(root, "package.json")),
121
- fileExists(join2(root, "go.mod")),
122
- readFileOrNull(join2(root, "pyproject.toml")),
123
- fileExists(join2(root, "Gemfile")),
124
- fileExists(join2(root, "Cargo.toml")),
125
- fileExists(join2(root, "pubspec.yaml")),
126
- readJsonOrNull(join2(root, "composer.json")),
127
- fileExists(join2(root, "pom.xml")),
128
- fileExists(join2(root, "build.gradle")),
129
- fileExists(join2(root, "build.gradle.kts")),
130
- fileExists(join2(root, "Package.swift")),
131
- fileExists(join2(root, "mix.exs")),
58
+ readJsonOrNull(join(root, "package.json")),
59
+ fileExists(join(root, "go.mod")),
60
+ readFileOrNull(join(root, "pyproject.toml")),
61
+ fileExists(join(root, "Gemfile")),
62
+ fileExists(join(root, "Cargo.toml")),
63
+ fileExists(join(root, "pubspec.yaml")),
64
+ readJsonOrNull(join(root, "composer.json")),
65
+ fileExists(join(root, "pom.xml")),
66
+ fileExists(join(root, "build.gradle")),
67
+ fileExists(join(root, "build.gradle.kts")),
68
+ fileExists(join(root, "Package.swift")),
69
+ fileExists(join(root, "mix.exs")),
132
70
  globExists(root, "*.csproj"),
133
71
  detectLockfiles(root)
134
72
  ]);
@@ -209,10 +147,10 @@ function detectFramework(m) {
209
147
  }
210
148
  async function detectLockfiles(root) {
211
149
  const [pnpmLock, yarnLock, bunLock, npmLock] = await Promise.all([
212
- fileExists(join2(root, "pnpm-lock.yaml")),
213
- fileExists(join2(root, "yarn.lock")),
214
- fileExists(join2(root, "bun.lockb")),
215
- fileExists(join2(root, "package-lock.json"))
150
+ fileExists(join(root, "pnpm-lock.yaml")),
151
+ fileExists(join(root, "yarn.lock")),
152
+ fileExists(join(root, "bun.lockb")),
153
+ fileExists(join(root, "package-lock.json"))
216
154
  ]);
217
155
  return { pnpmLock, yarnLock, bunLock, npmLock };
218
156
  }
@@ -619,6 +557,40 @@ function generateEnhanceSkill() {
619
557
  ].join("\n");
620
558
  }
621
559
 
560
+ // src/lib/memory-placement.ts
561
+ import { select } from "@inquirer/prompts";
562
+ function hasMemoryPermissions(settings) {
563
+ const permissions = settings.permissions;
564
+ const allow = permissions?.allow ?? [];
565
+ return allow.some((p) => p.includes("agentic-memory"));
566
+ }
567
+ async function getMemoryPlacement(root, skipPrompt = false) {
568
+ const local = await readSettingsLocalJson(root) ?? {};
569
+ const persisted = local.memoryPlacement;
570
+ if (persisted === "shared" || persisted === "local") {
571
+ return persisted;
572
+ }
573
+ if (hasMemoryPermissions(local)) {
574
+ await writeSettingsLocalJson(root, { ...local, memoryPlacement: "local" });
575
+ return "local";
576
+ }
577
+ const shared = await readSettingsJson(root) ?? {};
578
+ if (hasMemoryPermissions(shared)) {
579
+ await writeSettingsLocalJson(root, { ...local, memoryPlacement: "shared" });
580
+ return "shared";
581
+ }
582
+ if (skipPrompt) return "shared";
583
+ const choice = await select({
584
+ message: "Where should memory config go?",
585
+ choices: [
586
+ { value: "shared", name: "Shared (team sees it) \u2014 CLAUDE.md + settings.json" },
587
+ { value: "local", name: "Local (only you) \u2014 .claude/CLAUDE.md + settings.local.json" }
588
+ ]
589
+ });
590
+ await writeSettingsLocalJson(root, { ...local, memoryPlacement: choice });
591
+ return choice;
592
+ }
593
+
622
594
  // src/lib/stub-marker.ts
623
595
  var LP_STUB_OPEN = "<!-- LP-STUB: ai-recommended -->";
624
596
  var LP_STUB_CLOSE = "<!-- /LP-STUB -->";
@@ -628,23 +600,236 @@ ${content}
628
600
  ${LP_STUB_CLOSE}`;
629
601
  }
630
602
 
631
- // src/commands/doctor/fixer-memory.ts
632
- import { readFile as readFile3 } from "fs/promises";
603
+ // src/commands/doctor/fixer-sprint.ts
604
+ import { writeFile as writeFile2 } from "fs/promises";
633
605
  import { join as join3 } from "path";
606
+
607
+ // src/lib/hook-builder.ts
608
+ function addOrUpdateHook(existingHooks, options) {
609
+ const hookList = existingHooks?.[options.event] ?? [];
610
+ const alreadyHas = hookList.some((group) => {
611
+ const nested = group.hooks;
612
+ return nested?.some((h) => String(h.command ?? "").includes(options.dedupKeyword));
613
+ });
614
+ if (alreadyHas) {
615
+ return { hooks: existingHooks ?? {}, added: false };
616
+ }
617
+ const newEntry = options.entry;
618
+ const updated = options.prepend ? [newEntry, ...hookList] : [...hookList, newEntry];
619
+ return {
620
+ hooks: { ...existingHooks ?? {}, [options.event]: updated },
621
+ added: true
622
+ };
623
+ }
624
+ async function addHookToSettings(root, event, dedupKeyword, entry, successMsg) {
625
+ const settings = await readSettingsJson(root);
626
+ if (settings === null) return false;
627
+ const existingHooks = settings.hooks;
628
+ const result = addOrUpdateHook(existingHooks, { event, dedupKeyword, entry });
629
+ if (!result.added) return false;
630
+ await writeSettingsJson(root, { ...settings, hooks: result.hooks });
631
+ log.success(successMsg);
632
+ return true;
633
+ }
634
+
635
+ // src/lib/hook-scripts.ts
636
+ import { writeFile, mkdir, chmod } from "fs/promises";
637
+ import { join as join2 } from "path";
638
+ var SPRINT_SIZE_CHECK = `#!/usr/bin/env bash
639
+ # Warns when the current sprint is too small (<3) or too large (>7).
640
+ # Sweet spot is 3-6 work packages per sprint. Non-blocking (always exits 0).
641
+
642
+ set -u
643
+ tasks="\${1:-TASKS.md}"
644
+ [ -f "$tasks" ] || exit 0
645
+
646
+ section=$(sed -n '/^## Current/,/^## /p' "$tasks" 2>/dev/null)
647
+ [ -z "$section" ] && exit 0
648
+
649
+ unchecked=$(echo "$section" | grep -cF -e '- [ ]' || true)
650
+ checked=$(echo "$section" | grep -cF -e '- [x]' || true)
651
+ total=$((unchecked + checked))
652
+
653
+ if [ "$total" -eq 0 ]; then
654
+ echo "NOTE: Current sprint has no work packages yet. Pull 3-6 from BACKLOG.md to start."
655
+ exit 0
656
+ fi
657
+
658
+ if [ "$unchecked" -eq 0 ]; then exit 0; fi
659
+
660
+ if [ "$unchecked" -lt 3 ]; then
661
+ echo "NOTE: Current sprint has $unchecked open work package(s) \u2014 that's a microsprint. Pull from BACKLOG.md (aim 3-6)."
662
+ exit 0
663
+ fi
664
+
665
+ if [ "$unchecked" -gt 7 ]; then
666
+ echo "NOTE: Current sprint has $unchecked open work packages \u2014 oversized. Move some back to BACKLOG.md (aim 3-6)."
667
+ exit 0
668
+ fi
669
+
670
+ exit 0
671
+ `;
672
+ var SPRINT_OPEN_CHECK = `#!/usr/bin/env bash
673
+ # Warns when TASKS.md opens a new sprint block but BACKLOG.md has no staged
674
+ # deletions, i.e. the "remove pulled WPs from BACKLOG in the same edit" rule
675
+ # from CLAUDE.md was skipped. Non-blocking (always exits 0).
676
+
677
+ set -u
678
+ cmd="\${TOOL_INPUT_COMMAND:-}"
679
+
680
+ # Only act on \`git commit\`, word-boundary match.
681
+ echo "$cmd" | grep -qE '(^|[^a-zA-Z0-9_-])git[[:space:]]+commit([[:space:]]|$)' || exit 0
682
+
683
+ # Nothing staged
684
+ git diff --cached --quiet 2>/dev/null && exit 0
685
+
686
+ # TASKS.md not staged
687
+ git diff --cached --name-only --diff-filter=ACM 2>/dev/null | grep -q '^TASKS\\.md$' || exit 0
688
+
689
+ # Does the staged TASKS.md diff ADD a new \`## Current\` block?
690
+ new_sprint=$(git diff --cached TASKS.md 2>/dev/null | grep -cE '^\\+## Current')
691
+ [ "$new_sprint" -eq 0 ] && exit 0
692
+
693
+ # If a new sprint was opened, BACKLOG.md should have net deletions.
694
+ backlog_deletions=$(git diff --cached BACKLOG.md 2>/dev/null | grep -cE '^-[^-]')
695
+ if [ "$backlog_deletions" -eq 0 ]; then
696
+ echo ""
697
+ echo "WARNING: sprint-open hygiene"
698
+ echo ""
699
+ echo "TASKS.md stages a new '## Current' block, but BACKLOG.md has no"
700
+ echo "staged deletions. When a WP is pulled from BACKLOG.md into a sprint,"
701
+ echo "remove it from BACKLOG.md in the same edit. Overlap = drift."
702
+ echo ""
703
+ echo "If you opened a fresh-scope sprint with no BACKLOG pulls, ignore"
704
+ echo "this. Otherwise scrub BACKLOG.md before committing."
705
+ echo ""
706
+ fi
707
+
708
+ exit 0
709
+ `;
710
+ async function writeSprintHygieneScripts(root) {
711
+ const hooksDir = join2(root, ".claude", "hooks");
712
+ await mkdir(hooksDir, { recursive: true });
713
+ const sizePath = join2(hooksDir, "sprint-size-check.sh");
714
+ const openPath = join2(hooksDir, "sprint-open-check.sh");
715
+ await writeFile(sizePath, SPRINT_SIZE_CHECK);
716
+ await writeFile(openPath, SPRINT_OPEN_CHECK);
717
+ await chmod(sizePath, 493);
718
+ await chmod(openPath, 493);
719
+ return { sizePath, openPath };
720
+ }
721
+
722
+ // src/commands/doctor/fixer-sprint.ts
723
+ var WORKTREE_INCLUDE_TEMPLATE = `# Files copied into git worktrees that Claude Code creates for subagents.
724
+ # Listed files must be gitignored \u2014 that's the point: keep secrets out of
725
+ # commits while letting worktree subagents inherit local env so dev servers,
726
+ # tests, and integration runs work the same as the main tree.
727
+ # Anything needed by \`pnpm dev\`, \`pnpm test\`, etc. that's NOT committed
728
+ # should land here.
729
+
730
+ .env.local
731
+ .env
732
+ `;
733
+ async function createWorktreeInclude(root) {
734
+ await writeFile2(join3(root, ".worktreeinclude"), WORKTREE_INCLUDE_TEMPLATE);
735
+ log.success("Generated .worktreeinclude (worktree subagents inherit .env.local / .env)");
736
+ return true;
737
+ }
738
+ async function addSprintSizeHook(root) {
739
+ await writeSprintHygieneScripts(root);
740
+ return addHookToSettings(root, "SessionStart", "sprint-size-check.sh", {
741
+ matcher: "startup|resume",
742
+ hooks: [{ type: "command", command: "bash .claude/hooks/sprint-size-check.sh TASKS.md 2>/dev/null; exit 0" }]
743
+ }, "Added sprint-size-check hook (warns on microsprint/oversized sprints)");
744
+ }
745
+ async function addSprintOpenHook(root) {
746
+ await writeSprintHygieneScripts(root);
747
+ return addHookToSettings(root, "PreToolUse", "sprint-open-check.sh", {
748
+ matcher: "Bash",
749
+ hooks: [{ type: "command", command: "bash .claude/hooks/sprint-open-check.sh 2>/dev/null; exit 0" }]
750
+ }, "Added sprint-open-check hook (warns on new sprint without BACKLOG cleanup)");
751
+ }
752
+ async function addSprintCompleteNudge(root) {
753
+ return addHookToSettings(root, "PostToolUse", "Sprint complete", {
754
+ matcher: "Edit|Write",
755
+ hooks: [{
756
+ type: "command",
757
+ command: `echo "$TOOL_INPUT_FILE_PATH" | grep -q TASKS.md || exit 0; section=$(sed -n '/^## Current/,/^## /p' TASKS.md 2>/dev/null); [ -z "$section" ] && exit 0; unchecked=$(echo "$section" | grep -cF '- [ ]' || true); checked=$(echo "$section" | grep -cF '- [x]' || true); [ "$unchecked" -eq 0 ] && [ "$checked" -gt 0 ] && echo 'Sprint complete \u2014 all current tasks done. Consider a quick quality check before committing: scan for dead code, debug artifacts, TODO hacks, and convention violations. Run tests if available. Skip if trivial.'; exit 0`
758
+ }]
759
+ }, "Added sprint-complete nudge hook");
760
+ }
761
+
762
+ // src/commands/doctor/fixer-hooks.ts
763
+ var FORMATTERS = {
764
+ TypeScript: { extensions: ["ts", "tsx"], command: "npx prettier --write" },
765
+ JavaScript: { extensions: ["js", "jsx"], command: "npx prettier --write" },
766
+ Python: { extensions: ["py"], command: "ruff format" },
767
+ Go: { extensions: ["go"], command: "gofmt -w" },
768
+ Rust: { extensions: ["rs"], command: "rustfmt" },
769
+ Ruby: { extensions: ["rb"], command: "rubocop -A" },
770
+ PHP: { extensions: ["php"], command: "vendor/bin/pint" }
771
+ };
772
+ async function addEnvProtectionHook(root) {
773
+ return addHookToSettings(root, "PreToolUse", ".env", {
774
+ matcher: "Read|Write|Edit",
775
+ hooks: [{
776
+ type: "command",
777
+ command: `echo "$TOOL_INPUT_FILE_PATH" | grep -qE '\\.(env|env\\..*)$' && ! echo "$TOOL_INPUT_FILE_PATH" | grep -q '.env.example' && echo 'BLOCKED: .env files contain secrets' && exit 1; exit 0`
778
+ }]
779
+ }, "Added .env file protection hook (PreToolUse)");
780
+ }
781
+ async function addAutoFormatHook(root, detected) {
782
+ if (!detected.language) return false;
783
+ const config = detected.language ? FORMATTERS[detected.language] : null;
784
+ if (!config) return false;
785
+ const extChecks = config.extensions.map((ext) => `[ "$ext" = "${ext}" ]`).join(" || ");
786
+ return addHookToSettings(root, "PostToolUse", "format", {
787
+ matcher: "Write|Edit",
788
+ hooks: [{
789
+ type: "command",
790
+ command: `ext=\${TOOL_INPUT_FILE_PATH##*.}; (${extChecks}) && ${config.command} "$TOOL_INPUT_FILE_PATH" 2>/dev/null; exit 0`
791
+ }]
792
+ }, `Added auto-format hook (PostToolUse \u2192 ${config.command})`);
793
+ }
794
+ async function addForcePushProtection(root) {
795
+ return addHookToSettings(root, "PreToolUse", "force", {
796
+ matcher: "Bash",
797
+ hooks: [{
798
+ type: "command",
799
+ command: `echo "$TOOL_INPUT_COMMAND" | grep -qE 'push.*--force|push.*-f' && echo 'WARNING: Force push detected \u2014 this can destroy remote history' && exit 1; exit 0`
800
+ }]
801
+ }, "Added force-push protection hook (PreToolUse \u2192 Bash)");
802
+ }
803
+ async function addPostCompactHook(root) {
804
+ return addHookToSettings(root, "PostCompact", "TASKS.md", {
805
+ matcher: "",
806
+ hooks: [{ type: "command", command: "cat TASKS.md 2>/dev/null; exit 0" }]
807
+ }, "Added PostCompact hook (re-injects TASKS.md after compaction)");
808
+ }
809
+ async function addSessionStartHook(root) {
810
+ return addHookToSettings(root, "SessionStart", "TASKS.md", {
811
+ matcher: "startup|resume",
812
+ hooks: [{ type: "command", command: "cat TASKS.md 2>/dev/null; exit 0" }]
813
+ }, "Added SessionStart hook (injects TASKS.md at startup)");
814
+ }
815
+
816
+ // src/commands/doctor/fixer-memory.ts
817
+ import { readFile as readFile2 } from "fs/promises";
818
+ import { join as join4 } from "path";
634
819
  async function addPlacementHook(root, placement, event, dedupKeyword, entry, prepend, successMsg) {
635
820
  const read = placement === "local" ? readSettingsLocalJson : readSettingsJson;
636
821
  const write = placement === "local" ? writeSettingsLocalJson : writeSettingsJson;
637
822
  const settings = await read(root);
638
- const hooks = settings.hooks ?? {};
639
- const hookList = hooks[event] ?? [];
640
- const alreadyHas = hookList.some((g) => {
641
- const nested = g.hooks;
642
- return nested?.some((h) => String(h.command ?? "").includes(dedupKeyword));
823
+ if (settings === null) return false;
824
+ const existingHooks = settings.hooks;
825
+ const result = addOrUpdateHook(existingHooks, {
826
+ event,
827
+ dedupKeyword,
828
+ entry,
829
+ prepend
643
830
  });
644
- if (alreadyHas) return false;
645
- const updatedList = prepend ? [entry, ...hookList] : [...hookList, entry];
646
- const updatedSettings = { ...settings, hooks: { ...hooks, [event]: updatedList } };
647
- await write(root, updatedSettings);
831
+ if (!result.added) return false;
832
+ await write(root, { ...settings, hooks: result.hooks });
648
833
  log.success(successMsg);
649
834
  return true;
650
835
  }
@@ -652,6 +837,7 @@ async function disableAutoMemory(root, placement) {
652
837
  const read = placement === "local" ? readSettingsLocalJson : readSettingsJson;
653
838
  const write = placement === "local" ? writeSettingsLocalJson : writeSettingsJson;
654
839
  const settings = await read(root);
840
+ if (settings === null) return false;
655
841
  if (settings.autoMemoryEnabled === false) return false;
656
842
  const updated = { ...settings, autoMemoryEnabled: false };
657
843
  await write(root, updated);
@@ -663,6 +849,7 @@ async function addMemoryToolPermissions(root, placement) {
663
849
  const read = placement === "local" ? readSettingsLocalJson : readSettingsJson;
664
850
  const write = placement === "local" ? writeSettingsLocalJson : writeSettingsJson;
665
851
  const settings = await read(root);
852
+ if (settings === null) return false;
666
853
  const permissions = settings.permissions ?? {};
667
854
  const allow = permissions.allow ?? [];
668
855
  const tools = [
@@ -701,6 +888,7 @@ async function upgradeStaleSessionEndPushHook(root) {
701
888
  const read = placement === "local" ? readSettingsLocalJson : readSettingsJson;
702
889
  const write = placement === "local" ? writeSettingsLocalJson : writeSettingsJson;
703
890
  const settings = await read(root);
891
+ if (settings === null) continue;
704
892
  const hooks = settings.hooks;
705
893
  const sessionEnd = hooks?.SessionEnd;
706
894
  if (!sessionEnd) continue;
@@ -727,6 +915,7 @@ async function upgradeStaleSessionEndPushHook(root) {
727
915
  }
728
916
  async function removeStaleStopHook(root) {
729
917
  const settings = await readSettingsJson(root);
918
+ if (settings === null) return false;
730
919
  const hooks = settings.hooks;
731
920
  if (!hooks?.Stop) return false;
732
921
  const stopHooks = hooks.Stop;
@@ -749,6 +938,7 @@ async function addMemoryToAllowedMcpServers(root) {
749
938
  const read = placement === "local" ? readSettingsLocalJson : readSettingsJson;
750
939
  const write = placement === "local" ? writeSettingsLocalJson : writeSettingsJson;
751
940
  const settings = await read(root);
941
+ if (settings === null) continue;
752
942
  const existing = settings.allowedMcpServers;
753
943
  if (!Array.isArray(existing)) continue;
754
944
  const list = existing;
@@ -769,17 +959,19 @@ async function addAllowedMcpServers(root, placement) {
769
959
  const read = placement === "local" ? readSettingsLocalJson : readSettingsJson;
770
960
  const write = placement === "local" ? writeSettingsLocalJson : writeSettingsJson;
771
961
  const settings = await read(root);
962
+ if (settings === null) return false;
772
963
  if (settings.allowedMcpServers) return false;
773
964
  const other = placement === "local" ? await readSettingsJson(root) : await readSettingsLocalJson(root);
965
+ if (other === null) return false;
774
966
  if (other.allowedMcpServers) return false;
775
967
  const serverNames = /* @__PURE__ */ new Set();
776
968
  const settingsServers = settings.mcpServers;
777
969
  if (settingsServers && typeof settingsServers === "object") {
778
970
  for (const name of Object.keys(settingsServers)) serverNames.add(name);
779
971
  }
780
- const mcpJsonPath = join3(root, ".mcp.json");
972
+ const mcpJsonPath = join4(root, ".mcp.json");
781
973
  try {
782
- const mcpJson = JSON.parse(await readFile3(mcpJsonPath, "utf-8"));
974
+ const mcpJson = JSON.parse(await readFile2(mcpJsonPath, "utf-8"));
783
975
  const mcpServers = mcpJson.mcpServers;
784
976
  if (mcpServers && typeof mcpServers === "object") {
785
977
  for (const name of Object.keys(mcpServers)) serverNames.add(name);
@@ -849,6 +1041,10 @@ var FIX_TABLE = [
849
1041
  { analyzer: "Permissions", match: "Bypass permissions mode", fix: (root) => addBypassDisable(root) },
850
1042
  { analyzer: "Permissions", match: "Filesystem sandbox enabled", fix: (root) => removeSandboxSettings(root) },
851
1043
  { analyzer: "Permissions", match: ".env is protected by hooks but not in .claudeignore", fix: (root) => addEnvToClaudeignore(root) },
1044
+ { analyzer: "Permissions", match: ".worktreeinclude is missing or empty", fix: (root) => createWorktreeInclude(root) },
1045
+ { analyzer: "Hooks", match: "sprint-size-check", fix: (root) => addSprintSizeHook(root) },
1046
+ { analyzer: "Hooks", match: "sprint-open-check", fix: (root) => addSprintOpenHook(root) },
1047
+ { analyzer: "Hooks", match: "sprint-complete nudge", fix: (root) => addSprintCompleteNudge(root) },
852
1048
  { analyzer: "Rules", match: "No skill authoring conventions", fix: (root) => addSkillAuthoringConventions(root) },
853
1049
  { analyzer: "Rules", match: "No /lp-enhance skill", fix: (root) => createEnhanceSkill(root) },
854
1050
  { analyzer: "Rules", match: "lp-enhance skill is outdated", fix: (root) => updateEnhanceSkill(root) },
@@ -864,7 +1060,7 @@ var FIX_TABLE = [
864
1060
  { analyzer: "Memory", match: "SessionEnd push hook is not nohup-wrapped", fix: (root) => upgradeStaleSessionEndPushHook(root) },
865
1061
  { analyzer: "Memory", match: "CLAUDE.md missing memory guidance", fix: (root, _det, placement) => {
866
1062
  const content = "Use agentic-memory to persist knowledge across sessions:\n- Memories are automatically injected at session start\n- STORE IMMEDIATELY when: a dependency strategy changes, an architecture decision is made, a convention is established, a bug pattern is discovered, or a feature is killed/added\n- Use memory_search before memory_store to check for duplicates\n- NEVER store credentials, API keys, tokens, or secrets in memories";
867
- const target = placement === "local" ? join4(root, ".claude", "CLAUDE.md") : void 0;
1063
+ const target = placement === "local" ? join5(root, ".claude", "CLAUDE.md") : void 0;
868
1064
  return addClaudeMdSection(root, "## Memory", wrapStub(content), target);
869
1065
  } }
870
1066
  ];
@@ -879,81 +1075,9 @@ async function tryFix(issue, root, detected, placement) {
879
1075
  );
880
1076
  return entry ? entry.fix(root, detected, placement) : false;
881
1077
  }
882
- async function addHook(root, event, dedupKeyword, entry, successMsg) {
883
- const settings = await readSettingsJson(root);
884
- const hooks = settings.hooks ?? {};
885
- const hookList = hooks[event] ?? [];
886
- const alreadyHas = hookList.some((g) => {
887
- const nested = g.hooks;
888
- return nested?.some((h) => String(h.command ?? "").includes(dedupKeyword));
889
- });
890
- if (alreadyHas) return false;
891
- const updated = [...hookList, entry];
892
- const updatedSettings = { ...settings, hooks: { ...hooks, [event]: updated } };
893
- await writeSettingsJson(root, updatedSettings);
894
- log.success(successMsg);
895
- return true;
896
- }
897
- async function addEnvProtectionHook(root) {
898
- return addHook(root, "PreToolUse", ".env", {
899
- matcher: "Read|Write|Edit",
900
- hooks: [{
901
- type: "command",
902
- command: `echo "$TOOL_INPUT_FILE_PATH" | grep -qE '\\.(env|env\\..*)$' && ! echo "$TOOL_INPUT_FILE_PATH" | grep -q '.env.example' && echo 'BLOCKED: .env files contain secrets' && exit 1; exit 0`
903
- }]
904
- }, "Added .env file protection hook (PreToolUse)");
905
- }
906
- async function addAutoFormatHook(root, detected) {
907
- if (!detected.language) return false;
908
- const formatters = {
909
- TypeScript: { extensions: ["ts", "tsx"], command: "npx prettier --write" },
910
- JavaScript: { extensions: ["js", "jsx"], command: "npx prettier --write" },
911
- Python: { extensions: ["py"], command: "ruff format" },
912
- Go: { extensions: ["go"], command: "gofmt -w" },
913
- Rust: { extensions: ["rs"], command: "rustfmt" },
914
- Ruby: { extensions: ["rb"], command: "rubocop -A" },
915
- PHP: { extensions: ["php"], command: "vendor/bin/pint" }
916
- };
917
- const config = formatters[detected.language];
918
- if (!config) return false;
919
- const extChecks = config.extensions.map((ext) => `[ "$ext" = "${ext}" ]`).join(" || ");
920
- return addHook(root, "PostToolUse", "format", {
921
- matcher: "Write|Edit",
922
- hooks: [{
923
- type: "command",
924
- command: `ext=\${TOOL_INPUT_FILE_PATH##*.}; (${extChecks}) && ${config.command} "$TOOL_INPUT_FILE_PATH" 2>/dev/null; exit 0`
925
- }]
926
- }, `Added auto-format hook (PostToolUse \u2192 ${config.command})`);
927
- }
928
- async function addForcePushProtection(root) {
929
- return addHook(root, "PreToolUse", "force", {
930
- matcher: "Bash",
931
- hooks: [{
932
- type: "command",
933
- command: `echo "$TOOL_INPUT_COMMAND" | grep -qE 'push.*--force|push.*-f' && echo 'WARNING: Force push detected \u2014 this can destroy remote history' && exit 1; exit 0`
934
- }]
935
- }, "Added force-push protection hook (PreToolUse \u2192 Bash)");
936
- }
937
- async function addPostCompactHook(root) {
938
- return addHook(root, "PostCompact", "TASKS.md", {
939
- matcher: "",
940
- hooks: [{
941
- type: "command",
942
- command: "cat TASKS.md 2>/dev/null; exit 0"
943
- }]
944
- }, "Added PostCompact hook (re-injects TASKS.md after compaction)");
945
- }
946
- async function addSessionStartHook(root) {
947
- return addHook(root, "SessionStart", "TASKS.md", {
948
- matcher: "startup|resume",
949
- hooks: [{
950
- type: "command",
951
- command: "cat TASKS.md 2>/dev/null; exit 0"
952
- }]
953
- }, "Added SessionStart hook (injects TASKS.md at startup)");
954
- }
955
1078
  async function migrateAttribution(root) {
956
1079
  const settings = await readSettingsJson(root);
1080
+ if (settings === null) return false;
957
1081
  if (settings.includeCoAuthoredBy === void 0) return false;
958
1082
  const { includeCoAuthoredBy: _, ...rest } = settings;
959
1083
  const updated = { ...rest, attribution: { commit: "", pr: "" } };
@@ -963,6 +1087,7 @@ async function migrateAttribution(root) {
963
1087
  }
964
1088
  async function addCredentialDenyRules(root) {
965
1089
  const settings = await readSettingsJson(root);
1090
+ if (settings === null) return false;
966
1091
  const permissions = settings.permissions ?? {};
967
1092
  const deny = permissions.deny ?? [];
968
1093
  const toAdd = ["Read(~/.ssh/*)", "Read(~/.aws/*)", "Read(~/.npmrc)"];
@@ -975,6 +1100,7 @@ async function addCredentialDenyRules(root) {
975
1100
  }
976
1101
  async function addBypassDisable(root) {
977
1102
  const settings = await readSettingsJson(root);
1103
+ if (settings === null) return false;
978
1104
  if (settings.disableBypassPermissionsMode === "disable") return false;
979
1105
  const updated = { ...settings, disableBypassPermissionsMode: "disable" };
980
1106
  await writeSettingsJson(root, updated);
@@ -983,6 +1109,7 @@ async function addBypassDisable(root) {
983
1109
  }
984
1110
  async function removeSandboxSettings(root) {
985
1111
  const settings = await readSettingsJson(root);
1112
+ if (settings === null) return false;
986
1113
  if (settings.sandbox === void 0) return false;
987
1114
  const { sandbox: _sandbox, ...rest } = settings;
988
1115
  await writeSettingsJson(root, rest);
@@ -990,27 +1117,27 @@ async function removeSandboxSettings(root) {
990
1117
  return true;
991
1118
  }
992
1119
  async function addEnvToClaudeignore(root) {
993
- const ignorePath = join4(root, ".claudeignore");
1120
+ const ignorePath = join5(root, ".claudeignore");
994
1121
  let content;
995
1122
  try {
996
- content = await readFile4(ignorePath, "utf-8");
1123
+ content = await readFile3(ignorePath, "utf-8");
997
1124
  } catch {
998
1125
  return false;
999
1126
  }
1000
1127
  const lines = content.split("\n").map((l) => l.trim());
1001
1128
  if (lines.some((l) => l === ".env" || l === ".env.*" || l === ".env*")) return false;
1002
- await writeFile2(ignorePath, content.trimEnd() + "\n.env\n.env.*\n");
1129
+ await writeFile3(ignorePath, content.trimEnd() + "\n.env\n.env.*\n");
1003
1130
  log.success("Added .env to .claudeignore");
1004
1131
  return true;
1005
1132
  }
1006
1133
  async function addClaudeMdSection(root, heading, content, targetPath) {
1007
- const claudeMdPath = targetPath ?? join4(root, "CLAUDE.md");
1134
+ const claudeMdPath = targetPath ?? join5(root, "CLAUDE.md");
1008
1135
  let existing;
1009
1136
  try {
1010
- existing = await readFile4(claudeMdPath, "utf-8");
1137
+ existing = await readFile3(claudeMdPath, "utf-8");
1011
1138
  } catch {
1012
1139
  if (!targetPath) return false;
1013
- await mkdir2(join4(root, ".claude"), { recursive: true });
1140
+ await mkdir2(join5(root, ".claude"), { recursive: true });
1014
1141
  existing = "# Local Claude Config\n";
1015
1142
  }
1016
1143
  if (existing.includes(heading)) return false;
@@ -1022,20 +1149,20 @@ ${content}
1022
1149
 
1023
1150
  `;
1024
1151
  const updated = existing.slice(0, insertAt) + section + existing.slice(insertAt);
1025
- await writeFile2(claudeMdPath, updated);
1152
+ await writeFile3(claudeMdPath, updated);
1026
1153
  const label = targetPath ? ".claude/CLAUDE.md" : "CLAUDE.md";
1027
1154
  log.success(`Added "${heading}" section to ${label}`);
1028
1155
  return true;
1029
1156
  }
1030
1157
  async function createBacklogMd(root) {
1031
- const backlogPath = join4(root, "BACKLOG.md");
1158
+ const backlogPath = join5(root, "BACKLOG.md");
1032
1159
  try {
1033
1160
  await access2(backlogPath);
1034
1161
  return false;
1035
1162
  } catch {
1036
1163
  }
1037
1164
  const name = root.split("/").pop() ?? "Project";
1038
- await writeFile2(backlogPath, `# ${name} - Backlog
1165
+ await writeFile3(backlogPath, `# ${name} - Backlog
1039
1166
 
1040
1167
  > Features discussed but deferred. Pick up when relevant.
1041
1168
  > Priority: P0 = next sprint, P1 = soon, P2 = when relevant.
@@ -1044,14 +1171,14 @@ async function createBacklogMd(root) {
1044
1171
  return true;
1045
1172
  }
1046
1173
  async function createClaudeignore(root, detected) {
1047
- const ignorePath = join4(root, ".claudeignore");
1174
+ const ignorePath = join5(root, ".claudeignore");
1048
1175
  try {
1049
1176
  await access2(ignorePath);
1050
1177
  return false;
1051
1178
  } catch {
1052
1179
  }
1053
1180
  const content = generateClaudeignore(detected);
1054
- await writeFile2(ignorePath, content);
1181
+ await writeFile3(ignorePath, content);
1055
1182
  log.success("Generated .claudeignore with language-specific ignore patterns");
1056
1183
  return true;
1057
1184
  }
@@ -1061,15 +1188,15 @@ var SKILL_AUTHORING_SECTION = `
1061
1188
  ${SKILL_AUTHORING_CONTENT}
1062
1189
  `;
1063
1190
  async function createStarterRules(root) {
1064
- const rulesDir = join4(root, ".claude", "rules");
1191
+ const rulesDir = join5(root, ".claude", "rules");
1065
1192
  try {
1066
1193
  await access2(rulesDir);
1067
1194
  return false;
1068
1195
  } catch {
1069
1196
  }
1070
1197
  await mkdir2(rulesDir, { recursive: true });
1071
- await writeFile2(
1072
- join4(rulesDir, "conventions.md"),
1198
+ await writeFile3(
1199
+ join5(rulesDir, "conventions.md"),
1073
1200
  `# Project Conventions
1074
1201
 
1075
1202
  - Use conventional commits (feat:, fix:, docs:, refactor:, test:, chore:)
@@ -1082,36 +1209,36 @@ ${SKILL_AUTHORING_SECTION}`
1082
1209
  return true;
1083
1210
  }
1084
1211
  async function addSkillAuthoringConventions(root) {
1085
- const conventionsPath = join4(root, ".claude", "rules", "conventions.md");
1212
+ const conventionsPath = join5(root, ".claude", "rules", "conventions.md");
1086
1213
  let content;
1087
1214
  try {
1088
- content = await readFile4(conventionsPath, "utf-8");
1215
+ content = await readFile3(conventionsPath, "utf-8");
1089
1216
  } catch {
1090
1217
  return false;
1091
1218
  }
1092
1219
  if (/^##\s+Skill\s+Authoring/im.test(content)) return false;
1093
- await writeFile2(conventionsPath, content.trimEnd() + "\n" + SKILL_AUTHORING_SECTION);
1220
+ await writeFile3(conventionsPath, content.trimEnd() + "\n" + SKILL_AUTHORING_SECTION);
1094
1221
  log.success("Added Skill Authoring section to .claude/rules/conventions.md");
1095
1222
  return true;
1096
1223
  }
1097
1224
  async function createEnhanceSkill(root) {
1098
- const skillDir = join4(root, ".claude", "skills", "lp-enhance");
1099
- const skillPath = join4(skillDir, "SKILL.md");
1100
- const globalPath = join4(homedir(), ".claude", "skills", "lp-enhance", "SKILL.md");
1101
- const legacyProject = join4(root, ".claude", "commands", "lp-enhance.md");
1102
- const legacyGlobal = join4(homedir(), ".claude", "commands", "lp-enhance.md");
1225
+ const skillDir = join5(root, ".claude", "skills", "lp-enhance");
1226
+ const skillPath = join5(skillDir, "SKILL.md");
1227
+ const globalPath = join5(homedir(), ".claude", "skills", "lp-enhance", "SKILL.md");
1228
+ const legacyProject = join5(root, ".claude", "commands", "lp-enhance.md");
1229
+ const legacyGlobal = join5(homedir(), ".claude", "commands", "lp-enhance.md");
1103
1230
  if (await fileExists(skillPath) || await fileExists(globalPath) || await fileExists(legacyProject) || await fileExists(legacyGlobal)) return false;
1104
1231
  await mkdir2(skillDir, { recursive: true });
1105
- await writeFile2(skillPath, generateEnhanceSkill());
1232
+ await writeFile3(skillPath, generateEnhanceSkill());
1106
1233
  log.success("Generated /lp-enhance skill (.claude/skills/lp-enhance/)");
1107
1234
  return true;
1108
1235
  }
1109
1236
  async function updateEnhanceSkill(root) {
1110
- const projectPath = join4(root, ".claude", "skills", "lp-enhance", "SKILL.md");
1111
- const globalPath = join4(homedir(), ".claude", "skills", "lp-enhance", "SKILL.md");
1237
+ const projectPath = join5(root, ".claude", "skills", "lp-enhance", "SKILL.md");
1238
+ const globalPath = join5(homedir(), ".claude", "skills", "lp-enhance", "SKILL.md");
1112
1239
  const targetPath = await fileExists(projectPath) ? projectPath : await fileExists(globalPath) ? globalPath : null;
1113
1240
  if (!targetPath) return false;
1114
- await writeFile2(targetPath, generateEnhanceSkill());
1241
+ await writeFile3(targetPath, generateEnhanceSkill());
1115
1242
  log.success("Updated /lp-enhance skill to latest version");
1116
1243
  return true;
1117
1244
  }
@@ -1140,10 +1267,16 @@ var colors = {
1140
1267
  return map[sev](` ${sev.toUpperCase()} `);
1141
1268
  }
1142
1269
  };
1270
+ var warnedKeys = /* @__PURE__ */ new Set();
1143
1271
  var log = {
1144
1272
  success: (msg) => console.log(` ${chalk.green("\u2713")} ${msg}`),
1145
1273
  error: (msg) => console.log(` ${chalk.red("\u2717")} ${msg}`),
1146
1274
  warn: (msg) => console.log(` ${chalk.yellow("!")} ${msg}`),
1275
+ warnOnce: (key, msg) => {
1276
+ if (warnedKeys.has(key)) return;
1277
+ warnedKeys.add(key);
1278
+ console.log(` ${chalk.yellow("!")} ${msg}`);
1279
+ },
1147
1280
  step: (msg) => console.log(` ${chalk.cyan("\u2192")} ${msg}`),
1148
1281
  info: (msg) => console.log(` ${chalk.dim("\xB7")} ${msg}`),
1149
1282
  blank: () => console.log()
@@ -1212,6 +1345,41 @@ function renderDoctorReport(results, options) {
1212
1345
  return { overallScore, actionableCount: actionable.length };
1213
1346
  }
1214
1347
 
1348
+ // src/lib/settings.ts
1349
+ async function readJsonFile(path) {
1350
+ let raw;
1351
+ try {
1352
+ raw = await readFile4(path, "utf-8");
1353
+ } catch (err) {
1354
+ const code = err.code;
1355
+ if (code === "ENOENT") return {};
1356
+ log.warnOnce(`read:${path}`, `Could not read ${path}: ${err.message}`);
1357
+ return null;
1358
+ }
1359
+ try {
1360
+ return JSON.parse(raw);
1361
+ } catch (err) {
1362
+ log.warnOnce(`parse:${path}`, `${path} is not valid JSON: ${err.message}. Treating as unreadable to avoid clobbering it.`);
1363
+ return null;
1364
+ }
1365
+ }
1366
+ async function readSettingsJson(root) {
1367
+ return readJsonFile(join6(root, ".claude", "settings.json"));
1368
+ }
1369
+ async function writeSettingsJson(root, settings) {
1370
+ const dir = join6(root, ".claude");
1371
+ await mkdir3(dir, { recursive: true });
1372
+ await writeFile4(join6(dir, "settings.json"), JSON.stringify(settings, null, 2) + "\n");
1373
+ }
1374
+ async function readSettingsLocalJson(root) {
1375
+ return readJsonFile(join6(root, ".claude", "settings.local.json"));
1376
+ }
1377
+ async function writeSettingsLocalJson(root, settings) {
1378
+ const dir = join6(root, ".claude");
1379
+ await mkdir3(dir, { recursive: true });
1380
+ await writeFile4(join6(dir, "settings.local.json"), JSON.stringify(settings, null, 2) + "\n");
1381
+ }
1382
+
1215
1383
  export {
1216
1384
  __export,
1217
1385
  SESSION_START_CONTENT,
@@ -1231,10 +1399,12 @@ export {
1231
1399
  writeSettingsLocalJson,
1232
1400
  getMemoryPlacement,
1233
1401
  LP_STUB_OPEN,
1402
+ addOrUpdateHook,
1403
+ writeSprintHygieneScripts,
1234
1404
  applyFixes,
1235
1405
  log,
1236
1406
  printBanner,
1237
1407
  printScoreCard,
1238
1408
  renderDoctorReport
1239
1409
  };
1240
- //# sourceMappingURL=chunk-7QPZN27I.js.map
1410
+ //# sourceMappingURL=chunk-RCYLZUU6.js.map