@hasna/skills 0.10.54 → 0.10.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/bin/index.js +133 -5
- package/bin/mcp.js +94 -1
- package/bin/migrate.js +1 -1
- package/bin/server.js +1 -1
- package/bin/worker.js +1 -1
- package/dist/index.js +117 -2
- package/dist/lib/session-reconciliation.d.ts +23 -0
- package/dist/sdk/index.d.ts +1 -1
- package/dist/sdk/index.js +117 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -696,6 +696,10 @@ remain absent only while unchanged native settings and registrations select a
|
|
|
696
696
|
different extant, witnessed user version. Individual missing files, current
|
|
697
697
|
registered roots, aliases and registration changes still refuse; historical
|
|
698
698
|
source hashes and typed settings witnesses remain intact.
|
|
699
|
+
An explicit Claude discovery review must cover every registered active cache
|
|
700
|
+
manifest, skill root and hook source. Installation refuses an incomplete review
|
|
701
|
+
that covers only an older cache, before retirement of that cache can break prompt
|
|
702
|
+
delivery. Hook behavior still requires the existing no-skill-injection review.
|
|
699
703
|
|
|
700
704
|
For a Codex 0.160.0, 0.160.1 or 0.161.0 remote refresh omitted by an existing qualified-name disable,
|
|
701
705
|
a fresh native catalog and discovery review can plan the new exact path disable
|
package/bin/index.js
CHANGED
|
@@ -36888,7 +36888,7 @@ var package_default;
|
|
|
36888
36888
|
var init_package = __esm(() => {
|
|
36889
36889
|
package_default = {
|
|
36890
36890
|
name: "@hasna/skills",
|
|
36891
|
-
version: "0.10.
|
|
36891
|
+
version: "0.10.56",
|
|
36892
36892
|
description: "Skills library for AI coding agents",
|
|
36893
36893
|
type: "module",
|
|
36894
36894
|
bin: {
|
|
@@ -59361,6 +59361,97 @@ function read2(path, changes) {
|
|
|
59361
59361
|
throw new Error(`Unsupported or oversized native discovery input: ${path}`);
|
|
59362
59362
|
return readFileSync21(path, "utf8");
|
|
59363
59363
|
}
|
|
59364
|
+
function assertReviewedClaudeCacheClosures(home, config, review) {
|
|
59365
|
+
const enabledIds = Object.entries(config.enabledPlugins ?? {}).filter(([, enabled]) => enabled === true).map(([id]) => id);
|
|
59366
|
+
if (enabledIds.length === 0)
|
|
59367
|
+
return;
|
|
59368
|
+
const registryPath = join37(home, ".claude/plugins/installed_plugins.json");
|
|
59369
|
+
const cacheRoot = join37(home, ".claude/plugins/cache");
|
|
59370
|
+
const registryText = read2(registryPath);
|
|
59371
|
+
if (registryText === null)
|
|
59372
|
+
return;
|
|
59373
|
+
const registry = parseConfig(registryText, registryPath);
|
|
59374
|
+
if (registry.version !== 2 || !registry.plugins || typeof registry.plugins !== "object" || Array.isArray(registry.plugins))
|
|
59375
|
+
throw new Error("Reviewed Claude installed plugin registrations are invalid");
|
|
59376
|
+
const cachePath = (path) => typeof path === "string" && isAbsolute22(path) && resolve21(path) === path && (path === cacheRoot || path.startsWith(cacheRoot + sep8));
|
|
59377
|
+
const activeRoots = new Set;
|
|
59378
|
+
for (const id of enabledIds) {
|
|
59379
|
+
const rows = registry.plugins[id];
|
|
59380
|
+
if (rows === undefined)
|
|
59381
|
+
continue;
|
|
59382
|
+
if (!Array.isArray(rows))
|
|
59383
|
+
throw new Error("Reviewed Claude enabled plugin registrations must be an array");
|
|
59384
|
+
for (const row of rows) {
|
|
59385
|
+
if (!row || typeof row !== "object" || Array.isArray(row) || typeof row.installPath !== "string") {
|
|
59386
|
+
throw new Error("Reviewed Claude enabled plugin registration requires an object with an installPath string");
|
|
59387
|
+
}
|
|
59388
|
+
if (typeof row.installPath === "string" && (row.installPath === cacheRoot || row.installPath.startsWith(cacheRoot + sep8))) {
|
|
59389
|
+
if (!cachePath(row.installPath) || row.installPath.length > AGENT_POLICY_LIMITS.pathCharacters || row.version !== undefined && (typeof row.version !== "string" || basename11(row.installPath) !== row.version)) {
|
|
59390
|
+
throw new Error("Reviewed Claude discovery has a malformed active cache registration");
|
|
59391
|
+
}
|
|
59392
|
+
if (row.scope === "user" && (row.projectPath === undefined || row.projectPath === null)) {} else if (row.scope === "project" && typeof row.projectPath === "string" && isAbsolute22(row.projectPath) && resolve21(row.projectPath) === row.projectPath && row.projectPath.length <= AGENT_POLICY_LIMITS.pathCharacters) {} else
|
|
59393
|
+
throw new Error("Reviewed Claude discovery has an unsupported active cache registration scope");
|
|
59394
|
+
activeRoots.add(row.installPath);
|
|
59395
|
+
}
|
|
59396
|
+
}
|
|
59397
|
+
}
|
|
59398
|
+
if (activeRoots.size === 0)
|
|
59399
|
+
return;
|
|
59400
|
+
const registrySource = review.sources.find((source) => source.path === registryPath && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && (source.hashMode === undefined || source.hashMode === "bytes"));
|
|
59401
|
+
if (!registrySource)
|
|
59402
|
+
throw new Error("Reviewed Claude discovery must bind installed registrations for active cache plugins");
|
|
59403
|
+
const reviewedFile = (path, manifest = false) => review.sources.some((source) => source.path === path && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && (source.hashMode === undefined || source.hashMode === "bytes" || manifest && source.hashMode === "claude-plugin-manifest-v1"));
|
|
59404
|
+
const requireSource = (path, description, manifest = false) => {
|
|
59405
|
+
if (!reviewedFile(path, manifest))
|
|
59406
|
+
throw new Error(`Reviewed active Claude cache plugin ${description} is not bound`);
|
|
59407
|
+
};
|
|
59408
|
+
for (const root2 of activeRoots) {
|
|
59409
|
+
safe4(root2);
|
|
59410
|
+
const rootStat = lstatSync25(root2, { throwIfNoEntry: false });
|
|
59411
|
+
if (!rootStat?.isDirectory() || realpathSync11(root2) !== root2)
|
|
59412
|
+
throw new Error("Reviewed Claude active cache plugin root is missing or unsafe");
|
|
59413
|
+
const manifestPath = join37(root2, ".claude-plugin/plugin.json");
|
|
59414
|
+
const manifestStat = lstatSync25(manifestPath, { throwIfNoEntry: false });
|
|
59415
|
+
let manifest;
|
|
59416
|
+
if (manifestStat) {
|
|
59417
|
+
if (!manifestStat.isFile())
|
|
59418
|
+
throw new Error("Reviewed active Claude cache plugin manifest is unsupported");
|
|
59419
|
+
requireSource(manifestPath, "manifest", true);
|
|
59420
|
+
const raw = read2(manifestPath);
|
|
59421
|
+
if (raw === null)
|
|
59422
|
+
throw new Error("Reviewed Claude active cache plugin manifest is missing");
|
|
59423
|
+
manifest = JSON.parse(raw);
|
|
59424
|
+
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest))
|
|
59425
|
+
throw new Error("Reviewed Claude active cache plugin manifest is invalid");
|
|
59426
|
+
} else if (!review.sources.some((source) => source.path === manifestPath && source.hashMode === "path-bytes" && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined)) {
|
|
59427
|
+
throw new Error("Reviewed Claude active cache plugin manifest absence requires a path-bytes witness");
|
|
59428
|
+
}
|
|
59429
|
+
const roots = new Set([join37(root2, "skills")]);
|
|
59430
|
+
if (lstatSync25(join37(root2, "SKILL.md"), { throwIfNoEntry: false })?.isFile())
|
|
59431
|
+
roots.add(root2);
|
|
59432
|
+
const declared = manifest?.skills === undefined ? ["./skills"] : typeof manifest.skills === "string" ? [manifest.skills] : manifest.skills;
|
|
59433
|
+
if (!Array.isArray(declared) || declared.some((path) => typeof path !== "string"))
|
|
59434
|
+
throw new Error("Reviewed Claude active cache plugin has unsupported skill paths");
|
|
59435
|
+
for (const value of declared) {
|
|
59436
|
+
const path = resolve21(root2, value);
|
|
59437
|
+
if (path !== root2 && !path.startsWith(root2 + sep8))
|
|
59438
|
+
throw new Error("Reviewed Claude active cache plugin skill path escapes its root");
|
|
59439
|
+
roots.add(path.endsWith(`${sep8}SKILL.md`) ? dirname20(path) : path);
|
|
59440
|
+
}
|
|
59441
|
+
for (const skillRoot of roots)
|
|
59442
|
+
if (!review.roots.includes(skillRoot))
|
|
59443
|
+
throw new Error("Reviewed Claude active cache plugin skill root is not covered");
|
|
59444
|
+
const hooksPath = join37(root2, "hooks/hooks.json");
|
|
59445
|
+
const hooksStat = lstatSync25(hooksPath, { throwIfNoEntry: false });
|
|
59446
|
+
if (hooksStat) {
|
|
59447
|
+
if (!hooksStat.isFile())
|
|
59448
|
+
throw new Error("Reviewed Claude plugin hook manifest is unsupported");
|
|
59449
|
+
requireSource(hooksPath, "hook manifest");
|
|
59450
|
+
}
|
|
59451
|
+
if (typeof manifest?.hooks === "string")
|
|
59452
|
+
requireSource(resolve21(root2, manifest.hooks), "manifest-declared hook source");
|
|
59453
|
+
}
|
|
59454
|
+
}
|
|
59364
59455
|
function projectNativeDiscoveryFields(text3, format, fields2, path) {
|
|
59365
59456
|
const object6 = format === "yaml" ? parseHermesConfig(text3) : parseConfig(text3, path, format === "toml");
|
|
59366
59457
|
if (!object6 || typeof object6 !== "object" || Array.isArray(object6))
|
|
@@ -59937,6 +60028,8 @@ function resolveAgentDiscovery(options) {
|
|
|
59937
60028
|
throw new Error("Reviewed Claude plugin manifest requires separately reviewed exact hook file sources");
|
|
59938
60029
|
return { path: source.path, sha256: manifestSha256, hashMode: "claude-plugin-manifest-v1" };
|
|
59939
60030
|
});
|
|
60031
|
+
if (agent === "claude")
|
|
60032
|
+
assertReviewedClaudeCacheClosures(home, config, review);
|
|
59940
60033
|
const combined = [...reviewedSources, ...reviewedSourcesWithManifestProjection];
|
|
59941
60034
|
const unique = combined.filter((source, index) => combined.findIndex((candidate2) => isDeepStrictEqual11(candidate2, source)) === index);
|
|
59942
60035
|
return { ...supplied, roots: [...new Set([...roots, ...supplied.roots])].sort(), sources: unique, ...agent === "gemini" ? { builtinNames } : {} };
|
|
@@ -86070,8 +86163,12 @@ function assertPlanWindow(issuedAt, expiresAt, now3) {
|
|
|
86070
86163
|
throw new SkillSelectionError("SESSION_PLAN_EXPIRED", "The reviewed session reconciliation plan is not currently valid; inspect the receipt and prepare a new plan.");
|
|
86071
86164
|
}
|
|
86072
86165
|
}
|
|
86166
|
+
function selectionMetadata(selection) {
|
|
86167
|
+
return { slug: selection.slug, version: selection.version, bundleDigest: selection.bundleDigest };
|
|
86168
|
+
}
|
|
86073
86169
|
function inspectSkillSession(sessionId, options = {}) {
|
|
86074
86170
|
const snapshot2 = readSkillSessionSnapshot(sessionId, options), { receipt } = snapshot2;
|
|
86171
|
+
const loaded = new Set(receipt.loaded);
|
|
86075
86172
|
return {
|
|
86076
86173
|
sessionId,
|
|
86077
86174
|
path: snapshot2.path,
|
|
@@ -86083,7 +86180,8 @@ function inspectSkillSession(sessionId, options = {}) {
|
|
|
86083
86180
|
profileId: receipt.profile.profileId,
|
|
86084
86181
|
profileRevision: receipt.profile.profileRevision,
|
|
86085
86182
|
selectionCount: receipt.profile.selections.length,
|
|
86086
|
-
loadedCount: receipt.loaded.length
|
|
86183
|
+
loadedCount: receipt.loaded.length,
|
|
86184
|
+
selections: receipt.profile.selections.map((selection) => ({ ...selectionMetadata(selection), loaded: loaded.has(selectionKey(selection)) }))
|
|
86087
86185
|
};
|
|
86088
86186
|
}
|
|
86089
86187
|
function renewalDeadlineSignal(error61) {
|
|
@@ -86305,6 +86403,21 @@ async function reconcileSkillSession(input2, options = {}) {
|
|
|
86305
86403
|
}
|
|
86306
86404
|
const targetKeys = new Set(target.selections.map(selectionKey));
|
|
86307
86405
|
const loaded = old.loaded.filter((key) => targetKeys.has(key));
|
|
86406
|
+
const previouslyLoaded = new Set(old.loaded);
|
|
86407
|
+
const targetBySlug = new Map(target.selections.map((selection) => [selection.slug, selection]));
|
|
86408
|
+
const previousSlugs = new Set(old.profile.selections.map((selection) => selection.slug));
|
|
86409
|
+
const selectionDelta = old.profile.selections.map((previous) => {
|
|
86410
|
+
const current = targetBySlug.get(previous.slug);
|
|
86411
|
+
const wasLoaded = previouslyLoaded.has(selectionKey(previous));
|
|
86412
|
+
const reason2 = !current ? "selection-removed" : selectionKey(previous) === selectionKey(current) ? "same-bundle" : "bundle-changed";
|
|
86413
|
+
return {
|
|
86414
|
+
selection: selectionMetadata(previous),
|
|
86415
|
+
loaded: wasLoaded,
|
|
86416
|
+
outcome: !wasLoaded ? "unloaded" : reason2 === "same-bundle" ? "retained" : "retired",
|
|
86417
|
+
reason: reason2,
|
|
86418
|
+
...reason2 === "bundle-changed" ? { replacement: selectionMetadata(current) } : {}
|
|
86419
|
+
};
|
|
86420
|
+
});
|
|
86308
86421
|
const replacementGeneration = nextSkillSessionGeneration(snapshot2.generation);
|
|
86309
86422
|
const plan = {
|
|
86310
86423
|
schemaVersion: 1,
|
|
@@ -86317,6 +86430,8 @@ async function reconcileSkillSession(input2, options = {}) {
|
|
|
86317
86430
|
replacementGeneration,
|
|
86318
86431
|
retainedLoadedCount: loaded.length,
|
|
86319
86432
|
retiredLoadedCount: old.loaded.length - loaded.length,
|
|
86433
|
+
selectionDelta,
|
|
86434
|
+
addedSelections: target.selections.filter((selection) => !previousSlugs.has(selection.slug)).map(selectionMetadata),
|
|
86320
86435
|
scope: "Only this receipt; existing child sessions, project locks, shared profiles, hooks and running processes are unchanged."
|
|
86321
86436
|
};
|
|
86322
86437
|
const planDigest = digest3(plan);
|
|
@@ -86356,6 +86471,9 @@ __export(exports_context, {
|
|
|
86356
86471
|
contextResolverOptions: () => contextResolverOptions
|
|
86357
86472
|
});
|
|
86358
86473
|
import { readSync as readSync12 } from "fs";
|
|
86474
|
+
function sessionSelectionLabel(selection) {
|
|
86475
|
+
return `${selection.slug}@${selection.version} ${selection.bundleDigest}`;
|
|
86476
|
+
}
|
|
86359
86477
|
function selectedProfileId(explicit) {
|
|
86360
86478
|
return explicit ?? process.env.HASNA_SKILLS_SELECTION_PROFILE ?? readManagedSkillPolicy()?.profileId ?? "default";
|
|
86361
86479
|
}
|
|
@@ -86373,8 +86491,12 @@ function registerContextCommands(parent) {
|
|
|
86373
86491
|
sessions.command("show <id>").option("--json", "Return metadata as JSON", false).description("Inspect one session receipt without displaying payloads or changing its pin").action(async (id, options) => {
|
|
86374
86492
|
try {
|
|
86375
86493
|
const result2 = inspectSkillSession(id);
|
|
86376
|
-
await writeCliOutput(options.json ? JSON.stringify(result2) :
|
|
86377
|
-
|
|
86494
|
+
await writeCliOutput(options.json ? JSON.stringify(result2) : [
|
|
86495
|
+
`${JSON.stringify(id)}: ${result2.profileId} at ${result2.profileRevision}`,
|
|
86496
|
+
`Receipt SHA256: ${result2.receiptSha256}`,
|
|
86497
|
+
...result2.selections.map((selection) => `${selection.loaded ? "Loaded" : "Unloaded"}: ${sessionSelectionLabel(selection)}`)
|
|
86498
|
+
].join(`
|
|
86499
|
+
`));
|
|
86378
86500
|
} catch (error61) {
|
|
86379
86501
|
reportContextError(error61, options.json);
|
|
86380
86502
|
}
|
|
@@ -86401,7 +86523,13 @@ Review digest: ${result2.reviewDigest}`);
|
|
|
86401
86523
|
try {
|
|
86402
86524
|
const result2 = await reconcileSkillSession({ ...options, sessionId: id });
|
|
86403
86525
|
await writeCliOutput(options.json ? JSON.stringify(result2) : result2.applied ? `Reconciled ${JSON.stringify(id)} to ${result2.plan.target.profileId} at ${result2.plan.target.profileRevision}.
|
|
86404
|
-
Preserved original receipt: ${result2.archivePath}` :
|
|
86526
|
+
Preserved original receipt: ${result2.archivePath}` : [
|
|
86527
|
+
`Planned one session reconciliation: ${result2.plan.retainedLoadedCount} loaded retained, ${result2.plan.retiredLoadedCount} loaded retired.`,
|
|
86528
|
+
...result2.plan.selectionDelta.map((row) => `${row.outcome} (${row.reason}): ${sessionSelectionLabel(row.selection)}${row.replacement ? ` -> ${sessionSelectionLabel(row.replacement)}` : ""}`),
|
|
86529
|
+
...result2.plan.addedSelections.map((selection) => `Added selection (unloaded): ${sessionSelectionLabel(selection)}`),
|
|
86530
|
+
`Review --json output, then use --apply --plan-digest ${result2.planDigest} --plan-issued-at ${result2.plan.issuedAt} --plan-expires-at ${result2.plan.expiresAt}.`
|
|
86531
|
+
].join(`
|
|
86532
|
+
`));
|
|
86405
86533
|
} catch (error61) {
|
|
86406
86534
|
reportContextError(error61, options.json);
|
|
86407
86535
|
}
|
package/bin/mcp.js
CHANGED
|
@@ -20019,7 +20019,7 @@ var package_default;
|
|
|
20019
20019
|
var init_package = __esm(() => {
|
|
20020
20020
|
package_default = {
|
|
20021
20021
|
name: "@hasna/skills",
|
|
20022
|
-
version: "0.10.
|
|
20022
|
+
version: "0.10.56",
|
|
20023
20023
|
description: "Skills library for AI coding agents",
|
|
20024
20024
|
type: "module",
|
|
20025
20025
|
bin: {
|
|
@@ -50788,6 +50788,97 @@ function read(path, changes) {
|
|
|
50788
50788
|
throw new Error(`Unsupported or oversized native discovery input: ${path}`);
|
|
50789
50789
|
return readFileSync15(path, "utf8");
|
|
50790
50790
|
}
|
|
50791
|
+
function assertReviewedClaudeCacheClosures(home, config2, review) {
|
|
50792
|
+
const enabledIds = Object.entries(config2.enabledPlugins ?? {}).filter(([, enabled]) => enabled === true).map(([id]) => id);
|
|
50793
|
+
if (enabledIds.length === 0)
|
|
50794
|
+
return;
|
|
50795
|
+
const registryPath = join26(home, ".claude/plugins/installed_plugins.json");
|
|
50796
|
+
const cacheRoot = join26(home, ".claude/plugins/cache");
|
|
50797
|
+
const registryText = read(registryPath);
|
|
50798
|
+
if (registryText === null)
|
|
50799
|
+
return;
|
|
50800
|
+
const registry2 = parseConfig(registryText, registryPath);
|
|
50801
|
+
if (registry2.version !== 2 || !registry2.plugins || typeof registry2.plugins !== "object" || Array.isArray(registry2.plugins))
|
|
50802
|
+
throw new Error("Reviewed Claude installed plugin registrations are invalid");
|
|
50803
|
+
const cachePath = (path) => typeof path === "string" && isAbsolute15(path) && resolve17(path) === path && (path === cacheRoot || path.startsWith(cacheRoot + sep6));
|
|
50804
|
+
const activeRoots = new Set;
|
|
50805
|
+
for (const id of enabledIds) {
|
|
50806
|
+
const rows = registry2.plugins[id];
|
|
50807
|
+
if (rows === undefined)
|
|
50808
|
+
continue;
|
|
50809
|
+
if (!Array.isArray(rows))
|
|
50810
|
+
throw new Error("Reviewed Claude enabled plugin registrations must be an array");
|
|
50811
|
+
for (const row of rows) {
|
|
50812
|
+
if (!row || typeof row !== "object" || Array.isArray(row) || typeof row.installPath !== "string") {
|
|
50813
|
+
throw new Error("Reviewed Claude enabled plugin registration requires an object with an installPath string");
|
|
50814
|
+
}
|
|
50815
|
+
if (typeof row.installPath === "string" && (row.installPath === cacheRoot || row.installPath.startsWith(cacheRoot + sep6))) {
|
|
50816
|
+
if (!cachePath(row.installPath) || row.installPath.length > AGENT_POLICY_LIMITS.pathCharacters || row.version !== undefined && (typeof row.version !== "string" || basename9(row.installPath) !== row.version)) {
|
|
50817
|
+
throw new Error("Reviewed Claude discovery has a malformed active cache registration");
|
|
50818
|
+
}
|
|
50819
|
+
if (row.scope === "user" && (row.projectPath === undefined || row.projectPath === null)) {} else if (row.scope === "project" && typeof row.projectPath === "string" && isAbsolute15(row.projectPath) && resolve17(row.projectPath) === row.projectPath && row.projectPath.length <= AGENT_POLICY_LIMITS.pathCharacters) {} else
|
|
50820
|
+
throw new Error("Reviewed Claude discovery has an unsupported active cache registration scope");
|
|
50821
|
+
activeRoots.add(row.installPath);
|
|
50822
|
+
}
|
|
50823
|
+
}
|
|
50824
|
+
}
|
|
50825
|
+
if (activeRoots.size === 0)
|
|
50826
|
+
return;
|
|
50827
|
+
const registrySource = review.sources.find((source) => source.path === registryPath && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && (source.hashMode === undefined || source.hashMode === "bytes"));
|
|
50828
|
+
if (!registrySource)
|
|
50829
|
+
throw new Error("Reviewed Claude discovery must bind installed registrations for active cache plugins");
|
|
50830
|
+
const reviewedFile = (path, manifest = false) => review.sources.some((source) => source.path === path && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && (source.hashMode === undefined || source.hashMode === "bytes" || manifest && source.hashMode === "claude-plugin-manifest-v1"));
|
|
50831
|
+
const requireSource = (path, description, manifest = false) => {
|
|
50832
|
+
if (!reviewedFile(path, manifest))
|
|
50833
|
+
throw new Error(`Reviewed active Claude cache plugin ${description} is not bound`);
|
|
50834
|
+
};
|
|
50835
|
+
for (const root of activeRoots) {
|
|
50836
|
+
safe4(root);
|
|
50837
|
+
const rootStat = lstatSync18(root, { throwIfNoEntry: false });
|
|
50838
|
+
if (!rootStat?.isDirectory() || realpathSync5(root) !== root)
|
|
50839
|
+
throw new Error("Reviewed Claude active cache plugin root is missing or unsafe");
|
|
50840
|
+
const manifestPath = join26(root, ".claude-plugin/plugin.json");
|
|
50841
|
+
const manifestStat = lstatSync18(manifestPath, { throwIfNoEntry: false });
|
|
50842
|
+
let manifest;
|
|
50843
|
+
if (manifestStat) {
|
|
50844
|
+
if (!manifestStat.isFile())
|
|
50845
|
+
throw new Error("Reviewed active Claude cache plugin manifest is unsupported");
|
|
50846
|
+
requireSource(manifestPath, "manifest", true);
|
|
50847
|
+
const raw = read(manifestPath);
|
|
50848
|
+
if (raw === null)
|
|
50849
|
+
throw new Error("Reviewed Claude active cache plugin manifest is missing");
|
|
50850
|
+
manifest = JSON.parse(raw);
|
|
50851
|
+
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest))
|
|
50852
|
+
throw new Error("Reviewed Claude active cache plugin manifest is invalid");
|
|
50853
|
+
} else if (!review.sources.some((source) => source.path === manifestPath && source.hashMode === "path-bytes" && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined)) {
|
|
50854
|
+
throw new Error("Reviewed Claude active cache plugin manifest absence requires a path-bytes witness");
|
|
50855
|
+
}
|
|
50856
|
+
const roots = new Set([join26(root, "skills")]);
|
|
50857
|
+
if (lstatSync18(join26(root, "SKILL.md"), { throwIfNoEntry: false })?.isFile())
|
|
50858
|
+
roots.add(root);
|
|
50859
|
+
const declared = manifest?.skills === undefined ? ["./skills"] : typeof manifest.skills === "string" ? [manifest.skills] : manifest.skills;
|
|
50860
|
+
if (!Array.isArray(declared) || declared.some((path) => typeof path !== "string"))
|
|
50861
|
+
throw new Error("Reviewed Claude active cache plugin has unsupported skill paths");
|
|
50862
|
+
for (const value of declared) {
|
|
50863
|
+
const path = resolve17(root, value);
|
|
50864
|
+
if (path !== root && !path.startsWith(root + sep6))
|
|
50865
|
+
throw new Error("Reviewed Claude active cache plugin skill path escapes its root");
|
|
50866
|
+
roots.add(path.endsWith(`${sep6}SKILL.md`) ? dirname16(path) : path);
|
|
50867
|
+
}
|
|
50868
|
+
for (const skillRoot of roots)
|
|
50869
|
+
if (!review.roots.includes(skillRoot))
|
|
50870
|
+
throw new Error("Reviewed Claude active cache plugin skill root is not covered");
|
|
50871
|
+
const hooksPath = join26(root, "hooks/hooks.json");
|
|
50872
|
+
const hooksStat = lstatSync18(hooksPath, { throwIfNoEntry: false });
|
|
50873
|
+
if (hooksStat) {
|
|
50874
|
+
if (!hooksStat.isFile())
|
|
50875
|
+
throw new Error("Reviewed Claude plugin hook manifest is unsupported");
|
|
50876
|
+
requireSource(hooksPath, "hook manifest");
|
|
50877
|
+
}
|
|
50878
|
+
if (typeof manifest?.hooks === "string")
|
|
50879
|
+
requireSource(resolve17(root, manifest.hooks), "manifest-declared hook source");
|
|
50880
|
+
}
|
|
50881
|
+
}
|
|
50791
50882
|
function projectNativeDiscoveryFields(text2, format, fields2, path) {
|
|
50792
50883
|
const object7 = format === "yaml" ? parseHermesConfig(text2) : parseConfig(text2, path, format === "toml");
|
|
50793
50884
|
if (!object7 || typeof object7 !== "object" || Array.isArray(object7))
|
|
@@ -51254,6 +51345,8 @@ function resolveAgentDiscovery(options) {
|
|
|
51254
51345
|
throw new Error("Reviewed Claude plugin manifest requires separately reviewed exact hook file sources");
|
|
51255
51346
|
return { path: source.path, sha256: manifestSha256, hashMode: "claude-plugin-manifest-v1" };
|
|
51256
51347
|
});
|
|
51348
|
+
if (agent === "claude")
|
|
51349
|
+
assertReviewedClaudeCacheClosures(home, config2, review);
|
|
51257
51350
|
const combined = [...reviewedSources, ...reviewedSourcesWithManifestProjection];
|
|
51258
51351
|
const unique = combined.filter((source, index) => combined.findIndex((candidate) => isDeepStrictEqual4(candidate, source)) === index);
|
|
51259
51352
|
return { ...supplied, roots: [...new Set([...roots, ...supplied.roots])].sort(), sources: unique, ...agent === "gemini" ? { builtinNames } : {} };
|
package/bin/migrate.js
CHANGED
package/bin/server.js
CHANGED
|
@@ -24140,7 +24140,7 @@ restoreLaunchCwd();
|
|
|
24140
24140
|
// package.json
|
|
24141
24141
|
var package_default = {
|
|
24142
24142
|
name: "@hasna/skills",
|
|
24143
|
-
version: "0.10.
|
|
24143
|
+
version: "0.10.56",
|
|
24144
24144
|
description: "Skills library for AI coding agents",
|
|
24145
24145
|
type: "module",
|
|
24146
24146
|
bin: {
|
package/bin/worker.js
CHANGED
|
@@ -24143,7 +24143,7 @@ import { randomUUID as randomUUID6 } from "crypto";
|
|
|
24143
24143
|
// package.json
|
|
24144
24144
|
var package_default = {
|
|
24145
24145
|
name: "@hasna/skills",
|
|
24146
|
-
version: "0.10.
|
|
24146
|
+
version: "0.10.56",
|
|
24147
24147
|
description: "Skills library for AI coding agents",
|
|
24148
24148
|
type: "module",
|
|
24149
24149
|
bin: {
|
package/dist/index.js
CHANGED
|
@@ -11833,7 +11833,7 @@ function replaceSkillSession(expected, replacement, plan, options = {}, beforeCo
|
|
|
11833
11833
|
// package.json
|
|
11834
11834
|
var package_default = {
|
|
11835
11835
|
name: "@hasna/skills",
|
|
11836
|
-
version: "0.10.
|
|
11836
|
+
version: "0.10.56",
|
|
11837
11837
|
description: "Skills library for AI coding agents",
|
|
11838
11838
|
type: "module",
|
|
11839
11839
|
bin: {
|
|
@@ -20693,6 +20693,97 @@ function read2(path, changes) {
|
|
|
20693
20693
|
throw new Error(`Unsupported or oversized native discovery input: ${path}`);
|
|
20694
20694
|
return readFileSync20(path, "utf8");
|
|
20695
20695
|
}
|
|
20696
|
+
function assertReviewedClaudeCacheClosures(home, config, review) {
|
|
20697
|
+
const enabledIds = Object.entries(config.enabledPlugins ?? {}).filter(([, enabled]) => enabled === true).map(([id]) => id);
|
|
20698
|
+
if (enabledIds.length === 0)
|
|
20699
|
+
return;
|
|
20700
|
+
const registryPath = join32(home, ".claude/plugins/installed_plugins.json");
|
|
20701
|
+
const cacheRoot = join32(home, ".claude/plugins/cache");
|
|
20702
|
+
const registryText = read2(registryPath);
|
|
20703
|
+
if (registryText === null)
|
|
20704
|
+
return;
|
|
20705
|
+
const registry = parseConfig(registryText, registryPath);
|
|
20706
|
+
if (registry.version !== 2 || !registry.plugins || typeof registry.plugins !== "object" || Array.isArray(registry.plugins))
|
|
20707
|
+
throw new Error("Reviewed Claude installed plugin registrations are invalid");
|
|
20708
|
+
const cachePath = (path) => typeof path === "string" && isAbsolute21(path) && resolve20(path) === path && (path === cacheRoot || path.startsWith(cacheRoot + sep8));
|
|
20709
|
+
const activeRoots = new Set;
|
|
20710
|
+
for (const id of enabledIds) {
|
|
20711
|
+
const rows = registry.plugins[id];
|
|
20712
|
+
if (rows === undefined)
|
|
20713
|
+
continue;
|
|
20714
|
+
if (!Array.isArray(rows))
|
|
20715
|
+
throw new Error("Reviewed Claude enabled plugin registrations must be an array");
|
|
20716
|
+
for (const row of rows) {
|
|
20717
|
+
if (!row || typeof row !== "object" || Array.isArray(row) || typeof row.installPath !== "string") {
|
|
20718
|
+
throw new Error("Reviewed Claude enabled plugin registration requires an object with an installPath string");
|
|
20719
|
+
}
|
|
20720
|
+
if (typeof row.installPath === "string" && (row.installPath === cacheRoot || row.installPath.startsWith(cacheRoot + sep8))) {
|
|
20721
|
+
if (!cachePath(row.installPath) || row.installPath.length > AGENT_POLICY_LIMITS.pathCharacters || row.version !== undefined && (typeof row.version !== "string" || basename11(row.installPath) !== row.version)) {
|
|
20722
|
+
throw new Error("Reviewed Claude discovery has a malformed active cache registration");
|
|
20723
|
+
}
|
|
20724
|
+
if (row.scope === "user" && (row.projectPath === undefined || row.projectPath === null)) {} else if (row.scope === "project" && typeof row.projectPath === "string" && isAbsolute21(row.projectPath) && resolve20(row.projectPath) === row.projectPath && row.projectPath.length <= AGENT_POLICY_LIMITS.pathCharacters) {} else
|
|
20725
|
+
throw new Error("Reviewed Claude discovery has an unsupported active cache registration scope");
|
|
20726
|
+
activeRoots.add(row.installPath);
|
|
20727
|
+
}
|
|
20728
|
+
}
|
|
20729
|
+
}
|
|
20730
|
+
if (activeRoots.size === 0)
|
|
20731
|
+
return;
|
|
20732
|
+
const registrySource = review.sources.find((source) => source.path === registryPath && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && (source.hashMode === undefined || source.hashMode === "bytes"));
|
|
20733
|
+
if (!registrySource)
|
|
20734
|
+
throw new Error("Reviewed Claude discovery must bind installed registrations for active cache plugins");
|
|
20735
|
+
const reviewedFile = (path, manifest = false) => review.sources.some((source) => source.path === path && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && (source.hashMode === undefined || source.hashMode === "bytes" || manifest && source.hashMode === "claude-plugin-manifest-v1"));
|
|
20736
|
+
const requireSource = (path, description, manifest = false) => {
|
|
20737
|
+
if (!reviewedFile(path, manifest))
|
|
20738
|
+
throw new Error(`Reviewed active Claude cache plugin ${description} is not bound`);
|
|
20739
|
+
};
|
|
20740
|
+
for (const root2 of activeRoots) {
|
|
20741
|
+
safe4(root2);
|
|
20742
|
+
const rootStat = lstatSync25(root2, { throwIfNoEntry: false });
|
|
20743
|
+
if (!rootStat?.isDirectory() || realpathSync11(root2) !== root2)
|
|
20744
|
+
throw new Error("Reviewed Claude active cache plugin root is missing or unsafe");
|
|
20745
|
+
const manifestPath = join32(root2, ".claude-plugin/plugin.json");
|
|
20746
|
+
const manifestStat = lstatSync25(manifestPath, { throwIfNoEntry: false });
|
|
20747
|
+
let manifest;
|
|
20748
|
+
if (manifestStat) {
|
|
20749
|
+
if (!manifestStat.isFile())
|
|
20750
|
+
throw new Error("Reviewed active Claude cache plugin manifest is unsupported");
|
|
20751
|
+
requireSource(manifestPath, "manifest", true);
|
|
20752
|
+
const raw = read2(manifestPath);
|
|
20753
|
+
if (raw === null)
|
|
20754
|
+
throw new Error("Reviewed Claude active cache plugin manifest is missing");
|
|
20755
|
+
manifest = JSON.parse(raw);
|
|
20756
|
+
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest))
|
|
20757
|
+
throw new Error("Reviewed Claude active cache plugin manifest is invalid");
|
|
20758
|
+
} else if (!review.sources.some((source) => source.path === manifestPath && source.hashMode === "path-bytes" && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined)) {
|
|
20759
|
+
throw new Error("Reviewed Claude active cache plugin manifest absence requires a path-bytes witness");
|
|
20760
|
+
}
|
|
20761
|
+
const roots = new Set([join32(root2, "skills")]);
|
|
20762
|
+
if (lstatSync25(join32(root2, "SKILL.md"), { throwIfNoEntry: false })?.isFile())
|
|
20763
|
+
roots.add(root2);
|
|
20764
|
+
const declared = manifest?.skills === undefined ? ["./skills"] : typeof manifest.skills === "string" ? [manifest.skills] : manifest.skills;
|
|
20765
|
+
if (!Array.isArray(declared) || declared.some((path) => typeof path !== "string"))
|
|
20766
|
+
throw new Error("Reviewed Claude active cache plugin has unsupported skill paths");
|
|
20767
|
+
for (const value of declared) {
|
|
20768
|
+
const path = resolve20(root2, value);
|
|
20769
|
+
if (path !== root2 && !path.startsWith(root2 + sep8))
|
|
20770
|
+
throw new Error("Reviewed Claude active cache plugin skill path escapes its root");
|
|
20771
|
+
roots.add(path.endsWith(`${sep8}SKILL.md`) ? dirname20(path) : path);
|
|
20772
|
+
}
|
|
20773
|
+
for (const skillRoot of roots)
|
|
20774
|
+
if (!review.roots.includes(skillRoot))
|
|
20775
|
+
throw new Error("Reviewed Claude active cache plugin skill root is not covered");
|
|
20776
|
+
const hooksPath = join32(root2, "hooks/hooks.json");
|
|
20777
|
+
const hooksStat = lstatSync25(hooksPath, { throwIfNoEntry: false });
|
|
20778
|
+
if (hooksStat) {
|
|
20779
|
+
if (!hooksStat.isFile())
|
|
20780
|
+
throw new Error("Reviewed Claude plugin hook manifest is unsupported");
|
|
20781
|
+
requireSource(hooksPath, "hook manifest");
|
|
20782
|
+
}
|
|
20783
|
+
if (typeof manifest?.hooks === "string")
|
|
20784
|
+
requireSource(resolve20(root2, manifest.hooks), "manifest-declared hook source");
|
|
20785
|
+
}
|
|
20786
|
+
}
|
|
20696
20787
|
function projectNativeDiscoveryFields(text2, format, fields2, path) {
|
|
20697
20788
|
const object6 = format === "yaml" ? parseHermesConfig(text2) : parseConfig(text2, path, format === "toml");
|
|
20698
20789
|
if (!object6 || typeof object6 !== "object" || Array.isArray(object6))
|
|
@@ -21281,6 +21372,8 @@ function resolveAgentDiscovery(options) {
|
|
|
21281
21372
|
throw new Error("Reviewed Claude plugin manifest requires separately reviewed exact hook file sources");
|
|
21282
21373
|
return { path: source.path, sha256: manifestSha256, hashMode: "claude-plugin-manifest-v1" };
|
|
21283
21374
|
});
|
|
21375
|
+
if (agent === "claude")
|
|
21376
|
+
assertReviewedClaudeCacheClosures(home, config, review);
|
|
21284
21377
|
const combined = [...reviewedSources, ...reviewedSourcesWithManifestProjection];
|
|
21285
21378
|
const unique = combined.filter((source, index) => combined.findIndex((candidate2) => isDeepStrictEqual7(candidate2, source)) === index);
|
|
21286
21379
|
return { ...supplied, roots: [...new Set([...roots, ...supplied.roots])].sort(), sources: unique, ...agent === "gemini" ? { builtinNames } : {} };
|
|
@@ -48150,8 +48243,12 @@ function assertPlanWindow(issuedAt, expiresAt, now) {
|
|
|
48150
48243
|
throw new SkillSelectionError("SESSION_PLAN_EXPIRED", "The reviewed session reconciliation plan is not currently valid; inspect the receipt and prepare a new plan.");
|
|
48151
48244
|
}
|
|
48152
48245
|
}
|
|
48246
|
+
function selectionMetadata(selection) {
|
|
48247
|
+
return { slug: selection.slug, version: selection.version, bundleDigest: selection.bundleDigest };
|
|
48248
|
+
}
|
|
48153
48249
|
function inspectSkillSession(sessionId, options = {}) {
|
|
48154
48250
|
const snapshot2 = readSkillSessionSnapshot(sessionId, options), { receipt } = snapshot2;
|
|
48251
|
+
const loaded = new Set(receipt.loaded);
|
|
48155
48252
|
return {
|
|
48156
48253
|
sessionId,
|
|
48157
48254
|
path: snapshot2.path,
|
|
@@ -48163,7 +48260,8 @@ function inspectSkillSession(sessionId, options = {}) {
|
|
|
48163
48260
|
profileId: receipt.profile.profileId,
|
|
48164
48261
|
profileRevision: receipt.profile.profileRevision,
|
|
48165
48262
|
selectionCount: receipt.profile.selections.length,
|
|
48166
|
-
loadedCount: receipt.loaded.length
|
|
48263
|
+
loadedCount: receipt.loaded.length,
|
|
48264
|
+
selections: receipt.profile.selections.map((selection) => ({ ...selectionMetadata(selection), loaded: loaded.has(selectionKey(selection)) }))
|
|
48167
48265
|
};
|
|
48168
48266
|
}
|
|
48169
48267
|
var SESSION_RENEWAL_REFUSAL_TTL_MS = 5 * 60 * 1000;
|
|
@@ -48198,6 +48296,21 @@ async function reconcileSkillSession(input2, options = {}) {
|
|
|
48198
48296
|
}
|
|
48199
48297
|
const targetKeys = new Set(target.selections.map(selectionKey));
|
|
48200
48298
|
const loaded = old.loaded.filter((key) => targetKeys.has(key));
|
|
48299
|
+
const previouslyLoaded = new Set(old.loaded);
|
|
48300
|
+
const targetBySlug = new Map(target.selections.map((selection) => [selection.slug, selection]));
|
|
48301
|
+
const previousSlugs = new Set(old.profile.selections.map((selection) => selection.slug));
|
|
48302
|
+
const selectionDelta = old.profile.selections.map((previous) => {
|
|
48303
|
+
const current = targetBySlug.get(previous.slug);
|
|
48304
|
+
const wasLoaded = previouslyLoaded.has(selectionKey(previous));
|
|
48305
|
+
const reason2 = !current ? "selection-removed" : selectionKey(previous) === selectionKey(current) ? "same-bundle" : "bundle-changed";
|
|
48306
|
+
return {
|
|
48307
|
+
selection: selectionMetadata(previous),
|
|
48308
|
+
loaded: wasLoaded,
|
|
48309
|
+
outcome: !wasLoaded ? "unloaded" : reason2 === "same-bundle" ? "retained" : "retired",
|
|
48310
|
+
reason: reason2,
|
|
48311
|
+
...reason2 === "bundle-changed" ? { replacement: selectionMetadata(current) } : {}
|
|
48312
|
+
};
|
|
48313
|
+
});
|
|
48201
48314
|
const replacementGeneration = nextSkillSessionGeneration(snapshot2.generation);
|
|
48202
48315
|
const plan = {
|
|
48203
48316
|
schemaVersion: 1,
|
|
@@ -48210,6 +48323,8 @@ async function reconcileSkillSession(input2, options = {}) {
|
|
|
48210
48323
|
replacementGeneration,
|
|
48211
48324
|
retainedLoadedCount: loaded.length,
|
|
48212
48325
|
retiredLoadedCount: old.loaded.length - loaded.length,
|
|
48326
|
+
selectionDelta,
|
|
48327
|
+
addedSelections: target.selections.filter((selection) => !previousSlugs.has(selection.slug)).map(selectionMetadata),
|
|
48213
48328
|
scope: "Only this receipt; existing child sessions, project locks, shared profiles, hooks and running processes are unchanged."
|
|
48214
48329
|
};
|
|
48215
48330
|
const planDigest = digest3(plan);
|
|
@@ -16,6 +16,19 @@ export interface SessionReconciliationOptions extends SelectionCacheOptions {
|
|
|
16
16
|
client?: ProfileClient;
|
|
17
17
|
}
|
|
18
18
|
export declare const SESSION_RECONCILIATION_PLAN_TTL_MS: number;
|
|
19
|
+
/** Exact bundle identity only; never spread receipt, policy or payload fields. */
|
|
20
|
+
export interface SessionSelectionMetadata {
|
|
21
|
+
slug: string;
|
|
22
|
+
version: string;
|
|
23
|
+
bundleDigest: string;
|
|
24
|
+
}
|
|
25
|
+
export interface SessionSelectionDelta {
|
|
26
|
+
selection: SessionSelectionMetadata;
|
|
27
|
+
loaded: boolean;
|
|
28
|
+
outcome: "retained" | "retired" | "unloaded";
|
|
29
|
+
reason: "same-bundle" | "selection-removed" | "bundle-changed";
|
|
30
|
+
replacement?: SessionSelectionMetadata;
|
|
31
|
+
}
|
|
19
32
|
/** Metadata only: inspection never displays skill payloads or resolves credentials. */
|
|
20
33
|
export declare function inspectSkillSession(sessionId: string, options?: SelectionCacheOptions): {
|
|
21
34
|
sessionId: string;
|
|
@@ -29,6 +42,12 @@ export declare function inspectSkillSession(sessionId: string, options?: Selecti
|
|
|
29
42
|
profileRevision: string;
|
|
30
43
|
selectionCount: number;
|
|
31
44
|
loadedCount: number;
|
|
45
|
+
selections: {
|
|
46
|
+
loaded: boolean;
|
|
47
|
+
slug: string;
|
|
48
|
+
version: string;
|
|
49
|
+
bundleDigest: string;
|
|
50
|
+
}[];
|
|
32
51
|
};
|
|
33
52
|
/** Fits inside the managed context child's 6.5-second ceiling, including CLI work. */
|
|
34
53
|
export declare const SESSION_RENEWAL_TIMEOUT_MS = 4000;
|
|
@@ -75,6 +94,8 @@ export declare function reconcileSkillSession(input: SessionReconciliationInput,
|
|
|
75
94
|
replacementGeneration: number;
|
|
76
95
|
retainedLoadedCount: number;
|
|
77
96
|
retiredLoadedCount: number;
|
|
97
|
+
selectionDelta: SessionSelectionDelta[];
|
|
98
|
+
addedSelections: SessionSelectionMetadata[];
|
|
78
99
|
scope: string;
|
|
79
100
|
};
|
|
80
101
|
planDigest: string;
|
|
@@ -112,6 +133,8 @@ export declare function reconcileSkillSession(input: SessionReconciliationInput,
|
|
|
112
133
|
replacementGeneration: number;
|
|
113
134
|
retainedLoadedCount: number;
|
|
114
135
|
retiredLoadedCount: number;
|
|
136
|
+
selectionDelta: SessionSelectionDelta[];
|
|
137
|
+
addedSelections: SessionSelectionMetadata[];
|
|
115
138
|
scope: string;
|
|
116
139
|
};
|
|
117
140
|
planDigest: string;
|
package/dist/sdk/index.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ export { CloudExecutionClient, type CloudExecution, type CloudEligibility } from
|
|
|
19
19
|
export { readExecutionGrantPolicy, saveExecutionGrantPolicy, resolveExecutionGrant } from "../lib/execution-grant-client.js";
|
|
20
20
|
export type { ExecutionGrant, ExecutionGrantPolicy, ExecutionGrantRequest, ResolvedExecutionGrant } from "../lib/execution-grants.js";
|
|
21
21
|
export { HttpProfileClient, createProfileClient, type ProfileClient, type AuthenticatedProfilePrincipal, type AuthenticatedProfileRole } from "../lib/profile-client.js";
|
|
22
|
-
export { inspectSkillSession, reconcileSkillSession, type SessionReconciliationInput, type SessionReconciliationOptions } from "../lib/session-reconciliation.js";
|
|
22
|
+
export { inspectSkillSession, reconcileSkillSession, type SessionSelectionMetadata, type SessionSelectionDelta, type SessionReconciliationInput, type SessionReconciliationOptions } from "../lib/session-reconciliation.js";
|
|
23
23
|
export { resolveSelectedRun, executeSelectedLocal, prepareSelectedSecretBindings, type ResolvedSelectedRun, type SelectedLocalRunOptions } from "../lib/selected-run.js";
|
|
24
24
|
export { readSelectedSecretBindings, type SelectedSecretBindings, type SelectedSecretsClient } from "../lib/execution-secrets.js";
|
|
25
25
|
export type { SkillSelection, SkillProfile, ResolvedSkillSelection, ResolvedSkillProfile, StationSkillStateInput, StationSkillState } from "../types/skill-selection.js";
|
package/dist/sdk/index.js
CHANGED
|
@@ -32373,7 +32373,7 @@ var init_event_sink = __esm(() => {
|
|
|
32373
32373
|
// package.json
|
|
32374
32374
|
var package_default = {
|
|
32375
32375
|
name: "@hasna/skills",
|
|
32376
|
-
version: "0.10.
|
|
32376
|
+
version: "0.10.56",
|
|
32377
32377
|
description: "Skills library for AI coding agents",
|
|
32378
32378
|
type: "module",
|
|
32379
32379
|
bin: {
|
|
@@ -37490,8 +37490,12 @@ function assertPlanWindow(issuedAt, expiresAt, now) {
|
|
|
37490
37490
|
throw new SkillSelectionError("SESSION_PLAN_EXPIRED", "The reviewed session reconciliation plan is not currently valid; inspect the receipt and prepare a new plan.");
|
|
37491
37491
|
}
|
|
37492
37492
|
}
|
|
37493
|
+
function selectionMetadata(selection2) {
|
|
37494
|
+
return { slug: selection2.slug, version: selection2.version, bundleDigest: selection2.bundleDigest };
|
|
37495
|
+
}
|
|
37493
37496
|
function inspectSkillSession(sessionId, options = {}) {
|
|
37494
37497
|
const snapshot = readSkillSessionSnapshot(sessionId, options), { receipt } = snapshot;
|
|
37498
|
+
const loaded = new Set(receipt.loaded);
|
|
37495
37499
|
return {
|
|
37496
37500
|
sessionId,
|
|
37497
37501
|
path: snapshot.path,
|
|
@@ -37503,7 +37507,8 @@ function inspectSkillSession(sessionId, options = {}) {
|
|
|
37503
37507
|
profileId: receipt.profile.profileId,
|
|
37504
37508
|
profileRevision: receipt.profile.profileRevision,
|
|
37505
37509
|
selectionCount: receipt.profile.selections.length,
|
|
37506
|
-
loadedCount: receipt.loaded.length
|
|
37510
|
+
loadedCount: receipt.loaded.length,
|
|
37511
|
+
selections: receipt.profile.selections.map((selection2) => ({ ...selectionMetadata(selection2), loaded: loaded.has(selectionKey(selection2)) }))
|
|
37507
37512
|
};
|
|
37508
37513
|
}
|
|
37509
37514
|
var SESSION_RENEWAL_REFUSAL_TTL_MS = 5 * 60 * 1000;
|
|
@@ -37538,6 +37543,21 @@ async function reconcileSkillSession(input, options = {}) {
|
|
|
37538
37543
|
}
|
|
37539
37544
|
const targetKeys = new Set(target.selections.map(selectionKey));
|
|
37540
37545
|
const loaded = old.loaded.filter((key) => targetKeys.has(key));
|
|
37546
|
+
const previouslyLoaded = new Set(old.loaded);
|
|
37547
|
+
const targetBySlug = new Map(target.selections.map((selection2) => [selection2.slug, selection2]));
|
|
37548
|
+
const previousSlugs = new Set(old.profile.selections.map((selection2) => selection2.slug));
|
|
37549
|
+
const selectionDelta = old.profile.selections.map((previous) => {
|
|
37550
|
+
const current = targetBySlug.get(previous.slug);
|
|
37551
|
+
const wasLoaded = previouslyLoaded.has(selectionKey(previous));
|
|
37552
|
+
const reason = !current ? "selection-removed" : selectionKey(previous) === selectionKey(current) ? "same-bundle" : "bundle-changed";
|
|
37553
|
+
return {
|
|
37554
|
+
selection: selectionMetadata(previous),
|
|
37555
|
+
loaded: wasLoaded,
|
|
37556
|
+
outcome: !wasLoaded ? "unloaded" : reason === "same-bundle" ? "retained" : "retired",
|
|
37557
|
+
reason,
|
|
37558
|
+
...reason === "bundle-changed" ? { replacement: selectionMetadata(current) } : {}
|
|
37559
|
+
};
|
|
37560
|
+
});
|
|
37541
37561
|
const replacementGeneration = nextSkillSessionGeneration(snapshot.generation);
|
|
37542
37562
|
const plan = {
|
|
37543
37563
|
schemaVersion: 1,
|
|
@@ -37550,6 +37570,8 @@ async function reconcileSkillSession(input, options = {}) {
|
|
|
37550
37570
|
replacementGeneration,
|
|
37551
37571
|
retainedLoadedCount: loaded.length,
|
|
37552
37572
|
retiredLoadedCount: old.loaded.length - loaded.length,
|
|
37573
|
+
selectionDelta,
|
|
37574
|
+
addedSelections: target.selections.filter((selection2) => !previousSlugs.has(selection2.slug)).map(selectionMetadata),
|
|
37553
37575
|
scope: "Only this receipt; existing child sessions, project locks, shared profiles, hooks and running processes are unchanged."
|
|
37554
37576
|
};
|
|
37555
37577
|
const planDigest = digest(plan);
|
|
@@ -43511,6 +43533,97 @@ function read2(path, changes) {
|
|
|
43511
43533
|
throw new Error(`Unsupported or oversized native discovery input: ${path}`);
|
|
43512
43534
|
return readFileSync15(path, "utf8");
|
|
43513
43535
|
}
|
|
43536
|
+
function assertReviewedClaudeCacheClosures(home, config, review) {
|
|
43537
|
+
const enabledIds = Object.entries(config.enabledPlugins ?? {}).filter(([, enabled]) => enabled === true).map(([id2]) => id2);
|
|
43538
|
+
if (enabledIds.length === 0)
|
|
43539
|
+
return;
|
|
43540
|
+
const registryPath = join26(home, ".claude/plugins/installed_plugins.json");
|
|
43541
|
+
const cacheRoot = join26(home, ".claude/plugins/cache");
|
|
43542
|
+
const registryText = read2(registryPath);
|
|
43543
|
+
if (registryText === null)
|
|
43544
|
+
return;
|
|
43545
|
+
const registry = parseConfig(registryText, registryPath);
|
|
43546
|
+
if (registry.version !== 2 || !registry.plugins || typeof registry.plugins !== "object" || Array.isArray(registry.plugins))
|
|
43547
|
+
throw new Error("Reviewed Claude installed plugin registrations are invalid");
|
|
43548
|
+
const cachePath = (path) => typeof path === "string" && isAbsolute20(path) && resolve19(path) === path && (path === cacheRoot || path.startsWith(cacheRoot + sep6));
|
|
43549
|
+
const activeRoots = new Set;
|
|
43550
|
+
for (const id2 of enabledIds) {
|
|
43551
|
+
const rows = registry.plugins[id2];
|
|
43552
|
+
if (rows === undefined)
|
|
43553
|
+
continue;
|
|
43554
|
+
if (!Array.isArray(rows))
|
|
43555
|
+
throw new Error("Reviewed Claude enabled plugin registrations must be an array");
|
|
43556
|
+
for (const row of rows) {
|
|
43557
|
+
if (!row || typeof row !== "object" || Array.isArray(row) || typeof row.installPath !== "string") {
|
|
43558
|
+
throw new Error("Reviewed Claude enabled plugin registration requires an object with an installPath string");
|
|
43559
|
+
}
|
|
43560
|
+
if (typeof row.installPath === "string" && (row.installPath === cacheRoot || row.installPath.startsWith(cacheRoot + sep6))) {
|
|
43561
|
+
if (!cachePath(row.installPath) || row.installPath.length > AGENT_POLICY_LIMITS.pathCharacters || row.version !== undefined && (typeof row.version !== "string" || basename9(row.installPath) !== row.version)) {
|
|
43562
|
+
throw new Error("Reviewed Claude discovery has a malformed active cache registration");
|
|
43563
|
+
}
|
|
43564
|
+
if (row.scope === "user" && (row.projectPath === undefined || row.projectPath === null)) {} else if (row.scope === "project" && typeof row.projectPath === "string" && isAbsolute20(row.projectPath) && resolve19(row.projectPath) === row.projectPath && row.projectPath.length <= AGENT_POLICY_LIMITS.pathCharacters) {} else
|
|
43565
|
+
throw new Error("Reviewed Claude discovery has an unsupported active cache registration scope");
|
|
43566
|
+
activeRoots.add(row.installPath);
|
|
43567
|
+
}
|
|
43568
|
+
}
|
|
43569
|
+
}
|
|
43570
|
+
if (activeRoots.size === 0)
|
|
43571
|
+
return;
|
|
43572
|
+
const registrySource = review.sources.find((source) => source.path === registryPath && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && (source.hashMode === undefined || source.hashMode === "bytes"));
|
|
43573
|
+
if (!registrySource)
|
|
43574
|
+
throw new Error("Reviewed Claude discovery must bind installed registrations for active cache plugins");
|
|
43575
|
+
const reviewedFile = (path, manifest = false) => review.sources.some((source) => source.path === path && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && (source.hashMode === undefined || source.hashMode === "bytes" || manifest && source.hashMode === "claude-plugin-manifest-v1"));
|
|
43576
|
+
const requireSource = (path, description, manifest = false) => {
|
|
43577
|
+
if (!reviewedFile(path, manifest))
|
|
43578
|
+
throw new Error(`Reviewed active Claude cache plugin ${description} is not bound`);
|
|
43579
|
+
};
|
|
43580
|
+
for (const root2 of activeRoots) {
|
|
43581
|
+
safe4(root2);
|
|
43582
|
+
const rootStat = lstatSync21(root2, { throwIfNoEntry: false });
|
|
43583
|
+
if (!rootStat?.isDirectory() || realpathSync9(root2) !== root2)
|
|
43584
|
+
throw new Error("Reviewed Claude active cache plugin root is missing or unsafe");
|
|
43585
|
+
const manifestPath = join26(root2, ".claude-plugin/plugin.json");
|
|
43586
|
+
const manifestStat = lstatSync21(manifestPath, { throwIfNoEntry: false });
|
|
43587
|
+
let manifest;
|
|
43588
|
+
if (manifestStat) {
|
|
43589
|
+
if (!manifestStat.isFile())
|
|
43590
|
+
throw new Error("Reviewed active Claude cache plugin manifest is unsupported");
|
|
43591
|
+
requireSource(manifestPath, "manifest", true);
|
|
43592
|
+
const raw = read2(manifestPath);
|
|
43593
|
+
if (raw === null)
|
|
43594
|
+
throw new Error("Reviewed Claude active cache plugin manifest is missing");
|
|
43595
|
+
manifest = JSON.parse(raw);
|
|
43596
|
+
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest))
|
|
43597
|
+
throw new Error("Reviewed Claude active cache plugin manifest is invalid");
|
|
43598
|
+
} else if (!review.sources.some((source) => source.path === manifestPath && source.hashMode === "path-bytes" && source.sha256 !== null && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined)) {
|
|
43599
|
+
throw new Error("Reviewed Claude active cache plugin manifest absence requires a path-bytes witness");
|
|
43600
|
+
}
|
|
43601
|
+
const roots = new Set([join26(root2, "skills")]);
|
|
43602
|
+
if (lstatSync21(join26(root2, "SKILL.md"), { throwIfNoEntry: false })?.isFile())
|
|
43603
|
+
roots.add(root2);
|
|
43604
|
+
const declared = manifest?.skills === undefined ? ["./skills"] : typeof manifest.skills === "string" ? [manifest.skills] : manifest.skills;
|
|
43605
|
+
if (!Array.isArray(declared) || declared.some((path) => typeof path !== "string"))
|
|
43606
|
+
throw new Error("Reviewed Claude active cache plugin has unsupported skill paths");
|
|
43607
|
+
for (const value of declared) {
|
|
43608
|
+
const path = resolve19(root2, value);
|
|
43609
|
+
if (path !== root2 && !path.startsWith(root2 + sep6))
|
|
43610
|
+
throw new Error("Reviewed Claude active cache plugin skill path escapes its root");
|
|
43611
|
+
roots.add(path.endsWith(`${sep6}SKILL.md`) ? dirname17(path) : path);
|
|
43612
|
+
}
|
|
43613
|
+
for (const skillRoot of roots)
|
|
43614
|
+
if (!review.roots.includes(skillRoot))
|
|
43615
|
+
throw new Error("Reviewed Claude active cache plugin skill root is not covered");
|
|
43616
|
+
const hooksPath = join26(root2, "hooks/hooks.json");
|
|
43617
|
+
const hooksStat = lstatSync21(hooksPath, { throwIfNoEntry: false });
|
|
43618
|
+
if (hooksStat) {
|
|
43619
|
+
if (!hooksStat.isFile())
|
|
43620
|
+
throw new Error("Reviewed Claude plugin hook manifest is unsupported");
|
|
43621
|
+
requireSource(hooksPath, "hook manifest");
|
|
43622
|
+
}
|
|
43623
|
+
if (typeof manifest?.hooks === "string")
|
|
43624
|
+
requireSource(resolve19(root2, manifest.hooks), "manifest-declared hook source");
|
|
43625
|
+
}
|
|
43626
|
+
}
|
|
43514
43627
|
function projectNativeDiscoveryFields(text2, format, fields2, path) {
|
|
43515
43628
|
const object6 = format === "yaml" ? parseHermesConfig(text2) : parseConfig(text2, path, format === "toml");
|
|
43516
43629
|
if (!object6 || typeof object6 !== "object" || Array.isArray(object6))
|
|
@@ -44099,6 +44212,8 @@ function resolveAgentDiscovery(options) {
|
|
|
44099
44212
|
throw new Error("Reviewed Claude plugin manifest requires separately reviewed exact hook file sources");
|
|
44100
44213
|
return { path: source.path, sha256: manifestSha256, hashMode: "claude-plugin-manifest-v1" };
|
|
44101
44214
|
});
|
|
44215
|
+
if (agent === "claude")
|
|
44216
|
+
assertReviewedClaudeCacheClosures(home, config, review);
|
|
44102
44217
|
const combined = [...reviewedSources, ...reviewedSourcesWithManifestProjection];
|
|
44103
44218
|
const unique = combined.filter((source, index) => combined.findIndex((candidate2) => isDeepStrictEqual6(candidate2, source)) === index);
|
|
44104
44219
|
return { ...supplied, roots: [...new Set([...roots, ...supplied.roots])].sort(), sources: unique, ...agent === "gemini" ? { builtinNames } : {} };
|