claude-code-arcane 2.12.0 → 2.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ import { Command } from "commander";
5
5
 
6
6
  // src/commands/install.ts
7
7
  import fs10 from "fs";
8
- import path12 from "path";
8
+ import path13 from "path";
9
9
  import chalk2 from "chalk";
10
10
  import * as p2 from "@clack/prompts";
11
11
 
@@ -415,7 +415,7 @@ function hint(description, max = 90) {
415
415
 
416
416
  // src/installer.ts
417
417
  import fs7 from "fs";
418
- import path8 from "path";
418
+ import path9 from "path";
419
419
 
420
420
  // src/utils.ts
421
421
  import { fileURLToPath } from "url";
@@ -873,40 +873,37 @@ function computeSourceHashes(packageRoot, skills, rules, agents) {
873
873
  return hashes;
874
874
  }
875
875
 
876
- // src/installer.ts
877
- var SETTINGS_TEMPLATE = {
878
- $schema: "https://json.schemastore.org/claude-code-settings.json",
879
- permissions: { allow: [], deny: [] },
880
- hooks: {
876
+ // src/hook-commands.ts
877
+ import os2 from "os";
878
+ import path8 from "path";
879
+ var FILE_TOOLS_MATCHER = "Write|Edit|MultiEdit|NotebookEdit";
880
+ var LEGACY_COMMAND = /^bash \.claude\/hooks\/([\w.-]+\.sh)$/;
881
+ var ARCANE_COMMAND = /^bash "?(?:\$CLAUDE_PROJECT_DIR\/|[^"]*\/)?\.claude\/hooks\/([\w.-]+\.sh)"?$/;
882
+ var RETIRED_SCRIPTS = /* @__PURE__ */ new Set(["validate-push.sh", "validate-secrets.sh"]);
883
+ function hookCommand(target, script) {
884
+ if (path8.resolve(target) === path8.resolve(os2.homedir())) {
885
+ const absolute = path8.join(target, ".claude", "hooks", script).replace(/\\/g, "/");
886
+ return `bash "${absolute}"`;
887
+ }
888
+ return `bash "$CLAUDE_PROJECT_DIR/.claude/hooks/${script}"`;
889
+ }
890
+ function hook(target, script, timeout) {
891
+ return { type: "command", command: hookCommand(target, script), timeout };
892
+ }
893
+ function defaultHooks(target) {
894
+ const one = (script, timeout) => [
895
+ { matcher: "", hooks: [hook(target, script, timeout)] }
896
+ ];
897
+ return {
881
898
  SessionStart: [
882
899
  {
883
900
  matcher: "",
884
901
  hooks: [
885
- {
886
- type: "command",
887
- command: "bash .claude/hooks/session-start.sh",
888
- timeout: 10
889
- },
890
- {
891
- type: "command",
892
- command: "bash .claude/hooks/detect-division.sh",
893
- timeout: 10
894
- },
895
- {
896
- type: "command",
897
- command: "bash .claude/hooks/detect-unreal.sh",
898
- timeout: 10
899
- },
900
- {
901
- type: "command",
902
- command: "bash .claude/hooks/detect-gaps.sh",
903
- timeout: 10
904
- },
905
- {
906
- type: "command",
907
- command: "bash .claude/hooks/check-update.sh",
908
- timeout: 10
909
- }
902
+ hook(target, "session-start.sh", 10),
903
+ hook(target, "detect-division.sh", 10),
904
+ hook(target, "detect-unreal.sh", 10),
905
+ hook(target, "detect-gaps.sh", 10),
906
+ hook(target, "check-update.sh", 10)
910
907
  ]
911
908
  }
912
909
  ],
@@ -914,114 +911,84 @@ var SETTINGS_TEMPLATE = {
914
911
  {
915
912
  matcher: "Bash",
916
913
  hooks: [
917
- {
918
- type: "command",
919
- command: "bash .claude/hooks/validate-commit.sh",
920
- timeout: 15
921
- },
922
- {
923
- type: "command",
924
- command: "bash .claude/hooks/validate-push.sh",
925
- timeout: 10
926
- },
927
- {
928
- type: "command",
929
- command: "bash .claude/hooks/validate-secrets.sh",
930
- timeout: 10
931
- }
914
+ hook(target, "guard.sh", 10),
915
+ hook(target, "validate-commit.sh", 15)
932
916
  ]
933
- }
917
+ },
918
+ { matcher: FILE_TOOLS_MATCHER, hooks: [hook(target, "guard.sh", 10)] }
934
919
  ],
935
920
  PostToolUse: [
936
921
  {
937
922
  matcher: "Write|Edit",
938
- hooks: [
939
- {
940
- type: "command",
941
- command: "bash .claude/hooks/validate-assets.sh",
942
- timeout: 10
943
- },
944
- {
945
- type: "command",
946
- command: "bash .claude/hooks/validate-skill-change.sh",
947
- timeout: 5
948
- }
949
- ]
950
- }
951
- ],
952
- Notification: [
953
- {
954
- matcher: "",
955
- hooks: [
956
- {
957
- type: "command",
958
- command: "bash .claude/hooks/notify.sh",
959
- timeout: 10
960
- }
961
- ]
962
- }
963
- ],
964
- PreCompact: [
965
- {
966
- matcher: "",
967
- hooks: [
968
- {
969
- type: "command",
970
- command: "bash .claude/hooks/pre-compact.sh",
971
- timeout: 10
972
- }
973
- ]
974
- }
975
- ],
976
- PostCompact: [
977
- {
978
- matcher: "",
979
- hooks: [
980
- {
981
- type: "command",
982
- command: "bash .claude/hooks/post-compact.sh",
983
- timeout: 10
984
- }
985
- ]
923
+ hooks: [hook(target, "validate-assets.sh", 10), hook(target, "validate-skill-change.sh", 5)]
986
924
  }
987
925
  ],
926
+ Notification: one("notify.sh", 10),
927
+ PreCompact: one("pre-compact.sh", 10),
928
+ PostCompact: one("post-compact.sh", 10),
988
929
  Stop: [
989
- {
990
- matcher: "",
991
- hooks: [
992
- {
993
- type: "command",
994
- command: "bash .claude/hooks/session-stop.sh",
995
- timeout: 10
996
- }
997
- ]
998
- }
930
+ { matcher: "", hooks: [hook(target, "session-stop.sh", 10), hook(target, "worktree-stop.sh", 10)] }
999
931
  ],
1000
- SubagentStart: [
1001
- {
1002
- matcher: "",
1003
- hooks: [
1004
- {
1005
- type: "command",
1006
- command: "bash .claude/hooks/log-agent.sh",
1007
- timeout: 5
1008
- }
1009
- ]
1010
- }
1011
- ],
1012
- SubagentStop: [
1013
- {
1014
- matcher: "",
1015
- hooks: [
1016
- {
1017
- type: "command",
1018
- command: "bash .claude/hooks/log-agent-stop.sh",
1019
- timeout: 5
1020
- }
1021
- ]
932
+ SubagentStart: one("log-agent.sh", 5),
933
+ SubagentStop: one("log-agent-stop.sh", 5)
934
+ };
935
+ }
936
+ function healHooks(hooks, target) {
937
+ const changes = [];
938
+ for (const [event, groups] of Object.entries(hooks)) {
939
+ if (!Array.isArray(groups)) continue;
940
+ for (const group of groups) {
941
+ const kept = (group.hooks ?? []).filter((entry) => {
942
+ const script = typeof entry.command === "string" && entry.command.match(ARCANE_COMMAND);
943
+ if (script && RETIRED_SCRIPTS.has(script[1])) {
944
+ changes.push(`retired hook: ${script[1]}`);
945
+ return false;
946
+ }
947
+ return true;
948
+ });
949
+ if (kept.length !== (group.hooks ?? []).length) group.hooks = kept;
950
+ }
951
+ hooks[event] = groups.filter((group) => (group.hooks ?? []).length > 0);
952
+ }
953
+ for (const groups of Object.values(hooks)) {
954
+ for (const group of groups ?? []) {
955
+ for (const entry of group.hooks ?? []) {
956
+ const legacy = typeof entry.command === "string" && entry.command.match(LEGACY_COMMAND);
957
+ if (legacy) {
958
+ entry.command = hookCommand(target, legacy[1]);
959
+ changes.push(`hook path: ${legacy[1]}`);
960
+ }
1022
961
  }
1023
- ]
962
+ }
1024
963
  }
964
+ const pre = hooks.PreToolUse ??= [];
965
+ const hasGuard = (group) => group.hooks?.some((h) => h.command?.includes("guard.sh"));
966
+ const bash = pre.find((g) => g.matcher === "Bash");
967
+ if (!bash) {
968
+ pre.unshift({ matcher: "Bash", hooks: [hook(target, "guard.sh", 10)] });
969
+ changes.push("guard for Bash");
970
+ } else if (!hasGuard(bash)) {
971
+ bash.hooks = [hook(target, "guard.sh", 10), ...bash.hooks ?? []];
972
+ changes.push("guard for Bash");
973
+ }
974
+ if (!pre.some((g) => g.matcher === FILE_TOOLS_MATCHER && hasGuard(g))) {
975
+ pre.push({ matcher: FILE_TOOLS_MATCHER, hooks: [hook(target, "guard.sh", 10)] });
976
+ changes.push("guard for file edits");
977
+ }
978
+ const stop = hooks.Stop ??= [];
979
+ if (!stop.some((g) => g.hooks?.some((h) => h.command?.includes("worktree-stop.sh")))) {
980
+ const group = stop.find((g) => !g.matcher);
981
+ if (group) group.hooks = [...group.hooks ?? [], hook(target, "worktree-stop.sh", 10)];
982
+ else stop.push({ matcher: "", hooks: [hook(target, "worktree-stop.sh", 10)] });
983
+ changes.push("worktree exit on stop");
984
+ }
985
+ return changes;
986
+ }
987
+
988
+ // src/installer.ts
989
+ var SETTINGS_TEMPLATE = {
990
+ $schema: "https://json.schemastore.org/claude-code-settings.json",
991
+ permissions: { allow: [], deny: [] }
1025
992
  };
1026
993
  var Installer = class {
1027
994
  root;
@@ -1035,7 +1002,7 @@ var Installer = class {
1035
1002
  constructor(merged, opts) {
1036
1003
  this.root = opts.contentRoot ?? getPackageRoot();
1037
1004
  this.target = opts.target;
1038
- this.claudeDir = path8.join(opts.target, ".claude");
1005
+ this.claudeDir = path9.join(opts.target, ".claude");
1039
1006
  this.merged = merged;
1040
1007
  this.dryRun = opts.dryRun;
1041
1008
  this.shareFrom = opts.shareFrom;
@@ -1069,7 +1036,7 @@ var Installer = class {
1069
1036
  }
1070
1037
  backup() {
1071
1038
  if (!fs7.existsSync(this.claudeDir)) return;
1072
- const backup = path8.join(this.target, ".claude.bak");
1039
+ const backup = path9.join(this.target, ".claude.bak");
1073
1040
  if (this.dryRun) {
1074
1041
  this.log(" [dry-run] Would backup .claude/ -> .claude.bak/");
1075
1042
  return;
@@ -1083,10 +1050,10 @@ var Installer = class {
1083
1050
  createDirs() {
1084
1051
  if (this.dryRun) return;
1085
1052
  for (const d of ["skills", "rules", "agents", "docs"]) {
1086
- ensureDir(path8.join(this.claudeDir, d));
1053
+ ensureDir(path9.join(this.claudeDir, d));
1087
1054
  }
1088
1055
  if (this.merged.output_styles.length > 0) {
1089
- ensureDir(path8.join(this.claudeDir, "output-styles"));
1056
+ ensureDir(path9.join(this.claudeDir, "output-styles"));
1090
1057
  }
1091
1058
  }
1092
1059
  get hasStatusline() {
@@ -1094,7 +1061,7 @@ var Installer = class {
1094
1061
  }
1095
1062
  copyHooks() {
1096
1063
  this.log("\nHooks:");
1097
- const hooksDir = path8.join(this.claudeDir, "hooks");
1064
+ const hooksDir = path9.join(this.claudeDir, "hooks");
1098
1065
  if (this.dryRun) {
1099
1066
  if (this.shareFrom) {
1100
1067
  this.log(" [dry-run] hooks/ -> symlink from main worktree");
@@ -1105,7 +1072,7 @@ var Installer = class {
1105
1072
  return;
1106
1073
  }
1107
1074
  if (this.shareFrom) {
1108
- const sharedHooks = path8.join(this.shareFrom, ".claude", "hooks");
1075
+ const sharedHooks = path9.join(this.shareFrom, ".claude", "hooks");
1109
1076
  if (fs7.existsSync(sharedHooks)) {
1110
1077
  const result = linkOrCopyDir(sharedHooks, hooksDir, "symlink");
1111
1078
  if (result === "linked") {
@@ -1118,13 +1085,13 @@ var Installer = class {
1118
1085
  return;
1119
1086
  }
1120
1087
  }
1121
- const hooksSrc = path8.join(this.root, "hooks");
1088
+ const hooksSrc = path9.join(this.root, "hooks");
1122
1089
  if (fs7.existsSync(hooksSrc)) {
1123
1090
  ensureDir(hooksDir);
1124
1091
  for (const entry of fs7.readdirSync(hooksSrc, { withFileTypes: true })) {
1125
1092
  if (entry.name === "statusline.sh") continue;
1126
- const src = path8.join(hooksSrc, entry.name);
1127
- const dst = path8.join(hooksDir, entry.name);
1093
+ const src = path9.join(hooksSrc, entry.name);
1094
+ const dst = path9.join(hooksDir, entry.name);
1128
1095
  if (entry.isDirectory()) {
1129
1096
  copyDirSync(src, dst);
1130
1097
  } else {
@@ -1137,11 +1104,11 @@ var Installer = class {
1137
1104
  }
1138
1105
  copyStatusline() {
1139
1106
  if (!this.hasStatusline) return;
1140
- const statusline = path8.join(this.root, "hooks", "statusline.sh");
1107
+ const statusline = path9.join(this.root, "hooks", "statusline.sh");
1141
1108
  if (fs7.existsSync(statusline)) {
1142
1109
  fs7.copyFileSync(
1143
1110
  statusline,
1144
- path8.join(this.claudeDir, "statusline.sh")
1111
+ path9.join(this.claudeDir, "statusline.sh")
1145
1112
  );
1146
1113
  this.log(" [ok] statusline.sh (statusline profile)");
1147
1114
  }
@@ -1149,12 +1116,12 @@ var Installer = class {
1149
1116
  copySkills() {
1150
1117
  this.log("\nSkills:");
1151
1118
  for (const skill of this.merged.skills) {
1152
- const src = path8.join(this.root, "skills", skill);
1119
+ const src = path9.join(this.root, "skills", skill);
1153
1120
  if (!fs7.existsSync(src)) {
1154
1121
  this.log(` WARN: Skill '${skill}' not found in skills/`);
1155
1122
  continue;
1156
1123
  }
1157
- const dst = path8.join(this.claudeDir, "skills", skill);
1124
+ const dst = path9.join(this.claudeDir, "skills", skill);
1158
1125
  if (this.dryRun) {
1159
1126
  this.log(` [dry-run] ${skill}`);
1160
1127
  } else {
@@ -1164,9 +1131,9 @@ var Installer = class {
1164
1131
  }
1165
1132
  const hasGamedev = this.merged.rules.gamedev.length > 0;
1166
1133
  if (hasGamedev) {
1167
- const tplSrc = path8.join(this.root, "templates", "gamedev");
1134
+ const tplSrc = path9.join(this.root, "templates", "gamedev");
1168
1135
  if (fs7.existsSync(tplSrc)) {
1169
- const tplDst = path8.join(this.claudeDir, "skills", "_templates");
1136
+ const tplDst = path9.join(this.claudeDir, "skills", "_templates");
1170
1137
  if (this.dryRun) {
1171
1138
  this.log(" [dry-run] _templates/");
1172
1139
  } else {
@@ -1179,7 +1146,7 @@ var Installer = class {
1179
1146
  copyRules() {
1180
1147
  this.log("\nRules:");
1181
1148
  for (const rule of this.merged.rules.universal) {
1182
- const src = path8.join(this.root, "rules", `${rule}.md`);
1149
+ const src = path9.join(this.root, "rules", `${rule}.md`);
1183
1150
  if (!fs7.existsSync(src)) {
1184
1151
  this.log(` WARN: Rule '${rule}.md' not found`);
1185
1152
  continue;
@@ -1189,13 +1156,13 @@ var Installer = class {
1189
1156
  } else {
1190
1157
  fs7.copyFileSync(
1191
1158
  src,
1192
- path8.join(this.claudeDir, "rules", `${rule}.md`)
1159
+ path9.join(this.claudeDir, "rules", `${rule}.md`)
1193
1160
  );
1194
1161
  this.log(` [ok] ${rule}.md`);
1195
1162
  }
1196
1163
  }
1197
1164
  for (const rule of this.merged.rules.gamedev) {
1198
- const src = path8.join(this.root, "rules", "gamedev", `${rule}.md`);
1165
+ const src = path9.join(this.root, "rules", "gamedev", `${rule}.md`);
1199
1166
  if (!fs7.existsSync(src)) {
1200
1167
  this.log(` WARN: Rule '${rule}.md' not found`);
1201
1168
  continue;
@@ -1205,7 +1172,7 @@ var Installer = class {
1205
1172
  } else {
1206
1173
  fs7.copyFileSync(
1207
1174
  src,
1208
- path8.join(this.claudeDir, "rules", `${rule}.md`)
1175
+ path9.join(this.claudeDir, "rules", `${rule}.md`)
1209
1176
  );
1210
1177
  this.log(` [ok] ${rule}.md`);
1211
1178
  }
@@ -1215,7 +1182,7 @@ var Installer = class {
1215
1182
  if (this.merged.output_styles.length === 0) return;
1216
1183
  this.log("\nOutput styles:");
1217
1184
  for (const style of this.merged.output_styles) {
1218
- const src = path8.join(this.root, "output-styles", `${style}.md`);
1185
+ const src = path9.join(this.root, "output-styles", `${style}.md`);
1219
1186
  if (!fs7.existsSync(src)) {
1220
1187
  this.log(` WARN: Output style '${style}.md' not found`);
1221
1188
  continue;
@@ -1225,7 +1192,7 @@ var Installer = class {
1225
1192
  } else {
1226
1193
  fs7.copyFileSync(
1227
1194
  src,
1228
- path8.join(this.claudeDir, "output-styles", `${style}.md`)
1195
+ path9.join(this.claudeDir, "output-styles", `${style}.md`)
1229
1196
  );
1230
1197
  this.log(` [ok] ${style}.md`);
1231
1198
  }
@@ -1249,8 +1216,8 @@ var Installer = class {
1249
1216
  }
1250
1217
  }
1251
1218
  copyDocs() {
1252
- const docsDir = path8.join(this.claudeDir, "docs");
1253
- const docsSrc = path8.join(this.root, "docs");
1219
+ const docsDir = path9.join(this.claudeDir, "docs");
1220
+ const docsSrc = path9.join(this.root, "docs");
1254
1221
  if (!fs7.existsSync(docsSrc)) return;
1255
1222
  this.log("\nDocs:");
1256
1223
  if (this.dryRun) {
@@ -1262,7 +1229,7 @@ var Installer = class {
1262
1229
  return;
1263
1230
  }
1264
1231
  if (this.shareFrom) {
1265
- const sharedDocs = path8.join(this.shareFrom, ".claude", "docs");
1232
+ const sharedDocs = path9.join(this.shareFrom, ".claude", "docs");
1266
1233
  if (fs7.existsSync(sharedDocs)) {
1267
1234
  const result = linkOrCopyDir(sharedDocs, docsDir, "symlink");
1268
1235
  if (result === "linked") {
@@ -1282,6 +1249,7 @@ var Installer = class {
1282
1249
  const settings = JSON.parse(JSON.stringify(SETTINGS_TEMPLATE));
1283
1250
  settings.permissions.allow = [...this.merged.permissions.allow];
1284
1251
  settings.permissions.deny = [...this.merged.permissions.deny];
1252
+ settings.hooks = defaultHooks(this.target);
1285
1253
  if (this.hasStatusline) {
1286
1254
  settings.statusLine = {
1287
1255
  type: "command",
@@ -1298,7 +1266,7 @@ var Installer = class {
1298
1266
  );
1299
1267
  return;
1300
1268
  }
1301
- const settingsPath = path8.join(this.claudeDir, "settings.json");
1269
+ const settingsPath = path9.join(this.claudeDir, "settings.json");
1302
1270
  fs7.writeFileSync(
1303
1271
  settingsPath,
1304
1272
  JSON.stringify(settings, null, 2) + "\n",
@@ -1308,13 +1276,13 @@ var Installer = class {
1308
1276
  }
1309
1277
  };
1310
1278
  function clean(target) {
1311
- const claudeDir = path8.join(target, ".claude");
1312
- const backup = path8.join(target, ".claude.bak");
1279
+ const claudeDir = path9.join(target, ".claude");
1280
+ const backup = path9.join(target, ".claude.bak");
1313
1281
  if (!fs7.existsSync(claudeDir)) {
1314
1282
  console.log(`No .claude/ found in ${target} \u2014 nothing to clean.`);
1315
1283
  return;
1316
1284
  }
1317
- const manifestFile = path8.join(claudeDir, "arcane-manifest.json");
1285
+ const manifestFile = path9.join(claudeDir, "arcane-manifest.json");
1318
1286
  if (fs7.existsSync(manifestFile)) {
1319
1287
  const data = JSON.parse(fs7.readFileSync(manifestFile, "utf-8"));
1320
1288
  const profiles = (data.profiles ?? []).filter(
@@ -1324,7 +1292,7 @@ function clean(target) {
1324
1292
  console.log(` Installed: ${data.installed_at ?? "unknown"}`);
1325
1293
  const shared = data.worktree?.shared_dirs ?? [];
1326
1294
  for (const dir of shared) {
1327
- const p3 = path8.join(claudeDir, dir);
1295
+ const p3 = path9.join(claudeDir, dir);
1328
1296
  if (isSymlinkOrJunction(p3)) {
1329
1297
  fs7.unlinkSync(p3);
1330
1298
  console.log(` [ok] Unlinked shared ${dir}/`);
@@ -1332,12 +1300,12 @@ function clean(target) {
1332
1300
  }
1333
1301
  }
1334
1302
  for (const sub of ["hooks", "docs", "skills", "agents", "rules"]) {
1335
- const p3 = path8.join(claudeDir, sub);
1303
+ const p3 = path9.join(claudeDir, sub);
1336
1304
  safeRemove(p3);
1337
1305
  }
1338
1306
  const remaining = fs7.readdirSync(claudeDir);
1339
1307
  for (const f of remaining) {
1340
- const p3 = path8.join(claudeDir, f);
1308
+ const p3 = path9.join(claudeDir, f);
1341
1309
  safeRemove(p3);
1342
1310
  }
1343
1311
  if (fs7.existsSync(claudeDir)) {
@@ -1352,24 +1320,24 @@ function clean(target) {
1352
1320
 
1353
1321
  // src/content-source.ts
1354
1322
  import fs9 from "fs";
1355
- import path10 from "path";
1356
- import os2 from "os";
1323
+ import path11 from "path";
1324
+ import os3 from "os";
1357
1325
 
1358
1326
  // src/cache.ts
1359
1327
  import fs8 from "fs";
1360
- import path9 from "path";
1328
+ import path10 from "path";
1361
1329
  function cacheDir() {
1362
- return path9.join(arcaneHome(), "cache");
1330
+ return path10.join(arcaneHome(), "cache");
1363
1331
  }
1364
1332
  function getCachePath(version) {
1365
- return path9.join(cacheDir(), version);
1333
+ return path10.join(cacheDir(), version);
1366
1334
  }
1367
1335
  function isUsableContentRoot(dir) {
1368
- return fs8.existsSync(path9.join(dir, "profiles")) && fs8.existsSync(path9.join(dir, "skills"));
1336
+ return fs8.existsSync(path10.join(dir, "profiles")) && fs8.existsSync(path10.join(dir, "skills"));
1369
1337
  }
1370
1338
  function isCached(version) {
1371
1339
  const versionDir = getCachePath(version);
1372
- return fs8.existsSync(versionDir) && fs8.existsSync(path9.join(versionDir, ".cache-meta.json")) && isUsableContentRoot(versionDir);
1340
+ return fs8.existsSync(versionDir) && fs8.existsSync(path10.join(versionDir, ".cache-meta.json")) && isUsableContentRoot(versionDir);
1373
1341
  }
1374
1342
  function getCachedContentRoot(version) {
1375
1343
  if (!isCached(version)) return null;
@@ -1382,9 +1350,9 @@ function storeInCache(version, contentDir) {
1382
1350
  }
1383
1351
  fs8.mkdirSync(versionDir, { recursive: true });
1384
1352
  for (const dir of ["skills", "rules", "agents", "hooks", "profiles", "templates", "docs", "output-styles"]) {
1385
- const src = path9.join(contentDir, dir);
1353
+ const src = path10.join(contentDir, dir);
1386
1354
  if (fs8.existsSync(src)) {
1387
- copyDirRecursive(src, path9.join(versionDir, dir));
1355
+ copyDirRecursive(src, path10.join(versionDir, dir));
1388
1356
  }
1389
1357
  }
1390
1358
  const meta = {
@@ -1392,7 +1360,7 @@ function storeInCache(version, contentDir) {
1392
1360
  cached_at: (/* @__PURE__ */ new Date()).toISOString()
1393
1361
  };
1394
1362
  fs8.writeFileSync(
1395
- path9.join(versionDir, ".cache-meta.json"),
1363
+ path10.join(versionDir, ".cache-meta.json"),
1396
1364
  JSON.stringify(meta, null, 2) + "\n"
1397
1365
  );
1398
1366
  return versionDir;
@@ -1400,7 +1368,7 @@ function storeInCache(version, contentDir) {
1400
1368
  function pruneCache(keepCount = 3) {
1401
1369
  if (!fs8.existsSync(cacheDir())) return;
1402
1370
  const entries = fs8.readdirSync(cacheDir(), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => {
1403
- const metaPath = path9.join(cacheDir(), e.name, ".cache-meta.json");
1371
+ const metaPath = path10.join(cacheDir(), e.name, ".cache-meta.json");
1404
1372
  let cachedAt = 0;
1405
1373
  if (fs8.existsSync(metaPath)) {
1406
1374
  try {
@@ -1413,13 +1381,13 @@ function pruneCache(keepCount = 3) {
1413
1381
  return { name: e.name, cachedAt };
1414
1382
  }).sort((a, b) => b.cachedAt - a.cachedAt);
1415
1383
  for (const entry of entries.slice(keepCount)) {
1416
- fs8.rmSync(path9.join(cacheDir(), entry.name), { recursive: true, force: true });
1384
+ fs8.rmSync(path10.join(cacheDir(), entry.name), { recursive: true, force: true });
1417
1385
  }
1418
1386
  }
1419
1387
  function listCachedVersions() {
1420
1388
  if (!fs8.existsSync(cacheDir())) return [];
1421
1389
  return fs8.readdirSync(cacheDir(), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => {
1422
- const metaPath = path9.join(cacheDir(), e.name, ".cache-meta.json");
1390
+ const metaPath = path10.join(cacheDir(), e.name, ".cache-meta.json");
1423
1391
  let cachedAt = "unknown";
1424
1392
  if (fs8.existsSync(metaPath)) {
1425
1393
  try {
@@ -1434,8 +1402,8 @@ function listCachedVersions() {
1434
1402
  function copyDirRecursive(src, dest) {
1435
1403
  fs8.mkdirSync(dest, { recursive: true });
1436
1404
  for (const entry of fs8.readdirSync(src, { withFileTypes: true })) {
1437
- const srcPath = path9.join(src, entry.name);
1438
- const destPath = path9.join(dest, entry.name);
1405
+ const srcPath = path10.join(src, entry.name);
1406
+ const destPath = path10.join(dest, entry.name);
1439
1407
  if (entry.isDirectory()) {
1440
1408
  copyDirRecursive(srcPath, destPath);
1441
1409
  } else {
@@ -1455,7 +1423,7 @@ var BundledContentSource = class {
1455
1423
  }
1456
1424
  async isAvailable() {
1457
1425
  const root = getPackageRoot();
1458
- return fs9.existsSync(path10.join(root, "skills"));
1426
+ return fs9.existsSync(path11.join(root, "skills"));
1459
1427
  }
1460
1428
  async getVersion() {
1461
1429
  return getPackageVersion();
@@ -1495,7 +1463,7 @@ var GitHubContentSource = class {
1495
1463
  if (isCached(version)) {
1496
1464
  return getCachedContentRoot(version);
1497
1465
  }
1498
- const tmpDir = fs9.mkdtempSync(path10.join(os2.tmpdir(), "arcane-github-"));
1466
+ const tmpDir = fs9.mkdtempSync(path11.join(os3.tmpdir(), "arcane-github-"));
1499
1467
  try {
1500
1468
  await this.downloadAndExtract(tmpDir);
1501
1469
  const contentRoot = storeInCache(version, this.findExtractedRoot(tmpDir));
@@ -1573,7 +1541,7 @@ var GitHubContentSource = class {
1573
1541
  throw new Error(`Failed to download tarball: ${response.status}`);
1574
1542
  }
1575
1543
  const tar = await import("tar");
1576
- const tarballPath = path10.join(destDir, "repo.tar.gz");
1544
+ const tarballPath = path11.join(destDir, "repo.tar.gz");
1577
1545
  const fileStream = fs9.createWriteStream(tarballPath);
1578
1546
  const reader = response.body.getReader();
1579
1547
  const writable = new WritableStream({
@@ -1604,7 +1572,7 @@ var GitHubContentSource = class {
1604
1572
  (e) => e.isDirectory() && e.name !== "." && e.name !== ".."
1605
1573
  );
1606
1574
  if (dirs.length === 1) {
1607
- return path10.join(tmpDir, dirs[0].name);
1575
+ return path11.join(tmpDir, dirs[0].name);
1608
1576
  }
1609
1577
  return tmpDir;
1610
1578
  }
@@ -1669,10 +1637,10 @@ function findLatestCache() {
1669
1637
  }
1670
1638
 
1671
1639
  // src/registry.ts
1672
- import path11 from "path";
1640
+ import path12 from "path";
1673
1641
  var REGISTRY_VERSION = 1;
1674
1642
  function registryPath() {
1675
- return path11.join(arcaneHome(), "installations.json");
1643
+ return path12.join(arcaneHome(), "installations.json");
1676
1644
  }
1677
1645
  function readRegistryFile() {
1678
1646
  const p3 = registryPath();
@@ -1697,7 +1665,7 @@ function writeRegistryFile(registry) {
1697
1665
  }
1698
1666
  }
1699
1667
  function registerInstallation(target) {
1700
- const abs = path11.resolve(target);
1668
+ const abs = path12.resolve(target);
1701
1669
  const registry = readRegistryFile();
1702
1670
  if (registry.installations.some((e) => e.path === abs)) {
1703
1671
  return;
@@ -1722,7 +1690,7 @@ var PACKAGE_NAME = "claude-code-arcane";
1722
1690
  function isArcaneSourceTree(dir) {
1723
1691
  try {
1724
1692
  const pkg = JSON.parse(
1725
- fs10.readFileSync(path12.join(dir, "package.json"), "utf-8")
1693
+ fs10.readFileSync(path13.join(dir, "package.json"), "utf-8")
1726
1694
  );
1727
1695
  return pkg.name === PACKAGE_NAME;
1728
1696
  } catch {
@@ -1754,8 +1722,8 @@ async function installCommand(profileExpr, opts) {
1754
1722
  quiet: !profileExpr
1755
1723
  });
1756
1724
  const root = await source.getContentRoot();
1757
- const profilesDir = path12.join(root, "profiles");
1758
- const target = path12.resolve(opts.target ?? process.cwd());
1725
+ const profilesDir = path13.join(root, "profiles");
1726
+ const target = path13.resolve(opts.target ?? process.cwd());
1759
1727
  if (await refusesSelfInstall(target, profileExpr, opts)) return;
1760
1728
  if (!profileExpr) {
1761
1729
  const profiles = listProfiles(profilesDir);
@@ -1828,20 +1796,20 @@ Installing profile: ${chalk2.cyan(profileExpr)}`)
1828
1796
 
1829
1797
  // src/commands/add.ts
1830
1798
  import fs12 from "fs";
1831
- import path14 from "path";
1799
+ import path15 from "path";
1832
1800
  import chalk3 from "chalk";
1833
1801
 
1834
1802
  // src/skills-catalog.ts
1835
1803
  import fs11 from "fs";
1836
- import path13 from "path";
1804
+ import path14 from "path";
1837
1805
  var FRONTMATTER_BYTES = 4096;
1838
1806
  function listSkills(skillsDir) {
1839
1807
  if (!fs11.existsSync(skillsDir)) return [];
1840
1808
  return fs11.readdirSync(skillsDir, { withFileTypes: true }).filter(
1841
- (d) => d.isDirectory() && !d.name.startsWith("_") && fs11.existsSync(path13.join(skillsDir, d.name, "SKILL.md"))
1809
+ (d) => d.isDirectory() && !d.name.startsWith("_") && fs11.existsSync(path14.join(skillsDir, d.name, "SKILL.md"))
1842
1810
  ).map((d) => ({
1843
1811
  name: d.name,
1844
- description: readDescription(path13.join(skillsDir, d.name, "SKILL.md"))
1812
+ description: readDescription(path14.join(skillsDir, d.name, "SKILL.md"))
1845
1813
  })).sort((a, b) => a.name.localeCompare(b.name));
1846
1814
  }
1847
1815
  function readDescription(skillFile) {
@@ -1893,15 +1861,15 @@ async function addCommand(items = []) {
1893
1861
  return;
1894
1862
  }
1895
1863
  const selected = await runAddWizard({
1896
- profiles: listProfiles(path14.join(root, "profiles")),
1897
- skills: listSkills(path14.join(root, "skills")),
1864
+ profiles: listProfiles(path15.join(root, "profiles")),
1865
+ skills: listSkills(path15.join(root, "skills")),
1898
1866
  installedProfiles,
1899
1867
  installedSkills: manifest.installed_skills
1900
1868
  });
1901
1869
  if (!selected) return;
1902
1870
  items = selected;
1903
1871
  }
1904
- const claudeDir = path14.join(target, ".claude");
1872
+ const claudeDir = path15.join(target, ".claude");
1905
1873
  const added = [];
1906
1874
  const skipped = [];
1907
1875
  const notFound = [];
@@ -1911,7 +1879,7 @@ async function addCommand(items = []) {
1911
1879
  for (const item of items) {
1912
1880
  if (item.startsWith("+")) {
1913
1881
  const requested = item.slice(1);
1914
- const resolved = resolveProfile(path14.join(root, "profiles"), requested);
1882
+ const resolved = resolveProfile(path15.join(root, "profiles"), requested);
1915
1883
  if (!resolved) {
1916
1884
  console.error(chalk3.red(` Profile '${requested}' not found`));
1917
1885
  continue;
@@ -1926,10 +1894,10 @@ async function addCommand(items = []) {
1926
1894
  }
1927
1895
  for (const rule of profile.rules.universal) {
1928
1896
  if (!manifest.installed_rules.includes(rule)) {
1929
- const src = path14.join(root, "rules", `${rule}.md`);
1897
+ const src = path15.join(root, "rules", `${rule}.md`);
1930
1898
  if (fs12.existsSync(src)) {
1931
- ensureDir(path14.join(claudeDir, "rules"));
1932
- fs12.copyFileSync(src, path14.join(claudeDir, "rules", `${rule}.md`));
1899
+ ensureDir(path15.join(claudeDir, "rules"));
1900
+ fs12.copyFileSync(src, path15.join(claudeDir, "rules", `${rule}.md`));
1933
1901
  manifest.installed_rules.push(rule);
1934
1902
  addedRules.push(rule);
1935
1903
  }
@@ -1937,10 +1905,10 @@ async function addCommand(items = []) {
1937
1905
  }
1938
1906
  for (const rule of profile.rules.gamedev) {
1939
1907
  if (!manifest.installed_rules.includes(rule)) {
1940
- const src = path14.join(root, "rules", "gamedev", `${rule}.md`);
1908
+ const src = path15.join(root, "rules", "gamedev", `${rule}.md`);
1941
1909
  if (fs12.existsSync(src)) {
1942
- ensureDir(path14.join(claudeDir, "rules"));
1943
- fs12.copyFileSync(src, path14.join(claudeDir, "rules", `${rule}.md`));
1910
+ ensureDir(path15.join(claudeDir, "rules"));
1911
+ fs12.copyFileSync(src, path15.join(claudeDir, "rules", `${rule}.md`));
1944
1912
  manifest.installed_rules.push(rule);
1945
1913
  addedRules.push(rule);
1946
1914
  }
@@ -1956,9 +1924,9 @@ async function addCommand(items = []) {
1956
1924
  }
1957
1925
  }
1958
1926
  if (profileName === "statusline") {
1959
- const statuslineSrc = path14.join(root, "hooks", "statusline.sh");
1927
+ const statuslineSrc = path15.join(root, "hooks", "statusline.sh");
1960
1928
  if (fs12.existsSync(statuslineSrc)) {
1961
- fs12.copyFileSync(statuslineSrc, path14.join(claudeDir, "statusline.sh"));
1929
+ fs12.copyFileSync(statuslineSrc, path15.join(claudeDir, "statusline.sh"));
1962
1930
  statuslineAdded = true;
1963
1931
  }
1964
1932
  }
@@ -2041,10 +2009,10 @@ function persistAdditions(target, claudeDir, manifest, addedItems) {
2041
2009
  updateManifestFields(target, updates);
2042
2010
  }
2043
2011
  function printCatalog(root, installedProfiles, installedSkills) {
2044
- const profiles = listProfiles(path14.join(root, "profiles")).filter(
2012
+ const profiles = listProfiles(path15.join(root, "profiles")).filter(
2045
2013
  (pr) => !installedProfiles.includes(pr.name)
2046
2014
  );
2047
- const skills = listSkills(path14.join(root, "skills")).filter(
2015
+ const skills = listSkills(path15.join(root, "skills")).filter(
2048
2016
  (sk) => !installedSkills.includes(sk.name)
2049
2017
  );
2050
2018
  if (profiles.length === 0 && skills.length === 0) {
@@ -2070,14 +2038,14 @@ function printCatalog(root, installedProfiles, installedSkills) {
2070
2038
  }
2071
2039
  function addSkill(root, target, skill, installed) {
2072
2040
  if (installed.includes(skill)) return "skipped";
2073
- const src = path14.join(root, "skills", skill);
2041
+ const src = path15.join(root, "skills", skill);
2074
2042
  if (!fs12.existsSync(src)) return "not-found";
2075
- const dst = path14.join(target, ".claude", "skills", skill);
2043
+ const dst = path15.join(target, ".claude", "skills", skill);
2076
2044
  copyDirSync(src, dst);
2077
2045
  return "added";
2078
2046
  }
2079
2047
  function mergePermissions(claudeDir, newPerms) {
2080
- const settingsPath = path14.join(claudeDir, "settings.json");
2048
+ const settingsPath = path15.join(claudeDir, "settings.json");
2081
2049
  if (!fs12.existsSync(settingsPath)) return;
2082
2050
  const settings = readJsonSync(settingsPath);
2083
2051
  const perms = settings.permissions ?? { allow: [], deny: [] };
@@ -2091,7 +2059,7 @@ function mergePermissions(claudeDir, newPerms) {
2091
2059
  writeJsonSync(settingsPath, settings);
2092
2060
  }
2093
2061
  function addStatuslineToSettings(target, claudeDir) {
2094
- const settingsPath = path14.join(claudeDir, "settings.json");
2062
+ const settingsPath = path15.join(claudeDir, "settings.json");
2095
2063
  if (!fs12.existsSync(settingsPath)) return;
2096
2064
  const settings = readJsonSync(settingsPath);
2097
2065
  if (settings.statusLine) return;
@@ -2104,7 +2072,7 @@ function addStatuslineToSettings(target, claudeDir) {
2104
2072
 
2105
2073
  // src/commands/remove.ts
2106
2074
  import fs13 from "fs";
2107
- import path15 from "path";
2075
+ import path16 from "path";
2108
2076
  import chalk4 from "chalk";
2109
2077
  var CORE_SKILLS = [
2110
2078
  "commit",
@@ -2169,7 +2137,7 @@ async function removeCommand(items, opts = {}) {
2169
2137
  }
2170
2138
  manifest.total_skills = manifest.installed_skills.length;
2171
2139
  manifest.total_rules = manifest.installed_rules.length;
2172
- const mp = path15.join(target, ".claude", "arcane-manifest.json");
2140
+ const mp = path16.join(target, ".claude", "arcane-manifest.json");
2173
2141
  writeJsonSync(mp, manifest);
2174
2142
  const totalRemoved = removedSkills.length + removedAgents.length + removedProfiles.length;
2175
2143
  console.log(chalk4.bold(`
@@ -2190,7 +2158,7 @@ function removeSkill(target, skill, manifest) {
2190
2158
  );
2191
2159
  return "skipped";
2192
2160
  }
2193
- const skillDir = path15.join(target, ".claude", "skills", skill);
2161
+ const skillDir = path16.join(target, ".claude", "skills", skill);
2194
2162
  if (!fs13.existsSync(skillDir)) return "skipped";
2195
2163
  fs13.rmSync(skillDir, { recursive: true, force: true });
2196
2164
  manifest.installed_skills = manifest.installed_skills.filter(
@@ -2212,7 +2180,7 @@ function removeProfile(root, target, profileName, manifest) {
2212
2180
  );
2213
2181
  return { removed: false, skills: [], agents: [] };
2214
2182
  }
2215
- const resolved = resolveProfile(path15.join(root, "profiles"), installedName);
2183
+ const resolved = resolveProfile(path16.join(root, "profiles"), installedName);
2216
2184
  if (!resolved) {
2217
2185
  console.warn(
2218
2186
  chalk4.yellow(` WARNING: Profile '${installedName}.yaml' not found.`)
@@ -2227,7 +2195,7 @@ function removeProfile(root, target, profileName, manifest) {
2227
2195
  const sharedAgents = /* @__PURE__ */ new Set();
2228
2196
  const sharedRules = /* @__PURE__ */ new Set();
2229
2197
  for (const rp of remainingProfiles) {
2230
- const rpResolved = resolveProfile(path15.join(root, "profiles"), rp);
2198
+ const rpResolved = resolveProfile(path16.join(root, "profiles"), rp);
2231
2199
  if (!rpResolved) continue;
2232
2200
  const rpDef = parseProfile(rpResolved.file);
2233
2201
  rpDef.skills.forEach((s) => sharedSkills.add(s));
@@ -2239,7 +2207,7 @@ function removeProfile(root, target, profileName, manifest) {
2239
2207
  for (const skill of profile.skills) {
2240
2208
  if (sharedSkills.has(skill)) continue;
2241
2209
  if (CORE_SKILLS.includes(skill)) continue;
2242
- const skillDir = path15.join(target, ".claude", "skills", skill);
2210
+ const skillDir = path16.join(target, ".claude", "skills", skill);
2243
2211
  if (fs13.existsSync(skillDir)) {
2244
2212
  fs13.rmSync(skillDir, { recursive: true, force: true });
2245
2213
  removedSkills.push(skill);
@@ -2249,7 +2217,7 @@ function removeProfile(root, target, profileName, manifest) {
2249
2217
  );
2250
2218
  }
2251
2219
  const removedAgents = [];
2252
- const claudeDir = path15.join(target, ".claude");
2220
+ const claudeDir = path16.join(target, ".claude");
2253
2221
  for (const entry of profile.agents) {
2254
2222
  if (sharedAgents.has(entry)) continue;
2255
2223
  const { division } = parseAgentEntry(entry);
@@ -2264,7 +2232,7 @@ function removeProfile(root, target, profileName, manifest) {
2264
2232
  ...profile.rules.gamedev
2265
2233
  ]) {
2266
2234
  if (sharedRules.has(rule)) continue;
2267
- const ruleFile = path15.join(target, ".claude", "rules", `${rule}.md`);
2235
+ const ruleFile = path16.join(target, ".claude", "rules", `${rule}.md`);
2268
2236
  if (fs13.existsSync(ruleFile)) {
2269
2237
  fs13.rmSync(ruleFile);
2270
2238
  }
@@ -2273,18 +2241,18 @@ function removeProfile(root, target, profileName, manifest) {
2273
2241
  );
2274
2242
  }
2275
2243
  if (profileName === "statusline") {
2276
- const statuslineFile = path15.join(target, ".claude", "statusline.sh");
2244
+ const statuslineFile = path16.join(target, ".claude", "statusline.sh");
2277
2245
  if (fs13.existsSync(statuslineFile)) {
2278
2246
  fs13.rmSync(statuslineFile);
2279
2247
  }
2280
- removeStatuslineFromSettings(path15.join(target, ".claude"));
2248
+ removeStatuslineFromSettings(path16.join(target, ".claude"));
2281
2249
  }
2282
2250
  manifest.profiles = remainingProfiles;
2283
2251
  manifest.profile_command = remainingProfiles.filter((p3) => p3 !== "core").join("+");
2284
2252
  return { removed: true, skills: removedSkills, agents: removedAgents };
2285
2253
  }
2286
2254
  function removeStatuslineFromSettings(claudeDir) {
2287
- const settingsPath = path15.join(claudeDir, "settings.json");
2255
+ const settingsPath = path16.join(claudeDir, "settings.json");
2288
2256
  if (!fs13.existsSync(settingsPath)) return;
2289
2257
  const settings = JSON.parse(fs13.readFileSync(settingsPath, "utf-8"));
2290
2258
  if (settings.statusLine) {
@@ -2295,13 +2263,13 @@ function removeStatuslineFromSettings(claudeDir) {
2295
2263
 
2296
2264
  // src/commands/list.ts
2297
2265
  import fs14 from "fs";
2298
- import path16 from "path";
2266
+ import path17 from "path";
2299
2267
  import chalk5 from "chalk";
2300
2268
  async function listCommand() {
2301
2269
  const source = await resolveContentSource({ quiet: true });
2302
2270
  const root = await source.getContentRoot();
2303
- const profilesDir = path16.join(root, "profiles");
2304
- const skillsDir = path16.join(root, "skills");
2271
+ const profilesDir = path17.join(root, "profiles");
2272
+ const skillsDir = path17.join(root, "skills");
2305
2273
  const target = process.cwd();
2306
2274
  const manifest = readManifest(target);
2307
2275
  const profiles = listProfiles(profilesDir);
@@ -2386,8 +2354,8 @@ async function statusCommand() {
2386
2354
 
2387
2355
  // src/commands/update.ts
2388
2356
  import fs15 from "fs";
2389
- import path17 from "path";
2390
- import os3 from "os";
2357
+ import path18 from "path";
2358
+ import os4 from "os";
2391
2359
  import chalk7 from "chalk";
2392
2360
 
2393
2361
  // src/self-update.ts
@@ -2470,7 +2438,7 @@ async function updateCommand(opts) {
2470
2438
  registerInstallation(cwd);
2471
2439
  }
2472
2440
  const targets = pruneRegistry().map((e) => e.path);
2473
- const globalTarget = os3.homedir();
2441
+ const globalTarget = os4.homedir();
2474
2442
  if (!process.env.VITEST && fs15.existsSync(manifestPath(globalTarget)) && !targets.includes(globalTarget)) {
2475
2443
  targets.push(globalTarget);
2476
2444
  }
@@ -2540,11 +2508,11 @@ async function updateTarget(target, opts, sharedSource) {
2540
2508
  const currentVersion = await contentSource.getVersion();
2541
2509
  const installedVersion = manifest.source_version ?? manifest.arcane_version;
2542
2510
  const profileNamesForSync = manifest.profile_command.split("+").filter(Boolean);
2543
- const claudeDirForSync = path17.join(target, ".claude");
2511
+ const claudeDirForSync = path18.join(target, ".claude");
2544
2512
  const earlySync = syncUnhashedFiles(
2545
2513
  root,
2546
2514
  claudeDirForSync,
2547
- mergeProfiles(path17.join(root, "profiles"), profileNamesForSync),
2515
+ mergeProfiles(path18.join(root, "profiles"), profileNamesForSync),
2548
2516
  opts.dryRun ?? false
2549
2517
  );
2550
2518
  if (earlySync.length > 0 && !opts.quiet) {
@@ -2578,13 +2546,13 @@ Update available: ${chalk7.red(installedVersion)} -> ${chalk7.green(currentVersi
2578
2546
  );
2579
2547
  }
2580
2548
  const profileNames = manifest.profile_command.split("+").filter(Boolean);
2581
- const profilesDir = path17.join(root, "profiles");
2549
+ const profilesDir = path18.join(root, "profiles");
2582
2550
  const merged = mergeProfiles(profilesDir, profileNames);
2583
2551
  const allRules = [...merged.rules.universal, ...merged.rules.gamedev];
2584
2552
  const sourceHashes = computeSourceHashes(root, merged.skills, allRules, merged.agents);
2585
- const claudeDir = path17.join(target, ".claude");
2553
+ const claudeDir = path18.join(target, ".claude");
2586
2554
  const installedHashes = computeContentHashes(claudeDir);
2587
- if (isSymlink(path17.join(claudeDir, "docs"))) {
2555
+ if (isSymlink(path18.join(claudeDir, "docs"))) {
2588
2556
  sourceHashes.docs = {};
2589
2557
  installedHashes.docs = {};
2590
2558
  }
@@ -2877,18 +2845,18 @@ function applyUpdates(items, root, claudeDir, merged, force) {
2877
2845
  }
2878
2846
  function syncUnhashedFiles(root, claudeDir, merged, dryRun) {
2879
2847
  const targets = [];
2880
- const statuslineDst = path17.join(claudeDir, "statusline.sh");
2848
+ const statuslineDst = path18.join(claudeDir, "statusline.sh");
2881
2849
  if (merged.loaded.includes("statusline") || fs15.existsSync(statuslineDst)) {
2882
2850
  targets.push({
2883
- src: path17.join(root, "hooks", "statusline.sh"),
2851
+ src: path18.join(root, "hooks", "statusline.sh"),
2884
2852
  dst: statuslineDst,
2885
2853
  label: "statusline.sh"
2886
2854
  });
2887
2855
  }
2888
2856
  for (const style of merged.output_styles) {
2889
2857
  targets.push({
2890
- src: path17.join(root, "output-styles", `${style}.md`),
2891
- dst: path17.join(claudeDir, "output-styles", `${style}.md`),
2858
+ src: path18.join(root, "output-styles", `${style}.md`),
2859
+ dst: path18.join(claudeDir, "output-styles", `${style}.md`),
2892
2860
  label: `output-styles/${style}.md`
2893
2861
  });
2894
2862
  }
@@ -2896,21 +2864,21 @@ function syncUnhashedFiles(root, claudeDir, merged, dryRun) {
2896
2864
  const { division, agent } = parseAgentEntry(raw);
2897
2865
  if (!agent) continue;
2898
2866
  targets.push({
2899
- src: path17.join(root, "agents", division, `${agent}.md`),
2900
- dst: path17.join(claudeDir, "agents", division, `${agent}.md`),
2867
+ src: path18.join(root, "agents", division, `${agent}.md`),
2868
+ dst: path18.join(claudeDir, "agents", division, `${agent}.md`),
2901
2869
  label: `agents/${division}/${agent}.md`
2902
2870
  });
2903
2871
  }
2904
2872
  if (merged.rules.gamedev.length > 0) {
2905
- const templatesSrc = path17.join(root, "templates", "gamedev");
2873
+ const templatesSrc = path18.join(root, "templates", "gamedev");
2906
2874
  if (fs15.existsSync(templatesSrc)) {
2907
2875
  for (const rel of fs15.readdirSync(templatesSrc, { recursive: true })) {
2908
- const src = path17.join(templatesSrc, rel);
2876
+ const src = path18.join(templatesSrc, rel);
2909
2877
  if (!fs15.statSync(src).isFile()) continue;
2910
2878
  targets.push({
2911
2879
  src,
2912
- dst: path17.join(claudeDir, "skills", "_templates", rel),
2913
- label: `skills/_templates/${String(rel).split(path17.sep).join("/")}`
2880
+ dst: path18.join(claudeDir, "skills", "_templates", rel),
2881
+ label: `skills/_templates/${String(rel).split(path18.sep).join("/")}`
2914
2882
  });
2915
2883
  }
2916
2884
  }
@@ -2927,10 +2895,10 @@ function syncUnhashedFiles(root, claudeDir, merged, dryRun) {
2927
2895
  if (fs15.existsSync(bak)) fs15.rmSync(bak, { force: true });
2928
2896
  fs15.renameSync(t.dst, bak);
2929
2897
  }
2930
- fs15.mkdirSync(path17.dirname(t.dst), { recursive: true });
2898
+ fs15.mkdirSync(path18.dirname(t.dst), { recursive: true });
2931
2899
  fs15.copyFileSync(t.src, t.dst);
2932
2900
  }
2933
- const settingsPath = path17.join(claudeDir, "settings.json");
2901
+ const settingsPath = path18.join(claudeDir, "settings.json");
2934
2902
  if (fs15.existsSync(settingsPath)) {
2935
2903
  try {
2936
2904
  const settings = JSON.parse(fs15.readFileSync(settingsPath, "utf-8"));
@@ -2941,13 +2909,20 @@ function syncUnhashedFiles(root, claudeDir, merged, dryRun) {
2941
2909
  settings.outputStyle = activeStyle;
2942
2910
  dirty = true;
2943
2911
  }
2944
- const wanted = statuslineCommand(path17.dirname(claudeDir));
2912
+ const wanted = statuslineCommand(path18.dirname(claudeDir));
2945
2913
  const current = settings.statusLine;
2946
2914
  if (wanted !== RELATIVE_STATUSLINE_COMMAND && current && current.command !== wanted) {
2947
2915
  changed.push("settings.json (statusLine path)");
2948
2916
  settings.statusLine = { ...current, command: wanted };
2949
2917
  dirty = true;
2950
2918
  }
2919
+ if (settings.hooks && typeof settings.hooks === "object") {
2920
+ const healed = healHooks(settings.hooks, path18.dirname(claudeDir));
2921
+ if (healed.length > 0) {
2922
+ changed.push(`settings.json (${healed.join(", ")})`);
2923
+ dirty = true;
2924
+ }
2925
+ }
2951
2926
  if (dirty && !dryRun) {
2952
2927
  fs15.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
2953
2928
  `, "utf-8");
@@ -2967,7 +2942,7 @@ function applyItem(item, root, claudeDir) {
2967
2942
  }
2968
2943
  copyDirSync(src, dst);
2969
2944
  } else {
2970
- const dir = path17.dirname(dst);
2945
+ const dir = path18.dirname(dst);
2971
2946
  if (!fs15.existsSync(dir)) {
2972
2947
  fs15.mkdirSync(dir, { recursive: true });
2973
2948
  }
@@ -2977,8 +2952,8 @@ function applyItem(item, root, claudeDir) {
2977
2952
  function quarantineItem(item, claudeDir) {
2978
2953
  const installed = getInstalledPath(item, claudeDir);
2979
2954
  if (!fs15.existsSync(installed)) return;
2980
- const quarantine = path17.join(claudeDir, TRASH_DIR, `${item.type}s`, item.name);
2981
- ensureDir(path17.dirname(quarantine));
2955
+ const quarantine = path18.join(claudeDir, TRASH_DIR, `${item.type}s`, item.name);
2956
+ ensureDir(path18.dirname(quarantine));
2982
2957
  if (fs15.existsSync(quarantine)) {
2983
2958
  fs15.rmSync(quarantine, { recursive: true, force: true });
2984
2959
  }
@@ -2987,18 +2962,18 @@ function quarantineItem(item, claudeDir) {
2987
2962
  function getSourcePath(item, root) {
2988
2963
  switch (item.type) {
2989
2964
  case "skill":
2990
- return path17.join(root, "skills", item.name);
2965
+ return path18.join(root, "skills", item.name);
2991
2966
  case "rule": {
2992
- const direct = path17.join(root, "rules", item.name);
2967
+ const direct = path18.join(root, "rules", item.name);
2993
2968
  if (fs15.existsSync(direct)) return direct;
2994
- return path17.join(root, "rules", "gamedev", item.name);
2969
+ return path18.join(root, "rules", "gamedev", item.name);
2995
2970
  }
2996
2971
  case "agent":
2997
- return path17.join(root, "agents", item.name);
2972
+ return path18.join(root, "agents", item.name);
2998
2973
  case "hook":
2999
- return path17.join(root, "hooks", item.name);
2974
+ return path18.join(root, "hooks", item.name);
3000
2975
  case "doc":
3001
- return path17.join(root, "docs", item.name);
2976
+ return path18.join(root, "docs", item.name);
3002
2977
  default:
3003
2978
  return null;
3004
2979
  }
@@ -3006,15 +2981,15 @@ function getSourcePath(item, root) {
3006
2981
  function getInstalledPath(item, claudeDir) {
3007
2982
  switch (item.type) {
3008
2983
  case "skill":
3009
- return path17.join(claudeDir, "skills", item.name);
2984
+ return path18.join(claudeDir, "skills", item.name);
3010
2985
  case "rule":
3011
- return path17.join(claudeDir, "rules", item.name);
2986
+ return path18.join(claudeDir, "rules", item.name);
3012
2987
  case "agent":
3013
- return path17.join(claudeDir, "agents", item.name);
2988
+ return path18.join(claudeDir, "agents", item.name);
3014
2989
  case "hook":
3015
- return path17.join(claudeDir, "hooks", item.name);
2990
+ return path18.join(claudeDir, "hooks", item.name);
3016
2991
  case "doc":
3017
- return path17.join(claudeDir, "docs", item.name);
2992
+ return path18.join(claudeDir, "docs", item.name);
3018
2993
  }
3019
2994
  }
3020
2995
  function isSymlink(p3) {
@@ -3057,7 +3032,7 @@ async function cleanCommand(opts) {
3057
3032
 
3058
3033
  // src/commands/worktree.ts
3059
3034
  import fs16 from "fs";
3060
- import path18 from "path";
3035
+ import path19 from "path";
3061
3036
  import { execSync as execSync2 } from "child_process";
3062
3037
  import chalk9 from "chalk";
3063
3038
  async function worktreeCommand(branch, opts) {
@@ -3068,7 +3043,7 @@ async function worktreeCommand(branch, opts) {
3068
3043
  process.exit(1);
3069
3044
  }
3070
3045
  const repoRoot = wtInfo.isWorktree ? wtInfo.mainWorktreePath : cwd;
3071
- const worktreePath = opts.path ? path18.resolve(opts.path) : defaultWorktreePath(repoRoot, branch);
3046
+ const worktreePath = opts.path ? path19.resolve(opts.path) : defaultWorktreePath(repoRoot, branch);
3072
3047
  if (fs16.existsSync(worktreePath)) {
3073
3048
  console.error(
3074
3049
  chalk9.red(`Path already exists: ${worktreePath}`)
@@ -3116,7 +3091,7 @@ async function worktreeCommand(branch, opts) {
3116
3091
  }
3117
3092
  console.log(" Installing Arcane...");
3118
3093
  const root = getPackageRoot();
3119
- const profilesDir = path18.join(root, "profiles");
3094
+ const profilesDir = path19.join(root, "profiles");
3120
3095
  const profileNames = profileExpr.split("+").filter(Boolean);
3121
3096
  const merged = mergeProfiles(profilesDir, profileNames);
3122
3097
  const shareFrom = opts.share !== false ? cwd : void 0;
@@ -3152,7 +3127,7 @@ async function worktreeCommand(branch, opts) {
3152
3127
  }
3153
3128
  }
3154
3129
  if (opts.isolate) {
3155
- const isolateScript = path18.join(
3130
+ const isolateScript = path19.join(
3156
3131
  worktreePath,
3157
3132
  ".claude",
3158
3133
  "skills",
@@ -3194,12 +3169,12 @@ async function worktreeCommand(branch, opts) {
3194
3169
 
3195
3170
  // src/commands/global.ts
3196
3171
  import fs17 from "fs";
3197
- import path19 from "path";
3198
- import os4 from "os";
3172
+ import path20 from "path";
3173
+ import os5 from "os";
3199
3174
  import chalk10 from "chalk";
3200
- var CLAUDE_HOME = path19.join(os4.homedir(), ".claude");
3201
- var SCRIPTS_DIR = path19.join(CLAUDE_HOME, "scripts", "worktree-isolation");
3202
- var SETTINGS_PATH = path19.join(CLAUDE_HOME, "settings.json");
3175
+ var CLAUDE_HOME = path20.join(os5.homedir(), ".claude");
3176
+ var SCRIPTS_DIR = path20.join(CLAUDE_HOME, "scripts", "worktree-isolation");
3177
+ var SETTINGS_PATH = path20.join(CLAUDE_HOME, "settings.json");
3203
3178
  async function globalCommand(opts) {
3204
3179
  if (opts.status) {
3205
3180
  showStatus();
@@ -3229,7 +3204,7 @@ function settingsParseable() {
3229
3204
  function showStatus() {
3230
3205
  console.log(chalk10.bold("\n=== Arcane Global Status ===\n"));
3231
3206
  const hasScripts = fs17.existsSync(
3232
- path19.join(SCRIPTS_DIR, "apply.py")
3207
+ path20.join(SCRIPTS_DIR, "apply.py")
3233
3208
  );
3234
3209
  console.log(
3235
3210
  ` Worktree scripts: ${hasScripts ? chalk10.green("installed") : chalk10.dim("not installed")}`
@@ -3247,7 +3222,7 @@ function showStatus() {
3247
3222
  function installGlobal() {
3248
3223
  console.log(chalk10.bold("\nInstalling Arcane global hooks...\n"));
3249
3224
  const root = getPackageRoot();
3250
- const scriptsSrc = path19.join(
3225
+ const scriptsSrc = path20.join(
3251
3226
  root,
3252
3227
  "skills",
3253
3228
  "worktree-isolation",
@@ -3261,8 +3236,8 @@ function installGlobal() {
3261
3236
  }
3262
3237
  fs17.mkdirSync(SCRIPTS_DIR, { recursive: true });
3263
3238
  for (const file of ["apply.py", "lib.py"]) {
3264
- const src = path19.join(scriptsSrc, file);
3265
- const dst = path19.join(SCRIPTS_DIR, file);
3239
+ const src = path20.join(scriptsSrc, file);
3240
+ const dst = path20.join(SCRIPTS_DIR, file);
3266
3241
  if (!fs17.existsSync(src)) {
3267
3242
  console.warn(chalk10.yellow(` WARN: ${file} not found, skipping`));
3268
3243
  continue;
@@ -3319,7 +3294,7 @@ function readSettings() {
3319
3294
  }
3320
3295
  }
3321
3296
  function writeSettings(settings) {
3322
- fs17.mkdirSync(path19.dirname(SETTINGS_PATH), { recursive: true });
3297
+ fs17.mkdirSync(path20.dirname(SETTINGS_PATH), { recursive: true });
3323
3298
  fs17.writeFileSync(
3324
3299
  SETTINGS_PATH,
3325
3300
  JSON.stringify(settings, null, 2) + "\n",
@@ -3331,8 +3306,8 @@ function isHookInstalled() {
3331
3306
  const hooks = settings.hooks ?? {};
3332
3307
  const sessionStart = hooks.SessionStart ?? [];
3333
3308
  for (const entry of sessionStart) {
3334
- for (const hook of entry.hooks ?? []) {
3335
- if (hook.command?.includes("worktree-isolation/apply.py") && hook.command?.includes("--quiet")) {
3309
+ for (const hook2 of entry.hooks ?? []) {
3310
+ if (hook2.command?.includes("worktree-isolation/apply.py") && hook2.command?.includes("--quiet")) {
3336
3311
  return true;
3337
3312
  }
3338
3313
  }
@@ -3378,10 +3353,10 @@ function removeHookFromSettings() {
3378
3353
 
3379
3354
  // src/update-check.ts
3380
3355
  import fs18 from "fs";
3381
- import path20 from "path";
3356
+ import path21 from "path";
3382
3357
  import chalk11 from "chalk";
3383
3358
  function checkFile() {
3384
- return path20.join(arcaneHome(), "last-check.json");
3359
+ return path21.join(arcaneHome(), "last-check.json");
3385
3360
  }
3386
3361
  var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
3387
3362
  var GITHUB_OWNER2 = "SebastianLuser";
@@ -3519,7 +3494,7 @@ function readCachedCheck() {
3519
3494
  function writeCachedCheck(result) {
3520
3495
  try {
3521
3496
  const file = checkFile();
3522
- const dir = path20.dirname(file);
3497
+ const dir = path21.dirname(file);
3523
3498
  if (!fs18.existsSync(dir)) {
3524
3499
  fs18.mkdirSync(dir, { recursive: true });
3525
3500
  }