claude-launchpad 1.8.1 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/{chunk-COGKNJJB.js → chunk-72VWDNAE.js} +2 -2
  2. package/dist/{chunk-H2E7QMF4.js → chunk-DYHPVA6O.js} +2 -2
  3. package/dist/{chunk-THDBKJAV.js → chunk-GA3IUQUM.js} +3 -3
  4. package/dist/{chunk-RDID5P4K.js → chunk-I4S4Q2IV.js} +2 -2
  5. package/dist/{chunk-IY3Z54UK.js → chunk-RCYLZUU6.js} +268 -124
  6. package/dist/chunk-RCYLZUU6.js.map +1 -0
  7. package/dist/cli.js +96 -21
  8. package/dist/cli.js.map +1 -1
  9. package/dist/commands/memory/server.js +3 -3
  10. package/dist/{context-BCTOCTZD.js → context-X7UP2ODK.js} +5 -5
  11. package/dist/{install-L23C4YWJ.js → install-XBCEI5QK.js} +33 -44
  12. package/dist/install-XBCEI5QK.js.map +1 -0
  13. package/dist/{pull-5KYLJ6TH.js → pull-VA62U3OP.js} +7 -7
  14. package/dist/{push-UOVLT5HO.js → push-C3M6Q4V7.js} +7 -7
  15. package/dist/{require-deps-T2QOGQQ3.js → require-deps-UBU5CYM5.js} +3 -3
  16. package/dist/{stats-GL7P24U7.js → stats-R4TWCPHW.js} +6 -6
  17. package/dist/{sync-clean-7RNYS7EH.js → sync-clean-P4S7V2JS.js} +3 -3
  18. package/dist/{sync-status-ULZCMBQJ.js → sync-status-TPYUF43G.js} +7 -7
  19. package/dist/{tui-UGBWF2WT.js → tui-FFLCUR7E.js} +4 -4
  20. package/package.json +1 -1
  21. package/dist/chunk-IY3Z54UK.js.map +0 -1
  22. package/dist/install-L23C4YWJ.js.map +0 -1
  23. /package/dist/{chunk-COGKNJJB.js.map → chunk-72VWDNAE.js.map} +0 -0
  24. /package/dist/{chunk-H2E7QMF4.js.map → chunk-DYHPVA6O.js.map} +0 -0
  25. /package/dist/{chunk-THDBKJAV.js.map → chunk-GA3IUQUM.js.map} +0 -0
  26. /package/dist/{chunk-RDID5P4K.js.map → chunk-I4S4Q2IV.js.map} +0 -0
  27. /package/dist/{context-BCTOCTZD.js.map → context-X7UP2ODK.js.map} +0 -0
  28. /package/dist/{pull-5KYLJ6TH.js.map → pull-VA62U3OP.js.map} +0 -0
  29. /package/dist/{push-UOVLT5HO.js.map → push-C3M6Q4V7.js.map} +0 -0
  30. /package/dist/{require-deps-T2QOGQQ3.js.map → require-deps-UBU5CYM5.js.map} +0 -0
  31. /package/dist/{stats-GL7P24U7.js.map → stats-R4TWCPHW.js.map} +0 -0
  32. /package/dist/{sync-clean-7RNYS7EH.js.map → sync-clean-P4S7V2JS.js.map} +0 -0
  33. /package/dist/{sync-status-ULZCMBQJ.js.map → sync-status-TPYUF43G.js.map} +0 -0
  34. /package/dist/{tui-UGBWF2WT.js.map → tui-FFLCUR7E.js.map} +0 -0
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  log
4
- } from "./chunk-IY3Z54UK.js";
4
+ } from "./chunk-RCYLZUU6.js";
5
5
 
6
6
  // src/commands/memory/utils/gist-transport.ts
7
7
  import { execSync } from "child_process";
@@ -205,4 +205,4 @@ export {
205
205
  deleteGistFile,
206
206
  updateGistFiles
207
207
  };
208
- //# sourceMappingURL=chunk-COGKNJJB.js.map
208
+ //# sourceMappingURL=chunk-72VWDNAE.js.map
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  log
4
- } from "./chunk-IY3Z54UK.js";
4
+ } from "./chunk-RCYLZUU6.js";
5
5
 
6
6
  // src/commands/memory/utils/require-deps.ts
7
7
  import { createRequire } from "module";
@@ -44,4 +44,4 @@ export {
44
44
  cwdRequire,
45
45
  requireMemoryDeps
46
46
  };
47
- //# sourceMappingURL=chunk-H2E7QMF4.js.map
47
+ //# sourceMappingURL=chunk-DYHPVA6O.js.map
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  cwdRequire
4
- } from "./chunk-H2E7QMF4.js";
4
+ } from "./chunk-DYHPVA6O.js";
5
5
  import {
6
6
  __export
7
- } from "./chunk-IY3Z54UK.js";
7
+ } from "./chunk-RCYLZUU6.js";
8
8
 
9
9
  // src/commands/memory/config.ts
10
10
  import { z } from "zod";
@@ -363,4 +363,4 @@ export {
363
363
  closeDatabase,
364
364
  migrate
365
365
  };
366
- //# sourceMappingURL=chunk-THDBKJAV.js.map
366
+ //# sourceMappingURL=chunk-GA3IUQUM.js.map
@@ -10,7 +10,7 @@ import {
10
10
  loadConfig,
11
11
  migrate,
12
12
  resolveDataDir
13
- } from "./chunk-THDBKJAV.js";
13
+ } from "./chunk-GA3IUQUM.js";
14
14
 
15
15
  // src/commands/memory/subcommands/init-storage.ts
16
16
  function initStorage(dbPath) {
@@ -32,4 +32,4 @@ function initStorage(dbPath) {
32
32
  export {
33
33
  initStorage
34
34
  };
35
- //# sourceMappingURL=chunk-RDID5P4K.js.map
35
+ //# sourceMappingURL=chunk-I4S4Q2IV.js.map
@@ -32,15 +32,15 @@ async function readJsonOrNull(path) {
32
32
  }
33
33
 
34
34
  // src/lib/settings.ts
35
- import { readFile as readFile4, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
36
- import { join as join4 } from "path";
35
+ import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir3 } from "fs/promises";
36
+ import { join as join6 } from "path";
37
37
 
38
38
  // src/lib/output.ts
39
39
  import chalk from "chalk";
40
40
 
41
41
  // src/commands/doctor/fixer.ts
42
- import { readFile as readFile3, writeFile, mkdir, access as access2 } from "fs/promises";
43
- import { join as join3 } 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";
44
44
  import { homedir } from "os";
45
45
 
46
46
  // src/lib/sections.ts
@@ -600,24 +600,236 @@ ${content}
600
600
  ${LP_STUB_CLOSE}`;
601
601
  }
602
602
 
603
+ // src/commands/doctor/fixer-sprint.ts
604
+ import { writeFile as writeFile2 } from "fs/promises";
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
+
603
816
  // src/commands/doctor/fixer-memory.ts
604
817
  import { readFile as readFile2 } from "fs/promises";
605
- import { join as join2 } from "path";
818
+ import { join as join4 } from "path";
606
819
  async function addPlacementHook(root, placement, event, dedupKeyword, entry, prepend, successMsg) {
607
820
  const read = placement === "local" ? readSettingsLocalJson : readSettingsJson;
608
821
  const write = placement === "local" ? writeSettingsLocalJson : writeSettingsJson;
609
822
  const settings = await read(root);
610
823
  if (settings === null) return false;
611
- const hooks = settings.hooks ?? {};
612
- const hookList = hooks[event] ?? [];
613
- const alreadyHas = hookList.some((g) => {
614
- const nested = g.hooks;
615
- return nested?.some((h) => String(h.command ?? "").includes(dedupKeyword));
824
+ const existingHooks = settings.hooks;
825
+ const result = addOrUpdateHook(existingHooks, {
826
+ event,
827
+ dedupKeyword,
828
+ entry,
829
+ prepend
616
830
  });
617
- if (alreadyHas) return false;
618
- const updatedList = prepend ? [entry, ...hookList] : [...hookList, entry];
619
- const updatedSettings = { ...settings, hooks: { ...hooks, [event]: updatedList } };
620
- await write(root, updatedSettings);
831
+ if (!result.added) return false;
832
+ await write(root, { ...settings, hooks: result.hooks });
621
833
  log.success(successMsg);
622
834
  return true;
623
835
  }
@@ -757,7 +969,7 @@ async function addAllowedMcpServers(root, placement) {
757
969
  if (settingsServers && typeof settingsServers === "object") {
758
970
  for (const name of Object.keys(settingsServers)) serverNames.add(name);
759
971
  }
760
- const mcpJsonPath = join2(root, ".mcp.json");
972
+ const mcpJsonPath = join4(root, ".mcp.json");
761
973
  try {
762
974
  const mcpJson = JSON.parse(await readFile2(mcpJsonPath, "utf-8"));
763
975
  const mcpServers = mcpJson.mcpServers;
@@ -829,6 +1041,10 @@ var FIX_TABLE = [
829
1041
  { analyzer: "Permissions", match: "Bypass permissions mode", fix: (root) => addBypassDisable(root) },
830
1042
  { analyzer: "Permissions", match: "Filesystem sandbox enabled", fix: (root) => removeSandboxSettings(root) },
831
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) },
832
1048
  { analyzer: "Rules", match: "No skill authoring conventions", fix: (root) => addSkillAuthoringConventions(root) },
833
1049
  { analyzer: "Rules", match: "No /lp-enhance skill", fix: (root) => createEnhanceSkill(root) },
834
1050
  { analyzer: "Rules", match: "lp-enhance skill is outdated", fix: (root) => updateEnhanceSkill(root) },
@@ -844,7 +1060,7 @@ var FIX_TABLE = [
844
1060
  { analyzer: "Memory", match: "SessionEnd push hook is not nohup-wrapped", fix: (root) => upgradeStaleSessionEndPushHook(root) },
845
1061
  { analyzer: "Memory", match: "CLAUDE.md missing memory guidance", fix: (root, _det, placement) => {
846
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";
847
- const target = placement === "local" ? join3(root, ".claude", "CLAUDE.md") : void 0;
1063
+ const target = placement === "local" ? join5(root, ".claude", "CLAUDE.md") : void 0;
848
1064
  return addClaudeMdSection(root, "## Memory", wrapStub(content), target);
849
1065
  } }
850
1066
  ];
@@ -859,80 +1075,6 @@ async function tryFix(issue, root, detected, placement) {
859
1075
  );
860
1076
  return entry ? entry.fix(root, detected, placement) : false;
861
1077
  }
862
- async function addHook(root, event, dedupKeyword, entry, successMsg) {
863
- const settings = await readSettingsJson(root);
864
- if (settings === null) return false;
865
- const hooks = settings.hooks ?? {};
866
- const hookList = hooks[event] ?? [];
867
- const alreadyHas = hookList.some((g) => {
868
- const nested = g.hooks;
869
- return nested?.some((h) => String(h.command ?? "").includes(dedupKeyword));
870
- });
871
- if (alreadyHas) return false;
872
- const updated = [...hookList, entry];
873
- const updatedSettings = { ...settings, hooks: { ...hooks, [event]: updated } };
874
- await writeSettingsJson(root, updatedSettings);
875
- log.success(successMsg);
876
- return true;
877
- }
878
- async function addEnvProtectionHook(root) {
879
- return addHook(root, "PreToolUse", ".env", {
880
- matcher: "Read|Write|Edit",
881
- hooks: [{
882
- type: "command",
883
- 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`
884
- }]
885
- }, "Added .env file protection hook (PreToolUse)");
886
- }
887
- async function addAutoFormatHook(root, detected) {
888
- if (!detected.language) return false;
889
- const formatters = {
890
- TypeScript: { extensions: ["ts", "tsx"], command: "npx prettier --write" },
891
- JavaScript: { extensions: ["js", "jsx"], command: "npx prettier --write" },
892
- Python: { extensions: ["py"], command: "ruff format" },
893
- Go: { extensions: ["go"], command: "gofmt -w" },
894
- Rust: { extensions: ["rs"], command: "rustfmt" },
895
- Ruby: { extensions: ["rb"], command: "rubocop -A" },
896
- PHP: { extensions: ["php"], command: "vendor/bin/pint" }
897
- };
898
- const config = formatters[detected.language];
899
- if (!config) return false;
900
- const extChecks = config.extensions.map((ext) => `[ "$ext" = "${ext}" ]`).join(" || ");
901
- return addHook(root, "PostToolUse", "format", {
902
- matcher: "Write|Edit",
903
- hooks: [{
904
- type: "command",
905
- command: `ext=\${TOOL_INPUT_FILE_PATH##*.}; (${extChecks}) && ${config.command} "$TOOL_INPUT_FILE_PATH" 2>/dev/null; exit 0`
906
- }]
907
- }, `Added auto-format hook (PostToolUse \u2192 ${config.command})`);
908
- }
909
- async function addForcePushProtection(root) {
910
- return addHook(root, "PreToolUse", "force", {
911
- matcher: "Bash",
912
- hooks: [{
913
- type: "command",
914
- 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`
915
- }]
916
- }, "Added force-push protection hook (PreToolUse \u2192 Bash)");
917
- }
918
- async function addPostCompactHook(root) {
919
- return addHook(root, "PostCompact", "TASKS.md", {
920
- matcher: "",
921
- hooks: [{
922
- type: "command",
923
- command: "cat TASKS.md 2>/dev/null; exit 0"
924
- }]
925
- }, "Added PostCompact hook (re-injects TASKS.md after compaction)");
926
- }
927
- async function addSessionStartHook(root) {
928
- return addHook(root, "SessionStart", "TASKS.md", {
929
- matcher: "startup|resume",
930
- hooks: [{
931
- type: "command",
932
- command: "cat TASKS.md 2>/dev/null; exit 0"
933
- }]
934
- }, "Added SessionStart hook (injects TASKS.md at startup)");
935
- }
936
1078
  async function migrateAttribution(root) {
937
1079
  const settings = await readSettingsJson(root);
938
1080
  if (settings === null) return false;
@@ -975,7 +1117,7 @@ async function removeSandboxSettings(root) {
975
1117
  return true;
976
1118
  }
977
1119
  async function addEnvToClaudeignore(root) {
978
- const ignorePath = join3(root, ".claudeignore");
1120
+ const ignorePath = join5(root, ".claudeignore");
979
1121
  let content;
980
1122
  try {
981
1123
  content = await readFile3(ignorePath, "utf-8");
@@ -984,18 +1126,18 @@ async function addEnvToClaudeignore(root) {
984
1126
  }
985
1127
  const lines = content.split("\n").map((l) => l.trim());
986
1128
  if (lines.some((l) => l === ".env" || l === ".env.*" || l === ".env*")) return false;
987
- await writeFile(ignorePath, content.trimEnd() + "\n.env\n.env.*\n");
1129
+ await writeFile3(ignorePath, content.trimEnd() + "\n.env\n.env.*\n");
988
1130
  log.success("Added .env to .claudeignore");
989
1131
  return true;
990
1132
  }
991
1133
  async function addClaudeMdSection(root, heading, content, targetPath) {
992
- const claudeMdPath = targetPath ?? join3(root, "CLAUDE.md");
1134
+ const claudeMdPath = targetPath ?? join5(root, "CLAUDE.md");
993
1135
  let existing;
994
1136
  try {
995
1137
  existing = await readFile3(claudeMdPath, "utf-8");
996
1138
  } catch {
997
1139
  if (!targetPath) return false;
998
- await mkdir(join3(root, ".claude"), { recursive: true });
1140
+ await mkdir2(join5(root, ".claude"), { recursive: true });
999
1141
  existing = "# Local Claude Config\n";
1000
1142
  }
1001
1143
  if (existing.includes(heading)) return false;
@@ -1007,20 +1149,20 @@ ${content}
1007
1149
 
1008
1150
  `;
1009
1151
  const updated = existing.slice(0, insertAt) + section + existing.slice(insertAt);
1010
- await writeFile(claudeMdPath, updated);
1152
+ await writeFile3(claudeMdPath, updated);
1011
1153
  const label = targetPath ? ".claude/CLAUDE.md" : "CLAUDE.md";
1012
1154
  log.success(`Added "${heading}" section to ${label}`);
1013
1155
  return true;
1014
1156
  }
1015
1157
  async function createBacklogMd(root) {
1016
- const backlogPath = join3(root, "BACKLOG.md");
1158
+ const backlogPath = join5(root, "BACKLOG.md");
1017
1159
  try {
1018
1160
  await access2(backlogPath);
1019
1161
  return false;
1020
1162
  } catch {
1021
1163
  }
1022
1164
  const name = root.split("/").pop() ?? "Project";
1023
- await writeFile(backlogPath, `# ${name} - Backlog
1165
+ await writeFile3(backlogPath, `# ${name} - Backlog
1024
1166
 
1025
1167
  > Features discussed but deferred. Pick up when relevant.
1026
1168
  > Priority: P0 = next sprint, P1 = soon, P2 = when relevant.
@@ -1029,14 +1171,14 @@ async function createBacklogMd(root) {
1029
1171
  return true;
1030
1172
  }
1031
1173
  async function createClaudeignore(root, detected) {
1032
- const ignorePath = join3(root, ".claudeignore");
1174
+ const ignorePath = join5(root, ".claudeignore");
1033
1175
  try {
1034
1176
  await access2(ignorePath);
1035
1177
  return false;
1036
1178
  } catch {
1037
1179
  }
1038
1180
  const content = generateClaudeignore(detected);
1039
- await writeFile(ignorePath, content);
1181
+ await writeFile3(ignorePath, content);
1040
1182
  log.success("Generated .claudeignore with language-specific ignore patterns");
1041
1183
  return true;
1042
1184
  }
@@ -1046,15 +1188,15 @@ var SKILL_AUTHORING_SECTION = `
1046
1188
  ${SKILL_AUTHORING_CONTENT}
1047
1189
  `;
1048
1190
  async function createStarterRules(root) {
1049
- const rulesDir = join3(root, ".claude", "rules");
1191
+ const rulesDir = join5(root, ".claude", "rules");
1050
1192
  try {
1051
1193
  await access2(rulesDir);
1052
1194
  return false;
1053
1195
  } catch {
1054
1196
  }
1055
- await mkdir(rulesDir, { recursive: true });
1056
- await writeFile(
1057
- join3(rulesDir, "conventions.md"),
1197
+ await mkdir2(rulesDir, { recursive: true });
1198
+ await writeFile3(
1199
+ join5(rulesDir, "conventions.md"),
1058
1200
  `# Project Conventions
1059
1201
 
1060
1202
  - Use conventional commits (feat:, fix:, docs:, refactor:, test:, chore:)
@@ -1067,7 +1209,7 @@ ${SKILL_AUTHORING_SECTION}`
1067
1209
  return true;
1068
1210
  }
1069
1211
  async function addSkillAuthoringConventions(root) {
1070
- const conventionsPath = join3(root, ".claude", "rules", "conventions.md");
1212
+ const conventionsPath = join5(root, ".claude", "rules", "conventions.md");
1071
1213
  let content;
1072
1214
  try {
1073
1215
  content = await readFile3(conventionsPath, "utf-8");
@@ -1075,28 +1217,28 @@ async function addSkillAuthoringConventions(root) {
1075
1217
  return false;
1076
1218
  }
1077
1219
  if (/^##\s+Skill\s+Authoring/im.test(content)) return false;
1078
- await writeFile(conventionsPath, content.trimEnd() + "\n" + SKILL_AUTHORING_SECTION);
1220
+ await writeFile3(conventionsPath, content.trimEnd() + "\n" + SKILL_AUTHORING_SECTION);
1079
1221
  log.success("Added Skill Authoring section to .claude/rules/conventions.md");
1080
1222
  return true;
1081
1223
  }
1082
1224
  async function createEnhanceSkill(root) {
1083
- const skillDir = join3(root, ".claude", "skills", "lp-enhance");
1084
- const skillPath = join3(skillDir, "SKILL.md");
1085
- const globalPath = join3(homedir(), ".claude", "skills", "lp-enhance", "SKILL.md");
1086
- const legacyProject = join3(root, ".claude", "commands", "lp-enhance.md");
1087
- const legacyGlobal = join3(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");
1088
1230
  if (await fileExists(skillPath) || await fileExists(globalPath) || await fileExists(legacyProject) || await fileExists(legacyGlobal)) return false;
1089
- await mkdir(skillDir, { recursive: true });
1090
- await writeFile(skillPath, generateEnhanceSkill());
1231
+ await mkdir2(skillDir, { recursive: true });
1232
+ await writeFile3(skillPath, generateEnhanceSkill());
1091
1233
  log.success("Generated /lp-enhance skill (.claude/skills/lp-enhance/)");
1092
1234
  return true;
1093
1235
  }
1094
1236
  async function updateEnhanceSkill(root) {
1095
- const projectPath = join3(root, ".claude", "skills", "lp-enhance", "SKILL.md");
1096
- const globalPath = join3(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");
1097
1239
  const targetPath = await fileExists(projectPath) ? projectPath : await fileExists(globalPath) ? globalPath : null;
1098
1240
  if (!targetPath) return false;
1099
- await writeFile(targetPath, generateEnhanceSkill());
1241
+ await writeFile3(targetPath, generateEnhanceSkill());
1100
1242
  log.success("Updated /lp-enhance skill to latest version");
1101
1243
  return true;
1102
1244
  }
@@ -1222,20 +1364,20 @@ async function readJsonFile(path) {
1222
1364
  }
1223
1365
  }
1224
1366
  async function readSettingsJson(root) {
1225
- return readJsonFile(join4(root, ".claude", "settings.json"));
1367
+ return readJsonFile(join6(root, ".claude", "settings.json"));
1226
1368
  }
1227
1369
  async function writeSettingsJson(root, settings) {
1228
- const dir = join4(root, ".claude");
1229
- await mkdir2(dir, { recursive: true });
1230
- await writeFile2(join4(dir, "settings.json"), JSON.stringify(settings, null, 2) + "\n");
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");
1231
1373
  }
1232
1374
  async function readSettingsLocalJson(root) {
1233
- return readJsonFile(join4(root, ".claude", "settings.local.json"));
1375
+ return readJsonFile(join6(root, ".claude", "settings.local.json"));
1234
1376
  }
1235
1377
  async function writeSettingsLocalJson(root, settings) {
1236
- const dir = join4(root, ".claude");
1237
- await mkdir2(dir, { recursive: true });
1238
- await writeFile2(join4(dir, "settings.local.json"), JSON.stringify(settings, null, 2) + "\n");
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");
1239
1381
  }
1240
1382
 
1241
1383
  export {
@@ -1257,10 +1399,12 @@ export {
1257
1399
  writeSettingsLocalJson,
1258
1400
  getMemoryPlacement,
1259
1401
  LP_STUB_OPEN,
1402
+ addOrUpdateHook,
1403
+ writeSprintHygieneScripts,
1260
1404
  applyFixes,
1261
1405
  log,
1262
1406
  printBanner,
1263
1407
  printScoreCard,
1264
1408
  renderDoctorReport
1265
1409
  };
1266
- //# sourceMappingURL=chunk-IY3Z54UK.js.map
1410
+ //# sourceMappingURL=chunk-RCYLZUU6.js.map