@hasna/skills 0.9.12 → 0.9.14

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/index.js CHANGED
@@ -36868,7 +36868,7 @@ var package_default;
36868
36868
  var init_package = __esm(() => {
36869
36869
  package_default = {
36870
36870
  name: "@hasna/skills",
36871
- version: "0.9.12",
36871
+ version: "0.9.14",
36872
36872
  description: "Skills library for AI coding agents",
36873
36873
  type: "module",
36874
36874
  bin: {
@@ -39947,10 +39947,10 @@ function assertAgentPolicyCollections(policy) {
39947
39947
  text(source.path);
39948
39948
  requireBound(source.sha256 === null || typeof source.sha256 === "string" && /^[a-f0-9]{64}$/.test(source.sha256));
39949
39949
  if (source.hashMode !== undefined)
39950
- 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));
39951
- if (source.hashMode === "claude-settings-v1")
39950
+ 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));
39951
+ if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2")
39952
39952
  requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute2(source.path) && resolve3(source.path) === source.path && basename2(source.path) === "settings.json");
39953
- if (source.hashMode === "claude-marketplace-registry")
39953
+ if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2")
39954
39954
  requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute2(source.path) && resolve3(source.path) === source.path && basename2(source.path) === "known_marketplaces.json");
39955
39955
  if (source.hashMode === "claude-plugin-registry") {
39956
39956
  requireBound(agent === "claude");
@@ -50545,9 +50545,32 @@ function canonical2(value) {
50545
50545
  return { kind: "array", items: value.items.map(canonical2) };
50546
50546
  return value;
50547
50547
  }
50548
- function settingsDigest(text2) {
50548
+ function inferencePreference(key, child) {
50549
+ if (key === "skipDangerousModePermissionPrompt") {
50550
+ need2(child.kind === "literal" && (child.value === "true" || child.value === "false"), "contains an invalid dialog acknowledgment");
50551
+ return false;
50552
+ }
50553
+ if (key === "effortLevel") {
50554
+ need2(child.kind === "string" && PERSISTED_EFFORT_LEVELS.has(child.value), "contains an invalid persisted effort level");
50555
+ return true;
50556
+ }
50557
+ if (key !== "modelSettings")
50558
+ return false;
50559
+ need2(child.kind === "object", "requires an object of model preferences");
50560
+ child.entries = child.entries.filter(([model, preferences]) => {
50561
+ if (!BUILTIN_MODEL_SELECTIONS.has(model) || preferences.kind !== "object" || preferences.entries.length !== 1 || preferences.entries[0][0] !== "effortLevel")
50562
+ return true;
50563
+ const effort = preferences.entries[0][1];
50564
+ need2(effort.kind === "string" && PERSISTED_EFFORT_LEVELS.has(effort.value), "contains an invalid persisted model effort level");
50565
+ return false;
50566
+ });
50567
+ return child.entries.length === 0;
50568
+ }
50569
+ function settingsDigest(text2, version = 1) {
50549
50570
  const value = parse3(text2);
50550
50571
  value.entries = value.entries.filter(([key, child]) => {
50572
+ if (version === 2 && inferencePreference(key, child))
50573
+ return false;
50551
50574
  if (key === "model") {
50552
50575
  need2(child.kind === "string", "contains an invalid model selection");
50553
50576
  return !BUILTIN_MODEL_SELECTIONS.has(child.value);
@@ -50562,7 +50585,7 @@ function settingsDigest(text2) {
50562
50585
  }
50563
50586
  return true;
50564
50587
  });
50565
- return createHash9("sha256").update("hasna.skills.claude-settings.v1\x00").update(JSON.stringify(canonical2(value))).digest("hex");
50588
+ return createHash9("sha256").update(`hasna.skills.claude-settings.v${version}\x00`).update(JSON.stringify(canonical2(value))).digest("hex");
50566
50589
  }
50567
50590
  function hashClaudeSettingsReplacement(text2, budget) {
50568
50591
  need2(typeof text2 === "string", "requires settings text");
@@ -50574,7 +50597,17 @@ function hashClaudeSettingsReplacement(text2, budget) {
50574
50597
  function captureClaudeSettings(path, budget = { remaining: 256 * 1024 * 1024 }) {
50575
50598
  return { path, hashMode: "claude-settings-v1", sha256: settingsDigest(readSettings(path, budget)) };
50576
50599
  }
50577
- var CLAUDE_SETTINGS_WITNESS_LIMITS, sameFile = (a, b) => a.dev === b.dev && a.ino === b.ino && a.mode === b.mode && a.uid === b.uid && a.gid === b.gid && a.nlink === b.nlink && a.size === b.size && a.mtimeNs === b.mtimeNs && a.ctimeNs === b.ctimeNs, BOOLEAN_PREFERENCES, ENUM_PREFERENCES, BUILTIN_MODEL_SELECTIONS;
50600
+ function captureClaudeSettingsV2(path, budget = { remaining: 256 * 1024 * 1024 }) {
50601
+ return { path, hashMode: "claude-settings-v2", sha256: settingsDigest(readSettings(path, budget), 2) };
50602
+ }
50603
+ function hashClaudeSettingsReplacementV2(text2, budget) {
50604
+ need2(typeof text2 === "string", "requires settings text");
50605
+ const bytes = Buffer.byteLength(text2);
50606
+ need2(bytes <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes && Number.isSafeInteger(budget.remaining) && budget.remaining >= bytes, "exceeds its byte limit");
50607
+ budget.remaining -= bytes;
50608
+ return settingsDigest(text2, 2);
50609
+ }
50610
+ var CLAUDE_SETTINGS_WITNESS_LIMITS, sameFile = (a, b) => a.dev === b.dev && a.ino === b.ino && a.mode === b.mode && a.uid === b.uid && a.gid === b.gid && a.nlink === b.nlink && a.size === b.size && a.mtimeNs === b.mtimeNs && a.ctimeNs === b.ctimeNs, BOOLEAN_PREFERENCES, ENUM_PREFERENCES, BUILTIN_MODEL_SELECTIONS, PERSISTED_EFFORT_LEVELS;
50578
50611
  var init_claude_settings_witness = __esm(() => {
50579
50612
  CLAUDE_SETTINGS_WITNESS_LIMITS = Object.freeze({ bytes: 1024 * 1024, rows: 1024, depth: 32, nodes: 65536, stringCharacters: 16384, pathCharacters: 4096 });
50580
50613
  BOOLEAN_PREFERENCES = new Set([
@@ -50628,6 +50661,7 @@ var init_claude_settings_witness = __esm(() => {
50628
50661
  "claude-opus-4-6[1m]",
50629
50662
  "claude-sonnet-4-6[1m]"
50630
50663
  ]);
50664
+ PERSISTED_EFFORT_LEVELS = new Set(["low", "medium", "high", "xhigh"]);
50631
50665
  });
50632
50666
 
50633
50667
  // src/lib/agent-integration.ts
@@ -51199,6 +51233,20 @@ function planAgentIntegration(options) {
51199
51233
  recheckRootAliases(aliases);
51200
51234
  return { dataDir: dataDir3, profileId, changes, nativeSkills, observedPolicy: { path: policyPath, before: previousPolicy }, discoveryBefore: discoveries, discoveryAfter, ...aliases.length ? { rootAliases: aliases } : {} };
51201
51235
  }
51236
+ function verifyCoordinatedDiscovery(binding, home, aliases) {
51237
+ try {
51238
+ if (binding.method !== "automatic" && binding.method !== "reviewed")
51239
+ throw new Error("Invalid managed discovery method");
51240
+ verifyAgentDiscovery(binding);
51241
+ if (binding.method === "automatic") {
51242
+ const current = resolveAgentDiscovery({ home, agent: binding.agent, canonical: (path) => canonicalAgentPath(path, aliases) });
51243
+ if (JSON.stringify(current) !== JSON.stringify(binding))
51244
+ throw new Error("Configured native discovery roots changed during Claude update");
51245
+ }
51246
+ } catch (error) {
51247
+ throw new Error(`NATIVE_SKILL_DRIFT: ${error.message}`);
51248
+ }
51249
+ }
51202
51250
  function atomicWrite2(path, content) {
51203
51251
  assertSafePath(path);
51204
51252
  mkdirSync5(dirname10(path), { recursive: true, mode: 448 });
@@ -51223,8 +51271,12 @@ function applyAgentIntegration(plan) {
51223
51271
  recheckRootAliases(aliases);
51224
51272
  for (const agent of plan.managedAgentChecks?.agents ?? [])
51225
51273
  assertManagedAgentBridge(agent, { home: plan.managedAgentChecks.home, dataDir: plan.dataDir, projectDir: plan.managedAgentChecks.home });
51226
- for (const binding of plan.discoveryBefore ?? [])
51227
- verifyAgentDiscovery(binding);
51274
+ for (const binding of plan.discoveryBefore ?? []) {
51275
+ if (plan.managedAgentChecks)
51276
+ verifyCoordinatedDiscovery(binding, plan.managedAgentChecks.home, aliases);
51277
+ else
51278
+ verifyAgentDiscovery(binding);
51279
+ }
51228
51280
  if (plan.observedPolicy && currentText(plan.observedPolicy.path) !== plan.observedPolicy.before)
51229
51281
  throw new Error("Agent policy changed after planning");
51230
51282
  for (const change of plan.changes)
@@ -51256,8 +51308,12 @@ function applyAgentIntegration(plan) {
51256
51308
  written.push({ path: change.path, before: change.before, after: after2 });
51257
51309
  }
51258
51310
  recheckRootAliases(aliases);
51259
- for (const binding of plan.discoveryAfter ?? [])
51260
- verifyAgentDiscovery(binding);
51311
+ for (const binding of plan.discoveryAfter ?? []) {
51312
+ if (plan.managedAgentChecks)
51313
+ verifyCoordinatedDiscovery(binding, plan.managedAgentChecks.home, aliases);
51314
+ else
51315
+ verifyAgentDiscovery(binding);
51316
+ }
51261
51317
  for (const agent of plan.managedAgentChecks?.agents ?? [])
51262
51318
  assertManagedAgentBridge(agent, { home: plan.managedAgentChecks.home, dataDir: plan.dataDir, projectDir: plan.managedAgentChecks.home });
51263
51319
  atomicWrite2(join20(backupRoot, "receipt.json"), JSON.stringify({ version: 1, changes: written.map((change) => ({ path: change.path, beforeHash: change.before === null ? null : sha(change.before), afterHash: sha(change.after) })), backups, ...aliases.length ? { rootAliases: aliases } : {} }) + `
@@ -54402,9 +54458,13 @@ function textField(value) {
54402
54458
  need3(value?.kind === "string" && value.value.length > 0 && !/[\x00-\x1f\x7f]/.test(value.value), "contains an invalid recognized field");
54403
54459
  return value.value;
54404
54460
  }
54405
- function normalizeRow(row) {
54406
- if (!exactKeys(row, ["source", "installLocation", "lastUpdated"]))
54407
- return;
54461
+ function normalizeRow(row, version = 1) {
54462
+ if (!exactKeys(row, ["source", "installLocation", "lastUpdated"])) {
54463
+ if (version !== 2 || !exactKeys(row, ["source", "installLocation", "lastUpdated", "autoUpdate"]))
54464
+ return;
54465
+ const autoUpdate = field(row, "autoUpdate");
54466
+ need3(autoUpdate?.kind === "literal" && (autoUpdate.value === "true" || autoUpdate.value === "false"), "contains an invalid automatic-update preference");
54467
+ }
54408
54468
  const source = field(row, "source");
54409
54469
  if (source?.kind !== "object")
54410
54470
  return;
@@ -54431,6 +54491,13 @@ function captureClaudeMarketplaceRegistry(path, budget = { remaining: 256 * 1024
54431
54491
  const sha2562 = createHash13("sha256").update("hasna.skills.claude-marketplace-registry.v1\x00").update(JSON.stringify(value)).digest("hex");
54432
54492
  return { path, hashMode: "claude-marketplace-registry", sha256: sha2562 };
54433
54493
  }
54494
+ function captureClaudeMarketplaceRegistryV2(path, budget = { remaining: 256 * 1024 * 1024 }) {
54495
+ const value = parse6(readRegistry2(path, budget));
54496
+ for (const [, row] of value.entries)
54497
+ normalizeRow(row, 2);
54498
+ const sha2562 = createHash13("sha256").update("hasna.skills.claude-marketplace-registry.v2\x00").update(JSON.stringify(value)).digest("hex");
54499
+ return { path, hashMode: "claude-marketplace-registry-v2", sha256: sha2562 };
54500
+ }
54434
54501
  var CLAUDE_MARKETPLACE_REGISTRY_LIMITS, sameFile2 = (a, b) => a.dev === b.dev && a.ino === b.ino && a.mode === b.mode && a.uid === b.uid && a.gid === b.gid && a.nlink === b.nlink && a.size === b.size && a.mtimeNs === b.mtimeNs && a.ctimeNs === b.ctimeNs, field = (object4, key) => object4.entries.find(([name]) => name === key)?.[1];
54435
54502
  var init_claude_marketplace_registry = __esm(() => {
54436
54503
  CLAUDE_MARKETPLACE_REGISTRY_LIMITS = Object.freeze({ bytes: 1024 * 1024, rows: 1024, depth: 32, nodes: 65536, stringCharacters: 16384, pathCharacters: 4096 });
@@ -54714,18 +54781,18 @@ function read2(path, changes) {
54714
54781
  return readFileSync17(path, "utf8");
54715
54782
  }
54716
54783
  function projected(source, changes, budget = discoveryByteBudget()) {
54717
- if (source.hashMode === "claude-settings-v1") {
54784
+ if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") {
54718
54785
  if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
54719
54786
  throw new Error("Claude settings witnesses require exact typed metadata");
54720
- const current = captureClaudeSettings(source.path, budget).sha256;
54787
+ const current = (source.hashMode === "claude-settings-v2" ? captureClaudeSettingsV2 : captureClaudeSettings)(source.path, budget).sha256;
54721
54788
  if (current !== source.sha256)
54722
54789
  throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
54723
- return changes?.has(source.path) ? hashClaudeSettingsReplacement(changes.get(source.path), budget) : current;
54790
+ return changes?.has(source.path) ? (source.hashMode === "claude-settings-v2" ? hashClaudeSettingsReplacementV2 : hashClaudeSettingsReplacement)(changes.get(source.path), budget) : current;
54724
54791
  }
54725
- if (source.hashMode === "claude-marketplace-registry") {
54792
+ if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") {
54726
54793
  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))
54727
54794
  throw new Error("Claude marketplace witnesses require an exact reviewed registry without synthetic changes");
54728
- const sha2562 = captureClaudeMarketplaceRegistry(source.path, budget).sha256;
54795
+ const sha2562 = (source.hashMode === "claude-marketplace-registry-v2" ? captureClaudeMarketplaceRegistryV2 : captureClaudeMarketplaceRegistry)(source.path, budget).sha256;
54729
54796
  if (sha2562 !== source.sha256)
54730
54797
  throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
54731
54798
  return sha2562;
@@ -54782,9 +54849,9 @@ function verifyAgentDiscovery(binding, codexRecovery) {
54782
54849
  safe4(root2);
54783
54850
  }
54784
54851
  function assertMarketplaceBinding(binding, source) {
54785
- if (source.hashMode === "claude-settings-v1" && (binding.agent !== "claude" || binding.method !== "reviewed" || basename8(source.path) !== "settings.json"))
54852
+ if ((source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && (binding.agent !== "claude" || binding.method !== "reviewed" || basename8(source.path) !== "settings.json"))
54786
54853
  throw new Error("Claude settings witnesses require explicit reviewed Claude configuration");
54787
- if (source.hashMode === "claude-marketplace-registry" && (binding.agent !== "claude" || binding.method !== "reviewed"))
54854
+ if ((source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") && (binding.agent !== "claude" || binding.method !== "reviewed"))
54788
54855
  throw new Error("Claude marketplace witnesses require explicit reviewed Claude discovery");
54789
54856
  }
54790
54857
  function rebindAgentDiscovery(binding, changes) {
@@ -54913,7 +54980,7 @@ function resolveAgentDiscovery(options) {
54913
54980
  if (review.sources.some((source) => source.format !== undefined || source.fields !== undefined))
54914
54981
  throw new Error("Explicit discovery reviews require full source-file hashes");
54915
54982
  verifyAgentDiscovery(supplied);
54916
- if (review.sources.some((source) => source.hashMode === "claude-settings-v1" && source.path !== canonical4(configPath)))
54983
+ if (review.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && source.path !== canonical4(configPath)))
54917
54984
  throw new Error("Semantic settings witnesses must name the configured Claude configuration source");
54918
54985
  if (!review.sources.some((source) => source.path === canonical4(configPath)))
54919
54986
  throw new Error("Discovery review must include the agent configuration source");
@@ -109057,6 +109124,12 @@ function agents(value) {
109057
109124
  }
109058
109125
  function registerAgentIntegration(parent) {
109059
109126
  const hook = parent.command("hook").description("Load selected Skills context through agent lifecycle hooks");
109127
+ hook.command("witness").requiredOption("--kind <kind>", "claude-settings-v2 or claude-marketplace-registry-v2").requiredOption("--path <path>", "Canonical absolute path to the reviewed settings or registry file").option("--json", "Output the discovery witness as JSON", false).description("Capture an explicit versioned review witness without installing it").action(async (options) => {
109128
+ const capture2 = options.kind === "claude-settings-v2" ? captureClaudeSettingsV2 : options.kind === "claude-marketplace-registry-v2" ? captureClaudeMarketplaceRegistryV2 : null;
109129
+ if (!capture2)
109130
+ throw new Error("Unsupported witness kind; select claude-settings-v2 or claude-marketplace-registry-v2");
109131
+ await writeCliOutput(JSON.stringify(capture2(options.path), null, 2));
109132
+ });
109060
109133
  hook.command("agents").option("--json", "Output the adapter capability inventory", false).description("Show maintained native adapters and explicit coverage limits").action(async () => {
109061
109134
  await writeCliOutput(JSON.stringify({ agents: INTEGRATION_AGENTS.map((agent) => ({ agent, bridge: true, ...AGENT_ADAPTERS[agent] })), inventoryOnly: ["codewith", "windsurf", "pi", "amp", "cline", "roo", "copilot"], limitations: ["Cursor prompt hooks gate submission; selected context is injected at session start only.", "Native discovery checks cover known home roots and current project ancestors. External plugin hook injection and arbitrary added directories require separate review.", "Hermes injects selected prompt context, but native pre_llm_call fails open. Exact native hook trust, bundled reseeding opt-out, native payload retirement and a supervised pre-tool guard are required. Child failures block explicitly; native host/supervisor death is not a universal fail-closed guarantee.", "Restart agents and use their normal hook trust controls after installation."] }, null, 2));
109062
109135
  });
@@ -109243,6 +109316,8 @@ var init_agent_integration2 = __esm(() => {
109243
109316
  init_agent_codex_trust();
109244
109317
  init_hook_diagnostics();
109245
109318
  init_selection_cache();
109319
+ init_claude_settings_witness();
109320
+ init_claude_marketplace_registry();
109246
109321
  RECOVERABLE_CONTEXT_CACHE_ERRORS = new Set(["CACHED_PROFILE_EXPIRED", "CACHED_PROFILE_MISSING", "CACHED_BUNDLE_MISSING"]);
109247
109322
  });
109248
109323
 
@@ -2737,6 +2737,18 @@ function rowToSkill(row) {
2737
2737
  ...row.replacement_slug ? { replacementSlug: String(row.replacement_slug) } : {}
2738
2738
  };
2739
2739
  }
2740
+ function rowToPublishedSelectionState(row) {
2741
+ return {
2742
+ slug: String(row.slug),
2743
+ version: String(row.version),
2744
+ current: row.skill_exists ? {
2745
+ lifecycle: row.lifecycle === "archived" ? "archived" : "active",
2746
+ ...row.tombstoned_at ? { tombstonedAt: dateString(row.tombstoned_at) } : {}
2747
+ } : null,
2748
+ versionBundleSha256: typeof row.bundle_sha256 === "string" ? row.bundle_sha256 : null,
2749
+ bundleAvailable: Boolean(row.bundle_available)
2750
+ };
2751
+ }
2740
2752
  function rowToPin(row) {
2741
2753
  return {
2742
2754
  orgId: String(row.org_id),
@@ -2809,6 +2821,13 @@ function rowToSkillVersion(row) {
2809
2821
  };
2810
2822
  }
2811
2823
 
2824
+ // src/lib/profile-limits.ts
2825
+ var MAX_PROFILE_SELECTIONS = 4096;
2826
+ var MAX_PROFILE_DOCUMENT_BYTES = 8 * 1024 * 1024;
2827
+ var MAX_SKILL_SESSION_ID_CHARS = 256;
2828
+ var PROFILE_SESSION_ENVELOPE_BYTES = MAX_PROFILE_SELECTIONS * 67 + MAX_SKILL_SESSION_ID_CHARS * 12 + 512;
2829
+ var MAX_RESOLVED_PROFILE_BYTES = MAX_PROFILE_DOCUMENT_BYTES - PROFILE_SESSION_ENVELOPE_BYTES;
2830
+
2812
2831
  // src/lib/revision.ts
2813
2832
  import { createHash as createHash2 } from "crypto";
2814
2833
  function revisionIdOf(content) {
@@ -3495,6 +3514,22 @@ class SqliteSkillsStore {
3495
3514
  const row = this.get("SELECT * FROM skills_versions WHERE org_id = ? AND slug = ? AND version = ? LIMIT 1", [principal.orgId, slug, version]);
3496
3515
  return row ? rowToSkillVersion(row) : null;
3497
3516
  }
3517
+ async getPublishedSelectionStates(principal, selections2) {
3518
+ if (selections2.length > MAX_PROFILE_SELECTIONS)
3519
+ throw new RangeError("Published selection lookup exceeds the profile selection limit");
3520
+ if (!selections2.length)
3521
+ return [];
3522
+ const requested = JSON.stringify(selections2.map(({ slug, version }) => ({ slug, version })));
3523
+ return this.all(`SELECT json_extract(q.value, '$.slug') AS slug, json_extract(q.value, '$.version') AS version,
3524
+ r.slug IS NOT NULL AS skill_exists, r.lifecycle, r.tombstoned_at,
3525
+ v.bundle_sha256, b.sha256 IS NOT NULL AS bundle_available
3526
+ FROM json_each(?) q
3527
+ LEFT JOIN skills_registry r ON r.org_id = ? AND r.slug = json_extract(q.value, '$.slug')
3528
+ LEFT JOIN skills_versions v ON v.org_id = ?
3529
+ AND v.slug = json_extract(q.value, '$.slug') AND v.version = json_extract(q.value, '$.version')
3530
+ LEFT JOIN skills_bundles b ON b.org_id = ? AND b.sha256 = v.bundle_sha256
3531
+ ORDER BY CAST(q.key AS INTEGER)`, [requested, principal.orgId, principal.orgId, principal.orgId]).map(rowToPublishedSelectionState);
3532
+ }
3498
3533
  async pinSkill(principal, slug, metadata = {}) {
3499
3534
  const row = this.get(`INSERT INTO skills_pins (org_id, principal, slug, pinned_at, metadata_json)
3500
3535
  VALUES (?, ?, ?, ?, ?)
@@ -3899,6 +3934,25 @@ class MemorySkillsStore {
3899
3934
  const found = this.versions.get(versionKey(principal.orgId, slug, version));
3900
3935
  return found && found.orgId === principal.orgId ? found : null;
3901
3936
  }
3937
+ async getPublishedSelectionStates(principal, selections2) {
3938
+ if (selections2.length > MAX_PROFILE_SELECTIONS)
3939
+ throw new RangeError("Published selection lookup exceeds the profile selection limit");
3940
+ return selections2.map(({ slug, version }) => {
3941
+ const current = this.skills.get(skillKey(principal.orgId, slug));
3942
+ const selectedVersion = this.versions.get(versionKey(principal.orgId, slug, version));
3943
+ const bundle = selectedVersion && this.bundles.get(skillKey(principal.orgId, selectedVersion.bundleSha256));
3944
+ return {
3945
+ slug,
3946
+ version,
3947
+ current: current?.orgId === principal.orgId ? {
3948
+ lifecycle: current.lifecycle,
3949
+ ...current.tombstonedAt ? { tombstonedAt: current.tombstonedAt } : {}
3950
+ } : null,
3951
+ versionBundleSha256: selectedVersion?.orgId === principal.orgId ? selectedVersion.bundleSha256 : null,
3952
+ bundleAvailable: bundle?.orgId === principal.orgId
3953
+ };
3954
+ });
3955
+ }
3902
3956
  async listSkills(principal) {
3903
3957
  return Array.from(this.skills.values()).filter((skill) => skill.orgId === principal.orgId && !skill.tombstonedAt).sort((a, b) => a.slug.localeCompare(b.slug));
3904
3958
  }
@@ -4707,6 +4761,25 @@ class PostgresSkillsStore {
4707
4761
  `;
4708
4762
  return rows[0] ? rowToSkillVersion(rows[0]) : null;
4709
4763
  }
4764
+ async getPublishedSelectionStates(principal, selections2) {
4765
+ if (selections2.length > MAX_PROFILE_SELECTIONS)
4766
+ throw new RangeError("Published selection lookup exceeds the profile selection limit");
4767
+ if (!selections2.length)
4768
+ return [];
4769
+ const requested = JSON.stringify(selections2.map(({ slug, version }) => ({ slug, version })));
4770
+ const rows = await this.sql`
4771
+ SELECT q.value->>'slug' AS slug, q.value->>'version' AS version,
4772
+ r.slug IS NOT NULL AS skill_exists, r.lifecycle, r.tombstoned_at,
4773
+ v.bundle_sha256, b.sha256 IS NOT NULL AS bundle_available
4774
+ FROM jsonb_array_elements(${requested}::text::jsonb) WITH ORDINALITY AS q(value, ordinal)
4775
+ LEFT JOIN skills_registry r ON r.org_id = ${principal.orgId} AND r.slug = q.value->>'slug'
4776
+ LEFT JOIN skills_versions v ON v.org_id = ${principal.orgId}
4777
+ AND v.slug = q.value->>'slug' AND v.version = q.value->>'version'
4778
+ LEFT JOIN skills_bundles b ON b.org_id = ${principal.orgId} AND b.sha256 = v.bundle_sha256
4779
+ ORDER BY q.ordinal
4780
+ `;
4781
+ return rows.map(rowToPublishedSelectionState);
4782
+ }
4710
4783
  async pinSkill(principal, slug, metadata = {}) {
4711
4784
  const rows = await this.sql`
4712
4785
  INSERT INTO skills_pins (org_id, principal, slug, pinned_at, metadata_json)
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.9.12",
20022
+ version: "0.9.14",
20023
20023
  description: "Skills library for AI coding agents",
20024
20024
  type: "module",
20025
20025
  bin: {
@@ -38126,10 +38126,10 @@ function assertAgentPolicyCollections(policy) {
38126
38126
  text(source.path);
38127
38127
  requireBound(source.sha256 === null || typeof source.sha256 === "string" && /^[a-f0-9]{64}$/.test(source.sha256));
38128
38128
  if (source.hashMode !== undefined)
38129
- 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));
38130
- if (source.hashMode === "claude-settings-v1")
38129
+ 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));
38130
+ if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2")
38131
38131
  requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute3(source.path) && resolve2(source.path) === source.path && basename3(source.path) === "settings.json");
38132
- if (source.hashMode === "claude-marketplace-registry")
38132
+ if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2")
38133
38133
  requireBound(agent === "claude" && value.agent === "claude" && value.method === "reviewed" && !/[\x00-\x1f\x7f]/.test(source.path) && isAbsolute3(source.path) && resolve2(source.path) === source.path && basename3(source.path) === "known_marketplaces.json");
38134
38134
  if (source.hashMode === "claude-plugin-registry") {
38135
38135
  requireBound(agent === "claude");
@@ -47080,9 +47080,32 @@ function canonical2(value) {
47080
47080
  return { kind: "array", items: value.items.map(canonical2) };
47081
47081
  return value;
47082
47082
  }
47083
- function settingsDigest(text2) {
47083
+ function inferencePreference(key, child) {
47084
+ if (key === "skipDangerousModePermissionPrompt") {
47085
+ need2(child.kind === "literal" && (child.value === "true" || child.value === "false"), "contains an invalid dialog acknowledgment");
47086
+ return false;
47087
+ }
47088
+ if (key === "effortLevel") {
47089
+ need2(child.kind === "string" && PERSISTED_EFFORT_LEVELS.has(child.value), "contains an invalid persisted effort level");
47090
+ return true;
47091
+ }
47092
+ if (key !== "modelSettings")
47093
+ return false;
47094
+ need2(child.kind === "object", "requires an object of model preferences");
47095
+ child.entries = child.entries.filter(([model, preferences]) => {
47096
+ if (!BUILTIN_MODEL_SELECTIONS.has(model) || preferences.kind !== "object" || preferences.entries.length !== 1 || preferences.entries[0][0] !== "effortLevel")
47097
+ return true;
47098
+ const effort = preferences.entries[0][1];
47099
+ need2(effort.kind === "string" && PERSISTED_EFFORT_LEVELS.has(effort.value), "contains an invalid persisted model effort level");
47100
+ return false;
47101
+ });
47102
+ return child.entries.length === 0;
47103
+ }
47104
+ function settingsDigest(text2, version2 = 1) {
47084
47105
  const value = parse8(text2);
47085
47106
  value.entries = value.entries.filter(([key, child]) => {
47107
+ if (version2 === 2 && inferencePreference(key, child))
47108
+ return false;
47086
47109
  if (key === "model") {
47087
47110
  need2(child.kind === "string", "contains an invalid model selection");
47088
47111
  return !BUILTIN_MODEL_SELECTIONS.has(child.value);
@@ -47097,7 +47120,7 @@ function settingsDigest(text2) {
47097
47120
  }
47098
47121
  return true;
47099
47122
  });
47100
- return createHash9("sha256").update("hasna.skills.claude-settings.v1\x00").update(JSON.stringify(canonical2(value))).digest("hex");
47123
+ return createHash9("sha256").update(`hasna.skills.claude-settings.v${version2}\x00`).update(JSON.stringify(canonical2(value))).digest("hex");
47101
47124
  }
47102
47125
  function hashClaudeSettingsReplacement(text2, budget) {
47103
47126
  need2(typeof text2 === "string", "requires settings text");
@@ -47109,7 +47132,17 @@ function hashClaudeSettingsReplacement(text2, budget) {
47109
47132
  function captureClaudeSettings(path, budget = { remaining: 256 * 1024 * 1024 }) {
47110
47133
  return { path, hashMode: "claude-settings-v1", sha256: settingsDigest(readSettings(path, budget)) };
47111
47134
  }
47112
- var CLAUDE_SETTINGS_WITNESS_LIMITS, sameFile = (a, b) => a.dev === b.dev && a.ino === b.ino && a.mode === b.mode && a.uid === b.uid && a.gid === b.gid && a.nlink === b.nlink && a.size === b.size && a.mtimeNs === b.mtimeNs && a.ctimeNs === b.ctimeNs, BOOLEAN_PREFERENCES, ENUM_PREFERENCES, BUILTIN_MODEL_SELECTIONS;
47135
+ function captureClaudeSettingsV2(path, budget = { remaining: 256 * 1024 * 1024 }) {
47136
+ return { path, hashMode: "claude-settings-v2", sha256: settingsDigest(readSettings(path, budget), 2) };
47137
+ }
47138
+ function hashClaudeSettingsReplacementV2(text2, budget) {
47139
+ need2(typeof text2 === "string", "requires settings text");
47140
+ const bytes = Buffer.byteLength(text2);
47141
+ need2(bytes <= CLAUDE_SETTINGS_WITNESS_LIMITS.bytes && Number.isSafeInteger(budget.remaining) && budget.remaining >= bytes, "exceeds its byte limit");
47142
+ budget.remaining -= bytes;
47143
+ return settingsDigest(text2, 2);
47144
+ }
47145
+ var CLAUDE_SETTINGS_WITNESS_LIMITS, sameFile = (a, b) => a.dev === b.dev && a.ino === b.ino && a.mode === b.mode && a.uid === b.uid && a.gid === b.gid && a.nlink === b.nlink && a.size === b.size && a.mtimeNs === b.mtimeNs && a.ctimeNs === b.ctimeNs, BOOLEAN_PREFERENCES, ENUM_PREFERENCES, BUILTIN_MODEL_SELECTIONS, PERSISTED_EFFORT_LEVELS;
47113
47146
  var init_claude_settings_witness = __esm(() => {
47114
47147
  CLAUDE_SETTINGS_WITNESS_LIMITS = Object.freeze({ bytes: 1024 * 1024, rows: 1024, depth: 32, nodes: 65536, stringCharacters: 16384, pathCharacters: 4096 });
47115
47148
  BOOLEAN_PREFERENCES = new Set([
@@ -47163,6 +47196,7 @@ var init_claude_settings_witness = __esm(() => {
47163
47196
  "claude-opus-4-6[1m]",
47164
47197
  "claude-sonnet-4-6[1m]"
47165
47198
  ]);
47199
+ PERSISTED_EFFORT_LEVELS = new Set(["low", "medium", "high", "xhigh"]);
47166
47200
  });
47167
47201
 
47168
47202
  // src/lib/agent-integration.ts
@@ -48665,9 +48699,13 @@ function textField(value) {
48665
48699
  need3(value?.kind === "string" && value.value.length > 0 && !/[\x00-\x1f\x7f]/.test(value.value), "contains an invalid recognized field");
48666
48700
  return value.value;
48667
48701
  }
48668
- function normalizeRow(row) {
48669
- if (!exactKeys(row, ["source", "installLocation", "lastUpdated"]))
48670
- return;
48702
+ function normalizeRow(row, version2 = 1) {
48703
+ if (!exactKeys(row, ["source", "installLocation", "lastUpdated"])) {
48704
+ if (version2 !== 2 || !exactKeys(row, ["source", "installLocation", "lastUpdated", "autoUpdate"]))
48705
+ return;
48706
+ const autoUpdate = field(row, "autoUpdate");
48707
+ need3(autoUpdate?.kind === "literal" && (autoUpdate.value === "true" || autoUpdate.value === "false"), "contains an invalid automatic-update preference");
48708
+ }
48671
48709
  const source = field(row, "source");
48672
48710
  if (source?.kind !== "object")
48673
48711
  return;
@@ -48694,6 +48732,13 @@ function captureClaudeMarketplaceRegistry(path, budget = { remaining: 256 * 1024
48694
48732
  const sha2562 = createHash11("sha256").update("hasna.skills.claude-marketplace-registry.v1\x00").update(JSON.stringify(value)).digest("hex");
48695
48733
  return { path, hashMode: "claude-marketplace-registry", sha256: sha2562 };
48696
48734
  }
48735
+ function captureClaudeMarketplaceRegistryV2(path, budget = { remaining: 256 * 1024 * 1024 }) {
48736
+ const value = parse10(readRegistry2(path, budget));
48737
+ for (const [, row] of value.entries)
48738
+ normalizeRow(row, 2);
48739
+ const sha2562 = createHash11("sha256").update("hasna.skills.claude-marketplace-registry.v2\x00").update(JSON.stringify(value)).digest("hex");
48740
+ return { path, hashMode: "claude-marketplace-registry-v2", sha256: sha2562 };
48741
+ }
48697
48742
  var CLAUDE_MARKETPLACE_REGISTRY_LIMITS, sameFile2 = (a, b) => a.dev === b.dev && a.ino === b.ino && a.mode === b.mode && a.uid === b.uid && a.gid === b.gid && a.nlink === b.nlink && a.size === b.size && a.mtimeNs === b.mtimeNs && a.ctimeNs === b.ctimeNs, field = (object7, key) => object7.entries.find(([name]) => name === key)?.[1];
48698
48743
  var init_claude_marketplace_registry = __esm(() => {
48699
48744
  CLAUDE_MARKETPLACE_REGISTRY_LIMITS = Object.freeze({ bytes: 1024 * 1024, rows: 1024, depth: 32, nodes: 65536, stringCharacters: 16384, pathCharacters: 4096 });
@@ -48809,18 +48854,18 @@ function read(path, changes) {
48809
48854
  return readFileSync14(path, "utf8");
48810
48855
  }
48811
48856
  function projected(source, changes, budget = discoveryByteBudget()) {
48812
- if (source.hashMode === "claude-settings-v1") {
48857
+ if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") {
48813
48858
  if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
48814
48859
  throw new Error("Claude settings witnesses require exact typed metadata");
48815
- const current = captureClaudeSettings(source.path, budget).sha256;
48860
+ const current = (source.hashMode === "claude-settings-v2" ? captureClaudeSettingsV2 : captureClaudeSettings)(source.path, budget).sha256;
48816
48861
  if (current !== source.sha256)
48817
48862
  throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
48818
- return changes?.has(source.path) ? hashClaudeSettingsReplacement(changes.get(source.path), budget) : current;
48863
+ return changes?.has(source.path) ? (source.hashMode === "claude-settings-v2" ? hashClaudeSettingsReplacementV2 : hashClaudeSettingsReplacement)(changes.get(source.path), budget) : current;
48819
48864
  }
48820
- if (source.hashMode === "claude-marketplace-registry") {
48865
+ if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") {
48821
48866
  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))
48822
48867
  throw new Error("Claude marketplace witnesses require an exact reviewed registry without synthetic changes");
48823
- const sha2562 = captureClaudeMarketplaceRegistry(source.path, budget).sha256;
48868
+ const sha2562 = (source.hashMode === "claude-marketplace-registry-v2" ? captureClaudeMarketplaceRegistryV2 : captureClaudeMarketplaceRegistry)(source.path, budget).sha256;
48824
48869
  if (sha2562 !== source.sha256)
48825
48870
  throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
48826
48871
  return sha2562;
@@ -48877,9 +48922,9 @@ function verifyAgentDiscovery(binding, codexRecovery) {
48877
48922
  safe4(root);
48878
48923
  }
48879
48924
  function assertMarketplaceBinding(binding, source) {
48880
- if (source.hashMode === "claude-settings-v1" && (binding.agent !== "claude" || binding.method !== "reviewed" || basename7(source.path) !== "settings.json"))
48925
+ if ((source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && (binding.agent !== "claude" || binding.method !== "reviewed" || basename7(source.path) !== "settings.json"))
48881
48926
  throw new Error("Claude settings witnesses require explicit reviewed Claude configuration");
48882
- if (source.hashMode === "claude-marketplace-registry" && (binding.agent !== "claude" || binding.method !== "reviewed"))
48927
+ if ((source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") && (binding.agent !== "claude" || binding.method !== "reviewed"))
48883
48928
  throw new Error("Claude marketplace witnesses require explicit reviewed Claude discovery");
48884
48929
  }
48885
48930
  function agentDiscoveryConfigPath(home, agent) {
@@ -48961,7 +49006,7 @@ function resolveAgentDiscovery(options) {
48961
49006
  if (review.sources.some((source) => source.format !== undefined || source.fields !== undefined))
48962
49007
  throw new Error("Explicit discovery reviews require full source-file hashes");
48963
49008
  verifyAgentDiscovery(supplied);
48964
- if (review.sources.some((source) => source.hashMode === "claude-settings-v1" && source.path !== canonical4(configPath)))
49009
+ if (review.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && source.path !== canonical4(configPath)))
48965
49010
  throw new Error("Semantic settings witnesses must name the configured Claude configuration source");
48966
49011
  if (!review.sources.some((source) => source.path === canonical4(configPath)))
48967
49012
  throw new Error("Discovery review must include the agent configuration source");
package/bin/migrate.js CHANGED
@@ -7,7 +7,7 @@ import { join as join5 } from "path";
7
7
  // package.json
8
8
  var package_default = {
9
9
  name: "@hasna/skills",
10
- version: "0.9.12",
10
+ version: "0.9.14",
11
11
  description: "Skills library for AI coding agents",
12
12
  type: "module",
13
13
  bin: {
@@ -985,6 +985,18 @@ function rowToSkill(row) {
985
985
  ...row.replacement_slug ? { replacementSlug: String(row.replacement_slug) } : {}
986
986
  };
987
987
  }
988
+ function rowToPublishedSelectionState(row) {
989
+ return {
990
+ slug: String(row.slug),
991
+ version: String(row.version),
992
+ current: row.skill_exists ? {
993
+ lifecycle: row.lifecycle === "archived" ? "archived" : "active",
994
+ ...row.tombstoned_at ? { tombstonedAt: dateString(row.tombstoned_at) } : {}
995
+ } : null,
996
+ versionBundleSha256: typeof row.bundle_sha256 === "string" ? row.bundle_sha256 : null,
997
+ bundleAvailable: Boolean(row.bundle_available)
998
+ };
999
+ }
988
1000
  function rowToPin(row) {
989
1001
  return {
990
1002
  orgId: String(row.org_id),
@@ -1057,6 +1069,13 @@ function rowToSkillVersion(row) {
1057
1069
  };
1058
1070
  }
1059
1071
 
1072
+ // src/lib/profile-limits.ts
1073
+ var MAX_PROFILE_SELECTIONS = 4096;
1074
+ var MAX_PROFILE_DOCUMENT_BYTES = 8 * 1024 * 1024;
1075
+ var MAX_SKILL_SESSION_ID_CHARS = 256;
1076
+ var PROFILE_SESSION_ENVELOPE_BYTES = MAX_PROFILE_SELECTIONS * 67 + MAX_SKILL_SESSION_ID_CHARS * 12 + 512;
1077
+ var MAX_RESOLVED_PROFILE_BYTES = MAX_PROFILE_DOCUMENT_BYTES - PROFILE_SESSION_ENVELOPE_BYTES;
1078
+
1060
1079
  // src/server/types.ts
1061
1080
  class StaleLeaseGenerationError extends Error {
1062
1081
  runId;
@@ -1758,6 +1777,22 @@ class SqliteSkillsStore {
1758
1777
  const row = this.get("SELECT * FROM skills_versions WHERE org_id = ? AND slug = ? AND version = ? LIMIT 1", [principal.orgId, slug, version]);
1759
1778
  return row ? rowToSkillVersion(row) : null;
1760
1779
  }
1780
+ async getPublishedSelectionStates(principal, selections2) {
1781
+ if (selections2.length > MAX_PROFILE_SELECTIONS)
1782
+ throw new RangeError("Published selection lookup exceeds the profile selection limit");
1783
+ if (!selections2.length)
1784
+ return [];
1785
+ const requested = JSON.stringify(selections2.map(({ slug, version }) => ({ slug, version })));
1786
+ return this.all(`SELECT json_extract(q.value, '$.slug') AS slug, json_extract(q.value, '$.version') AS version,
1787
+ r.slug IS NOT NULL AS skill_exists, r.lifecycle, r.tombstoned_at,
1788
+ v.bundle_sha256, b.sha256 IS NOT NULL AS bundle_available
1789
+ FROM json_each(?) q
1790
+ LEFT JOIN skills_registry r ON r.org_id = ? AND r.slug = json_extract(q.value, '$.slug')
1791
+ LEFT JOIN skills_versions v ON v.org_id = ?
1792
+ AND v.slug = json_extract(q.value, '$.slug') AND v.version = json_extract(q.value, '$.version')
1793
+ LEFT JOIN skills_bundles b ON b.org_id = ? AND b.sha256 = v.bundle_sha256
1794
+ ORDER BY CAST(q.key AS INTEGER)`, [requested, principal.orgId, principal.orgId, principal.orgId]).map(rowToPublishedSelectionState);
1795
+ }
1761
1796
  async pinSkill(principal, slug, metadata = {}) {
1762
1797
  const row = this.get(`INSERT INTO skills_pins (org_id, principal, slug, pinned_at, metadata_json)
1763
1798
  VALUES (?, ?, ?, ?, ?)