@hasna/skills 0.9.12 → 0.9.13

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/bin/server.js CHANGED
@@ -24122,7 +24122,7 @@ var init_dist_es9 = __esm(() => {
24122
24122
  // package.json
24123
24123
  var package_default = {
24124
24124
  name: "@hasna/skills",
24125
- version: "0.9.12",
24125
+ version: "0.9.13",
24126
24126
  description: "Skills library for AI coding agents",
24127
24127
  type: "module",
24128
24128
  bin: {
@@ -35194,6 +35194,18 @@ function rowToSkill(row) {
35194
35194
  ...row.replacement_slug ? { replacementSlug: String(row.replacement_slug) } : {}
35195
35195
  };
35196
35196
  }
35197
+ function rowToPublishedSelectionState(row) {
35198
+ return {
35199
+ slug: String(row.slug),
35200
+ version: String(row.version),
35201
+ current: row.skill_exists ? {
35202
+ lifecycle: row.lifecycle === "archived" ? "archived" : "active",
35203
+ ...row.tombstoned_at ? { tombstonedAt: dateString(row.tombstoned_at) } : {}
35204
+ } : null,
35205
+ versionBundleSha256: typeof row.bundle_sha256 === "string" ? row.bundle_sha256 : null,
35206
+ bundleAvailable: Boolean(row.bundle_available)
35207
+ };
35208
+ }
35197
35209
  function rowToPin(row) {
35198
35210
  return {
35199
35211
  orgId: String(row.org_id),
@@ -35266,6 +35278,25 @@ function rowToSkillVersion(row) {
35266
35278
  };
35267
35279
  }
35268
35280
 
35281
+ // src/lib/profile-limits.ts
35282
+ var MAX_PROFILE_SELECTIONS = 4096;
35283
+ var MAX_PROFILE_DOCUMENT_BYTES = 8 * 1024 * 1024;
35284
+ var MAX_SKILL_SESSION_ID_CHARS = 256;
35285
+ var PROFILE_SESSION_ENVELOPE_BYTES = MAX_PROFILE_SELECTIONS * 67 + MAX_SKILL_SESSION_ID_CHARS * 12 + 512;
35286
+ var MAX_RESOLVED_PROFILE_BYTES = MAX_PROFILE_DOCUMENT_BYTES - PROFILE_SESSION_ENVELOPE_BYTES;
35287
+ function profileDocumentBytes(value) {
35288
+ return new TextEncoder().encode(JSON.stringify(value)).byteLength;
35289
+ }
35290
+ function resolvedProfileSnapshot(profile2, authority) {
35291
+ return {
35292
+ profileId: profile2.id,
35293
+ workspaceId: profile2.workspaceId,
35294
+ profileRevision: profile2.revision,
35295
+ authority,
35296
+ selections: profile2.selections.map((selection) => ({ ...selection, authority, workspaceId: profile2.workspaceId, profileRevision: profile2.revision }))
35297
+ };
35298
+ }
35299
+
35269
35300
  // src/server/types.ts
35270
35301
  class StaleLeaseGenerationError extends Error {
35271
35302
  runId;
@@ -35968,6 +35999,22 @@ class SqliteSkillsStore {
35968
35999
  const row = this.get("SELECT * FROM skills_versions WHERE org_id = ? AND slug = ? AND version = ? LIMIT 1", [principal.orgId, slug, version2]);
35969
36000
  return row ? rowToSkillVersion(row) : null;
35970
36001
  }
36002
+ async getPublishedSelectionStates(principal, selections2) {
36003
+ if (selections2.length > MAX_PROFILE_SELECTIONS)
36004
+ throw new RangeError("Published selection lookup exceeds the profile selection limit");
36005
+ if (!selections2.length)
36006
+ return [];
36007
+ const requested = JSON.stringify(selections2.map(({ slug, version: version2 }) => ({ slug, version: version2 })));
36008
+ return this.all(`SELECT json_extract(q.value, '$.slug') AS slug, json_extract(q.value, '$.version') AS version,
36009
+ r.slug IS NOT NULL AS skill_exists, r.lifecycle, r.tombstoned_at,
36010
+ v.bundle_sha256, b.sha256 IS NOT NULL AS bundle_available
36011
+ FROM json_each(?) q
36012
+ LEFT JOIN skills_registry r ON r.org_id = ? AND r.slug = json_extract(q.value, '$.slug')
36013
+ LEFT JOIN skills_versions v ON v.org_id = ?
36014
+ AND v.slug = json_extract(q.value, '$.slug') AND v.version = json_extract(q.value, '$.version')
36015
+ LEFT JOIN skills_bundles b ON b.org_id = ? AND b.sha256 = v.bundle_sha256
36016
+ ORDER BY CAST(q.key AS INTEGER)`, [requested, principal.orgId, principal.orgId, principal.orgId]).map(rowToPublishedSelectionState);
36017
+ }
35971
36018
  async pinSkill(principal, slug, metadata = {}) {
35972
36019
  const row = this.get(`INSERT INTO skills_pins (org_id, principal, slug, pinned_at, metadata_json)
35973
36020
  VALUES (?, ?, ?, ?, ?)
@@ -36532,25 +36579,6 @@ function selectionAliasError(selections2) {
36532
36579
  }
36533
36580
  }
36534
36581
 
36535
- // src/lib/profile-limits.ts
36536
- var MAX_PROFILE_SELECTIONS = 4096;
36537
- var MAX_PROFILE_DOCUMENT_BYTES = 8 * 1024 * 1024;
36538
- var MAX_SKILL_SESSION_ID_CHARS = 256;
36539
- var PROFILE_SESSION_ENVELOPE_BYTES = MAX_PROFILE_SELECTIONS * 67 + MAX_SKILL_SESSION_ID_CHARS * 12 + 512;
36540
- var MAX_RESOLVED_PROFILE_BYTES = MAX_PROFILE_DOCUMENT_BYTES - PROFILE_SESSION_ENVELOPE_BYTES;
36541
- function profileDocumentBytes(value) {
36542
- return new TextEncoder().encode(JSON.stringify(value)).byteLength;
36543
- }
36544
- function resolvedProfileSnapshot(profile2, authority) {
36545
- return {
36546
- profileId: profile2.id,
36547
- workspaceId: profile2.workspaceId,
36548
- profileRevision: profile2.revision,
36549
- authority,
36550
- selections: profile2.selections.map((selection) => ({ ...selection, authority, workspaceId: profile2.workspaceId, profileRevision: profile2.revision }))
36551
- };
36552
- }
36553
-
36554
36582
  // src/lib/selection-cache.ts
36555
36583
  class SkillSelectionError extends Error {
36556
36584
  code;
@@ -37601,16 +37629,21 @@ function selected(value) {
37601
37629
  return selections2;
37602
37630
  }
37603
37631
  async function validatePublished(store, principal, selections2) {
37604
- for (const selection2 of selections2) {
37605
- const current = await store.getSkill(principal, selection2.slug);
37632
+ const states = await store.getPublishedSelectionStates(principal, selections2);
37633
+ if (states.length !== selections2.length)
37634
+ throw new SkillRequestError(503, "SELECTION_UNAVAILABLE", "Selected publication state is unavailable");
37635
+ for (const [index, selection2] of selections2.entries()) {
37636
+ const state2 = states[index];
37637
+ if (!state2 || state2.slug !== selection2.slug || state2.version !== selection2.version)
37638
+ throw new SkillRequestError(503, "SELECTION_UNAVAILABLE", "Selected publication state is unavailable");
37639
+ const current = state2.current;
37606
37640
  if (!current || current.tombstonedAt)
37607
37641
  throw new SkillRequestError(current?.tombstonedAt ? 410 : 404, "SELECTION_UNAVAILABLE", `Selected skill '${selection2.slug}' is unavailable`);
37608
37642
  if (current.lifecycle === "archived")
37609
37643
  throw new SkillRequestError(409, "SELECTION_ARCHIVED", `Selected skill '${selection2.slug}' is archived`);
37610
- const version2 = await store.getSkillVersion(principal, selection2.slug, selection2.version);
37611
- if (!version2 || `sha256:${version2.bundleSha256}` !== selection2.bundleDigest)
37644
+ if (!state2.versionBundleSha256 || `sha256:${state2.versionBundleSha256}` !== selection2.bundleDigest)
37612
37645
  throw new SkillRequestError(409, "SELECTION_VERSION_MISMATCH", `Selected version of '${selection2.slug}' does not match its published digest`);
37613
- if (!await store.getSkillBundle(principal, version2.bundleSha256))
37646
+ if (!state2.bundleAvailable)
37614
37647
  throw new SkillRequestError(503, "SELECTION_BUNDLE_UNAVAILABLE", "Selected bundle is unavailable");
37615
37648
  }
37616
37649
  }
@@ -43171,6 +43204,25 @@ class MemorySkillsStore {
43171
43204
  const found = this.versions.get(versionKey(principal.orgId, slug, version2));
43172
43205
  return found && found.orgId === principal.orgId ? found : null;
43173
43206
  }
43207
+ async getPublishedSelectionStates(principal, selections2) {
43208
+ if (selections2.length > MAX_PROFILE_SELECTIONS)
43209
+ throw new RangeError("Published selection lookup exceeds the profile selection limit");
43210
+ return selections2.map(({ slug, version: version2 }) => {
43211
+ const current = this.skills.get(skillKey(principal.orgId, slug));
43212
+ const selectedVersion = this.versions.get(versionKey(principal.orgId, slug, version2));
43213
+ const bundle = selectedVersion && this.bundles.get(skillKey(principal.orgId, selectedVersion.bundleSha256));
43214
+ return {
43215
+ slug,
43216
+ version: version2,
43217
+ current: current?.orgId === principal.orgId ? {
43218
+ lifecycle: current.lifecycle,
43219
+ ...current.tombstonedAt ? { tombstonedAt: current.tombstonedAt } : {}
43220
+ } : null,
43221
+ versionBundleSha256: selectedVersion?.orgId === principal.orgId ? selectedVersion.bundleSha256 : null,
43222
+ bundleAvailable: bundle?.orgId === principal.orgId
43223
+ };
43224
+ });
43225
+ }
43174
43226
  async listSkills(principal) {
43175
43227
  return Array.from(this.skills.values()).filter((skill) => skill.orgId === principal.orgId && !skill.tombstonedAt).sort((a4, b4) => a4.slug.localeCompare(b4.slug));
43176
43228
  }
@@ -43979,6 +44031,25 @@ class PostgresSkillsStore {
43979
44031
  `;
43980
44032
  return rows[0] ? rowToSkillVersion(rows[0]) : null;
43981
44033
  }
44034
+ async getPublishedSelectionStates(principal, selections2) {
44035
+ if (selections2.length > MAX_PROFILE_SELECTIONS)
44036
+ throw new RangeError("Published selection lookup exceeds the profile selection limit");
44037
+ if (!selections2.length)
44038
+ return [];
44039
+ const requested = JSON.stringify(selections2.map(({ slug, version: version2 }) => ({ slug, version: version2 })));
44040
+ const rows = await this.sql`
44041
+ SELECT q.value->>'slug' AS slug, q.value->>'version' AS version,
44042
+ r.slug IS NOT NULL AS skill_exists, r.lifecycle, r.tombstoned_at,
44043
+ v.bundle_sha256, b.sha256 IS NOT NULL AS bundle_available
44044
+ FROM jsonb_array_elements(${requested}::text::jsonb) WITH ORDINALITY AS q(value, ordinal)
44045
+ LEFT JOIN skills_registry r ON r.org_id = ${principal.orgId} AND r.slug = q.value->>'slug'
44046
+ LEFT JOIN skills_versions v ON v.org_id = ${principal.orgId}
44047
+ AND v.slug = q.value->>'slug' AND v.version = q.value->>'version'
44048
+ LEFT JOIN skills_bundles b ON b.org_id = ${principal.orgId} AND b.sha256 = v.bundle_sha256
44049
+ ORDER BY q.ordinal
44050
+ `;
44051
+ return rows.map(rowToPublishedSelectionState);
44052
+ }
43982
44053
  async pinSkill(principal, slug, metadata = {}) {
43983
44054
  const rows = await this.sql`
43984
44055
  INSERT INTO skills_pins (org_id, principal, slug, pinned_at, metadata_json)
package/bin/worker.js CHANGED
@@ -24125,7 +24125,7 @@ import { randomUUID as randomUUID6 } from "crypto";
24125
24125
  // package.json
24126
24126
  var package_default = {
24127
24127
  name: "@hasna/skills",
24128
- version: "0.9.12",
24128
+ version: "0.9.13",
24129
24129
  description: "Skills library for AI coding agents",
24130
24130
  type: "module",
24131
24131
  bin: {
@@ -34942,6 +34942,18 @@ function rowToSkill(row) {
34942
34942
  ...row.replacement_slug ? { replacementSlug: String(row.replacement_slug) } : {}
34943
34943
  };
34944
34944
  }
34945
+ function rowToPublishedSelectionState(row) {
34946
+ return {
34947
+ slug: String(row.slug),
34948
+ version: String(row.version),
34949
+ current: row.skill_exists ? {
34950
+ lifecycle: row.lifecycle === "archived" ? "archived" : "active",
34951
+ ...row.tombstoned_at ? { tombstonedAt: dateString(row.tombstoned_at) } : {}
34952
+ } : null,
34953
+ versionBundleSha256: typeof row.bundle_sha256 === "string" ? row.bundle_sha256 : null,
34954
+ bundleAvailable: Boolean(row.bundle_available)
34955
+ };
34956
+ }
34945
34957
  function rowToPin(row) {
34946
34958
  return {
34947
34959
  orgId: String(row.org_id),
@@ -35014,6 +35026,13 @@ function rowToSkillVersion(row) {
35014
35026
  };
35015
35027
  }
35016
35028
 
35029
+ // src/lib/profile-limits.ts
35030
+ var MAX_PROFILE_SELECTIONS = 4096;
35031
+ var MAX_PROFILE_DOCUMENT_BYTES = 8 * 1024 * 1024;
35032
+ var MAX_SKILL_SESSION_ID_CHARS = 256;
35033
+ var PROFILE_SESSION_ENVELOPE_BYTES = MAX_PROFILE_SELECTIONS * 67 + MAX_SKILL_SESSION_ID_CHARS * 12 + 512;
35034
+ var MAX_RESOLVED_PROFILE_BYTES = MAX_PROFILE_DOCUMENT_BYTES - PROFILE_SESSION_ENVELOPE_BYTES;
35035
+
35017
35036
  // src/server/types.ts
35018
35037
  class StaleLeaseGenerationError extends Error {
35019
35038
  runId;
@@ -35715,6 +35734,22 @@ class SqliteSkillsStore {
35715
35734
  const row = this.get("SELECT * FROM skills_versions WHERE org_id = ? AND slug = ? AND version = ? LIMIT 1", [principal.orgId, slug, version2]);
35716
35735
  return row ? rowToSkillVersion(row) : null;
35717
35736
  }
35737
+ async getPublishedSelectionStates(principal, selections2) {
35738
+ if (selections2.length > MAX_PROFILE_SELECTIONS)
35739
+ throw new RangeError("Published selection lookup exceeds the profile selection limit");
35740
+ if (!selections2.length)
35741
+ return [];
35742
+ const requested = JSON.stringify(selections2.map(({ slug, version: version2 }) => ({ slug, version: version2 })));
35743
+ return this.all(`SELECT json_extract(q.value, '$.slug') AS slug, json_extract(q.value, '$.version') AS version,
35744
+ r.slug IS NOT NULL AS skill_exists, r.lifecycle, r.tombstoned_at,
35745
+ v.bundle_sha256, b.sha256 IS NOT NULL AS bundle_available
35746
+ FROM json_each(?) q
35747
+ LEFT JOIN skills_registry r ON r.org_id = ? AND r.slug = json_extract(q.value, '$.slug')
35748
+ LEFT JOIN skills_versions v ON v.org_id = ?
35749
+ AND v.slug = json_extract(q.value, '$.slug') AND v.version = json_extract(q.value, '$.version')
35750
+ LEFT JOIN skills_bundles b ON b.org_id = ? AND b.sha256 = v.bundle_sha256
35751
+ ORDER BY CAST(q.key AS INTEGER)`, [requested, principal.orgId, principal.orgId, principal.orgId]).map(rowToPublishedSelectionState);
35752
+ }
35718
35753
  async pinSkill(principal, slug, metadata = {}) {
35719
35754
  const row = this.get(`INSERT INTO skills_pins (org_id, principal, slug, pinned_at, metadata_json)
35720
35755
  VALUES (?, ?, ?, ?, ?)
@@ -35996,13 +36031,6 @@ var init_client = __esm2(() => {
35996
36031
  init_client();
35997
36032
  init_client();
35998
36033
 
35999
- // src/lib/profile-limits.ts
36000
- var MAX_PROFILE_SELECTIONS = 4096;
36001
- var MAX_PROFILE_DOCUMENT_BYTES = 8 * 1024 * 1024;
36002
- var MAX_SKILL_SESSION_ID_CHARS = 256;
36003
- var PROFILE_SESSION_ENVELOPE_BYTES = MAX_PROFILE_SELECTIONS * 67 + MAX_SKILL_SESSION_ID_CHARS * 12 + 512;
36004
- var MAX_RESOLVED_PROFILE_BYTES = MAX_PROFILE_DOCUMENT_BYTES - PROFILE_SESSION_ENVELOPE_BYTES;
36005
-
36006
36034
  // src/lib/selection-cache.ts
36007
36035
  var MAX_CACHED_PROFILE_AGE_MS = 24 * 60 * 60 * 1000;
36008
36036
 
@@ -36707,6 +36735,25 @@ class MemorySkillsStore {
36707
36735
  const found = this.versions.get(versionKey(principal.orgId, slug, version2));
36708
36736
  return found && found.orgId === principal.orgId ? found : null;
36709
36737
  }
36738
+ async getPublishedSelectionStates(principal, selections2) {
36739
+ if (selections2.length > MAX_PROFILE_SELECTIONS)
36740
+ throw new RangeError("Published selection lookup exceeds the profile selection limit");
36741
+ return selections2.map(({ slug, version: version2 }) => {
36742
+ const current = this.skills.get(skillKey(principal.orgId, slug));
36743
+ const selectedVersion = this.versions.get(versionKey(principal.orgId, slug, version2));
36744
+ const bundle = selectedVersion && this.bundles.get(skillKey(principal.orgId, selectedVersion.bundleSha256));
36745
+ return {
36746
+ slug,
36747
+ version: version2,
36748
+ current: current?.orgId === principal.orgId ? {
36749
+ lifecycle: current.lifecycle,
36750
+ ...current.tombstonedAt ? { tombstonedAt: current.tombstonedAt } : {}
36751
+ } : null,
36752
+ versionBundleSha256: selectedVersion?.orgId === principal.orgId ? selectedVersion.bundleSha256 : null,
36753
+ bundleAvailable: bundle?.orgId === principal.orgId
36754
+ };
36755
+ });
36756
+ }
36710
36757
  async listSkills(principal) {
36711
36758
  return Array.from(this.skills.values()).filter((skill) => skill.orgId === principal.orgId && !skill.tombstonedAt).sort((a3, b3) => a3.slug.localeCompare(b3.slug));
36712
36759
  }
@@ -37515,6 +37562,25 @@ class PostgresSkillsStore {
37515
37562
  `;
37516
37563
  return rows[0] ? rowToSkillVersion(rows[0]) : null;
37517
37564
  }
37565
+ async getPublishedSelectionStates(principal, selections2) {
37566
+ if (selections2.length > MAX_PROFILE_SELECTIONS)
37567
+ throw new RangeError("Published selection lookup exceeds the profile selection limit");
37568
+ if (!selections2.length)
37569
+ return [];
37570
+ const requested = JSON.stringify(selections2.map(({ slug, version: version2 }) => ({ slug, version: version2 })));
37571
+ const rows = await this.sql`
37572
+ SELECT q.value->>'slug' AS slug, q.value->>'version' AS version,
37573
+ r.slug IS NOT NULL AS skill_exists, r.lifecycle, r.tombstoned_at,
37574
+ v.bundle_sha256, b.sha256 IS NOT NULL AS bundle_available
37575
+ FROM jsonb_array_elements(${requested}::text::jsonb) WITH ORDINALITY AS q(value, ordinal)
37576
+ LEFT JOIN skills_registry r ON r.org_id = ${principal.orgId} AND r.slug = q.value->>'slug'
37577
+ LEFT JOIN skills_versions v ON v.org_id = ${principal.orgId}
37578
+ AND v.slug = q.value->>'slug' AND v.version = q.value->>'version'
37579
+ LEFT JOIN skills_bundles b ON b.org_id = ${principal.orgId} AND b.sha256 = v.bundle_sha256
37580
+ ORDER BY q.ordinal
37581
+ `;
37582
+ return rows.map(rowToPublishedSelectionState);
37583
+ }
37518
37584
  async pinSkill(principal, slug, metadata = {}) {
37519
37585
  const rows = await this.sql`
37520
37586
  INSERT INTO skills_pins (org_id, principal, slug, pinned_at, metadata_json)
package/dist/index.d.ts CHANGED
@@ -62,8 +62,8 @@ export { syncSelectionProfile } from "./lib/selection-resolver.js";
62
62
  export { inspectSkillSession, reconcileSkillSession, type SessionReconciliationInput, type SessionReconciliationOptions } from "./lib/session-reconciliation.js";
63
63
  export { planAgentIntegration, planClaudeStopHookUpdate, applyAgentIntegration, inventoryNativeSkills, archiveNativeSkills } from "./lib/agent-integration.js";
64
64
  export { captureDiscoveryDirectories, captureDiscoveryByteSources, captureDiscoveryPathSources, type DiscoveryDirectory, type DiscoverySource } from "./lib/agent-discovery.js";
65
- export { captureClaudeMarketplaceRegistry } from "./lib/claude-marketplace-registry.js";
66
- export { captureClaudeSettings } from "./lib/claude-settings-witness.js";
65
+ export { captureClaudeMarketplaceRegistry, captureClaudeMarketplaceRegistryV2 } from "./lib/claude-marketplace-registry.js";
66
+ export { captureClaudeSettings, captureClaudeSettingsV2 } from "./lib/claude-settings-witness.js";
67
67
  export { buildPluginProjection, pluginTreeDigest, type PluginProjectionManifest, type PluginPayloadMapping, type PluginFileWitness } from "./lib/plugin-projection.js";
68
68
  export { planPluginAdmission, admitPlugin, resolveAdmittedPlugin, pluginResolverCommand, type PluginAdmissionTarget, type PluginAdmissionBinding, type PluginAdmissionPrincipal, type PluginAdmissionPlan, type PluginAdmissionIdentity, type PluginBundleIdentity, type PluginRoutingIdentity, type PluginAdmissionReceipt } from "./lib/plugin-admission.js";
69
69
  export { captureManagedPluginRegistry, type ManagedPluginRegistrationWitness } from "./lib/plugin-discovery.js";
package/dist/index.js CHANGED
@@ -9055,10 +9055,10 @@ function assertAgentPolicyCollections(policy) {
9055
9055
  text(source.path);
9056
9056
  requireBound(source.sha256 === null || typeof source.sha256 === "string" && /^[a-f0-9]{64}$/.test(source.sha256));
9057
9057
  if (source.hashMode !== undefined)
9058
- requireBound(["bytes", "path-bytes", "claude-plugin-registry", "claude-marketplace-registry", "claude-settings-v1"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
9059
- if (source.hashMode === "claude-settings-v1")
9058
+ requireBound(["bytes", "path-bytes", "claude-plugin-registry", "claude-marketplace-registry", "claude-settings-v1", "claude-settings-v2", "claude-marketplace-registry-v2"].includes(source.hashMode) && source.format === undefined && source.fields === undefined && (source.hashMode === "bytes" || source.sha256 !== null));
9059
+ if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2")
9060
9060
  requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute2(source.path) && resolve2(source.path) === source.path && basename2(source.path) === "settings.json");
9061
- if (source.hashMode === "claude-marketplace-registry")
9061
+ if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2")
9062
9062
  requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute2(source.path) && resolve2(source.path) === source.path && basename2(source.path) === "known_marketplaces.json");
9063
9063
  if (source.hashMode === "claude-plugin-registry") {
9064
9064
  requireBound(agent === "claude");
@@ -9610,7 +9610,7 @@ import { isAbsolute as isAbsolute11, join as join23, resolve as resolve11 } from
9610
9610
  // package.json
9611
9611
  var package_default = {
9612
9612
  name: "@hasna/skills",
9613
- version: "0.9.12",
9613
+ version: "0.9.13",
9614
9614
  description: "Skills library for AI coding agents",
9615
9615
  type: "module",
9616
9616
  bin: {
@@ -12867,9 +12867,33 @@ function assertClaudeStopHookReplacement(before, replacement) {
12867
12867
  }
12868
12868
  need2(JSON.stringify(withoutStop(original)) === JSON.stringify(withoutStop(next)), "Stop update changed settings outside hooks.Stop");
12869
12869
  }
12870
- function settingsDigest(text2) {
12870
+ var PERSISTED_EFFORT_LEVELS = new Set(["low", "medium", "high", "xhigh"]);
12871
+ function inferencePreference(key, child) {
12872
+ if (key === "skipDangerousModePermissionPrompt") {
12873
+ need2(child.kind === "literal" && (child.value === "true" || child.value === "false"), "contains an invalid dialog acknowledgment");
12874
+ return true;
12875
+ }
12876
+ if (key === "effortLevel") {
12877
+ need2(child.kind === "string" && PERSISTED_EFFORT_LEVELS.has(child.value), "contains an invalid persisted effort level");
12878
+ return true;
12879
+ }
12880
+ if (key !== "modelSettings")
12881
+ return false;
12882
+ need2(child.kind === "object", "requires an object of model preferences");
12883
+ child.entries = child.entries.filter(([model, preferences]) => {
12884
+ if (!BUILTIN_MODEL_SELECTIONS.has(model) || preferences.kind !== "object" || preferences.entries.length !== 1 || preferences.entries[0][0] !== "effortLevel")
12885
+ return true;
12886
+ const effort = preferences.entries[0][1];
12887
+ need2(effort.kind === "string" && PERSISTED_EFFORT_LEVELS.has(effort.value), "contains an invalid persisted model effort level");
12888
+ return false;
12889
+ });
12890
+ return child.entries.length === 0;
12891
+ }
12892
+ function settingsDigest(text2, version = 1) {
12871
12893
  const value = parse3(text2);
12872
12894
  value.entries = value.entries.filter(([key, child]) => {
12895
+ if (version === 2 && inferencePreference(key, child))
12896
+ return false;
12873
12897
  if (key === "model") {
12874
12898
  need2(child.kind === "string", "contains an invalid model selection");
12875
12899
  return !BUILTIN_MODEL_SELECTIONS.has(child.value);
@@ -12884,7 +12908,7 @@ function settingsDigest(text2) {
12884
12908
  }
12885
12909
  return true;
12886
12910
  });
12887
- return createHash9("sha256").update("hasna.skills.claude-settings.v1\x00").update(JSON.stringify(canonical2(value))).digest("hex");
12911
+ return createHash9("sha256").update(`hasna.skills.claude-settings.v${version}\x00`).update(JSON.stringify(canonical2(value))).digest("hex");
12888
12912
  }
12889
12913
  function hashClaudeSettingsReplacement(text2, budget) {
12890
12914
  need2(typeof text2 === "string", "requires settings text");
@@ -12896,6 +12920,16 @@ function hashClaudeSettingsReplacement(text2, budget) {
12896
12920
  function captureClaudeSettings(path, budget = { remaining: 256 * 1024 * 1024 }) {
12897
12921
  return { path, hashMode: "claude-settings-v1", sha256: settingsDigest(readSettings(path, budget)) };
12898
12922
  }
12923
+ function captureClaudeSettingsV2(path, budget = { remaining: 256 * 1024 * 1024 }) {
12924
+ return { path, hashMode: "claude-settings-v2", sha256: settingsDigest(readSettings(path, budget), 2) };
12925
+ }
12926
+ function hashClaudeSettingsReplacementV2(text2, budget) {
12927
+ need2(typeof text2 === "string", "requires settings text");
12928
+ const bytes = Buffer.byteLength(text2);
12929
+ need2(bytes <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes && Number.isSafeInteger(budget.remaining) && budget.remaining >= bytes, "exceeds its byte limit");
12930
+ budget.remaining -= bytes;
12931
+ return settingsDigest(text2, 2);
12932
+ }
12899
12933
 
12900
12934
  // src/lib/agent-integration.ts
12901
12935
  var sha = (data) => createHash10("sha256").update(data).digest("hex");
@@ -16261,9 +16295,13 @@ function textField(value) {
16261
16295
  need3(value?.kind === "string" && value.value.length > 0 && !/[\x00-\x1f\x7f]/.test(value.value), "contains an invalid recognized field");
16262
16296
  return value.value;
16263
16297
  }
16264
- function normalizeRow(row) {
16265
- if (!exactKeys(row, ["source", "installLocation", "lastUpdated"]))
16266
- return;
16298
+ function normalizeRow(row, version = 1) {
16299
+ if (!exactKeys(row, ["source", "installLocation", "lastUpdated"])) {
16300
+ if (version !== 2 || !exactKeys(row, ["source", "installLocation", "lastUpdated", "autoUpdate"]))
16301
+ return;
16302
+ const autoUpdate = field(row, "autoUpdate");
16303
+ need3(autoUpdate?.kind === "literal" && (autoUpdate.value === "true" || autoUpdate.value === "false"), "contains an invalid automatic-update preference");
16304
+ }
16267
16305
  const source = field(row, "source");
16268
16306
  if (source?.kind !== "object")
16269
16307
  return;
@@ -16290,6 +16328,13 @@ function captureClaudeMarketplaceRegistry(path, budget = { remaining: 256 * 1024
16290
16328
  const sha2562 = createHash13("sha256").update("hasna.skills.claude-marketplace-registry.v1\x00").update(JSON.stringify(value)).digest("hex");
16291
16329
  return { path, hashMode: "claude-marketplace-registry", sha256: sha2562 };
16292
16330
  }
16331
+ function captureClaudeMarketplaceRegistryV2(path, budget = { remaining: 256 * 1024 * 1024 }) {
16332
+ const value = parse6(readRegistry2(path, budget));
16333
+ for (const [, row] of value.entries)
16334
+ normalizeRow(row, 2);
16335
+ const sha2562 = createHash13("sha256").update("hasna.skills.claude-marketplace-registry.v2\x00").update(JSON.stringify(value)).digest("hex");
16336
+ return { path, hashMode: "claude-marketplace-registry-v2", sha256: sha2562 };
16337
+ }
16293
16338
 
16294
16339
  // src/lib/codex-hook-discovery-recovery.ts
16295
16340
  import { createHash as createHash15 } from "crypto";
@@ -16393,18 +16438,18 @@ function read2(path, changes) {
16393
16438
  return readFileSync16(path, "utf8");
16394
16439
  }
16395
16440
  function projected(source, changes, budget = discoveryByteBudget()) {
16396
- if (source.hashMode === "claude-settings-v1") {
16441
+ if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") {
16397
16442
  if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
16398
16443
  throw new Error("Claude settings witnesses require exact typed metadata");
16399
- const current = captureClaudeSettings(source.path, budget).sha256;
16444
+ const current = (source.hashMode === "claude-settings-v2" ? captureClaudeSettingsV2 : captureClaudeSettings)(source.path, budget).sha256;
16400
16445
  if (current !== source.sha256)
16401
16446
  throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
16402
- return changes?.has(source.path) ? hashClaudeSettingsReplacement(changes.get(source.path), budget) : current;
16447
+ return changes?.has(source.path) ? (source.hashMode === "claude-settings-v2" ? hashClaudeSettingsReplacementV2 : hashClaudeSettingsReplacement)(changes.get(source.path), budget) : current;
16403
16448
  }
16404
- if (source.hashMode === "claude-marketplace-registry") {
16449
+ if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") {
16405
16450
  if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256) || changes?.has(source.path))
16406
16451
  throw new Error("Claude marketplace witnesses require an exact reviewed registry without synthetic changes");
16407
- const sha2562 = captureClaudeMarketplaceRegistry(source.path, budget).sha256;
16452
+ const sha2562 = (source.hashMode === "claude-marketplace-registry-v2" ? captureClaudeMarketplaceRegistryV2 : captureClaudeMarketplaceRegistry)(source.path, budget).sha256;
16408
16453
  if (sha2562 !== source.sha256)
16409
16454
  throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
16410
16455
  return sha2562;
@@ -16461,9 +16506,9 @@ function verifyAgentDiscovery(binding, codexRecovery) {
16461
16506
  safe4(root2);
16462
16507
  }
16463
16508
  function assertMarketplaceBinding(binding, source) {
16464
- if (source.hashMode === "claude-settings-v1" && (binding.agent !== "claude" || binding.method !== "reviewed" || basename8(source.path) !== "settings.json"))
16509
+ if ((source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && (binding.agent !== "claude" || binding.method !== "reviewed" || basename8(source.path) !== "settings.json"))
16465
16510
  throw new Error("Claude settings witnesses require explicit reviewed Claude configuration");
16466
- if (source.hashMode === "claude-marketplace-registry" && (binding.agent !== "claude" || binding.method !== "reviewed"))
16511
+ if ((source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") && (binding.agent !== "claude" || binding.method !== "reviewed"))
16467
16512
  throw new Error("Claude marketplace witnesses require explicit reviewed Claude discovery");
16468
16513
  }
16469
16514
  function rebindAgentDiscovery(binding, changes) {
@@ -16604,7 +16649,7 @@ function resolveAgentDiscovery(options) {
16604
16649
  if (review.sources.some((source) => source.format !== undefined || source.fields !== undefined))
16605
16650
  throw new Error("Explicit discovery reviews require full source-file hashes");
16606
16651
  verifyAgentDiscovery(supplied);
16607
- if (review.sources.some((source) => source.hashMode === "claude-settings-v1" && source.path !== canonical4(configPath)))
16652
+ if (review.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && source.path !== canonical4(configPath)))
16608
16653
  throw new Error("Semantic settings witnesses must name the configured Claude configuration source");
16609
16654
  if (!review.sources.some((source) => source.path === canonical4(configPath)))
16610
16655
  throw new Error("Discovery review must include the agent configuration source");
@@ -43728,7 +43773,9 @@ export {
43728
43773
  captureDiscoveryPathSources,
43729
43774
  captureDiscoveryDirectories,
43730
43775
  captureDiscoveryByteSources,
43776
+ captureClaudeSettingsV2,
43731
43777
  captureClaudeSettings,
43778
+ captureClaudeMarketplaceRegistryV2,
43732
43779
  captureClaudeMarketplaceRegistry,
43733
43780
  canonicalizeManifest,
43734
43781
  buildSkillsS3ObjectUrl,
@@ -6,7 +6,7 @@ export { captureDiscoveryDirectories, type DiscoveryDirectory } from "./agent-di
6
6
  export interface DiscoverySource {
7
7
  path: string;
8
8
  sha256: string | null;
9
- hashMode?: "bytes" | "path-bytes" | "claude-plugin-registry" | "claude-marketplace-registry" | "claude-settings-v1";
9
+ hashMode?: "bytes" | "path-bytes" | "claude-plugin-registry" | "claude-marketplace-registry" | "claude-settings-v1" | "claude-settings-v2" | "claude-marketplace-registry-v2";
10
10
  managedPlugins?: ManagedPluginRegistrationWitness[];
11
11
  format?: "json" | "toml" | "yaml";
12
12
  fields?: string[];
@@ -15,3 +15,9 @@ export declare function captureClaudeMarketplaceRegistry(path: string, budget?:
15
15
  hashMode: "claude-marketplace-registry";
16
16
  sha256: string;
17
17
  };
18
+ /** Explicit v2 review also recognizes rows with a bound boolean autoUpdate. */
19
+ export declare function captureClaudeMarketplaceRegistryV2(path: string, budget?: ClaudeMarketplaceRegistryBudget): {
20
+ path: string;
21
+ hashMode: "claude-marketplace-registry-v2";
22
+ sha256: string;
23
+ };
@@ -23,3 +23,10 @@ export declare function captureClaudeSettings(path: string, budget?: ClaudeSetti
23
23
  hashMode: "claude-settings-v1";
24
24
  sha256: string;
25
25
  };
26
+ /** A new explicit review is required; v1 witnesses retain their old meaning. */
27
+ export declare function captureClaudeSettingsV2(path: string, budget?: ClaudeSettingsWitnessBudget): {
28
+ path: string;
29
+ hashMode: "claude-settings-v2";
30
+ sha256: string;
31
+ };
32
+ export declare function hashClaudeSettingsReplacementV2(text: string, budget: ClaudeSettingsWitnessBudget): string;