@hasna/skills 0.10.53 → 0.10.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -643,9 +643,9 @@ skills hook native-catalog --cwd /absolute/project --output /private/catalog.jso
643
643
  skills hook install --agent codex --codex-native-catalog /private/catalog.json --json
644
644
  ```
645
645
 
646
- Codex 0.159.2, 0.160.0 and 0.160.1 have measured qualified-name catalog support.
646
+ Codex 0.159.2, 0.160.0, 0.160.1 and 0.161.0 have measured qualified-name catalog support.
647
647
  Native transport, capture and review use one capability registry of exact
648
- releases. Unmeasured releases, including 0.160.2 and 0.161.0, refuse until the
648
+ releases. Unmeasured releases, including 0.160.2 and 0.161.1, refuse until the
649
649
  native catalog/name-denial and hook-dispatch acceptance tests pass; an RPC
650
650
  handshake alone does not establish those semantics. Admitting a release does not
651
651
  admit its corpus: the transport still requires the existing native corpus
@@ -697,7 +697,7 @@ 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
699
 
700
- For a Codex 0.160.0 or 0.160.1 remote refresh omitted by an existing qualified-name disable,
700
+ For a Codex 0.160.0, 0.160.1 or 0.161.0 remote refresh omitted by an existing qualified-name disable,
701
701
  a fresh native catalog and discovery review can plan the new exact path disable
702
702
  in the same transaction. The remote installation receipt, namespace and capability
703
703
  controls must match; conflicting enable rules, unknown names and ambiguous
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.53",
36891
+ version: "0.10.55",
36892
36892
  description: "Skills library for AI coding agents",
36893
36893
  type: "module",
36894
36894
  bin: {
@@ -40291,7 +40291,8 @@ var init_codex_native_compatibility = __esm(() => {
40291
40291
  "codex-cli 0.159.0": ["hooks"],
40292
40292
  "codex-cli 0.159.2": ["hooks", "qualified-skill-catalog"],
40293
40293
  "codex-cli 0.160.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
40294
- "codex-cli 0.160.1": ["hooks", "qualified-skill-catalog", "installed-plugin-review"]
40294
+ "codex-cli 0.160.1": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
40295
+ "codex-cli 0.161.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"]
40295
40296
  };
40296
40297
  SUPPORTED_CODEX_HOOK_VERSIONS = Object.freeze(Object.keys(MEASURED_CODEX_CAPABILITIES).filter((version) => supportsCodexNativeCapability(version, "hooks")));
40297
40298
  });
@@ -59505,12 +59506,17 @@ function captureRetiredCodexDiscovery(binding, controls, rules) {
59505
59506
  return { ...current, codexRetiredMaterializations: previous };
59506
59507
  return { ...current, codexRetiredMaterializations: { roots, parents: parents3, directories: captureDiscoveryDirectoryProjection(directories.map((item) => item.path), roots) } };
59507
59508
  }
59509
+ function retainableAbsentSource(source) {
59510
+ if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined)
59511
+ return false;
59512
+ return [undefined, "bytes"].includes(source.hashMode) || source.hashMode === "claude-plugin-manifest-v1" && source.sha256 !== null && source.path.endsWith(join37(sep8, ".claude-plugin", "plugin.json"));
59513
+ }
59508
59514
  function absentRetiredClaudeRoots(binding) {
59509
59515
  if (binding.agent !== "claude" || binding.method !== "reviewed")
59510
59516
  return [];
59511
59517
  const result2 = new Set;
59512
59518
  for (const source of binding.sources) {
59513
- if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || ![undefined, "bytes"].includes(source.hashMode) || source.sha256 === null)
59519
+ if (!retainableAbsentSource(source) || source.sha256 === null)
59514
59520
  continue;
59515
59521
  const marker = sep8 + ".claude" + sep8 + "plugins" + sep8 + "cache" + sep8, at2 = source.path.indexOf(marker);
59516
59522
  if (at2 < 0 || source.path.indexOf(marker, at2 + 1) >= 0)
@@ -59622,7 +59628,7 @@ function verifyAgentDiscovery(binding, codexRecovery) {
59622
59628
  safe4(source.path);
59623
59629
  continue;
59624
59630
  }
59625
- if (absentRoots.some((root2) => source.path.startsWith(root2 + sep8)) && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && [undefined, "bytes"].includes(source.hashMode)) {
59631
+ if (absentRoots.some((root2) => source.path.startsWith(root2 + sep8)) && retainableAbsentSource(source)) {
59626
59632
  safe4(source.path);
59627
59633
  continue;
59628
59634
  }
@@ -59678,7 +59684,7 @@ function rebindAgentDiscovery(binding, changes) {
59678
59684
  const installationRoots = codexInstallationRoots(binding), absentRoots = [...absentCodexSkillRoots(binding), ...absentRetiredClaudeRoots(binding), ...retiredCodexRoots(binding).filter((root2) => !lstatSync25(root2, { throwIfNoEntry: false }))];
59679
59685
  const rebound = { ...binding, sources: binding.sources.map((source) => {
59680
59686
  assertMarketplaceBinding(binding, source);
59681
- if (!changes.has(source.path) && (installationRoots.some((root2) => source.path.startsWith(root2 + sep8)) || absentRoots.some((root2) => source.path.startsWith(root2 + sep8)) && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && [undefined, "bytes"].includes(source.hashMode)))
59687
+ if (!changes.has(source.path) && (installationRoots.some((root2) => source.path.startsWith(root2 + sep8)) || absentRoots.some((root2) => source.path.startsWith(root2 + sep8)) && retainableAbsentSource(source)))
59682
59688
  return source;
59683
59689
  const sha2563 = projected(source, changes, budget2);
59684
59690
  if (!changes.has(source.path) && sha2563 !== source.sha256)
@@ -86064,8 +86070,12 @@ function assertPlanWindow(issuedAt, expiresAt, now3) {
86064
86070
  throw new SkillSelectionError("SESSION_PLAN_EXPIRED", "The reviewed session reconciliation plan is not currently valid; inspect the receipt and prepare a new plan.");
86065
86071
  }
86066
86072
  }
86073
+ function selectionMetadata(selection) {
86074
+ return { slug: selection.slug, version: selection.version, bundleDigest: selection.bundleDigest };
86075
+ }
86067
86076
  function inspectSkillSession(sessionId, options = {}) {
86068
86077
  const snapshot2 = readSkillSessionSnapshot(sessionId, options), { receipt } = snapshot2;
86078
+ const loaded = new Set(receipt.loaded);
86069
86079
  return {
86070
86080
  sessionId,
86071
86081
  path: snapshot2.path,
@@ -86077,7 +86087,8 @@ function inspectSkillSession(sessionId, options = {}) {
86077
86087
  profileId: receipt.profile.profileId,
86078
86088
  profileRevision: receipt.profile.profileRevision,
86079
86089
  selectionCount: receipt.profile.selections.length,
86080
- loadedCount: receipt.loaded.length
86090
+ loadedCount: receipt.loaded.length,
86091
+ selections: receipt.profile.selections.map((selection) => ({ ...selectionMetadata(selection), loaded: loaded.has(selectionKey(selection)) }))
86081
86092
  };
86082
86093
  }
86083
86094
  function renewalDeadlineSignal(error61) {
@@ -86299,6 +86310,21 @@ async function reconcileSkillSession(input2, options = {}) {
86299
86310
  }
86300
86311
  const targetKeys = new Set(target.selections.map(selectionKey));
86301
86312
  const loaded = old.loaded.filter((key) => targetKeys.has(key));
86313
+ const previouslyLoaded = new Set(old.loaded);
86314
+ const targetBySlug = new Map(target.selections.map((selection) => [selection.slug, selection]));
86315
+ const previousSlugs = new Set(old.profile.selections.map((selection) => selection.slug));
86316
+ const selectionDelta = old.profile.selections.map((previous) => {
86317
+ const current = targetBySlug.get(previous.slug);
86318
+ const wasLoaded = previouslyLoaded.has(selectionKey(previous));
86319
+ const reason2 = !current ? "selection-removed" : selectionKey(previous) === selectionKey(current) ? "same-bundle" : "bundle-changed";
86320
+ return {
86321
+ selection: selectionMetadata(previous),
86322
+ loaded: wasLoaded,
86323
+ outcome: !wasLoaded ? "unloaded" : reason2 === "same-bundle" ? "retained" : "retired",
86324
+ reason: reason2,
86325
+ ...reason2 === "bundle-changed" ? { replacement: selectionMetadata(current) } : {}
86326
+ };
86327
+ });
86302
86328
  const replacementGeneration = nextSkillSessionGeneration(snapshot2.generation);
86303
86329
  const plan = {
86304
86330
  schemaVersion: 1,
@@ -86311,6 +86337,8 @@ async function reconcileSkillSession(input2, options = {}) {
86311
86337
  replacementGeneration,
86312
86338
  retainedLoadedCount: loaded.length,
86313
86339
  retiredLoadedCount: old.loaded.length - loaded.length,
86340
+ selectionDelta,
86341
+ addedSelections: target.selections.filter((selection) => !previousSlugs.has(selection.slug)).map(selectionMetadata),
86314
86342
  scope: "Only this receipt; existing child sessions, project locks, shared profiles, hooks and running processes are unchanged."
86315
86343
  };
86316
86344
  const planDigest = digest3(plan);
@@ -86350,6 +86378,9 @@ __export(exports_context, {
86350
86378
  contextResolverOptions: () => contextResolverOptions
86351
86379
  });
86352
86380
  import { readSync as readSync12 } from "fs";
86381
+ function sessionSelectionLabel(selection) {
86382
+ return `${selection.slug}@${selection.version} ${selection.bundleDigest}`;
86383
+ }
86353
86384
  function selectedProfileId(explicit) {
86354
86385
  return explicit ?? process.env.HASNA_SKILLS_SELECTION_PROFILE ?? readManagedSkillPolicy()?.profileId ?? "default";
86355
86386
  }
@@ -86367,8 +86398,12 @@ function registerContextCommands(parent) {
86367
86398
  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) => {
86368
86399
  try {
86369
86400
  const result2 = inspectSkillSession(id);
86370
- await writeCliOutput(options.json ? JSON.stringify(result2) : `${JSON.stringify(id)}: ${result2.profileId} at ${result2.profileRevision}
86371
- Receipt SHA256: ${result2.receiptSha256}`);
86401
+ await writeCliOutput(options.json ? JSON.stringify(result2) : [
86402
+ `${JSON.stringify(id)}: ${result2.profileId} at ${result2.profileRevision}`,
86403
+ `Receipt SHA256: ${result2.receiptSha256}`,
86404
+ ...result2.selections.map((selection) => `${selection.loaded ? "Loaded" : "Unloaded"}: ${sessionSelectionLabel(selection)}`)
86405
+ ].join(`
86406
+ `));
86372
86407
  } catch (error61) {
86373
86408
  reportContextError(error61, options.json);
86374
86409
  }
@@ -86395,7 +86430,13 @@ Review digest: ${result2.reviewDigest}`);
86395
86430
  try {
86396
86431
  const result2 = await reconcileSkillSession({ ...options, sessionId: id });
86397
86432
  await writeCliOutput(options.json ? JSON.stringify(result2) : result2.applied ? `Reconciled ${JSON.stringify(id)} to ${result2.plan.target.profileId} at ${result2.plan.target.profileRevision}.
86398
- Preserved original receipt: ${result2.archivePath}` : `Planned one session reconciliation. Review --json output, then use --apply --plan-digest ${result2.planDigest} --plan-issued-at ${result2.plan.issuedAt} --plan-expires-at ${result2.plan.expiresAt}.`);
86433
+ Preserved original receipt: ${result2.archivePath}` : [
86434
+ `Planned one session reconciliation: ${result2.plan.retainedLoadedCount} loaded retained, ${result2.plan.retiredLoadedCount} loaded retired.`,
86435
+ ...result2.plan.selectionDelta.map((row) => `${row.outcome} (${row.reason}): ${sessionSelectionLabel(row.selection)}${row.replacement ? ` -> ${sessionSelectionLabel(row.replacement)}` : ""}`),
86436
+ ...result2.plan.addedSelections.map((selection) => `Added selection (unloaded): ${sessionSelectionLabel(selection)}`),
86437
+ `Review --json output, then use --apply --plan-digest ${result2.planDigest} --plan-issued-at ${result2.plan.issuedAt} --plan-expires-at ${result2.plan.expiresAt}.`
86438
+ ].join(`
86439
+ `));
86399
86440
  } catch (error61) {
86400
86441
  reportContextError(error61, options.json);
86401
86442
  }
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.53",
20022
+ version: "0.10.55",
20023
20023
  description: "Skills library for AI coding agents",
20024
20024
  type: "module",
20025
20025
  bin: {
@@ -38560,7 +38560,8 @@ var init_codex_native_compatibility = __esm(() => {
38560
38560
  "codex-cli 0.159.0": ["hooks"],
38561
38561
  "codex-cli 0.159.2": ["hooks", "qualified-skill-catalog"],
38562
38562
  "codex-cli 0.160.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
38563
- "codex-cli 0.160.1": ["hooks", "qualified-skill-catalog", "installed-plugin-review"]
38563
+ "codex-cli 0.160.1": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
38564
+ "codex-cli 0.161.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"]
38564
38565
  };
38565
38566
  SUPPORTED_CODEX_HOOK_VERSIONS = Object.freeze(Object.keys(MEASURED_CODEX_CAPABILITIES).filter((version2) => supportsCodexNativeCapability(version2, "hooks")));
38566
38567
  });
@@ -50916,12 +50917,17 @@ function retiredCodexRoots(binding) {
50916
50917
  }
50917
50918
  return proof.roots;
50918
50919
  }
50920
+ function retainableAbsentSource(source) {
50921
+ if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined)
50922
+ return false;
50923
+ return [undefined, "bytes"].includes(source.hashMode) || source.hashMode === "claude-plugin-manifest-v1" && source.sha256 !== null && source.path.endsWith(join26(sep6, ".claude-plugin", "plugin.json"));
50924
+ }
50919
50925
  function absentRetiredClaudeRoots(binding) {
50920
50926
  if (binding.agent !== "claude" || binding.method !== "reviewed")
50921
50927
  return [];
50922
50928
  const result = new Set;
50923
50929
  for (const source of binding.sources) {
50924
- if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || ![undefined, "bytes"].includes(source.hashMode) || source.sha256 === null)
50930
+ if (!retainableAbsentSource(source) || source.sha256 === null)
50925
50931
  continue;
50926
50932
  const marker = sep6 + ".claude" + sep6 + "plugins" + sep6 + "cache" + sep6, at = source.path.indexOf(marker);
50927
50933
  if (at < 0 || source.path.indexOf(marker, at + 1) >= 0)
@@ -51033,7 +51039,7 @@ function verifyAgentDiscovery(binding, codexRecovery) {
51033
51039
  safe4(source.path);
51034
51040
  continue;
51035
51041
  }
51036
- if (absentRoots.some((root) => source.path.startsWith(root + sep6)) && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && [undefined, "bytes"].includes(source.hashMode)) {
51042
+ if (absentRoots.some((root) => source.path.startsWith(root + sep6)) && retainableAbsentSource(source)) {
51037
51043
  safe4(source.path);
51038
51044
  continue;
51039
51045
  }
package/bin/migrate.js CHANGED
@@ -25,7 +25,7 @@ import { join as join5 } from "path";
25
25
  // package.json
26
26
  var package_default = {
27
27
  name: "@hasna/skills",
28
- version: "0.10.53",
28
+ version: "0.10.55",
29
29
  description: "Skills library for AI coding agents",
30
30
  type: "module",
31
31
  bin: {
package/bin/server.js CHANGED
@@ -24140,7 +24140,7 @@ restoreLaunchCwd();
24140
24140
  // package.json
24141
24141
  var package_default = {
24142
24142
  name: "@hasna/skills",
24143
- version: "0.10.53",
24143
+ version: "0.10.55",
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.53",
24146
+ version: "0.10.55",
24147
24147
  description: "Skills library for AI coding agents",
24148
24148
  type: "module",
24149
24149
  bin: {
package/dist/index.js CHANGED
@@ -9398,7 +9398,8 @@ var MEASURED_CODEX_CAPABILITIES = {
9398
9398
  "codex-cli 0.159.0": ["hooks"],
9399
9399
  "codex-cli 0.159.2": ["hooks", "qualified-skill-catalog"],
9400
9400
  "codex-cli 0.160.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
9401
- "codex-cli 0.160.1": ["hooks", "qualified-skill-catalog", "installed-plugin-review"]
9401
+ "codex-cli 0.160.1": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
9402
+ "codex-cli 0.161.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"]
9402
9403
  };
9403
9404
  function supportsCodexNativeCapability(version, capability) {
9404
9405
  return typeof version === "string" && Object.hasOwn(MEASURED_CODEX_CAPABILITIES, version) && MEASURED_CODEX_CAPABILITIES[version].includes(capability);
@@ -11832,7 +11833,7 @@ function replaceSkillSession(expected, replacement, plan, options = {}, beforeCo
11832
11833
  // package.json
11833
11834
  var package_default = {
11834
11835
  name: "@hasna/skills",
11835
- version: "0.10.53",
11836
+ version: "0.10.55",
11836
11837
  description: "Skills library for AI coding agents",
11837
11838
  type: "module",
11838
11839
  bin: {
@@ -20837,12 +20838,17 @@ function captureRetiredCodexDiscovery(binding, controls, rules) {
20837
20838
  return { ...current, codexRetiredMaterializations: previous };
20838
20839
  return { ...current, codexRetiredMaterializations: { roots, parents: parents3, directories: captureDiscoveryDirectoryProjection(directories.map((item) => item.path), roots) } };
20839
20840
  }
20841
+ function retainableAbsentSource(source) {
20842
+ if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined)
20843
+ return false;
20844
+ return [undefined, "bytes"].includes(source.hashMode) || source.hashMode === "claude-plugin-manifest-v1" && source.sha256 !== null && source.path.endsWith(join32(sep8, ".claude-plugin", "plugin.json"));
20845
+ }
20840
20846
  function absentRetiredClaudeRoots(binding) {
20841
20847
  if (binding.agent !== "claude" || binding.method !== "reviewed")
20842
20848
  return [];
20843
20849
  const result = new Set;
20844
20850
  for (const source of binding.sources) {
20845
- if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || ![undefined, "bytes"].includes(source.hashMode) || source.sha256 === null)
20851
+ if (!retainableAbsentSource(source) || source.sha256 === null)
20846
20852
  continue;
20847
20853
  const marker = sep8 + ".claude" + sep8 + "plugins" + sep8 + "cache" + sep8, at = source.path.indexOf(marker);
20848
20854
  if (at < 0 || source.path.indexOf(marker, at + 1) >= 0)
@@ -20954,7 +20960,7 @@ function verifyAgentDiscovery(binding, codexRecovery) {
20954
20960
  safe4(source.path);
20955
20961
  continue;
20956
20962
  }
20957
- if (absentRoots.some((root2) => source.path.startsWith(root2 + sep8)) && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && [undefined, "bytes"].includes(source.hashMode)) {
20963
+ if (absentRoots.some((root2) => source.path.startsWith(root2 + sep8)) && retainableAbsentSource(source)) {
20958
20964
  safe4(source.path);
20959
20965
  continue;
20960
20966
  }
@@ -21010,7 +21016,7 @@ function rebindAgentDiscovery(binding, changes) {
21010
21016
  const installationRoots = codexInstallationRoots(binding), absentRoots = [...absentCodexSkillRoots(binding), ...absentRetiredClaudeRoots(binding), ...retiredCodexRoots(binding).filter((root2) => !lstatSync25(root2, { throwIfNoEntry: false }))];
21011
21017
  const rebound = { ...binding, sources: binding.sources.map((source) => {
21012
21018
  assertMarketplaceBinding(binding, source);
21013
- if (!changes.has(source.path) && (installationRoots.some((root2) => source.path.startsWith(root2 + sep8)) || absentRoots.some((root2) => source.path.startsWith(root2 + sep8)) && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && [undefined, "bytes"].includes(source.hashMode)))
21019
+ if (!changes.has(source.path) && (installationRoots.some((root2) => source.path.startsWith(root2 + sep8)) || absentRoots.some((root2) => source.path.startsWith(root2 + sep8)) && retainableAbsentSource(source)))
21014
21020
  return source;
21015
21021
  const sha2563 = projected(source, changes, budget2);
21016
21022
  if (!changes.has(source.path) && sha2563 !== source.sha256)
@@ -48144,8 +48150,12 @@ function assertPlanWindow(issuedAt, expiresAt, now) {
48144
48150
  throw new SkillSelectionError("SESSION_PLAN_EXPIRED", "The reviewed session reconciliation plan is not currently valid; inspect the receipt and prepare a new plan.");
48145
48151
  }
48146
48152
  }
48153
+ function selectionMetadata(selection) {
48154
+ return { slug: selection.slug, version: selection.version, bundleDigest: selection.bundleDigest };
48155
+ }
48147
48156
  function inspectSkillSession(sessionId, options = {}) {
48148
48157
  const snapshot2 = readSkillSessionSnapshot(sessionId, options), { receipt } = snapshot2;
48158
+ const loaded = new Set(receipt.loaded);
48149
48159
  return {
48150
48160
  sessionId,
48151
48161
  path: snapshot2.path,
@@ -48157,7 +48167,8 @@ function inspectSkillSession(sessionId, options = {}) {
48157
48167
  profileId: receipt.profile.profileId,
48158
48168
  profileRevision: receipt.profile.profileRevision,
48159
48169
  selectionCount: receipt.profile.selections.length,
48160
- loadedCount: receipt.loaded.length
48170
+ loadedCount: receipt.loaded.length,
48171
+ selections: receipt.profile.selections.map((selection) => ({ ...selectionMetadata(selection), loaded: loaded.has(selectionKey(selection)) }))
48161
48172
  };
48162
48173
  }
48163
48174
  var SESSION_RENEWAL_REFUSAL_TTL_MS = 5 * 60 * 1000;
@@ -48192,6 +48203,21 @@ async function reconcileSkillSession(input2, options = {}) {
48192
48203
  }
48193
48204
  const targetKeys = new Set(target.selections.map(selectionKey));
48194
48205
  const loaded = old.loaded.filter((key) => targetKeys.has(key));
48206
+ const previouslyLoaded = new Set(old.loaded);
48207
+ const targetBySlug = new Map(target.selections.map((selection) => [selection.slug, selection]));
48208
+ const previousSlugs = new Set(old.profile.selections.map((selection) => selection.slug));
48209
+ const selectionDelta = old.profile.selections.map((previous) => {
48210
+ const current = targetBySlug.get(previous.slug);
48211
+ const wasLoaded = previouslyLoaded.has(selectionKey(previous));
48212
+ const reason2 = !current ? "selection-removed" : selectionKey(previous) === selectionKey(current) ? "same-bundle" : "bundle-changed";
48213
+ return {
48214
+ selection: selectionMetadata(previous),
48215
+ loaded: wasLoaded,
48216
+ outcome: !wasLoaded ? "unloaded" : reason2 === "same-bundle" ? "retained" : "retired",
48217
+ reason: reason2,
48218
+ ...reason2 === "bundle-changed" ? { replacement: selectionMetadata(current) } : {}
48219
+ };
48220
+ });
48195
48221
  const replacementGeneration = nextSkillSessionGeneration(snapshot2.generation);
48196
48222
  const plan = {
48197
48223
  schemaVersion: 1,
@@ -48204,6 +48230,8 @@ async function reconcileSkillSession(input2, options = {}) {
48204
48230
  replacementGeneration,
48205
48231
  retainedLoadedCount: loaded.length,
48206
48232
  retiredLoadedCount: old.loaded.length - loaded.length,
48233
+ selectionDelta,
48234
+ addedSelections: target.selections.filter((selection) => !previousSlugs.has(selection.slug)).map(selectionMetadata),
48207
48235
  scope: "Only this receipt; existing child sessions, project locks, shared profiles, hooks and running processes are unchanged."
48208
48236
  };
48209
48237
  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;
@@ -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.53",
32376
+ version: "0.10.55",
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);
@@ -38446,7 +38468,8 @@ var MEASURED_CODEX_CAPABILITIES = {
38446
38468
  "codex-cli 0.159.0": ["hooks"],
38447
38469
  "codex-cli 0.159.2": ["hooks", "qualified-skill-catalog"],
38448
38470
  "codex-cli 0.160.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
38449
- "codex-cli 0.160.1": ["hooks", "qualified-skill-catalog", "installed-plugin-review"]
38471
+ "codex-cli 0.160.1": ["hooks", "qualified-skill-catalog", "installed-plugin-review"],
38472
+ "codex-cli 0.161.0": ["hooks", "qualified-skill-catalog", "installed-plugin-review"]
38450
38473
  };
38451
38474
  function supportsCodexNativeCapability(version, capability) {
38452
38475
  return typeof version === "string" && Object.hasOwn(MEASURED_CODEX_CAPABILITIES, version) && MEASURED_CODEX_CAPABILITIES[version].includes(capability);
@@ -43655,12 +43678,17 @@ function captureRetiredCodexDiscovery(binding, controls, rules) {
43655
43678
  return { ...current, codexRetiredMaterializations: previous };
43656
43679
  return { ...current, codexRetiredMaterializations: { roots, parents: parents3, directories: captureDiscoveryDirectoryProjection(directories.map((item) => item.path), roots) } };
43657
43680
  }
43681
+ function retainableAbsentSource(source) {
43682
+ if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined)
43683
+ return false;
43684
+ return [undefined, "bytes"].includes(source.hashMode) || source.hashMode === "claude-plugin-manifest-v1" && source.sha256 !== null && source.path.endsWith(join26(sep6, ".claude-plugin", "plugin.json"));
43685
+ }
43658
43686
  function absentRetiredClaudeRoots(binding) {
43659
43687
  if (binding.agent !== "claude" || binding.method !== "reviewed")
43660
43688
  return [];
43661
43689
  const result = new Set;
43662
43690
  for (const source of binding.sources) {
43663
- if (source.format !== undefined || source.fields !== undefined || source.managedPlugins !== undefined || ![undefined, "bytes"].includes(source.hashMode) || source.sha256 === null)
43691
+ if (!retainableAbsentSource(source) || source.sha256 === null)
43664
43692
  continue;
43665
43693
  const marker = sep6 + ".claude" + sep6 + "plugins" + sep6 + "cache" + sep6, at = source.path.indexOf(marker);
43666
43694
  if (at < 0 || source.path.indexOf(marker, at + 1) >= 0)
@@ -43772,7 +43800,7 @@ function verifyAgentDiscovery(binding, codexRecovery) {
43772
43800
  safe4(source.path);
43773
43801
  continue;
43774
43802
  }
43775
- if (absentRoots.some((root2) => source.path.startsWith(root2 + sep6)) && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && [undefined, "bytes"].includes(source.hashMode)) {
43803
+ if (absentRoots.some((root2) => source.path.startsWith(root2 + sep6)) && retainableAbsentSource(source)) {
43776
43804
  safe4(source.path);
43777
43805
  continue;
43778
43806
  }
@@ -43828,7 +43856,7 @@ function rebindAgentDiscovery(binding, changes) {
43828
43856
  const installationRoots = codexInstallationRoots(binding), absentRoots = [...absentCodexSkillRoots(binding), ...absentRetiredClaudeRoots(binding), ...retiredCodexRoots(binding).filter((root2) => !lstatSync21(root2, { throwIfNoEntry: false }))];
43829
43857
  const rebound = { ...binding, sources: binding.sources.map((source) => {
43830
43858
  assertMarketplaceBinding(binding, source);
43831
- if (!changes.has(source.path) && (installationRoots.some((root2) => source.path.startsWith(root2 + sep6)) || absentRoots.some((root2) => source.path.startsWith(root2 + sep6)) && source.format === undefined && source.fields === undefined && source.managedPlugins === undefined && [undefined, "bytes"].includes(source.hashMode)))
43859
+ if (!changes.has(source.path) && (installationRoots.some((root2) => source.path.startsWith(root2 + sep6)) || absentRoots.some((root2) => source.path.startsWith(root2 + sep6)) && retainableAbsentSource(source)))
43832
43860
  return source;
43833
43861
  const sha2562 = projected(source, changes, budget2);
43834
43862
  if (!changes.has(source.path) && sha2562 !== source.sha256)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/skills",
3
- "version": "0.10.53",
3
+ "version": "0.10.55",
4
4
  "description": "Skills library for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {