claudekit-cli 3.41.3-dev.2 → 3.41.3-dev.3

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/index.js +325 -84
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -44768,6 +44768,58 @@ import { join as join20 } from "node:path";
44768
44768
  function escapeRegex(value) {
44769
44769
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
44770
44770
  }
44771
+ function normalizeCommandRoot(root) {
44772
+ return root.replace(/\\/g, "/").replace(/\/+$/, "");
44773
+ }
44774
+ function formatCanonicalClaudeCommand(nodePrefix, root, relativePath, suffix = "") {
44775
+ const normalizedRoot = normalizeCommandRoot(root);
44776
+ let normalizedRelativePath = relativePath.replace(/\\/g, "/").replace(/^\/+/, "");
44777
+ if (normalizedRoot !== "$HOME" && normalizedRoot !== "$CLAUDE_PROJECT_DIR") {
44778
+ normalizedRelativePath = normalizedRelativePath.replace(/^\.claude\//, "");
44779
+ }
44780
+ return normalizedRoot === "$CLAUDE_PROJECT_DIR" ? `${nodePrefix}"${normalizedRoot}"/${normalizedRelativePath}${suffix}` : `${nodePrefix}"${normalizedRoot}/${normalizedRelativePath}"${suffix}`;
44781
+ }
44782
+ function isNodeClaudeCommand(cmd) {
44783
+ if (!cmd)
44784
+ return false;
44785
+ return /^\s*node\s+/.test(cmd) && (cmd.includes(".claude/") || cmd.includes(".claude\\"));
44786
+ }
44787
+ function repairClaudeNodeCommandPath(cmd, root) {
44788
+ if (!cmd || !isNodeClaudeCommand(cmd)) {
44789
+ return { command: cmd ?? "", changed: false, issue: null };
44790
+ }
44791
+ const bareRelativeMatch = cmd.match(/^(node\s+)(?:\.\/)?(\.claude[/\\][^\s"]+)(.*)$/);
44792
+ if (bareRelativeMatch) {
44793
+ const [, nodePrefix, relativePath, suffix] = bareRelativeMatch;
44794
+ const command = formatCanonicalClaudeCommand(nodePrefix, root, relativePath, suffix);
44795
+ return { command, changed: command !== cmd, issue: "raw-relative" };
44796
+ }
44797
+ const embeddedQuotedMatch = cmd.match(/^(node\s+)"(?:\$HOME|\$CLAUDE_PROJECT_DIR|%USERPROFILE%|%CLAUDE_PROJECT_DIR%)[/\\](\.claude[/\\][^"]+)"(.*)$/);
44798
+ if (embeddedQuotedMatch) {
44799
+ const [, nodePrefix, relativePath, suffix] = embeddedQuotedMatch;
44800
+ const command = formatCanonicalClaudeCommand(nodePrefix, root, relativePath, suffix);
44801
+ return { command, changed: command !== cmd, issue: "invalid-format" };
44802
+ }
44803
+ const varOnlyQuotedMatch = cmd.match(/^(node\s+)"(?:\$HOME|\$CLAUDE_PROJECT_DIR|%USERPROFILE%|%CLAUDE_PROJECT_DIR%)"[/\\](\.claude[/\\][^\s"]+)(.*)$/);
44804
+ if (varOnlyQuotedMatch) {
44805
+ const [, nodePrefix, relativePath, suffix] = varOnlyQuotedMatch;
44806
+ const command = formatCanonicalClaudeCommand(nodePrefix, root, relativePath, suffix);
44807
+ return { command, changed: command !== cmd, issue: "invalid-format" };
44808
+ }
44809
+ const tildeMatch = cmd.match(/^(node\s+)~[/\\](\.claude[/\\][^\s"]+)(.*)$/);
44810
+ if (tildeMatch) {
44811
+ const [, nodePrefix, relativePath, suffix] = tildeMatch;
44812
+ const command = formatCanonicalClaudeCommand(nodePrefix, root, relativePath, suffix);
44813
+ return { command, changed: command !== cmd, issue: "invalid-format" };
44814
+ }
44815
+ const unquotedMatch = cmd.match(/^(node\s+)(?:\$HOME|\$CLAUDE_PROJECT_DIR|%USERPROFILE%|%CLAUDE_PROJECT_DIR%)[/\\](\.claude[/\\][^\s"]+)(.*)$/);
44816
+ if (unquotedMatch) {
44817
+ const [, nodePrefix, relativePath, suffix] = unquotedMatch;
44818
+ const command = formatCanonicalClaudeCommand(nodePrefix, root, relativePath, suffix);
44819
+ return { command, changed: command !== cmd, issue: "invalid-format" };
44820
+ }
44821
+ return { command: cmd, changed: false, issue: null };
44822
+ }
44771
44823
  function normalizeCommand(cmd) {
44772
44824
  if (!cmd)
44773
44825
  return "";
@@ -57271,7 +57323,7 @@ var package_default;
57271
57323
  var init_package = __esm(() => {
57272
57324
  package_default = {
57273
57325
  name: "claudekit-cli",
57274
- version: "3.41.3-dev.2",
57326
+ version: "3.41.3-dev.3",
57275
57327
  description: "CLI tool for bootstrapping and updating ClaudeKit projects",
57276
57328
  type: "module",
57277
57329
  repository: {
@@ -65865,10 +65917,10 @@ var init_config_manager2 = __esm(() => {
65865
65917
 
65866
65918
  // src/services/package-installer/gemini-mcp/validation.ts
65867
65919
  import { existsSync as existsSync51, lstatSync, readlinkSync } from "node:fs";
65868
- import { homedir as homedir31 } from "node:os";
65920
+ import { homedir as homedir32 } from "node:os";
65869
65921
  import { join as join78 } from "node:path";
65870
65922
  function getGlobalMcpConfigPath() {
65871
- return join78(homedir31(), ".claude", ".mcp.json");
65923
+ return join78(homedir32(), ".claude", ".mcp.json");
65872
65924
  }
65873
65925
  function getLocalMcpConfigPath(projectDir) {
65874
65926
  return join78(projectDir, ".mcp.json");
@@ -65889,7 +65941,7 @@ function findMcpConfigPath(projectDir) {
65889
65941
  }
65890
65942
  function getGeminiSettingsPath(projectDir, isGlobal) {
65891
65943
  if (isGlobal) {
65892
- return join78(homedir31(), ".gemini", "settings.json");
65944
+ return join78(homedir32(), ".gemini", "settings.json");
65893
65945
  }
65894
65946
  return join78(projectDir, ".gemini", "settings.json");
65895
65947
  }
@@ -68517,7 +68569,7 @@ var init_content_validator = __esm(() => {
68517
68569
  import { createHash as createHash7 } from "node:crypto";
68518
68570
  import { existsSync as existsSync66, mkdirSync as mkdirSync4, readFileSync as readFileSync15, readdirSync as readdirSync9, statSync as statSync11 } from "node:fs";
68519
68571
  import { rename as rename10, writeFile as writeFile35 } from "node:fs/promises";
68520
- import { homedir as homedir36 } from "node:os";
68572
+ import { homedir as homedir37 } from "node:os";
68521
68573
  import { basename as basename23, join as join138 } from "node:path";
68522
68574
  function getCachedContext(repoPath) {
68523
68575
  const cachePath = getCacheFilePath(repoPath);
@@ -68592,7 +68644,7 @@ function getCacheFilePath(repoPath) {
68592
68644
  }
68593
68645
  var CACHE_DIR, CACHE_TTL_MS4;
68594
68646
  var init_context_cache_manager = __esm(() => {
68595
- CACHE_DIR = join138(homedir36(), ".claudekit", "cache");
68647
+ CACHE_DIR = join138(homedir37(), ".claudekit", "cache");
68596
68648
  CACHE_TTL_MS4 = 24 * 60 * 60 * 1000;
68597
68649
  });
68598
68650
 
@@ -69046,10 +69098,10 @@ IMPORTANT: Generate the image and output the path as JSON: {"imagePath": "/path/
69046
69098
  // src/commands/content/phases/photo-generator.ts
69047
69099
  import { execSync as execSync7 } from "node:child_process";
69048
69100
  import { existsSync as existsSync68, mkdirSync as mkdirSync5, readdirSync as readdirSync11 } from "node:fs";
69049
- import { homedir as homedir37 } from "node:os";
69101
+ import { homedir as homedir38 } from "node:os";
69050
69102
  import { join as join140 } from "node:path";
69051
69103
  async function generatePhoto(_content, context, config, platform16, contentId, contentLogger) {
69052
- const mediaDir = join140(config.contentDir.replace(/^~/, homedir37()), "media", String(contentId));
69104
+ const mediaDir = join140(config.contentDir.replace(/^~/, homedir38()), "media", String(contentId));
69053
69105
  if (!existsSync68(mediaDir)) {
69054
69106
  mkdirSync5(mediaDir, { recursive: true });
69055
69107
  }
@@ -69163,7 +69215,7 @@ var init_content_creator = __esm(() => {
69163
69215
 
69164
69216
  // src/commands/content/phases/content-logger.ts
69165
69217
  import { createWriteStream as createWriteStream4, existsSync as existsSync69, mkdirSync as mkdirSync6, statSync as statSync12 } from "node:fs";
69166
- import { homedir as homedir38 } from "node:os";
69218
+ import { homedir as homedir39 } from "node:os";
69167
69219
  import { join as join141 } from "node:path";
69168
69220
 
69169
69221
  class ContentLogger {
@@ -69172,7 +69224,7 @@ class ContentLogger {
69172
69224
  logDir;
69173
69225
  maxBytes;
69174
69226
  constructor(maxBytes = 0) {
69175
- this.logDir = join141(homedir38(), ".claudekit", "logs");
69227
+ this.logDir = join141(homedir39(), ".claudekit", "logs");
69176
69228
  this.maxBytes = maxBytes;
69177
69229
  }
69178
69230
  init() {
@@ -69270,7 +69322,7 @@ var init_sqlite_client = () => {};
69270
69322
 
69271
69323
  // src/commands/content/phases/db-manager.ts
69272
69324
  import { existsSync as existsSync70, mkdirSync as mkdirSync7 } from "node:fs";
69273
- import { dirname as dirname35 } from "node:path";
69325
+ import { dirname as dirname36 } from "node:path";
69274
69326
  function initDatabase(dbPath) {
69275
69327
  ensureParentDir(dbPath);
69276
69328
  const db = openDatabase(dbPath);
@@ -69291,7 +69343,7 @@ function runRetentionCleanup(db, retentionDays = 90) {
69291
69343
  db.prepare("DELETE FROM git_events WHERE processed = 1 AND created_at < ?").run(cutoff);
69292
69344
  }
69293
69345
  function ensureParentDir(dbPath) {
69294
- const dir = dirname35(dbPath);
69346
+ const dir = dirname36(dbPath);
69295
69347
  if (dir && !existsSync70(dir)) {
69296
69348
  mkdirSync7(dir, { recursive: true });
69297
69349
  }
@@ -70771,11 +70823,11 @@ var init_setup_wizard = __esm(() => {
70771
70823
 
70772
70824
  // src/commands/content/content-review-commands.ts
70773
70825
  import { existsSync as existsSync73 } from "node:fs";
70774
- import { homedir as homedir39 } from "node:os";
70826
+ import { homedir as homedir40 } from "node:os";
70775
70827
  async function queueContent() {
70776
70828
  const cwd2 = process.cwd();
70777
70829
  const config = await loadContentConfig(cwd2);
70778
- const dbPath = config.dbPath.replace(/^~/, homedir39());
70830
+ const dbPath = config.dbPath.replace(/^~/, homedir40());
70779
70831
  if (!existsSync73(dbPath)) {
70780
70832
  logger.info("No content database found. Run 'ck content setup' first.");
70781
70833
  return;
@@ -70802,7 +70854,7 @@ async function queueContent() {
70802
70854
  async function approveContentCmd(id) {
70803
70855
  const cwd2 = process.cwd();
70804
70856
  const config = await loadContentConfig(cwd2);
70805
- const dbPath = config.dbPath.replace(/^~/, homedir39());
70857
+ const dbPath = config.dbPath.replace(/^~/, homedir40());
70806
70858
  const db = initDatabase(dbPath);
70807
70859
  try {
70808
70860
  approveContent(db, Number.parseInt(id, 10));
@@ -70814,7 +70866,7 @@ async function approveContentCmd(id) {
70814
70866
  async function rejectContentCmd(id, reason) {
70815
70867
  const cwd2 = process.cwd();
70816
70868
  const config = await loadContentConfig(cwd2);
70817
- const dbPath = config.dbPath.replace(/^~/, homedir39());
70869
+ const dbPath = config.dbPath.replace(/^~/, homedir40());
70818
70870
  const db = initDatabase(dbPath);
70819
70871
  try {
70820
70872
  rejectContent(db, Number.parseInt(id, 10), reason);
@@ -70845,7 +70897,7 @@ __export(exports_content_subcommands, {
70845
70897
  approveContentCmd: () => approveContentCmd
70846
70898
  });
70847
70899
  import { existsSync as existsSync74, readFileSync as readFileSync18, unlinkSync as unlinkSync5 } from "node:fs";
70848
- import { homedir as homedir40 } from "node:os";
70900
+ import { homedir as homedir41 } from "node:os";
70849
70901
  import { join as join146 } from "node:path";
70850
70902
  function isDaemonRunning() {
70851
70903
  const lockFile = join146(LOCK_DIR, `${LOCK_NAME2}.lock`);
@@ -70919,7 +70971,7 @@ async function statusContent() {
70919
70971
  } catch {}
70920
70972
  }
70921
70973
  async function logsContent(options2) {
70922
- const logDir = join146(homedir40(), ".claudekit", "logs");
70974
+ const logDir = join146(homedir41(), ".claudekit", "logs");
70923
70975
  const dateStr = new Date().toISOString().slice(0, 10).replace(/-/g, "");
70924
70976
  const logPath = join146(logDir, `content-${dateStr}.log`);
70925
70977
  if (!existsSync74(logPath)) {
@@ -70953,12 +71005,12 @@ var init_content_subcommands = __esm(() => {
70953
71005
  init_setup_wizard();
70954
71006
  init_state_manager();
70955
71007
  init_content_review_commands();
70956
- LOCK_DIR = join146(homedir40(), ".claudekit", "locks");
71008
+ LOCK_DIR = join146(homedir41(), ".claudekit", "locks");
70957
71009
  });
70958
71010
 
70959
71011
  // src/commands/content/content-command.ts
70960
71012
  import { existsSync as existsSync75, mkdirSync as mkdirSync8, unlinkSync as unlinkSync6, writeFileSync as writeFileSync6 } from "node:fs";
70961
- import { homedir as homedir41 } from "node:os";
71013
+ import { homedir as homedir42 } from "node:os";
70962
71014
  import { join as join147 } from "node:path";
70963
71015
  async function contentCommand(options2) {
70964
71016
  const cwd2 = process.cwd();
@@ -70991,7 +71043,7 @@ async function contentCommand(options2) {
70991
71043
  if (!existsSync75(LOCK_DIR2))
70992
71044
  mkdirSync8(LOCK_DIR2, { recursive: true });
70993
71045
  writeFileSync6(LOCK_FILE, String(process.pid), "utf-8");
70994
- const dbPath = config.dbPath.replace(/^~/, homedir41());
71046
+ const dbPath = config.dbPath.replace(/^~/, homedir42());
70995
71047
  const db = initDatabase(dbPath);
70996
71048
  contentLogger.info(`Database initialised at ${dbPath}`);
70997
71049
  const adapters = initializeAdapters(config);
@@ -71137,7 +71189,7 @@ var init_content_command = __esm(() => {
71137
71189
  init_publisher();
71138
71190
  init_review_manager();
71139
71191
  init_state_manager();
71140
- LOCK_DIR2 = join147(homedir41(), ".claudekit", "locks");
71192
+ LOCK_DIR2 = join147(homedir42(), ".claudekit", "locks");
71141
71193
  LOCK_FILE = join147(LOCK_DIR2, "ck-content.lock");
71142
71194
  });
71143
71195
 
@@ -78888,13 +78940,15 @@ async function checkEnvKeys(setup) {
78888
78940
  return results;
78889
78941
  }
78890
78942
  // src/domains/health-checks/checkers/hook-health-checker.ts
78943
+ init_settings_merger();
78891
78944
  init_claudekit_constants();
78945
+ init_command_normalizer();
78892
78946
  init_logger();
78893
78947
  init_path_resolver();
78894
78948
  import { spawnSync as spawnSync2 } from "node:child_process";
78895
78949
  import { existsSync as existsSync47, readFileSync as readFileSync12, statSync as statSync7, writeFileSync as writeFileSync4 } from "node:fs";
78896
78950
  import { readdir as readdir14 } from "node:fs/promises";
78897
- import { tmpdir } from "node:os";
78951
+ import { homedir as homedir30, tmpdir } from "node:os";
78898
78952
  import { join as join67, resolve as resolve19 } from "node:path";
78899
78953
  var HOOK_CHECK_TIMEOUT_MS = 5000;
78900
78954
  var PYTHON_CHECK_TIMEOUT_MS = 3000;
@@ -78911,6 +78965,117 @@ function getHooksDir(projectDir) {
78911
78965
  function isPathWithin(filePath, parentDir) {
78912
78966
  return resolve19(filePath).startsWith(resolve19(parentDir));
78913
78967
  }
78968
+ function getCanonicalGlobalCommandRoot() {
78969
+ const configuredGlobalDir = PathResolver.getGlobalKitDir().replace(/\\/g, "/").replace(/\/+$/, "");
78970
+ const defaultGlobalDir = join67(homedir30(), ".claude").replace(/\\/g, "/");
78971
+ return configuredGlobalDir === defaultGlobalDir ? "$HOME" : configuredGlobalDir;
78972
+ }
78973
+ function getClaudeSettingsFiles(projectDir) {
78974
+ const globalClaudeDir = PathResolver.getGlobalKitDir();
78975
+ const candidates = [
78976
+ {
78977
+ path: resolve19(projectDir, ".claude", "settings.json"),
78978
+ label: "project settings.json",
78979
+ root: "$CLAUDE_PROJECT_DIR"
78980
+ },
78981
+ {
78982
+ path: resolve19(projectDir, ".claude", "settings.local.json"),
78983
+ label: "project settings.local.json",
78984
+ root: "$CLAUDE_PROJECT_DIR"
78985
+ },
78986
+ {
78987
+ path: resolve19(globalClaudeDir, "settings.json"),
78988
+ label: "global settings.json",
78989
+ root: getCanonicalGlobalCommandRoot()
78990
+ },
78991
+ {
78992
+ path: resolve19(globalClaudeDir, "settings.local.json"),
78993
+ label: "global settings.local.json",
78994
+ root: getCanonicalGlobalCommandRoot()
78995
+ }
78996
+ ];
78997
+ return candidates.filter((candidate) => existsSync47(candidate.path));
78998
+ }
78999
+ function collectHookCommandFindings(settings, settingsFile) {
79000
+ if (!settings.hooks) {
79001
+ return [];
79002
+ }
79003
+ const findings = [];
79004
+ for (const [eventName, entries] of Object.entries(settings.hooks)) {
79005
+ for (const entry of entries) {
79006
+ if ("command" in entry && typeof entry.command === "string") {
79007
+ const repair = repairClaudeNodeCommandPath(entry.command, settingsFile.root);
79008
+ if (repair.changed && repair.issue) {
79009
+ findings.push({
79010
+ path: settingsFile.path,
79011
+ label: settingsFile.label,
79012
+ eventName,
79013
+ command: entry.command,
79014
+ expected: repair.command,
79015
+ issue: repair.issue
79016
+ });
79017
+ }
79018
+ }
79019
+ if (!("hooks" in entry) || !entry.hooks) {
79020
+ continue;
79021
+ }
79022
+ for (const hook of entry.hooks) {
79023
+ if (!hook.command) {
79024
+ continue;
79025
+ }
79026
+ const repair = repairClaudeNodeCommandPath(hook.command, settingsFile.root);
79027
+ if (!repair.changed || !repair.issue) {
79028
+ continue;
79029
+ }
79030
+ findings.push({
79031
+ path: settingsFile.path,
79032
+ label: settingsFile.label,
79033
+ eventName,
79034
+ matcher: "matcher" in entry ? entry.matcher : undefined,
79035
+ command: hook.command,
79036
+ expected: repair.command,
79037
+ issue: repair.issue
79038
+ });
79039
+ }
79040
+ }
79041
+ }
79042
+ return findings;
79043
+ }
79044
+ async function repairHookCommandsInSettingsFile(settingsFile) {
79045
+ const settings = await SettingsMerger.readSettingsFile(settingsFile.path);
79046
+ if (!settings?.hooks) {
79047
+ return 0;
79048
+ }
79049
+ let repaired = 0;
79050
+ for (const entries of Object.values(settings.hooks)) {
79051
+ for (const entry of entries) {
79052
+ if ("command" in entry && typeof entry.command === "string") {
79053
+ const repair = repairClaudeNodeCommandPath(entry.command, settingsFile.root);
79054
+ if (repair.changed) {
79055
+ entry.command = repair.command;
79056
+ repaired++;
79057
+ }
79058
+ }
79059
+ if (!("hooks" in entry) || !entry.hooks) {
79060
+ continue;
79061
+ }
79062
+ for (const hook of entry.hooks) {
79063
+ if (!hook.command) {
79064
+ continue;
79065
+ }
79066
+ const repair = repairClaudeNodeCommandPath(hook.command, settingsFile.root);
79067
+ if (repair.changed) {
79068
+ hook.command = repair.command;
79069
+ repaired++;
79070
+ }
79071
+ }
79072
+ }
79073
+ }
79074
+ if (repaired > 0) {
79075
+ await SettingsMerger.writeSettingsFile(settingsFile.path, settings);
79076
+ }
79077
+ return repaired;
79078
+ }
78914
79079
  async function checkHookSyntax(projectDir) {
78915
79080
  const hooksDir = getHooksDir(projectDir);
78916
79081
  if (!hooksDir) {
@@ -79221,6 +79386,82 @@ async function checkHookRuntime(projectDir) {
79221
79386
  };
79222
79387
  }
79223
79388
  }
79389
+ async function checkHookCommandPaths(projectDir) {
79390
+ const settingsFiles = getClaudeSettingsFiles(projectDir);
79391
+ if (settingsFiles.length === 0) {
79392
+ return {
79393
+ id: "hook-command-paths",
79394
+ name: "Hook Command Paths",
79395
+ group: "claudekit",
79396
+ priority: "standard",
79397
+ status: "info",
79398
+ message: "No Claude settings files",
79399
+ autoFixable: false
79400
+ };
79401
+ }
79402
+ const findings = [];
79403
+ for (const settingsFile of settingsFiles) {
79404
+ const settings = await SettingsMerger.readSettingsFile(settingsFile.path);
79405
+ if (!settings) {
79406
+ continue;
79407
+ }
79408
+ findings.push(...collectHookCommandFindings(settings, settingsFile));
79409
+ }
79410
+ if (findings.length === 0) {
79411
+ return {
79412
+ id: "hook-command-paths",
79413
+ name: "Hook Command Paths",
79414
+ group: "claudekit",
79415
+ priority: "standard",
79416
+ status: "pass",
79417
+ message: `${settingsFiles.length} settings file(s) canonical`,
79418
+ autoFixable: false
79419
+ };
79420
+ }
79421
+ const details = findings.slice(0, 5).map((finding) => {
79422
+ const matcher = finding.matcher ? ` [${finding.matcher}]` : "";
79423
+ return `${finding.label} :: ${finding.eventName}${matcher} :: ${finding.issue} :: ${finding.command}`;
79424
+ }).join(`
79425
+ `);
79426
+ return {
79427
+ id: "hook-command-paths",
79428
+ name: "Hook Command Paths",
79429
+ group: "claudekit",
79430
+ priority: "standard",
79431
+ status: "fail",
79432
+ message: `${findings.length} stale hook command path(s)`,
79433
+ details,
79434
+ suggestion: "Run: ck doctor --fix",
79435
+ autoFixable: true,
79436
+ fix: {
79437
+ id: "fix-hook-command-paths",
79438
+ description: "Canonicalize stale .claude hook command paths in settings files",
79439
+ execute: async () => {
79440
+ try {
79441
+ let repaired = 0;
79442
+ for (const settingsFile of settingsFiles) {
79443
+ repaired += await repairHookCommandsInSettingsFile(settingsFile);
79444
+ }
79445
+ if (repaired === 0) {
79446
+ return {
79447
+ success: true,
79448
+ message: "No stale hook command paths needed repair"
79449
+ };
79450
+ }
79451
+ return {
79452
+ success: true,
79453
+ message: `Repaired ${repaired} stale hook command path(s)`
79454
+ };
79455
+ } catch (error) {
79456
+ return {
79457
+ success: false,
79458
+ message: `Failed to repair hook command paths: ${error}`
79459
+ };
79460
+ }
79461
+ }
79462
+ }
79463
+ };
79464
+ }
79224
79465
  async function checkHookConfig(projectDir) {
79225
79466
  const projectConfigPath = join67(projectDir, ".claude", ".ck.json");
79226
79467
  const globalConfigPath = join67(PathResolver.getGlobalKitDir(), ".ck.json");
@@ -79720,6 +79961,8 @@ class ClaudekitChecker {
79720
79961
  results.push(await checkHookDeps(this.projectDir));
79721
79962
  logger.verbose("ClaudekitChecker: Checking hook runtime");
79722
79963
  results.push(await checkHookRuntime(this.projectDir));
79964
+ logger.verbose("ClaudekitChecker: Checking hook command paths");
79965
+ results.push(await checkHookCommandPaths(this.projectDir));
79723
79966
  logger.verbose("ClaudekitChecker: Checking hook config");
79724
79967
  results.push(await checkHookConfig(this.projectDir));
79725
79968
  logger.verbose("ClaudekitChecker: Checking hook crash logs");
@@ -80084,7 +80327,7 @@ import { platform as platform8 } from "node:os";
80084
80327
  init_environment();
80085
80328
  init_path_resolver();
80086
80329
  import { constants as constants3, access as access3, mkdir as mkdir16, readFile as readFile34, unlink as unlink8, writeFile as writeFile17 } from "node:fs/promises";
80087
- import { arch as arch2, homedir as homedir30, platform as platform7 } from "node:os";
80330
+ import { arch as arch2, homedir as homedir31, platform as platform7 } from "node:os";
80088
80331
  import { join as join69, normalize as normalize7 } from "node:path";
80089
80332
  function shouldSkipExpensiveOperations4() {
80090
80333
  return shouldSkipExpensiveOperations();
@@ -80107,7 +80350,7 @@ async function checkPlatformDetect() {
80107
80350
  };
80108
80351
  }
80109
80352
  async function checkHomeDirResolution() {
80110
- const nodeHome = normalize7(homedir30());
80353
+ const nodeHome = normalize7(homedir31());
80111
80354
  const rawEnvHome = getHomeDirectoryFromEnv(platform7());
80112
80355
  const envHome = rawEnvHome ? normalize7(rawEnvHome) : "";
80113
80356
  const match = nodeHome === envHome && envHome !== "";
@@ -92434,7 +92677,7 @@ init_logger();
92434
92677
  init_types3();
92435
92678
  var import_fs_extra15 = __toESM(require_lib(), 1);
92436
92679
  var import_ignore3 = __toESM(require_ignore(), 1);
92437
- import { dirname as dirname28, join as join95, relative as relative13 } from "node:path";
92680
+ import { dirname as dirname29, join as join95, relative as relative13 } from "node:path";
92438
92681
 
92439
92682
  // src/domains/installation/selective-merger.ts
92440
92683
  import { stat as stat14 } from "node:fs/promises";
@@ -94104,8 +94347,8 @@ class FileScanner {
94104
94347
 
94105
94348
  // src/domains/installation/merger/settings-processor.ts
94106
94349
  import { execSync as execSync4 } from "node:child_process";
94107
- import { homedir as homedir32 } from "node:os";
94108
- import { join as join94 } from "node:path";
94350
+ import { homedir as homedir33 } from "node:os";
94351
+ import { dirname as dirname28, join as join94 } from "node:path";
94109
94352
 
94110
94353
  // src/domains/config/installed-settings-tracker.ts
94111
94354
  init_shared();
@@ -94284,6 +94527,7 @@ class SettingsProcessor {
94284
94527
  }
94285
94528
  await this.injectTeamHooksIfSupported(destFile);
94286
94529
  }
94530
+ await this.repairSiblingSettingsLocal(destFile);
94287
94531
  } catch (error) {
94288
94532
  logger.error(`Failed to process settings.json: ${error}`);
94289
94533
  await import_fs_extra14.copy(sourceFile, destFile, { overwrite: true });
@@ -94549,34 +94793,7 @@ class SettingsProcessor {
94549
94793
  return fixed;
94550
94794
  }
94551
94795
  fixSingleCommandPath(cmd) {
94552
- if (!cmd.includes(".claude/") && !cmd.includes(".claude\\"))
94553
- return cmd;
94554
- const bareRelativeMatch = cmd.match(/^(node\s+)(?:\.\/)?(\.claude[/\\][^\s"]+)(.*)$/);
94555
- if (bareRelativeMatch) {
94556
- const [, nodePrefix, relativePath, suffix] = bareRelativeMatch;
94557
- return this.formatCommandPath(nodePrefix, this.isGlobal ? this.getCanonicalGlobalCommandRoot() : "$CLAUDE_PROJECT_DIR", relativePath, suffix);
94558
- }
94559
- const embeddedQuotedMatch = cmd.match(/^(node\s+)"(\$HOME|\$CLAUDE_PROJECT_DIR|%USERPROFILE%|%CLAUDE_PROJECT_DIR%)[/\\](\.claude[/\\][^"]+)"(.*)$/);
94560
- if (embeddedQuotedMatch) {
94561
- const [, nodePrefix, capturedVar, relativePath, suffix] = embeddedQuotedMatch;
94562
- return this.formatCommandPath(nodePrefix, capturedVar, relativePath, suffix);
94563
- }
94564
- const varOnlyQuotedMatch = cmd.match(/^(node\s+)"(\$HOME|\$CLAUDE_PROJECT_DIR|%USERPROFILE%|%CLAUDE_PROJECT_DIR%)"[/\\](\.claude[/\\][^\s"]+)(.*)$/);
94565
- if (varOnlyQuotedMatch) {
94566
- const [, nodePrefix, capturedVar, relativePath, suffix] = varOnlyQuotedMatch;
94567
- return this.formatCommandPath(nodePrefix, capturedVar, relativePath, suffix);
94568
- }
94569
- const tildeMatch = cmd.match(/^(node\s+)~[/\\](\.claude[/\\][^\s"]+)(.*)$/);
94570
- if (tildeMatch) {
94571
- const [, nodePrefix, relativePath, suffix] = tildeMatch;
94572
- return this.formatCommandPath(nodePrefix, "$HOME", relativePath, suffix);
94573
- }
94574
- const unquotedMatch = cmd.match(/^(node\s+)(\$HOME|\$CLAUDE_PROJECT_DIR|%USERPROFILE%|%CLAUDE_PROJECT_DIR%)[/\\](\.claude[/\\][^\s"]+)(.*)$/);
94575
- if (unquotedMatch) {
94576
- const [, nodePrefix, capturedVar, relativePath, suffix] = unquotedMatch;
94577
- return this.formatCommandPath(nodePrefix, capturedVar, relativePath, suffix);
94578
- }
94579
- return cmd;
94796
+ return repairClaudeNodeCommandPath(cmd, this.getClaudeCommandRoot()).command;
94580
94797
  }
94581
94798
  formatCommandPath(nodePrefix, capturedVar, relativePath, suffix = "") {
94582
94799
  const canonicalRoot = this.canonicalizePathRoot(capturedVar);
@@ -94616,9 +94833,33 @@ class SettingsProcessor {
94616
94833
  return false;
94617
94834
  }
94618
94835
  const configuredGlobalDir = PathResolver.getGlobalKitDir().replace(/\\/g, "/").replace(/\/+$/, "");
94619
- const defaultGlobalDir = join94(homedir32(), ".claude").replace(/\\/g, "/");
94836
+ const defaultGlobalDir = join94(homedir33(), ".claude").replace(/\\/g, "/");
94620
94837
  return configuredGlobalDir !== defaultGlobalDir;
94621
94838
  }
94839
+ getClaudeCommandRoot() {
94840
+ return this.isGlobal ? this.getCanonicalGlobalCommandRoot() : "$CLAUDE_PROJECT_DIR";
94841
+ }
94842
+ async repairSettingsFile(filePath) {
94843
+ const settings = await SettingsMerger.readSettingsFile(filePath);
94844
+ if (!settings) {
94845
+ return false;
94846
+ }
94847
+ const pathsFixed = this.fixHookCommandPaths(settings);
94848
+ if (!pathsFixed) {
94849
+ return false;
94850
+ }
94851
+ await SettingsMerger.writeSettingsFile(filePath, settings);
94852
+ return true;
94853
+ }
94854
+ async repairSiblingSettingsLocal(destFile) {
94855
+ const settingsLocalPath = join94(dirname28(destFile), "settings.local.json");
94856
+ if (settingsLocalPath === destFile || !await import_fs_extra14.pathExists(settingsLocalPath)) {
94857
+ return;
94858
+ }
94859
+ if (await this.repairSettingsFile(settingsLocalPath)) {
94860
+ logger.info(`Repaired stale .claude command paths in ${settingsLocalPath}`);
94861
+ }
94862
+ }
94622
94863
  detectClaudeCodeVersion() {
94623
94864
  if (this.cachedVersion !== undefined)
94624
94865
  return this.cachedVersion;
@@ -94873,10 +95114,10 @@ class CopyExecutor {
94873
95114
  }
94874
95115
  trackInstalledFile(relativePath) {
94875
95116
  this.installedFiles.add(relativePath);
94876
- let dir = dirname28(relativePath);
95117
+ let dir = dirname29(relativePath);
94877
95118
  while (dir && dir !== "." && dir !== "/") {
94878
95119
  this.installedDirectories.add(`${dir}/`);
94879
- dir = dirname28(dir);
95120
+ dir = dirname29(dir);
94880
95121
  }
94881
95122
  }
94882
95123
  }
@@ -97978,7 +98219,7 @@ async function runPreflightChecks() {
97978
98219
  // src/domains/installation/fresh-installer.ts
97979
98220
  init_metadata_migration();
97980
98221
  import { existsSync as existsSync55, readdirSync as readdirSync6, rmSync as rmSync4, rmdirSync as rmdirSync2, unlinkSync as unlinkSync4 } from "node:fs";
97981
- import { basename as basename18, dirname as dirname29, join as join117, resolve as resolve26 } from "node:path";
98222
+ import { basename as basename18, dirname as dirname30, join as join117, resolve as resolve26 } from "node:path";
97982
98223
  init_logger();
97983
98224
  init_safe_spinner();
97984
98225
  var import_fs_extra34 = __toESM(require_lib(), 1);
@@ -98027,14 +98268,14 @@ async function analyzeFreshInstallation(claudeDir2) {
98027
98268
  }
98028
98269
  function cleanupEmptyDirectories2(filePath, claudeDir2) {
98029
98270
  const normalizedClaudeDir = resolve26(claudeDir2);
98030
- let currentDir = resolve26(dirname29(filePath));
98271
+ let currentDir = resolve26(dirname30(filePath));
98031
98272
  while (currentDir !== normalizedClaudeDir && currentDir.startsWith(normalizedClaudeDir)) {
98032
98273
  try {
98033
98274
  const entries = readdirSync6(currentDir);
98034
98275
  if (entries.length === 0) {
98035
98276
  rmdirSync2(currentDir);
98036
98277
  logger.debug(`Removed empty directory: ${currentDir}`);
98037
- currentDir = resolve26(dirname29(currentDir));
98278
+ currentDir = resolve26(dirname30(currentDir));
98038
98279
  } else {
98039
98280
  break;
98040
98281
  }
@@ -98580,7 +98821,7 @@ async function handleSelection(ctx) {
98580
98821
  }
98581
98822
  // src/commands/init/phases/sync-handler.ts
98582
98823
  import { copyFile as copyFile8, mkdir as mkdir32, open as open5, readFile as readFile50, rename as rename7, stat as stat18, unlink as unlink11, writeFile as writeFile30 } from "node:fs/promises";
98583
- import { dirname as dirname30, join as join119, resolve as resolve28 } from "node:path";
98824
+ import { dirname as dirname31, join as join119, resolve as resolve28 } from "node:path";
98584
98825
  init_logger();
98585
98826
  init_path_resolver();
98586
98827
  var import_fs_extra36 = __toESM(require_lib(), 1);
@@ -98699,7 +98940,7 @@ async function acquireSyncLock(global3) {
98699
98940
  const lockPath = join119(cacheDir, ".sync-lock");
98700
98941
  const startTime = Date.now();
98701
98942
  const lockTimeout = getLockTimeout();
98702
- await mkdir32(dirname30(lockPath), { recursive: true });
98943
+ await mkdir32(dirname31(lockPath), { recursive: true });
98703
98944
  while (Date.now() - startTime < lockTimeout) {
98704
98945
  try {
98705
98946
  const handle = await open5(lockPath, "wx");
@@ -99235,7 +99476,7 @@ async function transformFolderPaths(extractDir, folders, options2 = {}) {
99235
99476
  // src/services/transformers/global-path-transformer.ts
99236
99477
  init_logger();
99237
99478
  import { readFile as readFile52, readdir as readdir35, writeFile as writeFile32 } from "node:fs/promises";
99238
- import { homedir as homedir33, platform as platform15 } from "node:os";
99479
+ import { homedir as homedir34, platform as platform15 } from "node:os";
99239
99480
  import { extname as extname6, join as join122 } from "node:path";
99240
99481
  var IS_WINDOWS3 = platform15() === "win32";
99241
99482
  var HOME_PREFIX = IS_WINDOWS3 ? "%USERPROFILE%" : "$HOME";
@@ -99246,7 +99487,7 @@ function normalizeInstallPath(path15) {
99246
99487
  return path15.replace(/\\/g, "/").replace(/\/+$/, "");
99247
99488
  }
99248
99489
  function getDefaultGlobalClaudeDir() {
99249
- return normalizeInstallPath(join122(homedir33(), ".claude"));
99490
+ return normalizeInstallPath(join122(homedir34(), ".claude"));
99250
99491
  }
99251
99492
  function getCustomGlobalClaudeDir(targetClaudeDir) {
99252
99493
  if (!targetClaudeDir)
@@ -99657,7 +99898,7 @@ init_dist2();
99657
99898
  var import_picocolors29 = __toESM(require_picocolors(), 1);
99658
99899
  import { existsSync as existsSync56 } from "node:fs";
99659
99900
  import { readFile as readFile53, rm as rm15, unlink as unlink12 } from "node:fs/promises";
99660
- import { homedir as homedir34 } from "node:os";
99901
+ import { homedir as homedir35 } from "node:os";
99661
99902
  import { basename as basename19, join as join124, resolve as resolve29 } from "node:path";
99662
99903
  init_logger();
99663
99904
  init_agents_discovery();
@@ -100096,7 +100337,7 @@ async function processMetadataDeletions(skillSourcePath, installGlobally) {
100096
100337
  }
100097
100338
  if (!sourceMetadata.deletions || sourceMetadata.deletions.length === 0)
100098
100339
  return;
100099
- const claudeDir2 = installGlobally ? join124(homedir34(), ".claude") : join124(process.cwd(), ".claude");
100340
+ const claudeDir2 = installGlobally ? join124(homedir35(), ".claude") : join124(process.cwd(), ".claude");
100100
100341
  if (!existsSync56(claudeDir2))
100101
100342
  return;
100102
100343
  try {
@@ -100252,7 +100493,7 @@ async function migrateCommand(options2) {
100252
100493
  let installGlobally = options2.global ?? false;
100253
100494
  if (options2.global === undefined && !options2.yes) {
100254
100495
  const projectTarget = join124(process.cwd(), ".claude");
100255
- const globalTarget = join124(homedir34(), ".claude");
100496
+ const globalTarget = join124(homedir35(), ".claude");
100256
100497
  const scopeChoice = await ie({
100257
100498
  message: "Installation scope",
100258
100499
  options: [
@@ -100304,7 +100545,7 @@ async function migrateCommand(options2) {
100304
100545
  }
100305
100546
  const providerNames = selectedProviders.map((prov) => import_picocolors29.default.cyan(providers[prov].displayName)).join(", ");
100306
100547
  f2.message(` Providers: ${providerNames}`);
100307
- const targetDir = installGlobally ? join124(homedir34(), ".claude") : join124(process.cwd(), ".claude");
100548
+ const targetDir = installGlobally ? join124(homedir35(), ".claude") : join124(process.cwd(), ".claude");
100308
100549
  f2.message(` Scope: ${installGlobally ? "Global" : "Project"} ${import_picocolors29.default.dim(`-> ${targetDir}`)}`);
100309
100550
  const cmdProviders = getProvidersSupporting("commands");
100310
100551
  const unsupportedCmd = selectedProviders.filter((pv) => !cmdProviders.includes(pv));
@@ -101077,7 +101318,7 @@ Please use only one download method.`);
101077
101318
  // src/commands/plan/plan-command.ts
101078
101319
  init_output_manager();
101079
101320
  import { existsSync as existsSync58, statSync as statSync9 } from "node:fs";
101080
- import { dirname as dirname32, join as join128, parse as parse6, resolve as resolve33 } from "node:path";
101321
+ import { dirname as dirname33, join as join128, parse as parse6, resolve as resolve33 } from "node:path";
101081
101322
 
101082
101323
  // src/commands/plan/plan-read-handlers.ts
101083
101324
  init_plan_parser();
@@ -101085,7 +101326,7 @@ init_logger();
101085
101326
  init_output_manager();
101086
101327
  var import_picocolors31 = __toESM(require_picocolors(), 1);
101087
101328
  import { existsSync as existsSync57, statSync as statSync8 } from "node:fs";
101088
- import { basename as basename20, dirname as dirname31, join as join127, relative as relative21, resolve as resolve31 } from "node:path";
101329
+ import { basename as basename20, dirname as dirname32, join as join127, relative as relative21, resolve as resolve31 } from "node:path";
101089
101330
  async function handleParse(target, options2) {
101090
101331
  const planFile = resolvePlanFile(target);
101091
101332
  if (!planFile) {
@@ -101106,7 +101347,7 @@ async function handleParse(target, options2) {
101106
101347
  console.log(JSON.stringify({ file: relative21(process.cwd(), planFile), frontmatter, phases }, null, 2));
101107
101348
  return;
101108
101349
  }
101109
- const title = typeof frontmatter.title === "string" ? frontmatter.title : basename20(dirname31(planFile));
101350
+ const title = typeof frontmatter.title === "string" ? frontmatter.title : basename20(dirname32(planFile));
101110
101351
  console.log();
101111
101352
  console.log(import_picocolors31.default.bold(` Plan: ${title}`));
101112
101353
  console.log(` File: ${planFile}`);
@@ -101186,14 +101427,14 @@ async function handleStatus(target, options2) {
101186
101427
  try {
101187
101428
  const s = buildPlanSummary(pf);
101188
101429
  const bar = progressBar(s.completed, s.totalPhases);
101189
- const title2 = s.title ?? basename20(dirname31(pf));
101430
+ const title2 = s.title ?? basename20(dirname32(pf));
101190
101431
  console.log(` ${import_picocolors31.default.bold(title2)}`);
101191
101432
  console.log(` ${bar}`);
101192
101433
  if (s.inProgress > 0)
101193
101434
  console.log(` [~] ${s.inProgress} in progress`);
101194
101435
  console.log();
101195
101436
  } catch {
101196
- console.log(` [X] Failed to read: ${basename20(dirname31(pf))}`);
101437
+ console.log(` [X] Failed to read: ${basename20(dirname32(pf))}`);
101197
101438
  console.log();
101198
101439
  }
101199
101440
  }
@@ -101217,7 +101458,7 @@ async function handleStatus(target, options2) {
101217
101458
  console.log(JSON.stringify(summary, null, 2));
101218
101459
  return;
101219
101460
  }
101220
- const title = summary.title ?? basename20(dirname31(planFile));
101461
+ const title = summary.title ?? basename20(dirname32(planFile));
101221
101462
  console.log();
101222
101463
  console.log(import_picocolors31.default.bold(` ${title}`));
101223
101464
  if (summary.status)
@@ -101444,7 +101685,7 @@ function resolvePlanFile(target) {
101444
101685
  const candidate = join128(dir, "plan.md");
101445
101686
  if (existsSync58(candidate))
101446
101687
  return candidate;
101447
- dir = dirname32(dir);
101688
+ dir = dirname33(dir);
101448
101689
  }
101449
101690
  }
101450
101691
  return null;
@@ -102478,7 +102719,7 @@ var import_fs_extra41 = __toESM(require_lib(), 1);
102478
102719
  // src/commands/uninstall/analysis-handler.ts
102479
102720
  init_metadata_migration();
102480
102721
  import { readdirSync as readdirSync7, rmSync as rmSync5 } from "node:fs";
102481
- import { dirname as dirname33, join as join129 } from "node:path";
102722
+ import { dirname as dirname34, join as join129 } from "node:path";
102482
102723
  init_logger();
102483
102724
  init_safe_prompts();
102484
102725
  var import_fs_extra40 = __toESM(require_lib(), 1);
@@ -102500,7 +102741,7 @@ function classifyFileByOwnership(ownership, forceOverwrite, deleteReason) {
102500
102741
  }
102501
102742
  async function cleanupEmptyDirectories3(filePath, installationRoot) {
102502
102743
  let cleaned = 0;
102503
- let currentDir = dirname33(filePath);
102744
+ let currentDir = dirname34(filePath);
102504
102745
  while (currentDir !== installationRoot && currentDir.startsWith(installationRoot)) {
102505
102746
  try {
102506
102747
  const entries = readdirSync7(currentDir);
@@ -102508,7 +102749,7 @@ async function cleanupEmptyDirectories3(filePath, installationRoot) {
102508
102749
  rmSync5(currentDir, { recursive: true });
102509
102750
  cleaned++;
102510
102751
  logger.debug(`Removed empty directory: ${currentDir}`);
102511
- currentDir = dirname33(currentDir);
102752
+ currentDir = dirname34(currentDir);
102512
102753
  } else {
102513
102754
  break;
102514
102755
  }
@@ -104352,7 +104593,7 @@ init_file_io();
104352
104593
  init_logger();
104353
104594
  import { existsSync as existsSync61 } from "node:fs";
104354
104595
  import { mkdir as mkdir34, readFile as readFile55 } from "node:fs/promises";
104355
- import { dirname as dirname34 } from "node:path";
104596
+ import { dirname as dirname35 } from "node:path";
104356
104597
  var PROCESSED_ISSUES_CAP = 500;
104357
104598
  async function readCkJson(projectDir) {
104358
104599
  const configPath = CkConfigManager.getProjectConfigPath(projectDir);
@@ -104382,7 +104623,7 @@ async function loadWatchState(projectDir) {
104382
104623
  }
104383
104624
  async function saveWatchState(projectDir, state) {
104384
104625
  const configPath = CkConfigManager.getProjectConfigPath(projectDir);
104385
- const configDir = dirname34(configPath);
104626
+ const configDir = dirname35(configPath);
104386
104627
  if (!existsSync61(configDir)) {
104387
104628
  await mkdir34(configDir, { recursive: true });
104388
104629
  }
@@ -104549,7 +104790,7 @@ async function scanForRepos(parentDir) {
104549
104790
  init_logger();
104550
104791
  import { spawnSync as spawnSync7 } from "node:child_process";
104551
104792
  import { existsSync as existsSync63 } from "node:fs";
104552
- import { homedir as homedir35 } from "node:os";
104793
+ import { homedir as homedir36 } from "node:os";
104553
104794
  import { join as join135 } from "node:path";
104554
104795
  async function validateSetup(cwd2) {
104555
104796
  const workDir = cwd2 ?? process.cwd();
@@ -104581,7 +104822,7 @@ Run this command from a directory with a GitHub remote.`);
104581
104822
  } catch {
104582
104823
  throw new Error(`Failed to parse repository info: ${ghRepo.stdout}`);
104583
104824
  }
104584
- const skillsPath = join135(homedir35(), ".claude", "skills");
104825
+ const skillsPath = join135(homedir36(), ".claude", "skills");
104585
104826
  const skillsAvailable = existsSync63(skillsPath);
104586
104827
  if (!skillsAvailable) {
104587
104828
  logger.warning(`ClaudeKit Engineer skills not found at ${skillsPath}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudekit-cli",
3
- "version": "3.41.3-dev.2",
3
+ "version": "3.41.3-dev.3",
4
4
  "description": "CLI tool for bootstrapping and updating ClaudeKit projects",
5
5
  "type": "module",
6
6
  "repository": {