@hasna/skills 0.10.55 → 0.10.56

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/README.md CHANGED
@@ -696,6 +696,10 @@ remain absent only while unchanged native settings and registrations select a
696
696
  different extant, witnessed user version. Individual missing files, current
697
697
  registered roots, aliases and registration changes still refuse; historical
698
698
  source hashes and typed settings witnesses remain intact.
699
+ An explicit Claude discovery review must cover every registered active cache
700
+ manifest, skill root and hook source. Installation refuses an incomplete review
701
+ that covers only an older cache, before retirement of that cache can break prompt
702
+ delivery. Hook behavior still requires the existing no-skill-injection review.
699
703
 
700
704
  For a Codex 0.160.0, 0.160.1 or 0.161.0 remote refresh omitted by an existing qualified-name disable,
701
705
  a fresh native catalog and discovery review can plan the new exact path disable
package/bin/index.js CHANGED
@@ -36888,7 +36888,7 @@ var package_default;
36888
36888
  var init_package = __esm(() => {
36889
36889
  package_default = {
36890
36890
  name: "@hasna/skills",
36891
- version: "0.10.55",
36891
+ version: "0.10.56",
36892
36892
  description: "Skills library for AI coding agents",
36893
36893
  type: "module",
36894
36894
  bin: {
@@ -59361,6 +59361,97 @@ function read2(path, changes) {
59361
59361
  throw new Error(`Unsupported or oversized native discovery input: ${path}`);
59362
59362
  return readFileSync21(path, "utf8");
59363
59363
  }
59364
+ function assertReviewedClaudeCacheClosures(home, config, review) {
59365
+ const enabledIds = Object.entries(config.enabledPlugins ?? {}).filter(([, enabled]) => enabled === true).map(([id]) => id);
59366
+ if (enabledIds.length === 0)
59367
+ return;
59368
+ const registryPath = join37(home, ".claude/plugins/installed_plugins.json");
59369
+ const cacheRoot = join37(home, ".claude/plugins/cache");
59370
+ const registryText = read2(registryPath);
59371
+ if (registryText === null)
59372
+ return;
59373
+ const registry = parseConfig(registryText, registryPath);
59374
+ if (registry.version !== 2 || !registry.plugins || typeof registry.plugins !== "object" || Array.isArray(registry.plugins))
59375
+ throw new Error("Reviewed Claude installed plugin registrations are invalid");
59376
+ const cachePath = (path) => typeof path === "string" && isAbsolute22(path) && resolve21(path) === path && (path === cacheRoot || path.startsWith(cacheRoot + sep8));
59377
+ const activeRoots = new Set;
59378
+ for (const id of enabledIds) {
59379
+ const rows = registry.plugins[id];
59380
+ if (rows === undefined)
59381
+ continue;
59382
+ if (!Array.isArray(rows))
59383
+ throw new Error("Reviewed Claude enabled plugin registrations must be an array");
59384
+ for (const row of rows) {
59385
+ if (!row || typeof row !== "object" || Array.isArray(row) || typeof row.installPath !== "string") {
59386
+ throw new Error("Reviewed Claude enabled plugin registration requires an object with an installPath string");
59387
+ }
59388
+ if (typeof row.installPath === "string" && (row.installPath === cacheRoot || row.installPath.startsWith(cacheRoot + sep8))) {
59389
+ if (!cachePath(row.installPath) || row.installPath.length > AGENT_POLICY_LIMITS.pathCharacters || row.version !== undefined && (typeof row.version !== "string" || basename11(row.installPath) !== row.version)) {
59390
+ throw new Error("Reviewed Claude discovery has a malformed active cache registration");
59391
+ }
59392
+ if (row.scope === "user" && (row.projectPath === undefined || row.projectPath === null)) {} else if (row.scope === "project" && typeof row.projectPath === "string" && isAbsolute22(row.projectPath) && resolve21(row.projectPath) === row.projectPath && row.projectPath.length <= AGENT_POLICY_LIMITS.pathCharacters) {} else
59393
+ throw new Error("Reviewed Claude discovery has an unsupported active cache registration scope");
59394
+ activeRoots.add(row.installPath);
59395
+ }
59396
+ }
59397
+ }
59398
+ if (activeRoots.size === 0)
59399
+ return;
59400
+ const registrySource = review.sources.find((source) => source.path === registryPath && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && (source.hashMode === undefined || source.hashMode === "bytes"));
59401
+ if (!registrySource)
59402
+ throw new Error("Reviewed Claude discovery must bind installed registrations for active cache plugins");
59403
+ const reviewedFile = (path, manifest = false) => review.sources.some((source) => source.path === path && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && (source.hashMode === undefined || source.hashMode === "bytes" || manifest && source.hashMode === "claude-plugin-manifest-v1"));
59404
+ const requireSource = (path, description, manifest = false) => {
59405
+ if (!reviewedFile(path, manifest))
59406
+ throw new Error(`Reviewed active Claude cache plugin ${description} is not bound`);
59407
+ };
59408
+ for (const root2 of activeRoots) {
59409
+ safe4(root2);
59410
+ const rootStat = lstatSync25(root2, { throwIfNoEntry: false });
59411
+ if (!rootStat?.isDirectory() || realpathSync11(root2) !== root2)
59412
+ throw new Error("Reviewed Claude active cache plugin root is missing or unsafe");
59413
+ const manifestPath = join37(root2, ".claude-plugin/plugin.json");
59414
+ const manifestStat = lstatSync25(manifestPath, { throwIfNoEntry: false });
59415
+ let manifest;
59416
+ if (manifestStat) {
59417
+ if (!manifestStat.isFile())
59418
+ throw new Error("Reviewed active Claude cache plugin manifest is unsupported");
59419
+ requireSource(manifestPath, "manifest", true);
59420
+ const raw = read2(manifestPath);
59421
+ if (raw === null)
59422
+ throw new Error("Reviewed Claude active cache plugin manifest is missing");
59423
+ manifest = JSON.parse(raw);
59424
+ if (!manifest || typeof manifest !== "object" || Array.isArray(manifest))
59425
+ throw new Error("Reviewed Claude active cache plugin manifest is invalid");
59426
+ } else if (!review.sources.some((source) => source.path === manifestPath && source.hashMode === "path-bytes" && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined)) {
59427
+ throw new Error("Reviewed Claude active cache plugin manifest absence requires a path-bytes witness");
59428
+ }
59429
+ const roots = new Set([join37(root2, "skills")]);
59430
+ if (lstatSync25(join37(root2, "SKILL.md"), { throwIfNoEntry: false })?.isFile())
59431
+ roots.add(root2);
59432
+ const declared = manifest?.skills === undefined ? ["./skills"] : typeof manifest.skills === "string" ? [manifest.skills] : manifest.skills;
59433
+ if (!Array.isArray(declared) || declared.some((path) => typeof path !== "string"))
59434
+ throw new Error("Reviewed Claude active cache plugin has unsupported skill paths");
59435
+ for (const value of declared) {
59436
+ const path = resolve21(root2, value);
59437
+ if (path !== root2 && !path.startsWith(root2 + sep8))
59438
+ throw new Error("Reviewed Claude active cache plugin skill path escapes its root");
59439
+ roots.add(path.endsWith(`${sep8}SKILL.md`) ? dirname20(path) : path);
59440
+ }
59441
+ for (const skillRoot of roots)
59442
+ if (!review.roots.includes(skillRoot))
59443
+ throw new Error("Reviewed Claude active cache plugin skill root is not covered");
59444
+ const hooksPath = join37(root2, "hooks/hooks.json");
59445
+ const hooksStat = lstatSync25(hooksPath, { throwIfNoEntry: false });
59446
+ if (hooksStat) {
59447
+ if (!hooksStat.isFile())
59448
+ throw new Error("Reviewed Claude plugin hook manifest is unsupported");
59449
+ requireSource(hooksPath, "hook manifest");
59450
+ }
59451
+ if (typeof manifest?.hooks === "string")
59452
+ requireSource(resolve21(root2, manifest.hooks), "manifest-declared hook source");
59453
+ }
59454
+ }
59364
59455
  function projectNativeDiscoveryFields(text3, format, fields2, path) {
59365
59456
  const object6 = format === "yaml" ? parseHermesConfig(text3) : parseConfig(text3, path, format === "toml");
59366
59457
  if (!object6 || typeof object6 !== "object" || Array.isArray(object6))
@@ -59937,6 +60028,8 @@ function resolveAgentDiscovery(options) {
59937
60028
  throw new Error("Reviewed Claude plugin manifest requires separately reviewed exact hook file sources");
59938
60029
  return { path: source.path, sha256: manifestSha256, hashMode: "claude-plugin-manifest-v1" };
59939
60030
  });
60031
+ if (agent === "claude")
60032
+ assertReviewedClaudeCacheClosures(home, config, review);
59940
60033
  const combined = [...reviewedSources, ...reviewedSourcesWithManifestProjection];
59941
60034
  const unique = combined.filter((source, index) => combined.findIndex((candidate2) => isDeepStrictEqual11(candidate2, source)) === index);
59942
60035
  return { ...supplied, roots: [...new Set([...roots, ...supplied.roots])].sort(), sources: unique, ...agent === "gemini" ? { builtinNames } : {} };
package/bin/mcp.js CHANGED
@@ -20019,7 +20019,7 @@ var package_default;
20019
20019
  var init_package = __esm(() => {
20020
20020
  package_default = {
20021
20021
  name: "@hasna/skills",
20022
- version: "0.10.55",
20022
+ version: "0.10.56",
20023
20023
  description: "Skills library for AI coding agents",
20024
20024
  type: "module",
20025
20025
  bin: {
@@ -50788,6 +50788,97 @@ function read(path, changes) {
50788
50788
  throw new Error(`Unsupported or oversized native discovery input: ${path}`);
50789
50789
  return readFileSync15(path, "utf8");
50790
50790
  }
50791
+ function assertReviewedClaudeCacheClosures(home, config2, review) {
50792
+ const enabledIds = Object.entries(config2.enabledPlugins ?? {}).filter(([, enabled]) => enabled === true).map(([id]) => id);
50793
+ if (enabledIds.length === 0)
50794
+ return;
50795
+ const registryPath = join26(home, ".claude/plugins/installed_plugins.json");
50796
+ const cacheRoot = join26(home, ".claude/plugins/cache");
50797
+ const registryText = read(registryPath);
50798
+ if (registryText === null)
50799
+ return;
50800
+ const registry2 = parseConfig(registryText, registryPath);
50801
+ if (registry2.version !== 2 || !registry2.plugins || typeof registry2.plugins !== "object" || Array.isArray(registry2.plugins))
50802
+ throw new Error("Reviewed Claude installed plugin registrations are invalid");
50803
+ const cachePath = (path) => typeof path === "string" && isAbsolute15(path) && resolve17(path) === path && (path === cacheRoot || path.startsWith(cacheRoot + sep6));
50804
+ const activeRoots = new Set;
50805
+ for (const id of enabledIds) {
50806
+ const rows = registry2.plugins[id];
50807
+ if (rows === undefined)
50808
+ continue;
50809
+ if (!Array.isArray(rows))
50810
+ throw new Error("Reviewed Claude enabled plugin registrations must be an array");
50811
+ for (const row of rows) {
50812
+ if (!row || typeof row !== "object" || Array.isArray(row) || typeof row.installPath !== "string") {
50813
+ throw new Error("Reviewed Claude enabled plugin registration requires an object with an installPath string");
50814
+ }
50815
+ if (typeof row.installPath === "string" && (row.installPath === cacheRoot || row.installPath.startsWith(cacheRoot + sep6))) {
50816
+ if (!cachePath(row.installPath) || row.installPath.length > AGENT_POLICY_LIMITS.pathCharacters || row.version !== undefined && (typeof row.version !== "string" || basename9(row.installPath) !== row.version)) {
50817
+ throw new Error("Reviewed Claude discovery has a malformed active cache registration");
50818
+ }
50819
+ if (row.scope === "user" && (row.projectPath === undefined || row.projectPath === null)) {} else if (row.scope === "project" && typeof row.projectPath === "string" && isAbsolute15(row.projectPath) && resolve17(row.projectPath) === row.projectPath && row.projectPath.length <= AGENT_POLICY_LIMITS.pathCharacters) {} else
50820
+ throw new Error("Reviewed Claude discovery has an unsupported active cache registration scope");
50821
+ activeRoots.add(row.installPath);
50822
+ }
50823
+ }
50824
+ }
50825
+ if (activeRoots.size === 0)
50826
+ return;
50827
+ const registrySource = review.sources.find((source) => source.path === registryPath && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && (source.hashMode === undefined || source.hashMode === "bytes"));
50828
+ if (!registrySource)
50829
+ throw new Error("Reviewed Claude discovery must bind installed registrations for active cache plugins");
50830
+ const reviewedFile = (path, manifest = false) => review.sources.some((source) => source.path === path && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && (source.hashMode === undefined || source.hashMode === "bytes" || manifest && source.hashMode === "claude-plugin-manifest-v1"));
50831
+ const requireSource = (path, description, manifest = false) => {
50832
+ if (!reviewedFile(path, manifest))
50833
+ throw new Error(`Reviewed active Claude cache plugin ${description} is not bound`);
50834
+ };
50835
+ for (const root of activeRoots) {
50836
+ safe4(root);
50837
+ const rootStat = lstatSync18(root, { throwIfNoEntry: false });
50838
+ if (!rootStat?.isDirectory() || realpathSync5(root) !== root)
50839
+ throw new Error("Reviewed Claude active cache plugin root is missing or unsafe");
50840
+ const manifestPath = join26(root, ".claude-plugin/plugin.json");
50841
+ const manifestStat = lstatSync18(manifestPath, { throwIfNoEntry: false });
50842
+ let manifest;
50843
+ if (manifestStat) {
50844
+ if (!manifestStat.isFile())
50845
+ throw new Error("Reviewed active Claude cache plugin manifest is unsupported");
50846
+ requireSource(manifestPath, "manifest", true);
50847
+ const raw = read(manifestPath);
50848
+ if (raw === null)
50849
+ throw new Error("Reviewed Claude active cache plugin manifest is missing");
50850
+ manifest = JSON.parse(raw);
50851
+ if (!manifest || typeof manifest !== "object" || Array.isArray(manifest))
50852
+ throw new Error("Reviewed Claude active cache plugin manifest is invalid");
50853
+ } else if (!review.sources.some((source) => source.path === manifestPath && source.hashMode === "path-bytes" && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined)) {
50854
+ throw new Error("Reviewed Claude active cache plugin manifest absence requires a path-bytes witness");
50855
+ }
50856
+ const roots = new Set([join26(root, "skills")]);
50857
+ if (lstatSync18(join26(root, "SKILL.md"), { throwIfNoEntry: false })?.isFile())
50858
+ roots.add(root);
50859
+ const declared = manifest?.skills === undefined ? ["./skills"] : typeof manifest.skills === "string" ? [manifest.skills] : manifest.skills;
50860
+ if (!Array.isArray(declared) || declared.some((path) => typeof path !== "string"))
50861
+ throw new Error("Reviewed Claude active cache plugin has unsupported skill paths");
50862
+ for (const value of declared) {
50863
+ const path = resolve17(root, value);
50864
+ if (path !== root && !path.startsWith(root + sep6))
50865
+ throw new Error("Reviewed Claude active cache plugin skill path escapes its root");
50866
+ roots.add(path.endsWith(`${sep6}SKILL.md`) ? dirname16(path) : path);
50867
+ }
50868
+ for (const skillRoot of roots)
50869
+ if (!review.roots.includes(skillRoot))
50870
+ throw new Error("Reviewed Claude active cache plugin skill root is not covered");
50871
+ const hooksPath = join26(root, "hooks/hooks.json");
50872
+ const hooksStat = lstatSync18(hooksPath, { throwIfNoEntry: false });
50873
+ if (hooksStat) {
50874
+ if (!hooksStat.isFile())
50875
+ throw new Error("Reviewed Claude plugin hook manifest is unsupported");
50876
+ requireSource(hooksPath, "hook manifest");
50877
+ }
50878
+ if (typeof manifest?.hooks === "string")
50879
+ requireSource(resolve17(root, manifest.hooks), "manifest-declared hook source");
50880
+ }
50881
+ }
50791
50882
  function projectNativeDiscoveryFields(text2, format, fields2, path) {
50792
50883
  const object7 = format === "yaml" ? parseHermesConfig(text2) : parseConfig(text2, path, format === "toml");
50793
50884
  if (!object7 || typeof object7 !== "object" || Array.isArray(object7))
@@ -51254,6 +51345,8 @@ function resolveAgentDiscovery(options) {
51254
51345
  throw new Error("Reviewed Claude plugin manifest requires separately reviewed exact hook file sources");
51255
51346
  return { path: source.path, sha256: manifestSha256, hashMode: "claude-plugin-manifest-v1" };
51256
51347
  });
51348
+ if (agent === "claude")
51349
+ assertReviewedClaudeCacheClosures(home, config2, review);
51257
51350
  const combined = [...reviewedSources, ...reviewedSourcesWithManifestProjection];
51258
51351
  const unique = combined.filter((source, index) => combined.findIndex((candidate) => isDeepStrictEqual4(candidate, source)) === index);
51259
51352
  return { ...supplied, roots: [...new Set([...roots, ...supplied.roots])].sort(), sources: unique, ...agent === "gemini" ? { builtinNames } : {} };
package/bin/migrate.js CHANGED
@@ -25,7 +25,7 @@ import { join as join5 } from "path";
25
25
  // package.json
26
26
  var package_default = {
27
27
  name: "@hasna/skills",
28
- version: "0.10.55",
28
+ version: "0.10.56",
29
29
  description: "Skills library for AI coding agents",
30
30
  type: "module",
31
31
  bin: {
package/bin/server.js CHANGED
@@ -24140,7 +24140,7 @@ restoreLaunchCwd();
24140
24140
  // package.json
24141
24141
  var package_default = {
24142
24142
  name: "@hasna/skills",
24143
- version: "0.10.55",
24143
+ version: "0.10.56",
24144
24144
  description: "Skills library for AI coding agents",
24145
24145
  type: "module",
24146
24146
  bin: {
package/bin/worker.js CHANGED
@@ -24143,7 +24143,7 @@ import { randomUUID as randomUUID6 } from "crypto";
24143
24143
  // package.json
24144
24144
  var package_default = {
24145
24145
  name: "@hasna/skills",
24146
- version: "0.10.55",
24146
+ version: "0.10.56",
24147
24147
  description: "Skills library for AI coding agents",
24148
24148
  type: "module",
24149
24149
  bin: {
package/dist/index.js CHANGED
@@ -11833,7 +11833,7 @@ function replaceSkillSession(expected, replacement, plan, options = {}, beforeCo
11833
11833
  // package.json
11834
11834
  var package_default = {
11835
11835
  name: "@hasna/skills",
11836
- version: "0.10.55",
11836
+ version: "0.10.56",
11837
11837
  description: "Skills library for AI coding agents",
11838
11838
  type: "module",
11839
11839
  bin: {
@@ -20693,6 +20693,97 @@ function read2(path, changes) {
20693
20693
  throw new Error(`Unsupported or oversized native discovery input: ${path}`);
20694
20694
  return readFileSync20(path, "utf8");
20695
20695
  }
20696
+ function assertReviewedClaudeCacheClosures(home, config, review) {
20697
+ const enabledIds = Object.entries(config.enabledPlugins ?? {}).filter(([, enabled]) => enabled === true).map(([id]) => id);
20698
+ if (enabledIds.length === 0)
20699
+ return;
20700
+ const registryPath = join32(home, ".claude/plugins/installed_plugins.json");
20701
+ const cacheRoot = join32(home, ".claude/plugins/cache");
20702
+ const registryText = read2(registryPath);
20703
+ if (registryText === null)
20704
+ return;
20705
+ const registry = parseConfig(registryText, registryPath);
20706
+ if (registry.version !== 2 || !registry.plugins || typeof registry.plugins !== "object" || Array.isArray(registry.plugins))
20707
+ throw new Error("Reviewed Claude installed plugin registrations are invalid");
20708
+ const cachePath = (path) => typeof path === "string" && isAbsolute21(path) && resolve20(path) === path && (path === cacheRoot || path.startsWith(cacheRoot + sep8));
20709
+ const activeRoots = new Set;
20710
+ for (const id of enabledIds) {
20711
+ const rows = registry.plugins[id];
20712
+ if (rows === undefined)
20713
+ continue;
20714
+ if (!Array.isArray(rows))
20715
+ throw new Error("Reviewed Claude enabled plugin registrations must be an array");
20716
+ for (const row of rows) {
20717
+ if (!row || typeof row !== "object" || Array.isArray(row) || typeof row.installPath !== "string") {
20718
+ throw new Error("Reviewed Claude enabled plugin registration requires an object with an installPath string");
20719
+ }
20720
+ if (typeof row.installPath === "string" && (row.installPath === cacheRoot || row.installPath.startsWith(cacheRoot + sep8))) {
20721
+ if (!cachePath(row.installPath) || row.installPath.length > AGENT_POLICY_LIMITS.pathCharacters || row.version !== undefined && (typeof row.version !== "string" || basename11(row.installPath) !== row.version)) {
20722
+ throw new Error("Reviewed Claude discovery has a malformed active cache registration");
20723
+ }
20724
+ if (row.scope === "user" && (row.projectPath === undefined || row.projectPath === null)) {} else if (row.scope === "project" && typeof row.projectPath === "string" && isAbsolute21(row.projectPath) && resolve20(row.projectPath) === row.projectPath && row.projectPath.length <= AGENT_POLICY_LIMITS.pathCharacters) {} else
20725
+ throw new Error("Reviewed Claude discovery has an unsupported active cache registration scope");
20726
+ activeRoots.add(row.installPath);
20727
+ }
20728
+ }
20729
+ }
20730
+ if (activeRoots.size === 0)
20731
+ return;
20732
+ const registrySource = review.sources.find((source) => source.path === registryPath && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && (source.hashMode === undefined || source.hashMode === "bytes"));
20733
+ if (!registrySource)
20734
+ throw new Error("Reviewed Claude discovery must bind installed registrations for active cache plugins");
20735
+ const reviewedFile = (path, manifest = false) => review.sources.some((source) => source.path === path && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && (source.hashMode === undefined || source.hashMode === "bytes" || manifest && source.hashMode === "claude-plugin-manifest-v1"));
20736
+ const requireSource = (path, description, manifest = false) => {
20737
+ if (!reviewedFile(path, manifest))
20738
+ throw new Error(`Reviewed active Claude cache plugin ${description} is not bound`);
20739
+ };
20740
+ for (const root2 of activeRoots) {
20741
+ safe4(root2);
20742
+ const rootStat = lstatSync25(root2, { throwIfNoEntry: false });
20743
+ if (!rootStat?.isDirectory() || realpathSync11(root2) !== root2)
20744
+ throw new Error("Reviewed Claude active cache plugin root is missing or unsafe");
20745
+ const manifestPath = join32(root2, ".claude-plugin/plugin.json");
20746
+ const manifestStat = lstatSync25(manifestPath, { throwIfNoEntry: false });
20747
+ let manifest;
20748
+ if (manifestStat) {
20749
+ if (!manifestStat.isFile())
20750
+ throw new Error("Reviewed active Claude cache plugin manifest is unsupported");
20751
+ requireSource(manifestPath, "manifest", true);
20752
+ const raw = read2(manifestPath);
20753
+ if (raw === null)
20754
+ throw new Error("Reviewed Claude active cache plugin manifest is missing");
20755
+ manifest = JSON.parse(raw);
20756
+ if (!manifest || typeof manifest !== "object" || Array.isArray(manifest))
20757
+ throw new Error("Reviewed Claude active cache plugin manifest is invalid");
20758
+ } else if (!review.sources.some((source) => source.path === manifestPath && source.hashMode === "path-bytes" && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined)) {
20759
+ throw new Error("Reviewed Claude active cache plugin manifest absence requires a path-bytes witness");
20760
+ }
20761
+ const roots = new Set([join32(root2, "skills")]);
20762
+ if (lstatSync25(join32(root2, "SKILL.md"), { throwIfNoEntry: false })?.isFile())
20763
+ roots.add(root2);
20764
+ const declared = manifest?.skills === undefined ? ["./skills"] : typeof manifest.skills === "string" ? [manifest.skills] : manifest.skills;
20765
+ if (!Array.isArray(declared) || declared.some((path) => typeof path !== "string"))
20766
+ throw new Error("Reviewed Claude active cache plugin has unsupported skill paths");
20767
+ for (const value of declared) {
20768
+ const path = resolve20(root2, value);
20769
+ if (path !== root2 && !path.startsWith(root2 + sep8))
20770
+ throw new Error("Reviewed Claude active cache plugin skill path escapes its root");
20771
+ roots.add(path.endsWith(`${sep8}SKILL.md`) ? dirname20(path) : path);
20772
+ }
20773
+ for (const skillRoot of roots)
20774
+ if (!review.roots.includes(skillRoot))
20775
+ throw new Error("Reviewed Claude active cache plugin skill root is not covered");
20776
+ const hooksPath = join32(root2, "hooks/hooks.json");
20777
+ const hooksStat = lstatSync25(hooksPath, { throwIfNoEntry: false });
20778
+ if (hooksStat) {
20779
+ if (!hooksStat.isFile())
20780
+ throw new Error("Reviewed Claude plugin hook manifest is unsupported");
20781
+ requireSource(hooksPath, "hook manifest");
20782
+ }
20783
+ if (typeof manifest?.hooks === "string")
20784
+ requireSource(resolve20(root2, manifest.hooks), "manifest-declared hook source");
20785
+ }
20786
+ }
20696
20787
  function projectNativeDiscoveryFields(text2, format, fields2, path) {
20697
20788
  const object6 = format === "yaml" ? parseHermesConfig(text2) : parseConfig(text2, path, format === "toml");
20698
20789
  if (!object6 || typeof object6 !== "object" || Array.isArray(object6))
@@ -21281,6 +21372,8 @@ function resolveAgentDiscovery(options) {
21281
21372
  throw new Error("Reviewed Claude plugin manifest requires separately reviewed exact hook file sources");
21282
21373
  return { path: source.path, sha256: manifestSha256, hashMode: "claude-plugin-manifest-v1" };
21283
21374
  });
21375
+ if (agent === "claude")
21376
+ assertReviewedClaudeCacheClosures(home, config, review);
21284
21377
  const combined = [...reviewedSources, ...reviewedSourcesWithManifestProjection];
21285
21378
  const unique = combined.filter((source, index) => combined.findIndex((candidate2) => isDeepStrictEqual7(candidate2, source)) === index);
21286
21379
  return { ...supplied, roots: [...new Set([...roots, ...supplied.roots])].sort(), sources: unique, ...agent === "gemini" ? { builtinNames } : {} };
package/dist/sdk/index.js CHANGED
@@ -32373,7 +32373,7 @@ var init_event_sink = __esm(() => {
32373
32373
  // package.json
32374
32374
  var package_default = {
32375
32375
  name: "@hasna/skills",
32376
- version: "0.10.55",
32376
+ version: "0.10.56",
32377
32377
  description: "Skills library for AI coding agents",
32378
32378
  type: "module",
32379
32379
  bin: {
@@ -43533,6 +43533,97 @@ function read2(path, changes) {
43533
43533
  throw new Error(`Unsupported or oversized native discovery input: ${path}`);
43534
43534
  return readFileSync15(path, "utf8");
43535
43535
  }
43536
+ function assertReviewedClaudeCacheClosures(home, config, review) {
43537
+ const enabledIds = Object.entries(config.enabledPlugins ?? {}).filter(([, enabled]) => enabled === true).map(([id2]) => id2);
43538
+ if (enabledIds.length === 0)
43539
+ return;
43540
+ const registryPath = join26(home, ".claude/plugins/installed_plugins.json");
43541
+ const cacheRoot = join26(home, ".claude/plugins/cache");
43542
+ const registryText = read2(registryPath);
43543
+ if (registryText === null)
43544
+ return;
43545
+ const registry = parseConfig(registryText, registryPath);
43546
+ if (registry.version !== 2 || !registry.plugins || typeof registry.plugins !== "object" || Array.isArray(registry.plugins))
43547
+ throw new Error("Reviewed Claude installed plugin registrations are invalid");
43548
+ const cachePath = (path) => typeof path === "string" && isAbsolute20(path) && resolve19(path) === path && (path === cacheRoot || path.startsWith(cacheRoot + sep6));
43549
+ const activeRoots = new Set;
43550
+ for (const id2 of enabledIds) {
43551
+ const rows = registry.plugins[id2];
43552
+ if (rows === undefined)
43553
+ continue;
43554
+ if (!Array.isArray(rows))
43555
+ throw new Error("Reviewed Claude enabled plugin registrations must be an array");
43556
+ for (const row of rows) {
43557
+ if (!row || typeof row !== "object" || Array.isArray(row) || typeof row.installPath !== "string") {
43558
+ throw new Error("Reviewed Claude enabled plugin registration requires an object with an installPath string");
43559
+ }
43560
+ if (typeof row.installPath === "string" && (row.installPath === cacheRoot || row.installPath.startsWith(cacheRoot + sep6))) {
43561
+ if (!cachePath(row.installPath) || row.installPath.length > AGENT_POLICY_LIMITS.pathCharacters || row.version !== undefined && (typeof row.version !== "string" || basename9(row.installPath) !== row.version)) {
43562
+ throw new Error("Reviewed Claude discovery has a malformed active cache registration");
43563
+ }
43564
+ if (row.scope === "user" && (row.projectPath === undefined || row.projectPath === null)) {} else if (row.scope === "project" && typeof row.projectPath === "string" && isAbsolute20(row.projectPath) && resolve19(row.projectPath) === row.projectPath && row.projectPath.length <= AGENT_POLICY_LIMITS.pathCharacters) {} else
43565
+ throw new Error("Reviewed Claude discovery has an unsupported active cache registration scope");
43566
+ activeRoots.add(row.installPath);
43567
+ }
43568
+ }
43569
+ }
43570
+ if (activeRoots.size === 0)
43571
+ return;
43572
+ const registrySource = review.sources.find((source) => source.path === registryPath && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && (source.hashMode === undefined || source.hashMode === "bytes"));
43573
+ if (!registrySource)
43574
+ throw new Error("Reviewed Claude discovery must bind installed registrations for active cache plugins");
43575
+ const reviewedFile = (path, manifest = false) => review.sources.some((source) => source.path === path && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && (source.hashMode === undefined || source.hashMode === "bytes" || manifest && source.hashMode === "claude-plugin-manifest-v1"));
43576
+ const requireSource = (path, description, manifest = false) => {
43577
+ if (!reviewedFile(path, manifest))
43578
+ throw new Error(`Reviewed active Claude cache plugin ${description} is not bound`);
43579
+ };
43580
+ for (const root2 of activeRoots) {
43581
+ safe4(root2);
43582
+ const rootStat = lstatSync21(root2, { throwIfNoEntry: false });
43583
+ if (!rootStat?.isDirectory() || realpathSync9(root2) !== root2)
43584
+ throw new Error("Reviewed Claude active cache plugin root is missing or unsafe");
43585
+ const manifestPath = join26(root2, ".claude-plugin/plugin.json");
43586
+ const manifestStat = lstatSync21(manifestPath, { throwIfNoEntry: false });
43587
+ let manifest;
43588
+ if (manifestStat) {
43589
+ if (!manifestStat.isFile())
43590
+ throw new Error("Reviewed active Claude cache plugin manifest is unsupported");
43591
+ requireSource(manifestPath, "manifest", true);
43592
+ const raw = read2(manifestPath);
43593
+ if (raw === null)
43594
+ throw new Error("Reviewed Claude active cache plugin manifest is missing");
43595
+ manifest = JSON.parse(raw);
43596
+ if (!manifest || typeof manifest !== "object" || Array.isArray(manifest))
43597
+ throw new Error("Reviewed Claude active cache plugin manifest is invalid");
43598
+ } else if (!review.sources.some((source) => source.path === manifestPath && source.hashMode === "path-bytes" && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined)) {
43599
+ throw new Error("Reviewed Claude active cache plugin manifest absence requires a path-bytes witness");
43600
+ }
43601
+ const roots = new Set([join26(root2, "skills")]);
43602
+ if (lstatSync21(join26(root2, "SKILL.md"), { throwIfNoEntry: false })?.isFile())
43603
+ roots.add(root2);
43604
+ const declared = manifest?.skills === undefined ? ["./skills"] : typeof manifest.skills === "string" ? [manifest.skills] : manifest.skills;
43605
+ if (!Array.isArray(declared) || declared.some((path) => typeof path !== "string"))
43606
+ throw new Error("Reviewed Claude active cache plugin has unsupported skill paths");
43607
+ for (const value of declared) {
43608
+ const path = resolve19(root2, value);
43609
+ if (path !== root2 && !path.startsWith(root2 + sep6))
43610
+ throw new Error("Reviewed Claude active cache plugin skill path escapes its root");
43611
+ roots.add(path.endsWith(`${sep6}SKILL.md`) ? dirname17(path) : path);
43612
+ }
43613
+ for (const skillRoot of roots)
43614
+ if (!review.roots.includes(skillRoot))
43615
+ throw new Error("Reviewed Claude active cache plugin skill root is not covered");
43616
+ const hooksPath = join26(root2, "hooks/hooks.json");
43617
+ const hooksStat = lstatSync21(hooksPath, { throwIfNoEntry: false });
43618
+ if (hooksStat) {
43619
+ if (!hooksStat.isFile())
43620
+ throw new Error("Reviewed Claude plugin hook manifest is unsupported");
43621
+ requireSource(hooksPath, "hook manifest");
43622
+ }
43623
+ if (typeof manifest?.hooks === "string")
43624
+ requireSource(resolve19(root2, manifest.hooks), "manifest-declared hook source");
43625
+ }
43626
+ }
43536
43627
  function projectNativeDiscoveryFields(text2, format, fields2, path) {
43537
43628
  const object6 = format === "yaml" ? parseHermesConfig(text2) : parseConfig(text2, path, format === "toml");
43538
43629
  if (!object6 || typeof object6 !== "object" || Array.isArray(object6))
@@ -44121,6 +44212,8 @@ function resolveAgentDiscovery(options) {
44121
44212
  throw new Error("Reviewed Claude plugin manifest requires separately reviewed exact hook file sources");
44122
44213
  return { path: source.path, sha256: manifestSha256, hashMode: "claude-plugin-manifest-v1" };
44123
44214
  });
44215
+ if (agent === "claude")
44216
+ assertReviewedClaudeCacheClosures(home, config, review);
44124
44217
  const combined = [...reviewedSources, ...reviewedSourcesWithManifestProjection];
44125
44218
  const unique = combined.filter((source, index) => combined.findIndex((candidate2) => isDeepStrictEqual6(candidate2, source)) === index);
44126
44219
  return { ...supplied, roots: [...new Set([...roots, ...supplied.roots])].sort(), sources: unique, ...agent === "gemini" ? { builtinNames } : {} };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/skills",
3
- "version": "0.10.55",
3
+ "version": "0.10.56",
4
4
  "description": "Skills library for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {