claude-code-arcane 2.9.3 → 2.9.4

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/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## [2.9.4](https://github.com/SebastianLuser/claude-code-arcane/compare/v2.9.3...v2.9.4) (2026-09-07)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **add:** keep content_hashes instead of rebuilding the manifest ([1ecce98](https://github.com/SebastianLuser/claude-code-arcane/commit/1ecce98ebcb8a480dd4b748f41606badec9fb0fd))
7
+ * **cache:** resolve the arcane home in one place, honouring ARCANE_HOME ([4a7a24d](https://github.com/SebastianLuser/claude-code-arcane/commit/4a7a24d6a28df1abfe94be75f590c79d8428dca5))
8
+
1
9
  ## [2.9.3](https://github.com/SebastianLuser/claude-code-arcane/compare/v2.9.2...v2.9.3) (2026-09-07)
2
10
 
3
11
 
package/dist/cli.js CHANGED
@@ -391,6 +391,10 @@ import path8 from "path";
391
391
  import { fileURLToPath } from "url";
392
392
  import path3 from "path";
393
393
  import fs3 from "fs";
394
+ import os from "os";
395
+ function arcaneHome() {
396
+ return process.env.ARCANE_HOME ?? path3.join(os.homedir(), ".arcane");
397
+ }
394
398
  function getPackageRoot() {
395
399
  const thisFile = fileURLToPath(import.meta.url);
396
400
  return path3.resolve(path3.dirname(thisFile), "..");
@@ -1310,15 +1314,18 @@ import os2 from "os";
1310
1314
  // src/cache.ts
1311
1315
  import fs8 from "fs";
1312
1316
  import path9 from "path";
1313
- import os from "os";
1314
- var ARCANE_DIR = path9.join(os.homedir(), ".arcane");
1315
- var CACHE_DIR = path9.join(ARCANE_DIR, "cache");
1317
+ function cacheDir() {
1318
+ return path9.join(arcaneHome(), "cache");
1319
+ }
1316
1320
  function getCachePath(version) {
1317
- return path9.join(CACHE_DIR, version);
1321
+ return path9.join(cacheDir(), version);
1322
+ }
1323
+ function isUsableContentRoot(dir) {
1324
+ return fs8.existsSync(path9.join(dir, "profiles")) && fs8.existsSync(path9.join(dir, "skills"));
1318
1325
  }
1319
1326
  function isCached(version) {
1320
1327
  const versionDir = getCachePath(version);
1321
- return fs8.existsSync(versionDir) && fs8.existsSync(path9.join(versionDir, ".cache-meta.json"));
1328
+ return fs8.existsSync(versionDir) && fs8.existsSync(path9.join(versionDir, ".cache-meta.json")) && isUsableContentRoot(versionDir);
1322
1329
  }
1323
1330
  function getCachedContentRoot(version) {
1324
1331
  if (!isCached(version)) return null;
@@ -1347,9 +1354,9 @@ function storeInCache(version, contentDir) {
1347
1354
  return versionDir;
1348
1355
  }
1349
1356
  function pruneCache(keepCount = 3) {
1350
- if (!fs8.existsSync(CACHE_DIR)) return;
1351
- const entries = fs8.readdirSync(CACHE_DIR, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => {
1352
- const metaPath = path9.join(CACHE_DIR, e.name, ".cache-meta.json");
1357
+ if (!fs8.existsSync(cacheDir())) return;
1358
+ const entries = fs8.readdirSync(cacheDir(), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => {
1359
+ const metaPath = path9.join(cacheDir(), e.name, ".cache-meta.json");
1353
1360
  let cachedAt = 0;
1354
1361
  if (fs8.existsSync(metaPath)) {
1355
1362
  try {
@@ -1362,9 +1369,24 @@ function pruneCache(keepCount = 3) {
1362
1369
  return { name: e.name, cachedAt };
1363
1370
  }).sort((a, b) => b.cachedAt - a.cachedAt);
1364
1371
  for (const entry of entries.slice(keepCount)) {
1365
- fs8.rmSync(path9.join(CACHE_DIR, entry.name), { recursive: true, force: true });
1372
+ fs8.rmSync(path9.join(cacheDir(), entry.name), { recursive: true, force: true });
1366
1373
  }
1367
1374
  }
1375
+ function listCachedVersions() {
1376
+ if (!fs8.existsSync(cacheDir())) return [];
1377
+ return fs8.readdirSync(cacheDir(), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => {
1378
+ const metaPath = path9.join(cacheDir(), e.name, ".cache-meta.json");
1379
+ let cachedAt = "unknown";
1380
+ if (fs8.existsSync(metaPath)) {
1381
+ try {
1382
+ const meta = JSON.parse(fs8.readFileSync(metaPath, "utf-8"));
1383
+ cachedAt = meta.cached_at;
1384
+ } catch {
1385
+ }
1386
+ }
1387
+ return { version: e.name, cachedAt };
1388
+ });
1389
+ }
1368
1390
  function copyDirRecursive(src, dest) {
1369
1391
  fs8.mkdirSync(dest, { recursive: true });
1370
1392
  for (const entry of fs8.readdirSync(src, { withFileTypes: true })) {
@@ -1595,31 +1617,16 @@ async function resolveContentSourceForVersion(version, opts = {}) {
1595
1617
  return source;
1596
1618
  }
1597
1619
  function findLatestCache() {
1598
- const cacheDir = path10.join(os2.homedir(), ".arcane", "cache");
1599
- if (!fs9.existsSync(cacheDir)) return null;
1600
- const entries = fs9.readdirSync(cacheDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => {
1601
- const metaPath = path10.join(cacheDir, e.name, ".cache-meta.json");
1602
- let cachedAt = 0;
1603
- if (fs9.existsSync(metaPath)) {
1604
- try {
1605
- const meta = JSON.parse(fs9.readFileSync(metaPath, "utf-8"));
1606
- cachedAt = new Date(meta.cached_at).getTime();
1607
- } catch {
1608
- cachedAt = 0;
1609
- }
1610
- }
1611
- return { name: e.name, cachedAt };
1612
- }).sort((a, b) => b.cachedAt - a.cachedAt);
1613
- return entries.length > 0 ? entries[0].name : null;
1620
+ const usable = listCachedVersions().filter((entry) => isCached(entry.version)).map((entry) => ({
1621
+ version: entry.version,
1622
+ cachedAt: new Date(entry.cachedAt).getTime() || 0
1623
+ })).sort((a, b) => b.cachedAt - a.cachedAt);
1624
+ return usable.length > 0 ? usable[0].version : null;
1614
1625
  }
1615
1626
 
1616
1627
  // src/registry.ts
1617
1628
  import path11 from "path";
1618
- import os3 from "os";
1619
1629
  var REGISTRY_VERSION = 1;
1620
- function arcaneHome() {
1621
- return process.env.ARCANE_HOME ?? path11.join(os3.homedir(), ".arcane");
1622
- }
1623
1630
  function registryPath() {
1624
1631
  return path11.join(arcaneHome(), "installations.json");
1625
1632
  }
@@ -1898,16 +1905,11 @@ async function addCommand(items = []) {
1898
1905
  manifest.installed_skills.push(...added);
1899
1906
  manifest.total_skills = manifest.installed_skills.length;
1900
1907
  manifest.total_rules = manifest.installed_rules.length;
1901
- const merged = {
1902
- loaded: manifest.profiles,
1903
- skills: manifest.installed_skills,
1904
- rules: { universal: manifest.installed_rules, gamedev: [] },
1905
- agents: manifest.installed_agents,
1906
- hooks: [],
1907
- output_styles: [],
1908
- permissions: { allow: [], deny: [] }
1909
- };
1910
- writeManifest(target, merged, manifest.profile_command, root);
1908
+ persistAdditions(target, claudeDir, manifest, {
1909
+ skills: added,
1910
+ rules: addedRules,
1911
+ agents: addedAgents
1912
+ });
1911
1913
  const totalAdded = added.length + addedRules.length + addedAgents.length + (statuslineAdded ? 1 : 0);
1912
1914
  console.log(
1913
1915
  chalk3.bold(
@@ -1932,6 +1934,35 @@ Added ${totalAdded} items:`
1932
1934
  );
1933
1935
  }
1934
1936
  }
1937
+ function persistAdditions(target, claudeDir, manifest, addedItems) {
1938
+ const updates = {
1939
+ profiles: manifest.profiles,
1940
+ profile_command: manifest.profile_command,
1941
+ installed_skills: manifest.installed_skills,
1942
+ installed_rules: manifest.installed_rules,
1943
+ installed_agents: manifest.installed_agents,
1944
+ total_skills: manifest.total_skills,
1945
+ total_rules: manifest.total_rules,
1946
+ updated_at: (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z")
1947
+ };
1948
+ const hashes = manifest.content_hashes;
1949
+ if (hashes) {
1950
+ const fresh = computeContentHashes(claudeDir);
1951
+ for (const skill of addedItems.skills) {
1952
+ if (fresh.skills[skill]) hashes.skills[skill] = fresh.skills[skill];
1953
+ }
1954
+ for (const rule of addedItems.rules) {
1955
+ const key = rule.endsWith(".md") ? rule : `${rule}.md`;
1956
+ if (fresh.rules[key]) hashes.rules[key] = fresh.rules[key];
1957
+ }
1958
+ for (const entry of addedItems.agents) {
1959
+ const { division } = parseAgentEntry(entry);
1960
+ if (fresh.agents[division]) hashes.agents[division] = fresh.agents[division];
1961
+ }
1962
+ updates.content_hashes = hashes;
1963
+ }
1964
+ updateManifestFields(target, updates);
1965
+ }
1935
1966
  function printCatalog(root, installedProfiles, installedSkills) {
1936
1967
  const profiles = listProfiles(path14.join(root, "profiles")).filter(
1937
1968
  (pr) => !installedProfiles.includes(pr.name)
@@ -2278,7 +2309,7 @@ async function statusCommand() {
2278
2309
  // src/commands/update.ts
2279
2310
  import fs14 from "fs";
2280
2311
  import path17 from "path";
2281
- import os4 from "os";
2312
+ import os3 from "os";
2282
2313
  import chalk7 from "chalk";
2283
2314
 
2284
2315
  // src/self-update.ts
@@ -2358,7 +2389,7 @@ async function updateCommand(opts) {
2358
2389
  registerInstallation(cwd);
2359
2390
  }
2360
2391
  const targets = pruneRegistry().map((e) => e.path);
2361
- const globalTarget = os4.homedir();
2392
+ const globalTarget = os3.homedir();
2362
2393
  if (!process.env.VITEST && fs14.existsSync(manifestPath(globalTarget)) && !targets.includes(globalTarget)) {
2363
2394
  targets.push(globalTarget);
2364
2395
  }
@@ -3022,9 +3053,9 @@ async function worktreeCommand(branch, opts) {
3022
3053
  // src/commands/global.ts
3023
3054
  import fs16 from "fs";
3024
3055
  import path19 from "path";
3025
- import os5 from "os";
3056
+ import os4 from "os";
3026
3057
  import chalk10 from "chalk";
3027
- var CLAUDE_HOME = path19.join(os5.homedir(), ".claude");
3058
+ var CLAUDE_HOME = path19.join(os4.homedir(), ".claude");
3028
3059
  var SCRIPTS_DIR = path19.join(CLAUDE_HOME, "scripts", "worktree-isolation");
3029
3060
  var SETTINGS_PATH = path19.join(CLAUDE_HOME, "settings.json");
3030
3061
  async function globalCommand(opts) {
@@ -3191,9 +3222,10 @@ function removeHookFromSettings() {
3191
3222
  // src/update-check.ts
3192
3223
  import fs17 from "fs";
3193
3224
  import path20 from "path";
3194
- import os6 from "os";
3195
3225
  import chalk11 from "chalk";
3196
- var CHECK_FILE = path20.join(os6.homedir(), ".arcane", "last-check.json");
3226
+ function checkFile() {
3227
+ return path20.join(arcaneHome(), "last-check.json");
3228
+ }
3197
3229
  var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
3198
3230
  var GITHUB_OWNER2 = "SebastianLuser";
3199
3231
  var GITHUB_REPO2 = "Claude-Code-Arcane";
@@ -3318,19 +3350,21 @@ async function getLatestNpmVersion() {
3318
3350
  }
3319
3351
  function readCachedCheck() {
3320
3352
  try {
3321
- if (!fs17.existsSync(CHECK_FILE)) return null;
3322
- return JSON.parse(fs17.readFileSync(CHECK_FILE, "utf-8"));
3353
+ const file = checkFile();
3354
+ if (!fs17.existsSync(file)) return null;
3355
+ return JSON.parse(fs17.readFileSync(file, "utf-8"));
3323
3356
  } catch {
3324
3357
  return null;
3325
3358
  }
3326
3359
  }
3327
3360
  function writeCachedCheck(result) {
3328
3361
  try {
3329
- const dir = path20.dirname(CHECK_FILE);
3362
+ const file = checkFile();
3363
+ const dir = path20.dirname(file);
3330
3364
  if (!fs17.existsSync(dir)) {
3331
3365
  fs17.mkdirSync(dir, { recursive: true });
3332
3366
  }
3333
- fs17.writeFileSync(CHECK_FILE, JSON.stringify(result, null, 2) + "\n");
3367
+ fs17.writeFileSync(file, JSON.stringify(result, null, 2) + "\n");
3334
3368
  } catch {
3335
3369
  }
3336
3370
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-arcane",
3
- "version": "2.9.3",
3
+ "version": "2.9.4",
4
4
  "description": "Skills, agents, hooks and rules for Claude Code — installable via npx claude-code-arcane",
5
5
  "type": "module",
6
6
  "bin": {