claude-code-starter 0.15.0 → 0.16.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 (2) hide show
  1. package/dist/cli.js +563 -216
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -6,15 +6,15 @@ import fs5 from "fs";
6
6
  import path5 from "path";
7
7
  import { fileURLToPath } from "url";
8
8
  import ora from "ora";
9
- import pc from "picocolors";
10
- import prompts from "prompts";
9
+ import pc2 from "picocolors";
10
+ import prompts2 from "prompts";
11
11
 
12
12
  // src/analyzer.ts
13
13
  import fs from "fs";
14
14
  import path from "path";
15
15
  function analyzeRepository(rootDir) {
16
16
  const techStack = detectTechStack(rootDir);
17
- const fileCount = countSourceFiles(rootDir, techStack.languages);
17
+ const fileCount = countSourceFiles(rootDir);
18
18
  const packageJson = readPackageJson(rootDir);
19
19
  return {
20
20
  isExisting: fileCount > 0,
@@ -37,7 +37,7 @@ function detectTechStack(rootDir) {
37
37
  const linter = detectLinter(packageJson, files);
38
38
  const formatter = detectFormatter(packageJson, files);
39
39
  const bundler = detectBundler(packageJson, files);
40
- const isMonorepo = detectMonorepo(rootDir, files, packageJson);
40
+ const isMonorepo = detectMonorepo(files, packageJson);
41
41
  const hasDocker = files.includes("Dockerfile") || files.includes("docker-compose.yml") || files.includes("docker-compose.yaml");
42
42
  const { hasCICD, cicdPlatform } = detectCICD(rootDir, files);
43
43
  const { hasClaudeConfig, existingClaudeFiles } = detectExistingClaudeConfig(rootDir);
@@ -59,6 +59,12 @@ function detectTechStack(rootDir) {
59
59
  existingClaudeFiles
60
60
  };
61
61
  }
62
+ function getAllDeps(packageJson) {
63
+ return {
64
+ ...packageJson.dependencies || {},
65
+ ...packageJson.devDependencies || {}
66
+ };
67
+ }
62
68
  function readPackageJson(rootDir) {
63
69
  const packageJsonPath = path.join(rootDir, "package.json");
64
70
  if (!fs.existsSync(packageJsonPath)) return null;
@@ -271,10 +277,7 @@ function detectPackageManager(files) {
271
277
  }
272
278
  function detectTestingFramework(packageJson, files) {
273
279
  if (packageJson) {
274
- const allDeps = {
275
- ...packageJson.dependencies || {},
276
- ...packageJson.devDependencies || {}
277
- };
280
+ const allDeps = getAllDeps(packageJson);
278
281
  if (allDeps.vitest) return "vitest";
279
282
  if (allDeps.jest) return "jest";
280
283
  if (allDeps.mocha) return "mocha";
@@ -286,7 +289,8 @@ function detectTestingFramework(packageJson, files) {
286
289
  if (files.includes("pytest.ini") || files.includes("conftest.py")) return "pytest";
287
290
  if (files.includes("go.mod")) return "go-test";
288
291
  if (files.includes("Cargo.toml")) return "rust-test";
289
- if (files.includes("Gemfile")) return "rspec";
292
+ if (files.includes(".rspec")) return "rspec";
293
+ if (files.includes("Gemfile") && files.includes("spec")) return "rspec";
290
294
  return null;
291
295
  }
292
296
  function detectLinter(packageJson, files) {
@@ -297,10 +301,7 @@ function detectLinter(packageJson, files) {
297
301
  }
298
302
  if (files.includes("biome.json") || files.includes("biome.jsonc")) return "biome";
299
303
  if (packageJson) {
300
- const allDeps = {
301
- ...packageJson.dependencies || {},
302
- ...packageJson.devDependencies || {}
303
- };
304
+ const allDeps = getAllDeps(packageJson);
304
305
  if (allDeps.eslint) return "eslint";
305
306
  if (allDeps["@biomejs/biome"]) return "biome";
306
307
  }
@@ -316,16 +317,12 @@ function detectFormatter(packageJson, files) {
316
317
  }
317
318
  if (files.includes("biome.json") || files.includes("biome.jsonc")) return "biome";
318
319
  if (packageJson) {
319
- const allDeps = {
320
- ...packageJson.dependencies || {},
321
- ...packageJson.devDependencies || {}
322
- };
320
+ const allDeps = getAllDeps(packageJson);
323
321
  if (allDeps.prettier) return "prettier";
324
322
  if (allDeps["@biomejs/biome"]) return "biome";
325
323
  }
326
- if (files.includes("pyproject.toml")) {
327
- return "black";
328
- }
324
+ if (files.includes("ruff.toml") || files.includes(".ruff.toml")) return "ruff";
325
+ if (files.includes("pyproject.toml")) return "black";
329
326
  return null;
330
327
  }
331
328
  function detectBundler(packageJson, files) {
@@ -335,10 +332,7 @@ function detectBundler(packageJson, files) {
335
332
  if (files.some((f) => f.startsWith("rollup.config"))) return "rollup";
336
333
  if (files.some((f) => f.startsWith("esbuild"))) return "esbuild";
337
334
  if (packageJson) {
338
- const allDeps = {
339
- ...packageJson.dependencies || {},
340
- ...packageJson.devDependencies || {}
341
- };
335
+ const allDeps = getAllDeps(packageJson);
342
336
  if (allDeps.vite) return "vite";
343
337
  if (allDeps.webpack) return "webpack";
344
338
  if (allDeps.tsup) return "tsup";
@@ -350,16 +344,12 @@ function detectBundler(packageJson, files) {
350
344
  }
351
345
  return null;
352
346
  }
353
- function detectMonorepo(rootDir, files, packageJson) {
347
+ function detectMonorepo(files, packageJson) {
354
348
  if (files.includes("pnpm-workspace.yaml")) return true;
355
349
  if (files.includes("lerna.json")) return true;
356
350
  if (files.includes("nx.json")) return true;
357
351
  if (files.includes("turbo.json")) return true;
358
352
  if (packageJson?.workspaces) return true;
359
- const packagesDir = path.join(rootDir, "packages");
360
- const appsDir = path.join(rootDir, "apps");
361
- if (fs.existsSync(packagesDir) && fs.statSync(packagesDir).isDirectory()) return true;
362
- if (fs.existsSync(appsDir) && fs.statSync(appsDir).isDirectory()) return true;
363
353
  return false;
364
354
  }
365
355
  function detectCICD(rootDir, files) {
@@ -435,7 +425,7 @@ function listSourceFilesShallow(rootDir, extensions) {
435
425
  scan(rootDir, 0);
436
426
  return files;
437
427
  }
438
- function countSourceFiles(rootDir, _languages) {
428
+ function countSourceFiles(rootDir) {
439
429
  const extensions = [
440
430
  // JavaScript/TypeScript
441
431
  ".js",
@@ -512,96 +502,13 @@ function countSourceFiles(rootDir, _languages) {
512
502
  return count;
513
503
  }
514
504
 
515
- // src/generator.ts
516
- import fs2 from "fs";
517
- import path2 from "path";
518
- function ensureDirectories(rootDir) {
519
- const dirs = [".claude", ".claude/skills", ".claude/agents", ".claude/rules", ".claude/commands"];
520
- for (const dir of dirs) {
521
- fs2.mkdirSync(path2.join(rootDir, dir), { recursive: true });
522
- }
523
- }
524
- function generateSettings(stack) {
525
- const permissions = ["Read(**)", "Edit(**)", "Write(.claude/**)", "Bash(git:*)"];
526
- const pkgManagers = ["npm", "yarn", "pnpm", "bun", "npx"];
527
- for (const pm of pkgManagers) {
528
- permissions.push(`Bash(${pm}:*)`);
529
- }
530
- if (stack.languages.includes("typescript") || stack.languages.includes("javascript")) {
531
- permissions.push("Bash(node:*)", "Bash(tsc:*)");
532
- }
533
- if (stack.languages.includes("python")) {
534
- permissions.push(
535
- "Bash(python:*)",
536
- "Bash(pip:*)",
537
- "Bash(poetry:*)",
538
- "Bash(pytest:*)",
539
- "Bash(uvicorn:*)"
540
- );
541
- }
542
- if (stack.languages.includes("go")) {
543
- permissions.push("Bash(go:*)");
544
- }
545
- if (stack.languages.includes("rust")) {
546
- permissions.push("Bash(cargo:*)", "Bash(rustc:*)");
547
- }
548
- if (stack.languages.includes("ruby")) {
549
- permissions.push("Bash(ruby:*)", "Bash(bundle:*)", "Bash(rails:*)", "Bash(rake:*)");
550
- }
551
- if (stack.testingFramework) {
552
- const testCommands = {
553
- jest: ["jest:*"],
554
- vitest: ["vitest:*"],
555
- playwright: ["playwright:*"],
556
- cypress: ["cypress:*"],
557
- pytest: ["pytest:*"],
558
- rspec: ["rspec:*"]
559
- };
560
- const cmds = testCommands[stack.testingFramework];
561
- if (cmds) {
562
- permissions.push(...cmds.map((c) => `Bash(${c})`));
563
- }
564
- }
565
- if (stack.linter) {
566
- permissions.push(`Bash(${stack.linter}:*)`);
567
- }
568
- if (stack.formatter) {
569
- permissions.push(`Bash(${stack.formatter}:*)`);
570
- }
571
- permissions.push(
572
- "Bash(ls:*)",
573
- "Bash(mkdir:*)",
574
- "Bash(cat:*)",
575
- "Bash(echo:*)",
576
- "Bash(grep:*)",
577
- "Bash(find:*)"
578
- );
579
- if (stack.hasDocker) {
580
- permissions.push("Bash(docker:*)", "Bash(docker-compose:*)");
581
- }
582
- const settings = {
583
- $schema: "https://json.schemastore.org/claude-code-settings.json",
584
- permissions: {
585
- allow: [...new Set(permissions)]
586
- // Deduplicate
587
- }
588
- };
589
- return {
590
- path: ".claude/settings.json",
591
- content: JSON.stringify(settings, null, 2)
592
- };
593
- }
594
- function writeSettings(rootDir, stack) {
595
- const { path: settingsPath, content } = generateSettings(stack);
596
- const fullPath = path2.join(rootDir, settingsPath);
597
- const dir = path2.dirname(fullPath);
598
- fs2.mkdirSync(dir, { recursive: true });
599
- fs2.writeFileSync(fullPath, content);
600
- }
505
+ // src/extras.ts
506
+ import pc from "picocolors";
507
+ import prompts from "prompts";
601
508
 
602
509
  // src/hooks.ts
603
- import fs3 from "fs";
604
- import path3 from "path";
510
+ import fs2 from "fs";
511
+ import path2 from "path";
605
512
  var HOOK_SCRIPT = String.raw`#!/usr/bin/env node
606
513
  /**
607
514
  * Block Dangerous Commands - PreToolUse Hook for Bash
@@ -804,38 +711,394 @@ if (require.main === module) {
804
711
  module.exports = { PATTERNS, LEVELS, SAFETY_LEVEL, checkCommand };
805
712
  }
806
713
  `;
714
+ function checkHookStatus(rootDir) {
715
+ const homeDir = process.env.HOME || "";
716
+ const projectScriptPath = path2.join(rootDir, ".claude", "hooks", "block-dangerous-commands.js");
717
+ const globalScriptPath = path2.join(homeDir, ".claude", "hooks", "block-dangerous-commands.js");
718
+ const result = {
719
+ projectInstalled: false,
720
+ globalInstalled: false,
721
+ projectMatchesOurs: false,
722
+ globalMatchesOurs: false
723
+ };
724
+ if (fs2.existsSync(projectScriptPath)) {
725
+ result.projectInstalled = true;
726
+ const content = fs2.readFileSync(projectScriptPath, "utf-8");
727
+ result.projectMatchesOurs = content.trim() === HOOK_SCRIPT.trim();
728
+ }
729
+ if (fs2.existsSync(globalScriptPath)) {
730
+ result.globalInstalled = true;
731
+ const content = fs2.readFileSync(globalScriptPath, "utf-8");
732
+ result.globalMatchesOurs = content.trim() === HOOK_SCRIPT.trim();
733
+ }
734
+ return result;
735
+ }
807
736
  function installHook(rootDir) {
808
- const hooksDir = path3.join(rootDir, ".claude", "hooks");
809
- const hookPath = path3.join(hooksDir, "block-dangerous-commands.js");
810
- const settingsPath = path3.join(rootDir, ".claude", "settings.json");
811
- fs3.mkdirSync(hooksDir, { recursive: true });
812
- fs3.writeFileSync(hookPath, HOOK_SCRIPT);
813
- fs3.chmodSync(hookPath, 493);
737
+ const hooksDir = path2.join(rootDir, ".claude", "hooks");
738
+ const hookPath = path2.join(hooksDir, "block-dangerous-commands.js");
739
+ const settingsPath = path2.join(rootDir, ".claude", "settings.json");
740
+ fs2.mkdirSync(hooksDir, { recursive: true });
741
+ fs2.writeFileSync(hookPath, HOOK_SCRIPT);
742
+ fs2.chmodSync(hookPath, 493);
814
743
  try {
815
- const existing = fs3.existsSync(settingsPath) ? JSON.parse(fs3.readFileSync(settingsPath, "utf-8")) : {};
816
- existing.hooks = {
817
- ...existing.hooks,
818
- PreToolUse: [
744
+ const existing = fs2.existsSync(settingsPath) ? JSON.parse(fs2.readFileSync(settingsPath, "utf-8")) : {};
745
+ const newEntry = {
746
+ matcher: "Bash",
747
+ hooks: [
819
748
  {
820
- matcher: "Bash",
821
- hooks: [
822
- {
823
- type: "command",
824
- command: "node .claude/hooks/block-dangerous-commands.js"
825
- }
826
- ]
749
+ type: "command",
750
+ command: "node .claude/hooks/block-dangerous-commands.js"
827
751
  }
828
752
  ]
829
753
  };
830
- fs3.writeFileSync(settingsPath, JSON.stringify(existing, null, 2));
754
+ const existingPreToolUse = Array.isArray(existing.hooks?.PreToolUse) ? existing.hooks.PreToolUse : [];
755
+ const alreadyInstalled = existingPreToolUse.some(
756
+ (e) => Array.isArray(e.hooks) && e.hooks.some((h) => h.command?.includes("block-dangerous-commands.js"))
757
+ );
758
+ existing.hooks = {
759
+ ...existing.hooks,
760
+ PreToolUse: alreadyInstalled ? existingPreToolUse : [...existingPreToolUse, newEntry]
761
+ };
762
+ fs2.writeFileSync(settingsPath, JSON.stringify(existing, null, 2));
763
+ } catch (err) {
764
+ const msg = err instanceof Error ? err.message : "unknown error";
765
+ console.error(` Warning: could not patch settings.json (${msg}) \u2014 add hook config manually`);
766
+ }
767
+ }
768
+ function installHookGlobal() {
769
+ const homeDir = process.env.HOME || "";
770
+ const hooksDir = path2.join(homeDir, ".claude", "hooks");
771
+ const hookPath = path2.join(hooksDir, "block-dangerous-commands.js");
772
+ const settingsPath = path2.join(homeDir, ".claude", "settings.json");
773
+ fs2.mkdirSync(hooksDir, { recursive: true });
774
+ fs2.writeFileSync(hookPath, HOOK_SCRIPT);
775
+ fs2.chmodSync(hookPath, 493);
776
+ try {
777
+ const existing = fs2.existsSync(settingsPath) ? JSON.parse(fs2.readFileSync(settingsPath, "utf-8")) : {};
778
+ const newEntry = {
779
+ matcher: "Bash",
780
+ hooks: [{ type: "command", command: "node ~/.claude/hooks/block-dangerous-commands.js" }]
781
+ };
782
+ const existingPreToolUse = Array.isArray(existing.hooks?.PreToolUse) ? existing.hooks.PreToolUse : [];
783
+ const alreadyInstalled = existingPreToolUse.some(
784
+ (e) => Array.isArray(e.hooks) && e.hooks.some((h) => h.command?.includes("block-dangerous-commands.js"))
785
+ );
786
+ existing.hooks = {
787
+ ...existing.hooks,
788
+ PreToolUse: alreadyInstalled ? existingPreToolUse : [...existingPreToolUse, newEntry]
789
+ };
790
+ fs2.writeFileSync(settingsPath, JSON.stringify(existing, null, 2));
791
+ } catch (err) {
792
+ const msg = err instanceof Error ? err.message : "unknown error";
793
+ console.error(` Warning: could not patch settings.json (${msg}) \u2014 add hook config manually`);
794
+ }
795
+ }
796
+ var STATUSLINE_SCRIPT = [
797
+ "#!/usr/bin/env bash",
798
+ "# Claude Code statusline \u2014 portable, no runtime dependency beyond jq",
799
+ "",
800
+ "set -euo pipefail",
801
+ "",
802
+ "# Colors (using $'...' so escapes resolve at assignment, not at output time)",
803
+ "RST=$'\\033[0m'",
804
+ "CYAN=$'\\033[36m'",
805
+ "MAGENTA=$'\\033[35m'",
806
+ "BLUE=$'\\033[34m'",
807
+ "GREEN=$'\\033[32m'",
808
+ "YELLOW=$'\\033[33m'",
809
+ "RED=$'\\033[31m'",
810
+ "",
811
+ "# Read JSON from stdin (Claude Code pipes session data)",
812
+ 'INPUT="$(cat)"',
813
+ "",
814
+ "# Parse fields with jq",
815
+ `CWD="$(echo "$INPUT" | jq -r '.workspace.current_dir // .cwd // ""')"`,
816
+ 'PROJECT="$(basename "$CWD")"',
817
+ `SESSION_ID="$(echo "$INPUT" | jq -r '.session_id // empty')"`,
818
+ `SESSION_NAME="$(echo "$INPUT" | jq -r '.session_name // empty')"`,
819
+ `REMAINING="$(echo "$INPUT" | jq -r '.context_window.remaining_percentage // empty')"`,
820
+ `MODEL="$(echo "$INPUT" | jq -r '.model.display_name // empty')"`,
821
+ "",
822
+ "# Line 1: [user] project [on branch]",
823
+ 'LINE1=""',
824
+ 'if [[ -n "${SSH_CONNECTION:-}" ]]; then',
825
+ ' LINE1+="${BLUE}$(whoami)${RST} "',
826
+ "fi",
827
+ 'LINE1+="${CYAN}${PROJECT}${RST}"',
828
+ "",
829
+ 'BRANCH="$(git branch --show-current 2>/dev/null || git rev-parse --short HEAD 2>/dev/null || true)"',
830
+ 'if [[ -n "$BRANCH" ]]; then',
831
+ ' LINE1+=" on ${MAGENTA}\u{1F331} ${BRANCH}${RST}"',
832
+ "fi",
833
+ "",
834
+ "# Line 2: session + context + model",
835
+ 'PARTS=""',
836
+ 'if [[ -n "$SESSION_ID" ]]; then',
837
+ ' if [[ -n "$SESSION_NAME" ]]; then',
838
+ ' PARTS+="${MAGENTA}${SESSION_NAME} \xB7 sid: ${SESSION_ID}${RST}"',
839
+ " else",
840
+ ' PARTS+="${MAGENTA}sid: ${SESSION_ID}${RST}"',
841
+ " fi",
842
+ "fi",
843
+ "",
844
+ 'if [[ -n "$REMAINING" ]]; then',
845
+ ' RND="${REMAINING%%.*}"',
846
+ " if (( RND < 20 )); then",
847
+ ' CTX_COLOR="$RED"',
848
+ " elif (( RND < 50 )); then",
849
+ ' CTX_COLOR="$YELLOW"',
850
+ " else",
851
+ ' CTX_COLOR="$GREEN"',
852
+ " fi",
853
+ ' [[ -n "$PARTS" ]] && PARTS+=" "',
854
+ ' PARTS+="${CTX_COLOR}[ctx: ${RND}%]${RST}"',
855
+ "fi",
856
+ "",
857
+ 'if [[ -n "$MODEL" ]]; then',
858
+ ' [[ -n "$PARTS" ]] && PARTS+=" "',
859
+ ' PARTS+="[${CYAN}${MODEL}${RST}]"',
860
+ "fi",
861
+ "",
862
+ 'echo "$LINE1"',
863
+ 'echo "$PARTS"'
864
+ ].join("\n");
865
+ function checkStatuslineStatus(rootDir) {
866
+ const homeDir = process.env.HOME || "";
867
+ const projectScriptPath = path2.join(rootDir, ".claude", "config", "statusline-command.sh");
868
+ const globalScriptPath = path2.join(homeDir, ".claude", "config", "statusline-command.sh");
869
+ const projectSettingsPath = path2.join(rootDir, ".claude", "settings.json");
870
+ const globalSettingsPath = path2.join(homeDir, ".claude", "settings.json");
871
+ const result = {
872
+ projectInstalled: false,
873
+ globalInstalled: false,
874
+ projectMatchesOurs: false,
875
+ globalMatchesOurs: false
876
+ };
877
+ try {
878
+ if (fs2.existsSync(projectSettingsPath)) {
879
+ const settings = JSON.parse(fs2.readFileSync(projectSettingsPath, "utf-8"));
880
+ if (settings.statusLine?.command) {
881
+ result.projectInstalled = true;
882
+ if (fs2.existsSync(projectScriptPath)) {
883
+ const content = fs2.readFileSync(projectScriptPath, "utf-8");
884
+ result.projectMatchesOurs = content.trim() === STATUSLINE_SCRIPT.trim();
885
+ }
886
+ }
887
+ }
888
+ } catch {
889
+ }
890
+ try {
891
+ if (fs2.existsSync(globalSettingsPath)) {
892
+ const settings = JSON.parse(fs2.readFileSync(globalSettingsPath, "utf-8"));
893
+ if (settings.statusLine?.command) {
894
+ result.globalInstalled = true;
895
+ if (fs2.existsSync(globalScriptPath)) {
896
+ const content = fs2.readFileSync(globalScriptPath, "utf-8");
897
+ result.globalMatchesOurs = content.trim() === STATUSLINE_SCRIPT.trim();
898
+ }
899
+ }
900
+ }
831
901
  } catch {
832
902
  }
903
+ return result;
904
+ }
905
+ function installStatusline(rootDir) {
906
+ const configDir = path2.join(rootDir, ".claude", "config");
907
+ const scriptPath = path2.join(configDir, "statusline-command.sh");
908
+ const settingsPath = path2.join(rootDir, ".claude", "settings.json");
909
+ fs2.mkdirSync(configDir, { recursive: true });
910
+ fs2.writeFileSync(scriptPath, STATUSLINE_SCRIPT);
911
+ fs2.chmodSync(scriptPath, 493);
912
+ patchSettings(settingsPath, {
913
+ statusLine: { type: "command", command: "bash .claude/config/statusline-command.sh" }
914
+ });
915
+ }
916
+ function installStatuslineGlobal() {
917
+ const homeDir = process.env.HOME || "";
918
+ const configDir = path2.join(homeDir, ".claude", "config");
919
+ const scriptPath = path2.join(configDir, "statusline-command.sh");
920
+ const settingsPath = path2.join(homeDir, ".claude", "settings.json");
921
+ fs2.mkdirSync(configDir, { recursive: true });
922
+ fs2.writeFileSync(scriptPath, STATUSLINE_SCRIPT);
923
+ fs2.chmodSync(scriptPath, 493);
924
+ patchSettings(settingsPath, {
925
+ statusLine: { type: "command", command: "bash ~/.claude/config/statusline-command.sh" }
926
+ });
927
+ }
928
+ function patchSettings(settingsPath, patch) {
929
+ try {
930
+ const existing = fs2.existsSync(settingsPath) ? JSON.parse(fs2.readFileSync(settingsPath, "utf-8")) : {};
931
+ Object.assign(existing, patch);
932
+ fs2.writeFileSync(settingsPath, JSON.stringify(existing, null, 2));
933
+ } catch (err) {
934
+ const msg = err instanceof Error ? err.message : "unknown error";
935
+ console.error(
936
+ ` Warning: could not patch settings.json (${msg}) \u2014 add statusLine config manually`
937
+ );
938
+ }
939
+ }
940
+
941
+ // src/extras.ts
942
+ var EXTRAS = [
943
+ {
944
+ id: "safety-hook",
945
+ name: "Safety hook",
946
+ description: "Block dangerous commands (git push, rm -rf, etc.)",
947
+ checkStatus: checkHookStatus,
948
+ installProject: installHook,
949
+ installGlobal: installHookGlobal,
950
+ projectPath: ".claude/hooks/block-dangerous-commands.js",
951
+ globalPath: "~/.claude/hooks/block-dangerous-commands.js"
952
+ },
953
+ {
954
+ id: "statusline",
955
+ name: "Custom statusline",
956
+ description: "Shows project, branch, context, model",
957
+ checkStatus: checkStatuslineStatus,
958
+ installProject: installStatusline,
959
+ installGlobal: installStatuslineGlobal,
960
+ projectPath: ".claude/config/statusline-command.sh",
961
+ globalPath: "~/.claude/config/statusline-command.sh"
962
+ }
963
+ ];
964
+ async function promptExtras(projectDir) {
965
+ for (const extra of EXTRAS) {
966
+ const status = extra.checkStatus(projectDir);
967
+ if (status.projectMatchesOurs || status.globalMatchesOurs) {
968
+ continue;
969
+ }
970
+ if (status.projectInstalled || status.globalInstalled) {
971
+ const where = status.globalInstalled ? "globally" : "in this project";
972
+ const { action } = await prompts({
973
+ type: "select",
974
+ name: "action",
975
+ message: `A different ${extra.name.toLowerCase()} is already configured ${where}. Replace it?`,
976
+ choices: [
977
+ { title: "Install for this project only", value: "project" },
978
+ { title: "Install globally (all projects)", value: "global" },
979
+ { title: "Skip \u2014 keep existing", value: "skip" }
980
+ ],
981
+ initial: 2
982
+ });
983
+ applyAction(action, extra, projectDir);
984
+ } else {
985
+ const { action } = await prompts({
986
+ type: "select",
987
+ name: "action",
988
+ message: `Add ${extra.name.toLowerCase()}? (${extra.description})`,
989
+ choices: [
990
+ { title: "Install for this project only", value: "project" },
991
+ { title: "Install globally (all projects)", value: "global" },
992
+ { title: "Skip", value: "skip" }
993
+ ],
994
+ initial: 0
995
+ });
996
+ applyAction(action, extra, projectDir);
997
+ }
998
+ }
999
+ }
1000
+ function applyAction(action, extra, projectDir) {
1001
+ if (action === "project") {
1002
+ extra.installProject(projectDir);
1003
+ console.log(pc.green(` + ${extra.projectPath}`));
1004
+ } else if (action === "global") {
1005
+ extra.installGlobal();
1006
+ console.log(pc.green(` + ${extra.globalPath} (global)`));
1007
+ }
1008
+ }
1009
+
1010
+ // src/generator.ts
1011
+ import fs3 from "fs";
1012
+ import path3 from "path";
1013
+ function ensureDirectories(rootDir) {
1014
+ const dirs = [".claude", ".claude/skills", ".claude/agents", ".claude/rules", ".claude/commands"];
1015
+ for (const dir of dirs) {
1016
+ fs3.mkdirSync(path3.join(rootDir, dir), { recursive: true });
1017
+ }
1018
+ }
1019
+ function generateSettings(stack) {
1020
+ const permissions = ["Read(**)", "Edit(**)", "Write(.claude/**)", "Bash(git:*)"];
1021
+ const pkgManagers = ["npm", "yarn", "pnpm", "bun", "npx"];
1022
+ for (const pm of pkgManagers) {
1023
+ permissions.push(`Bash(${pm}:*)`);
1024
+ }
1025
+ if (stack.languages.includes("typescript") || stack.languages.includes("javascript")) {
1026
+ permissions.push("Bash(node:*)", "Bash(tsc:*)");
1027
+ }
1028
+ if (stack.languages.includes("python")) {
1029
+ permissions.push(
1030
+ "Bash(python:*)",
1031
+ "Bash(pip:*)",
1032
+ "Bash(poetry:*)",
1033
+ "Bash(pytest:*)",
1034
+ "Bash(uvicorn:*)"
1035
+ );
1036
+ }
1037
+ if (stack.languages.includes("go")) {
1038
+ permissions.push("Bash(go:*)");
1039
+ }
1040
+ if (stack.languages.includes("rust")) {
1041
+ permissions.push("Bash(cargo:*)", "Bash(rustc:*)");
1042
+ }
1043
+ if (stack.languages.includes("ruby")) {
1044
+ permissions.push("Bash(ruby:*)", "Bash(bundle:*)", "Bash(rails:*)", "Bash(rake:*)");
1045
+ }
1046
+ if (stack.testingFramework) {
1047
+ const testCommands = {
1048
+ jest: ["jest:*"],
1049
+ vitest: ["vitest:*"],
1050
+ playwright: ["playwright:*"],
1051
+ cypress: ["cypress:*"],
1052
+ pytest: ["pytest:*"],
1053
+ rspec: ["rspec:*"]
1054
+ };
1055
+ const cmds = testCommands[stack.testingFramework];
1056
+ if (cmds) {
1057
+ permissions.push(...cmds.map((c) => `Bash(${c})`));
1058
+ }
1059
+ }
1060
+ if (stack.linter) {
1061
+ permissions.push(`Bash(${stack.linter}:*)`);
1062
+ }
1063
+ if (stack.formatter) {
1064
+ permissions.push(`Bash(${stack.formatter}:*)`);
1065
+ }
1066
+ permissions.push(
1067
+ "Bash(ls:*)",
1068
+ "Bash(mkdir:*)",
1069
+ "Bash(cat:*)",
1070
+ "Bash(echo:*)",
1071
+ "Bash(grep:*)",
1072
+ "Bash(find:*)"
1073
+ );
1074
+ if (stack.hasDocker) {
1075
+ permissions.push("Bash(docker:*)", "Bash(docker-compose:*)");
1076
+ }
1077
+ const settings = {
1078
+ $schema: "https://json.schemastore.org/claude-code-settings.json",
1079
+ permissions: {
1080
+ allow: [...new Set(permissions)]
1081
+ // Deduplicate
1082
+ }
1083
+ };
1084
+ return {
1085
+ path: ".claude/settings.json",
1086
+ content: JSON.stringify(settings, null, 2)
1087
+ };
1088
+ }
1089
+ function writeSettings(rootDir, stack) {
1090
+ const { path: settingsPath, content } = generateSettings(stack);
1091
+ const fullPath = path3.join(rootDir, settingsPath);
1092
+ const dir = path3.dirname(fullPath);
1093
+ fs3.mkdirSync(dir, { recursive: true });
1094
+ fs3.writeFileSync(fullPath, content);
833
1095
  }
834
1096
 
835
1097
  // src/prompt.ts
836
- function getAnalysisPrompt(projectInfo) {
1098
+ function getAnalysisPrompt(projectInfo, options = { claudeMdMode: "replace", existingClaudeMd: null }) {
837
1099
  const context = buildContextSection(projectInfo);
838
1100
  const templateVars = buildTemplateVariables(projectInfo);
1101
+ const claudeMdInstructions = buildClaudeMdInstructions(options);
839
1102
  return `${ANALYSIS_PROMPT}
840
1103
 
841
1104
  ${SKILLS_PROMPT}
@@ -859,15 +1122,17 @@ ${context}
859
1122
 
860
1123
  ${templateVars}
861
1124
 
1125
+ ${claudeMdInstructions}
1126
+
862
1127
  ---
863
1128
 
864
1129
  ## Execute Now
865
1130
 
866
1131
  1. Read this entire prompt to understand all phases
867
1132
  2. Execute Phase 1 completely - read files, analyze code, gather all data
868
- 3. Execute Phase 2 - generate the CLAUDE.md (max 120 lines) using only discovered information
1133
+ ${options.claudeMdMode === "keep" ? `3. Skip CLAUDE.md generation \u2014 the existing file is being kept as-is` : options.claudeMdMode === "improve" ? `3. Execute Phase 2 \u2014 IMPROVE the existing CLAUDE.md (see Improvement Mode instructions above)` : `3. Execute Phase 2 - generate the CLAUDE.md (max 120 lines) using only discovered information`}
869
1134
  4. Execute Phase 3 - verify quality before writing
870
- 5. Use the Write tool to create \`.claude/CLAUDE.md\` with the final content
1135
+ ${options.claudeMdMode === "keep" ? `5. Skip writing CLAUDE.md \u2014 it is being preserved` : `5. Use the Write tool to create \`.claude/CLAUDE.md\` with the final content`}
871
1136
  6. Execute Phase 4 - generate ALL skill files (4 core + framework-specific if detected)
872
1137
  7. Execute Phase 5 - generate agent files
873
1138
  8. Execute Phase 6 - generate rule files
@@ -878,6 +1143,41 @@ ${templateVars}
878
1143
  Do NOT output file contents to stdout. Write all files to disk using the Write tool.
879
1144
  Generate ALL files in a single pass \u2014 do not stop after CLAUDE.md.`;
880
1145
  }
1146
+ function buildClaudeMdInstructions(options) {
1147
+ if (options.claudeMdMode === "keep") {
1148
+ return `---
1149
+
1150
+ ## CLAUDE.md Mode: KEEP
1151
+
1152
+ The user chose to keep their existing CLAUDE.md unchanged.
1153
+ **Do NOT read, modify, or overwrite \`.claude/CLAUDE.md\`.**
1154
+ Generate all other files (skills, agents, rules, commands) normally.
1155
+ Use the existing CLAUDE.md as the source of truth for cross-references.`;
1156
+ }
1157
+ if (options.claudeMdMode === "improve" && options.existingClaudeMd) {
1158
+ return `---
1159
+
1160
+ ## CLAUDE.md Mode: IMPROVE
1161
+
1162
+ The user has an existing CLAUDE.md and wants it improved, not replaced.
1163
+ Here is the current content:
1164
+
1165
+ \`\`\`markdown
1166
+ ${options.existingClaudeMd}
1167
+ \`\`\`
1168
+
1169
+ ### Improvement Rules
1170
+
1171
+ 1. **Preserve all manually-added content** \u2014 sections, notes, and custom rules the user wrote
1172
+ 2. **Enhance with discovered information** \u2014 fill gaps, add missing sections, improve specificity
1173
+ 3. **Fix generic content** \u2014 replace boilerplate with project-specific details found during Phase 1
1174
+ 4. **Update stale references** \u2014 fix file paths, commands, or patterns that no longer match the codebase
1175
+ 5. **Respect the 120-line cap** \u2014 if the file is already near the limit, prioritize density over additions
1176
+ 6. **Keep the user's structure** \u2014 if they organized sections differently from the template, keep their layout
1177
+ 7. **Do NOT remove content you don't understand** \u2014 if a section seems custom or domain-specific, preserve it`;
1178
+ }
1179
+ return "";
1180
+ }
881
1181
  function buildContextSection(projectInfo) {
882
1182
  const { name, description, techStack, fileCount } = projectInfo;
883
1183
  const lines = [];
@@ -1633,14 +1933,17 @@ function validateArtifacts(rootDir) {
1633
1933
  const files = walkMdFiles(claudeDir).filter((f) => !f.endsWith("CLAUDE.md"));
1634
1934
  for (const filePath of files) {
1635
1935
  result.filesChecked++;
1636
- const changes = processFile(filePath, commands, fingerprints);
1637
- if (changes.length > 0) {
1638
- result.filesModified++;
1639
- result.duplicationsRemoved += changes.length;
1640
- for (const change of changes) {
1641
- change.file = path4.relative(rootDir, filePath);
1936
+ try {
1937
+ const changes = processFile(filePath, commands, fingerprints);
1938
+ if (changes.length > 0) {
1939
+ result.filesModified++;
1940
+ result.duplicationsRemoved += changes.length;
1941
+ for (const change of changes) {
1942
+ change.file = path4.relative(rootDir, filePath);
1943
+ }
1944
+ result.changes.push(...changes);
1642
1945
  }
1643
- result.changes.push(...changes);
1946
+ } catch {
1644
1947
  }
1645
1948
  }
1646
1949
  } catch {
@@ -1668,21 +1971,21 @@ function getVersion() {
1668
1971
  }
1669
1972
  function showHelp() {
1670
1973
  console.log(`
1671
- ${pc.cyan("Claude Code Starter")} v${VERSION}
1974
+ ${pc2.cyan("Claude Code Starter")} v${VERSION}
1672
1975
 
1673
1976
  Bootstrap intelligent Claude Code configurations for any repository.
1674
1977
 
1675
- ${pc.bold("USAGE")}
1978
+ ${pc2.bold("USAGE")}
1676
1979
  npx claude-code-starter [OPTIONS]
1677
1980
 
1678
- ${pc.bold("OPTIONS")}
1981
+ ${pc2.bold("OPTIONS")}
1679
1982
  -h, --help Show this help message
1680
1983
  -v, --version Show version number
1681
1984
  -f, --force Force overwrite existing .claude files
1682
1985
  -y, --no-interactive Skip interactive prompts (use defaults)
1683
1986
  -V, --verbose Show detailed output
1684
1987
 
1685
- ${pc.bold("WHAT IT DOES")}
1988
+ ${pc2.bold("WHAT IT DOES")}
1686
1989
  1. Analyzes your repository's tech stack
1687
1990
  2. Launches Claude CLI to deeply analyze your codebase
1688
1991
  3. Generates all .claude/ configuration files:
@@ -1692,53 +1995,53 @@ ${pc.bold("WHAT IT DOES")}
1692
1995
  - Rules matching your code style
1693
1996
  - Commands for analysis and code review
1694
1997
 
1695
- ${pc.bold("REQUIREMENTS")}
1998
+ ${pc2.bold("REQUIREMENTS")}
1696
1999
  Claude CLI must be installed: https://claude.ai/download
1697
2000
 
1698
- ${pc.bold("MORE INFO")}
2001
+ ${pc2.bold("MORE INFO")}
1699
2002
  https://github.com/cassmtnr/claude-code-starter
1700
2003
  `);
1701
2004
  }
1702
2005
  function showBanner() {
1703
2006
  console.log();
1704
- console.log(pc.bold("Claude Code Starter") + pc.gray(` v${VERSION}`));
1705
- console.log(pc.gray("Intelligent AI-Assisted Development Setup"));
2007
+ console.log(pc2.bold("Claude Code Starter") + pc2.gray(` v${VERSION}`));
2008
+ console.log(pc2.gray("Intelligent AI-Assisted Development Setup"));
1706
2009
  console.log();
1707
2010
  }
1708
2011
  function showTechStack(projectInfo, verbose) {
1709
2012
  const { techStack } = projectInfo;
1710
- console.log(pc.bold("Tech Stack"));
2013
+ console.log(pc2.bold("Tech Stack"));
1711
2014
  console.log();
1712
2015
  if (techStack.primaryLanguage) {
1713
- console.log(` ${pc.bold("Language:")} ${formatLanguage(techStack.primaryLanguage)}`);
2016
+ console.log(` ${pc2.bold("Language:")} ${formatLanguage(techStack.primaryLanguage)}`);
1714
2017
  }
1715
2018
  if (techStack.primaryFramework) {
1716
- console.log(` ${pc.bold("Framework:")} ${formatFramework(techStack.primaryFramework)}`);
2019
+ console.log(` ${pc2.bold("Framework:")} ${formatFramework(techStack.primaryFramework)}`);
1717
2020
  }
1718
2021
  if (techStack.packageManager) {
1719
- console.log(` ${pc.bold("Package Manager:")} ${techStack.packageManager}`);
2022
+ console.log(` ${pc2.bold("Package Manager:")} ${techStack.packageManager}`);
1720
2023
  }
1721
2024
  if (techStack.testingFramework) {
1722
- console.log(` ${pc.bold("Testing:")} ${techStack.testingFramework}`);
2025
+ console.log(` ${pc2.bold("Testing:")} ${techStack.testingFramework}`);
1723
2026
  }
1724
2027
  if (verbose) {
1725
2028
  if (techStack.linter) {
1726
- console.log(` ${pc.bold("Linter:")} ${techStack.linter}`);
2029
+ console.log(` ${pc2.bold("Linter:")} ${techStack.linter}`);
1727
2030
  }
1728
2031
  if (techStack.formatter) {
1729
- console.log(` ${pc.bold("Formatter:")} ${techStack.formatter}`);
2032
+ console.log(` ${pc2.bold("Formatter:")} ${techStack.formatter}`);
1730
2033
  }
1731
2034
  if (techStack.bundler) {
1732
- console.log(` ${pc.bold("Bundler:")} ${techStack.bundler}`);
2035
+ console.log(` ${pc2.bold("Bundler:")} ${techStack.bundler}`);
1733
2036
  }
1734
2037
  if (techStack.isMonorepo) {
1735
- console.log(` ${pc.bold("Monorepo:")} yes`);
2038
+ console.log(` ${pc2.bold("Monorepo:")} yes`);
1736
2039
  }
1737
2040
  if (techStack.hasDocker) {
1738
- console.log(` ${pc.bold("Docker:")} yes`);
2041
+ console.log(` ${pc2.bold("Docker:")} yes`);
1739
2042
  }
1740
2043
  if (techStack.hasCICD) {
1741
- console.log(` ${pc.bold("CI/CD:")} ${techStack.cicdPlatform}`);
2044
+ console.log(` ${pc2.bold("CI/CD:")} ${techStack.cicdPlatform}`);
1742
2045
  }
1743
2046
  }
1744
2047
  console.log();
@@ -1814,9 +2117,9 @@ async function promptNewProject(args) {
1814
2117
  if (!args.interactive) {
1815
2118
  return null;
1816
2119
  }
1817
- console.log(pc.yellow("New project detected - let's set it up!"));
2120
+ console.log(pc2.yellow("New project detected - let's set it up!"));
1818
2121
  console.log();
1819
- const descResponse = await prompts({
2122
+ const descResponse = await prompts2({
1820
2123
  type: "text",
1821
2124
  name: "description",
1822
2125
  message: "What are you building?",
@@ -1825,7 +2128,7 @@ async function promptNewProject(args) {
1825
2128
  if (!descResponse.description) {
1826
2129
  return null;
1827
2130
  }
1828
- const langResponse = await prompts({
2131
+ const langResponse = await prompts2({
1829
2132
  type: "select",
1830
2133
  name: "primaryLanguage",
1831
2134
  message: "Primary language?",
@@ -1846,34 +2149,34 @@ async function promptNewProject(args) {
1846
2149
  });
1847
2150
  const lang = langResponse.primaryLanguage || "typescript";
1848
2151
  const fwChoices = frameworkChoices[lang] || defaultFrameworkChoices;
1849
- const fwResponse = await prompts({
2152
+ const fwResponse = await prompts2({
1850
2153
  type: "select",
1851
2154
  name: "framework",
1852
2155
  message: "Framework?",
1853
2156
  choices: fwChoices
1854
2157
  });
1855
2158
  const pmChoices = getPackageManagerChoices(lang);
1856
- const pmResponse = await prompts({
2159
+ const pmResponse = await prompts2({
1857
2160
  type: "select",
1858
2161
  name: "packageManager",
1859
2162
  message: "Package manager?",
1860
2163
  choices: pmChoices
1861
2164
  });
1862
2165
  const testChoices = getTestingFrameworkChoices(lang);
1863
- const testResponse = await prompts({
2166
+ const testResponse = await prompts2({
1864
2167
  type: "select",
1865
2168
  name: "testingFramework",
1866
2169
  message: "Testing framework?",
1867
2170
  choices: testChoices
1868
2171
  });
1869
2172
  const lintChoices = getLinterFormatterChoices(lang);
1870
- const lintResponse = await prompts({
2173
+ const lintResponse = await prompts2({
1871
2174
  type: "select",
1872
2175
  name: "linter",
1873
2176
  message: "Linter/Formatter?",
1874
2177
  choices: lintChoices
1875
2178
  });
1876
- const typeResponse = await prompts({
2179
+ const typeResponse = await prompts2({
1877
2180
  type: "select",
1878
2181
  name: "projectType",
1879
2182
  message: "Project type?",
@@ -1981,7 +2284,6 @@ function getLinterFormatterChoices(lang) {
1981
2284
  return [
1982
2285
  { title: "Biome", value: "biome" },
1983
2286
  { title: "ESLint + Prettier", value: "eslint" },
1984
- { title: "ESLint", value: "eslint" },
1985
2287
  { title: "None", value: null }
1986
2288
  ];
1987
2289
  }
@@ -2112,12 +2414,12 @@ function checkClaudeCli() {
2112
2414
  return false;
2113
2415
  }
2114
2416
  }
2115
- function runClaudeAnalysis(projectDir, projectInfo) {
2417
+ function runClaudeAnalysis(projectDir, projectInfo, options = { claudeMdMode: "replace", existingClaudeMd: null }) {
2116
2418
  return new Promise((resolve) => {
2117
- const prompt = getAnalysisPrompt(projectInfo);
2118
- console.log(pc.cyan("Launching Claude for deep project analysis..."));
2419
+ const prompt = getAnalysisPrompt(projectInfo, options);
2420
+ console.log(pc2.cyan("Launching Claude for deep project analysis..."));
2119
2421
  console.log(
2120
- pc.gray("Claude will read your codebase and generate all .claude/ configuration files")
2422
+ pc2.gray("Claude will read your codebase and generate all .claude/ configuration files")
2121
2423
  );
2122
2424
  console.log();
2123
2425
  const spinner = ora({
@@ -2132,6 +2434,8 @@ function runClaudeAnalysis(projectDir, projectInfo) {
2132
2434
  "claude",
2133
2435
  [
2134
2436
  "-p",
2437
+ "--verbose",
2438
+ "--output-format=stream-json",
2135
2439
  "--allowedTools",
2136
2440
  "Read",
2137
2441
  "--allowedTools",
@@ -2148,8 +2452,43 @@ function runClaudeAnalysis(projectDir, projectInfo) {
2148
2452
  stdio: ["pipe", "pipe", "pipe"]
2149
2453
  }
2150
2454
  );
2455
+ let stdoutBuffer = "";
2456
+ child.stdout.on("data", (chunk) => {
2457
+ stdoutBuffer += chunk.toString();
2458
+ const lines = stdoutBuffer.split("\n");
2459
+ stdoutBuffer = lines.pop() || "";
2460
+ for (const line of lines) {
2461
+ if (!line.trim()) continue;
2462
+ try {
2463
+ const event = JSON.parse(line);
2464
+ if (event.type === "assistant" && Array.isArray(event.message?.content)) {
2465
+ for (const block of event.message.content) {
2466
+ if (block.type === "tool_use" && block.name && block.input) {
2467
+ const toolName = block.name;
2468
+ const toolInput = block.input;
2469
+ const filePath = toolInput.file_path || toolInput.path || toolInput.pattern || "";
2470
+ const shortPath = filePath.split("/").slice(-2).join("/");
2471
+ const action = toolName === "Write" || toolName === "Edit" ? "Writing" : "Reading";
2472
+ if (shortPath) {
2473
+ spinner.text = `${action} ${shortPath}...`;
2474
+ } else {
2475
+ spinner.text = `Using ${toolName}...`;
2476
+ }
2477
+ }
2478
+ }
2479
+ }
2480
+ } catch {
2481
+ }
2482
+ }
2483
+ });
2484
+ child.stdin.on("error", () => {
2485
+ });
2151
2486
  child.stdin.write(prompt);
2152
2487
  child.stdin.end();
2488
+ let stderrOutput = "";
2489
+ child.stderr.on("data", (chunk) => {
2490
+ stderrOutput += chunk.toString();
2491
+ });
2153
2492
  child.on("error", (err) => {
2154
2493
  spinner.fail(`Failed to launch Claude CLI: ${err.message}`);
2155
2494
  resolve(false);
@@ -2160,6 +2499,9 @@ function runClaudeAnalysis(projectDir, projectInfo) {
2160
2499
  resolve(true);
2161
2500
  } else {
2162
2501
  spinner.fail(`Claude exited with code ${code}`);
2502
+ if (stderrOutput.trim()) {
2503
+ console.error(pc2.gray(stderrOutput.trim()));
2504
+ }
2163
2505
  resolve(false);
2164
2506
  }
2165
2507
  });
@@ -2195,7 +2537,7 @@ async function main() {
2195
2537
  }
2196
2538
  showBanner();
2197
2539
  const projectDir = process.cwd();
2198
- console.log(pc.gray("Analyzing repository..."));
2540
+ console.log(pc2.gray("Analyzing repository..."));
2199
2541
  console.log();
2200
2542
  const projectInfo = analyzeRepository(projectDir);
2201
2543
  showTechStack(projectInfo, args.verbose);
@@ -2223,62 +2565,77 @@ async function main() {
2223
2565
  projectInfo.description = preferences.description;
2224
2566
  }
2225
2567
  } else {
2226
- console.log(pc.gray(`Existing project with ${projectInfo.fileCount} source files`));
2568
+ console.log(pc2.gray(`Existing project with ${projectInfo.fileCount} source files`));
2227
2569
  console.log();
2228
2570
  }
2229
- if (projectInfo.techStack.hasClaudeConfig && !args.force) {
2230
- console.log(pc.yellow("Existing .claude/ configuration detected"));
2231
- console.log();
2232
- if (args.interactive) {
2233
- const { proceed } = await prompts({
2234
- type: "confirm",
2235
- name: "proceed",
2236
- message: "Update existing configuration?",
2237
- initial: true
2571
+ let claudeMdMode = "replace";
2572
+ let existingClaudeMd = null;
2573
+ const claudeMdPath = path5.join(projectDir, ".claude", "CLAUDE.md");
2574
+ if (fs5.existsSync(claudeMdPath)) {
2575
+ existingClaudeMd = fs5.readFileSync(claudeMdPath, "utf-8");
2576
+ if (args.force) {
2577
+ claudeMdMode = "replace";
2578
+ } else if (args.interactive) {
2579
+ console.log(pc2.yellow("Existing CLAUDE.md detected"));
2580
+ console.log();
2581
+ const { mode } = await prompts2({
2582
+ type: "select",
2583
+ name: "mode",
2584
+ message: "How should we handle the existing CLAUDE.md?",
2585
+ choices: [
2586
+ { title: "Improve \u2014 scan and enhance the existing file", value: "improve" },
2587
+ { title: "Replace \u2014 generate a new one from scratch", value: "replace" },
2588
+ { title: "Keep \u2014 leave CLAUDE.md as-is, regenerate other files", value: "keep" }
2589
+ ],
2590
+ initial: 0
2238
2591
  });
2239
- if (!proceed) {
2240
- console.log(pc.gray("Cancelled. Use --force to overwrite."));
2592
+ if (mode === void 0) {
2593
+ console.log(pc2.gray("Cancelled."));
2241
2594
  process.exit(0);
2242
2595
  }
2596
+ claudeMdMode = mode;
2243
2597
  }
2244
2598
  console.log();
2245
2599
  }
2246
2600
  if (!checkClaudeCli()) {
2247
- console.error(pc.red("Claude CLI is required but not found."));
2248
- console.error(pc.gray("Install it from: https://claude.ai/download"));
2601
+ console.error(pc2.red("Claude CLI is required but not found."));
2602
+ console.error(pc2.gray("Install it from: https://claude.ai/download"));
2249
2603
  process.exit(1);
2250
2604
  }
2251
- console.log(pc.gray("Setting up .claude/ directory structure..."));
2605
+ console.log(pc2.gray("Setting up .claude/ directory structure..."));
2252
2606
  console.log();
2253
2607
  writeSettings(projectDir, projectInfo.techStack);
2254
2608
  ensureDirectories(projectDir);
2255
- console.log(pc.green("Created:"));
2256
- console.log(pc.green(" + .claude/settings.json"));
2609
+ console.log(pc2.green("Created:"));
2610
+ console.log(pc2.green(" + .claude/settings.json"));
2257
2611
  console.log();
2258
- const success = await runClaudeAnalysis(projectDir, projectInfo);
2612
+ const success = await runClaudeAnalysis(projectDir, projectInfo, {
2613
+ claudeMdMode,
2614
+ existingClaudeMd: claudeMdMode === "improve" ? existingClaudeMd : null
2615
+ });
2259
2616
  if (!success) {
2260
- console.error(pc.red("Claude analysis failed. Please try again."));
2617
+ console.error(pc2.red("Claude analysis failed. Please try again."));
2261
2618
  process.exit(1);
2262
2619
  }
2263
2620
  const validation = validateArtifacts(projectDir);
2264
2621
  if (validation.duplicationsRemoved > 0) {
2265
2622
  console.log(
2266
- pc.gray(
2623
+ pc2.gray(
2267
2624
  ` Deduplication: removed ${validation.duplicationsRemoved} redundancies from ${validation.filesModified} files`
2268
2625
  )
2269
2626
  );
2270
2627
  }
2271
2628
  const generatedFiles = getGeneratedFiles(projectDir);
2272
2629
  console.log();
2273
- console.log(pc.green(`Done! (${generatedFiles.length} files)`));
2630
+ console.log(pc2.green(`Done! (${generatedFiles.length} files)`));
2274
2631
  console.log();
2275
- console.log(pc.bold("Generated for your stack:"));
2632
+ console.log(pc2.bold("Generated for your stack:"));
2276
2633
  const skills = generatedFiles.filter((f) => f.includes("/skills/"));
2277
2634
  const agents = generatedFiles.filter((f) => f.includes("/agents/"));
2278
2635
  const rules = generatedFiles.filter((f) => f.includes("/rules/"));
2279
2636
  const commands = generatedFiles.filter((f) => f.includes("/commands/"));
2280
2637
  if (generatedFiles.some((f) => f.endsWith("CLAUDE.md"))) {
2281
- console.log(pc.cyan(" CLAUDE.md (deep analysis by Claude)"));
2638
+ console.log(pc2.cyan(" CLAUDE.md (deep analysis by Claude)"));
2282
2639
  }
2283
2640
  if (skills.length > 0) {
2284
2641
  console.log(
@@ -2299,23 +2656,13 @@ async function main() {
2299
2656
  console.log();
2300
2657
  if (args.interactive) {
2301
2658
  console.log();
2302
- const { installSafetyHook } = await prompts({
2303
- type: "confirm",
2304
- name: "installSafetyHook",
2305
- message: "Add a safety hook to block dangerous commands? (git push, rm -rf, etc.)",
2306
- initial: true
2307
- });
2308
- if (installSafetyHook) {
2309
- installHook(projectDir);
2310
- console.log(pc.green(" + .claude/hooks/block-dangerous-commands.js"));
2311
- console.log(pc.gray(" Blocks destructive Bash commands before execution"));
2312
- }
2659
+ await promptExtras(projectDir);
2313
2660
  }
2314
2661
  console.log();
2315
- console.log(`${pc.cyan("Next step:")} Run ${pc.bold("claude")} to start working!`);
2662
+ console.log(`${pc2.cyan("Next step:")} Run ${pc2.bold("claude")} to start working!`);
2316
2663
  console.log();
2317
2664
  console.log(
2318
- pc.gray(
2665
+ pc2.gray(
2319
2666
  "Your .claude/ files were generated by deep analysis - review them with: ls -la .claude/"
2320
2667
  )
2321
2668
  );
@@ -2324,7 +2671,7 @@ try {
2324
2671
  const isMain = process.argv[1] && fs5.realpathSync(process.argv[1]) === fileURLToPath(import.meta.url);
2325
2672
  if (isMain) {
2326
2673
  main().catch((err) => {
2327
- console.error(pc.red("Error:"), err.message);
2674
+ console.error(pc2.red("Error:"), err.message);
2328
2675
  if (process.env.DEBUG) {
2329
2676
  console.error(err.stack);
2330
2677
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-starter",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "A lightweight starter kit for AI-assisted development with Claude Code",
5
5
  "keywords": [
6
6
  "claude",