@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/index.js +71 -18
- package/bin/maintenance.js +73 -0
- package/bin/mcp.js +63 -18
- package/bin/migrate.js +36 -1
- package/bin/server.js +96 -25
- package/bin/worker.js +74 -8
- package/dist/index.d.ts +2 -2
- package/dist/index.js +64 -17
- package/dist/lib/agent-discovery.d.ts +1 -1
- package/dist/lib/claude-marketplace-registry.d.ts +6 -0
- package/dist/lib/claude-settings-witness.d.ts +7 -0
- package/dist/sdk/index.js +138 -22
- package/dist/server/rows.d.ts +2 -1
- package/dist/server/sqlite-store.d.ts +2 -1
- package/dist/server/store.d.ts +3 -1
- package/dist/server/types.d.ts +18 -0
- package/docs/plugin-admission.md +36 -0
- package/package.json +1 -1
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.
|
|
36871
|
+
version: "0.9.13",
|
|
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
|
|
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 true;
|
|
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(
|
|
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
|
-
|
|
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
|
|
@@ -54402,9 +54436,13 @@ function textField(value) {
|
|
|
54402
54436
|
need3(value?.kind === "string" && value.value.length > 0 && !/[\x00-\x1f\x7f]/.test(value.value), "contains an invalid recognized field");
|
|
54403
54437
|
return value.value;
|
|
54404
54438
|
}
|
|
54405
|
-
function normalizeRow(row) {
|
|
54406
|
-
if (!exactKeys(row, ["source", "installLocation", "lastUpdated"]))
|
|
54407
|
-
|
|
54439
|
+
function normalizeRow(row, version = 1) {
|
|
54440
|
+
if (!exactKeys(row, ["source", "installLocation", "lastUpdated"])) {
|
|
54441
|
+
if (version !== 2 || !exactKeys(row, ["source", "installLocation", "lastUpdated", "autoUpdate"]))
|
|
54442
|
+
return;
|
|
54443
|
+
const autoUpdate = field(row, "autoUpdate");
|
|
54444
|
+
need3(autoUpdate?.kind === "literal" && (autoUpdate.value === "true" || autoUpdate.value === "false"), "contains an invalid automatic-update preference");
|
|
54445
|
+
}
|
|
54408
54446
|
const source = field(row, "source");
|
|
54409
54447
|
if (source?.kind !== "object")
|
|
54410
54448
|
return;
|
|
@@ -54431,6 +54469,13 @@ function captureClaudeMarketplaceRegistry(path, budget = { remaining: 256 * 1024
|
|
|
54431
54469
|
const sha2562 = createHash13("sha256").update("hasna.skills.claude-marketplace-registry.v1\x00").update(JSON.stringify(value)).digest("hex");
|
|
54432
54470
|
return { path, hashMode: "claude-marketplace-registry", sha256: sha2562 };
|
|
54433
54471
|
}
|
|
54472
|
+
function captureClaudeMarketplaceRegistryV2(path, budget = { remaining: 256 * 1024 * 1024 }) {
|
|
54473
|
+
const value = parse6(readRegistry2(path, budget));
|
|
54474
|
+
for (const [, row] of value.entries)
|
|
54475
|
+
normalizeRow(row, 2);
|
|
54476
|
+
const sha2562 = createHash13("sha256").update("hasna.skills.claude-marketplace-registry.v2\x00").update(JSON.stringify(value)).digest("hex");
|
|
54477
|
+
return { path, hashMode: "claude-marketplace-registry-v2", sha256: sha2562 };
|
|
54478
|
+
}
|
|
54434
54479
|
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
54480
|
var init_claude_marketplace_registry = __esm(() => {
|
|
54436
54481
|
CLAUDE_MARKETPLACE_REGISTRY_LIMITS = Object.freeze({ bytes: 1024 * 1024, rows: 1024, depth: 32, nodes: 65536, stringCharacters: 16384, pathCharacters: 4096 });
|
|
@@ -54714,18 +54759,18 @@ function read2(path, changes) {
|
|
|
54714
54759
|
return readFileSync17(path, "utf8");
|
|
54715
54760
|
}
|
|
54716
54761
|
function projected(source, changes, budget = discoveryByteBudget()) {
|
|
54717
|
-
if (source.hashMode === "claude-settings-v1") {
|
|
54762
|
+
if (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") {
|
|
54718
54763
|
if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || typeof source.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(source.sha256))
|
|
54719
54764
|
throw new Error("Claude settings witnesses require exact typed metadata");
|
|
54720
|
-
const current = captureClaudeSettings(source.path, budget).sha256;
|
|
54765
|
+
const current = (source.hashMode === "claude-settings-v2" ? captureClaudeSettingsV2 : captureClaudeSettings)(source.path, budget).sha256;
|
|
54721
54766
|
if (current !== source.sha256)
|
|
54722
54767
|
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;
|
|
54768
|
+
return changes?.has(source.path) ? (source.hashMode === "claude-settings-v2" ? hashClaudeSettingsReplacementV2 : hashClaudeSettingsReplacement)(changes.get(source.path), budget) : current;
|
|
54724
54769
|
}
|
|
54725
|
-
if (source.hashMode === "claude-marketplace-registry") {
|
|
54770
|
+
if (source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") {
|
|
54726
54771
|
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
54772
|
throw new Error("Claude marketplace witnesses require an exact reviewed registry without synthetic changes");
|
|
54728
|
-
const sha2562 = captureClaudeMarketplaceRegistry(source.path, budget).sha256;
|
|
54773
|
+
const sha2562 = (source.hashMode === "claude-marketplace-registry-v2" ? captureClaudeMarketplaceRegistryV2 : captureClaudeMarketplaceRegistry)(source.path, budget).sha256;
|
|
54729
54774
|
if (sha2562 !== source.sha256)
|
|
54730
54775
|
throw new Error(`Native discovery input changed; run skills hook install with a fresh discovery review: ${source.path}`);
|
|
54731
54776
|
return sha2562;
|
|
@@ -54782,9 +54827,9 @@ function verifyAgentDiscovery(binding, codexRecovery) {
|
|
|
54782
54827
|
safe4(root2);
|
|
54783
54828
|
}
|
|
54784
54829
|
function assertMarketplaceBinding(binding, source) {
|
|
54785
|
-
if (source.hashMode === "claude-settings-v1" && (binding.agent !== "claude" || binding.method !== "reviewed" || basename8(source.path) !== "settings.json"))
|
|
54830
|
+
if ((source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && (binding.agent !== "claude" || binding.method !== "reviewed" || basename8(source.path) !== "settings.json"))
|
|
54786
54831
|
throw new Error("Claude settings witnesses require explicit reviewed Claude configuration");
|
|
54787
|
-
if (source.hashMode === "claude-marketplace-registry" && (binding.agent !== "claude" || binding.method !== "reviewed"))
|
|
54832
|
+
if ((source.hashMode === "claude-marketplace-registry" || source.hashMode === "claude-marketplace-registry-v2") && (binding.agent !== "claude" || binding.method !== "reviewed"))
|
|
54788
54833
|
throw new Error("Claude marketplace witnesses require explicit reviewed Claude discovery");
|
|
54789
54834
|
}
|
|
54790
54835
|
function rebindAgentDiscovery(binding, changes) {
|
|
@@ -54913,7 +54958,7 @@ function resolveAgentDiscovery(options) {
|
|
|
54913
54958
|
if (review.sources.some((source) => source.format !== undefined || source.fields !== undefined))
|
|
54914
54959
|
throw new Error("Explicit discovery reviews require full source-file hashes");
|
|
54915
54960
|
verifyAgentDiscovery(supplied);
|
|
54916
|
-
if (review.sources.some((source) => source.hashMode === "claude-settings-v1" && source.path !== canonical4(configPath)))
|
|
54961
|
+
if (review.sources.some((source) => (source.hashMode === "claude-settings-v1" || source.hashMode === "claude-settings-v2") && source.path !== canonical4(configPath)))
|
|
54917
54962
|
throw new Error("Semantic settings witnesses must name the configured Claude configuration source");
|
|
54918
54963
|
if (!review.sources.some((source) => source.path === canonical4(configPath)))
|
|
54919
54964
|
throw new Error("Discovery review must include the agent configuration source");
|
|
@@ -109057,6 +109102,12 @@ function agents(value) {
|
|
|
109057
109102
|
}
|
|
109058
109103
|
function registerAgentIntegration(parent) {
|
|
109059
109104
|
const hook = parent.command("hook").description("Load selected Skills context through agent lifecycle hooks");
|
|
109105
|
+
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) => {
|
|
109106
|
+
const capture2 = options.kind === "claude-settings-v2" ? captureClaudeSettingsV2 : options.kind === "claude-marketplace-registry-v2" ? captureClaudeMarketplaceRegistryV2 : null;
|
|
109107
|
+
if (!capture2)
|
|
109108
|
+
throw new Error("Unsupported witness kind; select claude-settings-v2 or claude-marketplace-registry-v2");
|
|
109109
|
+
await writeCliOutput(JSON.stringify(capture2(options.path), null, 2));
|
|
109110
|
+
});
|
|
109060
109111
|
hook.command("agents").option("--json", "Output the adapter capability inventory", false).description("Show maintained native adapters and explicit coverage limits").action(async () => {
|
|
109061
109112
|
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
109113
|
});
|
|
@@ -109243,6 +109294,8 @@ var init_agent_integration2 = __esm(() => {
|
|
|
109243
109294
|
init_agent_codex_trust();
|
|
109244
109295
|
init_hook_diagnostics();
|
|
109245
109296
|
init_selection_cache();
|
|
109297
|
+
init_claude_settings_witness();
|
|
109298
|
+
init_claude_marketplace_registry();
|
|
109246
109299
|
RECOVERABLE_CONTEXT_CACHE_ERRORS = new Set(["CACHED_PROFILE_EXPIRED", "CACHED_PROFILE_MISSING", "CACHED_BUNDLE_MISSING"]);
|
|
109247
109300
|
});
|
|
109248
109301
|
|
package/bin/maintenance.js
CHANGED
|
@@ -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.
|
|
20022
|
+
version: "0.9.13",
|
|
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
|
|
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 true;
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
10
|
+
version: "0.9.13",
|
|
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 (?, ?, ?, ?, ?)
|