claude-code-arcane 2.11.0 → 2.13.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,75 @@ 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
- ]
923
+ hooks: [hook(target, "validate-assets.sh", 10), hook(target, "validate-skill-change.sh", 5)]
974
924
  }
975
925
  ],
976
- PostCompact: [
977
- {
978
- matcher: "",
979
- hooks: [
980
- {
981
- type: "command",
982
- command: "bash .claude/hooks/post-compact.sh",
983
- timeout: 10
984
- }
985
- ]
986
- }
987
- ],
988
- Stop: [
989
- {
990
- matcher: "",
991
- hooks: [
992
- {
993
- type: "command",
994
- command: "bash .claude/hooks/session-stop.sh",
995
- timeout: 10
996
- }
997
- ]
998
- }
999
- ],
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
- ]
926
+ Notification: one("notify.sh", 10),
927
+ PreCompact: one("pre-compact.sh", 10),
928
+ PostCompact: one("post-compact.sh", 10),
929
+ Stop: one("session-stop.sh", 10),
930
+ SubagentStart: one("log-agent.sh", 5),
931
+ SubagentStop: one("log-agent-stop.sh", 5)
932
+ };
933
+ }
934
+ function healHooks(hooks, target) {
935
+ const changes = [];
936
+ for (const [event, groups] of Object.entries(hooks)) {
937
+ if (!Array.isArray(groups)) continue;
938
+ for (const group of groups) {
939
+ const kept = (group.hooks ?? []).filter((entry) => {
940
+ const script = typeof entry.command === "string" && entry.command.match(ARCANE_COMMAND);
941
+ if (script && RETIRED_SCRIPTS.has(script[1])) {
942
+ changes.push(`retired hook: ${script[1]}`);
943
+ return false;
944
+ }
945
+ return true;
946
+ });
947
+ if (kept.length !== (group.hooks ?? []).length) group.hooks = kept;
948
+ }
949
+ hooks[event] = groups.filter((group) => (group.hooks ?? []).length > 0);
950
+ }
951
+ for (const groups of Object.values(hooks)) {
952
+ for (const group of groups ?? []) {
953
+ for (const entry of group.hooks ?? []) {
954
+ const legacy = typeof entry.command === "string" && entry.command.match(LEGACY_COMMAND);
955
+ if (legacy) {
956
+ entry.command = hookCommand(target, legacy[1]);
957
+ changes.push(`hook path: ${legacy[1]}`);
958
+ }
1022
959
  }
1023
- ]
960
+ }
961
+ }
962
+ const pre = hooks.PreToolUse ??= [];
963
+ const hasGuard = (group) => group.hooks?.some((h) => h.command?.includes("guard.sh"));
964
+ const bash = pre.find((g) => g.matcher === "Bash");
965
+ if (!bash) {
966
+ pre.unshift({ matcher: "Bash", hooks: [hook(target, "guard.sh", 10)] });
967
+ changes.push("guard for Bash");
968
+ } else if (!hasGuard(bash)) {
969
+ bash.hooks = [hook(target, "guard.sh", 10), ...bash.hooks ?? []];
970
+ changes.push("guard for Bash");
1024
971
  }
972
+ if (!pre.some((g) => g.matcher === FILE_TOOLS_MATCHER && hasGuard(g))) {
973
+ pre.push({ matcher: FILE_TOOLS_MATCHER, hooks: [hook(target, "guard.sh", 10)] });
974
+ changes.push("guard for file edits");
975
+ }
976
+ return changes;
977
+ }
978
+
979
+ // src/installer.ts
980
+ var SETTINGS_TEMPLATE = {
981
+ $schema: "https://json.schemastore.org/claude-code-settings.json",
982
+ permissions: { allow: [], deny: [] }
1025
983
  };
1026
984
  var Installer = class {
1027
985
  root;
@@ -1035,7 +993,7 @@ var Installer = class {
1035
993
  constructor(merged, opts) {
1036
994
  this.root = opts.contentRoot ?? getPackageRoot();
1037
995
  this.target = opts.target;
1038
- this.claudeDir = path8.join(opts.target, ".claude");
996
+ this.claudeDir = path9.join(opts.target, ".claude");
1039
997
  this.merged = merged;
1040
998
  this.dryRun = opts.dryRun;
1041
999
  this.shareFrom = opts.shareFrom;
@@ -1069,7 +1027,7 @@ var Installer = class {
1069
1027
  }
1070
1028
  backup() {
1071
1029
  if (!fs7.existsSync(this.claudeDir)) return;
1072
- const backup = path8.join(this.target, ".claude.bak");
1030
+ const backup = path9.join(this.target, ".claude.bak");
1073
1031
  if (this.dryRun) {
1074
1032
  this.log(" [dry-run] Would backup .claude/ -> .claude.bak/");
1075
1033
  return;
@@ -1083,10 +1041,10 @@ var Installer = class {
1083
1041
  createDirs() {
1084
1042
  if (this.dryRun) return;
1085
1043
  for (const d of ["skills", "rules", "agents", "docs"]) {
1086
- ensureDir(path8.join(this.claudeDir, d));
1044
+ ensureDir(path9.join(this.claudeDir, d));
1087
1045
  }
1088
1046
  if (this.merged.output_styles.length > 0) {
1089
- ensureDir(path8.join(this.claudeDir, "output-styles"));
1047
+ ensureDir(path9.join(this.claudeDir, "output-styles"));
1090
1048
  }
1091
1049
  }
1092
1050
  get hasStatusline() {
@@ -1094,7 +1052,7 @@ var Installer = class {
1094
1052
  }
1095
1053
  copyHooks() {
1096
1054
  this.log("\nHooks:");
1097
- const hooksDir = path8.join(this.claudeDir, "hooks");
1055
+ const hooksDir = path9.join(this.claudeDir, "hooks");
1098
1056
  if (this.dryRun) {
1099
1057
  if (this.shareFrom) {
1100
1058
  this.log(" [dry-run] hooks/ -> symlink from main worktree");
@@ -1105,7 +1063,7 @@ var Installer = class {
1105
1063
  return;
1106
1064
  }
1107
1065
  if (this.shareFrom) {
1108
- const sharedHooks = path8.join(this.shareFrom, ".claude", "hooks");
1066
+ const sharedHooks = path9.join(this.shareFrom, ".claude", "hooks");
1109
1067
  if (fs7.existsSync(sharedHooks)) {
1110
1068
  const result = linkOrCopyDir(sharedHooks, hooksDir, "symlink");
1111
1069
  if (result === "linked") {
@@ -1118,13 +1076,13 @@ var Installer = class {
1118
1076
  return;
1119
1077
  }
1120
1078
  }
1121
- const hooksSrc = path8.join(this.root, "hooks");
1079
+ const hooksSrc = path9.join(this.root, "hooks");
1122
1080
  if (fs7.existsSync(hooksSrc)) {
1123
1081
  ensureDir(hooksDir);
1124
1082
  for (const entry of fs7.readdirSync(hooksSrc, { withFileTypes: true })) {
1125
1083
  if (entry.name === "statusline.sh") continue;
1126
- const src = path8.join(hooksSrc, entry.name);
1127
- const dst = path8.join(hooksDir, entry.name);
1084
+ const src = path9.join(hooksSrc, entry.name);
1085
+ const dst = path9.join(hooksDir, entry.name);
1128
1086
  if (entry.isDirectory()) {
1129
1087
  copyDirSync(src, dst);
1130
1088
  } else {
@@ -1137,11 +1095,11 @@ var Installer = class {
1137
1095
  }
1138
1096
  copyStatusline() {
1139
1097
  if (!this.hasStatusline) return;
1140
- const statusline = path8.join(this.root, "hooks", "statusline.sh");
1098
+ const statusline = path9.join(this.root, "hooks", "statusline.sh");
1141
1099
  if (fs7.existsSync(statusline)) {
1142
1100
  fs7.copyFileSync(
1143
1101
  statusline,
1144
- path8.join(this.claudeDir, "statusline.sh")
1102
+ path9.join(this.claudeDir, "statusline.sh")
1145
1103
  );
1146
1104
  this.log(" [ok] statusline.sh (statusline profile)");
1147
1105
  }
@@ -1149,12 +1107,12 @@ var Installer = class {
1149
1107
  copySkills() {
1150
1108
  this.log("\nSkills:");
1151
1109
  for (const skill of this.merged.skills) {
1152
- const src = path8.join(this.root, "skills", skill);
1110
+ const src = path9.join(this.root, "skills", skill);
1153
1111
  if (!fs7.existsSync(src)) {
1154
1112
  this.log(` WARN: Skill '${skill}' not found in skills/`);
1155
1113
  continue;
1156
1114
  }
1157
- const dst = path8.join(this.claudeDir, "skills", skill);
1115
+ const dst = path9.join(this.claudeDir, "skills", skill);
1158
1116
  if (this.dryRun) {
1159
1117
  this.log(` [dry-run] ${skill}`);
1160
1118
  } else {
@@ -1164,9 +1122,9 @@ var Installer = class {
1164
1122
  }
1165
1123
  const hasGamedev = this.merged.rules.gamedev.length > 0;
1166
1124
  if (hasGamedev) {
1167
- const tplSrc = path8.join(this.root, "templates", "gamedev");
1125
+ const tplSrc = path9.join(this.root, "templates", "gamedev");
1168
1126
  if (fs7.existsSync(tplSrc)) {
1169
- const tplDst = path8.join(this.claudeDir, "skills", "_templates");
1127
+ const tplDst = path9.join(this.claudeDir, "skills", "_templates");
1170
1128
  if (this.dryRun) {
1171
1129
  this.log(" [dry-run] _templates/");
1172
1130
  } else {
@@ -1179,7 +1137,7 @@ var Installer = class {
1179
1137
  copyRules() {
1180
1138
  this.log("\nRules:");
1181
1139
  for (const rule of this.merged.rules.universal) {
1182
- const src = path8.join(this.root, "rules", `${rule}.md`);
1140
+ const src = path9.join(this.root, "rules", `${rule}.md`);
1183
1141
  if (!fs7.existsSync(src)) {
1184
1142
  this.log(` WARN: Rule '${rule}.md' not found`);
1185
1143
  continue;
@@ -1189,13 +1147,13 @@ var Installer = class {
1189
1147
  } else {
1190
1148
  fs7.copyFileSync(
1191
1149
  src,
1192
- path8.join(this.claudeDir, "rules", `${rule}.md`)
1150
+ path9.join(this.claudeDir, "rules", `${rule}.md`)
1193
1151
  );
1194
1152
  this.log(` [ok] ${rule}.md`);
1195
1153
  }
1196
1154
  }
1197
1155
  for (const rule of this.merged.rules.gamedev) {
1198
- const src = path8.join(this.root, "rules", "gamedev", `${rule}.md`);
1156
+ const src = path9.join(this.root, "rules", "gamedev", `${rule}.md`);
1199
1157
  if (!fs7.existsSync(src)) {
1200
1158
  this.log(` WARN: Rule '${rule}.md' not found`);
1201
1159
  continue;
@@ -1205,7 +1163,7 @@ var Installer = class {
1205
1163
  } else {
1206
1164
  fs7.copyFileSync(
1207
1165
  src,
1208
- path8.join(this.claudeDir, "rules", `${rule}.md`)
1166
+ path9.join(this.claudeDir, "rules", `${rule}.md`)
1209
1167
  );
1210
1168
  this.log(` [ok] ${rule}.md`);
1211
1169
  }
@@ -1215,7 +1173,7 @@ var Installer = class {
1215
1173
  if (this.merged.output_styles.length === 0) return;
1216
1174
  this.log("\nOutput styles:");
1217
1175
  for (const style of this.merged.output_styles) {
1218
- const src = path8.join(this.root, "output-styles", `${style}.md`);
1176
+ const src = path9.join(this.root, "output-styles", `${style}.md`);
1219
1177
  if (!fs7.existsSync(src)) {
1220
1178
  this.log(` WARN: Output style '${style}.md' not found`);
1221
1179
  continue;
@@ -1225,7 +1183,7 @@ var Installer = class {
1225
1183
  } else {
1226
1184
  fs7.copyFileSync(
1227
1185
  src,
1228
- path8.join(this.claudeDir, "output-styles", `${style}.md`)
1186
+ path9.join(this.claudeDir, "output-styles", `${style}.md`)
1229
1187
  );
1230
1188
  this.log(` [ok] ${style}.md`);
1231
1189
  }
@@ -1249,8 +1207,8 @@ var Installer = class {
1249
1207
  }
1250
1208
  }
1251
1209
  copyDocs() {
1252
- const docsDir = path8.join(this.claudeDir, "docs");
1253
- const docsSrc = path8.join(this.root, "docs");
1210
+ const docsDir = path9.join(this.claudeDir, "docs");
1211
+ const docsSrc = path9.join(this.root, "docs");
1254
1212
  if (!fs7.existsSync(docsSrc)) return;
1255
1213
  this.log("\nDocs:");
1256
1214
  if (this.dryRun) {
@@ -1262,7 +1220,7 @@ var Installer = class {
1262
1220
  return;
1263
1221
  }
1264
1222
  if (this.shareFrom) {
1265
- const sharedDocs = path8.join(this.shareFrom, ".claude", "docs");
1223
+ const sharedDocs = path9.join(this.shareFrom, ".claude", "docs");
1266
1224
  if (fs7.existsSync(sharedDocs)) {
1267
1225
  const result = linkOrCopyDir(sharedDocs, docsDir, "symlink");
1268
1226
  if (result === "linked") {
@@ -1282,6 +1240,7 @@ var Installer = class {
1282
1240
  const settings = JSON.parse(JSON.stringify(SETTINGS_TEMPLATE));
1283
1241
  settings.permissions.allow = [...this.merged.permissions.allow];
1284
1242
  settings.permissions.deny = [...this.merged.permissions.deny];
1243
+ settings.hooks = defaultHooks(this.target);
1285
1244
  if (this.hasStatusline) {
1286
1245
  settings.statusLine = {
1287
1246
  type: "command",
@@ -1298,7 +1257,7 @@ var Installer = class {
1298
1257
  );
1299
1258
  return;
1300
1259
  }
1301
- const settingsPath = path8.join(this.claudeDir, "settings.json");
1260
+ const settingsPath = path9.join(this.claudeDir, "settings.json");
1302
1261
  fs7.writeFileSync(
1303
1262
  settingsPath,
1304
1263
  JSON.stringify(settings, null, 2) + "\n",
@@ -1308,13 +1267,13 @@ var Installer = class {
1308
1267
  }
1309
1268
  };
1310
1269
  function clean(target) {
1311
- const claudeDir = path8.join(target, ".claude");
1312
- const backup = path8.join(target, ".claude.bak");
1270
+ const claudeDir = path9.join(target, ".claude");
1271
+ const backup = path9.join(target, ".claude.bak");
1313
1272
  if (!fs7.existsSync(claudeDir)) {
1314
1273
  console.log(`No .claude/ found in ${target} \u2014 nothing to clean.`);
1315
1274
  return;
1316
1275
  }
1317
- const manifestFile = path8.join(claudeDir, "arcane-manifest.json");
1276
+ const manifestFile = path9.join(claudeDir, "arcane-manifest.json");
1318
1277
  if (fs7.existsSync(manifestFile)) {
1319
1278
  const data = JSON.parse(fs7.readFileSync(manifestFile, "utf-8"));
1320
1279
  const profiles = (data.profiles ?? []).filter(
@@ -1324,7 +1283,7 @@ function clean(target) {
1324
1283
  console.log(` Installed: ${data.installed_at ?? "unknown"}`);
1325
1284
  const shared = data.worktree?.shared_dirs ?? [];
1326
1285
  for (const dir of shared) {
1327
- const p3 = path8.join(claudeDir, dir);
1286
+ const p3 = path9.join(claudeDir, dir);
1328
1287
  if (isSymlinkOrJunction(p3)) {
1329
1288
  fs7.unlinkSync(p3);
1330
1289
  console.log(` [ok] Unlinked shared ${dir}/`);
@@ -1332,12 +1291,12 @@ function clean(target) {
1332
1291
  }
1333
1292
  }
1334
1293
  for (const sub of ["hooks", "docs", "skills", "agents", "rules"]) {
1335
- const p3 = path8.join(claudeDir, sub);
1294
+ const p3 = path9.join(claudeDir, sub);
1336
1295
  safeRemove(p3);
1337
1296
  }
1338
1297
  const remaining = fs7.readdirSync(claudeDir);
1339
1298
  for (const f of remaining) {
1340
- const p3 = path8.join(claudeDir, f);
1299
+ const p3 = path9.join(claudeDir, f);
1341
1300
  safeRemove(p3);
1342
1301
  }
1343
1302
  if (fs7.existsSync(claudeDir)) {
@@ -1352,24 +1311,24 @@ function clean(target) {
1352
1311
 
1353
1312
  // src/content-source.ts
1354
1313
  import fs9 from "fs";
1355
- import path10 from "path";
1356
- import os2 from "os";
1314
+ import path11 from "path";
1315
+ import os3 from "os";
1357
1316
 
1358
1317
  // src/cache.ts
1359
1318
  import fs8 from "fs";
1360
- import path9 from "path";
1319
+ import path10 from "path";
1361
1320
  function cacheDir() {
1362
- return path9.join(arcaneHome(), "cache");
1321
+ return path10.join(arcaneHome(), "cache");
1363
1322
  }
1364
1323
  function getCachePath(version) {
1365
- return path9.join(cacheDir(), version);
1324
+ return path10.join(cacheDir(), version);
1366
1325
  }
1367
1326
  function isUsableContentRoot(dir) {
1368
- return fs8.existsSync(path9.join(dir, "profiles")) && fs8.existsSync(path9.join(dir, "skills"));
1327
+ return fs8.existsSync(path10.join(dir, "profiles")) && fs8.existsSync(path10.join(dir, "skills"));
1369
1328
  }
1370
1329
  function isCached(version) {
1371
1330
  const versionDir = getCachePath(version);
1372
- return fs8.existsSync(versionDir) && fs8.existsSync(path9.join(versionDir, ".cache-meta.json")) && isUsableContentRoot(versionDir);
1331
+ return fs8.existsSync(versionDir) && fs8.existsSync(path10.join(versionDir, ".cache-meta.json")) && isUsableContentRoot(versionDir);
1373
1332
  }
1374
1333
  function getCachedContentRoot(version) {
1375
1334
  if (!isCached(version)) return null;
@@ -1382,9 +1341,9 @@ function storeInCache(version, contentDir) {
1382
1341
  }
1383
1342
  fs8.mkdirSync(versionDir, { recursive: true });
1384
1343
  for (const dir of ["skills", "rules", "agents", "hooks", "profiles", "templates", "docs", "output-styles"]) {
1385
- const src = path9.join(contentDir, dir);
1344
+ const src = path10.join(contentDir, dir);
1386
1345
  if (fs8.existsSync(src)) {
1387
- copyDirRecursive(src, path9.join(versionDir, dir));
1346
+ copyDirRecursive(src, path10.join(versionDir, dir));
1388
1347
  }
1389
1348
  }
1390
1349
  const meta = {
@@ -1392,7 +1351,7 @@ function storeInCache(version, contentDir) {
1392
1351
  cached_at: (/* @__PURE__ */ new Date()).toISOString()
1393
1352
  };
1394
1353
  fs8.writeFileSync(
1395
- path9.join(versionDir, ".cache-meta.json"),
1354
+ path10.join(versionDir, ".cache-meta.json"),
1396
1355
  JSON.stringify(meta, null, 2) + "\n"
1397
1356
  );
1398
1357
  return versionDir;
@@ -1400,7 +1359,7 @@ function storeInCache(version, contentDir) {
1400
1359
  function pruneCache(keepCount = 3) {
1401
1360
  if (!fs8.existsSync(cacheDir())) return;
1402
1361
  const entries = fs8.readdirSync(cacheDir(), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => {
1403
- const metaPath = path9.join(cacheDir(), e.name, ".cache-meta.json");
1362
+ const metaPath = path10.join(cacheDir(), e.name, ".cache-meta.json");
1404
1363
  let cachedAt = 0;
1405
1364
  if (fs8.existsSync(metaPath)) {
1406
1365
  try {
@@ -1413,13 +1372,13 @@ function pruneCache(keepCount = 3) {
1413
1372
  return { name: e.name, cachedAt };
1414
1373
  }).sort((a, b) => b.cachedAt - a.cachedAt);
1415
1374
  for (const entry of entries.slice(keepCount)) {
1416
- fs8.rmSync(path9.join(cacheDir(), entry.name), { recursive: true, force: true });
1375
+ fs8.rmSync(path10.join(cacheDir(), entry.name), { recursive: true, force: true });
1417
1376
  }
1418
1377
  }
1419
1378
  function listCachedVersions() {
1420
1379
  if (!fs8.existsSync(cacheDir())) return [];
1421
1380
  return fs8.readdirSync(cacheDir(), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => {
1422
- const metaPath = path9.join(cacheDir(), e.name, ".cache-meta.json");
1381
+ const metaPath = path10.join(cacheDir(), e.name, ".cache-meta.json");
1423
1382
  let cachedAt = "unknown";
1424
1383
  if (fs8.existsSync(metaPath)) {
1425
1384
  try {
@@ -1434,8 +1393,8 @@ function listCachedVersions() {
1434
1393
  function copyDirRecursive(src, dest) {
1435
1394
  fs8.mkdirSync(dest, { recursive: true });
1436
1395
  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);
1396
+ const srcPath = path10.join(src, entry.name);
1397
+ const destPath = path10.join(dest, entry.name);
1439
1398
  if (entry.isDirectory()) {
1440
1399
  copyDirRecursive(srcPath, destPath);
1441
1400
  } else {
@@ -1455,7 +1414,7 @@ var BundledContentSource = class {
1455
1414
  }
1456
1415
  async isAvailable() {
1457
1416
  const root = getPackageRoot();
1458
- return fs9.existsSync(path10.join(root, "skills"));
1417
+ return fs9.existsSync(path11.join(root, "skills"));
1459
1418
  }
1460
1419
  async getVersion() {
1461
1420
  return getPackageVersion();
@@ -1495,7 +1454,7 @@ var GitHubContentSource = class {
1495
1454
  if (isCached(version)) {
1496
1455
  return getCachedContentRoot(version);
1497
1456
  }
1498
- const tmpDir = fs9.mkdtempSync(path10.join(os2.tmpdir(), "arcane-github-"));
1457
+ const tmpDir = fs9.mkdtempSync(path11.join(os3.tmpdir(), "arcane-github-"));
1499
1458
  try {
1500
1459
  await this.downloadAndExtract(tmpDir);
1501
1460
  const contentRoot = storeInCache(version, this.findExtractedRoot(tmpDir));
@@ -1573,7 +1532,7 @@ var GitHubContentSource = class {
1573
1532
  throw new Error(`Failed to download tarball: ${response.status}`);
1574
1533
  }
1575
1534
  const tar = await import("tar");
1576
- const tarballPath = path10.join(destDir, "repo.tar.gz");
1535
+ const tarballPath = path11.join(destDir, "repo.tar.gz");
1577
1536
  const fileStream = fs9.createWriteStream(tarballPath);
1578
1537
  const reader = response.body.getReader();
1579
1538
  const writable = new WritableStream({
@@ -1604,7 +1563,7 @@ var GitHubContentSource = class {
1604
1563
  (e) => e.isDirectory() && e.name !== "." && e.name !== ".."
1605
1564
  );
1606
1565
  if (dirs.length === 1) {
1607
- return path10.join(tmpDir, dirs[0].name);
1566
+ return path11.join(tmpDir, dirs[0].name);
1608
1567
  }
1609
1568
  return tmpDir;
1610
1569
  }
@@ -1669,10 +1628,10 @@ function findLatestCache() {
1669
1628
  }
1670
1629
 
1671
1630
  // src/registry.ts
1672
- import path11 from "path";
1631
+ import path12 from "path";
1673
1632
  var REGISTRY_VERSION = 1;
1674
1633
  function registryPath() {
1675
- return path11.join(arcaneHome(), "installations.json");
1634
+ return path12.join(arcaneHome(), "installations.json");
1676
1635
  }
1677
1636
  function readRegistryFile() {
1678
1637
  const p3 = registryPath();
@@ -1697,7 +1656,7 @@ function writeRegistryFile(registry) {
1697
1656
  }
1698
1657
  }
1699
1658
  function registerInstallation(target) {
1700
- const abs = path11.resolve(target);
1659
+ const abs = path12.resolve(target);
1701
1660
  const registry = readRegistryFile();
1702
1661
  if (registry.installations.some((e) => e.path === abs)) {
1703
1662
  return;
@@ -1722,7 +1681,7 @@ var PACKAGE_NAME = "claude-code-arcane";
1722
1681
  function isArcaneSourceTree(dir) {
1723
1682
  try {
1724
1683
  const pkg = JSON.parse(
1725
- fs10.readFileSync(path12.join(dir, "package.json"), "utf-8")
1684
+ fs10.readFileSync(path13.join(dir, "package.json"), "utf-8")
1726
1685
  );
1727
1686
  return pkg.name === PACKAGE_NAME;
1728
1687
  } catch {
@@ -1754,8 +1713,8 @@ async function installCommand(profileExpr, opts) {
1754
1713
  quiet: !profileExpr
1755
1714
  });
1756
1715
  const root = await source.getContentRoot();
1757
- const profilesDir = path12.join(root, "profiles");
1758
- const target = path12.resolve(opts.target ?? process.cwd());
1716
+ const profilesDir = path13.join(root, "profiles");
1717
+ const target = path13.resolve(opts.target ?? process.cwd());
1759
1718
  if (await refusesSelfInstall(target, profileExpr, opts)) return;
1760
1719
  if (!profileExpr) {
1761
1720
  const profiles = listProfiles(profilesDir);
@@ -1828,20 +1787,20 @@ Installing profile: ${chalk2.cyan(profileExpr)}`)
1828
1787
 
1829
1788
  // src/commands/add.ts
1830
1789
  import fs12 from "fs";
1831
- import path14 from "path";
1790
+ import path15 from "path";
1832
1791
  import chalk3 from "chalk";
1833
1792
 
1834
1793
  // src/skills-catalog.ts
1835
1794
  import fs11 from "fs";
1836
- import path13 from "path";
1795
+ import path14 from "path";
1837
1796
  var FRONTMATTER_BYTES = 4096;
1838
1797
  function listSkills(skillsDir) {
1839
1798
  if (!fs11.existsSync(skillsDir)) return [];
1840
1799
  return fs11.readdirSync(skillsDir, { withFileTypes: true }).filter(
1841
- (d) => d.isDirectory() && !d.name.startsWith("_") && fs11.existsSync(path13.join(skillsDir, d.name, "SKILL.md"))
1800
+ (d) => d.isDirectory() && !d.name.startsWith("_") && fs11.existsSync(path14.join(skillsDir, d.name, "SKILL.md"))
1842
1801
  ).map((d) => ({
1843
1802
  name: d.name,
1844
- description: readDescription(path13.join(skillsDir, d.name, "SKILL.md"))
1803
+ description: readDescription(path14.join(skillsDir, d.name, "SKILL.md"))
1845
1804
  })).sort((a, b) => a.name.localeCompare(b.name));
1846
1805
  }
1847
1806
  function readDescription(skillFile) {
@@ -1893,15 +1852,15 @@ async function addCommand(items = []) {
1893
1852
  return;
1894
1853
  }
1895
1854
  const selected = await runAddWizard({
1896
- profiles: listProfiles(path14.join(root, "profiles")),
1897
- skills: listSkills(path14.join(root, "skills")),
1855
+ profiles: listProfiles(path15.join(root, "profiles")),
1856
+ skills: listSkills(path15.join(root, "skills")),
1898
1857
  installedProfiles,
1899
1858
  installedSkills: manifest.installed_skills
1900
1859
  });
1901
1860
  if (!selected) return;
1902
1861
  items = selected;
1903
1862
  }
1904
- const claudeDir = path14.join(target, ".claude");
1863
+ const claudeDir = path15.join(target, ".claude");
1905
1864
  const added = [];
1906
1865
  const skipped = [];
1907
1866
  const notFound = [];
@@ -1911,7 +1870,7 @@ async function addCommand(items = []) {
1911
1870
  for (const item of items) {
1912
1871
  if (item.startsWith("+")) {
1913
1872
  const requested = item.slice(1);
1914
- const resolved = resolveProfile(path14.join(root, "profiles"), requested);
1873
+ const resolved = resolveProfile(path15.join(root, "profiles"), requested);
1915
1874
  if (!resolved) {
1916
1875
  console.error(chalk3.red(` Profile '${requested}' not found`));
1917
1876
  continue;
@@ -1926,10 +1885,10 @@ async function addCommand(items = []) {
1926
1885
  }
1927
1886
  for (const rule of profile.rules.universal) {
1928
1887
  if (!manifest.installed_rules.includes(rule)) {
1929
- const src = path14.join(root, "rules", `${rule}.md`);
1888
+ const src = path15.join(root, "rules", `${rule}.md`);
1930
1889
  if (fs12.existsSync(src)) {
1931
- ensureDir(path14.join(claudeDir, "rules"));
1932
- fs12.copyFileSync(src, path14.join(claudeDir, "rules", `${rule}.md`));
1890
+ ensureDir(path15.join(claudeDir, "rules"));
1891
+ fs12.copyFileSync(src, path15.join(claudeDir, "rules", `${rule}.md`));
1933
1892
  manifest.installed_rules.push(rule);
1934
1893
  addedRules.push(rule);
1935
1894
  }
@@ -1937,10 +1896,10 @@ async function addCommand(items = []) {
1937
1896
  }
1938
1897
  for (const rule of profile.rules.gamedev) {
1939
1898
  if (!manifest.installed_rules.includes(rule)) {
1940
- const src = path14.join(root, "rules", "gamedev", `${rule}.md`);
1899
+ const src = path15.join(root, "rules", "gamedev", `${rule}.md`);
1941
1900
  if (fs12.existsSync(src)) {
1942
- ensureDir(path14.join(claudeDir, "rules"));
1943
- fs12.copyFileSync(src, path14.join(claudeDir, "rules", `${rule}.md`));
1901
+ ensureDir(path15.join(claudeDir, "rules"));
1902
+ fs12.copyFileSync(src, path15.join(claudeDir, "rules", `${rule}.md`));
1944
1903
  manifest.installed_rules.push(rule);
1945
1904
  addedRules.push(rule);
1946
1905
  }
@@ -1956,9 +1915,9 @@ async function addCommand(items = []) {
1956
1915
  }
1957
1916
  }
1958
1917
  if (profileName === "statusline") {
1959
- const statuslineSrc = path14.join(root, "hooks", "statusline.sh");
1918
+ const statuslineSrc = path15.join(root, "hooks", "statusline.sh");
1960
1919
  if (fs12.existsSync(statuslineSrc)) {
1961
- fs12.copyFileSync(statuslineSrc, path14.join(claudeDir, "statusline.sh"));
1920
+ fs12.copyFileSync(statuslineSrc, path15.join(claudeDir, "statusline.sh"));
1962
1921
  statuslineAdded = true;
1963
1922
  }
1964
1923
  }
@@ -2041,10 +2000,10 @@ function persistAdditions(target, claudeDir, manifest, addedItems) {
2041
2000
  updateManifestFields(target, updates);
2042
2001
  }
2043
2002
  function printCatalog(root, installedProfiles, installedSkills) {
2044
- const profiles = listProfiles(path14.join(root, "profiles")).filter(
2003
+ const profiles = listProfiles(path15.join(root, "profiles")).filter(
2045
2004
  (pr) => !installedProfiles.includes(pr.name)
2046
2005
  );
2047
- const skills = listSkills(path14.join(root, "skills")).filter(
2006
+ const skills = listSkills(path15.join(root, "skills")).filter(
2048
2007
  (sk) => !installedSkills.includes(sk.name)
2049
2008
  );
2050
2009
  if (profiles.length === 0 && skills.length === 0) {
@@ -2070,14 +2029,14 @@ function printCatalog(root, installedProfiles, installedSkills) {
2070
2029
  }
2071
2030
  function addSkill(root, target, skill, installed) {
2072
2031
  if (installed.includes(skill)) return "skipped";
2073
- const src = path14.join(root, "skills", skill);
2032
+ const src = path15.join(root, "skills", skill);
2074
2033
  if (!fs12.existsSync(src)) return "not-found";
2075
- const dst = path14.join(target, ".claude", "skills", skill);
2034
+ const dst = path15.join(target, ".claude", "skills", skill);
2076
2035
  copyDirSync(src, dst);
2077
2036
  return "added";
2078
2037
  }
2079
2038
  function mergePermissions(claudeDir, newPerms) {
2080
- const settingsPath = path14.join(claudeDir, "settings.json");
2039
+ const settingsPath = path15.join(claudeDir, "settings.json");
2081
2040
  if (!fs12.existsSync(settingsPath)) return;
2082
2041
  const settings = readJsonSync(settingsPath);
2083
2042
  const perms = settings.permissions ?? { allow: [], deny: [] };
@@ -2091,7 +2050,7 @@ function mergePermissions(claudeDir, newPerms) {
2091
2050
  writeJsonSync(settingsPath, settings);
2092
2051
  }
2093
2052
  function addStatuslineToSettings(target, claudeDir) {
2094
- const settingsPath = path14.join(claudeDir, "settings.json");
2053
+ const settingsPath = path15.join(claudeDir, "settings.json");
2095
2054
  if (!fs12.existsSync(settingsPath)) return;
2096
2055
  const settings = readJsonSync(settingsPath);
2097
2056
  if (settings.statusLine) return;
@@ -2104,7 +2063,7 @@ function addStatuslineToSettings(target, claudeDir) {
2104
2063
 
2105
2064
  // src/commands/remove.ts
2106
2065
  import fs13 from "fs";
2107
- import path15 from "path";
2066
+ import path16 from "path";
2108
2067
  import chalk4 from "chalk";
2109
2068
  var CORE_SKILLS = [
2110
2069
  "commit",
@@ -2169,7 +2128,7 @@ async function removeCommand(items, opts = {}) {
2169
2128
  }
2170
2129
  manifest.total_skills = manifest.installed_skills.length;
2171
2130
  manifest.total_rules = manifest.installed_rules.length;
2172
- const mp = path15.join(target, ".claude", "arcane-manifest.json");
2131
+ const mp = path16.join(target, ".claude", "arcane-manifest.json");
2173
2132
  writeJsonSync(mp, manifest);
2174
2133
  const totalRemoved = removedSkills.length + removedAgents.length + removedProfiles.length;
2175
2134
  console.log(chalk4.bold(`
@@ -2190,7 +2149,7 @@ function removeSkill(target, skill, manifest) {
2190
2149
  );
2191
2150
  return "skipped";
2192
2151
  }
2193
- const skillDir = path15.join(target, ".claude", "skills", skill);
2152
+ const skillDir = path16.join(target, ".claude", "skills", skill);
2194
2153
  if (!fs13.existsSync(skillDir)) return "skipped";
2195
2154
  fs13.rmSync(skillDir, { recursive: true, force: true });
2196
2155
  manifest.installed_skills = manifest.installed_skills.filter(
@@ -2212,7 +2171,7 @@ function removeProfile(root, target, profileName, manifest) {
2212
2171
  );
2213
2172
  return { removed: false, skills: [], agents: [] };
2214
2173
  }
2215
- const resolved = resolveProfile(path15.join(root, "profiles"), installedName);
2174
+ const resolved = resolveProfile(path16.join(root, "profiles"), installedName);
2216
2175
  if (!resolved) {
2217
2176
  console.warn(
2218
2177
  chalk4.yellow(` WARNING: Profile '${installedName}.yaml' not found.`)
@@ -2227,7 +2186,7 @@ function removeProfile(root, target, profileName, manifest) {
2227
2186
  const sharedAgents = /* @__PURE__ */ new Set();
2228
2187
  const sharedRules = /* @__PURE__ */ new Set();
2229
2188
  for (const rp of remainingProfiles) {
2230
- const rpResolved = resolveProfile(path15.join(root, "profiles"), rp);
2189
+ const rpResolved = resolveProfile(path16.join(root, "profiles"), rp);
2231
2190
  if (!rpResolved) continue;
2232
2191
  const rpDef = parseProfile(rpResolved.file);
2233
2192
  rpDef.skills.forEach((s) => sharedSkills.add(s));
@@ -2239,7 +2198,7 @@ function removeProfile(root, target, profileName, manifest) {
2239
2198
  for (const skill of profile.skills) {
2240
2199
  if (sharedSkills.has(skill)) continue;
2241
2200
  if (CORE_SKILLS.includes(skill)) continue;
2242
- const skillDir = path15.join(target, ".claude", "skills", skill);
2201
+ const skillDir = path16.join(target, ".claude", "skills", skill);
2243
2202
  if (fs13.existsSync(skillDir)) {
2244
2203
  fs13.rmSync(skillDir, { recursive: true, force: true });
2245
2204
  removedSkills.push(skill);
@@ -2249,7 +2208,7 @@ function removeProfile(root, target, profileName, manifest) {
2249
2208
  );
2250
2209
  }
2251
2210
  const removedAgents = [];
2252
- const claudeDir = path15.join(target, ".claude");
2211
+ const claudeDir = path16.join(target, ".claude");
2253
2212
  for (const entry of profile.agents) {
2254
2213
  if (sharedAgents.has(entry)) continue;
2255
2214
  const { division } = parseAgentEntry(entry);
@@ -2264,7 +2223,7 @@ function removeProfile(root, target, profileName, manifest) {
2264
2223
  ...profile.rules.gamedev
2265
2224
  ]) {
2266
2225
  if (sharedRules.has(rule)) continue;
2267
- const ruleFile = path15.join(target, ".claude", "rules", `${rule}.md`);
2226
+ const ruleFile = path16.join(target, ".claude", "rules", `${rule}.md`);
2268
2227
  if (fs13.existsSync(ruleFile)) {
2269
2228
  fs13.rmSync(ruleFile);
2270
2229
  }
@@ -2273,18 +2232,18 @@ function removeProfile(root, target, profileName, manifest) {
2273
2232
  );
2274
2233
  }
2275
2234
  if (profileName === "statusline") {
2276
- const statuslineFile = path15.join(target, ".claude", "statusline.sh");
2235
+ const statuslineFile = path16.join(target, ".claude", "statusline.sh");
2277
2236
  if (fs13.existsSync(statuslineFile)) {
2278
2237
  fs13.rmSync(statuslineFile);
2279
2238
  }
2280
- removeStatuslineFromSettings(path15.join(target, ".claude"));
2239
+ removeStatuslineFromSettings(path16.join(target, ".claude"));
2281
2240
  }
2282
2241
  manifest.profiles = remainingProfiles;
2283
2242
  manifest.profile_command = remainingProfiles.filter((p3) => p3 !== "core").join("+");
2284
2243
  return { removed: true, skills: removedSkills, agents: removedAgents };
2285
2244
  }
2286
2245
  function removeStatuslineFromSettings(claudeDir) {
2287
- const settingsPath = path15.join(claudeDir, "settings.json");
2246
+ const settingsPath = path16.join(claudeDir, "settings.json");
2288
2247
  if (!fs13.existsSync(settingsPath)) return;
2289
2248
  const settings = JSON.parse(fs13.readFileSync(settingsPath, "utf-8"));
2290
2249
  if (settings.statusLine) {
@@ -2295,13 +2254,13 @@ function removeStatuslineFromSettings(claudeDir) {
2295
2254
 
2296
2255
  // src/commands/list.ts
2297
2256
  import fs14 from "fs";
2298
- import path16 from "path";
2257
+ import path17 from "path";
2299
2258
  import chalk5 from "chalk";
2300
2259
  async function listCommand() {
2301
2260
  const source = await resolveContentSource({ quiet: true });
2302
2261
  const root = await source.getContentRoot();
2303
- const profilesDir = path16.join(root, "profiles");
2304
- const skillsDir = path16.join(root, "skills");
2262
+ const profilesDir = path17.join(root, "profiles");
2263
+ const skillsDir = path17.join(root, "skills");
2305
2264
  const target = process.cwd();
2306
2265
  const manifest = readManifest(target);
2307
2266
  const profiles = listProfiles(profilesDir);
@@ -2386,8 +2345,8 @@ async function statusCommand() {
2386
2345
 
2387
2346
  // src/commands/update.ts
2388
2347
  import fs15 from "fs";
2389
- import path17 from "path";
2390
- import os3 from "os";
2348
+ import path18 from "path";
2349
+ import os4 from "os";
2391
2350
  import chalk7 from "chalk";
2392
2351
 
2393
2352
  // src/self-update.ts
@@ -2470,7 +2429,7 @@ async function updateCommand(opts) {
2470
2429
  registerInstallation(cwd);
2471
2430
  }
2472
2431
  const targets = pruneRegistry().map((e) => e.path);
2473
- const globalTarget = os3.homedir();
2432
+ const globalTarget = os4.homedir();
2474
2433
  if (!process.env.VITEST && fs15.existsSync(manifestPath(globalTarget)) && !targets.includes(globalTarget)) {
2475
2434
  targets.push(globalTarget);
2476
2435
  }
@@ -2540,11 +2499,11 @@ async function updateTarget(target, opts, sharedSource) {
2540
2499
  const currentVersion = await contentSource.getVersion();
2541
2500
  const installedVersion = manifest.source_version ?? manifest.arcane_version;
2542
2501
  const profileNamesForSync = manifest.profile_command.split("+").filter(Boolean);
2543
- const claudeDirForSync = path17.join(target, ".claude");
2502
+ const claudeDirForSync = path18.join(target, ".claude");
2544
2503
  const earlySync = syncUnhashedFiles(
2545
2504
  root,
2546
2505
  claudeDirForSync,
2547
- mergeProfiles(path17.join(root, "profiles"), profileNamesForSync),
2506
+ mergeProfiles(path18.join(root, "profiles"), profileNamesForSync),
2548
2507
  opts.dryRun ?? false
2549
2508
  );
2550
2509
  if (earlySync.length > 0 && !opts.quiet) {
@@ -2578,13 +2537,13 @@ Update available: ${chalk7.red(installedVersion)} -> ${chalk7.green(currentVersi
2578
2537
  );
2579
2538
  }
2580
2539
  const profileNames = manifest.profile_command.split("+").filter(Boolean);
2581
- const profilesDir = path17.join(root, "profiles");
2540
+ const profilesDir = path18.join(root, "profiles");
2582
2541
  const merged = mergeProfiles(profilesDir, profileNames);
2583
2542
  const allRules = [...merged.rules.universal, ...merged.rules.gamedev];
2584
2543
  const sourceHashes = computeSourceHashes(root, merged.skills, allRules, merged.agents);
2585
- const claudeDir = path17.join(target, ".claude");
2544
+ const claudeDir = path18.join(target, ".claude");
2586
2545
  const installedHashes = computeContentHashes(claudeDir);
2587
- if (isSymlink(path17.join(claudeDir, "docs"))) {
2546
+ if (isSymlink(path18.join(claudeDir, "docs"))) {
2588
2547
  sourceHashes.docs = {};
2589
2548
  installedHashes.docs = {};
2590
2549
  }
@@ -2877,18 +2836,18 @@ function applyUpdates(items, root, claudeDir, merged, force) {
2877
2836
  }
2878
2837
  function syncUnhashedFiles(root, claudeDir, merged, dryRun) {
2879
2838
  const targets = [];
2880
- const statuslineDst = path17.join(claudeDir, "statusline.sh");
2839
+ const statuslineDst = path18.join(claudeDir, "statusline.sh");
2881
2840
  if (merged.loaded.includes("statusline") || fs15.existsSync(statuslineDst)) {
2882
2841
  targets.push({
2883
- src: path17.join(root, "hooks", "statusline.sh"),
2842
+ src: path18.join(root, "hooks", "statusline.sh"),
2884
2843
  dst: statuslineDst,
2885
2844
  label: "statusline.sh"
2886
2845
  });
2887
2846
  }
2888
2847
  for (const style of merged.output_styles) {
2889
2848
  targets.push({
2890
- src: path17.join(root, "output-styles", `${style}.md`),
2891
- dst: path17.join(claudeDir, "output-styles", `${style}.md`),
2849
+ src: path18.join(root, "output-styles", `${style}.md`),
2850
+ dst: path18.join(claudeDir, "output-styles", `${style}.md`),
2892
2851
  label: `output-styles/${style}.md`
2893
2852
  });
2894
2853
  }
@@ -2896,21 +2855,21 @@ function syncUnhashedFiles(root, claudeDir, merged, dryRun) {
2896
2855
  const { division, agent } = parseAgentEntry(raw);
2897
2856
  if (!agent) continue;
2898
2857
  targets.push({
2899
- src: path17.join(root, "agents", division, `${agent}.md`),
2900
- dst: path17.join(claudeDir, "agents", division, `${agent}.md`),
2858
+ src: path18.join(root, "agents", division, `${agent}.md`),
2859
+ dst: path18.join(claudeDir, "agents", division, `${agent}.md`),
2901
2860
  label: `agents/${division}/${agent}.md`
2902
2861
  });
2903
2862
  }
2904
2863
  if (merged.rules.gamedev.length > 0) {
2905
- const templatesSrc = path17.join(root, "templates", "gamedev");
2864
+ const templatesSrc = path18.join(root, "templates", "gamedev");
2906
2865
  if (fs15.existsSync(templatesSrc)) {
2907
2866
  for (const rel of fs15.readdirSync(templatesSrc, { recursive: true })) {
2908
- const src = path17.join(templatesSrc, rel);
2867
+ const src = path18.join(templatesSrc, rel);
2909
2868
  if (!fs15.statSync(src).isFile()) continue;
2910
2869
  targets.push({
2911
2870
  src,
2912
- dst: path17.join(claudeDir, "skills", "_templates", rel),
2913
- label: `skills/_templates/${String(rel).split(path17.sep).join("/")}`
2871
+ dst: path18.join(claudeDir, "skills", "_templates", rel),
2872
+ label: `skills/_templates/${String(rel).split(path18.sep).join("/")}`
2914
2873
  });
2915
2874
  }
2916
2875
  }
@@ -2927,10 +2886,10 @@ function syncUnhashedFiles(root, claudeDir, merged, dryRun) {
2927
2886
  if (fs15.existsSync(bak)) fs15.rmSync(bak, { force: true });
2928
2887
  fs15.renameSync(t.dst, bak);
2929
2888
  }
2930
- fs15.mkdirSync(path17.dirname(t.dst), { recursive: true });
2889
+ fs15.mkdirSync(path18.dirname(t.dst), { recursive: true });
2931
2890
  fs15.copyFileSync(t.src, t.dst);
2932
2891
  }
2933
- const settingsPath = path17.join(claudeDir, "settings.json");
2892
+ const settingsPath = path18.join(claudeDir, "settings.json");
2934
2893
  if (fs15.existsSync(settingsPath)) {
2935
2894
  try {
2936
2895
  const settings = JSON.parse(fs15.readFileSync(settingsPath, "utf-8"));
@@ -2941,13 +2900,20 @@ function syncUnhashedFiles(root, claudeDir, merged, dryRun) {
2941
2900
  settings.outputStyle = activeStyle;
2942
2901
  dirty = true;
2943
2902
  }
2944
- const wanted = statuslineCommand(path17.dirname(claudeDir));
2903
+ const wanted = statuslineCommand(path18.dirname(claudeDir));
2945
2904
  const current = settings.statusLine;
2946
2905
  if (wanted !== RELATIVE_STATUSLINE_COMMAND && current && current.command !== wanted) {
2947
2906
  changed.push("settings.json (statusLine path)");
2948
2907
  settings.statusLine = { ...current, command: wanted };
2949
2908
  dirty = true;
2950
2909
  }
2910
+ if (settings.hooks && typeof settings.hooks === "object") {
2911
+ const healed = healHooks(settings.hooks, path18.dirname(claudeDir));
2912
+ if (healed.length > 0) {
2913
+ changed.push(`settings.json (${healed.join(", ")})`);
2914
+ dirty = true;
2915
+ }
2916
+ }
2951
2917
  if (dirty && !dryRun) {
2952
2918
  fs15.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
2953
2919
  `, "utf-8");
@@ -2967,7 +2933,7 @@ function applyItem(item, root, claudeDir) {
2967
2933
  }
2968
2934
  copyDirSync(src, dst);
2969
2935
  } else {
2970
- const dir = path17.dirname(dst);
2936
+ const dir = path18.dirname(dst);
2971
2937
  if (!fs15.existsSync(dir)) {
2972
2938
  fs15.mkdirSync(dir, { recursive: true });
2973
2939
  }
@@ -2977,8 +2943,8 @@ function applyItem(item, root, claudeDir) {
2977
2943
  function quarantineItem(item, claudeDir) {
2978
2944
  const installed = getInstalledPath(item, claudeDir);
2979
2945
  if (!fs15.existsSync(installed)) return;
2980
- const quarantine = path17.join(claudeDir, TRASH_DIR, `${item.type}s`, item.name);
2981
- ensureDir(path17.dirname(quarantine));
2946
+ const quarantine = path18.join(claudeDir, TRASH_DIR, `${item.type}s`, item.name);
2947
+ ensureDir(path18.dirname(quarantine));
2982
2948
  if (fs15.existsSync(quarantine)) {
2983
2949
  fs15.rmSync(quarantine, { recursive: true, force: true });
2984
2950
  }
@@ -2987,18 +2953,18 @@ function quarantineItem(item, claudeDir) {
2987
2953
  function getSourcePath(item, root) {
2988
2954
  switch (item.type) {
2989
2955
  case "skill":
2990
- return path17.join(root, "skills", item.name);
2956
+ return path18.join(root, "skills", item.name);
2991
2957
  case "rule": {
2992
- const direct = path17.join(root, "rules", item.name);
2958
+ const direct = path18.join(root, "rules", item.name);
2993
2959
  if (fs15.existsSync(direct)) return direct;
2994
- return path17.join(root, "rules", "gamedev", item.name);
2960
+ return path18.join(root, "rules", "gamedev", item.name);
2995
2961
  }
2996
2962
  case "agent":
2997
- return path17.join(root, "agents", item.name);
2963
+ return path18.join(root, "agents", item.name);
2998
2964
  case "hook":
2999
- return path17.join(root, "hooks", item.name);
2965
+ return path18.join(root, "hooks", item.name);
3000
2966
  case "doc":
3001
- return path17.join(root, "docs", item.name);
2967
+ return path18.join(root, "docs", item.name);
3002
2968
  default:
3003
2969
  return null;
3004
2970
  }
@@ -3006,15 +2972,15 @@ function getSourcePath(item, root) {
3006
2972
  function getInstalledPath(item, claudeDir) {
3007
2973
  switch (item.type) {
3008
2974
  case "skill":
3009
- return path17.join(claudeDir, "skills", item.name);
2975
+ return path18.join(claudeDir, "skills", item.name);
3010
2976
  case "rule":
3011
- return path17.join(claudeDir, "rules", item.name);
2977
+ return path18.join(claudeDir, "rules", item.name);
3012
2978
  case "agent":
3013
- return path17.join(claudeDir, "agents", item.name);
2979
+ return path18.join(claudeDir, "agents", item.name);
3014
2980
  case "hook":
3015
- return path17.join(claudeDir, "hooks", item.name);
2981
+ return path18.join(claudeDir, "hooks", item.name);
3016
2982
  case "doc":
3017
- return path17.join(claudeDir, "docs", item.name);
2983
+ return path18.join(claudeDir, "docs", item.name);
3018
2984
  }
3019
2985
  }
3020
2986
  function isSymlink(p3) {
@@ -3057,7 +3023,7 @@ async function cleanCommand(opts) {
3057
3023
 
3058
3024
  // src/commands/worktree.ts
3059
3025
  import fs16 from "fs";
3060
- import path18 from "path";
3026
+ import path19 from "path";
3061
3027
  import { execSync as execSync2 } from "child_process";
3062
3028
  import chalk9 from "chalk";
3063
3029
  async function worktreeCommand(branch, opts) {
@@ -3068,7 +3034,7 @@ async function worktreeCommand(branch, opts) {
3068
3034
  process.exit(1);
3069
3035
  }
3070
3036
  const repoRoot = wtInfo.isWorktree ? wtInfo.mainWorktreePath : cwd;
3071
- const worktreePath = opts.path ? path18.resolve(opts.path) : defaultWorktreePath(repoRoot, branch);
3037
+ const worktreePath = opts.path ? path19.resolve(opts.path) : defaultWorktreePath(repoRoot, branch);
3072
3038
  if (fs16.existsSync(worktreePath)) {
3073
3039
  console.error(
3074
3040
  chalk9.red(`Path already exists: ${worktreePath}`)
@@ -3116,7 +3082,7 @@ async function worktreeCommand(branch, opts) {
3116
3082
  }
3117
3083
  console.log(" Installing Arcane...");
3118
3084
  const root = getPackageRoot();
3119
- const profilesDir = path18.join(root, "profiles");
3085
+ const profilesDir = path19.join(root, "profiles");
3120
3086
  const profileNames = profileExpr.split("+").filter(Boolean);
3121
3087
  const merged = mergeProfiles(profilesDir, profileNames);
3122
3088
  const shareFrom = opts.share !== false ? cwd : void 0;
@@ -3152,7 +3118,7 @@ async function worktreeCommand(branch, opts) {
3152
3118
  }
3153
3119
  }
3154
3120
  if (opts.isolate) {
3155
- const isolateScript = path18.join(
3121
+ const isolateScript = path19.join(
3156
3122
  worktreePath,
3157
3123
  ".claude",
3158
3124
  "skills",
@@ -3194,12 +3160,12 @@ async function worktreeCommand(branch, opts) {
3194
3160
 
3195
3161
  // src/commands/global.ts
3196
3162
  import fs17 from "fs";
3197
- import path19 from "path";
3198
- import os4 from "os";
3163
+ import path20 from "path";
3164
+ import os5 from "os";
3199
3165
  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");
3166
+ var CLAUDE_HOME = path20.join(os5.homedir(), ".claude");
3167
+ var SCRIPTS_DIR = path20.join(CLAUDE_HOME, "scripts", "worktree-isolation");
3168
+ var SETTINGS_PATH = path20.join(CLAUDE_HOME, "settings.json");
3203
3169
  async function globalCommand(opts) {
3204
3170
  if (opts.status) {
3205
3171
  showStatus();
@@ -3229,7 +3195,7 @@ function settingsParseable() {
3229
3195
  function showStatus() {
3230
3196
  console.log(chalk10.bold("\n=== Arcane Global Status ===\n"));
3231
3197
  const hasScripts = fs17.existsSync(
3232
- path19.join(SCRIPTS_DIR, "apply.py")
3198
+ path20.join(SCRIPTS_DIR, "apply.py")
3233
3199
  );
3234
3200
  console.log(
3235
3201
  ` Worktree scripts: ${hasScripts ? chalk10.green("installed") : chalk10.dim("not installed")}`
@@ -3247,7 +3213,7 @@ function showStatus() {
3247
3213
  function installGlobal() {
3248
3214
  console.log(chalk10.bold("\nInstalling Arcane global hooks...\n"));
3249
3215
  const root = getPackageRoot();
3250
- const scriptsSrc = path19.join(
3216
+ const scriptsSrc = path20.join(
3251
3217
  root,
3252
3218
  "skills",
3253
3219
  "worktree-isolation",
@@ -3261,8 +3227,8 @@ function installGlobal() {
3261
3227
  }
3262
3228
  fs17.mkdirSync(SCRIPTS_DIR, { recursive: true });
3263
3229
  for (const file of ["apply.py", "lib.py"]) {
3264
- const src = path19.join(scriptsSrc, file);
3265
- const dst = path19.join(SCRIPTS_DIR, file);
3230
+ const src = path20.join(scriptsSrc, file);
3231
+ const dst = path20.join(SCRIPTS_DIR, file);
3266
3232
  if (!fs17.existsSync(src)) {
3267
3233
  console.warn(chalk10.yellow(` WARN: ${file} not found, skipping`));
3268
3234
  continue;
@@ -3319,7 +3285,7 @@ function readSettings() {
3319
3285
  }
3320
3286
  }
3321
3287
  function writeSettings(settings) {
3322
- fs17.mkdirSync(path19.dirname(SETTINGS_PATH), { recursive: true });
3288
+ fs17.mkdirSync(path20.dirname(SETTINGS_PATH), { recursive: true });
3323
3289
  fs17.writeFileSync(
3324
3290
  SETTINGS_PATH,
3325
3291
  JSON.stringify(settings, null, 2) + "\n",
@@ -3331,8 +3297,8 @@ function isHookInstalled() {
3331
3297
  const hooks = settings.hooks ?? {};
3332
3298
  const sessionStart = hooks.SessionStart ?? [];
3333
3299
  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")) {
3300
+ for (const hook2 of entry.hooks ?? []) {
3301
+ if (hook2.command?.includes("worktree-isolation/apply.py") && hook2.command?.includes("--quiet")) {
3336
3302
  return true;
3337
3303
  }
3338
3304
  }
@@ -3378,10 +3344,10 @@ function removeHookFromSettings() {
3378
3344
 
3379
3345
  // src/update-check.ts
3380
3346
  import fs18 from "fs";
3381
- import path20 from "path";
3347
+ import path21 from "path";
3382
3348
  import chalk11 from "chalk";
3383
3349
  function checkFile() {
3384
- return path20.join(arcaneHome(), "last-check.json");
3350
+ return path21.join(arcaneHome(), "last-check.json");
3385
3351
  }
3386
3352
  var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
3387
3353
  var GITHUB_OWNER2 = "SebastianLuser";
@@ -3519,7 +3485,7 @@ function readCachedCheck() {
3519
3485
  function writeCachedCheck(result) {
3520
3486
  try {
3521
3487
  const file = checkFile();
3522
- const dir = path20.dirname(file);
3488
+ const dir = path21.dirname(file);
3523
3489
  if (!fs18.existsSync(dir)) {
3524
3490
  fs18.mkdirSync(dir, { recursive: true });
3525
3491
  }